In KFF, the basic building block of the UI is the view. Despite the name it is more like a component. Its primary goal is to render some part of the UI (genereate a DOM), keep it in sync with javascript state and to react to user interactions by dispatching actions.
Every view is bound to a single DOM element. Views are declared in the data-kff-view
attribute of the element. Every view can contain subviews which are nested views bound to descendant elements of the view element.
Every view can also contain bindings. A binding is just a simple way to bind some data to some properties of the DOM element such as the text content, attribute, class, input value etc. Whenever the data change, the DOM is efficiently updated. Some of the bindings are two-way but not in the MVC style – it's more like the flux style. Whenever the value changes as a result of user interaction, an action is dispatched, the state is changed and the whole view tree is rerendered (but only nessesary DOM modifications are applied). Bindings are declared in the data-kff-bind
attribute.
<html>
<body>
<p data-kff-bind="state.hello:text"></p>
</body>
</html>
import {View, Cursor} from 'kff';
const stateCursor = new Cursor({
hello: 'Hello World'
});
const myView = new View({
scope: {
state: stateCursor
}
});
myView.initAll();
A state is a simple javascript object. As you can see, the state is wrapped in something called Cursor
. Cursor is a wrapper around the state and allows to point to some property deep in the state object and change it in immutable way. More about cursors see Cursor.
The example above can also be rewritten as follows:
<html>
<body>
<p data-kff-bind="hello:text"></p>
</body>
</html>
import {View, Cursor} from 'kff';
const stateCursor = new Cursor({
hello: 'Hello World'
});
const helloCursor = stateCursor.refine('hello');
const myView = new View({
scope: {
hello: helloCursor
}
});
myView.initAll();
Here we are creating a subcursor that points to the hello
property inside the state.
<html>
<body>
<ul data-kff-bind="todos.length:ifnot(0)">
<li data-kff-bind="todos:each .title:text"></li>
</ul>
</body>
</html>
import {View, Cursor} from 'kff';
const stateCursor = new Cursor({
todos: [
{
title: 'first todo'
},
{
title: 'second todo'
}
]
});
const myView = new View({
scope: {
todos: stateCursor.refine('todos')
}
});
myView.initAll();
todos.length:ifnot(0)
-todos.length
obviously gets the length of the array. Theifnot
binder removes theul
element from the DOM when the length is zero.todos:each
is a collection binder. It will generate a newli
element for every item of thetodo
array.
- Multiple bindings can be used in a single
data-kff-bind
attribute separated by a space. - KFF doesn't use any nonstandard html-invalid syntax nor mustache-like templating syntax, everything is inside
data-kff-
attributes
<html>
<body>
<p><input type="text" data-kff-bind="name:val" placeholder="Your name…"></p>
<p data-kff-bind="name:if">Hello, <span data-kff-bind="name:text"></span></p>
</body>
</html>
import {View, Cursor} from 'kff';
const myView = new View({
scope: {
name: new Cursor(null);
}
});
myView.initAll();
- You can create a Cursor of not only an object, but also of a primitive value, even of
null
. It's not a common case but it's perfectly possible. - Whenever you type in the input, the default
set
action is dispatched and the entire view is refreshed with the new state.
More about creating custom Views
More about Data binding