jsketcher/web/app/3d/ui/bind.js
2016-10-21 20:45:03 -07:00

144 lines
No EOL
3.4 KiB
JavaScript

import {sprintf} from 'sprintf'
export const BINDING_CALLBACK = 'OnBind';
export function Bind(node, data, policy) {
if (!policy) policy = DEFAULT_POLICY;
const props = Object.getOwnPropertyNames(data);
const scope = getScope(node);
for (let prop of props) {
if (prop == BINDING_CALLBACK) continue;
let value = data[prop];
let bindFunc, subNode;
if (Array.isArray(value)) {
bindFunc = BindArray;
subNode = scope.nestedScopes[prop];
} else if (typeof value === 'object') {
bindFunc = Bind;
subNode = scope.nestedScopes[prop];
} else {
bindFunc = BindContent;
subNode = scope.bindings[prop];
}
if (!subNode) continue;
bindFunc(subNode, value, policy);
}
var callback = data[BINDING_CALLBACK];
if (callback) {
callback(node, data, policy)
}
}
export function BindArray(node, array, policy, path) {
let template = node.data("BindingTemplate");
if (!template) {
template = node.children();
template.detach();
node.data("BindingTemplate", template);
}
let scope = getScope(node);
node.children().detach();
clearScope(node);
for (let value of array) {
let child = scope.nestedScopes[value.id];
if (!child) {
child = template.clone();
} else {
delete scope.nestedScopes[value.id];
}
child.attr('data-bind-scope', value.id);
Bind(child, value, policy);
node.append(child);
}
for (let toDelete of Object.getOwnPropertyNames(scope.nestedScopes)) {
scope.nestedScopes[toDelete].remove();
}
}
export function BindContent(node, value, policy) {
if (!policy.hideEmptyValue || value || value === 0) {
var format = node.attr('data-bind-format');
if (format == '') {
format = node.text();
node.attr('data-bind-format', format);
}
if (format) {
value = sprintf(format, value);
}
node.text(value);
node.show();
} else {
node.text('');
node.hide();
}
}
function clearScope(dom) {
dom.removeData('BindingScope');
}
function getScope(dom) {
let scope = dom.data('BindingScope');
if (!scope) {
scope = index(dom);
dom.data('BindingScope', scope);
}
return scope;
}
function index(dom) {
const scope = new Scope();
//do bfs
const queue = [];
function advance(node) {
var binding = node.attr('data-bind');
if (binding) {
scope.bindings[binding] = node;
}
node.children().each((i, e) => queue.push($(e)))
}
advance(dom);
while (queue.length != 0) {
let node = queue.shift();
var nestedScope = node.attr('data-bind-scope');
if (nestedScope) {
scope.nestedScopes[nestedScope] = node;
} else {
advance(node);
}
}
return scope;
}
const DEFAULT_POLICY = {
hideEmptyValue: true
};
export function Scope() {
this.bindings = {};
this.nestedScopes = {};
}
function example(dom) {
let initState = {
title : 'this is title',
users : [
{id: 1, name: 'Peach', email: 'Peach@ooo.com'},
{id: 2, name: 'Melon', email: 'Melon@ooo.com'},
{id: 3, name: 'Berry', email: 'Berry@ooo.com'},
{id: 4, name: 'Apple', email: 'Apple@ooo.com'},
{id: 5, name: 'Banana', email: 'Banana@ooo.com'}
]
};
Bind(dom, initState);
//reordering, removing, updating provided attributes
Bind(dom, {users: [ {id:3}, {id:1, name: 'Peach-Beach'}, {id:2} ]});
//only content update
Bind(dom, {users: {
'3' : {name: 'updated', email: 'light@update.com'}
}});
}