Client based programming

Notes for a course at Linnaeus University: https://coursepress.lnu.se/kurs/klientbaserad-webbprogrammering/

L0 Introduction

3 parts:

Describe the web browser (internal structure, security models, developer tools)

JavaScript, HTML and CSS should be clearly separated.

DOM (traversing, selecting, manipulating). Handling events.

Ajax and JSON.

SPAs with offline support and browser native APIs.

Handling history in the browser.

Literature:

Tools:

L1 SSG and CSS preprocessors

CSS preprocessors extend the CSS language.

Preprocessors enable calculations, nesting, mixins and variables.

The course default is sass, other options include less and stylus.

Partials, @import and operators.

https://sass-lang.com/guide

@mixin defines a function used with @include

@import "_partial";

width: 300px / $width * 100%;

Since line numbers in browser no longer match line numbers in source debugging gets harder. It’s easy to add a CSS preprocessor to the tech stack. But, it’s not easy to remove it down the line, should we so choose.

The .map files enable debugging.

Static Site Generators take for example plain text and generate html code. This website is built with a custom SSG written in Julia.

SSGs are simple and secure.

Features:

Import partials from _includes folder with: {% include head.html %}


<ul>
  {% for post in site.posts %}
    <li>
      <a href="{{ post.url }}">{{ post.title }}</a>
    </li>
  {% endfor %}
</ul>

In Jekyll Liquid is the templating engine

Realtime content has to be brought in with JavaScript and user input handled with third party form receivers.

Getting started:

First assignment:

L2 Browser & JavaScript

Chrome is the most used browser followed by Safari and then FireFox.

http://caniuse.com/

UI, rendering engine, js engine, network layer, cookies, web storage, cache.

Rendering engines: Blink, Gecko, WebKit

JavaScript engines: V8, SpiderMonkey, WebKit

Load content in the background (XHR / WS)

Sandboxing prevents JavaScript from:

<script src="js/app.js"></script> (Avoid coding directly within script tags), add async or defer to modify loading behaviour. Specify type="module" to use ESM.

In the browser all variables are attached to the window object.

ESM benefits:


import { a as b } from './foo.js'
import { a } from './foo.js'
import * as nspace from './foo.js'

export class MyClass {
  constructor(){

  }
}

Developer tools:

L3 Document Object Model

DOM:

The Document Object Model is an API for the rendering engine.

Traversing the DOM:

Select nodes:

let tags = document.querySelectorAll('p.tag')

let pTagsArray = Array.from(pTagsNodeList)

node.


let newTag = document.createElement('p')
let newText = document.createTextNode('Cool text!')
newTag.appendChild(newText)
document.querySelector('#main').appendChild(newTag)

Both textContent and innerHTML remove all children, textContent is more secure.

getAttribute, setAttribute, removeAttribute

node.style.color = '#AA5698'

Avoid css in js except dynamic properties: movingElement.style.left = '${x}px'

Use classes instead: node.classList.add('jschanged'), .remove, .toggle, .contains

Clone template nodes.


class BartBoard extends window.HTMLElement {
  constructor() {
      super()        
  }

  connectedCallback () { // Called when added to the document
      let text = this.hasAttribute('text') ? this.getAttribute('text') : ''
      this.textContent = Array(10).join(text)
  }
}
window.customElements.define('bart-board', BartBoard)

class BartBoard extends window.HTMLElement {
  constructor() {
      super()        
  }
  connectedCallback () {
      this._updateRendering()
  }

  static get observedAttributes() {
      return ['text'];
  }
  attributeChangedCallback(name, oldValue, newValue) {
      this._updateRendering()
  }

  _updateRendering() {
      let text = this.hasAttribute('text') ? this.getAttribute('text') : ''
      this.textContent = Array(10).join(text)
  }
}

class BartBoard extends window.HTMLElement {
  constructor () {
    super()
    this.attachShadow({mode: 'open'})
    this.shadowRoot.appendChild(template.content.cloneNode(true))
  }
  ...
  _updateRendering() {
    let text = this.hasAttribute('text') ? this.getAttribute('text') : ''
    this.shadowRoot.querySelector('#board-text').textContent = Array(10).join(text)
}

https://developers.google.com/web/fundamentals/web-components/

L4 Event driven programming

a href="page.html" onclick="doSomething()"

tag.addEventListener('click', buttonClicked)

event.stopPropagation()

Use .bind(this) on addEventListener.

If a loop variable is used in a function definition it will when called read the final value of the loop variable. Do .forEach( (a, i) => { } )

event.preventDefault() or return false


let timer = setTimeout( () => {
    console.log('At least 3 seconds passed..')
}, 3000)
clearTimeout(timer) // Stops the timer

let signupEvent = new window.CustomEvent('signup', { detail: myInput.value  })
myCustomElement.dispatchEvent(signupEvent) // This will "trigger"/dispatch the event
let element = document.querySelector('#theCustomElement')
element.addEventListener('signup', event => {
    console.log(event.detail)
})

L5 Asynchronous programming

JavaScript Object Notation is a language independent subset of JavaScript with MIME application/json


[
    {"name":"Bill","type":2,"alive":false},
    {"name":"Sam","type":5,"alive":false},
    {"name":"Jason","type":1,"alive":true}
]

https://jsonlint.com/


let obj = {
    name: 'Ellen',
    age: 25,
    city: 'Kalmar'
}
console.log(obj)
obj = JSON.stringify(obj)
console.log(obj)
obj = JSON.parse(obj)
console.log(obj)

Avoid blocking calls.

HTTP methods: GET, POST, PUT, DELETE


POST /theserverpage/ HTTP/1.1
Host: lnuserver.se
Content-Length: 24

user=Ellen&message=Hello

Put response handlers in event listeners:
let req = new window.XMLHttpRequest()
req.addEventListener('load', function(){
    console.log(req.responseText)
})
req.open('GET', 'data.json')
req.send()

AJAX: Asynchronous JavaScript And XML


async getShow (id) {
    let show = await window.fetch(`http://tv-api.se/shows/${id}`)
    return show.json()
}

async searchUpdate (str) {
    let res = await window.fetch(`http://tv-api.se/shows/?q=${str}`)

    res = await res.json() // { searchString: "break", result: [24, 35]}

    let showPromises = []
    res.result.forEach(id => {
      showPromises.push(this.getShow(id))
    })
    this.shows = []
    this.shows = await Promise.all(showPromises)
}

connectedCallback () {
    this._searchButton.addEventListener('click', event => {
      this.searchUpdate(this._input.value).then(() => this._updateRendering())
    })
}
_updateRendering () {
    this._ul.innerHTML = ''
    this.shows.forEach(show => {
    let li = document.createElement('li')
    li.textContent = `${show.name} ${show.year}`
    this._ul.appendChild(li)
    })
}

let res = await window.fetch("http://url-to-fetch.at/path", {
    method: "POST",
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
})

Forms:

CORS (Cross-Origin Resource Sharing) is for fetching data from other domains.

A domain can set up to 50 cookies with max sizes 4096 bytes.

window.localStorage.setItem('bgcolor', document.getElementById('bgcolor').value)

L6 Single Page Applications


http://www.test.com:8080/index.html?id=1432&name=Tim#anchor
location.hash       // #anchor
location.host       // www.test.com
location.hostname   // www.test.com:8080
location.href       // http://www.test.com:8080/index.html?id=1432&name=Tim#anchor
location.pathname   // /index.html
location.port       // 8080
location.protocol   // http:
location.search     // ?id=1432&name=Tim

// Router
window.addEventListener('hashchange', event => {
  let hash = window.location.hash // #!/account/47435675
  if(hash.substr(2,9) === '/account/'){
      let account = hash.substr(11) //47435675
      // Fetch account information from server
      // Display account information
  }
})

window.history.

Pushstate:
let ul = document.querySelector('#links')

ul.addEventListener('click', event => {
  event.preventDefault()

  let stateObj =  {
    accountnbr: event.target.getAttribute('data-account'),
    account: event.target.getAttribute('data-account-title')
  }

  window.history.pushState(stateObj, `Account information: ${stateObj.account}`,
    `/account/${stateObj.accountnbr}`);

  // Load accountinformation and show it
  console.log(stateObj)
})

window.addEventListener('popstate', event => {
  // When an old state is triggered.
  console.log(`${event.state.account} with account number ${event.state.accountnbr}`)
})

var currentState = window.history.state

XHR is not optimal for real time apps.


let socket = new window.WebSocket('ws://www.example.com/socketserver', 'charcords')
let data = {x: 12, y: 14}

socket.addEventListener('open', event => {
  socket.send(JSON.stringify(data))
})

socket.addEventListener('message', event => {
  console.log(event.data)
})

Problems, how to

https://github.hubspot.com/offline/docs/welcome/

Application cache is deprecated.

ServiceWorkers have their own queue and WorkerGlobalScopes.

L7 Optimization & Accessibility

Up to one in five people have some impairment.

Ensure keyboard navigation is possible. Use interactive elements, aria-attributes and tabindex.

Don't add unusual interactivity for example by adding a click listener to a header.

Specify alt="image description"

Try a screenreader!

L8 APIs


let worker = new window.Worker('worker.js')
let dataContainer = document.querySelector('#pn')

worker.addEventListener('message', e => {
  dataContainer.textContent = e.data
})

//worker.js
let n = 1
search: while(true) {
  n += 1
  for(let i = 2; i <= Math.sqrt(n); i += 1){
    if (n % i === 0)
      continue search
  }
  postMessage(n)
}

Do animations with <canvas>

https://webvr.info/


window.addEventListener('gamepadconnected', e => {
    let index = e.gamepad.index
    let id = e.gamepad.id
    let nbrButtons = e.gamepad.buttons.length
    let nbrAxes = e.gamepad.axes.length
})

https://www.w3.org/TR/gamepad/

Physical web beacons: https://google.github.io/physical-web/

https://webbluetoothcg.github.io/web-bluetooth/

https://w3c.github.io/web-nfc/

https://wicg.github.io/webusb/

Web components:

Web components polyfil: https://www.polymer-project.org/

WebRTC

https://webrtc.org/

WebGL: https://en.wikipedia.org/wiki/Three.js

WASM: https://github.com/webassembly/

Web Payments

Web Share API.




Updated on 2020-08-07.