How to build a full-stack serverless movie tracker with OpenJS Architect - Part 2
Published: 2021-05-06 16:27:24

# Progressive Enhancement and delivering client-side JS

The basic functionality is completed. Now it's time to enhance this form with some JavaScript so it saves the watched checkboxes in real-time by submitting the form when a checkbox is clicked. 

```javascript
// public/index.js
let forms = document.querySelectorAll("form[action='/watched']")

for (let f of forms) {
  f.querySelector('button').style.display = 'none'
  let check = f.querySelector('input[type="checkbox"]')  
  check.addEventListener('change', function (e) {
    f.submit() 
  }, false)
}
```

This is the simplest version of progressive enhancement we can make. The client-side JavavScript selects the forms, hides the 'save' button and adds an event listener to the checkbox. Each time the checkbox changes, it submits the form for the user. 

# Adding star ratings and comments 
We're going to continue making our application more complex. In the next few sections, we will add a "star rating" feature as well as a comment system. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uytlynhelri4gn87h8l0.png)

First, lets change our markup in the `get-index` route. 

```javascript
// src/http/get-index/index.js

const arc = require('@architect/functions')
const data = require('@begin/data')

exports.handler = arc.http.async(http)

function authControl(account) {
  if (account && account.name) {
    return `
    Welcome back ${account.name}
    <form action=/logout method="post">
    <button>Logout</button>
    </form>`
  } else {
    let clientID = process.env.GITHUB_CLIENT_ID
    let redirectURL = process.env.GITHUB_REDIRECT
    let href = `https://github.com/login/oauth/authorize?client_id=${clientID}&redirect_url=${redirectURL}`
    return `
    <a href='${href}'>Login with GitHub to see a list of movies</a>
    `
  }
}

function movie({ key, watched, title, rating, review }) {

  return `
<form action="/watched" method="post">
  <input type="hidden" name="movieId" value="${key}">
  <input type="checkbox" data-movieid="${key}" name=watched ${watched ? 'checked' : ''}>
  ${title}
  <input type="text" data-movieid="${key}" name="review" placeholder="leave a review here" value="${review || ''}">

  <input type="radio" name="rating" data-movieid="${key}" value="1" ${rating === '1' ? 'checked' : ''}>
  <input type="radio" name="rating" data-movieid="${key}" value="2" ${rating === '2' ? 'checked' : ''}>
  <input type="radio" name="rating" data-movieid="${key}" value="3" ${rating === '3' ? 'checked' : ''}>

  <button class=cage>Save</button>
  </form>`
}

async function getMovies(account) {
  let movies = [
    { key: '001', title: 'Raising Arizona' },
    { key: '002', title: 'Con Air' },
    { key: '003', title: 'National Treasure' },
  ]
  if (account) {
    let accountMovies = await data.get({
      table: `${account.id}-movies`
    })

    let result = ''
    for (let mov of movies) {
      let found = (accountMovies.find(m => m.key === mov.key))
      result += movie({
        key: mov.key,
        title: mov.title,
        watched: !!found,
        rating: found ? found.rating : '',
        review: found ? found.review : ''
      })
    }
    return result
  }
  return ''
}

async function http(req) {

  return {
    html: `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="/_static/index.css">
  <link rel="shortcut icon" href="#">
  <title>Praise Cage</title>
</head>
<body>
<h1>Praise Cage</h1>

${authControl(req.session.account)}
${await getMovies(req.session.account)}

<script src=/_static/index.js type=module></script>
</body>
</html>
`
  }
}
```

Next, we can modify the `post-watched` endpoint. We now want to include `review` and `rating` properties to be saved to the database. 

```javascript
// src/http/post-watched/index.js

const arc = require('@architect/functions')
const data = require('@begin/data')

exports.handler = arc.http.async(route)

async function route(req) {

  console.log('post-watched req.body:', req.body )

  let account = req.session.account.id

  if (req.body.watched) {
    await data.set({
      table: `${account}-movies`,
      key: req.body.movieId,
      review: req.body.review,
      rating: req.body.rating
    })
  } else {
    await data.destroy({
      table: `${account}-movies`,
      key: req.body.movieId
    })
  }

  return {
    location: '/'
  }
}
```

Finally, we can update our client-side JS with the following: 

```javascript
// public/index.js

let forms = document.querySelectorAll("form[action='/watched']")

console.log(forms)

for (let f of forms) {

  // hide all submit buttons
  f.querySelector('button').style.display = 'none'

  // get a ref to the form checkbox
  let check = f.querySelector('input[type="checkbox"]')

  // get a ref to the form radios
  let radios = f.querySelectorAll('input[type="radio"]')

  // get a ref to the form text
  let text = f.querySelector('input[type="text"]')

  // mutates state
  function changed (e) {

    let movieId = e.target.dataset.movieid
    let payload = { movieId }
    payload.watched = f.querySelector('input[name="watched"]').checked
    payload.review = f.querySelectorAll('input[name="review"]')[0].value

    let rating = f.querySelectorAll('input[name="rating"]:checked')
    payload.rating = rating.length === 1 ? rating[0].value : ''

   //make an HTTP post with fetch

    fetch('/watched', {
      method: 'POST',
      headers: {
        'content-type': 'application/json',
        'x-nick-cage': 'fetch'
      },
      body: JSON.stringify(payload)
    }).catch(function fail(err) {
      console.log('failed', err)
    })
  }

  // listen to checkbox changes
  check.addEventListener('change', changed, false)

  // listen to radio buttons getting hit
  for (let r of radios) {
    r.addEventListener('input', changed, false)
  }

  // listen to changes to review text
  text.addEventListener('input', changed, false)
}
```

The full source code can be found here: [https://github.com/pchinjr/movie-tracker](https://github.com/pchinjr/movie-tracker)