From 64eadfe278318723e623af1d74f3eb993edb2e8e Mon Sep 17 00:00:00 2001 From: Chad Hietala Date: Mon, 22 Oct 2018 15:34:31 -0400 Subject: [PATCH 1/4] [WIP] Modifier Manager --- packages/@ember/-internals/glimmer/index.ts | 1 + .../glimmer/lib/modifiers/custom.ts | 109 +++++++++++ .../@ember/-internals/glimmer/lib/resolver.ts | 36 +++- .../lib/utils/custom-modifier-manager.ts | 32 +++ .../custom-modifier-manager-test.js | 182 ++++++++++++++++++ packages/@ember/canary-features/index.ts | 2 + .../lib/test-cases/abstract-rendering.js | 6 + 7 files changed, 363 insertions(+), 5 deletions(-) create mode 100644 packages/@ember/-internals/glimmer/lib/modifiers/custom.ts create mode 100644 packages/@ember/-internals/glimmer/lib/utils/custom-modifier-manager.ts create mode 100644 packages/@ember/-internals/glimmer/tests/integration/custom-modifier-manager-test.js diff --git a/packages/@ember/-internals/glimmer/index.ts b/packages/@ember/-internals/glimmer/index.ts index c942b1d416d..6697901b6d5 100644 --- a/packages/@ember/-internals/glimmer/index.ts +++ b/packages/@ember/-internals/glimmer/index.ts @@ -306,4 +306,5 @@ 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 { isSerializationFirstNode } from './lib/utils/serialization-first-node-helpers'; diff --git a/packages/@ember/-internals/glimmer/lib/modifiers/custom.ts b/packages/@ember/-internals/glimmer/lib/modifiers/custom.ts new file mode 100644 index 00000000000..b395809dd7c --- /dev/null +++ b/packages/@ember/-internals/glimmer/lib/modifiers/custom.ts @@ -0,0 +1,109 @@ +import { Factory } from '@ember/-internals/owner'; +import { Dict, Opaque } from '@glimmer/interfaces'; +import { Tag } from '@glimmer/reference'; +import { Arguments, CapturedArguments, ModifierManager } from '@glimmer/runtime'; + +export interface CustomModifierDefinitionState { + ModifierClass: Factory; + name: string; + delegate: ModifierManagerDelegate; +} + +// Currently there are no capabilities for modifiers +export function capabilities() { + return {}; +} + +export class CustomModifierDefinition { + public state: CustomModifierDefinitionState; + public manager = CUSTOM_MODIFIER_MANAGER; + constructor( + public name: string, + ModifierClass: Factory, + public delegate: ModifierManagerDelegate + ) { + this.state = { + ModifierClass, + name, + delegate, + }; + } +} + +export class CustomModifierState { + constructor( + public element: Element, + public delegate: ModifierManagerDelegate, + public modifier: ModifierInstance, + public args: CapturedArguments + ) {} + + destroy() { + const { delegate, modifier, args } = this; + let modifierArgs = valueForCapturedArgs(args); + delegate.destroyModifier(modifier, modifierArgs); + } +} + +export interface CustomModifierManagerArgs { + named: Dict; + positional: Opaque[]; +} + +export interface ModifierManagerDelegate { + createModifier(factory: Opaque, args: CustomModifierManagerArgs): ModifierInstance; + installModifier( + instance: ModifierInstance, + element: Element, + args: CustomModifierManagerArgs + ): void; + updateModifier(instance: ModifierInstance, args: CustomModifierManagerArgs): void; + destroyModifier(instance: ModifierInstance, args: CustomModifierManagerArgs): void; +} + +function valueForCapturedArgs(args: CapturedArguments): CustomModifierManagerArgs { + return { + named: args.named.value(), + positional: args.positional.value(), + }; +} + +class CustomModifierManager + implements + ModifierManager< + CustomModifierState, + CustomModifierDefinitionState + > { + create( + element: Element, + definition: CustomModifierDefinitionState, + 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): Tag { + return args.tag; + } + + install(state: CustomModifierState) { + let { element, args, delegate, modifier } = state; + let modifierArgs = valueForCapturedArgs(args); + delegate.installModifier(modifier, element, modifierArgs); + } + + update(state: CustomModifierState) { + let { args, delegate, modifier } = state; + let modifierArgs = valueForCapturedArgs(args); + delegate.updateModifier(modifier, modifierArgs); + } + + getDestructor(state: CustomModifierState) { + return state; + } +} + +const CUSTOM_MODIFIER_MANAGER = new CustomModifierManager(); diff --git a/packages/@ember/-internals/glimmer/lib/resolver.ts b/packages/@ember/-internals/glimmer/lib/resolver.ts index 754c2fc93fc..8df76b995af 100644 --- a/packages/@ember/-internals/glimmer/lib/resolver.ts +++ b/packages/@ember/-internals/glimmer/lib/resolver.ts @@ -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'; @@ -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 } 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) { @@ -175,8 +181,8 @@ export default class RuntimeResolver implements IRuntimeResolver { - return this.handle(this._lookupModifier(name)); + lookupModifier(name: string, meta: OwnedTemplateMeta): Option { + return this.handle(this._lookupModifier(name, meta)); } /** @@ -272,8 +278,28 @@ export default class RuntimeResolver implements IRuntimeResolver ModifierManagerDelegate; + +const MANAGERS: WeakMap = new WeakMap(); + +export function setModifierManager(factory: ModifierManagerFactory, obj: any) { + MANAGERS.set(obj, factory); + return obj; +} + +export function getModifierManager(obj: any): undefined | ModifierManagerFactory { + if (!GLIMMER_MODIFIER_MANAGER) { + return; + } + + let pointer = obj; + while (pointer !== undefined && pointer !== null) { + if (MANAGERS.has(pointer)) { + return MANAGERS.get(pointer); + } + + pointer = getPrototypeOf(pointer); + } + + return; +} diff --git a/packages/@ember/-internals/glimmer/tests/integration/custom-modifier-manager-test.js b/packages/@ember/-internals/glimmer/tests/integration/custom-modifier-manager-test.js new file mode 100644 index 00000000000..e01786ede08 --- /dev/null +++ b/packages/@ember/-internals/glimmer/tests/integration/custom-modifier-manager-test.js @@ -0,0 +1,182 @@ +import { moduleFor, RenderingTest } from '../utils/test-case'; +import { Object as EmberObject } from '@ember/-internals/runtime'; +import { GLIMMER_MODIFIER_MANAGER } from '@ember/canary-features'; +import { setModifierManager } from '@ember/-internals/glimmer'; +import { set } from '@ember/-internals/metal'; + +if (GLIMMER_MODIFIER_MANAGER) { + class ModifierManagerTest extends RenderingTest {} + + class CustomModifierManager { + constructor(owner) { + this.owner = owner; + } + + createModifier(factory, args) { + return factory.create(args); + } + + installModifier(instance, element, args) { + instance.element = element; + let { positional, named } = args; + instance.didInsertElement(positional, named); + } + + updateModifier(instance, args) { + let { positional, named } = args; + instance.didUpdate(positional, named); + } + + destroyModifier(instance) { + instance.willDestroyElement(); + } + } + + moduleFor( + 'Basic Custom Modifier Manager', + class extends ModifierManagerTest { + '@test can register a custom element modifier and render it'(assert) { + let ModifierClass = setModifierManager( + owner => { + return new CustomModifierManager(owner); + }, + EmberObject.extend({ + didInsertElement() {}, + didUpdate() {}, + willDestroyElement() {}, + }) + ); + + this.registerModifier( + 'foo-bar', + ModifierClass.extend({ + didInsertElement() { + assert.ok(true, 'Called didInsertElement'); + }, + }) + ); + + this.render('

hello world

'); + this.assertHTML(`

hello world

`); + } + + '@test custom lifecycle hooks'(assert) { + assert.expect(9); + let ModifierClass = setModifierManager( + owner => { + return new CustomModifierManager(owner); + }, + EmberObject.extend({ + didInsertElement() {}, + didUpdate() {}, + willDestroyElement() {}, + }) + ); + + this.registerModifier( + 'foo-bar', + ModifierClass.extend({ + didUpdate([truthy]) { + assert.ok(true, 'Called didUpdate'); + assert.equal(truthy, 'true', 'gets updated args'); + }, + didInsertElement([truthy]) { + assert.ok(true, 'Called didInsertElement'); + assert.equal(truthy, true, 'gets initial args'); + }, + willDestroyElement() { + assert.ok(true, 'Called willDestroyElement'); + }, + }) + ); + + this.render('{{#if truthy}}

hello world

{{/if}}', { + truthy: true, + }); + this.assertHTML(`

hello world

`); + + this.runTask(() => set(this.context, 'truthy', 'true')); + + this.runTask(() => set(this.context, 'truthy', false)); + + this.runTask(() => set(this.context, 'truthy', true)); + } + + '@test associates manager even through an inheritance structure'(assert) { + assert.expect(5); + let ModifierClass = setModifierManager( + owner => { + return new CustomModifierManager(owner); + }, + EmberObject.extend({ + didInsertElement() {}, + didUpdate() {}, + willDestroyElement() {}, + }) + ); + + ModifierClass = ModifierClass.extend({ + didInsertElement([truthy]) { + this._super(...arguments); + assert.ok(true, 'Called didInsertElement'); + assert.equal(truthy, true, 'gets initial args'); + }, + }); + + this.registerModifier( + 'foo-bar', + ModifierClass.extend({ + didInsertElement([truthy]) { + this._super(...arguments); + assert.ok(true, 'Called didInsertElement'); + assert.equal(truthy, true, 'gets initial args'); + }, + }) + ); + + this.render('

hello world

', { + truthy: true, + }); + this.assertHTML(`

hello world

`); + } + + '@test can give consistent access to underlying DOM element'(assert) { + assert.expect(4); + let ModifierClass = setModifierManager( + owner => { + return new CustomModifierManager(owner); + }, + EmberObject.extend({ + didInsertElement() {}, + didUpdate() {}, + willDestroyElement() {}, + }) + ); + + this.registerModifier( + 'foo-bar', + ModifierClass.extend({ + savedElement: undefined, + didInsertElement() { + assert.equal(this.element.tagName, 'H1'); + this.set('savedElement', this.element); + }, + didUpdate() { + assert.equal(this.element, this.savedElement); + }, + willDestroyElement() { + assert.equal(this.element, this.savedElement); + }, + }) + ); + + this.render('

hello world

', { + truthy: true, + }); + this.assertHTML(`

hello world

`); + + this.runTask(() => set(this.context, 'truthy', 'true')); + } + } + ); +} diff --git a/packages/@ember/canary-features/index.ts b/packages/@ember/canary-features/index.ts index 07627d4a9ed..4a247f167eb 100644 --- a/packages/@ember/canary-features/index.ts +++ b/packages/@ember/canary-features/index.ts @@ -14,6 +14,7 @@ export const DEFAULT_FEATURES = { EMBER_ENGINES_MOUNT_PARAMS: true, EMBER_MODULE_UNIFICATION: null, GLIMMER_CUSTOM_COMPONENT_MANAGER: true, + GLIMMER_MODIFIER_MANAGER: null, EMBER_TEMPLATE_BLOCK_LET_HELPER: true, EMBER_METAL_TRACKED_PROPERTIES: null, EMBER_GLIMMER_ANGLE_BRACKET_INVOCATION: true, @@ -86,3 +87,4 @@ export const EMBER_TEMPLATE_BLOCK_LET_HELPER = featureValue( export const EMBER_GLIMMER_ANGLE_BRACKET_INVOCATION = featureValue( FEATURES.EMBER_GLIMMER_ANGLE_BRACKET_INVOCATION ); +export const GLIMMER_MODIFIER_MANAGER = featureValue(FEATURES.GLIMMER_MODIFIER_MANAGER); diff --git a/packages/internal-test-helpers/lib/test-cases/abstract-rendering.js b/packages/internal-test-helpers/lib/test-cases/abstract-rendering.js index 7fb0a33599f..f32af637b57 100644 --- a/packages/internal-test-helpers/lib/test-cases/abstract-rendering.js +++ b/packages/internal-test-helpers/lib/test-cases/abstract-rendering.js @@ -170,6 +170,12 @@ export default class AbstractRenderingTestCase extends AbstractTestCase { } } + registerModifier(name, ModifierClass) { + let { owner } = this; + + owner.register(`modifier:${name}`, ModifierClass); + } + registerComponentManager(name, manager) { let owner = this.env.owner || this.owner; owner.register(`component-manager:${name}`, manager); From 8bc9483d6e5f5c138246aa2e39e5d7715dcb5f32 Mon Sep 17 00:00:00 2001 From: Chad Hietala Date: Mon, 22 Oct 2018 16:40:18 -0400 Subject: [PATCH 2/4] Share more stuff between custom managers --- packages/@ember/-internals/glimmer/index.ts | 1 + .../glimmer/lib/component-managers/custom.ts | 16 ++------ .../glimmer/lib/modifiers/custom.ts | 34 ++++++---------- .../@ember/-internals/glimmer/lib/resolver.ts | 25 +++--------- .../lib/utils/custom-component-manager.ts | 33 +++++++--------- .../lib/utils/custom-modifier-manager.ts | 27 +++---------- .../-internals/glimmer/lib/utils/managers.ts | 39 +++++++++++++++++++ 7 files changed, 80 insertions(+), 95 deletions(-) create mode 100644 packages/@ember/-internals/glimmer/lib/utils/managers.ts diff --git a/packages/@ember/-internals/glimmer/index.ts b/packages/@ember/-internals/glimmer/index.ts index 6697901b6d5..8cacb65eff4 100644 --- a/packages/@ember/-internals/glimmer/index.ts +++ b/packages/@ember/-internals/glimmer/index.ts @@ -307,4 +307,5 @@ 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'; diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/custom.ts b/packages/@ember/-internals/glimmer/lib/component-managers/custom.ts index 311f2fbaa13..587bce404d0 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/custom.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/custom.ts @@ -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'; @@ -64,15 +65,10 @@ export interface Capabilities { destructor: boolean; } -export interface CustomComponentManagerArgs { - named: Dict; - positional: Opaque[]; -} - export interface ManagerDelegate { 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; } @@ -104,12 +100,6 @@ export interface ComponentArguments { named: Dict; } -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 diff --git a/packages/@ember/-internals/glimmer/lib/modifiers/custom.ts b/packages/@ember/-internals/glimmer/lib/modifiers/custom.ts index b395809dd7c..32c30af833f 100644 --- a/packages/@ember/-internals/glimmer/lib/modifiers/custom.ts +++ b/packages/@ember/-internals/glimmer/lib/modifiers/custom.ts @@ -1,7 +1,8 @@ import { Factory } from '@ember/-internals/owner'; -import { Dict, Opaque } from '@glimmer/interfaces'; +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 { ModifierClass: Factory; @@ -9,8 +10,10 @@ export interface CustomModifierDefinitionState { delegate: ModifierManagerDelegate; } +export interface Capabilities {} + // Currently there are no capabilities for modifiers -export function capabilities() { +export function capabilities(_managerAPI: string, _optionalFeatures?: {}): Capabilities { return {}; } @@ -19,7 +22,7 @@ export class CustomModifierDefinition { public manager = CUSTOM_MODIFIER_MANAGER; constructor( public name: string, - ModifierClass: Factory, + public ModifierClass: Factory, public delegate: ModifierManagerDelegate ) { this.state = { @@ -45,27 +48,12 @@ export class CustomModifierState { } } -export interface CustomModifierManagerArgs { - named: Dict; - positional: Opaque[]; -} - export interface ModifierManagerDelegate { - createModifier(factory: Opaque, args: CustomModifierManagerArgs): ModifierInstance; - installModifier( - instance: ModifierInstance, - element: Element, - args: CustomModifierManagerArgs - ): void; - updateModifier(instance: ModifierInstance, args: CustomModifierManagerArgs): void; - destroyModifier(instance: ModifierInstance, args: CustomModifierManagerArgs): void; -} - -function valueForCapturedArgs(args: CapturedArguments): CustomModifierManagerArgs { - return { - named: args.named.value(), - positional: args.positional.value(), - }; + 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 diff --git a/packages/@ember/-internals/glimmer/lib/resolver.ts b/packages/@ember/-internals/glimmer/lib/resolver.ts index 8df76b995af..0be90ea2d6a 100644 --- a/packages/@ember/-internals/glimmer/lib/resolver.ts +++ b/packages/@ember/-internals/glimmer/lib/resolver.ts @@ -41,7 +41,7 @@ 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 } from './modifiers/custom'; +import { CustomModifierDefinition, ModifierManagerDelegate } from './modifiers/custom'; import { populateMacros } from './syntax'; import { mountHelper } from './syntax/mount'; import { outletHelper } from './syntax/outlet'; @@ -285,14 +285,7 @@ export default class RuntimeResolver implements IRuntimeResolver>(modifier.class); let manager = managerFactory!(owner); return new CustomModifierDefinition(name, modifier, manager); @@ -352,20 +345,14 @@ export default class RuntimeResolver implements IRuntimeResolver>(component.class); + if (managerFactory) { + let delegate = managerFactory(meta.owner); let definition = new CustomManagerDefinition( name, component, - manager, + delegate, layout || meta.owner.lookup(P`template:components/-default`) ); finalizer(); diff --git a/packages/@ember/-internals/glimmer/lib/utils/custom-component-manager.ts b/packages/@ember/-internals/glimmer/lib/utils/custom-component-manager.ts index 5f3f5d56483..f5fd66dda47 100644 --- a/packages/@ember/-internals/glimmer/lib/utils/custom-component-manager.ts +++ b/packages/@ember/-internals/glimmer/lib/utils/custom-component-manager.ts @@ -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 = new WeakMap(); - -export function setComponentManager(managerId: string, obj: any) { - MANAGERS.set(obj, managerId); - - return obj; +export function setComponentManager(stringOrFunction: string | ManagerFactory, 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(obj: any): undefined | ManagerFactory { 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); } diff --git a/packages/@ember/-internals/glimmer/lib/utils/custom-modifier-manager.ts b/packages/@ember/-internals/glimmer/lib/utils/custom-modifier-manager.ts index 8109c57d271..d4d0ff85357 100644 --- a/packages/@ember/-internals/glimmer/lib/utils/custom-modifier-manager.ts +++ b/packages/@ember/-internals/glimmer/lib/utils/custom-modifier-manager.ts @@ -1,32 +1,15 @@ -import { Owner } from '@ember/-internals/owner'; import { GLIMMER_MODIFIER_MANAGER } from '@ember/canary-features'; import { Opaque } from '@glimmer/util'; -import { ModifierManagerDelegate } from '../modifiers/custom'; +import { getManager, ManagerFactory, setManager } from './managers'; -const getPrototypeOf = Object.getPrototypeOf; - -export type ModifierManagerFactory = (owner: Owner) => ModifierManagerDelegate; - -const MANAGERS: WeakMap = new WeakMap(); - -export function setModifierManager(factory: ModifierManagerFactory, obj: any) { - MANAGERS.set(obj, factory); - return obj; +export function setModifierManager(factory: ManagerFactory, obj: any) { + return setManager(factory, obj); } -export function getModifierManager(obj: any): undefined | ModifierManagerFactory { +export function getModifierManager(obj: any): undefined | ManagerFactory { if (!GLIMMER_MODIFIER_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); } diff --git a/packages/@ember/-internals/glimmer/lib/utils/managers.ts b/packages/@ember/-internals/glimmer/lib/utils/managers.ts new file mode 100644 index 00000000000..fc5d05830c6 --- /dev/null +++ b/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> = new WeakMap(); + +const getPrototypeOf = Object.getPrototypeOf; + +export type ManagerFactory = (owner: Owner) => ManagerDelegate; + +export function setManager(factory: ManagerFactory, obj: any) { + MANAGERS.set(obj, factory); + return obj; +} + +export function getManager(obj: any): undefined | ManagerFactory { + let pointer = obj; + while (pointer !== undefined && pointer !== null) { + if (MANAGERS.has(pointer)) { + return MANAGERS.get(pointer) as ManagerFactory; + } + + pointer = getPrototypeOf(pointer); + } + + return; +} + +export function valueForCapturedArgs(args: CapturedArguments): ManagerArgs { + return { + named: args.named.value(), + positional: args.positional.value(), + }; +} + +export interface ManagerArgs { + named: Dict; + positional: Opaque[]; +} From 978a5bc4dfaf2b1aec855ba7ab09e1ac98882402 Mon Sep 17 00:00:00 2001 From: Chad Hietala Date: Tue, 23 Oct 2018 10:58:19 -0400 Subject: [PATCH 3/4] Privately expose capabilties and setModifierManager --- packages/@ember/modifier/index.ts | 1 + packages/ember/index.js | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 packages/@ember/modifier/index.ts diff --git a/packages/@ember/modifier/index.ts b/packages/@ember/modifier/index.ts new file mode 100644 index 00000000000..795f5b60cfe --- /dev/null +++ b/packages/@ember/modifier/index.ts @@ -0,0 +1 @@ +export { setModifierManager, modifierCapabilties as capabilties } from '@ember/-internals/glimmer'; diff --git a/packages/ember/index.js b/packages/ember/index.js index ff4f8cc1efa..7785566e688 100644 --- a/packages/ember/index.js +++ b/packages/ember/index.js @@ -107,6 +107,8 @@ import { TextField, TextArea, isSerializationFirstNode, + setModifierManager, + modifierCapabilties, } from '@ember/-internals/glimmer'; // eslint-disable-next-line import/no-unresolved import VERSION from './version'; @@ -508,6 +510,8 @@ Ember.TextArea = TextArea; Ember.LinkComponent = LinkComponent; Ember._setComponentManager = setComponentManager; Ember._componentManagerCapabilities = capabilities; +Ember._setModifierManager = setModifierManager; +Ember._modifierManagerCapabilties = modifierCapabilties; Ember.Handlebars = { template, Utils: { From 05301603cdb00f800473ca3d8eb941ea63254f07 Mon Sep 17 00:00:00 2001 From: Chad Hietala Date: Tue, 23 Oct 2018 12:27:02 -0400 Subject: [PATCH 4/4] add test for setComponentManager with a factory --- .../custom-component-manager-test.js | 53 ++++++++++++------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/packages/@ember/-internals/glimmer/tests/integration/custom-component-manager-test.js b/packages/@ember/-internals/glimmer/tests/integration/custom-component-manager-test.js index dfb7ac9367d..60a5907fced 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/custom-component-manager-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/custom-component-manager-test.js @@ -8,28 +8,27 @@ import { import { setComponentManager, capabilities } from '@ember/-internals/glimmer'; if (GLIMMER_CUSTOM_COMPONENT_MANAGER) { - class ComponentManagerTest extends RenderingTest { - constructor(assert) { - super(...arguments); + let BasicComponentManager = EmberObject.extend({ + capabilities: capabilities('3.4'), - this.registerComponentManager( - 'basic', - EmberObject.extend({ - capabilities: capabilities('3.4'), + createComponent(factory, args) { + return factory.create({ args }); + }, - createComponent(factory, args) { - return factory.create({ args }); - }, + updateComponent(component, args) { + set(component, 'args', args); + }, - updateComponent(component, args) { - set(component, 'args', args); - }, + getContext(component) { + return component; + }, + }); - getContext(component) { - return component; - }, - }) - ); + class ComponentManagerTest extends RenderingTest { + constructor(assert) { + super(...arguments); + + this.registerComponentManager('basic', BasicComponentManager); this.registerComponentManager( 'instrumented-full', @@ -94,6 +93,24 @@ if (GLIMMER_CUSTOM_COMPONENT_MANAGER) { this.assertHTML(`

hello world

`); } + ['@test it can render a basic component with custom component manager with a factory']() { + let ComponentClass = setComponentManager( + () => BasicComponentManager.create(), + EmberObject.extend({ + greeting: 'hello', + }) + ); + + this.registerComponent('foo-bar', { + template: `

{{greeting}} world

`, + ComponentClass, + }); + + this.render('{{foo-bar}}'); + + this.assertHTML(`

hello world

`); + } + ['@test it can have no template context']() { this.registerComponentManager( 'pseudo-template-only',