Skip to content

Commit

Permalink
Merge pull request #17143 from emberjs/modifier-managers
Browse files Browse the repository at this point in the history
Modifier Manager
  • Loading branch information
chadhietala committed Oct 23, 2018
2 parents 6d57f74 + 0530160 commit dec37c3
Show file tree
Hide file tree
Showing 13 changed files with 429 additions and 64 deletions.
2 changes: 2 additions & 0 deletions packages/@ember/-internals/glimmer/index.ts
Expand Up @@ -306,4 +306,6 @@ export { default as DebugStack } from './lib/utils/debug-stack';
export { default as OutletView } from './lib/views/outlet';
export { capabilities } from './lib/component-managers/custom';
export { setComponentManager, getComponentManager } from './lib/utils/custom-component-manager';
export { setModifierManager, getModifierManager } from './lib/utils/custom-modifier-manager';
export { capabilities as modifierCapabilties } from './lib/modifiers/custom';
export { isSerializationFirstNode } from './lib/utils/serialization-first-node-helpers';
Expand Up @@ -22,6 +22,7 @@ import { Destroyable } from '@glimmer/util';
import Environment from '../environment';
import RuntimeResolver from '../resolver';
import { OwnedTemplate } from '../template';
import { ManagerArgs, valueForCapturedArgs } from '../utils/managers';
import { RootReference } from '../utils/references';
import AbstractComponentManager from './abstract';

Expand Down Expand Up @@ -64,15 +65,10 @@ export interface Capabilities {
destructor: boolean;
}

export interface CustomComponentManagerArgs {
named: Dict<Opaque>;
positional: Opaque[];
}

export interface ManagerDelegate<ComponentInstance> {
capabilities: Capabilities;
createComponent(factory: Opaque, args: CustomComponentManagerArgs): ComponentInstance;
updateComponent(instance: ComponentInstance, args: CustomComponentManagerArgs): void;
createComponent(factory: Opaque, args: ManagerArgs): ComponentInstance;
updateComponent(instance: ComponentInstance, args: ManagerArgs): void;
getContext(instance: ComponentInstance): Opaque;
}

Expand Down Expand Up @@ -104,12 +100,6 @@ export interface ComponentArguments {
named: Dict<Opaque>;
}

function valueForCapturedArgs(args: CapturedArguments): CustomComponentManagerArgs {
return {
named: args.named.value(),
positional: args.positional.value(),
};
}
/**
The CustomComponentManager allows addons to provide custom component
implementations that integrate seamlessly into Ember. This is accomplished
Expand Down
97 changes: 97 additions & 0 deletions packages/@ember/-internals/glimmer/lib/modifiers/custom.ts
@@ -0,0 +1,97 @@
import { Factory } from '@ember/-internals/owner';
import { Opaque } from '@glimmer/interfaces';
import { Tag } from '@glimmer/reference';
import { Arguments, CapturedArguments, ModifierManager } from '@glimmer/runtime';
import { ManagerArgs, valueForCapturedArgs } from '../utils/managers';

export interface CustomModifierDefinitionState<ModifierInstance> {
ModifierClass: Factory<ModifierInstance>;
name: string;
delegate: ModifierManagerDelegate<ModifierInstance>;
}

export interface Capabilities {}

// Currently there are no capabilities for modifiers
export function capabilities(_managerAPI: string, _optionalFeatures?: {}): Capabilities {
return {};
}

export class CustomModifierDefinition<ModifierInstance> {
public state: CustomModifierDefinitionState<ModifierInstance>;
public manager = CUSTOM_MODIFIER_MANAGER;
constructor(
public name: string,
public ModifierClass: Factory<ModifierInstance>,
public delegate: ModifierManagerDelegate<ModifierInstance>
) {
this.state = {
ModifierClass,
name,
delegate,
};
}
}

export class CustomModifierState<ModifierInstance> {
constructor(
public element: Element,
public delegate: ModifierManagerDelegate<ModifierInstance>,
public modifier: ModifierInstance,
public args: CapturedArguments
) {}

destroy() {
const { delegate, modifier, args } = this;
let modifierArgs = valueForCapturedArgs(args);
delegate.destroyModifier(modifier, modifierArgs);
}
}

export interface ModifierManagerDelegate<ModifierInstance> {
capabilities: Capabilities;
createModifier(factory: Opaque, args: ManagerArgs): ModifierInstance;
installModifier(instance: ModifierInstance, element: Element, args: ManagerArgs): void;
updateModifier(instance: ModifierInstance, args: ManagerArgs): void;
destroyModifier(instance: ModifierInstance, args: ManagerArgs): void;
}

class CustomModifierManager<ModifierInstance>
implements
ModifierManager<
CustomModifierState<ModifierInstance>,
CustomModifierDefinitionState<ModifierInstance>
> {
create(
element: Element,
definition: CustomModifierDefinitionState<ModifierInstance>,
args: Arguments
) {
const capturedArgs = args.capture();
let modifierArgs = valueForCapturedArgs(capturedArgs);
let instance = definition.delegate.createModifier(definition.ModifierClass, modifierArgs);
return new CustomModifierState(element, definition.delegate, instance, capturedArgs);
}

getTag({ args }: CustomModifierState<ModifierInstance>): Tag {
return args.tag;
}

install(state: CustomModifierState<ModifierInstance>) {
let { element, args, delegate, modifier } = state;
let modifierArgs = valueForCapturedArgs(args);
delegate.installModifier(modifier, element, modifierArgs);
}

update(state: CustomModifierState<ModifierInstance>) {
let { args, delegate, modifier } = state;
let modifierArgs = valueForCapturedArgs(args);
delegate.updateModifier(modifier, modifierArgs);
}

getDestructor(state: CustomModifierState<ModifierInstance>) {
return state;
}
}

const CUSTOM_MODIFIER_MANAGER = new CustomModifierManager();
43 changes: 28 additions & 15 deletions packages/@ember/-internals/glimmer/lib/resolver.ts
Expand Up @@ -2,7 +2,11 @@ import { privatize as P } from '@ember/-internals/container';
import { ENV } from '@ember/-internals/environment';
import { LookupOptions, Owner, setOwner } from '@ember/-internals/owner';
import { lookupComponent, lookupPartial, OwnedTemplateMeta } from '@ember/-internals/views';
import { EMBER_MODULE_UNIFICATION, GLIMMER_CUSTOM_COMPONENT_MANAGER } from '@ember/canary-features';
import {
EMBER_MODULE_UNIFICATION,
GLIMMER_CUSTOM_COMPONENT_MANAGER,
GLIMMER_MODIFIER_MANAGER,
} from '@ember/canary-features';
import { assert } from '@ember/debug';
import { _instrumentStart } from '@ember/instrumentation';
import { DEBUG } from '@glimmer/env';
Expand Down Expand Up @@ -37,11 +41,13 @@ import { default as queryParams } from './helpers/query-param';
import { default as readonly } from './helpers/readonly';
import { default as unbound } from './helpers/unbound';
import ActionModifierManager from './modifiers/action';
import { CustomModifierDefinition, ModifierManagerDelegate } from './modifiers/custom';
import { populateMacros } from './syntax';
import { mountHelper } from './syntax/mount';
import { outletHelper } from './syntax/outlet';
import { Factory as TemplateFactory, Injections, OwnedTemplate } from './template';
import { getComponentManager } from './utils/custom-component-manager';
import { getModifierManager } from './utils/custom-modifier-manager';
import { ClassBasedHelperReference, SimpleHelperReference } from './utils/references';

function instrumentationPayload(name: string) {
Expand Down Expand Up @@ -175,8 +181,8 @@ export default class RuntimeResolver implements IRuntimeResolver<OwnedTemplateMe
/**
* Called by CompileTimeLookup compiling the
*/
lookupModifier(name: string, _meta: OwnedTemplateMeta): Option<number> {
return this.handle(this._lookupModifier(name));
lookupModifier(name: string, meta: OwnedTemplateMeta): Option<number> {
return this.handle(this._lookupModifier(name, meta));
}

/**
Expand Down Expand Up @@ -272,8 +278,21 @@ export default class RuntimeResolver implements IRuntimeResolver<OwnedTemplateMe
}
}

private _lookupModifier(name: string) {
return this.builtInModifiers[name];
private _lookupModifier(name: string, meta: OwnedTemplateMeta) {
let builtin = this.builtInModifiers[name];

if (GLIMMER_MODIFIER_MANAGER && builtin === undefined) {
let { owner } = meta;
let modifier = owner.factoryFor(`modifier:${name}`);
if (modifier !== undefined) {
let managerFactory = getModifierManager<ModifierManagerDelegate<Opaque>>(modifier.class);
let manager = managerFactory!(owner);

return new CustomModifierDefinition(name, modifier, manager);
}
}

return builtin;
}

private _parseNameForNamespace(_name: string) {
Expand Down Expand Up @@ -326,20 +345,14 @@ export default class RuntimeResolver implements IRuntimeResolver<OwnedTemplateMe
}

if (GLIMMER_CUSTOM_COMPONENT_MANAGER && component && component.class) {
let managerId = getComponentManager(component.class);
if (managerId) {
let manager = this._lookupComponentManager(meta.owner, managerId);
assert(
`Could not find custom component manager '${managerId}' which was specified by ${
component.class
}`,
!!manager
);
let managerFactory = getComponentManager<ManagerDelegate<Opaque>>(component.class);
if (managerFactory) {
let delegate = managerFactory(meta.owner);

let definition = new CustomManagerDefinition(
name,
component,
manager,
delegate,
layout || meta.owner.lookup<OwnedTemplate>(P`template:components/-default`)
);
finalizer();
Expand Down
@@ -1,27 +1,24 @@
import { Owner } from '@ember/-internals/owner';
import { GLIMMER_CUSTOM_COMPONENT_MANAGER } from '@ember/canary-features';
import { Opaque } from '@glimmer/interfaces';
import { getManager, ManagerFactory, setManager } from './managers';

const getPrototypeOf = Object.getPrototypeOf;
const MANAGERS: WeakMap<any, string> = new WeakMap();

export function setComponentManager(managerId: string, obj: any) {
MANAGERS.set(obj, managerId);

return obj;
export function setComponentManager(stringOrFunction: string | ManagerFactory<Opaque>, obj: any) {
let factory;
if (typeof stringOrFunction === 'string') {
factory = function(owner: Owner) {
return owner.lookup(`component-manager:${stringOrFunction}`);
};
} else {
factory = stringOrFunction;
}
return setManager(factory, obj);
}

export function getComponentManager(obj: any): string | undefined {
export function getComponentManager<T>(obj: any): undefined | ManagerFactory<T> {
if (!GLIMMER_CUSTOM_COMPONENT_MANAGER) {
return;
}

let pointer = obj;
while (pointer !== undefined && pointer !== null) {
if (MANAGERS.has(pointer)) {
return MANAGERS.get(pointer);
}

pointer = getPrototypeOf(pointer);
}

return;
return getManager(obj);
}
@@ -0,0 +1,15 @@
import { GLIMMER_MODIFIER_MANAGER } from '@ember/canary-features';
import { Opaque } from '@glimmer/util';
import { getManager, ManagerFactory, setManager } from './managers';

export function setModifierManager(factory: ManagerFactory<Opaque>, obj: any) {
return setManager(factory, obj);
}

export function getModifierManager<T>(obj: any): undefined | ManagerFactory<T> {
if (!GLIMMER_MODIFIER_MANAGER) {
return;
}

return getManager(obj);
}
39 changes: 39 additions & 0 deletions packages/@ember/-internals/glimmer/lib/utils/managers.ts
@@ -0,0 +1,39 @@
import { Owner } from '@ember/-internals/owner';
import { Dict, Opaque } from '@glimmer/interfaces';
import { CapturedArguments } from '@glimmer/runtime';

const MANAGERS: WeakMap<any, ManagerFactory<Opaque>> = new WeakMap();

const getPrototypeOf = Object.getPrototypeOf;

export type ManagerFactory<ManagerDelegate> = (owner: Owner) => ManagerDelegate;

export function setManager<ManagerDelegate>(factory: ManagerFactory<ManagerDelegate>, obj: any) {
MANAGERS.set(obj, factory);
return obj;
}

export function getManager<T>(obj: any): undefined | ManagerFactory<T> {
let pointer = obj;
while (pointer !== undefined && pointer !== null) {
if (MANAGERS.has(pointer)) {
return MANAGERS.get(pointer) as ManagerFactory<T>;
}

pointer = getPrototypeOf(pointer);
}

return;
}

export function valueForCapturedArgs(args: CapturedArguments): ManagerArgs {
return {
named: args.named.value(),
positional: args.positional.value(),
};
}

export interface ManagerArgs {
named: Dict<Opaque>;
positional: Opaque[];
}

0 comments on commit dec37c3

Please sign in to comment.