The No Framework SPA Tutorial, part 1

Let's say it's been a few years, and you want to dabble with building a web UI for something you're working on. Since it's 2017, the first thing you'll do is crap yourself when you see what JavaScript developers are doing. Then you'll have a brain aneurysm when you try to implement any of it.

You just want to roll out some basic stuff and build up from there. There is a better way.

I think my fellow JavaScript developers are nuts. It's ok, I'm one of them and I'm nuts too. But I'm also not afraid of working without a framework. Some folks don't even realize how realistic of an option that is.

If you are a javascript developer and you have never built an SPA without a framework, I highly recommend it. You will learn and grow as a developer from it.

Ok, let's get started. We are going to build a page that gives us information about countries, using the very nice REST Countries API. It will end up being around 50 lines of code, including our Vanilla JS dependency.

First off, you'll need an index.html page. Let's fill it with the normal junk:

<!doctype html>
<html>
<head>
  <title>List of Countries</title>
</head>
<body></body>
<script>
// Woohoo, I'm ready when you are!
</script>
</html>

Looks great, that's all we need to start writing a web app. We'll be doing everything in this single file for now. Assume all following JS examples are in the <script> tag.

Let's write a little boilerplate. It's nice to have a 'main' function that is the entrypoint to your application.

function main () {
  const title = document.createElement('h2')
  title.innerText = document.title
}

main()

Look ma, no React/Vue/Angular/Webpack/Rollup/Gulp/Typescript/Babel/Config! (Yet...)

Yes, I'll be writing ES2015+ without a modern-to-legacy-javascript compiler. This is ok for a few reasons:

  1. You should be developing with a modern browser, like Chrome or Firefox. Even Edge and Safari will work.
  2. If you need to support IE, take note of point one. You don't need to develop a new application in IE. Later on, you can look into compiling down to ES5 or ES3. We don't need to get distracted with a build system yet.

Ok, that's not much. Let's build a helper function, because we're going to be creating a lot of elements.

function create(element, text) {
  const el = document.createElement(element)
  el.innerText = text
  return el
}

Not a bad start, but this will start to get gross pretty quick.

const mockData = [ { fish: 'one' }
                 , { fish: 'two' }
                 , { fish: 'red' }
                 , { fish: 'blue' }
                 ]

function Table (data) {
  const table = create('table')
  const thead = create('thead')
  const tbody = create('tbody')
  const tr = create('tr')
  const columns = Object.keys(data.reduce((a, v) => Object.assign(a, v)))

  columns.forEach(column => tr.appendChild(create('th', column)))
  thead.appendChild(tr)
  table.appendChild(thead)
  data.forEach(record => {
    const tr = create('tr')
    columns.forEach(column => tr.appendChild('td', record[column]))
    tbody.appendChild(tr)
  }

  return table.appendCHild(tbody)
}

Not bad, but let's make creating this table a little cleaner. Let's call our create function h so it can be à la mode.

function h(element, children) {
  const el = document.createElement(element)

  if (arguments[h.length]) {
    for (let i = h.length, j = arguments.length, i < j; i++) {
      const arg = arguments[i]
      if (typeof arg === 'string') {
          el.appendChild(document.createTextNode(arg)
      } else if (arg instanceof HTMLElement) {
        el.appendChild(arg)
      }
    }
  }
  return el
}
function h(element = 'div', classes, children) {
  const el = document.createElement(element)

  if (Array.isArray(classes)) {
    classes.forEach(c => el.classList.add(c))
  } else if (typeof classes === 'string') {
    el.classList.add(classes)
  }

  if (Array.isArray(children)) {
    children.forEach(c => {
      if (c instanceof HTMLElement) {
        el.append(c)
      }
    })
  } else if (children instanceof HTMLElement) {
    el.append(children)
  } else if (typeof children === 'string') {
    el.innerText = children
  }

  return el
}

This provides functionality comparable to element-creating functions in various libraries and frameworks. Let's disect it a bit:

This is pretty powerful stuff. You'll see why we wanted to give the function such a short name:

// Nice table ya got there...
h('table', 'table', [ 
  h('thead', null,
    h('tr', null, [
      h('th', null, 'name'),
      h('th', null, 'capital')
    ])
  ),
  h('tbody', null, [
    h('tr', null, [
      h('td', null, 'Belgium'),
      h('td', null, 'Brussels')
    ]),
    h('tr', null, [
      h('td', null, 'Chile'),
      h('td', null, 'Santiago')
    ])
  ])
])

last deployment: 2017-7-19 07:46:43 (source)