Dominik Lubański
Senior Frontend Developer at
Market share stats from w3techs.com
The top 10 million websites from rankings provided by Alexa
GIF from www.webcodeexpert.com
<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:
var React = require('react');
var AppComponent = require('./app');
server.get('*', function(req, res) {
var component = AppComponent();
var html = React.renderToString(component);
res.send(html);
});
<h1>{{ Title }}</h1>
<p>{{ description }}</p>
Angular:
Twig (php):
<h1>{{ Title }}</h1>
<p>{{ description }}</p>
A multi-purpose HTML & data linking library
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.
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):
<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:
// For html above Engine will call:
TextMarker(engine, h1, "name")
<h1 --text="name">Title</h1>
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 } });
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!
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
}
}
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
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!
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!
Icons from Icons8