diff --git a/SlimElement.js b/SlimElement.js index 3ab207b..50db72b 100644 --- a/SlimElement.js +++ b/SlimElement.js @@ -72,8 +72,22 @@ return camel.replace(/([A-Z])/g, '-$1').toLowerCase(); } + function __describeRepeater(attribute) { + + return { + type: "R", + attribute: attribute.nodeName, + properties: [attribute.nodeValue], + source: attribute.ownerElement.parentBind + } + } + function __describeAttribute(attribute) { + if (attribute.nodeName === 'slim-repeat') { + return __describeRepeater(attribute) + } + const rxInject = /\{(.+[^(\((.+)\))])\}/.exec(attribute.nodeValue) const rxProp = /\[(.+[^(\((.+)\))])\]/.exec(attribute.nodeValue) const rxMethod = /\[(.+)(\((.+)\)){1}\]/.exec(attribute.nodeValue) @@ -104,6 +118,17 @@ child[dashToCamel(descriptor.attribute)] = window.SlimInjector.getInjector(descriptor.factory)() } + function __repeater(descriptor) { + let sRepeat = document.createElement('s-repeat') + sRepeat.setAttribute('source', descriptor.properties[0]) + sRepeat.parentBind = descriptor.source + descriptor.child.removeAttribute('slim-repeat') + descriptor.child.parentNode.insertBefore(sRepeat, descriptor.child) + sRepeat.appendChild(descriptor.child) + sRepeat.createdCallback(true) + sRepeat.update() + } + function __bind(source, target, descriptor) { descriptor.properties.forEach( prop => { source.__bindings[prop] = source.__bindings[prop] || { @@ -124,6 +149,7 @@ if (result !== undefined) { target[dashToCamel(descriptor.attribute)] = result target.setAttribute(camelToDash(descriptor.attribute), result) + source.update() } } @@ -150,10 +176,15 @@ */ get template() { return null } + get isSlim() { + return true + } + //noinspection JSMethodCanBeStatic get updateOnAttributes() { return [] } + onBeforeCreated() {} onCreated() {} beforeRender() {} render() {} @@ -180,16 +211,22 @@ /** * Lifecycle */ - createdCallback() { + createdCallback(force) { + if (!this.isAttached && !force) return + this.onBeforeCreated() this.__bindings = {} this.__bindingTree = document.createElement('slim-component') - this._captureBindings() - this._applyBindings() + this._bindingCycle() this.onCreated() this.dispatchEvent(new Event('elementCreated', {bubbles:true})) this._renderCycle() } + _bindingCycle() { + this._captureBindings() + this._applyBindings() + } + _renderCycle(skipTree = false) { this.beforeRender() @@ -209,14 +246,19 @@ return obj; } - for (let child of this.querySelectorAll('*[bind]')) { + let allChildren = this.querySelectorAll('*[bind]') + allChildren = Array.prototype.slice.call(allChildren).concat(this) + for (let child of allChildren) { + if (child.sourceTextContent) { + child.textContent = child.sourceTextContent + } var match = child.textContent.match(/\[\[([\w|.]+)\]\]/g) child.sourceTextContent = child.textContent; if (match) { for (var i = 0; i < match.length; i++) { - let result = x(this, match[i].match(/([^\[].+[^\]])/)[0]) + let result = x(child.parentBind || this, match[i].match(/([^\[].+[^\]])/)[0]) if (result) { - child.innerText = child.innerText.replace(match[i], result) + child.textContent = child.textContent.replace(match[i], result) } } @@ -310,6 +352,8 @@ __bind(this, descriptor.child, descriptor) } else if (descriptor.type === 'I') { __inject(descriptor, descriptor.child) + } else if (descriptor.type === 'R') { + __repeater(descriptor, descriptor.child) } }) } diff --git a/components/s-repeat.js b/components/s-repeat.js index b67bfbc..a0ec7d6 100644 --- a/components/s-repeat.js +++ b/components/s-repeat.js @@ -2,10 +2,6 @@ Slim('s-repeat', class SlimRepeat extends SlimBaseElement { - afterRender() { - this.update() - } - get sourceData() { try { return this.parentBind[this.getAttribute('source')] @@ -21,20 +17,32 @@ update() { this.innerHTML = '' + let childrenToAdd = [] for (let dataItem of this.sourceData) { for (let child of this.__bindingTree.children) { - let node = document.importNode(child, false) + let node = child.cloneNode(true) + node.parentBind = node node.data = dataItem + if (!node.parentBind) { + node.parentBind = node + } for (let prop in dataItem) { node[prop] = dataItem[prop] if (!(typeof dataItem[prop] === "function") && !(typeof dataItem[prop] === "object")) { node.setAttribute(prop, dataItem[prop]) } } - this.appendChild(node) - node.update() + if (node.isSlim) { + node.createdCallback(true) + } else { + this._applyTextBindings.bind(node)() + } + childrenToAdd.push(node) } } + for (let child of childrenToAdd) { + this.appendChild(child) + } } }) })() \ No newline at end of file diff --git a/example/Kanban/KanbanDemo.html b/example/Kanban/KanbanDemo.html index 12592dc..32fd144 100644 --- a/example/Kanban/KanbanDemo.html +++ b/example/Kanban/KanbanDemo.html @@ -6,6 +6,8 @@ + + diff --git a/example/Kanban/KanbanModel.js b/example/Kanban/KanbanModel.js index d6a8c6d..f2bc832 100644 --- a/example/Kanban/KanbanModel.js +++ b/example/Kanban/KanbanModel.js @@ -6,18 +6,18 @@ class KanbanModel { constructor() { - this.lists = ['Todo', 'In-Progress', 'Done'] + this.columns = [{name: 'Todo'},{name:'In-Progress'},{name:'Done'}] this.tasks = [] } getTasks(column) { - return tasks.filter( task => { + return this.tasks.filter( task => { return task.column === column }) } - addTask(name = 'New Task', description = '') { - let newTask = { name, description} + addTask(title = 'New Task', description = '') { + let newTask = { title, description} newTask.column = 'Todo' this.tasks.push(newTask) } diff --git a/example/Kanban/kanban-app.html b/example/Kanban/kanban-app.html index 0c6406a..62abb4b 100644 --- a/example/Kanban/kanban-app.html +++ b/example/Kanban/kanban-app.html @@ -2,15 +2,15 @@ Slim('kanban-app', class extends SlimBaseElement { + get template() { - return ` -[[model.hello]]
-` + return `