From 064738b7cfd75eac659a616be04f24fa673d463f Mon Sep 17 00:00:00 2001 From: Jon Jensen Date: Sun, 5 Apr 2015 21:34:25 -0600 Subject: [PATCH] runtime wrapper/interpolation enhancements refs #3, #4 ensure that leading/trailing standalone elements in wrappers and in the top-level interpolator are preserved also ensure that nested tags in wrappers area applied correctly --- ComponentInterpolator.js | 66 ++++++++++++++++++++++--- __tests__/ComponentInterpolator.test.js | 9 ++-- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/ComponentInterpolator.js b/ComponentInterpolator.js index 77c969e..e5426f3 100644 --- a/ComponentInterpolator.js +++ b/ComponentInterpolator.js @@ -6,15 +6,64 @@ var { string, object } = React.PropTypes; var WRAPPER_PATTERN = /(\*+)/; var PLACEHOLDER_PATTERN = /(%\{.*?\})/; +var toArray = function(children) { + if (children instanceof Array) return children.slice(); + if (!children) return []; + return [children]; +}; + +// Replace a "$1" text descendant in this tree with the newDescendants +var injectNewDescendants = function(element, newDescendants, props, ensureInjected) { + newDescendants.injectedCount = newDescendants.injectedCount || 0; + props = props || {}; + + var children = toArray(element.props.children); + for (var i = 0; i < children.length; i++) { + var child = children[i]; + children[i] = child.type ? injectNewDescendants(child, newDescendants) : child; + } + + var injectIndex = getInjectIndex(children); + if (injectIndex >= 0) { + children.splice.apply(children, [injectIndex, 1].concat(newDescendants)); + newDescendants.injectedCount++; + } + + props.children = children; + if (ensureInjected) { + invariant(newDescendants.injectedCount === 1, 'wrappers must have a single "$1" text descendant'); + } + return cloneWithProps(element, props); +}; + +var getInjectIndex = function(children, containerName) { + var child, index = -1; + for (var i = 0; i < children.length; i++) { + child = children[i]; + if (typeof child !== "string") continue; + invariant(child === "$1", containerName + ' may not have any non-"$1" text children"'); + invariant(index === -1, containerName + ' may not have multiple "$1" text children"'); + index = i; + } + return index; +}; + var ComponentInterpolator = React.createClass({ propTypes: { string: string.isRequired, - wrappers: object.isRequired + wrappers: object }, inferChildren() { var tokens = (this.props.string || '').split(WRAPPER_PATTERN); - return this.interpolateAllComponents(tokens); + var inferredChildren = this.interpolateAllComponents(tokens); + + var currentChildren = toArray(this.props.children); + + var index = getInjectIndex(currentChildren, ''); + invariant(index >= 0, ' must have a "$1" text child"'); + currentChildren.splice.apply(currentChildren, [index, 1].concat(inferredChildren)); + return currentChildren; }, interpolateAllComponents(tokens, eof) { @@ -29,10 +78,13 @@ var ComponentInterpolator = React.createClass({ child = wrappers[token], ` expected '${token}' wrapper, none found` ); - child = cloneWithProps(child, { - key: tokens.length, - children: this.interpolateAllComponents(tokens, token) - }); + + child = injectNewDescendants( + child, + this.interpolateAllComponents(tokens, token), + { key: tokens.length }, + true + ); children.push(child); } else { @@ -54,7 +106,7 @@ var ComponentInterpolator = React.createClass({ child = this.props[token], ` expected '${token}' placeholder value, none found` ); - child = cloneWithProps(child, {key: tokens.length}); + child = child.type ? cloneWithProps(child, {key: tokens.length}) : child; children.push(child); } else { children.push(token); diff --git a/__tests__/ComponentInterpolator.test.js b/__tests__/ComponentInterpolator.test.js index b5d232a..25816fe 100644 --- a/__tests__/ComponentInterpolator.test.js +++ b/__tests__/ComponentInterpolator.test.js @@ -35,18 +35,19 @@ describe('ComponentInterpolator', function() { } }, [
, "$1"]); expect(removeNoise(subject.getDOMNode().innerHTML)).toEqual( - '
Ohai, Jane, click here right now please ' + '
Ohai, Jane, click here right now please ' ); }); it('interpolates placeholder components', function() { var subject = Subject({ - string: 'Create %{count} new accounts', + string: 'Hi %{user}, create %{count} new accounts', wrappers: {}, - count: + user: "Jane", + count: }, ["$1"]); expect(removeNoise(subject.getDOMNode().innerHTML)).toEqual( - 'Create new accounts' + 'Hi Jane, create new accounts' ); }); });