Skip to content
This repository has been archived by the owner on Jul 30, 2018. It is now read-only.

Commit

Permalink
Add back in attach handle and make nodehandler sync
Browse files Browse the repository at this point in the history
  • Loading branch information
agubler committed Nov 9, 2017
1 parent e3b4ca4 commit 18ee570
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 61 deletions.
28 changes: 18 additions & 10 deletions src/mixins/Projector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { assign } from '@dojo/core/lang';
import global from '@dojo/shim/global';
import { createHandle } from '@dojo/core/lang';
import { Handle } from '@dojo/interfaces/core';
import 'pepjs';
import cssTransitions from '../animations/cssTransitions';
import { Constructor, DNode, Projection, ProjectionOptions } from './../interfaces';
Expand Down Expand Up @@ -51,7 +53,7 @@ export interface ProjectorMixin<P> {
/**
* Append the projector to the root.
*/
append(root?: Element): void;
append(root?: Element): Handle;

/**
* Merge the projector onto the root.
Expand All @@ -60,12 +62,12 @@ export interface ProjectorMixin<P> {
* will be created.
* @param root The root element that the root virtual DOM node will be merged with. Defaults to `document.body`.
*/
merge(root?: Element): void;
merge(root?: Element): Handle;

/**
* Replace the root with the projector node.
*/
replace(root?: Element): void;
replace(root?: Element): Handle;

/**
* Pause the projector.
Expand Down Expand Up @@ -145,6 +147,7 @@ export function ProjectorMixin<P, T extends Constructor<WidgetBase<P>>>(Base: T)

private _root: Element;
private _async = true;
private _attachHandle: Handle;
private _projectionOptions: Partial<ProjectionOptions>;
private _projection: Projection | undefined;
private _scheduled: number | undefined;
Expand Down Expand Up @@ -175,7 +178,7 @@ export function ProjectorMixin<P, T extends Constructor<WidgetBase<P>>>(Base: T)
this.scheduleRender();
}

public append(root?: Element) {
public append(root?: Element): Handle {
const options = {
type: AttachType.Append,
root
Expand All @@ -184,7 +187,7 @@ export function ProjectorMixin<P, T extends Constructor<WidgetBase<P>>>(Base: T)
return this._attach(options);
}

public merge(root?: Element) {
public merge(root?: Element): Handle {
const options = {
type: AttachType.Merge,
root
Expand All @@ -193,7 +196,7 @@ export function ProjectorMixin<P, T extends Constructor<WidgetBase<P>>>(Base: T)
return this._attach(options);
}

public replace(root?: Element) {
public replace(root?: Element): Handle {
const options = {
type: AttachType.Replace,
root
Expand Down Expand Up @@ -335,25 +338,28 @@ export function ProjectorMixin<P, T extends Constructor<WidgetBase<P>>>(Base: T)
}
}

private _attach({ type, root }: AttachOptions): void {
private _attach({ type, root }: AttachOptions): Handle {
this._attachType = type;
if (root) {
this.root = root;
}

if (this.projectorState === ProjectorAttachState.Attached) {
return;
return this._attachHandle;
}

this.projectorState = ProjectorAttachState.Attached;

this.own(() => {
const handle = () => {
if (this.projectorState === ProjectorAttachState.Attached) {
this.pause();
this._projection = undefined;
this.projectorState = ProjectorAttachState.Detached;
}
});
};

this.own(handle);
this._attachHandle = createHandle(handle);

this._projectionOptions = { ...this._projectionOptions, ...{ sync: !this._async } };

Expand All @@ -369,6 +375,8 @@ export function ProjectorMixin<P, T extends Constructor<WidgetBase<P>>>(Base: T)
this._projection = dom.replace(this.root, this._boundRender(), this, this._projectionOptions);
break;
}

return this._attachHandle;
}
}

Expand Down
36 changes: 9 additions & 27 deletions src/vdom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,9 +639,7 @@ function initPropertiesAndChildren(
}
setProperties(domNode, dnode.properties, projectionOptions);
if (dnode.properties.key !== null && dnode.properties.key !== undefined) {
projectionOptions.afterRenderCallbacks.push(() => {
parentInstance.nodeHandler.add(domNode as HTMLElement, `${dnode.properties.key}`);
});
parentInstance.nodeHandler.add(domNode as HTMLElement, `${dnode.properties.key}`);
projectionOptions.afterRenderCallbacks.push(() => {
parentInstance.onElementCreated(domNode as HTMLElement, dnode.properties.key as any);
});
Expand Down Expand Up @@ -678,9 +676,7 @@ function createDom(
dnode.rendered = filteredRendered;
addChildren(parentNode, filteredRendered, projectionOptions, instance as WidgetBase, insertBefore, childNodes);
}
projectionOptions.afterRenderCallbacks.push(() => {
parentInstance.nodeHandler.addRoot();
});
instance.nodeHandler.addRoot();
}
else {
if (projectionOptions.merge && projectionOptions.mergeElement !== undefined) {
Expand Down Expand Up @@ -744,9 +740,7 @@ function updateDom(previous: any, dnode: InternalDNode, projectionOptions: Proje
dnode.rendered = filterAndDecorateChildren(rendered, instance);
if (hasRenderChanged(previousRendered, rendered)) {
updateChildren(parentNode, previousRendered, dnode.rendered, instance, projectionOptions);
projectionOptions.afterRenderCallbacks.push(() => {
parentInstance.nodeHandler.addRoot();
});
instance.nodeHandler.addRoot();
}
}
else {
Expand Down Expand Up @@ -787,9 +781,7 @@ function updateDom(previous: any, dnode: InternalDNode, projectionOptions: Proje
updated = updateProperties(domNode, previous.properties, dnode.properties, projectionOptions) || updated;

if (dnode.properties.key !== null && dnode.properties.key !== undefined) {
projectionOptions.afterRenderCallbacks.push(() => {
parentInstance.nodeHandler.add(domNode, `${dnode.properties.key}`);
});
parentInstance.nodeHandler.add(domNode, `${dnode.properties.key}`);
projectionOptions.afterRenderCallbacks.push(() => {
parentInstance.onElementUpdated(domNode as HTMLElement, dnode.properties.key as any);
});
Expand Down Expand Up @@ -873,9 +865,7 @@ function createProjection(dnode: InternalDNode | InternalDNode[], parentInstance

updatedDNode = filterAndDecorateChildren(updatedDNode, parentInstance);
updateChildren(domNode, projectionDNode, updatedDNode as InternalDNode[], parentInstance, projectionOptions);
projectionOptions.afterRenderCallbacks.push(() => {
parentInstance.nodeHandler.addRoot();
});
parentInstance.nodeHandler.addRoot();
runDeferredRenderCallbacks(projectionOptions);
runAfterRenderCallbacks(projectionOptions);
projectionDNode = updatedDNode as InternalDNode[];
Expand All @@ -891,9 +881,7 @@ export const dom = {
finalProjectorOptions.rootNode = rootNode;
const decoratedNode = filterAndDecorateChildren(dNode, instance);
addChildren(rootNode, decoratedNode, finalProjectorOptions, instance, undefined);
finalProjectorOptions.afterRenderCallbacks.push(() => {
instance.nodeHandler.addRoot();
});
instance.nodeHandler.addRoot();
runDeferredRenderCallbacks(finalProjectorOptions);
runAfterRenderCallbacks(finalProjectorOptions);
return createProjection(decoratedNode, instance, finalProjectorOptions);
Expand All @@ -903,9 +891,7 @@ export const dom = {
finalProjectorOptions.rootNode = parentNode;
const decoratedNode = filterAndDecorateChildren(dNode, instance);
addChildren(parentNode, decoratedNode, finalProjectorOptions, instance, undefined);
finalProjectorOptions.afterRenderCallbacks.push(() => {
instance.nodeHandler.addRoot();
});
instance.nodeHandler.addRoot();
runDeferredRenderCallbacks(finalProjectorOptions);
runAfterRenderCallbacks(finalProjectorOptions);
return createProjection(decoratedNode, instance, finalProjectorOptions);
Expand All @@ -921,9 +907,7 @@ export const dom = {
const decoratedNode = filterAndDecorateChildren(dNode, instance)[0] as InternalHNode;

createDom(decoratedNode, finalProjectorOptions.rootNode, undefined, finalProjectorOptions, instance);
finalProjectorOptions.afterRenderCallbacks.push(() => {
instance.nodeHandler.addRoot();
});
instance.nodeHandler.addRoot();
runDeferredRenderCallbacks(finalProjectorOptions);
runAfterRenderCallbacks(finalProjectorOptions);
return createProjection(decoratedNode, instance, finalProjectorOptions);
Expand All @@ -936,9 +920,7 @@ export const dom = {
const decoratedNode = filterAndDecorateChildren(dNode, instance)[0] as InternalHNode;
finalProjectorOptions.rootNode = element.parentNode! as Element;
createDom(decoratedNode, element.parentNode!, element, finalProjectorOptions, instance);
finalProjectorOptions.afterRenderCallbacks.push(() => {
instance.nodeHandler.addRoot();
});
instance.nodeHandler.addRoot();
runDeferredRenderCallbacks(finalProjectorOptions);
runAfterRenderCallbacks(finalProjectorOptions);
element.parentNode!.removeChild(element);
Expand Down
123 changes: 99 additions & 24 deletions tests/unit/vdom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ const projectorStub: any = {
add: stub(),
addRoot: stub()
},
onElementCreated() {},
onElementUpdated() {}
onElementCreated: stub(),
onElementUpdated: stub()
};

class MainBar extends WidgetBase<any> {
Expand Down Expand Up @@ -723,6 +723,58 @@ describe('vdom', () => {
}, Error, 'Unable to replace a node with an array of nodes. (consider adding one extra level to the virtual DOM)');
});

it('removes existing widget and uses new widget when widget changes', () => {
let fooCreated = false;
let barCreatedCount = 0;
class Foo extends WidgetBase {

constructor() {
super();
fooCreated = true;
}

render() {
return v('div');
}
}

class Bar extends WidgetBase {
constructor() {
super();
barCreatedCount++;
}

render() {
return v('span');
}
}

class Baz extends WidgetBase {
private _foo = true;

set foo(value: boolean) {
this._foo = value;
this.invalidate();
}

render() {
return v('div', [
this._foo ? w(Foo, {}) : w(Bar, {}),
this._foo ? w(Bar, { key: '1' }) : w(Bar, { key: '2' })
]);
}
}

const widget = new Baz();
const projection = dom.create(widget.__render__() as HNode, widget);
resolvers.resolve();
assert.isTrue(fooCreated);
widget.foo = false;
projection.update(widget.__render__() as HNode);
resolvers.resolve();
assert.strictEqual(barCreatedCount, 3);
});

it('remove elements for embedded WNodes', () => {
class Foo extends WidgetBase {
render() {
Expand Down Expand Up @@ -2162,46 +2214,69 @@ describe('vdom', () => {

describe('node callbacks', () => {

it('element-created not emitted for new nodes without a key', () => {
dom.create(v('div'), projectorStub);
it('element not added to node handler for nodes without a key', () => {
const projection = dom.create(v('div'), projectorStub);
resolvers.resolve();
projection.update(v('div'));
resolvers.resolve();
assert.isTrue(projectorStub.nodeHandler.add.notCalled);
});

it('element-created emitted for new nodes with a key', () => {
it('element added on create to node handler for nodes with a key', () => {
const projection = dom.create(v('div', { key: '1' }), projectorStub);
resolvers.resolve();
assert.isTrue(projectorStub.nodeHandler.add.called);
assert.isTrue(projectorStub.nodeHandler.add.calledWith(projection.domNode.childNodes[0] as Element, '1' ));
projectorStub.nodeHandler.add.reset();
projection.update(v('div', { key: '1' }));
assert.isTrue(projectorStub.nodeHandler.add.called);
assert.isTrue(projectorStub.nodeHandler.add.calledWith(projection.domNode.childNodes[0] as Element, '1' ));
});

it('element-created emitted for new nodes with a key of 0', () => {
it('element added on update to node handler for nodes with a key of 0', () => {
const projection = dom.create(v('div', { key: 0 }), projectorStub);
resolvers.resolve();
assert.isTrue(projectorStub.nodeHandler.add.called);
assert.isTrue(projectorStub.nodeHandler.add.calledWith(projection.domNode.childNodes[0] as Element, '0' ));
projectorStub.nodeHandler.add.reset();
projection.update(v('div', { key: 0 }));
assert.isTrue(projectorStub.nodeHandler.add.called);
assert.isTrue(projectorStub.nodeHandler.add.calledWith(projection.domNode.childNodes[0] as Element, '0' ));
});

it('element-updated not emitted for updated nodes without a key', () => {
const projection = dom.create(v('div'), projectorStub);
it('on element created and updated callbacks are called for nodes with keys', () => {
const projection = dom.create(v('div', { key: 0 }), projectorStub);
resolvers.resolve();
projection.update(v('div'));
assert.isTrue(projectorStub.onElementCreated.called);
assert.isTrue(projectorStub.onElementCreated.calledWith(projection.domNode.childNodes[0] as Element, 0 ));
projection.update(v('div', { key: 0 }));
resolvers.resolve();
assert.isTrue(projectorStub.nodeHandler.add.notCalled);
assert.isTrue(projectorStub.onElementUpdated.called);
assert.isTrue(projectorStub.onElementUpdated.calledWith(projection.domNode.childNodes[0] as Element, 0 ));
});

it('element-updated emitted for updated nodes with a key', () => {
const projection = dom.create(v('div', { key: '1' }), projectorStub);
resolvers.resolve();
projection.update(v('div', { key: '1' }));
resolvers.resolve();
assert.isTrue(projectorStub.nodeHandler.add.calledWith(projection.domNode.childNodes[0] as Element, '1' ));
it('addRoot called on node handler for created widgets with a zero key', () => {
const widget = new WidgetBase();
widget.__setProperties__({ key: 0 });

const projection = dom.create(widget.__render__(), projectorStub);
assert.isTrue(projectorStub.nodeHandler.addRoot.called);
projectorStub.nodeHandler.addRoot.reset();
widget.invalidate();
projection.update(widget.__render__());
assert.isTrue(projectorStub.nodeHandler.addRoot.called);
projectorStub.nodeHandler.addRoot.reset();
});

it('element-updated emitted for updated nodes with a key of 0', () => {
const projection = dom.create(v('div', { key: 0 }), projectorStub);
resolvers.resolve();
projection.update(v('div', { key: 0 }));
resolvers.resolve();
assert.isTrue(projectorStub.nodeHandler.add.calledWith(projection.domNode.childNodes[0] as Element, '0' ));
it('addRoot called on node handler for updated widgets with key', () => {
const widget = new WidgetBase();
widget.__setProperties__({ key: '1' });

const projection = dom.create(widget.__render__(), projectorStub);
assert.isTrue(projectorStub.nodeHandler.addRoot.called);
projectorStub.nodeHandler.addRoot.reset();
widget.invalidate();
projection.update(widget.__render__());
assert.isTrue(projectorStub.nodeHandler.addRoot.called);
projectorStub.nodeHandler.addRoot.reset();
});

});
Expand Down

0 comments on commit 18ee570

Please sign in to comment.