Technical Training and The Training Manifesto

Sam Dutton, a Developer Advocate at Google, wrote A Manifesto For Teaching Web Development as a way to write down his ideas for explaining HTML, CSS, and JavaScript to absolute beginners.

As an instructional designer, I’m in agreement with pretty close to 100% of Sam’s ideas but would further tailor some of them to make them better. I understand that Sam wrote bullet points but these

For live classes, consider flipped classroom techniques. Get students to learn at home (with specific goals and requirements) then complete activities with supervision in the classroom. Be upfront about the level of commitment expected. Provide a clear path: how to get help, where to find out more, what to do next.

I like the idea of using a flipped classroom as our primary teaching methodology but I think we also need to be upfront on the following:

Be clear on what a flipped classroom is and what the expectations are I’ve had experiences where half the class did not complete pre-requisite assignments and it changed the dynamics of the class for everyone: the students who had completed the assignments grew bored when I had to cover the material that was in the assignments and some of the students who did not complete the assignments felt that we were rushing over content that they were not familiar with.

You could state that material in the ‘home-review’ portion is required knowledge for a face to face or online workshop. This puts the ball back on the students’ court in terms of expectations. Students may not need to do the ‘home-review’ if you know the content but you should at least glance it to make sure they’re on the same page with how you’re teaching the content.

Either provide the content to review before class or do the rigorous vetting of the content before you give to students. In learning to work with Express (a Node.js based server framework) I’ve come across at least 3 different ways to accomplish a task. What will happen if students picked different ways to accomplish a task, for example: setting up a new Express application or configuring routes? Which way is correct? Why should students learn one way over the other ones? That’s for the facilitator (and the Subject Matter Experts she works with) to decide.

It is unlikely but there may be resources that are factually incorrect; this used to be the problem with W3Schools documented in w3fools. If people access incorrect information and that’s all they learn

If you provide your handouts and a list of resources in advance students will all be on the same page and will have time to work through the material ahead of the lecture/demonstration/discussion.

Make examples as simple as possible. Get rid of all redundant content and code.

Make sure that the code examples work either on their own or when added to an existing script.

You should consider that students, and other people who may come upon the code outside the content of teaching a workshop or doing flipped classes, may want to see the code work and have a fully developed example to work with and modify.

Many people new to coding don’t use or understand hierarchical file systems. When you start, make sure everyone is comfortable with files and folders. Many problems for beginning web developers are caused by something ‘in the wrong place’.

Provide a sample project with files and folders for people to get familiar with. If your pre-training assessment tells you it’s necessary you may want to consider providing a structure that they can become familiar with. It may also make sense to introduce some terminology at this point. A folder is a directory and a relative path is different than an absolute one.

Beginners make different kinds of mistakes than experienced developers. Be aware of this when you’re debugging. Show beginners what to watch out for: missing angle brackets (<div class=”foo”), missing equals signs (<div class”foo”>), missing closing tags, copy-pasted curly quotes instead of straight quotes.

Leave assumptions at the door I’ve always thought it better to assume that students don’t know the content. This makes it easier to start from scratch and for them to gradually get acclimated to the content. This point dovetails well with the next one.

Tailor your content and teaching to your audience. Try to work out how to reach a wide variety of people and capture their interest via problems they want or need to solve.

If possible do a survey of your student population as part of your needs assessment. The needs assessment is part of the analysis phase in the ADDIE Instructional Design process.

There are other questions but for the purposes of this discussion, I will only worry about the audience.

Who is the audience? Can we find self-reported skills and comfort levels? In an ideal world, I would have a survey for participants before the training begins or they get access to the content if a self-paced instruction.

Researching (Web) Workers

One thing that I’ve always been curious about is web workers. I know they are not the same as service workers but I don’t really know what they are.

So in this post, I’ll talk about Web Workers (not service workers) and what I have learned about them.

What are Web Workers

Web Workers allow scripts to run in the background, in a separate thread of execution, from the main thread running in the browser. In essence, web workers or workers allow for limited multi threading in Javascript.

What need do they fill

Because JavaScript is single threaded there are times when an app or a page will not be able to execute all the tasks the application needs without degrading the app’s overall performance. Using Workers you can delegate the task to a background thread and get the data when the computation ends without slowing down the main thread and any UI work it does.

The Web Workers Section of the WHATWG HTML standard specified two types of workers: dedicated and shared. I’ll concentrate on dedicated modules now and do more research on shared workers.

How do they work

Dedicated workers are failry simple. In the host page, we use the following script.

const supportsWorker = 'Worker' in window;

if (supportsWorker) {
  // Create the worker
  const worker = new Worker('echoWorker.js');
  // Grab a reference to the result div
  const result = document.querySelector('#result');

  // post message to worker
  worker.postMessage('<h1>This was sent from the main thread</h1>');

  // This will receive the message from the worker
  worker.onmessage = event => {
    result.innerHTML = event.data;
  }
} else {
  console.log('Web Workers not supported');
}

This script will test if the browser supports dedicated workers and, if supported, initialize the worker and run the script inside it using the new Worker() constructor.

Inside echoWorker.js we just return the message that we got from the main script

onmessage = e => {
  console.log('Echoing message we got from main script');
  postMessage(e.data);
}

We can get progressively more complex. In a later section, I will explore if we can use a worker to generate HTML from Markdown and then insert it into the parent page.

Longer Example

Using the echo example I got an idea.

Can we use workers to inject different chunks of JSON to fetch Markdown and convert it to HTML before puting the converted HTML into the parent document?

We’ll experiment with pulling a markdown file using Fetch API from a Worker to retrieve a Markdown file and then use Remarkable to convert the Markdown file we retrieved to HTML. The final step of the experiment, if it works, is to send the HTML content back to the origin page where it’ll be inserted into the #result element.

The script on the host page looks like this:

const supportsWorker = 'Worker' in window;

if (supportsWorker) {
  // Create the worker
  const worker = new Worker('./markdownWorker.js');
  // Grab a reference to the result div
  const result = document.querySelector('#result');

  // post message to worker
  worker.postMessage('./content.md');

  // This will receive the message from the worker
  // and place it inside our result element
  worker.onmessage = event => {
    result.innerHTML = event.data;
  }
} else {
  console.log('Web Workers not supported');
}

The worker, markdownWorker, does a few things that I hadn’t done before in a worker. It synchronously imports a third-party script using importScript to make sure we have it before we use it.

Inside onmmessage we initialize Remarkable, fetch the Markdown file, convert the result as text, process it with Remarkable and post the result back to the “master” page where it will render.

importScripts(
  'https://cdn.jsdelivr.net/npm/[email protected]/dist/remarkable.js');

self.onmessage = (event) => {
  // Create a remarkable instance and
  // change some default values
  const md = new Remarkable('full', {
    html: true,
    linkify: true,
    typographer: true,
  });

  // Fetch the URL passed as data for the event
  fetch(event.data)
  .then((response) => {
    // Convert the response to text
    return response.text();
  })
  .then((content) => {
    // Transform the text using the Remarkable
    // instance configured earlier
    let transformedSource = md.render(content);
    // Return the transformed text to the
    // origin page
    postMessage(transformedSource);
  })
  .catch((err) => {
    console.log('There\'s been a problem completing your request: ', err);
  });
};

There are a few questions that I still have about the markdownWorker. Some of the questions that I may follow up on later posts:

What’s the impact of fetching files from a remote server? To validate the question the worker fetches a file from the same directory it lives in. Will changing it to retrieve a file from a third party directory slow the overall process?

A related question is how do we make sure that a site’s content security policy will not prevent a worker from fetching content?

Will the worker work faster if it uses async / await? I’ve seen posts in both directions so I started with Promises. I’m thinking about using async/await in a future iteration to see if it actually works faster.

CRAP in Design

When working in Instructional and Presentation Design I remember the way we were taught basic design, to think CRAP.

CRAP is the not very polite acronym for these four basic design principles

  • Contrast
  • Repetition
  • Alignment
  • Proximity

Why am I interested in these principles?

Whenever working on a presentation it’s important to keep people engaged and tell a good story that will engage the audience. It will also let you move the content between formats: PowerPoint to Captivate and self-paced learning and to web presentations.

The Principles

Now that we’ve looked at what the principles are and why I think they are important, let’s revisit them and talk about them in more detail.

Contrast

In this context, contrast refers to any difference in size, shape, or color used to distinguish text.

The use of bold or italics is one common form of contrast; the difference in shape makes the bolded or italicized text stand out from the surrounding text.

Changing the size of our text or the font face we use for specific portions of the presentation (headings, subheadings, body text) is another way to create contrast.

One particular way to change the contrast is to change the foreground or text color and compare it with the background color. To be absolutely sure we have a good contrast ratio between the two colors we can use tools like Lea Verou’s contrast-ratio.com or, if you’re working on your browser, use Chrome DevTools color accessibility checker.

Font selection is another way to provide contrast for a slide or document. It can be through different fonts or it can be through different typefaces of the same font family.

Repetition

Repetition: Repetition in your text is bad; repetition of your design elements is not only good but necessary. Once you’ve decided on a size and typeface for an element then all instances of that element should look the same.

For most documents use a few, two or maybe three, fonts — leaning heavily on one for all the body text, with the other two for headers and maybe sidebars — are enough.

The same idea of repetition applies to other elements in your pages/slides such as bullets in lits, placement of information that is repeated in multiple pages/slides of your document, and design elements — like horizontal rules between sections or corporate logos.

Repetition of design elements pulls the document together into a cohesive whole and also improves readability as it sets the reader’s expectations about the text looks and what it does (e.g. the start of a new section, a major point, or a piece of code).

Alignment

Alignment is crucial not just to the cohesive appearance of your document but to the creation of contrast for elements like bulleted lists or double-indented long quotes.

Your document should have a couple of vertical baselines and all text should be aligned to one of them.

Proximity

Pieces of information that are meant to complement each other should be near each other. Readers should be given a logical way to get to the next piece of information they need to see next.

Links and Resources

Immersive Web: WebGL, WebVR and Beyond

Looking back at WebVR I realized how important it’s to keep paying attention to technologies that you’re interested in or they may change from under your feet.

VR (and now AR) is one of these areas.

What it is

WebXR is an evolutionary step currently under Origin Trials, a way to run experiments for a subset of developers using a feature. This allows for rapid iteration and quick feedback but without running the risk of the feature becoming a defacto standard, particularly when the API or feature is not finished.

In this context, I’ll use WebXR to mean the WebXR Device API.

WebXR lets you create AR and VR websites by providing access to input and output capabilities of AR/VR hardware.

Examples of hardware devices include:

For more details, watch Brandon Jones’ presentation at Google I/O this year for more details on WebXR.

How it works?

We’ll look at two examples of what we can do with WebXR: Using Magic Windows to place 3D content inside a regular web page and a second example of how to create old-school WebVR content to use with VR devices.

The WebXR demo uses Three.js to actually build the content.

Playing WebGL content

There shouldn’t be any difference in playing with WebGL content in browsers that support WebGL and WebVR/WebXR. WebGL is not part of this trial and shouldn’t affect the way WebGL content look on the browser.

Playing WebXR content from a browser

The next step is how to render content using one codebase that will work well with all possible uses for WebXR.

Mozilla has released an extension to Three.js called three.xr.js that makes it easier to work creating WebXR content. 

The example below assumes that you’ve imported three.js and three.xr.js.

What I like is that, while there’s a lot of code, even in this simple example, it’s mostly boilerplate for three.xr and can be abstracted to a separate file to be reused later.

This is the basic example from three.xr.js that will display the basic WebGL scene and detect if there is a VR or AR device available and, if there is one, display buttons to enter the Mixed Reality mode that the device supports.

var clock = new THREE.Clock();
var container;
var renderer, camera, scene;
var floor, cube, sky;

// ar, magicWindow, vr
var activeRealityType = 'magicWindow';
// Query for available displays then run init
THREE.WebXRUtils.getDisplays().then(init);

function init(displays) {
  // Create a container div and append it to body
  container = document.createElement( 'div' );
  document.body.appendChild( container );

  // Create three.js scene and camera
  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera();
  scene.add( camera );
  camera.position.set(0, 1, 0);
  renderer = new THREE.WebGLRenderer( { alpha: true } );
  renderer.autoClear = false;
  container.appendChild( renderer.domElement );

  // Add custom content
  // Define the floor
  floor =   new THREE.Mesh(new THREE.PlaneGeometry(8, 8),
            new THREE.MeshBasicMaterial({
              color:0xffffff,
              transparent: true,
              opacity: 0.3
            }));

  floor.geometry.rotateX(- Math.PI / 2);
  scene.add (floor);

  // Define a cube
  var geometry = new THREE.BoxGeometry(0.5,0.5,0.5);
  var material = new THREE.MeshNormalMaterial();
  cube = new THREE.Mesh(geometry, material);
  cube.position.set(0, 1, -1.5);
  cube.rotation.y = Math.PI/4;
  scene.add (cube);

  // Define the sky
  var skyGeometry = new THREE.SphereGeometry(5);
  var skyMaterial = new THREE.MeshNormalMaterial({
    side: THREE.BackSide
  });
  sky = new THREE.Mesh(skyGeometry, skyMaterial);
  scene.add (sky);
  // End custom content

  // Define standard evnts and elements
  window.addEventListener( 'resize', onWindowResize, false );
  onWindowResize();
  var options = {
    // Flag to start AR if is the unique display available.
    AR_AUTOSTART: false, // Default: true
  }
  renderer.xr = new THREE.WebXRManager(options, displays, renderer, camera, scene, update);
  renderer.xr.addEventListener('sessionStarted', sessionStarted);
  renderer.xr.addEventListener('sessionEnded', sessionEnded);

  if(!renderer.xr.autoStarted){
    var buttonsContainer = document.createElement( 'div' );
    buttonsContainer.id = 'buttonsContainer';
    buttonsContainer.style.position = 'absolute';
    buttonsContainer.style.bottom = '10%';
    buttonsContainer.style.width = '100%';
    buttonsContainer.style.textAlign = 'center';
    buttonsContainer.style.zIndex = '999';
    document.body.appendChild(buttonsContainer);
    addEnterButtons(displays);
  }
  renderer.animate(render);
}

function sessionStarted(data) {
  activeRealityType = data.session.realityType;
  if(data.session.realityType === 'ar'){
    sky.visible = false;
  }
}

function sessionEnded(data) {
  activeRealityType = 'magicWindow';
  if(data.session.realityType === 'ar'){
    sky.visible = true;
  }
}

function addEnterButtons(displays) {
  for (var i = 0; i < displays.length; i++) {
    var display = displays[i];
    if(display.supportedRealities.vr){
      buttonsContainer.appendChild(getEnterButton(display, 'vr'));
    }
    if(display.supportedRealities.ar){
      buttonsContainer.appendChild(getEnterButton(display, 'ar'));
    }
  }
}

function getEnterButton(display, reality) {
  // HMDs require the call to start presenting to
  // occur due to a user input event, so make a
  // button to trigger that
  var button = document.createElement( 'button' );
  button.style.display = 'inline-block';
  button.style.margin = '5px';
  button.style.width = '120px';
  button.style.border = '0';
  button.style.padding = '8px';
  button.style.cursor = 'pointer';
  button.style.backgroundColor = '#000';
  button.style.color = '#fff';
  button.style.fontFamily = 'sans-serif';
  button.style.fontSize = '13px';
  button.style.fontStyle = 'normal';
  button.style.textAlign = 'center';
  if(reality === 'vr'){
    button.textContent = 'ENTER VR';
  }else{
    button.textContent = 'ENTER AR';
  }

  button.addEventListener('click', ev => {
    if(reality === 'ar'){
      if(!renderer.xr.sessionActive){
        // Entering AR.
        button.textContent = 'EXIT AR';
        renderer.xr.startSession(display, reality, true);
      } else {
        // Exiting AR.
        button.textContent = 'ENTER AR';
        renderer.xr.endSession();
        sky.visible = true;
      }
    } else {
      document.getElementById('buttonsContainer').style.display = 'none';
      renderer.xr.startPresenting();
    }
  });

  if(reality === 'vr'){
    window.addEventListener('vrdisplaypresentchange', (evt) => {
      // Polyfill places cameraActivateddisplay inside the detail property
      var display = evt.display || evt.detail.display;
      if (!display.isPresenting) {
        // Exiting VR.
        renderer.xr.endSession();
        document.getElementById('buttonsContainer').style.display = 'block';
      }
    });
  }
  return button;
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
}

// Called once per frame, before render, to give the app a chance to update this.scene
function update(frame) {
  render();
}

function render() {
  switch (activeRealityType) {
    case 'magicWindow':
    case 'vr':
      var delta = clock.getDelta() * 60;
      // cube.rotation.y += delta * 0.01;
      break;
  }

  if(!renderer.xr.sessionActive){
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.render(scene, camera);
  } else {}
}

Future Looking: Magic Window, glipmses of VR in a 2D context

One of the really cool things you can do with WebXR is to create “Magic Windows”, areas where we can view 3D content inside our regular web page.

So far all the examples I’ve see of WebXR are either full screen 3D on-device experiences or experiences that require an AR-enabled device to work. That’s awesome but wouldn’t it be nice if we could launch the content from a regular web page?

At Google I/O this year I saw such a demo.

It included an AR object inserted as an image or iframe in a page that would only go into AR mode when viewed in an AR-capable device and the user actually clicked on the image to activate the the placement of the AR object in the physical space.

I’m not 100% certain that this was WebXR or if it was more on the ARCore/ARKit side of the AR/VR experience. Since it was on a webpage I expect the earlier but there was a big push on AR for the Android and iOS platforms so I’m not sure.

I’ll continue researching and will post more when I have configmration either way.

Links and examples

HTTP2 Development in Node Using Self Signed Certs

There may be times when we want to test our application with SSL or want to configure it and use it locally. Even though newer APIs whitelist localhost for development I still would rather have HTTPS/SSL enabled to make sure that other parts of my apps work as intended, particularly since most of the new web APIs require HTTPS to run.

My preferred approach is to use the NODE_EXTRA_CA_CERTS environment variable to give Node the path to a file containing one or more certificates that you want to use. The extra certificates are used, in addition, to the list of Certificate Authorities that Node already trusts.

For example, if I want to trust a self-signed certificate that I created on my machine, I might execute the following command in my terminal or add it to my .bashrc:

export NODE_EXTRA_CA_CERTS=/c/code/server.crt.

Generating certificates for local development

If you want to generate a certificate for local development, something like this will work:

openssl req \
       -newkey rsa:2048 -nodes -keyout domain.key \
       -x509 -days 365 -out domain.crt

Note you’ll want to set the Common Name to be **localhost if you intend to work locally, as opposed to 127.0.0.1. You also need to execute all your local requests to localhost as opposed to 127.0.0.1.**