/*! * @overview Ember - JavaScript Application Framework * @copyright Copyright 2011-2015 Tilde Inc. and contributors * Portions Copyright 2006-2011 Strobe Inc. * Portions Copyright 2008-2011 Apple Inc. All rights reserved. * @license Licensed under MIT license * See https://raw.github.com/emberjs/ember.js/master/LICENSE * @version 1.13.8 */ (function() { var enifed, requireModule, eriuqer, requirejs, Ember; var mainContext = this; (function() { var isNode = 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 = {}; var seen = {}; enifed = function(name, deps, callback) { var value = { }; if (!callback) { value.deps = []; value.callback = deps; } else { value.deps = deps; value.callback = callback; } registry[name] = value; }; requirejs = eriuqer = requireModule = function(name) { return internalRequire(name, null); } function internalRequire(name, referrerName) { var exports = seen[name]; if (exports !== undefined) { return exports; } exports = seen[name] = {}; if (!registry[name]) { if (referrerName) { throw new Error('Could not find module ' + name + ' required by: ' + referrerName); } else { throw new Error('Could not find module ' + name); } } var mod = registry[name]; var deps = mod.deps; var callback = mod.callback; var reified = []; var length = deps.length; for (var i=0; i 2) { args = new Array(length - 2); for (var i = 0, l = length - 2; i < l; i++) { args[i] = arguments[i + 2]; } } else { args = []; } var onError = getOnError(this.options); this.begin(); // guard against Safari 6's double-finally bug var didFinally = false; if (onError) { try { return method.apply(target, args); } catch (error) { onError(error); } finally { if (!didFinally) { didFinally = true; this.end(); } } } else { try { return method.apply(target, args); } finally { if (!didFinally) { didFinally = true; this.end(); } } } }, join: function () /* target, method, args */{ if (this.currentInstance) { var length = arguments.length; var method, target; if (length === 1) { method = arguments[0]; target = null; } else { target = arguments[0]; method = arguments[1]; } if (_backburnerUtils.isString(method)) { method = target[method]; } if (length === 1) { return method(); } else if (length === 2) { return method.call(target); } else { var args = new Array(length - 2); for (var i = 0, l = length - 2; i < l; i++) { args[i] = arguments[i + 2]; } return method.apply(target, args); } } else { return this.run.apply(this, arguments); } }, defer: function (queueName /* , target, method, args */) { var length = arguments.length; var method, target, args; if (length === 2) { method = arguments[1]; target = null; } else { target = arguments[1]; method = arguments[2]; } if (_backburnerUtils.isString(method)) { method = target[method]; } var stack = this.DEBUG ? new Error() : undefined; if (length > 3) { args = new Array(length - 3); for (var i = 3; i < length; i++) { args[i - 3] = arguments[i]; } } else { args = undefined; } if (!this.currentInstance) { createAutorun(this); } return this.currentInstance.schedule(queueName, target, method, args, false, stack); }, deferOnce: function (queueName /* , target, method, args */) { var length = arguments.length; var method, target, args; if (length === 2) { method = arguments[1]; target = null; } else { target = arguments[1]; method = arguments[2]; } if (_backburnerUtils.isString(method)) { method = target[method]; } var stack = this.DEBUG ? new Error() : undefined; if (length > 3) { args = new Array(length - 3); for (var i = 3; i < length; i++) { args[i - 3] = arguments[i]; } } else { args = undefined; } if (!this.currentInstance) { createAutorun(this); } return this.currentInstance.schedule(queueName, target, method, args, true, stack); }, setTimeout: function () { var l = arguments.length; var args = new Array(l); for (var x = 0; x < l; x++) { args[x] = arguments[x]; } var length = args.length, method, wait, target, methodOrTarget, methodOrWait, methodOrArgs; if (length === 0) { return; } else if (length === 1) { method = args.shift(); wait = 0; } else if (length === 2) { methodOrTarget = args[0]; methodOrWait = args[1]; if (_backburnerUtils.isFunction(methodOrWait) || _backburnerUtils.isFunction(methodOrTarget[methodOrWait])) { target = args.shift(); method = args.shift(); wait = 0; } else if (_backburnerUtils.isCoercableNumber(methodOrWait)) { method = args.shift(); wait = args.shift(); } else { method = args.shift(); wait = 0; } } else { var last = args[args.length - 1]; if (_backburnerUtils.isCoercableNumber(last)) { wait = args.pop(); } else { wait = 0; } methodOrTarget = args[0]; methodOrArgs = args[1]; if (_backburnerUtils.isFunction(methodOrArgs) || _backburnerUtils.isString(methodOrArgs) && methodOrTarget !== null && methodOrArgs in methodOrTarget) { target = args.shift(); method = args.shift(); } else { method = args.shift(); } } var executeAt = _backburnerUtils.now() + parseInt(wait, 10); if (_backburnerUtils.isString(method)) { method = target[method]; } var onError = getOnError(this.options); function fn() { if (onError) { try { method.apply(target, args); } catch (e) { onError(e); } } else { method.apply(target, args); } } // find position to insert var i = _backburnerBinarySearch["default"](executeAt, this._timers); this._timers.splice(i, 0, executeAt, fn); updateLaterTimer(this, executeAt, wait); return fn; }, throttle: function (target, method /* , args, wait, [immediate] */) { var backburner = this; var args = new Array(arguments.length); for (var i = 0; i < arguments.length; i++) { args[i] = arguments[i]; } var immediate = args.pop(); var wait, throttler, index, timer; if (_backburnerUtils.isNumber(immediate) || _backburnerUtils.isString(immediate)) { wait = immediate; immediate = true; } else { wait = args.pop(); } wait = parseInt(wait, 10); index = findThrottler(target, method, this._throttlers); if (index > -1) { return this._throttlers[index]; } // throttled timer = _backburnerPlatform["default"].setTimeout(function () { if (!immediate) { backburner.run.apply(backburner, args); } var index = findThrottler(target, method, backburner._throttlers); if (index > -1) { backburner._throttlers.splice(index, 1); } }, wait); if (immediate) { this.run.apply(this, args); } throttler = [target, method, timer]; this._throttlers.push(throttler); return throttler; }, debounce: function (target, method /* , args, wait, [immediate] */) { var backburner = this; var args = new Array(arguments.length); for (var i = 0; i < arguments.length; i++) { args[i] = arguments[i]; } var immediate = args.pop(); var wait, index, debouncee, timer; if (_backburnerUtils.isNumber(immediate) || _backburnerUtils.isString(immediate)) { wait = immediate; immediate = false; } else { wait = args.pop(); } wait = parseInt(wait, 10); // Remove debouncee index = findDebouncee(target, method, this._debouncees); if (index > -1) { debouncee = this._debouncees[index]; this._debouncees.splice(index, 1); clearTimeout(debouncee[2]); } timer = _backburnerPlatform["default"].setTimeout(function () { if (!immediate) { backburner.run.apply(backburner, args); } var index = findDebouncee(target, method, backburner._debouncees); if (index > -1) { backburner._debouncees.splice(index, 1); } }, wait); if (immediate && index === -1) { backburner.run.apply(backburner, args); } debouncee = [target, method, timer]; backburner._debouncees.push(debouncee); return debouncee; }, cancelTimers: function () { var clearItems = function (item) { clearTimeout(item[2]); }; _backburnerUtils.each(this._throttlers, clearItems); this._throttlers = []; _backburnerUtils.each(this._debouncees, clearItems); this._debouncees = []; if (this._laterTimer) { clearTimeout(this._laterTimer); this._laterTimer = null; } this._timers = []; if (this._autorun) { clearTimeout(this._autorun); this._autorun = null; } }, hasTimers: function () { return !!this._timers.length || !!this._debouncees.length || !!this._throttlers.length || this._autorun; }, cancel: function (timer) { var timerType = typeof timer; if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce return timer.queue.cancel(timer); } else if (timerType === 'function') { // we're cancelling a setTimeout for (var i = 0, l = this._timers.length; i < l; i += 2) { if (this._timers[i + 1] === timer) { this._timers.splice(i, 2); // remove the two elements if (i === 0) { if (this._laterTimer) { // Active timer? Then clear timer and reset for future timer clearTimeout(this._laterTimer); this._laterTimer = null; } if (this._timers.length > 0) { // Update to next available timer when available updateLaterTimer(this, this._timers[0], this._timers[0] - _backburnerUtils.now()); } } return true; } } } else if (Object.prototype.toString.call(timer) === '[object Array]') { // we're cancelling a throttle or debounce return this._cancelItem(findThrottler, this._throttlers, timer) || this._cancelItem(findDebouncee, this._debouncees, timer); } else { return; // timer was null or not a timer } }, _cancelItem: function (findMethod, array, timer) { var item, index; if (timer.length < 3) { return false; } index = findMethod(timer[0], timer[1], array); if (index > -1) { item = array[index]; if (item[2] === timer[2]) { array.splice(index, 1); clearTimeout(timer[2]); return true; } } return false; } }; Backburner.prototype.schedule = Backburner.prototype.defer; Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce; Backburner.prototype.later = Backburner.prototype.setTimeout; if (_backburnerPlatform.needsIETryCatchFix) { var originalRun = Backburner.prototype.run; Backburner.prototype.run = _backburnerUtils.wrapInTryCatch(originalRun); var originalEnd = Backburner.prototype.end; Backburner.prototype.end = _backburnerUtils.wrapInTryCatch(originalEnd); } function getOnError(options) { return options.onError || options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]; } function createAutorun(backburner) { backburner.begin(); backburner._autorun = _backburnerPlatform["default"].setTimeout(function () { backburner._autorun = null; backburner.end(); }); } function updateLaterTimer(backburner, executeAt, wait) { var n = _backburnerUtils.now(); if (!backburner._laterTimer || executeAt < backburner._laterTimerExpiresAt || backburner._laterTimerExpiresAt < n) { if (backburner._laterTimer) { // Clear when: // - Already expired // - New timer is earlier clearTimeout(backburner._laterTimer); if (backburner._laterTimerExpiresAt < n) { // If timer was never triggered // Calculate the left-over wait-time wait = Math.max(0, executeAt - n); } } backburner._laterTimer = _backburnerPlatform["default"].setTimeout(function () { backburner._laterTimer = null; backburner._laterTimerExpiresAt = null; executeTimers(backburner); }, wait); backburner._laterTimerExpiresAt = n + wait; } } function executeTimers(backburner) { var n = _backburnerUtils.now(); var fns, i, l; backburner.run(function () { i = _backburnerBinarySearch["default"](n, backburner._timers); fns = backburner._timers.splice(0, i); for (i = 1, l = fns.length; i < l; i += 2) { backburner.schedule(backburner.options.defaultQueue, null, fns[i]); } }); if (backburner._timers.length) { updateLaterTimer(backburner, backburner._timers[0], backburner._timers[0] - n); } } function findDebouncee(target, method, debouncees) { return findItem(target, method, debouncees); } function findThrottler(target, method, throttlers) { return findItem(target, method, throttlers); } function findItem(target, method, collection) { var item; var index = -1; for (var i = 0, l = collection.length; i < l; i++) { item = collection[i]; if (item[0] === target && item[1] === method) { index = i; break; } } return index; } }); enifed("backburner/binary-search", ["exports"], function (exports) { "use strict"; exports["default"] = binarySearch; function binarySearch(time, timers) { var start = 0; var end = timers.length - 2; var middle, l; while (start < end) { // since timers is an array of pairs 'l' will always // be an integer l = (end - start) / 2; // compensate for the index in case even number // of pairs inside timers middle = start + l - l % 2; if (time >= timers[middle]) { start = middle + 2; } else { end = middle; } } return time >= timers[start] ? start + 2 : start; } }); enifed('backburner/deferred-action-queues', ['exports', './utils', './queue'], function (exports, _utils, _queue) { 'use strict'; exports["default"] = DeferredActionQueues; function DeferredActionQueues(queueNames, options) { var queues = this.queues = {}; this.queueNames = queueNames = queueNames || []; this.options = options; _utils.each(queueNames, function (queueName) { queues[queueName] = new _queue["default"](queueName, options[queueName], options); }); } function noSuchQueue(name) { throw new Error('You attempted to schedule an action in a queue (' + name + ') that doesn\'t exist'); } DeferredActionQueues.prototype = { schedule: function (name, target, method, args, onceFlag, stack) { var queues = this.queues; var queue = queues[name]; if (!queue) { noSuchQueue(name); } if (onceFlag) { return queue.pushUnique(target, method, args, stack); } else { return queue.push(target, method, args, stack); } }, flush: function () { var queues = this.queues; var queueNames = this.queueNames; var queueName, queue, queueItems, priorQueueNameIndex; var queueNameIndex = 0; var numberOfQueues = queueNames.length; var options = this.options; while (queueNameIndex < numberOfQueues) { queueName = queueNames[queueNameIndex]; queue = queues[queueName]; var numberOfQueueItems = queue._queue.length; if (numberOfQueueItems === 0) { queueNameIndex++; } else { queue.flush(false /* async */); queueNameIndex = 0; } } } }; }); enifed('backburner/platform', ['exports'], function (exports) { // In IE 6-8, try/finally doesn't work without a catch. // Unfortunately, this is impossible to test for since wrapping it in a parent try/catch doesn't trigger the bug. // This tests for another broken try/catch behavior that only exhibits in the same versions of IE. 'use strict'; var needsIETryCatchFix = (function (e, x) { try { x(); } catch (e) {} // jshint ignore:line return !!e; })(); exports.needsIETryCatchFix = needsIETryCatchFix; var platform; /* global self */ if (typeof self === 'object') { platform = self; /* global global */ } else if (typeof global === 'object') { platform = global; } else { throw new Error('no global: `self` or `global` found'); } exports["default"] = platform; }); enifed('backburner/queue', ['exports', './utils'], function (exports, _utils) { 'use strict'; exports["default"] = Queue; function Queue(name, options, globalOptions) { this.name = name; this.globalOptions = globalOptions || {}; this.options = options; this._queue = []; this.targetQueues = {}; this._queueBeingFlushed = undefined; } Queue.prototype = { push: function (target, method, args, stack) { var queue = this._queue; queue.push(target, method, args, stack); return { queue: this, target: target, method: method }; }, pushUniqueWithoutGuid: function (target, method, args, stack) { var queue = this._queue; for (var i = 0, l = queue.length; i < l; i += 4) { var currentTarget = queue[i]; var currentMethod = queue[i + 1]; if (currentTarget === target && currentMethod === method) { queue[i + 2] = args; // replace args queue[i + 3] = stack; // replace stack return; } } queue.push(target, method, args, stack); }, targetQueue: function (targetQueue, target, method, args, stack) { var queue = this._queue; for (var i = 0, l = targetQueue.length; i < l; i += 2) { var currentMethod = targetQueue[i]; var currentIndex = targetQueue[i + 1]; if (currentMethod === method) { queue[currentIndex + 2] = args; // replace args queue[currentIndex + 3] = stack; // replace stack return; } } targetQueue.push(method, queue.push(target, method, args, stack) - 4); }, pushUniqueWithGuid: function (guid, target, method, args, stack) { var hasLocalQueue = this.targetQueues[guid]; if (hasLocalQueue) { this.targetQueue(hasLocalQueue, target, method, args, stack); } else { this.targetQueues[guid] = [method, this._queue.push(target, method, args, stack) - 4]; } return { queue: this, target: target, method: method }; }, pushUnique: function (target, method, args, stack) { var queue = this._queue, currentTarget, currentMethod, i, l; var KEY = this.globalOptions.GUID_KEY; if (target && KEY) { var guid = target[KEY]; if (guid) { return this.pushUniqueWithGuid(guid, target, method, args, stack); } } this.pushUniqueWithoutGuid(target, method, args, stack); return { queue: this, target: target, method: method }; }, invoke: function (target, method, args, _, _errorRecordedForStack) { if (args && args.length > 0) { method.apply(target, args); } else { method.call(target); } }, invokeWithOnError: function (target, method, args, onError, errorRecordedForStack) { try { if (args && args.length > 0) { method.apply(target, args); } else { method.call(target); } } catch (error) { onError(error, errorRecordedForStack); } }, flush: function (sync) { var queue = this._queue; var length = queue.length; if (length === 0) { return; } var globalOptions = this.globalOptions; var options = this.options; var before = options && options.before; var after = options && options.after; var onError = globalOptions.onError || globalOptions.onErrorTarget && globalOptions.onErrorTarget[globalOptions.onErrorMethod]; var target, method, args, errorRecordedForStack; var invoke = onError ? this.invokeWithOnError : this.invoke; this.targetQueues = Object.create(null); var queueItems = this._queueBeingFlushed = this._queue.slice(); this._queue = []; if (before) { before(); } for (var i = 0; i < length; i += 4) { target = queueItems[i]; method = queueItems[i + 1]; args = queueItems[i + 2]; errorRecordedForStack = queueItems[i + 3]; // Debugging assistance if (_utils.isString(method)) { method = target[method]; } // method could have been nullified / canceled during flush if (method) { // // ** Attention intrepid developer ** // // To find out the stack of this task when it was scheduled onto // the run loop, add the following to your app.js: // // Ember.run.backburner.DEBUG = true; // NOTE: This slows your app, don't leave it on in production. // // Once that is in place, when you are at a breakpoint and navigate // here in the stack explorer, you can look at `errorRecordedForStack.stack`, // which will be the captured stack when this job was scheduled. // invoke(target, method, args, onError, errorRecordedForStack); } } if (after) { after(); } this._queueBeingFlushed = undefined; if (sync !== false && this._queue.length > 0) { // check if new items have been added this.flush(true); } }, cancel: function (actionToCancel) { var queue = this._queue, currentTarget, currentMethod, i, l; var target = actionToCancel.target; var method = actionToCancel.method; var GUID_KEY = this.globalOptions.GUID_KEY; if (GUID_KEY && this.targetQueues && target) { var targetQueue = this.targetQueues[target[GUID_KEY]]; if (targetQueue) { for (i = 0, l = targetQueue.length; i < l; i++) { if (targetQueue[i] === method) { targetQueue.splice(i, 1); } } } } for (i = 0, l = queue.length; i < l; i += 4) { currentTarget = queue[i]; currentMethod = queue[i + 1]; if (currentTarget === target && currentMethod === method) { queue.splice(i, 4); return true; } } // if not found in current queue // could be in the queue that is being flushed queue = this._queueBeingFlushed; if (!queue) { return; } for (i = 0, l = queue.length; i < l; i += 4) { currentTarget = queue[i]; currentMethod = queue[i + 1]; if (currentTarget === target && currentMethod === method) { // don't mess with array during flush // just nullify the method queue[i + 1] = null; return true; } } } }; }); enifed('backburner/utils', ['exports'], function (exports) { 'use strict'; exports.each = each; exports.isString = isString; exports.isFunction = isFunction; exports.isNumber = isNumber; exports.isCoercableNumber = isCoercableNumber; exports.wrapInTryCatch = wrapInTryCatch; var NUMBER = /\d+/; function each(collection, callback) { for (var i = 0; i < collection.length; i++) { callback(collection[i]); } } // Date.now is not available in browsers < IE9 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility var now = Date.now || function () { return new Date().getTime(); }; exports.now = now; function isString(suspect) { return typeof suspect === 'string'; } function isFunction(suspect) { return typeof suspect === 'function'; } function isNumber(suspect) { return typeof suspect === 'number'; } function isCoercableNumber(number) { return isNumber(number) || NUMBER.test(number); } function wrapInTryCatch(func) { return function () { try { return func.apply(this, arguments); } catch (e) { throw e; } }; } }); enifed('calculateVersion', ['exports'], function (exports) { 'use strict'; var fs = eriuqer('fs'); var path = eriuqer('path'); module.exports = function () { var packageVersion = eriuqer('../package.json').version; var output = [packageVersion]; var gitPath = path.join(__dirname, '..', '.git'); var headFilePath = path.join(gitPath, 'HEAD'); if (packageVersion.indexOf('+') > -1) { try { if (fs.existsSync(headFilePath)) { var headFile = fs.readFileSync(headFilePath, { encoding: 'utf8' }); var branchName = headFile.split('/').slice(-1)[0].trim(); var refPath = headFile.split(' ')[1]; var branchSHA; if (refPath) { var branchPath = path.join(gitPath, refPath.trim()); branchSHA = fs.readFileSync(branchPath); } else { branchSHA = branchName; } output.push(branchSHA.slice(0, 10)); } } catch (err) { console.error(err.stack); } return output.join('.'); } else { return packageVersion; } }; }); enifed('container', ['exports', 'container/registry', 'container/container'], function (exports, _containerRegistry, _containerContainer) { /* Public api for the container is still in flux. The public api, specified on the application namespace should be considered the stable api. // @module container @private */ /* Flag to enable/disable model factory injections (disabled by default) If model factory injections are enabled, models should not be accessed globally (only through `container.lookupFactory('model:modelName'))`); */ 'use strict'; Ember.MODEL_FACTORY_INJECTIONS = false; if (Ember.ENV && typeof Ember.ENV.MODEL_FACTORY_INJECTIONS !== 'undefined') { Ember.MODEL_FACTORY_INJECTIONS = !!Ember.ENV.MODEL_FACTORY_INJECTIONS; } exports.Registry = _containerRegistry["default"]; exports.Container = _containerContainer["default"]; }); enifed('container/container', ['exports', 'ember-metal/core', 'ember-metal/keys', 'ember-metal/dictionary'], function (exports, _emberMetalCore, _emberMetalKeys, _emberMetalDictionary) { 'use strict'; // TODO - Temporary workaround for v0.4.0 of the ES6 transpiler, which lacks support for circular dependencies. // See the below usage of requireModule. Instead, it should be possible to simply `import Registry from './registry';` var Registry; /** 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 */ function Container(registry, options) { this._registry = registry || (function () { // TODO - See note above about transpiler import workaround. if (!Registry) { Registry = requireModule('container/registry')['default']; } return new Registry(); })(); this.cache = _emberMetalDictionary["default"](options && options.cache ? options.cache : null); this.factoryCache = _emberMetalDictionary["default"](options && options.factoryCache ? options.factoryCache : null); this.validationCache = _emberMetalDictionary["default"](options && options.validationCache ? options.validationCache : null); } Container.prototype = { /** @private @property _registry @type Registry @since 1.11.0 */ _registry: null, /** @private @property cache @type InheritingDict */ cache: null, /** @private @property factoryCache @type InheritingDict */ factoryCache: null, /** @private @property validationCache @type InheritingDict */ validationCache: null, /** Given a fullName return a corresponding instance. The default behaviour 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 var registry = new Registry(); var container = registry.container(); registry.register('api:twitter', Twitter); var twitter = container.lookup('api:twitter'); twitter instanceof Twitter; // => true // by default the container will return singletons var 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 var registry = new Registry(); var container = registry.container(); registry.register('api:twitter', Twitter); var twitter = container.lookup('api:twitter', { singleton: false }); var twitter2 = container.lookup('api:twitter', { singleton: false }); twitter === twitter2; //=> false ``` @private @method lookup @param {String} fullName @param {Object} options @return {any} */ lookup: function (fullName, options) { return lookup(this, this._registry.normalize(fullName), options); }, /** Given a fullName return the corresponding factory. @private @method lookupFactory @param {String} fullName @return {any} */ lookupFactory: function (fullName) { return factoryFor(this, this._registry.normalize(fullName)); }, /** A depth first traversal, destroying the container, its descendant containers and all their managed objects. @private @method destroy */ destroy: function () { eachDestroyable(this, function (item) { if (item.destroy) { item.destroy(); } }); 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: function (fullName) { if (arguments.length > 0) { resetMember(this, this._registry.normalize(fullName)); } else { resetCache(this); } } }; (function exposeRegistryMethods() { var methods = ['register', 'unregister', 'resolve', 'normalize', 'typeInjection', 'injection', 'factoryInjection', 'factoryTypeInjection', 'has', 'options', 'optionsForType']; function exposeRegistryMethod(method) { Container.prototype[method] = function () { return this._registry[method].apply(this._registry, arguments); }; } for (var i = 0, l = methods.length; i < l; i++) { exposeRegistryMethod(methods[i]); } })(); function lookup(container, fullName, options) { options = options || {}; if (container.cache[fullName] && options.singleton !== false) { return container.cache[fullName]; } var value = instantiate(container, fullName); if (value === undefined) { return; } if (container._registry.getOption(fullName, 'singleton') !== false && options.singleton !== false) { container.cache[fullName] = value; } return value; } function buildInjections(container) { var hash = {}; if (arguments.length > 1) { var injectionArgs = Array.prototype.slice.call(arguments, 1); var injections = []; var injection; for (var i = 0, l = injectionArgs.length; i < l; i++) { if (injectionArgs[i]) { injections = injections.concat(injectionArgs[i]); } } container._registry.validateInjections(injections); for (i = 0, l = injections.length; i < l; i++) { injection = injections[i]; hash[injection.property] = lookup(container, injection.fullName); } } return hash; } function factoryFor(container, fullName) { var cache = container.factoryCache; if (cache[fullName]) { return cache[fullName]; } var registry = container._registry; var factory = registry.resolve(fullName); if (factory === undefined) { return; } var type = fullName.split(':')[0]; if (!factory || typeof factory.extend !== 'function' || !_emberMetalCore["default"].MODEL_FACTORY_INJECTIONS && type === 'model') { if (factory && typeof factory._onLookup === 'function') { factory._onLookup(fullName); } // TODO: think about a 'safe' merge style extension // for now just fallback to create time injection cache[fullName] = factory; return factory; } else { var injections = injectionsFor(container, fullName); var factoryInjections = factoryInjectionsFor(container, fullName); factoryInjections._toString = registry.makeToString(factory, fullName); var injectedFactory = factory.extend(injections); injectedFactory.reopenClass(factoryInjections); if (factory && typeof factory._onLookup === 'function') { factory._onLookup(fullName); } cache[fullName] = injectedFactory; return injectedFactory; } } function injectionsFor(container, fullName) { var registry = container._registry; var splitName = fullName.split(':'); var type = splitName[0]; var injections = buildInjections(container, registry.getTypeInjections(type), registry.getInjections(fullName)); injections._debugContainerKey = fullName; injections.container = container; return injections; } function factoryInjectionsFor(container, fullName) { var registry = container._registry; var splitName = fullName.split(':'); var type = splitName[0]; var factoryInjections = buildInjections(container, registry.getFactoryTypeInjections(type), registry.getFactoryInjections(fullName)); factoryInjections._debugContainerKey = fullName; return factoryInjections; } function instantiate(container, fullName) { var factory = factoryFor(container, fullName); var lazyInjections, validationCache; if (container._registry.getOption(fullName, 'instantiate') === false) { return factory; } if (factory) { if (typeof factory.create !== 'function') { throw new Error('Failed to create an instance of \'' + fullName + '\'. ' + 'Most likely an improperly defined class or an invalid module export.'); } validationCache = container.validationCache; // Ensure that all lazy injections are valid at instantiation time if (!validationCache[fullName] && typeof factory._lazyInjections === 'function') { lazyInjections = factory._lazyInjections(); lazyInjections = container._registry.normalizeInjectionsHash(lazyInjections); container._registry.validateInjections(lazyInjections); } validationCache[fullName] = true; if (typeof factory.extend === 'function') { // assume the factory was extendable and is already injected return factory.create(); } else { // assume the factory was extendable // to create time injections // TODO: support new'ing for instantiation and merge injections for pure JS Functions return factory.create(injectionsFor(container, fullName)); } } } function eachDestroyable(container, callback) { var cache = container.cache; var keys = _emberMetalKeys["default"](cache); var key, value; for (var i = 0, l = keys.length; i < l; i++) { key = keys[i]; value = cache[key]; if (container._registry.getOption(key, 'instantiate') !== false) { callback(value); } } } function resetCache(container) { eachDestroyable(container, function (value) { if (value.destroy) { value.destroy(); } }); container.cache.dict = _emberMetalDictionary["default"](null); } function resetMember(container, fullName) { var member = container.cache[fullName]; delete container.factoryCache[fullName]; if (member) { delete container.cache[fullName]; if (member.destroy) { member.destroy(); } } } exports["default"] = Container; }); // Ember.assert enifed('container/registry', ['exports', 'ember-metal/core', 'ember-metal/dictionary', 'ember-metal/keys', 'ember-metal/merge', './container'], function (exports, _emberMetalCore, _emberMetalDictionary, _emberMetalKeys, _emberMetalMerge, _container) { 'use strict'; var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/; var instanceInitializersFeatureEnabled; instanceInitializersFeatureEnabled = true; /** 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 public API for `Registry` is still in flux and should not be considered stable. @private @class Registry @since 1.11.0 */ function Registry(options) { this.fallback = options && options.fallback ? options.fallback : null; this.resolver = options && options.resolver ? options.resolver : function () {}; this.registrations = _emberMetalDictionary["default"](options && options.registrations ? options.registrations : null); this._typeInjections = _emberMetalDictionary["default"](null); this._injections = _emberMetalDictionary["default"](null); this._factoryTypeInjections = _emberMetalDictionary["default"](null); this._factoryInjections = _emberMetalDictionary["default"](null); this._normalizeCache = _emberMetalDictionary["default"](null); this._resolveCache = _emberMetalDictionary["default"](null); this._failCache = _emberMetalDictionary["default"](null); this._options = _emberMetalDictionary["default"](null); this._typeOptions = _emberMetalDictionary["default"](null); } Registry.prototype = { /** A backup registry for resolving registrations when no matches can be found. @private @property fallback @type Registry */ fallback: null, /** @private @property resolver @type function */ resolver: null, /** @private @property registrations @type InheritingDict */ registrations: null, /** @private @property _typeInjections @type InheritingDict */ _typeInjections: null, /** @private @property _injections @type InheritingDict */ _injections: null, /** @private @property _factoryTypeInjections @type InheritingDict */ _factoryTypeInjections: null, /** @private @property _factoryInjections @type InheritingDict */ _factoryInjections: null, /** @private @property _normalizeCache @type InheritingDict */ _normalizeCache: null, /** @private @property _resolveCache @type InheritingDict */ _resolveCache: null, /** @private @property _options @type InheritingDict */ _options: null, /** @private @property _typeOptions @type InheritingDict */ _typeOptions: null, /** The first container created for this registry. This allows deprecated access to `lookup` and `lookupFactory` to avoid breaking compatibility for Ember 1.x initializers. @private @property _defaultContainer @type Container */ _defaultContainer: null, /** Creates a container based on this registry. @private @method container @param {Object} options @return {Container} created container */ container: function (options) { var container = new _container["default"](this, options); // 2.0TODO - remove `registerContainer` this.registerContainer(container); return container; }, /** Register the first container created for a registery to allow deprecated access to its `lookup` and `lookupFactory` methods to avoid breaking compatibility for Ember 1.x initializers. 2.0TODO: Remove this method. The bookkeeping is only needed to support deprecated behavior. @private @param {Container} newly created container */ registerContainer: function (container) { if (!this._defaultContainer) { this._defaultContainer = container; } if (this.fallback) { this.fallback.registerContainer(container); } }, lookup: function (fullName, options) { if (instanceInitializersFeatureEnabled) { } return this._defaultContainer.lookup(fullName, options); }, lookupFactory: function (fullName) { if (instanceInitializersFeatureEnabled) { } return this._defaultContainer.lookupFactory(fullName); }, /** Registers a factory for later injection. Example: ```javascript var 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: function (fullName, factory, options) { if (factory === undefined) { throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`'); } var normalizedName = this.normalize(fullName); if (this._resolveCache[normalizedName]) { throw new Error('Cannot re-register: `' + fullName + '`, as it has already been resolved.'); } delete this._failCache[normalizedName]; this.registrations[normalizedName] = factory; this._options[normalizedName] = options || {}; }, /** Unregister a fullName ```javascript var 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: function (fullName) { var normalizedName = this.normalize(fullName); delete this.registrations[normalizedName]; delete this._resolveCache[normalizedName]; delete this._failCache[normalizedName]; delete this._options[normalizedName]; }, /** Given a fullName return the corresponding factory. By default `resolve` will retrieve the factory from the registry. ```javascript var 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 var 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 @return {Function} fullName's factory */ resolve: function (fullName) { var factory = resolve(this, this.normalize(fullName)); if (factory === undefined && this.fallback) { factory = this.fallback.resolve(fullName); } 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: function (fullName) { return fullName; }, /** A hook to enable custom fullName normalization behaviour @private @method normalizeFullName @param {String} fullName @return {string} normalized fullName */ normalizeFullName: function (fullName) { return fullName; }, /** normalize a fullName based on the applications conventions @private @method normalize @param {String} fullName @return {string} normalized fullName */ normalize: function (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: function (factory, fullName) { return factory.toString(); }, /** Given a fullName check if the container is aware of its factory or singleton instance. @private @method has @param {String} fullName @return {Boolean} */ has: function (fullName) { return has(this, this.normalize(fullName)); }, /** Allow registering options for all factories of a type. ```javascript var registry = new Registry(); var 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); var twitter = container.lookup('connection:twitter'); var twitter2 = container.lookup('connection:twitter'); twitter === twitter2; // => false var facebook = container.lookup('connection:facebook'); var facebook2 = container.lookup('connection:facebook'); facebook === facebook2; // => false ``` @private @method optionsForType @param {String} type @param {Object} options */ optionsForType: function (type, options) { this._typeOptions[type] = options; }, getOptionsForType: function (type) { var optionsForType = this._typeOptions[type]; if (optionsForType === undefined && this.fallback) { optionsForType = this.fallback.getOptionsForType(type); } return optionsForType; }, /** @private @method options @param {String} fullName @param {Object} options */ options: function (fullName, options) { options = options || {}; var normalizedName = this.normalize(fullName); this._options[normalizedName] = options; }, getOptions: function (fullName) { var normalizedName = this.normalize(fullName); var options = this._options[normalizedName]; if (options === undefined && this.fallback) { options = this.fallback.getOptions(fullName); } return options; }, getOption: function (fullName, optionName) { var options = this._options[fullName]; if (options && options[optionName] !== undefined) { return options[optionName]; } var type = fullName.split(':')[0]; options = this._typeOptions[type]; if (options && options[optionName] !== undefined) { return options[optionName]; } else if (this.fallback) { return this.fallback.getOption(fullName, optionName); } }, option: function (fullName, optionName) { return this.getOption(fullName, optionName); }, /** 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 var registry = new Registry(); var container = registry.container(); registry.register('router:main', Router); registry.register('controller:user', UserController); registry.register('controller:post', PostController); registry.typeInjection('controller', 'router', 'router:main'); var user = container.lookup('controller:user'); var 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: function (type, property, fullName) { var fullNameType = fullName.split(':')[0]; if (fullNameType === type) { throw new Error('Cannot inject a `' + fullName + '` on other ' + type + '(s).'); } var injections = this._typeInjections[type] || (this._typeInjections[type] = []); injections.push({ property: property, fullName: 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 var registry = new Registry(); var 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'); var user = container.lookup('model:user'); var 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: function (fullName, property, injectionName) { this.validateFullName(injectionName); var normalizedInjectionName = this.normalize(injectionName); if (fullName.indexOf(':') === -1) { return this.typeInjection(fullName, property, normalizedInjectionName); } var normalizedName = this.normalize(fullName); var injections = this._injections[normalizedName] || (this._injections[normalizedName] = []); injections.push({ property: property, fullName: normalizedInjectionName }); }, /** Used only via `factoryInjection`. Provides a specialized form of injection, specifically enabling all factory of one type to be injected with a reference to another object. For example, provided each factory of type `model` needed a `store`. one would do the following: ```javascript var registry = new Registry(); registry.register('store:main', SomeStore); registry.factoryTypeInjection('model', 'store', 'store:main'); var store = registry.lookup('store:main'); var UserFactory = registry.lookupFactory('model:user'); UserFactory.store instanceof SomeStore; //=> true ``` @private @method factoryTypeInjection @param {String} type @param {String} property @param {String} fullName */ factoryTypeInjection: function (type, property, fullName) { var injections = this._factoryTypeInjections[type] || (this._factoryTypeInjections[type] = []); injections.push({ property: property, fullName: this.normalize(fullName) }); }, /** Defines factory injection rules. Similar to regular injection rules, but are run against factories, via `Registry#lookupFactory`. These rules are used to inject objects onto factories when they are looked up. Two forms of injections are possible: * Injecting one fullName on another fullName * Injecting one fullName on a type Example: ```javascript var registry = new Registry(); var container = registry.container(); registry.register('store:main', Store); registry.register('store:secondary', OtherStore); registry.register('model:user', User); registry.register('model:post', Post); // injecting one fullName on another type registry.factoryInjection('model', 'store', 'store:main'); // injecting one fullName on another fullName registry.factoryInjection('model:post', 'secondaryStore', 'store:secondary'); var UserFactory = container.lookupFactory('model:user'); var PostFactory = container.lookupFactory('model:post'); var store = container.lookup('store:main'); UserFactory.store instanceof Store; //=> true UserFactory.secondaryStore instanceof OtherStore; //=> false PostFactory.store instanceof Store; //=> true PostFactory.secondaryStore instanceof OtherStore; //=> true // and both models share the same source instance UserFactory.store === PostFactory.store; //=> true ``` @private @method factoryInjection @param {String} factoryName @param {String} property @param {String} injectionName */ factoryInjection: function (fullName, property, injectionName) { var normalizedName = this.normalize(fullName); var normalizedInjectionName = this.normalize(injectionName); this.validateFullName(injectionName); if (fullName.indexOf(':') === -1) { return this.factoryTypeInjection(normalizedName, property, normalizedInjectionName); } var injections = this._factoryInjections[normalizedName] || (this._factoryInjections[normalizedName] = []); injections.push({ property: property, fullName: normalizedInjectionName }); }, /** @method knownForType @param {String} type the type to iterate over @private */ knownForType: function (type) { var fallbackKnown = undefined, resolverKnown = undefined; var localKnown = _emberMetalDictionary["default"](null); var registeredNames = _emberMetalKeys["default"](this.registrations); for (var index = 0, _length = registeredNames.length; index < _length; index++) { var fullName = registeredNames[index]; var itemType = fullName.split(':')[0]; if (itemType === type) { localKnown[fullName] = true; } } if (this.fallback) { fallbackKnown = this.fallback.knownForType(type); } if (this.resolver.knownForType) { resolverKnown = this.resolver.knownForType(type); } return _emberMetalMerge.assign({}, fallbackKnown, localKnown, resolverKnown); }, validateFullName: function (fullName) { if (!VALID_FULL_NAME_REGEXP.test(fullName)) { throw new TypeError('Invalid Fullname, expected: `type:name` got: ' + fullName); } return true; }, validateInjections: function (injections) { if (!injections) { return; } var fullName; for (var i = 0, length = injections.length; i < length; i++) { fullName = injections[i].fullName; if (!this.has(fullName)) { throw new Error('Attempting to inject an unknown injection: `' + fullName + '`'); } } }, normalizeInjectionsHash: function (hash) { var injections = []; for (var key in hash) { if (hash.hasOwnProperty(key)) { injections.push({ property: key, fullName: hash[key] }); } } return injections; }, getInjections: function (fullName) { var injections = this._injections[fullName] || []; if (this.fallback) { injections = injections.concat(this.fallback.getInjections(fullName)); } return injections; }, getTypeInjections: function (type) { var injections = this._typeInjections[type] || []; if (this.fallback) { injections = injections.concat(this.fallback.getTypeInjections(type)); } return injections; }, getFactoryInjections: function (fullName) { var injections = this._factoryInjections[fullName] || []; if (this.fallback) { injections = injections.concat(this.fallback.getFactoryInjections(fullName)); } return injections; }, getFactoryTypeInjections: function (type) { var injections = this._factoryTypeInjections[type] || []; if (this.fallback) { injections = injections.concat(this.fallback.getFactoryTypeInjections(type)); } return injections; } }; function resolve(registry, normalizedName) { var cached = registry._resolveCache[normalizedName]; if (cached) { return cached; } if (registry._failCache[normalizedName]) { return; } var resolved = registry.resolver(normalizedName) || registry.registrations[normalizedName]; if (resolved) { registry._resolveCache[normalizedName] = resolved; } else { registry._failCache[normalizedName] = true; } return resolved; } function has(registry, fullName) { return registry.resolve(fullName) !== undefined; } exports["default"] = Registry; }); // Ember.assert enifed("dag-map", ["exports"], function (exports) { "use strict"; function visit(vertex, fn, visited, path) { var name = vertex.name; var vertices = vertex.incoming; var names = vertex.incomingNames; var len = names.length; var i; if (!visited) { visited = {}; } if (!path) { path = []; } if (visited.hasOwnProperty(name)) { return; } path.push(name); visited[name] = true; for (i = 0; i < len; i++) { visit(vertices[names[i]], fn, visited, path); } fn(vertex, path); path.pop(); } /** * DAG stands for Directed acyclic graph. * * It is used to build a graph of dependencies checking that there isn't circular * dependencies. p.e Registering initializers with a certain precedence order. * * @class DAG * @constructor */ function DAG() { this.names = []; this.vertices = Object.create(null); } /** * DAG Vertex * * @class Vertex * @constructor */ function Vertex(name) { this.name = name; this.incoming = {}; this.incomingNames = []; this.hasOutgoing = false; this.value = null; } /** * Adds a vertex entry to the graph unless it is already added. * * @private * @method add * @param {String} name The name of the vertex to add */ DAG.prototype.add = function (name) { if (!name) { throw new Error("Can't add Vertex without name"); } if (this.vertices[name] !== undefined) { return this.vertices[name]; } var vertex = new Vertex(name); this.vertices[name] = vertex; this.names.push(name); return vertex; }; /** * Adds a vertex to the graph and sets its value. * * @private * @method map * @param {String} name The name of the vertex. * @param value The value to put in the vertex. */ DAG.prototype.map = function (name, value) { this.add(name).value = value; }; /** * Connects the vertices with the given names, adding them to the graph if * necessary, only if this does not produce is any circular dependency. * * @private * @method addEdge * @param {String} fromName The name the vertex where the edge starts. * @param {String} toName The name the vertex where the edge ends. */ DAG.prototype.addEdge = function (fromName, toName) { if (!fromName || !toName || fromName === toName) { return; } var from = this.add(fromName); var to = this.add(toName); if (to.incoming.hasOwnProperty(fromName)) { return; } function checkCycle(vertex, path) { if (vertex.name === toName) { throw new Error("cycle detected: " + toName + " <- " + path.join(" <- ")); } } visit(from, checkCycle); from.hasOutgoing = true; to.incoming[fromName] = from; to.incomingNames.push(fromName); }; /** * Visits all the vertex of the graph calling the given function with each one, * ensuring that the vertices are visited respecting their precedence. * * @method topsort * @param {Function} fn The function to be invoked on each vertex. */ DAG.prototype.topsort = function (fn) { var visited = {}; var vertices = this.vertices; var names = this.names; var len = names.length; var i, vertex; for (i = 0; i < len; i++) { vertex = vertices[names[i]]; if (!vertex.hasOutgoing) { visit(vertex, fn, visited); } } }; /** * Adds a vertex with the given name and value to the graph and joins it with the * vertices referenced in _before_ and _after_. If there isn't vertices with those * names, they are added too. * * If either _before_ or _after_ are falsy/empty, the added vertex will not have * an incoming/outgoing edge. * * @method addEdges * @param {String} name The name of the vertex to be added. * @param value The value of that vertex. * @param before An string or array of strings with the names of the vertices before * which this vertex must be visited. * @param after An string or array of strings with the names of the vertex after * which this vertex must be visited. * */ DAG.prototype.addEdges = function (name, value, before, after) { var i; this.map(name, value); if (before) { if (typeof before === 'string') { this.addEdge(name, before); } else { for (i = 0; i < before.length; i++) { this.addEdge(name, before[i]); } } } if (after) { if (typeof after === 'string') { this.addEdge(after, name); } else { for (i = 0; i < after.length; i++) { this.addEdge(after[i], name); } } } }; exports["default"] = DAG; }); enifed('dag-map.umd', ['exports', './dag-map'], function (exports, _dagMap) { 'use strict'; /* global define:true module:true window: true */ if (typeof enifed === 'function' && enifed.amd) { enifed(function () { return _dagMap["default"]; }); } else if (typeof module !== 'undefined' && module.exports) { module.exports = _dagMap["default"]; } else if (typeof undefined !== 'undefined') { undefined['DAG'] = _dagMap["default"]; } }); enifed("dom-helper", ["exports", "./htmlbars-runtime/morph", "./morph-attr", "./dom-helper/build-html-dom", "./dom-helper/classes", "./dom-helper/prop"], function (exports, _htmlbarsRuntimeMorph, _morphAttr, _domHelperBuildHtmlDom, _domHelperClasses, _domHelperProp) { "use strict"; var doc = typeof document === 'undefined' ? false : document; var deletesBlankTextNodes = doc && (function (document) { var element = document.createElement('div'); element.appendChild(document.createTextNode('')); var clonedElement = element.cloneNode(true); return clonedElement.childNodes.length === 0; })(doc); var ignoresCheckedAttribute = doc && (function (document) { var element = document.createElement('input'); element.setAttribute('checked', 'checked'); var clonedElement = element.cloneNode(false); return !clonedElement.checked; })(doc); var canRemoveSvgViewBoxAttribute = doc && (doc.createElementNS ? (function (document) { var element = document.createElementNS(_domHelperBuildHtmlDom.svgNamespace, 'svg'); element.setAttribute('viewBox', '0 0 100 100'); element.removeAttribute('viewBox'); return !element.getAttribute('viewBox'); })(doc) : true); var canClone = doc && (function (document) { var element = document.createElement('div'); element.appendChild(document.createTextNode(' ')); element.appendChild(document.createTextNode(' ')); var clonedElement = element.cloneNode(true); return clonedElement.childNodes[0].nodeValue === ' '; })(doc); // This is not the namespace of the element, but of // the elements inside that elements. function interiorNamespace(element) { if (element && element.namespaceURI === _domHelperBuildHtmlDom.svgNamespace && !_domHelperBuildHtmlDom.svgHTMLIntegrationPoints[element.tagName]) { return _domHelperBuildHtmlDom.svgNamespace; } else { return null; } } // The HTML spec allows for "omitted start tags". These tags are optional // when their intended child is the first thing in the parent tag. For // example, this is a tbody start tag: // // // // // // The tbody may be omitted, and the browser will accept and render: // //
// // // However, the omitted start tag will still be added to the DOM. Here // we test the string and context to see if the browser is about to // perform this cleanup. // // http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags // describes which tags are omittable. The spec for tbody and colgroup // explains this behavior: // // http://www.whatwg.org/specs/web-apps/current-work/multipage/tables.html#the-tbody-element // http://www.whatwg.org/specs/web-apps/current-work/multipage/tables.html#the-colgroup-element // var omittedStartTagChildTest = /<([\w:]+)/; function detectOmittedStartTag(string, contextualElement) { // Omitted start tags are only inside table tags. if (contextualElement.tagName === 'TABLE') { var omittedStartTagChildMatch = omittedStartTagChildTest.exec(string); if (omittedStartTagChildMatch) { var omittedStartTagChild = omittedStartTagChildMatch[1]; // It is already asserted that the contextual element is a table // and not the proper start tag. Just see if a tag was omitted. return omittedStartTagChild === 'tr' || omittedStartTagChild === 'col'; } } } function buildSVGDOM(html, dom) { var div = dom.document.createElement('div'); div.innerHTML = '' + html + ''; return div.firstChild.childNodes; } var guid = 1; function ElementMorph(element, dom, namespace) { this.element = element; this.dom = dom; this.namespace = namespace; this.guid = "element" + guid++; this.state = {}; this.isDirty = true; } // renderAndCleanup calls `clear` on all items in the morph map // just before calling `destroy` on the morph. // // As a future refactor this could be changed to set the property // back to its original/default value. ElementMorph.prototype.clear = function () {}; ElementMorph.prototype.destroy = function () { this.element = null; this.dom = null; }; /* * A class wrapping DOM functions to address environment compatibility, * namespaces, contextual elements for morph un-escaped content * insertion. * * When entering a template, a DOMHelper should be passed: * * template(context, { hooks: hooks, dom: new DOMHelper() }); * * TODO: support foreignObject as a passed contextual element. It has * a namespace (svg) that does not match its internal namespace * (xhtml). * * @class DOMHelper * @constructor * @param {HTMLDocument} _document The document DOM methods are proxied to */ function DOMHelper(_document) { this.document = _document || document; if (!this.document) { throw new Error("A document object must be passed to the DOMHelper, or available on the global scope"); } this.canClone = canClone; this.namespace = null; } var prototype = DOMHelper.prototype; prototype.constructor = DOMHelper; prototype.getElementById = function (id, rootNode) { rootNode = rootNode || this.document; return rootNode.getElementById(id); }; prototype.insertBefore = function (element, childElement, referenceChild) { return element.insertBefore(childElement, referenceChild); }; prototype.appendChild = function (element, childElement) { return element.appendChild(childElement); }; prototype.childAt = function (element, indices) { var child = element; for (var i = 0; i < indices.length; i++) { child = child.childNodes.item(indices[i]); } return child; }; // Note to a Fellow Implementor: // Ahh, accessing a child node at an index. Seems like it should be so simple, // doesn't it? Unfortunately, this particular method has caused us a surprising // amount of pain. As you'll note below, this method has been modified to walk // the linked list of child nodes rather than access the child by index // directly, even though there are two (2) APIs in the DOM that do this for us. // If you're thinking to yourself, "What an oversight! What an opportunity to // optimize this code!" then to you I say: stop! For I have a tale to tell. // // First, this code must be compatible with simple-dom for rendering on the // server where there is no real DOM. Previously, we accessed a child node // directly via `element.childNodes[index]`. While we *could* in theory do a // full-fidelity simulation of a live `childNodes` array, this is slow, // complicated and error-prone. // // "No problem," we thought, "we'll just use the similar // `childNodes.item(index)` API." Then, we could just implement our own `item` // method in simple-dom and walk the child node linked list there, allowing // us to retain the performance advantages of the (surely optimized) `item()` // API in the browser. // // Unfortunately, an enterprising soul named Samy Alzahrani discovered that in // IE8, accessing an item out-of-bounds via `item()` causes an exception where // other browsers return null. This necessitated a... check of // `childNodes.length`, bringing us back around to having to support a // full-fidelity `childNodes` array! // // Worst of all, Kris Selden investigated how browsers are actualy implemented // and discovered that they're all linked lists under the hood anyway. Accessing // `childNodes` requires them to allocate a new live collection backed by that // linked list, which is itself a rather expensive operation. Our assumed // optimization had backfired! That is the danger of magical thinking about // the performance of native implementations. // // And this, my friends, is why the following implementation just walks the // linked list, as surprised as that may make you. Please ensure you understand // the above before changing this and submitting a PR. // // Tom Dale, January 18th, 2015, Portland OR prototype.childAtIndex = function (element, index) { var node = element.firstChild; for (var idx = 0; node && idx < index; idx++) { node = node.nextSibling; } return node; }; prototype.appendText = function (element, text) { return element.appendChild(this.document.createTextNode(text)); }; prototype.setAttribute = function (element, name, value) { element.setAttribute(name, String(value)); }; prototype.getAttribute = function (element, name) { return element.getAttribute(name); }; prototype.setAttributeNS = function (element, namespace, name, value) { element.setAttributeNS(namespace, name, String(value)); }; prototype.getAttributeNS = function (element, namespace, name) { return element.getAttributeNS(namespace, name); }; if (canRemoveSvgViewBoxAttribute) { prototype.removeAttribute = function (element, name) { element.removeAttribute(name); }; } else { prototype.removeAttribute = function (element, name) { if (element.tagName === 'svg' && name === 'viewBox') { element.setAttribute(name, null); } else { element.removeAttribute(name); } }; } prototype.setPropertyStrict = function (element, name, value) { if (value === undefined) { value = null; } if (value === null && (name === 'value' || name === 'type' || name === 'src')) { value = ''; } element[name] = value; }; prototype.getPropertyStrict = function (element, name) { return element[name]; }; prototype.setProperty = function (element, name, value, namespace) { var lowercaseName = name.toLowerCase(); if (element.namespaceURI === _domHelperBuildHtmlDom.svgNamespace || lowercaseName === 'style') { if (_domHelperProp.isAttrRemovalValue(value)) { element.removeAttribute(name); } else { if (namespace) { element.setAttributeNS(namespace, name, value); } else { element.setAttribute(name, value); } } } else { var _normalizeProperty = _domHelperProp.normalizeProperty(element, name); var normalized = _normalizeProperty.normalized; var type = _normalizeProperty.type; if (type === 'prop') { element[normalized] = value; } else { if (_domHelperProp.isAttrRemovalValue(value)) { element.removeAttribute(name); } else { if (namespace && element.setAttributeNS) { element.setAttributeNS(namespace, name, value); } else { element.setAttribute(name, value); } } } } }; if (doc && doc.createElementNS) { // Only opt into namespace detection if a contextualElement // is passed. prototype.createElement = function (tagName, contextualElement) { var namespace = this.namespace; if (contextualElement) { if (tagName === 'svg') { namespace = _domHelperBuildHtmlDom.svgNamespace; } else { namespace = interiorNamespace(contextualElement); } } if (namespace) { return this.document.createElementNS(namespace, tagName); } else { return this.document.createElement(tagName); } }; prototype.setAttributeNS = function (element, namespace, name, value) { element.setAttributeNS(namespace, name, String(value)); }; } else { prototype.createElement = function (tagName) { return this.document.createElement(tagName); }; prototype.setAttributeNS = function (element, namespace, name, value) { element.setAttribute(name, String(value)); }; } prototype.addClasses = _domHelperClasses.addClasses; prototype.removeClasses = _domHelperClasses.removeClasses; prototype.setNamespace = function (ns) { this.namespace = ns; }; prototype.detectNamespace = function (element) { this.namespace = interiorNamespace(element); }; prototype.createDocumentFragment = function () { return this.document.createDocumentFragment(); }; prototype.createTextNode = function (text) { return this.document.createTextNode(text); }; prototype.createComment = function (text) { return this.document.createComment(text); }; prototype.repairClonedNode = function (element, blankChildTextNodes, isChecked) { if (deletesBlankTextNodes && blankChildTextNodes.length > 0) { for (var i = 0, len = blankChildTextNodes.length; i < len; i++) { var textNode = this.document.createTextNode(''), offset = blankChildTextNodes[i], before = this.childAtIndex(element, offset); if (before) { element.insertBefore(textNode, before); } else { element.appendChild(textNode); } } } if (ignoresCheckedAttribute && isChecked) { element.setAttribute('checked', 'checked'); } }; prototype.cloneNode = function (element, deep) { var clone = element.cloneNode(!!deep); return clone; }; prototype.AttrMorphClass = _morphAttr["default"]; prototype.createAttrMorph = function (element, attrName, namespace) { return new this.AttrMorphClass(element, attrName, this, namespace); }; prototype.ElementMorphClass = ElementMorph; prototype.createElementMorph = function (element, namespace) { return new this.ElementMorphClass(element, this, namespace); }; prototype.createUnsafeAttrMorph = function (element, attrName, namespace) { var morph = this.createAttrMorph(element, attrName, namespace); morph.escaped = false; return morph; }; prototype.MorphClass = _htmlbarsRuntimeMorph["default"]; prototype.createMorph = function (parent, start, end, contextualElement) { if (contextualElement && contextualElement.nodeType === 11) { throw new Error("Cannot pass a fragment as the contextual element to createMorph"); } if (!contextualElement && parent && parent.nodeType === 1) { contextualElement = parent; } var morph = new this.MorphClass(this, contextualElement); morph.firstNode = start; morph.lastNode = end; return morph; }; prototype.createFragmentMorph = function (contextualElement) { if (contextualElement && contextualElement.nodeType === 11) { throw new Error("Cannot pass a fragment as the contextual element to createMorph"); } var fragment = this.createDocumentFragment(); return _htmlbarsRuntimeMorph["default"].create(this, contextualElement, fragment); }; prototype.replaceContentWithMorph = function (element) { var firstChild = element.firstChild; if (!firstChild) { var comment = this.createComment(''); this.appendChild(element, comment); return _htmlbarsRuntimeMorph["default"].create(this, element, comment); } else { var morph = _htmlbarsRuntimeMorph["default"].attach(this, element, firstChild, element.lastChild); morph.clear(); return morph; } }; prototype.createUnsafeMorph = function (parent, start, end, contextualElement) { var morph = this.createMorph(parent, start, end, contextualElement); morph.parseTextAsHTML = true; return morph; }; // This helper is just to keep the templates good looking, // passing integers instead of element references. prototype.createMorphAt = function (parent, startIndex, endIndex, contextualElement) { var single = startIndex === endIndex; var start = this.childAtIndex(parent, startIndex); var end = single ? start : this.childAtIndex(parent, endIndex); return this.createMorph(parent, start, end, contextualElement); }; prototype.createUnsafeMorphAt = function (parent, startIndex, endIndex, contextualElement) { var morph = this.createMorphAt(parent, startIndex, endIndex, contextualElement); morph.parseTextAsHTML = true; return morph; }; prototype.insertMorphBefore = function (element, referenceChild, contextualElement) { var insertion = this.document.createComment(''); element.insertBefore(insertion, referenceChild); return this.createMorph(element, insertion, insertion, contextualElement); }; prototype.appendMorph = function (element, contextualElement) { var insertion = this.document.createComment(''); element.appendChild(insertion); return this.createMorph(element, insertion, insertion, contextualElement); }; prototype.insertBoundary = function (fragment, index) { // this will always be null or firstChild var child = index === null ? null : this.childAtIndex(fragment, index); this.insertBefore(fragment, this.createTextNode(''), child); }; prototype.setMorphHTML = function (morph, html) { morph.setHTML(html); }; prototype.parseHTML = function (html, contextualElement) { var childNodes; if (interiorNamespace(contextualElement) === _domHelperBuildHtmlDom.svgNamespace) { childNodes = buildSVGDOM(html, this); } else { var nodes = _domHelperBuildHtmlDom.buildHTMLDOM(html, contextualElement, this); if (detectOmittedStartTag(html, contextualElement)) { var node = nodes[0]; while (node && node.nodeType !== 1) { node = node.nextSibling; } childNodes = node.childNodes; } else { childNodes = nodes; } } // Copy node list to a fragment. var fragment = this.document.createDocumentFragment(); if (childNodes && childNodes.length > 0) { var currentNode = childNodes[0]; // We prepend an