Skip to content

Commit

Permalink
Improves handling of conditionally rendered elements - Fixes #147
Browse files Browse the repository at this point in the history
  • Loading branch information
mairatma committed Aug 31, 2016
1 parent 774ddbc commit 75b62f0
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 0 deletions.
7 changes: 7 additions & 0 deletions packages/metal-incremental-dom/src/IncrementalDomRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ class IncrementalDomRenderer extends ComponentRenderer {
* @protected
*/
handleInterceptedCloseCall_(originalFn, tag) {
this.emit(IncrementalDomRenderer.ELEMENT_CLOSED, {tag});
var element = originalFn(tag);
this.resetData_(domData.get(element).incDomData_);
return element;
Expand Down Expand Up @@ -464,6 +465,7 @@ class IncrementalDomRenderer extends ComponentRenderer {
* @protected
*/
handleRegularCall_(originalFn, ...args) {
this.emit(IncrementalDomRenderer.ELEMENT_OPENED, {args});
var currComp = IncrementalDomRenderer.getComponentBeingRendered();
var currRenderer = currComp.getRenderer();
if (!currRenderer.rootElementReached_) {
Expand Down Expand Up @@ -856,6 +858,11 @@ class IncrementalDomRenderer extends ComponentRenderer {
var renderingComponents_ = [];
var emptyChildren_ = [];

// Constants used as event names.
IncrementalDomRenderer.ELEMENT_OPENED = 'elementOpened';
IncrementalDomRenderer.ELEMENT_CLOSED = 'elementClosed';

// Regex pattern used to find inline listeners.
IncrementalDomRenderer.LISTENER_REGEX = /^(?:on([A-Z]\w+))|(?:data-on(\w+))$/;

export default IncrementalDomRenderer;
51 changes: 51 additions & 0 deletions packages/metal-jsx/src/JSXRenderer.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
'use strict';

import core from 'metal';
import IncrementalDomRenderer from 'metal-incremental-dom';

const childrenCount = [];

/**
* Renderer that handles JSX.
*/
class JSXRenderer extends IncrementalDomRenderer {
/**
* @inheritDoc
*/
constructor(comp) {
super(comp);

this.on(IncrementalDomRenderer.ELEMENT_OPENED, this.handleJSXElementOpened_);
this.on(IncrementalDomRenderer.ELEMENT_CLOSED, this.handleJSXElementClosed_);
}

/**
* @inheritDoc
*/
Expand All @@ -32,6 +45,32 @@ class JSXRenderer extends IncrementalDomRenderer {
}
}

/**
* Called when an element is opened during render via incremental dom. Adds
* keys to elements that don't have one yet, according to their position in
* the parent. This helps use cases that use conditionally rendered elements,
* which is very common in JSX.
* @param {!{args: !Array}} data
* @protected
*/
handleJSXElementOpened_({args}) {
if (childrenCount.length > 0) {
const count = ++childrenCount[childrenCount.length - 1];
if (!core.isDef(args[1])) {
args[1] = JSXRenderer.KEY_PREFIX + count;
}
}
childrenCount.push(0);
}

/**
* Called when an element is closed during render via incremental dom.
* @protected
*/
handleJSXElementClosed_() {
childrenCount.pop();
}

/**
* @inheritDoc
*/
Expand All @@ -52,6 +91,18 @@ class JSXRenderer extends IncrementalDomRenderer {
super.renderIncDom();
}
}

/**
* Skips the current child in the count (used when a conditional render
* decided not to render anything).
*/
static skipChild() {
if (childrenCount.length > 0) {
childrenCount[childrenCount.length - 1]++;
}
}
}

JSXRenderer.KEY_PREFIX = '_metal_jsx_';

export default JSXRenderer;
3 changes: 3 additions & 0 deletions packages/metal-jsx/src/iDOMHelpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

import IncrementalDomRenderer from 'metal-incremental-dom';
import JSXRenderer from './JSXRenderer';

/**
* These helpers are all from "babel-plugin-incremental-dom". See its README
Expand Down Expand Up @@ -46,6 +47,8 @@ window.iDOMHelpers.renderArbitrary = function(child) {
} else {
window.iDOMHelpers.forOwn(child, window.iDOMHelpers.renderArbitrary);
}
} else if (!child) {
JSXRenderer.skipChild();
}
};

Expand Down
44 changes: 44 additions & 0 deletions packages/metal-jsx/test/JSXRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,48 @@ describe('JSXRenderer', function() {
done();
});
});

it('should reuse component rendered after a conditionally rendered component', function(done) {
var createdChildren = [];
class ChildComponent extends TestJSXComponent {
constructor(...args) {
super(...args);
createdChildren.push(this);
}
render() {
return <span>Child</span>;
}
}

class ChildComponent2 extends ChildComponent {
}

class TestComponent extends TestJSXComponent {
render() {
return <div>
{!this.props.hide && <div><ChildComponent /></div>}
<div><ChildComponent2 /></div>
</div>
}
}
TestComponent.PROPS = {
hide: {
}
}

component = new TestComponent();
assert.strictEqual(2, createdChildren.length);
assert.ok(createdChildren[0] instanceof ChildComponent);
assert.ok(createdChildren[1] instanceof ChildComponent2);
assert.ok(!createdChildren[0].isDisposed());
assert.ok(!createdChildren[1].isDisposed());

component.props.hide = true;
component.once('stateSynced', function() {
assert.strictEqual(2, createdChildren.length);
assert.ok(createdChildren[0].isDisposed());
assert.ok(!createdChildren[1].isDisposed());
done();
});
});
});

0 comments on commit 75b62f0

Please sign in to comment.