Introduction to
Linked HTML library

Dominik Lubański

Senior Frontend Developer at

research

Let's go back to the 90s for a moment

1995 ········ 

First official HTML specification

  • Web sites were based on complete HTML pages
     
  • Each user action required loading a complete page from the server
     
  • Inefficient process: all page content disappeared, then reappeared

········ 2005 ········

The term "Ajax" is publicly stated on 18 February 2005 by Jesse James Garrett

  • Websites began to have a dynamic parts without full page refresh
     
  • Client side JS frameworks are created
     
  • Single Page Application architecture

········ 2015

Usage of JavaScript libraries for websites

Market share stats from w3techs.com

0.1%

0.3%

67.4%

The top 10 million websites from rankings provided by Alexa

Conclusions from research

  • Most of the websites still use backend frameworks to render data
     
  • Web development has not changed much since 1995...
     
  • Javascript frameworks are not used as much as we would like

problems

Javascript frameworks are
too complicated for simple tasks

They need initial data model to render view

<input type="text" ng-model="name" value="Dominik" />
<span ng-bind="count">120</span>
<input ... value="Dominik" ng-init="name = 'Dominik'" />
<span ng-bind="count" ng-init="count = 120">120</span>

Angular will not use value from server and render
empty input and span.

You have to use ng-init:

No server side rendering without Javascript (node.js)

var React = require('react');
var AppComponent = require('./app');

server.get('*', function(req, res) {
  var component = AppComponent();
  var html = React.renderToString(component);
  res.send(html);
});

Template syntax conflicts with backend template engines

<h1>{{ Title }}</h1>
<p>{{ description }}</p>

Angular:

Twig (php):

<h1>{{ Title }}</h1>
<p>{{ description }}</p>

idea

  • Simple and easy to learn API
     
  • Data model extracted from html generated by any server-side language or template engine
     
  • Safe markup syntax
     
  • Dynamic data binding for boosting development
     
  • Superfast DOM rendering

Linked HTML

A multi-purpose HTML & data linking library 

architecture

easy to learn API

Linked HTML architecture covers three concepts

  • Engine -  It connects all the parts together, library starts with this module
     
  • Markers - Micro modules for linking data model with DOM
     
  • Expression - It evaluates values passed to markers

engine / markers / expression 

Library starts with Engine module:

import { Engine } from 'linked-html';
// Set root node
const el = document.getElementById('example');
// Initialize live view
const myView1 = new Engine(el);

// or with options
const myView2 = new Engine(el, {
  state: { title: 'super' } // Initial state
  prefix: 'data', // for <div data-marker="...">..
  markers: { myMarker }, 
  filters: { time, something }
});
// with options
const myView2 = new Engine(el, {
  state: { title: 'super' } // Initial state
  prefix: 'data', // for <div data-marker="...">..
  markers: { myMarker }, 
  filters: { time, something }
});

Engine state property is used as data model container.

engine markers / expression 

safe markup syntax

Preprocessed syntax like Mustache / Handlebars

<div data-marker="...">

Part of HTML specification - custom attributes

NO: Javascript on server-side, possible conflict with template engines.

YES:

<div --marker="...">

Shorter syntax with custom prefix ( instead of data):

extracting data model

<h1 class="new" --class="new: isNew" --text="name">
  Mug white with black roses
</h1>

In example above are used --class, --text, --prop markers.

<img src="/path/to/product/thumb.png" --prop="src: thumbUrl" />
<p class="desc" --text="description">
  Mug white with floral motifs and Golden liseret, 
  black flowers. Fast shipping upon receipt of payment, 
  anywhere in the world.
</p>

Consider example structure of e-commerce product:

Marker implementation

  • Markers are nothing more then ordinary functions called in compilation process
// For html above Engine will call:
TextMarker(engine, h1, "name")
  • Engine has a map that points --text to TextMarker function:
<h1 --text="name">Title</h1>
  • Markers are especially created for linking data with html nodes 
     
  • They are something like JSON Schema for JSON objects, but built-in html nodes
  • Library provides few more of them with other functionality - for linking arrays, working with forms, listen for events etc..

engine / markers / expression 

Expression represents  destination property path

<div --marker="expression"></div>

<div --marker="key: expression; key: expression;..."></div>
import { Expression } from 'linked-html';

function Marker(engine, node, evaluate) {
  ...
  const expr = new Expression(engine, evaluate);
  ...
}

Markers use attribute value as an expression:

Expression has to be initialized:

Expression uses engine state property as destination root.

// myView.state after compilation
{
  "isNew": true,
  "name": "Mug white with black roses",
  "thumbUrl": "/path/to/product/thumb.png",
  "description": "Mug white with floral motifs..."
}

Markers initially push state from DOM to data model. It is done only once during compilation process:

Library can take state from constructor and merge it with state from HTML.

Only expressions with undefined value are set from DOM:

const myView = new Engine(el, {
  state: { "isNew": false }
});
// myView.state after compilation
{
  "isNew": false, // not set from DOM,
  "name": "Mug white with black roses",
  "thumbUrl": "/path/to/product/thumb.png",
  "description": "Mug white with floral motifs..."
}
something.one

path

something.one[0].path
count + other || 1

YES

NO

Path works with nested properties 
(but It is not interpolated JavaScript):

Expression path has to be a settable end-point because of two-way nature of initializing data model. 

something.one
! @ & *

flags

path

Flags are one character prefixes which change expression behavior

@ set engine instance as destination root:

<button --on="click: @doSomething"></button>
<span --text="@count"></span>
class MyEngine extends Engine {
  // event callback
  doSomething() {
    this.state.count++;
  }
  
  // computed property
  get count() {
    return this.state.count * 5;
  }
}

makes expression settable only one way (no push from DOM
to data model):

<div class="with-title" --class="with-title: &title">
  <h1 --text="title">My title</h1>
</div>
// Without &
engine.state === { "title": true }

// With &
engine.state === { "title": "My title" }

! inverts expression value:

<div class="box" --prop="hidden: &!desc">
  <p --text="desc">
    Mug white with floral motifs and Golden liseret, 
    black flowers. Fast shipping upon receipt of payment, 
    anywhere in the world.
  </p>
</div>
engine.state === {
  "desc": "Mug white with floral motifs and Golden liseret..."
}
something.one
|filter

path

filter

Filter modifies setting/getting value of expression:

<div --text="updatedAt|date">10-12-2015</div>
import moment from 'momentjs';

const date = {
  // Data model --> DOM
  get: (d)=> d.format('DD-MM-YYYY'),

  // DOM --> data model
  set: (d)=> moment(d, 'DD-MM-YYYY') 
};

const engine = new Engine(el, { filters: { date } });

dynamic data binding

Expressions can be observed for detecting changes:

const expr = new Expression(engine, 'someProperty');

// Equality check: a === b
expr.observe((value) => {
    console.log('New value of someProperty:', value);
});

Markers uses that feature to reflect state changes in DOM.

// Deep properties watching with 'papillon' library
expr.observe((value, changelog) => {
    console.log('Deep property changed:', changelog.propertyA);
}, false, true);

Getting or setting `state` property of Engine instance queues change detection:

myView.state.isNew = false;
myView.state.name = 'Super Mug ...';
<!-- After next repaint -->
...
<div class --class="new: new">
  <h1 --text="name">Super Mug...</h1>
...

We have dynamic and responsive UI!

limitations

Content has to be rendered by the server:

{% if ... %}
  <div class="info" --text="imporantThing">
    Aspernatur blanditiis aut delectus quo aliquam eaque 
    asperiores ad. Ullam animi consequatur sunt.
  </div>
{% endif %}
<div --text="imporantThing" {% if ... %}hidden{% endif %}>
  Aspernatur blanditiis aut delectus quo aliquam eaque 
  asperiores ad. Ullam animi consequatur sunt.
</div>

No:

Yes:

Complex server side logic has to copied:

<div class="{% if logic() %}superClass{% endif %}" ...
<div 

    
    ... --class="superClass: @logic">
class MyEngine extends Engine {
  get logic() {
    // code here
  }
}

Other things in short

  • State property is never prototypical chained (Angular nested and isolated scope problem).
     
  • Object and array markers uses spawned Engine instance.
     
  • Markers are designed to produce possibly minimal DOM changes.
     
  • Architecture is ready for using as a client side rendering engine.

use cases

  • Extract data model for SEO events and other general uses
     

  • Dynamic user interface from backend rendered view
     

  • Rendering engine for web components
     

  • DOM equality testing tool

live example

roadmap

  • End of this year:

    • Finished API documentation

    • Helpful guides and tutorials
       

  • Q1 2016:

    • Missing markers and core functionality

    • Web components wrapper module
       

  • Q2+ 2016:

    • Everything you propose!

help

If you want to be part of
something big:

  • Give Linked HTML repository a star!
     

  • Try it yourself, use it with your projects and give a feedback
     

  • Be a contributor and make it even better - 
    any idea is welcome!

thank you

Icons from Icons8

Made with Slides.com