Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 56 additions & 45 deletions browser/DocumentRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ class DocumentRenderer {
const id = this._getId(element);
const localContext = this._localContextProvider.getContextById(id);
const hadChildrenNodes = (element.children.length > 0);
let isInitialRender = false;

if (!renderingContext) {
renderingContext = this._createRenderingContext();
Expand All @@ -161,6 +162,7 @@ class DocumentRenderer {
const ComponentConstructor = localContext.constructor;

if (!instance) {
isInitialRender = true;
ComponentConstructor.prototype.$context = this._getComponentContext(localContext, element);
instance = new ComponentConstructor(this._locator);
instance.$context = ComponentConstructor.prototype.$context;
Expand All @@ -169,64 +171,73 @@ class DocumentRenderer {

this._componentElements[id] = element;

return Promise.resolve()
.then(() => {
// we need to unbind the whole hierarchy only at
// the beginning, not for any new elements
if (!(id in renderingContext.rootIds) || !hadChildrenNodes) {
const shouldComponentUpdateMethod = moduleHelper.getMethodToInvoke(instance, 'shouldComponentUpdate');

return moduleHelper.getSafePromise(shouldComponentUpdateMethod)
.then((isShouldComponentUpdate = true) => {
if (!isShouldComponentUpdate && !isInitialRender) {
return null;
}

return this._unbindAll(element, renderingContext);
})
.catch((reason) => this._eventBus.emit('error', reason))
.then(() => this._bindWatcher(localContext, element))
.then(() => {
const renderMethod = moduleHelper.getMethodToInvoke(instance, 'render');
return moduleHelper.getSafePromise(renderMethod);
})
.then((dataContext) => instance.template(dataContext))
.catch((reason) => this._handleRenderError(element, reason))
.then(html => {
const isHead = element.tagName === TAG_NAMES.HEAD;
return Promise.resolve()
.then(() => {
// we need to unbind the whole hierarchy only at
// the beginning, not for any new elements
if (!(id in renderingContext.rootIds) || !hadChildrenNodes) {
return null;
}

if (html === '' && isHead) {
return null;
}
return this._unbindAll(element, renderingContext);
})
.catch((reason) => this._eventBus.emit('error', reason))
.then(() => this._bindWatcher(localContext, element))
.then(() => {
const renderMethod = moduleHelper.getMethodToInvoke(instance, 'render');
return moduleHelper.getSafePromise(renderMethod);
})
.then((dataContext) => instance.template(dataContext))
.catch((reason) => this._handleRenderError(element, reason))
.then(html => {
const isHead = element.tagName === TAG_NAMES.HEAD;

if (html === '' && isHead) {
return null;
}

const tmpElement = element.cloneNode(false);
tmpElement.innerHTML = html;
const tmpElement = element.cloneNode(false);
tmpElement.innerHTML = html;

this._updateSlotContent(tmpElement);
this._updateSlotContent(tmpElement);

if (isHead) {
this._mergeHead(element, tmpElement);
return null;
}
if (isHead) {
this._mergeHead(element, tmpElement);
return null;
}

morphdom(element, tmpElement, {
onBeforeMorphElChildren: (foundElement) =>
foundElement === element || !moduleHelper.isComponentNode(foundElement)
});
morphdom(element, tmpElement, {
onBeforeMorphElChildren: (foundElement) =>
foundElement === element || !moduleHelper.isComponentNode(foundElement)
});

// Morphing slot elements
let slot = findSlot(element);
let fragment = this._componentSlotContents[id];
// Morphing slot elements
let slot = findSlot(element);
let fragment = this._componentSlotContents[id];

if (slot && fragment) {
let content = fragment.cloneNode(true);
if (slot && fragment) {
let content = fragment.cloneNode(true);

Array.from(content.children)
.forEach((child) => {
child.$parentId = this._localContextProvider.getParentById(id);
});
Array.from(content.children)
.forEach((child) => {
child.$parentId = this._localContextProvider.getParentById(id);
});

slot.innerHTML = '';
slot.appendChild(content);
}
slot.innerHTML = '';
slot.appendChild(content);
}
})
.then(() => this._bindComponent(element));
})
.then(() => this._bindComponent(element))
.catch(reason => this._eventBus.emit('error', reason));
.catch((reason) => this._eventBus.emit('error', reason));
};

return this._traverseComponentsWithContext([rootElement], action, rootContext)
Expand Down
87 changes: 87 additions & 0 deletions test/browser/DocumentRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,93 @@ lab.experiment('browser/DocumentRenderer', () => {
});
});

lab.test('Should block render if shouldComponentUpdate return false', (done) => {
const locator = createLocator();
const eventBus = locator.resolve('eventBus');
let renderCount = 0;

const expected = 'test<br><div>Hello, World 1!</div>';

eventBus.on('error', done);
jsdom.env({
html: ' ',
done: function (errors, window) {
class Component {
template () {
renderCount += 1;
return `test<br><div>Hello, World ${renderCount}!</div>`;
}

shouldComponentUpdate () {
return false;
}
}

locator.registerInstance('window', window);

const renderer = new DocumentRenderer(locator);
const element = window.document.createElement('cat-test');

renderer.renderComponent(element, { constructor: Component })
.then(() => {
assert.strictEqual(element.innerHTML, expected);
assert.strictEqual(renderCount, 1);
return renderer.renderComponent(element, { constructor: Component });
})
.then(() => {
assert.strictEqual(element.innerHTML, expected);
assert.strictEqual(renderCount, 1);
done();
})
.catch(done);
}
});
});

lab.test('Should not block render if shouldComponentUpdate return true', (done) => {
const locator = createLocator();
const eventBus = locator.resolve('eventBus');
let renderCount = 0;

const expected1 = 'test<br><div>Hello, World 1!</div>';
const expected2 = 'test<br><div>Hello, World 2!</div>';

eventBus.on('error', done);
jsdom.env({
html: ' ',
done: function (errors, window) {
class Component {
template () {
renderCount += 1;
return `test<br><div>Hello, World ${renderCount}!</div>`;
}

shouldComponentUpdate () {
return true;
}
}

locator.registerInstance('window', window);

const renderer = new DocumentRenderer(locator);
const element = window.document.createElement('cat-test');

renderer.renderComponent(element, { constructor: Component })
.then(() => {
assert.strictEqual(element.innerHTML, expected1);
assert.strictEqual(renderCount, 1);
return renderer.renderComponent(element, { constructor: Component });
})
.then(() => {
assert.strictEqual(element.innerHTML, expected2);
assert.strictEqual(renderCount, 2);
done();
})
.catch(done);
}
});
});

lab.test('Should render debug output instead the content when error in debug mode', (done) => {
const locator = createLocator();
const eventBus = locator.resolve('eventBus');
Expand Down