diff --git a/.gitignore b/.gitignore index 4720f4c3..886833f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store *.class /target/ /out/ diff --git a/web/app/3d/ui/bind.js b/web/app/3d/ui/bind.js index af409091..bfebb489 100644 --- a/web/app/3d/ui/bind.js +++ b/web/app/3d/ui/bind.js @@ -1,26 +1,144 @@ import {sprintf} from 'sprintf' -export function Bind(dom, data, policy) { +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) { - const node = $(dom).find('[data-bind="'+prop+'"]'); - if (node.length == 0) continue; + if (prop == BINDING_CALLBACK) continue; let value = data[prop]; - if (!policy.hideEmptyValue || value || value === 0) { - var format = node.attr('bind-format'); - if (format) { - value = sprintf(format, value); - } - node.text(value); - node.show(); + 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 { - node.text(''); - node.hide(); + 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 -}; \ No newline at end of file +}; + +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'} + }}); +} \ No newline at end of file diff --git a/web/app/3d/ui/ctrl.js b/web/app/3d/ui/ctrl.js index 1f2cd1e8..8c597973 100644 --- a/web/app/3d/ui/ctrl.js +++ b/web/app/3d/ui/ctrl.js @@ -11,6 +11,8 @@ import {PlaneWizard} from '../wizards/plane' import {BoxWizard} from '../wizards/box' import {SphereWizard} from '../wizards/sphere' import {TransformWizard} from '../wizards/transform' +import {LoadTemplate} from './utils' +import {BindArray} from './bind' function UI(app) { this.app = app; @@ -20,10 +22,11 @@ function UI(app) { $('#right-panel').append(mainBox.root); var modelFolder = new tk.Folder("Model"); var modificationsFolder = new tk.Folder("Modifications"); + var modificationsDom = $(LoadTemplate('modifications')({})); + tk.add(mainBox, modelFolder); tk.add(mainBox, modificationsFolder); - var modificationsListComp = new tk.List(); - tk.add(modificationsFolder, modificationsListComp); + modificationsFolder.content.append(modificationsDom); var toolbarVertOffset = 10; //this.mainBox.root.position().top; @@ -51,7 +54,7 @@ function UI(app) { var craft = ui.app.craft; var historyEditMode = craft.historyPointer != craft.history.length; if (historyEditMode) { - var rows = modificationsListComp.root.find('.tc-row'); + var rows = modificationsDom.find('.tc-row'); rows.removeClass('history-selected'); rows.eq(craft.historyPointer).addClass('history-selected'); var op = craft.history[craft.historyPointer]; @@ -63,20 +66,20 @@ function UI(app) { } this.app.bus.subscribe("craft", function() { - modificationsListComp.root.empty(); - for (var i = 0; i < app.craft.history.length; i++) { - var op = app.craft.history[i]; - var row = modificationsListComp.addRow(ui.getInfoForOp(op)); - var icon = UI.getIconForOp(op); - if (icon != null) { - tk.List.setIconForRow(row, icon); - } - (function(i) { - row.click(function () { - ui.app.craft.historyPointer = i; - }) - })(i); + let modifications = []; + for (let i = 0; i < app.craft.history.length; i++) { + let op = app.craft.history[i]; + let m = { + id : i, + info: ui.getInfoForOp(op), + OnBind : (dom, data) => { + dom.css('background-image', 'url('+ UI.getIconForOp(op)+')'); + dom.click(() => ui.app.craft.historyPointer = data.id); + } + }; + modifications.push(m); } + BindArray(modificationsDom, modifications); updateHistoryPointer(); }); diff --git a/web/app/3d/ui/tmpl/action-info.html b/web/app/3d/ui/tmpl/action-info.html index 9ad94cf5..d8bb65db 100644 --- a/web/app/3d/ui/tmpl/action-info.html +++ b/web/app/3d/ui/tmpl/action-info.html @@ -1,5 +1,5 @@