This repository has been archived by the owner on Jul 30, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 39
Add evented Node handler to meta #666
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
838903a
th progress
tomdye ed9bc3e
dimensions tests
tomdye 6f75f56
fixed tests
tomdye f61028d
fixed interface comments
tomdye 8aa52fe
fixed Matches
tomdye 2bb5260
comments
tomdye 35ca787
remove beforeRender
tomdye 66b4d05
pr comments
tomdye 2d4e39f
fix typings
tomdye 47b4f75
add nodeEvent typing
tomdye 0162789
rename Type enum
tomdye 43ab419
rename Type enum
tomdye ea370b8
pr comments, changed dimensions implementation
tomdye 1142321
added getnode function
tomdye 076085d
updated tests
tomdye 4d31f7d
adding integration test
tomdye 5decea2
integration tests
tomdye 0aa14cc
uncomment tests
tomdye e88241b
fix test after changed functionality
tomdye fda32dd
remove cancel raf
tomdye b9787b5
pr nits
tomdye 3e50f14
pr alignment
tomdye 6207249
change to weakmaps
tomdye 1e7cbd6
uncomment tests
tomdye 5fdcf8b
move projector emit, remove event typing, revert maps
tomdye b067ace
pr comments
tomdye 2e80508
pr comments
tomdye File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { Evented } from '@dojo/core/Evented'; | ||
import { VNodeProperties } from '@dojo/interfaces/vdom'; | ||
import Map from '@dojo/shim/Map'; | ||
import { 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 NodeEventType { | ||
Projector = 'Projector', | ||
Widget = 'Widget' | ||
} | ||
|
||
export class NodeHandler extends Evented implements NodeHandlerInterface { | ||
|
||
private _nodeMap = new Map<string, HTMLElement>(); | ||
|
||
public get(key: string): HTMLElement | undefined { | ||
return this._nodeMap.get(key); | ||
} | ||
|
||
public has(key: string): boolean { | ||
return this._nodeMap.has(key); | ||
} | ||
|
||
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): void { | ||
if (properties && properties.key) { | ||
this.add(element, properties); | ||
} | ||
|
||
this.emit({ type: NodeEventType.Widget }); | ||
} | ||
|
||
public addProjector(): void { | ||
this.emit({ type: NodeEventType.Projector }); | ||
} | ||
|
||
public clear(): void { | ||
this._nodeMap.clear(); | ||
} | ||
} | ||
|
||
export default NodeHandler; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,10 @@ | ||
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'; | ||
import { Handle } from '@dojo/interfaces/core'; | ||
import { isWNode, v, isHNode } from './d'; | ||
import { auto, ignore } from './diff'; | ||
import { | ||
|
@@ -20,10 +22,10 @@ import { | |
WidgetMetaConstructor, | ||
WidgetBaseConstructor, | ||
WidgetBaseInterface, | ||
WidgetProperties, | ||
WidgetMetaRequiredNodeCallback | ||
WidgetProperties | ||
} from './interfaces'; | ||
import RegistryHandler from './RegistryHandler'; | ||
import NodeHandler from './NodeHandler'; | ||
import { isWidgetBaseConstructor, WIDGET_BASE_TYPE, Registry } from './Registry'; | ||
|
||
/** | ||
|
@@ -177,16 +179,20 @@ export class WidgetBase<P = WidgetProperties, C extends DNode = DNode> extends E | |
|
||
private _metaMap = new WeakMap<WidgetMetaConstructor<any>, WidgetMetaBase>(); | ||
|
||
private _nodeMap = new Map<string, HTMLElement>(); | ||
|
||
private _requiredNodes = new Map<string, ([ WidgetMetaBase, WidgetMetaRequiredNodeCallback ])[]>(); | ||
|
||
private _boundRenderFunc: Render; | ||
|
||
private _boundInvalidate: () => void; | ||
|
||
private _defaultRegistry = new Registry(); | ||
|
||
private _nodeHandler: NodeHandler; | ||
|
||
private _projectorAttachEvent: Handle; | ||
|
||
private _currentRootNode = 0; | ||
|
||
private _numRootNodes = 0; | ||
|
||
/** | ||
* @constructor | ||
*/ | ||
|
@@ -200,14 +206,10 @@ export class WidgetBase<P = WidgetProperties, C extends DNode = DNode> extends E | |
this._diffPropertyFunctionMap = new Map<string, string>(); | ||
this._bindFunctionPropertyMap = new WeakMap<(...args: any[]) => any, { boundFunc: (...args: any[]) => any, scope: any }>(); | ||
this._registries = new RegistryHandler(); | ||
this._nodeHandler = new NodeHandler(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
this._registries.add(this._defaultRegistry, true); | ||
this.own(this._registries); | ||
this.own({ | ||
destroy: () => { | ||
this._nodeMap.clear(); | ||
this._requiredNodes.clear(); | ||
} | ||
}); | ||
this.own(this._nodeHandler); | ||
this._boundRenderFunc = this.render.bind(this); | ||
this._boundInvalidate = this.invalidate.bind(this); | ||
|
||
|
@@ -219,9 +221,8 @@ export class WidgetBase<P = WidgetProperties, C extends DNode = DNode> extends E | |
let cached = this._metaMap.get(MetaType); | ||
if (!cached) { | ||
cached = new MetaType({ | ||
nodes: this._nodeMap, | ||
requiredNodes: this._requiredNodes, | ||
invalidate: this._boundInvalidate | ||
invalidate: this._boundInvalidate, | ||
nodeHandler: this._nodeHandler | ||
}); | ||
this._metaMap.set(MetaType, cached); | ||
this.own(cached); | ||
|
@@ -230,52 +231,71 @@ export class WidgetBase<P = WidgetProperties, C extends DNode = DNode> extends E | |
return cached as T; | ||
} | ||
|
||
/** | ||
* 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; | ||
}; | ||
} | ||
|
||
/** | ||
* vnode afterCreate callback that calls the onElementCreated lifecycle method. | ||
*/ | ||
private _afterCreateCallback( | ||
element: Element, | ||
element: HTMLElement, | ||
projectionOptions: ProjectionOptions, | ||
vnodeSelector: string, | ||
properties: VNodeProperties | ||
): void { | ||
this._nodeHandler.add(element, properties); | ||
this.onElementCreated(element, String(properties.key)); | ||
} | ||
|
||
private _afterRootCreateCallback( | ||
element: HTMLElement, | ||
projectionOptions: ProjectionOptions, | ||
vnodeSelector: string, | ||
properties: VNodeProperties | ||
): void { | ||
this._setNode(element, properties); | ||
this._addElementToNodeHandler(element, projectionOptions, properties); | ||
this.onElementCreated(element, String(properties.key)); | ||
} | ||
|
||
/** | ||
* vnode afterUpdate callback that calls the onElementUpdated lifecycle method. | ||
*/ | ||
private _afterUpdateCallback( | ||
element: Element, | ||
element: HTMLElement, | ||
projectionOptions: ProjectionOptions, | ||
vnodeSelector: string, | ||
properties: VNodeProperties | ||
): void { | ||
this._nodeHandler.add(element, properties); | ||
this.onElementUpdated(element, String(properties.key)); | ||
} | ||
|
||
private _afterRootUpdateCallback( | ||
element: HTMLElement, | ||
projectionOptions: ProjectionOptions, | ||
vnodeSelector: string, | ||
properties: VNodeProperties | ||
): void { | ||
this._setNode(element, properties); | ||
this._addElementToNodeHandler(element, projectionOptions, properties); | ||
this.onElementUpdated(element, String(properties.key)); | ||
} | ||
|
||
private _addElementToNodeHandler(element: HTMLElement, projectionOptions: ProjectionOptions, properties: VNodeProperties) { | ||
this._currentRootNode++; | ||
const isLastRootNode = (this._currentRootNode === this._numRootNodes); | ||
|
||
if (this._projectorAttachEvent === undefined) { | ||
this._projectorAttachEvent = projectionOptions.nodeEvent.on('rendered', () => { | ||
this._nodeHandler.addProjector(); | ||
}); | ||
this.own(this._projectorAttachEvent); | ||
} | ||
|
||
if (isLastRootNode) { | ||
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,17 +318,6 @@ export class WidgetBase<P = WidgetProperties, C extends DNode = DNode> extends E | |
// Do nothing by default. | ||
} | ||
|
||
private _setNode(element: Element, properties: VNodeProperties): void { | ||
const key = String(properties.key); | ||
this._nodeMap.set(key, <HTMLElement> element); | ||
const callbacks = this._requiredNodes.get(key); | ||
if (callbacks) { | ||
for (const [ meta, callback ] of callbacks) { | ||
callback.call(meta, element); | ||
} | ||
} | ||
} | ||
|
||
public get properties(): Readonly<P> & Readonly<WidgetProperties> { | ||
return this._properties; | ||
} | ||
|
@@ -442,6 +451,7 @@ export class WidgetBase<P = WidgetProperties, C extends DNode = DNode> extends E | |
this._decorateNodes(dNode); | ||
const widget = this._dNodeToVNode(dNode); | ||
this._manageDetachedChildren(); | ||
this._nodeHandler.clear(); | ||
this._cachedVNode = widget; | ||
this._renderState = WidgetRenderState.IDLE; | ||
return widget; | ||
|
@@ -452,12 +462,26 @@ export class WidgetBase<P = WidgetProperties, C extends DNode = DNode> 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) === -1 && node.properties.key) { | ||
node.properties.afterCreate = this._afterCreateCallback; | ||
node.properties.afterUpdate = this._afterUpdateCallback; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two things I've been struggling with using meta that this could perhaps help with. The first is doing a reverse lookup and the second is knowing what the currently rendered keys are. I need the reverse lookup for intersection meta where I have a single observer that receives multiple events. I don't have a good use case for knowing all the keys, but I could see it being potentially helpful/related to whatever we'd have to do for a reverse lookup.