diff --git a/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts b/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts index 37177b06b2b..b5b56e55291 100644 --- a/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts +++ b/packages/@ember/-internals/glimmer/lib/component-managers/curly.ts @@ -105,6 +105,19 @@ const EMPTY_POSITIONAL_ARGS: VersionedPathReference[] = []; debugFreeze(EMPTY_POSITIONAL_ARGS); +function _setupLazyEventsForComponent(dispatcher: any, component: object) { + // non-interactive rendering (e.g. SSR) has no event dispatcher + if (dispatcher === undefined) { return; } + + let lazyEvents = dispatcher._lazyEvents; + + lazyEvents.forEach((mappedEventName: string, event: string) => { + if (mappedEventName !== null && typeof component[mappedEventName] === 'function') { + dispatcher.setupHandler(event, mappedEventName); + } + }); +} + export default class CurlyComponentManager extends AbstractManager implements @@ -311,6 +324,8 @@ export default class CurlyComponentManager } } + _setupLazyEventsForComponent(environment.eventDispatcher, component); + // Track additional lifecycle metadata about this component in a state bucket. // Essentially we're saving off all the state we'll need in the future. let bucket = new ComponentStateBucket( diff --git a/packages/@ember/-internals/glimmer/lib/component.ts b/packages/@ember/-internals/glimmer/lib/component.ts index d51999a9d39..3d43217a309 100644 --- a/packages/@ember/-internals/glimmer/lib/component.ts +++ b/packages/@ember/-internals/glimmer/lib/component.ts @@ -735,7 +735,7 @@ const Component = CoreView.extend( !this.renderer._destinedForDOM || !(() => { let eventDispatcher = getOwner(this).lookup('event_dispatcher:main'); - let events = (eventDispatcher && eventDispatcher._finalEvents) || {}; + let events = (eventDispatcher && eventDispatcher._finalEventNameMapping) || {}; // tslint:disable-next-line:forin for (let key in events) { diff --git a/packages/@ember/-internals/glimmer/lib/environment.ts b/packages/@ember/-internals/glimmer/lib/environment.ts index fda6bcae98b..13f5bbc2c0f 100644 --- a/packages/@ember/-internals/glimmer/lib/environment.ts +++ b/packages/@ember/-internals/glimmer/lib/environment.ts @@ -35,6 +35,7 @@ export default class Environment extends GlimmerEnvironment { public debugStack: typeof DebugStack; public inTransaction = false; + public eventDispatcher: any; constructor(injections: any) { super(injections); @@ -49,6 +50,10 @@ export default class Environment extends GlimmerEnvironment { if (DEBUG) { this.debugStack = new DebugStack(); } + + if (this.isInteractive) { + this.eventDispatcher = this.owner.lookup('event_dispatcher:main'); + } } // this gets clobbered by installPlatformSpecificProtocolForURL diff --git a/packages/@ember/-internals/glimmer/lib/modifiers/action.ts b/packages/@ember/-internals/glimmer/lib/modifiers/action.ts index 2cab05c658a..68622eb11dd 100644 --- a/packages/@ember/-internals/glimmer/lib/modifiers/action.ts +++ b/packages/@ember/-internals/glimmer/lib/modifiers/action.ts @@ -14,6 +14,7 @@ import { } from '@glimmer/runtime'; import { Destroyable } from '@glimmer/util'; import { INVOKE } from '../utils/references'; +import { Owner } from '@ember/-internals/owner'; const MODIFIERS = ['alt', 'shift', 'meta', 'ctrl']; const POINTER_EVENT_TYPE_REGEX = /^click|mouse|touch/; @@ -187,6 +188,23 @@ export class ActionState { // implements ModifierManager export default class ActionModifierManager implements ModifierManager { + public owner: Owner; + private _setupEventHandler?: (eventName: string) => void; + + + constructor(owner: Owner) { + this.owner = owner; + } + + get setupEventHandler(): (eventName: string) => void { + if (this._setupEventHandler === undefined) { + let dispatcher = this.owner.lookup('event_dispatcher:main'); + this._setupEventHandler = (eventName) => dispatcher['setupHandler'](eventName); + } + + return this._setupEventHandler; + } + create( element: Simple.Element, _state: Opaque, @@ -246,6 +264,8 @@ export default class ActionModifierManager implements ModifierManager { public compiler: LazyCompiler; @@ -102,7 +98,7 @@ export default class RuntimeResolver implements IRuntimeResolver> = new Map(); @@ -114,10 +110,14 @@ export default class RuntimeResolver implements IRuntimeResolver(new CompileTimeLookup(this), this, macros); + + this.builtInModifiers = { + action: { manager: new ActionModifierManager(owner), state: null }, + }; } /*** IRuntimeResolver ***/ diff --git a/packages/@ember/-internals/glimmer/lib/template-compiler.ts b/packages/@ember/-internals/glimmer/lib/template-compiler.ts index 1e67214d7b5..5a88e686d6d 100644 --- a/packages/@ember/-internals/glimmer/lib/template-compiler.ts +++ b/packages/@ember/-internals/glimmer/lib/template-compiler.ts @@ -1,9 +1,12 @@ import { Compiler } from '@glimmer/interfaces'; import RuntimeResolver from './resolver'; +import { getOwner } from '@ember/-internals/owner'; // factory for DI export default { - create(): Compiler { - return new RuntimeResolver().compiler; + create(props: any): Compiler { + let owner = getOwner(props); + + return new RuntimeResolver(owner).compiler; }, }; diff --git a/packages/@ember/-internals/views/lib/system/event_dispatcher.js b/packages/@ember/-internals/views/lib/system/event_dispatcher.js index 3cb5295b3b4..032f306e31d 100644 --- a/packages/@ember/-internals/views/lib/system/event_dispatcher.js +++ b/packages/@ember/-internals/views/lib/system/event_dispatcher.js @@ -127,6 +127,9 @@ export default EmberObject.extend({ ); this._eventHandlers = Object.create(null); + this._finalEventNameMapping = null; + this._sanitizedRootElement = null; + this._lazyEvents = new Map(); }, /** @@ -142,7 +145,8 @@ export default EmberObject.extend({ @param addedEvents {Object} */ setup(addedEvents, _rootElement) { - let events = (this._finalEvents = assign({}, get(this, 'events'), addedEvents)); + let events = (this._finalEventNameMapping = assign({}, get(this, 'events'), addedEvents)); + let lazyEvents = this._lazyEvents; if (_rootElement !== undefined && _rootElement !== null) { set(this, 'rootElement', _rootElement); @@ -216,9 +220,13 @@ export default EmberObject.extend({ } } + // save off the final sanitized root element (for usage in setupHandler) + this._sanitizedRootElement = rootElement; + + // setup event listeners for the non-lazily setup events for (let event in events) { if (events.hasOwnProperty(event)) { - this.setupHandler(rootElement, event, events[event]); + lazyEvents.set(event, events[event]); } } }, @@ -234,12 +242,16 @@ export default EmberObject.extend({ @private @method setupHandler @param {Element} rootElement - @param {String} event the browser-originated event to listen to + @param {String} event the name of the event in the browser @param {String} eventName the name of the method to call on the view */ - setupHandler(rootElement, event, eventName) { - if (eventName === null) { - return; + setupHandler( + event, + eventName = this._finalEventNameMapping[event], + rootElement = this._sanitizedRootElement + ) { + if (eventName === null || !this._lazyEvents.has(event)) { + return; // nothing to do } if (!JQUERY_INTEGRATION || jQueryDisabled) { @@ -417,6 +429,8 @@ export default EmberObject.extend({ } }); } + + this._lazyEvents.delete(event); }, destroy() {