From 838903ad9c1d448b0402f3deb833ede58f5c109c Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Thu, 7 Sep 2017 18:10:32 +0100 Subject: [PATCH 01/27] th progress --- src/NodeHandler.ts | 37 ++++ src/WidgetBase.ts | 143 +++++++++---- src/interfaces.d.ts | 13 +- src/meta/Base.ts | 27 +-- src/meta/Dimensions.ts | 16 +- src/mixins/Projector.ts | 26 ++- tests/unit/NodeHandler.ts | 61 ++++++ tests/unit/WidgetBase.ts | 4 +- tests/unit/all.ts | 1 + tests/unit/meta/Dimensions.ts | 243 ++++++++++++--------- tests/unit/meta/meta.ts | 387 +++++----------------------------- 11 files changed, 445 insertions(+), 513 deletions(-) create mode 100644 src/NodeHandler.ts create mode 100644 tests/unit/NodeHandler.ts diff --git a/src/NodeHandler.ts b/src/NodeHandler.ts new file mode 100644 index 00000000..2b912a8d --- /dev/null +++ b/src/NodeHandler.ts @@ -0,0 +1,37 @@ +import { Evented } from '@dojo/core/Evented'; +import { VNodeProperties } from '@dojo/interfaces/vdom'; +import Map from '@dojo/shim/Map'; + +export enum Type { + Projector = 'Projector', + Widget = 'Widget' +} + +export default class NodeHandler extends Evented { + + private _nodeMap = new Map(); + + public get(key: string): Element | undefined { + return this._nodeMap.get(key); + } + + public has(key: string): Boolean { + return this._nodeMap.has(key); + } + + public add(element: Element, properties: VNodeProperties) { + const key = String(properties.key); + this._nodeMap.set(key, element); + this.emit({ type: key }); + } + + public addRoot(element: Element, properties: VNodeProperties) { + this.add(element, properties); + this.emit({ type: Type.Widget }); + } + + public addProjector(element: Element, properties: VNodeProperties) { + this.add(element, properties); + this.emit({ type: Type.Projector }); + } +} diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index 41b24123..9862d667 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -24,6 +24,7 @@ import { WidgetMetaRequiredNodeCallback } from './interfaces'; import RegistryHandler from './RegistryHandler'; +import NodeHandler from './NodeHandler'; import { isWidgetBaseConstructor, WIDGET_BASE_TYPE, Registry } from './Registry'; /** @@ -177,9 +178,9 @@ export class WidgetBase

extends E private _metaMap = new WeakMap, WidgetMetaBase>(); - private _nodeMap = new Map(); + // private _nodeMap = new Map(); - private _requiredNodes = new Map(); + // private _requiredNodes = new Map(); private _boundRenderFunc: Render; @@ -187,6 +188,14 @@ export class WidgetBase

extends E private _defaultRegistry = new Registry(); + private _nodeHandler: NodeHandler; + + private _projectorMountEvent: any; + + private _currentRootNode = 0; + + private _numRootNodes = 0; + /** * @constructor */ @@ -200,14 +209,15 @@ export class WidgetBase

extends E this._diffPropertyFunctionMap = new Map(); this._bindFunctionPropertyMap = new WeakMap<(...args: any[]) => any, { boundFunc: (...args: any[]) => any, scope: any }>(); this._registries = new RegistryHandler(); + this._nodeHandler = new NodeHandler(); this._registries.add(this._defaultRegistry, true); this.own(this._registries); - this.own({ - destroy: () => { - this._nodeMap.clear(); - this._requiredNodes.clear(); - } - }); + // this.own({ + // destroy: () => { + // this._nodeMap.clear(); + // this._requiredNodes.clear(); + // } + // }); this._boundRenderFunc = this.render.bind(this); this._boundInvalidate = this.invalidate.bind(this); @@ -219,9 +229,10 @@ export class WidgetBase

extends E let cached = this._metaMap.get(MetaType); if (!cached) { cached = new MetaType({ - nodes: this._nodeMap, - requiredNodes: this._requiredNodes, - invalidate: this._boundInvalidate + // nodes: this._nodeMap, + // requiredNodes: this._requiredNodes, + invalidate: this._boundInvalidate, + nodeHandler: this._nodeHandler }); this._metaMap.set(MetaType, cached); this.own(cached); @@ -234,21 +245,21 @@ export class WidgetBase

extends E * A render decorator that verifies nodes required in * 'meta' calls in this render, */ - @beforeRender() - protected verifyRequiredNodes(renderFunc: () => DNode, properties: WidgetProperties, children: DNode[]): () => DNode { - return () => { - this._requiredNodes.forEach((element, key) => { - /* istanbul ignore else: only checking for errors */ - if (!this._nodeMap.has(key)) { - throw new Error(`Required node ${key} not found`); - } - }); - this._requiredNodes.clear(); - const dNodes = renderFunc(); - this._nodeMap.clear(); - return dNodes; - }; - } + // @beforeRender() + // protected verifyRequiredNodes(renderFunc: () => DNode, properties: WidgetProperties, children: DNode[]): () => DNode { + // return () => { + // this._requiredNodes.forEach((element, key) => { + // /* istanbul ignore else: only checking for errors */ + // if (!this._nodeMap.has(key)) { + // throw new Error(`Required node ${key} not found`); + // } + // }); + // this._requiredNodes.clear(); + // const dNodes = renderFunc(); + // this._nodeMap.clear(); + // return dNodes; + // }; + // } /** * vnode afterCreate callback that calls the onElementCreated lifecycle method. @@ -259,7 +270,18 @@ export class WidgetBase

extends E vnodeSelector: string, properties: VNodeProperties ): void { - this._setNode(element, properties); + // this._setNode(element, properties); + this._nodeHandler.add(element, properties); + this.onElementCreated(element, String(properties.key)); + } + + protected _afterRootCreateCallback( + element: Element, + projectionOptions: ProjectionOptions, + vnodeSelector: string, + properties: VNodeProperties + ): void { + this._addElementToNodeHandler(element, projectionOptions, properties); this.onElementCreated(element, String(properties.key)); } @@ -272,10 +294,39 @@ export class WidgetBase

extends E vnodeSelector: string, properties: VNodeProperties ): void { - this._setNode(element, properties); + // this._setNode(element, properties); + this._nodeHandler.add(element, properties); this.onElementUpdated(element, String(properties.key)); } + protected _afterRootUpdateCallback( + element: Element, + projectionOptions: ProjectionOptions, + vnodeSelector: string, + properties: VNodeProperties + ): void { + this._addElementToNodeHandler(element, projectionOptions, properties); + this.onElementUpdated(element, String(properties.key)); + } + + _addElementToNodeHandler(element: Element, projectionOptions: ProjectionOptions, properties: VNodeProperties) { + this._currentRootNode += 1; + + if (!this._projectorMountEvent) { + this._projectorMountEvent = projectionOptions.nodeEvent.on('mounted', + (element: Element, properties: VNodeProperties) => { + this._nodeHandler.addProjector(element, properties); + }); + } + + if (this._currentRootNode === this._numRootNodes) { + this._nodeHandler.addRoot(element, properties); + } + else { + this._nodeHandler.add(element, properties); + } + } + /** * Widget lifecycle method that is called whenever a dom node is created for a vnode. * Override this method to access the dom nodes that were inserted into the dom. @@ -298,16 +349,16 @@ export class WidgetBase

extends E // Do nothing by default. } - private _setNode(element: Element, properties: VNodeProperties): void { - const key = String(properties.key); - this._nodeMap.set(key, element); - const callbacks = this._requiredNodes.get(key); - if (callbacks) { - for (const [ meta, callback ] of callbacks) { - callback.call(meta, element); - } - } - } + // private _setNode(element: Element, properties: VNodeProperties): void { + // const key = String(properties.key); + // this._nodeMap.set(key, element); + // const callbacks = this._requiredNodes.get(key); + // if (callbacks) { + // for (const [ meta, callback ] of callbacks) { + // callback.call(meta, element); + // } + // } + // } public get properties(): Readonly

& Readonly { return this._properties; @@ -452,12 +503,26 @@ export class WidgetBase

extends E private _decorateNodes(node: DNode | DNode[]) { let nodes = Array.isArray(node) ? [ ...node ] : [ node ]; + + this._numRootNodes = nodes.length; + this._currentRootNode = 0; + const rootNodes: DNode[] = []; + + nodes.forEach(node => { + if (isHNode(node)) { + rootNodes.push(node); + node.properties = node.properties || {}; + node.properties.afterCreate = this._afterRootCreateCallback; + node.properties.afterUpdate = this._afterRootUpdateCallback; + } + }); + while (nodes.length) { const node = nodes.pop(); if (isHNode(node) || isWNode(node)) { node.properties = node.properties || {}; if (isHNode(node)) { - if (node.properties.key) { + if (rootNodes.indexOf(node) < 0 && node.properties.key) { node.properties.afterCreate = this._afterCreateCallback; node.properties.afterUpdate = this._afterUpdateCallback; } diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 66f514d7..7502a1c4 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -399,7 +399,7 @@ export interface WidgetBaseInterface< * Meta Base type */ export interface WidgetMetaBase extends Destroyable { - has(key: string): boolean; + // has(key: string): boolean; } /** @@ -413,17 +413,18 @@ export interface WidgetMetaConstructor { * Properties passed to meta Base constructors */ export interface WidgetMetaProperties { - nodes: Map; - requiredNodes: Map; + // nodes: Map; + // requiredNodes: Map; invalidate: () => void; + nodeHandler: any; } /** * Callback when asking widget meta for a required node */ -export interface WidgetMetaRequiredNodeCallback { - (node: HTMLElement): void; -} +// export interface WidgetMetaRequiredNodeCallback { +// (node: HTMLElement): void; +// } export interface Render { (): DNode | DNode[]; diff --git a/src/meta/Base.ts b/src/meta/Base.ts index 36db1daf..44ab4f03 100644 --- a/src/meta/Base.ts +++ b/src/meta/Base.ts @@ -1,46 +1,27 @@ import { Destroyable } from '@dojo/core/Destroyable'; import global from '@dojo/shim/global'; -import Map from '@dojo/shim/Map'; -import { WidgetMetaBase, WidgetMetaProperties, WidgetMetaRequiredNodeCallback } from '../interfaces'; +import { WidgetMetaBase, WidgetMetaProperties } from '../interfaces'; export class Base extends Destroyable implements WidgetMetaBase { private _invalidate: () => void; private _invalidating: number; - private _requiredNodes: Map; - protected nodes: Map; + protected nodeHandler: any; constructor(properties: WidgetMetaProperties) { super(); this._invalidate = properties.invalidate; - this._requiredNodes = properties.requiredNodes; - - this.nodes = properties.nodes; + this.nodeHandler = properties.nodeHandler; } public has(key: string): boolean { - return this.nodes.has(key); + return this.nodeHandler.has(key); } protected invalidate(): void { global.cancelAnimationFrame(this._invalidating); this._invalidating = global.requestAnimationFrame(this._invalidate); } - - protected requireNode(key: string, callback?: WidgetMetaRequiredNodeCallback): void { - const node = this.nodes.get(key); - if (node) { - callback && callback.call(this, node); - } - else { - const callbacks = this._requiredNodes.get(key) || []; - callback && callbacks.push([ this, callback ]); - this._requiredNodes.set(key, callbacks); - if (!callback) { - this.invalidate(); - } - } - } } export default Base; diff --git a/src/meta/Dimensions.ts b/src/meta/Dimensions.ts index ced67b49..601e813c 100644 --- a/src/meta/Dimensions.ts +++ b/src/meta/Dimensions.ts @@ -1,5 +1,7 @@ import { Base } from './Base'; import { deepAssign } from '@dojo/core/lang'; +import { Type } from '../NodeHandler'; +import { WidgetMetaProperties } from '../interfaces'; export interface TopLeft { left: number; @@ -23,7 +25,7 @@ export interface DimensionResults { scroll: TopLeft & Size; } -const defaultDimensions = { +export const defaultDimensions = { offset: { height: 0, left: 0, @@ -49,10 +51,16 @@ const defaultDimensions = { }; export class Dimensions extends Base { - public get(key: string): Readonly { - this.requireNode(key); + constructor(properties: WidgetMetaProperties) { + super(properties); + + this.nodeHandler.on(Type.Projector, () => { + this.invalidate(); + }); + } - const node = this.nodes.get(key); + public get(key: string): Readonly { + const node = this.nodeHandler.get(key); if (!node) { return deepAssign({}, defaultDimensions); } diff --git a/src/mixins/Projector.ts b/src/mixins/Projector.ts index b80a9cec..51fb77bd 100644 --- a/src/mixins/Projector.ts +++ b/src/mixins/Projector.ts @@ -2,8 +2,9 @@ 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 { VNode } from '@dojo/interfaces/vdom'; -import { dom, h, Projection, ProjectionOptions } from 'maquette'; +import { Evented } from '@dojo/core/Evented'; +import { VNode, VNodeProperties, ProjectionOptions } from '@dojo/interfaces/vdom'; +import { dom, h, Projection } from 'maquette'; import 'pepjs'; import cssTransitions from '../animations/cssTransitions'; import { Constructor, DNode } from './../interfaces'; @@ -164,7 +165,8 @@ export function ProjectorMixin>>(Base: T) this._projectionOptions = { transitions: cssTransitions, - eventHandlerInterceptor: eventHandlerInterceptor.bind(this) + eventHandlerInterceptor: eventHandlerInterceptor.bind(this), + nodeEvent: new Evented() }; this._boundDoRender = this._doRender.bind(this); @@ -286,6 +288,24 @@ export function ProjectorMixin>>(Base: T) return this._projection.domNode.outerHTML; } + _afterRootCreateCallback(element: Element, + projectionOptions: ProjectionOptions, + vnodeSelector: string, + properties: VNodeProperties) { + + super._afterRootCreateCallback(element, projectionOptions, vnodeSelector, properties); + projectionOptions.nodeEvent.emit({ type: 'mounted' }, element, properties); + } + + _afterRootUpdateCallback(element: Element, + projectionOptions: ProjectionOptions, + vnodeSelector: string, + properties: VNodeProperties) { + + super._afterRootUpdateCallback(element, projectionOptions, vnodeSelector, properties); + projectionOptions.nodeEvent.emit({ type: 'mounted' }, element, properties); + } + public __render__(): VNode { if (this._projectorChildren) { this.setChildren(this._projectorChildren); diff --git a/tests/unit/NodeHandler.ts b/tests/unit/NodeHandler.ts new file mode 100644 index 00000000..50ca0fd0 --- /dev/null +++ b/tests/unit/NodeHandler.ts @@ -0,0 +1,61 @@ +import * as registerSuite from 'intern!object'; +import * as assert from 'intern/chai!assert'; +import { stub, SinonStub } from 'sinon'; +import NodeHandler, { Type } from '../../src/NodeHandler'; + +const elementStub: SinonStub = stub(); +const widgetStub: SinonStub = stub(); +const projectorStub: SinonStub = stub(); +let nodeHandler: NodeHandler; +let element: Element; + +registerSuite({ + name: 'NodeHandler', + beforeEach() { + nodeHandler = new NodeHandler(); + element = document.createElement('div'); + }, + 'add populates nodehandler map'() { + nodeHandler.add(element, { key: 'foo' }); + assert.isTrue(nodeHandler.has('foo')); + }, + 'has returns undefined when element does not exist'() { + assert.isFalse(nodeHandler.has('foo')); + }, + 'get returns elements that have been added'() { + nodeHandler.add(element, { key: 'foo' }); + assert.equal(nodeHandler.get('foo'), element); + }, + 'events': { + beforeEach() { + elementStub.reset(); + widgetStub.reset(); + projectorStub.reset(); + + nodeHandler.on('foo', elementStub); + nodeHandler.on(Type.Widget, widgetStub); + nodeHandler.on(Type.Projector, projectorStub); + }, + 'add emits event when element added'() { + nodeHandler.add(element, { key: 'foo' }); + + assert.isTrue(elementStub.calledOnce); + assert.isTrue(widgetStub.notCalled); + assert.isTrue(projectorStub.notCalled); + }, + 'add root emits Widget and element event'() { + nodeHandler.addRoot(element, { key: 'foo' }); + + assert.isTrue(widgetStub.calledOnce); + assert.isTrue(elementStub.calledOnce); + assert.isTrue(projectorStub.notCalled); + }, + 'add projector emits Projector, Widget and element event'() { + nodeHandler.addProjector(element, { key: 'foo' }); + + assert.isTrue(widgetStub.notCalled); + assert.isTrue(elementStub.calledOnce); + assert.isTrue(projectorStub.calledOnce); + } + } +}); diff --git a/tests/unit/WidgetBase.ts b/tests/unit/WidgetBase.ts index 59c43b05..3240624f 100644 --- a/tests/unit/WidgetBase.ts +++ b/tests/unit/WidgetBase.ts @@ -566,7 +566,9 @@ registerSuite({ widget.__setProperties__({ foo: 'bar' }); const qux: any = widget.__render__(); assert.equal(qux.vnodeSelector, 'qux'); - assert.deepEqual(qux.properties, { bind: widget, bar: 'foo', foo: 'bar' }); + assert.deepEqual(qux.properties.bind, widget); + assert.equal(qux.properties.bar, 'foo'); + assert.equal(qux.properties.foo, 'bar'); assert.lengthOf(qux.children, 1); const bar = qux.children[0]; assert.equal(bar.vnodeSelector, 'bar'); diff --git a/tests/unit/all.ts b/tests/unit/all.ts index 134a8b9e..dec244a1 100644 --- a/tests/unit/all.ts +++ b/tests/unit/all.ts @@ -13,4 +13,5 @@ import './diff'; import './RegistryHandler'; import './Injector'; import './tsx'; +import './NodeHandler'; import './meta/all'; diff --git a/tests/unit/meta/Dimensions.ts b/tests/unit/meta/Dimensions.ts index f46611f2..0a2378fc 100644 --- a/tests/unit/meta/Dimensions.ts +++ b/tests/unit/meta/Dimensions.ts @@ -2,11 +2,12 @@ import global from '@dojo/shim/global'; import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; import { stub } from 'sinon'; -import { v } from '../../../src/d'; -import { ProjectorMixin } from '../../../src/main'; -import Dimensions from '../../../src/meta/Dimensions'; -import { WidgetBase } from '../../../src/WidgetBase'; -import { ThemeableMixin } from './../../../src/mixins/Themeable'; +// import { v } from '../../../src/d'; +// import { ProjectorMixin } from '../../../src/main'; +import Dimensions, { defaultDimensions } from '../../../src/meta/Dimensions'; +// import { WidgetBase } from '../../../src/WidgetBase'; +// import { ThemeableMixin } from './../../../src/mixins/Themeable'; +import NodeHandler, { Type } from '../../../src/NodeHandler'; let rAF: any; @@ -28,105 +29,151 @@ registerSuite({ rAF.restore(); }, - 'dimensions are correctly configured'(this: any) { - const dimensions: any[] = []; + 'Dimensions will call invalidate when nodehandler projector event fires'() { + const nodeHandler = new NodeHandler(); + const invalidate = stub(); - class TestWidget extends ProjectorMixin(ThemeableMixin(WidgetBase)) { - render() { - dimensions.push(this.meta(Dimensions).get('root')); - return v('div', { - innerHTML: 'hello world', - key: 'root' - }); - } - } - - const div = document.createElement('div'); - - document.body.appendChild(div); - - const widget = new TestWidget(); - widget.append(div); - - resolveRAF(); - - assert.strictEqual(dimensions.length, 2); - assert.deepEqual(dimensions[0], { - offset: { - height: 0, - left: 0, - top: 0, - width: 0 - }, - position: { - bottom: 0, - left: 0, - right: 0, - top: 0 - }, - scroll: { - height: 0, - left: 0, - top: 0, - width: 0 - }, - size: { - height: 0, - width: 0 - } + new Dimensions({ + invalidate, + nodeHandler }); - }, - - 'dimensions has returns false for keys that dont exist'(this: any) { - class TestWidget extends ProjectorMixin(ThemeableMixin(WidgetBase)) { - render() { - this.meta(Dimensions); - - return v('div', { - innerHTML: 'hello world', - key: 'root' - }); - } - - getDimensions() { - return this.meta(Dimensions).has('test'); - } - } - - const div = document.createElement('div'); - document.body.appendChild(div); - - const widget = new TestWidget(); - widget.append(div); + nodeHandler.emit({ type: Type.Projector }); resolveRAF(); - assert.isFalse(widget.getDimensions()); + assert.isTrue(invalidate.calledOnce); }, + 'Will return default dimensions if node not loaded'() { + const nodeHandler = new NodeHandler(); + // const element = document.createElement('div'); + // nodeHandler.add(element, { key: 'foo' }); + + const dimensions = new Dimensions({ + invalidate: () => {}, + nodeHandler + }); - 'dimensions has returns true for keys that exist'(this: any) { - class TestWidget extends ProjectorMixin(ThemeableMixin(WidgetBase)) { - render() { - this.meta(Dimensions); - - return v('div', { - innerHTML: 'hello world', - key: 'root' - }); - } - - getDimensions() { - return this.meta(Dimensions).has('root'); - } - } - - const div = document.createElement('div'); - - document.body.appendChild(div); - - const widget = new TestWidget(); - widget.append(div); - resolveRAF(); + assert.deepEqual(dimensions.get('foo'), defaultDimensions); + }, + 'Will return element dimensions if node is loaded'() { + const nodeHandler = new NodeHandler(); + const element = document.createElement('div'); + nodeHandler.add(element, { key: 'foo' }); + + const dimensions = new Dimensions({ + invalidate: () => {}, + nodeHandler + }); - assert.isTrue(widget.getDimensions()); + assert.deepEqual(dimensions.get('foo'), defaultDimensions); } + // , + // 'Dimensions will call invalidate when nodehandler projector event fires'() { + // const nodeHandler = new NodeHandler(); + // const element = document.createElement('div'); + // nodeHandler.add(element, { key: 'foo' }); + // const dimensions = new Dimensions({ + // invalidate: () => {}, + // nodeHandler + // }); + // } + // const dimensions: any[] = []; + + // class TestWidget extends ProjectorMixin(ThemeableMixin(WidgetBase)) { + // render() { + // dimensions.push(this.meta(Dimensions).get('root')); + // return v('div', { + // innerHTML: 'hello world', + // key: 'root' + // }); + // } + // } + + // const div = document.createElement('div'); + + // document.body.appendChild(div); + + // const widget = new TestWidget(); + // widget.append(div); + + // resolveRAF(); + + // assert.strictEqual(dimensions.length, 2); + // assert.deepEqual(dimensions[0], { + // offset: { + // height: 0, + // left: 0, + // top: 0, + // width: 0 + // }, + // position: { + // bottom: 0, + // left: 0, + // right: 0, + // top: 0 + // }, + // scroll: { + // height: 0, + // left: 0, + // top: 0, + // width: 0 + // }, + // size: { + // height: 0, + // width: 0 + // } + // }); + // }, + + // 'dimensions has returns false for keys that dont exist'(this: any) { + // class TestWidget extends ProjectorMixin(ThemeableMixin(WidgetBase)) { + // render() { + // this.meta(Dimensions); + + // return v('div', { + // innerHTML: 'hello world', + // key: 'root' + // }); + // } + + // getDimensions() { + // return this.meta(Dimensions).has('test'); + // } + // } + + // const div = document.createElement('div'); + + // document.body.appendChild(div); + + // const widget = new TestWidget(); + // widget.append(div); + // resolveRAF(); + // assert.isFalse(widget.getDimensions()); + // }, + + // 'dimensions has returns true for keys that exist'(this: any) { + // class TestWidget extends ProjectorMixin(ThemeableMixin(WidgetBase)) { + // render() { + // this.meta(Dimensions); + + // return v('div', { + // innerHTML: 'hello world', + // key: 'root' + // }); + // } + + // getDimensions() { + // return this.meta(Dimensions).has('root'); + // } + // } + + // const div = document.createElement('div'); + + // document.body.appendChild(div); + + // const widget = new TestWidget(); + // widget.append(div); + // resolveRAF(); + + // assert.isTrue(widget.getDimensions()); + // } }); diff --git a/tests/unit/meta/meta.ts b/tests/unit/meta/meta.ts index f5e4f988..dab5058e 100644 --- a/tests/unit/meta/meta.ts +++ b/tests/unit/meta/meta.ts @@ -1,371 +1,80 @@ import global from '@dojo/shim/global'; import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; -import { v } from '../../../src/d'; -import { ProjectorMixin } from '../../../src/main'; import { Base as MetaBase } from '../../../src/meta/Base'; -import { WidgetBase } from '../../../src/WidgetBase'; -import { stub } from 'sinon'; -import { ThemeableMixin } from './../../../src/mixins/Themeable'; +import { stub, SinonStub } from 'sinon'; +import NodeHandler from '../../../src/NodeHandler'; -class TestWidgetBase

extends ThemeableMixin(WidgetBase)

{} - -let rAF: any; +let rAFStub: SinonStub; +let cancelrAFStub: SinonStub; function resolveRAF() { - for (let i = 0; i < rAF.callCount; i++) { - rAF.getCall(i).args[0](); + for (let i = 0; i < rAFStub.callCount; i++) { + rAFStub.getCall(i).args[0](); } - rAF.reset(); + rAFStub.reset(); } registerSuite({ name: 'meta base', beforeEach() { - rAF = stub(global, 'requestAnimationFrame').returns(1); + rAFStub = stub(global, 'requestAnimationFrame').returns(1); + cancelrAFStub = stub(global, 'cancelAnimationFrame'); }, afterEach() { - rAF.restore(); + rAFStub.restore(); + cancelrAFStub.restore(); }, - 'meta returns a singleton'() { - class TestMeta extends MetaBase { - } - - class TestWidget extends ProjectorMixin(TestWidgetBase) { - render() { - return v('div', { - innerHTML: 'hello world', - key: 'root' - }); - } - - getMeta() { - return this.meta(TestMeta); - } - } - - const div = document.createElement('div'); - - const widget = new TestWidget(); - widget.append(div); - - assert.strictEqual(widget.getMeta(), widget.getMeta()); - }, - 'meta is provided a list of nodes with keys'() { - class TestMeta extends MetaBase { - } - - class TestWidget extends ProjectorMixin(TestWidgetBase) { - render() { - return v('div', { - innerHTML: 'hello world', - key: 'root' - }); - } - - getMeta() { - return this.meta(TestMeta); - } - } - - const div = document.createElement('div'); - - const widget = new TestWidget(); - widget.append(div); - const meta = widget.getMeta(); - - assert.isTrue(meta.has('root')); - }, - 'meta renders the node if it has to'() { - class TestMeta extends MetaBase { - get(key: string) { - this.requireNode(key); - return this.nodes.get(key); - } - } - - let renders = 0; - - class TestWidget extends ProjectorMixin(TestWidgetBase) { - nodes: any; - - render() { - renders++; - - this.meta(TestMeta).get('greeting'); - this.meta(TestMeta).get('name'); - - return v('div', { - innerHTML: 'hello', - key: 'greeting' - }, [ - v('div', { - innerHTML: 'world', - key: 'name' - }) - ]); - } - } - - const div = document.createElement('div'); - - const widget = new TestWidget(); - widget.append(div); - - resolveRAF(); - - assert.strictEqual(renders, 2, 'expected two renders'); + 'has checks nodehandler for nodes'() { + const nodeHandler = new NodeHandler(); + const element = document.createElement('div'); + nodeHandler.add(element, { key: 'foo' }); + const meta = new MetaBase({ + invalidate: () => {}, + nodeHandler + }); + + assert.isTrue(meta.has('foo')); + assert.isFalse(meta.has('bar')); }, - '.has does not re-render'() { - class TestMeta extends MetaBase { - } - - let renders = 0; + 'invalidate calls passed in invalidate function'() { + const nodeHandler = new NodeHandler(); + const invalidate = stub(); - class TestWidget extends ProjectorMixin(TestWidgetBase) { - nodes: any; - - render() { - renders++; - - this.meta(TestMeta).has('greeting'); - this.meta(TestMeta).has('name'); - - return v('div', { - innerHTML: 'hello', - key: 'greeting' - }, [ - v('div', { - innerHTML: 'world', - key: 'name' - }) - ]); + class MyMeta extends MetaBase { + callInvalidate() { + this.invalidate(); } } - const div = document.createElement('div'); - - const widget = new TestWidget(); - widget.append(div); + const meta = new MyMeta({ + invalidate, + nodeHandler + }); + meta.callInvalidate(); resolveRAF(); - - assert.strictEqual(renders, 1, 'expected one renders'); + assert.isTrue(invalidate.calledOnce); }, - 'multi-step render'() { - class TestMeta extends MetaBase { - get(key: string) { - this.requireNode(key); - return this.nodes.get(key); - } - } + 'old invalidate calls are cancelled'() { + const nodeHandler = new NodeHandler(); + const invalidate = stub(); - let renders = 0; - - class TestWidget extends ProjectorMixin(TestWidgetBase) { - nodes: any; - - render() { - renders++; - - const test = this.meta(TestMeta); - - return v('div', { - innerHTML: 'hello', - key: 'greeting' - }, [ - test.get('greeting') ? v('div', { - innerHTML: 'world', - key: 'name' - }, [ - test.get('name') ? v('div', { - innerHTML: '!', - key: 'exclamation' - }) : null - ]) : null - ]); + class MyMeta extends MetaBase { + callInvalidate() { + this.invalidate(); } } - const div = document.createElement('div'); - - const widget = new TestWidget(); - widget.append(div); + const meta = new MyMeta({ + invalidate, + nodeHandler + }); + meta.callInvalidate(); + meta.callInvalidate(); + meta.callInvalidate(); resolveRAF(); - - assert.strictEqual(renders, 3, 'expected three renders'); - }, - 'meta throws an error if a required node is not found'() { - class TestMeta extends MetaBase { - get(key: string) { - this.requireNode(key); - return this.nodes.get(key); - } - } - - let renders = 0; - - class TestWidget extends ProjectorMixin(TestWidgetBase) { - nodes: any; - - render() { - renders++; - - this.meta(TestMeta).get('test'); - - return v('div', { - innerHTML: 'hello world', - key: 'root' - }); - } - } - - const div = document.createElement('div'); - - const widget = new TestWidget(); - widget.append(div); - - assert.throws(() => { - resolveRAF(); - }, Error, 'Required node test not found'); - }, - 'requireNode accepts a callback'() { - let callbacks = 0; - let context: any; - let foundNode: HTMLElement; - - class TestMeta extends MetaBase { - get(key: string) { - this.requireNode(key, function (this: TestMeta, node) { - callbacks++; - context = this; - foundNode = node; - }); - } - } - - let renders = 0; - - class TestWidget extends ProjectorMixin(TestWidgetBase) { - nodes: any; - - getMeta() { - return this.meta(TestMeta); - } - - render() { - renders++; - - this.meta(TestMeta).get('root'); - - return v('div', { - innerHTML: 'hello world', - key: 'root' - }); - } - } - - const div = document.createElement('div'); - - const widget = new TestWidget(); - widget.append(div); - - resolveRAF(); - - assert.strictEqual(callbacks, 1, 'callback fired when node was missing'); - assert.strictEqual(context, widget.getMeta(), 'required node called in meta context'); - assert.strictEqual(foundNode! && foundNode!.tagName, 'DIV'); - assert.strictEqual(renders, 1, 'callback did not call invalidate and did not re-render'); - }, - 'asynchronous invalidation with dynamic nodes does not throw an error'() { - let callbacks = 0; - - class TestMeta extends MetaBase { - get(key: string) { - this.requireNode(key, (node) => { - callbacks++; - - this.invalidate(); - }); - } - } - - let renders = 0; - - class TestWidget extends ProjectorMixin(TestWidgetBase) { - nodes: any; - - render() { - renders++; - - if (renders === 1) { - this.meta(TestMeta).get('world'); - } - - return v('div', { - key: 'root', - innerHTML: 'hello ' - }, [ - renders === 1 ? v('div', { - key: 'world', - innerHTML: 'world' - }) : null - ]); - } - } - - const div = document.createElement('div'); - - const widget = new TestWidget(); - widget.append(div); - - assert.strictEqual(callbacks, 1, 'callback fired when node was missing'); - assert.strictEqual(renders, 1, 'only one render on until asynchronous invalidation completes'); - - resolveRAF(); - - assert.strictEqual(callbacks, 1, 'callback did not fire on second render'); - assert.strictEqual(renders, 2, 'callback re-rendered without required node error'); - }, - 'callback can invalidate'() { - let callbacks = 0; - - class TestMeta extends MetaBase { - get(key: string) { - const invalidate = !this.nodes.has(key); - this.requireNode(key, (node) => { - callbacks++; - invalidate && this.invalidate(); - }); - } - } - - let renders = 0; - - class TestWidget extends ProjectorMixin(TestWidgetBase) { - nodes: any; - - render() { - renders++; - - this.meta(TestMeta).get('root'); - - return v('div', { - innerHTML: 'hello world', - key: 'root' - }); - } - } - - const div = document.createElement('div'); - - const widget = new TestWidget(); - widget.append(div); - - resolveRAF(); - - assert.strictEqual(callbacks, 2, 'callback fired when node was missing and when existing'); - assert.strictEqual(renders, 2, 'callback called invalidate causing a re-render'); + assert.isTrue(cancelrAFStub.calledThrice); } }); From ed9bc3e12fa08d0f4034ee03480f84128b59e9ec Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 8 Sep 2017 14:34:46 +0100 Subject: [PATCH 02/27] dimensions tests --- src/meta/Dimensions.ts | 2 +- tests/unit/meta/Dimensions.ts | 164 ++++++++++------------------------ 2 files changed, 49 insertions(+), 117 deletions(-) diff --git a/src/meta/Dimensions.ts b/src/meta/Dimensions.ts index 601e813c..c7d4994a 100644 --- a/src/meta/Dimensions.ts +++ b/src/meta/Dimensions.ts @@ -25,7 +25,7 @@ export interface DimensionResults { scroll: TopLeft & Size; } -export const defaultDimensions = { +const defaultDimensions = { offset: { height: 0, left: 0, diff --git a/tests/unit/meta/Dimensions.ts b/tests/unit/meta/Dimensions.ts index 0a2378fc..fe355241 100644 --- a/tests/unit/meta/Dimensions.ts +++ b/tests/unit/meta/Dimensions.ts @@ -2,14 +2,39 @@ import global from '@dojo/shim/global'; import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; import { stub } from 'sinon'; +// import { assign } from '@dojo/core/lang'; // import { v } from '../../../src/d'; // import { ProjectorMixin } from '../../../src/main'; -import Dimensions, { defaultDimensions } from '../../../src/meta/Dimensions'; +import Dimensions from '../../../src/meta/Dimensions'; // import { WidgetBase } from '../../../src/WidgetBase'; // import { ThemeableMixin } from './../../../src/mixins/Themeable'; import NodeHandler, { Type } from '../../../src/NodeHandler'; let rAF: any; +const defaultDimensions = { + offset: { + height: 0, + left: 0, + top: 0, + width: 0 + }, + position: { + bottom: 0, + left: 0, + right: 0, + top: 0 + }, + scroll: { + height: 0, + left: 0, + top: 0, + width: 0 + }, + size: { + height: 0, + width: 0 + } +}; function resolveRAF() { for (let i = 0; i < rAF.callCount; i++) { @@ -44,8 +69,6 @@ registerSuite({ }, 'Will return default dimensions if node not loaded'() { const nodeHandler = new NodeHandler(); - // const element = document.createElement('div'); - // nodeHandler.add(element, { key: 'foo' }); const dimensions = new Dimensions({ invalidate: () => {}, @@ -56,124 +79,33 @@ registerSuite({ }, 'Will return element dimensions if node is loaded'() { const nodeHandler = new NodeHandler(); - const element = document.createElement('div'); - nodeHandler.add(element, { key: 'foo' }); + + const offset = { offsetHeight: 10, offsetLeft: 10, offsetTop: 10, offsetWidth: 10 }; + const scroll = { scrollHeight: 10, scrollLeft: 10, scrollTop: 10, scrollWidth: 10 }; + const position = { bottom: 10, left: 10, right: 10, top: 10 }; + const size = { width: 10, height: 10 }; + + const element = { + ...offset, + ...scroll, + getBoundingClientRect: stub().returns({ + ...position, + ...size + }) + }; + + nodeHandler.add(element as any, { key: 'foo' }); const dimensions = new Dimensions({ invalidate: () => {}, nodeHandler }); - assert.deepEqual(dimensions.get('foo'), defaultDimensions); + assert.deepEqual(dimensions.get('foo'), { + offset: { height: 10, left: 10, top: 10, width: 10 }, + scroll: { height: 10, left: 10, top: 10, width: 10 }, + position, + size + }); } - // , - // 'Dimensions will call invalidate when nodehandler projector event fires'() { - // const nodeHandler = new NodeHandler(); - // const element = document.createElement('div'); - // nodeHandler.add(element, { key: 'foo' }); - // const dimensions = new Dimensions({ - // invalidate: () => {}, - // nodeHandler - // }); - // } - // const dimensions: any[] = []; - - // class TestWidget extends ProjectorMixin(ThemeableMixin(WidgetBase)) { - // render() { - // dimensions.push(this.meta(Dimensions).get('root')); - // return v('div', { - // innerHTML: 'hello world', - // key: 'root' - // }); - // } - // } - - // const div = document.createElement('div'); - - // document.body.appendChild(div); - - // const widget = new TestWidget(); - // widget.append(div); - - // resolveRAF(); - - // assert.strictEqual(dimensions.length, 2); - // assert.deepEqual(dimensions[0], { - // offset: { - // height: 0, - // left: 0, - // top: 0, - // width: 0 - // }, - // position: { - // bottom: 0, - // left: 0, - // right: 0, - // top: 0 - // }, - // scroll: { - // height: 0, - // left: 0, - // top: 0, - // width: 0 - // }, - // size: { - // height: 0, - // width: 0 - // } - // }); - // }, - - // 'dimensions has returns false for keys that dont exist'(this: any) { - // class TestWidget extends ProjectorMixin(ThemeableMixin(WidgetBase)) { - // render() { - // this.meta(Dimensions); - - // return v('div', { - // innerHTML: 'hello world', - // key: 'root' - // }); - // } - - // getDimensions() { - // return this.meta(Dimensions).has('test'); - // } - // } - - // const div = document.createElement('div'); - - // document.body.appendChild(div); - - // const widget = new TestWidget(); - // widget.append(div); - // resolveRAF(); - // assert.isFalse(widget.getDimensions()); - // }, - - // 'dimensions has returns true for keys that exist'(this: any) { - // class TestWidget extends ProjectorMixin(ThemeableMixin(WidgetBase)) { - // render() { - // this.meta(Dimensions); - - // return v('div', { - // innerHTML: 'hello world', - // key: 'root' - // }); - // } - - // getDimensions() { - // return this.meta(Dimensions).has('root'); - // } - // } - - // const div = document.createElement('div'); - - // document.body.appendChild(div); - - // const widget = new TestWidget(); - // widget.append(div); - // resolveRAF(); - - // assert.isTrue(widget.getDimensions()); - // } }); From 6f75f5624ed23b2a2431da6540b9c4cc7b3cff52 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 8 Sep 2017 16:08:36 +0100 Subject: [PATCH 03/27] fixed tests --- src/NodeHandler.ts | 9 +++++- src/WidgetBase.ts | 53 ++++++---------------------------- tests/unit/NodeHandler.ts | 6 ++++ tests/unit/meta/Dimensions.ts | 5 ---- tests/unit/mixins/Projector.ts | 51 +++++--------------------------- 5 files changed, 31 insertions(+), 93 deletions(-) diff --git a/src/NodeHandler.ts b/src/NodeHandler.ts index 2b912a8d..cc95bf00 100644 --- a/src/NodeHandler.ts +++ b/src/NodeHandler.ts @@ -31,7 +31,14 @@ export default class NodeHandler extends Evented { } public addProjector(element: Element, properties: VNodeProperties) { - this.add(element, properties); + if (properties && properties.key) { + this.add(element, properties); + } + this.emit({ type: Type.Projector }); } + + public clear() { + this._nodeMap.clear(); + } } diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index 9862d667..f9b23d09 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -20,8 +20,7 @@ import { WidgetMetaConstructor, WidgetBaseConstructor, WidgetBaseInterface, - WidgetProperties, - WidgetMetaRequiredNodeCallback + WidgetProperties } from './interfaces'; import RegistryHandler from './RegistryHandler'; import NodeHandler from './NodeHandler'; @@ -178,10 +177,6 @@ export class WidgetBase

extends E private _metaMap = new WeakMap, WidgetMetaBase>(); - // private _nodeMap = new Map(); - - // private _requiredNodes = new Map(); - private _boundRenderFunc: Render; private _boundInvalidate: () => void; @@ -212,12 +207,6 @@ export class WidgetBase

extends E this._nodeHandler = new NodeHandler(); this._registries.add(this._defaultRegistry, true); this.own(this._registries); - // this.own({ - // destroy: () => { - // this._nodeMap.clear(); - // this._requiredNodes.clear(); - // } - // }); this._boundRenderFunc = this.render.bind(this); this._boundInvalidate = this.invalidate.bind(this); @@ -229,8 +218,6 @@ export class WidgetBase

extends E let cached = this._metaMap.get(MetaType); if (!cached) { cached = new MetaType({ - // nodes: this._nodeMap, - // requiredNodes: this._requiredNodes, invalidate: this._boundInvalidate, nodeHandler: this._nodeHandler }); @@ -242,24 +229,15 @@ export class WidgetBase

extends E } /** - * A render decorator that verifies nodes required in - * 'meta' calls in this render, + * A render decorator that clears the nodehandles map */ - // @beforeRender() - // protected verifyRequiredNodes(renderFunc: () => DNode, properties: WidgetProperties, children: DNode[]): () => DNode { - // return () => { - // this._requiredNodes.forEach((element, key) => { - // /* istanbul ignore else: only checking for errors */ - // if (!this._nodeMap.has(key)) { - // throw new Error(`Required node ${key} not found`); - // } - // }); - // this._requiredNodes.clear(); - // const dNodes = renderFunc(); - // this._nodeMap.clear(); - // return dNodes; - // }; - // } + @beforeRender() + protected clearNodeHandlerMap(renderFunc: () => DNode, properties: WidgetProperties, children: DNode[]): () => DNode { + return () => { + this._nodeHandler.clear(); + return renderFunc(); + }; + } /** * vnode afterCreate callback that calls the onElementCreated lifecycle method. @@ -270,7 +248,6 @@ export class WidgetBase

extends E vnodeSelector: string, properties: VNodeProperties ): void { - // this._setNode(element, properties); this._nodeHandler.add(element, properties); this.onElementCreated(element, String(properties.key)); } @@ -294,7 +271,6 @@ export class WidgetBase

extends E vnodeSelector: string, properties: VNodeProperties ): void { - // this._setNode(element, properties); this._nodeHandler.add(element, properties); this.onElementUpdated(element, String(properties.key)); } @@ -349,17 +325,6 @@ export class WidgetBase

extends E // Do nothing by default. } - // private _setNode(element: Element, properties: VNodeProperties): void { - // const key = String(properties.key); - // this._nodeMap.set(key, element); - // const callbacks = this._requiredNodes.get(key); - // if (callbacks) { - // for (const [ meta, callback ] of callbacks) { - // callback.call(meta, element); - // } - // } - // } - public get properties(): Readonly

& Readonly { return this._properties; } diff --git a/tests/unit/NodeHandler.ts b/tests/unit/NodeHandler.ts index 50ca0fd0..2a9c043d 100644 --- a/tests/unit/NodeHandler.ts +++ b/tests/unit/NodeHandler.ts @@ -26,6 +26,12 @@ registerSuite({ nodeHandler.add(element, { key: 'foo' }); assert.equal(nodeHandler.get('foo'), element); }, + 'clear removes nodes from map'() { + nodeHandler.add(element, { key: 'foo' }); + assert.isTrue(nodeHandler.has('foo')); + nodeHandler.clear(); + assert.isFalse(nodeHandler.has('foo')); + }, 'events': { beforeEach() { elementStub.reset(); diff --git a/tests/unit/meta/Dimensions.ts b/tests/unit/meta/Dimensions.ts index fe355241..2f951b07 100644 --- a/tests/unit/meta/Dimensions.ts +++ b/tests/unit/meta/Dimensions.ts @@ -2,12 +2,7 @@ import global from '@dojo/shim/global'; import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; import { stub } from 'sinon'; -// import { assign } from '@dojo/core/lang'; -// import { v } from '../../../src/d'; -// import { ProjectorMixin } from '../../../src/main'; import Dimensions from '../../../src/meta/Dimensions'; -// import { WidgetBase } from '../../../src/WidgetBase'; -// import { ThemeableMixin } from './../../../src/mixins/Themeable'; import NodeHandler, { Type } from '../../../src/NodeHandler'; let rAF: any; diff --git a/tests/unit/mixins/Projector.ts b/tests/unit/mixins/Projector.ts index e14e4cfc..2a676405 100644 --- a/tests/unit/mixins/Projector.ts +++ b/tests/unit/mixins/Projector.ts @@ -818,43 +818,39 @@ registerSuite({ }, Error, 'Projector already attached, cannot create sandbox'); }, 'can attach an event handler'() { - let domNode: any; let domEvent: any; const oninput = (evt: any) => { domEvent = evt; }; - const afterCreate = (node: Node) => { - domNode = node; - }; + const Projector = class extends BaseTestWidget { render() { - return v('div', { oninput, afterCreate }); + return v('div', { oninput, id: 'handler-test-root' }); } }; const projector = new Projector(); projector.append(); - dispatchEvent(domNode, 'input'); + const domNode = document.getElementById('handler-test-root'); + dispatchEvent(domNode as HTMLElement, 'input'); assert.instanceOf(domEvent, Event); }, 'can attach an event listener'() { - let domNode: any; let domEvent: any; const onpointermove = (evt: any) => { domEvent = evt; }; - const afterCreate = (node: Node) => { - domNode = node; - }; + const Projector = class extends BaseTestWidget { render() { - return v('div', { onpointermove, afterCreate }); + return v('div', { onpointermove, id: 'listener-test-root' }); } }; const projector = new Projector(); projector.append(); - dispatchEvent(domNode, 'pointermove'); + const domNode = document.getElementById('listener-test-root'); + dispatchEvent(domNode as HTMLElement, 'pointermove'); assert.instanceOf(domEvent, Event); }, async '-active gets appended to enter/exit animations by default'(this: any) { @@ -1002,36 +998,5 @@ registerSuite({ await waitFor(() => { return document.getElementById('test-element') === null; }, 'Element never got removed'); - }, - 'afterCreate can be overridden'() { - let afterCreateCalled = false; - - function afterCreate(this: any, element: any, projectorOptions: any, vNodeSelector: any, properties: any, children: any) { - afterCreateCalled = true; - - assert.isNotNull(element); - assert.isNotNull(projectorOptions); - assert.isNotNull(vNodeSelector); - assert.isNotNull(properties); - assert.isNotNull(children); - assert.strictEqual(this, projector); - } - - const root = document.createElement('div'); - document.body.appendChild(root); - - const projector = new (class extends BaseTestWidget { - root = root; - - render() { - return v('span', { - innerHTML: 'hello world', - afterCreate - }); - } - })(); - - projector.append(); - assert.isTrue(afterCreateCalled); } }); From f61028d3484d738fef3b5a42b4f54f1e91f221c0 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 8 Sep 2017 16:16:55 +0100 Subject: [PATCH 04/27] fixed interface comments --- src/interfaces.d.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 7502a1c4..18cbe11c 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -399,7 +399,7 @@ export interface WidgetBaseInterface< * Meta Base type */ export interface WidgetMetaBase extends Destroyable { - // has(key: string): boolean; + has(key: string): boolean; } /** @@ -413,19 +413,10 @@ export interface WidgetMetaConstructor { * Properties passed to meta Base constructors */ export interface WidgetMetaProperties { - // nodes: Map; - // requiredNodes: Map; invalidate: () => void; nodeHandler: any; } -/** - * Callback when asking widget meta for a required node - */ -// export interface WidgetMetaRequiredNodeCallback { -// (node: HTMLElement): void; -// } - export interface Render { (): DNode | DNode[]; } From 8aa52fe6f73c0ff276ae22f6f4597ff5bb7c7471 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 8 Sep 2017 16:51:28 +0100 Subject: [PATCH 05/27] fixed Matches --- src/meta/Matches.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meta/Matches.ts b/src/meta/Matches.ts index 71e587c4..15848b2d 100644 --- a/src/meta/Matches.ts +++ b/src/meta/Matches.ts @@ -7,6 +7,6 @@ export default class Matches extends Base { * @param event The event object */ public get(key: string, event: Event): boolean { - return this.nodes.get(key) === event.target; + return this.nodeHandler.get(key) === event.target; } } From 2bb5260d0d08daa4b4673f0ffe0c1ec0efd4c6bd Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 8 Sep 2017 17:29:37 +0100 Subject: [PATCH 06/27] comments --- src/NodeHandler.ts | 27 +++++++++++++++++++-------- src/WidgetBase.ts | 10 ++++++---- src/interfaces.d.ts | 11 ++++++++++- src/meta/Base.ts | 4 ++-- src/meta/Dimensions.ts | 2 +- src/mixins/Projector.ts | 10 ++++++---- tests/unit/NodeHandler.ts | 16 +++++++++++++++- tests/unit/meta/Dimensions.ts | 26 +------------------------- 8 files changed, 60 insertions(+), 46 deletions(-) diff --git a/src/NodeHandler.ts b/src/NodeHandler.ts index cc95bf00..2566107d 100644 --- a/src/NodeHandler.ts +++ b/src/NodeHandler.ts @@ -1,36 +1,45 @@ import { Evented } from '@dojo/core/Evented'; import { VNodeProperties } from '@dojo/interfaces/vdom'; import Map from '@dojo/shim/Map'; +import { NodeHandler as NodeHandlerInterface } from './interfaces'; +/** + * Enum to identify the type of event. + * Listening to 'Projector' will notify when projector is created or updated + * Listening to 'Widget' will notifiy when widget root is created or updated + */ export enum Type { Projector = 'Projector', Widget = 'Widget' } -export default class NodeHandler extends Evented { +export class NodeHandler extends Evented implements NodeHandlerInterface { - private _nodeMap = new Map(); + private _nodeMap = new Map(); - public get(key: string): Element | undefined { + public get(key: string): HTMLElement | undefined { return this._nodeMap.get(key); } - public has(key: string): Boolean { + public has(key: string): boolean { return this._nodeMap.has(key); } - public add(element: Element, properties: VNodeProperties) { + public add(element: HTMLElement, properties: VNodeProperties) { const key = String(properties.key); this._nodeMap.set(key, element); this.emit({ type: key }); } - public addRoot(element: Element, properties: VNodeProperties) { - this.add(element, properties); + public addRoot(element: HTMLElement, properties: VNodeProperties) { + if (properties && properties.key) { + this.add(element, properties); + } + this.emit({ type: Type.Widget }); } - public addProjector(element: Element, properties: VNodeProperties) { + public addProjector(element: HTMLElement, properties: VNodeProperties) { if (properties && properties.key) { this.add(element, properties); } @@ -42,3 +51,5 @@ export default class NodeHandler extends Evented { this._nodeMap.clear(); } } + +export default NodeHandler; diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index f9b23d09..c4613e57 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -3,6 +3,7 @@ import { ProjectionOptions, VNodeProperties } from '@dojo/interfaces/vdom'; import Map from '@dojo/shim/Map'; import '@dojo/shim/Promise'; // Imported for side-effects import WeakMap from '@dojo/shim/WeakMap'; +import { Handle } from '@dojo/interfaces/core'; import { isWNode, v, isHNode } from './d'; import { auto, ignore } from './diff'; import { @@ -185,7 +186,7 @@ export class WidgetBase

extends E private _nodeHandler: NodeHandler; - private _projectorMountEvent: any; + private _projectorAttachEvent: Handle; private _currentRootNode = 0; @@ -207,6 +208,7 @@ export class WidgetBase

extends E this._nodeHandler = new NodeHandler(); this._registries.add(this._defaultRegistry, true); this.own(this._registries); + this.own(this._nodeHandler); this._boundRenderFunc = this.render.bind(this); this._boundInvalidate = this.invalidate.bind(this); @@ -288,8 +290,8 @@ export class WidgetBase

extends E _addElementToNodeHandler(element: Element, projectionOptions: ProjectionOptions, properties: VNodeProperties) { this._currentRootNode += 1; - if (!this._projectorMountEvent) { - this._projectorMountEvent = projectionOptions.nodeEvent.on('mounted', + if (!this._projectorAttachEvent) { + this._projectorAttachEvent = projectionOptions.nodeEvent.on('attached', (element: Element, properties: VNodeProperties) => { this._nodeHandler.addProjector(element, properties); }); @@ -487,7 +489,7 @@ export class WidgetBase

extends E if (isHNode(node) || isWNode(node)) { node.properties = node.properties || {}; if (isHNode(node)) { - if (rootNodes.indexOf(node) < 0 && node.properties.key) { + if (rootNodes.indexOf(node) === -1 && node.properties.key) { node.properties.afterCreate = this._afterCreateCallback; node.properties.afterUpdate = this._afterUpdateCallback; } diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 18cbe11c..94d9b7ad 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -409,12 +409,21 @@ export interface WidgetMetaConstructor { new (properties: WidgetMetaProperties): T; } +export interface NodeHandler extends Evented { + get(key: string): HTMLElement | undefined; + has(key: string): boolean; + add(element: HTMLElement, properties: VNodeProperties): void; + addRoot(element: HTMLElement, properties: VNodeProperties): void; + addProjector(element: HTMLElement, properties: VNodeProperties): void; + clear(): void; +} + /** * Properties passed to meta Base constructors */ export interface WidgetMetaProperties { invalidate: () => void; - nodeHandler: any; + nodeHandler: NodeHandler; } export interface Render { diff --git a/src/meta/Base.ts b/src/meta/Base.ts index 44ab4f03..69fcaca1 100644 --- a/src/meta/Base.ts +++ b/src/meta/Base.ts @@ -1,11 +1,11 @@ import { Destroyable } from '@dojo/core/Destroyable'; import global from '@dojo/shim/global'; -import { WidgetMetaBase, WidgetMetaProperties } from '../interfaces'; +import { WidgetMetaBase, WidgetMetaProperties, NodeHandler } from '../interfaces'; export class Base extends Destroyable implements WidgetMetaBase { private _invalidate: () => void; private _invalidating: number; - protected nodeHandler: any; + protected nodeHandler: NodeHandler; constructor(properties: WidgetMetaProperties) { super(); diff --git a/src/meta/Dimensions.ts b/src/meta/Dimensions.ts index c7d4994a..601e813c 100644 --- a/src/meta/Dimensions.ts +++ b/src/meta/Dimensions.ts @@ -25,7 +25,7 @@ export interface DimensionResults { scroll: TopLeft & Size; } -const defaultDimensions = { +export const defaultDimensions = { offset: { height: 0, left: 0, diff --git a/src/mixins/Projector.ts b/src/mixins/Projector.ts index 51fb77bd..cd79d071 100644 --- a/src/mixins/Projector.ts +++ b/src/mixins/Projector.ts @@ -291,19 +291,21 @@ export function ProjectorMixin>>(Base: T) _afterRootCreateCallback(element: Element, projectionOptions: ProjectionOptions, vnodeSelector: string, - properties: VNodeProperties) { + properties: VNodeProperties + ) { super._afterRootCreateCallback(element, projectionOptions, vnodeSelector, properties); - projectionOptions.nodeEvent.emit({ type: 'mounted' }, element, properties); + projectionOptions.nodeEvent.emit({ type: 'attached' }, element, properties); } _afterRootUpdateCallback(element: Element, projectionOptions: ProjectionOptions, vnodeSelector: string, - properties: VNodeProperties) { + properties: VNodeProperties + ) { super._afterRootUpdateCallback(element, projectionOptions, vnodeSelector, properties); - projectionOptions.nodeEvent.emit({ type: 'mounted' }, element, properties); + projectionOptions.nodeEvent.emit({ type: 'attached' }, element, properties); } public __render__(): VNode { diff --git a/tests/unit/NodeHandler.ts b/tests/unit/NodeHandler.ts index 2a9c043d..90cca1a9 100644 --- a/tests/unit/NodeHandler.ts +++ b/tests/unit/NodeHandler.ts @@ -56,12 +56,26 @@ registerSuite({ assert.isTrue(elementStub.calledOnce); assert.isTrue(projectorStub.notCalled); }, - 'add projector emits Projector, Widget and element event'() { + 'add root without a key emits Widget event only'() { + nodeHandler.addRoot(element, {}); + + assert.isTrue(widgetStub.calledOnce); + assert.isTrue(elementStub.notCalled); + assert.isTrue(projectorStub.notCalled); + }, + 'add projector emits Projector and element event'() { nodeHandler.addProjector(element, { key: 'foo' }); assert.isTrue(widgetStub.notCalled); assert.isTrue(elementStub.calledOnce); assert.isTrue(projectorStub.calledOnce); + }, + 'add projector without a key emits Projector event only'() { + nodeHandler.addProjector(element, {}); + + assert.isTrue(widgetStub.notCalled); + assert.isTrue(elementStub.notCalled); + assert.isTrue(projectorStub.calledOnce); } } }); diff --git a/tests/unit/meta/Dimensions.ts b/tests/unit/meta/Dimensions.ts index 2f951b07..eb6cbd68 100644 --- a/tests/unit/meta/Dimensions.ts +++ b/tests/unit/meta/Dimensions.ts @@ -2,34 +2,10 @@ import global from '@dojo/shim/global'; import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; import { stub } from 'sinon'; -import Dimensions from '../../../src/meta/Dimensions'; +import Dimensions, { defaultDimensions } from '../../../src/meta/Dimensions'; import NodeHandler, { Type } from '../../../src/NodeHandler'; let rAF: any; -const defaultDimensions = { - offset: { - height: 0, - left: 0, - top: 0, - width: 0 - }, - position: { - bottom: 0, - left: 0, - right: 0, - top: 0 - }, - scroll: { - height: 0, - left: 0, - top: 0, - width: 0 - }, - size: { - height: 0, - width: 0 - } -}; function resolveRAF() { for (let i = 0; i < rAF.callCount; i++) { From 35ca787184f6877aaadb99fa07bfb0bc9146d38a Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 8 Sep 2017 17:31:41 +0100 Subject: [PATCH 07/27] remove beforeRender --- src/WidgetBase.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index c4613e57..8119b938 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -230,17 +230,6 @@ export class WidgetBase

extends E return cached as T; } - /** - * A render decorator that clears the nodehandles map - */ - @beforeRender() - protected clearNodeHandlerMap(renderFunc: () => DNode, properties: WidgetProperties, children: DNode[]): () => DNode { - return () => { - this._nodeHandler.clear(); - return renderFunc(); - }; - } - /** * vnode afterCreate callback that calls the onElementCreated lifecycle method. */ @@ -451,6 +440,7 @@ export class WidgetBase

extends E } public __render__(): VirtualDomNode | VirtualDomNode[] { + this._nodeHandler.clear(); this._renderState = WidgetRenderState.RENDER; if (this._dirty === true || this._cachedVNode === undefined) { this._dirty = false; From 66b4d05a096cd6ca2c4d57f46efaf52074204d70 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 8 Sep 2017 20:13:53 +0100 Subject: [PATCH 08/27] pr comments --- src/NodeHandler.ts | 2 +- src/WidgetBase.ts | 17 +++++++++-------- src/interfaces.d.ts | 4 ++-- src/meta/Dimensions.ts | 2 +- tests/unit/meta/Dimensions.ts | 26 +++++++++++++++++++++++++- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/NodeHandler.ts b/src/NodeHandler.ts index 2566107d..199eae0c 100644 --- a/src/NodeHandler.ts +++ b/src/NodeHandler.ts @@ -1,7 +1,7 @@ import { Evented } from '@dojo/core/Evented'; import { VNodeProperties } from '@dojo/interfaces/vdom'; import Map from '@dojo/shim/Map'; -import { NodeHandler as NodeHandlerInterface } from './interfaces'; +import { NodeHandlerInterface } from './interfaces'; /** * Enum to identify the type of event. diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index 8119b938..b3ffea9e 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -234,7 +234,7 @@ export class WidgetBase

extends E * vnode afterCreate callback that calls the onElementCreated lifecycle method. */ private _afterCreateCallback( - element: Element, + element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, properties: VNodeProperties @@ -244,7 +244,7 @@ export class WidgetBase

extends E } protected _afterRootCreateCallback( - element: Element, + element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, properties: VNodeProperties @@ -257,7 +257,7 @@ export class WidgetBase

extends E * vnode afterUpdate callback that calls the onElementUpdated lifecycle method. */ private _afterUpdateCallback( - element: Element, + element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, properties: VNodeProperties @@ -267,7 +267,7 @@ export class WidgetBase

extends E } protected _afterRootUpdateCallback( - element: Element, + element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, properties: VNodeProperties @@ -276,14 +276,15 @@ export class WidgetBase

extends E this.onElementUpdated(element, String(properties.key)); } - _addElementToNodeHandler(element: Element, projectionOptions: ProjectionOptions, properties: VNodeProperties) { + private _addElementToNodeHandler(element: HTMLElement, projectionOptions: ProjectionOptions, properties: VNodeProperties) { this._currentRootNode += 1; - if (!this._projectorAttachEvent) { + if (this._projectorAttachEvent === undefined) { this._projectorAttachEvent = projectionOptions.nodeEvent.on('attached', - (element: Element, properties: VNodeProperties) => { + (element: HTMLElement, properties: VNodeProperties) => { this._nodeHandler.addProjector(element, properties); }); + this.own(this._projectorAttachEvent); } if (this._currentRootNode === this._numRootNodes) { @@ -440,10 +441,10 @@ export class WidgetBase

extends E } public __render__(): VirtualDomNode | VirtualDomNode[] { - this._nodeHandler.clear(); this._renderState = WidgetRenderState.RENDER; if (this._dirty === true || this._cachedVNode === undefined) { this._dirty = false; + this._nodeHandler.clear(); const render = this._runBeforeRenders(); let dNode = render(); dNode = this.runAfterRenders(dNode); diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 94d9b7ad..303367b1 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -409,7 +409,7 @@ export interface WidgetMetaConstructor { new (properties: WidgetMetaProperties): T; } -export interface NodeHandler extends Evented { +export interface NodeHandlerInterface extends Evented { get(key: string): HTMLElement | undefined; has(key: string): boolean; add(element: HTMLElement, properties: VNodeProperties): void; @@ -423,7 +423,7 @@ export interface NodeHandler extends Evented { */ export interface WidgetMetaProperties { invalidate: () => void; - nodeHandler: NodeHandler; + nodeHandler: NodeHandlerInterface; } export interface Render { diff --git a/src/meta/Dimensions.ts b/src/meta/Dimensions.ts index 601e813c..c7d4994a 100644 --- a/src/meta/Dimensions.ts +++ b/src/meta/Dimensions.ts @@ -25,7 +25,7 @@ export interface DimensionResults { scroll: TopLeft & Size; } -export const defaultDimensions = { +const defaultDimensions = { offset: { height: 0, left: 0, diff --git a/tests/unit/meta/Dimensions.ts b/tests/unit/meta/Dimensions.ts index eb6cbd68..2f951b07 100644 --- a/tests/unit/meta/Dimensions.ts +++ b/tests/unit/meta/Dimensions.ts @@ -2,10 +2,34 @@ import global from '@dojo/shim/global'; import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; import { stub } from 'sinon'; -import Dimensions, { defaultDimensions } from '../../../src/meta/Dimensions'; +import Dimensions from '../../../src/meta/Dimensions'; import NodeHandler, { Type } from '../../../src/NodeHandler'; let rAF: any; +const defaultDimensions = { + offset: { + height: 0, + left: 0, + top: 0, + width: 0 + }, + position: { + bottom: 0, + left: 0, + right: 0, + top: 0 + }, + scroll: { + height: 0, + left: 0, + top: 0, + width: 0 + }, + size: { + height: 0, + width: 0 + } +}; function resolveRAF() { for (let i = 0; i < rAF.callCount; i++) { From 2d4e39f29de4cc8955f148bb67ff4e164bf2b6b2 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 8 Sep 2017 20:29:13 +0100 Subject: [PATCH 09/27] fix typings --- src/WidgetBase.ts | 8 ++++---- src/meta/Base.ts | 4 ++-- src/mixins/Projector.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index b3ffea9e..8490bed1 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -243,7 +243,7 @@ export class WidgetBase

extends E this.onElementCreated(element, String(properties.key)); } - protected _afterRootCreateCallback( + protected afterRootCreateCallback( element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, @@ -266,7 +266,7 @@ export class WidgetBase

extends E this.onElementUpdated(element, String(properties.key)); } - protected _afterRootUpdateCallback( + protected afterRootUpdateCallback( element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, @@ -470,8 +470,8 @@ export class WidgetBase

extends E if (isHNode(node)) { rootNodes.push(node); node.properties = node.properties || {}; - node.properties.afterCreate = this._afterRootCreateCallback; - node.properties.afterUpdate = this._afterRootUpdateCallback; + node.properties.afterCreate = this.afterRootCreateCallback; + node.properties.afterUpdate = this.afterRootUpdateCallback; } }); diff --git a/src/meta/Base.ts b/src/meta/Base.ts index 69fcaca1..645c8678 100644 --- a/src/meta/Base.ts +++ b/src/meta/Base.ts @@ -1,11 +1,11 @@ import { Destroyable } from '@dojo/core/Destroyable'; import global from '@dojo/shim/global'; -import { WidgetMetaBase, WidgetMetaProperties, NodeHandler } from '../interfaces'; +import { WidgetMetaBase, WidgetMetaProperties, NodeHandlerInterface } from '../interfaces'; export class Base extends Destroyable implements WidgetMetaBase { private _invalidate: () => void; private _invalidating: number; - protected nodeHandler: NodeHandler; + protected nodeHandler: NodeHandlerInterface; constructor(properties: WidgetMetaProperties) { super(); diff --git a/src/mixins/Projector.ts b/src/mixins/Projector.ts index cd79d071..abc32d32 100644 --- a/src/mixins/Projector.ts +++ b/src/mixins/Projector.ts @@ -288,23 +288,23 @@ export function ProjectorMixin>>(Base: T) return this._projection.domNode.outerHTML; } - _afterRootCreateCallback(element: Element, + protected afterRootCreateCallback(element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, properties: VNodeProperties ) { - super._afterRootCreateCallback(element, projectionOptions, vnodeSelector, properties); + super.afterRootCreateCallback(element, projectionOptions, vnodeSelector, properties); projectionOptions.nodeEvent.emit({ type: 'attached' }, element, properties); } - _afterRootUpdateCallback(element: Element, + protected afterRootUpdateCallback(element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, properties: VNodeProperties ) { - super._afterRootUpdateCallback(element, projectionOptions, vnodeSelector, properties); + super.afterRootUpdateCallback(element, projectionOptions, vnodeSelector, properties); projectionOptions.nodeEvent.emit({ type: 'attached' }, element, properties); } From 47b4f758192a9684edec197aa455d1b5279349ee Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 8 Sep 2017 20:38:57 +0100 Subject: [PATCH 10/27] add nodeEvent typing --- src/WidgetBase.ts | 3 ++- src/interfaces.d.ts | 9 ++++++++- src/mixins/Projector.ts | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index 8490bed1..eee4e15a 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -1,5 +1,6 @@ import { Evented } from '@dojo/core/Evented'; -import { ProjectionOptions, VNodeProperties } from '@dojo/interfaces/vdom'; +import { VNodeProperties } from '@dojo/interfaces/vdom'; +import { ProjectionOptions } from './interfaces'; import Map from '@dojo/shim/Map'; import '@dojo/shim/Promise'; // Imported for side-effects import WeakMap from '@dojo/shim/WeakMap'; diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 303367b1..89bfa541 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -1,8 +1,15 @@ import { Destroyable } from '@dojo/core/Destroyable'; import { Evented } from '@dojo/core/Evented'; -import { VNode, VNodeProperties, ProjectionOptions } from '@dojo/interfaces/vdom'; +import { VNode, VNodeProperties, ProjectionOptions as MaquetteProjectionOptions } from '@dojo/interfaces/vdom'; import Map from '@dojo/shim/Map'; +/** + * Extended Dojo 2 projection options + */ +export interface ProjectionOptions extends MaquetteProjectionOptions { + nodeEvent: Evented; +} + /** * Generic constructor type */ diff --git a/src/mixins/Projector.ts b/src/mixins/Projector.ts index abc32d32..a85bcbd6 100644 --- a/src/mixins/Projector.ts +++ b/src/mixins/Projector.ts @@ -3,7 +3,8 @@ import global from '@dojo/shim/global'; import { createHandle } from '@dojo/core/lang'; import { Handle } from '@dojo/interfaces/core'; import { Evented } from '@dojo/core/Evented'; -import { VNode, VNodeProperties, ProjectionOptions } from '@dojo/interfaces/vdom'; +import { VNode, VNodeProperties } from '@dojo/interfaces/vdom'; +import { ProjectionOptions } from '../interfaces'; import { dom, h, Projection } from 'maquette'; import 'pepjs'; import cssTransitions from '../animations/cssTransitions'; From 0162789637ad2e31f2828b55583c6ac91bed27f5 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 8 Sep 2017 20:43:32 +0100 Subject: [PATCH 11/27] rename Type enum --- src/NodeHandler.ts | 6 +++--- src/meta/Dimensions.ts | 4 ++-- tests/unit/NodeHandler.ts | 6 +++--- tests/unit/meta/Dimensions.ts | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/NodeHandler.ts b/src/NodeHandler.ts index 199eae0c..a6a52726 100644 --- a/src/NodeHandler.ts +++ b/src/NodeHandler.ts @@ -8,7 +8,7 @@ import { NodeHandlerInterface } from './interfaces'; * Listening to 'Projector' will notify when projector is created or updated * Listening to 'Widget' will notifiy when widget root is created or updated */ -export enum Type { +export enum MetaEventType { Projector = 'Projector', Widget = 'Widget' } @@ -36,7 +36,7 @@ export class NodeHandler extends Evented implements NodeHandlerInterface { this.add(element, properties); } - this.emit({ type: Type.Widget }); + this.emit({ type: MetaEventType.Widget }); } public addProjector(element: HTMLElement, properties: VNodeProperties) { @@ -44,7 +44,7 @@ export class NodeHandler extends Evented implements NodeHandlerInterface { this.add(element, properties); } - this.emit({ type: Type.Projector }); + this.emit({ type: MetaEventType.Projector }); } public clear() { diff --git a/src/meta/Dimensions.ts b/src/meta/Dimensions.ts index c7d4994a..2bbe1ecc 100644 --- a/src/meta/Dimensions.ts +++ b/src/meta/Dimensions.ts @@ -1,6 +1,6 @@ import { Base } from './Base'; import { deepAssign } from '@dojo/core/lang'; -import { Type } from '../NodeHandler'; +import { MetaEventType } from '../NodeHandler'; import { WidgetMetaProperties } from '../interfaces'; export interface TopLeft { @@ -54,7 +54,7 @@ export class Dimensions extends Base { constructor(properties: WidgetMetaProperties) { super(properties); - this.nodeHandler.on(Type.Projector, () => { + this.nodeHandler.on(MetaEventType.Projector, () => { this.invalidate(); }); } diff --git a/tests/unit/NodeHandler.ts b/tests/unit/NodeHandler.ts index 90cca1a9..ace0b5b1 100644 --- a/tests/unit/NodeHandler.ts +++ b/tests/unit/NodeHandler.ts @@ -1,7 +1,7 @@ import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; import { stub, SinonStub } from 'sinon'; -import NodeHandler, { Type } from '../../src/NodeHandler'; +import NodeHandler, { MetaEventType } from '../../src/NodeHandler'; const elementStub: SinonStub = stub(); const widgetStub: SinonStub = stub(); @@ -39,8 +39,8 @@ registerSuite({ projectorStub.reset(); nodeHandler.on('foo', elementStub); - nodeHandler.on(Type.Widget, widgetStub); - nodeHandler.on(Type.Projector, projectorStub); + nodeHandler.on(MetaEventType.Widget, widgetStub); + nodeHandler.on(MetaEventType.Projector, projectorStub); }, 'add emits event when element added'() { nodeHandler.add(element, { key: 'foo' }); diff --git a/tests/unit/meta/Dimensions.ts b/tests/unit/meta/Dimensions.ts index 2f951b07..3f8b3bcd 100644 --- a/tests/unit/meta/Dimensions.ts +++ b/tests/unit/meta/Dimensions.ts @@ -3,7 +3,7 @@ import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; import { stub } from 'sinon'; import Dimensions from '../../../src/meta/Dimensions'; -import NodeHandler, { Type } from '../../../src/NodeHandler'; +import NodeHandler, { MetaEventType } from '../../../src/NodeHandler'; let rAF: any; const defaultDimensions = { @@ -58,7 +58,7 @@ registerSuite({ nodeHandler }); - nodeHandler.emit({ type: Type.Projector }); + nodeHandler.emit({ type: MetaEventType.Projector }); resolveRAF(); assert.isTrue(invalidate.calledOnce); }, From 43ab419915030eef964ce6a6a4984d5e9cc56c1d Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Fri, 8 Sep 2017 20:51:25 +0100 Subject: [PATCH 12/27] rename Type enum --- src/NodeHandler.ts | 6 +++--- src/meta/Dimensions.ts | 4 ++-- tests/unit/NodeHandler.ts | 6 +++--- tests/unit/meta/Dimensions.ts | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/NodeHandler.ts b/src/NodeHandler.ts index a6a52726..7109e588 100644 --- a/src/NodeHandler.ts +++ b/src/NodeHandler.ts @@ -8,7 +8,7 @@ import { NodeHandlerInterface } from './interfaces'; * Listening to 'Projector' will notify when projector is created or updated * Listening to 'Widget' will notifiy when widget root is created or updated */ -export enum MetaEventType { +export enum NodeEventType { Projector = 'Projector', Widget = 'Widget' } @@ -36,7 +36,7 @@ export class NodeHandler extends Evented implements NodeHandlerInterface { this.add(element, properties); } - this.emit({ type: MetaEventType.Widget }); + this.emit({ type: NodeEventType.Widget }); } public addProjector(element: HTMLElement, properties: VNodeProperties) { @@ -44,7 +44,7 @@ export class NodeHandler extends Evented implements NodeHandlerInterface { this.add(element, properties); } - this.emit({ type: MetaEventType.Projector }); + this.emit({ type: NodeEventType.Projector }); } public clear() { diff --git a/src/meta/Dimensions.ts b/src/meta/Dimensions.ts index 2bbe1ecc..7e574ddf 100644 --- a/src/meta/Dimensions.ts +++ b/src/meta/Dimensions.ts @@ -1,6 +1,6 @@ import { Base } from './Base'; import { deepAssign } from '@dojo/core/lang'; -import { MetaEventType } from '../NodeHandler'; +import { NodeEventType } from '../NodeHandler'; import { WidgetMetaProperties } from '../interfaces'; export interface TopLeft { @@ -54,7 +54,7 @@ export class Dimensions extends Base { constructor(properties: WidgetMetaProperties) { super(properties); - this.nodeHandler.on(MetaEventType.Projector, () => { + this.nodeHandler.on(NodeEventType.Projector, () => { this.invalidate(); }); } diff --git a/tests/unit/NodeHandler.ts b/tests/unit/NodeHandler.ts index ace0b5b1..9c713359 100644 --- a/tests/unit/NodeHandler.ts +++ b/tests/unit/NodeHandler.ts @@ -1,7 +1,7 @@ import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; import { stub, SinonStub } from 'sinon'; -import NodeHandler, { MetaEventType } from '../../src/NodeHandler'; +import NodeHandler, { NodeEventType } from '../../src/NodeHandler'; const elementStub: SinonStub = stub(); const widgetStub: SinonStub = stub(); @@ -39,8 +39,8 @@ registerSuite({ projectorStub.reset(); nodeHandler.on('foo', elementStub); - nodeHandler.on(MetaEventType.Widget, widgetStub); - nodeHandler.on(MetaEventType.Projector, projectorStub); + nodeHandler.on(NodeEventType.Widget, widgetStub); + nodeHandler.on(NodeEventType.Projector, projectorStub); }, 'add emits event when element added'() { nodeHandler.add(element, { key: 'foo' }); diff --git a/tests/unit/meta/Dimensions.ts b/tests/unit/meta/Dimensions.ts index 3f8b3bcd..84096722 100644 --- a/tests/unit/meta/Dimensions.ts +++ b/tests/unit/meta/Dimensions.ts @@ -3,7 +3,7 @@ import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; import { stub } from 'sinon'; import Dimensions from '../../../src/meta/Dimensions'; -import NodeHandler, { MetaEventType } from '../../../src/NodeHandler'; +import NodeHandler, { NodeEventType } from '../../../src/NodeHandler'; let rAF: any; const defaultDimensions = { @@ -58,7 +58,7 @@ registerSuite({ nodeHandler }); - nodeHandler.emit({ type: MetaEventType.Projector }); + nodeHandler.emit({ type: NodeEventType.Projector }); resolveRAF(); assert.isTrue(invalidate.calledOnce); }, From ea370b82b6039278d7db7e7538a6924a8ee87f79 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 11 Sep 2017 14:17:21 +0100 Subject: [PATCH 13/27] pr comments, changed dimensions implementation --- src/WidgetBase.ts | 9 +++-- src/interfaces.d.ts | 9 +++++ src/meta/Dimensions.ts | 24 +++++++---- src/mixins/Projector.ts | 6 +-- tests/unit/NodeHandler.ts | 2 +- tests/unit/WidgetBase.ts | 2 +- tests/unit/meta/Dimensions.ts | 75 ++++++++++++++++++++++++++++++----- 7 files changed, 98 insertions(+), 29 deletions(-) diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index eee4e15a..5aa332ca 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -22,7 +22,8 @@ import { WidgetMetaConstructor, WidgetBaseConstructor, WidgetBaseInterface, - WidgetProperties + WidgetProperties, + ProjectorRenderedEvent } from './interfaces'; import RegistryHandler from './RegistryHandler'; import NodeHandler from './NodeHandler'; @@ -281,8 +282,8 @@ export class WidgetBase

extends E this._currentRootNode += 1; if (this._projectorAttachEvent === undefined) { - this._projectorAttachEvent = projectionOptions.nodeEvent.on('attached', - (element: HTMLElement, properties: VNodeProperties) => { + this._projectorAttachEvent = projectionOptions.nodeEvent.on('rendered', + ({ element, properties }: ProjectorRenderedEvent) => { this._nodeHandler.addProjector(element, properties); }); this.own(this._projectorAttachEvent); @@ -445,13 +446,13 @@ export class WidgetBase

extends E this._renderState = WidgetRenderState.RENDER; if (this._dirty === true || this._cachedVNode === undefined) { this._dirty = false; - this._nodeHandler.clear(); const render = this._runBeforeRenders(); let dNode = render(); dNode = this.runAfterRenders(dNode); this._decorateNodes(dNode); const widget = this._dNodeToVNode(dNode); this._manageDetachedChildren(); + this._nodeHandler.clear(); this._cachedVNode = widget; this._renderState = WidgetRenderState.IDLE; return widget; diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index 89bfa541..df61b582 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -1,5 +1,6 @@ import { Destroyable } from '@dojo/core/Destroyable'; import { Evented } from '@dojo/core/Evented'; +import { EventTargettedObject } from '@dojo/interfaces/core'; import { VNode, VNodeProperties, ProjectionOptions as MaquetteProjectionOptions } from '@dojo/interfaces/vdom'; import Map from '@dojo/shim/Map'; @@ -22,6 +23,14 @@ export interface TypedTargetEvent extends Event { target: T; } +/** + * Projector rendered event + */ +export interface ProjectorRenderedEvent extends EventTargettedObject { + element: HTMLElement; + properties: VNodeProperties; +} + /* These are the event handlers exposed by Maquette. */ diff --git a/src/meta/Dimensions.ts b/src/meta/Dimensions.ts index 7e574ddf..fa3e7973 100644 --- a/src/meta/Dimensions.ts +++ b/src/meta/Dimensions.ts @@ -1,7 +1,6 @@ import { Base } from './Base'; import { deepAssign } from '@dojo/core/lang'; -import { NodeEventType } from '../NodeHandler'; -import { WidgetMetaProperties } from '../interfaces'; +import Set from '@dojo/shim/Set'; export interface TopLeft { left: number; @@ -51,18 +50,27 @@ const defaultDimensions = { }; export class Dimensions extends Base { - constructor(properties: WidgetMetaProperties) { - super(properties); - this.nodeHandler.on(NodeEventType.Projector, () => { - this.invalidate(); - }); - } + private _requestedNodeKeys = new Set(); public get(key: string): Readonly { const node = this.nodeHandler.get(key); + if (!node) { + + if (this._requestedNodeKeys.has(key)) { + throw new Error(`Required node ${key} not found`); + } + + const handle = this.nodeHandler.on(key, () => { + handle.destroy(); + this._requestedNodeKeys.delete(key); + this.invalidate(); + }); + + this._requestedNodeKeys.add(key); return deepAssign({}, defaultDimensions); + } const boundingDimensions = node.getBoundingClientRect(); diff --git a/src/mixins/Projector.ts b/src/mixins/Projector.ts index a85bcbd6..aabce396 100644 --- a/src/mixins/Projector.ts +++ b/src/mixins/Projector.ts @@ -294,9 +294,8 @@ export function ProjectorMixin>>(Base: T) vnodeSelector: string, properties: VNodeProperties ) { - super.afterRootCreateCallback(element, projectionOptions, vnodeSelector, properties); - projectionOptions.nodeEvent.emit({ type: 'attached' }, element, properties); + projectionOptions.nodeEvent.emit({ type: 'rendered', element, properties }); } protected afterRootUpdateCallback(element: HTMLElement, @@ -304,9 +303,8 @@ export function ProjectorMixin>>(Base: T) vnodeSelector: string, properties: VNodeProperties ) { - super.afterRootUpdateCallback(element, projectionOptions, vnodeSelector, properties); - projectionOptions.nodeEvent.emit({ type: 'attached' }, element, properties); + projectionOptions.nodeEvent.emit({ type: 'rendered', element, properties }); } public __render__(): VNode { diff --git a/tests/unit/NodeHandler.ts b/tests/unit/NodeHandler.ts index 9c713359..e18f1353 100644 --- a/tests/unit/NodeHandler.ts +++ b/tests/unit/NodeHandler.ts @@ -7,7 +7,7 @@ const elementStub: SinonStub = stub(); const widgetStub: SinonStub = stub(); const projectorStub: SinonStub = stub(); let nodeHandler: NodeHandler; -let element: Element; +let element: HTMLElement; registerSuite({ name: 'NodeHandler', diff --git a/tests/unit/WidgetBase.ts b/tests/unit/WidgetBase.ts index 3240624f..982833ed 100644 --- a/tests/unit/WidgetBase.ts +++ b/tests/unit/WidgetBase.ts @@ -1585,7 +1585,7 @@ registerSuite({ const testWidget = new TestWidget(); - assert.equal(testWidget.getDecoratorCount('beforeRender'), 2, 'beforeRender = 1 (existing) + 1'); + assert.equal(testWidget.getDecoratorCount('beforeRender'), 1, 'beforeRender = 0 (existing) + 1'); assert.equal(testWidget.getDecoratorCount('afterRender'), 1, 'afterRender = 0 (existing) + 1'); } }); diff --git a/tests/unit/meta/Dimensions.ts b/tests/unit/meta/Dimensions.ts index 84096722..168f3d45 100644 --- a/tests/unit/meta/Dimensions.ts +++ b/tests/unit/meta/Dimensions.ts @@ -1,9 +1,9 @@ import global from '@dojo/shim/global'; import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; -import { stub } from 'sinon'; +import { stub, spy } from 'sinon'; import Dimensions from '../../../src/meta/Dimensions'; -import NodeHandler, { NodeEventType } from '../../../src/NodeHandler'; +import NodeHandler from '../../../src/NodeHandler'; let rAF: any; const defaultDimensions = { @@ -49,28 +49,81 @@ registerSuite({ rAF.restore(); }, - 'Dimensions will call invalidate when nodehandler projector event fires'() { + 'Will return default dimensions if node not loaded'() { const nodeHandler = new NodeHandler(); - const invalidate = stub(); - new Dimensions({ - invalidate, + const dimensions = new Dimensions({ + invalidate: () => {}, nodeHandler }); - nodeHandler.emit({ type: NodeEventType.Projector }); - resolveRAF(); - assert.isTrue(invalidate.calledOnce); + assert.deepEqual(dimensions.get('foo'), defaultDimensions); }, - 'Will return default dimensions if node not loaded'() { + 'Will create event listener for node if not yet loaded'() { const nodeHandler = new NodeHandler(); + const onSpy = spy(nodeHandler, 'on'); const dimensions = new Dimensions({ invalidate: () => {}, nodeHandler }); - assert.deepEqual(dimensions.get('foo'), defaultDimensions); + dimensions.get('foo'); + assert.isTrue(onSpy.calledOnce); + assert.isTrue(onSpy.firstCall.calledWith('foo')); + }, + 'Will call invalidate when awaited node is available'() { + const nodeHandler = new NodeHandler(); + const onSpy = spy(nodeHandler, 'on'); + const invalidateStub = stub(); + + const dimensions = new Dimensions({ + invalidate: invalidateStub, + nodeHandler + }); + + dimensions.get('foo'); + assert.isTrue(onSpy.calledOnce); + assert.isTrue(onSpy.firstCall.calledWith('foo')); + + const element = document.createElement('div'); + const getRectSpy = spy(element, 'getBoundingClientRect'); + + nodeHandler.add(element, { key: 'foo' }); + + resolveRAF(); + assert.isTrue(invalidateStub.calledOnce); + + onSpy.reset(); + dimensions.get('foo'); + + assert.isFalse(onSpy.called); + assert.isTrue(getRectSpy.calledOnce); + }, + 'Will throw error if node not available on second get'() { + const nodeHandler = new NodeHandler(); + const onSpy = spy(nodeHandler, 'on'); + const invalidateStub = stub(); + let errorThrown = false; + + const dimensions = new Dimensions({ + invalidate: invalidateStub, + nodeHandler + }); + + dimensions.get('foo'); + assert.isTrue(onSpy.calledOnce); + assert.isTrue(onSpy.firstCall.calledWith('foo')); + + try { + dimensions.get('foo'); + } + catch (e) { + errorThrown = true; + } + + assert.isTrue(errorThrown); + assert.isFalse(invalidateStub.called); }, 'Will return element dimensions if node is loaded'() { const nodeHandler = new NodeHandler(); From 1142321ea9c88aa0bf83ed39bf51684d35dcea87 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 11 Sep 2017 15:39:55 +0100 Subject: [PATCH 14/27] added getnode function --- src/meta/Base.ts | 23 +++++++++++++++++++++++ src/meta/Dimensions.ts | 18 +----------------- src/meta/Matches.ts | 2 +- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/meta/Base.ts b/src/meta/Base.ts index 645c8678..4b92d9c8 100644 --- a/src/meta/Base.ts +++ b/src/meta/Base.ts @@ -1,5 +1,6 @@ import { Destroyable } from '@dojo/core/Destroyable'; import global from '@dojo/shim/global'; +import Set from '@dojo/shim/Set'; import { WidgetMetaBase, WidgetMetaProperties, NodeHandlerInterface } from '../interfaces'; export class Base extends Destroyable implements WidgetMetaBase { @@ -7,6 +8,8 @@ export class Base extends Destroyable implements WidgetMetaBase { private _invalidating: number; protected nodeHandler: NodeHandlerInterface; + private _requestedNodeKeys = new Set(); + constructor(properties: WidgetMetaProperties) { super(); @@ -18,6 +21,26 @@ export class Base extends Destroyable implements WidgetMetaBase { return this.nodeHandler.has(key); } + protected getNode(key: string): HTMLElement | undefined { + const node = this.nodeHandler.get(key); + + if (!node) { + if (this._requestedNodeKeys.has(key)) { + throw new Error(`Required node ${key} not found`); + } + + const handle = this.nodeHandler.on(key, () => { + handle.destroy(); + this._requestedNodeKeys.delete(key); + this.invalidate(); + }); + + this._requestedNodeKeys.add(key); + } + + return node; + } + protected invalidate(): void { global.cancelAnimationFrame(this._invalidating); this._invalidating = global.requestAnimationFrame(this._invalidate); diff --git a/src/meta/Dimensions.ts b/src/meta/Dimensions.ts index fa3e7973..3fc63dc5 100644 --- a/src/meta/Dimensions.ts +++ b/src/meta/Dimensions.ts @@ -1,6 +1,5 @@ import { Base } from './Base'; import { deepAssign } from '@dojo/core/lang'; -import Set from '@dojo/shim/Set'; export interface TopLeft { left: number; @@ -51,26 +50,11 @@ const defaultDimensions = { export class Dimensions extends Base { - private _requestedNodeKeys = new Set(); - public get(key: string): Readonly { - const node = this.nodeHandler.get(key); + const node = this.getNode(key); if (!node) { - - if (this._requestedNodeKeys.has(key)) { - throw new Error(`Required node ${key} not found`); - } - - const handle = this.nodeHandler.on(key, () => { - handle.destroy(); - this._requestedNodeKeys.delete(key); - this.invalidate(); - }); - - this._requestedNodeKeys.add(key); return deepAssign({}, defaultDimensions); - } const boundingDimensions = node.getBoundingClientRect(); diff --git a/src/meta/Matches.ts b/src/meta/Matches.ts index 15848b2d..72b1bcb1 100644 --- a/src/meta/Matches.ts +++ b/src/meta/Matches.ts @@ -7,6 +7,6 @@ export default class Matches extends Base { * @param event The event object */ public get(key: string, event: Event): boolean { - return this.nodeHandler.get(key) === event.target; + return this.getNode(key) === event.target; } } From 076085dd574a786440a56038eaef1a20e01f0f3b Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 11 Sep 2017 15:49:29 +0100 Subject: [PATCH 15/27] updated tests --- tests/unit/meta/meta.ts | 105 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/tests/unit/meta/meta.ts b/tests/unit/meta/meta.ts index dab5058e..9baee0e6 100644 --- a/tests/unit/meta/meta.ts +++ b/tests/unit/meta/meta.ts @@ -2,7 +2,7 @@ import global from '@dojo/shim/global'; import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; import { Base as MetaBase } from '../../../src/meta/Base'; -import { stub, SinonStub } from 'sinon'; +import { stub, SinonStub, spy } from 'sinon'; import NodeHandler from '../../../src/NodeHandler'; let rAFStub: SinonStub; @@ -37,6 +37,109 @@ registerSuite({ assert.isTrue(meta.has('foo')); assert.isFalse(meta.has('bar')); }, + 'get node returns element from nodehandler'() { + const nodeHandler = new NodeHandler(); + const invalidate = stub(); + const element = document.createElement('div'); + nodeHandler.add(element, { key: 'foo' }); + + class MyMeta extends MetaBase { + callGetNode(key: string) { + return this.getNode(key); + } + } + + const meta = new MyMeta({ + invalidate, + nodeHandler + }); + + const node = meta.callGetNode('foo'); + assert.equal(node, element); + }, + 'Will create event listener for node if not yet loaded'() { + const nodeHandler = new NodeHandler(); + const invalidate = stub(); + const onSpy = spy(nodeHandler, 'on'); + + class MyMeta extends MetaBase { + callGetNode(key: string) { + return this.getNode(key); + } + } + + const meta = new MyMeta({ + invalidate, + nodeHandler + }); + + meta.callGetNode('foo'); + assert.isTrue(onSpy.calledOnce); + assert.isTrue(onSpy.firstCall.calledWith('foo')); + }, + 'Will call invalidate when awaited node is available'() { + const nodeHandler = new NodeHandler(); + const onSpy = spy(nodeHandler, 'on'); + const invalidate = stub(); + + class MyMeta extends MetaBase { + callGetNode(key: string) { + return this.getNode(key); + } + } + + const meta = new MyMeta({ + invalidate, + nodeHandler + }); + + meta.callGetNode('foo'); + assert.isTrue(onSpy.calledOnce); + assert.isTrue(onSpy.firstCall.calledWith('foo')); + + const element = document.createElement('div'); + + nodeHandler.add(element, { key: 'foo' }); + + resolveRAF(); + assert.isTrue(invalidate.calledOnce); + + onSpy.reset(); + meta.callGetNode('foo'); + + assert.isFalse(onSpy.called); + }, + 'Will throw error if node not available on second get'() { + const nodeHandler = new NodeHandler(); + const onSpy = spy(nodeHandler, 'on'); + const invalidate = stub(); + let errorThrown = false; + + class MyMeta extends MetaBase { + callGetNode(key: string) { + return this.getNode(key); + } + } + + const meta = new MyMeta({ + invalidate, + nodeHandler + }); + + meta.callGetNode('foo'); + assert.isTrue(onSpy.calledOnce); + assert.isTrue(onSpy.firstCall.calledWith('foo')); + + try { + meta.callGetNode('foo'); + } + catch (e) { + errorThrown = true; + } + + assert.isTrue(errorThrown); + assert.isFalse(invalidate.called); + }, 'invalidate calls passed in invalidate function'() { const nodeHandler = new NodeHandler(); const invalidate = stub(); From 4d31f7d4bc20f4ffbd947ea3d7f5779f88fbdee2 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 11 Sep 2017 17:20:40 +0100 Subject: [PATCH 16/27] adding integration test --- tests/unit/meta/meta.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/unit/meta/meta.ts b/tests/unit/meta/meta.ts index 9baee0e6..1732e3a6 100644 --- a/tests/unit/meta/meta.ts +++ b/tests/unit/meta/meta.ts @@ -4,6 +4,10 @@ import * as assert from 'intern/chai!assert'; import { Base as MetaBase } from '../../../src/meta/Base'; import { stub, SinonStub, spy } from 'sinon'; import NodeHandler from '../../../src/NodeHandler'; +import { v } from '../../../src/d'; +import { ProjectorMixin } from '../../../src/main'; +import { WidgetBase } from '../../../src/WidgetBase'; +import { ThemeableMixin } from './../../../src/mixins/Themeable'; let rAFStub: SinonStub; let cancelrAFStub: SinonStub; @@ -179,5 +183,35 @@ registerSuite({ meta.callInvalidate(); resolveRAF(); assert.isTrue(cancelrAFStub.calledThrice); + }, + 'integration'() { + // class TestWidgetBase

extends ThemeableMixin(WidgetBase)

{} + + class MyMeta extends MetaBase { + callGetNode(key: string) { + return this.getNode(key); + } + } + + class TestWidget extends ProjectorMixin(WidgetBase) { + render() { + return v('div', { key: 'foo' }, [ + v('div', { 'key': 'bar' }, [ 'hello world' ]) + ]); + } + + getMeta() { + return this.meta(MyMeta); + } + } + + const div = document.createElement('div'); + + const widget = new TestWidget(); + widget.append(div); + const meta = widget.getMeta(); + + assert.isTrue(meta.has('foo')); + assert.isTrue(meta.has('bar')); } }); From 5decea220dff003e4aa5d570cfb08aa4143b4e9e Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 11 Sep 2017 17:37:22 +0100 Subject: [PATCH 17/27] integration tests --- src/NodeHandler.ts | 4 ---- tests/unit/all.ts | 30 +++++++++++++++--------------- tests/unit/meta/meta.ts | 32 +++++++++++++++++++++++++------- 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/NodeHandler.ts b/src/NodeHandler.ts index 7109e588..ec0c77f0 100644 --- a/src/NodeHandler.ts +++ b/src/NodeHandler.ts @@ -40,10 +40,6 @@ export class NodeHandler extends Evented implements NodeHandlerInterface { } public addProjector(element: HTMLElement, properties: VNodeProperties) { - if (properties && properties.key) { - this.add(element, properties); - } - this.emit({ type: NodeEventType.Projector }); } diff --git a/tests/unit/all.ts b/tests/unit/all.ts index dec244a1..f8a2d082 100644 --- a/tests/unit/all.ts +++ b/tests/unit/all.ts @@ -1,17 +1,17 @@ import 'dojo/has!host-node?../support/loadJsdom'; -import './Container'; -import './WidgetBase'; -import './Registry'; -import './lifecycle'; -import './customElements'; -import './d'; -import './decorators/all'; -import './mixins/all'; -import './util/all'; -import './main'; -import './diff'; -import './RegistryHandler'; -import './Injector'; -import './tsx'; -import './NodeHandler'; +// import './Container'; +// import './WidgetBase'; +// import './Registry'; +// import './lifecycle'; +// import './customElements'; +// import './d'; +// import './decorators/all'; +// import './mixins/all'; +// import './util/all'; +// import './main'; +// import './diff'; +// import './RegistryHandler'; +// import './Injector'; +// import './tsx'; +// import './NodeHandler'; import './meta/all'; diff --git a/tests/unit/meta/meta.ts b/tests/unit/meta/meta.ts index 1732e3a6..8e6530ca 100644 --- a/tests/unit/meta/meta.ts +++ b/tests/unit/meta/meta.ts @@ -3,11 +3,11 @@ import * as registerSuite from 'intern!object'; import * as assert from 'intern/chai!assert'; import { Base as MetaBase } from '../../../src/meta/Base'; import { stub, SinonStub, spy } from 'sinon'; -import NodeHandler from '../../../src/NodeHandler'; +import NodeHandler, { NodeEventType } from '../../../src/NodeHandler'; import { v } from '../../../src/d'; import { ProjectorMixin } from '../../../src/main'; import { WidgetBase } from '../../../src/WidgetBase'; -import { ThemeableMixin } from './../../../src/mixins/Themeable'; +// import { ThemeableMixin } from './../../../src/mixins/Themeable'; let rAFStub: SinonStub; let cancelrAFStub: SinonStub; @@ -185,12 +185,13 @@ registerSuite({ assert.isTrue(cancelrAFStub.calledThrice); }, 'integration'() { - // class TestWidgetBase

extends ThemeableMixin(WidgetBase)

{} - class MyMeta extends MetaBase { callGetNode(key: string) { return this.getNode(key); } + getNodeHandler() { + return this.nodeHandler; + } } class TestWidget extends ProjectorMixin(WidgetBase) { @@ -205,13 +206,30 @@ registerSuite({ } } - const div = document.createElement('div'); - const widget = new TestWidget(); - widget.append(div); const meta = widget.getMeta(); + const nodeHandler = meta.getNodeHandler(); + const onFoo = stub(); + const onBar = stub(); + const onWidget = stub(); + const onProjector = stub(); + + nodeHandler.on('foo', onFoo); + nodeHandler.on('bar', onBar); + nodeHandler.on(NodeEventType.Widget, onWidget); + nodeHandler.on(NodeEventType.Projector, onProjector); + + const div = document.createElement('div'); + widget.append(div); + assert.isTrue(meta.has('foo')); assert.isTrue(meta.has('bar')); + assert.isTrue(onFoo.calledOnce); + assert.isTrue(onBar.calledOnce); + assert.isTrue(onWidget.calledOnce); + assert.isTrue(onProjector.calledOnce); + assert.isTrue(onFoo.calledBefore(onWidget)); + assert.isTrue(onFoo.calledBefore(onProjector)); } }); From 0aa14cc0a04bd81147158849f35072bbbe110149 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 11 Sep 2017 17:39:30 +0100 Subject: [PATCH 18/27] uncomment tests --- tests/unit/all.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/unit/all.ts b/tests/unit/all.ts index f8a2d082..dec244a1 100644 --- a/tests/unit/all.ts +++ b/tests/unit/all.ts @@ -1,17 +1,17 @@ import 'dojo/has!host-node?../support/loadJsdom'; -// import './Container'; -// import './WidgetBase'; -// import './Registry'; -// import './lifecycle'; -// import './customElements'; -// import './d'; -// import './decorators/all'; -// import './mixins/all'; -// import './util/all'; -// import './main'; -// import './diff'; -// import './RegistryHandler'; -// import './Injector'; -// import './tsx'; -// import './NodeHandler'; +import './Container'; +import './WidgetBase'; +import './Registry'; +import './lifecycle'; +import './customElements'; +import './d'; +import './decorators/all'; +import './mixins/all'; +import './util/all'; +import './main'; +import './diff'; +import './RegistryHandler'; +import './Injector'; +import './tsx'; +import './NodeHandler'; import './meta/all'; From e88241b9e463b58a9f80a4252c401248c6420c8c Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 11 Sep 2017 18:31:07 +0100 Subject: [PATCH 19/27] fix test after changed functionality --- tests/unit/NodeHandler.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/unit/NodeHandler.ts b/tests/unit/NodeHandler.ts index e18f1353..0174d937 100644 --- a/tests/unit/NodeHandler.ts +++ b/tests/unit/NodeHandler.ts @@ -63,16 +63,9 @@ registerSuite({ assert.isTrue(elementStub.notCalled); assert.isTrue(projectorStub.notCalled); }, - 'add projector emits Projector and element event'() { + 'add projector emits Projector event'() { nodeHandler.addProjector(element, { key: 'foo' }); - assert.isTrue(widgetStub.notCalled); - assert.isTrue(elementStub.calledOnce); - assert.isTrue(projectorStub.calledOnce); - }, - 'add projector without a key emits Projector event only'() { - nodeHandler.addProjector(element, {}); - assert.isTrue(widgetStub.notCalled); assert.isTrue(elementStub.notCalled); assert.isTrue(projectorStub.calledOnce); From fda32dd9bf1b53963f12783bfb45b65f9071a048 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 11 Sep 2017 18:36:21 +0100 Subject: [PATCH 20/27] remove cancel raf --- src/meta/Base.ts | 5 +---- tests/unit/meta/meta.ts | 25 ------------------------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/src/meta/Base.ts b/src/meta/Base.ts index 4b92d9c8..3538b596 100644 --- a/src/meta/Base.ts +++ b/src/meta/Base.ts @@ -1,11 +1,9 @@ import { Destroyable } from '@dojo/core/Destroyable'; -import global from '@dojo/shim/global'; import Set from '@dojo/shim/Set'; import { WidgetMetaBase, WidgetMetaProperties, NodeHandlerInterface } from '../interfaces'; export class Base extends Destroyable implements WidgetMetaBase { private _invalidate: () => void; - private _invalidating: number; protected nodeHandler: NodeHandlerInterface; private _requestedNodeKeys = new Set(); @@ -42,8 +40,7 @@ export class Base extends Destroyable implements WidgetMetaBase { } protected invalidate(): void { - global.cancelAnimationFrame(this._invalidating); - this._invalidating = global.requestAnimationFrame(this._invalidate); + this._invalidate(); } } diff --git a/tests/unit/meta/meta.ts b/tests/unit/meta/meta.ts index 8e6530ca..c91ec606 100644 --- a/tests/unit/meta/meta.ts +++ b/tests/unit/meta/meta.ts @@ -7,10 +7,8 @@ import NodeHandler, { NodeEventType } from '../../../src/NodeHandler'; import { v } from '../../../src/d'; import { ProjectorMixin } from '../../../src/main'; import { WidgetBase } from '../../../src/WidgetBase'; -// import { ThemeableMixin } from './../../../src/mixins/Themeable'; let rAFStub: SinonStub; -let cancelrAFStub: SinonStub; function resolveRAF() { for (let i = 0; i < rAFStub.callCount; i++) { @@ -23,11 +21,9 @@ registerSuite({ name: 'meta base', beforeEach() { rAFStub = stub(global, 'requestAnimationFrame').returns(1); - cancelrAFStub = stub(global, 'cancelAnimationFrame'); }, afterEach() { rAFStub.restore(); - cancelrAFStub.restore(); }, 'has checks nodehandler for nodes'() { const nodeHandler = new NodeHandler(); @@ -163,27 +159,6 @@ registerSuite({ resolveRAF(); assert.isTrue(invalidate.calledOnce); }, - 'old invalidate calls are cancelled'() { - const nodeHandler = new NodeHandler(); - const invalidate = stub(); - - class MyMeta extends MetaBase { - callInvalidate() { - this.invalidate(); - } - } - - const meta = new MyMeta({ - invalidate, - nodeHandler - }); - - meta.callInvalidate(); - meta.callInvalidate(); - meta.callInvalidate(); - resolveRAF(); - assert.isTrue(cancelrAFStub.calledThrice); - }, 'integration'() { class MyMeta extends MetaBase { callGetNode(key: string) { From b9787b5d700d3ee83b30d090e392a17c57e13dbc Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 11 Sep 2017 19:57:12 +0100 Subject: [PATCH 21/27] pr nits --- src/NodeHandler.ts | 8 ++++---- src/meta/Base.ts | 7 ++----- src/mixins/Projector.ts | 4 ++-- tests/unit/meta/Dimensions.ts | 25 ------------------------- tests/unit/meta/meta.ts | 17 +++++------------ 5 files changed, 13 insertions(+), 48 deletions(-) diff --git a/src/NodeHandler.ts b/src/NodeHandler.ts index ec0c77f0..cf192769 100644 --- a/src/NodeHandler.ts +++ b/src/NodeHandler.ts @@ -25,13 +25,13 @@ export class NodeHandler extends Evented implements NodeHandlerInterface { return this._nodeMap.has(key); } - public add(element: HTMLElement, properties: VNodeProperties) { + public add(element: HTMLElement, properties: VNodeProperties): void { const key = String(properties.key); this._nodeMap.set(key, element); this.emit({ type: key }); } - public addRoot(element: HTMLElement, properties: VNodeProperties) { + public addRoot(element: HTMLElement, properties: VNodeProperties): void { if (properties && properties.key) { this.add(element, properties); } @@ -39,11 +39,11 @@ export class NodeHandler extends Evented implements NodeHandlerInterface { this.emit({ type: NodeEventType.Widget }); } - public addProjector(element: HTMLElement, properties: VNodeProperties) { + public addProjector(element: HTMLElement, properties: VNodeProperties): void { this.emit({ type: NodeEventType.Projector }); } - public clear() { + public clear(): void { this._nodeMap.clear(); } } diff --git a/src/meta/Base.ts b/src/meta/Base.ts index 3538b596..1b2fb98f 100644 --- a/src/meta/Base.ts +++ b/src/meta/Base.ts @@ -22,17 +22,14 @@ export class Base extends Destroyable implements WidgetMetaBase { protected getNode(key: string): HTMLElement | undefined { const node = this.nodeHandler.get(key); - if (!node) { - if (this._requestedNodeKeys.has(key)) { - throw new Error(`Required node ${key} not found`); - } - + if (!node && !this._requestedNodeKeys.has(key)) { const handle = this.nodeHandler.on(key, () => { handle.destroy(); this._requestedNodeKeys.delete(key); this.invalidate(); }); + this.own(handle); this._requestedNodeKeys.add(key); } diff --git a/src/mixins/Projector.ts b/src/mixins/Projector.ts index aabce396..ed93f0f6 100644 --- a/src/mixins/Projector.ts +++ b/src/mixins/Projector.ts @@ -293,7 +293,7 @@ export function ProjectorMixin>>(Base: T) projectionOptions: ProjectionOptions, vnodeSelector: string, properties: VNodeProperties - ) { + ): void { super.afterRootCreateCallback(element, projectionOptions, vnodeSelector, properties); projectionOptions.nodeEvent.emit({ type: 'rendered', element, properties }); } @@ -302,7 +302,7 @@ export function ProjectorMixin>>(Base: T) projectionOptions: ProjectionOptions, vnodeSelector: string, properties: VNodeProperties - ) { + ): void { super.afterRootUpdateCallback(element, projectionOptions, vnodeSelector, properties); projectionOptions.nodeEvent.emit({ type: 'rendered', element, properties }); } diff --git a/tests/unit/meta/Dimensions.ts b/tests/unit/meta/Dimensions.ts index 168f3d45..252bd281 100644 --- a/tests/unit/meta/Dimensions.ts +++ b/tests/unit/meta/Dimensions.ts @@ -100,31 +100,6 @@ registerSuite({ assert.isFalse(onSpy.called); assert.isTrue(getRectSpy.calledOnce); }, - 'Will throw error if node not available on second get'() { - const nodeHandler = new NodeHandler(); - const onSpy = spy(nodeHandler, 'on'); - const invalidateStub = stub(); - let errorThrown = false; - - const dimensions = new Dimensions({ - invalidate: invalidateStub, - nodeHandler - }); - - dimensions.get('foo'); - assert.isTrue(onSpy.calledOnce); - assert.isTrue(onSpy.firstCall.calledWith('foo')); - - try { - dimensions.get('foo'); - } - catch (e) { - errorThrown = true; - } - - assert.isTrue(errorThrown); - assert.isFalse(invalidateStub.called); - }, 'Will return element dimensions if node is loaded'() { const nodeHandler = new NodeHandler(); diff --git a/tests/unit/meta/meta.ts b/tests/unit/meta/meta.ts index c91ec606..3b3d18b4 100644 --- a/tests/unit/meta/meta.ts +++ b/tests/unit/meta/meta.ts @@ -109,11 +109,10 @@ registerSuite({ assert.isFalse(onSpy.called); }, - 'Will throw error if node not available on second get'() { + 'Will not add a second callback handle if one already exists'() { const nodeHandler = new NodeHandler(); const onSpy = spy(nodeHandler, 'on'); const invalidate = stub(); - let errorThrown = false; class MyMeta extends MetaBase { callGetNode(key: string) { @@ -129,16 +128,10 @@ registerSuite({ meta.callGetNode('foo'); assert.isTrue(onSpy.calledOnce); assert.isTrue(onSpy.firstCall.calledWith('foo')); - - try { - meta.callGetNode('foo'); - } - catch (e) { - errorThrown = true; - } - - assert.isTrue(errorThrown); - assert.isFalse(invalidate.called); + onSpy.reset(); + meta.callGetNode('foo'); + assert.isTrue(onSpy.notCalled); + assert.isTrue(invalidate.notCalled); }, 'invalidate calls passed in invalidate function'() { const nodeHandler = new NodeHandler(); From 3e50f14db551cd792c0482cde6fca26170fa24c0 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Mon, 11 Sep 2017 20:06:32 +0100 Subject: [PATCH 22/27] pr alignment --- src/mixins/Projector.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mixins/Projector.ts b/src/mixins/Projector.ts index ed93f0f6..98a7f688 100644 --- a/src/mixins/Projector.ts +++ b/src/mixins/Projector.ts @@ -289,7 +289,8 @@ export function ProjectorMixin>>(Base: T) return this._projection.domNode.outerHTML; } - protected afterRootCreateCallback(element: HTMLElement, + protected afterRootCreateCallback( + element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, properties: VNodeProperties @@ -298,7 +299,8 @@ export function ProjectorMixin>>(Base: T) projectionOptions.nodeEvent.emit({ type: 'rendered', element, properties }); } - protected afterRootUpdateCallback(element: HTMLElement, + protected afterRootUpdateCallback( + element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, properties: VNodeProperties From 6207249f0123e0e17e87fe47f21aef0c2edc63d0 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Tue, 12 Sep 2017 12:41:10 +0100 Subject: [PATCH 23/27] change to weakmaps --- src/WidgetBase.ts | 27 ++-- src/mixins/Projector.ts | 16 ++- tests/unit/meta/meta.ts | 279 +++++++++++++++++++++++----------------- 3 files changed, 196 insertions(+), 126 deletions(-) diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index 5aa332ca..85513276 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -102,6 +102,16 @@ export function diffProperty(propertyName: string, diffFunction: DiffPropertyFun }); } +/** + * WeakMap containing the current root node number + */ +export const currentRootNodeMap = new WeakMap(); + +/** + * Weakmap containing the number of root nodes + */ +export const numRootNodesMap = new WeakMap(); + /** * Generic decorator handler to take care of whether or not the decorator was called at the class level * or the method level. @@ -190,10 +200,6 @@ export class WidgetBase

extends E private _projectorAttachEvent: Handle; - private _currentRootNode = 0; - - private _numRootNodes = 0; - /** * @constructor */ @@ -279,7 +285,12 @@ export class WidgetBase

extends E } private _addElementToNodeHandler(element: HTMLElement, projectionOptions: ProjectionOptions, properties: VNodeProperties) { - this._currentRootNode += 1; + let currentRootNode = currentRootNodeMap.get(this) || 0; + const numRootNodes = numRootNodesMap.get(this); + + currentRootNode += 1; + currentRootNodeMap.set(this, currentRootNode); + const isLastRootNode = (currentRootNode === numRootNodes); if (this._projectorAttachEvent === undefined) { this._projectorAttachEvent = projectionOptions.nodeEvent.on('rendered', @@ -289,7 +300,7 @@ export class WidgetBase

extends E this.own(this._projectorAttachEvent); } - if (this._currentRootNode === this._numRootNodes) { + if (isLastRootNode) { this._nodeHandler.addRoot(element, properties); } else { @@ -464,8 +475,8 @@ export class WidgetBase

extends E private _decorateNodes(node: DNode | DNode[]) { let nodes = Array.isArray(node) ? [ ...node ] : [ node ]; - this._numRootNodes = nodes.length; - this._currentRootNode = 0; + numRootNodesMap.set(this, nodes.length); + currentRootNodeMap.set(this, 0); const rootNodes: DNode[] = []; nodes.forEach(node => { diff --git a/src/mixins/Projector.ts b/src/mixins/Projector.ts index 98a7f688..b1fb1ef9 100644 --- a/src/mixins/Projector.ts +++ b/src/mixins/Projector.ts @@ -9,7 +9,7 @@ import { dom, h, Projection } from 'maquette'; import 'pepjs'; import cssTransitions from '../animations/cssTransitions'; import { Constructor, DNode } from './../interfaces'; -import { WidgetBase } from './../WidgetBase'; +import { WidgetBase, currentRootNodeMap, numRootNodesMap } from './../WidgetBase'; import eventHandlerInterceptor from '../util/eventHandlerInterceptor'; /** @@ -296,7 +296,7 @@ export function ProjectorMixin>>(Base: T) properties: VNodeProperties ): void { super.afterRootCreateCallback(element, projectionOptions, vnodeSelector, properties); - projectionOptions.nodeEvent.emit({ type: 'rendered', element, properties }); + this._emitProjectorRenderedEvent(element, properties, projectionOptions.nodeEvent); } protected afterRootUpdateCallback( @@ -306,7 +306,17 @@ export function ProjectorMixin>>(Base: T) properties: VNodeProperties ): void { super.afterRootUpdateCallback(element, projectionOptions, vnodeSelector, properties); - projectionOptions.nodeEvent.emit({ type: 'rendered', element, properties }); + this._emitProjectorRenderedEvent(element, properties, projectionOptions.nodeEvent); + } + + private _emitProjectorRenderedEvent(element: HTMLElement, properties: VNodeProperties, nodeEvent: Evented) { + const currentRootNode = currentRootNodeMap.get(this) || 0; + const numRootNodes = numRootNodesMap.get(this); + const isLastRootNode = (currentRootNode === numRootNodes); + + if (isLastRootNode) { + nodeEvent.emit({ type: 'rendered', element, properties }); + } } public __render__(): VNode { diff --git a/tests/unit/meta/meta.ts b/tests/unit/meta/meta.ts index 3b3d18b4..72126e7e 100644 --- a/tests/unit/meta/meta.ts +++ b/tests/unit/meta/meta.ts @@ -25,134 +25,182 @@ registerSuite({ afterEach() { rAFStub.restore(); }, - 'has checks nodehandler for nodes'() { - const nodeHandler = new NodeHandler(); - const element = document.createElement('div'); - nodeHandler.add(element, { key: 'foo' }); - const meta = new MetaBase({ - invalidate: () => {}, - nodeHandler - }); - - assert.isTrue(meta.has('foo')); - assert.isFalse(meta.has('bar')); - }, - 'get node returns element from nodehandler'() { - const nodeHandler = new NodeHandler(); - const invalidate = stub(); - const element = document.createElement('div'); - nodeHandler.add(element, { key: 'foo' }); - + // 'has checks nodehandler for nodes'() { + // const nodeHandler = new NodeHandler(); + // const element = document.createElement('div'); + // nodeHandler.add(element, { key: 'foo' }); + // const meta = new MetaBase({ + // invalidate: () => {}, + // nodeHandler + // }); + + // assert.isTrue(meta.has('foo')); + // assert.isFalse(meta.has('bar')); + // }, + // 'get node returns element from nodehandler'() { + // const nodeHandler = new NodeHandler(); + // const invalidate = stub(); + // const element = document.createElement('div'); + // nodeHandler.add(element, { key: 'foo' }); + + // class MyMeta extends MetaBase { + // callGetNode(key: string) { + // return this.getNode(key); + // } + // } + + // const meta = new MyMeta({ + // invalidate, + // nodeHandler + // }); + + // const node = meta.callGetNode('foo'); + // assert.equal(node, element); + // }, + // 'Will create event listener for node if not yet loaded'() { + // const nodeHandler = new NodeHandler(); + // const invalidate = stub(); + // const onSpy = spy(nodeHandler, 'on'); + + // class MyMeta extends MetaBase { + // callGetNode(key: string) { + // return this.getNode(key); + // } + // } + + // const meta = new MyMeta({ + // invalidate, + // nodeHandler + // }); + + // meta.callGetNode('foo'); + // assert.isTrue(onSpy.calledOnce); + // assert.isTrue(onSpy.firstCall.calledWith('foo')); + // }, + // 'Will call invalidate when awaited node is available'() { + // const nodeHandler = new NodeHandler(); + // const onSpy = spy(nodeHandler, 'on'); + // const invalidate = stub(); + + // class MyMeta extends MetaBase { + // callGetNode(key: string) { + // return this.getNode(key); + // } + // } + + // const meta = new MyMeta({ + // invalidate, + // nodeHandler + // }); + + // meta.callGetNode('foo'); + // assert.isTrue(onSpy.calledOnce); + // assert.isTrue(onSpy.firstCall.calledWith('foo')); + + // const element = document.createElement('div'); + + // nodeHandler.add(element, { key: 'foo' }); + + // resolveRAF(); + // assert.isTrue(invalidate.calledOnce); + + // onSpy.reset(); + // meta.callGetNode('foo'); + + // assert.isFalse(onSpy.called); + // }, + // 'Will not add a second callback handle if one already exists'() { + // const nodeHandler = new NodeHandler(); + // const onSpy = spy(nodeHandler, 'on'); + // const invalidate = stub(); + + // class MyMeta extends MetaBase { + // callGetNode(key: string) { + // return this.getNode(key); + // } + // } + + // const meta = new MyMeta({ + // invalidate, + // nodeHandler + // }); + + // meta.callGetNode('foo'); + // assert.isTrue(onSpy.calledOnce); + // assert.isTrue(onSpy.firstCall.calledWith('foo')); + // onSpy.reset(); + // meta.callGetNode('foo'); + // assert.isTrue(onSpy.notCalled); + // assert.isTrue(invalidate.notCalled); + // }, + // 'invalidate calls passed in invalidate function'() { + // const nodeHandler = new NodeHandler(); + // const invalidate = stub(); + + // class MyMeta extends MetaBase { + // callInvalidate() { + // this.invalidate(); + // } + // } + + // const meta = new MyMeta({ + // invalidate, + // nodeHandler + // }); + + // meta.callInvalidate(); + // resolveRAF(); + // assert.isTrue(invalidate.calledOnce); + // }, + 'integration with single root node'() { class MyMeta extends MetaBase { callGetNode(key: string) { return this.getNode(key); } - } - - const meta = new MyMeta({ - invalidate, - nodeHandler - }); - - const node = meta.callGetNode('foo'); - assert.equal(node, element); - }, - 'Will create event listener for node if not yet loaded'() { - const nodeHandler = new NodeHandler(); - const invalidate = stub(); - const onSpy = spy(nodeHandler, 'on'); - - class MyMeta extends MetaBase { - callGetNode(key: string) { - return this.getNode(key); + getNodeHandler() { + return this.nodeHandler; } } - const meta = new MyMeta({ - invalidate, - nodeHandler - }); - - meta.callGetNode('foo'); - assert.isTrue(onSpy.calledOnce); - assert.isTrue(onSpy.firstCall.calledWith('foo')); - }, - 'Will call invalidate when awaited node is available'() { - const nodeHandler = new NodeHandler(); - const onSpy = spy(nodeHandler, 'on'); - const invalidate = stub(); - - class MyMeta extends MetaBase { - callGetNode(key: string) { - return this.getNode(key); + class TestWidget extends ProjectorMixin(WidgetBase) { + render() { + return v('div', { key: 'foo' }, [ + v('div', { key: 'bar' }, [ 'hello world' ]) + ]); } - } - - const meta = new MyMeta({ - invalidate, - nodeHandler - }); - - meta.callGetNode('foo'); - assert.isTrue(onSpy.calledOnce); - assert.isTrue(onSpy.firstCall.calledWith('foo')); - - const element = document.createElement('div'); - - nodeHandler.add(element, { key: 'foo' }); - - resolveRAF(); - assert.isTrue(invalidate.calledOnce); - - onSpy.reset(); - meta.callGetNode('foo'); - - assert.isFalse(onSpy.called); - }, - 'Will not add a second callback handle if one already exists'() { - const nodeHandler = new NodeHandler(); - const onSpy = spy(nodeHandler, 'on'); - const invalidate = stub(); - class MyMeta extends MetaBase { - callGetNode(key: string) { - return this.getNode(key); + getMeta() { + return this.meta(MyMeta); } } - const meta = new MyMeta({ - invalidate, - nodeHandler - }); - - meta.callGetNode('foo'); - assert.isTrue(onSpy.calledOnce); - assert.isTrue(onSpy.firstCall.calledWith('foo')); - onSpy.reset(); - meta.callGetNode('foo'); - assert.isTrue(onSpy.notCalled); - assert.isTrue(invalidate.notCalled); - }, - 'invalidate calls passed in invalidate function'() { - const nodeHandler = new NodeHandler(); - const invalidate = stub(); + const widget = new TestWidget(); + const meta = widget.getMeta(); - class MyMeta extends MetaBase { - callInvalidate() { - this.invalidate(); - } - } + const nodeHandler = meta.getNodeHandler(); + const onFoo = stub(); + const onBar = stub(); + const onWidget = stub(); + const onProjector = stub(); - const meta = new MyMeta({ - invalidate, - nodeHandler - }); + nodeHandler.on('foo', onFoo); + nodeHandler.on('bar', onBar); + nodeHandler.on(NodeEventType.Widget, onWidget); + nodeHandler.on(NodeEventType.Projector, onProjector); + + const div = document.createElement('div'); + widget.append(div); - meta.callInvalidate(); - resolveRAF(); - assert.isTrue(invalidate.calledOnce); + assert.isTrue(meta.has('foo')); + assert.isTrue(meta.has('bar')); + assert.isTrue(onFoo.calledOnce); + assert.isTrue(onBar.calledOnce); + assert.isTrue(onWidget.calledOnce); + assert.isTrue(onProjector.calledOnce); + assert.isTrue(onFoo.calledBefore(onWidget)); + assert.isTrue(onFoo.calledBefore(onProjector)); }, - 'integration'() { + 'integration with multiple root node'() { class MyMeta extends MetaBase { callGetNode(key: string) { return this.getNode(key); @@ -164,9 +212,10 @@ registerSuite({ class TestWidget extends ProjectorMixin(WidgetBase) { render() { - return v('div', { key: 'foo' }, [ - v('div', { 'key': 'bar' }, [ 'hello world' ]) - ]); + return [ + v('div', { key: 'foo' }), + v('div', { key: 'bar' }) + ]; } getMeta() { From 1e7cbd6029afcde68d33d4166a080230ca8d7da8 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Tue, 12 Sep 2017 15:32:55 +0100 Subject: [PATCH 24/27] uncomment tests --- tests/unit/meta/meta.ts | 254 ++++++++++++++++++++-------------------- 1 file changed, 127 insertions(+), 127 deletions(-) diff --git a/tests/unit/meta/meta.ts b/tests/unit/meta/meta.ts index 72126e7e..cc698186 100644 --- a/tests/unit/meta/meta.ts +++ b/tests/unit/meta/meta.ts @@ -25,133 +25,133 @@ registerSuite({ afterEach() { rAFStub.restore(); }, - // 'has checks nodehandler for nodes'() { - // const nodeHandler = new NodeHandler(); - // const element = document.createElement('div'); - // nodeHandler.add(element, { key: 'foo' }); - // const meta = new MetaBase({ - // invalidate: () => {}, - // nodeHandler - // }); - - // assert.isTrue(meta.has('foo')); - // assert.isFalse(meta.has('bar')); - // }, - // 'get node returns element from nodehandler'() { - // const nodeHandler = new NodeHandler(); - // const invalidate = stub(); - // const element = document.createElement('div'); - // nodeHandler.add(element, { key: 'foo' }); - - // class MyMeta extends MetaBase { - // callGetNode(key: string) { - // return this.getNode(key); - // } - // } - - // const meta = new MyMeta({ - // invalidate, - // nodeHandler - // }); - - // const node = meta.callGetNode('foo'); - // assert.equal(node, element); - // }, - // 'Will create event listener for node if not yet loaded'() { - // const nodeHandler = new NodeHandler(); - // const invalidate = stub(); - // const onSpy = spy(nodeHandler, 'on'); - - // class MyMeta extends MetaBase { - // callGetNode(key: string) { - // return this.getNode(key); - // } - // } - - // const meta = new MyMeta({ - // invalidate, - // nodeHandler - // }); - - // meta.callGetNode('foo'); - // assert.isTrue(onSpy.calledOnce); - // assert.isTrue(onSpy.firstCall.calledWith('foo')); - // }, - // 'Will call invalidate when awaited node is available'() { - // const nodeHandler = new NodeHandler(); - // const onSpy = spy(nodeHandler, 'on'); - // const invalidate = stub(); - - // class MyMeta extends MetaBase { - // callGetNode(key: string) { - // return this.getNode(key); - // } - // } - - // const meta = new MyMeta({ - // invalidate, - // nodeHandler - // }); - - // meta.callGetNode('foo'); - // assert.isTrue(onSpy.calledOnce); - // assert.isTrue(onSpy.firstCall.calledWith('foo')); - - // const element = document.createElement('div'); - - // nodeHandler.add(element, { key: 'foo' }); - - // resolveRAF(); - // assert.isTrue(invalidate.calledOnce); - - // onSpy.reset(); - // meta.callGetNode('foo'); - - // assert.isFalse(onSpy.called); - // }, - // 'Will not add a second callback handle if one already exists'() { - // const nodeHandler = new NodeHandler(); - // const onSpy = spy(nodeHandler, 'on'); - // const invalidate = stub(); - - // class MyMeta extends MetaBase { - // callGetNode(key: string) { - // return this.getNode(key); - // } - // } - - // const meta = new MyMeta({ - // invalidate, - // nodeHandler - // }); - - // meta.callGetNode('foo'); - // assert.isTrue(onSpy.calledOnce); - // assert.isTrue(onSpy.firstCall.calledWith('foo')); - // onSpy.reset(); - // meta.callGetNode('foo'); - // assert.isTrue(onSpy.notCalled); - // assert.isTrue(invalidate.notCalled); - // }, - // 'invalidate calls passed in invalidate function'() { - // const nodeHandler = new NodeHandler(); - // const invalidate = stub(); - - // class MyMeta extends MetaBase { - // callInvalidate() { - // this.invalidate(); - // } - // } - - // const meta = new MyMeta({ - // invalidate, - // nodeHandler - // }); - - // meta.callInvalidate(); - // resolveRAF(); - // assert.isTrue(invalidate.calledOnce); - // }, + 'has checks nodehandler for nodes'() { + const nodeHandler = new NodeHandler(); + const element = document.createElement('div'); + nodeHandler.add(element, { key: 'foo' }); + const meta = new MetaBase({ + invalidate: () => {}, + nodeHandler + }); + + assert.isTrue(meta.has('foo')); + assert.isFalse(meta.has('bar')); + }, + 'get node returns element from nodehandler'() { + const nodeHandler = new NodeHandler(); + const invalidate = stub(); + const element = document.createElement('div'); + nodeHandler.add(element, { key: 'foo' }); + + class MyMeta extends MetaBase { + callGetNode(key: string) { + return this.getNode(key); + } + } + + const meta = new MyMeta({ + invalidate, + nodeHandler + }); + + const node = meta.callGetNode('foo'); + assert.equal(node, element); + }, + 'Will create event listener for node if not yet loaded'() { + const nodeHandler = new NodeHandler(); + const invalidate = stub(); + const onSpy = spy(nodeHandler, 'on'); + + class MyMeta extends MetaBase { + callGetNode(key: string) { + return this.getNode(key); + } + } + + const meta = new MyMeta({ + invalidate, + nodeHandler + }); + + meta.callGetNode('foo'); + assert.isTrue(onSpy.calledOnce); + assert.isTrue(onSpy.firstCall.calledWith('foo')); + }, + 'Will call invalidate when awaited node is available'() { + const nodeHandler = new NodeHandler(); + const onSpy = spy(nodeHandler, 'on'); + const invalidate = stub(); + + class MyMeta extends MetaBase { + callGetNode(key: string) { + return this.getNode(key); + } + } + + const meta = new MyMeta({ + invalidate, + nodeHandler + }); + + meta.callGetNode('foo'); + assert.isTrue(onSpy.calledOnce); + assert.isTrue(onSpy.firstCall.calledWith('foo')); + + const element = document.createElement('div'); + + nodeHandler.add(element, { key: 'foo' }); + + resolveRAF(); + assert.isTrue(invalidate.calledOnce); + + onSpy.reset(); + meta.callGetNode('foo'); + + assert.isFalse(onSpy.called); + }, + 'Will not add a second callback handle if one already exists'() { + const nodeHandler = new NodeHandler(); + const onSpy = spy(nodeHandler, 'on'); + const invalidate = stub(); + + class MyMeta extends MetaBase { + callGetNode(key: string) { + return this.getNode(key); + } + } + + const meta = new MyMeta({ + invalidate, + nodeHandler + }); + + meta.callGetNode('foo'); + assert.isTrue(onSpy.calledOnce); + assert.isTrue(onSpy.firstCall.calledWith('foo')); + onSpy.reset(); + meta.callGetNode('foo'); + assert.isTrue(onSpy.notCalled); + assert.isTrue(invalidate.notCalled); + }, + 'invalidate calls passed in invalidate function'() { + const nodeHandler = new NodeHandler(); + const invalidate = stub(); + + class MyMeta extends MetaBase { + callInvalidate() { + this.invalidate(); + } + } + + const meta = new MyMeta({ + invalidate, + nodeHandler + }); + + meta.callInvalidate(); + resolveRAF(); + assert.isTrue(invalidate.calledOnce); + }, 'integration with single root node'() { class MyMeta extends MetaBase { callGetNode(key: string) { From 5fdcf8bffb711908fa06ae8927189e1887377407 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Tue, 12 Sep 2017 18:43:24 +0100 Subject: [PATCH 25/27] move projector emit, remove event typing, revert maps --- src/NodeHandler.ts | 2 +- src/WidgetBase.ts | 39 +++++++++++++-------------------------- src/interfaces.d.ts | 8 -------- src/mixins/Projector.ts | 37 +++++-------------------------------- tests/unit/NodeHandler.ts | 2 +- tests/unit/meta/meta.ts | 16 ++++++++-------- 6 files changed, 28 insertions(+), 76 deletions(-) diff --git a/src/NodeHandler.ts b/src/NodeHandler.ts index cf192769..34d4442b 100644 --- a/src/NodeHandler.ts +++ b/src/NodeHandler.ts @@ -39,7 +39,7 @@ export class NodeHandler extends Evented implements NodeHandlerInterface { this.emit({ type: NodeEventType.Widget }); } - public addProjector(element: HTMLElement, properties: VNodeProperties): void { + public addProjector(): void { this.emit({ type: NodeEventType.Projector }); } diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index 85513276..51a31d07 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -22,8 +22,7 @@ import { WidgetMetaConstructor, WidgetBaseConstructor, WidgetBaseInterface, - WidgetProperties, - ProjectorRenderedEvent + WidgetProperties } from './interfaces'; import RegistryHandler from './RegistryHandler'; import NodeHandler from './NodeHandler'; @@ -102,16 +101,6 @@ export function diffProperty(propertyName: string, diffFunction: DiffPropertyFun }); } -/** - * WeakMap containing the current root node number - */ -export const currentRootNodeMap = new WeakMap(); - -/** - * Weakmap containing the number of root nodes - */ -export const numRootNodesMap = new WeakMap(); - /** * Generic decorator handler to take care of whether or not the decorator was called at the class level * or the method level. @@ -200,6 +189,9 @@ export class WidgetBase

extends E private _projectorAttachEvent: Handle; + private _currentRootNode = 0; + private _numRootNodes = 0; + /** * @constructor */ @@ -251,7 +243,7 @@ export class WidgetBase

extends E this.onElementCreated(element, String(properties.key)); } - protected afterRootCreateCallback( + private afterRootCreateCallback( element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, @@ -274,7 +266,7 @@ export class WidgetBase

extends E this.onElementUpdated(element, String(properties.key)); } - protected afterRootUpdateCallback( + private afterRootUpdateCallback( element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, @@ -285,18 +277,13 @@ export class WidgetBase

extends E } private _addElementToNodeHandler(element: HTMLElement, projectionOptions: ProjectionOptions, properties: VNodeProperties) { - let currentRootNode = currentRootNodeMap.get(this) || 0; - const numRootNodes = numRootNodesMap.get(this); - - currentRootNode += 1; - currentRootNodeMap.set(this, currentRootNode); - const isLastRootNode = (currentRootNode === numRootNodes); + this._currentRootNode += 1; + const isLastRootNode = (this._currentRootNode === this._numRootNodes); if (this._projectorAttachEvent === undefined) { - this._projectorAttachEvent = projectionOptions.nodeEvent.on('rendered', - ({ element, properties }: ProjectorRenderedEvent) => { - this._nodeHandler.addProjector(element, properties); - }); + this._projectorAttachEvent = projectionOptions.nodeEvent.on('rendered', () => { + this._nodeHandler.addProjector(); + }); this.own(this._projectorAttachEvent); } @@ -475,8 +462,8 @@ export class WidgetBase

extends E private _decorateNodes(node: DNode | DNode[]) { let nodes = Array.isArray(node) ? [ ...node ] : [ node ]; - numRootNodesMap.set(this, nodes.length); - currentRootNodeMap.set(this, 0); + this._numRootNodes = nodes.length; + this._currentRootNode = 0; const rootNodes: DNode[] = []; nodes.forEach(node => { diff --git a/src/interfaces.d.ts b/src/interfaces.d.ts index df61b582..466c33b7 100644 --- a/src/interfaces.d.ts +++ b/src/interfaces.d.ts @@ -23,14 +23,6 @@ export interface TypedTargetEvent extends Event { target: T; } -/** - * Projector rendered event - */ -export interface ProjectorRenderedEvent extends EventTargettedObject { - element: HTMLElement; - properties: VNodeProperties; -} - /* These are the event handlers exposed by Maquette. */ diff --git a/src/mixins/Projector.ts b/src/mixins/Projector.ts index b1fb1ef9..79764641 100644 --- a/src/mixins/Projector.ts +++ b/src/mixins/Projector.ts @@ -3,13 +3,13 @@ import global from '@dojo/shim/global'; import { createHandle } from '@dojo/core/lang'; import { Handle } from '@dojo/interfaces/core'; import { Evented } from '@dojo/core/Evented'; -import { VNode, VNodeProperties } from '@dojo/interfaces/vdom'; +import { VNode } from '@dojo/interfaces/vdom'; import { ProjectionOptions } from '../interfaces'; import { dom, h, Projection } from 'maquette'; import 'pepjs'; import cssTransitions from '../animations/cssTransitions'; import { Constructor, DNode } from './../interfaces'; -import { WidgetBase, currentRootNodeMap, numRootNodesMap } from './../WidgetBase'; +import { WidgetBase } from './../WidgetBase'; import eventHandlerInterceptor from '../util/eventHandlerInterceptor'; /** @@ -289,36 +289,6 @@ export function ProjectorMixin>>(Base: T) return this._projection.domNode.outerHTML; } - protected afterRootCreateCallback( - element: HTMLElement, - projectionOptions: ProjectionOptions, - vnodeSelector: string, - properties: VNodeProperties - ): void { - super.afterRootCreateCallback(element, projectionOptions, vnodeSelector, properties); - this._emitProjectorRenderedEvent(element, properties, projectionOptions.nodeEvent); - } - - protected afterRootUpdateCallback( - element: HTMLElement, - projectionOptions: ProjectionOptions, - vnodeSelector: string, - properties: VNodeProperties - ): void { - super.afterRootUpdateCallback(element, projectionOptions, vnodeSelector, properties); - this._emitProjectorRenderedEvent(element, properties, projectionOptions.nodeEvent); - } - - private _emitProjectorRenderedEvent(element: HTMLElement, properties: VNodeProperties, nodeEvent: Evented) { - const currentRootNode = currentRootNodeMap.get(this) || 0; - const numRootNodes = numRootNodesMap.get(this); - const isLastRootNode = (currentRootNode === numRootNodes); - - if (isLastRootNode) { - nodeEvent.emit({ type: 'rendered', element, properties }); - } - } - public __render__(): VNode { if (this._projectorChildren) { this.setChildren(this._projectorChildren); @@ -360,6 +330,7 @@ export function ProjectorMixin>>(Base: T) if (this._projection) { this._projection.update(this._boundRender()); + this._projectionOptions.nodeEvent.emit({ type: 'rendered' }); } } @@ -401,6 +372,8 @@ export function ProjectorMixin>>(Base: T) break; } + this._projectionOptions.nodeEvent.emit({ type: 'rendered' }); + return this._attachHandle; } } diff --git a/tests/unit/NodeHandler.ts b/tests/unit/NodeHandler.ts index 0174d937..b5a32592 100644 --- a/tests/unit/NodeHandler.ts +++ b/tests/unit/NodeHandler.ts @@ -64,7 +64,7 @@ registerSuite({ assert.isTrue(projectorStub.notCalled); }, 'add projector emits Projector event'() { - nodeHandler.addProjector(element, { key: 'foo' }); + nodeHandler.addProjector(); assert.isTrue(widgetStub.notCalled); assert.isTrue(elementStub.notCalled); diff --git a/tests/unit/meta/meta.ts b/tests/unit/meta/meta.ts index cc698186..6ed9e1b7 100644 --- a/tests/unit/meta/meta.ts +++ b/tests/unit/meta/meta.ts @@ -191,14 +191,14 @@ registerSuite({ const div = document.createElement('div'); widget.append(div); - assert.isTrue(meta.has('foo')); - assert.isTrue(meta.has('bar')); - assert.isTrue(onFoo.calledOnce); - assert.isTrue(onBar.calledOnce); - assert.isTrue(onWidget.calledOnce); - assert.isTrue(onProjector.calledOnce); - assert.isTrue(onFoo.calledBefore(onWidget)); - assert.isTrue(onFoo.calledBefore(onProjector)); + assert.isTrue(meta.has('foo'), '1'); + assert.isTrue(meta.has('bar'), '2'); + assert.isTrue(onFoo.calledOnce, '3'); + assert.isTrue(onBar.calledOnce, '4'); + assert.isTrue(onWidget.calledOnce, '5'); + assert.isTrue(onProjector.calledOnce, '6'); + assert.isTrue(onFoo.calledBefore(onWidget), '7'); + assert.isTrue(onFoo.calledBefore(onProjector), '8'); }, 'integration with multiple root node'() { class MyMeta extends MetaBase { From b067ace03116103744807d22dd49c9c08a698960 Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Thu, 14 Sep 2017 10:51:47 +0100 Subject: [PATCH 26/27] pr comments --- src/WidgetBase.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index 51a31d07..b0e9b866 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -243,7 +243,7 @@ export class WidgetBase

extends E this.onElementCreated(element, String(properties.key)); } - private afterRootCreateCallback( + private _afterRootCreateCallback( element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, @@ -266,7 +266,7 @@ export class WidgetBase

extends E this.onElementUpdated(element, String(properties.key)); } - private afterRootUpdateCallback( + private _afterRootUpdateCallback( element: HTMLElement, projectionOptions: ProjectionOptions, vnodeSelector: string, @@ -277,7 +277,7 @@ export class WidgetBase

extends E } private _addElementToNodeHandler(element: HTMLElement, projectionOptions: ProjectionOptions, properties: VNodeProperties) { - this._currentRootNode += 1; + this._currentRootNode++; const isLastRootNode = (this._currentRootNode === this._numRootNodes); if (this._projectorAttachEvent === undefined) { @@ -470,8 +470,8 @@ export class WidgetBase

extends E if (isHNode(node)) { rootNodes.push(node); node.properties = node.properties || {}; - node.properties.afterCreate = this.afterRootCreateCallback; - node.properties.afterUpdate = this.afterRootUpdateCallback; + node.properties.afterCreate = this._afterRootCreateCallback; + node.properties.afterUpdate = this._afterRootUpdateCallback; } }); From 2e8050884ed3af30d20667ff1f89fb6040fdef4b Mon Sep 17 00:00:00 2001 From: Tom Dye Date: Thu, 14 Sep 2017 11:16:34 +0100 Subject: [PATCH 27/27] pr comments --- src/WidgetBase.ts | 1 + src/mixins/Projector.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/WidgetBase.ts b/src/WidgetBase.ts index b0e9b866..1ce612f8 100644 --- a/src/WidgetBase.ts +++ b/src/WidgetBase.ts @@ -190,6 +190,7 @@ export class WidgetBase

extends E private _projectorAttachEvent: Handle; private _currentRootNode = 0; + private _numRootNodes = 0; /** diff --git a/src/mixins/Projector.ts b/src/mixins/Projector.ts index 79764641..772fadf0 100644 --- a/src/mixins/Projector.ts +++ b/src/mixins/Projector.ts @@ -164,10 +164,13 @@ export function ProjectorMixin>>(Base: T) constructor(...args: any[]) { super(...args); + const nodeEvent = new Evented(); + this.own(nodeEvent); + this._projectionOptions = { transitions: cssTransitions, eventHandlerInterceptor: eventHandlerInterceptor.bind(this), - nodeEvent: new Evented() + nodeEvent }; this._boundDoRender = this._doRender.bind(this);