Dominik Lubański
JavaScript Expert, Frontend Architect, and Team Leader with 10+ years of experience in web development. Taking roles in a wide range of projects with software houses, media agencies, rising startups, and other technology companies.
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
By Dominik Lubański
JavaScript Expert, Frontend Architect, and Team Leader with 10+ years of experience in web development. Taking roles in a wide range of projects with software houses, media agencies, rising startups, and other technology companies.