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

Support Injector instances in Registry and add inject decorator #668

Merged
merged 8 commits into from
Sep 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 14 additions & 28 deletions src/Container.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,26 @@
import { WidgetBase } from './WidgetBase';
import {
Constructor,
DNode,
RegistryLabel,
WidgetBaseInterface,
WidgetProperties
} from './interfaces';
import { inject, GetProperties } from './decorators/inject';
import { Constructor, DNode, RegistryLabel } from './interfaces';
import { w } from './d';
import { BaseInjector, defaultMappers, Mappers } from './Injector';

export type Container<T extends WidgetBaseInterface> = Constructor<WidgetBase<Partial<T['properties']> & WidgetProperties>>;
export type Container<T extends WidgetBase> = Constructor<WidgetBase<Partial<T['properties']>>>;

export function Container<W extends WidgetBaseInterface>(
export function Container<W extends WidgetBase> (
component: Constructor<W> | RegistryLabel,
name: RegistryLabel,
mappers: Partial<Mappers> = defaultMappers
{ getProperties }: { getProperties: GetProperties }
): Container<W> {
const {
getProperties = defaultMappers.getProperties,
getChildren = defaultMappers.getChildren
} = mappers;

return class extends WidgetBase<any> {
@inject({ name, getProperties })
class WidgetContainer extends WidgetBase<Partial<W['properties']>> {
public __setProperties__(properties: Partial<W['properties']>): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

have we thought how we can better support this in the future? this is the equivalent of shouldComponentUpdate set to true in react?

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 have thought about it, shall raise an enhancement issue - for now this is the only way I could support it.

super.__setProperties__(properties as any);
this.invalidate();
}
protected render(): DNode {
const { properties, children } = this;

return w<BaseInjector<any>>(name, {
scope: this,
render: () => w(component, properties, children),
getProperties,
properties,
getChildren,
children
});
return w(component, this.properties, this.children);
}
};
}
return WidgetContainer;
}

export default Container;
146 changes: 7 additions & 139 deletions src/Injector.ts
Original file line number Diff line number Diff line change
@@ -1,154 +1,22 @@
import { assign } from '@dojo/core/lang';
import { Evented } from '@dojo/core/Evented';
import {
diffProperty,
WidgetBase
} from './WidgetBase';
import { decorate, isHNode, isWNode } from './d';
import { always } from './diff';
import {
Constructor,
DNode,
HNode,
WidgetProperties,
WNode
} from './interfaces';

export interface GetProperties {
<P extends WidgetProperties>(inject: any, properties: P): any;
}
export class Injector<T = any> extends Evented {

export interface GetChildren {
(inject: any, children: DNode[]): DNode[];
}
private _payload: T;

/**
* The binding mappers for properties and children.
*/
export interface Mappers {
getProperties: GetProperties;
getChildren: GetChildren;
}

/**
* Default noop Mappers for the container.
*/
export const defaultMappers: Mappers = {
getProperties(inject: any, properties: any): any {
return Object.create(null);
},
getChildren(inject: any, children: DNode[]): DNode[] {
return [];
}
};

/**
* Base context class that extends Evented and
* returns the context using `.get()`.
*/
export class Context<T = any> extends Evented {

private _context: T;

constructor(context: T = <T> {}) {
constructor(payload: T) {
super({});
this._context = context;
this._payload = payload;
}

public get(): T {
return this._context;
return this._payload;
}

public set(context: T): void {
this._context = context;
public set(payload: T): void {
this._payload = payload;
this.emit({ type: 'invalidate' });
}
}

export interface InjectorProperties extends WidgetProperties {
scope: WidgetBase;
render(): DNode | DNode[];
getProperties?: GetProperties;
properties: any;
getChildren?: GetChildren;
children: DNode[];
}

export interface Base<C = Context> {
toInject(): C;
}

export class Base<C = Context> extends WidgetBase<InjectorProperties> implements Base<C> {

protected context: C = <C> {};

public toInject(): C {
return this.context;
}
}

export class BaseInjector<C extends Evented = Context> extends Base<C> {

constructor(context?: C) {
super();
if (context) {
this.context = context;
this.context.on('invalidate', this.invalidate.bind(this));
}
}

}

/**
* Mixin that extends the supplied Injector class with the proxy `render` and passing the provided to `context` to the Injector
* class via the constructor.
*/
export function Injector<C, T extends Constructor<Base<C>>>(Base: T, context: C): T {

@diffProperty('render', always)
class Injector extends Base {

constructor(...args: any[]) {
super(context);
}

protected decorateBind(node: DNode | DNode[]): DNode | DNode[] {
const { scope } = this.properties;
decorate(node, (node: WNode | HNode) => {
if (isHNode(node)) {
(<any> node.properties).bind = scope;
}
else {
node.coreProperties.bind = scope;
}
}, (node: DNode) => { return isHNode(node) || isWNode(node); });

return node;
}

protected render(): DNode | DNode[] {
const {
render,
properties,
getProperties = defaultMappers.getProperties,
children,
getChildren = defaultMappers.getChildren
} = this.properties;
const injectedChildren = getChildren(this.toInject(), children);

assign(properties, getProperties(this.toInject(), properties));
if (injectedChildren && injectedChildren.length) {
children.push(...injectedChildren);
}

return this.decorateBind(render());
}

protected runAfterRenders(dNode: DNode | DNode[]): DNode | DNode[] {
return super.runAfterRenders.call(this.properties.scope, dNode);
}
}
return Injector;
}

export default Injector;
Loading