mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-11 10:53:45 +01:00
187 lines
No EOL
4.5 KiB
JavaScript
187 lines
No EOL
4.5 KiB
JavaScript
import {sprintf} from 'sprintf'
|
|
|
|
export const BINDING_CALLBACK = 'OnBind';
|
|
|
|
export function Bind(node, data, policy) {
|
|
policy = adjustPolicyForNode(node, policy, data.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) {
|
|
policy = adjustPolicyForNode(node, policy);
|
|
let template = detachTemplate(node);
|
|
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) {
|
|
policy = adjustPolicyForNode(node, policy);
|
|
var formatData = node.attr('data-bind-format');
|
|
if (!formatData && policy.format) {
|
|
formatData = node.text();
|
|
node.attr('data-bind-format', formatData);
|
|
}
|
|
|
|
var isEmpty = value === '' || value === undefined || value === null;
|
|
if (isEmpty && policy.hideEmpty) {
|
|
node.text('');
|
|
node.hide();
|
|
} else {
|
|
if (formatData) {
|
|
value = sprintf(formatData, value);
|
|
}
|
|
node.text(value);
|
|
node.show();
|
|
}
|
|
}
|
|
|
|
function detachTemplate(node) {
|
|
let template = node.data("BindingTemplate");
|
|
if (!template) {
|
|
template = node.children();
|
|
template.detach();
|
|
node.data("BindingTemplate", template);
|
|
}
|
|
return template;
|
|
}
|
|
|
|
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 list = false;
|
|
let node = queue.shift();
|
|
var nestedScope = node.attr('data-bind-scope');
|
|
if (!nestedScope) {
|
|
nestedScope = node.attr('data-bind-list');
|
|
list = true;
|
|
}
|
|
if (nestedScope) {
|
|
scope.nestedScopes[nestedScope] = node;
|
|
if (list) {
|
|
detachTemplate(node);
|
|
}
|
|
} else {
|
|
advance(node);
|
|
}
|
|
}
|
|
|
|
return scope;
|
|
}
|
|
|
|
function adjustPolicyForNode(dom, propagatedPolicy, dataPolicy) {
|
|
let policy = propagatedPolicy || DEFAULT_POLICY;
|
|
let policyFromHints = getPolicyFromHints(dom);
|
|
if (policyFromHints) {
|
|
policy = Object.assign({}, policy, policyFromHints);
|
|
}
|
|
if (dataPolicy) {
|
|
policy = Object.assign({}, policy, dataPolicy);
|
|
}
|
|
return policy;
|
|
}
|
|
|
|
function getPolicyFromHints(dom) {
|
|
var hintsAttr = dom.attr('data-bind-hints');
|
|
if (!hintsAttr) {
|
|
return undefined;
|
|
}
|
|
var parsedHints = dom.data('BindingPolicy');
|
|
if (!parsedHints) {
|
|
parsedHints = {};
|
|
hintsAttr.split('|').forEach(h => parsedHints[h] = true);
|
|
dom.data('BindingPolicy', parsedHints);
|
|
}
|
|
return parsedHints;
|
|
}
|
|
|
|
const DEFAULT_POLICY = {
|
|
hideEmpty: true,
|
|
format: false
|
|
};
|
|
|
|
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'}
|
|
}});
|
|
} |