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

Add evented Node handler to meta #666

Merged
merged 27 commits into from
Sep 14, 2017
Merged

Add evented Node handler to meta #666

merged 27 commits into from
Sep 14, 2017

Conversation

tomdye
Copy link
Member

@tomdye tomdye commented Sep 8, 2017

Type: feature

The following has been addressed in the PR:

  • There is a related issue
  • All code matches the style guide
  • Unit or Functional tests are included in the PR

Description:

  • adds nodehandler to meta
  • tracks elements, widget roots and projector creation / updates
  • changes the way nodes are acquired within meta
  • fixes tests
  • events for element creation

Resolves #662

@@ -199,14 +203,9 @@ 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();
Copy link
Contributor

@matt-gadd matt-gadd Sep 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should own this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Member

@agubler agubler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a few comments

Widget = 'Widget'
}

export default class NodeHandler extends Evented {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a named export and export default below (a pattern for most widget-core modules)

import { VNodeProperties } from '@dojo/interfaces/vdom';
import Map from '@dojo/shim/Map';

export enum Type {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have some documentation on these (and the rest of the module?), is type descriptive enough?

}

public addRoot(element: Element, properties: VNodeProperties) {
this.add(element, properties);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need the key check for adding the root too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a failing test for this if it needs the guard 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

way ahead of you 😉

private _boundRenderFunc: Render;

private _boundInvalidate: () => void;

private _defaultRegistry = new WidgetRegistry();

private _nodeHandler: NodeHandler;

private _projectorMountEvent: any;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this really any?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unlikely

@@ -199,14 +203,9 @@ 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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to own this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not === -1?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks a bit confusing like that to me

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

*/
export interface WidgetMetaRequiredNodeCallback {
(node: HTMLElement): void;
nodeHandler: any;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type? might need to create an interface in here for NodeHandler

src/meta/Base.ts Outdated

export class Base extends Destroyable implements WidgetMetaBase {
private _invalidate: () => void;
private _invalidating: number;
private _requiredNodes: Map<string, ([ WidgetMetaBase, WidgetMetaRequiredNodeCallback ])[]>;
protected nodes: Map<string, HTMLElement>;
protected nodeHandler: any;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type please

@@ -286,6 +288,24 @@ export function ProjectorMixin<P, T extends Constructor<WidgetBase<P>>>(Base: T)
return this._projection.domNode.outerHTML;
}

_afterRootCreateCallback(element: Element,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting a bit weird here (and the other functions)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


let rAF: any;
const defaultDimensions = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we export these from dimensions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, i did add that earlier but removed again, makes sense

this.emit({ type: key });
}

public addRoot(element: HTMLElement, properties: VNodeProperties) {
Copy link
Member

@agubler agubler Sep 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is a bit picky, but you could have a single function that does this, and three specific functions that pass their type. Like:

public add(element: HTMLElement, properties: VNodeProperties) {
     if (properties.key) {
        this.add(element, properties);
    }
    this.emit({ type: properties.key });
}

public addRoot(element: HTMLElement, properties: VNodeProperties) {
    this._add(element, properties, Type.Widget);
}

public addProjector(element: HTMLElement, properties: VNodeProperties) {
    this._add(element, properties, Type.Projector);
}

private _add(element: HTMLElement, properties: VNodeProperties = {}, type: Type) {
    if (properties.key) {
        this.add(element, properties);
    }
    this.emit({ type });
}

Copy link
Member Author

@tomdye tomdye Sep 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair. but doesn't quite work, as a root or a projector may have a key and thus needs to be emited via it's key as well as via it's type

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that logically actually same?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disclosure, I updated the code :)

this.onElementUpdated(element, String(properties.key));
}

_addElementToNodeHandler(element: Element, projectionOptions: ProjectionOptions, properties: VNodeProperties) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this private? let's add the visibility modifier.

_addElementToNodeHandler(element: Element, projectionOptions: ProjectionOptions, properties: VNodeProperties) {
this._currentRootNode += 1;

if (!this._projectorAttachEvent) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit: I believe if (this._projectorAttachEvent === undefined) { is more performant and also personally I find it clearer.

this._currentRootNode += 1;

if (!this._projectorAttachEvent) {
this._projectorAttachEvent = projectionOptions.nodeEvent.on('attached',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should own this._projectorAttachEvent also

@@ -433,6 +440,7 @@ export class WidgetBase<P = WidgetProperties, C extends DNode = DNode> extends E
}

public __render__(): VirtualDomNode | VirtualDomNode[] {
this._nodeHandler.clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this the correct place for this? so it clears regardless of whether the widget was invalidated.... I don't think that this is the equivalent to when it was executed in the @beforeRender...

@@ -409,20 +409,21 @@ export interface WidgetMetaConstructor<T extends WidgetMetaBase> {
new (properties: WidgetMetaProperties): T;
}

export interface NodeHandler extends Evented {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we tend to have doc on exported interfaces (we also tend to suffix with Interface)

@@ -23,7 +25,7 @@ export interface DimensionResults {
scroll: TopLeft & Size;
}

const defaultDimensions = {
export const defaultDimensions = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thinking about this, doesn't this mean that something external could mess with the defaults?

constructor(properties: WidgetMetaProperties) {
super(properties);

this.nodeHandler.on(NodeEventType.Projector, () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this is going to put us in a loop no?

this._currentRootNode += 1;

if (this._projectorAttachEvent === undefined) {
this._projectorAttachEvent = projectionOptions.nodeEvent.on('attached',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't really attached or mount (which it previously was), and more after dom update no?

@@ -436,6 +445,7 @@ export class WidgetBase<P = WidgetProperties, C extends DNode = DNode> extends E
this._renderState = WidgetRenderState.RENDER;
if (this._dirty === true || this._cachedVNode === undefined) {
this._dirty = false;
this._nodeHandler.clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think that this is in the wrong place, it will clear the map before running the renders and therefore always return back the default dimensions! This is were integration tests for using the meta with WidgetBase come in handy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved down

Widget = 'Widget'
}

export class NodeHandler extends Evented implements NodeHandlerInterface {
Copy link
Contributor

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.

Copy link
Member

@agubler agubler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couple more nits

return this._nodeMap.has(key);
}

public add(element: HTMLElement, properties: VNodeProperties) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can you add return type of : void please?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other places through the PR also?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's okay because it is defined on the interface, I don't know what the standards are around this. Can't hurt to add them I guess.

src/meta/Base.ts Outdated
}

const handle = this.nodeHandler.on(key, () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe should own this

@@ -286,6 +289,24 @@ export function ProjectorMixin<P, T extends Constructor<WidgetBase<P>>>(Base: T)
return this._projection.domNode.outerHTML;
}

protected afterRootCreateCallback(element: HTMLElement,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this formatting is really funky :) is it different from the formatting in widget base?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're really funky

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting is the same as in widgetbase

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not the same, it is like this in widget base

	protected afterRootCreateCallback(
		element: HTMLElement,
  		projectionOptions: ProjectionOptions,
  		vnodeSelector: string,
  		properties: VNodeProperties
  	): void {
              // ... stuff
  	}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

notice the first param is on the next line?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my apologies, have fixed

src/meta/Base.ts Outdated
if (!callback) {
this.invalidate();
if (!node) {
if (this._requestedNodeKeys.has(key)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this throw an error if it's called twice? And is the assumption that .has would always be used to check to see if a node exists and never .getNode? Throwing an error as a side-effect of a getter could be unexpected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch re the error @pottedmeat. I'll remove the error and only add to the requested keys if it does not already exist in there. Thanks

@@ -274,7 +266,7 @@ export class WidgetBase<P = WidgetProperties, C extends DNode = DNode> extends E
this.onElementUpdated(element, String(properties.key));
}

protected afterRootUpdateCallback(
private afterRootUpdateCallback(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs the _ prefix

@@ -251,7 +243,7 @@ export class WidgetBase<P = WidgetProperties, C extends DNode = DNode> extends E
this.onElementCreated(element, String(properties.key));
}

protected afterRootCreateCallback(
private afterRootCreateCallback(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs the _ prefix

currentRootNode += 1;
currentRootNodeMap.set(this, currentRootNode);
const isLastRootNode = (currentRootNode === numRootNodes);
this._currentRootNode += 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not this._currentRootNode++

@tomdye tomdye merged commit 0f7f45e into dojo:master Sep 14, 2017
@dylans dylans added this to the 2017.09 milestone Sep 14, 2017
@agubler agubler mentioned this pull request Sep 22, 2017
3 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add eventing system to meta to determine when nodes are processed
5 participants