/*globals process */ var enifed, requireModule, Ember; // Used in @ember/-internals/environment/lib/global.js mainContext = this; // eslint-disable-line no-undef (function() { function missingModule(name, referrerName) { if (referrerName) { throw new Error('Could not find module ' + name + ' required by: ' + referrerName); } else { throw new Error('Could not find module ' + name); } } function internalRequire(_name, referrerName) { var name = _name; var mod = registry[name]; if (!mod) { name = name + '/index'; mod = registry[name]; } var exports = seen[name]; if (exports !== undefined) { return exports; } exports = seen[name] = {}; if (!mod) { missingModule(_name, referrerName); } var deps = mod.deps; var callback = mod.callback; var reified = new Array(deps.length); for (var i = 0; i < deps.length; i++) { if (deps[i] === 'exports') { reified[i] = exports; } else if (deps[i] === 'require') { reified[i] = requireModule; } else { reified[i] = internalRequire(deps[i], name); } } callback.apply(this, reified); return exports; } var isNode = typeof window === 'undefined' && typeof process !== 'undefined' && {}.toString.call(process) === '[object process]'; if (!isNode) { Ember = this.Ember = this.Ember || {}; } if (typeof Ember === 'undefined') { Ember = {}; } if (typeof Ember.__loader === 'undefined') { var registry = Object.create(null); var seen = Object.create(null); enifed = function(name, deps, callback) { var value = {}; if (!callback) { value.deps = []; value.callback = deps; } else { value.deps = deps; value.callback = callback; } registry[name] = value; }; requireModule = function(name) { return internalRequire(name, null); }; // setup `require` module requireModule['default'] = requireModule; requireModule.has = function registryHas(moduleName) { return Boolean(registry[moduleName]) || Boolean(registry[moduleName + '/index']); }; requireModule._eak_seen = registry; Ember.__loader = { define: enifed, require: requireModule, registry: registry, }; } else { enifed = Ember.__loader.define; requireModule = Ember.__loader.require; } })(); enifed("@ember/-internals/browser-environment/index", ["exports", "@ember/-internals/browser-environment/lib/has-dom"], function (_exports, _hasDom) { "use strict"; _exports.__esModule = true; _exports.isFirefox = _exports.isChrome = _exports.userAgent = _exports.history = _exports.location = _exports.window = _exports.hasDOM = void 0; _exports.hasDOM = _hasDom.default; const window = _hasDom.default ? self : null; _exports.window = window; const location = _hasDom.default ? self.location : null; _exports.location = location; const history = _hasDom.default ? self.history : null; _exports.history = history; const userAgent = _hasDom.default ? self.navigator.userAgent : 'Lynx (textmode)'; _exports.userAgent = userAgent; const isChrome = _hasDom.default ? Boolean(window.chrome) && !window.opera : false; _exports.isChrome = isChrome; const isFirefox = _hasDom.default ? typeof InstallTrigger !== 'undefined' : false; _exports.isFirefox = isFirefox; }); enifed("@ember/-internals/browser-environment/lib/has-dom", ["exports"], function (_exports) { "use strict"; _exports.__esModule = true; _exports.default = void 0; // check if window exists and actually is the global var _default = typeof self === 'object' && self !== null && self.Object === Object && typeof Window !== 'undefined' && self.constructor === Window && typeof document === 'object' && document !== null && self.document === document && typeof location === 'object' && location !== null && self.location === location && typeof history === 'object' && history !== null && self.history === history && typeof navigator === 'object' && navigator !== null && self.navigator === navigator && typeof navigator.userAgent === 'string'; _exports.default = _default; }); enifed("@ember/-internals/console/index", ["exports", "@ember/debug", "@ember/deprecated-features"], function (_exports, _debug, _deprecatedFeatures) { "use strict"; _exports.__esModule = true; _exports.default = void 0; // Deliver message that the function is deprecated const DEPRECATION_MESSAGE = 'Use of Ember.Logger is deprecated. Please use `console` for logging.'; const DEPRECATION_ID = 'ember-console.deprecate-logger'; const DEPRECATION_URL = 'https://emberjs.com/deprecations/v3.x#toc_use-console-rather-than-ember-logger'; /** @module ember */ /** Inside Ember-Metal, simply uses the methods from `imports.console`. Override this to provide more robust logging functionality. @class Logger @deprecated Use 'console' instead @namespace Ember @public */ let DEPRECATED_LOGGER; if (_deprecatedFeatures.LOGGER) { DEPRECATED_LOGGER = { /** Logs the arguments to the console. You can pass as many arguments as you want and they will be joined together with a space. ```javascript var foo = 1; Ember.Logger.log('log value of foo:', foo); // "log value of foo: 1" will be printed to the console ``` @method log @for Ember.Logger @param {*} arguments @public */ log() { (0, _debug.deprecate)(DEPRECATION_MESSAGE, false, { id: DEPRECATION_ID, until: '4.0.0', url: DEPRECATION_URL }); return console.log(...arguments); // eslint-disable-line no-console }, /** Prints the arguments to the console with a warning icon. You can pass as many arguments as you want and they will be joined together with a space. ```javascript Ember.Logger.warn('Something happened!'); // "Something happened!" will be printed to the console with a warning icon. ``` @method warn @for Ember.Logger @param {*} arguments @public */ warn() { (0, _debug.deprecate)(DEPRECATION_MESSAGE, false, { id: DEPRECATION_ID, until: '4.0.0', url: DEPRECATION_URL }); return console.warn(...arguments); // eslint-disable-line no-console }, /** Prints the arguments to the console with an error icon, red text and a stack trace. You can pass as many arguments as you want and they will be joined together with a space. ```javascript Ember.Logger.error('Danger! Danger!'); // "Danger! Danger!" will be printed to the console in red text. ``` @method error @for Ember.Logger @param {*} arguments @public */ error() { (0, _debug.deprecate)(DEPRECATION_MESSAGE, false, { id: DEPRECATION_ID, until: '4.0.0', url: DEPRECATION_URL }); return console.error(...arguments); // eslint-disable-line no-console }, /** Logs the arguments to the console. You can pass as many arguments as you want and they will be joined together with a space. ```javascript var foo = 1; Ember.Logger.info('log value of foo:', foo); // "log value of foo: 1" will be printed to the console ``` @method info @for Ember.Logger @param {*} arguments @public */ info() { (0, _debug.deprecate)(DEPRECATION_MESSAGE, false, { id: DEPRECATION_ID, until: '4.0.0', url: DEPRECATION_URL }); return console.info(...arguments); // eslint-disable-line no-console }, /** Logs the arguments to the console in blue text. You can pass as many arguments as you want and they will be joined together with a space. ```javascript var foo = 1; Ember.Logger.debug('log value of foo:', foo); // "log value of foo: 1" will be printed to the console ``` @method debug @for Ember.Logger @param {*} arguments @public */ debug() { (0, _debug.deprecate)(DEPRECATION_MESSAGE, false, { id: DEPRECATION_ID, until: '4.0.0', url: DEPRECATION_URL }); /* eslint-disable no-console */ if (console.debug) { return console.debug(...arguments); } return console.info(...arguments); /* eslint-enable no-console */ }, /** If the value passed into `Ember.Logger.assert` is not truthy it will throw an error with a stack trace. ```javascript Ember.Logger.assert(true); // undefined Ember.Logger.assert(true === false); // Throws an Assertion failed error. Ember.Logger.assert(true === false, 'Something invalid'); // Throws an Assertion failed error with message. ``` @method assert @for Ember.Logger @param {Boolean} bool Value to test @param {String} message Assertion message on failed @public */ assert() { (0, _debug.deprecate)(DEPRECATION_MESSAGE, false, { id: DEPRECATION_ID, until: '4.0.0', url: DEPRECATION_URL }); return console.assert(...arguments); // eslint-disable-line no-console } }; } var _default = DEPRECATED_LOGGER; _exports.default = _default; }); enifed("@ember/-internals/container/index", ["exports", "@ember/-internals/container/lib/registry", "@ember/-internals/container/lib/container"], function (_exports, _registry, _container) { "use strict"; _exports.__esModule = true; _exports.FACTORY_FOR = _exports.privatize = _exports.Container = _exports.Registry = void 0; _exports.Registry = _registry.default; _exports.privatize = _registry.privatize; _exports.Container = _container.default; _exports.FACTORY_FOR = _container.FACTORY_FOR; }); enifed("@ember/-internals/container/lib/container", ["exports", "@ember/-internals/owner", "@ember/-internals/utils", "@ember/canary-features", "@ember/debug", "@ember/polyfills", "@glimmer/env"], function (_exports, _owner, _utils, _canaryFeatures, _debug, _polyfills, _env) { "use strict"; _exports.__esModule = true; _exports.FACTORY_FOR = _exports.default = void 0; let leakTracking; let containers; if (_env.DEBUG) { // requires v8 // chrome --js-flags="--allow-natives-syntax --expose-gc" // node --allow-natives-syntax --expose-gc try { if (typeof gc === 'function') { leakTracking = (() => { // avoid syntax errors when --allow-natives-syntax not present let GetWeakSetValues = new Function('weakSet', 'return %GetWeakSetValues(weakSet, 0)'); containers = new WeakSet(); return { hasContainers() { gc(); return GetWeakSetValues(containers).length > 0; }, reset() { let values = GetWeakSetValues(containers); for (let i = 0; i < values.length; i++) { containers.delete(values[i]); } } }; })(); } } catch (e) {// ignore } } /** A container used to instantiate and cache objects. Every `Container` must be associated with a `Registry`, which is referenced to determine the factory and options that should be used to instantiate objects. The public API for `Container` is still in flux and should not be considered stable. @private @class Container */ class Container { constructor(registry, options = {}) { this.registry = registry; this.owner = options.owner || null; this.cache = (0, _utils.dictionary)(options.cache || null); this.factoryManagerCache = (0, _utils.dictionary)(options.factoryManagerCache || null); this.isDestroyed = false; this.isDestroying = false; if (_env.DEBUG) { this.validationCache = (0, _utils.dictionary)(options.validationCache || null); if (containers !== undefined) { containers.add(this); } } } /** @private @property registry @type Registry @since 1.11.0 */ /** @private @property cache @type InheritingDict */ /** @private @property validationCache @type InheritingDict */ /** Given a fullName return a corresponding instance. The default behavior is for lookup to return a singleton instance. The singleton is scoped to the container, allowing multiple containers to all have their own locally scoped singletons. ```javascript let registry = new Registry(); let container = registry.container(); registry.register('api:twitter', Twitter); let twitter = container.lookup('api:twitter'); twitter instanceof Twitter; // => true // by default the container will return singletons let twitter2 = container.lookup('api:twitter'); twitter2 instanceof Twitter; // => true twitter === twitter2; //=> true ``` If singletons are not wanted, an optional flag can be provided at lookup. ```javascript let registry = new Registry(); let container = registry.container(); registry.register('api:twitter', Twitter); let twitter = container.lookup('api:twitter', { singleton: false }); let twitter2 = container.lookup('api:twitter', { singleton: false }); twitter === twitter2; //=> false ``` @private @method lookup @param {String} fullName @param {Object} [options] @param {String} [options.source] The fullname of the request source (used for local lookup) @return {any} */ lookup(fullName, options) { (0, _debug.assert)('expected container not to be destroyed', !this.isDestroyed); (0, _debug.assert)('fullName must be a proper full name', this.registry.isValidFullName(fullName)); return lookup(this, this.registry.normalize(fullName), options); } /** A depth first traversal, destroying the container, its descendant containers and all their managed objects. @private @method destroy */ destroy() { destroyDestroyables(this); this.isDestroying = true; } finalizeDestroy() { resetCache(this); this.isDestroyed = true; } /** Clear either the entire cache or just the cache for a particular key. @private @method reset @param {String} fullName optional key to reset; if missing, resets everything */ reset(fullName) { if (this.isDestroyed) return; if (fullName === undefined) { destroyDestroyables(this); resetCache(this); } else { resetMember(this, this.registry.normalize(fullName)); } } /** Returns an object that can be used to provide an owner to a manually created instance. @private @method ownerInjection @returns { Object } */ ownerInjection() { return { [_owner.OWNER]: this.owner }; } /** Given a fullName, return the corresponding factory. The consumer of the factory is responsible for the destruction of any factory instances, as there is no way for the container to ensure instances are destroyed when it itself is destroyed. @public @method factoryFor @param {String} fullName @param {Object} [options] @param {String} [options.source] The fullname of the request source (used for local lookup) @return {any} */ factoryFor(fullName, options = {}) { (0, _debug.assert)('expected container not to be destroyed', !this.isDestroyed); let normalizedName = this.registry.normalize(fullName); (0, _debug.assert)('fullName must be a proper full name', this.registry.isValidFullName(normalizedName)); (0, _debug.assert)('EMBER_MODULE_UNIFICATION must be enabled to pass a namespace option to factoryFor', _canaryFeatures.EMBER_MODULE_UNIFICATION || !options.namespace); if (options.source || options.namespace) { normalizedName = this.registry.expandLocalLookup(fullName, options); if (!normalizedName) { return; } } return factoryFor(this, normalizedName, fullName); } } _exports.default = Container; if (_env.DEBUG) { Container._leakTracking = leakTracking; } /* * Wrap a factory manager in a proxy which will not permit properties to be * set on the manager. */ function wrapManagerInDeprecationProxy(manager) { if (_utils.HAS_NATIVE_PROXY) { let validator = { set(_obj, prop) { throw new Error("You attempted to set \"" + prop + "\" on a factory manager created by container#factoryFor. A factory manager is a read-only construct."); } }; // Note: // We have to proxy access to the manager here so that private property // access doesn't cause the above errors to occur. let m = manager; let proxiedManager = { class: m.class, create(props) { return m.create(props); } }; let proxy = new Proxy(proxiedManager, validator); FACTORY_FOR.set(proxy, manager); } return manager; } function isSingleton(container, fullName) { return container.registry.getOption(fullName, 'singleton') !== false; } function isInstantiatable(container, fullName) { return container.registry.getOption(fullName, 'instantiate') !== false; } function lookup(container, fullName, options = {}) { (0, _debug.assert)('EMBER_MODULE_UNIFICATION must be enabled to pass a namespace option to lookup', _canaryFeatures.EMBER_MODULE_UNIFICATION || !options.namespace); let normalizedName = fullName; if (options.source || options.namespace) { normalizedName = container.registry.expandLocalLookup(fullName, options); if (!normalizedName) { return; } } if (options.singleton !== false) { let cached = container.cache[normalizedName]; if (cached !== undefined) { return cached; } } return instantiateFactory(container, normalizedName, fullName, options); } function factoryFor(container, normalizedName, fullName) { let cached = container.factoryManagerCache[normalizedName]; if (cached !== undefined) { return cached; } let factory = container.registry.resolve(normalizedName); if (factory === undefined) { return; } if (_env.DEBUG && factory && typeof factory._onLookup === 'function') { factory._onLookup(fullName); } let manager = new FactoryManager(container, factory, fullName, normalizedName); if (_env.DEBUG) { manager = wrapManagerInDeprecationProxy(manager); } container.factoryManagerCache[normalizedName] = manager; return manager; } function isSingletonClass(container, fullName, { instantiate, singleton }) { return singleton !== false && !instantiate && isSingleton(container, fullName) && !isInstantiatable(container, fullName); } function isSingletonInstance(container, fullName, { instantiate, singleton }) { return singleton !== false && instantiate !== false && isSingleton(container, fullName) && isInstantiatable(container, fullName); } function isFactoryClass(container, fullname, { instantiate, singleton }) { return instantiate === false && (singleton === false || !isSingleton(container, fullname)) && !isInstantiatable(container, fullname); } function isFactoryInstance(container, fullName, { instantiate, singleton }) { return instantiate !== false && (singleton !== false || isSingleton(container, fullName)) && isInstantiatable(container, fullName); } function instantiateFactory(container, normalizedName, fullName, options) { let factoryManager = factoryFor(container, normalizedName, fullName); if (factoryManager === undefined) { return; } // SomeClass { singleton: true, instantiate: true } | { singleton: true } | { instantiate: true } | {} // By default majority of objects fall into this case if (isSingletonInstance(container, fullName, options)) { return container.cache[normalizedName] = factoryManager.create(); } // SomeClass { singleton: false, instantiate: true } if (isFactoryInstance(container, fullName, options)) { return factoryManager.create(); } // SomeClass { singleton: true, instantiate: false } | { instantiate: false } | { singleton: false, instantiation: false } if (isSingletonClass(container, fullName, options) || isFactoryClass(container, fullName, options)) { return factoryManager.class; } throw new Error('Could not create factory'); } function processInjections(container, injections, result) { if (_env.DEBUG) { container.registry.validateInjections(injections); } let hash = result.injections; if (hash === undefined) { hash = result.injections = {}; } for (let i = 0; i < injections.length; i++) { let { property, specifier, source } = injections[i]; if (source) { hash[property] = lookup(container, specifier, { source }); } else { hash[property] = lookup(container, specifier); } if (!result.isDynamic) { result.isDynamic = !isSingleton(container, specifier); } } } function buildInjections(container, typeInjections, injections) { let result = { injections: undefined, isDynamic: false }; if (typeInjections !== undefined) { processInjections(container, typeInjections, result); } if (injections !== undefined) { processInjections(container, injections, result); } return result; } function injectionsFor(container, fullName) { let registry = container.registry; let [type] = fullName.split(':'); let typeInjections = registry.getTypeInjections(type); let injections = registry.getInjections(fullName); return buildInjections(container, typeInjections, injections); } function destroyDestroyables(container) { let cache = container.cache; let keys = Object.keys(cache); for (let i = 0; i < keys.length; i++) { let key = keys[i]; let value = cache[key]; if (value.destroy) { value.destroy(); } } } function resetCache(container) { container.cache = (0, _utils.dictionary)(null); container.factoryManagerCache = (0, _utils.dictionary)(null); } function resetMember(container, fullName) { let member = container.cache[fullName]; delete container.factoryManagerCache[fullName]; if (member) { delete container.cache[fullName]; if (member.destroy) { member.destroy(); } } } const FACTORY_FOR = new WeakMap(); _exports.FACTORY_FOR = FACTORY_FOR; class FactoryManager { constructor(container, factory, fullName, normalizedName) { this.container = container; this.owner = container.owner; this.class = factory; this.fullName = fullName; this.normalizedName = normalizedName; this.madeToString = undefined; this.injections = undefined; FACTORY_FOR.set(this, this); } toString() { if (this.madeToString === undefined) { this.madeToString = this.container.registry.makeToString(this.class, this.fullName); } return this.madeToString; } create(options) { let injectionsCache = this.injections; if (injectionsCache === undefined) { let { injections, isDynamic } = injectionsFor(this.container, this.normalizedName); injectionsCache = injections; if (!isDynamic) { this.injections = injections; } } let props = injectionsCache; if (options !== undefined) { props = (0, _polyfills.assign)({}, injectionsCache, options); } if (_env.DEBUG) { let lazyInjections; let validationCache = this.container.validationCache; // Ensure that all lazy injections are valid at instantiation time if (!validationCache[this.fullName] && this.class && typeof this.class._lazyInjections === 'function') { lazyInjections = this.class._lazyInjections(); lazyInjections = this.container.registry.normalizeInjectionsHash(lazyInjections); this.container.registry.validateInjections(lazyInjections); } validationCache[this.fullName] = true; } if (!this.class.create) { throw new Error("Failed to create an instance of '" + this.normalizedName + "'. Most likely an improperly defined class or" + " an invalid module export."); } // required to allow access to things like // the customized toString, _debugContainerKey, // owner, etc. without a double extend and without // modifying the objects properties if (typeof this.class._initFactory === 'function') { this.class._initFactory(this); } else { // in the non-EmberObject case we need to still setOwner // this is required for supporting glimmer environment and // template instantiation which rely heavily on // `options[OWNER]` being passed into `create` // TODO: clean this up, and remove in future versions if (options === undefined || props === undefined) { // avoid mutating `props` here since they are the cached injections props = (0, _polyfills.assign)({}, props); } (0, _owner.setOwner)(props, this.owner); } let instance = this.class.create(props); FACTORY_FOR.set(instance, this); return instance; } } }); enifed("@ember/-internals/container/lib/registry", ["exports", "@ember/-internals/utils", "@ember/debug", "@ember/polyfills", "@glimmer/env", "@ember/-internals/container/lib/container"], function (_exports, _utils, _debug, _polyfills, _env, _container) { "use strict"; _exports.__esModule = true; _exports.privatize = privatize; _exports.default = void 0; const VALID_FULL_NAME_REGEXP = /^[^:]+:[^:]+$/; /** A registry used to store factory and option information keyed by type. A `Registry` stores the factory and option information needed by a `Container` to instantiate and cache objects. The API for `Registry` is still in flux and should not be considered stable. @private @class Registry @since 1.11.0 */ class Registry { constructor(options = {}) { this.fallback = options.fallback || null; this.resolver = options.resolver || null; this.registrations = (0, _utils.dictionary)(options.registrations || null); this._typeInjections = (0, _utils.dictionary)(null); this._injections = (0, _utils.dictionary)(null); this._localLookupCache = Object.create(null); this._normalizeCache = (0, _utils.dictionary)(null); this._resolveCache = (0, _utils.dictionary)(null); this._failSet = new Set(); this._options = (0, _utils.dictionary)(null); this._typeOptions = (0, _utils.dictionary)(null); } /** A backup registry for resolving registrations when no matches can be found. @private @property fallback @type Registry */ /** An object that has a `resolve` method that resolves a name. @private @property resolver @type Resolver */ /** @private @property registrations @type InheritingDict */ /** @private @property _typeInjections @type InheritingDict */ /** @private @property _injections @type InheritingDict */ /** @private @property _normalizeCache @type InheritingDict */ /** @private @property _resolveCache @type InheritingDict */ /** @private @property _options @type InheritingDict */ /** @private @property _typeOptions @type InheritingDict */ /** Creates a container based on this registry. @private @method container @param {Object} options @return {Container} created container */ container(options) { return new _container.default(this, options); } /** Registers a factory for later injection. Example: ```javascript let registry = new Registry(); registry.register('model:user', Person, {singleton: false }); registry.register('fruit:favorite', Orange); registry.register('communication:main', Email, {singleton: false}); ``` @private @method register @param {String} fullName @param {Function} factory @param {Object} options */ register(fullName, factory, options = {}) { (0, _debug.assert)('fullName must be a proper full name', this.isValidFullName(fullName)); (0, _debug.assert)("Attempting to register an unknown factory: '" + fullName + "'", factory !== undefined); let normalizedName = this.normalize(fullName); (0, _debug.assert)("Cannot re-register: '" + fullName + "', as it has already been resolved.", !this._resolveCache[normalizedName]); this._failSet.delete(normalizedName); this.registrations[normalizedName] = factory; this._options[normalizedName] = options; } /** Unregister a fullName ```javascript let registry = new Registry(); registry.register('model:user', User); registry.resolve('model:user').create() instanceof User //=> true registry.unregister('model:user') registry.resolve('model:user') === undefined //=> true ``` @private @method unregister @param {String} fullName */ unregister(fullName) { (0, _debug.assert)('fullName must be a proper full name', this.isValidFullName(fullName)); let normalizedName = this.normalize(fullName); this._localLookupCache = Object.create(null); delete this.registrations[normalizedName]; delete this._resolveCache[normalizedName]; delete this._options[normalizedName]; this._failSet.delete(normalizedName); } /** Given a fullName return the corresponding factory. By default `resolve` will retrieve the factory from the registry. ```javascript let registry = new Registry(); registry.register('api:twitter', Twitter); registry.resolve('api:twitter') // => Twitter ``` Optionally the registry can be provided with a custom resolver. If provided, `resolve` will first provide the custom resolver the opportunity to resolve the fullName, otherwise it will fallback to the registry. ```javascript let registry = new Registry(); registry.resolver = function(fullName) { // lookup via the module system of choice }; // the twitter factory is added to the module system registry.resolve('api:twitter') // => Twitter ``` @private @method resolve @param {String} fullName @param {Object} [options] @param {String} [options.source] the fullname of the request source (used for local lookups) @return {Function} fullName's factory */ resolve(fullName, options) { let factory = resolve(this, this.normalize(fullName), options); if (factory === undefined && this.fallback !== null) { factory = this.fallback.resolve(...arguments); } return factory; } /** A hook that can be used to describe how the resolver will attempt to find the factory. For example, the default Ember `.describe` returns the full class name (including namespace) where Ember's resolver expects to find the `fullName`. @private @method describe @param {String} fullName @return {string} described fullName */ describe(fullName) { if (this.resolver !== null && this.resolver.lookupDescription) { return this.resolver.lookupDescription(fullName); } else if (this.fallback !== null) { return this.fallback.describe(fullName); } else { return fullName; } } /** A hook to enable custom fullName normalization behavior @private @method normalizeFullName @param {String} fullName @return {string} normalized fullName */ normalizeFullName(fullName) { if (this.resolver !== null && this.resolver.normalize) { return this.resolver.normalize(fullName); } else if (this.fallback !== null) { return this.fallback.normalizeFullName(fullName); } else { return fullName; } } /** Normalize a fullName based on the application's conventions @private @method normalize @param {String} fullName @return {string} normalized fullName */ normalize(fullName) { return this._normalizeCache[fullName] || (this._normalizeCache[fullName] = this.normalizeFullName(fullName)); } /** @method makeToString @private @param {any} factory @param {string} fullName @return {function} toString function */ makeToString(factory, fullName) { if (this.resolver !== null && this.resolver.makeToString) { return this.resolver.makeToString(factory, fullName); } else if (this.fallback !== null) { return this.fallback.makeToString(factory, fullName); } else { return factory.toString(); } } /** Given a fullName check if the container is aware of its factory or singleton instance. @private @method has @param {String} fullName @param {Object} [options] @param {String} [options.source] the fullname of the request source (used for local lookups) @return {Boolean} */ has(fullName, options) { if (!this.isValidFullName(fullName)) { return false; } let source = options && options.source && this.normalize(options.source); let namespace = options && options.namespace || undefined; return has(this, this.normalize(fullName), source, namespace); } /** Allow registering options for all factories of a type. ```javascript let registry = new Registry(); let container = registry.container(); // if all of type `connection` must not be singletons registry.optionsForType('connection', { singleton: false }); registry.register('connection:twitter', TwitterConnection); registry.register('connection:facebook', FacebookConnection); let twitter = container.lookup('connection:twitter'); let twitter2 = container.lookup('connection:twitter'); twitter === twitter2; // => false let facebook = container.lookup('connection:facebook'); let facebook2 = container.lookup('connection:facebook'); facebook === facebook2; // => false ``` @private @method optionsForType @param {String} type @param {Object} options */ optionsForType(type, options) { this._typeOptions[type] = options; } getOptionsForType(type) { let optionsForType = this._typeOptions[type]; if (optionsForType === undefined && this.fallback !== null) { optionsForType = this.fallback.getOptionsForType(type); } return optionsForType; } /** @private @method options @param {String} fullName @param {Object} options */ options(fullName, options) { let normalizedName = this.normalize(fullName); this._options[normalizedName] = options; } getOptions(fullName) { let normalizedName = this.normalize(fullName); let options = this._options[normalizedName]; if (options === undefined && this.fallback !== null) { options = this.fallback.getOptions(fullName); } return options; } getOption(fullName, optionName) { let options = this._options[fullName]; if (options !== undefined && options[optionName] !== undefined) { return options[optionName]; } let type = fullName.split(':')[0]; options = this._typeOptions[type]; if (options && options[optionName] !== undefined) { return options[optionName]; } else if (this.fallback !== null) { return this.fallback.getOption(fullName, optionName); } return undefined; } /** Used only via `injection`. Provides a specialized form of injection, specifically enabling all objects of one type to be injected with a reference to another object. For example, provided each object of type `controller` needed a `router`. one would do the following: ```javascript let registry = new Registry(); let container = registry.container(); registry.register('router:main', Router); registry.register('controller:user', UserController); registry.register('controller:post', PostController); registry.typeInjection('controller', 'router', 'router:main'); let user = container.lookup('controller:user'); let post = container.lookup('controller:post'); user.router instanceof Router; //=> true post.router instanceof Router; //=> true // both controllers share the same router user.router === post.router; //=> true ``` @private @method typeInjection @param {String} type @param {String} property @param {String} fullName */ typeInjection(type, property, fullName) { (0, _debug.assert)('fullName must be a proper full name', this.isValidFullName(fullName)); let fullNameType = fullName.split(':')[0]; (0, _debug.assert)("Cannot inject a '" + fullName + "' on other " + type + "(s).", fullNameType !== type); let injections = this._typeInjections[type] || (this._typeInjections[type] = []); injections.push({ property, specifier: fullName }); } /** Defines injection rules. These rules are used to inject dependencies onto objects when they are instantiated. Two forms of injections are possible: * Injecting one fullName on another fullName * Injecting one fullName on a type Example: ```javascript let registry = new Registry(); let container = registry.container(); registry.register('source:main', Source); registry.register('model:user', User); registry.register('model:post', Post); // injecting one fullName on another fullName // eg. each user model gets a post model registry.injection('model:user', 'post', 'model:post'); // injecting one fullName on another type registry.injection('model', 'source', 'source:main'); let user = container.lookup('model:user'); let post = container.lookup('model:post'); user.source instanceof Source; //=> true post.source instanceof Source; //=> true user.post instanceof Post; //=> true // and both models share the same source user.source === post.source; //=> true ``` @private @method injection @param {String} factoryName @param {String} property @param {String} injectionName */ injection(fullName, property, injectionName) { (0, _debug.assert)("Invalid injectionName, expected: 'type:name' got: " + injectionName, this.isValidFullName(injectionName)); let normalizedInjectionName = this.normalize(injectionName); if (fullName.indexOf(':') === -1) { return this.typeInjection(fullName, property, normalizedInjectionName); } (0, _debug.assert)('fullName must be a proper full name', this.isValidFullName(fullName)); let normalizedName = this.normalize(fullName); let injections = this._injections[normalizedName] || (this._injections[normalizedName] = []); injections.push({ property, specifier: normalizedInjectionName }); } /** @private @method knownForType @param {String} type the type to iterate over */ knownForType(type) { let localKnown = (0, _utils.dictionary)(null); let registeredNames = Object.keys(this.registrations); for (let index = 0; index < registeredNames.length; index++) { let fullName = registeredNames[index]; let itemType = fullName.split(':')[0]; if (itemType === type) { localKnown[fullName] = true; } } let fallbackKnown, resolverKnown; if (this.fallback !== null) { fallbackKnown = this.fallback.knownForType(type); } if (this.resolver !== null && this.resolver.knownForType) { resolverKnown = this.resolver.knownForType(type); } return (0, _polyfills.assign)({}, fallbackKnown, localKnown, resolverKnown); } isValidFullName(fullName) { return VALID_FULL_NAME_REGEXP.test(fullName); } getInjections(fullName) { let injections = this._injections[fullName]; if (this.fallback !== null) { let fallbackInjections = this.fallback.getInjections(fullName); if (fallbackInjections !== undefined) { injections = injections === undefined ? fallbackInjections : injections.concat(fallbackInjections); } } return injections; } getTypeInjections(type) { let injections = this._typeInjections[type]; if (this.fallback !== null) { let fallbackInjections = this.fallback.getTypeInjections(type); if (fallbackInjections !== undefined) { injections = injections === undefined ? fallbackInjections : injections.concat(fallbackInjections); } } return injections; } /** Given a fullName and a source fullName returns the fully resolved fullName. Used to allow for local lookup. ```javascript let registry = new Registry(); // the twitter factory is added to the module system registry.expandLocalLookup('component:post-title', { source: 'template:post' }) // => component:post/post-title ``` @private @method expandLocalLookup @param {String} fullName @param {Object} [options] @param {String} [options.source] the fullname of the request source (used for local lookups) @return {String} fullName */ expandLocalLookup(fullName, options) { if (this.resolver !== null && this.resolver.expandLocalLookup) { (0, _debug.assert)('fullName must be a proper full name', this.isValidFullName(fullName)); (0, _debug.assert)('options.source must be a proper full name', !options.source || this.isValidFullName(options.source)); let normalizedFullName = this.normalize(fullName); let normalizedSource = this.normalize(options.source); return expandLocalLookup(this, normalizedFullName, normalizedSource, options.namespace); } else if (this.fallback !== null) { return this.fallback.expandLocalLookup(fullName, options); } else { return null; } } } _exports.default = Registry; if (_env.DEBUG) { const proto = Registry.prototype; proto.normalizeInjectionsHash = function (hash) { let injections = []; for (let key in hash) { if (hash.hasOwnProperty(key)) { let { specifier, source, namespace } = hash[key]; (0, _debug.assert)("Expected a proper full name, given '" + specifier + "'", this.isValidFullName(specifier)); injections.push({ property: key, specifier, source, namespace }); } } return injections; }; proto.validateInjections = function (injections) { if (!injections) { return; } for (let i = 0; i < injections.length; i++) { let { specifier, source, namespace } = injections[i]; (0, _debug.assert)("Attempting to inject an unknown injection: '" + specifier + "'", this.has(specifier, { source, namespace })); } }; } function expandLocalLookup(registry, normalizedName, normalizedSource, namespace) { let cache = registry._localLookupCache; let normalizedNameCache = cache[normalizedName]; if (!normalizedNameCache) { normalizedNameCache = cache[normalizedName] = Object.create(null); } let cacheKey = namespace || normalizedSource; let cached = normalizedNameCache[cacheKey]; if (cached !== undefined) { return cached; } let expanded = registry.resolver.expandLocalLookup(normalizedName, normalizedSource, namespace); return normalizedNameCache[cacheKey] = expanded; } function resolve(registry, _normalizedName, options) { let normalizedName = _normalizedName; // when `source` is provided expand normalizedName // and source into the full normalizedName if (options !== undefined && (options.source || options.namespace)) { normalizedName = registry.expandLocalLookup(_normalizedName, options); if (!normalizedName) { return; } } let cached = registry._resolveCache[normalizedName]; if (cached !== undefined) { return cached; } if (registry._failSet.has(normalizedName)) { return; } let resolved; if (registry.resolver) { resolved = registry.resolver.resolve(normalizedName); } if (resolved === undefined) { resolved = registry.registrations[normalizedName]; } if (resolved === undefined) { registry._failSet.add(normalizedName); } else { registry._resolveCache[normalizedName] = resolved; } return resolved; } function has(registry, fullName, source, namespace) { return registry.resolve(fullName, { source, namespace }) !== undefined; } const privateNames = (0, _utils.dictionary)(null); const privateSuffix = ("" + Math.random() + Date.now()).replace('.', ''); function privatize([fullName]) { let name = privateNames[fullName]; if (name) { return name; } let [type, rawName] = fullName.split(':'); return privateNames[fullName] = (0, _utils.intern)(type + ":" + rawName + "-" + privateSuffix); } }); enifed("@ember/-internals/container/tests/container_test", ["@ember/-internals/owner", "@ember/polyfills", "@ember/canary-features", "@glimmer/env", "@ember/-internals/container", "internal-test-helpers"], function (_owner, _polyfills, _canaryFeatures, _env, _container, _internalTestHelpers) { "use strict"; (0, _internalTestHelpers.moduleFor)('Container', class extends _internalTestHelpers.AbstractTestCase { ['@test A registered factory returns the same instance each time'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = (0, _internalTestHelpers.factory)(); registry.register('controller:post', PostController); let postController = container.lookup('controller:post'); assert.ok(postController instanceof PostController, 'The lookup is an instance of the factory'); assert.equal(postController, container.lookup('controller:post')); } ['@test uses create time injections if factory has no extend'](assert) { let registry = new _container.Registry(); let container = registry.container(); let AppleController = (0, _internalTestHelpers.factory)(); let PostController = (0, _internalTestHelpers.factory)(); PostController.extend = undefined; // remove extend registry.register('controller:apple', AppleController); registry.register('controller:post', PostController); registry.injection('controller:post', 'apple', 'controller:apple'); let postController = container.lookup('controller:post'); assert.ok(postController.apple instanceof AppleController, 'instance receives an apple of instance AppleController'); } ['@test A registered factory returns a fresh instance if singleton: false is passed as an option'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = (0, _internalTestHelpers.factory)(); registry.register('controller:post', PostController); let postController1 = container.lookup('controller:post'); let postController2 = container.lookup('controller:post', { singleton: false }); let postController3 = container.lookup('controller:post', { singleton: false }); let postController4 = container.lookup('controller:post'); assert.equal(postController1.toString(), postController4.toString(), 'Singleton factories looked up normally return the same value'); assert.notEqual(postController1.toString(), postController2.toString(), 'Singleton factories are not equal to factories looked up with singleton: false'); assert.notEqual(postController2.toString(), postController3.toString(), 'Two factories looked up with singleton: false are not equal'); assert.notEqual(postController3.toString(), postController4.toString(), 'A singleton factory looked up after a factory called with singleton: false is not equal'); assert.ok(postController1 instanceof PostController, 'All instances are instances of the registered factory'); assert.ok(postController2 instanceof PostController, 'All instances are instances of the registered factory'); assert.ok(postController3 instanceof PostController, 'All instances are instances of the registered factory'); assert.ok(postController4 instanceof PostController, 'All instances are instances of the registered factory'); } ["@test A factory type with a registered injection's instances receive that injection"](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = (0, _internalTestHelpers.factory)(); let Store = (0, _internalTestHelpers.factory)(); registry.register('controller:post', PostController); registry.register('store:main', Store); registry.typeInjection('controller', 'store', 'store:main'); let postController = container.lookup('controller:post'); let store = container.lookup('store:main'); assert.equal(postController.store, store); } ['@test An individual factory with a registered injection receives the injection'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = (0, _internalTestHelpers.factory)(); let Store = (0, _internalTestHelpers.factory)(); registry.register('controller:post', PostController); registry.register('store:main', Store); registry.injection('controller:post', 'store', 'store:main'); let postController = container.lookup('controller:post'); let store = container.lookup('store:main'); assert.equal(postController.store, store, 'has the correct store injected'); } ['@test A factory with both type and individual injections'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = (0, _internalTestHelpers.factory)(); let Store = (0, _internalTestHelpers.factory)(); let Router = (0, _internalTestHelpers.factory)(); registry.register('controller:post', PostController); registry.register('store:main', Store); registry.register('router:main', Router); registry.injection('controller:post', 'store', 'store:main'); registry.typeInjection('controller', 'router', 'router:main'); let postController = container.lookup('controller:post'); let store = container.lookup('store:main'); let router = container.lookup('router:main'); assert.equal(postController.store, store); assert.equal(postController.router, router); } ['@test A non-singleton instance is never cached'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostView = (0, _internalTestHelpers.factory)(); registry.register('view:post', PostView, { singleton: false }); let postView1 = container.lookup('view:post'); let postView2 = container.lookup('view:post'); assert.ok(postView1 !== postView2, 'Non-singletons are not cached'); } ['@test A non-instantiated property is not instantiated'](assert) { let registry = new _container.Registry(); let container = registry.container(); let template = function () {}; registry.register('template:foo', template, { instantiate: false }); assert.equal(container.lookup('template:foo'), template); } ['@test A failed lookup returns undefined'](assert) { let registry = new _container.Registry(); let container = registry.container(); assert.equal(container.lookup('doesnot:exist'), undefined); } ['@test An invalid factory throws an error'](assert) { let registry = new _container.Registry(); let container = registry.container(); registry.register('controller:foo', {}); assert.throws(() => { container.lookup('controller:foo'); }, /Failed to create an instance of \'controller:foo\'/); } ['@test Injecting a failed lookup raises an error']() { let registry = new _container.Registry(); let container = registry.container(); let fooInstance = {}; let fooFactory = {}; let Foo = { create() { return fooInstance; }, extend() { return fooFactory; } }; registry.register('model:foo', Foo); registry.injection('model:foo', 'store', 'store:main'); expectAssertion(() => { container.lookup('model:foo'); }); } ['@test Injecting a falsy value does not raise an error'](assert) { let registry = new _container.Registry(); let container = registry.container(); let ApplicationController = (0, _internalTestHelpers.factory)(); registry.register('controller:application', ApplicationController); registry.register('user:current', null, { instantiate: false }); registry.injection('controller:application', 'currentUser', 'user:current'); assert.strictEqual(container.lookup('controller:application').currentUser, null); } ['@test The container returns same value each time even if the value is falsy'](assert) { let registry = new _container.Registry(); let container = registry.container(); registry.register('falsy:value', null, { instantiate: false }); assert.strictEqual(container.lookup('falsy:value'), container.lookup('falsy:value')); } ['@test Destroying the container destroys any cached singletons'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = (0, _internalTestHelpers.factory)(); let PostView = (0, _internalTestHelpers.factory)(); let template = function () {}; registry.register('controller:post', PostController); registry.register('view:post', PostView, { singleton: false }); registry.register('template:post', template, { instantiate: false }); registry.injection('controller:post', 'postView', 'view:post'); let postController = container.lookup('controller:post'); let postView = postController.postView; assert.ok(postView instanceof PostView, 'The non-singleton was injected'); container.destroy(); assert.ok(postController.isDestroyed, 'Singletons are destroyed'); assert.ok(!postView.isDestroyed, 'Non-singletons are not destroyed'); } ['@test The container can use a registry hook to resolve factories lazily'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = (0, _internalTestHelpers.factory)(); registry.resolver = { resolve(fullName) { if (fullName === 'controller:post') { return PostController; } } }; let postController = container.lookup('controller:post'); assert.ok(postController instanceof PostController, 'The correct factory was provided'); } ['@test The container normalizes names before resolving'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = (0, _internalTestHelpers.factory)(); registry.normalizeFullName = function () { return 'controller:post'; }; registry.register('controller:post', PostController); let postController = container.lookup('controller:normalized'); assert.ok(postController instanceof PostController, 'Normalizes the name before resolving'); } ['@test The container normalizes names when looking factory up'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = (0, _internalTestHelpers.factory)(); registry.normalizeFullName = function () { return 'controller:post'; }; registry.register('controller:post', PostController); let fact = container.factoryFor('controller:normalized'); let factInstance = fact.create(); assert.ok(factInstance instanceof PostController, 'Normalizes the name'); } ['@test Options can be registered that should be applied to a given factory'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostView = (0, _internalTestHelpers.factory)(); registry.resolver = { resolve(fullName) { if (fullName === 'view:post') { return PostView; } } }; registry.options('view:post', { instantiate: true, singleton: false }); let postView1 = container.lookup('view:post'); let postView2 = container.lookup('view:post'); assert.ok(postView1 instanceof PostView, 'The correct factory was provided'); assert.ok(postView2 instanceof PostView, 'The correct factory was provided'); assert.ok(postView1 !== postView2, 'The two lookups are different'); } ['@test Options can be registered that should be applied to all factories for a given type'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostView = (0, _internalTestHelpers.factory)(); registry.resolver = { resolve(fullName) { if (fullName === 'view:post') { return PostView; } } }; registry.optionsForType('view', { singleton: false }); let postView1 = container.lookup('view:post'); let postView2 = container.lookup('view:post'); assert.ok(postView1 instanceof PostView, 'The correct factory was provided'); assert.ok(postView2 instanceof PostView, 'The correct factory was provided'); assert.ok(postView1 !== postView2, 'The two lookups are different'); } ['@test An injected non-singleton instance is never cached'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostView = (0, _internalTestHelpers.factory)(); let PostViewHelper = (0, _internalTestHelpers.factory)(); registry.register('view:post', PostView, { singleton: false }); registry.register('view_helper:post', PostViewHelper, { singleton: false }); registry.injection('view:post', 'viewHelper', 'view_helper:post'); let postView1 = container.lookup('view:post'); let postView2 = container.lookup('view:post'); assert.ok(postView1.viewHelper !== postView2.viewHelper, 'Injected non-singletons are not cached'); } ['@test Factory resolves are cached'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = (0, _internalTestHelpers.factory)(); let resolveWasCalled = []; registry.resolve = function (fullName) { resolveWasCalled.push(fullName); return PostController; }; assert.deepEqual(resolveWasCalled, []); container.factoryFor('controller:post'); assert.deepEqual(resolveWasCalled, ['controller:post']); container.factoryFor('controller:post'); assert.deepEqual(resolveWasCalled, ['controller:post']); } ['@test factory for non extendables (MODEL) resolves are cached'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = (0, _internalTestHelpers.factory)(); let resolveWasCalled = []; registry.resolve = function (fullName) { resolveWasCalled.push(fullName); return PostController; }; assert.deepEqual(resolveWasCalled, []); container.factoryFor('model:post'); assert.deepEqual(resolveWasCalled, ['model:post']); container.factoryFor('model:post'); assert.deepEqual(resolveWasCalled, ['model:post']); } ['@test factory for non extendables resolves are cached'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = {}; let resolveWasCalled = []; registry.resolve = function (fullName) { resolveWasCalled.push(fullName); return PostController; }; assert.deepEqual(resolveWasCalled, []); container.factoryFor('foo:post'); assert.deepEqual(resolveWasCalled, ['foo:post']); container.factoryFor('foo:post'); assert.deepEqual(resolveWasCalled, ['foo:post']); } ["@test A factory's lazy injections are validated when first instantiated"]() { let registry = new _container.Registry(); let container = registry.container(); let Apple = (0, _internalTestHelpers.factory)(); let Orange = (0, _internalTestHelpers.factory)(); Apple.reopenClass({ _lazyInjections() { return [{ specifier: 'orange:main' }, { specifier: 'banana:main' }]; } }); registry.register('apple:main', Apple); registry.register('orange:main', Orange); expectAssertion(() => { container.lookup('apple:main'); }, /Attempting to inject an unknown injection: 'banana:main'/); } ['@test Lazy injection validations are cached'](assert) { if (!_env.DEBUG) { assert.expect(0); return; } assert.expect(1); let registry = new _container.Registry(); let container = registry.container(); let Apple = (0, _internalTestHelpers.factory)(); let Orange = (0, _internalTestHelpers.factory)(); Apple.reopenClass({ _lazyInjections: () => { assert.ok(true, 'should call lazy injection method'); return [{ specifier: 'orange:main' }]; } }); registry.register('apple:main', Apple); registry.register('orange:main', Orange); container.lookup('apple:main'); container.lookup('apple:main'); } ['@test An object with its owner pre-set should be returned from ownerInjection'](assert) { let owner = {}; let registry = new _container.Registry(); let container = registry.container({ owner }); let result = container.ownerInjection(); assert.equal(result[_owner.OWNER], owner, 'owner is properly included'); } ['@test lookup passes options through to expandlocallookup'](assert) { let registry = new _container.Registry(); let container = registry.container(); let PostController = (0, _internalTestHelpers.factory)(); registry.register('controller:post', PostController); registry.expandLocalLookup = (fullName, options) => { assert.ok(true, 'expandLocalLookup was called'); assert.equal(fullName, 'foo:bar'); assert.deepEqual(options, { source: 'baz:qux' }); return 'controller:post'; }; let PostControllerLookupResult = container.lookup('foo:bar', { source: 'baz:qux' }); assert.ok(PostControllerLookupResult instanceof PostController); } ['@test #factoryFor class is registered class'](assert) { let registry = new _container.Registry(); let container = registry.container(); let Component = (0, _internalTestHelpers.factory)(); registry.register('component:foo-bar', Component); let factoryManager = container.factoryFor('component:foo-bar'); assert.deepEqual(factoryManager.class, Component, 'No double extend'); } ['@test #factoryFor must supply a fullname']() { let registry = new _container.Registry(); let container = registry.container(); expectAssertion(() => { container.factoryFor('chad-bar'); }, /fullName must be a proper full name/); } ['@test #factoryFor returns a factory manager'](assert) { let registry = new _container.Registry(); let container = registry.container(); let Component = (0, _internalTestHelpers.factory)(); registry.register('component:foo-bar', Component); let factoryManager = container.factoryFor('component:foo-bar'); assert.ok(factoryManager.create); assert.ok(factoryManager.class); } ['@test #factoryFor returns a cached factory manager for the same type'](assert) { let registry = new _container.Registry(); let container = registry.container(); let Component = (0, _internalTestHelpers.factory)(); registry.register('component:foo-bar', Component); registry.register('component:baz-bar', Component); let factoryManager1 = container.factoryFor('component:foo-bar'); let factoryManager2 = container.factoryFor('component:foo-bar'); let factoryManager3 = container.factoryFor('component:baz-bar'); assert.equal(factoryManager1, factoryManager2, 'cache hit'); assert.notEqual(factoryManager1, factoryManager3, 'cache miss'); } ['@test #factoryFor class returns the factory function'](assert) { let registry = new _container.Registry(); let container = registry.container(); let Component = (0, _internalTestHelpers.factory)(); registry.register('component:foo-bar', Component); let factoryManager = container.factoryFor('component:foo-bar'); assert.deepEqual(factoryManager.class, Component, 'No double extend'); } ['@test #factoryFor instance have a common parent'](assert) { let registry = new _container.Registry(); let container = registry.container(); let Component = (0, _internalTestHelpers.factory)(); registry.register('component:foo-bar', Component); let factoryManager1 = container.factoryFor('component:foo-bar'); let factoryManager2 = container.factoryFor('component:foo-bar'); let instance1 = factoryManager1.create({ foo: 'foo' }); let instance2 = factoryManager2.create({ bar: 'bar' }); assert.deepEqual(instance1.constructor, instance2.constructor); } ['@test can properly reset cache'](assert) { let registry = new _container.Registry(); let container = registry.container(); let Component = (0, _internalTestHelpers.factory)(); registry.register('component:foo-bar', Component); let factory1 = container.factoryFor('component:foo-bar'); let factory2 = container.factoryFor('component:foo-bar'); let instance1 = container.lookup('component:foo-bar'); let instance2 = container.lookup('component:foo-bar'); assert.equal(instance1, instance2); assert.equal(factory1, factory2); container.reset(); let factory3 = container.factoryFor('component:foo-bar'); let instance3 = container.lookup('component:foo-bar'); assert.notEqual(instance1, instance3); assert.notEqual(factory1, factory3); } ['@test #factoryFor created instances come with instance injections'](assert) { let registry = new _container.Registry(); let container = registry.container(); let Component = (0, _internalTestHelpers.factory)(); let Ajax = (0, _internalTestHelpers.factory)(); registry.register('component:foo-bar', Component); registry.register('util:ajax', Ajax); registry.injection('component:foo-bar', 'ajax', 'util:ajax'); let componentFactory = container.factoryFor('component:foo-bar'); let component = componentFactory.create(); assert.ok(component.ajax); assert.ok(component.ajax instanceof Ajax); } ['@test #factoryFor options passed to create clobber injections'](assert) { let registry = new _container.Registry(); let container = registry.container(); let Component = (0, _internalTestHelpers.factory)(); let Ajax = (0, _internalTestHelpers.factory)(); registry.register('component:foo-bar', Component); registry.register('util:ajax', Ajax); registry.injection('component:foo-bar', 'ajax', 'util:ajax'); let componentFactory = container.factoryFor('component:foo-bar'); let instrance = componentFactory.create({ ajax: 'fetch' }); assert.equal(instrance.ajax, 'fetch'); } ['@test #factoryFor does not add properties to the object being instantiated when _initFactory is present'](assert) { let registry = new _container.Registry(); let container = registry.container(); class Component { static _initFactory() {} static create(options) { let instance = new this(); (0, _polyfills.assign)(instance, options); return instance; } } registry.register('component:foo-bar', Component); let componentFactory = container.factoryFor('component:foo-bar'); let instance = componentFactory.create(); // note: _guid and isDestroyed are being set in the `factory` constructor // not via registry/container shenanigans assert.deepEqual(Object.keys(instance), []); } ["@test assert when calling lookup after destroy on a container"](assert) { let registry = new _container.Registry(); let container = registry.container(); let Component = (0, _internalTestHelpers.factory)(); registry.register('component:foo-bar', Component); let instance = container.lookup('component:foo-bar'); assert.ok(instance, 'precond lookup successful'); (0, _internalTestHelpers.runTask)(() => { container.destroy(); container.finalizeDestroy(); }); expectAssertion(() => { container.lookup('component:foo-bar'); }); } ["@test assert when calling factoryFor after destroy on a container"](assert) { let registry = new _container.Registry(); let container = registry.container(); let Component = (0, _internalTestHelpers.factory)(); registry.register('component:foo-bar', Component); let instance = container.factoryFor('component:foo-bar'); assert.ok(instance, 'precond lookup successful'); (0, _internalTestHelpers.runTask)(() => { container.destroy(); container.finalizeDestroy(); }); expectAssertion(() => { container.factoryFor('component:foo-bar'); }); } // this is skipped until templates and the glimmer environment do not require `OWNER` to be // passed in as constructor args ['@skip #factoryFor does not add properties to the object being instantiated'](assert) { let registry = new _container.Registry(); let container = registry.container(); class Component { static create(options) { let instance = new this(); (0, _polyfills.assign)(instance, options); return instance; } } registry.register('component:foo-bar', Component); let componentFactory = container.factoryFor('component:foo-bar'); let instance = componentFactory.create(); // note: _guid and isDestroyed are being set in the `factory` constructor // not via registry/container shenanigans assert.deepEqual(Object.keys(instance), []); } }); if (_canaryFeatures.EMBER_MODULE_UNIFICATION) { (0, _internalTestHelpers.moduleFor)('Container module unification', class extends _internalTestHelpers.AbstractTestCase { ['@test The container can expand and resolve a source to factoryFor'](assert) { let PrivateComponent = (0, _internalTestHelpers.factory)(); let lookup = 'component:my-input'; let expectedSource = 'template:routes/application'; let registry = new _container.Registry(); let resolveCount = 0; let expandedKey = 'boom, special expanded key'; registry.expandLocalLookup = (specifier, options) => { this.assert.strictEqual(specifier, lookup, 'specifier is expanded'); this.assert.strictEqual(options.source, expectedSource, 'source is expanded'); return expandedKey; }; registry.resolve = function (fullName) { resolveCount++; if (fullName === expandedKey) { return PrivateComponent; } }; let container = registry.container(); assert.strictEqual(container.factoryFor(lookup, { source: expectedSource }).class, PrivateComponent, 'The correct factory was provided'); assert.strictEqual(container.factoryFor(lookup, { source: expectedSource }).class, PrivateComponent, 'The correct factory was provided again'); assert.equal(resolveCount, 1, 'resolve called only once and a cached factory was returned the second time'); } ['@test The container can expand and resolve a source to lookup']() { let PrivateComponent = (0, _internalTestHelpers.factory)(); let lookup = 'component:my-input'; let expectedSource = 'template:routes/application'; let registry = new _container.Registry(); let expandedKey = 'boom, special expanded key'; registry.expandLocalLookup = (specifier, options) => { this.assert.strictEqual(specifier, lookup, 'specifier is expanded'); this.assert.strictEqual(options.source, expectedSource, 'source is expanded'); return expandedKey; }; registry.resolve = function (fullName) { if (fullName === expandedKey) { return PrivateComponent; } }; let container = registry.container(); let result = container.lookup(lookup, { source: expectedSource }); this.assert.ok(result instanceof PrivateComponent, 'The correct factory was provided'); this.assert.ok(container.cache[expandedKey] instanceof PrivateComponent, 'The correct factory was stored in the cache with the correct key which includes the source.'); } ['@test The container can expand and resolve a namespace to factoryFor'](assert) { let PrivateComponent = (0, _internalTestHelpers.factory)(); let lookup = 'component:my-input'; let expectedNamespace = 'my-addon'; let registry = new _container.Registry(); let resolveCount = 0; let expandedKey = 'boom, special expanded key'; registry.expandLocalLookup = (specifier, options) => { this.assert.strictEqual(specifier, lookup, 'specifier is expanded'); this.assert.strictEqual(options.namespace, expectedNamespace, 'namespace is expanded'); return expandedKey; }; registry.resolve = function (fullName) { resolveCount++; if (fullName === expandedKey) { return PrivateComponent; } }; let container = registry.container(); assert.strictEqual(container.factoryFor(lookup, { namespace: expectedNamespace }).class, PrivateComponent, 'The correct factory was provided'); assert.strictEqual(container.factoryFor(lookup, { namespace: expectedNamespace }).class, PrivateComponent, 'The correct factory was provided again'); assert.equal(resolveCount, 1, 'resolve called only once and a cached factory was returned the second time'); } ['@test The container can expand and resolve a namespace to lookup']() { let PrivateComponent = (0, _internalTestHelpers.factory)(); let lookup = 'component:my-input'; let expectedNamespace = 'my-addon'; let registry = new _container.Registry(); let expandedKey = 'boom, special expanded key'; registry.expandLocalLookup = (specifier, options) => { this.assert.strictEqual(specifier, lookup, 'specifier is expanded'); this.assert.strictEqual(options.namespace, expectedNamespace, 'namespace is expanded'); return expandedKey; }; registry.resolve = function (fullName) { if (fullName === expandedKey) { return PrivateComponent; } }; let container = registry.container(); let result = container.lookup(lookup, { namespace: expectedNamespace }); this.assert.ok(result instanceof PrivateComponent, 'The correct factory was provided'); this.assert.ok(container.cache[expandedKey] instanceof PrivateComponent, 'The correct factory was stored in the cache with the correct key which includes the source.'); } }); } }); enifed("@ember/-internals/container/tests/owner_test", ["@ember/-internals/owner", "internal-test-helpers"], function (_owner, _internalTestHelpers) { "use strict"; (0, _internalTestHelpers.moduleFor)('Owner', class extends _internalTestHelpers.AbstractTestCase { ['@test An owner can be set with `setOwner` and retrieved with `getOwner`'](assert) { let owner = {}; let obj = {}; assert.strictEqual((0, _owner.getOwner)(obj), undefined, 'owner has not been set'); (0, _owner.setOwner)(obj, owner); assert.strictEqual((0, _owner.getOwner)(obj), owner, 'owner has been set'); assert.strictEqual(obj[_owner.OWNER], owner, 'owner has been set to the OWNER symbol'); } }); }); enifed("@ember/-internals/container/tests/registry_test", ["@ember/-internals/container", "internal-test-helpers", "@ember/canary-features"], function (_container, _internalTestHelpers, _canaryFeatures) { "use strict"; (0, _internalTestHelpers.moduleFor)('Registry', class extends _internalTestHelpers.AbstractTestCase { ['@test A registered factory is returned from resolve'](assert) { let registry = new _container.Registry(); let PostController = (0, _internalTestHelpers.factory)(); registry.register('controller:post', PostController); let PostControllerFactory = registry.resolve('controller:post'); assert.ok(PostControllerFactory, 'factory is returned'); assert.ok(PostControllerFactory.create() instanceof PostController, 'The return of factory.create is an instance of PostController'); } ['@test The registered factory returned from resolve is the same factory each time'](assert) { let registry = new _container.Registry(); let PostController = (0, _internalTestHelpers.factory)(); registry.register('controller:post', PostController); assert.deepEqual(registry.resolve('controller:post'), registry.resolve('controller:post'), 'The return of resolve is always the same'); } ['@test The registered value returned from resolve is the same value each time even if the value is falsy'](assert) { let registry = new _container.Registry(); registry.register('falsy:value', null, { instantiate: false }); assert.strictEqual(registry.resolve('falsy:value'), registry.resolve('falsy:value'), 'The return of resolve is always the same'); } ['@test The value returned from resolver is the same value as the original value even if the value is falsy'](assert) { let resolver = { resolve(fullName) { if (fullName === 'falsy:value') { return null; } } }; let registry = new _container.Registry({ resolver }); assert.strictEqual(registry.resolve('falsy:value'), null); } ['@test A registered factory returns true for `has` if an item is registered'](assert) { let registry = new _container.Registry(); let PostController = (0, _internalTestHelpers.factory)(); registry.register('controller:post', PostController); assert.equal(registry.has('controller:post'), true, 'The `has` method returned true for registered factories'); assert.equal(registry.has('controller:posts'), false, 'The `has` method returned false for unregistered factories'); } ['@test Throw exception when trying to inject `type:thing` on all type(s)']() { let registry = new _container.Registry(); let PostController = (0, _internalTestHelpers.factory)(); registry.register('controller:post', PostController); expectAssertion(() => { registry.typeInjection('controller', 'injected', 'controller:post'); }, /Cannot inject a 'controller:post' on other controller\(s\)\./); } ['@test The registry can take a hook to resolve factories lazily'](assert) { let PostController = (0, _internalTestHelpers.factory)(); let resolver = { resolve(fullName) { if (fullName === 'controller:post') { return PostController; } } }; let registry = new _container.Registry({ resolver }); assert.strictEqual(registry.resolve('controller:post'), PostController, 'The correct factory was provided'); } ['@test The registry respects the resolver hook for `has`'](assert) { let PostController = (0, _internalTestHelpers.factory)(); let resolver = { resolve(fullName) { if (fullName === 'controller:post') { return PostController; } } }; let registry = new _container.Registry({ resolver }); assert.ok(registry.has('controller:post'), 'the `has` method uses the resolver hook'); } ['@test The registry normalizes names when resolving'](assert) { let registry = new _container.Registry(); let PostController = (0, _internalTestHelpers.factory)(); registry.normalizeFullName = function () { return 'controller:post'; }; registry.register('controller:post', PostController); let type = registry.resolve('controller:normalized'); assert.strictEqual(type, PostController, 'Normalizes the name when resolving'); } ['@test The registry normalizes names when checking if the factory is registered'](assert) { let registry = new _container.Registry(); let PostController = (0, _internalTestHelpers.factory)(); registry.normalizeFullName = function (fullName) { return fullName === 'controller:normalized' ? 'controller:post' : fullName; }; registry.register('controller:post', PostController); let isPresent = registry.has('controller:normalized'); assert.equal(isPresent, true, 'Normalizes the name when checking if the factory or instance is present'); } ['@test The registry normalizes names when injecting'](assert) { let registry = new _container.Registry(); let PostController = (0, _internalTestHelpers.factory)(); let user = { name: 'Stef' }; registry.normalize = function () { return 'controller:post'; }; registry.register('controller:post', PostController); registry.register('user:post', user, { instantiate: false }); registry.injection('controller:post', 'user', 'controller:normalized'); assert.deepEqual(registry.resolve('controller:post'), user, 'Normalizes the name when injecting'); } ['@test cannot register an `undefined` factory']() { let registry = new _container.Registry(); expectAssertion(() => { registry.register('controller:apple', undefined); }, ''); } ['@test can re-register a factory'](assert) { let registry = new _container.Registry(); let FirstApple = (0, _internalTestHelpers.factory)('first'); let SecondApple = (0, _internalTestHelpers.factory)('second'); registry.register('controller:apple', FirstApple); registry.register('controller:apple', SecondApple); assert.ok(registry.resolve('controller:apple').create() instanceof SecondApple); } ['@test cannot re-register a factory if it has been resolved'](assert) { let registry = new _container.Registry(); let FirstApple = (0, _internalTestHelpers.factory)('first'); let SecondApple = (0, _internalTestHelpers.factory)('second'); registry.register('controller:apple', FirstApple); assert.strictEqual(registry.resolve('controller:apple'), FirstApple); expectAssertion(function () { registry.register('controller:apple', SecondApple); }, /Cannot re-register: 'controller:apple', as it has already been resolved\./); assert.strictEqual(registry.resolve('controller:apple'), FirstApple); } ['@test registry.has should not accidentally cause injections on that factory to be run. (Mitigate merely on observing)'](assert) { assert.expect(1); let registry = new _container.Registry(); let FirstApple = (0, _internalTestHelpers.factory)('first'); let SecondApple = (0, _internalTestHelpers.factory)('second'); SecondApple.extend = function () { assert.ok(false, 'should not extend or touch the injected model, merely to inspect existence of another'); }; registry.register('controller:apple', FirstApple); registry.register('controller:second-apple', SecondApple); registry.injection('controller:apple', 'badApple', 'controller:second-apple'); assert.ok(registry.has('controller:apple')); } ['@test registry.has should not error for invalid fullNames'](assert) { let registry = new _container.Registry(); assert.ok(!registry.has('foo:bar:baz')); } ['@test once resolved, always return the same result'](assert) { let registry = new _container.Registry(); registry.resolver = { resolve() { return 'bar'; } }; let Bar = registry.resolve('models:bar'); registry.resolver = { resolve() { return 'not bar'; } }; assert.equal(registry.resolve('models:bar'), Bar); } ['@test factory resolves are cached'](assert) { let registry = new _container.Registry(); let PostController = (0, _internalTestHelpers.factory)(); let resolveWasCalled = []; registry.resolver = { resolve(fullName) { resolveWasCalled.push(fullName); return PostController; } }; assert.deepEqual(resolveWasCalled, []); registry.resolve('controller:post'); assert.deepEqual(resolveWasCalled, ['controller:post']); registry.resolve('controller:post'); assert.deepEqual(resolveWasCalled, ['controller:post']); } ['@test factory for non extendables (MODEL) resolves are cached'](assert) { let registry = new _container.Registry(); let PostController = (0, _internalTestHelpers.factory)(); let resolveWasCalled = []; registry.resolver = { resolve(fullName) { resolveWasCalled.push(fullName); return PostController; } }; assert.deepEqual(resolveWasCalled, []); registry.resolve('model:post'); assert.deepEqual(resolveWasCalled, ['model:post']); registry.resolve('model:post'); assert.deepEqual(resolveWasCalled, ['model:post']); } ['@test factory for non extendables resolves are cached'](assert) { let registry = new _container.Registry(); let PostController = {}; let resolveWasCalled = []; registry.resolver = { resolve(fullName) { resolveWasCalled.push(fullName); return PostController; } }; assert.deepEqual(resolveWasCalled, []); registry.resolve('foo:post'); assert.deepEqual(resolveWasCalled, ['foo:post']); registry.resolve('foo:post'); assert.deepEqual(resolveWasCalled, ['foo:post']); } ['@test registry.container creates a container'](assert) { let registry = new _container.Registry(); let PostController = (0, _internalTestHelpers.factory)(); registry.register('controller:post', PostController); let container = registry.container(); let postController = container.lookup('controller:post'); assert.ok(postController instanceof PostController, 'The lookup is an instance of the registered factory'); } ['@test `describe` will be handled by the resolver, then by the fallback registry, if available'](assert) { let fallback = { describe(fullName) { return fullName + "-fallback"; } }; let resolver = { lookupDescription(fullName) { return fullName + "-resolver"; } }; let registry = new _container.Registry({ fallback, resolver }); assert.equal(registry.describe('controller:post'), 'controller:post-resolver', '`describe` handled by the resolver first.'); registry.resolver = null; assert.equal(registry.describe('controller:post'), 'controller:post-fallback', '`describe` handled by fallback registry next.'); registry.fallback = null; assert.equal(registry.describe('controller:post'), 'controller:post', '`describe` by default returns argument.'); } ['@test `normalizeFullName` will be handled by the resolver, then by the fallback registry, if available'](assert) { let fallback = { normalizeFullName(fullName) { return fullName + "-fallback"; } }; let resolver = { normalize(fullName) { return fullName + "-resolver"; } }; let registry = new _container.Registry({ fallback, resolver }); assert.equal(registry.normalizeFullName('controller:post'), 'controller:post-resolver', '`normalizeFullName` handled by the resolver first.'); registry.resolver = null; assert.equal(registry.normalizeFullName('controller:post'), 'controller:post-fallback', '`normalizeFullName` handled by fallback registry next.'); registry.fallback = null; assert.equal(registry.normalizeFullName('controller:post'), 'controller:post', '`normalizeFullName` by default returns argument.'); } ['@test `makeToString` will be handled by the resolver, then by the fallback registry, if available'](assert) { let fallback = { makeToString(fullName) { return fullName + "-fallback"; } }; let resolver = { makeToString(fullName) { return fullName + "-resolver"; } }; let registry = new _container.Registry({ fallback, resolver }); assert.equal(registry.makeToString('controller:post'), 'controller:post-resolver', '`makeToString` handled by the resolver first.'); registry.resolver = null; assert.equal(registry.makeToString('controller:post'), 'controller:post-fallback', '`makeToString` handled by fallback registry next.'); registry.fallback = null; assert.equal(registry.makeToString('controller:post'), 'controller:post', '`makeToString` by default returns argument.'); } ['@test `resolve` can be handled by a fallback registry'](assert) { let fallback = new _container.Registry(); let registry = new _container.Registry({ fallback: fallback }); let PostController = (0, _internalTestHelpers.factory)(); fallback.register('controller:post', PostController); let PostControllerFactory = registry.resolve('controller:post'); assert.ok(PostControllerFactory, 'factory is returned'); assert.ok(PostControllerFactory.create() instanceof PostController, 'The return of factory.create is an instance of PostController'); } ['@test `has` can be handled by a fallback registry'](assert) { let fallback = new _container.Registry(); let registry = new _container.Registry({ fallback: fallback }); let PostController = (0, _internalTestHelpers.factory)(); fallback.register('controller:post', PostController); assert.equal(registry.has('controller:post'), true, 'Fallback registry is checked for registration'); } ['@test `getInjections` includes injections from a fallback registry'](assert) { let fallback = new _container.Registry(); let registry = new _container.Registry({ fallback: fallback }); assert.strictEqual(registry.getInjections('model:user'), undefined, 'No injections in the primary registry'); fallback.injection('model:user', 'post', 'model:post'); assert.equal(registry.getInjections('model:user').length, 1, 'Injections from the fallback registry are merged'); } ['@test `getTypeInjections` includes type injections from a fallback registry'](assert) { let fallback = new _container.Registry(); let registry = new _container.Registry({ fallback: fallback }); assert.strictEqual(registry.getTypeInjections('model'), undefined, 'No injections in the primary registry'); fallback.injection('model', 'source', 'source:main'); assert.equal(registry.getTypeInjections('model').length, 1, 'Injections from the fallback registry are merged'); } ['@test `knownForType` contains keys for each item of a given type'](assert) { let registry = new _container.Registry(); registry.register('foo:bar-baz', 'baz'); registry.register('foo:qux-fez', 'fez'); let found = registry.knownForType('foo'); assert.deepEqual(found, { 'foo:bar-baz': true, 'foo:qux-fez': true }); } ['@test `knownForType` includes fallback registry results'](assert) { let fallback = new _container.Registry(); let registry = new _container.Registry({ fallback: fallback }); registry.register('foo:bar-baz', 'baz'); registry.register('foo:qux-fez', 'fez'); fallback.register('foo:zurp-zorp', 'zorp'); let found = registry.knownForType('foo'); assert.deepEqual(found, { 'foo:bar-baz': true, 'foo:qux-fez': true, 'foo:zurp-zorp': true }); } ['@test `knownForType` is called on the resolver if present'](assert) { assert.expect(3); let resolver = { knownForType(type) { assert.ok(true, 'knownForType called on the resolver'); assert.equal(type, 'foo', 'the type was passed through'); return { 'foo:yorp': true }; } }; let registry = new _container.Registry({ resolver }); registry.register('foo:bar-baz', 'baz'); let found = registry.knownForType('foo'); assert.deepEqual(found, { 'foo:yorp': true, 'foo:bar-baz': true }); } ['@test resolver.expandLocalLookup is not required'](assert) { let registry = new _container.Registry({ resolver: {} }); let result = registry.expandLocalLookup('foo:bar', { source: 'baz:qux' }); assert.equal(result, null); } ['@test expandLocalLookup is called on the resolver if present'](assert) { assert.expect(4); let resolver = { expandLocalLookup: (targetFullName, sourceFullName) => { assert.ok(true, 'expandLocalLookup is called on the resolver'); assert.equal(targetFullName, 'foo:bar', 'the targetFullName was passed through'); assert.equal(sourceFullName, 'baz:qux', 'the sourceFullName was passed through'); return 'foo:qux/bar'; } }; let registry = new _container.Registry({ resolver }); let result = registry.expandLocalLookup('foo:bar', { source: 'baz:qux' }); assert.equal(result, 'foo:qux/bar'); } ['@test `expandLocalLookup` is handled by the resolver, then by the fallback registry, if available'](assert) { assert.expect(9); let fallbackResolver = { expandLocalLookup: (targetFullName, sourceFullName) => { assert.ok(true, 'expandLocalLookup is called on the fallback resolver'); assert.equal(targetFullName, 'foo:bar', 'the targetFullName was passed through'); assert.equal(sourceFullName, 'baz:qux', 'the sourceFullName was passed through'); return 'foo:qux/bar-fallback'; } }; let resolver = { expandLocalLookup: (targetFullName, sourceFullName) => { assert.ok(true, 'expandLocalLookup is called on the resolver'); assert.equal(targetFullName, 'foo:bar', 'the targetFullName was passed through'); assert.equal(sourceFullName, 'baz:qux', 'the sourceFullName was passed through'); return 'foo:qux/bar-resolver'; } }; let fallbackRegistry = new _container.Registry({ resolver: fallbackResolver }); let registry = new _container.Registry({ fallback: fallbackRegistry, resolver }); let result = registry.expandLocalLookup('foo:bar', { source: 'baz:qux' }); assert.equal(result, 'foo:qux/bar-resolver', 'handled by the resolver'); registry.resolver = null; result = registry.expandLocalLookup('foo:bar', { source: 'baz:qux' }); assert.equal(result, 'foo:qux/bar-fallback', 'handled by the fallback registry'); registry.fallback = null; result = registry.expandLocalLookup('foo:bar', { source: 'baz:qux' }); assert.equal(result, null, 'null is returned by default when no resolver or fallback registry is present'); } ['@test resolver.expandLocalLookup result is cached'](assert) { assert.expect(3); let result; let resolver = { expandLocalLookup: () => { assert.ok(true, 'expandLocalLookup is called on the resolver'); return 'foo:qux/bar'; } }; let registry = new _container.Registry({ resolver }); result = registry.expandLocalLookup('foo:bar', { source: 'baz:qux' }); assert.equal(result, 'foo:qux/bar'); result = registry.expandLocalLookup('foo:bar', { source: 'baz:qux' }); assert.equal(result, 'foo:qux/bar'); } ['@test resolver.expandLocalLookup cache is busted when any unregister is called'](assert) { assert.expect(4); let result; let resolver = { expandLocalLookup: () => { assert.ok(true, 'expandLocalLookup is called on the resolver'); return 'foo:qux/bar'; } }; let registry = new _container.Registry({ resolver }); result = registry.expandLocalLookup('foo:bar', { source: 'baz:qux' }); assert.equal(result, 'foo:qux/bar'); registry.unregister('foo:bar'); result = registry.expandLocalLookup('foo:bar', { source: 'baz:qux' }); assert.equal(result, 'foo:qux/bar'); } ['@test resolve calls expandLocallookup when it receives options.source'](assert) { assert.expect(3); let resolver = { resolve() {}, expandLocalLookup: (targetFullName, sourceFullName) => { assert.ok(true, 'expandLocalLookup is called on the resolver'); assert.equal(targetFullName, 'foo:bar', 'the targetFullName was passed through'); assert.equal(sourceFullName, 'baz:qux', 'the sourceFullName was passed through'); return 'foo:qux/bar'; } }; let registry = new _container.Registry({ resolver }); registry.resolve('foo:bar', { source: 'baz:qux' }); } ['@test has uses expandLocalLookup'](assert) { assert.expect(5); let resolvedFullNames = []; let result; let resolver = { resolve(name) { resolvedFullNames.push(name); return 'yippie!'; }, expandLocalLookup: targetFullName => { assert.ok(true, 'expandLocalLookup is called on the resolver'); if (targetFullName === 'foo:bar') { return 'foo:qux/bar'; } else { return null; } } }; let registry = new _container.Registry({ resolver }); result = registry.has('foo:bar', { source: 'baz:qux' }); assert.ok(result, 'found foo:bar/qux'); result = registry.has('foo:baz', { source: 'baz:qux' }); assert.ok(!result, 'foo:baz/qux not found'); assert.deepEqual(['foo:qux/bar'], resolvedFullNames); } }); (0, _internalTestHelpers.moduleFor)('Registry privatize', class extends _internalTestHelpers.AbstractTestCase { ['@test valid format'](assert) { let privatized = (0, _container.privatize)(['secret:factory']); let matched = privatized.match(/^([^:]+):([^:]+)-(\d+)$/); assert.ok(matched, 'privatized format was recognized'); assert.equal(matched[1], 'secret'); assert.equal(matched[2], 'factory'); assert.ok(/^\d+$/.test(matched[3])); } }); if (_canaryFeatures.EMBER_MODULE_UNIFICATION) { (0, _internalTestHelpers.moduleFor)('Registry module unification', class extends _internalTestHelpers.AbstractTestCase { ['@test The registry can pass a source to the resolver'](assert) { let PrivateComponent = (0, _internalTestHelpers.factory)(); let type = 'component'; let name = 'my-input'; let specifier = type + ":" + name; let source = 'template:routes/application'; let resolver = new _internalTestHelpers.ModuleBasedTestResolver(); resolver.add({ specifier, source }, PrivateComponent); let registry = new _container.Registry({ resolver }); assert.strictEqual(registry.resolve(specifier), undefined, 'Not returned when specifier not scoped'); assert.strictEqual(registry.resolve(specifier, { source }), PrivateComponent, 'The correct factory was provided'); assert.strictEqual(registry.resolve(specifier, { source }), PrivateComponent, 'The correct factory was provided again'); } ['@test The registry can pass a namespace to the resolver'](assert) { let PrivateComponent = (0, _internalTestHelpers.factory)(); let type = 'component'; let name = 'my-input'; let specifier = type + ":" + name; let source = 'template:routes/application'; let namespace = 'my-addon'; let resolver = new _internalTestHelpers.ModuleBasedTestResolver(); resolver.add({ specifier, source, namespace }, PrivateComponent); let registry = new _container.Registry({ resolver }); assert.strictEqual(registry.resolve(specifier), undefined, 'Not returned when specifier not scoped'); assert.strictEqual(registry.resolve(specifier, { source }), undefined, 'Not returned when specifier is missing namespace'); assert.strictEqual(registry.resolve(specifier, { source, namespace }), PrivateComponent, 'The correct factory was provided'); assert.strictEqual(registry.resolve(specifier, { source, namespace }), PrivateComponent, 'The correct factory was provided again'); } }); } }); enifed("@ember/-internals/environment/index", ["exports", "@ember/-internals/environment/lib/context", "@ember/-internals/environment/lib/env", "@ember/-internals/environment/lib/global"], function (_exports, _context, _env, _global) { "use strict"; _exports.__esModule = true; var _exportNames = { global: true }; _exports.global = void 0; Object.keys(_context).forEach(function (key) { if (key === "default" || key === "__esModule") return; if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; _exports[key] = _context[key]; }); Object.keys(_env).forEach(function (key) { if (key === "default" || key === "__esModule") return; if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; _exports[key] = _env[key]; }); _exports.global = _global.default; }); enifed("@ember/-internals/environment/lib/context", ["exports", "@ember/-internals/environment/lib/global"], function (_exports, _global) { "use strict"; _exports.__esModule = true; _exports.getLookup = getLookup; _exports.setLookup = setLookup; _exports.context = void 0; // legacy imports/exports/lookup stuff (should we keep this??) const context = function (global, Ember) { return Ember === undefined ? { imports: global, exports: global, lookup: global } : { // import jQuery imports: Ember.imports || global, // export Ember exports: Ember.exports || global, // search for Namespaces lookup: Ember.lookup || global }; }(_global.default, _global.default.Ember); _exports.context = context; function getLookup() { return context.lookup; } function setLookup(value) { context.lookup = value; } }); enifed("@ember/-internals/environment/lib/env", ["exports", "@ember/-internals/environment/lib/global"], function (_exports, _global) { "use strict"; _exports.__esModule = true; _exports.getENV = getENV; _exports.ENV = void 0; /** The hash of environment variables used to control various configuration settings. To specify your own or override default settings, add the desired properties to a global hash named `EmberENV` (or `ENV` for backwards compatibility with earlier versions of Ember). The `EmberENV` hash must be created before loading Ember. @class EmberENV @type Object @public */ const ENV = { ENABLE_OPTIONAL_FEATURES: false, /** Determines whether Ember should add to `Array`, `Function`, and `String` native object prototypes, a few extra methods in order to provide a more friendly API. We generally recommend leaving this option set to true however, if you need to turn it off, you can add the configuration property `EXTEND_PROTOTYPES` to `EmberENV` and set it to `false`. Note, when disabled (the default configuration for Ember Addons), you will instead have to access all methods and functions from the Ember namespace. @property EXTEND_PROTOTYPES @type Boolean @default true @for EmberENV @public */ EXTEND_PROTOTYPES: { Array: true, Function: true, String: true }, /** The `LOG_STACKTRACE_ON_DEPRECATION` property, when true, tells Ember to log a full stack trace during deprecation warnings. @property LOG_STACKTRACE_ON_DEPRECATION @type Boolean @default true @for EmberENV @public */ LOG_STACKTRACE_ON_DEPRECATION: true, /** The `LOG_VERSION` property, when true, tells Ember to log versions of all dependent libraries in use. @property LOG_VERSION @type Boolean @default true @for EmberENV @public */ LOG_VERSION: true, RAISE_ON_DEPRECATION: false, STRUCTURED_PROFILE: false, /** Whether to insert a `
` wrapper around the application template. See RFC #280. This is not intended to be set directly, as the implementation may change in the future. Use `@ember/optional-features` instead. @property _APPLICATION_TEMPLATE_WRAPPER @for EmberENV @type Boolean @default true @private */ _APPLICATION_TEMPLATE_WRAPPER: true, /** Whether to use Glimmer Component semantics (as opposed to the classic "Curly" components semantics) for template-only components. See RFC #278. This is not intended to be set directly, as the implementation may change in the future. Use `@ember/optional-features` instead. @property _TEMPLATE_ONLY_GLIMMER_COMPONENTS @for EmberENV @type Boolean @default false @private */ _TEMPLATE_ONLY_GLIMMER_COMPONENTS: false, /** Whether the app is using jQuery. See RFC #294. This is not intended to be set directly, as the implementation may change in the future. Use `@ember/optional-features` instead. @property _JQUERY_INTEGRATION @for EmberENV @type Boolean @default true @private */ _JQUERY_INTEGRATION: true, /** Controls the maximum number of scheduled rerenders without "settling". In general, applications should not need to modify this environment variable, but please open an issue so that we can determine if a better default value is needed. @property _RERENDER_LOOP_LIMIT @for EmberENV @type number @default 1000 @private */ _RERENDER_LOOP_LIMIT: 1000, EMBER_LOAD_HOOKS: {}, FEATURES: {} }; _exports.ENV = ENV; (EmberENV => { if (typeof EmberENV !== 'object' || EmberENV === null) return; for (let flag in EmberENV) { if (!EmberENV.hasOwnProperty(flag) || flag === 'EXTEND_PROTOTYPES' || flag === 'EMBER_LOAD_HOOKS') continue; let defaultValue = ENV[flag]; if (defaultValue === true) { ENV[flag] = EmberENV[flag] !== false; } else if (defaultValue === false) { ENV[flag] = EmberENV[flag] === true; } } let { EXTEND_PROTOTYPES } = EmberENV; if (EXTEND_PROTOTYPES !== undefined) { if (typeof EXTEND_PROTOTYPES === 'object' && EXTEND_PROTOTYPES !== null) { ENV.EXTEND_PROTOTYPES.String = EXTEND_PROTOTYPES.String !== false; ENV.EXTEND_PROTOTYPES.Function = EXTEND_PROTOTYPES.Function !== false; ENV.EXTEND_PROTOTYPES.Array = EXTEND_PROTOTYPES.Array !== false; } else { let isEnabled = EXTEND_PROTOTYPES !== false; ENV.EXTEND_PROTOTYPES.String = isEnabled; ENV.EXTEND_PROTOTYPES.Function = isEnabled; ENV.EXTEND_PROTOTYPES.Array = isEnabled; } } // TODO this does not seem to be used by anything, // can we remove it? do we need to deprecate it? let { EMBER_LOAD_HOOKS } = EmberENV; if (typeof EMBER_LOAD_HOOKS === 'object' && EMBER_LOAD_HOOKS !== null) { for (let hookName in EMBER_LOAD_HOOKS) { if (!EMBER_LOAD_HOOKS.hasOwnProperty(hookName)) continue; let hooks = EMBER_LOAD_HOOKS[hookName]; if (Array.isArray(hooks)) { ENV.EMBER_LOAD_HOOKS[hookName] = hooks.filter(hook => typeof hook === 'function'); } } } let { FEATURES } = EmberENV; if (typeof FEATURES === 'object' && FEATURES !== null) { for (let feature in FEATURES) { if (!FEATURES.hasOwnProperty(feature)) continue; ENV.FEATURES[feature] = FEATURES[feature] === true; } } })(_global.default.EmberENV || _global.default.ENV); function getENV() { return ENV; } }); enifed("@ember/-internals/environment/lib/global", ["exports"], function (_exports) { "use strict"; _exports.__esModule = true; _exports.default = void 0; // from lodash to catch fake globals function checkGlobal(value) { return value && value.Object === Object ? value : undefined; } // element ids can ruin global miss checks function checkElementIdShadowing(value) { return value && value.nodeType === undefined ? value : undefined; } // export real global var _default = checkGlobal(checkElementIdShadowing(typeof global === 'object' && global)) || checkGlobal(typeof self === 'object' && self) || checkGlobal(typeof window === 'object' && window) || typeof mainContext !== 'undefined' && mainContext || // set before strict mode in Ember loader/wrapper new Function('return this')(); // eval outside of strict mode _exports.default = _default; }); enifed("@ember/-internals/error-handling/index", ["exports"], function (_exports) { "use strict"; _exports.__esModule = true; _exports.getOnerror = getOnerror; _exports.setOnerror = setOnerror; _exports.getDispatchOverride = getDispatchOverride; _exports.setDispatchOverride = setDispatchOverride; _exports.onErrorTarget = void 0; let onerror; const onErrorTarget = { get onerror() { return onerror; } }; // Ember.onerror getter _exports.onErrorTarget = onErrorTarget; function getOnerror() { return onerror; } // Ember.onerror setter function setOnerror(handler) { onerror = handler; } let dispatchOverride; // allows testing adapter to override dispatch function getDispatchOverride() { return dispatchOverride; } function setDispatchOverride(handler) { dispatchOverride = handler; } }); enifed("@ember/-internals/extension-support/index", ["exports", "@ember/-internals/extension-support/lib/data_adapter", "@ember/-internals/extension-support/lib/container_debug_adapter"], function (_exports, _data_adapter, _container_debug_adapter) { "use strict"; _exports.__esModule = true; _exports.ContainerDebugAdapter = _exports.DataAdapter = void 0; _exports.DataAdapter = _data_adapter.default; _exports.ContainerDebugAdapter = _container_debug_adapter.default; }); enifed("@ember/-internals/extension-support/lib/container_debug_adapter", ["exports", "@ember/string", "@ember/-internals/runtime"], function (_exports, _string, _runtime) { "use strict"; _exports.__esModule = true; _exports.default = void 0; /** @module @ember/debug */ /** The `ContainerDebugAdapter` helps the container and resolver interface with tools that debug Ember such as the [Ember Inspector](https://github.com/emberjs/ember-inspector) for Chrome and Firefox. This class can be extended by a custom resolver implementer to override some of the methods with library-specific code. The methods likely to be overridden are: * `canCatalogEntriesByType` * `catalogEntriesByType` The adapter will need to be registered in the application's container as `container-debug-adapter:main`. Example: ```javascript Application.initializer({ name: "containerDebugAdapter", initialize(application) { application.register('container-debug-adapter:main', require('app/container-debug-adapter')); } }); ``` @class ContainerDebugAdapter @extends EmberObject @since 1.5.0 @public */ var _default = _runtime.Object.extend({ /** The resolver instance of the application being debugged. This property will be injected on creation. @property resolver @default null @public */ resolver: null, /** Returns true if it is possible to catalog a list of available classes in the resolver for a given type. @method canCatalogEntriesByType @param {String} type The type. e.g. "model", "controller", "route". @return {boolean} whether a list is available for this type. @public */ canCatalogEntriesByType(type) { if (type === 'model' || type === 'template') { return false; } return true; }, /** Returns the available classes a given type. @method catalogEntriesByType @param {String} type The type. e.g. "model", "controller", "route". @return {Array} An array of strings. @public */ catalogEntriesByType(type) { let namespaces = (0, _runtime.A)(_runtime.Namespace.NAMESPACES); let types = (0, _runtime.A)(); let typeSuffixRegex = new RegExp((0, _string.classify)(type) + "$"); namespaces.forEach(namespace => { for (let key in namespace) { if (!namespace.hasOwnProperty(key)) { continue; } if (typeSuffixRegex.test(key)) { let klass = namespace[key]; if ((0, _runtime.typeOf)(klass) === 'class') { types.push((0, _string.dasherize)(key.replace(typeSuffixRegex, ''))); } } } }); return types; } }); _exports.default = _default; }); enifed("@ember/-internals/extension-support/lib/data_adapter", ["exports", "@ember/-internals/owner", "@ember/runloop", "@ember/-internals/metal", "@ember/string", "@ember/-internals/runtime"], function (_exports, _owner, _runloop, _metal, _string, _runtime) { "use strict"; _exports.__esModule = true; _exports.default = void 0; /** @module @ember/debug */ /** The `DataAdapter` helps a data persistence library interface with tools that debug Ember such as the [Ember Inspector](https://github.com/emberjs/ember-inspector) for Chrome and Firefox. This class will be extended by a persistence library which will override some of the methods with library-specific code. The methods likely to be overridden are: * `getFilters` * `detect` * `columnsForType` * `getRecords` * `getRecordColumnValues` * `getRecordKeywords` * `getRecordFilterValues` * `getRecordColor` * `observeRecord` The adapter will need to be registered in the application's container as `dataAdapter:main`. Example: ```javascript Application.initializer({ name: "data-adapter", initialize: function(application) { application.register('data-adapter:main', DS.DataAdapter); } }); ``` @class DataAdapter @extends EmberObject @public */ var _default = _runtime.Object.extend({ init() { this._super(...arguments); this.releaseMethods = (0, _runtime.A)(); }, /** The container-debug-adapter which is used to list all models. @property containerDebugAdapter @default undefined @since 1.5.0 @public **/ containerDebugAdapter: undefined, /** The number of attributes to send as columns. (Enough to make the record identifiable). @private @property attributeLimit @default 3 @since 1.3.0 */ attributeLimit: 3, /** Ember Data > v1.0.0-beta.18 requires string model names to be passed around instead of the actual factories. This is a stamp for the Ember Inspector to differentiate between the versions to be able to support older versions too. @public @property acceptsModelName */ acceptsModelName: true, /** Stores all methods that clear observers. These methods will be called on destruction. @private @property releaseMethods @since 1.3.0 */ releaseMethods: (0, _runtime.A)(), /** Specifies how records can be filtered. Records returned will need to have a `filterValues` property with a key for every name in the returned array. @public @method getFilters @return {Array} List of objects defining filters. The object should have a `name` and `desc` property. */ getFilters() { return (0, _runtime.A)(); }, /** Fetch the model types and observe them for changes. @public @method watchModelTypes @param {Function} typesAdded Callback to call to add types. Takes an array of objects containing wrapped types (returned from `wrapModelType`). @param {Function} typesUpdated Callback to call when a type has changed. Takes an array of objects containing wrapped types. @return {Function} Method to call to remove all observers */ watchModelTypes(typesAdded, typesUpdated) { let modelTypes = this.getModelTypes(); let releaseMethods = (0, _runtime.A)(); let typesToSend; typesToSend = modelTypes.map(type => { let klass = type.klass; let wrapped = this.wrapModelType(klass, type.name); releaseMethods.push(this.observeModelType(type.name, typesUpdated)); return wrapped; }); typesAdded(typesToSend); let release = () => { releaseMethods.forEach(fn => fn()); this.releaseMethods.removeObject(release); }; this.releaseMethods.pushObject(release); return release; }, _nameToClass(type) { if (typeof type === 'string') { let owner = (0, _owner.getOwner)(this); let Factory = owner.factoryFor("model:" + type); type = Factory && Factory.class; } return type; }, /** Fetch the records of a given type and observe them for changes. @public @method watchRecords @param {String} modelName The model name. @param {Function} recordsAdded Callback to call to add records. Takes an array of objects containing wrapped records. The object should have the following properties: columnValues: {Object} The key and value of a table cell. object: {Object} The actual record object. @param {Function} recordsUpdated Callback to call when a record has changed. Takes an array of objects containing wrapped records. @param {Function} recordsRemoved Callback to call when a record has removed. Takes the following parameters: index: The array index where the records were removed. count: The number of records removed. @return {Function} Method to call to remove all observers. */ watchRecords(modelName, recordsAdded, recordsUpdated, recordsRemoved) { let releaseMethods = (0, _runtime.A)(); let klass = this._nameToClass(modelName); let records = this.getRecords(klass, modelName); let release; function recordUpdated(updatedRecord) { recordsUpdated([updatedRecord]); } let recordsToSend = records.map(record => { releaseMethods.push(this.observeRecord(record, recordUpdated)); return this.wrapRecord(record); }); let contentDidChange = (array, idx, removedCount, addedCount) => { for (let i = idx; i < idx + addedCount; i++) { let record = (0, _metal.objectAt)(array, i); let wrapped = this.wrapRecord(record); releaseMethods.push(this.observeRecord(record, recordUpdated)); recordsAdded([wrapped]); } if (removedCount) { recordsRemoved(idx, removedCount); } }; let observer = { didChange: contentDidChange, willChange() { return this; } }; (0, _metal.addArrayObserver)(records, this, observer); release = () => { releaseMethods.forEach(fn => fn()); (0, _metal.removeArrayObserver)(records, this, observer); this.releaseMethods.removeObject(release); }; recordsAdded(recordsToSend); this.releaseMethods.pushObject(release); return release; }, /** Clear all observers before destruction @private @method willDestroy */ willDestroy() { this._super(...arguments); this.releaseMethods.forEach(fn => fn()); }, /** Detect whether a class is a model. Test that against the model class of your persistence library. @public @method detect @return boolean Whether the class is a model class or not. */ detect() { return false; }, /** Get the columns for a given model type. @public @method columnsForType @return {Array} An array of columns of the following format: name: {String} The name of the column. desc: {String} Humanized description (what would show in a table column name). */ columnsForType() { return (0, _runtime.A)(); }, /** Adds observers to a model type class. @private @method observeModelType @param {String} modelName The model type name. @param {Function} typesUpdated Called when a type is modified. @return {Function} The function to call to remove observers. */ observeModelType(modelName, typesUpdated) { let klass = this._nameToClass(modelName); let records = this.getRecords(klass, modelName); function onChange() { typesUpdated([this.wrapModelType(klass, modelName)]); } let observer = { didChange(array, idx, removedCount, addedCount) { // Only re-fetch records if the record count changed // (which is all we care about as far as model types are concerned). if (removedCount > 0 || addedCount > 0) { (0, _runloop.scheduleOnce)('actions', this, onChange); } }, willChange() { return this; } }; (0, _metal.addArrayObserver)(records, this, observer); let release = () => (0, _metal.removeArrayObserver)(records, this, observer); return release; }, /** Wraps a given model type and observes changes to it. @private @method wrapModelType @param {Class} klass A model class. @param {String} modelName Name of the class. @return {Object} Contains the wrapped type and the function to remove observers Format: type: {Object} The wrapped type. The wrapped type has the following format: name: {String} The name of the type. count: {Integer} The number of records available. columns: {Columns} An array of columns to describe the record. object: {Class} The actual Model type class. release: {Function} The function to remove observers. */ wrapModelType(klass, name) { let records = this.getRecords(klass, name); let typeToSend; typeToSend = { name, count: (0, _metal.get)(records, 'length'), columns: this.columnsForType(klass), object: klass }; return typeToSend; }, /** Fetches all models defined in the application. @private @method getModelTypes @return {Array} Array of model types. */ getModelTypes() { let containerDebugAdapter = this.get('containerDebugAdapter'); let types; if (containerDebugAdapter.canCatalogEntriesByType('model')) { types = containerDebugAdapter.catalogEntriesByType('model'); } else { types = this._getObjectsOnNamespaces(); } // New adapters return strings instead of classes. types = (0, _runtime.A)(types).map(name => { return { klass: this._nameToClass(name), name }; }); types = (0, _runtime.A)(types).filter(type => this.detect(type.klass)); return (0, _runtime.A)(types); }, /** Loops over all namespaces and all objects attached to them. @private @method _getObjectsOnNamespaces @return {Array} Array of model type strings. */ _getObjectsOnNamespaces() { let namespaces = (0, _runtime.A)(_runtime.Namespace.NAMESPACES); let types = (0, _runtime.A)(); namespaces.forEach(namespace => { for (let key in namespace) { if (!namespace.hasOwnProperty(key)) { continue; } // Even though we will filter again in `getModelTypes`, // we should not call `lookupFactory` on non-models if (!this.detect(namespace[key])) { continue; } let name = (0, _string.dasherize)(key); types.push(name); } }); return types; }, /** Fetches all loaded records for a given type. @public @method getRecords @return {Array} An array of records. This array will be observed for changes, so it should update when new records are added/removed. */ getRecords() { return (0, _runtime.A)(); }, /** Wraps a record and observers changes to it. @private @method wrapRecord @param {Object} record The record instance. @return {Object} The wrapped record. Format: columnValues: {Array} searchKeywords: {Array} */ wrapRecord(record) { let recordToSend = { object: record }; recordToSend.columnValues = this.getRecordColumnValues(record); recordToSend.searchKeywords = this.getRecordKeywords(record); recordToSend.filterValues = this.getRecordFilterValues(record); recordToSend.color = this.getRecordColor(record); return recordToSend; }, /** Gets the values for each column. @public @method getRecordColumnValues @return {Object} Keys should match column names defined by the model type. */ getRecordColumnValues() { return {}; }, /** Returns keywords to match when searching records. @public @method getRecordKeywords @return {Array} Relevant keywords for search. */ getRecordKeywords() { return (0, _runtime.A)(); }, /** Returns the values of filters defined by `getFilters`. @public @method getRecordFilterValues @param {Object} record The record instance. @return {Object} The filter values. */ getRecordFilterValues() { return {}; }, /** Each record can have a color that represents its state. @public @method getRecordColor @param {Object} record The record instance @return {String} The records color. Possible options: black, red, blue, green. */ getRecordColor() { return null; }, /** Observes all relevant properties and re-sends the wrapped record when a change occurs. @public @method observerRecord @return {Function} The function to call to remove all observers. */ observeRecord() { return function () {}; } }); _exports.default = _default; }); enifed("@ember/-internals/extension-support/tests/container_debug_adapter_test", ["internal-test-helpers", "@ember/polyfills", "@ember/runloop", "@ember/controller", "@ember/-internals/extension-support/index", "@ember/debug"], function (_internalTestHelpers, _polyfills, _runloop, _controller, _index, _debug) { "use strict"; // Must be required to export Ember.ContainerDebugAdapter. const originalDebug = (0, _debug.getDebugFunction)('debug'); (0, _internalTestHelpers.moduleFor)('Container Debug Adapter', class extends _internalTestHelpers.ApplicationTestCase { constructor() { (0, _debug.setDebugFunction)('debug', () => {}); super(); this.adapter = this.application.__deprecatedInstance__.lookup('container-debug-adapter:main'); } get applicationOptions() { return (0, _polyfills.assign)(super.applicationOptions, { autoboot: true }); } teardown() { (0, _debug.setDebugFunction)('debug', originalDebug); (0, _runloop.run)(() => { this.adapter.destroy(); }); super.teardown(); } ['@test default ContainerDebugAdapter cannot catalog certain entries by type'](assert) { assert.equal(this.adapter.canCatalogEntriesByType('model'), false, 'canCatalogEntriesByType should return false for model'); assert.equal(this.adapter.canCatalogEntriesByType('template'), false, 'canCatalogEntriesByType should return false for template'); } ['@test default ContainerDebugAdapter can catalog typical entries by type'](assert) { assert.equal(this.adapter.canCatalogEntriesByType('controller'), true, 'canCatalogEntriesByType should return true for controller'); assert.equal(this.adapter.canCatalogEntriesByType('route'), true, 'canCatalogEntriesByType should return true for route'); assert.equal(this.adapter.canCatalogEntriesByType('view'), true, 'canCatalogEntriesByType should return true for view'); } ['@test default ContainerDebugAdapter catalogs controller entries'](assert) { this.application.PostController = _controller.default.extend(); let controllerClasses = this.adapter.catalogEntriesByType('controller'); assert.equal(controllerClasses.length, 1, 'found 1 class'); assert.equal(controllerClasses[0], 'post', 'found the right class'); } }); }); enifed("@ember/-internals/extension-support/tests/data_adapter_test", ["@ember/runloop", "@ember/-internals/metal", "@ember/-internals/runtime", "@ember/-internals/extension-support/lib/data_adapter", "internal-test-helpers"], function (_runloop, _metal, _runtime, _data_adapter, _internalTestHelpers) { "use strict"; let adapter; const Model = _runtime.Object.extend(); const PostClass = Model.extend(); const DataAdapter = _data_adapter.default.extend({ detect(klass) { return klass !== Model && Model.detect(klass); }, init() { this._super(...arguments); this.set('containerDebugAdapter', { canCatalogEntriesByType() { return true; }, catalogEntriesByType() { return (0, _runtime.A)(['post']); } }); } }); (0, _internalTestHelpers.moduleFor)('Data Adapter', class extends _internalTestHelpers.ApplicationTestCase { teardown() { super.teardown(); adapter = undefined; } ['@test Model types added'](assert) { this.add('data-adapter:main', DataAdapter.extend({ getRecords() { return (0, _runtime.A)([1, 2, 3]); }, columnsForType() { return [{ name: 'title', desc: 'Title' }]; } })); this.add('model:post', PostClass); return this.visit('/').then(() => { let adapter = this.applicationInstance.lookup('data-adapter:main'); function modelTypesAdded(types) { assert.equal(types.length, 1); let postType = types[0]; assert.equal(postType.name, 'post', 'Correctly sets the name'); assert.equal(postType.count, 3, 'Correctly sets the record count'); assert.strictEqual(postType.object, PostClass, 'Correctly sets the object'); assert.deepEqual(postType.columns, [{ name: 'title', desc: 'Title' }], 'Correctly sets the columns'); } adapter.watchModelTypes(modelTypesAdded); }); } ['@test getRecords gets a model name as second argument'](assert) { this.add('data-adapter:main', DataAdapter.extend({ getRecords(klass, name) { assert.equal(name, 'post'); return (0, _runtime.A)(); } })); this.add('model:post', PostClass); return this.visit('/').then(() => { adapter = this.applicationInstance.lookup('data-adapter:main'); adapter.watchModelTypes(function () {}); }); } ['@test Model types added with custom container-debug-adapter'](assert) { let StubContainerDebugAdapter = _runtime.Object.extend({ canCatalogEntriesByType() { return true; }, catalogEntriesByType() { return (0, _runtime.A)(['post']); } }); this.add('container-debug-adapter:main', StubContainerDebugAdapter); this.add('data-adapter:main', DataAdapter.extend({ getRecords() { return (0, _runtime.A)([1, 2, 3]); }, columnsForType() { return [{ name: 'title', desc: 'Title' }]; } })); this.add('model:post', PostClass); return this.visit('/').then(() => { let adapter = this.applicationInstance.lookup('data-adapter:main'); function modelTypesAdded(types) { assert.equal(types.length, 1); let postType = types[0]; assert.equal(postType.name, 'post', 'Correctly sets the name'); assert.equal(postType.count, 3, 'Correctly sets the record count'); assert.strictEqual(postType.object, PostClass, 'Correctly sets the object'); assert.deepEqual(postType.columns, [{ name: 'title', desc: 'Title' }], 'Correctly sets the columns'); } adapter.watchModelTypes(modelTypesAdded); }); } ['@test Model Types Updated'](assert) { let records = (0, _runtime.A)([1, 2, 3]); this.add('data-adapter:main', DataAdapter.extend({ getRecords() { return records; } })); this.add('model:post', PostClass); return this.visit('/').then(() => { adapter = this.applicationInstance.lookup('data-adapter:main'); function modelTypesAdded() { (0, _runloop.run)(() => { records.pushObject(4); }); } function modelTypesUpdated(types) { let postType = types[0]; assert.equal(postType.count, 4, 'Correctly updates the count'); } adapter.watchModelTypes(modelTypesAdded, modelTypesUpdated); }); } ['@test Model Types Updated but Unchanged Do not Trigger Callbacks'](assert) { assert.expect(0); let records = (0, _runtime.A)([1, 2, 3]); this.add('data-adapter:main', DataAdapter.extend({ getRecords() { return records; } })); this.add('model:post', PostClass); return this.visit('/').then(() => { adapter = this.applicationInstance.lookup('data-adapter:main'); function modelTypesAdded() { (0, _runloop.run)(() => { records.arrayContentDidChange(0, 0, 0); }); } function modelTypesUpdated() { assert.ok(false, "modelTypesUpdated should not be triggered if the array didn't change"); } adapter.watchModelTypes(modelTypesAdded, modelTypesUpdated); }); } ['@test Records Added'](assert) { let countAdded = 1; let post = PostClass.create(); let recordList = (0, _runtime.A)([post]); this.add('data-adapter:main', DataAdapter.extend({ getRecords() { return recordList; }, getRecordColor() { return 'blue'; }, getRecordColumnValues() { return { title: 'Post ' + countAdded }; }, getRecordKeywords() { return ['Post ' + countAdded]; } })); this.add('model:post', PostClass); return this.visit('/').then(() => { adapter = this.applicationInstance.lookup('data-adapter:main'); function recordsAdded(records) { let record = records[0]; assert.equal(record.color, 'blue', 'Sets the color correctly'); assert.deepEqual(record.columnValues, { title: 'Post ' + countAdded }, 'Sets the column values correctly'); assert.deepEqual(record.searchKeywords, ['Post ' + countAdded], 'Sets search keywords correctly'); assert.strictEqual(record.object, post, 'Sets the object to the record instance'); } adapter.watchRecords('post', recordsAdded); countAdded++; post = PostClass.create(); recordList.pushObject(post); }); } ['@test Observes and releases a record correctly'](assert) { let updatesCalled = 0; let post = PostClass.create({ title: 'Post' }); let recordList = (0, _runtime.A)([post]); this.add('data-adapter:main', DataAdapter.extend({ getRecords() { return recordList; }, observeRecord(record, recordUpdated) { let self = this; function callback() { recordUpdated(self.wrapRecord(record)); } (0, _metal.addObserver)(record, 'title', callback); return function () { (0, _metal.removeObserver)(record, 'title', callback); }; }, getRecordColumnValues(record) { return { title: (0, _metal.get)(record, 'title') }; } })); this.add('model:post', PostClass); return this.visit('/').then(() => { adapter = this.applicationInstance.lookup('data-adapter:main'); function recordsAdded() { (0, _metal.set)(post, 'title', 'Post Modified'); } function recordsUpdated(records) { updatesCalled++; assert.equal(records[0].columnValues.title, 'Post Modified'); } let release = adapter.watchRecords('post', recordsAdded, recordsUpdated); release(); (0, _metal.set)(post, 'title', 'New Title'); assert.equal(updatesCalled, 1, 'Release function removes observers'); }); } ['@test _nameToClass does not error when not found'](assert) { this.add('data-adapter:main', DataAdapter); return this.visit('/').then(() => { adapter = this.applicationInstance.lookup('data-adapter:main'); let klass = adapter._nameToClass('foo'); assert.equal(klass, undefined, 'returns undefined'); }); } }); }); enifed("@ember/-internals/glimmer/index", ["exports", "@ember/-internals/glimmer/lib/templates/root", "@ember/-internals/glimmer/lib/template", "@ember/-internals/glimmer/lib/components/checkbox", "@ember/-internals/glimmer/lib/components/text-field", "@ember/-internals/glimmer/lib/components/textarea", "@ember/-internals/glimmer/lib/components/link-to", "@ember/-internals/glimmer/lib/component", "@ember/-internals/glimmer/lib/helper", "@ember/-internals/glimmer/lib/environment", "@ember/-internals/glimmer/lib/utils/string", "@ember/-internals/glimmer/lib/renderer", "@ember/-internals/glimmer/lib/template_registry", "@ember/-internals/glimmer/lib/setup-registry", "@ember/-internals/glimmer/lib/dom", "@ember/-internals/glimmer/lib/syntax", "@ember/-internals/glimmer/lib/component-managers/abstract", "@ember/-internals/glimmer/lib/utils/references", "@ember/-internals/glimmer/lib/utils/iterable", "@ember/-internals/glimmer/lib/utils/debug-stack", "@ember/-internals/glimmer/lib/views/outlet", "@ember/-internals/glimmer/lib/component-managers/custom", "@ember/-internals/glimmer/lib/utils/custom-component-manager", "@ember/-internals/glimmer/lib/utils/custom-modifier-manager", "@ember/-internals/glimmer/lib/modifiers/custom", "@ember/-internals/glimmer/lib/utils/serialization-first-node-helpers"], function (_exports, _root, _template, _checkbox, _textField, _textarea, _linkTo, _component, _helper, _environment, _string, _renderer, _template_registry, _setupRegistry, _dom, _syntax, _abstract, _references, _iterable, _debugStack, _outlet, _custom, _customComponentManager, _customModifierManager, _custom2, _serializationFirstNodeHelpers) { "use strict"; _exports.__esModule = true; _exports.isSerializationFirstNode = _exports.getModifierManager = _exports.setModifierManager = _exports.getComponentManager = _exports.setComponentManager = _exports.modifierCapabilties = _exports.capabilities = _exports.INVOKE = _exports.UpdatableReference = _exports._experimentalMacros = _exports._registerMacros = _exports.DOMTreeConstruction = _exports.NodeDOMTreeConstruction = _exports.DOMChanges = _exports.setupApplicationRegistry = _exports.setupEngineRegistry = _exports.setTemplates = _exports.getTemplates = _exports.hasTemplate = _exports.setTemplate = _exports.getTemplate = _exports.renderSettled = _exports._resetRenderers = _exports.InteractiveRenderer = _exports.InertRenderer = _exports.Renderer = _exports.isHTMLSafe = _exports.htmlSafe = _exports.escapeExpression = _exports.SafeString = _exports.helper = _exports.ROOT_REF = _exports.OutletView = _exports.DebugStack = _exports.iterableFor = _exports.AbstractComponentManager = _exports.Environment = _exports.Helper = _exports.Component = _exports.LinkComponent = _exports.TextArea = _exports.TextField = _exports.Checkbox = _exports.template = _exports.RootTemplate = void 0; _exports.RootTemplate = _root.default; _exports.template = _template.default; _exports.Checkbox = _checkbox.default; _exports.TextField = _textField.default; _exports.TextArea = _textarea.default; _exports.LinkComponent = _linkTo.default; _exports.Component = _component.default; _exports.ROOT_REF = _component.ROOT_REF; _exports.Helper = _helper.default; _exports.helper = _helper.helper; _exports.Environment = _environment.default; _exports.SafeString = _string.SafeString; _exports.escapeExpression = _string.escapeExpression; _exports.htmlSafe = _string.htmlSafe; _exports.isHTMLSafe = _string.isHTMLSafe; _exports.Renderer = _renderer.Renderer; _exports.InertRenderer = _renderer.InertRenderer; _exports.InteractiveRenderer = _renderer.InteractiveRenderer; _exports._resetRenderers = _renderer._resetRenderers; _exports.renderSettled = _renderer.renderSettled; _exports.getTemplate = _template_registry.getTemplate; _exports.setTemplate = _template_registry.setTemplate; _exports.hasTemplate = _template_registry.hasTemplate; _exports.getTemplates = _template_registry.getTemplates; _exports.setTemplates = _template_registry.setTemplates; _exports.setupEngineRegistry = _setupRegistry.setupEngineRegistry; _exports.setupApplicationRegistry = _setupRegistry.setupApplicationRegistry; _exports.DOMChanges = _dom.DOMChanges; _exports.NodeDOMTreeConstruction = _dom.NodeDOMTreeConstruction; _exports.DOMTreeConstruction = _dom.DOMTreeConstruction; _exports._registerMacros = _syntax.registerMacros; _exports._experimentalMacros = _syntax.experimentalMacros; _exports.AbstractComponentManager = _abstract.default; _exports.UpdatableReference = _references.UpdatableReference; _exports.INVOKE = _references.INVOKE; _exports.iterableFor = _iterable.default; _exports.DebugStack = _debugStack.default; _exports.OutletView = _outlet.default; _exports.capabilities = _custom.capabilities; _exports.setComponentManager = _customComponentManager.setComponentManager; _exports.getComponentManager = _customComponentManager.getComponentManager; _exports.setModifierManager = _customModifierManager.setModifierManager; _exports.getModifierManager = _customModifierManager.getModifierManager; _exports.modifierCapabilties = _custom2.capabilities; _exports.isSerializationFirstNode = _serializationFirstNodeHelpers.isSerializationFirstNode; }); enifed("@ember/-internals/glimmer/lib/compile-time-lookup", ["exports"], function (_exports) { "use strict"; _exports.__esModule = true; _exports.default = void 0; class CompileTimeLookup { constructor(resolver) { this.resolver = resolver; } getCapabilities(handle) { let definition = this.resolver.resolve(handle); let { manager, state } = definition; return manager.getCapabilities(state); } getLayout(handle) { const { manager, state } = this.resolver.resolve(handle); const capabilities = manager.getCapabilities(state); if (capabilities.dynamicLayout) { return null; } const invocation = manager.getLayout(state, this.resolver); return { // TODO: this seems weird, it already is compiled compile() { return invocation.handle; }, symbolTable: invocation.symbolTable }; } lookupHelper(name, referrer) { return this.resolver.lookupHelper(name, referrer); } lookupModifier(name, referrer) { return this.resolver.lookupModifier(name, referrer); } lookupComponentDefinition(name, referrer) { return this.resolver.lookupComponentHandle(name, referrer); } lookupPartial(name, referrer) { return this.resolver.lookupPartial(name, referrer); } } _exports.default = CompileTimeLookup; }); enifed("@ember/-internals/glimmer/lib/component-managers/abstract", ["exports", "@glimmer/env"], function (_exports, _env) { "use strict"; _exports.__esModule = true; _exports.default = void 0; // implements the ComponentManager interface as defined in glimmer: // tslint:disable-next-line:max-line-length // https://github.com/glimmerjs/glimmer-vm/blob/v0.24.0-beta.4/packages/%40glimmer/runtime/lib/component/interfaces.ts#L21 class AbstractManager { constructor() { this.debugStack = undefined; } prepareArgs(_state, _args) { return null; } didCreateElement(_component, _element, _operations) {} // noop // inheritors should also call `this.debugStack.pop()` to // ensure the rerendering assertion messages are properly // maintained didRenderLayout(_component, _bounds) {// noop } didCreate(_bucket) {} // noop // inheritors should also call `this._pushToDebugStack` // to ensure the rerendering assertion messages are // properly maintained update(_bucket, _dynamicScope) {} // noop // inheritors should also call `this.debugStack.pop()` to // ensure the rerendering assertion messages are properly // maintained didUpdateLayout(_bucket, _bounds) {// noop } didUpdate(_bucket) {// noop } } _exports.default = AbstractManager; if (_env.DEBUG) { AbstractManager.prototype._pushToDebugStack = function (name, environment) { this.debugStack = environment.debugStack; this.debugStack.push(name); }; AbstractManager.prototype._pushEngineToDebugStack = function (name, environment) { this.debugStack = environment.debugStack; this.debugStack.pushEngine(name); }; } }); enifed("@ember/-internals/glimmer/lib/component-managers/curly", ["exports", "@ember/-internals/container", "@ember/-internals/owner", "@ember/-internals/utils", "@ember/-internals/views", "@ember/debug", "@ember/instrumentation", "@ember/polyfills", "@glimmer/env", "@glimmer/reference", "@glimmer/runtime", "@glimmer/util", "@ember/-internals/glimmer/lib/component", "@ember/-internals/glimmer/lib/utils/bindings", "@ember/-internals/glimmer/lib/utils/curly-component-state-bucket", "@ember/-internals/glimmer/lib/utils/process-args", "@ember/-internals/glimmer/lib/component-managers/abstract"], function (_exports, _container, _owner, _utils, _views, _debug, _instrumentation, _polyfills, _env, _reference, _runtime, _util, _component, _bindings, _curlyComponentStateBucket, _processArgs, _abstract) { "use strict"; _exports.__esModule = true; _exports.validatePositionalParameters = validatePositionalParameters; _exports.processComponentInitializationAssertions = processComponentInitializationAssertions; _exports.initialRenderInstrumentDetails = initialRenderInstrumentDetails; _exports.rerenderInstrumentDetails = rerenderInstrumentDetails; _exports.CurlyComponentDefinition = _exports.CURLY_CAPABILITIES = _exports.default = void 0; function _templateObject() { const data = _taggedTemplateLiteralLoose(["template:components/-default"]); _templateObject = function () { return data; }; return data; } function _taggedTemplateLiteralLoose(strings, raw) { if (!raw) { raw = strings.slice(0); } strings.raw = raw; return strings; } function aliasIdToElementId(args, props) { if (args.named.has('id')) { // tslint:disable-next-line:max-line-length (0, _debug.assert)("You cannot invoke a component with both 'id' and 'elementId' at the same time.", !args.named.has('elementId')); props.elementId = props.id; } } function isTemplateFactory(template) { return typeof template.create === 'function'; } // We must traverse the attributeBindings in reverse keeping track of // what has already been applied. This is essentially refining the concatenated // properties applying right to left. function applyAttributeBindings(element, attributeBindings, component, operations) { let seen = []; let i = attributeBindings.length - 1; while (i !== -1) { let binding = attributeBindings[i]; let parsed = _bindings.AttributeBinding.parse(binding); let attribute = parsed[1]; if (seen.indexOf(attribute) === -1) { seen.push(attribute); _bindings.AttributeBinding.install(element, component, parsed, operations); } i--; } if (seen.indexOf('id') === -1) { let id = component.elementId ? component.elementId : (0, _utils.guidFor)(component); operations.setAttribute('id', _runtime.PrimitiveReference.create(id), false, null); } if (seen.indexOf('style') === -1) { _bindings.IsVisibleBinding.install(element, component, operations); } } const DEFAULT_LAYOUT = (0, _container.privatize)(_templateObject()); const EMPTY_POSITIONAL_ARGS = []; (0, _debug.debugFreeze)(EMPTY_POSITIONAL_ARGS); class CurlyComponentManager extends _abstract.default { getLayout(state, _resolver) { return { // TODO fix handle: state.handle, symbolTable: state.symbolTable }; } templateFor(component, resolver) { let { layout, layoutName } = component; let owner = (0, _owner.getOwner)(component); if (layout !== undefined) { // This needs to be cached by template.id if (isTemplateFactory(layout)) { return resolver.createTemplate(layout, (0, _owner.getOwner)(component)); } else { // we were provided an instance already return layout; } } if (layoutName) { let template = owner.lookup('template:' + layoutName); if (template) { return template; } } return owner.lookup(DEFAULT_LAYOUT); } getDynamicLayout({ component }, resolver) { const template = this.templateFor(component, resolver); const layout = template.asWrappedLayout(); return { handle: layout.compile(), symbolTable: layout.symbolTable }; } getTagName(state) { const { component, hasWrappedElement } = state; if (!hasWrappedElement) { return null; } return component && component.tagName || 'div'; } getCapabilities(state) { return state.capabilities; } prepareArgs(state, args) { if (args.named.has('__ARGS__')) { let __args__ = args.named.get('__ARGS__').value(); let prepared = { positional: EMPTY_POSITIONAL_ARGS, named: Object.assign({}, args.named.capture().map, __args__) }; if (_env.DEBUG) { delete prepared.named.__ARGS__; } return prepared; } const { positionalParams } = state.ComponentClass.class; // early exits if (positionalParams === undefined || positionalParams === null || args.positional.length === 0) { return null; } let named; if (typeof positionalParams === 'string') { (0, _debug.assert)("You cannot specify positional parameters and the hash argument `" + positionalParams + "`.", !args.named.has(positionalParams)); named = { [positionalParams]: args.positional.capture() }; (0, _polyfills.assign)(named, args.named.capture().map); } else if (Array.isArray(positionalParams) && positionalParams.length > 0) { const count = Math.min(positionalParams.length, args.positional.length); named = {}; (0, _polyfills.assign)(named, args.named.capture().map); for (let i = 0; i < count; i++) { const name = positionalParams[i]; (0, _debug.assert)("You cannot specify both a positional param (at position " + i + ") and the hash argument `" + name + "`.", !args.named.has(name)); named[name] = args.positional.at(i); } } else { return null; } return { positional: _util.EMPTY_ARRAY, named }; } /* * This hook is responsible for actually instantiating the component instance. * It also is where we perform additional bookkeeping to support legacy * features like exposed by view mixins like ChildViewSupport, ActionSupport, * etc. */ create(environment, state, args, dynamicScope, callerSelfRef, hasBlock) { if (_env.DEBUG) { this._pushToDebugStack("component:" + state.name, environment); } // Get the nearest concrete component instance from the scope. "Virtual" // components will be skipped. let parentView = dynamicScope.view; // Get the Ember.Component subclass to instantiate for this component. let factory = state.ComponentClass; // Capture the arguments, which tells Glimmer to give us our own, stable // copy of the Arguments object that is safe to hold on to between renders. let capturedArgs = args.named.capture(); let props = (0, _processArgs.processComponentArgs)(capturedArgs); // Alias `id` argument to `elementId` property on the component instance. aliasIdToElementId(args, props); // Set component instance's parentView property to point to nearest concrete // component. props.parentView = parentView; // Set whether this component was invoked with a block // (`{{#my-component}}{{/my-component}}`) or without one // (`{{my-component}}`). props[_component.HAS_BLOCK] = hasBlock; // Save the current `this` context of the template as the component's // `_target`, so bubbled actions are routed to the right place. props._target = callerSelfRef.value(); // static layout asserts CurriedDefinition if (state.template) { props.layout = state.template; } // Now that we've built up all of the properties to set on the component instance, // actually create it. let component = factory.create(props); let finalizer = (0, _instrumentation._instrumentStart)('render.component', initialRenderInstrumentDetails, component); // We become the new parentView for downstream components, so save our // component off on the dynamic scope. dynamicScope.view = component; // Unless we're the root component, we need to add ourselves to our parent // component's childViews array. if (parentView !== null && parentView !== undefined) { (0, _views.addChildView)(parentView, component); } component.trigger('didReceiveAttrs'); let hasWrappedElement = component.tagName !== ''; // We usually do this in the `didCreateElement`, but that hook doesn't fire for tagless components if (!hasWrappedElement) { if (environment.isInteractive) { component.trigger('willRender'); } component._transitionTo('hasElement'); if (environment.isInteractive) { component.trigger('willInsertElement'); } } // 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 _curlyComponentStateBucket.default(environment, component, capturedArgs, finalizer, hasWrappedElement); if (args.named.has('class')) { bucket.classRef = args.named.get('class'); } if (_env.DEBUG) { processComponentInitializationAssertions(component, props); } if (environment.isInteractive && hasWrappedElement) { component.trigger('willRender'); } return bucket; } getSelf({ component }) { return component[_component.ROOT_REF]; } didCreateElement({ component, classRef, environment }, element, operations) { (0, _views.setViewElement)(component, element); (0, _views.setElementView)(element, component); let { attributeBindings, classNames, classNameBindings } = component; if (attributeBindings && attributeBindings.length) { applyAttributeBindings(element, attributeBindings, component, operations); } else { let id = component.elementId ? component.elementId : (0, _utils.guidFor)(component); operations.setAttribute('id', _runtime.PrimitiveReference.create(id), false, null); _bindings.IsVisibleBinding.install(element, component, operations); } if (classRef) { const ref = new _bindings.SimpleClassNameBindingReference(classRef, classRef['propertyKey']); operations.setAttribute('class', ref, false, null); } if (classNames && classNames.length) { classNames.forEach(name => { operations.setAttribute('class', _runtime.PrimitiveReference.create(name), false, null); }); } if (classNameBindings && classNameBindings.length) { classNameBindings.forEach(binding => { _bindings.ClassNameBinding.install(element, component, binding, operations); }); } operations.setAttribute('class', _runtime.PrimitiveReference.create('ember-view'), false, null); if ('ariaRole' in component) { operations.setAttribute('role', (0, _bindings.referenceForKey)(component, 'ariaRole'), false, null); } component._transitionTo('hasElement'); if (environment.isInteractive) { component.trigger('willInsertElement'); } } didRenderLayout(bucket, bounds) { bucket.component[_component.BOUNDS] = bounds; bucket.finalize(); if (_env.DEBUG) { this.debugStack.pop(); } } getTag({ args, component }) { return args ? (0, _reference.combine)([args.tag, component[_component.DIRTY_TAG]]) : component[_component.DIRTY_TAG]; } didCreate({ component, environment }) { if (environment.isInteractive) { component._transitionTo('inDOM'); component.trigger('didInsertElement'); component.trigger('didRender'); } } update(bucket) { let { component, args, argsRevision, environment } = bucket; if (_env.DEBUG) { this._pushToDebugStack(component._debugContainerKey, environment); } bucket.finalizer = (0, _instrumentation._instrumentStart)('render.component', rerenderInstrumentDetails, component); if (args && !args.tag.validate(argsRevision)) { let props = (0, _processArgs.processComponentArgs)(args); bucket.argsRevision = args.tag.value(); component[_component.IS_DISPATCHING_ATTRS] = true; component.setProperties(props); component[_component.IS_DISPATCHING_ATTRS] = false; component.trigger('didUpdateAttrs'); component.trigger('didReceiveAttrs'); } if (environment.isInteractive) { component.trigger('willUpdate'); component.trigger('willRender'); } } didUpdateLayout(bucket) { bucket.finalize(); if (_env.DEBUG) { this.debugStack.pop(); } } didUpdate({ component, environment }) { if (environment.isInteractive) { component.trigger('didUpdate'); component.trigger('didRender'); } } getDestructor(stateBucket) { return stateBucket; } } _exports.default = CurlyComponentManager; function validatePositionalParameters(named, positional, positionalParamsDefinition) { if (_env.DEBUG) { if (!named || !positional || !positional.length) { return; } let paramType = typeof positionalParamsDefinition; if (paramType === 'string') { // tslint:disable-next-line:max-line-length (0, _debug.assert)("You cannot specify positional parameters and the hash argument `" + positionalParamsDefinition + "`.", !named.has(positionalParamsDefinition)); } else { if (positional.length < positionalParamsDefinition.length) { positionalParamsDefinition = positionalParamsDefinition.slice(0, positional.length); } for (let i = 0; i < positionalParamsDefinition.length; i++) { let name = positionalParamsDefinition[i]; (0, _debug.assert)("You cannot specify both a positional param (at position " + i + ") and the hash argument `" + name + "`.", !named.has(name)); } } } } function processComponentInitializationAssertions(component, props) { (0, _debug.assert)("classNameBindings must be non-empty strings: " + component, (() => { let { classNameBindings } = component; for (let i = 0; i < classNameBindings.length; i++) { let binding = classNameBindings[i]; if (typeof binding !== 'string' || binding.length === 0) { return false; } } return true; })()); (0, _debug.assert)("classNameBindings must not have spaces in them: " + component, (() => { let { classNameBindings } = component; for (let i = 0; i < classNameBindings.length; i++) { let binding = classNameBindings[i]; if (binding.split(' ').length > 1) { return false; } } return true; })()); (0, _debug.assert)("You cannot use `classNameBindings` on a tag-less component: " + component, component.tagName !== '' || !component.classNameBindings || component.classNameBindings.length === 0); (0, _debug.assert)("You cannot use `elementId` on a tag-less component: " + component, component.tagName !== '' || props.id === component.elementId || !component.elementId && component.elementId !== ''); (0, _debug.assert)("You cannot use `attributeBindings` on a tag-less component: " + component, component.tagName !== '' || !component.attributeBindings || component.attributeBindings.length === 0); } function initialRenderInstrumentDetails(component) { return component.instrumentDetails({ initialRender: true }); } function rerenderInstrumentDetails(component) { return component.instrumentDetails({ initialRender: false }); } const CURLY_CAPABILITIES = { dynamicLayout: true, dynamicTag: true, prepareArgs: true, createArgs: true, attributeHook: true, elementHook: true, createCaller: true, dynamicScope: true, updateHook: true, createInstance: true }; _exports.CURLY_CAPABILITIES = CURLY_CAPABILITIES; const CURLY_COMPONENT_MANAGER = new CurlyComponentManager(); class CurlyComponentDefinition { // tslint:disable-next-line:no-shadowed-variable constructor(name, ComponentClass, handle, template, args) { this.name = name; this.ComponentClass = ComponentClass; this.handle = handle; this.manager = CURLY_COMPONENT_MANAGER; const layout = template && template.asLayout(); const symbolTable = layout ? layout.symbolTable : undefined; this.symbolTable = symbolTable; this.template = template; this.args = args; this.state = { name, ComponentClass, handle, template, capabilities: CURLY_CAPABILITIES, symbolTable }; } } _exports.CurlyComponentDefinition = CurlyComponentDefinition; }); enifed("@ember/-internals/glimmer/lib/component-managers/custom", ["exports", "@ember/debug", "@ember/-internals/glimmer/lib/utils/references", "@ember/-internals/glimmer/lib/component-managers/abstract"], function (_exports, _debug, _references, _abstract) { "use strict"; _exports.__esModule = true; _exports.capabilities = capabilities; _exports.hasAsyncLifeCycleCallbacks = hasAsyncLifeCycleCallbacks; _exports.hasDestructors = hasDestructors; _exports.CustomManagerDefinition = _exports.CustomComponentState = _exports.default = void 0; const CAPABILITIES = { dynamicLayout: false, dynamicTag: false, prepareArgs: false, createArgs: true, attributeHook: false, elementHook: false, createCaller: false, dynamicScope: true, updateHook: true, createInstance: true }; function capabilities(managerAPI, options = {}) { (0, _debug.assert)('Invalid component manager compatibility specified', managerAPI === '3.4'); return { asyncLifeCycleCallbacks: Boolean(options.asyncLifecycleCallbacks), destructor: Boolean(options.destructor) }; } function hasAsyncLifeCycleCallbacks(delegate) { return delegate.capabilities.asyncLifeCycleCallbacks; } function hasDestructors(delegate) { return delegate.capabilities.destructor; } /** The CustomComponentManager allows addons to provide custom component implementations that integrate seamlessly into Ember. This is accomplished through a delegate, registered with the custom component manager, which implements a set of hooks that determine component behavior. To create a custom component manager, instantiate a new CustomComponentManager class and pass the delegate as the first argument: ```js let manager = new CustomComponentManager({ // ...delegate implementation... }); ``` ## Delegate Hooks Throughout the lifecycle of a component, the component manager will invoke delegate hooks that are responsible for surfacing those lifecycle changes to the end developer. * `create()` - invoked when a new instance of a component should be created * `update()` - invoked when the arguments passed to a component change * `getContext()` - returns the object that should be */ class CustomComponentManager extends _abstract.default { create(_env, definition, args) { const { delegate } = definition; const capturedArgs = args.capture(); const component = delegate.createComponent(definition.ComponentClass.class, capturedArgs.value()); return new CustomComponentState(delegate, component, capturedArgs); } update({ delegate, component, args }) { delegate.updateComponent(component, args.value()); } didCreate({ delegate, component }) { if (hasAsyncLifeCycleCallbacks(delegate)) { delegate.didCreateComponent(component); } } didUpdate({ delegate, component }) { if (hasAsyncLifeCycleCallbacks(delegate)) { delegate.didUpdateComponent(component); } } getContext({ delegate, component }) { delegate.getContext(component); } getSelf({ delegate, component }) { return _references.RootReference.create(delegate.getContext(component)); } getDestructor(state) { if (hasDestructors(state.delegate)) { return state; } else { return null; } } getCapabilities() { return CAPABILITIES; } getTag({ args }) { return args.tag; } didRenderLayout() {} getLayout(state) { return { handle: state.template.asLayout().compile(), symbolTable: state.symbolTable }; } } _exports.default = CustomComponentManager; const CUSTOM_COMPONENT_MANAGER = new CustomComponentManager(); /** * Stores internal state about a component instance after it's been created. */ class CustomComponentState { constructor(delegate, component, args) { this.delegate = delegate; this.component = component; this.args = args; } destroy() { const { delegate, component } = this; if (hasDestructors(delegate)) { delegate.destroyComponent(component); } } } _exports.CustomComponentState = CustomComponentState; class CustomManagerDefinition { constructor(name, ComponentClass, delegate, template) { this.name = name; this.ComponentClass = ComponentClass; this.delegate = delegate; this.template = template; this.manager = CUSTOM_COMPONENT_MANAGER; const layout = template.asLayout(); const symbolTable = layout.symbolTable; this.symbolTable = symbolTable; this.state = { name, ComponentClass, template, symbolTable, delegate }; } } _exports.CustomManagerDefinition = CustomManagerDefinition; }); enifed("@ember/-internals/glimmer/lib/component-managers/definition-state", [], function () { "use strict"; }); enifed("@ember/-internals/glimmer/lib/component-managers/input", ["exports", "@ember/-internals/metal", "@ember/debug", "@glimmer/reference", "@ember/-internals/glimmer/lib/utils/references", "@ember/-internals/glimmer/lib/component-managers/internal"], function (_exports, _metal, _debug, _reference, _references, _internal) { "use strict"; _exports.__esModule = true; _exports.InputComponentManagerFactory = _exports.default = void 0; const CAPABILITIES = { dynamicLayout: false, dynamicTag: false, prepareArgs: true, createArgs: true, attributeHook: false, elementHook: false, createCaller: true, dynamicScope: false, updateHook: true, createInstance: true }; const EMPTY_POSITIONAL_ARGS = []; (0, _debug.debugFreeze)(EMPTY_POSITIONAL_ARGS); class InputComponentManager extends _internal.default { getCapabilities() { return CAPABILITIES; } prepareArgs(_state, args) { (0, _debug.assert)('The `` component does not take any positional arguments', args.positional.length === 0); let __ARGS__ = args.named.capture().map; return { positional: EMPTY_POSITIONAL_ARGS, named: { __ARGS__: new _references.RootReference(__ARGS__), type: args.named.get('type') } }; } create(_env, { ComponentClass }, args, _dynamicScope, caller) { (0, _debug.assert)('caller must be const', (0, _reference.isConst)(caller)); let type = args.named.get('type'); let instance = ComponentClass.create({ caller: caller.value(), type: type.value() }); return { type, instance }; } getSelf({ instance }) { return new _references.RootReference(instance); } getTag() { return _reference.CONSTANT_TAG; } update({ type, instance }) { (0, _metal.set)(instance, 'type', type.value()); } getDestructor({ instance }) { return instance; } } _exports.default = InputComponentManager; const InputComponentManagerFactory = owner => { return new InputComponentManager(owner); }; _exports.InputComponentManagerFactory = InputComponentManagerFactory; }); enifed("@ember/-internals/glimmer/lib/component-managers/internal", ["exports", "@ember/-internals/glimmer/lib/component-managers/abstract"], function (_exports, _abstract) { "use strict"; _exports.__esModule = true; _exports.default = _exports.InternalComponentDefinition = void 0; class InternalComponentDefinition { constructor(manager, ComponentClass, layout) { this.manager = manager; this.state = { ComponentClass, layout }; } } _exports.InternalComponentDefinition = InternalComponentDefinition; class InternalManager extends _abstract.default { constructor(owner) { super(); this.owner = owner; } getLayout({ layout: _layout }) { let layout = _layout.asLayout(); return { handle: layout.compile(), symbolTable: layout.symbolTable }; } } _exports.default = InternalManager; }); enifed("@ember/-internals/glimmer/lib/component-managers/mount", ["exports", "@glimmer/env", "@glimmer/reference", "@ember/-internals/routing", "@ember/-internals/glimmer/lib/utils/references", "@ember/-internals/glimmer/lib/component-managers/abstract"], function (_exports, _env, _reference, _routing, _references, _abstract) { "use strict"; _exports.__esModule = true; _exports.MountDefinition = void 0; const CAPABILITIES = { dynamicLayout: true, dynamicTag: false, prepareArgs: false, createArgs: false, attributeHook: false, elementHook: false, createCaller: true, dynamicScope: true, updateHook: true, createInstance: true }; class MountManager extends _abstract.default { getDynamicLayout(state, _) { let template = state.engine.lookup('template:application'); let layout = template.asLayout(); return { handle: layout.compile(), symbolTable: layout.symbolTable }; } getCapabilities() { return CAPABILITIES; } create(environment, state) { if (_env.DEBUG) { this._pushEngineToDebugStack("engine:" + state.name, environment); } // TODO // mount is a runtime helper, this shouldn't use dynamic layout // we should resolve the engine app template in the helper // it also should use the owner that looked up the mount helper. let engine = environment.owner.buildChildEngineInstance(state.name); engine.boot(); let applicationFactory = engine.factoryFor("controller:application"); let controllerFactory = applicationFactory || (0, _routing.generateControllerFactory)(engine, 'application'); let controller; let self; let bucket; let tag; let modelRef = state.modelRef; if (modelRef === undefined) { controller = controllerFactory.create(); self = new _references.RootReference(controller); tag = _reference.CONSTANT_TAG; bucket = { engine, controller, self, tag }; } else { let model = modelRef.value(); let modelRev = modelRef.tag.value(); controller = controllerFactory.create({ model }); self = new _references.RootReference(controller); tag = modelRef.tag; bucket = { engine, controller, self, tag, modelRef, modelRev }; } return bucket; } getSelf({ self }) { return self; } getTag(state) { return state.tag; } getDestructor({ engine }) { return engine; } didRenderLayout() { if (_env.DEBUG) { this.debugStack.pop(); } } update(bucket) { let { controller, modelRef, modelRev } = bucket; if (!modelRef.tag.validate(modelRev)) { let model = modelRef.value(); bucket.modelRev = modelRef.tag.value(); controller.set('model', model); } } } const MOUNT_MANAGER = new MountManager(); class MountDefinition { constructor(name, modelRef) { this.manager = MOUNT_MANAGER; this.state = { name, modelRef }; } } _exports.MountDefinition = MountDefinition; }); enifed("@ember/-internals/glimmer/lib/component-managers/outlet", ["exports", "@ember/-internals/environment", "@ember/-internals/utils", "@ember/instrumentation", "@ember/polyfills", "@glimmer/env", "@glimmer/reference", "@glimmer/runtime", "@ember/-internals/glimmer/lib/utils/references", "@ember/-internals/glimmer/lib/component-managers/abstract"], function (_exports, _environment, _utils, _instrumentation, _polyfills, _env, _reference, _runtime, _references, _abstract) { "use strict"; _exports.__esModule = true; _exports.createRootOutlet = createRootOutlet; _exports.OutletComponentDefinition = void 0; function instrumentationPayload(def) { return { object: def.name + ":" + def.outlet }; } const CAPABILITIES = { dynamicLayout: false, dynamicTag: false, prepareArgs: false, createArgs: false, attributeHook: false, elementHook: false, createCaller: true, dynamicScope: true, updateHook: false, createInstance: true }; class OutletComponentManager extends _abstract.default { create(environment, definition, _args, dynamicScope) { if (_env.DEBUG) { this._pushToDebugStack("template:" + definition.template.referrer.moduleName, environment); } dynamicScope.outletState = definition.ref; let controller = definition.controller; let self = controller === undefined ? _runtime.UNDEFINED_REFERENCE : new _references.RootReference(controller); return { self, finalize: (0, _instrumentation._instrumentStart)('render.outlet', instrumentationPayload, definition) }; } getLayout({ template }, _resolver) { // The router has already resolved the template const layout = template.asLayout(); return { handle: layout.compile(), symbolTable: layout.symbolTable }; } getCapabilities() { return CAPABILITIES; } getSelf({ self }) { return self; } getTag() { // an outlet has no hooks return _reference.CONSTANT_TAG; } didRenderLayout(state) { state.finalize(); if (_env.DEBUG) { this.debugStack.pop(); } } getDestructor() { return null; } } const OUTLET_MANAGER = new OutletComponentManager(); class OutletComponentDefinition { constructor(state, manager = OUTLET_MANAGER) { this.state = state; this.manager = manager; } } _exports.OutletComponentDefinition = OutletComponentDefinition; function createRootOutlet(outletView) { if (_environment.ENV._APPLICATION_TEMPLATE_WRAPPER) { const WRAPPED_CAPABILITIES = (0, _polyfills.assign)({}, CAPABILITIES, { dynamicTag: true, elementHook: true }); const WrappedOutletComponentManager = class extends OutletComponentManager { getTagName(_component) { return 'div'; } getLayout(state) { // The router has already resolved the template const template = state.template; const layout = template.asWrappedLayout(); return { handle: layout.compile(), symbolTable: layout.symbolTable }; } getCapabilities() { return WRAPPED_CAPABILITIES; } didCreateElement(component, element, _operations) { // to add GUID id and class element.setAttribute('class', 'ember-view'); element.setAttribute('id', (0, _utils.guidFor)(component)); } }; const WRAPPED_OUTLET_MANAGER = new WrappedOutletComponentManager(); return new OutletComponentDefinition(outletView.state, WRAPPED_OUTLET_MANAGER); } else { return new OutletComponentDefinition(outletView.state); } } }); enifed("@ember/-internals/glimmer/lib/component-managers/root", ["exports", "@ember/-internals/container", "@ember/instrumentation", "@glimmer/env", "@ember/-internals/glimmer/lib/component", "@ember/-internals/glimmer/lib/utils/curly-component-state-bucket", "@ember/-internals/glimmer/lib/component-managers/curly"], function (_exports, _container, _instrumentation, _env, _component, _curlyComponentStateBucket, _curly) { "use strict"; _exports.__esModule = true; _exports.RootComponentDefinition = _exports.ROOT_CAPABILITIES = void 0; class RootComponentManager extends _curly.default { constructor(component) { super(); this.component = component; } getLayout(_state, resolver) { const template = this.templateFor(this.component, resolver); const layout = template.asWrappedLayout(); return { handle: layout.compile(), symbolTable: layout.symbolTable }; } create(environment, _state, _args, dynamicScope) { let component = this.component; if (_env.DEBUG) { this._pushToDebugStack(component._debugContainerKey, environment); } let finalizer = (0, _instrumentation._instrumentStart)('render.component', _curly.initialRenderInstrumentDetails, component); dynamicScope.view = component; let hasWrappedElement = component.tagName !== ''; // We usually do this in the `didCreateElement`, but that hook doesn't fire for tagless components if (!hasWrappedElement) { if (environment.isInteractive) { component.trigger('willRender'); } component._transitionTo('hasElement'); if (environment.isInteractive) { component.trigger('willInsertElement'); } } if (_env.DEBUG) { (0, _curly.processComponentInitializationAssertions)(component, {}); } return new _curlyComponentStateBucket.default(environment, component, null, finalizer, hasWrappedElement); } } // ROOT is the top-level template it has nothing but one yield. // it is supposed to have a dummy element const ROOT_CAPABILITIES = { dynamicLayout: false, dynamicTag: true, prepareArgs: false, createArgs: false, attributeHook: true, elementHook: true, createCaller: true, dynamicScope: true, updateHook: true, createInstance: true }; _exports.ROOT_CAPABILITIES = ROOT_CAPABILITIES; class RootComponentDefinition { constructor(component) { this.component = component; let manager = new RootComponentManager(component); this.manager = manager; let factory = _container.FACTORY_FOR.get(component); this.state = { name: factory.fullName.slice(10), capabilities: ROOT_CAPABILITIES, ComponentClass: factory, handle: null }; } getTag({ component }) { return component[_component.DIRTY_TAG]; } } _exports.RootComponentDefinition = RootComponentDefinition; }); enifed("@ember/-internals/glimmer/lib/component-managers/template-only", ["exports", "@glimmer/reference", "@glimmer/runtime", "@ember/-internals/glimmer/lib/component-managers/abstract"], function (_exports, _reference, _runtime, _abstract) { "use strict"; _exports.__esModule = true; _exports.TemplateOnlyComponentDefinition = _exports.default = void 0; const CAPABILITIES = { dynamicLayout: false, dynamicTag: false, prepareArgs: false, createArgs: false, attributeHook: false, elementHook: false, createCaller: false, dynamicScope: false, updateHook: false, createInstance: true }; class TemplateOnlyComponentManager extends _abstract.default { getLayout(template) { const layout = template.asLayout(); return { handle: layout.compile(), symbolTable: layout.symbolTable }; } getCapabilities() { return CAPABILITIES; } create() { return null; } getSelf() { return _runtime.NULL_REFERENCE; } getTag() { return _reference.CONSTANT_TAG; } getDestructor() { return null; } } _exports.default = TemplateOnlyComponentManager; const MANAGER = new TemplateOnlyComponentManager(); class TemplateOnlyComponentDefinition { constructor(state) { this.state = state; this.manager = MANAGER; } } _exports.TemplateOnlyComponentDefinition = TemplateOnlyComponentDefinition; }); enifed("@ember/-internals/glimmer/lib/component", ["exports", "@ember/-internals/metal", "@ember/-internals/owner", "@ember/-internals/runtime", "@ember/-internals/utils", "@ember/-internals/views", "@ember/debug", "@glimmer/env", "@glimmer/reference", "@glimmer/runtime", "@ember/-internals/glimmer/lib/utils/references"], function (_exports, _metal, _owner, _runtime, _utils, _views, _debug, _env, _reference, _runtime2, _references) { "use strict"; _exports.__esModule = true; _exports.default = _exports.BOUNDS = _exports.HAS_BLOCK = _exports.IS_DISPATCHING_ATTRS = _exports.ROOT_REF = _exports.ARGS = _exports.DIRTY_TAG = void 0; const DIRTY_TAG = (0, _utils.symbol)('DIRTY_TAG'); _exports.DIRTY_TAG = DIRTY_TAG; const ARGS = (0, _utils.symbol)('ARGS'); _exports.ARGS = ARGS; const ROOT_REF = (0, _utils.symbol)('ROOT_REF'); _exports.ROOT_REF = ROOT_REF; const IS_DISPATCHING_ATTRS = (0, _utils.symbol)('IS_DISPATCHING_ATTRS'); _exports.IS_DISPATCHING_ATTRS = IS_DISPATCHING_ATTRS; const HAS_BLOCK = (0, _utils.symbol)('HAS_BLOCK'); _exports.HAS_BLOCK = HAS_BLOCK; const BOUNDS = (0, _utils.symbol)('BOUNDS'); /** @module @ember/component */ /** A component is an isolated piece of UI, represented by a template and an optional class. When a component has a class, its template's `this` value is an instance of the component class. ## Template-only Components The simplest way to create a component is to create a template file in `app/templates/components`. For example, if you name a template `app/templates/components/person-profile.hbs`: ```app/templates/components/person-profile.hbs

{{@person.name}}

{{@person.signature}}

``` You will be able to use `` to invoke this component elsewhere in your application: ```app/templates/application.hbs ``` Note that component names are capitalized here in order to distinguish them from regular HTML elements, but they are dasherized in the file system. While the angle bracket invocation form is generally preferred, it is also possible to invoke the same component with the `{{person-profile}}` syntax: ```app/templates/application.hbs {{person-profile person=this.currentUser}} ``` Note that with this syntax, you use dashes in the component name and arguments are passed without the `@` sign. In both cases, Ember will render the content of the component template we created above. The end result will be something like this: ```html

Tomster

Out of office this week

``` ## File System Nesting Components can be nested inside sub-folders for logical groupping. For example, if we placed our template in `app/templates/components/person/short-profile.hbs`, we can invoke it as ``: ```app/templates/application.hbs ``` Or equivalently, `{{person/short-profile}}`: ```app/templates/application.hbs {{person/short-profile person=this.currentUser}} ``` ## Yielding Contents You can use `yield` inside a template to include the **contents** of any block attached to the component. The block will be executed in its original context: ```handlebars

Admin mode

{{! Executed in the current context. }}
``` or ```handlebars {{#person-profile person=this.currentUser}}

Admin mode

{{! Executed in the current context. }} {{/person-profile}} ``` ```app/templates/components/person-profile.hbs

{{@person.name}}

{{yield}} ``` ## Customizing Components With JavaScript If you want to customize the component in order to handle events, transform arguments or maintain internal state, you implement a subclass of `Component`. One example is to add computed properties to your component: ```app/components/person-profile.js import Component from '@ember/component'; export default Component.extend({ displayName: computed('person.title', 'person.firstName', 'person.lastName', function() { let { title, firstName, lastName } = this; if (title) { return `${title} ${lastName}`; } else { return `${firstName} ${lastName}; } }) }); ``` And then use it in the component's template: ```app/templates/components/person-profile.hbs

{{this.displayName}}

{{yield}} ``` ## Customizing a Component's HTML Element in JavaScript ### HTML Tag The default HTML tag name used for a component's HTML representation is `div`. This can be customized by setting the `tagName` property. Consider the following component class: ```app/components/emphasized-paragraph.js import Component from '@ember/component'; export default Component.extend({ tagName: 'em' }); ``` When invoked, this component would produce output that looks something like this: ```html ``` ### HTML `class` Attribute The HTML `class` attribute of a component's tag can be set by providing a `classNames` property that is set to an array of strings: ```app/components/my-widget.js import Component from '@ember/component'; export default Component.extend({ classNames: ['my-class', 'my-other-class'] }); ``` Invoking this component will produce output that looks like this: ```html
``` `class` attribute values can also be set by providing a `classNameBindings` property set to an array of properties names for the component. The return value of these properties will be added as part of the value for the components's `class` attribute. These properties can be computed properties: ```app/components/my-widget.js import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ classNames: ['my-class', 'my-other-class'], classNameBindings: ['propertyA', 'propertyB'], propertyA: 'from-a', propertyB: computed(function() { if (someLogic) { return 'from-b'; } }) }); ``` Invoking this component will produce HTML that looks like: ```html
``` Note that `classNames` and `classNameBindings` is in addition to the `class` attribute passed with the angle bracket invocation syntax. Therefore, if this component was invoked like so: ```handlebars ``` The resulting HTML will look similar to this: ```html
``` If the value of a class name binding returns a boolean the property name itself will be used as the class name if the property is true. The class name will not be added if the value is `false` or `undefined`. ```app/components/my-widget.js import Component from '@ember/component'; export default Component.extend({ classNameBindings: ['hovered'], hovered: true }); ``` Invoking this component will produce HTML that looks like: ```html
``` ### Custom Class Names for Boolean Values When using boolean class name bindings you can supply a string value other than the property name for use as the `class` HTML attribute by appending the preferred value after a ":" character when defining the binding: ```app/components/my-widget.js import Component from '@ember/component'; export default Component.extend({ classNameBindings: ['awesome:so-very-cool'], awesome: true }); ``` Invoking this component will produce HTML that looks like: ```html
``` Boolean value class name bindings whose property names are in a camelCase-style format will be converted to a dasherized format: ```app/components/my-widget.js import Component from '@ember/component'; export default Component.extend({ classNameBindings: ['isUrgent'], isUrgent: true }); ``` Invoking this component will produce HTML that looks like: ```html
``` Class name bindings can also refer to object values that are found by traversing a path relative to the component itself: ```app/components/my-widget.js import Component from '@ember/component'; import EmberObject from '@ember/object'; export default Component.extend({ classNameBindings: ['messages.empty'], messages: EmberObject.create({ empty: true }) }); ``` Invoking this component will produce HTML that looks like: ```html
``` If you want to add a class name for a property which evaluates to true and and a different class name if it evaluates to false, you can pass a binding like this: ```app/components/my-widget.js import Component from '@ember/component'; export default Component.extend({ classNameBindings: ['isEnabled:enabled:disabled'], isEnabled: true }); ``` Invoking this component will produce HTML that looks like: ```html
``` When isEnabled is `false`, the resulting HTML representation looks like this: ```html
``` This syntax offers the convenience to add a class if a property is `false`: ```app/components/my-widget.js import Component from '@ember/component'; // Applies no class when isEnabled is true and class 'disabled' when isEnabled is false export default Component.extend({ classNameBindings: ['isEnabled::disabled'], isEnabled: true }); ``` Invoking this component when the `isEnabled` property is true will produce HTML that looks like: ```html
``` Invoking it when the `isEnabled` property on the component is `false` will produce HTML that looks like: ```html
``` Updates to the value of a class name binding will result in automatic update of the HTML `class` attribute in the component's rendered HTML representation. If the value becomes `false` or `undefined` the class name will be removed. Both `classNames` and `classNameBindings` are concatenated properties. See [EmberObject](/api/ember/release/classes/EmberObject) documentation for more information about concatenated properties. ### Other HTML Attributes The HTML attribute section of a component's tag can be set by providing an `attributeBindings` property set to an array of property names on the component. The return value of these properties will be used as the value of the component's HTML associated attribute: ```app/components/my-anchor.js import Component from '@ember/component'; export default Component.extend({ tagName: 'a', attributeBindings: ['href'], href: 'http://google.com' }); ``` Invoking this component will produce HTML that looks like: ```html ``` One property can be mapped on to another by placing a ":" between the source property and the destination property: ```app/components/my-anchor.js import Component from '@ember/component'; export default Component.extend({ tagName: 'a', attributeBindings: ['url:href'], url: 'http://google.com' }); ``` Invoking this component will produce HTML that looks like: ```html ``` HTML attributes passed with angle bracket invocations will take precedence over those specified in `attributeBindings`. Therefore, if this component was invoked like so: ```handlebars ``` The resulting HTML will looks like this: ```html ``` Note that the `href` attribute is ultimately set to `http://bing.com`, despite it having attribute binidng to the `url` property, which was set to `http://google.com`. Namespaced attributes (e.g. `xlink:href`) are supported, but have to be mapped, since `:` is not a valid character for properties in Javascript: ```app/components/my-use.js import Component from '@ember/component'; export default Component.extend({ tagName: 'use', attributeBindings: ['xlinkHref:xlink:href'], xlinkHref: '#triangle' }); ``` Invoking this component will produce HTML that looks like: ```html ``` If the value of a property monitored by `attributeBindings` is a boolean, the attribute will be present or absent depending on the value: ```app/components/my-text-input.js import Component from '@ember/component'; export default Component.extend({ tagName: 'input', attributeBindings: ['disabled'], disabled: false }); ``` Invoking this component will produce HTML that looks like: ```html ``` `attributeBindings` can refer to computed properties: ```app/components/my-text-input.js import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ tagName: 'input', attributeBindings: ['disabled'], disabled: computed(function() { if (someLogic) { return true; } else { return false; } }) }); ``` To prevent setting an attribute altogether, use `null` or `undefined` as the value of the property used in `attributeBindings`: ```app/components/my-text-input.js import Component from '@ember/component'; export default Component.extend({ tagName: 'form', attributeBindings: ['novalidate'], novalidate: null }); ``` Updates to the property of an attribute binding will result in automatic update of the HTML attribute in the component's HTML output. `attributeBindings` is a concatenated property. See [EmberObject](/api/ember/release/classes/EmberObject) documentation for more information about concatenated properties. ## Layouts The `layout` property can be used to dynamically specify a template associated with a component class, instead of relying on Ember to link together a component class and a template based on file names. In general, applications should not use this feature, but it's commonly used in addons for historical reasons. The `layout` property should be set to the default export of a template module, which is the name of a template file without the `.hbs` extension. ```app/templates/components/person-profile.hbs

Person's Title

{{yield}}
``` ```app/components/person-profile.js import Component from '@ember/component'; import layout from '../templates/components/person-profile'; export default Component.extend({ layout }); ``` If you invoke the component: ```handlebars

Chief Basket Weaver

Fisherman Industries

``` or ```handlebars {{#person-profile}}

Chief Basket Weaver

Fisherman Industries

{{/person-profile}} ``` It will result in the following HTML output: ```html

Person's Title

Chief Basket Weaver

Fisherman Industries

``` ## Handling Browser Events Components can respond to user-initiated events in one of two ways: adding event handler methods to the component's class, or adding actions to the component's template. ### Event Handler Methods Components can respond to user-initiated events by implementing a method that matches the event name. An event object will be passed as the argument to this method. ```app/components/my-widget.js import Component from '@ember/component'; export default Component.extend({ click(event) { // `event.target` is either the component's element or one of its children let tag = event.target.tagName.toLowerCase(); console.log('clicked on a `<${tag}>` HTML element!'); } }); ``` In this example, whenever the user clicked anywhere inside the component, it will log a message to the console. It is possible to handle event types other than `click` by implementing the following event handler methods. In addition, custom events can be registered by using `Application.customEvents`. Touch events: * `touchStart` * `touchMove` * `touchEnd` * `touchCancel` Keyboard events: * `keyDown` * `keyUp` * `keyPress` Mouse events: * `mouseDown` * `mouseUp` * `contextMenu` * `click` * `doubleClick` * `mouseMove` * `focusIn` * `focusOut` * `mouseEnter` * `mouseLeave` Form events: * `submit` * `change` * `focusIn` * `focusOut` * `input` Drag and drop events: * `dragStart` * `drag` * `dragEnter` * `dragLeave` * `dragOver` * `dragEnd` * `drop` ### `{{action}}` Helper Instead of handling all events of a particular type anywhere inside the component's element, you may instead want to limit it to a particular element in the component's template. In this case, it would be more convenient to implement an action instead. For example, you could implement the action `hello` for the `person-profile` component: ```app/components/person-profile.js import Component from '@ember/component'; export default Component.extend({ actions: { hello(name) { console.log("Hello", name); } } }); ``` And then use it in the component's template: ```app/templates/components/person-profile.hbs

{{@person.name}}

``` When the user clicks the button, Ember will invoke the `hello` action, passing in the current value of `@person.name` as an argument. See [Ember.Templates.helpers.action](/api/ember/release/classes/Ember.Templates.helpers/methods/action?anchor=action). @class Component @extends Ember.CoreView @uses Ember.TargetActionSupport @uses Ember.ClassNamesSupport @uses Ember.ActionSupport @uses Ember.ViewMixin @uses Ember.ViewStateSupport @public */ _exports.BOUNDS = BOUNDS; const Component = _views.CoreView.extend(_views.ChildViewsSupport, _views.ViewStateSupport, _views.ClassNamesSupport, _runtime.TargetActionSupport, _views.ActionSupport, _views.ViewMixin, { isComponent: true, init() { this._super(...arguments); this[IS_DISPATCHING_ATTRS] = false; this[DIRTY_TAG] = _reference.DirtyableTag.create(); this[ROOT_REF] = new _references.RootReference(this); this[BOUNDS] = null; if (_env.DEBUG && this.renderer._destinedForDOM && this.tagName === '') { let eventNames = []; let eventDispatcher = (0, _owner.getOwner)(this).lookup('event_dispatcher:main'); let events = eventDispatcher && eventDispatcher._finalEvents || {}; // tslint:disable-next-line:forin for (let key in events) { let methodName = events[key]; if (typeof this[methodName] === 'function') { eventNames.push(methodName); } } // If in a tagless component, assert that no event handlers are defined (0, _debug.assert)( // tslint:disable-next-line:max-line-length "You can not define `" + eventNames + "` function(s) to handle DOM event in the `" + this + "` tagless component since it doesn't have any DOM element.", !eventNames.length); } }, rerender() { this[DIRTY_TAG].inner.dirty(); this._super(); }, [_metal.PROPERTY_DID_CHANGE](key) { if (this[IS_DISPATCHING_ATTRS]) { return; } let args = this[ARGS]; let reference = args !== undefined ? args[key] : undefined; if (reference !== undefined && reference[_references.UPDATE] !== undefined) { reference[_references.UPDATE]((0, _metal.get)(this, key)); } }, getAttr(key) { // TODO Intimate API should be deprecated return this.get(key); }, /** Normally, Ember's component model is "write-only". The component takes a bunch of attributes that it got passed in, and uses them to render its template. One nice thing about this model is that if you try to set a value to the same thing as last time, Ember (through HTMLBars) will avoid doing any work on the DOM. This is not just a performance optimization. If an attribute has not changed, it is important not to clobber the element's "hidden state". For example, if you set an input's `value` to the same value as before, it will clobber selection state and cursor position. In other words, setting an attribute is not **always** idempotent. This method provides a way to read an element's attribute and also update the last value Ember knows about at the same time. This makes setting an attribute idempotent. In particular, what this means is that if you get an `` element's `value` attribute and then re-render the template with the same value, it will avoid clobbering the cursor and selection position. Since most attribute sets are idempotent in the browser, you typically can get away with reading attributes using jQuery, but the most reliable way to do so is through this method. @method readDOMAttr @param {String} name the name of the attribute @return String @public */ readDOMAttr(name) { // TODO revisit this let _element = (0, _views.getViewElement)(this); (0, _debug.assert)("Cannot call `readDOMAttr` on " + this + " which does not have an element", _element !== null); let element = _element; let isSVG = element.namespaceURI === _runtime2.SVG_NAMESPACE; let { type, normalized } = (0, _runtime2.normalizeProperty)(element, name); if (isSVG || type === 'attr') { return element.getAttribute(normalized); } return element[normalized]; }, /** The WAI-ARIA role of the control represented by this view. For example, a button may have a role of type 'button', or a pane may have a role of type 'alertdialog'. This property is used by assistive software to help visually challenged users navigate rich web applications. The full list of valid WAI-ARIA roles is available at: [https://www.w3.org/TR/wai-aria/#roles_categorization](https://www.w3.org/TR/wai-aria/#roles_categorization) @property ariaRole @type String @default null @public */ /** Enables components to take a list of parameters as arguments. For example, a component that takes two parameters with the names `name` and `age`: ```app/components/my-component.js import Component from '@ember/component'; let MyComponent = Component.extend(); MyComponent.reopenClass({ positionalParams: ['name', 'age'] }); export default MyComponent; ``` It can then be invoked like this: ```hbs {{my-component "John" 38}} ``` The parameters can be referred to just like named parameters: ```hbs Name: {{name}}, Age: {{age}}. ``` Using a string instead of an array allows for an arbitrary number of parameters: ```app/components/my-component.js import Component from '@ember/component'; let MyComponent = Component.extend(); MyComponent.reopenClass({ positionalParams: 'names' }); export default MyComponent; ``` It can then be invoked like this: ```hbs {{my-component "John" "Michael" "Scott"}} ``` The parameters can then be referred to by enumerating over the list: ```hbs {{#each names as |name|}}{{name}}{{/each}} ``` @static @public @property positionalParams @since 1.13.0 */ /** Called when the attributes passed into the component have been updated. Called both during the initial render of a container and during a rerender. Can be used in place of an observer; code placed here will be executed every time any attribute updates. @method didReceiveAttrs @public @since 1.13.0 */ didReceiveAttrs() {}, /** Called when the attributes passed into the component have been updated. Called both during the initial render of a container and during a rerender. Can be used in place of an observer; code placed here will be executed every time any attribute updates. @event didReceiveAttrs @public @since 1.13.0 */ /** Called after a component has been rendered, both on initial render and in subsequent rerenders. @method didRender @public @since 1.13.0 */ didRender() {}, /** Called after a component has been rendered, both on initial render and in subsequent rerenders. @event didRender @public @since 1.13.0 */ /** Called before a component has been rendered, both on initial render and in subsequent rerenders. @method willRender @public @since 1.13.0 */ willRender() {}, /** Called before a component has been rendered, both on initial render and in subsequent rerenders. @event willRender @public @since 1.13.0 */ /** Called when the attributes passed into the component have been changed. Called only during a rerender, not during an initial render. @method didUpdateAttrs @public @since 1.13.0 */ didUpdateAttrs() {}, /** Called when the attributes passed into the component have been changed. Called only during a rerender, not during an initial render. @event didUpdateAttrs @public @since 1.13.0 */ /** Called when the component is about to update and rerender itself. Called only during a rerender, not during an initial render. @method willUpdate @public @since 1.13.0 */ willUpdate() {}, /** Called when the component is about to update and rerender itself. Called only during a rerender, not during an initial render. @event willUpdate @public @since 1.13.0 */ /** Called when the component has updated and rerendered itself. Called only during a rerender, not during an initial render. @method didUpdate @public @since 1.13.0 */ didUpdate() {} }); Component.toString = () => '@ember/component'; Component.reopenClass({ isComponentFactory: true, positionalParams: [] }); var _default = Component; _exports.default = _default; }); enifed("@ember/-internals/glimmer/lib/components/checkbox", ["exports", "@ember/-internals/metal", "@ember/debug", "@glimmer/env", "@ember/-internals/glimmer/lib/component", "@ember/-internals/glimmer/lib/templates/empty"], function (_exports, _metal, _debug, _env, _component, _empty) { "use strict"; _exports.__esModule = true; _exports.default = void 0; /** @module @ember/component */ /** The internal class used to create text inputs when the `{{input}}` helper is used with `type` of `checkbox`. See [Ember.Templates.helpers.input](/api/ember/release/classes/Ember.Templates.helpers/methods/input?anchor=input) for usage details. ## Direct manipulation of `checked` The `checked` attribute of an `Checkbox` object should always be set through the Ember object or by interacting with its rendered element representation via the mouse, keyboard, or touch. Updating the value of the checkbox via jQuery will result in the checked value of the object and its element losing synchronization. ## Layout and LayoutName properties Because HTML `input` elements are self closing `layout` and `layoutName` properties will not be applied. @class Checkbox @extends Component @public */ const Checkbox = _component.default.extend({ layout: _empty.default, /** By default, this component will add the `ember-checkbox` class to the component's element. @property classNames @type Array | String @default ['ember-checkbox'] @public */ classNames: ['ember-checkbox'], tagName: 'input', /** By default this component will forward a number of arguments to attributes on the the component's element: * indeterminate * disabled * tabindex * name * autofocus * required * form When invoked with curly braces, this is the exhaustive list of HTML attributes you can customize (i.e. `{{input type="checkbox" disabled=true}}`). When invoked with angle bracket invocation, this list is irrelevant, because you can use HTML attribute syntax to customize the element (i.e. ``). However, `@type` and `@checked` must be passed as named arguments, not attributes. @property attributeBindings @type Array | String @default ['type', 'checked', 'indeterminate', 'disabled', 'tabindex', 'name', 'autofocus', 'required', 'form'] @public */ attributeBindings: ['type', 'checked', 'indeterminate', 'disabled', 'tabindex', 'name', 'autofocus', 'required', 'form'], /** Sets the `type` attribute of the `Checkbox`'s element @property disabled @default false @private */ type: 'checkbox', /** Sets the `disabled` attribute of the `Checkbox`'s element @property disabled @default false @public */ disabled: false, /** Corresponds to the `indeterminate` property of the `Checkbox`'s element @property disabled @default false @public */ indeterminate: false, /** Whenever the checkbox is inserted into the DOM, perform initialization steps, which include setting the indeterminate property if needed. If this method is overridden, `super` must be called. @method @public */ didInsertElement() { this._super(...arguments); this.element.indeterminate = Boolean(this.indeterminate); }, /** Whenever the `change` event is fired on the checkbox, update its `checked` property to reflect whether the checkbox is checked. If this method is overridden, `super` must be called. @method @public */ change() { (0, _metal.set)(this, 'checked', this.element.checked); } }); if (_env.DEBUG) { const UNSET = {}; Checkbox.reopen({ value: UNSET, didReceiveAttrs() { this._super(); (0, _debug.assert)("`` is not supported; " + "please use `` instead.", !(this.type === 'checkbox' && this.value !== UNSET)); } }); } Checkbox.toString = () => '@ember/component/checkbox'; var _default = Checkbox; _exports.default = _default; }); enifed("@ember/-internals/glimmer/lib/components/input", ["exports", "@ember/-internals/metal", "@ember/-internals/runtime", "@ember/canary-features", "@ember/-internals/glimmer/lib/component-managers/input", "@ember/-internals/glimmer/lib/utils/managers"], function (_exports, _metal, _runtime, _canaryFeatures, _input, _managers) { "use strict"; _exports.__esModule = true; _exports.default = void 0; /** @module @ember/component */ let Input; if (_canaryFeatures.EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS) { /** See [Ember.Templates.components.Input](/api/ember/release/classes/Ember.Templates.components/methods/Input?anchor=Input). @method input @for Ember.Templates.helpers @param {Hash} options @public */ /** The `Input` component lets you create an HTML `` element. ```handlebars ``` creates an `` element with `type="text"` and value set to 987. ### Text field If no `type` argument is specified, a default of type 'text' is used. ```handlebars Search: ``` In this example, the initial value in the `` will be set to the value of `this.searchWord`. If the user changes the text, the value of `this.searchWord` will also be updated. ### Actions The `Input` component takes a number of arguments with callbacks that are invoked in response to user events. * `enter` * `insert-newline` * `escape-press` * `focus-in` * `focus-out` * `key-press` * `key-up` These callbacks are passed to `Input` like this: ```handlebars ``` ### `` HTML Attributes to Avoid In most cases, if you want to pass an attribute to the underlying HTML `` element, you can pass the attribute directly, just like any other Ember component. ```handlebars ``` In this example, the `size` attribute will be applied to the underlying `` element in the outputted HTML. However, there are a few attributes where you **must** use the `@` version. * `@type`: This argument is used to control which Ember component is used under the hood * `@value`: The `@value` argument installs a two-way binding onto the element. If you wanted a one-way binding, use `` with the `value` property and the `input` event instead. * `@checked` (for checkboxes): like `@value`, the `@checked` argument installs a two-way binding onto the element. If you wanted a one-way binding, use `` with `checked` and the `input` event instead. ### Extending `TextField` Internally, `` creates an instance of `TextField`, passing arguments from the helper to `TextField`'s `create` method. Subclassing `TextField` is supported but not recommended. See [TextField](/api/ember/release/classes/TextField) ### Checkbox To create an ``: ```handlebars Emberize Everything: ``` This will bind the checked state of this checkbox to the value of `isEmberized` -- if either one changes, it will be reflected in the other. ### Extending `Checkbox` Internally, `` creates an instance of `Checkbox`. Subclassing `TextField` is supported but not recommended. See [Checkbox](/api/ember/release/classes/Checkbox) @method Input @for Ember.Templates.components @see {TextField} @see {Checkbox} @param {Hash} options @public */ Input = _runtime.Object.extend({ isCheckbox: (0, _metal.computed)('type', function () { return this.type === 'checkbox'; }) }); (0, _managers.setManager)({ factory: _input.InputComponentManagerFactory, internal: true, type: 'component' }, Input); Input.toString = () => '@ember/component/input'; } var _default = Input; _exports.default = _default; }); enifed("@ember/-internals/glimmer/lib/components/link-to", ["exports", "@ember/-internals/metal", "@ember/-internals/views", "@ember/canary-features", "@ember/debug", "@ember/instrumentation", "@ember/polyfills", "@ember/service", "@glimmer/env", "@ember/-internals/glimmer/lib/component", "@ember/-internals/glimmer/lib/templates/link-to"], function (_exports, _metal, _views, _canaryFeatures, _debug, _instrumentation, _polyfills, _service, _env, _component, _linkTo) { "use strict"; _exports.__esModule = true; _exports.default = void 0; /** @module ember */ let LinkComponent; if (_canaryFeatures.EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS) { /** The `LinkTo` component renders a link to the supplied `routeName` passing an optionally supplied model to the route as its `model` context of the route. The block for `LinkTo` becomes the contents of the rendered element: ```handlebars Great Hamster Photos ``` This will result in: ```html Great Hamster Photos ``` ### Disabling the `LinkTo` component The `LinkTo` component can be disabled by using the `disabled` argument. A disabled link doesn't result in a transition when activated, and adds the `disabled` class to the `` element. (The class name to apply to the element can be overridden by using the `disabledClass` argument) ```handlebars Great Hamster Photos ``` ### Handling `href` `` will use your application's Router to fill the element's `href` property with a URL that matches the path to the supplied `routeName`. ### Handling current route The `LinkTo` component will apply a CSS class name of 'active' when the application's current route matches the supplied routeName. For example, if the application's current route is 'photoGallery.recent', then the following invocation of `LinkTo`: ```handlebars Great Hamster Photos ``` will result in ```html Great Hamster Photos ``` The CSS class used for active classes can be customized by passing an `activeClass` argument: ```handlebars Great Hamster Photos ``` ```html Great Hamster Photos ``` ### Keeping a link active for other routes If you need a link to be 'active' even when it doesn't match the current route, you can use the `current-when` argument. ```handlebars Photo Gallery ``` This may be helpful for keeping links active for: * non-nested routes that are logically related * some secondary menu approaches * 'top navigation' with 'sub navigation' scenarios A link will be active if `current-when` is `true` or the current route is the route this link would transition to. To match multiple routes 'space-separate' the routes: ```handlebars Art Gallery ``` ### Supplying a model An optional `model` argument can be used for routes whose paths contain dynamic segments. This argument will become the model context of the linked route: ```javascript Router.map(function() { this.route("photoGallery", {path: "hamster-photos/:photo_id"}); }); ``` ```handlebars {{aPhoto.title}} ``` ```html Tomster ``` ### Supplying multiple models For deep-linking to route paths that contain multiple dynamic segments, the `models` argument can be used. As the router transitions through the route path, each supplied model argument will become the context for the route with the dynamic segments: ```javascript Router.map(function() { this.route("photoGallery", { path: "hamster-photos/:photo_id" }, function() { this.route("comment", {path: "comments/:comment_id"}); }); }); ``` This argument will become the model context of the linked route: ```handlebars {{comment.body}} ``` ```html A+++ would snuggle again. ``` ### Supplying an explicit dynamic segment value If you don't have a model object available to pass to `LinkTo`, an optional string or integer argument can be passed for routes whose paths contain dynamic segments. This argument will become the value of the dynamic segment: ```javascript Router.map(function() { this.route("photoGallery", { path: "hamster-photos/:photo_id" }); }); ``` ```handlebars {{this.aPhoto.title}} ``` ```html Tomster ``` When transitioning into the linked route, the `model` hook will be triggered with parameters including this passed identifier. ### Allowing Default Action By default the `` component prevents the default browser action by calling `preventDefault()` to avoid reloading the browser page. If you need to trigger a full browser reload pass `@preventDefault={{false}}`: ```handlebars {{this.aPhotoId.title}} ``` ### Supplying a `tagName` By default `` renders an `` element. This can be overridden for a single use of `` by supplying a `tagName` argument: ```handlebars Great Hamster Photos ``` This produces: ```html
  • Great Hamster Photos
  • ``` In general, this is not recommended. Instead, you can use the `transition-to` helper together with a click event handler on the HTML tag of your choosing. @for Ember.Templates.components @method LinkTo @see {LinkComponent} @public */ /** @module @ember/routing */ /** See [Ember.Templates.components.LinkTo](/api/ember/release/classes/Ember.Templates.components/methods/input?anchor=input). @for Ember.Templates.helpers @method link-to @see {Ember.Templates.components.LinkTo} @public **/ /** `LinkComponent` is the internal component invoked with `` or `{{link-to}}`. @class LinkComponent @extends Component @see {Ember.Templates.components.LinkTo} @public **/ const UNDEFINED = Object.freeze({ toString() { return 'UNDEFINED'; } }); const EMPTY_QUERY_PARAMS = Object.freeze({}); LinkComponent = _component.default.extend({ layout: _linkTo.default, tagName: 'a', /** @property route @category EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS @public */ route: UNDEFINED, /** @property model @category EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS @public */ model: UNDEFINED, /** @property models @category EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS @public */ models: UNDEFINED, /** @property query @category EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS @public */ query: UNDEFINED, /** Used to determine when this `LinkComponent` is active. @property current-when @public */ 'current-when': null, /** Sets the `title` attribute of the `LinkComponent`'s HTML element. @property title @default null @public **/ title: null, /** Sets the `rel` attribute of the `LinkComponent`'s HTML element. @property rel @default null @public **/ rel: null, /** Sets the `tabindex` attribute of the `LinkComponent`'s HTML element. @property tabindex @default null @public **/ tabindex: null, /** Sets the `target` attribute of the `LinkComponent`'s HTML element. @since 1.8.0 @property target @default null @public **/ target: null, /** The CSS class to apply to `LinkComponent`'s element when its `active` property is `true`. @property activeClass @type String @default active @public **/ activeClass: 'active', /** The CSS class to apply to `LinkComponent`'s element when its `loading` property is `true`. @property loadingClass @type String @default loading @private **/ loadingClass: 'loading', /** The CSS class to apply to a `LinkComponent`'s element when its `disabled` property is `true`. @property disabledClass @type String @default disabled @private **/ disabledClass: 'disabled', /** Determines whether the `LinkComponent` will trigger routing via the `replaceWith` routing strategy. @property replace @type Boolean @default false @public **/ replace: false, /** By default this component will forward `href`, `title`, `rel`, `tabindex`, and `target` arguments to attributes on the component's element. When invoked with `{{link-to}}`, you can only customize these attributes. When invoked with ``, you can just use HTML attributes directly. @property attributeBindings @type Array | String @default ['title', 'rel', 'tabindex', 'target'] @public */ attributeBindings: ['href', 'title', 'rel', 'tabindex', 'target'], /** By default this component will set classes on its element when any of the following arguments are truthy: * active * loading * disabled When these arguments are truthy, a class with the same name will be set on the element. When falsy, the associated class will not be on the element. @property classNameBindings @type Array @default ['active', 'loading', 'disabled', 'ember-transitioning-in', 'ember-transitioning-out'] @public */ classNameBindings: ['active', 'loading', 'disabled', 'transitioningIn', 'transitioningOut'], /** By default this component responds to the `click` event. When the component element is an `
    ` element, activating the link in another way, such as using the keyboard, triggers the click event. @property eventName @type String @default click @private */ eventName: 'click', // this is doc'ed here so it shows up in the events // section of the API documentation, which is where // people will likely go looking for it. /** Triggers the `LinkComponent`'s routing behavior. If `eventName` is changed to a value other than `click` the routing behavior will trigger on that custom event instead. @event click @private */ /** An overridable method called when `LinkComponent` objects are instantiated. Example: ```app/components/my-link.js import LinkComponent from '@ember/routing/link-component'; export default LinkComponent.extend({ init() { this._super(...arguments); console.log('Event is ' + this.get('eventName')); } }); ``` NOTE: If you do override `init` for a framework class like `Component`, be sure to call `this._super(...arguments)` in your `init` declaration! If you don't, Ember may not have an opportunity to do important setup work, and you'll see strange behavior in your application. @method init @private */ init() { this._super(...arguments); // Map desired event name to invoke function let { eventName } = this; this.on(eventName, this, this._invoke); }, _routing: (0, _service.inject)('-routing'), _currentRoute: (0, _metal.alias)('_routing.currentRouteName'), _currentRouterState: (0, _metal.alias)('_routing.currentState'), _targetRouterState: (0, _metal.alias)('_routing.targetState'), _route: (0, _metal.computed)('route', '_currentRouterState', function computeLinkToComponentRoute() { let { route } = this; return route === UNDEFINED ? this._currentRoute : route; }), _models: (0, _metal.computed)('model', 'models', function computeLinkToComponentModels() { let { model, models } = this; (0, _debug.assert)('You cannot provide both the `@model` and `@models` arguments to the component.', model === UNDEFINED || models === UNDEFINED); if (model !== UNDEFINED) { return [model]; } else if (models !== UNDEFINED) { (0, _debug.assert)('The `@models` argument must be an array.', Array.isArray(models)); return models; } else { return []; } }), _query: (0, _metal.computed)('query', function computeLinkToComponentQuery() { let { query } = this; if (query === UNDEFINED) { return EMPTY_QUERY_PARAMS; } else { return Object.assign({}, query); } }), /** Accessed as a classname binding to apply the component's `disabledClass` CSS `class` to the element when the link is disabled. When `true`, interactions with the element will not trigger route changes. @property disabled @private */ disabled: (0, _metal.computed)({ get(_key) { // always returns false for `get` because (due to the `set` just below) // the cached return value from the set will prevent this getter from _ever_ // being called after a set has occured return false; }, set(_key, value) { this._isDisabled = value; return value ? this.disabledClass : false; } }), /** Accessed as a classname binding to apply the component's `activeClass` CSS `class` to the element when the link is active. This component is considered active when its `currentWhen` property is `true` or the application's current route is the route this component would trigger transitions into. The `currentWhen` property can match against multiple routes by separating route names using the ` ` (space) character. @property active @private */ active: (0, _metal.computed)('activeClass', '_active', function computeLinkToComponentActiveClass() { return this._active ? this.activeClass : false; }), _active: (0, _metal.computed)('_currentRouterState', '_route', '_models', '_query', 'loading', 'current-when', function computeLinkToComponentActive() { let { _currentRouterState: state } = this; if (state) { return this._isActive(state); } else { return false; } }), willBeActive: (0, _metal.computed)('_currentRouterState', '_targetRouterState', '_route', '_models', '_query', 'loading', 'current-when', function computeLinkToComponentWillBeActive() { let { _currentRouterState: current, _targetRouterState: target } = this; if (current === target) { return; } return this._isActive(target); }), _isActive(routerState) { if (this.loading) { return false; } let currentWhen = this['current-when']; if (typeof currentWhen === 'boolean') { return currentWhen; } let isCurrentWhenSpecified = Boolean(currentWhen); if (isCurrentWhenSpecified) { currentWhen = currentWhen.split(' '); } else { currentWhen = [this._route]; } let { _models: models, _query: query, _routing: routing } = this; for (let i = 0; i < currentWhen.length; i++) { if (routing.isActiveForRoute(models, query, currentWhen[i], routerState, isCurrentWhenSpecified)) { return true; } } return false; }, transitioningIn: (0, _metal.computed)('_active', 'willBeActive', function computeLinkToComponentTransitioningIn() { if (this.willBeActive === true && !this._active) { return 'ember-transitioning-in'; } else { return false; } }), transitioningOut: (0, _metal.computed)('_active', 'willBeActive', function computeLinkToComponentTransitioningOut() { if (this.willBeActive === false && this._active) { return 'ember-transitioning-out'; } else { return false; } }), /** Event handler that invokes the link, activating the associated route. @method _invoke @param {Event} event @private */ _invoke(event) { if (!(0, _views.isSimpleClick)(event)) { return true; } let { bubbles, preventDefault } = this; let target = this.element.target; let isSelf = !target || target === '_self'; if (preventDefault !== false && isSelf) { event.preventDefault(); } if (bubbles === false) { event.stopPropagation(); } if (this._isDisabled) { return false; } if (this.loading) { // tslint:disable-next-line:max-line-length (0, _debug.warn)('This link is in an inactive loading state because at least one of its models ' + 'currently has a null/undefined value, or the provided route name is invalid.', false, { id: 'ember-glimmer.link-to.inactive-loading-state' }); return false; } if (!isSelf) { return false; } let { _route: routeName, _models: models, _query: queryParams, replace: shouldReplace } = this; let payload = { queryParams, routeName }; (0, _instrumentation.flaggedInstrument)('interaction.link-to', payload, this._generateTransition(payload, routeName, models, queryParams, shouldReplace)); return false; }, _generateTransition(payload, qualifiedRouteName, models, queryParams, shouldReplace) { let { _routing: routing } = this; return () => { payload.transition = routing.transitionTo(qualifiedRouteName, models, queryParams, shouldReplace); }; }, /** Sets the element's `href` attribute to the url for the `LinkComponent`'s targeted route. If the `LinkComponent`'s `tagName` is changed to a value other than `a`, this property will be ignored. @property href @private */ href: (0, _metal.computed)('_currentRouterState', '_route', '_models', '_query', 'tagName', 'loading', 'loadingHref', function computeLinkToComponentHref() { if (this.tagName !== 'a') { return; } if (this.loading) { return this.loadingHref; } let { _route: route, _models: models, _query: query, _routing: routing } = this; if (_env.DEBUG) { /* * Unfortunately, to get decent error messages, we need to do this. * In some future state we should be able to use a "feature flag" * which allows us to strip this without needing to call it twice. * * if (isDebugBuild()) { * // Do the useful debug thing, probably including try/catch. * } else { * // Do the performant thing. * } */ try { return routing.generateURL(route, models, query); } catch (e) { // tslint:disable-next-line:max-line-length (0, _debug.assert)("You attempted to generate a link for the \"" + this.route + "\" route, but did not " + "pass the models required for generating its dynamic segments. " + e.message); } } else { return routing.generateURL(route, models, query); } }), loading: (0, _metal.computed)('_route', '_modelsAreLoaded', 'loadingClass', function computeLinkToComponentLoading() { let { _route: route, _modelsAreLoaded: loaded } = this; if (!loaded || route === null || route === undefined) { return this.loadingClass; } }), _modelsAreLoaded: (0, _metal.computed)('_models', function computeLinkToComponentModelsAreLoaded() { let { _models: models } = this; for (let i = 0; i < models.length; i++) { let model = models[i]; if (model === null || model === undefined) { return false; } } return true; }), /** The default href value to use while a link-to is loading. Only applies when tagName is 'a' @property loadingHref @type String @default # @private */ loadingHref: '#', didReceiveAttrs() { let { disabledWhen } = this; if (disabledWhen !== undefined) { this.set('disabled', disabledWhen); } let { params } = this; if (!params || params.length === 0) { (0, _debug.assert)('You must provide at least one of the `@route`, `@model`, `@models` or `@query` argument to ``.', !(this.route === UNDEFINED && this.model === UNDEFINED && this.models === UNDEFINED && this.query === UNDEFINED)); if (_env.DEBUG && this.query === UNDEFINED) { let { _models: models } = this; let lastModel = models.length > 0 && models[models.length - 1]; (0, _debug.assert)('The `(query-params)` helper can only be used when invoking the `{{link-to}}` component.', !(lastModel && lastModel.isQueryParams)); } return; } params = params.slice(); // Process the positional arguments, in order. // 1. Inline link title comes first, if present. if (!this[_component.HAS_BLOCK]) { this.set('linkTitle', params.shift()); } // 2. The last argument is possibly the `query` object. let queryParams = params[params.length - 1]; if (queryParams && queryParams.isQueryParams) { this.set('query', params.pop().values); } else { this.set('query', UNDEFINED); } // 3. If there is a `route`, it is now at index 0. if (params.length === 0) { this.set('route', UNDEFINED); } else { this.set('route', params.shift()); } // 4. Any remaining indices (if any) are `models`. this.set('model', UNDEFINED); this.set('models', params); } }); LinkComponent.toString = () => '@ember/routing/link-component'; LinkComponent.reopenClass({ positionalParams: 'params' }); } else { /** The `{{link-to}}` component renders a link to the supplied `routeName` passing an optionally supplied model to the route as its `model` context of the route. The block for `{{link-to}}` becomes the innerHTML of the rendered element: ```handlebars {{#link-to 'photoGallery'}} Great Hamster Photos {{/link-to}} ``` You can also use an inline form of `{{link-to}}` component by passing the link text as the first argument to the component: ```handlebars {{link-to 'Great Hamster Photos' 'photoGallery'}} ``` Both will result in: ```html Great Hamster Photos ``` ### Supplying a tagName By default `{{link-to}}` renders an `` element. This can be overridden for a single use of `{{link-to}}` by supplying a `tagName` option: ```handlebars {{#link-to 'photoGallery' tagName="li"}} Great Hamster Photos {{/link-to}} ``` ```html
  • Great Hamster Photos
  • ``` To override this option for your entire application, see "Overriding Application-wide Defaults". ### Disabling the `link-to` component By default `{{link-to}}` is enabled. any passed value to the `disabled` component property will disable the `link-to` component. static use: the `disabled` option: ```handlebars {{#link-to 'photoGallery' disabled=true}} Great Hamster Photos {{/link-to}} ``` dynamic use: the `disabledWhen` option: ```handlebars {{#link-to 'photoGallery' disabledWhen=controller.someProperty}} Great Hamster Photos {{/link-to}} ``` any truthy value passed to `disabled` will disable it except `undefined`. See "Overriding Application-wide Defaults" for more. ### Handling `href` `{{link-to}}` will use your application's Router to fill the element's `href` property with a url that matches the path to the supplied `routeName` for your router's configured `Location` scheme, which defaults to HashLocation. ### Handling current route `{{link-to}}` will apply a CSS class name of 'active' when the application's current route matches the supplied routeName. For example, if the application's current route is 'photoGallery.recent' the following use of `{{link-to}}`: ```handlebars {{#link-to 'photoGallery.recent'}} Great Hamster Photos {{/link-to}} ``` will result in ```html
    Great Hamster Photos ``` The CSS class name used for active classes can be customized for a single use of `{{link-to}}` by passing an `activeClass` option: ```handlebars {{#link-to 'photoGallery.recent' activeClass="current-url"}} Great Hamster Photos {{/link-to}} ``` ```html Great Hamster Photos ``` To override this option for your entire application, see "Overriding Application-wide Defaults". ### Keeping a link active for other routes If you need a link to be 'active' even when it doesn't match the current route, you can use the `current-when` argument. ```handlebars {{#link-to 'photoGallery' current-when='photos'}} Photo Gallery {{/link-to}} ``` This may be helpful for keeping links active for: * non-nested routes that are logically related * some secondary menu approaches * 'top navigation' with 'sub navigation' scenarios A link will be active if `current-when` is `true` or the current route is the route this link would transition to. To match multiple routes 'space-separate' the routes: ```handlebars {{#link-to 'gallery' current-when='photos drawings paintings'}} Art Gallery {{/link-to}} ``` ### Supplying a model An optional model argument can be used for routes whose paths contain dynamic segments. This argument will become the model context of the linked route: ```javascript Router.map(function() { this.route("photoGallery", {path: "hamster-photos/:photo_id"}); }); ``` ```handlebars {{#link-to 'photoGallery' aPhoto}} {{aPhoto.title}} {{/link-to}} ``` ```html Tomster ``` ### Supplying multiple models For deep-linking to route paths that contain multiple dynamic segments, multiple model arguments can be used. As the router transitions through the route path, each supplied model argument will become the context for the route with the dynamic segments: ```javascript Router.map(function() { this.route("photoGallery", { path: "hamster-photos/:photo_id" }, function() { this.route("comment", {path: "comments/:comment_id"}); }); }); ``` This argument will become the model context of the linked route: ```handlebars {{#link-to 'photoGallery.comment' aPhoto comment}} {{comment.body}} {{/link-to}} ``` ```html A+++ would snuggle again. ``` ### Supplying an explicit dynamic segment value If you don't have a model object available to pass to `{{link-to}}`, an optional string or integer argument can be passed for routes whose paths contain dynamic segments. This argument will become the value of the dynamic segment: ```javascript Router.map(function() { this.route("photoGallery", { path: "hamster-photos/:photo_id" }); }); ``` ```handlebars {{#link-to 'photoGallery' aPhotoId}} {{aPhoto.title}} {{/link-to}} ``` ```html Tomster ``` When transitioning into the linked route, the `model` hook will be triggered with parameters including this passed identifier. ### Allowing Default Action By default the `{{link-to}}` component prevents the default browser action by calling `preventDefault()` as this sort of action bubbling is normally handled internally and we do not want to take the browser to a new URL (for example). If you need to override this behavior specify `preventDefault=false` in your template: ```handlebars {{#link-to 'photoGallery' aPhotoId preventDefault=false}} {{aPhotoId.title}} {{/link-to}} ``` ### Overriding attributes You can override any given property of the `LinkComponent` that is generated by the `{{link-to}}` component by passing key/value pairs, like so: ```handlebars {{#link-to aPhoto tagName='li' title='Following this link will change your life' classNames='pic sweet'}} Uh-mazing! {{/link-to}} ``` See [LinkComponent](/api/ember/release/classes/LinkComponent) for a complete list of overrideable properties. Be sure to also check out inherited properties of `LinkComponent`. ### Overriding Application-wide Defaults ``{{link-to}}`` creates an instance of `LinkComponent` for rendering. To override options for your entire application, export your customized `LinkComponent` from `app/components/link-to.js` with the desired overrides: ```javascript // app/components/link-to.js import LinkComponent from '@ember/routing/link-component'; export default LinkComponent.extend({ activeClass: "is-active", tagName: 'li' }) ``` It is also possible to override the default event in this manner: ```javascript import LinkComponent from '@ember/routing/link-component'; export default LinkComponent.extend({ eventName: 'customEventName' }); ``` @method link-to @for Ember.Templates.helpers @param {String} routeName @param {Object} [context]* @param [options] {Object} Handlebars key/value pairs of options, you can override any property of Ember.LinkComponent @return {String} HTML string @see {LinkComponent} @public */ /** @module @ember/routing */ /** `LinkComponent` renders an element whose `click` event triggers a transition of the application's instance of `Router` to a supplied route by name. `LinkComponent` components are invoked with {{#link-to}}. Properties of this class can be overridden with `reopen` to customize application-wide behavior. @class LinkComponent @extends Component @see {Ember.Templates.helpers.link-to} @public **/ const EMPTY_QUERY_PARAMS = Object.freeze({ values: Object.freeze({}) }); LinkComponent = _component.default.extend({ layout: _linkTo.default, tagName: 'a', /** Used to determine when this `LinkComponent` is active. @property current-when @public */ 'current-when': null, /** Sets the `title` attribute of the `LinkComponent`'s HTML element. @property title @default null @public **/ title: null, /** Sets the `rel` attribute of the `LinkComponent`'s HTML element. @property rel @default null @public **/ rel: null, /** Sets the `tabindex` attribute of the `LinkComponent`'s HTML element. @property tabindex @default null @public **/ tabindex: null, /** Sets the `target` attribute of the `LinkComponent`'s HTML element. @since 1.8.0 @property target @default null @public **/ target: null, /** The CSS class to apply to `LinkComponent`'s element when its `active` property is `true`. @property activeClass @type String @default active @public **/ activeClass: 'active', /** The CSS class to apply to `LinkComponent`'s element when its `loading` property is `true`. @property loadingClass @type String @default loading @private **/ loadingClass: 'loading', /** The CSS class to apply to a `LinkComponent`'s element when its `disabled` property is `true`. @property disabledClass @type String @default disabled @private **/ disabledClass: 'disabled', /** Determines whether the `LinkComponent` will trigger routing via the `replaceWith` routing strategy. @property replace @type Boolean @default false @public **/ replace: false, /** By default the `{{link-to}}` component will bind to the `href` and `title` attributes. It's discouraged that you override these defaults, however you can push onto the array if needed. @property attributeBindings @type Array | String @default ['title', 'rel', 'tabindex', 'target'] @public */ attributeBindings: ['href', 'title', 'rel', 'tabindex', 'target'], /** By default the `{{link-to}}` component will bind to the `active`, `loading`, and `disabled` classes. It is discouraged to override these directly. @property classNameBindings @type Array @default ['active', 'loading', 'disabled', 'ember-transitioning-in', 'ember-transitioning-out'] @public */ classNameBindings: ['active', 'loading', 'disabled', 'transitioningIn', 'transitioningOut'], /** By default the `{{link-to}}` component responds to the `click` event. You can override this globally by setting this property to your custom event name. This is particularly useful on mobile when one wants to avoid the 300ms click delay using some sort of custom `tap` event. @property eventName @type String @default click @private */ eventName: 'click', // this is doc'ed here so it shows up in the events // section of the API documentation, which is where // people will likely go looking for it. /** Triggers the `LinkComponent`'s routing behavior. If `eventName` is changed to a value other than `click` the routing behavior will trigger on that custom event instead. @event click @private */ /** An overridable method called when `LinkComponent` objects are instantiated. Example: ```app/components/my-link.js import LinkComponent from '@ember/routing/link-component'; export default LinkComponent.extend({ init() { this._super(...arguments); console.log('Event is ' + this.get('eventName')); } }); ``` NOTE: If you do override `init` for a framework class like `Component`, be sure to call `this._super(...arguments)` in your `init` declaration! If you don't, Ember may not have an opportunity to do important setup work, and you'll see strange behavior in your application. @method init @private */ init() { this._super(...arguments); // Map desired event name to invoke function let eventName = (0, _metal.get)(this, 'eventName'); this.on(eventName, this, this._invoke); }, _routing: (0, _service.inject)('-routing'), /** Accessed as a classname binding to apply the `LinkComponent`'s `disabledClass` CSS `class` to the element when the link is disabled. When `true` interactions with the element will not trigger route changes. @property disabled @private */ disabled: (0, _metal.computed)({ get(_key) { // always returns false for `get` because (due to the `set` just below) // the cached return value from the set will prevent this getter from _ever_ // being called after a set has occured return false; }, set(_key, value) { this._isDisabled = value; return value ? (0, _metal.get)(this, 'disabledClass') : false; } }), _isActive(routerState) { if ((0, _metal.get)(this, 'loading')) { return false; } let currentWhen = (0, _metal.get)(this, 'current-when'); if (typeof currentWhen === 'boolean') { return currentWhen; } let isCurrentWhenSpecified = Boolean(currentWhen); currentWhen = currentWhen || (0, _metal.get)(this, 'qualifiedRouteName'); currentWhen = currentWhen.split(' '); let routing = this._routing; let models = (0, _metal.get)(this, 'models'); let resolvedQueryParams = (0, _metal.get)(this, 'resolvedQueryParams'); for (let i = 0; i < currentWhen.length; i++) { if (routing.isActiveForRoute(models, resolvedQueryParams, currentWhen[i], routerState, isCurrentWhenSpecified)) { return true; } } return false; }, /** Accessed as a classname binding to apply the `LinkComponent`'s `activeClass` CSS `class` to the element when the link is active. A `LinkComponent` is considered active when its `currentWhen` property is `true` or the application's current route is the route the `LinkComponent` would trigger transitions into. The `currentWhen` property can match against multiple routes by separating route names using the ` ` (space) character. @property active @private */ active: (0, _metal.computed)('activeClass', '_active', function computeLinkToComponentActiveClass() { return this.get('_active') ? (0, _metal.get)(this, 'activeClass') : false; }), _active: (0, _metal.computed)('_routing.currentState', 'attrs.params', function computeLinkToComponentActive() { let currentState = (0, _metal.get)(this, '_routing.currentState'); if (!currentState) { return false; } return this._isActive(currentState); }), willBeActive: (0, _metal.computed)('_routing.targetState', function computeLinkToComponentWillBeActive() { let routing = this._routing; let targetState = (0, _metal.get)(routing, 'targetState'); if ((0, _metal.get)(routing, 'currentState') === targetState) { return; } return this._isActive(targetState); }), transitioningIn: (0, _metal.computed)('active', 'willBeActive', function computeLinkToComponentTransitioningIn() { if ((0, _metal.get)(this, 'willBeActive') === true && !(0, _metal.get)(this, '_active')) { return 'ember-transitioning-in'; } else { return false; } }), transitioningOut: (0, _metal.computed)('active', 'willBeActive', function computeLinkToComponentTransitioningOut() { if ((0, _metal.get)(this, 'willBeActive') === false && (0, _metal.get)(this, '_active')) { return 'ember-transitioning-out'; } else { return false; } }), /** Event handler that invokes the link, activating the associated route. @method _invoke @param {Event} event @private */ _invoke(event) { if (!(0, _views.isSimpleClick)(event)) { return true; } let preventDefault = (0, _metal.get)(this, 'preventDefault'); let targetAttribute = (0, _metal.get)(this, 'target'); if (preventDefault !== false && (!targetAttribute || targetAttribute === '_self')) { event.preventDefault(); } if ((0, _metal.get)(this, 'bubbles') === false) { event.stopPropagation(); } if (this._isDisabled) { return false; } if ((0, _metal.get)(this, 'loading')) { // tslint:disable-next-line:max-line-length (0, _debug.warn)('This link-to is in an inactive loading state because at least one of its parameters presently has a null/undefined value, or the provided route name is invalid.', false, { id: 'ember-glimmer.link-to.inactive-loading-state' }); return false; } if (targetAttribute && targetAttribute !== '_self') { return false; } let qualifiedRouteName = (0, _metal.get)(this, 'qualifiedRouteName'); let models = (0, _metal.get)(this, 'models'); let queryParams = (0, _metal.get)(this, 'queryParams.values'); let shouldReplace = (0, _metal.get)(this, 'replace'); let payload = { queryParams, routeName: qualifiedRouteName }; // tslint:disable-next-line:max-line-length (0, _instrumentation.flaggedInstrument)('interaction.link-to', payload, this._generateTransition(payload, qualifiedRouteName, models, queryParams, shouldReplace)); return false; }, _generateTransition(payload, qualifiedRouteName, models, queryParams, shouldReplace) { let routing = this._routing; return () => { payload.transition = routing.transitionTo(qualifiedRouteName, models, queryParams, shouldReplace); }; }, queryParams: EMPTY_QUERY_PARAMS, qualifiedRouteName: (0, _metal.computed)('targetRouteName', '_routing.currentState', function computeLinkToComponentQualifiedRouteName() { let params = (0, _metal.get)(this, 'params'); let paramsLength = params.length; let lastParam = params[paramsLength - 1]; if (lastParam && lastParam.isQueryParams) { paramsLength--; } let onlyQueryParamsSupplied = this[_component.HAS_BLOCK] ? paramsLength === 0 : paramsLength === 1; if (onlyQueryParamsSupplied) { return (0, _metal.get)(this, '_routing.currentRouteName'); } return (0, _metal.get)(this, 'targetRouteName'); }), resolvedQueryParams: (0, _metal.computed)('queryParams', function computeLinkToComponentResolvedQueryParams() { let resolvedQueryParams = {}; let queryParams = (0, _metal.get)(this, 'queryParams'); if (queryParams !== EMPTY_QUERY_PARAMS) { let { values } = queryParams; (0, _polyfills.assign)(resolvedQueryParams, values); } return resolvedQueryParams; }), /** Sets the element's `href` attribute to the url for the `LinkComponent`'s targeted route. If the `LinkComponent`'s `tagName` is changed to a value other than `a`, this property will be ignored. @property href @private */ href: (0, _metal.computed)('models', 'qualifiedRouteName', function computeLinkToComponentHref() { if ((0, _metal.get)(this, 'tagName') !== 'a') { return; } let qualifiedRouteName = (0, _metal.get)(this, 'qualifiedRouteName'); let models = (0, _metal.get)(this, 'models'); if ((0, _metal.get)(this, 'loading')) { return (0, _metal.get)(this, 'loadingHref'); } let routing = this._routing; let queryParams = (0, _metal.get)(this, 'queryParams.values'); if (_env.DEBUG) { /* * Unfortunately, to get decent error messages, we need to do this. * In some future state we should be able to use a "feature flag" * which allows us to strip this without needing to call it twice. * * if (isDebugBuild()) { * // Do the useful debug thing, probably including try/catch. * } else { * // Do the performant thing. * } */ try { routing.generateURL(qualifiedRouteName, models, queryParams); } catch (e) { // tslint:disable-next-line:max-line-length (0, _debug.assert)('You attempted to define a `{{link-to "' + qualifiedRouteName + '"}}` but did not pass the parameters required for generating its dynamic segments. ' + e.message); } } return routing.generateURL(qualifiedRouteName, models, queryParams); }), loading: (0, _metal.computed)('_modelsAreLoaded', 'qualifiedRouteName', function computeLinkToComponentLoading() { let qualifiedRouteName = (0, _metal.get)(this, 'qualifiedRouteName'); let modelsAreLoaded = (0, _metal.get)(this, '_modelsAreLoaded'); if (!modelsAreLoaded || qualifiedRouteName === null || qualifiedRouteName === undefined) { return (0, _metal.get)(this, 'loadingClass'); } }), _modelsAreLoaded: (0, _metal.computed)('models', function computeLinkToComponentModelsAreLoaded() { let models = (0, _metal.get)(this, 'models'); for (let i = 0; i < models.length; i++) { let model = models[i]; if (model === null || model === undefined) { return false; } } return true; }), /** The default href value to use while a link-to is loading. Only applies when tagName is 'a' @property loadingHref @type String @default # @private */ loadingHref: '#', didReceiveAttrs() { let queryParams; let params = (0, _metal.get)(this, 'params'); if (params) { // Do not mutate params in place params = params.slice(); } (0, _debug.assert)('You must provide one or more parameters to the `{{link-to}}` component.', params && params.length > 0); let disabledWhen = (0, _metal.get)(this, 'disabledWhen'); if (disabledWhen !== undefined) { this.set('disabled', disabledWhen); } // Process the positional arguments, in order. // 1. Inline link title comes first, if present. if (!this[_component.HAS_BLOCK]) { this.set('linkTitle', params.shift()); } // 2. `targetRouteName` is now always at index 0. this.set('targetRouteName', params[0]); // 3. The last argument (if still remaining) is the `queryParams` object. let lastParam = params[params.length - 1]; if (lastParam && lastParam.isQueryParams) { queryParams = params.pop(); } else { queryParams = EMPTY_QUERY_PARAMS; } this.set('queryParams', queryParams); // 4. Any remaining indices (excepting `targetRouteName` at 0) are `models`. params.shift(); this.set('models', params); } }); LinkComponent.toString = () => '@ember/routing/link-component'; LinkComponent.reopenClass({ positionalParams: 'params' }); } var _default = LinkComponent; _exports.default = _default; }); enifed("@ember/-internals/glimmer/lib/components/text-field", ["exports", "@ember/-internals/browser-environment", "@ember/-internals/metal", "@ember/-internals/views", "@ember/-internals/glimmer/lib/component", "@ember/-internals/glimmer/lib/templates/empty"], function (_exports, _browserEnvironment, _metal, _views, _component, _empty) { "use strict"; _exports.__esModule = true; _exports.default = void 0; /** @module @ember/component */ const inputTypes = _browserEnvironment.hasDOM ? Object.create(null) : null; function canSetTypeOfInput(type) { // if running in outside of a browser always return // the original type if (!_browserEnvironment.hasDOM) { return Boolean(type); } if (type in inputTypes) { return inputTypes[type]; } let inputTypeTestElement = document.createElement('input'); try { inputTypeTestElement.type = type; } catch (e) {// ignored } return inputTypes[type] = inputTypeTestElement.type === type; } /** The internal class used to create text inputs when the `Input` component is used with `type` of `text`. See [Ember.Templates.components.Input](/api/ember/release/classes/Ember.Templates.components/methods/Input?anchor=Input) for usage details. ## Layout and LayoutName properties Because HTML `input` elements are self closing `layout` and `layoutName` properties will not be applied. @class TextField @extends Component @uses Ember.TextSupport @public */ const TextField = _component.default.extend(_views.TextSupport, { layout: _empty.default, /** By default, this component will add the `ember-text-field` class to the component's element. @property classNames @type Array | String @default ['ember-text-field'] @public */ classNames: ['ember-text-field'], tagName: 'input', /** By default this component will forward a number of arguments to attributes on the the component's element: * accept * autocomplete * autosave * dir * formaction * formenctype * formmethod * formnovalidate * formtarget * height * inputmode * lang * list * type * max * min * multiple * name * pattern * size * step * value * width When invoked with `{{input type="text"}}`, you can only customize these attributes. When invoked with ``, you can just use HTML attributes directly. @property attributeBindings @type Array | String @default ['accept', 'autocomplete', 'autosave', 'dir', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', 'height', 'inputmode', 'lang', 'list', 'type', 'max', 'min', 'multiple', 'name', 'pattern', 'size', 'step', 'value', 'width'] @public */ attributeBindings: ['accept', 'autocomplete', 'autosave', 'dir', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget', 'height', 'inputmode', 'lang', 'list', 'type', 'max', 'min', 'multiple', 'name', 'pattern', 'size', 'step', 'value', 'width'], /** As the user inputs text, this property is updated to reflect the `value` property of the HTML element. @property value @type String @default "" @public */ value: '', /** The `type` attribute of the input element. @property type @type String @default "text" @public */ type: (0, _metal.computed)({ get() { return 'text'; }, set(_key, value) { let type = 'text'; if (canSetTypeOfInput(value)) { type = value; } return type; } }), /** The `size` of the text field in characters. @property size @type String @default null @public */ size: null, /** The `pattern` attribute of input element. @property pattern @type String @default null @public */ pattern: null, /** The `min` attribute of input element used with `type="number"` or `type="range"`. @property min @type String @default null @since 1.4.0 @public */ min: null, /** The `max` attribute of input element used with `type="number"` or `type="range"`. @property max @type String @default null @since 1.4.0 @public */ max: null }); TextField.toString = () => '@ember/component/text-field'; var _default = TextField; _exports.default = _default; }); enifed("@ember/-internals/glimmer/lib/components/textarea", ["exports", "@ember/-internals/views", "@ember/-internals/glimmer/lib/component", "@ember/-internals/glimmer/lib/templates/empty"], function (_exports, _views, _component, _empty) { "use strict"; _exports.__esModule = true; _exports.default = void 0; /** @module @ember/component */ /** The `Textarea` component inserts a new instance of ` ``` The `@value` argument is two-way bound. If the user types text into the textarea, the `@value` argument is updated. If the `@value` argument is updated, the text in the textarea is updated. In the following example, the `writtenWords` property on the component will be updated as the user types 'Lots of text' into the text area of their browser's window. ```app/components/word-editor.js import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; export default class extends Component { @tracked writtenWords = "Lots of text that IS bound"; } ``` ```handlebars ``` If you wanted a one way binding, you could use the `