From 5615bf6d307b3b99b4b0114ecc86ee8923685e81 Mon Sep 17 00:00:00 2001 From: Brian M Hunt Date: Tue, 26 Jun 2018 10:34:36 -0400 Subject: [PATCH] component/jsx) Support partials in `children` This would improve substantially with a `trackArrayChanges` on the right observables. --- .../spec/componentBindingBehaviors.js | 42 ++++++++++++- packages/tko.utils.jsx/src/jsx.js | 60 +++++++++++++------ 2 files changed, 82 insertions(+), 20 deletions(-) diff --git a/packages/tko.binding.component/spec/componentBindingBehaviors.js b/packages/tko.binding.component/spec/componentBindingBehaviors.js index ab76259a..6aaad161 100644 --- a/packages/tko.binding.component/spec/componentBindingBehaviors.js +++ b/packages/tko.binding.component/spec/componentBindingBehaviors.js @@ -894,8 +894,6 @@ describe('Components: Component binding', function () { it('inserts a partial when the `template` is an array', function () { class ViewModel extends components.ComponentABC { static get template () { - // Passing
{o2}
through - // babel-plugin-transform-jsx will yield: return [ { elementName: 'b', attributes: { }, children: ['x'] }, { elementName: 'i', attributes: { }, children: ['y'] }, @@ -907,6 +905,46 @@ describe('Components: Component binding', function () { applyBindings(outerViewModel, testNode) expect(testNode.children[0].innerHTML).toEqual('xyz') }) + + it('inserts partials from `children`', function () { + const children = [ + 'abc', + { elementName: 'c', attributes: {}, children: [['C']] } + ] + class ViewModel extends components.ComponentABC { + static get template () { + return [ + { elementName: 'b', attributes: { }, children: ['x', children] } + ] + } + } + ViewModel.register('test-component') + applyBindings(outerViewModel, testNode) + expect(testNode.children[0].innerHTML).toEqual('xabcC') + }) + + it('inserts & updates observable partials from `children`', function () { + const children = observableArray([ + 'abc', + { elementName: 'c', attributes: {}, children: [['C']] } + ]) + class ViewModel extends components.ComponentABC { + static get template () { + return [ + { elementName: 'b', attributes: { }, children: ['x', children] } + ] + } + } + ViewModel.register('test-component') + applyBindings(outerViewModel, testNode) + expect(testNode.children[0].innerHTML).toEqual('xabcC') + + children.pop() + expect(testNode.children[0].innerHTML).toEqual('xabc') + + children.unshift('rrr') + expect(testNode.children[0].innerHTML).toEqual('xrrrabc') + }) }) describe('slots', function () { diff --git a/packages/tko.utils.jsx/src/jsx.js b/packages/tko.utils.jsx/src/jsx.js index a6788752..d19a3471 100644 --- a/packages/tko.utils.jsx/src/jsx.js +++ b/packages/tko.utils.jsx/src/jsx.js @@ -1,6 +1,6 @@ import { - cleanNode, removeNode, addDisposeCallback + removeNode, addDisposeCallback } from 'tko.utils' import { @@ -44,8 +44,12 @@ export function jsxToNode (jsx) { return node } -function appendChild (possibleTemplateElement, nodeToAppend) { - if ('content' in possibleTemplateElement) { +function appendChildOrChildren (possibleTemplateElement, nodeToAppend) { + if (Array.isArray(nodeToAppend)) { + for (const node of nodeToAppend) { + appendChildOrChildren(possibleTemplateElement, node) + } + } else if ('content' in possibleTemplateElement) { possibleTemplateElement.content.appendChild(nodeToAppend) } else { possibleTemplateElement.appendChild(nodeToAppend) @@ -69,7 +73,7 @@ function updateChildren (node, children, subscriptions) { if (isObservable(child)) { subscriptions.push(monitorObservableChild(node, child)) } else { - appendChild(node, convertJsxChildToDom(child)) + appendChildOrChildren(node, convertJsxChildToDom(child)) } } } @@ -102,28 +106,48 @@ function updateAttributes (node, attributes, subscriptions) { } } +/** + * + * @param {jsx} newJsx + * @param {HTMLElement|Array} toReplace + * @return {HTMLElement|Array} Nodes to replace next time + * + * TODO: Use trackArrayChanges to minimize changes to the DOM and state-loss. + */ +function replaceNodeOrNodes (newJsx, toReplace, parentNode) { + const newNodeOrNodes = convertJsxChildToDom(newJsx) + const $context = contextFor(toReplace) + + if (Array.isArray(toReplace)) { + for (const node of toReplace) { removeNode(node) } + } else { + removeNode(toReplace) + } + appendChildOrChildren(parentNode, newNodeOrNodes) + if ($context) { applyBindings($context, newNodeOrNodes) } + return newNodeOrNodes +} + function monitorObservableChild (node, child) { const jsx = unwrap(child) - let nodeToReplace = convertJsxChildToDom(jsx) + let toReplace = convertJsxChildToDom(jsx) + appendChildOrChildren(node, toReplace) const subscription = child.subscribe(newJsx => { - const newNode = convertJsxChildToDom(newJsx) - const $context = contextFor(node) - cleanNode(nodeToReplace) - node.replaceChild(newNode, nodeToReplace) - if ($context) { - applyBindings(contextFor(node), newNode) - } - nodeToReplace = newNode + toReplace = replaceNodeOrNodes(newJsx, toReplace, node) }) - appendChild(node, nodeToReplace) return subscription } +/** + * Convert a child to the anticipated HTMLElement(s). + * @param {string|array|jsx} child + * @return {Array|Comment|HTMLElement} + */ function convertJsxChildToDom (child) { - return typeof child === 'string' - ? document.createTextNode(child) - : child ? jsxToNode(child) - : document.createComment('[jsx placeholder]') + return typeof child === 'string' ? document.createTextNode(child) + : Array.isArray(child) ? child.map(convertJsxChildToDom) + : child ? jsxToNode(child) + : document.createComment('[jsx placeholder]') }