1 // Version: v1.0.0-pre.4
2 // Last commit: 855db1a (2013-01-17 23:06:53 -0800)
11 @submodule ember-debug
18 if ('undefined' === typeof Ember) {
21 if ('undefined' !== typeof window) {
22 window.Em = window.Ember = Em = Ember;
26 Ember.ENV = 'undefined' === typeof ENV ? {} : ENV;
28 if (!('MANDATORY_SETTER' in Ember.ENV)) {
29 Ember.ENV.MANDATORY_SETTER = true; // default to true for debug dist
33 Define an assertion that will throw an exception if the condition is not
34 met. Ember build tools will remove any calls to `Ember.assert()` when
35 doing a production build. Example:
38 // Test for truthiness
39 Ember.assert('Must pass a valid object', obj);
40 // Fail unconditionally
41 Ember.assert('This code path should never be run')
45 @param {String} desc A description of the assertion. This will become
46 the text of the Error thrown if the assertion fails.
47 @param {Boolean} test Must be truthy for the assertion to pass. If
48 falsy, an exception will be thrown.
50 Ember.assert = function(desc, test) {
51 if (!test) throw new Error("assertion failed: "+desc);
56 Display a warning with the provided message. Ember build tools will
57 remove any calls to `Ember.warn()` when doing a production build.
60 @param {String} message A warning to display.
61 @param {Boolean} test An optional boolean. If falsy, the warning
64 Ember.warn = function(message, test) {
66 Ember.Logger.warn("WARNING: "+message);
67 if ('trace' in Ember.Logger) Ember.Logger.trace();
72 Display a deprecation warning with the provided message and a stack trace
73 (Chrome and Firefox only). Ember build tools will remove any calls to
74 `Ember.deprecate()` when doing a production build.
77 @param {String} message A description of the deprecation.
78 @param {Boolean} test An optional boolean. If falsy, the deprecation
81 Ember.deprecate = function(message, test) {
82 if (Ember && Ember.TESTING_DEPRECATION) { return; }
84 if (arguments.length === 1) { test = false; }
87 if (Ember && Ember.ENV.RAISE_ON_DEPRECATION) { throw new Error(message); }
91 // When using new Error, we can't do the arguments check for Chrome. Alternatives are welcome
92 try { __fail__.fail(); } catch (e) { error = e; }
94 if (Ember.LOG_STACKTRACE_ON_DEPRECATION && error.stack) {
95 var stack, stackStr = '';
96 if (error['arguments']) {
98 stack = error.stack.replace(/^\s+at\s+/gm, '').
99 replace(/^([^\(]+?)([\n$])/gm, '{anonymous}($1)$2').
100 replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}($1)').split('\n');
104 stack = error.stack.replace(/(?:\n@:0)?\s+$/m, '').
105 replace(/^\(/gm, '{anonymous}(').split('\n');
108 stackStr = "\n " + stack.slice(2).join("\n ");
109 message = message + stackStr;
112 Ember.Logger.warn("DEPRECATION: "+message);
118 Display a deprecation warning with the provided message and a stack trace
119 (Chrome and Firefox only) when the wrapped method is called.
121 Ember build tools will not remove calls to `Ember.deprecateFunc()`, though
122 no warnings will be shown in production.
124 @method deprecateFunc
125 @param {String} message A description of the deprecation.
126 @param {Function} func The function to be deprecated.
128 Ember.deprecateFunc = function(message, func) {
130 Ember.deprecate(message);
131 return func.apply(this, arguments);
135 if ('undefined' !== typeof window) {
136 window.ember_assert = Ember.deprecateFunc("ember_assert is deprecated. Please use Ember.assert instead.", Ember.assert);
137 window.ember_warn = Ember.deprecateFunc("ember_warn is deprecated. Please use Ember.warn instead.", Ember.warn);
138 window.ember_deprecate = Ember.deprecateFunc("ember_deprecate is deprecated. Please use Ember.deprecate instead.", Ember.deprecate);
139 window.ember_deprecateFunc = Ember.deprecateFunc("ember_deprecateFunc is deprecated. Please use Ember.deprecateFunc instead.", Ember.deprecateFunc);
144 // Version: v1.0.0-pre.3-19-g015138e
145 // Last commit: 015138e (2013-01-17 23:02:17 -0800)
149 var define, requireModule;
152 var registry = {}, seen = {};
154 define = function(name, deps, callback) {
155 registry[name] = { deps: deps, callback: callback };
158 requireModule = function(name) {
159 if (seen[name]) { return seen[name]; }
162 var mod = registry[name],
164 callback = mod.callback,
168 for (var i=0, l=deps.length; i<l; i++) {
169 if (deps[i] === 'exports') {
170 reified.push(exports = {});
172 reified.push(requireModule(deps[i]));
176 var value = callback.apply(this, reified);
177 return seen[name] = exports || value;
181 /*globals Em:true ENV */
185 @submodule ember-metal
189 All Ember methods and functions are defined inside of this namespace. You
190 generally should not add new properties to this namespace as it may be
191 overwritten by future versions of Ember.
193 You can also use the shorthand `Em` instead of `Ember`.
195 Ember-Runtime is a framework that provides core functions for Ember including
196 cross-platform functions, support for property observing and objects. Its
197 focus is on small size and performance. You can use this in place of or
198 along-side other cross-platform libraries such as jQuery.
200 The core Runtime framework is based on the jQuery API with a number of
201 performance optimizations.
208 if ('undefined' === typeof Ember) {
209 // Create core object. Make it act like an instance of Ember.Namespace so that
210 // objects assigned to it are given a sane string representation.
214 // Default imports, exports and lookup to the global object;
215 var imports = Ember.imports = Ember.imports || this;
216 var exports = Ember.exports = Ember.exports || this;
217 var lookup = Ember.lookup = Ember.lookup || this;
219 // aliases needed to keep minifiers from removing the global context
220 exports.Em = exports.Ember = Em = Ember;
222 // Make sure these are set whether Ember was already defined or not
224 Ember.isNamespace = true;
226 Ember.toString = function() { return "Ember"; };
232 @default '1.0.0-pre.4'
235 Ember.VERSION = '1.0.0-pre.4';
238 Standard environmental variables. You can define these in a global `ENV`
239 variable before loading Ember to control various configuration
245 Ember.ENV = Ember.ENV || ('undefined' === typeof ENV ? {} : ENV);
247 Ember.config = Ember.config || {};
249 // ..........................................................
254 Determines whether Ember should enhances some built-in object prototypes to
255 provide a more friendly API. If enabled, a few methods will be added to
256 `Function`, `String`, and `Array`. `Object.prototype` will not be enhanced,
257 which is the one that causes most trouble for people.
259 In general we recommend leaving this option set to true since it rarely
260 conflicts with other code. If you need to turn it off however, you can
261 define an `ENV.EXTEND_PROTOTYPES` config to disable it.
263 @property EXTEND_PROTOTYPES
267 Ember.EXTEND_PROTOTYPES = Ember.ENV.EXTEND_PROTOTYPES;
269 if (typeof Ember.EXTEND_PROTOTYPES === 'undefined') {
270 Ember.EXTEND_PROTOTYPES = true;
274 Determines whether Ember logs a full stack trace during deprecation warnings
276 @property LOG_STACKTRACE_ON_DEPRECATION
280 Ember.LOG_STACKTRACE_ON_DEPRECATION = (Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION !== false);
283 Determines whether Ember should add ECMAScript 5 shims to older browsers.
287 @default Ember.EXTEND_PROTOTYPES
289 Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES;
292 Empty function. Useful for some operations.
298 Ember.K = function() { return this; };
301 // Stub out the methods defined by the ember-debug package in case it's not loaded
303 if ('undefined' === typeof Ember.assert) { Ember.assert = Ember.K; }
304 if ('undefined' === typeof Ember.warn) { Ember.warn = Ember.K; }
305 if ('undefined' === typeof Ember.deprecate) { Ember.deprecate = Ember.K; }
306 if ('undefined' === typeof Ember.deprecateFunc) {
307 Ember.deprecateFunc = function(_, func) { return func; };
310 // These are deprecated but still supported
312 if ('undefined' === typeof ember_assert) { exports.ember_assert = Ember.K; }
313 if ('undefined' === typeof ember_warn) { exports.ember_warn = Ember.K; }
314 if ('undefined' === typeof ember_deprecate) { exports.ember_deprecate = Ember.K; }
315 if ('undefined' === typeof ember_deprecateFunc) {
316 exports.ember_deprecateFunc = function(_, func) { return func; };
320 Previously we used `Ember.$.uuid`, however `$.uuid` has been removed from
321 jQuery master. We'll just bootstrap our own uuid now.
329 // ..........................................................
334 Inside Ember-Metal, simply uses the `imports.console` object.
335 Override this to provide more robust logging functionality.
340 Ember.Logger = imports.console || { log: Ember.K, warn: Ember.K, error: Ember.K, info: Ember.K, debug: Ember.K };
343 // ..........................................................
348 A function may be assigned to `Ember.onerror` to be called when Ember
349 internals encounter an error. This is useful for specialized error handling
354 @param {Exception} error the error object
356 Ember.onerror = null;
361 Wrap code block in a try/catch if {{#crossLink "Ember/onerror"}}{{/crossLink}} is set.
365 @param {Function} func
368 Ember.handleErrors = function(func, context) {
369 // Unfortunately in some browsers we lose the backtrace if we rethrow the existing error,
370 // so in the event that we don't have an `onerror` handler we don't wrap in a try/catch
371 if ('function' === typeof Ember.onerror) {
373 return func.apply(context || this);
375 Ember.onerror(error);
378 return func.apply(context || this);
382 Ember.merge = function(original, updates) {
383 for (var prop in updates) {
384 if (!updates.hasOwnProperty(prop)) { continue; }
385 original[prop] = updates[prop];
400 Platform specific methods and feature detectors needed by the framework.
406 var platform = Ember.platform = {};
410 Identical to `Object.create()`. Implements if not available natively.
415 Ember.create = Object.create;
417 // STUB_OBJECT_CREATE allows us to override other libraries that stub
418 // Object.create different than we would prefer
419 if (!Ember.create || Ember.ENV.STUB_OBJECT_CREATE) {
420 var K = function() {};
422 Ember.create = function(obj, props) {
427 for (var prop in props) {
428 K.prototype[prop] = props[prop].value;
437 Ember.create.isSimulated = true;
440 var defineProperty = Object.defineProperty;
441 var canRedefineProperties, canDefinePropertyOnDOM;
443 // Catch IE8 where Object.defineProperty exists but only works on DOM elements
444 if (defineProperty) {
446 defineProperty({}, 'a',{get:function(){}});
448 defineProperty = null;
452 if (defineProperty) {
453 // Detects a bug in Android <3.2 where you cannot redefine a property using
454 // Object.defineProperty once accessors have already been set.
455 canRedefineProperties = (function() {
458 defineProperty(obj, 'a', {
465 defineProperty(obj, 'a', {
472 return obj.a === true;
475 // This is for Safari 5.0, which supports Object.defineProperty, but not
477 canDefinePropertyOnDOM = (function(){
479 defineProperty(document.createElement('div'), 'definePropertyOnDOM', {});
486 if (!canRedefineProperties) {
487 defineProperty = null;
488 } else if (!canDefinePropertyOnDOM) {
489 defineProperty = function(obj, keyName, desc){
492 if (typeof Node === "object") {
493 isNode = obj instanceof Node;
495 isNode = typeof obj === "object" && typeof obj.nodeType === "number" && typeof obj.nodeName === "string";
499 // TODO: Should we have a warning here?
500 return (obj[keyName] = desc.value);
502 return Object.defineProperty(obj, keyName, desc);
514 Identical to `Object.defineProperty()`. Implements as much functionality
515 as possible if not available natively.
517 @method defineProperty
518 @param {Object} obj The object to modify
519 @param {String} keyName property name to modify
520 @param {Object} desc descriptor hash
523 platform.defineProperty = defineProperty;
526 Set to true if the platform supports native getters and setters.
528 @property hasPropertyAccessors
531 platform.hasPropertyAccessors = true;
533 if (!platform.defineProperty) {
534 platform.hasPropertyAccessors = false;
536 platform.defineProperty = function(obj, keyName, desc) {
537 if (!desc.get) { obj[keyName] = desc.value; }
540 platform.defineProperty.isSimulated = true;
543 if (Ember.ENV.MANDATORY_SETTER && !platform.hasPropertyAccessors) {
544 Ember.ENV.MANDATORY_SETTER = false;
557 var o_defineProperty = Ember.platform.defineProperty,
558 o_create = Ember.create,
559 // Used for guid generation...
560 GUID_KEY = '__ember'+ (+ new Date()),
565 var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
570 A unique key used to assign guids and other private metadata to objects.
571 If you inspect an object in your browser debugger you will often see these.
572 They can be safely ignored.
574 On browsers that support it, these properties are added with enumeration
575 disabled so they won't show up when you iterate over your properties.
582 Ember.GUID_KEY = GUID_KEY;
594 Generates a new guid, optionally saving the guid to the object that you
595 pass in. You will rarely need to use this method. Instead you should
596 call `Ember.guidFor(obj)`, which return an existing guid if available.
600 @param {Object} [obj] Object the guid will be used for. If passed in, the guid will
601 be saved on the object and reused whenever you pass the same object
604 If no object is passed, just generate a new guid.
605 @param {String} [prefix] Prefix to place in front of the guid. Useful when you want to
606 separate the guid into separate namespaces.
607 @return {String} the guid
609 Ember.generateGuid = function generateGuid(obj, prefix) {
610 if (!prefix) prefix = 'ember';
611 var ret = (prefix + (uuid++));
613 GUID_DESC.value = ret;
614 o_defineProperty(obj, GUID_KEY, GUID_DESC);
622 Returns a unique id for the object. If the object does not yet have a guid,
623 one will be assigned to it. You can call this on any object,
624 `Ember.Object`-based or not, but be aware that it will add a `_guid`
627 You can also use this method on DOM Element objects.
631 @param obj {Object} any object, string, number, Element, or primitive
632 @return {String} the unique guid for this instance.
634 Ember.guidFor = function guidFor(obj) {
636 // special cases where we don't want to add a key to object
637 if (obj === undefined) return "(undefined)";
638 if (obj === null) return "(null)";
641 var type = typeof obj;
643 // Don't allow prototype changes to String etc. to change the guidFor
646 ret = numberCache[obj];
647 if (!ret) ret = numberCache[obj] = 'nu'+obj;
651 ret = stringCache[obj];
652 if (!ret) ret = stringCache[obj] = 'st'+(uuid++);
656 return obj ? '(true)' : '(false)';
659 if (obj[GUID_KEY]) return obj[GUID_KEY];
660 if (obj === Object) return '(Object)';
661 if (obj === Array) return '(Array)';
662 ret = 'ember'+(uuid++);
663 GUID_DESC.value = ret;
664 o_defineProperty(obj, GUID_KEY, GUID_DESC);
669 // ..........................................................
680 var META_KEY = Ember.GUID_KEY+'_meta';
683 The key used to store meta information on object for property observing.
691 Ember.META_KEY = META_KEY;
693 // Placeholder for non-writable metas.
699 if (MANDATORY_SETTER) { EMPTY_META.values = {}; }
701 Ember.EMPTY_META = EMPTY_META;
703 if (Object.freeze) Object.freeze(EMPTY_META);
705 var isDefinePropertySimulated = Ember.platform.defineProperty.isSimulated;
714 if (isDefinePropertySimulated) {
715 // on platforms that don't support enumerable false
716 // make meta fail jQuery.isPlainObject() to hide from
717 // jQuery.extend() by having a property that fails
718 // hasOwnProperty check.
719 Meta.prototype.__preventPlainObject__ = true;
721 // Without non-enumerable properties, meta objects will be output in JSON
722 // unless explicitly suppressed
723 Meta.prototype.toJSON = function () { };
727 Retrieves the meta hash for an object. If `writable` is true ensures the
728 hash is writable for this object as well.
730 The meta object contains information about computed property descriptors as
731 well as any watched properties and other information. You generally will
732 not access this information directly but instead work with higher level
733 methods that manipulate this hash indirectly.
739 @param {Object} obj The object to retrieve meta for
740 @param {Boolean} [writable=true] Pass `false` if you do not intend to modify
741 the meta hash, allowing the method to avoid making an unnecessary copy.
744 Ember.meta = function meta(obj, writable) {
746 var ret = obj[META_KEY];
747 if (writable===false) return ret || EMPTY_META;
750 if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC);
754 if (MANDATORY_SETTER) { ret.values = {}; }
758 // make sure we don't accidentally try to create constructor like desc
759 ret.descs.constructor = null;
761 } else if (ret.source !== obj) {
762 if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC);
765 ret.descs = o_create(ret.descs);
766 ret.watching = o_create(ret.watching);
770 if (MANDATORY_SETTER) { ret.values = o_create(ret.values); }
777 Ember.getMeta = function getMeta(obj, property) {
778 var meta = Ember.meta(obj, false);
779 return meta[property];
782 Ember.setMeta = function setMeta(obj, property, value) {
783 var meta = Ember.meta(obj, true);
784 meta[property] = value;
791 In order to store defaults for a class, a prototype may need to create
792 a default meta object, which will be inherited by any objects instantiated
793 from the class's constructor.
795 However, the properties of that meta object are only shallow-cloned,
796 so if a property is a hash (like the event system's `listeners` hash),
797 it will by default be shared across all instances of that class.
799 This method allows extensions to deeply clone a series of nested hashes or
800 other complex objects. For instance, the event system might pass
801 `['listeners', 'foo:change', 'ember157']` to `prepareMetaPath`, which will
802 walk down the keys provided.
804 For each key, if the key does not exist, it is created. If it already
805 exists and it was inherited from its constructor, the constructor's
808 You can also pass false for `writable`, which will simply return
809 undefined if `prepareMetaPath` discovers any part of the path that
814 @param {Object} obj The object whose meta we are examining
815 @param {Array} path An array of keys to walk down
816 @param {Boolean} writable whether or not to create a new meta
817 (or meta property) if one does not already exist or if it's
818 shared with its constructor
820 Ember.metaPath = function metaPath(obj, path, writable) {
821 var meta = Ember.meta(obj, writable), keyName, value;
823 for (var i=0, l=path.length; i<l; i++) {
825 value = meta[keyName];
828 if (!writable) { return undefined; }
829 value = meta[keyName] = { __ember_source__: obj };
830 } else if (value.__ember_source__ !== obj) {
831 if (!writable) { return undefined; }
832 value = meta[keyName] = o_create(value);
833 value.__ember_source__ = obj;
845 Wraps the passed function so that `this._super` will point to the superFunc
846 when the function is invoked. This is the primitive we use to implement
851 @param {Function} func The function to call
852 @param {Function} superFunc The super function.
853 @return {Function} wrapped function.
855 Ember.wrap = function(func, superFunc) {
858 function superWrapper() {
859 var ret, sup = this._super;
860 this._super = superFunc || K;
861 ret = func.apply(this, arguments);
866 superWrapper.wrappedFunction = func;
867 superWrapper.__ember_observes__ = func.__ember_observes__;
868 superWrapper.__ember_observesBefore__ = func.__ember_observesBefore__;
874 Returns true if the passed object is an array or Array-like.
876 Ember Array Protocol:
878 - the object has an objectAt property
879 - the object is a native Array
880 - the object is an Object, and has a length property
882 Unlike `Ember.typeOf` this method returns true even if the passed object is
883 not formally array but appears to be array-like (i.e. implements `Ember.Array`)
886 Ember.isArray(); // false
887 Ember.isArray([]); // true
888 Ember.isArray( Ember.ArrayProxy.create({ content: [] }) ); // true
893 @param {Object} obj The object to test
896 Ember.isArray = function(obj) {
897 if (!obj || obj.setInterval) { return false; }
898 if (Array.isArray && Array.isArray(obj)) { return true; }
899 if (Ember.Array && Ember.Array.detect(obj)) { return true; }
900 if ((obj.length !== undefined) && 'object'===typeof obj) { return true; }
905 Forces the passed object to be part of an array. If the object is already
906 an array or array-like, returns the object. Otherwise adds the object to
907 an array. If obj is `null` or `undefined`, returns an empty array.
910 Ember.makeArray(); // []
911 Ember.makeArray(null); // []
912 Ember.makeArray(undefined); // []
913 Ember.makeArray('lindsay'); // ['lindsay']
914 Ember.makeArray([1,2,42]); // [1,2,42]
916 var controller = Ember.ArrayProxy.create({ content: [] });
917 Ember.makeArray(controller) === controller; // true
922 @param {Object} obj the object
925 Ember.makeArray = function(obj) {
926 if (obj === null || obj === undefined) { return []; }
927 return Ember.isArray(obj) ? obj : [obj];
930 function canInvoke(obj, methodName) {
931 return !!(obj && typeof obj[methodName] === 'function');
935 Checks to see if the `methodName` exists on the `obj`.
939 @param {Object} obj The object to check for the method
940 @param {String} methodName The method name to check for
942 Ember.canInvoke = canInvoke;
945 Checks to see if the `methodName` exists on the `obj`,
946 and if it does, invokes it with the arguments passed.
950 @param {Object} obj The object to check for the method
951 @param {String} methodName The method name to check for
952 @param {Array} [args] The arguments to pass to the method
953 @return {anything} the return value of the invoked method or undefined if it cannot be invoked
955 Ember.tryInvoke = function(obj, methodName, args) {
956 if (canInvoke(obj, methodName)) {
957 return obj[methodName].apply(obj, args || []);
961 // https://github.com/emberjs/ember.js/pull/1617
962 var needsFinallyFix = (function() {
968 throw new Error('needsFinallyFixTest');
976 Provides try { } finally { } functionality, while working
977 around Safari's double finally bug.
981 @param {Function} function The function to run the try callback
982 @param {Function} function The function to run the finally callback
984 @return {anything} The return value is the that of the finalizer,
985 unless that valueis undefined, in which case it is the return value
989 if (needsFinallyFix) {
990 Ember.tryFinally = function(tryable, finalizer, binding) {
991 var result, finalResult, finalError;
993 binding = binding || this;
996 result = tryable.call(binding);
999 finalResult = finalizer.call(binding);
1005 if (finalError) { throw finalError; }
1007 return (finalResult === undefined) ? result : finalResult;
1010 Ember.tryFinally = function(tryable, finalizer, binding) {
1011 var result, finalResult;
1013 binding = binding || this;
1016 result = tryable.call(binding);
1018 finalResult = finalizer.call(binding);
1021 return (finalResult === undefined) ? result : finalResult;
1026 Provides try { } catch finally { } functionality, while working
1027 around Safari's double finally bug.
1029 @method tryCatchFinally
1031 @param {Function} function The function to run the try callback
1032 @param {Function} function The function to run the catchable callback
1033 @param {Function} function The function to run the finally callback
1035 @return {anything} The return value is the that of the finalizer,
1036 unless that value is undefined, in which case it is the return value
1039 if (needsFinallyFix) {
1040 Ember.tryCatchFinally = function(tryable, catchable, finalizer, binding) {
1041 var result, finalResult, finalError, finalReturn;
1043 binding = binding || this;
1046 result = tryable.call(binding);
1048 result = catchable.call(binding, error);
1051 finalResult = finalizer.call(binding);
1057 if (finalError) { throw finalError; }
1059 return (finalResult === undefined) ? result : finalResult;
1062 Ember.tryCatchFinally = function(tryable, catchable, finalizer, binding) {
1063 var result, finalResult;
1065 binding = binding || this;
1068 result = tryable.call(binding);
1070 result = catchable.call(binding, error);
1072 finalResult = finalizer.call(binding);
1075 return (finalResult === undefined) ? result : finalResult;
1084 // Ember.tryCatchFinally
1087 The purpose of the Ember Instrumentation module is
1088 to provide efficient, general-purpose instrumentation
1091 Subscribe to a listener by using `Ember.subscribe`:
1094 Ember.subscribe("render", {
1095 before: function(name, timestamp, payload) {
1099 after: function(name, timestamp, payload) {
1105 If you return a value from the `before` callback, that same
1106 value will be passed as a fourth parameter to the `after`
1109 Instrument a block of code by using `Ember.instrument`:
1112 Ember.instrument("render.handlebars", payload, function() {
1117 Event names passed to `Ember.instrument` are namespaced
1118 by periods, from more general to more specific. Subscribers
1119 can listen for events by whatever level of granularity they
1122 In the above example, the event is `render.handlebars`,
1123 and the subscriber listened for all events beginning with
1124 `render`. It would receive callbacks for events named
1125 `render`, `render.handlebars`, `render.container`, or
1126 even `render.handlebars.layout`.
1128 @class Instrumentation
1132 Ember.Instrumentation = {};
1134 var subscribers = [], cache = {};
1136 var populateListeners = function(name) {
1137 var listeners = [], subscriber;
1139 for (var i=0, l=subscribers.length; i<l; i++) {
1140 subscriber = subscribers[i];
1141 if (subscriber.regex.test(name)) {
1142 listeners.push(subscriber.object);
1146 cache[name] = listeners;
1150 var time = (function() {
1151 var perf = 'undefined' !== typeof window ? window.performance || {} : {};
1152 var fn = perf.now || perf.mozNow || perf.webkitNow || perf.msNow || perf.oNow;
1153 // fn.bind will be available in all the browsers that support the advanced window.performance... ;-)
1154 return fn ? fn.bind(perf) : function() { return +new Date(); };
1158 Ember.Instrumentation.instrument = function(name, payload, callback, binding) {
1159 var listeners = cache[name], timeName, ret;
1161 if (Ember.STRUCTURED_PROFILE) {
1162 timeName = name + ": " + payload.object;
1163 console.time(timeName);
1167 listeners = populateListeners(name);
1170 if (listeners.length === 0) {
1171 ret = callback.call(binding);
1172 if (Ember.STRUCTURED_PROFILE) { console.timeEnd(timeName); }
1176 var beforeValues = [], listener, i, l;
1179 for (i=0, l=listeners.length; i<l; i++) {
1180 listener = listeners[i];
1181 beforeValues[i] = listener.before(name, time(), payload);
1184 return callback.call(binding);
1187 function catchable(e){
1188 payload = payload || {};
1189 payload.exception = e;
1192 function finalizer() {
1193 for (i=0, l=listeners.length; i<l; i++) {
1194 listener = listeners[i];
1195 listener.after(name, time(), payload, beforeValues[i]);
1198 if (Ember.STRUCTURED_PROFILE) {
1199 console.timeEnd(timeName);
1203 return Ember.tryCatchFinally(tryable, catchable, finalizer);
1206 Ember.Instrumentation.subscribe = function(pattern, object) {
1207 var paths = pattern.split("."), path, regex = [];
1209 for (var i=0, l=paths.length; i<l; i++) {
1212 regex.push("[^\\.]*");
1218 regex = regex.join("\\.");
1219 regex = regex + "(\\..*)?";
1223 regex: new RegExp("^" + regex + "$"),
1227 subscribers.push(subscriber);
1233 Ember.Instrumentation.unsubscribe = function(subscriber) {
1236 for (var i=0, l=subscribers.length; i<l; i++) {
1237 if (subscribers[i] === subscriber) {
1242 subscribers.splice(index, 1);
1246 Ember.Instrumentation.reset = function() {
1251 Ember.instrument = Ember.Instrumentation.instrument;
1252 Ember.subscribe = Ember.Instrumentation.subscribe;
1259 /*jshint newcap:false*/
1265 // NOTE: There is a bug in jshint that doesn't recognize `Object()` without `new`
1266 // as being ok unless both `newcap:false` and not `use strict`.
1267 // https://github.com/jshint/jshint/issues/392
1269 // Testing this is not ideal, but we want to use native functions
1270 // if available, but not to use versions created by libraries like Prototype
1271 var isNativeFunc = function(func) {
1272 // This should probably work in all browsers likely to have ES5 array methods
1273 return func && Function.prototype.toString.call(func).indexOf('[native code]') > -1;
1276 // From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map
1277 var arrayMap = isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun /*, thisp */) {
1280 if (this === void 0 || this === null) {
1281 throw new TypeError();
1284 var t = Object(this);
1285 var len = t.length >>> 0;
1286 if (typeof fun !== "function") {
1287 throw new TypeError();
1290 var res = new Array(len);
1291 var thisp = arguments[1];
1292 for (var i = 0; i < len; i++) {
1294 res[i] = fun.call(thisp, t[i], i, t);
1301 // From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
1302 var arrayForEach = isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun /*, thisp */) {
1305 if (this === void 0 || this === null) {
1306 throw new TypeError();
1309 var t = Object(this);
1310 var len = t.length >>> 0;
1311 if (typeof fun !== "function") {
1312 throw new TypeError();
1315 var thisp = arguments[1];
1316 for (var i = 0; i < len; i++) {
1318 fun.call(thisp, t[i], i, t);
1323 var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj, fromIndex) {
1324 if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; }
1325 else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); }
1326 for (var i = fromIndex, j = this.length; i < j; i++) {
1327 if (this[i] === obj) { return i; }
1332 Ember.ArrayPolyfills = {
1334 forEach: arrayForEach,
1335 indexOf: arrayIndexOf
1338 var utils = Ember.EnumerableUtils = {
1339 map: function(obj, callback, thisArg) {
1340 return obj.map ? obj.map.call(obj, callback, thisArg) : arrayMap.call(obj, callback, thisArg);
1343 forEach: function(obj, callback, thisArg) {
1344 return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : arrayForEach.call(obj, callback, thisArg);
1347 indexOf: function(obj, element, index) {
1348 return obj.indexOf ? obj.indexOf.call(obj, element, index) : arrayIndexOf.call(obj, element, index);
1351 indexesOf: function(obj, elements) {
1352 return elements === undefined ? [] : utils.map(elements, function(item) {
1353 return utils.indexOf(obj, item);
1357 addObject: function(array, item) {
1358 var index = utils.indexOf(array, item);
1359 if (index === -1) { array.push(item); }
1362 removeObject: function(array, item) {
1363 var index = utils.indexOf(array, item);
1364 if (index !== -1) { array.splice(index, 1); }
1367 replace: function(array, idx, amt, objects) {
1368 if (array.replace) {
1369 return array.replace(idx, amt, objects);
1371 var args = Array.prototype.concat.apply([idx, amt], objects);
1372 return array.splice.apply(array, args);
1378 if (Ember.SHIM_ES5) {
1379 if (!Array.prototype.map) {
1380 Array.prototype.map = arrayMap;
1383 if (!Array.prototype.forEach) {
1384 Array.prototype.forEach = arrayForEach;
1387 if (!Array.prototype.indexOf) {
1388 Array.prototype.indexOf = arrayIndexOf;
1402 JavaScript (before ES6) does not have a Map implementation. Objects,
1403 which are often used as dictionaries, may only have Strings as keys.
1405 Because Ember has a way to get a unique identifier for every object
1406 via `Ember.guidFor`, we can implement a performant Map with arbitrary
1407 keys. Because it is commonly used in low-level bookkeeping, Map is
1408 implemented as a pure JavaScript object for performance.
1410 This implementation follows the current iteration of the ES6 proposal for
1411 maps (http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets),
1412 with two exceptions. First, because we need our implementation to be pleasant
1413 on older browsers, we do not use the `delete` name (using `remove` instead).
1414 Second, as we do not have the luxury of in-VM iteration, we implement a
1415 forEach method for iteration.
1417 Map is mocked out to look like an Ember object, so you can do
1418 `Ember.Map.create()` for symmetry with other Ember classes.
1420 var guidFor = Ember.guidFor,
1421 indexOf = Ember.ArrayPolyfills.indexOf;
1423 var copy = function(obj) {
1426 for (var prop in obj) {
1427 if (obj.hasOwnProperty(prop)) { output[prop] = obj[prop]; }
1433 var copyMap = function(original, newObject) {
1434 var keys = original.keys.copy(),
1435 values = copy(original.values);
1437 newObject.keys = keys;
1438 newObject.values = values;
1444 This class is used internally by Ember and Ember Data.
1445 Please do not use it at this time. We plan to clean it up
1446 and add many tests soon.
1453 var OrderedSet = Ember.OrderedSet = function() {
1460 @return {Ember.OrderedSet}
1462 OrderedSet.create = function() {
1463 return new OrderedSet();
1467 OrderedSet.prototype = {
1472 this.presenceSet = {};
1480 add: function(obj) {
1481 var guid = guidFor(obj),
1482 presenceSet = this.presenceSet,
1485 if (guid in presenceSet) { return; }
1487 presenceSet[guid] = true;
1495 remove: function(obj) {
1496 var guid = guidFor(obj),
1497 presenceSet = this.presenceSet,
1500 delete presenceSet[guid];
1502 var index = indexOf.call(list, obj);
1504 list.splice(index, 1);
1512 isEmpty: function() {
1513 return this.list.length === 0;
1521 has: function(obj) {
1522 var guid = guidFor(obj),
1523 presenceSet = this.presenceSet;
1525 return guid in presenceSet;
1530 @param {Function} function
1533 forEach: function(fn, self) {
1534 // allow mutation during iteration
1535 var list = this.list.slice();
1537 for (var i = 0, j = list.length; i < j; i++) {
1538 fn.call(self, list[i]);
1546 toArray: function() {
1547 return this.list.slice();
1552 @return {Ember.OrderedSet}
1555 var set = new OrderedSet();
1557 set.presenceSet = copy(this.presenceSet);
1558 set.list = this.list.slice();
1565 A Map stores values indexed by keys. Unlike JavaScript's
1566 default Objects, the keys of a Map can be any JavaScript
1569 Internally, a Map has two data structures:
1571 1. `keys`: an OrderedSet of all of the existing keys
1572 2. `values`: a JavaScript Object indexed by the `Ember.guidFor(key)`
1574 When a key/value pair is added for the first time, we
1575 add the key to the `keys` OrderedSet, and create or
1576 replace an entry in `values`. When an entry is deleted,
1577 we delete its entry in `keys` and `values`.
1584 var Map = Ember.Map = function() {
1585 this.keys = Ember.OrderedSet.create();
1593 Map.create = function() {
1599 Retrieve the value associated with a given key.
1602 @param {anything} key
1603 @return {anything} the value associated with the key, or `undefined`
1605 get: function(key) {
1606 var values = this.values,
1607 guid = guidFor(key);
1609 return values[guid];
1613 Adds a value to the map. If a value for the given key has already been
1614 provided, the new value will replace the old value.
1617 @param {anything} key
1618 @param {anything} value
1620 set: function(key, value) {
1621 var keys = this.keys,
1622 values = this.values,
1623 guid = guidFor(key);
1626 values[guid] = value;
1630 Removes a value from the map for an associated key.
1633 @param {anything} key
1634 @return {Boolean} true if an item was removed, false otherwise
1636 remove: function(key) {
1637 // don't use ES6 "delete" because it will be annoying
1638 // to use in browsers that are not ES6 friendly;
1639 var keys = this.keys,
1640 values = this.values,
1641 guid = guidFor(key),
1644 if (values.hasOwnProperty(guid)) {
1646 value = values[guid];
1647 delete values[guid];
1655 Check whether a key is present.
1658 @param {anything} key
1659 @return {Boolean} true if the item was present, false otherwise
1661 has: function(key) {
1662 var values = this.values,
1663 guid = guidFor(key);
1665 return values.hasOwnProperty(guid);
1669 Iterate over all the keys and values. Calls the function once
1670 for each key, passing in the key and value, in that order.
1672 The keys are guaranteed to be iterated over in insertion order.
1675 @param {Function} callback
1676 @param {anything} self if passed, the `this` value inside the
1677 callback. By default, `this` is the map.
1679 forEach: function(callback, self) {
1680 var keys = this.keys,
1681 values = this.values;
1683 keys.forEach(function(key) {
1684 var guid = guidFor(key);
1685 callback.call(self, key, values[guid]);
1694 return copyMap(this, new Map());
1699 @class MapWithDefault
1705 @param {anything} [options.defaultValue]
1707 var MapWithDefault = Ember.MapWithDefault = function(options) {
1709 this.defaultValue = options.defaultValue;
1716 @param {anything} [options.defaultValue]
1717 @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
1718 `Ember.MapWithDefault` otherwise returns `Ember.Map`
1720 MapWithDefault.create = function(options) {
1722 return new MapWithDefault(options);
1728 MapWithDefault.prototype = Ember.create(Map.prototype);
1731 Retrieve the value associated with a given key.
1734 @param {anything} key
1735 @return {anything} the value associated with the key, or the default value
1737 MapWithDefault.prototype.get = function(key) {
1738 var hasValue = this.has(key);
1741 return Map.prototype.get.call(this, key);
1743 var defaultValue = this.defaultValue(key);
1744 this.set(key, defaultValue);
1745 return defaultValue;
1751 @return {Ember.MapWithDefault}
1753 MapWithDefault.prototype.copy = function() {
1754 return copyMap(this, new MapWithDefault({
1755 defaultValue: this.defaultValue
1768 var META_KEY = Ember.META_KEY, get, set;
1770 var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
1772 var IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
1773 var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/;
1774 var HAS_THIS = /^this[\.\*]/;
1775 var FIRST_KEY = /^([^\.\*]+)/;
1777 // ..........................................................
1780 // If we are on a platform that supports accessors we can get use those.
1781 // Otherwise simulate accessors by looking up the property directly on the
1785 Gets the value of a property on an object. If the property is computed,
1786 the function will be invoked. If the property is not defined but the
1787 object implements the `unknownProperty` method then that will be invoked.
1789 If you plan to run on IE8 and older browsers then you should use this
1790 method anytime you want to retrieve a property on an object that you don't
1791 know for sure is private. (Properties beginning with an underscore '_'
1792 are considered private.)
1794 On all newer browsers, you only need to use this method to retrieve
1795 properties if the property might not be defined on the object and you want
1796 to respect the `unknownProperty` handler. Otherwise you can ignore this
1799 Note that if the object itself is `undefined`, this method will throw
1804 @param {Object} obj The object to retrieve from.
1805 @param {String} keyName The property key to retrieve
1806 @return {Object} the property value or `null`.
1808 get = function get(obj, keyName) {
1809 // Helpers that operate with 'this' within an #each
1810 if (keyName === '') {
1814 if (!keyName && 'string'===typeof obj) {
1819 if (!obj || keyName.indexOf('.') !== -1) {
1820 Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined);
1821 return getPath(obj, keyName);
1824 Ember.assert("You need to provide an object and key to `get`.", !!obj && keyName);
1826 var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret;
1828 return desc.get(obj, keyName);
1830 if (MANDATORY_SETTER && meta && meta.watching[keyName] > 0) {
1831 ret = meta.values[keyName];
1836 if (ret === undefined &&
1837 'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) {
1838 return obj.unknownProperty(keyName);
1846 Sets the value of a property on an object, respecting computed properties
1847 and notifying observers and other listeners of the change. If the
1848 property is not defined but the object implements the `unknownProperty`
1849 method then that will be invoked as well.
1851 If you plan to run on IE8 and older browsers then you should use this
1852 method anytime you want to set a property on an object that you don't
1853 know for sure is private. (Properties beginning with an underscore '_'
1854 are considered private.)
1856 On all newer browsers, you only need to use this method to set
1857 properties if the property might not be defined on the object and you want
1858 to respect the `unknownProperty` handler. Otherwise you can ignore this
1863 @param {Object} obj The object to modify.
1864 @param {String} keyName The property key to set
1865 @param {Object} value The value to set
1866 @return {Object} the passed value.
1868 set = function set(obj, keyName, value, tolerant) {
1869 if (typeof obj === 'string') {
1870 Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
1876 if (!obj || keyName.indexOf('.') !== -1) {
1877 return setPath(obj, keyName, value, tolerant);
1880 Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
1881 Ember.assert('calling set on destroyed object', !obj.isDestroyed);
1883 var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
1884 isUnknown, currentValue;
1886 desc.set(obj, keyName, value);
1888 isUnknown = 'object' === typeof obj && !(keyName in obj);
1890 // setUnknownProperty is called if `obj` is an object,
1891 // the property does not already exist, and the
1892 // `setUnknownProperty` method exists on the object
1893 if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
1894 obj.setUnknownProperty(keyName, value);
1895 } else if (meta && meta.watching[keyName] > 0) {
1896 if (MANDATORY_SETTER) {
1897 currentValue = meta.values[keyName];
1899 currentValue = obj[keyName];
1901 // only trigger a change if the value has changed
1902 if (value !== currentValue) {
1903 Ember.propertyWillChange(obj, keyName);
1904 if (MANDATORY_SETTER) {
1905 if (currentValue === undefined && !(keyName in obj)) {
1906 Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
1908 meta.values[keyName] = value;
1911 obj[keyName] = value;
1913 Ember.propertyDidChange(obj, keyName);
1916 obj[keyName] = value;
1922 // Currently used only by Ember Data tests
1923 if (Ember.config.overrideAccessors) {
1926 Ember.config.overrideAccessors();
1931 function firstKey(path) {
1932 return path.match(FIRST_KEY)[0];
1935 // assumes path is already normalized
1936 function normalizeTuple(target, path) {
1937 var hasThis = HAS_THIS.test(path),
1938 isGlobal = !hasThis && IS_GLOBAL_PATH.test(path),
1941 if (!target || isGlobal) target = Ember.lookup;
1942 if (hasThis) path = path.slice(5);
1944 if (target === Ember.lookup) {
1945 key = firstKey(path);
1946 target = get(target, key);
1947 path = path.slice(key.length+1);
1950 // must return some kind of path to be valid else other things will break.
1951 if (!path || path.length===0) throw new Error('Invalid Path');
1953 return [ target, path ];
1956 function getPath(root, path) {
1957 var hasThis, parts, tuple, idx, len;
1959 // If there is no root and path is a key name, return that
1960 // property from the global object.
1961 // E.g. get('Ember') -> Ember
1962 if (root === null && path.indexOf('.') === -1) { return get(Ember.lookup, path); }
1964 // detect complicated paths and normalize them
1965 hasThis = HAS_THIS.test(path);
1967 if (!root || hasThis) {
1968 tuple = normalizeTuple(root, path);
1974 parts = path.split(".");
1976 for (idx=0; root && idx<len; idx++) {
1977 root = get(root, parts[idx], true);
1978 if (root && root.isDestroyed) { return undefined; }
1983 function setPath(root, path, value, tolerant) {
1986 // get the last part of the path
1987 keyName = path.slice(path.lastIndexOf('.') + 1);
1989 // get the first part of the part
1990 path = path.slice(0, path.length-(keyName.length+1));
1992 // unless the path is this, look up the first part to
1994 if (path !== 'this') {
1995 root = getPath(root, path);
1998 if (!keyName || keyName.length === 0) {
1999 throw new Error('You passed an empty path');
2003 if (tolerant) { return; }
2004 else { throw new Error('Object in path '+path+' could not be found or was destroyed.'); }
2007 return set(root, keyName, value);
2013 Normalizes a target/path pair to reflect that actual target/path that should
2014 be observed, etc. This takes into account passing in global property
2015 paths (i.e. a path beginning with a captial letter not defined on the
2016 target) and * separators.
2018 @method normalizeTuple
2020 @param {Object} target The current target. May be `null`.
2021 @param {String} path A path on the target or a global property path.
2022 @return {Array} a temporary array with the normalized target/path pair.
2024 Ember.normalizeTuple = function(target, path) {
2025 return normalizeTuple(target, path);
2028 Ember.getWithDefault = function(root, key, defaultValue) {
2029 var value = get(root, key);
2031 if (value === undefined) { return defaultValue; }
2037 Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get);
2040 Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now supports paths', Ember.set);
2043 Error-tolerant form of `Ember.set`. Will not blow up if any part of the
2044 chain is `undefined`, `null`, or destroyed.
2046 This is primarily used when syncing bindings, which may try to update after
2047 an object has been destroyed.
2051 @param {Object} obj The object to modify.
2052 @param {String} keyName The property key to set
2053 @param {Object} value The value to set
2055 Ember.trySet = function(root, path, value) {
2056 return set(root, path, value, true);
2058 Ember.trySetPath = Ember.deprecateFunc('trySetPath has been renamed to trySet', Ember.trySet);
2061 Returns true if the provided path is global (e.g., `MyApp.fooController.bar`)
2062 instead of local (`foo.bar.baz`).
2064 @method isGlobalPath
2067 @param {String} path
2070 Ember.isGlobalPath = function(path) {
2071 return IS_GLOBAL.test(path);
2084 var GUID_KEY = Ember.GUID_KEY,
2085 META_KEY = Ember.META_KEY,
2086 EMPTY_META = Ember.EMPTY_META,
2087 metaFor = Ember.meta,
2088 o_create = Ember.create,
2089 objectDefineProperty = Ember.platform.defineProperty;
2091 var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
2093 // ..........................................................
2098 Objects of this type can implement an interface to responds requests to
2099 get and set. The default implementation handles simple properties.
2101 You generally won't need to create or subclass this directly.
2108 var Descriptor = Ember.Descriptor = function() {};
2110 // ..........................................................
2111 // DEFINING PROPERTIES API
2117 NOTE: This is a low-level method used by other parts of the API. You almost
2118 never want to call this method directly. Instead you should use
2119 `Ember.mixin()` to define new properties.
2121 Defines a property on an object. This method works much like the ES5
2122 `Object.defineProperty()` method except that it can also accept computed
2123 properties and other special descriptors.
2125 Normally this method takes only three parameters. However if you pass an
2126 instance of `Ember.Descriptor` as the third param then you can pass an
2127 optional value as the fourth parameter. This is often more efficient than
2128 creating new descriptor hashes for each property.
2133 // ES5 compatible mode
2134 Ember.defineProperty(contact, 'firstName', {
2136 configurable: false,
2141 // define a simple property
2142 Ember.defineProperty(contact, 'lastName', undefined, 'Jolley');
2144 // define a computed property
2145 Ember.defineProperty(contact, 'fullName', Ember.computed(function() {
2146 return this.firstName+' '+this.lastName;
2147 }).property('firstName', 'lastName'));
2150 @method defineProperty
2152 @param {Object} obj the object to define this property on. This may be a prototype.
2153 @param {String} keyName the name of the property
2154 @param {Ember.Descriptor} [desc] an instance of `Ember.Descriptor` (typically a
2155 computed property) or an ES5 descriptor.
2156 You must provide this or `data` but not both.
2157 @param {anything} [data] something other than a descriptor, that will
2158 become the explicit value of this property.
2160 Ember.defineProperty = function(obj, keyName, desc, data, meta) {
2161 var descs, existingDesc, watching, value;
2163 if (!meta) meta = metaFor(obj);
2165 existingDesc = meta.descs[keyName];
2166 watching = meta.watching[keyName] > 0;
2168 if (existingDesc instanceof Ember.Descriptor) {
2169 existingDesc.teardown(obj, keyName);
2172 if (desc instanceof Ember.Descriptor) {
2175 descs[keyName] = desc;
2176 if (MANDATORY_SETTER && watching) {
2177 objectDefineProperty(obj, keyName, {
2181 value: undefined // make enumerable
2184 obj[keyName] = undefined; // make enumerable
2186 desc.setup(obj, keyName);
2188 descs[keyName] = undefined; // shadow descriptor in proto
2192 if (MANDATORY_SETTER && watching) {
2193 meta.values[keyName] = data;
2194 objectDefineProperty(obj, keyName, {
2198 Ember.assert('Must use Ember.set() to access this property', false);
2201 var meta = this[META_KEY];
2202 return meta && meta.values[keyName];
2206 obj[keyName] = data;
2211 // compatibility with ES5
2212 objectDefineProperty(obj, keyName, desc);
2216 // if key is being watched, override chains that
2217 // were initialized with the prototype
2218 if (watching) { Ember.overrideChains(obj, keyName, meta); }
2220 // The `value` passed to the `didDefineProperty` hook is
2221 // either the descriptor or data, whichever was passed.
2222 if (obj.didDefineProperty) { obj.didDefineProperty(obj, keyName, value); }
2238 var AFTER_OBSERVERS = ':change';
2239 var BEFORE_OBSERVERS = ':before';
2241 var guidFor = Ember.guidFor;
2246 this.observerSet = {
2247 [senderGuid]: { // variable name: `keySet`
2248 [keyName]: listIndex
2255 eventName: eventName,
2257 [target, method, onceFlag, suspendedFlag]
2263 function ObserverSet() {
2267 ObserverSet.prototype.add = function(sender, keyName, eventName) {
2268 var observerSet = this.observerSet,
2269 observers = this.observers,
2270 senderGuid = Ember.guidFor(sender),
2271 keySet = observerSet[senderGuid],
2275 observerSet[senderGuid] = keySet = {};
2277 index = keySet[keyName];
2278 if (index === undefined) {
2279 index = observers.push({
2282 eventName: eventName,
2285 keySet[keyName] = index;
2287 return observers[index].listeners;
2290 ObserverSet.prototype.flush = function() {
2291 var observers = this.observers, i, len, observer, sender;
2293 for (i=0, len=observers.length; i < len; ++i) {
2294 observer = observers[i];
2295 sender = observer.sender;
2296 if (sender.isDestroying || sender.isDestroyed) { continue; }
2297 Ember.sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
2301 ObserverSet.prototype.clear = function() {
2302 this.observerSet = {};
2303 this.observers = [];
2306 var beforeObserverSet = new ObserverSet(), observerSet = new ObserverSet();
2309 @method beginPropertyChanges
2312 Ember.beginPropertyChanges = function() {
2317 @method endPropertyChanges
2319 Ember.endPropertyChanges = function() {
2322 beforeObserverSet.clear();
2323 observerSet.flush();
2328 Make a series of property changes together in an
2332 Ember.changeProperties(function() {
2333 obj1.set('foo', mayBlowUpWhenSet);
2334 obj2.set('bar', baz);
2338 @method changeProperties
2339 @param {Function} callback
2342 Ember.changeProperties = function(cb, binding){
2343 Ember.beginPropertyChanges();
2344 Ember.tryFinally(cb, Ember.endPropertyChanges, binding);
2348 Set a list of properties on an object. These properties are set inside
2349 a single `beginPropertyChanges` and `endPropertyChanges` batch, so
2350 observers will be buffered.
2352 @method setProperties
2354 @param {Hash} properties
2357 Ember.setProperties = function(self, hash) {
2358 Ember.changeProperties(function(){
2359 for(var prop in hash) {
2360 if (hash.hasOwnProperty(prop)) Ember.set(self, prop, hash[prop]);
2367 function changeEvent(keyName) {
2368 return keyName+AFTER_OBSERVERS;
2371 function beforeEvent(keyName) {
2372 return keyName+BEFORE_OBSERVERS;
2378 @param {String} path
2379 @param {Object|Function} targetOrMethod
2380 @param {Function|String} [method]
2382 Ember.addObserver = function(obj, path, target, method) {
2383 Ember.addListener(obj, changeEvent(path), target, method);
2384 Ember.watch(obj, path);
2388 Ember.observersFor = function(obj, path) {
2389 return Ember.listenersFor(obj, changeEvent(path));
2393 @method removeObserver
2395 @param {String} path
2396 @param {Object|Function} targetOrMethod
2397 @param {Function|String} [method]
2399 Ember.removeObserver = function(obj, path, target, method) {
2400 Ember.unwatch(obj, path);
2401 Ember.removeListener(obj, changeEvent(path), target, method);
2406 @method addBeforeObserver
2408 @param {String} path
2409 @param {Object|Function} targetOrMethod
2410 @param {Function|String} [method]
2412 Ember.addBeforeObserver = function(obj, path, target, method) {
2413 Ember.addListener(obj, beforeEvent(path), target, method);
2414 Ember.watch(obj, path);
2418 // Suspend observer during callback.
2420 // This should only be used by the target of the observer
2421 // while it is setting the observed path.
2422 Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
2423 return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
2426 Ember._suspendObserver = function(obj, path, target, method, callback) {
2427 return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
2430 var map = Ember.ArrayPolyfills.map;
2432 Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
2433 var events = map.call(paths, beforeEvent);
2434 return Ember._suspendListeners(obj, events, target, method, callback);
2437 Ember._suspendObservers = function(obj, paths, target, method, callback) {
2438 var events = map.call(paths, changeEvent);
2439 return Ember._suspendListeners(obj, events, target, method, callback);
2442 Ember.beforeObserversFor = function(obj, path) {
2443 return Ember.listenersFor(obj, beforeEvent(path));
2447 @method removeBeforeObserver
2449 @param {String} path
2450 @param {Object|Function} targetOrMethod
2451 @param {Function|String} [method]
2453 Ember.removeBeforeObserver = function(obj, path, target, method) {
2454 Ember.unwatch(obj, path);
2455 Ember.removeListener(obj, beforeEvent(path), target, method);
2459 Ember.notifyBeforeObservers = function(obj, keyName) {
2460 if (obj.isDestroying) { return; }
2462 var eventName = beforeEvent(keyName), listeners, listenersDiff;
2464 listeners = beforeObserverSet.add(obj, keyName, eventName);
2465 listenersDiff = Ember.listenersDiff(obj, eventName, listeners);
2466 Ember.sendEvent(obj, eventName, [obj, keyName], listenersDiff);
2468 Ember.sendEvent(obj, eventName, [obj, keyName]);
2472 Ember.notifyObservers = function(obj, keyName) {
2473 if (obj.isDestroying) { return; }
2475 var eventName = changeEvent(keyName), listeners;
2477 listeners = observerSet.add(obj, keyName, eventName);
2478 Ember.listenersUnion(obj, eventName, listeners);
2480 Ember.sendEvent(obj, eventName, [obj, keyName]);
2493 var guidFor = Ember.guidFor, // utils.js
2494 metaFor = Ember.meta, // utils.js
2495 get = Ember.get, // accessors.js
2496 set = Ember.set, // accessors.js
2497 normalizeTuple = Ember.normalizeTuple, // accessors.js
2498 GUID_KEY = Ember.GUID_KEY, // utils.js
2499 META_KEY = Ember.META_KEY, // utils.js
2500 // circular reference observer depends on Ember.watch
2501 // we should move change events to this file or its own property_events.js
2502 notifyObservers = Ember.notifyObservers, // observer.js
2503 forEach = Ember.ArrayPolyfills.forEach, // array.js
2504 FIRST_KEY = /^([^\.\*]+)/,
2507 var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
2508 o_defineProperty = Ember.platform.defineProperty;
2510 function firstKey(path) {
2511 return path.match(FIRST_KEY)[0];
2514 // returns true if the passed path is just a keyName
2515 function isKeyName(path) {
2516 return path==='*' || !IS_PATH.test(path);
2519 // ..........................................................
2523 function iterDeps(method, obj, depKey, seen, meta) {
2525 var guid = guidFor(obj);
2526 if (!seen[guid]) seen[guid] = {};
2527 if (seen[guid][depKey]) return;
2528 seen[guid][depKey] = true;
2530 var deps = meta.deps;
2531 deps = deps && deps[depKey];
2533 for(var key in deps) {
2534 var desc = meta.descs[key];
2535 if (desc && desc._suspended === obj) continue;
2542 var WILL_SEEN, DID_SEEN;
2544 // called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...)
2545 function dependentKeysWillChange(obj, depKey, meta) {
2546 if (obj.isDestroying) { return; }
2548 var seen = WILL_SEEN, top = !seen;
2549 if (top) { seen = WILL_SEEN = {}; }
2550 iterDeps(propertyWillChange, obj, depKey, seen, meta);
2551 if (top) { WILL_SEEN = null; }
2554 // called whenever a property has just changed to update dependent keys
2555 function dependentKeysDidChange(obj, depKey, meta) {
2556 if (obj.isDestroying) { return; }
2558 var seen = DID_SEEN, top = !seen;
2559 if (top) { seen = DID_SEEN = {}; }
2560 iterDeps(propertyDidChange, obj, depKey, seen, meta);
2561 if (top) { DID_SEEN = null; }
2564 // ..........................................................
2568 function addChainWatcher(obj, keyName, node) {
2569 if (!obj || ('object' !== typeof obj)) { return; } // nothing to do
2571 var m = metaFor(obj), nodes = m.chainWatchers;
2573 if (!m.hasOwnProperty('chainWatchers')) {
2574 nodes = m.chainWatchers = {};
2577 if (!nodes[keyName]) { nodes[keyName] = []; }
2578 nodes[keyName].push(node);
2579 Ember.watch(obj, keyName);
2582 function removeChainWatcher(obj, keyName, node) {
2583 if (!obj || 'object' !== typeof obj) { return; } // nothing to do
2585 var m = metaFor(obj, false);
2586 if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
2588 var nodes = m.chainWatchers;
2590 if (nodes[keyName]) {
2591 nodes = nodes[keyName];
2592 for (var i = 0, l = nodes.length; i < l; i++) {
2593 if (nodes[i] === node) { nodes.splice(i, 1); }
2596 Ember.unwatch(obj, keyName);
2599 var pendingQueue = [];
2601 // attempts to add the pendingQueue chains again. If some of them end up
2602 // back in the queue and reschedule is true, schedules a timeout to try
2604 function flushPendingChains() {
2605 if (pendingQueue.length === 0) { return; } // nothing to do
2607 var queue = pendingQueue;
2610 forEach.call(queue, function(q) { q[0].add(q[1]); });
2612 Ember.warn('Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0);
2615 function isProto(pvalue) {
2616 return metaFor(pvalue, false).proto === pvalue;
2619 // A ChainNode watches a single key on an object. If you provide a starting
2620 // value for the key then the node won't actually watch it. For a root node
2621 // pass null for parent and key and object for value.
2622 var ChainNode = function(parent, key, value) {
2624 this._parent = parent;
2627 // _watching is true when calling get(this._parent, this._key) will
2628 // return the value of this node.
2630 // It is false for the root of a chain (because we have no parent)
2631 // and for global paths (because the parent node is the object with
2632 // the observer on it)
2633 this._watching = value===undefined;
2635 this._value = value;
2637 if (this._watching) {
2638 this._object = parent.value();
2639 if (this._object) { addChainWatcher(this._object, this._key, this); }
2642 // Special-case: the EachProxy relies on immediate evaluation to
2643 // establish its observers.
2645 // TODO: Replace this with an efficient callback that the EachProxy
2647 if (this._parent && this._parent._key === '@each') {
2652 var ChainNodePrototype = ChainNode.prototype;
2654 ChainNodePrototype.value = function() {
2655 if (this._value === undefined && this._watching) {
2656 var obj = this._parent.value();
2657 this._value = (obj && !isProto(obj)) ? get(obj, this._key) : undefined;
2662 ChainNodePrototype.destroy = function() {
2663 if (this._watching) {
2664 var obj = this._object;
2665 if (obj) { removeChainWatcher(obj, this._key, this); }
2666 this._watching = false; // so future calls do nothing
2670 // copies a top level object only
2671 ChainNodePrototype.copy = function(obj) {
2672 var ret = new ChainNode(null, null, obj),
2673 paths = this._paths, path;
2674 for (path in paths) {
2675 if (paths[path] <= 0) { continue; } // this check will also catch non-number vals.
2681 // called on the root node of a chain to setup watchers on the specified
2683 ChainNodePrototype.add = function(path) {
2684 var obj, tuple, key, src, paths;
2686 paths = this._paths;
2687 paths[path] = (paths[path] || 0) + 1;
2690 tuple = normalizeTuple(obj, path);
2692 // the path was a local path
2693 if (tuple[0] && tuple[0] === obj) {
2695 key = firstKey(path);
2696 path = path.slice(key.length+1);
2698 // global path, but object does not exist yet.
2699 // put into a queue and try to connect later.
2700 } else if (!tuple[0]) {
2701 pendingQueue.push([this, path]);
2705 // global path, and object already exists
2708 key = path.slice(0, 0-(tuple[1].length+1));
2713 this.chain(key, path, src);
2716 // called on the root node of a chain to teardown watcher on the specified
2718 ChainNodePrototype.remove = function(path) {
2719 var obj, tuple, key, src, paths;
2721 paths = this._paths;
2722 if (paths[path] > 0) { paths[path]--; }
2725 tuple = normalizeTuple(obj, path);
2726 if (tuple[0] === obj) {
2728 key = firstKey(path);
2729 path = path.slice(key.length+1);
2732 key = path.slice(0, 0-(tuple[1].length+1));
2737 this.unchain(key, path);
2740 ChainNodePrototype.count = 0;
2742 ChainNodePrototype.chain = function(key, path, src) {
2743 var chains = this._chains, node;
2744 if (!chains) { chains = this._chains = {}; }
2747 if (!node) { node = chains[key] = new ChainNode(this, key, src); }
2748 node.count++; // count chains...
2750 // chain rest of path if there is one
2751 if (path && path.length>0) {
2752 key = firstKey(path);
2753 path = path.slice(key.length+1);
2754 node.chain(key, path); // NOTE: no src means it will observe changes...
2758 ChainNodePrototype.unchain = function(key, path) {
2759 var chains = this._chains, node = chains[key];
2761 // unchain rest of path first...
2762 if (path && path.length>1) {
2763 key = firstKey(path);
2764 path = path.slice(key.length+1);
2765 node.unchain(key, path);
2768 // delete node if needed.
2770 if (node.count<=0) {
2771 delete chains[node._key];
2777 ChainNodePrototype.willChange = function() {
2778 var chains = this._chains;
2780 for(var key in chains) {
2781 if (!chains.hasOwnProperty(key)) { continue; }
2782 chains[key].willChange();
2786 if (this._parent) { this._parent.chainWillChange(this, this._key, 1); }
2789 ChainNodePrototype.chainWillChange = function(chain, path, depth) {
2790 if (this._key) { path = this._key + '.' + path; }
2793 this._parent.chainWillChange(this, path, depth+1);
2795 if (depth > 1) { Ember.propertyWillChange(this.value(), path); }
2796 path = 'this.' + path;
2797 if (this._paths[path] > 0) { Ember.propertyWillChange(this.value(), path); }
2801 ChainNodePrototype.chainDidChange = function(chain, path, depth) {
2802 if (this._key) { path = this._key + '.' + path; }
2804 this._parent.chainDidChange(this, path, depth+1);
2806 if (depth > 1) { Ember.propertyDidChange(this.value(), path); }
2807 path = 'this.' + path;
2808 if (this._paths[path] > 0) { Ember.propertyDidChange(this.value(), path); }
2812 ChainNodePrototype.didChange = function(suppressEvent) {
2813 // invalidate my own value first.
2814 if (this._watching) {
2815 var obj = this._parent.value();
2816 if (obj !== this._object) {
2817 removeChainWatcher(this._object, this._key, this);
2819 addChainWatcher(obj, this._key, this);
2821 this._value = undefined;
2823 // Special-case: the EachProxy relies on immediate evaluation to
2824 // establish its observers.
2825 if (this._parent && this._parent._key === '@each')
2829 // then notify chains...
2830 var chains = this._chains;
2832 for(var key in chains) {
2833 if (!chains.hasOwnProperty(key)) { continue; }
2834 chains[key].didChange(suppressEvent);
2838 if (suppressEvent) { return; }
2840 // and finally tell parent about my path changing...
2841 if (this._parent) { this._parent.chainDidChange(this, this._key, 1); }
2844 // get the chains for the current object. If the current object has
2845 // chains inherited from the proto they will be cloned and reconfigured for
2846 // the current object.
2847 function chainsFor(obj) {
2848 var m = metaFor(obj), ret = m.chains;
2850 ret = m.chains = new ChainNode(null, null, obj);
2851 } else if (ret.value() !== obj) {
2852 ret = m.chains = ret.copy(obj);
2857 Ember.overrideChains = function(obj, keyName, m) {
2858 chainsDidChange(obj, keyName, m, true);
2861 function chainsWillChange(obj, keyName, m, arg) {
2862 if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
2864 var nodes = m.chainWatchers;
2866 nodes = nodes[keyName];
2867 if (!nodes) { return; }
2869 for(var i = 0, l = nodes.length; i < l; i++) {
2870 nodes[i].willChange(arg);
2874 function chainsDidChange(obj, keyName, m, arg) {
2875 if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
2877 var nodes = m.chainWatchers;
2879 nodes = nodes[keyName];
2880 if (!nodes) { return; }
2882 // looping in reverse because the chainWatchers array can be modified inside didChange
2883 for (var i = nodes.length - 1; i >= 0; i--) {
2884 nodes[i].didChange(arg);
2888 // ..........................................................
2895 Starts watching a property on an object. Whenever the property changes,
2896 invokes `Ember.propertyWillChange` and `Ember.propertyDidChange`. This is the
2897 primitive used by observers and dependent keys; usually you will never call
2898 this method directly but instead use higher level methods like
2899 `Ember.addObserver()`
2904 @param {String} keyName
2906 Ember.watch = function(obj, keyName) {
2907 // can't watch length on Array - it is special...
2908 if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
2910 var m = metaFor(obj), watching = m.watching, desc;
2912 // activate watching first time
2913 if (!watching[keyName]) {
2914 watching[keyName] = 1;
2915 if (isKeyName(keyName)) {
2916 desc = m.descs[keyName];
2917 if (desc && desc.willWatch) { desc.willWatch(obj, keyName); }
2919 if ('function' === typeof obj.willWatchProperty) {
2920 obj.willWatchProperty(keyName);
2923 if (MANDATORY_SETTER && keyName in obj) {
2924 m.values[keyName] = obj[keyName];
2925 o_defineProperty(obj, keyName, {
2929 Ember.assert('Must use Ember.set() to access this property', false);
2932 var meta = this[META_KEY];
2933 return meta && meta.values[keyName];
2938 chainsFor(obj).add(keyName);
2942 watching[keyName] = (watching[keyName] || 0) + 1;
2947 Ember.isWatching = function isWatching(obj, key) {
2948 var meta = obj[META_KEY];
2949 return (meta && meta.watching[key]) > 0;
2952 Ember.watch.flushPending = flushPendingChains;
2954 Ember.unwatch = function(obj, keyName) {
2955 // can't watch length on Array - it is special...
2956 if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
2958 var m = metaFor(obj), watching = m.watching, desc;
2960 if (watching[keyName] === 1) {
2961 watching[keyName] = 0;
2963 if (isKeyName(keyName)) {
2964 desc = m.descs[keyName];
2965 if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); }
2967 if ('function' === typeof obj.didUnwatchProperty) {
2968 obj.didUnwatchProperty(keyName);
2971 if (MANDATORY_SETTER && keyName in obj) {
2972 o_defineProperty(obj, keyName, {
2976 value: m.values[keyName]
2978 delete m.values[keyName];
2981 chainsFor(obj).remove(keyName);
2984 } else if (watching[keyName]>1) {
2985 watching[keyName]--;
2994 Call on an object when you first beget it from another object. This will
2995 setup any chained watchers on the object instance as needed. This method is
2996 safe to call multiple times.
3002 Ember.rewatch = function(obj) {
3003 var m = metaFor(obj, false), chains = m.chains;
3005 // make sure the object has its own guid.
3006 if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
3007 Ember.generateGuid(obj, 'ember');
3010 // make sure any chained watchers update.
3011 if (chains && chains.value() !== obj) {
3012 m.chains = chains.copy(obj);
3018 Ember.finishChains = function(obj) {
3019 var m = metaFor(obj, false), chains = m.chains;
3021 if (chains.value() !== obj) {
3022 m.chains = chains = chains.copy(obj);
3024 chains.didChange(true);
3028 // ..........................................................
3033 This function is called just before an object property is about to change.
3034 It will notify any before observers and prepare caches among other things.
3036 Normally you will not need to call this method directly but if for some
3037 reason you can't directly watch a property you can invoke this method
3038 manually along with `Ember.propertyDidChange()` which you should call just
3039 after the property value changes.
3041 @method propertyWillChange
3043 @param {Object} obj The object with the property that will change
3044 @param {String} keyName The property key (or path) that will change.
3047 function propertyWillChange(obj, keyName, value) {
3048 var m = metaFor(obj, false),
3049 watching = m.watching[keyName] > 0 || keyName === 'length',
3051 desc = m.descs[keyName];
3053 if (!watching) { return; }
3054 if (proto === obj) { return; }
3055 if (desc && desc.willChange) { desc.willChange(obj, keyName); }
3056 dependentKeysWillChange(obj, keyName, m);
3057 chainsWillChange(obj, keyName, m);
3058 Ember.notifyBeforeObservers(obj, keyName);
3061 Ember.propertyWillChange = propertyWillChange;
3064 This function is called just after an object property has changed.
3065 It will notify any observers and clear caches among other things.
3067 Normally you will not need to call this method directly but if for some
3068 reason you can't directly watch a property you can invoke this method
3069 manually along with `Ember.propertyWilLChange()` which you should call just
3070 before the property value changes.
3072 @method propertyDidChange
3074 @param {Object} obj The object with the property that will change
3075 @param {String} keyName The property key (or path) that will change.
3078 function propertyDidChange(obj, keyName) {
3079 var m = metaFor(obj, false),
3080 watching = m.watching[keyName] > 0 || keyName === 'length',
3082 desc = m.descs[keyName];
3084 if (proto === obj) { return; }
3086 // shouldn't this mean that we're watching this key?
3087 if (desc && desc.didChange) { desc.didChange(obj, keyName); }
3088 if (!watching && keyName !== 'length') { return; }
3090 dependentKeysDidChange(obj, keyName, m);
3091 chainsDidChange(obj, keyName, m);
3092 Ember.notifyObservers(obj, keyName);
3095 Ember.propertyDidChange = propertyDidChange;
3097 var NODE_STACK = [];
3100 Tears down the meta on an object so that it can be garbage collected.
3101 Multiple calls will have no effect.
3105 @param {Object} obj the object to destroy
3108 Ember.destroy = function (obj) {
3109 var meta = obj[META_KEY], node, nodes, key, nodeObject;
3111 obj[META_KEY] = null;
3112 // remove chainWatchers to remove circular references that would prevent GC
3115 NODE_STACK.push(node);
3117 while (NODE_STACK.length > 0) {
3118 node = NODE_STACK.pop();
3120 nodes = node._chains;
3122 for (key in nodes) {
3123 if (nodes.hasOwnProperty(key)) {
3124 NODE_STACK.push(nodes[key]);
3128 // remove chainWatcher in node object
3129 if (node._watching) {
3130 nodeObject = node._object;
3132 removeChainWatcher(nodeObject, node._key, node);
3149 Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properties are always cached by default. Use `volatile` if you don't want caching.", Ember.ENV.CP_DEFAULT_CACHEABLE !== false);
3152 var get = Ember.get,
3154 metaFor = Ember.meta,
3155 guidFor = Ember.guidFor,
3157 o_create = Ember.create,
3158 META_KEY = Ember.META_KEY,
3159 watch = Ember.watch,
3160 unwatch = Ember.unwatch;
3162 // ..........................................................
3169 // 'keyName': count,
3174 This function returns a map of unique dependencies for a
3175 given object and key.
3177 function keysForDep(obj, depsMeta, depKey) {
3178 var keys = depsMeta[depKey];
3180 // if there are no dependencies yet for a the given key
3181 // create a new empty list of dependencies for the key
3182 keys = depsMeta[depKey] = {};
3183 } else if (!depsMeta.hasOwnProperty(depKey)) {
3184 // otherwise if the dependency list is inherited from
3185 // a superclass, clone the hash
3186 keys = depsMeta[depKey] = o_create(keys);
3191 /* return obj[META_KEY].deps */
3192 function metaForDeps(obj, meta) {
3193 var deps = meta.deps;
3194 // If the current object has no dependencies...
3196 // initialize the dependencies with a pointer back to
3197 // the current object
3198 deps = meta.deps = {};
3199 } else if (!meta.hasOwnProperty('deps')) {
3200 // otherwise if the dependencies are inherited from the
3201 // object's superclass, clone the deps
3202 deps = meta.deps = o_create(deps);
3207 function addDependentKeys(desc, obj, keyName, meta) {
3208 // the descriptor has a list of dependent keys, so
3209 // add all of its dependent keys.
3210 var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
3211 if (!depKeys) return;
3213 depsMeta = metaForDeps(obj, meta);
3215 for(idx = 0, len = depKeys.length; idx < len; idx++) {
3216 depKey = depKeys[idx];
3217 // Lookup keys meta for depKey
3218 keys = keysForDep(obj, depsMeta, depKey);
3219 // Increment the number of times depKey depends on keyName.
3220 keys[keyName] = (keys[keyName] || 0) + 1;
3226 function removeDependentKeys(desc, obj, keyName, meta) {
3227 // the descriptor has a list of dependent keys, so
3228 // add all of its dependent keys.
3229 var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
3230 if (!depKeys) return;
3232 depsMeta = metaForDeps(obj, meta);
3234 for(idx = 0, len = depKeys.length; idx < len; idx++) {
3235 depKey = depKeys[idx];
3236 // Lookup keys meta for depKey
3237 keys = keysForDep(obj, depsMeta, depKey);
3238 // Increment the number of times depKey depends on keyName.
3239 keys[keyName] = (keys[keyName] || 0) - 1;
3241 unwatch(obj, depKey);
3245 // ..........................................................
3246 // COMPUTED PROPERTY
3250 @class ComputedProperty
3252 @extends Ember.Descriptor
3255 function ComputedProperty(func, opts) {
3257 this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
3258 this._dependentKeys = opts && opts.dependentKeys;
3261 Ember.ComputedProperty = ComputedProperty;
3262 ComputedProperty.prototype = new Ember.Descriptor();
3264 var ComputedPropertyPrototype = ComputedProperty.prototype;
3267 Call on a computed property to set it into cacheable mode. When in this
3268 mode the computed property will automatically cache the return value of
3269 your function until one of the dependent keys changes.
3272 MyApp.president = Ember.Object.create({
3273 fullName: function() {
3274 return this.get('firstName') + ' ' + this.get('lastName');
3276 // After calculating the value of this function, Ember will
3277 // return that value without re-executing this function until
3278 // one of the dependent properties change.
3279 }.property('firstName', 'lastName')
3283 Properties are cacheable by default.
3286 @param {Boolean} aFlag optional set to `false` to disable caching
3289 ComputedPropertyPrototype.cacheable = function(aFlag) {
3290 this._cacheable = aFlag !== false;
3295 Call on a computed property to set it into non-cached mode. When in this
3296 mode the computed property will not automatically cache the return value.
3299 MyApp.outsideService = Ember.Object.create({
3301 return OutsideService.getValue();
3302 }.property().volatile()
3309 ComputedPropertyPrototype.volatile = function() {
3310 return this.cacheable(false);
3314 Sets the dependent keys on this computed property. Pass any number of
3315 arguments containing key paths that this computed property depends on.
3318 MyApp.president = Ember.Object.create({
3319 fullName: Ember.computed(function() {
3320 return this.get('firstName') + ' ' + this.get('lastName');
3322 // Tell Ember that this computed property depends on firstName
3324 }).property('firstName', 'lastName')
3329 @param {String} path* zero or more property paths
3332 ComputedPropertyPrototype.property = function() {
3334 for (var i = 0, l = arguments.length; i < l; i++) {
3335 args.push(arguments[i]);
3337 this._dependentKeys = args;
3342 In some cases, you may want to annotate computed properties with additional
3343 metadata about how they function or what values they operate on. For example,
3344 computed property functions may close over variables that are then no longer
3345 available for introspection.
3347 You can pass a hash of these values to a computed property like this:
3350 person: function() {
3351 var personId = this.get('personId');
3352 return App.Person.create({ id: personId });
3353 }.property().meta({ type: App.Person })
3356 The hash that you pass to the `meta()` function will be saved on the
3357 computed property descriptor under the `_meta` key. Ember runtime
3358 exposes a public API for retrieving these values from classes,
3359 via the `metaForProperty()` function.
3366 ComputedPropertyPrototype.meta = function(meta) {
3367 if (arguments.length === 0) {
3368 return this._meta || {};
3375 /* impl descriptor API */
3376 ComputedPropertyPrototype.willWatch = function(obj, keyName) {
3377 // watch already creates meta for this instance
3378 var meta = obj[META_KEY];
3379 Ember.assert('watch should have setup meta to be writable', meta.source === obj);
3380 if (!(keyName in meta.cache)) {
3381 addDependentKeys(this, obj, keyName, meta);
3385 ComputedPropertyPrototype.didUnwatch = function(obj, keyName) {
3386 var meta = obj[META_KEY];
3387 Ember.assert('unwatch should have setup meta to be writable', meta.source === obj);
3388 if (!(keyName in meta.cache)) {
3389 // unwatch already creates meta for this instance
3390 removeDependentKeys(this, obj, keyName, meta);
3394 /* impl descriptor API */
3395 ComputedPropertyPrototype.didChange = function(obj, keyName) {
3396 // _suspended is set via a CP.set to ensure we don't clear
3397 // the cached value set by the setter
3398 if (this._cacheable && this._suspended !== obj) {
3399 var meta = metaFor(obj);
3400 if (keyName in meta.cache) {
3401 delete meta.cache[keyName];
3402 if (!meta.watching[keyName]) {
3403 removeDependentKeys(this, obj, keyName, meta);
3409 /* impl descriptor API */
3410 ComputedPropertyPrototype.get = function(obj, keyName) {
3411 var ret, cache, meta;
3412 if (this._cacheable) {
3413 meta = metaFor(obj);
3415 if (keyName in cache) { return cache[keyName]; }
3416 ret = cache[keyName] = this.func.call(obj, keyName);
3417 if (!meta.watching[keyName]) {
3418 addDependentKeys(this, obj, keyName, meta);
3421 ret = this.func.call(obj, keyName);
3426 /* impl descriptor API */
3427 ComputedPropertyPrototype.set = function(obj, keyName, value) {
3428 var cacheable = this._cacheable,
3430 meta = metaFor(obj, cacheable),
3431 watched = meta.watching[keyName],
3432 oldSuspended = this._suspended,
3433 hadCachedValue = false,
3437 this._suspended = obj;
3440 if (cacheable && cache.hasOwnProperty(keyName)) {
3441 cachedValue = cache[keyName];
3442 hadCachedValue = true;
3445 // Check if the CP has been wrapped
3446 if (func.wrappedFunction) { func = func.wrappedFunction; }
3448 // For backwards-compatibility with computed properties
3449 // that check for arguments.length === 2 to determine if
3450 // they are being get or set, only pass the old cached
3451 // value if the computed property opts into a third
3453 if (func.length === 3) {
3454 ret = func.call(obj, keyName, value, cachedValue);
3455 } else if (func.length === 2) {
3456 ret = func.call(obj, keyName, value);
3458 Ember.defineProperty(obj, keyName, null, cachedValue);
3459 Ember.set(obj, keyName, value);
3463 if (hadCachedValue && cachedValue === ret) { return; }
3465 if (watched) { Ember.propertyWillChange(obj, keyName); }
3467 if (hadCachedValue) {
3468 delete cache[keyName];
3472 if (!watched && !hadCachedValue) {
3473 addDependentKeys(this, obj, keyName, meta);
3475 cache[keyName] = ret;
3478 if (watched) { Ember.propertyDidChange(obj, keyName); }
3480 this._suspended = oldSuspended;
3485 /* called when property is defined */
3486 ComputedPropertyPrototype.setup = function(obj, keyName) {
3487 var meta = obj[META_KEY];
3488 if (meta && meta.watching[keyName]) {
3489 addDependentKeys(this, obj, keyName, metaFor(obj));
3493 /* called before property is overridden */
3494 ComputedPropertyPrototype.teardown = function(obj, keyName) {
3495 var meta = metaFor(obj);
3497 if (meta.watching[keyName] || keyName in meta.cache) {
3498 removeDependentKeys(this, obj, keyName, meta);
3501 if (this._cacheable) { delete meta.cache[keyName]; }
3503 return null; // no value to restore
3508 This helper returns a new property descriptor that wraps the passed
3509 computed property function. You can use this helper to define properties
3510 with mixins or via `Ember.defineProperty()`.
3512 The function you pass will be used to both get and set property values.
3513 The function should accept two parameters, key and value. If value is not
3514 undefined you should set the value first. In either case return the
3515 current value of the property.
3519 @param {Function} func The computed property function.
3520 @return {Ember.ComputedProperty} property descriptor instance
3522 Ember.computed = function(func) {
3525 if (arguments.length > 1) {
3526 args = a_slice.call(arguments, 0, -1);
3527 func = a_slice.call(arguments, -1)[0];
3530 var cp = new ComputedProperty(func);
3533 cp.property.apply(cp, args);
3540 Returns the cached value for a property, if one exists.
3541 This can be useful for peeking at the value of a computed
3542 property that is generated lazily, without accidentally causing
3547 @param {Object} obj the object whose property you want to check
3548 @param {String} key the name of the property whose cached value you want
3551 Ember.cacheFor = function cacheFor(obj, key) {
3552 var cache = metaFor(obj, false).cache;
3554 if (cache && key in cache) {
3560 @method computed.not
3562 @param {String} dependentKey
3564 Ember.computed.not = function(dependentKey) {
3565 return Ember.computed(dependentKey, function(key) {
3566 return !get(this, dependentKey);
3571 @method computed.empty
3573 @param {String} dependentKey
3575 Ember.computed.empty = function(dependentKey) {
3576 return Ember.computed(dependentKey, function(key) {
3577 var val = get(this, dependentKey);
3578 return val === undefined || val === null || val === '' || (Ember.isArray(val) && get(val, 'length') === 0);
3583 @method computed.bool
3585 @param {String} dependentKey
3587 Ember.computed.bool = function(dependentKey) {
3588 return Ember.computed(dependentKey, function(key) {
3589 return !!get(this, dependentKey);
3594 @method computed.alias
3596 @param {String} dependentKey
3598 Ember.computed.alias = function(dependentKey) {
3599 return Ember.computed(dependentKey, function(key, value){
3600 if (arguments.length === 1) {
3601 return get(this, dependentKey);
3603 set(this, dependentKey, value);
3618 var o_create = Ember.create,
3619 metaFor = Ember.meta,
3620 metaPath = Ember.metaPath,
3621 META_KEY = Ember.META_KEY;
3624 The event system uses a series of nested hashes to store listeners on an
3625 object. When a listener is registered, or when an event arrives, these
3626 hashes are consulted to determine which target and action pair to invoke.
3628 The hashes are stored in the object's meta hash, and look like this:
3630 // Object's meta hash
3632 listeners: { // variable name: `listenerSet`
3633 "foo:changed": [ // variable name: `actions`
3634 [target, method, onceFlag, suspendedFlag]
3641 function indexOf(array, target, method) {
3643 for (var i = 0, l = array.length; i < l; i++) {
3644 if (target === array[i][0] && method === array[i][1]) { index = i; break; }
3649 function actionsFor(obj, eventName) {
3650 var meta = metaFor(obj, true),
3653 if (!meta.listeners) { meta.listeners = {}; }
3655 if (!meta.hasOwnProperty('listeners')) {
3656 // setup inherited copy of the listeners object
3657 meta.listeners = o_create(meta.listeners);
3660 actions = meta.listeners[eventName];
3662 // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype
3663 if (actions && !meta.listeners.hasOwnProperty(eventName)) {
3664 actions = meta.listeners[eventName] = meta.listeners[eventName].slice();
3665 } else if (!actions) {
3666 actions = meta.listeners[eventName] = [];
3672 function actionsUnion(obj, eventName, otherActions) {
3673 var meta = obj[META_KEY],
3674 actions = meta && meta.listeners && meta.listeners[eventName];
3676 if (!actions) { return; }
3677 for (var i = actions.length - 1; i >= 0; i--) {
3678 var target = actions[i][0],
3679 method = actions[i][1],
3680 once = actions[i][2],
3681 suspended = actions[i][3],
3682 actionIndex = indexOf(otherActions, target, method);
3684 if (actionIndex === -1) {
3685 otherActions.push([target, method, once, suspended]);
3690 function actionsDiff(obj, eventName, otherActions) {
3691 var meta = obj[META_KEY],
3692 actions = meta && meta.listeners && meta.listeners[eventName],
3695 if (!actions) { return; }
3696 for (var i = actions.length - 1; i >= 0; i--) {
3697 var target = actions[i][0],
3698 method = actions[i][1],
3699 once = actions[i][2],
3700 suspended = actions[i][3],
3701 actionIndex = indexOf(otherActions, target, method);
3703 if (actionIndex !== -1) { continue; }
3705 otherActions.push([target, method, once, suspended]);
3706 diffActions.push([target, method, once, suspended]);
3713 Add an event listener
3718 @param {String} eventName
3719 @param {Object|Function} targetOrMethod A target object or a function
3720 @param {Function|String} method A function or the name of a function to be called on `target`
3722 function addListener(obj, eventName, target, method, once) {
3723 Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
3725 if (!method && 'function' === typeof target) {
3730 var actions = actionsFor(obj, eventName),
3731 actionIndex = indexOf(actions, target, method);
3733 if (actionIndex !== -1) { return; }
3735 actions.push([target, method, once, undefined]);
3737 if ('function' === typeof obj.didAddListener) {
3738 obj.didAddListener(eventName, target, method);
3743 Remove an event listener
3745 Arguments should match those passed to {{#crossLink "Ember/addListener"}}{{/crossLink}}
3747 @method removeListener
3750 @param {String} eventName
3751 @param {Object|Function} targetOrMethod A target object or a function
3752 @param {Function|String} method A function or the name of a function to be called on `target`
3754 function removeListener(obj, eventName, target, method) {
3755 Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
3757 if (!method && 'function' === typeof target) {
3762 function _removeListener(target, method, once) {
3763 var actions = actionsFor(obj, eventName),
3764 actionIndex = indexOf(actions, target, method);
3766 // action doesn't exist, give up silently
3767 if (actionIndex === -1) { return; }
3769 actions.splice(actionIndex, 1);
3771 if ('function' === typeof obj.didRemoveListener) {
3772 obj.didRemoveListener(eventName, target, method);
3777 _removeListener(target, method);
3779 var meta = obj[META_KEY],
3780 actions = meta && meta.listeners && meta.listeners[eventName];
3782 if (!actions) { return; }
3783 for (var i = actions.length - 1; i >= 0; i--) {
3784 _removeListener(actions[i][0], actions[i][1]);
3792 Suspend listener during callback.
3794 This should only be used by the target of the event listener
3795 when it is taking an action that would cause the event, e.g.
3796 an object might suspend its property change listener while it is
3797 setting that property.
3799 @method suspendListener
3802 @param {String} eventName
3803 @param {Object|Function} targetOrMethod A target object or a function
3804 @param {Function|String} method A function or the name of a function to be called on `target`
3805 @param {Function} callback
3807 function suspendListener(obj, eventName, target, method, callback) {
3808 if (!method && 'function' === typeof target) {
3813 var actions = actionsFor(obj, eventName),
3814 actionIndex = indexOf(actions, target, method),
3817 if (actionIndex !== -1) {
3818 action = actions[actionIndex].slice(); // copy it, otherwise we're modifying a shared object
3819 action[3] = true; // mark the action as suspended
3820 actions[actionIndex] = action; // replace the shared object with our copy
3823 function tryable() { return callback.call(target); }
3824 function finalizer() { if (action) { action[3] = undefined; } }
3826 return Ember.tryFinally(tryable, finalizer);
3832 Suspend listener during callback.
3834 This should only be used by the target of the event listener
3835 when it is taking an action that would cause the event, e.g.
3836 an object might suspend its property change listener while it is
3837 setting that property.
3839 @method suspendListener
3842 @param {Array} eventName Array of event names
3843 @param {Object|Function} targetOrMethod A target object or a function
3844 @param {Function|String} method A function or the name of a function to be called on `target`
3845 @param {Function} callback
3847 function suspendListeners(obj, eventNames, target, method, callback) {
3848 if (!method && 'function' === typeof target) {
3853 var suspendedActions = [],
3854 eventName, actions, action, i, l;
3856 for (i=0, l=eventNames.length; i<l; i++) {
3857 eventName = eventNames[i];
3858 actions = actionsFor(obj, eventName);
3859 var actionIndex = indexOf(actions, target, method);
3861 if (actionIndex !== -1) {
3862 action = actions[actionIndex].slice();
3864 actions[actionIndex] = action;
3865 suspendedActions.push(action);
3869 function tryable() { return callback.call(target); }
3871 function finalizer() {
3872 for (i = 0, l = suspendedActions.length; i < l; i++) {
3873 suspendedActions[i][3] = undefined;
3877 return Ember.tryFinally(tryable, finalizer);
3883 Return a list of currently watched events
3885 @method watchedEvents
3889 function watchedEvents(obj) {
3890 var listeners = obj[META_KEY].listeners, ret = [];
3893 for(var eventName in listeners) {
3894 if (listeners[eventName]) { ret.push(eventName); }
3904 @param {String} eventName
3905 @param {Array} params
3908 function sendEvent(obj, eventName, params, actions) {
3909 // first give object a chance to handle it
3910 if (obj !== Ember && 'function' === typeof obj.sendEvent) {
3911 obj.sendEvent(eventName, params);
3915 var meta = obj[META_KEY];
3916 actions = meta && meta.listeners && meta.listeners[eventName];
3919 if (!actions) { return; }
3921 for (var i = actions.length - 1; i >= 0; i--) { // looping in reverse for once listeners
3922 if (!actions[i] || actions[i][3] === true) { continue; }
3924 var target = actions[i][0],
3925 method = actions[i][1],
3926 once = actions[i][2];
3928 if (once) { removeListener(obj, eventName, target, method); }
3929 if (!target) { target = obj; }
3930 if ('string' === typeof method) { method = target[method]; }
3932 method.apply(target, params);
3934 method.apply(target);
3942 @method hasListeners
3945 @param {String} eventName
3947 function hasListeners(obj, eventName) {
3948 var meta = obj[META_KEY],
3949 actions = meta && meta.listeners && meta.listeners[eventName];
3951 return !!(actions && actions.length);
3956 @method listenersFor
3959 @param {String} eventName
3961 function listenersFor(obj, eventName) {
3963 var meta = obj[META_KEY],
3964 actions = meta && meta.listeners && meta.listeners[eventName];
3966 if (!actions) { return ret; }
3968 for (var i = 0, l = actions.length; i < l; i++) {
3969 var target = actions[i][0],
3970 method = actions[i][1];
3971 ret.push([target, method]);
3977 Ember.addListener = addListener;
3978 Ember.removeListener = removeListener;
3979 Ember._suspendListener = suspendListener;
3980 Ember._suspendListeners = suspendListeners;
3981 Ember.sendEvent = sendEvent;
3982 Ember.hasListeners = hasListeners;
3983 Ember.watchedEvents = watchedEvents;
3984 Ember.listenersFor = listenersFor;
3985 Ember.listenersDiff = actionsDiff;
3986 Ember.listenersUnion = actionsUnion;
3994 // Ember.watch.flushPending
3995 // Ember.beginPropertyChanges, Ember.endPropertyChanges
3996 // Ember.guidFor, Ember.tryFinally
4002 // ..........................................................
4006 var slice = [].slice,
4007 forEach = Ember.ArrayPolyfills.forEach;
4009 // invokes passed params - normalizing so you can pass target/func,
4010 // target/string or just func
4011 function invoke(target, method, args, ignore) {
4013 if (method === undefined) {
4018 if ('string' === typeof method) { method = target[method]; }
4019 if (args && ignore > 0) {
4020 args = args.length > ignore ? slice.call(args, ignore) : null;
4023 return Ember.handleErrors(function() {
4024 // IE8's Function.prototype.apply doesn't accept undefined/null arguments.
4025 return method.apply(target || this, args || []);
4030 // ..........................................................
4034 var timerMark; // used by timers...
4037 Ember RunLoop (Private)
4044 var RunLoop = function(prev) {
4045 this._prev = prev || null;
4046 this.onceTimers = {};
4049 RunLoop.prototype = {
4064 // ..........................................................
4070 @param {String} queueName
4074 schedule: function(queueName, target, method) {
4075 var queues = this._queues, queue;
4076 if (!queues) { queues = this._queues = {}; }
4077 queue = queues[queueName];
4078 if (!queue) { queue = queues[queueName] = []; }
4080 var args = arguments.length > 3 ? slice.call(arguments, 3) : null;
4081 queue.push({ target: target, method: method, args: args });
4087 @param {String} queueName
4089 flush: function(queueName) {
4090 var queueNames, idx, len, queue, log;
4092 if (!this._queues) { return this; } // nothing to do
4094 function iter(item) {
4095 invoke(item.target, item.method, item.args);
4098 function tryable() {
4099 forEach.call(queue, iter);
4102 Ember.watch.flushPending(); // make sure all chained watchers are setup
4105 while (this._queues && (queue = this._queues[queueName])) {
4106 this._queues[queueName] = null;
4108 // the sync phase is to allow property changes to propagate. don't
4109 // invoke observers until that is finished.
4110 if (queueName === 'sync') {
4111 log = Ember.LOG_BINDINGS;
4112 if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
4114 Ember.beginPropertyChanges();
4116 Ember.tryFinally(tryable, Ember.endPropertyChanges);
4118 if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
4121 forEach.call(queue, iter);
4126 queueNames = Ember.run.queues;
4127 len = queueNames.length;
4132 queueName = queueNames[idx];
4133 queue = this._queues && this._queues[queueName];
4134 delete this._queues[queueName];
4137 // the sync phase is to allow property changes to propagate. don't
4138 // invoke observers until that is finished.
4139 if (queueName === 'sync') {
4140 log = Ember.LOG_BINDINGS;
4141 if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
4143 Ember.beginPropertyChanges();
4145 Ember.tryFinally(tryable, Ember.endPropertyChanges);
4147 if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
4149 forEach.call(queue, iter);
4153 // Loop through prior queues
4154 for (var i = 0; i <= idx; i++) {
4155 if (this._queues && this._queues[queueNames[i]]) {
4156 // Start over at the first queue with contents
4173 Ember.RunLoop = RunLoop;
4175 // ..........................................................
4176 // Ember.run - this is ideally the only public API the dev sees
4180 Runs the passed target and method inside of a RunLoop, ensuring any
4181 deferred actions including bindings and views updates are flushed at the
4184 Normally you should not need to invoke this method yourself. However if
4185 you are implementing raw event handlers when interfacing with other
4186 libraries or plugins, you should probably wrap all of your code inside this
4190 Ember.run(function(){
4191 // code to be execute within a RunLoop
4199 @param {Object} [target] target of method to call
4200 @param {Function|String} method Method to invoke.
4201 May be a function or a string. If you pass a string
4202 then it will be looked up on the passed target.
4203 @param {Object} [args*] Any additional arguments you wish to pass to the method.
4204 @return {Object} return value from invoking the passed function.
4206 Ember.run = function(target, method) {
4211 function tryable() {
4212 if (target || method) {
4213 return invoke(target, method, args, 2);
4217 return Ember.tryFinally(tryable, run.end);
4220 var run = Ember.run;
4224 Begins a new RunLoop. Any deferred actions invoked after the begin will
4225 be buffered until you invoke a matching call to `Ember.run.end()`. This is
4226 an lower-level way to use a RunLoop instead of using `Ember.run()`.
4230 // code to be execute within a RunLoop
4237 Ember.run.begin = function() {
4238 run.currentRunLoop = new RunLoop(run.currentRunLoop);
4242 Ends a RunLoop. This must be called sometime after you call
4243 `Ember.run.begin()` to flush any deferred actions. This is a lower-level way
4244 to use a RunLoop instead of using `Ember.run()`.
4248 // code to be execute within a RunLoop
4255 Ember.run.end = function() {
4256 Ember.assert('must have a current run loop', run.currentRunLoop);
4258 function tryable() { run.currentRunLoop.end(); }
4259 function finalizer() { run.currentRunLoop = run.currentRunLoop.prev(); }
4261 Ember.tryFinally(tryable, finalizer);
4265 Array of named queues. This array determines the order in which queues
4266 are flushed at the end of the RunLoop. You can define your own queues by
4267 simply adding the queue name to this array. Normally you should not need
4268 to inspect or modify this property.
4272 @default ['sync', 'actions', 'destroy', 'timers']
4274 Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
4277 Adds the passed target/method and any optional arguments to the named
4278 queue to be executed at the end of the RunLoop. If you have not already
4279 started a RunLoop when calling this method one will be started for you
4282 At the end of a RunLoop, any methods scheduled in this way will be invoked.
4283 Methods will be invoked in an order matching the named queues defined in
4284 the `run.queues` property.
4287 Ember.run.schedule('timers', this, function(){
4288 // this will be executed at the end of the RunLoop, when timers are run
4289 console.log("scheduled on timers queue");
4292 Ember.run.schedule('sync', this, function(){
4293 // this will be executed at the end of the RunLoop, when bindings are synced
4294 console.log("scheduled on sync queue");
4297 // Note the functions will be run in order based on the run queues order. Output would be:
4298 // scheduled on sync queue
4299 // scheduled on timers queue
4303 @param {String} queue The name of the queue to schedule against.
4304 Default queues are 'sync' and 'actions'
4305 @param {Object} [target] target object to use as the context when invoking a method.
4306 @param {String|Function} method The method to invoke. If you pass a string it
4307 will be resolved on the target object at the time the scheduled item is
4308 invoked allowing you to change the target function.
4309 @param {Object} [arguments*] Optional arguments to be passed to the queued method.
4312 Ember.run.schedule = function(queue, target, method) {
4313 var loop = run.autorun();
4314 loop.schedule.apply(loop, arguments);
4317 var scheduledAutorun;
4318 function autorun() {
4319 scheduledAutorun = null;
4320 if (run.currentRunLoop) { run.end(); }
4323 // Used by global test teardown
4324 Ember.run.hasScheduledTimers = function() {
4325 return !!(scheduledAutorun || scheduledLater || scheduledNext);
4328 // Used by global test teardown
4329 Ember.run.cancelTimers = function () {
4330 if (scheduledAutorun) {
4331 clearTimeout(scheduledAutorun);
4332 scheduledAutorun = null;
4334 if (scheduledLater) {
4335 clearTimeout(scheduledLater);
4336 scheduledLater = null;
4338 if (scheduledNext) {
4339 clearTimeout(scheduledNext);
4340 scheduledNext = null;
4346 Begins a new RunLoop if necessary and schedules a timer to flush the
4347 RunLoop at a later time. This method is used by parts of Ember to
4348 ensure the RunLoop always finishes. You normally do not need to call this
4349 method directly. Instead use `Ember.run()`
4353 Ember.run.autorun();
4354 @return {Ember.RunLoop} the new current RunLoop
4356 Ember.run.autorun = function() {
4357 if (!run.currentRunLoop) {
4358 Ember.assert("You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in an Ember.run", !Ember.testing);
4362 if (!scheduledAutorun) {
4363 scheduledAutorun = setTimeout(autorun, 1);
4367 return run.currentRunLoop;
4371 Immediately flushes any events scheduled in the 'sync' queue. Bindings
4372 use this queue so this method is a useful way to immediately force all
4373 bindings in the application to sync.
4375 You should call this method anytime you need any changed state to propagate
4376 throughout the app immediately without repainting the UI.
4385 Ember.run.sync = function() {
4387 run.currentRunLoop.flush('sync');
4390 // ..........................................................
4394 var timers = {}; // active timers...
4397 function invokeLaterTimers() {
4398 scheduledLater = null;
4399 var now = (+ new Date()), earliest = -1;
4400 for (var key in timers) {
4401 if (!timers.hasOwnProperty(key)) { continue; }
4402 var timer = timers[key];
4403 if (timer && timer.expires) {
4404 if (now >= timer.expires) {
4406 invoke(timer.target, timer.method, timer.args, 2);
4408 if (earliest<0 || (timer.expires < earliest)) earliest=timer.expires;
4413 // schedule next timeout to fire...
4414 if (earliest > 0) { scheduledLater = setTimeout(invokeLaterTimers, earliest-(+ new Date())); }
4418 Invokes the passed target/method and optional arguments after a specified
4419 period if time. The last parameter of this method must always be a number
4422 You should use this method whenever you need to run some action after a
4423 period of time instead of using `setTimeout()`. This method will ensure that
4424 items that expire during the same script execution cycle all execute
4425 together, which is often more efficient than using a real setTimeout.
4428 Ember.run.later(myContext, function(){
4429 // code here will execute within a RunLoop in about 500ms with this == myContext
4434 @param {Object} [target] target of method to invoke
4435 @param {Function|String} method The method to invoke.
4436 If you pass a string it will be resolved on the
4437 target at the time the method is invoked.
4438 @param {Object} [args*] Optional arguments to pass to the timeout.
4439 @param {Number} wait
4440 Number of milliseconds to wait.
4441 @return {String} a string you can use to cancel the timer in
4442 {{#crossLink "Ember/run.cancel"}}{{/crossLink}} later.
4444 Ember.run.later = function(target, method) {
4445 var args, expires, timer, guid, wait;
4447 // setTimeout compatibility...
4448 if (arguments.length===2 && 'function' === typeof target) {
4452 args = [target, method];
4454 args = slice.call(arguments);
4458 expires = (+ new Date()) + wait;
4459 timer = { target: target, method: method, expires: expires, args: args };
4460 guid = Ember.guidFor(timer);
4461 timers[guid] = timer;
4462 run.once(timers, invokeLaterTimers);
4466 function invokeOnceTimer(guid, onceTimers) {
4467 if (onceTimers[this.tguid]) { delete onceTimers[this.tguid][this.mguid]; }
4468 if (timers[guid]) { invoke(this.target, this.method, this.args); }
4469 delete timers[guid];
4472 function scheduleOnce(queue, target, method, args) {
4473 var tguid = Ember.guidFor(target),
4474 mguid = Ember.guidFor(method),
4475 onceTimers = run.autorun().onceTimers,
4476 guid = onceTimers[tguid] && onceTimers[tguid][mguid],
4479 if (guid && timers[guid]) {
4480 timers[guid].args = args; // replace args
4490 guid = Ember.guidFor(timer);
4491 timers[guid] = timer;
4492 if (!onceTimers[tguid]) { onceTimers[tguid] = {}; }
4493 onceTimers[tguid][mguid] = guid; // so it isn't scheduled more than once
4495 run.schedule(queue, timer, invokeOnceTimer, guid, onceTimers);
4502 Schedules an item to run one time during the current RunLoop. Calling
4503 this method with the same target/method combination will have no effect.
4505 Note that although you can pass optional arguments these will not be
4506 considered when looking for duplicates. New arguments will replace previous
4510 Ember.run(function(){
4511 var doFoo = function() { foo(); }
4512 Ember.run.once(myContext, doFoo);
4513 Ember.run.once(myContext, doFoo);
4514 // doFoo will only be executed once at the end of the RunLoop
4519 @param {Object} [target] target of method to invoke
4520 @param {Function|String} method The method to invoke.
4521 If you pass a string it will be resolved on the
4522 target at the time the method is invoked.
4523 @param {Object} [args*] Optional arguments to pass to the timeout.
4524 @return {Object} timer
4526 Ember.run.once = function(target, method) {
4527 return scheduleOnce('actions', target, method, slice.call(arguments, 2));
4530 Ember.run.scheduleOnce = function(queue, target, method, args) {
4531 return scheduleOnce(queue, target, method, slice.call(arguments, 3));
4535 function invokeNextTimers() {
4536 scheduledNext = null;
4537 for(var key in timers) {
4538 if (!timers.hasOwnProperty(key)) { continue; }
4539 var timer = timers[key];
4542 invoke(timer.target, timer.method, timer.args, 2);
4548 Schedules an item to run after control has been returned to the system.
4549 This is often equivalent to calling `setTimeout(function() {}, 1)`.
4552 Ember.run.next(myContext, function(){
4553 // code to be executed in the next RunLoop, which will be scheduled after the current one
4558 @param {Object} [target] target of method to invoke
4559 @param {Function|String} method The method to invoke.
4560 If you pass a string it will be resolved on the
4561 target at the time the method is invoked.
4562 @param {Object} [args*] Optional arguments to pass to the timeout.
4563 @return {Object} timer
4565 Ember.run.next = function(target, method) {
4570 args: slice.call(arguments),
4574 guid = Ember.guidFor(timer);
4575 timers[guid] = timer;
4577 if (!scheduledNext) { scheduledNext = setTimeout(invokeNextTimers, 1); }
4582 Cancels a scheduled item. Must be a value returned by `Ember.run.later()`,
4583 `Ember.run.once()`, or `Ember.run.next()`.
4586 var runNext = Ember.run.next(myContext, function(){
4587 // will not be executed
4589 Ember.run.cancel(runNext);
4591 var runLater = Ember.run.later(myContext, function(){
4592 // will not be executed
4594 Ember.run.cancel(runLater);
4596 var runOnce = Ember.run.once(myContext, function(){
4597 // will not be executed
4599 Ember.run.cancel(runOnce);
4603 @param {Object} timer Timer object to cancel
4606 Ember.run.cancel = function(timer) {
4607 delete timers[timer];
4617 // guidFor, isArray, meta
4618 // addObserver, removeObserver
4619 // Ember.run.schedule
4624 // ..........................................................
4629 Debug parameter you can turn on. This will log all bindings that fire to
4630 the console. This should be disabled in production code. Note that you
4631 can also enable this from the console or temporarily.
4633 @property LOG_BINDINGS
4638 Ember.LOG_BINDINGS = false || !!Ember.ENV.LOG_BINDINGS;
4640 var get = Ember.get,
4642 guidFor = Ember.guidFor,
4643 isGlobalPath = Ember.isGlobalPath;
4646 function getWithGlobals(obj, path) {
4647 return get(isGlobalPath(path) ? Ember.lookup : obj, path);
4650 // ..........................................................
4654 var Binding = function(toPath, fromPath) {
4655 this._direction = 'fwd';
4656 this._from = fromPath;
4658 this._directionMap = Ember.Map.create();
4666 Binding.prototype = {
4668 This copies the Binding so it can be connected to another object.
4671 @return {Ember.Binding}
4674 var copy = new Binding(this._to, this._from);
4675 if (this._oneWay) { copy._oneWay = true; }
4679 // ..........................................................
4684 This will set `from` property path to the specified value. It will not
4685 attempt to resolve this property path to an actual object until you
4686 connect the binding.
4688 The binding will search for the property path starting at the root object
4689 you pass when you `connect()` the binding. It follows the same rules as
4690 `get()` - see that method for more information.
4693 @param {String} propertyPath the property path to connect to
4694 @return {Ember.Binding} `this`
4696 from: function(path) {
4702 This will set the `to` property path to the specified value. It will not
4703 attempt to resolve this property path to an actual object until you
4704 connect the binding.
4706 The binding will search for the property path starting at the root object
4707 you pass when you `connect()` the binding. It follows the same rules as
4708 `get()` - see that method for more information.
4711 @param {String|Tuple} propertyPath A property path or tuple
4712 @return {Ember.Binding} `this`
4714 to: function(path) {
4720 Configures the binding as one way. A one-way binding will relay changes
4721 on the `from` side to the `to` side, but not the other way around. This
4722 means that if you change the `to` side directly, the `from` side may have
4726 @return {Ember.Binding} `this`
4728 oneWay: function() {
4729 this._oneWay = true;
4733 toString: function() {
4734 var oneWay = this._oneWay ? '[oneWay]' : '';
4735 return "Ember.Binding<" + guidFor(this) + ">(" + this._from + " -> " + this._to + ")" + oneWay;
4738 // ..........................................................
4743 Attempts to connect this binding instance so that it can receive and relay
4744 changes. This method will raise an exception if you have not set the
4745 from/to properties yet.
4748 @param {Object} obj The root object for this binding.
4749 @return {Ember.Binding} `this`
4751 connect: function(obj) {
4752 Ember.assert('Must pass a valid object to Ember.Binding.connect()', !!obj);
4754 var fromPath = this._from, toPath = this._to;
4755 Ember.trySet(obj, toPath, getWithGlobals(obj, fromPath));
4757 // add an observer on the object to be notified when the binding should be updated
4758 Ember.addObserver(obj, fromPath, this, this.fromDidChange);
4760 // if the binding is a two-way binding, also set up an observer on the target
4761 if (!this._oneWay) { Ember.addObserver(obj, toPath, this, this.toDidChange); }
4763 this._readyToSync = true;
4769 Disconnects the binding instance. Changes will no longer be relayed. You
4770 will not usually need to call this method.
4773 @param {Object} obj The root object you passed when connecting the binding.
4774 @return {Ember.Binding} `this`
4776 disconnect: function(obj) {
4777 Ember.assert('Must pass a valid object to Ember.Binding.disconnect()', !!obj);
4779 var twoWay = !this._oneWay;
4781 // remove an observer on the object so we're no longer notified of
4782 // changes that should update bindings.
4783 Ember.removeObserver(obj, this._from, this, this.fromDidChange);
4785 // if the binding is two-way, remove the observer from the target as well
4786 if (twoWay) { Ember.removeObserver(obj, this._to, this, this.toDidChange); }
4788 this._readyToSync = false; // disable scheduled syncs...
4792 // ..........................................................
4796 /* called when the from side changes */
4797 fromDidChange: function(target) {
4798 this._scheduleSync(target, 'fwd');
4801 /* called when the to side changes */
4802 toDidChange: function(target) {
4803 this._scheduleSync(target, 'back');
4806 _scheduleSync: function(obj, dir) {
4807 var directionMap = this._directionMap;
4808 var existingDir = directionMap.get(obj);
4810 // if we haven't scheduled the binding yet, schedule it
4812 Ember.run.schedule('sync', this, this._sync, obj);
4813 directionMap.set(obj, dir);
4816 // If both a 'back' and 'fwd' sync have been scheduled on the same object,
4817 // default to a 'fwd' sync so that it remains deterministic.
4818 if (existingDir === 'back' && dir === 'fwd') {
4819 directionMap.set(obj, 'fwd');
4823 _sync: function(obj) {
4824 var log = Ember.LOG_BINDINGS;
4826 // don't synchronize destroyed objects or disconnected bindings
4827 if (obj.isDestroyed || !this._readyToSync) { return; }
4829 // get the direction of the binding for the object we are
4830 // synchronizing from
4831 var directionMap = this._directionMap;
4832 var direction = directionMap.get(obj);
4834 var fromPath = this._from, toPath = this._to;
4836 directionMap.remove(obj);
4838 // if we're synchronizing from the remote object...
4839 if (direction === 'fwd') {
4840 var fromValue = getWithGlobals(obj, this._from);
4842 Ember.Logger.log(' ', this.toString(), '->', fromValue, obj);
4845 Ember.trySet(obj, toPath, fromValue);
4847 Ember._suspendObserver(obj, toPath, this, this.toDidChange, function () {
4848 Ember.trySet(obj, toPath, fromValue);
4851 // if we're synchronizing *to* the remote object
4852 } else if (direction === 'back') {
4853 var toValue = get(obj, this._to);
4855 Ember.Logger.log(' ', this.toString(), '<-', toValue, obj);
4857 Ember._suspendObserver(obj, fromPath, this, this.fromDidChange, function () {
4858 Ember.trySet(Ember.isGlobalPath(fromPath) ? Ember.lookup : obj, fromPath, toValue);
4865 function mixinProperties(to, from) {
4866 for (var key in from) {
4867 if (from.hasOwnProperty(key)) {
4868 to[key] = from[key];
4873 mixinProperties(Binding, {
4876 See {{#crossLink "Ember.Binding/from"}}{{/crossLink}}
4882 var C = this, binding = new C();
4883 return binding.from.apply(binding, arguments);
4887 See {{#crossLink "Ember.Binding/to"}}{{/crossLink}}
4893 var C = this, binding = new C();
4894 return binding.to.apply(binding, arguments);
4898 Creates a new Binding instance and makes it apply in a single direction.
4899 A one-way binding will relay changes on the `from` side object (supplied
4900 as the `from` argument) the `to` side, but not the other way around.
4901 This means that if you change the "to" side directly, the "from" side may have
4904 See {{#crossLink "Binding/oneWay"}}{{/crossLink}}
4907 @param {String} from from path.
4908 @param {Boolean} [flag] (Optional) passing nothing here will make the
4909 binding `oneWay`. You can instead pass `false` to disable `oneWay`, making the
4910 binding two way again.
4912 oneWay: function(from, flag) {
4913 var C = this, binding = new C(null, from);
4914 return binding.oneWay(flag);
4920 An `Ember.Binding` connects the properties of two objects so that whenever
4921 the value of one property changes, the other property will be changed also.
4923 ## Automatic Creation of Bindings with `/^*Binding/`-named Properties
4925 You do not usually create Binding objects directly but instead describe
4926 bindings in your class or object definition using automatic binding
4929 Properties ending in a `Binding` suffix will be converted to `Ember.Binding`
4930 instances. The value of this property should be a string representing a path
4931 to another object or a custom binding instanced created using Binding helpers
4932 (see "Customizing Your Bindings"):
4935 valueBinding: "MyApp.someController.title"
4938 This will create a binding from `MyApp.someController.title` to the `value`
4939 property of your object instance automatically. Now the two values will be
4944 One especially useful binding customization you can use is the `oneWay()`
4945 helper. This helper tells Ember that you are only interested in
4946 receiving changes on the object you are binding from. For example, if you
4947 are binding to a preference and you want to be notified if the preference
4948 has changed, but your object will not be changing the preference itself, you
4952 bigTitlesBinding: Ember.Binding.oneWay("MyApp.preferencesController.bigTitles")
4955 This way if the value of `MyApp.preferencesController.bigTitles` changes the
4956 `bigTitles` property of your object will change also. However, if you
4957 change the value of your `bigTitles` property, it will not update the
4958 `preferencesController`.
4960 One way bindings are almost twice as fast to setup and twice as fast to
4961 execute because the binding only has to worry about changes to one side.
4963 You should consider using one way bindings anytime you have an object that
4964 may be created frequently and you do not intend to change a property; only
4965 to monitor it for changes. (such as in the example above).
4967 ## Adding Bindings Manually
4969 All of the examples above show you how to configure a custom binding, but the
4970 result of these customizations will be a binding template, not a fully active
4971 Binding instance. The binding will actually become active only when you
4972 instantiate the object the binding belongs to. It is useful however, to
4973 understand what actually happens when the binding is activated.
4975 For a binding to function it must have at least a `from` property and a `to`
4976 property. The `from` property path points to the object/key that you want to
4977 bind from while the `to` path points to the object/key you want to bind to.
4979 When you define a custom binding, you are usually describing the property
4980 you want to bind from (such as `MyApp.someController.value` in the examples
4981 above). When your object is created, it will automatically assign the value
4982 you want to bind `to` based on the name of your binding key. In the
4983 examples above, during init, Ember objects will effectively call
4984 something like this on your binding:
4987 binding = Ember.Binding.from(this.valueBinding).to("value");
4990 This creates a new binding instance based on the template you provide, and
4991 sets the to path to the `value` property of the new object. Now that the
4992 binding is fully configured with a `from` and a `to`, it simply needs to be
4993 connected to become active. This is done through the `connect()` method:
4996 binding.connect(this);
4999 Note that when you connect a binding you pass the object you want it to be
5000 connected to. This object will be used as the root for both the from and
5001 to side of the binding when inspecting relative paths. This allows the
5002 binding to be automatically inherited by subclassed objects as well.
5004 Now that the binding is connected, it will observe both the from and to side
5007 If you ever needed to do so (you almost never will, but it is useful to
5008 understand this anyway), you could manually create an active binding by
5009 using the `Ember.bind()` helper method. (This is the same method used by
5010 to setup your bindings on objects):
5013 Ember.bind(MyApp.anotherObject, "value", "MyApp.someController.value");
5016 Both of these code fragments have the same effect as doing the most friendly
5017 form of binding creation like so:
5020 MyApp.anotherObject = Ember.Object.create({
5021 valueBinding: "MyApp.someController.value",
5023 // OTHER CODE FOR THIS OBJECT...
5027 Ember's built in binding creation method makes it easy to automatically
5028 create bindings for you. You should always use the highest-level APIs
5029 available, even if you understand how it works underneath.
5035 Ember.Binding = Binding;
5039 Global helper method to create a new binding. Just pass the root object
5040 along with a `to` and `from` path to create and connect the binding.
5044 @param {Object} obj The root object of the transform.
5045 @param {String} to The path to the 'to' side of the binding.
5046 Must be relative to obj.
5047 @param {String} from The path to the 'from' side of the binding.
5048 Must be relative to obj or a global path.
5049 @return {Ember.Binding} binding instance
5051 Ember.bind = function(obj, to, from) {
5052 return new Ember.Binding(to, from).connect(obj);
5058 @param {Object} obj The root object of the transform.
5059 @param {String} to The path to the 'to' side of the binding.
5060 Must be relative to obj.
5061 @param {String} from The path to the 'from' side of the binding.
5062 Must be relative to obj or a global path.
5063 @return {Ember.Binding} binding instance
5065 Ember.oneWay = function(obj, to, from) {
5066 return new Ember.Binding(to, from).oneWay().connect(obj);
5078 var Mixin, REQUIRED, Alias,
5079 a_map = Ember.ArrayPolyfills.map,
5080 a_indexOf = Ember.ArrayPolyfills.indexOf,
5081 a_forEach = Ember.ArrayPolyfills.forEach,
5083 EMPTY_META = {}, // dummy for non-writable meta
5084 o_create = Ember.create,
5085 defineProperty = Ember.defineProperty,
5086 guidFor = Ember.guidFor;
5088 function mixinsMeta(obj) {
5089 var m = Ember.meta(obj, true), ret = m.mixins;
5091 ret = m.mixins = {};
5092 } else if (!m.hasOwnProperty('mixins')) {
5093 ret = m.mixins = o_create(ret);
5098 function initMixin(mixin, args) {
5099 if (args && args.length > 0) {
5100 mixin.mixins = a_map.call(args, function(x) {
5101 if (x instanceof Mixin) { return x; }
5103 // Note: Manually setup a primitive mixin here. This is the only
5104 // way to actually get a primitive mixin. This way normal creation
5105 // of mixins will give you combined mixins...
5106 var mixin = new Mixin();
5107 mixin.properties = x;
5114 function isMethod(obj) {
5115 return 'function' === typeof obj &&
5116 obj.isMethod !== false &&
5117 obj !== Boolean && obj !== Object && obj !== Number && obj !== Array && obj !== Date && obj !== String;
5122 function mixinProperties(mixinsMeta, mixin) {
5125 if (mixin instanceof Mixin) {
5126 guid = guidFor(mixin);
5127 if (mixinsMeta[guid]) { return CONTINUE; }
5128 mixinsMeta[guid] = mixin;
5129 return mixin.properties;
5131 return mixin; // apply anonymous mixin properties
5135 function concatenatedProperties(props, values, base) {
5138 // reset before adding each new mixin to pickup concats from previous
5139 concats = values.concatenatedProperties || base.concatenatedProperties;
5140 if (props.concatenatedProperties) {
5141 concats = concats ? concats.concat(props.concatenatedProperties) : props.concatenatedProperties;
5147 function giveDescriptorSuper(meta, key, property, values, descs) {
5150 // Computed properties override methods, and do not call super to them
5151 if (values[key] === undefined) {
5152 // Find the original descriptor in a parent mixin
5153 superProperty = descs[key];
5156 // If we didn't find the original descriptor in a parent mixin, find
5157 // it on the original object.
5158 superProperty = superProperty || meta.descs[key];
5160 if (!superProperty || !(superProperty instanceof Ember.ComputedProperty)) {
5164 // Since multiple mixins may inherit from the same parent, we need
5165 // to clone the computed property so that other mixins do not receive
5166 // the wrapped version.
5167 property = o_create(property);
5168 property.func = Ember.wrap(property.func, superProperty.func);
5173 function giveMethodSuper(obj, key, method, values, descs) {
5176 // Methods overwrite computed properties, and do not call super to them.
5177 if (descs[key] === undefined) {
5178 // Find the original method in a parent mixin
5179 superMethod = values[key];
5182 // If we didn't find the original value in a parent mixin, find it in
5183 // the original object
5184 superMethod = superMethod || obj[key];
5186 // Only wrap the new method if the original method was a function
5187 if ('function' !== typeof superMethod) {
5191 return Ember.wrap(method, superMethod);
5194 function applyConcatenatedProperties(obj, key, value, values) {
5195 var baseValue = values[key] || obj[key];
5198 if ('function' === typeof baseValue.concat) {
5199 return baseValue.concat(value);
5201 return Ember.makeArray(baseValue).concat(value);
5204 return Ember.makeArray(value);
5208 function addNormalizedProperty(base, key, value, meta, descs, values, concats) {
5209 if (value instanceof Ember.Descriptor) {
5210 if (value === REQUIRED && descs[key]) { return CONTINUE; }
5212 // Wrap descriptor function to implement
5213 // _super() if needed
5215 value = giveDescriptorSuper(meta, key, value, values, descs);
5219 values[key] = undefined;
5221 // impl super if needed...
5222 if (isMethod(value)) {
5223 value = giveMethodSuper(base, key, value, values, descs);
5224 } else if ((concats && a_indexOf.call(concats, key) >= 0) || key === 'concatenatedProperties') {
5225 value = applyConcatenatedProperties(base, key, value, values);
5228 descs[key] = undefined;
5229 values[key] = value;
5233 function mergeMixins(mixins, m, descs, values, base) {
5234 var mixin, props, key, concats, meta;
5236 function removeKeys(keyName) {
5237 delete descs[keyName];
5238 delete values[keyName];
5241 for(var i=0, l=mixins.length; i<l; i++) {
5243 Ember.assert('Expected hash or Mixin instance, got ' + Object.prototype.toString.call(mixin), typeof mixin === 'object' && mixin !== null && Object.prototype.toString.call(mixin) !== '[object Array]');
5245 props = mixinProperties(m, mixin);
5246 if (props === CONTINUE) { continue; }
5249 meta = Ember.meta(base);
5250 concats = concatenatedProperties(props, values, base);
5252 for (key in props) {
5253 if (!props.hasOwnProperty(key)) { continue; }
5254 addNormalizedProperty(base, key, props[key], meta, descs, values, concats);
5257 // manually copy toString() because some JS engines do not enumerate it
5258 if (props.hasOwnProperty('toString')) { base.toString = props.toString; }
5259 } else if (mixin.mixins) {
5260 mergeMixins(mixin.mixins, m, descs, values, base);
5261 if (mixin._without) { a_forEach.call(mixin._without, removeKeys); }
5266 function writableReq(obj) {
5267 var m = Ember.meta(obj), req = m.required;
5268 if (!req || !m.hasOwnProperty('required')) {
5269 req = m.required = req ? o_create(req) : {};
5274 var IS_BINDING = Ember.IS_BINDING = /^.+Binding$/;
5276 function detectBinding(obj, key, value, m) {
5277 if (IS_BINDING.test(key)) {
5278 var bindings = m.bindings;
5280 bindings = m.bindings = {};
5281 } else if (!m.hasOwnProperty('bindings')) {
5282 bindings = m.bindings = o_create(m.bindings);
5284 bindings[key] = value;
5288 function connectBindings(obj, m) {
5289 // TODO Mixin.apply(instance) should disconnect binding if exists
5290 var bindings = m.bindings, key, binding, to;
5292 for (key in bindings) {
5293 binding = bindings[key];
5295 to = key.slice(0, -7); // strip Binding off end
5296 if (binding instanceof Ember.Binding) {
5297 binding = binding.copy(); // copy prototypes' instance
5299 } else { // binding is string path
5300 binding = new Ember.Binding(to, binding);
5302 binding.connect(obj);
5311 function finishPartial(obj, m) {
5312 connectBindings(obj, m || Ember.meta(obj));
5316 function followAlias(obj, desc, m, descs, values) {
5317 var altKey = desc.methodName, value;
5318 if (descs[altKey] || values[altKey]) {
5319 value = values[altKey];
5320 desc = descs[altKey];
5321 } else if (m.descs[altKey]) {
5322 desc = m.descs[altKey];
5326 value = obj[altKey];
5329 return { desc: desc, value: value };
5332 function updateObservers(obj, key, observer, observerKey, method) {
5333 if ('function' !== typeof observer) { return; }
5335 var paths = observer[observerKey];
5338 for (var i=0, l=paths.length; i<l; i++) {
5339 Ember[method](obj, paths[i], null, key);
5344 function replaceObservers(obj, key, observer) {
5345 var prevObserver = obj[key];
5347 updateObservers(obj, key, prevObserver, '__ember_observesBefore__', 'removeBeforeObserver');
5348 updateObservers(obj, key, prevObserver, '__ember_observes__', 'removeObserver');
5350 updateObservers(obj, key, observer, '__ember_observesBefore__', 'addBeforeObserver');
5351 updateObservers(obj, key, observer, '__ember_observes__', 'addObserver');
5354 function applyMixin(obj, mixins, partial) {
5355 var descs = {}, values = {}, m = Ember.meta(obj),
5358 // Go through all mixins and hashes passed in, and:
5360 // * Handle concatenated properties
5361 // * Set up _super wrapping if necessary
5362 // * Set up computed property descriptors
5363 // * Copying `toString` in broken browsers
5364 mergeMixins(mixins, mixinsMeta(obj), descs, values, obj);
5366 for(key in values) {
5367 if (key === 'contructor' || !values.hasOwnProperty(key)) { continue; }
5370 value = values[key];
5372 if (desc === REQUIRED) { continue; }
5374 while (desc && desc instanceof Alias) {
5375 var followed = followAlias(obj, desc, m, descs, values);
5376 desc = followed.desc;
5377 value = followed.value;
5380 if (desc === undefined && value === undefined) { continue; }
5382 replaceObservers(obj, key, value);
5383 detectBinding(obj, key, value, m);
5384 defineProperty(obj, key, desc, value, m);
5387 if (!partial) { // don't apply to prototype
5388 finishPartial(obj, m);
5401 Ember.mixin = function(obj) {
5402 var args = a_slice.call(arguments, 1);
5403 applyMixin(obj, args, false);
5408 The `Ember.Mixin` class allows you to create mixins, whose properties can be
5409 added to other classes. For instance,
5412 App.Editable = Ember.Mixin.create({
5414 console.log('starting to edit');
5415 this.set('isEditing', true);
5420 // Mix mixins into classes by passing them as the first arguments to
5421 // .extend or .create.
5422 App.CommentView = Ember.View.extend(App.Editable, {
5423 template: Ember.Handlebars.compile('{{#if isEditing}}...{{else}}...{{/if}}')
5426 commentView = App.CommentView.create();
5427 commentView.edit(); // outputs 'starting to edit'
5430 Note that Mixins are created with `Ember.Mixin.create`, not
5431 `Ember.Mixin.extend`.
5436 Ember.Mixin = function() { return initMixin(this, arguments); };
5438 Mixin = Ember.Mixin;
5440 Mixin._apply = applyMixin;
5442 Mixin.applyPartial = function(obj) {
5443 var args = a_slice.call(arguments, 1);
5444 return applyMixin(obj, args, true);
5447 Mixin.finishPartial = finishPartial;
5449 Ember.anyUnprocessedMixins = false;
5456 Mixin.create = function() {
5457 Ember.anyUnprocessedMixins = true;
5459 return initMixin(new M(), arguments);
5462 var MixinPrototype = Mixin.prototype;
5468 MixinPrototype.reopen = function() {
5471 if (this.properties) {
5472 mixin = Mixin.create();
5473 mixin.properties = this.properties;
5474 delete this.properties;
5475 this.mixins = [mixin];
5476 } else if (!this.mixins) {
5480 var len = arguments.length, mixins = this.mixins, idx;
5482 for(idx=0; idx < len; idx++) {
5483 mixin = arguments[idx];
5484 Ember.assert('Expected hash or Mixin instance, got ' + Object.prototype.toString.call(mixin), typeof mixin === 'object' && mixin !== null && Object.prototype.toString.call(mixin) !== '[object Array]');
5486 if (mixin instanceof Mixin) {
5489 tmp = Mixin.create();
5490 tmp.properties = mixin;
5501 @return applied object
5503 MixinPrototype.apply = function(obj) {
5504 return applyMixin(obj, [this], false);
5507 MixinPrototype.applyPartial = function(obj) {
5508 return applyMixin(obj, [this], true);
5511 function _detect(curMixin, targetMixin, seen) {
5512 var guid = guidFor(curMixin);
5514 if (seen[guid]) { return false; }
5517 if (curMixin === targetMixin) { return true; }
5518 var mixins = curMixin.mixins, loc = mixins ? mixins.length : 0;
5519 while (--loc >= 0) {
5520 if (_detect(mixins[loc], targetMixin, seen)) { return true; }
5530 MixinPrototype.detect = function(obj) {
5531 if (!obj) { return false; }
5532 if (obj instanceof Mixin) { return _detect(obj, this, {}); }
5533 var mixins = Ember.meta(obj, false).mixins;
5535 return !!mixins[guidFor(this)];
5540 MixinPrototype.without = function() {
5541 var ret = new Mixin(this);
5542 ret._without = a_slice.call(arguments);
5546 function _keys(ret, mixin, seen) {
5547 if (seen[guidFor(mixin)]) { return; }
5548 seen[guidFor(mixin)] = true;
5550 if (mixin.properties) {
5551 var props = mixin.properties;
5552 for (var key in props) {
5553 if (props.hasOwnProperty(key)) { ret[key] = true; }
5555 } else if (mixin.mixins) {
5556 a_forEach.call(mixin.mixins, function(x) { _keys(ret, x, seen); });
5560 MixinPrototype.keys = function() {
5561 var keys = {}, seen = {}, ret = [];
5562 _keys(keys, this, seen);
5563 for(var key in keys) {
5564 if (keys.hasOwnProperty(key)) { ret.push(key); }
5569 // returns the mixins currently applied to the specified object
5570 // TODO: Make Ember.mixin
5571 Mixin.mixins = function(obj) {
5572 var mixins = Ember.meta(obj, false).mixins, ret = [];
5574 if (!mixins) { return ret; }
5576 for (var key in mixins) {
5577 var mixin = mixins[key];
5579 // skip primitive mixins since these are always anonymous
5580 if (!mixin.properties) { ret.push(mixin); }
5586 REQUIRED = new Ember.Descriptor();
5587 REQUIRED.toString = function() { return '(Required Property)'; };
5590 Denotes a required property for a mixin
5595 Ember.required = function() {
5599 Alias = function(methodName) {
5600 this.methodName = methodName;
5602 Alias.prototype = new Ember.Descriptor();
5605 Makes a property or method available via an additional name.
5608 App.PaintSample = Ember.Object.extend({
5610 colour: Ember.alias('color'),
5614 moniker: Ember.alias("name")
5617 var paintSample = App.PaintSample.create()
5618 paintSample.get('colour'); // 'red'
5619 paintSample.moniker(); // 'Zed'
5624 @param {String} methodName name of the method or property to alias
5625 @return {Ember.Descriptor}
5626 @deprecated Use `Ember.aliasMethod` or `Ember.computed.alias` instead
5628 Ember.alias = function(methodName) {
5629 return new Alias(methodName);
5632 Ember.deprecateFunc("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead.", Ember.alias);
5635 Makes a method available via an additional name.
5638 App.Person = Ember.Object.extend({
5640 return 'Tomhuda Katzdale';
5642 moniker: Ember.aliasMethod('name')
5645 var goodGuy = App.Person.create()
5650 @param {String} methodName name of the method to alias
5651 @return {Ember.Descriptor}
5653 Ember.aliasMethod = function(methodName) {
5654 return new Alias(methodName);
5657 // ..........................................................
5664 @param {Function} func
5665 @param {String} propertyNames*
5668 Ember.observer = function(func) {
5669 var paths = a_slice.call(arguments, 1);
5670 func.__ember_observes__ = paths;
5674 // If observers ever become asynchronous, Ember.immediateObserver
5675 // must remain synchronous.
5677 @method immediateObserver
5679 @param {Function} func
5680 @param {String} propertyNames*
5683 Ember.immediateObserver = function() {
5684 for (var i=0, l=arguments.length; i<l; i++) {
5685 var arg = arguments[i];
5686 Ember.assert("Immediate observers must observe internal properties only, not properties on other objects.", typeof arg !== "string" || arg.indexOf('.') === -1);
5689 return Ember.observer.apply(this, arguments);
5693 @method beforeObserver
5695 @param {Function} func
5696 @param {String} propertyNames*
5699 Ember.beforeObserver = function(func) {
5700 var paths = a_slice.call(arguments, 1);
5701 func.__ember_observesBefore__ = paths;
5714 @submodule ember-metal
5724 var browserGlobal = (typeof window !== 'undefined') ? window : {};
5726 var MutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
5729 if (typeof process !== 'undefined' &&
5730 {}.toString.call(process) === '[object process]') {
5731 async = function(callback, binding) {
5732 process.nextTick(function() {
5733 callback.call(binding);
5736 } else if (MutationObserver) {
5739 var observer = new MutationObserver(function() {
5740 var toProcess = queue.slice();
5743 toProcess.forEach(function(tuple) {
5744 var callback = tuple[0], binding = tuple[1];
5745 callback.call(binding);
5749 var element = document.createElement('div');
5750 observer.observe(element, { attributes: true });
5752 // Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661
5753 window.addEventListener('unload', function(){
5754 observer.disconnect();
5758 async = function(callback, binding) {
5759 queue.push([callback, binding]);
5760 element.setAttribute('drainQueue', 'drainQueue');
5763 async = function(callback, binding) {
5764 setTimeout(function() {
5765 callback.call(binding);
5770 var Event = function(type, options) {
5773 for (var option in options) {
5774 if (!options.hasOwnProperty(option)) { continue; }
5776 this[option] = options[option];
5780 var indexOf = function(callbacks, callback) {
5781 for (var i=0, l=callbacks.length; i<l; i++) {
5782 if (callbacks[i][0] === callback) { return i; }
5788 var callbacksFor = function(object) {
5789 var callbacks = object._promiseCallbacks;
5792 callbacks = object._promiseCallbacks = {};
5799 mixin: function(object) {
5800 object.on = this.on;
5801 object.off = this.off;
5802 object.trigger = this.trigger;
5806 on: function(eventNames, callback, binding) {
5807 var allCallbacks = callbacksFor(this), callbacks, eventName;
5808 eventNames = eventNames.split(/\s+/);
5809 binding = binding || this;
5811 while (eventName = eventNames.shift()) {
5812 callbacks = allCallbacks[eventName];
5815 callbacks = allCallbacks[eventName] = [];
5818 if (indexOf(callbacks, callback) === -1) {
5819 callbacks.push([callback, binding]);
5824 off: function(eventNames, callback) {
5825 var allCallbacks = callbacksFor(this), callbacks, eventName, index;
5826 eventNames = eventNames.split(/\s+/);
5828 while (eventName = eventNames.shift()) {
5830 allCallbacks[eventName] = [];
5834 callbacks = allCallbacks[eventName];
5836 index = indexOf(callbacks, callback);
5838 if (index !== -1) { callbacks.splice(index, 1); }
5842 trigger: function(eventName, options) {
5843 var allCallbacks = callbacksFor(this),
5844 callbacks, callbackTuple, callback, binding, event;
5846 if (callbacks = allCallbacks[eventName]) {
5847 for (var i=0, l=callbacks.length; i<l; i++) {
5848 callbackTuple = callbacks[i];
5849 callback = callbackTuple[0];
5850 binding = callbackTuple[1];
5852 if (typeof options !== 'object') {
5853 options = { detail: options };
5856 event = new Event(eventName, options);
5857 callback.call(binding, event);
5863 var Promise = function() {
5864 this.on('promise:resolved', function(event) {
5865 this.trigger('success', { detail: event.detail });
5868 this.on('promise:failed', function(event) {
5869 this.trigger('error', { detail: event.detail });
5873 var noop = function() {};
5875 var invokeCallback = function(type, promise, callback, event) {
5876 var hasCallback = typeof callback === 'function',
5877 value, error, succeeded, failed;
5881 value = callback(event.detail);
5888 value = event.detail;
5892 if (value && typeof value.then === 'function') {
5893 value.then(function(value) {
5894 promise.resolve(value);
5895 }, function(error) {
5896 promise.reject(error);
5898 } else if (hasCallback && succeeded) {
5899 promise.resolve(value);
5900 } else if (failed) {
5901 promise.reject(error);
5903 promise[type](value);
5907 Promise.prototype = {
5908 then: function(done, fail) {
5909 var thenPromise = new Promise();
5911 if (this.isResolved) {
5912 RSVP.async(function() {
5913 invokeCallback('resolve', thenPromise, done, { detail: this.resolvedValue });
5917 if (this.isRejected) {
5918 RSVP.async(function() {
5919 invokeCallback('reject', thenPromise, fail, { detail: this.rejectedValue });
5923 this.on('promise:resolved', function(event) {
5924 invokeCallback('resolve', thenPromise, done, event);
5927 this.on('promise:failed', function(event) {
5928 invokeCallback('reject', thenPromise, fail, event);
5934 resolve: function(value) {
5935 resolve(this, value);
5937 this.resolve = noop;
5941 reject: function(value) {
5942 reject(this, value);
5944 this.resolve = noop;
5949 function resolve(promise, value) {
5950 RSVP.async(function() {
5951 promise.trigger('promise:resolved', { detail: value });
5952 promise.isResolved = true;
5953 promise.resolvedValue = value;
5957 function reject(promise, value) {
5958 RSVP.async(function() {
5959 promise.trigger('promise:failed', { detail: value });
5960 promise.isRejected = true;
5961 promise.rejectedValue = value;
5965 EventTarget.mixin(Promise.prototype);
5967 RSVP = { async: async, Promise: Promise, Event: Event, EventTarget: EventTarget };
5978 var objectCreate = Object.create || function(parent) {
5980 F.prototype = parent;
5984 function InheritingDict(parent) {
5985 this.parent = parent;
5989 InheritingDict.prototype = {
5990 get: function(key) {
5991 var dict = this.dict;
5993 if (dict.hasOwnProperty(key)) {
5998 return this.parent.get(key);
6002 set: function(key, value) {
6003 this.dict[key] = value;
6006 has: function(key) {
6007 var dict = this.dict;
6009 if (dict.hasOwnProperty(key)) {
6014 return this.parent.has(key);
6020 eachLocal: function(callback, binding) {
6021 var dict = this.dict;
6023 for (var prop in dict) {
6024 if (dict.hasOwnProperty(prop)) {
6025 callback.call(binding, prop, dict[prop]);
6031 function Container(parent) {
6032 this.parent = parent;
6035 this.resolver = parent && parent.resolver || function() {};
6036 this.registry = new InheritingDict(parent && parent.registry);
6037 this.cache = new InheritingDict(parent && parent.cache);
6038 this.typeInjections = {};
6039 this.injections = {};
6041 this.typeOptions = {};
6044 Container.prototype = {
6046 var container = new Container(this);
6047 this.children.push(container);
6051 set: function(object, key, value) {
6052 object[key] = value;
6055 register: function(type, name, factory, options) {
6056 this.registry.set(type + ":" + name, factory);
6057 this.options[type + ":" + name] = options || {};
6060 resolve: function(fullName) {
6061 return this.resolver(fullName) || this.registry.get(fullName);
6064 lookup: function(fullName) {
6065 if (this.cache.has(fullName)) {
6066 return this.cache.get(fullName);
6069 var value = instantiate(this, fullName);
6071 if (!value) { return; }
6073 if (isSingleton(this, fullName)) {
6074 this.cache.set(fullName, value);
6080 has: function(fullName) {
6081 if (this.cache.has(fullName)) {
6085 return !!factoryFor(this, fullName);
6088 optionsForType: function(type, options) {
6089 if (this.parent) { illegalChildOperation('optionsForType'); }
6091 this.typeOptions[type] = options;
6094 typeInjection: function(type, property, fullName) {
6095 if (this.parent) { illegalChildOperation('typeInjection'); }
6097 var injections = this.typeInjections[type] = this.typeInjections[type] || [];
6098 injections.push({ property: property, fullName: fullName });
6101 injection: function(factoryName, property, injectionName) {
6102 if (this.parent) { illegalChildOperation('injection'); }
6104 var injections = this.injections[factoryName] = this.injections[factoryName] || [];
6105 injections.push({ property: property, fullName: injectionName });
6108 destroy: function() {
6109 this.isDestroyed = true;
6111 for (var i=0, l=this.children.length; i<l; i++) {
6112 this.children[i].destroy();
6117 eachDestroyable(this, function(item) {
6118 item.isDestroying = true;
6121 eachDestroyable(this, function(item) {
6126 this.isDestroyed = true;
6130 for (var i=0, l=this.children.length; i<l; i++) {
6131 resetCache(this.children[i]);
6137 function illegalChildOperation(operation) {
6138 throw new Error(operation + " is not currently supported on child containers");
6141 function isSingleton(container, fullName) {
6142 var singleton = option(container, fullName, 'singleton');
6144 return singleton !== false;
6147 function buildInjections(container, injections) {
6150 if (!injections) { return hash; }
6152 var injection, lookup;
6154 for (var i=0, l=injections.length; i<l; i++) {
6155 injection = injections[i];
6156 lookup = container.lookup(injection.fullName);
6157 hash[injection.property] = lookup;
6163 function option(container, fullName, optionName) {
6164 var options = container.options[fullName];
6166 if (options && options[optionName] !== undefined) {
6167 return options[optionName];
6170 var type = fullName.split(":")[0];
6171 options = container.typeOptions[type];
6174 return options[optionName];
6178 function factoryFor(container, fullName) {
6179 return container.resolve(fullName);
6182 function instantiate(container, fullName) {
6183 var factory = factoryFor(container, fullName);
6185 var splitName = fullName.split(":"),
6186 type = splitName[0], name = splitName[1],
6189 if (option(container, fullName, 'instantiate') === false) {
6194 var injections = [];
6195 injections = injections.concat(container.typeInjections[type] || []);
6196 injections = injections.concat(container.injections[fullName] || []);
6198 var hash = buildInjections(container, injections);
6199 hash.container = container;
6201 value = factory.create(hash);
6207 function eachDestroyable(container, callback) {
6208 container.cache.eachLocal(function(key, value) {
6209 if (option(container, key, 'instantiate') === false) { return; }
6214 function resetCache(container) {
6215 container.cache.eachLocal(function(key, value) {
6216 if (option(container, key, 'instantiate') === false) { return; }
6219 container.cache.dict = {};
6231 @submodule ember-runtime
6234 var indexOf = Ember.EnumerableUtils.indexOf;
6236 // ........................................
6237 // TYPING & ARRAY MESSAGING
6241 var t = "Boolean Number String Function Array Date RegExp Object".split(" ");
6242 Ember.ArrayPolyfills.forEach.call(t, function(name) {
6243 TYPE_MAP[ "[object " + name + "]" ] = name.toLowerCase();
6246 var toString = Object.prototype.toString;
6249 Returns a consistent type for the passed item.
6251 Use this instead of the built-in `typeof` to get the type of an item.
6252 It will return the same result across all browsers and includes a bit
6253 more detail. Here is what will be returned:
6255 | Return Value | Meaning |
6256 |---------------|------------------------------------------------------|
6257 | 'string' | String primitive |
6258 | 'number' | Number primitive |
6259 | 'boolean' | Boolean primitive |
6260 | 'null' | Null value |
6261 | 'undefined' | Undefined value |
6262 | 'function' | A function |
6263 | 'array' | An instance of Array |
6264 | 'class' | A Ember class (created using Ember.Object.extend()) |
6265 | 'instance' | A Ember object instance |
6266 | 'error' | An instance of the Error object |
6267 | 'object' | A JavaScript object not inheriting from Ember.Object |
6272 Ember.typeOf(); // 'undefined'
6273 Ember.typeOf(null); // 'null'
6274 Ember.typeOf(undefined); // 'undefined'
6275 Ember.typeOf('michael'); // 'string'
6276 Ember.typeOf(101); // 'number'
6277 Ember.typeOf(true); // 'boolean'
6278 Ember.typeOf(Ember.makeArray); // 'function'
6279 Ember.typeOf([1,2,90]); // 'array'
6280 Ember.typeOf(Ember.Object.extend()); // 'class'
6281 Ember.typeOf(Ember.Object.create()); // 'instance'
6282 Ember.typeOf(new Error('teamocil')); // 'error'
6284 // "normal" JavaScript object
6285 Ember.typeOf({a: 'b'}); // 'object'
6290 @param item {Object} the item to check
6291 @return {String} the type
6293 Ember.typeOf = function(item) {
6296 ret = (item === null || item === undefined) ? String(item) : TYPE_MAP[toString.call(item)] || 'object';
6298 if (ret === 'function') {
6299 if (Ember.Object && Ember.Object.detect(item)) ret = 'class';
6300 } else if (ret === 'object') {
6301 if (item instanceof Error) ret = 'error';
6302 else if (Ember.Object && item instanceof Ember.Object) ret = 'instance';
6303 else ret = 'object';
6310 Returns true if the passed value is null or undefined. This avoids errors
6311 from JSLint complaining about use of ==, which can be technically
6315 Ember.isNone(); // true
6316 Ember.isNone(null); // true
6317 Ember.isNone(undefined); // true
6318 Ember.isNone(''); // false
6319 Ember.isNone([]); // false
6320 Ember.isNone(function(){}); // false
6325 @param {Object} obj Value to test
6328 Ember.isNone = function(obj) {
6329 return obj === null || obj === undefined;
6331 Ember.none = Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.", Ember.isNone);
6334 Verifies that a value is `null` or an empty string, empty array,
6337 Constrains the rules on `Ember.isNone` by returning false for empty
6338 string and empty arrays.
6341 Ember.isEmpty(); // true
6342 Ember.isEmpty(null); // true
6343 Ember.isEmpty(undefined); // true
6344 Ember.isEmpty(''); // true
6345 Ember.isEmpty([]); // true
6346 Ember.isEmpty('Adam Hawkins'); // false
6347 Ember.isEmpty([0,1,2]); // false
6352 @param {Object} obj Value to test
6355 Ember.isEmpty = function(obj) {
6356 return obj === null || obj === undefined || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0);
6358 Ember.empty = Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.", Ember.isEmpty) ;
6361 This will compare two javascript values of possibly different types.
6362 It will tell you which one is greater than the other by returning:
6364 - -1 if the first is smaller than the second,
6365 - 0 if both are equal,
6366 - 1 if the first is greater than the second.
6368 The order is calculated based on `Ember.ORDER_DEFINITION`, if types are different.
6369 In case they have the same type an appropriate comparison for this type is made.
6372 Ember.compare('hello', 'hello'); // 0
6373 Ember.compare('abc', 'dfg'); // -1
6374 Ember.compare(2, 1); // 1
6379 @param {Object} v First value to compare
6380 @param {Object} w Second value to compare
6381 @return {Number} -1 if v < w, 0 if v = w and 1 if v > w.
6383 Ember.compare = function compare(v, w) {
6384 if (v === w) { return 0; }
6386 var type1 = Ember.typeOf(v);
6387 var type2 = Ember.typeOf(w);
6389 var Comparable = Ember.Comparable;
6391 if (type1==='instance' && Comparable.detect(v.constructor)) {
6392 return v.constructor.compare(v, w);
6395 if (type2 === 'instance' && Comparable.detect(w.constructor)) {
6396 return 1-w.constructor.compare(w, v);
6400 // If we haven't yet generated a reverse-mapping of Ember.ORDER_DEFINITION,
6402 var mapping = Ember.ORDER_DEFINITION_MAPPING;
6404 var order = Ember.ORDER_DEFINITION;
6405 mapping = Ember.ORDER_DEFINITION_MAPPING = {};
6407 for (idx = 0, len = order.length; idx < len; ++idx) {
6408 mapping[order[idx]] = idx;
6411 // We no longer need Ember.ORDER_DEFINITION.
6412 delete Ember.ORDER_DEFINITION;
6415 var type1Index = mapping[type1];
6416 var type2Index = mapping[type2];
6418 if (type1Index < type2Index) { return -1; }
6419 if (type1Index > type2Index) { return 1; }
6421 // types are equal - so we have to check values now
6425 if (v < w) { return -1; }
6426 if (v > w) { return 1; }
6430 var comp = v.localeCompare(w);
6431 if (comp < 0) { return -1; }
6432 if (comp > 0) { return 1; }
6436 var vLen = v.length;
6437 var wLen = w.length;
6438 var l = Math.min(vLen, wLen);
6441 while (r === 0 && i < l) {
6442 r = compare(v[i],w[i]);
6445 if (r !== 0) { return r; }
6447 // all elements are equal now
6448 // shorter array should be ordered first
6449 if (vLen < wLen) { return -1; }
6450 if (vLen > wLen) { return 1; }
6451 // arrays are equal now
6455 if (Ember.Comparable && Ember.Comparable.detect(v)) {
6456 return v.compare(v, w);
6461 var vNum = v.getTime();
6462 var wNum = w.getTime();
6463 if (vNum < wNum) { return -1; }
6464 if (vNum > wNum) { return 1; }
6472 function _copy(obj, deep, seen, copies) {
6475 // primitive data types are immutable, just return them.
6476 if ('object' !== typeof obj || obj===null) return obj;
6478 // avoid cyclical loops
6479 if (deep && (loc=indexOf(seen, obj))>=0) return copies[loc];
6481 Ember.assert('Cannot clone an Ember.Object that does not implement Ember.Copyable', !(obj instanceof Ember.Object) || (Ember.Copyable && Ember.Copyable.detect(obj)));
6483 // IMPORTANT: this specific test will detect a native array only. Any other
6484 // object will need to implement Copyable.
6485 if (Ember.typeOf(obj) === 'array') {
6489 while(--loc>=0) ret[loc] = _copy(ret[loc], deep, seen, copies);
6491 } else if (Ember.Copyable && Ember.Copyable.detect(obj)) {
6492 ret = obj.copy(deep, seen, copies);
6496 if (!obj.hasOwnProperty(key)) continue;
6498 // Prevents browsers that don't respect non-enumerability from
6499 // copying internal Ember properties
6500 if (key.substring(0,2) === '__') continue;
6502 ret[key] = deep ? _copy(obj[key], deep, seen, copies) : obj[key];
6515 Creates a clone of the passed object. This function can take just about
6516 any type of object and create a clone of it, including primitive values
6517 (which are not actually cloned because they are immutable).
6519 If the passed object implements the `clone()` method, then this function
6520 will simply call that method and return the result.
6524 @param {Object} object The object to clone
6525 @param {Boolean} deep If true, a deep copy of the object is made
6526 @return {Object} The cloned object
6528 Ember.copy = function(obj, deep) {
6530 if ('object' !== typeof obj || obj===null) return obj; // can't copy primitives
6531 if (Ember.Copyable && Ember.Copyable.detect(obj)) return obj.copy(deep);
6532 return _copy(obj, deep, deep ? [] : null, deep ? [] : null);
6536 Convenience method to inspect an object. This method will attempt to
6537 convert the object into a useful string description.
6539 It is a pretty simple implementation. If you want something more robust,
6540 use something like JSDump: https://github.com/NV/jsDump
6544 @param {Object} obj The object you want to inspect.
6545 @return {String} A description of the object
6547 Ember.inspect = function(obj) {
6548 if (typeof obj !== 'object' || obj === null) {
6553 for(var key in obj) {
6554 if (obj.hasOwnProperty(key)) {
6556 if (v === 'toString') { continue; } // ignore useless items
6557 if (Ember.typeOf(v) === 'function') { v = "function() { ... }"; }
6558 ret.push(key + ": " + v);
6561 return "{" + ret.join(", ") + "}";
6565 Compares two objects, returning true if they are logically equal. This is
6566 a deeper comparison than a simple triple equal. For sets it will compare the
6567 internal objects. For any other object that implements `isEqual()` it will
6568 respect that method.
6571 Ember.isEqual('hello', 'hello'); // true
6572 Ember.isEqual(1, 2); // false
6573 Ember.isEqual([4,2], [4,2]); // false
6578 @param {Object} a first object to compare
6579 @param {Object} b second object to compare
6582 Ember.isEqual = function(a, b) {
6583 if (a && 'function'===typeof a.isEqual) return a.isEqual(b);
6587 // Used by Ember.compare
6588 Ember.ORDER_DEFINITION = Ember.ENV.ORDER_DEFINITION || [
6603 Returns all of the keys defined on an object or hash. This is useful
6604 when inspecting objects for debugging. On browsers that support it, this
6605 uses the native `Object.keys` implementation.
6610 @return {Array} Array containing keys of obj
6612 Ember.keys = Object.keys;
6615 Ember.keys = function(obj) {
6617 for(var key in obj) {
6618 if (obj.hasOwnProperty(key)) { ret.push(key); }
6624 // ..........................................................
6628 var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
6631 A subclass of the JavaScript Error object for use in Ember.
6638 Ember.Error = function() {
6639 var tmp = Error.prototype.constructor.apply(this, arguments);
6641 // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
6642 for (var idx = 0; idx < errorProps.length; idx++) {
6643 this[errorProps[idx]] = tmp[errorProps[idx]];
6647 Ember.Error.prototype = Ember.create(Error.prototype);
6656 @submodule ember-runtime
6659 var STRING_DASHERIZE_REGEXP = (/[ _]/g);
6660 var STRING_DASHERIZE_CACHE = {};
6661 var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g);
6662 var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g);
6663 var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g);
6664 var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g);
6667 Defines the hash of localized strings for the current language. Used by
6668 the `Ember.String.loc()` helper. To localize, add string values to this
6678 Defines string helper methods including string formatting and localization.
6679 Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be
6680 added to the `String.prototype` as well.
6689 Apply formatting options to the string. This will look for occurrences
6690 of "%@" in your string and substitute them with the arguments you pass into
6691 this method. If you want to control the specific order of replacement,
6692 you can add a number after the key as well to indicate which argument
6695 Ordered insertions are most useful when building loc strings where values
6696 you need to insert may appear in different orders.
6699 "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe"
6700 "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John"
6704 @param {Object...} [args]
6705 @return {String} formatted string
6707 fmt: function(str, formats) {
6708 // first, replace any ORDERED replacements.
6709 var idx = 0; // the current index for non-numerical replacements
6710 return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
6711 argIndex = (argIndex) ? parseInt(argIndex,0) - 1 : idx++ ;
6712 s = formats[argIndex];
6713 return ((s === null) ? '(null)' : (s === undefined) ? '' : s).toString();
6718 Formats the passed string, but first looks up the string in the localized
6719 strings hash. This is a convenient way to localize text. See
6720 `Ember.String.fmt()` for more information on formatting.
6722 Note that it is traditional but not required to prefix localized string
6723 keys with an underscore or other character so you can easily identify
6728 '_Hello World': 'Bonjour le monde',
6729 '_Hello %@ %@': 'Bonjour %@ %@'
6732 Ember.String.loc("_Hello World"); // 'Bonjour le monde';
6733 Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith";
6737 @param {String} str The string to format
6738 @param {Array} formats Optional array of parameters to interpolate into string.
6739 @return {String} formatted string
6741 loc: function(str, formats) {
6742 str = Ember.STRINGS[str] || str;
6743 return Ember.String.fmt(str, formats) ;
6747 Splits a string into separate units separated by spaces, eliminating any
6748 empty strings in the process. This is a convenience method for split that
6749 is mostly useful when applied to the `String.prototype`.
6752 Ember.String.w("alpha beta gamma").forEach(function(key) {
6762 @param {String} str The string to split
6763 @return {String} split string
6765 w: function(str) { return str.split(/\s+/); },
6768 Converts a camelized string into all lower case separated by underscores.
6771 'innerHTML'.decamelize(); // 'inner_html'
6772 'action_name'.decamelize(); // 'action_name'
6773 'css-class-name'.decamelize(); // 'css-class-name'
6774 'my favorite items'.decamelize(); // 'my favorite items'
6778 @param {String} str The string to decamelize.
6779 @return {String} the decamelized string.
6781 decamelize: function(str) {
6782 return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();
6786 Replaces underscores or spaces with dashes.
6789 'innerHTML'.dasherize(); // 'inner-html'
6790 'action_name'.dasherize(); // 'action-name'
6791 'css-class-name'.dasherize(); // 'css-class-name'
6792 'my favorite items'.dasherize(); // 'my-favorite-items'
6796 @param {String} str The string to dasherize.
6797 @return {String} the dasherized string.
6799 dasherize: function(str) {
6800 var cache = STRING_DASHERIZE_CACHE,
6806 ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-');
6814 Returns the lowerCaseCamel form of a string.
6817 'innerHTML'.camelize(); // 'innerHTML'
6818 'action_name'.camelize(); // 'actionName'
6819 'css-class-name'.camelize(); // 'cssClassName'
6820 'my favorite items'.camelize(); // 'myFavoriteItems'
6824 @param {String} str The string to camelize.
6825 @return {String} the camelized string.
6827 camelize: function(str) {
6828 return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) {
6829 return chr ? chr.toUpperCase() : '';
6834 Returns the UpperCamelCase form of a string.
6837 'innerHTML'.classify(); // 'InnerHTML'
6838 'action_name'.classify(); // 'ActionName'
6839 'css-class-name'.classify(); // 'CssClassName'
6840 'my favorite items'.classify(); // 'MyFavoriteItems'
6844 @param {String} str the string to classify
6845 @return {String} the classified string
6847 classify: function(str) {
6848 var parts = str.split("."),
6851 for (var i=0, l=parts.length; i<l; i++) {
6852 var camelized = Ember.String.camelize(parts[i]);
6853 out.push(camelized.charAt(0).toUpperCase() + camelized.substr(1));
6856 return out.join(".");
6860 More general than decamelize. Returns the lower\_case\_and\_underscored
6864 'innerHTML'.underscore(); // 'inner_html'
6865 'action_name'.underscore(); // 'action_name'
6866 'css-class-name'.underscore(); // 'css_class_name'
6867 'my favorite items'.underscore(); // 'my_favorite_items'
6871 @param {String} str The string to underscore.
6872 @return {String} the underscored string.
6874 underscore: function(str) {
6875 return str.replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2').
6876 replace(STRING_UNDERSCORE_REGEXP_2, '_').toLowerCase();
6880 Returns the Capitalized form of a string
6882 'innerHTML'.capitalize() => 'InnerHTML'
6883 'action_name'.capitalize() => 'Action_name'
6884 'css-class-name'.capitalize() => 'Css-class-name'
6885 'my favorite items'.capitalize() => 'My favorite items'
6891 capitalize: function(str) {
6892 return str.charAt(0).toUpperCase() + str.substr(1);
6904 @submodule ember-runtime
6909 var fmt = Ember.String.fmt,
6911 loc = Ember.String.loc,
6912 camelize = Ember.String.camelize,
6913 decamelize = Ember.String.decamelize,
6914 dasherize = Ember.String.dasherize,
6915 underscore = Ember.String.underscore,
6916 capitalize = Ember.String.capitalize,
6917 classify = Ember.String.classify;
6919 if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
6922 See {{#crossLink "Ember.String/fmt"}}{{/crossLink}}
6927 String.prototype.fmt = function() {
6928 return fmt(this, arguments);
6932 See {{#crossLink "Ember.String/w"}}{{/crossLink}}
6937 String.prototype.w = function() {
6942 See {{#crossLink "Ember.String/loc"}}{{/crossLink}}
6947 String.prototype.loc = function() {
6948 return loc(this, arguments);
6952 See {{#crossLink "Ember.String/camelize"}}{{/crossLink}}
6957 String.prototype.camelize = function() {
6958 return camelize(this);
6962 See {{#crossLink "Ember.String/decamelize"}}{{/crossLink}}
6967 String.prototype.decamelize = function() {
6968 return decamelize(this);
6972 See {{#crossLink "Ember.String/dasherize"}}{{/crossLink}}
6977 String.prototype.dasherize = function() {
6978 return dasherize(this);
6982 See {{#crossLink "Ember.String/underscore"}}{{/crossLink}}
6987 String.prototype.underscore = function() {
6988 return underscore(this);
6992 See {{#crossLink "Ember.String/classify"}}{{/crossLink}}
6997 String.prototype.classify = function() {
6998 return classify(this);
7002 See {{#crossLink "Ember.String/capitalize"}}{{/crossLink}}
7007 String.prototype.capitalize = function() {
7008 return capitalize(this);
7021 @submodule ember-runtime
7024 var a_slice = Array.prototype.slice;
7026 if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) {
7029 The `property` extension of Javascript's Function prototype is available
7030 when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
7031 `true`, which is the default.
7033 Computed properties allow you to treat a function like a property:
7036 MyApp.president = Ember.Object.create({
7037 firstName: "Barack",
7040 fullName: function() {
7041 return this.get('firstName') + ' ' + this.get('lastName');
7043 // Call this flag to mark the function as a property
7047 MyApp.president.get('fullName'); // "Barack Obama"
7050 Treating a function like a property is useful because they can work with
7051 bindings, just like any other property.
7053 Many computed properties have dependencies on other properties. For
7054 example, in the above example, the `fullName` property depends on
7055 `firstName` and `lastName` to determine its value. You can tell Ember
7056 about these dependencies like this:
7059 MyApp.president = Ember.Object.create({
7060 firstName: "Barack",
7063 fullName: function() {
7064 return this.get('firstName') + ' ' + this.get('lastName');
7066 // Tell Ember.js that this computed property depends on firstName
7068 }.property('firstName', 'lastName')
7072 Make sure you list these dependencies so Ember knows when to update
7073 bindings that connect to a computed property. Changing a dependency
7074 will not immediately trigger an update of the computed property, but
7075 will instead clear the cache so that it is updated when the next `get`
7076 is called on the property.
7078 See {{#crossLink "Ember.ComputedProperty"}}{{/crossLink}},
7079 {{#crossLink "Ember/computed"}}{{/crossLink}}
7084 Function.prototype.property = function() {
7085 var ret = Ember.computed(this);
7086 return ret.property.apply(ret, arguments);
7090 The `observes` extension of Javascript's Function prototype is available
7091 when `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Function` is
7092 true, which is the default.
7094 You can observe property changes simply by adding the `observes`
7095 call to the end of your method declarations in classes that you write.
7099 Ember.Object.create({
7100 valueObserver: function() {
7101 // Executes whenever the "value" property changes
7106 See {{#crossLink "Ember.Observable/observes"}}{{/crossLink}}
7111 Function.prototype.observes = function() {
7112 this.__ember_observes__ = a_slice.call(arguments);
7117 The `observesBefore` extension of Javascript's Function prototype is
7118 available when `Ember.EXTEND_PROTOTYPES` or
7119 `Ember.EXTEND_PROTOTYPES.Function` is true, which is the default.
7121 You can get notified when a property changes is about to happen by
7122 by adding the `observesBefore` call to the end of your method
7123 declarations in classes that you write. For example:
7126 Ember.Object.create({
7127 valueObserver: function() {
7128 // Executes whenever the "value" property is about to change
7129 }.observesBefore('value')
7133 See {{#crossLink "Ember.Observable/observesBefore"}}{{/crossLink}}
7135 @method observesBefore
7138 Function.prototype.observesBefore = function() {
7139 this.__ember_observesBefore__ = a_slice.call(arguments);
7159 @submodule ember-runtime
7162 // ..........................................................
7166 var get = Ember.get, set = Ember.set;
7167 var a_slice = Array.prototype.slice;
7168 var a_indexOf = Ember.EnumerableUtils.indexOf;
7173 return contexts.length===0 ? {} : contexts.pop();
7176 function pushCtx(ctx) {
7181 function iter(key, value) {
7182 var valueProvided = arguments.length === 2;
7185 var cur = get(item, key);
7186 return valueProvided ? value===cur : !!cur;
7192 This mixin defines the common interface implemented by enumerable objects
7193 in Ember. Most of these methods follow the standard Array iteration
7194 API defined up to JavaScript 1.8 (excluding language-specific features that
7195 cannot be emulated in older versions of JavaScript).
7197 This mixin is applied automatically to the Array class on page load, so you
7198 can use any of these methods on simple arrays. If Array already implements
7199 one of these methods, the mixin will not override them.
7201 ## Writing Your Own Enumerable
7203 To make your own custom class enumerable, you need two items:
7205 1. You must have a length property. This property should change whenever
7206 the number of items in your enumerable object changes. If you using this
7207 with an `Ember.Object` subclass, you should be sure to change the length
7208 property using `set().`
7210 2. If you must implement `nextObject().` See documentation.
7212 Once you have these two methods implement, apply the `Ember.Enumerable` mixin
7213 to your class and you will be able to enumerate the contents of your object
7214 like any other collection.
7216 ## Using Ember Enumeration with Other Libraries
7218 Many other libraries provide some kind of iterator or enumeration like
7219 facility. This is often where the most common API conflicts occur.
7220 Ember's API is designed to be as friendly as possible with other
7221 libraries by implementing only methods that mostly correspond to the
7226 @extends Ember.Mixin
7229 Ember.Enumerable = Ember.Mixin.create(
7230 /** @scope Ember.Enumerable.prototype */ {
7236 Implement this method to make your class enumerable.
7238 This method will be call repeatedly during enumeration. The index value
7239 will always begin with 0 and increment monotonically. You don't have to
7240 rely on the index value to determine what object to return, but you should
7241 always check the value and start from the beginning when you see the
7242 requested index is 0.
7244 The `previousObject` is the object that was returned from the last call
7245 to `nextObject` for the current iteration. This is a useful way to
7246 manage iteration if you are tracing a linked list, for example.
7248 Finally the context parameter will always contain a hash you can use as
7249 a "scratchpad" to maintain any other state you need in order to iterate
7250 properly. The context object is reused and is not reset between
7251 iterations so make sure you setup the context with a fresh state whenever
7252 the index parameter is 0.
7254 Generally iterators will continue to call `nextObject` until the index
7255 reaches the your current length-1. If you run out of data before this
7256 time for some reason, you should simply return undefined.
7258 The default implementation of this method simply looks up the index.
7259 This works great on any Array-like objects.
7262 @param {Number} index the current index of the iteration
7263 @param {Object} previousObject the value returned by the last call to
7265 @param {Object} context a context object you can use to maintain state.
7266 @return {Object} the next object in the iteration or undefined
7268 nextObject: Ember.required(Function),
7271 Helper method returns the first object from a collection. This is usually
7272 used by bindings and other parts of the framework to extract a single
7273 object if the enumerable contains only one item.
7275 If you override this method, you should implement it so that it will
7276 always return the same value each time it is called. If your enumerable
7277 contains only one object, this method should always return that object.
7278 If your enumerable is empty, this method should return `undefined`.
7281 var arr = ["a", "b", "c"];
7282 arr.firstObject(); // "a"
7285 arr.firstObject(); // undefined
7288 @property firstObject
7289 @return {Object} the object or undefined
7291 firstObject: Ember.computed(function() {
7292 if (get(this, 'length')===0) return undefined ;
7294 // handle generic enumerables
7295 var context = popCtx(), ret;
7296 ret = this.nextObject(0, null, context);
7302 Helper method returns the last object from a collection. If your enumerable
7303 contains only one object, this method should always return that object.
7304 If your enumerable is empty, this method should return `undefined`.
7307 var arr = ["a", "b", "c"];
7308 arr.lastObject(); // "c"
7311 arr.lastObject(); // undefined
7314 @property lastObject
7315 @return {Object} the last object or undefined
7317 lastObject: Ember.computed(function() {
7318 var len = get(this, 'length');
7319 if (len===0) return undefined ;
7320 var context = popCtx(), idx=0, cur, last = null;
7323 cur = this.nextObject(idx++, last, context);
7324 } while (cur !== undefined);
7330 Returns `true` if the passed object can be found in the receiver. The
7331 default version will iterate through the enumerable until the object
7332 is found. You may want to override this with a more efficient version.
7335 var arr = ["a", "b", "c"];
7336 arr.contains("a"); // true
7337 arr.contains("z"); // false
7341 @param {Object} obj The object to search for.
7342 @return {Boolean} `true` if object is found in enumerable.
7344 contains: function(obj) {
7345 return this.find(function(item) { return item===obj; }) !== undefined;
7349 Iterates through the enumerable, calling the passed function on each
7350 item. This method corresponds to the `forEach()` method defined in
7353 The callback method you provide should have the following signature (all
7354 parameters are optional):
7357 function(item, index, enumerable);
7360 - `item` is the current item in the iteration.
7361 - `index` is the current index in the iteration.
7362 - `enumerable` is the enumerable object itself.
7364 Note that in addition to a callback, you can also pass an optional target
7365 object that will be set as `this` on the context. This is a good way
7366 to give your iterator function access to the current object.
7369 @param {Function} callback The callback to execute
7370 @param {Object} [target] The target object to use
7371 @return {Object} receiver
7373 forEach: function(callback, target) {
7374 if (typeof callback !== "function") throw new TypeError() ;
7375 var len = get(this, 'length'), last = null, context = popCtx();
7377 if (target === undefined) target = null;
7379 for(var idx=0;idx<len;idx++) {
7380 var next = this.nextObject(idx, last, context) ;
7381 callback.call(target, next, idx, this);
7385 context = pushCtx(context);
7390 Alias for `mapProperty`
7393 @param {String} key name of the property
7394 @return {Array} The mapped array.
7396 getEach: function(key) {
7397 return this.mapProperty(key);
7401 Sets the value on the named property for each member. This is more
7402 efficient than using other methods defined on this helper. If the object
7403 implements Ember.Observable, the value will be changed to `set(),` otherwise
7404 it will be set directly. `null` objects are skipped.
7407 @param {String} key The key to set
7408 @param {Object} value The object to set
7409 @return {Object} receiver
7411 setEach: function(key, value) {
7412 return this.forEach(function(item) {
7413 set(item, key, value);
7418 Maps all of the items in the enumeration to another value, returning
7419 a new array. This method corresponds to `map()` defined in JavaScript 1.6.
7421 The callback method you provide should have the following signature (all
7422 parameters are optional):
7425 function(item, index, enumerable);
7428 - `item` is the current item in the iteration.
7429 - `index` is the current index in the iteration.
7430 - `enumerable` is the enumerable object itself.
7432 It should return the mapped value.
7434 Note that in addition to a callback, you can also pass an optional target
7435 object that will be set as `this` on the context. This is a good way
7436 to give your iterator function access to the current object.
7439 @param {Function} callback The callback to execute
7440 @param {Object} [target] The target object to use
7441 @return {Array} The mapped array.
7443 map: function(callback, target) {
7445 this.forEach(function(x, idx, i) {
7446 ret[idx] = callback.call(target, x, idx,i);
7452 Similar to map, this specialized function returns the value of the named
7453 property on all items in the enumeration.
7456 @param {String} key name of the property
7457 @return {Array} The mapped array.
7459 mapProperty: function(key) {
7460 return this.map(function(next) {
7461 return get(next, key);
7466 Returns an array with all of the items in the enumeration that the passed
7467 function returns true for. This method corresponds to `filter()` defined in
7470 The callback method you provide should have the following signature (all
7471 parameters are optional):
7474 function(item, index, enumerable);
7477 - `item` is the current item in the iteration.
7478 - `index` is the current index in the iteration.
7479 - `enumerable` is the enumerable object itself.
7481 It should return the `true` to include the item in the results, `false`
7484 Note that in addition to a callback, you can also pass an optional target
7485 object that will be set as `this` on the context. This is a good way
7486 to give your iterator function access to the current object.
7489 @param {Function} callback The callback to execute
7490 @param {Object} [target] The target object to use
7491 @return {Array} A filtered array.
7493 filter: function(callback, target) {
7495 this.forEach(function(x, idx, i) {
7496 if (callback.call(target, x, idx, i)) ret.push(x);
7502 Returns an array with all of the items in the enumeration where the passed
7503 function returns false for. This method is the inverse of filter().
7505 The callback method you provide should have the following signature (all
7506 parameters are optional):
7508 function(item, index, enumerable);
7510 - *item* is the current item in the iteration.
7511 - *index* is the current index in the iteration
7512 - *enumerable* is the enumerable object itself.
7514 It should return the a falsey value to include the item in the results.
7516 Note that in addition to a callback, you can also pass an optional target
7517 object that will be set as "this" on the context. This is a good way
7518 to give your iterator function access to the current object.
7521 @param {Function} callback The callback to execute
7522 @param {Object} [target] The target object to use
7523 @return {Array} A rejected array.
7525 reject: function(callback, target) {
7526 return this.filter(function() {
7527 return !(callback.apply(target, arguments));
7532 Returns an array with just the items with the matched property. You
7533 can pass an optional second argument with the target value. Otherwise
7534 this will match any property that evaluates to `true`.
7536 @method filterProperty
7537 @param {String} key the property to test
7538 @param {String} [value] optional value to test against.
7539 @return {Array} filtered array
7541 filterProperty: function(key, value) {
7542 return this.filter(iter.apply(this, arguments));
7546 Returns an array with the items that do not have truthy values for
7547 key. You can pass an optional second argument with the target value. Otherwise
7548 this will match any property that evaluates to false.
7550 @method rejectProperty
7551 @param {String} key the property to test
7552 @param {String} [value] optional value to test against.
7553 @return {Array} rejected array
7555 rejectProperty: function(key, value) {
7556 var exactValue = function(item) { return get(item, key) === value; },
7557 hasValue = function(item) { return !!get(item, key); },
7558 use = (arguments.length === 2 ? exactValue : hasValue);
7560 return this.reject(use);
7564 Returns the first item in the array for which the callback returns true.
7565 This method works similar to the `filter()` method defined in JavaScript 1.6
7566 except that it will stop working on the array once a match is found.
7568 The callback method you provide should have the following signature (all
7569 parameters are optional):
7572 function(item, index, enumerable);
7575 - `item` is the current item in the iteration.
7576 - `index` is the current index in the iteration.
7577 - `enumerable` is the enumerable object itself.
7579 It should return the `true` to include the item in the results, `false`
7582 Note that in addition to a callback, you can also pass an optional target
7583 object that will be set as `this` on the context. This is a good way
7584 to give your iterator function access to the current object.
7587 @param {Function} callback The callback to execute
7588 @param {Object} [target] The target object to use
7589 @return {Object} Found item or `null`.
7591 find: function(callback, target) {
7592 var len = get(this, 'length') ;
7593 if (target === undefined) target = null;
7595 var last = null, next, found = false, ret ;
7596 var context = popCtx();
7597 for(var idx=0;idx<len && !found;idx++) {
7598 next = this.nextObject(idx, last, context) ;
7599 if (found = callback.call(target, next, idx, this)) ret = next ;
7602 next = last = null ;
7603 context = pushCtx(context);
7608 Returns the first item with a property matching the passed value. You
7609 can pass an optional second argument with the target value. Otherwise
7610 this will match any property that evaluates to `true`.
7612 This method works much like the more generic `find()` method.
7614 @method findProperty
7615 @param {String} key the property to test
7616 @param {String} [value] optional value to test against.
7617 @return {Object} found item or `null`
7619 findProperty: function(key, value) {
7620 return this.find(iter.apply(this, arguments));
7624 Returns `true` if the passed function returns true for every item in the
7625 enumeration. This corresponds with the `every()` method in JavaScript 1.6.
7627 The callback method you provide should have the following signature (all
7628 parameters are optional):
7631 function(item, index, enumerable);
7634 - `item` is the current item in the iteration.
7635 - `index` is the current index in the iteration.
7636 - `enumerable` is the enumerable object itself.
7638 It should return the `true` or `false`.
7640 Note that in addition to a callback, you can also pass an optional target
7641 object that will be set as `this` on the context. This is a good way
7642 to give your iterator function access to the current object.
7647 if (people.every(isEngineer)) { Paychecks.addBigBonus(); }
7651 @param {Function} callback The callback to execute
7652 @param {Object} [target] The target object to use
7655 every: function(callback, target) {
7656 return !this.find(function(x, idx, i) {
7657 return !callback.call(target, x, idx, i);
7662 Returns `true` if the passed property resolves to `true` for all items in
7663 the enumerable. This method is often simpler/faster than using a callback.
7665 @method everyProperty
7666 @param {String} key the property to test
7667 @param {String} [value] optional value to test against.
7670 everyProperty: function(key, value) {
7671 return this.every(iter.apply(this, arguments));
7676 Returns `true` if the passed function returns true for any item in the
7677 enumeration. This corresponds with the `every()` method in JavaScript 1.6.
7679 The callback method you provide should have the following signature (all
7680 parameters are optional):
7683 function(item, index, enumerable);
7686 - `item` is the current item in the iteration.
7687 - `index` is the current index in the iteration.
7688 - `enumerable` is the enumerable object itself.
7690 It should return the `true` to include the item in the results, `false`
7693 Note that in addition to a callback, you can also pass an optional target
7694 object that will be set as `this` on the context. This is a good way
7695 to give your iterator function access to the current object.
7700 if (people.some(isManager)) { Paychecks.addBiggerBonus(); }
7704 @param {Function} callback The callback to execute
7705 @param {Object} [target] The target object to use
7706 @return {Array} A filtered array.
7708 some: function(callback, target) {
7709 return !!this.find(function(x, idx, i) {
7710 return !!callback.call(target, x, idx, i);
7715 Returns `true` if the passed property resolves to `true` for any item in
7716 the enumerable. This method is often simpler/faster than using a callback.
7718 @method someProperty
7719 @param {String} key the property to test
7720 @param {String} [value] optional value to test against.
7721 @return {Boolean} `true`
7723 someProperty: function(key, value) {
7724 return this.some(iter.apply(this, arguments));
7728 This will combine the values of the enumerator into a single value. It
7729 is a useful way to collect a summary value from an enumeration. This
7730 corresponds to the `reduce()` method defined in JavaScript 1.8.
7732 The callback method you provide should have the following signature (all
7733 parameters are optional):
7736 function(previousValue, item, index, enumerable);
7739 - `previousValue` is the value returned by the last call to the iterator.
7740 - `item` is the current item in the iteration.
7741 - `index` is the current index in the iteration.
7742 - `enumerable` is the enumerable object itself.
7744 Return the new cumulative value.
7746 In addition to the callback you can also pass an `initialValue`. An error
7747 will be raised if you do not pass an initial value and the enumerator is
7750 Note that unlike the other methods, this method does not allow you to
7751 pass a target object to set as this for the callback. It's part of the
7755 @param {Function} callback The callback to execute
7756 @param {Object} initialValue Initial value for the reduce
7757 @param {String} reducerProperty internal use only.
7758 @return {Object} The reduced value.
7760 reduce: function(callback, initialValue, reducerProperty) {
7761 if (typeof callback !== "function") { throw new TypeError(); }
7763 var ret = initialValue;
7765 this.forEach(function(item, i) {
7766 ret = callback.call(null, ret, item, i, this, reducerProperty);
7773 Invokes the named method on every object in the receiver that
7774 implements it. This method corresponds to the implementation in
7778 @param {String} methodName the name of the method
7779 @param {Object...} args optional arguments to pass as well.
7780 @return {Array} return values from calling invoke.
7782 invoke: function(methodName) {
7784 if (arguments.length>1) args = a_slice.call(arguments, 1);
7786 this.forEach(function(x, idx) {
7787 var method = x && x[methodName];
7788 if ('function' === typeof method) {
7789 ret[idx] = args ? method.apply(x, args) : method.call(x);
7797 Simply converts the enumerable into a genuine array. The order is not
7798 guaranteed. Corresponds to the method implemented by Prototype.
7801 @return {Array} the enumerable as an array.
7803 toArray: function() {
7805 this.forEach(function(o, idx) { ret[idx] = o; });
7810 Returns a copy of the array with all null elements removed.
7813 var arr = ["a", null, "c", null];
7814 arr.compact(); // ["a", "c"]
7818 @return {Array} the array without null elements.
7820 compact: function() { return this.without(null); },
7823 Returns a new enumerable that excludes the passed value. The default
7824 implementation returns an array regardless of the receiver type unless
7825 the receiver does not contain the value.
7828 var arr = ["a", "b", "a", "c"];
7829 arr.without("a"); // ["b", "c"]
7833 @param {Object} value
7834 @return {Ember.Enumerable}
7836 without: function(value) {
7837 if (!this.contains(value)) return this; // nothing to do
7839 this.forEach(function(k) {
7840 if (k !== value) ret[ret.length] = k;
7846 Returns a new enumerable that contains only unique values. The default
7847 implementation returns an array regardless of the receiver type.
7850 var arr = ["a", "a", "b", "b"];
7851 arr.uniq(); // ["a", "b"]
7855 @return {Ember.Enumerable}
7859 this.forEach(function(k){
7860 if (a_indexOf(ret, k)<0) ret.push(k);
7866 This property will trigger anytime the enumerable's content changes.
7867 You can observe this property to be notified of changes to the enumerables
7870 For plain enumerables, this property is read only. `Ember.Array` overrides
7876 '[]': Ember.computed(function(key, value) {
7880 // ..........................................................
7881 // ENUMERABLE OBSERVERS
7885 Registers an enumerable observer. Must implement `Ember.EnumerableObserver`
7888 @method addEnumerableObserver
7889 @param target {Object}
7892 addEnumerableObserver: function(target, opts) {
7893 var willChange = (opts && opts.willChange) || 'enumerableWillChange',
7894 didChange = (opts && opts.didChange) || 'enumerableDidChange';
7896 var hasObservers = get(this, 'hasEnumerableObservers');
7897 if (!hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
7898 Ember.addListener(this, '@enumerable:before', target, willChange);
7899 Ember.addListener(this, '@enumerable:change', target, didChange);
7900 if (!hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
7905 Removes a registered enumerable observer.
7907 @method removeEnumerableObserver
7908 @param target {Object}
7909 @param [opts] {Hash}
7911 removeEnumerableObserver: function(target, opts) {
7912 var willChange = (opts && opts.willChange) || 'enumerableWillChange',
7913 didChange = (opts && opts.didChange) || 'enumerableDidChange';
7915 var hasObservers = get(this, 'hasEnumerableObservers');
7916 if (hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
7917 Ember.removeListener(this, '@enumerable:before', target, willChange);
7918 Ember.removeListener(this, '@enumerable:change', target, didChange);
7919 if (hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
7924 Becomes true whenever the array currently has observers watching changes
7927 @property hasEnumerableObservers
7930 hasEnumerableObservers: Ember.computed(function() {
7931 return Ember.hasListeners(this, '@enumerable:change') || Ember.hasListeners(this, '@enumerable:before');
7936 Invoke this method just before the contents of your enumerable will
7937 change. You can either omit the parameters completely or pass the objects
7938 to be removed or added if available or just a count.
7940 @method enumerableContentWillChange
7941 @param {Ember.Enumerable|Number} removing An enumerable of the objects to
7942 be removed or the number of items to be removed.
7943 @param {Ember.Enumerable|Number} adding An enumerable of the objects to be
7944 added or the number of items to be added.
7947 enumerableContentWillChange: function(removing, adding) {
7949 var removeCnt, addCnt, hasDelta;
7951 if ('number' === typeof removing) removeCnt = removing;
7952 else if (removing) removeCnt = get(removing, 'length');
7953 else removeCnt = removing = -1;
7955 if ('number' === typeof adding) addCnt = adding;
7956 else if (adding) addCnt = get(adding,'length');
7957 else addCnt = adding = -1;
7959 hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
7961 if (removing === -1) removing = null;
7962 if (adding === -1) adding = null;
7964 Ember.propertyWillChange(this, '[]');
7965 if (hasDelta) Ember.propertyWillChange(this, 'length');
7966 Ember.sendEvent(this, '@enumerable:before', [this, removing, adding]);
7972 Invoke this method when the contents of your enumerable has changed.
7973 This will notify any observers watching for content changes. If your are
7974 implementing an ordered enumerable (such as an array), also pass the
7975 start and end values where the content changed so that it can be used to
7976 notify range observers.
7978 @method enumerableContentDidChange
7979 @param {Number} [start] optional start offset for the content change.
7980 For unordered enumerables, you should always pass -1.
7981 @param {Ember.Enumerable|Number} removing An enumerable of the objects to
7982 be removed or the number of items to be removed.
7983 @param {Ember.Enumerable|Number} adding An enumerable of the objects to
7984 be added or the number of items to be added.
7987 enumerableContentDidChange: function(removing, adding) {
7988 var notify = this.propertyDidChange, removeCnt, addCnt, hasDelta;
7990 if ('number' === typeof removing) removeCnt = removing;
7991 else if (removing) removeCnt = get(removing, 'length');
7992 else removeCnt = removing = -1;
7994 if ('number' === typeof adding) addCnt = adding;
7995 else if (adding) addCnt = get(adding, 'length');
7996 else addCnt = adding = -1;
7998 hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
8000 if (removing === -1) removing = null;
8001 if (adding === -1) adding = null;
8003 Ember.sendEvent(this, '@enumerable:change', [this, removing, adding]);
8004 if (hasDelta) Ember.propertyDidChange(this, 'length');
8005 Ember.propertyDidChange(this, '[]');
8019 @submodule ember-runtime
8022 // ..........................................................
8026 var get = Ember.get, set = Ember.set, meta = Ember.meta, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
8028 function none(obj) { return obj===null || obj===undefined; }
8030 // ..........................................................
8034 This module implements Observer-friendly Array-like behavior. This mixin is
8035 picked up by the Array class as well as other controllers, etc. that want to
8036 appear to be arrays.
8038 Unlike `Ember.Enumerable,` this mixin defines methods specifically for
8039 collections that provide index-ordered access to their contents. When you
8040 are designing code that needs to accept any kind of Array-like object, you
8041 should use these methods instead of Array primitives because these will
8042 properly notify observers of changes to the array.
8044 Although these methods are efficient, they do add a layer of indirection to
8045 your application so it is a good idea to use them only when you need the
8046 flexibility of using both true JavaScript arrays and "virtual" arrays such
8047 as controllers and collections.
8049 You can use the methods defined in this module to access and modify array
8050 contents in a KVO-friendly way. You can also be notified whenever the
8051 membership if an array changes by changing the syntax of the property to
8052 `.observes('*myProperty.[]')`.
8054 To support `Ember.Array` in your own class, you must override two
8055 primitives to use it: `replace()` and `objectAt()`.
8057 Note that the Ember.Array mixin also incorporates the `Ember.Enumerable`
8058 mixin. All `Ember.Array`-like objects are also enumerable.
8062 @extends Ember.Mixin
8063 @uses Ember.Enumerable
8066 Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.prototype */ {
8072 Your array must support the `length` property. Your replace methods should
8073 set this property whenever it changes.
8075 @property {Number} length
8077 length: Ember.required(),
8080 Returns the object at the given `index`. If the given `index` is negative
8081 or is greater or equal than the array length, returns `undefined`.
8083 This is one of the primitives you must implement to support `Ember.Array`.
8084 If your object supports retrieving the value of an array item using `get()`
8085 (i.e. `myArray.get(0)`), then you do not need to implement this method
8089 var arr = ['a', 'b', 'c', 'd'];
8090 arr.objectAt(0); // "a"
8091 arr.objectAt(3); // "d"
8092 arr.objectAt(-1); // undefined
8093 arr.objectAt(4); // undefined
8094 arr.objectAt(5); // undefined
8098 @param {Number} idx The index of the item to return.
8100 objectAt: function(idx) {
8101 if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ;
8102 return get(this, idx);
8106 This returns the objects at the specified indexes, using `objectAt`.
8109 var arr =Â ['a', 'b', 'c', 'd'];
8110 arr.objectsAt([0, 1, 2]); // ["a", "b", "c"]
8111 arr.objectsAt([2, 3, 4]); // ["c", "d", undefined]
8115 @param {Array} indexes An array of indexes of items to return.
8117 objectsAt: function(indexes) {
8119 return map(indexes, function(idx){ return self.objectAt(idx); });
8122 // overrides Ember.Enumerable version
8123 nextObject: function(idx) {
8124 return this.objectAt(idx);
8128 This is the handler for the special array content property. If you get
8129 this property, it will return this. If you set this property it a new
8130 array, it will replace the current content.
8132 This property overrides the default property defined in `Ember.Enumerable`.
8136 '[]': Ember.computed(function(key, value) {
8137 if (value !== undefined) this.replace(0, get(this, 'length'), value) ;
8141 firstObject: Ember.computed(function() {
8142 return this.objectAt(0);
8145 lastObject: Ember.computed(function() {
8146 return this.objectAt(get(this, 'length')-1);
8149 // optimized version from Enumerable
8150 contains: function(obj){
8151 return this.indexOf(obj) >= 0;
8154 // Add any extra methods to Ember.Array that are native to the built-in Array.
8156 Returns a new array that is a slice of the receiver. This implementation
8157 uses the observable array methods to retrieve the objects for the new
8161 var arr = ['red', 'green', 'blue'];
8162 arr.slice(0); // ['red', 'green', 'blue']
8163 arr.slice(0, 2); // ['red', 'green']
8164 arr.slice(1, 100); // ['green', 'blue']
8168 @param beginIndex {Integer} (Optional) index to begin slicing from.
8169 @param endIndex {Integer} (Optional) index to end the slice at.
8170 @return {Array} New array with specified slice
8172 slice: function(beginIndex, endIndex) {
8174 var length = get(this, 'length') ;
8175 if (none(beginIndex)) beginIndex = 0 ;
8176 if (none(endIndex) || (endIndex > length)) endIndex = length ;
8177 while(beginIndex < endIndex) {
8178 ret[ret.length] = this.objectAt(beginIndex++) ;
8184 Returns the index of the given object's first occurrence.
8185 If no `startAt` argument is given, the starting location to
8186 search is 0. If it's negative, will count backward from
8187 the end of the array. Returns -1 if no match is found.
8190 var arr = ["a", "b", "c", "d", "a"];
8191 arr.indexOf("a"); // 0
8192 arr.indexOf("z"); // -1
8193 arr.indexOf("a", 2); // 4
8194 arr.indexOf("a", -1); // 4
8195 arr.indexOf("b", 3); // -1
8196 arr.indexOf("a", 100); // -1
8200 @param {Object} object the item to search for
8201 @param {Number} startAt optional starting location to search, default 0
8202 @return {Number} index or -1 if not found
8204 indexOf: function(object, startAt) {
8205 var idx, len = get(this, 'length');
8207 if (startAt === undefined) startAt = 0;
8208 if (startAt < 0) startAt += len;
8210 for(idx=startAt;idx<len;idx++) {
8211 if (this.objectAt(idx, true) === object) return idx ;
8217 Returns the index of the given object's last occurrence.
8218 If no `startAt` argument is given, the search starts from
8219 the last position. If it's negative, will count backward
8220 from the end of the array. Returns -1 if no match is found.
8223 var arr = ["a", "b", "c", "d", "a"];
8224 arr.lastIndexOf("a"); // 4
8225 arr.lastIndexOf("z"); // -1
8226 arr.lastIndexOf("a", 2); // 0
8227 arr.lastIndexOf("a", -1); // 4
8228 arr.lastIndexOf("b", 3); // 1
8229 arr.lastIndexOf("a", 100); // 4
8233 @param {Object} object the item to search for
8234 @param {Number} startAt optional starting location to search, default 0
8235 @return {Number} index or -1 if not found
8237 lastIndexOf: function(object, startAt) {
8238 var idx, len = get(this, 'length');
8240 if (startAt === undefined || startAt >= len) startAt = len-1;
8241 if (startAt < 0) startAt += len;
8243 for(idx=startAt;idx>=0;idx--) {
8244 if (this.objectAt(idx) === object) return idx ;
8249 // ..........................................................
8254 Adds an array observer to the receiving array. The array observer object
8255 normally must implement two methods:
8257 * `arrayWillChange(start, removeCount, addCount)` - This method will be
8258 called just before the array is modified.
8259 * `arrayDidChange(start, removeCount, addCount)` - This method will be
8260 called just after the array is modified.
8262 Both callbacks will be passed the starting index of the change as well a
8263 a count of the items to be removed and added. You can use these callbacks
8264 to optionally inspect the array during the change, clear caches, or do
8265 any other bookkeeping necessary.
8267 In addition to passing a target, you can also include an options hash
8268 which you can use to override the method names that will be invoked on the
8271 @method addArrayObserver
8272 @param {Object} target The observer object.
8273 @param {Hash} opts Optional hash of configuration options including
8274 `willChange`, `didChange`, and a `context` option.
8275 @return {Ember.Array} receiver
8277 addArrayObserver: function(target, opts) {
8278 var willChange = (opts && opts.willChange) || 'arrayWillChange',
8279 didChange = (opts && opts.didChange) || 'arrayDidChange';
8281 var hasObservers = get(this, 'hasArrayObservers');
8282 if (!hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
8283 Ember.addListener(this, '@array:before', target, willChange);
8284 Ember.addListener(this, '@array:change', target, didChange);
8285 if (!hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
8290 Removes an array observer from the object if the observer is current
8291 registered. Calling this method multiple times with the same object will
8294 @method removeArrayObserver
8295 @param {Object} target The object observing the array.
8296 @return {Ember.Array} receiver
8298 removeArrayObserver: function(target, opts) {
8299 var willChange = (opts && opts.willChange) || 'arrayWillChange',
8300 didChange = (opts && opts.didChange) || 'arrayDidChange';
8302 var hasObservers = get(this, 'hasArrayObservers');
8303 if (hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
8304 Ember.removeListener(this, '@array:before', target, willChange);
8305 Ember.removeListener(this, '@array:change', target, didChange);
8306 if (hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
8311 Becomes true whenever the array currently has observers watching changes
8316 hasArrayObservers: Ember.computed(function() {
8317 return Ember.hasListeners(this, '@array:change') || Ember.hasListeners(this, '@array:before');
8321 If you are implementing an object that supports `Ember.Array`, call this
8322 method just before the array content changes to notify any observers and
8323 invalidate any related properties. Pass the starting index of the change
8324 as well as a delta of the amounts to change.
8326 @method arrayContentWillChange
8327 @param {Number} startIdx The starting index in the array that will change.
8328 @param {Number} removeAmt The number of items that will be removed. If you
8329 pass `null` assumes 0
8330 @param {Number} addAmt The number of items that will be added If you
8331 pass `null` assumes 0.
8332 @return {Ember.Array} receiver
8334 arrayContentWillChange: function(startIdx, removeAmt, addAmt) {
8336 // if no args are passed assume everything changes
8337 if (startIdx===undefined) {
8339 removeAmt = addAmt = -1;
8341 if (removeAmt === undefined) removeAmt=-1;
8342 if (addAmt === undefined) addAmt=-1;
8345 // Make sure the @each proxy is set up if anyone is observing @each
8346 if (Ember.isWatching(this, '@each')) { get(this, '@each'); }
8348 Ember.sendEvent(this, '@array:before', [this, startIdx, removeAmt, addAmt]);
8351 if (startIdx>=0 && removeAmt>=0 && get(this, 'hasEnumerableObservers')) {
8353 lim = startIdx+removeAmt;
8354 for(var idx=startIdx;idx<lim;idx++) removing.push(this.objectAt(idx));
8356 removing = removeAmt;
8359 this.enumerableContentWillChange(removing, addAmt);
8364 arrayContentDidChange: function(startIdx, removeAmt, addAmt) {
8366 // if no args are passed assume everything changes
8367 if (startIdx===undefined) {
8369 removeAmt = addAmt = -1;
8371 if (removeAmt === undefined) removeAmt=-1;
8372 if (addAmt === undefined) addAmt=-1;
8376 if (startIdx>=0 && addAmt>=0 && get(this, 'hasEnumerableObservers')) {
8378 lim = startIdx+addAmt;
8379 for(var idx=startIdx;idx<lim;idx++) adding.push(this.objectAt(idx));
8384 this.enumerableContentDidChange(removeAmt, adding);
8385 Ember.sendEvent(this, '@array:change', [this, startIdx, removeAmt, addAmt]);
8387 var length = get(this, 'length'),
8388 cachedFirst = cacheFor(this, 'firstObject'),
8389 cachedLast = cacheFor(this, 'lastObject');
8390 if (this.objectAt(0) !== cachedFirst) {
8391 Ember.propertyWillChange(this, 'firstObject');
8392 Ember.propertyDidChange(this, 'firstObject');
8394 if (this.objectAt(length-1) !== cachedLast) {
8395 Ember.propertyWillChange(this, 'lastObject');
8396 Ember.propertyDidChange(this, 'lastObject');
8402 // ..........................................................
8403 // ENUMERATED PROPERTIES
8407 Returns a special object that can be used to observe individual properties
8408 on the array. Just get an equivalent property on this object and it will
8409 return an enumerable that maps automatically to the named key on the
8414 '@each': Ember.computed(function() {
8415 if (!this.__each) this.__each = new Ember.EachProxy(this);
8428 @submodule ember-runtime
8433 Implements some standard methods for comparing objects. Add this mixin to
8434 any class you create that can compare its instances.
8436 You should implement the `compare()` method.
8440 @extends Ember.Mixin
8443 Ember.Comparable = Ember.Mixin.create( /** @scope Ember.Comparable.prototype */{
8446 walk like a duck. Indicates that the object can be compared.
8448 @property isComparable
8455 Override to return the result of the comparison of the two parameters. The
8456 compare method should return:
8462 Default implementation raises an exception.
8465 @param a {Object} the first object to compare
8466 @param b {Object} the second object to compare
8467 @return {Integer} the result of the comparison
8469 compare: Ember.required(Function)
8481 @submodule ember-runtime
8486 var get = Ember.get, set = Ember.set;
8489 Implements some standard methods for copying an object. Add this mixin to
8490 any object you create that can create a copy of itself. This mixin is
8491 added automatically to the built-in array.
8493 You should generally implement the `copy()` method to return a copy of the
8496 Note that `frozenCopy()` will only work if you also implement
8501 @extends Ember.Mixin
8504 Ember.Copyable = Ember.Mixin.create(
8505 /** @scope Ember.Copyable.prototype */ {
8508 Override to return a copy of the receiver. Default implementation raises
8512 @param deep {Boolean} if `true`, a deep copy of the object should be made
8513 @return {Object} copy of receiver
8515 copy: Ember.required(Function),
8518 If the object implements `Ember.Freezable`, then this will return a new
8519 copy if the object is not frozen and the receiver if the object is frozen.
8521 Raises an exception if you try to call this method on a object that does
8522 not support freezing.
8524 You should use this method whenever you want a copy of a freezable object
8525 since a freezable object can simply return itself without actually
8526 consuming more memory.
8529 @return {Object} copy of receiver or receiver
8531 frozenCopy: function() {
8532 if (Ember.Freezable && Ember.Freezable.detect(this)) {
8533 return get(this, 'isFrozen') ? this : this.copy().freeze();
8535 throw new Error(Ember.String.fmt("%@ does not support freezing", [this]));
8547 @submodule ember-runtime
8551 var get = Ember.get, set = Ember.set;
8554 The `Ember.Freezable` mixin implements some basic methods for marking an
8555 object as frozen. Once an object is frozen it should be read only. No changes
8556 may be made the internal state of the object.
8560 To fully support freezing in your subclass, you must include this mixin and
8561 override any method that might alter any property on the object to instead
8562 raise an exception. You can check the state of an object by checking the
8563 `isFrozen` property.
8565 Although future versions of JavaScript may support language-level freezing
8566 object objects, that is not the case today. Even if an object is freezable,
8567 it is still technically possible to modify the object, even though it could
8568 break other parts of your application that do not expect a frozen object to
8569 change. It is, therefore, very important that you always respect the
8570 `isFrozen` property on all freezable objects.
8574 The example below shows a simple object that implement the `Ember.Freezable`
8578 Contact = Ember.Object.extend(Ember.Freezable, {
8583 swapNames: function() {
8584 if (this.get('isFrozen')) throw Ember.FROZEN_ERROR;
8585 var tmp = this.get('firstName');
8586 this.set('firstName', this.get('lastName'));
8587 this.set('lastName', tmp);
8593 c = Context.create({ firstName: "John", lastName: "Doe" });
8594 c.swapNames(); // returns c
8596 c.swapNames(); // EXCEPTION
8601 Usually the `Ember.Freezable` protocol is implemented in cooperation with the
8602 `Ember.Copyable` protocol, which defines a `frozenCopy()` method that will
8603 return a frozen object, if the object implements this method as well.
8607 @extends Ember.Mixin
8610 Ember.Freezable = Ember.Mixin.create(
8611 /** @scope Ember.Freezable.prototype */ {
8614 Set to `true` when the object is frozen. Use this property to detect
8615 whether your object is frozen or not.
8623 Freezes the object. Once this method has been called the object should
8624 no longer allow any properties to be edited.
8627 @return {Object} receiver
8629 freeze: function() {
8630 if (get(this, 'isFrozen')) return this;
8631 set(this, 'isFrozen', true);
8637 Ember.FROZEN_ERROR = "Frozen object cannot be modified.";
8646 @submodule ember-runtime
8649 var forEach = Ember.EnumerableUtils.forEach;
8652 This mixin defines the API for modifying generic enumerables. These methods
8653 can be applied to an object regardless of whether it is ordered or
8656 Note that an Enumerable can change even if it does not implement this mixin.
8657 For example, a MappedEnumerable cannot be directly modified but if its
8658 underlying enumerable changes, it will change also.
8662 To add an object to an enumerable, use the `addObject()` method. This
8663 method will only add the object to the enumerable if the object is not
8664 already present and the object if of a type supported by the enumerable.
8667 set.addObject(contact);
8672 To remove an object form an enumerable, use the `removeObject()` method. This
8673 will only remove the object if it is already in the enumerable, otherwise
8674 this method has no effect.
8677 set.removeObject(contact);
8680 ## Implementing In Your Own Code
8682 If you are implementing an object and want to support this API, just include
8683 this mixin in your class and implement the required methods. In your unit
8684 tests, be sure to apply the Ember.MutableEnumerableTests to your object.
8686 @class MutableEnumerable
8688 @extends Ember.Mixin
8689 @uses Ember.Enumerable
8691 Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable,
8692 /** @scope Ember.MutableEnumerable.prototype */ {
8695 __Required.__ You must implement this method to apply this mixin.
8697 Attempts to add the passed object to the receiver if the object is not
8698 already present in the collection. If the object is present, this method
8701 If the passed object is of a type not supported by the receiver
8702 then this method should raise an exception.
8705 @param {Object} object The object to add to the enumerable.
8706 @return {Object} the passed object
8708 addObject: Ember.required(Function),
8711 Adds each object in the passed enumerable to the receiver.
8714 @param {Ember.Enumerable} objects the objects to add.
8715 @return {Object} receiver
8717 addObjects: function(objects) {
8718 Ember.beginPropertyChanges(this);
8719 forEach(objects, function(obj) { this.addObject(obj); }, this);
8720 Ember.endPropertyChanges(this);
8725 __Required.__ You must implement this method to apply this mixin.
8727 Attempts to remove the passed object from the receiver collection if the
8728 object is in present in the collection. If the object is not present,
8729 this method has no effect.
8731 If the passed object is of a type not supported by the receiver
8732 then this method should raise an exception.
8734 @method removeObject
8735 @param {Object} object The object to remove from the enumerable.
8736 @return {Object} the passed object
8738 removeObject: Ember.required(Function),
8742 Removes each objects in the passed enumerable from the receiver.
8744 @method removeObjects
8745 @param {Ember.Enumerable} objects the objects to remove
8746 @return {Object} receiver
8748 removeObjects: function(objects) {
8749 Ember.beginPropertyChanges(this);
8750 forEach(objects, function(obj) { this.removeObject(obj); }, this);
8751 Ember.endPropertyChanges(this);
8764 @submodule ember-runtime
8766 // ..........................................................
8770 var OUT_OF_RANGE_EXCEPTION = "Index out of range" ;
8773 // ..........................................................
8777 var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
8780 This mixin defines the API for modifying array-like objects. These methods
8781 can be applied only to a collection that keeps its items in an ordered set.
8783 Note that an Array can change even if it does not implement this mixin.
8784 For example, one might implement a SparseArray that cannot be directly
8785 modified, but if its underlying enumerable changes, it will change also.
8789 @extends Ember.Mixin
8791 @uses Ember.MutableEnumerable
8793 Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
8794 /** @scope Ember.MutableArray.prototype */ {
8797 __Required.__ You must implement this method to apply this mixin.
8799 This is one of the primitives you must implement to support `Ember.Array`.
8800 You should replace amt objects started at idx with the objects in the
8801 passed array. You should also call `this.enumerableContentDidChange()`
8804 @param {Number} idx Starting index in the array to replace. If
8805 idx >= length, then append to the end of the array.
8806 @param {Number} amt Number of elements that should be removed from
8807 the array, starting at *idx*.
8808 @param {Array} objects An array of zero or more objects that should be
8809 inserted into the array at *idx*
8811 replace: Ember.required(),
8814 Remove all elements from self. This is useful if you
8815 want to reuse an existing array without having to recreate it.
8818 var colors = ["red", "green", "blue"];
8819 color.length(); // 3
8820 colors.clear(); // []
8821 colors.length(); // 0
8825 @return {Ember.Array} An empty Array.
8827 clear: function () {
8828 var len = get(this, 'length');
8829 if (len === 0) return this;
8830 this.replace(0, len, EMPTY);
8835 This will use the primitive `replace()` method to insert an object at the
8839 var colors = ["red", "green", "blue"];
8840 colors.insertAt(2, "yellow"); // ["red", "green", "yellow", "blue"]
8841 colors.insertAt(5, "orange"); // Error: Index out of range
8845 @param {Number} idx index of insert the object at.
8846 @param {Object} object object to insert
8848 insertAt: function(idx, object) {
8849 if (idx > get(this, 'length')) throw new Error(OUT_OF_RANGE_EXCEPTION) ;
8850 this.replace(idx, 0, [object]) ;
8855 Remove an object at the specified index using the `replace()` primitive
8856 method. You can pass either a single index, or a start and a length.
8858 If you pass a start and length that is beyond the
8859 length this method will throw an `Ember.OUT_OF_RANGE_EXCEPTION`
8862 var colors = ["red", "green", "blue", "yellow", "orange"];
8863 colors.removeAt(0); // ["green", "blue", "yellow", "orange"]
8864 colors.removeAt(2, 2); // ["green", "blue"]
8865 colors.removeAt(4, 2); // Error: Index out of range
8869 @param {Number} start index, start of range
8870 @param {Number} len length of passing range
8871 @return {Object} receiver
8873 removeAt: function(start, len) {
8874 if ('number' === typeof start) {
8876 if ((start < 0) || (start >= get(this, 'length'))) {
8877 throw new Error(OUT_OF_RANGE_EXCEPTION);
8881 if (len === undefined) len = 1;
8882 this.replace(start, len, EMPTY);
8889 Push the object onto the end of the array. Works just like `push()` but it
8893 var colors = ["red", "green", "blue"];
8894 colors.pushObject("black"); // ["red", "green", "blue", "black"]
8895 colors.pushObject(["yellow", "orange"]); // ["red", "green", "blue", "black", ["yellow", "orange"]]
8899 @param {anything} obj object to push
8901 pushObject: function(obj) {
8902 this.insertAt(get(this, 'length'), obj) ;
8907 Add the objects in the passed numerable to the end of the array. Defers
8908 notifying observers of the change until all objects are added.
8911 var colors = ["red", "green", "blue"];
8912 colors.pushObjects("black"); // ["red", "green", "blue", "black"]
8913 colors.pushObjects(["yellow", "orange"]); // ["red", "green", "blue", "black", "yellow", "orange"]
8917 @param {Ember.Enumerable} objects the objects to add
8918 @return {Ember.Array} receiver
8920 pushObjects: function(objects) {
8921 this.replace(get(this, 'length'), 0, objects);
8926 Pop object from array or nil if none are left. Works just like `pop()` but
8927 it is KVO-compliant.
8930 var colors = ["red", "green", "blue"];
8931 colors.popObject(); // "blue"
8932 console.log(colors); // ["red", "green"]
8938 popObject: function() {
8939 var len = get(this, 'length') ;
8940 if (len === 0) return null ;
8942 var ret = this.objectAt(len-1) ;
8943 this.removeAt(len-1, 1) ;
8948 Shift an object from start of array or nil if none are left. Works just
8949 like `shift()` but it is KVO-compliant.
8952 var colors = ["red", "green", "blue"];
8953 colors.shiftObject(); // "red"
8954 console.log(colors); // ["green", "blue"]
8960 shiftObject: function() {
8961 if (get(this, 'length') === 0) return null ;
8962 var ret = this.objectAt(0) ;
8968 Unshift an object to start of array. Works just like `unshift()` but it is
8972 var colors = ["red", "green", "blue"];
8973 colors.unshiftObject("yellow"); // ["yellow", "red", "green", "blue"]
8974 colors.unshiftObject(["black", "white"]); // [["black", "white"], "yellow", "red", "green", "blue"]
8977 @method unshiftObject
8978 @param {anything} obj object to unshift
8980 unshiftObject: function(obj) {
8981 this.insertAt(0, obj) ;
8986 Adds the named objects to the beginning of the array. Defers notifying
8987 observers until all objects have been added.
8990 var colors = ["red", "green", "blue"];
8991 colors.unshiftObjects(["black", "white"]); // ["black", "white", "red", "green", "blue"]
8992 colors.unshiftObjects("yellow"); // Type Error: 'undefined' is not a function
8995 @method unshiftObjects
8996 @param {Ember.Enumerable} objects the objects to add
8997 @return {Ember.Array} receiver
8999 unshiftObjects: function(objects) {
9000 this.replace(0, 0, objects);
9005 Reverse objects in the array. Works just like `reverse()` but it is
9008 @method reverseObjects
9009 @return {Ember.Array} receiver
9011 reverseObjects: function() {
9012 var len = get(this, 'length');
9013 if (len === 0) return this;
9014 var objects = this.toArray().reverse();
9015 this.replace(0, len, objects);
9020 Replace all the the receiver's content with content of the argument.
9021 If argument is an empty array receiver will be cleared.
9024 var colors = ["red", "green", "blue"];
9025 colors.setObjects(["black", "white"]); // ["black", "white"]
9026 colors.setObjects([]); // []
9030 @param {Ember.Array} objects array whose content will be used for replacing
9031 the content of the receiver
9032 @return {Ember.Array} receiver with the new content
9034 setObjects: function(objects) {
9035 if (objects.length === 0) return this.clear();
9037 var len = get(this, 'length');
9038 this.replace(0, len, objects);
9042 // ..........................................................
9043 // IMPLEMENT Ember.MutableEnumerable
9046 removeObject: function(obj) {
9047 var loc = get(this, 'length') || 0;
9049 var curObject = this.objectAt(loc) ;
9050 if (curObject === obj) this.removeAt(loc) ;
9055 addObject: function(obj) {
9056 if (!this.contains(obj)) this.pushObject(obj);
9070 @submodule ember-runtime
9073 var get = Ember.get, set = Ember.set, defineProperty = Ember.defineProperty;
9078 This mixin provides properties and property observing functionality, core
9079 features of the Ember object model.
9081 Properties and observers allow one object to observe changes to a
9082 property on another object. This is one of the fundamental ways that
9083 models, controllers and views communicate with each other in an Ember
9086 Any object that has this mixin applied can be used in observer
9087 operations. That includes `Ember.Object` and most objects you will
9088 interact with as you write your Ember application.
9090 Note that you will not generally apply this mixin to classes yourself,
9091 but you will use the features provided by this module frequently, so it
9092 is important to understand how to use it.
9094 ## Using `get()` and `set()`
9096 Because of Ember's support for bindings and observers, you will always
9097 access properties using the get method, and set properties using the
9098 set method. This allows the observing objects to be notified and
9099 computed properties to be handled properly.
9101 More documentation about `get` and `set` are below.
9103 ## Observing Property Changes
9105 You typically observe property changes simply by adding the `observes`
9106 call to the end of your method declarations in classes that you write.
9110 Ember.Object.create({
9111 valueObserver: function() {
9112 // Executes whenever the "value" property changes
9117 Although this is the most common way to add an observer, this capability
9118 is actually built into the `Ember.Object` class on top of two methods
9119 defined in this mixin: `addObserver` and `removeObserver`. You can use
9120 these two methods to add and remove observers yourself if you need to
9123 To add an observer for a property, call:
9126 object.addObserver('propertyKey', targetObject, targetAction)
9129 This will call the `targetAction` method on the `targetObject` to be called
9130 whenever the value of the `propertyKey` changes.
9132 Note that if `propertyKey` is a computed property, the observer will be
9133 called when any of the property dependencies are changed, even if the
9134 resulting value of the computed property is unchanged. This is necessary
9135 because computed properties are not computed until `get` is called.
9139 @extends Ember.Mixin
9141 Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
9144 isObserverable: true,
9147 Retrieves the value of a property from the object.
9149 This method is usually similar to using `object[keyName]` or `object.keyName`,
9150 however it supports both computed properties and the unknownProperty
9153 Because `get` unifies the syntax for accessing all these kinds
9154 of properties, it can make many refactorings easier, such as replacing a
9155 simple property with a computed property, or vice versa.
9157 ### Computed Properties
9159 Computed properties are methods defined with the `property` modifier
9160 declared at the end, such as:
9163 fullName: function() {
9164 return this.getEach('firstName', 'lastName').compact().join(' ');
9165 }.property('firstName', 'lastName')
9168 When you call `get` on a computed property, the function will be
9169 called and the return value will be returned instead of the function
9172 ### Unknown Properties
9174 Likewise, if you try to call `get` on a property whose value is
9175 `undefined`, the `unknownProperty()` method will be called on the object.
9176 If this method returns any value other than `undefined`, it will be returned
9177 instead. This allows you to implement "virtual" properties that are
9178 not defined upfront.
9181 @param {String} key The property to retrieve
9182 @return {Object} The property value or undefined.
9184 get: function(keyName) {
9185 return get(this, keyName);
9189 To get multiple properties at once, call `getProperties`
9190 with a list of strings or an array:
9193 record.getProperties('firstName', 'lastName', 'zipCode'); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
9199 record.getProperties(['firstName', 'lastName', 'zipCode']); // { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
9202 @method getProperties
9203 @param {String...|Array} list of keys to get
9206 getProperties: function() {
9208 var propertyNames = arguments;
9209 if (arguments.length === 1 && Ember.typeOf(arguments[0]) === 'array') {
9210 propertyNames = arguments[0];
9212 for(var i = 0; i < propertyNames.length; i++) {
9213 ret[propertyNames[i]] = get(this, propertyNames[i]);
9219 Sets the provided key or path to the value.
9221 This method is generally very similar to calling `object[key] = value` or
9222 `object.key = value`, except that it provides support for computed
9223 properties, the `unknownProperty()` method and property observers.
9225 ### Computed Properties
9227 If you try to set a value on a key that has a computed property handler
9228 defined (see the `get()` method for an example), then `set()` will call
9229 that method, passing both the value and key instead of simply changing
9230 the value itself. This is useful for those times when you need to
9231 implement a property that is composed of one or more member
9234 ### Unknown Properties
9236 If you try to set a value on a key that is undefined in the target
9237 object, then the `unknownProperty()` handler will be called instead. This
9238 gives you an opportunity to implement complex "virtual" properties that
9239 are not predefined on the object. If `unknownProperty()` returns
9240 undefined, then `set()` will simply set the value on the object.
9242 ### Property Observers
9244 In addition to changing the property, `set()` will also register a property
9245 change with the object. Unless you have placed this call inside of a
9246 `beginPropertyChanges()` and `endPropertyChanges(),` any "local" observers
9247 (i.e. observer methods declared on the same object), will be called
9248 immediately. Any "remote" observers (i.e. observer methods declared on
9249 another object) will be placed in a queue and called at a later time in a
9254 In addition to property changes, `set()` returns the value of the object
9255 itself so you can do chaining like this:
9258 record.set('firstName', 'Charles').set('lastName', 'Jolley');
9262 @param {String} key The property to set
9263 @param {Object} value The value to set or `null`.
9264 @return {Ember.Observable}
9266 set: function(keyName, value) {
9267 set(this, keyName, value);
9272 To set multiple properties at once, call `setProperties`
9276 record.setProperties({ firstName: 'Charles', lastName: 'Jolley' });
9279 @method setProperties
9280 @param {Hash} hash the hash of keys and values to set
9281 @return {Ember.Observable}
9283 setProperties: function(hash) {
9284 return Ember.setProperties(this, hash);
9288 Begins a grouping of property changes.
9290 You can use this method to group property changes so that notifications
9291 will not be sent until the changes are finished. If you plan to make a
9292 large number of changes to an object at one time, you should call this
9293 method at the beginning of the changes to begin deferring change
9294 notifications. When you are done making changes, call
9295 `endPropertyChanges()` to deliver the deferred change notifications and end
9298 @method beginPropertyChanges
9299 @return {Ember.Observable}
9301 beginPropertyChanges: function() {
9302 Ember.beginPropertyChanges();
9307 Ends a grouping of property changes.
9309 You can use this method to group property changes so that notifications
9310 will not be sent until the changes are finished. If you plan to make a
9311 large number of changes to an object at one time, you should call
9312 `beginPropertyChanges()` at the beginning of the changes to defer change
9313 notifications. When you are done making changes, call this method to
9314 deliver the deferred change notifications and end deferring.
9316 @method endPropertyChanges
9317 @return {Ember.Observable}
9319 endPropertyChanges: function() {
9320 Ember.endPropertyChanges();
9325 Notify the observer system that a property is about to change.
9327 Sometimes you need to change a value directly or indirectly without
9328 actually calling `get()` or `set()` on it. In this case, you can use this
9329 method and `propertyDidChange()` instead. Calling these two methods
9330 together will notify all observers that the property has potentially
9333 Note that you must always call `propertyWillChange` and `propertyDidChange`
9334 as a pair. If you do not, it may get the property change groups out of
9335 order and cause notifications to be delivered more often than you would
9338 @method propertyWillChange
9339 @param {String} key The property key that is about to change.
9340 @return {Ember.Observable}
9342 propertyWillChange: function(keyName){
9343 Ember.propertyWillChange(this, keyName);
9348 Notify the observer system that a property has just changed.
9350 Sometimes you need to change a value directly or indirectly without
9351 actually calling `get()` or `set()` on it. In this case, you can use this
9352 method and `propertyWillChange()` instead. Calling these two methods
9353 together will notify all observers that the property has potentially
9356 Note that you must always call `propertyWillChange` and `propertyDidChange`
9357 as a pair. If you do not, it may get the property change groups out of
9358 order and cause notifications to be delivered more often than you would
9361 @method propertyDidChange
9362 @param {String} keyName The property key that has just changed.
9363 @return {Ember.Observable}
9365 propertyDidChange: function(keyName) {
9366 Ember.propertyDidChange(this, keyName);
9371 Convenience method to call `propertyWillChange` and `propertyDidChange` in
9374 @method notifyPropertyChange
9375 @param {String} keyName The property key to be notified about.
9376 @return {Ember.Observable}
9378 notifyPropertyChange: function(keyName) {
9379 this.propertyWillChange(keyName);
9380 this.propertyDidChange(keyName);
9384 addBeforeObserver: function(key, target, method) {
9385 Ember.addBeforeObserver(this, key, target, method);
9389 Adds an observer on a property.
9391 This is the core method used to register an observer for a property.
9393 Once you call this method, anytime the key's value is set, your observer
9394 will be notified. Note that the observers are triggered anytime the
9395 value is set, regardless of whether it has actually changed. Your
9396 observer should be prepared to handle that.
9398 You can also pass an optional context parameter to this method. The
9399 context will be passed to your observer method whenever it is triggered.
9400 Note that if you add the same target/method pair on a key multiple times
9401 with different context parameters, your observer will only be called once
9402 with the last context you passed.
9404 ### Observer Methods
9406 Observer methods you pass should generally have the following signature if
9407 you do not pass a `context` parameter:
9410 fooDidChange: function(sender, key, value, rev) { };
9413 The sender is the object that changed. The key is the property that
9414 changes. The value property is currently reserved and unused. The rev
9415 is the last property revision of the object when it changed, which you can
9416 use to detect if the key value has really changed or not.
9418 If you pass a `context` parameter, the context will be passed before the
9422 fooDidChange: function(sender, key, value, context, rev) { };
9425 Usually you will not need the value, context or revision parameters at
9426 the end. In this case, it is common to write observer methods that take
9427 only a sender and key value as parameters or, if you aren't interested in
9428 any of these values, to write an observer that has no parameters at all.
9431 @param {String} key The key to observer
9432 @param {Object} target The target object to invoke
9433 @param {String|Function} method The method to invoke.
9434 @return {Ember.Object} self
9436 addObserver: function(key, target, method) {
9437 Ember.addObserver(this, key, target, method);
9441 Remove an observer you have previously registered on this object. Pass
9442 the same key, target, and method you passed to `addObserver()` and your
9443 target will no longer receive notifications.
9445 @method removeObserver
9446 @param {String} key The key to observer
9447 @param {Object} target The target object to invoke
9448 @param {String|Function} method The method to invoke.
9449 @return {Ember.Observable} receiver
9451 removeObserver: function(key, target, method) {
9452 Ember.removeObserver(this, key, target, method);
9456 Returns `true` if the object currently has observers registered for a
9457 particular key. You can use this method to potentially defer performing
9458 an expensive action until someone begins observing a particular property
9461 @method hasObserverFor
9462 @param {String} key Key to check
9465 hasObserverFor: function(key) {
9466 return Ember.hasListeners(this, key+':change');
9472 @param {String} path The property path to retrieve
9473 @return {Object} The property value or undefined.
9475 getPath: function(path) {
9476 Ember.deprecate("getPath is deprecated since get now supports paths");
9477 return this.get(path);
9483 @param {String} path The path to the property that will be set
9484 @param {Object} value The value to set or `null`.
9485 @return {Ember.Observable}
9487 setPath: function(path, value) {
9488 Ember.deprecate("setPath is deprecated since set now supports paths");
9489 return this.set(path, value);
9493 Retrieves the value of a property, or a default value in the case that the
9494 property returns `undefined`.
9497 person.getWithDefault('lastName', 'Doe');
9500 @method getWithDefault
9501 @param {String} keyName The name of the property to retrieve
9502 @param {Object} defaultValue The value to return if the property value is undefined
9503 @return {Object} The property value or the defaultValue.
9505 getWithDefault: function(keyName, defaultValue) {
9506 return Ember.getWithDefault(this, keyName, defaultValue);
9510 Set the value of a property to the current value plus some amount.
9513 person.incrementProperty('age');
9514 team.incrementProperty('score', 2);
9517 @method incrementProperty
9518 @param {String} keyName The name of the property to increment
9519 @param {Object} increment The amount to increment by. Defaults to 1
9520 @return {Object} The new property value
9522 incrementProperty: function(keyName, increment) {
9523 if (!increment) { increment = 1; }
9524 set(this, keyName, (get(this, keyName) || 0)+increment);
9525 return get(this, keyName);
9529 Set the value of a property to the current value minus some amount.
9532 player.decrementProperty('lives');
9533 orc.decrementProperty('health', 5);
9536 @method decrementProperty
9537 @param {String} keyName The name of the property to decrement
9538 @param {Object} increment The amount to decrement by. Defaults to 1
9539 @return {Object} The new property value
9541 decrementProperty: function(keyName, increment) {
9542 if (!increment) { increment = 1; }
9543 set(this, keyName, (get(this, keyName) || 0)-increment);
9544 return get(this, keyName);
9548 Set the value of a boolean property to the opposite of it's
9552 starship.toggleProperty('warpDriveEnaged');
9555 @method toggleProperty
9556 @param {String} keyName The name of the property to toggle
9557 @return {Object} The new property value
9559 toggleProperty: function(keyName) {
9560 set(this, keyName, !get(this, keyName));
9561 return get(this, keyName);
9565 Returns the cached value of a computed property, if it exists.
9566 This allows you to inspect the value of a computed property
9567 without accidentally invoking it if it is intended to be
9571 @param {String} keyName
9572 @return {Object} The cached value of the computed property, if any
9574 cacheFor: function(keyName) {
9575 return Ember.cacheFor(this, keyName);
9578 // intended for debugging purposes
9579 observersForKey: function(keyName) {
9580 return Ember.observersFor(this, keyName);
9592 @submodule ember-runtime
9595 var get = Ember.get, set = Ember.set;
9598 @class TargetActionSupport
9600 @extends Ember.Mixin
9602 Ember.TargetActionSupport = Ember.Mixin.create({
9606 targetObject: Ember.computed(function() {
9607 var target = get(this, 'target');
9609 if (Ember.typeOf(target) === "string") {
9610 var value = get(this, target);
9611 if (value === undefined) { value = get(Ember.lookup, target); }
9616 }).property('target'),
9618 triggerAction: function() {
9619 var action = get(this, 'action'),
9620 target = get(this, 'targetObject');
9622 if (target && action) {
9625 if (typeof target.send === 'function') {
9626 ret = target.send(action, this);
9628 if (typeof action === 'string') {
9629 action = target[action];
9631 ret = action.call(target, this);
9633 if (ret !== false) ret = true;
9649 @submodule ember-runtime
9653 This mixin allows for Ember objects to subscribe to and emit events.
9656 App.Person = Ember.Object.extend(Ember.Evented, {
9659 this.trigger('greet');
9663 var person = App.Person.create();
9665 person.on('greet', function() {
9666 console.log('Our person has greeted');
9671 // outputs: 'Our person has greeted'
9676 @extends Ember.Mixin
9678 Ember.Evented = Ember.Mixin.create({
9681 Subscribes to a named event with given function.
9684 person.on('didLoad', function() {
9685 // fired once the person has loaded
9689 An optional target can be passed in as the 2nd argument that will
9690 be set as the "this" for the callback. This is a good way to give your
9691 function access to the object triggering the event. When the target
9692 parameter is used the callback becomes the third argument.
9695 @param {String} name The name of the event
9696 @param {Object} [target] The "this" binding for the callback
9697 @param {Function} method The callback to execute
9699 on: function(name, target, method) {
9700 Ember.addListener(this, name, target, method);
9704 Subscribes a function to a named event and then cancels the subscription
9705 after the first time the event is triggered. It is good to use ``one`` when
9706 you only care about the first time an event has taken place.
9708 This function takes an optional 2nd argument that will become the "this"
9709 value for the callback. If this argument is passed then the 3rd argument
9710 becomes the function.
9713 @param {String} name The name of the event
9714 @param {Object} [target] The "this" binding for the callback
9715 @param {Function} method The callback to execute
9717 one: function(name, target, method) {
9723 Ember.addListener(this, name, target, method, true);
9727 Triggers a named event for the object. Any additional arguments
9728 will be passed as parameters to the functions that are subscribed to the
9732 person.on('didEat', food) {
9733 console.log('person ate some ' + food);
9736 person.trigger('didEat', 'broccoli');
9738 // outputs: person ate some broccoli
9741 @param {String} name The name of the event
9742 @param {Object...} args Optional arguments to pass on
9744 trigger: function(name) {
9745 var args = [], i, l;
9746 for (i = 1, l = arguments.length; i < l; i++) {
9747 args.push(arguments[i]);
9749 Ember.sendEvent(this, name, args);
9752 fire: function(name) {
9753 Ember.deprecate("Ember.Evented#fire() has been deprecated in favor of trigger() for compatibility with jQuery. It will be removed in 1.0. Please update your code to call trigger() instead.");
9754 this.trigger.apply(this, arguments);
9758 Cancels subscription for give name, target, and method.
9761 @param {String} name The name of the event
9762 @param {Object} target The target of the subscription
9763 @param {Function} method The function of the subscription
9765 off: function(name, target, method) {
9766 Ember.removeListener(this, name, target, method);
9770 Checks to see if object has any subscriptions for named event.
9773 @param {String} name The name of the event
9774 @return {Boolean} does the object have a subscription for event
9776 has: function(name) {
9777 return Ember.hasListeners(this, name);
9786 var RSVP = requireModule("rsvp");
9788 RSVP.async = function(callback, binding) {
9789 Ember.run.schedule('actions', binding, callback);
9794 @submodule ember-runtime
9797 var get = Ember.get,
9798 slice = Array.prototype.slice;
9803 @extends Ember.Mixin
9805 Ember.DeferredMixin = Ember.Mixin.create({
9807 Add handlers to be called when the Deferred object is resolved or rejected.
9810 @param {Function} doneCallback a callback function to be called when done
9811 @param {Function} failCallback a callback function to be called when failed
9813 then: function(doneCallback, failCallback) {
9814 var promise = get(this, 'promise');
9815 return promise.then.apply(promise, arguments);
9819 Resolve a Deferred object and call any `doneCallbacks` with the given args.
9823 resolve: function(value) {
9824 get(this, 'promise').resolve(value);
9828 Reject a Deferred object and call any `failCallbacks` with the given args.
9832 reject: function(value) {
9833 get(this, 'promise').reject(value);
9836 promise: Ember.computed(function() {
9837 return new RSVP.Promise();
9853 Ember.Container = requireModule('container');
9854 Ember.Container.set = Ember.set;
9863 @submodule ember-runtime
9867 // NOTE: this object should never be included directly. Instead use Ember.
9868 // Ember.Object. We only define this separately so that Ember.Set can depend on it
9871 var set = Ember.set, get = Ember.get,
9872 o_create = Ember.create,
9873 o_defineProperty = Ember.platform.defineProperty,
9874 a_slice = Array.prototype.slice,
9875 GUID_KEY = Ember.GUID_KEY,
9876 guidFor = Ember.guidFor,
9877 generateGuid = Ember.generateGuid,
9879 rewatch = Ember.rewatch,
9880 finishChains = Ember.finishChains,
9881 destroy = Ember.destroy,
9882 schedule = Ember.run.schedule,
9883 Mixin = Ember.Mixin,
9884 applyMixin = Mixin._apply,
9885 finishPartial = Mixin.finishPartial,
9886 reopen = Mixin.prototype.reopen,
9887 MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
9888 indexOf = Ember.EnumerableUtils.indexOf;
9890 var undefinedDescriptor = {
9897 function makeCtor() {
9899 // Note: avoid accessing any properties on the object since it makes the
9900 // method a lot faster. This is glue code so we want it to be as fast as
9903 var wasApplied = false, initMixins, initProperties;
9905 var Class = function() {
9907 Class.proto(); // prepare prototype...
9909 o_defineProperty(this, GUID_KEY, undefinedDescriptor);
9910 o_defineProperty(this, '_super', undefinedDescriptor);
9914 // capture locally so we can clear the closed over variable
9915 var mixins = initMixins;
9917 this.reopen.apply(this, mixins);
9919 if (initProperties) {
9920 // capture locally so we can clear the closed over variable
9921 var props = initProperties;
9922 initProperties = null;
9924 var concatenatedProperties = this.concatenatedProperties;
9926 for (var i = 0, l = props.length; i < l; i++) {
9927 var properties = props[i];
9928 for (var keyName in properties) {
9929 if (!properties.hasOwnProperty(keyName)) { continue; }
9931 var value = properties[keyName],
9932 IS_BINDING = Ember.IS_BINDING;
9934 if (IS_BINDING.test(keyName)) {
9935 var bindings = m.bindings;
9937 bindings = m.bindings = {};
9938 } else if (!m.hasOwnProperty('bindings')) {
9939 bindings = m.bindings = o_create(m.bindings);
9941 bindings[keyName] = value;
9944 var desc = m.descs[keyName];
9946 Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
9947 Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
9949 if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
9950 var baseValue = this[keyName];
9953 if ('function' === typeof baseValue.concat) {
9954 value = baseValue.concat(value);
9956 value = Ember.makeArray(baseValue).concat(value);
9959 value = Ember.makeArray(value);
9964 desc.set(this, keyName, value);
9966 if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
9967 this.setUnknownProperty(keyName, value);
9968 } else if (MANDATORY_SETTER) {
9969 Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
9971 this[keyName] = value;
9977 finishPartial(this, m);
9980 this.init.apply(this, arguments);
9983 Class.toString = Mixin.prototype.toString;
9984 Class.willReopen = function() {
9986 Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
9991 Class._initMixins = function(args) { initMixins = args; };
9992 Class._initProperties = function(args) { initProperties = args; };
9994 Class.proto = function() {
9995 var superclass = Class.superclass;
9996 if (superclass) { superclass.proto(); }
10000 Class.PrototypeMixin.applyPartial(Class.prototype);
10001 rewatch(Class.prototype);
10004 return this.prototype;
10011 var CoreObject = makeCtor();
10013 CoreObject.PrototypeMixin = Mixin.create({
10015 reopen: function() {
10016 applyMixin(this, arguments, true);
10022 init: function() {},
10025 Defines the properties that will be concatenated from the superclass
10026 (instead of overridden).
10028 By default, when you extend an Ember class a property defined in
10029 the subclass overrides a property with the same name that is defined
10030 in the superclass. However, there are some cases where it is preferable
10031 to build up a property's value by combining the superclass' property
10032 value with the subclass' value. An example of this in use within Ember
10033 is the `classNames` property of `Ember.View`.
10035 Here is some sample code showing the difference between a concatenated
10036 property and a normal one:
10039 App.BarView = Ember.View.extend({
10040 someNonConcatenatedProperty: ['bar'],
10041 classNames: ['bar']
10044 App.FooBarView = App.BarView.extend({
10045 someNonConcatenatedProperty: ['foo'],
10046 classNames: ['foo'],
10049 var fooBarView = App.FooBarView.create();
10050 fooBarView.get('someNonConcatenatedProperty'); // ['foo']
10051 fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo']
10054 This behavior extends to object creation as well. Continuing the
10058 var view = App.FooBarView.create({
10059 someNonConcatenatedProperty: ['baz'],
10060 classNames: ['baz']
10062 view.get('someNonConcatenatedProperty'); // ['baz']
10063 view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
10065 Adding a single property that is not an array will just add it in the array:
10068 var view = App.FooBarView.create({
10071 view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz']
10074 Using the `concatenatedProperties` property, we can tell to Ember that mix
10075 the content of the properties.
10077 In `Ember.View` the `classNameBindings` and `attributeBindings` properties
10078 are also concatenated, in addition to `classNames`.
10080 This feature is available for you to use throughout the Ember object model,
10081 although typical app developers are likely to use it infrequently.
10083 @property concatenatedProperties
10087 concatenatedProperties: null,
10090 @property isDestroyed
10093 isDestroyed: false,
10096 @property isDestroying
10099 isDestroying: false,
10102 Destroys an object by setting the `isDestroyed` flag and removing its
10103 metadata, which effectively destroys observers and bindings.
10105 If you try to set a property on a destroyed object, an exception will be
10108 Note that destruction is scheduled for the end of the run loop and does not
10109 happen immediately.
10112 @return {Ember.Object} receiver
10114 destroy: function() {
10115 if (this.isDestroying) { return; }
10117 this.isDestroying = true;
10119 if (this.willDestroy) { this.willDestroy(); }
10121 schedule('destroy', this, this._scheduledDestroy);
10128 Invoked by the run loop to actually destroy the object. This is
10129 scheduled for execution by the `destroy` method.
10131 @method _scheduledDestroy
10133 _scheduledDestroy: function() {
10135 set(this, 'isDestroyed', true);
10137 if (this.didDestroy) { this.didDestroy(); }
10140 bind: function(to, from) {
10141 if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
10142 from.to(to).connect(this);
10147 Returns a string representation which attempts to provide more information
10148 than Javascript's `toString` typically does, in a generic way for all Ember
10151 App.Person = Em.Object.extend()
10152 person = App.Person.create()
10153 person.toString() //=> "<App.Person:ember1024>"
10155 If the object's class is not defined on an Ember namespace, it will
10156 indicate it is a subclass of the registered superclass:
10158 Student = App.Person.extend()
10159 student = Student.create()
10160 student.toString() //=> "<(subclass of App.Person):ember1025>"
10162 If the method `toStringExtension` is defined, its return value will be
10163 included in the output.
10165 App.Teacher = App.Person.extend({
10166 toStringExtension: function(){
10167 return this.get('fullName');
10170 teacher = App.Teacher.create()
10171 teacher.toString(); // #=> "<App.Teacher:ember1026:Tom Dale>"
10174 @return {String} string representation
10176 toString: function toString() {
10177 var hasToStringExtension = typeof this.toStringExtension === 'function',
10178 extension = hasToStringExtension ? ":" + this.toStringExtension() : '';
10179 var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>';
10180 this.toString = makeToString(ret);
10185 function makeToString(ret) {
10186 return function() { return ret; };
10189 if (Ember.config.overridePrototypeMixin) {
10190 Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin);
10193 CoreObject.__super__ = null;
10195 var ClassMixin = Mixin.create({
10197 ClassMixin: Ember.required(),
10199 PrototypeMixin: Ember.required(),
10205 extend: function() {
10206 var Class = makeCtor(), proto;
10207 Class.ClassMixin = Mixin.create(this.ClassMixin);
10208 Class.PrototypeMixin = Mixin.create(this.PrototypeMixin);
10210 Class.ClassMixin.ownerConstructor = Class;
10211 Class.PrototypeMixin.ownerConstructor = Class;
10213 reopen.apply(Class.PrototypeMixin, arguments);
10215 Class.superclass = this;
10216 Class.__super__ = this.prototype;
10218 proto = Class.prototype = o_create(this.prototype);
10219 proto.constructor = Class;
10220 generateGuid(proto, 'ember');
10221 meta(proto).proto = proto; // this will disable observers on prototype
10223 Class.ClassMixin.apply(Class);
10227 createWithMixins: function() {
10229 if (arguments.length>0) { this._initMixins(arguments); }
10233 create: function() {
10235 if (arguments.length>0) { this._initProperties(arguments); }
10239 reopen: function() {
10241 reopen.apply(this.PrototypeMixin, arguments);
10245 reopenClass: function() {
10246 reopen.apply(this.ClassMixin, arguments);
10247 applyMixin(this, arguments, false);
10251 detect: function(obj) {
10252 if ('function' !== typeof obj) { return false; }
10254 if (obj===this) { return true; }
10255 obj = obj.superclass;
10260 detectInstance: function(obj) {
10261 return obj instanceof this;
10265 In some cases, you may want to annotate computed properties with additional
10266 metadata about how they function or what values they operate on. For
10267 example, computed property functions may close over variables that are then
10268 no longer available for introspection.
10270 You can pass a hash of these values to a computed property like this:
10273 person: function() {
10274 var personId = this.get('personId');
10275 return App.Person.create({ id: personId });
10276 }.property().meta({ type: App.Person })
10279 Once you've done this, you can retrieve the values saved to the computed
10280 property from your class like this:
10283 MyClass.metaForProperty('person');
10286 This will return the original hash that was passed to `meta()`.
10288 @method metaForProperty
10289 @param key {String} property name
10291 metaForProperty: function(key) {
10292 var desc = meta(this.proto(), false).descs[key];
10294 Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
10295 return desc._meta || {};
10299 Iterate over each computed property for the class, passing its name
10300 and any associated metadata (see `metaForProperty`) to the callback.
10302 @method eachComputedProperty
10303 @param {Function} callback
10304 @param {Object} binding
10306 eachComputedProperty: function(callback, binding) {
10307 var proto = this.proto(),
10308 descs = meta(proto).descs,
10312 for (var name in descs) {
10313 property = descs[name];
10315 if (property instanceof Ember.ComputedProperty) {
10316 callback.call(binding || this, name, property._meta || empty);
10323 if (Ember.config.overrideClassMixin) {
10324 Ember.config.overrideClassMixin(ClassMixin);
10327 CoreObject.ClassMixin = ClassMixin;
10328 ClassMixin.apply(CoreObject);
10334 Ember.CoreObject = CoreObject;
10343 @submodule ember-runtime
10346 var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.isNone;
10349 An unordered collection of objects.
10351 A Set works a bit like an array except that its items are not ordered. You
10352 can create a set to efficiently test for membership for an object. You can
10353 also iterate through a set just like an array, even accessing objects by
10354 index, however there is no guarantee as to their order.
10356 All Sets are observable via the Enumerable Observer API - which works
10357 on any enumerable object including both Sets and Arrays.
10361 You can create a set like you would most objects using
10362 `new Ember.Set()`. Most new sets you create will be empty, but you can
10363 also initialize the set with some content by passing an array or other
10364 enumerable of objects to the constructor.
10366 Finally, you can pass in an existing set and the set will be copied. You
10367 can also create a copy of a set by calling `Ember.Set#copy()`.
10370 // creates a new empty set
10371 var foundNames = new Ember.Set();
10373 // creates a set with four names in it.
10374 var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P
10376 // creates a copy of the names set.
10377 var namesCopy = new Ember.Set(names);
10380 var anotherNamesCopy = names.copy();
10383 ## Adding/Removing Objects
10385 You generally add or remove objects from a set using `add()` or
10386 `remove()`. You can add any type of object including primitives such as
10387 numbers, strings, and booleans.
10389 Unlike arrays, objects can only exist one time in a set. If you call `add()`
10390 on a set with the same object multiple times, the object will only be added
10391 once. Likewise, calling `remove()` with the same object multiple times will
10392 remove the object the first time and have no effect on future calls until
10393 you add the object to the set again.
10395 NOTE: You cannot add/remove `null` or `undefined` to a set. Any attempt to do
10396 so will be ignored.
10398 In addition to add/remove you can also call `push()`/`pop()`. Push behaves
10399 just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary
10400 object, remove it and return it. This is a good way to use a set as a job
10401 queue when you don't care which order the jobs are executed in.
10403 ## Testing for an Object
10405 To test for an object's presence in a set you simply call
10406 `Ember.Set#contains()`.
10408 ## Observing changes
10410 When using `Ember.Set`, you can observe the `"[]"` property to be
10411 alerted whenever the content changes. You can also add an enumerable
10412 observer to the set to be notified of specific objects that are added and
10413 removed from the set. See `Ember.Enumerable` for more information on
10416 This is often unhelpful. If you are filtering sets of objects, for instance,
10417 it is very inefficient to re-filter all of the items each time the set
10418 changes. It would be better if you could just adjust the filtered set based
10419 on what was changed on the original set. The same issue applies to merging
10424 `Ember.Set` primary implements other mixin APIs. For a complete reference
10425 on the methods you will use with `Ember.Set`, please consult these mixins.
10426 The most useful ones will be `Ember.Enumerable` and
10427 `Ember.MutableEnumerable` which implement most of the common iterator
10428 methods you are used to on Array.
10430 Note that you can also use the `Ember.Copyable` and `Ember.Freezable`
10431 APIs on `Ember.Set` as well. Once a set is frozen it can no longer be
10432 modified. The benefit of this is that when you call `frozenCopy()` on it,
10433 Ember will avoid making copies of the set. This allows you to write
10434 code that can know with certainty when the underlying set data will or
10435 will not be modified.
10439 @extends Ember.CoreObject
10440 @uses Ember.MutableEnumerable
10441 @uses Ember.Copyable
10442 @uses Ember.Freezable
10445 Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable,
10446 /** @scope Ember.Set.prototype */ {
10448 // ..........................................................
10449 // IMPLEMENT ENUMERABLE APIS
10453 This property will change as the number of objects in the set changes.
10462 Clears the set. This is useful if you want to reuse an existing set
10463 without having to recreate it.
10466 var colors = new Ember.Set(["red", "green", "blue"]);
10467 colors.length; // 3
10469 colors.length; // 0
10473 @return {Ember.Set} An empty Set
10475 clear: function() {
10476 if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); }
10478 var len = get(this, 'length');
10479 if (len === 0) { return this; }
10483 this.enumerableContentWillChange(len, 0);
10484 Ember.propertyWillChange(this, 'firstObject');
10485 Ember.propertyWillChange(this, 'lastObject');
10487 for (var i=0; i < len; i++){
10488 guid = guidFor(this[i]);
10493 set(this, 'length', 0);
10495 Ember.propertyDidChange(this, 'firstObject');
10496 Ember.propertyDidChange(this, 'lastObject');
10497 this.enumerableContentDidChange(len, 0);
10503 Returns true if the passed object is also an enumerable that contains the
10504 same objects as the receiver.
10507 var colors = ["red", "green", "blue"],
10508 same_colors = new Ember.Set(colors);
10510 same_colors.isEqual(colors); // true
10511 same_colors.isEqual(["purple", "brown"]); // false
10515 @param {Ember.Set} obj the other object.
10518 isEqual: function(obj) {
10520 if (!Ember.Enumerable.detect(obj)) return false;
10522 var loc = get(this, 'length');
10523 if (get(obj, 'length') !== loc) return false;
10525 while(--loc >= 0) {
10526 if (!obj.contains(this[loc])) return false;
10533 Adds an object to the set. Only non-`null` objects can be added to a set
10534 and those can only be added once. If the object is already in the set or
10535 the passed value is null this method will have no effect.
10537 This is an alias for `Ember.MutableEnumerable.addObject()`.
10540 var colors = new Ember.Set();
10541 colors.add("blue"); // ["blue"]
10542 colors.add("blue"); // ["blue"]
10543 colors.add("red"); // ["blue", "red"]
10544 colors.add(null); // ["blue", "red"]
10545 colors.add(undefined); // ["blue", "red"]
10549 @param {Object} obj The object to add.
10550 @return {Ember.Set} The set itself.
10552 add: Ember.aliasMethod('addObject'),
10555 Removes the object from the set if it is found. If you pass a `null` value
10556 or an object that is already not in the set, this method will have no
10557 effect. This is an alias for `Ember.MutableEnumerable.removeObject()`.
10560 var colors = new Ember.Set(["red", "green", "blue"]);
10561 colors.remove("red"); // ["blue", "green"]
10562 colors.remove("purple"); // ["blue", "green"]
10563 colors.remove(null); // ["blue", "green"]
10567 @param {Object} obj The object to remove
10568 @return {Ember.Set} The set itself.
10570 remove: Ember.aliasMethod('removeObject'),
10573 Removes the last element from the set and returns it, or `null` if it's empty.
10576 var colors = new Ember.Set(["green", "blue"]);
10577 colors.pop(); // "blue"
10578 colors.pop(); // "green"
10579 colors.pop(); // null
10583 @return {Object} The removed object from the set or null.
10586 if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
10587 var obj = this.length > 0 ? this[this.length-1] : null;
10593 Inserts the given object on to the end of the set. It returns
10596 This is an alias for `Ember.MutableEnumerable.addObject()`.
10599 var colors = new Ember.Set();
10600 colors.push("red"); // ["red"]
10601 colors.push("green"); // ["red", "green"]
10602 colors.push("blue"); // ["red", "green", "blue"]
10606 @return {Ember.Set} The set itself.
10608 push: Ember.aliasMethod('addObject'),
10611 Removes the last element from the set and returns it, or `null` if it's empty.
10613 This is an alias for `Ember.Set.pop()`.
10616 var colors = new Ember.Set(["green", "blue"]);
10617 colors.shift(); // "blue"
10618 colors.shift(); // "green"
10619 colors.shift(); // null
10623 @return {Object} The removed object from the set or null.
10625 shift: Ember.aliasMethod('pop'),
10628 Inserts the given object on to the end of the set. It returns
10631 This is an alias of `Ember.Set.push()`
10634 var colors = new Ember.Set();
10635 colors.unshift("red"); // ["red"]
10636 colors.unshift("green"); // ["red", "green"]
10637 colors.unshift("blue"); // ["red", "green", "blue"]
10641 @return {Ember.Set} The set itself.
10643 unshift: Ember.aliasMethod('push'),
10646 Adds each object in the passed enumerable to the set.
10648 This is an alias of `Ember.MutableEnumerable.addObjects()`
10651 var colors = new Ember.Set();
10652 colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"]
10656 @param {Ember.Enumerable} objects the objects to add.
10657 @return {Ember.Set} The set itself.
10659 addEach: Ember.aliasMethod('addObjects'),
10662 Removes each object in the passed enumerable to the set.
10664 This is an alias of `Ember.MutableEnumerable.removeObjects()`
10667 var colors = new Ember.Set(["red", "green", "blue"]);
10668 colors.removeEach(["red", "blue"]); // ["green"]
10672 @param {Ember.Enumerable} objects the objects to remove.
10673 @return {Ember.Set} The set itself.
10675 removeEach: Ember.aliasMethod('removeObjects'),
10677 // ..........................................................
10678 // PRIVATE ENUMERABLE SUPPORT
10681 init: function(items) {
10683 if (items) this.addObjects(items);
10686 // implement Ember.Enumerable
10687 nextObject: function(idx) {
10691 // more optimized version
10692 firstObject: Ember.computed(function() {
10693 return this.length > 0 ? this[0] : undefined;
10696 // more optimized version
10697 lastObject: Ember.computed(function() {
10698 return this.length > 0 ? this[this.length-1] : undefined;
10701 // implements Ember.MutableEnumerable
10702 addObject: function(obj) {
10703 if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
10704 if (none(obj)) return this; // nothing to do
10706 var guid = guidFor(obj),
10708 len = get(this, 'length'),
10711 if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added
10715 this.enumerableContentWillChange(null, added);
10716 Ember.propertyWillChange(this, 'lastObject');
10718 len = get(this, 'length');
10721 set(this, 'length', len+1);
10723 Ember.propertyDidChange(this, 'lastObject');
10724 this.enumerableContentDidChange(null, added);
10729 // implements Ember.MutableEnumerable
10730 removeObject: function(obj) {
10731 if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
10732 if (none(obj)) return this; // nothing to do
10734 var guid = guidFor(obj),
10736 len = get(this, 'length'),
10737 isFirst = idx === 0,
10738 isLast = idx === len-1,
10742 if (idx>=0 && idx<len && (this[idx] === obj)) {
10745 this.enumerableContentWillChange(removed, null);
10746 if (isFirst) { Ember.propertyWillChange(this, 'firstObject'); }
10747 if (isLast) { Ember.propertyWillChange(this, 'lastObject'); }
10749 // swap items - basically move the item to the end so it can be removed
10751 last = this[len-1];
10753 this[guidFor(last)] = idx;
10757 delete this[len-1];
10758 set(this, 'length', len-1);
10760 if (isFirst) { Ember.propertyDidChange(this, 'firstObject'); }
10761 if (isLast) { Ember.propertyDidChange(this, 'lastObject'); }
10762 this.enumerableContentDidChange(removed, null);
10768 // optimized version
10769 contains: function(obj) {
10770 return this[guidFor(obj)]>=0;
10774 var C = this.constructor, ret = new C(), loc = get(this, 'length');
10775 set(ret, 'length', loc);
10777 ret[loc] = this[loc];
10778 ret[guidFor(this[loc])] = loc;
10783 toString: function() {
10784 var len = this.length, idx, array = [];
10785 for(idx = 0; idx < len; idx++) {
10786 array[idx] = this[idx];
10788 return "Ember.Set<%@>".fmt(array.join(','));
10800 @submodule ember-runtime
10804 `Ember.Object` is the main base class for all Ember objects. It is a subclass
10805 of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
10806 see the documentation for each of these.
10810 @extends Ember.CoreObject
10811 @uses Ember.Observable
10813 Ember.Object = Ember.CoreObject.extend(Ember.Observable);
10822 @submodule ember-runtime
10825 var get = Ember.get, indexOf = Ember.ArrayPolyfills.indexOf;
10828 A Namespace is an object usually used to contain other objects or methods
10829 such as an application or framework. Create a namespace anytime you want
10830 to define one of these new containers.
10835 MyFramework = Ember.Namespace.create({
10842 @extends Ember.Object
10844 var Namespace = Ember.Namespace = Ember.Object.extend({
10848 Ember.Namespace.NAMESPACES.push(this);
10849 Ember.Namespace.PROCESSED = false;
10852 toString: function() {
10853 var name = get(this, 'name');
10854 if (name) { return name; }
10857 return this[Ember.GUID_KEY+'_name'];
10860 nameClasses: function() {
10861 processNamespace([this.toString()], this, {});
10864 destroy: function() {
10865 var namespaces = Ember.Namespace.NAMESPACES;
10866 Ember.lookup[this.toString()] = undefined;
10867 namespaces.splice(indexOf.call(namespaces, this), 1);
10872 Namespace.reopenClass({
10873 NAMESPACES: [Ember],
10875 processAll: processAllNamespaces
10878 var hasOwnProp = ({}).hasOwnProperty,
10879 guidFor = Ember.guidFor;
10881 function processNamespace(paths, root, seen) {
10882 var idx = paths.length;
10884 // Loop over all of the keys in the namespace, looking for classes
10885 for(var key in root) {
10886 if (!hasOwnProp.call(root, key)) { continue; }
10887 var obj = root[key];
10889 // If we are processing the `Ember` namespace, for example, the
10890 // `paths` will start with `["Ember"]`. Every iteration through
10891 // the loop will update the **second** element of this list with
10892 // the key, so processing `Ember.View` will make the Array
10893 // `['Ember', 'View']`.
10896 // If we have found an unprocessed class
10897 if (obj && obj.toString === classToString) {
10898 // Replace the class' `toString` with the dot-separated path
10899 // and set its `NAME_KEY`
10900 obj.toString = makeToString(paths.join('.'));
10901 obj[NAME_KEY] = paths.join('.');
10903 // Support nested namespaces
10904 } else if (obj && obj.isNamespace) {
10905 // Skip aliased namespaces
10906 if (seen[guidFor(obj)]) { continue; }
10907 seen[guidFor(obj)] = true;
10909 // Process the child namespace
10910 processNamespace(paths, obj, seen);
10914 paths.length = idx; // cut out last item
10917 function findNamespaces() {
10918 var Namespace = Ember.Namespace, lookup = Ember.lookup, obj, isNamespace;
10920 if (Namespace.PROCESSED) { return; }
10922 for (var prop in lookup) {
10923 // These don't raise exceptions but can cause warnings
10924 if (prop === "parent" || prop === "top" || prop === "frameElement") { continue; }
10926 // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox.
10927 // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage
10928 if (prop === "globalStorage" && lookup.StorageList && lookup.globalStorage instanceof lookup.StorageList) { continue; }
10929 // Unfortunately, some versions of IE don't support window.hasOwnProperty
10930 if (lookup.hasOwnProperty && !lookup.hasOwnProperty(prop)) { continue; }
10932 // At times we are not allowed to access certain properties for security reasons.
10933 // There are also times where even if we can access them, we are not allowed to access their properties.
10935 obj = Ember.lookup[prop];
10936 isNamespace = obj && obj.isNamespace;
10942 Ember.deprecate("Namespaces should not begin with lowercase.", /^[A-Z]/.test(prop));
10943 obj[NAME_KEY] = prop;
10948 var NAME_KEY = Ember.NAME_KEY = Ember.GUID_KEY + '_name';
10950 function superClassString(mixin) {
10951 var superclass = mixin.superclass;
10953 if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; }
10954 else { return superClassString(superclass); }
10960 function classToString() {
10961 if (!Ember.BOOTED && !this[NAME_KEY]) {
10962 processAllNamespaces();
10967 if (this[NAME_KEY]) {
10968 ret = this[NAME_KEY];
10970 var str = superClassString(this);
10972 ret = "(subclass of " + str + ")";
10974 ret = "(unknown mixin)";
10976 this.toString = makeToString(ret);
10982 function processAllNamespaces() {
10983 if (!Namespace.PROCESSED) {
10985 Namespace.PROCESSED = true;
10988 if (Ember.anyUnprocessedMixins) {
10989 var namespaces = Namespace.NAMESPACES, namespace;
10990 for (var i=0, l=namespaces.length; i<l; i++) {
10991 namespace = namespaces[i];
10992 processNamespace([namespace.toString()], namespace, {});
10995 Ember.anyUnprocessedMixins = false;
10999 function makeToString(ret) {
11000 return function() { return ret; };
11003 Ember.Mixin.prototype.toString = classToString;
11012 @submodule ember-runtime
11016 Defines a namespace that will contain an executable application. This is
11017 very similar to a normal namespace except that it is expected to include at
11018 least a 'ready' function which can be run to initialize the application.
11020 Currently `Ember.Application` is very similar to `Ember.Namespace.` However,
11021 this class may be augmented by additional frameworks so it is important to
11022 use this instance when building new applications.
11027 MyApp = Ember.Application.create({
11029 store: Ember.Store.create().from(Ember.fixtures)
11032 MyApp.ready = function() {
11033 //..init code goes here...
11039 @extends Ember.Namespace
11041 Ember.Application = Ember.Namespace.extend();
11051 @submodule ember-runtime
11055 var get = Ember.get, set = Ember.set;
11058 An ArrayProxy wraps any other object that implements `Ember.Array` and/or
11059 `Ember.MutableArray,` forwarding all requests. This makes it very useful for
11060 a number of binding use cases or other cases where being able to swap
11061 out the underlying array is useful.
11063 A simple example of usage:
11066 var pets = ['dog', 'cat', 'fish'];
11067 var ap = Ember.ArrayProxy.create({ content: Ember.A(pets) });
11069 ap.get('firstObject'); // 'dog'
11070 ap.set('content', ['amoeba', 'paramecium']);
11071 ap.get('firstObject'); // 'amoeba'
11074 This class can also be useful as a layer to transform the contents of
11075 an array, as they are accessed. This can be done by overriding
11079 var pets = ['dog', 'cat', 'fish'];
11080 var ap = Ember.ArrayProxy.create({
11081 content: Ember.A(pets),
11082 objectAtContent: function(idx) {
11083 return this.get('content').objectAt(idx).toUpperCase();
11087 ap.get('firstObject'); // . 'DOG'
11092 @extends Ember.Object
11093 @uses Ember.MutableArray
11095 Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,
11096 /** @scope Ember.ArrayProxy.prototype */ {
11099 The content array. Must be an object that implements `Ember.Array` and/or
11100 `Ember.MutableArray.`
11108 The array that the proxy pretends to be. In the default `ArrayProxy`
11109 implementation, this and `content` are the same. Subclasses of `ArrayProxy`
11110 can override this property to provide things like sorting and filtering.
11112 @property arrangedContent
11114 arrangedContent: Ember.computed('content', function() {
11115 return get(this, 'content');
11119 Should actually retrieve the object at the specified index from the
11120 content. You can override this method in subclasses to transform the
11121 content item to something new.
11123 This method will only be called if content is non-`null`.
11125 @method objectAtContent
11126 @param {Number} idx The index to retrieve.
11127 @return {Object} the value or undefined if none found
11129 objectAtContent: function(idx) {
11130 return get(this, 'arrangedContent').objectAt(idx);
11134 Should actually replace the specified objects on the content array.
11135 You can override this method in subclasses to transform the content item
11136 into something new.
11138 This method will only be called if content is non-`null`.
11140 @method replaceContent
11141 @param {Number} idx The starting index
11142 @param {Number} amt The number of items to remove from the content.
11143 @param {Array} objects Optional array of objects to insert or null if no
11147 replaceContent: function(idx, amt, objects) {
11148 get(this, 'content').replace(idx, amt, objects);
11154 Invoked when the content property is about to change. Notifies observers that the
11155 entire array content will change.
11157 @method _contentWillChange
11159 _contentWillChange: Ember.beforeObserver(function() {
11160 this._teardownContent();
11163 _teardownContent: function() {
11164 var content = get(this, 'content');
11167 content.removeArrayObserver(this, {
11168 willChange: 'contentArrayWillChange',
11169 didChange: 'contentArrayDidChange'
11174 contentArrayWillChange: Ember.K,
11175 contentArrayDidChange: Ember.K,
11180 Invoked when the content property changes. Notifies observers that the
11181 entire array content has changed.
11183 @method _contentDidChange
11185 _contentDidChange: Ember.observer(function() {
11186 var content = get(this, 'content');
11188 Ember.assert("Can't set ArrayProxy's content to itself", content !== this);
11190 this._setupContent();
11193 _setupContent: function() {
11194 var content = get(this, 'content');
11197 content.addArrayObserver(this, {
11198 willChange: 'contentArrayWillChange',
11199 didChange: 'contentArrayDidChange'
11204 _arrangedContentWillChange: Ember.beforeObserver(function() {
11205 var arrangedContent = get(this, 'arrangedContent'),
11206 len = arrangedContent ? get(arrangedContent, 'length') : 0;
11208 this.arrangedContentArrayWillChange(this, 0, len, undefined);
11209 this.arrangedContentWillChange(this);
11211 this._teardownArrangedContent(arrangedContent);
11212 }, 'arrangedContent'),
11214 _arrangedContentDidChange: Ember.observer(function() {
11215 var arrangedContent = get(this, 'arrangedContent'),
11216 len = arrangedContent ? get(arrangedContent, 'length') : 0;
11218 Ember.assert("Can't set ArrayProxy's content to itself", arrangedContent !== this);
11220 this._setupArrangedContent();
11222 this.arrangedContentDidChange(this);
11223 this.arrangedContentArrayDidChange(this, 0, undefined, len);
11224 }, 'arrangedContent'),
11226 _setupArrangedContent: function() {
11227 var arrangedContent = get(this, 'arrangedContent');
11229 if (arrangedContent) {
11230 arrangedContent.addArrayObserver(this, {
11231 willChange: 'arrangedContentArrayWillChange',
11232 didChange: 'arrangedContentArrayDidChange'
11237 _teardownArrangedContent: function() {
11238 var arrangedContent = get(this, 'arrangedContent');
11240 if (arrangedContent) {
11241 arrangedContent.removeArrayObserver(this, {
11242 willChange: 'arrangedContentArrayWillChange',
11243 didChange: 'arrangedContentArrayDidChange'
11248 arrangedContentWillChange: Ember.K,
11249 arrangedContentDidChange: Ember.K,
11251 objectAt: function(idx) {
11252 return get(this, 'content') && this.objectAtContent(idx);
11255 length: Ember.computed(function() {
11256 var arrangedContent = get(this, 'arrangedContent');
11257 return arrangedContent ? get(arrangedContent, 'length') : 0;
11258 // No dependencies since Enumerable notifies length of change
11261 replace: function(idx, amt, objects) {
11262 Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', this.get('content'));
11263 if (get(this, 'content')) this.replaceContent(idx, amt, objects);
11267 arrangedContentArrayWillChange: function(item, idx, removedCnt, addedCnt) {
11268 this.arrayContentWillChange(idx, removedCnt, addedCnt);
11271 arrangedContentArrayDidChange: function(item, idx, removedCnt, addedCnt) {
11272 this.arrayContentDidChange(idx, removedCnt, addedCnt);
11277 this._setupContent();
11278 this._setupArrangedContent();
11281 willDestroy: function() {
11282 this._teardownArrangedContent();
11283 this._teardownContent();
11295 @submodule ember-runtime
11298 var get = Ember.get,
11300 fmt = Ember.String.fmt,
11301 addBeforeObserver = Ember.addBeforeObserver,
11302 addObserver = Ember.addObserver,
11303 removeBeforeObserver = Ember.removeBeforeObserver,
11304 removeObserver = Ember.removeObserver,
11305 propertyWillChange = Ember.propertyWillChange,
11306 propertyDidChange = Ember.propertyDidChange;
11308 function contentPropertyWillChange(content, contentKey) {
11309 var key = contentKey.slice(8); // remove "content."
11310 if (key in this) { return; } // if shadowed in proxy
11311 propertyWillChange(this, key);
11314 function contentPropertyDidChange(content, contentKey) {
11315 var key = contentKey.slice(8); // remove "content."
11316 if (key in this) { return; } // if shadowed in proxy
11317 propertyDidChange(this, key);
11321 `Ember.ObjectProxy` forwards all properties not defined by the proxy itself
11322 to a proxied `content` object.
11325 object = Ember.Object.create({
11329 proxy = Ember.ObjectProxy.create({
11333 // Access and change existing properties
11334 proxy.get('name') // 'Foo'
11335 proxy.set('name', 'Bar');
11336 object.get('name') // 'Bar'
11338 // Create new 'description' property on `object`
11339 proxy.set('description', 'Foo is a whizboo baz');
11340 object.get('description') // 'Foo is a whizboo baz'
11343 While `content` is unset, setting a property to be delegated will throw an
11347 proxy = Ember.ObjectProxy.create({
11351 proxy.set('flag', true);
11352 proxy.get('flag'); // true
11353 proxy.get('foo'); // undefined
11354 proxy.set('foo', 'data'); // throws Error
11357 Delegated properties can be bound to and will change when content is updated.
11359 Computed properties on the proxy itself can depend on delegated properties.
11362 ProxyWithComputedProperty = Ember.ObjectProxy.extend({
11363 fullName: function () {
11364 var firstName = this.get('firstName'),
11365 lastName = this.get('lastName');
11366 if (firstName && lastName) {
11367 return firstName + ' ' + lastName;
11369 return firstName || lastName;
11370 }.property('firstName', 'lastName')
11373 proxy = ProxyWithComputedProperty.create();
11375 proxy.get('fullName'); // undefined
11376 proxy.set('content', {
11377 firstName: 'Tom', lastName: 'Dale'
11378 }); // triggers property change for fullName on proxy
11380 proxy.get('fullName'); // 'Tom Dale'
11385 @extends Ember.Object
11387 Ember.ObjectProxy = Ember.Object.extend(
11388 /** @scope Ember.ObjectProxy.prototype */ {
11390 The object whose properties will be forwarded.
11397 _contentDidChange: Ember.observer(function() {
11398 Ember.assert("Can't set ObjectProxy's content to itself", this.get('content') !== this);
11401 willWatchProperty: function (key) {
11402 var contentKey = 'content.' + key;
11403 addBeforeObserver(this, contentKey, null, contentPropertyWillChange);
11404 addObserver(this, contentKey, null, contentPropertyDidChange);
11407 didUnwatchProperty: function (key) {
11408 var contentKey = 'content.' + key;
11409 removeBeforeObserver(this, contentKey, null, contentPropertyWillChange);
11410 removeObserver(this, contentKey, null, contentPropertyDidChange);
11413 unknownProperty: function (key) {
11414 var content = get(this, 'content');
11416 return get(content, key);
11420 setUnknownProperty: function (key, value) {
11421 var content = get(this, 'content');
11422 Ember.assert(fmt("Cannot delegate set('%@', %@) to the 'content' property of object proxy %@: its 'content' is undefined.", [key, value, this]), content);
11423 return set(content, key, value);
11434 @submodule ember-runtime
11438 var set = Ember.set, get = Ember.get, guidFor = Ember.guidFor;
11439 var forEach = Ember.EnumerableUtils.forEach;
11441 var EachArray = Ember.Object.extend(Ember.Array, {
11443 init: function(content, keyName, owner) {
11445 this._keyName = keyName;
11446 this._owner = owner;
11447 this._content = content;
11450 objectAt: function(idx) {
11451 var item = this._content.objectAt(idx);
11452 return item && get(item, this._keyName);
11455 length: Ember.computed(function() {
11456 var content = this._content;
11457 return content ? get(content, 'length') : 0;
11462 var IS_OBSERVER = /^.+:(before|change)$/;
11464 function addObserverForContentKey(content, keyName, proxy, idx, loc) {
11465 var objects = proxy._objects, guid;
11466 if (!objects) objects = proxy._objects = {};
11468 while(--loc>=idx) {
11469 var item = content.objectAt(loc);
11471 Ember.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
11472 Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange');
11474 // keep track of the indicies each item was found at so we can map
11475 // it back when the obj changes.
11476 guid = guidFor(item);
11477 if (!objects[guid]) objects[guid] = [];
11478 objects[guid].push(loc);
11483 function removeObserverForContentKey(content, keyName, proxy, idx, loc) {
11484 var objects = proxy._objects;
11485 if (!objects) objects = proxy._objects = {};
11486 var indicies, guid;
11488 while(--loc>=idx) {
11489 var item = content.objectAt(loc);
11491 Ember.removeBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
11492 Ember.removeObserver(item, keyName, proxy, 'contentKeyDidChange');
11494 guid = guidFor(item);
11495 indicies = objects[guid];
11496 indicies[indicies.indexOf(loc)] = null;
11502 This is the object instance returned when you get the `@each` property on an
11503 array. It uses the unknownProperty handler to automatically create
11504 EachArray instances for property names.
11509 @extends Ember.Object
11511 Ember.EachProxy = Ember.Object.extend({
11513 init: function(content) {
11515 this._content = content;
11516 content.addArrayObserver(this);
11518 // in case someone is already observing some keys make sure they are
11520 forEach(Ember.watchedEvents(this), function(eventName) {
11521 this.didAddListener(eventName);
11526 You can directly access mapped properties by simply requesting them.
11527 The `unknownProperty` handler will generate an EachArray of each item.
11529 @method unknownProperty
11530 @param keyName {String}
11531 @param value {anything}
11533 unknownProperty: function(keyName, value) {
11535 ret = new EachArray(this._content, keyName, this);
11536 Ember.defineProperty(this, keyName, null, ret);
11537 this.beginObservingContentKey(keyName);
11541 // ..........................................................
11543 // Invokes whenever the content array itself changes.
11545 arrayWillChange: function(content, idx, removedCnt, addedCnt) {
11546 var keys = this._keys, key, array, lim;
11548 lim = removedCnt>0 ? idx+removedCnt : -1;
11549 Ember.beginPropertyChanges(this);
11552 if (!keys.hasOwnProperty(key)) { continue; }
11554 if (lim>0) removeObserverForContentKey(content, key, this, idx, lim);
11556 Ember.propertyWillChange(this, key);
11559 Ember.propertyWillChange(this._content, '@each');
11560 Ember.endPropertyChanges(this);
11563 arrayDidChange: function(content, idx, removedCnt, addedCnt) {
11564 var keys = this._keys, key, array, lim;
11566 lim = addedCnt>0 ? idx+addedCnt : -1;
11567 Ember.beginPropertyChanges(this);
11570 if (!keys.hasOwnProperty(key)) { continue; }
11572 if (lim>0) addObserverForContentKey(content, key, this, idx, lim);
11574 Ember.propertyDidChange(this, key);
11577 Ember.propertyDidChange(this._content, '@each');
11578 Ember.endPropertyChanges(this);
11581 // ..........................................................
11582 // LISTEN FOR NEW OBSERVERS AND OTHER EVENT LISTENERS
11583 // Start monitoring keys based on who is listening...
11585 didAddListener: function(eventName) {
11586 if (IS_OBSERVER.test(eventName)) {
11587 this.beginObservingContentKey(eventName.slice(0, -7));
11591 didRemoveListener: function(eventName) {
11592 if (IS_OBSERVER.test(eventName)) {
11593 this.stopObservingContentKey(eventName.slice(0, -7));
11597 // ..........................................................
11598 // CONTENT KEY OBSERVING
11599 // Actual watch keys on the source content.
11601 beginObservingContentKey: function(keyName) {
11602 var keys = this._keys;
11603 if (!keys) keys = this._keys = {};
11604 if (!keys[keyName]) {
11606 var content = this._content,
11607 len = get(content, 'length');
11608 addObserverForContentKey(content, keyName, this, 0, len);
11614 stopObservingContentKey: function(keyName) {
11615 var keys = this._keys;
11616 if (keys && (keys[keyName]>0) && (--keys[keyName]<=0)) {
11617 var content = this._content,
11618 len = get(content, 'length');
11619 removeObserverForContentKey(content, keyName, this, 0, len);
11623 contentKeyWillChange: function(obj, keyName) {
11624 Ember.propertyWillChange(this, keyName);
11627 contentKeyDidChange: function(obj, keyName) {
11628 Ember.propertyDidChange(this, keyName);
11642 @submodule ember-runtime
11646 var get = Ember.get, set = Ember.set;
11648 // Add Ember.Array to Array.prototype. Remove methods with native
11649 // implementations and supply some more optimized versions of generic methods
11650 // because they are so common.
11651 var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember.Copyable, {
11653 // because length is a built-in property we need to know to just get the
11654 // original property.
11655 get: function(key) {
11656 if (key==='length') return this.length;
11657 else if ('number' === typeof key) return this[key];
11658 else return this._super(key);
11661 objectAt: function(idx) {
11665 // primitive for array support.
11666 replace: function(idx, amt, objects) {
11668 if (this.isFrozen) throw Ember.FROZEN_ERROR ;
11670 // if we replaced exactly the same number of items, then pass only the
11671 // replaced range. Otherwise, pass the full remaining array length
11672 // since everything has shifted
11673 var len = objects ? get(objects, 'length') : 0;
11674 this.arrayContentWillChange(idx, amt, len);
11676 if (!objects || objects.length === 0) {
11677 this.splice(idx, amt) ;
11679 var args = [idx, amt].concat(objects) ;
11680 this.splice.apply(this,args) ;
11683 this.arrayContentDidChange(idx, amt, len);
11687 // If you ask for an unknown property, then try to collect the value
11688 // from member items.
11689 unknownProperty: function(key, value) {
11690 var ret;// = this.reducedProperty(key, value) ;
11691 if ((value !== undefined) && ret === undefined) {
11692 ret = this[key] = value;
11697 // If browser did not implement indexOf natively, then override with
11698 // specialized version
11699 indexOf: function(object, startAt) {
11700 var idx, len = this.length;
11702 if (startAt === undefined) startAt = 0;
11703 else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
11704 if (startAt < 0) startAt += len;
11706 for(idx=startAt;idx<len;idx++) {
11707 if (this[idx] === object) return idx ;
11712 lastIndexOf: function(object, startAt) {
11713 var idx, len = this.length;
11715 if (startAt === undefined) startAt = len-1;
11716 else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
11717 if (startAt < 0) startAt += len;
11719 for(idx=startAt;idx>=0;idx--) {
11720 if (this[idx] === object) return idx ;
11725 copy: function(deep) {
11727 return this.map(function(item){ return Ember.copy(item, true); });
11730 return this.slice();
11734 // Remove any methods implemented natively so we don't override them
11735 var ignore = ['length'];
11736 Ember.EnumerableUtils.forEach(NativeArray.keys(), function(methodName) {
11737 if (Array.prototype[methodName]) ignore.push(methodName);
11740 if (ignore.length>0) {
11741 NativeArray = NativeArray.without.apply(NativeArray, ignore);
11745 The NativeArray mixin contains the properties needed to to make the native
11746 Array support Ember.MutableArray and all of its dependent APIs. Unless you
11747 have `Ember.EXTEND_PROTOTYPES or `Ember.EXTEND_PROTOTYPES.Array` set to
11748 false, this will be applied automatically. Otherwise you can apply the mixin
11749 at anytime by calling `Ember.NativeArray.activate`.
11753 @extends Ember.Mixin
11754 @uses Ember.MutableArray
11755 @uses Ember.MutableEnumerable
11756 @uses Ember.Copyable
11757 @uses Ember.Freezable
11759 Ember.NativeArray = NativeArray;
11762 Creates an `Ember.NativeArray` from an Array like object.
11763 Does not modify the original object.
11767 @return {Ember.NativeArray}
11769 Ember.A = function(arr){
11770 if (arr === undefined) { arr = []; }
11771 return Ember.Array.detect(arr) ? arr : Ember.NativeArray.apply(arr);
11775 Activates the mixin on the Array.prototype if not already applied. Calling
11776 this method more than once is safe.
11779 @for Ember.NativeArray
11783 Ember.NativeArray.activate = function() {
11784 NativeArray.apply(Array.prototype);
11786 Ember.A = function(arr) { return arr || []; };
11789 if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
11790 Ember.NativeArray.activate();
11799 var DeferredMixin = Ember.DeferredMixin, // mixins/deferred
11800 EmberObject = Ember.Object, // system/object
11803 var Deferred = Ember.Object.extend(DeferredMixin);
11805 Deferred.reopenClass({
11806 promise: function(callback, binding) {
11807 var deferred = Deferred.create();
11808 callback.call(binding, deferred);
11809 return get(deferred, 'promise');
11813 Ember.Deferred = Deferred;
11822 @submodule ember-runtime
11825 var loadHooks = {};
11831 @param name {String} name of hook
11832 @param callback {Function} callback to be called
11834 Ember.onLoad = function(name, callback) {
11837 loadHooks[name] = loadHooks[name] || Ember.A();
11838 loadHooks[name].pushObject(callback);
11840 if (object = loaded[name]) {
11846 @method runLoadHooks
11848 @param name {String} name of hook
11849 @param object {Object} object to pass to callbacks
11851 Ember.runLoadHooks = function(name, object) {
11854 loaded[name] = object;
11856 if (hooks = loadHooks[name]) {
11857 loadHooks[name].forEach(function(callback) {
11874 var get = Ember.get;
11878 @submodule ember-runtime
11882 `Ember.ControllerMixin` provides a standard interface for all classes that
11883 compose Ember's controller layer: `Ember.Controller`,
11884 `Ember.ArrayController`, and `Ember.ObjectController`.
11886 Within an `Ember.Router`-managed application single shared instaces of every
11887 Controller object in your application's namespace will be added to the
11888 application's `Ember.Router` instance. See `Ember.Application#initialize`
11889 for additional information.
11893 By default a controller instance will be the rendering context
11894 for its associated `Ember.View.` This connection is made during calls to
11895 `Ember.ControllerMixin#connectOutlet`.
11897 Within the view's template, the `Ember.View` instance can be accessed
11898 through the controller with `{{view}}`.
11900 ## Target Forwarding
11902 By default a controller will target your application's `Ember.Router`
11903 instance. Calls to `{{action}}` within the template of a controller's view
11904 are forwarded to the router. See `Ember.Handlebars.helpers.action` for
11905 additional information.
11907 @class ControllerMixin
11909 @extends Ember.Mixin
11911 Ember.ControllerMixin = Ember.Mixin.create({
11913 The object to which events from the view should be sent.
11915 For example, when a Handlebars template uses the `{{action}}` helper,
11916 it will attempt to send the event to the view's controller's `target`.
11918 By default, a controller's `target` is set to the router after it is
11919 instantiated by `Ember.Application#initialize`.
11930 send: function(actionName) {
11931 var args = [].slice.call(arguments, 1), target;
11933 if (this[actionName]) {
11934 Ember.assert("The controller " + this + " does not have the action " + actionName, typeof this[actionName] === 'function');
11935 this[actionName].apply(this, args);
11936 } else if(target = get(this, 'target')) {
11937 Ember.assert("The target for controller " + this + " (" + target + ") did not define a `send` method", typeof target.send === 'function');
11938 target.send.apply(target, arguments);
11946 @extends Ember.Object
11947 @uses Ember.ControllerMixin
11949 Ember.Controller = Ember.Object.extend(Ember.ControllerMixin);
11958 @submodule ember-runtime
11961 var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
11964 `Ember.SortableMixin` provides a standard interface for array proxies
11965 to specify a sort order and maintain this sorting when objects are added,
11966 removed, or updated without changing the implicit order of their underlying
11971 {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'},
11972 {trackNumber: 2, title: 'Back in the U.S.S.R.'},
11973 {trackNumber: 3, title: 'Glass Onion'},
11976 songsController = Ember.ArrayController.create({
11978 sortProperties: ['trackNumber']
11981 songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
11983 songsController.addObject({trackNumber: 1, title: 'Dear Prudence'});
11984 songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'}
11987 @class SortableMixin
11989 @extends Ember.Mixin
11990 @uses Ember.MutableEnumerable
11992 Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
11993 sortProperties: null,
11994 sortAscending: true,
11996 orderBy: function(item1, item2) {
11998 sortProperties = get(this, 'sortProperties'),
11999 sortAscending = get(this, 'sortAscending');
12001 Ember.assert("you need to define `sortProperties`", !!sortProperties);
12003 forEach(sortProperties, function(propertyName) {
12004 if (result === 0) {
12005 result = Ember.compare(get(item1, propertyName), get(item2, propertyName));
12006 if ((result !== 0) && !sortAscending) {
12007 result = (-1) * result;
12015 destroy: function() {
12016 var content = get(this, 'content'),
12017 sortProperties = get(this, 'sortProperties');
12019 if (content && sortProperties) {
12020 forEach(content, function(item) {
12021 forEach(sortProperties, function(sortProperty) {
12022 Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
12027 return this._super();
12030 isSorted: Ember.computed('sortProperties', function() {
12031 return !!get(this, 'sortProperties');
12034 arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) {
12035 var content = get(this, 'content'),
12036 isSorted = get(this, 'isSorted'),
12037 sortProperties = get(this, 'sortProperties'),
12040 if (content && isSorted) {
12041 content = content.slice();
12042 content.sort(function(item1, item2) {
12043 return self.orderBy(item1, item2);
12045 forEach(content, function(item) {
12046 forEach(sortProperties, function(sortProperty) {
12047 Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
12050 return Ember.A(content);
12056 _contentWillChange: Ember.beforeObserver(function() {
12057 var content = get(this, 'content'),
12058 sortProperties = get(this, 'sortProperties');
12060 if (content && sortProperties) {
12061 forEach(content, function(item) {
12062 forEach(sortProperties, function(sortProperty) {
12063 Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
12071 sortAscendingWillChange: Ember.beforeObserver(function() {
12072 this._lastSortAscending = get(this, 'sortAscending');
12073 }, 'sortAscending'),
12075 sortAscendingDidChange: Ember.observer(function() {
12076 if (get(this, 'sortAscending') !== this._lastSortAscending) {
12077 var arrangedContent = get(this, 'arrangedContent');
12078 arrangedContent.reverseObjects();
12080 }, 'sortAscending'),
12082 contentArrayWillChange: function(array, idx, removedCount, addedCount) {
12083 var isSorted = get(this, 'isSorted');
12086 var arrangedContent = get(this, 'arrangedContent');
12087 var removedObjects = array.slice(idx, idx+removedCount);
12088 var sortProperties = get(this, 'sortProperties');
12090 forEach(removedObjects, function(item) {
12091 arrangedContent.removeObject(item);
12093 forEach(sortProperties, function(sortProperty) {
12094 Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
12099 return this._super(array, idx, removedCount, addedCount);
12102 contentArrayDidChange: function(array, idx, removedCount, addedCount) {
12103 var isSorted = get(this, 'isSorted'),
12104 sortProperties = get(this, 'sortProperties');
12107 var addedObjects = array.slice(idx, idx+addedCount);
12108 var arrangedContent = get(this, 'arrangedContent');
12110 forEach(addedObjects, function(item) {
12111 this.insertItemSorted(item);
12113 forEach(sortProperties, function(sortProperty) {
12114 Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
12119 return this._super(array, idx, removedCount, addedCount);
12122 insertItemSorted: function(item) {
12123 var arrangedContent = get(this, 'arrangedContent');
12124 var length = get(arrangedContent, 'length');
12126 var idx = this._binarySearch(item, 0, length);
12127 arrangedContent.insertAt(idx, item);
12130 contentItemSortPropertyDidChange: function(item) {
12131 var arrangedContent = get(this, 'arrangedContent'),
12132 oldIndex = arrangedContent.indexOf(item),
12133 leftItem = arrangedContent.objectAt(oldIndex - 1),
12134 rightItem = arrangedContent.objectAt(oldIndex + 1),
12135 leftResult = leftItem && this.orderBy(item, leftItem),
12136 rightResult = rightItem && this.orderBy(item, rightItem);
12138 if (leftResult < 0 || rightResult > 0) {
12139 arrangedContent.removeObject(item);
12140 this.insertItemSorted(item);
12144 _binarySearch: function(item, low, high) {
12145 var mid, midItem, res, arrangedContent;
12147 if (low === high) {
12151 arrangedContent = get(this, 'arrangedContent');
12153 mid = low + Math.floor((high - low) / 2);
12154 midItem = arrangedContent.objectAt(mid);
12156 res = this.orderBy(midItem, item);
12159 return this._binarySearch(item, mid+1, high);
12160 } else if (res > 0) {
12161 return this._binarySearch(item, low, mid);
12175 @submodule ember-runtime
12178 var get = Ember.get, set = Ember.set, isGlobalPath = Ember.isGlobalPath,
12179 forEach = Ember.EnumerableUtils.forEach, replace = Ember.EnumerableUtils.replace;
12182 `Ember.ArrayController` provides a way for you to publish a collection of
12183 objects so that you can easily bind to the collection from a Handlebars
12184 `#each` helper, an `Ember.CollectionView`, or other controllers.
12186 The advantage of using an `ArrayController` is that you only have to set up
12187 your view bindings once; to change what's displayed, simply swap out the
12188 `content` property on the controller.
12190 For example, imagine you wanted to display a list of items fetched via an XHR
12191 request. Create an `Ember.ArrayController` and set its `content` property:
12194 MyApp.listController = Ember.ArrayController.create();
12196 $.get('people.json', function(data) {
12197 MyApp.listController.set('content', data);
12201 Then, create a view that binds to your new controller:
12204 {{#each MyApp.listController}}
12205 {{firstName}} {{lastName}}
12209 Although you are binding to the controller, the behavior of this controller
12210 is to pass through any methods or properties to the underlying array. This
12211 capability comes from `Ember.ArrayProxy`, which this class inherits from.
12213 Sometimes you want to display computed properties within the body of an
12214 `#each` helper that depend on the underlying items in `content`, but are not
12215 present on those items. To do this, set `itemController` to the name of a
12216 controller (probably an `ObjectController`) that will wrap each individual item.
12221 {{#each post in controller}}
12222 <li>{{title}} ({{titleLength}} characters)</li>
12227 App.PostsController = Ember.ArrayController.extend({
12228 itemController: 'post'
12231 App.PostController = Ember.ObjectController.extend({
12232 // the `title` property will be proxied to the underlying post.
12234 titleLength: function() {
12235 return this.get('title').length;
12236 }.property('title')
12240 In some cases it is helpful to return a different `itemController` depending
12241 on the particular item. Subclasses can do this by overriding
12242 `lookupItemController`.
12247 App.MyArrayController = Ember.ArrayController.extend({
12248 lookupItemController: function( object ) {
12249 if (object.get('isSpecial')) {
12250 return "special"; // use App.SpecialController
12252 return "regular"; // use App.RegularController
12258 @class ArrayController
12260 @extends Ember.ArrayProxy
12261 @uses Ember.SortableMixin
12262 @uses Ember.ControllerMixin
12265 Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
12266 Ember.SortableMixin, {
12269 The controller used to wrap items, if any.
12271 @property itemController
12275 itemController: null,
12278 Return the name of the controller to wrap items, or `null` if items should
12279 be returned directly. The default implementation simply returns the
12280 `itemController` property, but subclasses can override this method to return
12281 different controllers for different objects.
12286 App.MyArrayController = Ember.ArrayController.extend({
12287 lookupItemController: function( object ) {
12288 if (object.get('isSpecial')) {
12289 return "special"; // use App.SpecialController
12291 return "regular"; // use App.RegularController
12301 lookupItemController: function(object) {
12302 return get(this, 'itemController');
12305 objectAtContent: function(idx) {
12306 var length = get(this, 'length'),
12307 object = get(this,'arrangedContent').objectAt(idx),
12308 controllerClass = this.lookupItemController(object);
12310 if (controllerClass && idx < length) {
12311 return this.controllerAt(idx, object, controllerClass);
12313 // When controllerClass is falsy we have not opted in to using item
12314 // controllers, so return the object directly. However, when
12315 // controllerClass is defined but the index is out of range, we want to
12316 // return the "out of range" value, whatever that might be. Rather than
12317 // make assumptions (e.g. guessing `null` or `undefined`) we defer this to
12318 // `arrangedContent`.
12323 arrangedContentDidChange: function() {
12325 this._resetSubContainers();
12328 arrayContentDidChange: function(idx, removedCnt, addedCnt) {
12329 var subContainers = get(this, 'subContainers'),
12330 subContainersToRemove = subContainers.slice(idx, idx+removedCnt);
12332 forEach(subContainersToRemove, function(subContainer) {
12333 if (subContainer) { subContainer.destroy(); }
12336 replace(subContainers, idx, removedCnt, new Array(addedCnt));
12338 // The shadow array of subcontainers must be updated before we trigger
12339 // observers, otherwise observers will get the wrong subcontainer when
12340 // calling `objectAt`
12341 this._super(idx, removedCnt, addedCnt);
12346 this._resetSubContainers();
12349 controllerAt: function(idx, object, controllerClass) {
12350 var container = get(this, 'container'),
12351 subContainers = get(this, 'subContainers'),
12352 subContainer = subContainers[idx],
12355 if (!subContainer) {
12356 subContainer = subContainers[idx] = container.child();
12359 controller = subContainer.lookup("controller:" + controllerClass);
12361 throw new Error('Could not resolve itemController: "' + controllerClass + '"');
12364 controller.set('target', this);
12365 controller.set('content', object);
12370 subContainers: null,
12372 _resetSubContainers: function() {
12373 var subContainers = get(this, 'subContainers');
12375 if (subContainers) {
12376 forEach(subContainers, function(subContainer) {
12377 if (subContainer) { subContainer.destroy(); }
12381 this.set('subContainers', Ember.A());
12392 @submodule ember-runtime
12396 `Ember.ObjectController` is part of Ember's Controller layer. A single shared
12397 instance of each `Ember.ObjectController` subclass in your application's
12398 namespace will be created at application initialization and be stored on your
12399 application's `Ember.Router` instance.
12401 `Ember.ObjectController` derives its functionality from its superclass
12402 `Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin.
12404 @class ObjectController
12406 @extends Ember.ObjectProxy
12407 @uses Ember.ControllerMixin
12409 Ember.ObjectController = Ember.ObjectProxy.extend(Ember.ControllerMixin);
12426 @submodule ember-runtime
12427 @requires ember-metal
12435 @submodule ember-views
12438 var jQuery = Ember.imports.jQuery;
12439 Ember.assert("Ember Views require jQuery 1.7 (>= 1.7.2), 1.8 or 1.9", jQuery && (jQuery().jquery.match(/^1\.(7(?!$)(?!\.[01])|8|9)(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
12456 @submodule ember-views
12459 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
12460 var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
12462 // Copies the `dataTransfer` property from a browser event object onto the
12463 // jQuery event object for the specified events
12464 Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
12465 Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
12475 @submodule ember-views
12478 /*** BEGIN METAMORPH HELPERS ***/
12480 // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
12481 // is a "zero-scope" element. This problem can be worked around by making
12482 // the first node an invisible text node. We, like Modernizr, use ­
12483 var needsShy = (function(){
12484 var testEl = document.createElement('div');
12485 testEl.innerHTML = "<div></div>";
12486 testEl.firstChild.innerHTML = "<script></script>";
12487 return testEl.firstChild.innerHTML === '';
12490 // IE 8 (and likely earlier) likes to move whitespace preceeding
12491 // a script tag to appear after it. This means that we can
12492 // accidentally remove whitespace when updating a morph.
12493 var movesWhitespace = (function() {
12494 var testEl = document.createElement('div');
12495 testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
12496 return testEl.childNodes[0].nodeValue === 'Test:' &&
12497 testEl.childNodes[2].nodeValue === ' Value';
12500 // Use this to find children by ID instead of using jQuery
12501 var findChildById = function(element, id) {
12502 if (element.getAttribute('id') === id) { return element; }
12504 var len = element.childNodes.length, idx, node, found;
12505 for (idx=0; idx<len; idx++) {
12506 node = element.childNodes[idx];
12507 found = node.nodeType === 1 && findChildById(node, id);
12508 if (found) { return found; }
12512 var setInnerHTMLWithoutFix = function(element, html) {
12514 html = '­' + html;
12518 if (movesWhitespace) {
12519 // Right now we only check for script tags with ids with the
12520 // goal of targeting morphs.
12521 html = html.replace(/(\s+)(<script id='([^']+)')/g, function(match, spaces, tag, id) {
12522 matches.push([id, spaces]);
12527 element.innerHTML = html;
12529 // If we have to do any whitespace adjustments do them now
12530 if (matches.length > 0) {
12531 var len = matches.length, idx;
12532 for (idx=0; idx<len; idx++) {
12533 var script = findChildById(element, matches[idx][0]),
12534 node = document.createTextNode(matches[idx][1]);
12535 script.parentNode.insertBefore(node, script);
12540 var shyElement = element.firstChild;
12541 while (shyElement.nodeType === 1 && !shyElement.nodeName) {
12542 shyElement = shyElement.firstChild;
12544 if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") {
12545 shyElement.nodeValue = shyElement.nodeValue.slice(1);
12550 /*** END METAMORPH HELPERS */
12553 var innerHTMLTags = {};
12554 var canSetInnerHTML = function(tagName) {
12555 if (innerHTMLTags[tagName] !== undefined) {
12556 return innerHTMLTags[tagName];
12561 // IE 8 and earlier don't allow us to do innerHTML on select
12562 if (tagName.toLowerCase() === 'select') {
12563 var el = document.createElement('select');
12564 setInnerHTMLWithoutFix(el, '<option value="test">Test</option>');
12565 canSet = el.options.length === 1;
12568 innerHTMLTags[tagName] = canSet;
12573 var setInnerHTML = function(element, html) {
12574 var tagName = element.tagName;
12576 if (canSetInnerHTML(tagName)) {
12577 setInnerHTMLWithoutFix(element, html);
12579 Ember.assert("Can't set innerHTML on "+element.tagName+" in this browser", element.outerHTML);
12581 var startTag = element.outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0],
12582 endTag = '</'+tagName+'>';
12584 var wrapper = document.createElement('div');
12585 setInnerHTMLWithoutFix(wrapper, startTag + html + endTag);
12586 element = wrapper.firstChild;
12587 while (element.tagName !== tagName) {
12588 element = element.nextSibling;
12595 function isSimpleClick(event) {
12596 var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey,
12597 secondaryClick = event.which > 1; // IE9 may return undefined
12599 return !modifier && !secondaryClick;
12602 Ember.ViewUtils = {
12603 setInnerHTML: setInnerHTML,
12604 isSimpleClick: isSimpleClick
12614 @submodule ember-views
12617 var get = Ember.get, set = Ember.set;
12618 var indexOf = Ember.ArrayPolyfills.indexOf;
12624 var ClassSet = function() {
12629 ClassSet.prototype = {
12630 add: function(string) {
12631 if (string in this.seen) { return; }
12632 this.seen[string] = true;
12634 this.list.push(string);
12637 toDOM: function() {
12638 return this.list.join(" ");
12643 `Ember.RenderBuffer` gathers information regarding the a view and generates the
12644 final representation. `Ember.RenderBuffer` will generate HTML which can be pushed
12647 @class RenderBuffer
12651 Ember.RenderBuffer = function(tagName) {
12652 return new Ember._RenderBuffer(tagName);
12655 Ember._RenderBuffer = function(tagName) {
12656 this.tagNames = [tagName || null];
12660 Ember._RenderBuffer.prototype =
12661 /** @scope Ember.RenderBuffer.prototype */ {
12663 // The root view's element
12669 An internal set used to de-dupe class names when `addClass()` is
12670 used. After each call to `addClass()`, the `classes` property
12673 @property elementClasses
12677 elementClasses: null,
12680 Array of class names which will be applied in the class attribute.
12682 You can use `setClasses()` to set this property directly. If you
12683 use `addClass()`, it will be maintained for you.
12692 The id in of the element, to be applied in the id attribute.
12694 You should not set this property yourself, rather, you should use
12695 the `id()` method of `Ember.RenderBuffer`.
12697 @property elementId
12704 A hash keyed on the name of the attribute and whose value will be
12705 applied to that attribute. For example, if you wanted to apply a
12706 `data-view="Foo.bar"` property to an element, you would set the
12707 elementAttributes hash to `{'data-view':'Foo.bar'}`.
12709 You should not maintain this hash yourself, rather, you should use
12710 the `attr()` method of `Ember.RenderBuffer`.
12712 @property elementAttributes
12716 elementAttributes: null,
12719 The tagname of the element an instance of `Ember.RenderBuffer` represents.
12721 Usually, this gets set as the first parameter to `Ember.RenderBuffer`. For
12722 example, if you wanted to create a `p` tag, then you would call
12725 Ember.RenderBuffer('p')
12728 @property elementTag
12735 A hash keyed on the name of the style attribute and whose value will
12736 be applied to that attribute. For example, if you wanted to apply a
12737 `background-color:black;` style to an element, you would set the
12738 elementStyle hash to `{'background-color':'black'}`.
12740 You should not maintain this hash yourself, rather, you should use
12741 the `style()` method of `Ember.RenderBuffer`.
12743 @property elementStyle
12747 elementStyle: null,
12750 Nested `RenderBuffers` will set this to their parent `RenderBuffer`
12753 @property parentBuffer
12754 @type Ember._RenderBuffer
12756 parentBuffer: null,
12759 Adds a string of HTML to the `RenderBuffer`.
12762 @param {String} string HTML to push into the buffer
12765 push: function(string) {
12766 this.buffer.push(string);
12771 Adds a class to the buffer, which will be rendered to the class attribute.
12774 @param {String} className Class name to add to the buffer
12777 addClass: function(className) {
12778 // lazily create elementClasses
12779 var elementClasses = this.elementClasses = (this.elementClasses || new ClassSet());
12780 this.elementClasses.add(className);
12781 this.classes = this.elementClasses.list;
12786 setClasses: function(classNames) {
12787 this.classes = classNames;
12791 Sets the elementID to be used for the element.
12798 this.elementId = id;
12802 // duck type attribute functionality like jQuery so a render buffer
12803 // can be used like a jQuery object in attribute binding scenarios.
12806 Adds an attribute which will be rendered to the element.
12809 @param {String} name The name of the attribute
12810 @param {String} value The value to add to the attribute
12812 @return {Ember.RenderBuffer|String} this or the current attribute value
12814 attr: function(name, value) {
12815 var attributes = this.elementAttributes = (this.elementAttributes || {});
12817 if (arguments.length === 1) {
12818 return attributes[name];
12820 attributes[name] = value;
12827 Remove an attribute from the list of attributes to render.
12830 @param {String} name The name of the attribute
12833 removeAttr: function(name) {
12834 var attributes = this.elementAttributes;
12835 if (attributes) { delete attributes[name]; }
12841 Adds a style to the style attribute which will be rendered to the element.
12844 @param {String} name Name of the style
12845 @param {String} value
12848 style: function(name, value) {
12849 var style = this.elementStyle = (this.elementStyle || {});
12851 this.elementStyle[name] = value;
12855 begin: function(tagName) {
12856 this.tagNames.push(tagName || null);
12860 pushOpeningTag: function() {
12861 var tagName = this.currentTagName();
12862 if (!tagName) { return; }
12864 if (!this._element && this.buffer.length === 0) {
12865 this._element = this.generateElement();
12869 var buffer = this.buffer,
12870 id = this.elementId,
12871 classes = this.classes,
12872 attrs = this.elementAttributes,
12873 style = this.elementStyle,
12876 buffer.push('<' + tagName);
12879 buffer.push(' id="' + this._escapeAttribute(id) + '"');
12880 this.elementId = null;
12883 buffer.push(' class="' + this._escapeAttribute(classes.join(' ')) + '"');
12884 this.classes = null;
12888 buffer.push(' style="');
12890 for (prop in style) {
12891 if (style.hasOwnProperty(prop)) {
12892 buffer.push(prop + ':' + this._escapeAttribute(style[prop]) + ';');
12898 this.elementStyle = null;
12902 for (prop in attrs) {
12903 if (attrs.hasOwnProperty(prop)) {
12904 buffer.push(' ' + prop + '="' + this._escapeAttribute(attrs[prop]) + '"');
12908 this.elementAttributes = null;
12914 pushClosingTag: function() {
12915 var tagName = this.tagNames.pop();
12916 if (tagName) { this.buffer.push('</' + tagName + '>'); }
12919 currentTagName: function() {
12920 return this.tagNames[this.tagNames.length-1];
12923 generateElement: function() {
12924 var tagName = this.tagNames.pop(), // pop since we don't need to close
12925 element = document.createElement(tagName),
12926 $element = Ember.$(element),
12927 id = this.elementId,
12928 classes = this.classes,
12929 attrs = this.elementAttributes,
12930 style = this.elementStyle,
12931 styleBuffer = '', prop;
12934 $element.attr('id', id);
12935 this.elementId = null;
12938 $element.attr('class', classes.join(' '));
12939 this.classes = null;
12943 for (prop in style) {
12944 if (style.hasOwnProperty(prop)) {
12945 styleBuffer += (prop + ':' + style[prop] + ';');
12949 $element.attr('style', styleBuffer);
12951 this.elementStyle = null;
12955 for (prop in attrs) {
12956 if (attrs.hasOwnProperty(prop)) {
12957 $element.attr(prop, attrs[prop]);
12961 this.elementAttributes = null;
12969 @return {DOMElement} The element corresponding to the generated HTML
12972 element: function() {
12973 var html = this.innerString();
12976 this._element = Ember.ViewUtils.setInnerHTML(this._element, html);
12979 return this._element;
12983 Generates the HTML content for this buffer.
12986 @return {String} The generated HTML
12988 string: function() {
12989 if (this._element) {
12990 return this.element().outerHTML;
12992 return this.innerString();
12996 innerString: function() {
12997 return this.buffer.join('');
13000 _escapeAttribute: function(value) {
13001 // Stolen shamelessly from Handlebars
13011 var badChars = /&(?!\w+;)|[<>"'`]/g;
13012 var possible = /[&<>"'`]/;
13014 var escapeChar = function(chr) {
13015 return escape[chr] || "&";
13018 var string = value.toString();
13020 if(!possible.test(string)) { return string; }
13021 return string.replace(badChars, escapeChar);
13033 @submodule ember-views
13036 var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
13039 `Ember.EventDispatcher` handles delegating browser events to their
13040 corresponding `Ember.Views.` For example, when you click on a view,
13041 `Ember.EventDispatcher` ensures that that view's `mouseDown` method gets
13044 @class EventDispatcher
13047 @extends Ember.Object
13049 Ember.EventDispatcher = Ember.Object.extend(
13050 /** @scope Ember.EventDispatcher.prototype */{
13055 The root DOM element to which event listeners should be attached. Event
13056 listeners will be attached to the document unless this is overridden.
13058 Can be specified as a DOMElement or a selector string.
13060 The default body is a string since this may be evaluated before document.body
13063 @property rootElement
13067 rootElement: 'body',
13072 Sets up event listeners for standard browser events.
13074 This will be called after the browser sends a `DOMContentReady` event. By
13075 default, it will set up all of the listeners on the document body. If you
13076 would like to register the listeners on a different element, set the event
13077 dispatcher's `root` property.
13080 @param addedEvents {Hash}
13082 setup: function(addedEvents) {
13083 var event, events = {
13084 touchstart : 'touchStart',
13085 touchmove : 'touchMove',
13086 touchend : 'touchEnd',
13087 touchcancel : 'touchCancel',
13088 keydown : 'keyDown',
13090 keypress : 'keyPress',
13091 mousedown : 'mouseDown',
13092 mouseup : 'mouseUp',
13093 contextmenu : 'contextMenu',
13095 dblclick : 'doubleClick',
13096 mousemove : 'mouseMove',
13097 focusin : 'focusIn',
13098 focusout : 'focusOut',
13099 mouseenter : 'mouseEnter',
13100 mouseleave : 'mouseLeave',
13104 dragstart : 'dragStart',
13106 dragenter : 'dragEnter',
13107 dragleave : 'dragLeave',
13108 dragover : 'dragOver',
13110 dragend : 'dragEnd'
13113 Ember.$.extend(events, addedEvents || {});
13115 var rootElement = Ember.$(get(this, 'rootElement'));
13117 Ember.assert(fmt('You cannot use the same root element (%@) multiple times in an Ember.Application', [rootElement.selector || rootElement[0].tagName]), !rootElement.is('.ember-application'));
13118 Ember.assert('You cannot make a new Ember.Application using a root element that is a descendent of an existing Ember.Application', !rootElement.closest('.ember-application').length);
13119 Ember.assert('You cannot make a new Ember.Application using a root element that is an ancestor of an existing Ember.Application', !rootElement.find('.ember-application').length);
13121 rootElement.addClass('ember-application');
13123 Ember.assert('Unable to add "ember-application" class to rootElement. Make sure you set rootElement to the body or an element in the body.', rootElement.is('.ember-application'));
13125 for (event in events) {
13126 if (events.hasOwnProperty(event)) {
13127 this.setupHandler(rootElement, event, events[event]);
13135 Registers an event listener on the document. If the given event is
13136 triggered, the provided event handler will be triggered on the target view.
13138 If the target view does not implement the event handler, or if the handler
13139 returns `false`, the parent view will be called. The event will continue to
13140 bubble to each successive parent view until it reaches the top.
13142 For example, to have the `mouseDown` method called on the target view when
13143 a `mousedown` event is received from the browser, do the following:
13146 setupHandler('mousedown', 'mouseDown');
13149 @method setupHandler
13150 @param {Element} rootElement
13151 @param {String} event the browser-originated event to listen to
13152 @param {String} eventName the name of the method to call on the view
13154 setupHandler: function(rootElement, event, eventName) {
13157 rootElement.delegate('.ember-view', event + '.ember', function(evt, triggeringManager) {
13158 return Ember.handleErrors(function() {
13159 var view = Ember.View.views[this.id],
13160 result = true, manager = null;
13162 manager = self._findNearestEventManager(view,eventName);
13164 if (manager && manager !== triggeringManager) {
13165 result = self._dispatchEvent(manager, evt, eventName, view);
13167 result = self._bubbleEvent(view,evt,eventName);
13169 evt.stopPropagation();
13176 rootElement.delegate('[data-ember-action]', event + '.ember', function(evt) {
13177 return Ember.handleErrors(function() {
13178 var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'),
13179 action = Ember.Handlebars.ActionHelper.registeredActions[actionId],
13180 handler = action.handler;
13182 if (action.eventName === eventName) {
13183 return handler(evt);
13189 _findNearestEventManager: function(view, eventName) {
13190 var manager = null;
13193 manager = get(view, 'eventManager');
13194 if (manager && manager[eventName]) { break; }
13196 view = get(view, 'parentView');
13202 _dispatchEvent: function(object, evt, eventName, view) {
13205 var handler = object[eventName];
13206 if (Ember.typeOf(handler) === 'function') {
13207 result = handler.call(object, evt, view);
13208 // Do not preventDefault in eventManagers.
13209 evt.stopPropagation();
13212 result = this._bubbleEvent(view, evt, eventName);
13218 _bubbleEvent: function(view, evt, eventName) {
13219 return Ember.run(function() {
13220 return view.handleEvent(eventName, evt);
13224 destroy: function() {
13225 var rootElement = get(this, 'rootElement');
13226 Ember.$(rootElement).undelegate('.ember').removeClass('ember-application');
13227 return this._super();
13238 @submodule ember-views
13241 // Add a new named queue for rendering views that happens
13242 // after bindings have synced, and a queue for scheduling actions
13243 // that that should occur after view rendering.
13244 var queues = Ember.run.queues;
13245 queues.splice(Ember.$.inArray('actions', queues)+1, 0, 'render', 'afterRender');
13254 @submodule ember-views
13257 var get = Ember.get, set = Ember.set;
13259 // Original class declaration and documentation in runtime/lib/controllers/controller.js
13260 // NOTE: It may be possible with YUIDoc to combine docs in two locations
13263 Additional methods for the ControllerMixin
13265 @class ControllerMixin
13268 Ember.ControllerMixin.reopen({
13290 @submodule ember-views
13293 var get = Ember.get, set = Ember.set, addObserver = Ember.addObserver, removeObserver = Ember.removeObserver;
13294 var meta = Ember.meta, guidFor = Ember.guidFor, fmt = Ember.String.fmt;
13295 var a_slice = [].slice;
13296 var a_forEach = Ember.EnumerableUtils.forEach;
13297 var a_addObject = Ember.EnumerableUtils.addObject;
13299 var childViewsProperty = Ember.computed(function() {
13300 var childViews = this._childViews;
13302 var ret = Ember.A();
13304 a_forEach(childViews, function(view) {
13305 if (view.isVirtual) {
13306 ret.pushObjects(get(view, 'childViews'));
13315 Ember.warn("The VIEW_PRESERVES_CONTEXT flag has been removed and the functionality can no longer be disabled.", Ember.ENV.VIEW_PRESERVES_CONTEXT !== false);
13318 Global hash of shared templates. This will automatically be populated
13319 by the build tools so that you can store your Handlebars templates in
13320 separate files that get loaded into JavaScript at buildtime.
13322 @property TEMPLATES
13326 Ember.TEMPLATES = {};
13328 Ember.CoreView = Ember.Object.extend(Ember.Evented, {
13336 // Register the view for event handling. This hash is used by
13337 // Ember.EventDispatcher to dispatch incoming events.
13338 if (!this.isVirtual) Ember.View.views[this.elementId] = this;
13340 this.addBeforeObserver('elementId', function() {
13341 throw new Error("Changing a view's elementId after creation is not allowed");
13344 this.transitionTo('preRender');
13348 If the view is currently inserted into the DOM of a parent view, this
13349 property will point to the parent of the view.
13351 @property parentView
13355 parentView: Ember.computed(function() {
13356 var parent = this._parentView;
13358 if (parent && parent.isVirtual) {
13359 return get(parent, 'parentView');
13363 }).property('_parentView'),
13369 // return the current view, not including virtual views
13370 concreteView: Ember.computed(function() {
13371 if (!this.isVirtual) { return this; }
13372 else { return get(this, 'parentView'); }
13373 }).property('parentView').volatile(),
13375 instrumentName: 'core_view',
13377 instrumentDetails: function(hash) {
13378 hash.object = this.toString();
13384 Invoked by the view system when this view needs to produce an HTML
13385 representation. This method will create a new render buffer, if needed,
13386 then apply any default attributes, such as class names and visibility.
13387 Finally, the `render()` method is invoked, which is responsible for
13388 doing the bulk of the rendering.
13390 You should not need to override this method; instead, implement the
13391 `template` property, or if you need more control, override the `render`
13394 @method renderToBuffer
13395 @param {Ember.RenderBuffer} buffer the render buffer. If no buffer is
13396 passed, a default buffer, using the current view's `tagName`, will
13399 renderToBuffer: function(parentBuffer, bufferOperation) {
13400 var name = 'render.' + this.instrumentName,
13403 this.instrumentDetails(details);
13405 return Ember.instrument(name, details, function() {
13406 return this._renderToBuffer(parentBuffer, bufferOperation);
13410 _renderToBuffer: function(parentBuffer, bufferOperation) {
13413 // If this is the top-most view, start a new buffer. Otherwise,
13414 // create a new buffer relative to the original using the
13415 // provided buffer operation (for example, `insertAfter` will
13416 // insert a new buffer after the "parent buffer").
13417 var tagName = this.tagName;
13419 if (tagName === null || tagName === undefined) {
13423 var buffer = this.buffer = parentBuffer && parentBuffer.begin(tagName) || Ember.RenderBuffer(tagName);
13424 this.transitionTo('inBuffer', false);
13426 this.beforeRender(buffer);
13427 this.render(buffer);
13428 this.afterRender(buffer);
13436 Override the default event firing from `Ember.Evented` to
13437 also call methods with the given name.
13440 @param name {String}
13442 trigger: function(name) {
13443 this._super.apply(this, arguments);
13444 var method = this[name];
13446 var args = [], i, l;
13447 for (i = 1, l = arguments.length; i < l; i++) {
13448 args.push(arguments[i]);
13450 return method.apply(this, args);
13454 has: function(name) {
13455 return Ember.typeOf(this[name]) === 'function' || this._super(name);
13458 willDestroy: function() {
13459 var parent = this._parentView;
13461 // destroy the element -- this will avoid each child view destroying
13462 // the element over and over again...
13463 if (!this.removedFromDOM) { this.destroyElement(); }
13465 // remove from parent if found. Don't call removeFromParent,
13466 // as removeFromParent will try to remove the element from
13468 if (parent) { parent.removeChild(this); }
13470 this.transitionTo('destroyed');
13472 // next remove view from global hash
13473 if (!this.isVirtual) delete Ember.View.views[this.elementId];
13476 clearRenderedChildren: Ember.K,
13477 triggerRecursively: Ember.K,
13478 invokeRecursively: Ember.K,
13479 transitionTo: Ember.K,
13480 destroyElement: Ember.K
13484 `Ember.View` is the class in Ember responsible for encapsulating templates of
13485 HTML content, combining templates with data to render as sections of a page's
13486 DOM, and registering and responding to user-initiated events.
13490 The default HTML tag name used for a view's DOM representation is `div`. This
13491 can be customized by setting the `tagName` property. The following view
13495 ParagraphView = Ember.View.extend({
13500 Would result in instances with the following HTML:
13503 <em id="ember1" class="ember-view"></em>
13506 ## HTML `class` Attribute
13508 The HTML `class` attribute of a view's tag can be set by providing a
13509 `classNames` property that is set to an array of strings:
13512 MyView = Ember.View.extend({
13513 classNames: ['my-class', 'my-other-class']
13517 Will result in view instances with an HTML representation of:
13520 <div id="ember1" class="ember-view my-class my-other-class"></div>
13523 `class` attribute values can also be set by providing a `classNameBindings`
13524 property set to an array of properties names for the view. The return value
13525 of these properties will be added as part of the value for the view's `class`
13526 attribute. These properties can be computed properties:
13529 MyView = Ember.View.extend({
13530 classNameBindings: ['propertyA', 'propertyB'],
13531 propertyA: 'from-a',
13532 propertyB: function(){
13533 if(someLogic){ return 'from-b'; }
13538 Will result in view instances with an HTML representation of:
13541 <div id="ember1" class="ember-view from-a from-b"></div>
13544 If the value of a class name binding returns a boolean the property name
13545 itself will be used as the class name if the property is true. The class name
13546 will not be added if the value is `false` or `undefined`.
13549 MyView = Ember.View.extend({
13550 classNameBindings: ['hovered'],
13555 Will result in view instances with an HTML representation of:
13558 <div id="ember1" class="ember-view hovered"></div>
13561 When using boolean class name bindings you can supply a string value other
13562 than the property name for use as the `class` HTML attribute by appending the
13563 preferred value after a ":" character when defining the binding:
13566 MyView = Ember.View.extend({
13567 classNameBindings: ['awesome:so-very-cool'],
13572 Will result in view instances with an HTML representation of:
13575 <div id="ember1" class="ember-view so-very-cool"></div>
13578 Boolean value class name bindings whose property names are in a
13579 camelCase-style format will be converted to a dasherized format:
13582 MyView = Ember.View.extend({
13583 classNameBindings: ['isUrgent'],
13588 Will result in view instances with an HTML representation of:
13591 <div id="ember1" class="ember-view is-urgent"></div>
13594 Class name bindings can also refer to object values that are found by
13595 traversing a path relative to the view itself:
13598 MyView = Ember.View.extend({
13599 classNameBindings: ['messages.empty']
13600 messages: Ember.Object.create({
13606 Will result in view instances with an HTML representation of:
13609 <div id="ember1" class="ember-view empty"></div>
13612 If you want to add a class name for a property which evaluates to true and
13613 and a different class name if it evaluates to false, you can pass a binding
13617 // Applies 'enabled' class when isEnabled is true and 'disabled' when isEnabled is false
13618 Ember.View.create({
13619 classNameBindings: ['isEnabled:enabled:disabled']
13624 Will result in view instances with an HTML representation of:
13627 <div id="ember1" class="ember-view enabled"></div>
13630 When isEnabled is `false`, the resulting HTML reprensentation looks like
13634 <div id="ember1" class="ember-view disabled"></div>
13637 This syntax offers the convenience to add a class if a property is `false`:
13640 // Applies no class when isEnabled is true and class 'disabled' when isEnabled is false
13641 Ember.View.create({
13642 classNameBindings: ['isEnabled::disabled']
13647 Will result in view instances with an HTML representation of:
13650 <div id="ember1" class="ember-view"></div>
13653 When the `isEnabled` property on the view is set to `false`, it will result
13654 in view instances with an HTML representation of:
13657 <div id="ember1" class="ember-view disabled"></div>
13660 Updates to the the value of a class name binding will result in automatic
13661 update of the HTML `class` attribute in the view's rendered HTML
13662 representation. If the value becomes `false` or `undefined` the class name
13665 Both `classNames` and `classNameBindings` are concatenated properties. See
13666 `Ember.Object` documentation for more information about concatenated
13671 The HTML attribute section of a view's tag can be set by providing an
13672 `attributeBindings` property set to an array of property names on the view.
13673 The return value of these properties will be used as the value of the view's
13674 HTML associated attribute:
13677 AnchorView = Ember.View.extend({
13679 attributeBindings: ['href'],
13680 href: 'http://google.com'
13684 Will result in view instances with an HTML representation of:
13687 <a id="ember1" class="ember-view" href="http://google.com"></a>
13690 If the return value of an `attributeBindings` monitored property is a boolean
13691 the property will follow HTML's pattern of repeating the attribute's name as
13695 MyTextInput = Ember.View.extend({
13697 attributeBindings: ['disabled'],
13702 Will result in view instances with an HTML representation of:
13705 <input id="ember1" class="ember-view" disabled="disabled" />
13708 `attributeBindings` can refer to computed properties:
13711 MyTextInput = Ember.View.extend({
13713 attributeBindings: ['disabled'],
13714 disabled: function(){
13724 Updates to the the property of an attribute binding will result in automatic
13725 update of the HTML attribute in the view's rendered HTML representation.
13727 `attributeBindings` is a concatenated property. See `Ember.Object`
13728 documentation for more information about concatenated properties.
13732 The HTML contents of a view's rendered representation are determined by its
13733 template. Templates can be any function that accepts an optional context
13734 parameter and returns a string of HTML that will be inserted within the
13735 view's tag. Most typically in Ember this function will be a compiled
13736 `Ember.Handlebars` template.
13739 AView = Ember.View.extend({
13740 template: Ember.Handlebars.compile('I am the template')
13744 Will result in view instances with an HTML representation of:
13747 <div id="ember1" class="ember-view">I am the template</div>
13750 Within an Ember application is more common to define a Handlebars templates as
13754 <script type='text/x-handlebars' data-template-name='some-template'>
13759 And associate it by name using a view's `templateName` property:
13762 AView = Ember.View.extend({
13763 templateName: 'some-template'
13767 Using a value for `templateName` that does not have a Handlebars template
13768 with a matching `data-template-name` attribute will throw an error.
13770 Assigning a value to both `template` and `templateName` properties will throw
13773 For views classes that may have a template later defined (e.g. as the block
13774 portion of a `{{view}}` Handlebars helper call in another template or in
13775 a subclass), you can provide a `defaultTemplate` property set to compiled
13776 template function. If a template is not later provided for the view instance
13777 the `defaultTemplate` value will be used:
13780 AView = Ember.View.extend({
13781 defaultTemplate: Ember.Handlebars.compile('I was the default'),
13787 Will result in instances with an HTML representation of:
13790 <div id="ember1" class="ember-view">I was the default</div>
13793 If a `template` or `templateName` is provided it will take precedence over
13797 AView = Ember.View.extend({
13798 defaultTemplate: Ember.Handlebars.compile('I was the default')
13801 aView = AView.create({
13802 template: Ember.Handlebars.compile('I was the template, not default')
13806 Will result in the following HTML representation when rendered:
13809 <div id="ember1" class="ember-view">I was the template, not default</div>
13814 The default context of the compiled template is the view's controller:
13817 AView = Ember.View.extend({
13818 template: Ember.Handlebars.compile('Hello {{excitedGreeting}}')
13821 aController = Ember.Object.create({
13822 firstName: 'Barry',
13823 excitedGreeting: function(){
13824 return this.get("content.firstName") + "!!!"
13828 aView = AView.create({
13829 controller: aController,
13833 Will result in an HTML representation of:
13836 <div id="ember1" class="ember-view">Hello Barry!!!</div>
13839 A context can also be explicitly supplied through the view's `context`
13840 property. If the view has neither `context` nor `controller` properties, the
13841 `parentView`'s context will be used.
13845 Views can have a secondary template that wraps their main template. Like
13846 primary templates, layouts can be any function that accepts an optional
13847 context parameter and returns a string of HTML that will be inserted inside
13848 view's tag. Views whose HTML element is self closing (e.g. `<input />`)
13849 cannot have a layout and this property will be ignored.
13851 Most typically in Ember a layout will be a compiled `Ember.Handlebars`
13854 A view's layout can be set directly with the `layout` property or reference
13855 an existing Handlebars template by name with the `layoutName` property.
13857 A template used as a layout must contain a single use of the Handlebars
13858 `{{yield}}` helper. The HTML contents of a view's rendered `template` will be
13859 inserted at this location:
13862 AViewWithLayout = Ember.View.extend({
13863 layout: Ember.Handlebars.compile("<div class='my-decorative-class'>{{yield}}</div>")
13864 template: Ember.Handlebars.compile("I got wrapped"),
13868 Will result in view instances with an HTML representation of:
13871 <div id="ember1" class="ember-view">
13872 <div class="my-decorative-class">
13878 See `Handlebars.helpers.yield` for more information.
13880 ## Responding to Browser Events
13882 Views can respond to user-initiated events in one of three ways: method
13883 implementation, through an event manager, and through `{{action}}` helper use
13884 in their template or layout.
13886 ### Method Implementation
13888 Views can respond to user-initiated events by implementing a method that
13889 matches the event name. A `jQuery.Event` object will be passed as the
13890 argument to this method.
13893 AView = Ember.View.extend({
13894 click: function(event){
13895 // will be called when when an instance's
13896 // rendered element is clicked
13903 Views can define an object as their `eventManager` property. This object can
13904 then implement methods that match the desired event names. Matching events
13905 that occur on the view's rendered HTML or the rendered HTML of any of its DOM
13906 descendants will trigger this method. A `jQuery.Event` object will be passed
13907 as the first argument to the method and an `Ember.View` object as the
13908 second. The `Ember.View` will be the view whose rendered HTML was interacted
13909 with. This may be the view with the `eventManager` property or one of its
13913 AView = Ember.View.extend({
13914 eventManager: Ember.Object.create({
13915 doubleClick: function(event, view){
13916 // will be called when when an instance's
13917 // rendered element or any rendering
13918 // of this views's descendent
13919 // elements is clicked
13925 An event defined for an event manager takes precedence over events of the
13926 same name handled through methods on the view.
13929 AView = Ember.View.extend({
13930 mouseEnter: function(event){
13931 // will never trigger.
13933 eventManager: Ember.Object.create({
13934 mouseEnter: function(event, view){
13935 // takes presedence over AView#mouseEnter
13941 Similarly a view's event manager will take precedence for events of any views
13942 rendered as a descendent. A method name that matches an event name will not
13943 be called if the view instance was rendered inside the HTML representation of
13944 a view that has an `eventManager` property defined that handles events of the
13945 name. Events not handled by the event manager will still trigger method calls
13949 OuterView = Ember.View.extend({
13950 template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"),
13951 eventManager: Ember.Object.create({
13952 mouseEnter: function(event, view){
13953 // view might be instance of either
13954 // OutsideView or InnerView depending on
13955 // where on the page the user interaction occured
13960 InnerView = Ember.View.extend({
13961 click: function(event){
13962 // will be called if rendered inside
13963 // an OuterView because OuterView's
13964 // eventManager doesn't handle click events
13966 mouseEnter: function(event){
13967 // will never be called if rendered inside
13973 ### Handlebars `{{action}}` Helper
13975 See `Handlebars.helpers.action`.
13979 Possible events names for any of the responding approaches described above
14016 HTML5 drag and drop events:
14025 ## Handlebars `{{view}}` Helper
14027 Other `Ember.View` instances can be included as part of a view's template by
14028 using the `{{view}}` Handlebars helper. See `Handlebars.helpers.view` for
14029 additional information.
14033 @extends Ember.Object
14034 @uses Ember.Evented
14036 Ember.View = Ember.CoreView.extend(
14037 /** @scope Ember.View.prototype */ {
14039 concatenatedProperties: ['classNames', 'classNameBindings', 'attributeBindings'],
14049 // ..........................................................
14050 // TEMPLATE SUPPORT
14054 The name of the template to lookup if no template is provided.
14056 `Ember.View` will look for a template with this name in this view's
14057 `templates` object. By default, this will be a global object
14058 shared in `Ember.TEMPLATES`.
14060 @property templateName
14064 templateName: null,
14067 The name of the layout to lookup if no layout is provided.
14069 `Ember.View` will look for a template with this name in this view's
14070 `templates` object. By default, this will be a global object
14071 shared in `Ember.TEMPLATES`.
14073 @property layoutName
14080 The hash in which to look for `templateName`.
14082 @property templates
14084 @default Ember.TEMPLATES
14086 templates: Ember.TEMPLATES,
14089 The template used to render the view. This should be a function that
14090 accepts an optional context parameter and returns a string of HTML that
14091 will be inserted into the DOM relative to its parent view.
14093 In general, you should set the `templateName` property instead of setting
14094 the template yourself.
14099 template: Ember.computed(function(key, value) {
14100 if (value !== undefined) { return value; }
14102 var templateName = get(this, 'templateName'),
14103 template = this.templateForName(templateName, 'template');
14105 return template || get(this, 'defaultTemplate');
14106 }).property('templateName'),
14108 container: Ember.computed(function() {
14109 var parentView = get(this, '_parentView');
14111 if (parentView) { return get(parentView, 'container'); }
14113 return Ember.Container && Ember.Container.defaultContainer;
14117 The controller managing this view. If this property is set, it will be
14118 made available for use by the template.
14120 @property controller
14123 controller: Ember.computed(function(key) {
14124 var parentView = get(this, '_parentView');
14125 return parentView ? get(parentView, 'controller') : null;
14126 }).property('_parentView'),
14129 A view may contain a layout. A layout is a regular template but
14130 supersedes the `template` property during rendering. It is the
14131 responsibility of the layout template to retrieve the `template`
14132 property from the view (or alternatively, call `Handlebars.helpers.yield`,
14133 `{{yield}}`) to render it in the correct location.
14135 This is useful for a view that has a shared wrapper, but which delegates
14136 the rendering of the contents of the wrapper to the `template` property
14142 layout: Ember.computed(function(key) {
14143 var layoutName = get(this, 'layoutName'),
14144 layout = this.templateForName(layoutName, 'layout');
14146 return layout || get(this, 'defaultLayout');
14147 }).property('layoutName'),
14149 templateForName: function(name, type) {
14150 if (!name) { return; }
14152 Ember.assert("templateNames are not allowed to contain periods: "+name, name.indexOf('.') === -1);
14154 var container = get(this, 'container');
14157 return container.lookup('template:' + name);
14162 The object from which templates should access properties.
14164 This object will be passed to the template function each time the render
14165 method is called, but it is up to the individual function to decide what
14168 By default, this will be the view's controller.
14173 context: Ember.computed(function(key, value) {
14174 if (arguments.length === 2) {
14175 set(this, '_context', value);
14178 return get(this, '_context');
14185 Private copy of the view's template context. This can be set directly
14186 by Handlebars without triggering the observer that causes the view
14189 The context of a view is looked up as follows:
14191 1. Supplied context (usually by Handlebars)
14192 2. Specified controller
14193 3. `parentView`'s context (for a child of a ContainerView)
14195 The code in Handlebars that overrides the `_context` property first
14196 checks to see whether the view has a specified controller. This is
14197 something of a hack and should be revisited.
14201 _context: Ember.computed(function(key) {
14202 var parentView, controller;
14204 if (controller = get(this, 'controller')) {
14208 parentView = this._parentView;
14210 return get(parentView, '_context');
14219 If a value that affects template rendering changes, the view should be
14220 re-rendered to reflect the new value.
14222 @method _displayPropertyDidChange
14224 _contextDidChange: Ember.observer(function() {
14229 If `false`, the view will appear hidden in DOM.
14231 @property isVisible
14240 Array of child views. You should never edit this array directly.
14241 Instead, use `appendChild` and `removeFromParent`.
14243 @property childViews
14247 childViews: childViewsProperty,
14251 // When it's a virtual view, we need to notify the parent that their
14252 // childViews will change.
14253 _childViewsWillChange: Ember.beforeObserver(function() {
14254 if (this.isVirtual) {
14255 var parentView = get(this, 'parentView');
14256 if (parentView) { Ember.propertyWillChange(parentView, 'childViews'); }
14260 // When it's a virtual view, we need to notify the parent that their
14261 // childViews did change.
14262 _childViewsDidChange: Ember.observer(function() {
14263 if (this.isVirtual) {
14264 var parentView = get(this, 'parentView');
14265 if (parentView) { Ember.propertyDidChange(parentView, 'childViews'); }
14270 Return the nearest ancestor that is an instance of the provided
14273 @property nearestInstanceOf
14274 @param {Class} klass Subclass of Ember.View (or Ember.View itself)
14278 nearestInstanceOf: function(klass) {
14279 Ember.deprecate("nearestInstanceOf is deprecated and will be removed from future releases. Use nearestOfType.");
14280 var view = get(this, 'parentView');
14283 if(view instanceof klass) { return view; }
14284 view = get(view, 'parentView');
14289 Return the nearest ancestor that is an instance of the provided
14292 @property nearestOfType
14293 @param {Class,Mixin} klass Subclass of Ember.View (or Ember.View itself),
14294 or an instance of Ember.Mixin.
14297 nearestOfType: function(klass) {
14298 var view = get(this, 'parentView'),
14299 isOfType = klass instanceof Ember.Mixin ?
14300 function(view) { return klass.detect(view); } :
14301 function(view) { return klass.detect(view.constructor); };
14304 if( isOfType(view) ) { return view; }
14305 view = get(view, 'parentView');
14310 Return the nearest ancestor that has a given property.
14312 @property nearestWithProperty
14313 @param {String} property A property name
14316 nearestWithProperty: function(property) {
14317 var view = get(this, 'parentView');
14320 if (property in view) { return view; }
14321 view = get(view, 'parentView');
14326 Return the nearest ancestor whose parent is an instance of
14329 @property nearestChildOf
14330 @param {Class} klass Subclass of Ember.View (or Ember.View itself)
14333 nearestChildOf: function(klass) {
14334 var view = get(this, 'parentView');
14337 if(get(view, 'parentView') instanceof klass) { return view; }
14338 view = get(view, 'parentView');
14345 When the parent view changes, recursively invalidate `controller`
14347 @method _parentViewDidChange
14349 _parentViewDidChange: Ember.observer(function() {
14350 if (this.isDestroying) { return; }
14352 if (get(this, 'parentView.controller') && !get(this, 'controller')) {
14353 this.notifyPropertyChange('controller');
14357 _controllerDidChange: Ember.observer(function() {
14358 if (this.isDestroying) { return; }
14362 this.forEachChildView(function(view) {
14363 view.propertyDidChange('controller');
14367 cloneKeywords: function() {
14368 var templateData = get(this, 'templateData');
14370 var keywords = templateData ? Ember.copy(templateData.keywords) : {};
14371 set(keywords, 'view', get(this, 'concreteView'));
14372 set(keywords, '_view', this);
14373 set(keywords, 'controller', get(this, 'controller'));
14379 Called on your view when it should push strings of HTML into a
14380 `Ember.RenderBuffer`. Most users will want to override the `template`
14381 or `templateName` properties instead of this method.
14383 By default, `Ember.View` will look for a function in the `template`
14384 property and invoke it with the value of `context`. The value of
14385 `context` will be the view's controller unless you override it.
14388 @param {Ember.RenderBuffer} buffer The render buffer
14390 render: function(buffer) {
14391 // If this view has a layout, it is the responsibility of the
14392 // the layout to render the view's template. Otherwise, render the template
14394 var template = get(this, 'layout') || get(this, 'template');
14397 var context = get(this, 'context');
14398 var keywords = this.cloneKeywords();
14404 isRenderData: true,
14405 keywords: keywords,
14406 insideGroup: get(this, 'templateData.insideGroup')
14409 // Invoke the template with the provided template context, which
14410 // is the view's controller by default. A hash of data is also passed that provides
14411 // the template with access to the view and render buffer.
14413 Ember.assert('template must be a function. Did you mean to call Ember.Handlebars.compile("...") or specify templateName instead?', typeof template === 'function');
14414 // The template should write directly to the render buffer instead
14415 // of returning a string.
14416 output = template(context, { data: data });
14418 // If the template returned a string instead of writing to the buffer,
14419 // push the string onto the buffer.
14420 if (output !== undefined) { buffer.push(output); }
14425 Renders the view again. This will work regardless of whether the
14426 view is already in the DOM or not. If the view is in the DOM, the
14427 rendering process will be deferred to give bindings a chance
14430 If children were added during the rendering process using `appendChild`,
14431 `rerender` will remove them, because they will be added again
14432 if needed by the next `render`.
14434 In general, if the display of your view changes, you should modify
14435 the DOM element directly instead of manually calling `rerender`, which can
14440 rerender: function() {
14441 return this.currentState.rerender(this);
14444 clearRenderedChildren: function() {
14445 var lengthBefore = this.lengthBeforeRender,
14446 lengthAfter = this.lengthAfterRender;
14448 // If there were child views created during the last call to render(),
14449 // remove them under the assumption that they will be re-created when
14452 // VIEW-TODO: Unit test this path.
14453 var childViews = this._childViews;
14454 for (var i=lengthAfter-1; i>=lengthBefore; i--) {
14455 if (childViews[i]) { childViews[i].destroy(); }
14462 Iterates over the view's `classNameBindings` array, inserts the value
14463 of the specified property into the `classNames` array, then creates an
14464 observer to update the view's element if the bound property ever changes
14467 @method _applyClassNameBindings
14469 _applyClassNameBindings: function(classBindings) {
14470 var classNames = this.classNames,
14471 elem, newClass, dasherizedClass;
14473 // Loop through all of the configured bindings. These will be either
14474 // property names ('isUrgent') or property paths relative to the view
14475 // ('content.isUrgent')
14476 a_forEach(classBindings, function(binding) {
14478 // Variable in which the old class value is saved. The observer function
14479 // closes over this variable, so it knows which string to remove when
14480 // the property changes.
14482 // Extract just the property name from bindings like 'foo:bar'
14483 var parsedPath = Ember.View._parsePropertyPath(binding);
14485 // Set up an observer on the context. If the property changes, toggle the
14487 var observer = function() {
14488 // Get the current value of the property
14489 newClass = this._classStringForProperty(binding);
14492 // If we had previously added a class to the element, remove it.
14494 elem.removeClass(oldClass);
14495 // Also remove from classNames so that if the view gets rerendered,
14496 // the class doesn't get added back to the DOM.
14497 classNames.removeObject(oldClass);
14500 // If necessary, add a new class. Make sure we keep track of it so
14501 // it can be removed in the future.
14503 elem.addClass(newClass);
14504 oldClass = newClass;
14510 // Get the class name for the property at its current value
14511 dasherizedClass = this._classStringForProperty(binding);
14513 if (dasherizedClass) {
14514 // Ensure that it gets into the classNames array
14515 // so it is displayed when we render.
14516 a_addObject(classNames, dasherizedClass);
14518 // Save a reference to the class name so we can remove it
14519 // if the observer fires. Remember that this variable has
14520 // been closed over by the observer.
14521 oldClass = dasherizedClass;
14524 addObserver(this, parsedPath.path, observer);
14526 this.one('willClearRender', function() {
14527 removeObserver(this, parsedPath.path, observer);
14535 Iterates through the view's attribute bindings, sets up observers for each,
14536 then applies the current value of the attributes to the passed render buffer.
14538 @method _applyAttributeBindings
14539 @param {Ember.RenderBuffer} buffer
14541 _applyAttributeBindings: function(buffer, attributeBindings) {
14542 var attributeValue, elem, type;
14544 a_forEach(attributeBindings, function(binding) {
14545 var split = binding.split(':'),
14546 property = split[0],
14547 attributeName = split[1] || property;
14549 // Create an observer to add/remove/change the attribute if the
14550 // JavaScript property changes.
14551 var observer = function() {
14553 if (!elem) { return; }
14555 attributeValue = get(this, property);
14557 Ember.View.applyAttributeBindings(elem, attributeName, attributeValue);
14560 addObserver(this, property, observer);
14562 this.one('willClearRender', function() {
14563 removeObserver(this, property, observer);
14566 // Determine the current value and add it to the render buffer
14568 attributeValue = get(this, property);
14569 Ember.View.applyAttributeBindings(buffer, attributeName, attributeValue);
14576 Given a property name, returns a dasherized version of that
14577 property name if the property evaluates to a non-falsy value.
14579 For example, if the view has property `isUrgent` that evaluates to true,
14580 passing `isUrgent` to this method will return `"is-urgent"`.
14582 @method _classStringForProperty
14585 _classStringForProperty: function(property) {
14586 var parsedPath = Ember.View._parsePropertyPath(property);
14587 var path = parsedPath.path;
14589 var val = get(this, path);
14590 if (val === undefined && Ember.isGlobalPath(path)) {
14591 val = get(Ember.lookup, path);
14594 return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
14597 // ..........................................................
14602 Returns the current DOM element for the view.
14607 element: Ember.computed(function(key, value) {
14608 if (value !== undefined) {
14609 return this.currentState.setElement(this, value);
14611 return this.currentState.getElement(this);
14613 }).property('_parentView'),
14616 Returns a jQuery object for this view's element. If you pass in a selector
14617 string, this method will return a jQuery object, using the current element
14620 For example, calling `view.$('li')` will return a jQuery object containing
14621 all of the `li` elements inside the DOM element of this view.
14624 @param {String} [selector] a jQuery-compatible selector string
14625 @return {jQuery} the CoreQuery object for the DOM node
14628 return this.currentState.$(this, sel);
14631 mutateChildViews: function(callback) {
14632 var childViews = this._childViews,
14633 idx = childViews.length,
14636 while(--idx >= 0) {
14637 view = childViews[idx];
14638 callback.call(this, view, idx);
14644 forEachChildView: function(callback) {
14645 var childViews = this._childViews;
14647 if (!childViews) { return this; }
14649 var len = childViews.length,
14652 for(idx = 0; idx < len; idx++) {
14653 view = childViews[idx];
14654 callback.call(this, view);
14661 Appends the view's element to the specified parent element.
14663 If the view does not have an HTML representation yet, `createElement()`
14664 will be called automatically.
14666 Note that this method just schedules the view to be appended; the DOM
14667 element will not be appended to the given element until all bindings have
14668 finished synchronizing.
14670 This is not typically a function that you will need to call directly when
14671 building your application. You might consider using `Ember.ContainerView`
14672 instead. If you do need to use `appendTo`, be sure that the target element
14673 you are providing is associated with an `Ember.Application` and does not
14674 have an ancestor element that is associated with an Ember view.
14677 @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object
14678 @return {Ember.View} receiver
14680 appendTo: function(target) {
14681 // Schedule the DOM element to be created and appended to the given
14682 // element after bindings have synchronized.
14683 this._insertElementLater(function() {
14684 Ember.assert("You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view'));
14685 this.$().appendTo(target);
14692 Replaces the content of the specified parent element with this view's
14693 element. If the view does not have an HTML representation yet,
14694 `createElement()` will be called automatically.
14696 Note that this method just schedules the view to be appended; the DOM
14697 element will not be appended to the given element until all bindings have
14698 finished synchronizing
14701 @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object
14702 @return {Ember.View} received
14704 replaceIn: function(target) {
14705 Ember.assert("You cannot replace an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view'));
14707 this._insertElementLater(function() {
14708 Ember.$(target).empty();
14709 this.$().appendTo(target);
14718 Schedules a DOM operation to occur during the next render phase. This
14719 ensures that all bindings have finished synchronizing before the view is
14722 To use, pass a function that performs a DOM operation.
14724 Before your function is called, this view and all child views will receive
14725 the `willInsertElement` event. After your function is invoked, this view
14726 and all of its child views will receive the `didInsertElement` event.
14729 view._insertElementLater(function() {
14730 this.createElement();
14731 this.$().appendTo('body');
14735 @method _insertElementLater
14736 @param {Function} fn the function that inserts the element into the DOM
14738 _insertElementLater: function(fn) {
14739 this._scheduledInsert = Ember.run.scheduleOnce('render', this, '_insertElement', fn);
14742 _insertElement: function (fn) {
14743 this._scheduledInsert = null;
14744 this.currentState.insertElement(this, fn);
14748 Appends the view's element to the document body. If the view does
14749 not have an HTML representation yet, `createElement()` will be called
14752 Note that this method just schedules the view to be appended; the DOM
14753 element will not be appended to the document body until all bindings have
14754 finished synchronizing.
14757 @return {Ember.View} receiver
14759 append: function() {
14760 return this.appendTo(document.body);
14764 Removes the view's element from the element to which it is attached.
14767 @return {Ember.View} receiver
14769 remove: function() {
14770 // What we should really do here is wait until the end of the run loop
14771 // to determine if the element has been re-appended to a different
14773 // In the interim, we will just re-render if that happens. It is more
14774 // important than elements get garbage collected.
14775 this.destroyElement();
14776 this.invokeRecursively(function(view) {
14777 if (view.clearRenderedChildren) { view.clearRenderedChildren(); }
14784 Attempts to discover the element in the parent element. The default
14785 implementation looks for an element with an ID of `elementId` (or the
14786 view's guid if `elementId` is null). You can override this method to
14787 provide your own form of lookup. For example, if you want to discover your
14788 element using a CSS class name instead of an ID.
14790 @method findElementInParentElement
14791 @param {DOMElement} parentElement The parent's DOM element
14792 @return {DOMElement} The discovered element
14794 findElementInParentElement: function(parentElem) {
14795 var id = "#" + this.elementId;
14796 return Ember.$(id)[0] || Ember.$(id, parentElem)[0];
14800 Creates a DOM representation of the view and all of its
14801 child views by recursively calling the `render()` method.
14803 After the element has been created, `didInsertElement` will
14804 be called on this view and all of its child views.
14806 @method createElement
14807 @return {Ember.View} receiver
14809 createElement: function() {
14810 if (get(this, 'element')) { return this; }
14812 var buffer = this.renderToBuffer();
14813 set(this, 'element', buffer.element());
14819 Called when a view is going to insert an element into the DOM.
14821 @event willInsertElement
14823 willInsertElement: Ember.K,
14826 Called when the element of the view has been inserted into the DOM.
14827 Override this function to do any set up that requires an element in the
14830 @event didInsertElement
14832 didInsertElement: Ember.K,
14835 Called when the view is about to rerender, but before anything has
14836 been torn down. This is a good opportunity to tear down any manual
14837 observers you have installed based on the DOM state
14839 @event willClearRender
14841 willClearRender: Ember.K,
14846 Run this callback on the current view and recursively on child views.
14848 @method invokeRecursively
14849 @param fn {Function}
14851 invokeRecursively: function(fn) {
14852 var childViews = [this], currentViews, view;
14854 while (childViews.length) {
14855 currentViews = childViews.slice();
14858 for (var i=0, l=currentViews.length; i<l; i++) {
14859 view = currentViews[i];
14860 fn.call(view, view);
14861 if (view._childViews) {
14862 childViews.push.apply(childViews, view._childViews);
14868 triggerRecursively: function(eventName) {
14869 var childViews = [this], currentViews, view;
14871 while (childViews.length) {
14872 currentViews = childViews.slice();
14875 for (var i=0, l=currentViews.length; i<l; i++) {
14876 view = currentViews[i];
14877 if (view.trigger) { view.trigger(eventName); }
14878 if (view._childViews) {
14879 childViews.push.apply(childViews, view._childViews);
14886 Destroys any existing element along with the element for any child views
14887 as well. If the view does not currently have a element, then this method
14890 If you implement `willDestroyElement()` on your view, then this method will
14891 be invoked on your view before your element is destroyed to give you a
14892 chance to clean up any event handlers, etc.
14894 If you write a `willDestroyElement()` handler, you can assume that your
14895 `didInsertElement()` handler was called earlier for the same element.
14897 Normally you will not call or override this method yourself, but you may
14898 want to implement the above callbacks when it is run.
14900 @method destroyElement
14901 @return {Ember.View} receiver
14903 destroyElement: function() {
14904 return this.currentState.destroyElement(this);
14908 Called when the element of the view is going to be destroyed. Override
14909 this function to do any teardown that requires an element, like removing
14912 @event willDestroyElement
14914 willDestroyElement: function() {},
14919 Triggers the `willDestroyElement` event (which invokes the
14920 `willDestroyElement()` method if it exists) on this view and all child
14923 Before triggering `willDestroyElement`, it first triggers the
14924 `willClearRender` event recursively.
14926 @method _notifyWillDestroyElement
14928 _notifyWillDestroyElement: function() {
14929 this.triggerRecursively('willClearRender');
14930 this.triggerRecursively('willDestroyElement');
14933 _elementWillChange: Ember.beforeObserver(function() {
14934 this.forEachChildView(function(view) {
14935 Ember.propertyWillChange(view, 'element');
14942 If this view's element changes, we need to invalidate the caches of our
14943 child views so that we do not retain references to DOM elements that are
14946 @method _elementDidChange
14948 _elementDidChange: Ember.observer(function() {
14949 this.forEachChildView(function(view) {
14950 Ember.propertyDidChange(view, 'element');
14955 Called when the parentView property has changed.
14957 @event parentViewDidChange
14959 parentViewDidChange: Ember.K,
14961 instrumentName: 'view',
14963 instrumentDetails: function(hash) {
14964 hash.template = get(this, 'templateName');
14968 _renderToBuffer: function(parentBuffer, bufferOperation) {
14969 this.lengthBeforeRender = this._childViews.length;
14970 var buffer = this._super(parentBuffer, bufferOperation);
14971 this.lengthAfterRender = this._childViews.length;
14976 renderToBufferIfNeeded: function () {
14977 return this.currentState.renderToBufferIfNeeded(this, this);
14980 beforeRender: function(buffer) {
14981 this.applyAttributesToBuffer(buffer);
14982 buffer.pushOpeningTag();
14985 afterRender: function(buffer) {
14986 buffer.pushClosingTag();
14989 applyAttributesToBuffer: function(buffer) {
14990 // Creates observers for all registered class name and attribute bindings,
14991 // then adds them to the element.
14992 var classNameBindings = get(this, 'classNameBindings');
14993 if (classNameBindings.length) {
14994 this._applyClassNameBindings(classNameBindings);
14997 // Pass the render buffer so the method can apply attributes directly.
14998 // This isn't needed for class name bindings because they use the
14999 // existing classNames infrastructure.
15000 var attributeBindings = get(this, 'attributeBindings');
15001 if (attributeBindings.length) {
15002 this._applyAttributeBindings(buffer, attributeBindings);
15005 buffer.setClasses(this.classNames);
15006 buffer.id(this.elementId);
15008 var role = get(this, 'ariaRole');
15010 buffer.attr('role', role);
15013 if (get(this, 'isVisible') === false) {
15014 buffer.style('display', 'none');
15018 // ..........................................................
15019 // STANDARD RENDER PROPERTIES
15023 Tag name for the view's outer element. The tag name is only used when an
15024 element is first created. If you change the `tagName` for an element, you
15025 must destroy and recreate the view element.
15027 By default, the render buffer will use a `<div>` tag for views.
15034 // We leave this null by default so we can tell the difference between
15035 // the default case and a user-specified tag.
15039 The WAI-ARIA role of the control represented by this view. For example, a
15040 button may have a role of type 'button', or a pane may have a role of
15041 type 'alertdialog'. This property is used by assistive software to help
15042 visually challenged users navigate rich web applications.
15044 The full list of valid WAI-ARIA roles is available at:
15045 http://www.w3.org/TR/wai-aria/roles#roles_categorization
15054 Standard CSS class names to apply to the view's outer element. This
15055 property automatically inherits any class names defined by the view's
15056 superclasses as well.
15058 @property classNames
15060 @default ['ember-view']
15062 classNames: ['ember-view'],
15065 A list of properties of the view to apply as class names. If the property
15066 is a string value, the value of that string will be applied as a class
15070 // Applies the 'high' class to the view element
15071 Ember.View.create({
15072 classNameBindings: ['priority']
15077 If the value of the property is a Boolean, the name of that property is
15078 added as a dasherized class name.
15081 // Applies the 'is-urgent' class to the view element
15082 Ember.View.create({
15083 classNameBindings: ['isUrgent']
15088 If you would prefer to use a custom value instead of the dasherized
15089 property name, you can pass a binding like this:
15092 // Applies the 'urgent' class to the view element
15093 Ember.View.create({
15094 classNameBindings: ['isUrgent:urgent']
15099 This list of properties is inherited from the view's superclasses as well.
15101 @property classNameBindings
15105 classNameBindings: [],
15108 A list of properties of the view to apply as attributes. If the property is
15109 a string value, the value of that string will be applied as the attribute.
15112 // Applies the type attribute to the element
15113 // with the value "button", like <div type="button">
15114 Ember.View.create({
15115 attributeBindings: ['type'],
15120 If the value of the property is a Boolean, the name of that property is
15121 added as an attribute.
15124 // Renders something like <div enabled="enabled">
15125 Ember.View.create({
15126 attributeBindings: ['enabled'],
15131 @property attributeBindings
15133 attributeBindings: [],
15135 // .......................................................
15136 // CORE DISPLAY METHODS
15142 Setup a view, but do not finish waking it up.
15143 - configure `childViews`
15144 - register the view with the global views hash, which is used for event
15150 this.elementId = this.elementId || guidFor(this);
15154 // setup child views. be sure to clone the child views array first
15155 this._childViews = this._childViews.slice();
15157 Ember.assert("Only arrays are allowed for 'classNameBindings'", Ember.typeOf(this.classNameBindings) === 'array');
15158 this.classNameBindings = Ember.A(this.classNameBindings.slice());
15160 Ember.assert("Only arrays are allowed for 'classNames'", Ember.typeOf(this.classNames) === 'array');
15161 this.classNames = Ember.A(this.classNames.slice());
15163 var viewController = get(this, 'viewController');
15164 if (viewController) {
15165 viewController = get(viewController);
15166 if (viewController) {
15167 set(viewController, 'view', this);
15172 appendChild: function(view, options) {
15173 return this.currentState.appendChild(this, view, options);
15177 Removes the child view from the parent view.
15179 @method removeChild
15180 @param {Ember.View} view
15181 @return {Ember.View} receiver
15183 removeChild: function(view) {
15184 // If we're destroying, the entire subtree will be
15185 // freed, and the DOM will be handled separately,
15186 // so no need to mess with childViews.
15187 if (this.isDestroying) { return; }
15189 // update parent node
15190 set(view, '_parentView', null);
15192 // remove view from childViews array.
15193 var childViews = this._childViews;
15195 Ember.EnumerableUtils.removeObject(childViews, view);
15197 this.propertyDidChange('childViews'); // HUH?! what happened to will change?
15203 Removes all children from the `parentView`.
15205 @method removeAllChildren
15206 @return {Ember.View} receiver
15208 removeAllChildren: function() {
15209 return this.mutateChildViews(function(view) {
15210 this.removeChild(view);
15214 destroyAllChildren: function() {
15215 return this.mutateChildViews(function(view) {
15221 Removes the view from its `parentView`, if one is found. Otherwise
15224 @method removeFromParent
15225 @return {Ember.View} receiver
15227 removeFromParent: function() {
15228 var parent = this._parentView;
15230 // Remove DOM element from parent
15233 if (parent) { parent.removeChild(this); }
15238 You must call `destroy` on a view to destroy the view (and all of its
15239 child views). This will remove the view from any parent node, then make
15240 sure that the DOM element managed by the view can be released by the
15243 @method willDestroy
15245 willDestroy: function() {
15246 // calling this._super() will nuke computed properties and observers,
15247 // so collect any information we need before calling super.
15248 var childViews = this._childViews,
15249 parent = this._parentView,
15252 // destroy the element -- this will avoid each child view destroying
15253 // the element over and over again...
15254 if (!this.removedFromDOM) { this.destroyElement(); }
15256 // remove from non-virtual parent view if viewName was specified
15257 if (this.viewName) {
15258 var nonVirtualParentView = get(this, 'parentView');
15259 if (nonVirtualParentView) {
15260 set(nonVirtualParentView, this.viewName, null);
15264 // remove from parent if found. Don't call removeFromParent,
15265 // as removeFromParent will try to remove the element from
15267 if (parent) { parent.removeChild(this); }
15269 this.transitionTo('destroyed');
15271 childLen = childViews.length;
15272 for (var i=childLen-1; i>=0; i--) {
15273 childViews[i].removedFromDOM = true;
15274 childViews[i].destroy();
15277 // next remove view from global hash
15278 if (!this.isVirtual) delete Ember.View.views[get(this, 'elementId')];
15282 Instantiates a view to be added to the childViews array during view
15283 initialization. You generally will not call this method directly unless
15284 you are overriding `createChildViews()`. Note that this method will
15285 automatically configure the correct settings on the new view instance to
15286 act as a child of the parent.
15288 @method createChildView
15289 @param {Class} viewClass
15290 @param {Hash} [attrs] Attributes to add
15291 @return {Ember.View} new instance
15293 createChildView: function(view, attrs) {
15294 if (view.isView && view._parentView === this) { return view; }
15296 if (Ember.CoreView.detect(view)) {
15297 attrs = attrs || {};
15298 attrs._parentView = this;
15299 attrs.templateData = attrs.templateData || get(this, 'templateData');
15301 view = view.create(attrs);
15303 // don't set the property on a virtual view, as they are invisible to
15304 // consumers of the view API
15305 if (view.viewName) { set(get(this, 'concreteView'), view.viewName, view); }
15307 Ember.assert('You must pass instance or subclass of View', view.isView);
15310 view.setProperties(attrs);
15313 if (!get(view, 'templateData')) {
15314 set(view, 'templateData', get(this, 'templateData'));
15317 set(view, '_parentView', this);
15323 becameVisible: Ember.K,
15324 becameHidden: Ember.K,
15329 When the view's `isVisible` property changes, toggle the visibility
15330 element of the actual DOM element.
15332 @method _isVisibleDidChange
15334 _isVisibleDidChange: Ember.observer(function() {
15335 var $el = this.$();
15336 if (!$el) { return; }
15338 var isVisible = get(this, 'isVisible');
15340 $el.toggle(isVisible);
15342 if (this._isAncestorHidden()) { return; }
15345 this._notifyBecameVisible();
15347 this._notifyBecameHidden();
15351 _notifyBecameVisible: function() {
15352 this.trigger('becameVisible');
15354 this.forEachChildView(function(view) {
15355 var isVisible = get(view, 'isVisible');
15357 if (isVisible || isVisible === null) {
15358 view._notifyBecameVisible();
15363 _notifyBecameHidden: function() {
15364 this.trigger('becameHidden');
15365 this.forEachChildView(function(view) {
15366 var isVisible = get(view, 'isVisible');
15368 if (isVisible || isVisible === null) {
15369 view._notifyBecameHidden();
15374 _isAncestorHidden: function() {
15375 var parent = get(this, 'parentView');
15378 if (get(parent, 'isVisible') === false) { return true; }
15380 parent = get(parent, 'parentView');
15386 clearBuffer: function() {
15387 this.invokeRecursively(function(view) {
15388 view.buffer = null;
15392 transitionTo: function(state, children) {
15393 this.currentState = this.states[state];
15394 this.state = state;
15396 if (children !== false) {
15397 this.forEachChildView(function(view) {
15398 view.transitionTo(state);
15403 // .......................................................
15410 Handle events from `Ember.EventDispatcher`
15412 @method handleEvent
15413 @param eventName {String}
15416 handleEvent: function(eventName, evt) {
15417 return this.currentState.handleEvent(this, eventName, evt);
15423 Describe how the specified actions should behave in the various
15424 states that a view can exist in. Possible states:
15426 * preRender: when a view is first instantiated, and after its
15427 element was destroyed, it is in the preRender state
15428 * inBuffer: once a view has been rendered, but before it has
15429 been inserted into the DOM, it is in the inBuffer state
15430 * inDOM: once a view has been inserted into the DOM it is in
15431 the inDOM state. A view spends the vast majority of its
15432 existence in this state.
15433 * destroyed: once a view has been destroyed (using the destroy
15434 method), it is in this state. No further actions can be invoked
15435 on a destroyed view.
15438 // in the destroyed state, everything is illegal
15440 // before rendering has begun, all legal manipulations are noops.
15442 // inside the buffer, legal manipulations are done on the buffer
15444 // once the view has been inserted into the DOM, legal manipulations
15445 // are done on the DOM element.
15448 prepend: function(view, html) {
15449 view.$().prepend(html);
15452 after: function(view, html) {
15453 view.$().after(html);
15456 html: function(view, html) {
15457 view.$().html(html);
15460 replace: function(view) {
15461 var element = get(view, 'element');
15463 set(view, 'element', null);
15465 view._insertElementLater(function() {
15466 Ember.$(element).replaceWith(get(view, 'element'));
15470 remove: function(view) {
15474 empty: function(view) {
15479 Ember.View.reopen({
15480 domManager: DOMManager
15483 Ember.View.reopenClass({
15488 Parse a path and return an object which holds the parsed properties.
15490 For example a path like "content.isEnabled:enabled:disabled" wil return the
15495 path: "content.isEnabled",
15496 className: "enabled",
15497 falsyClassName: "disabled",
15498 classNames: ":enabled:disabled"
15502 @method _parsePropertyPath
15505 _parsePropertyPath: function(path) {
15506 var split = path.split(':'),
15507 propertyPath = split[0],
15512 // check if the property is defined as prop:class or prop:trueClass:falseClass
15513 if (split.length > 1) {
15514 className = split[1];
15515 if (split.length === 3) { falsyClassName = split[2]; }
15517 classNames = ':' + className;
15518 if (falsyClassName) { classNames += ":" + falsyClassName; }
15522 path: propertyPath,
15523 classNames: classNames,
15524 className: (className === '') ? undefined : className,
15525 falsyClassName: falsyClassName
15532 Get the class name for a given value, based on the path, optional
15533 `className` and optional `falsyClassName`.
15535 - if a `className` or `falsyClassName` has been specified:
15536 - if the value is truthy and `className` has been specified,
15537 `className` is returned
15538 - if the value is falsy and `falsyClassName` has been specified,
15539 `falsyClassName` is returned
15540 - otherwise `null` is returned
15541 - if the value is `true`, the dasherized last part of the supplied path
15543 - if the value is not `false`, `undefined` or `null`, the `value`
15545 - if none of the above rules apply, `null` is returned
15547 @method _classStringForValue
15551 @param falsyClassName
15554 _classStringForValue: function(path, val, className, falsyClassName) {
15555 // When using the colon syntax, evaluate the truthiness or falsiness
15556 // of the value to determine which className to return
15557 if (className || falsyClassName) {
15558 if (className && !!val) {
15561 } else if (falsyClassName && !val) {
15562 return falsyClassName;
15568 // If value is a Boolean and true, return the dasherized property
15570 } else if (val === true) {
15571 // Normalize property path to be suitable for use
15572 // as a class name. For exaple, content.foo.barBaz
15573 // becomes bar-baz.
15574 var parts = path.split('.');
15575 return Ember.String.dasherize(parts[parts.length-1]);
15577 // If the value is not false, undefined, or null, return the current
15578 // value of the property.
15579 } else if (val !== false && val !== undefined && val !== null) {
15582 // Nothing to display. Return null so that the old class is removed
15583 // but no new class is added.
15597 Ember.View.views = {};
15599 // If someone overrides the child views computed property when
15600 // defining their class, we want to be able to process the user's
15601 // supplied childViews and then restore the original computed property
15602 // at view initialization time. This happens in Ember.ContainerView's init
15604 Ember.View.childViewsProperty = childViewsProperty;
15606 Ember.View.applyAttributeBindings = function(elem, name, value) {
15607 var type = Ember.typeOf(value);
15608 var currentValue = elem.attr(name);
15610 // if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js
15611 if ((type === 'string' || (type === 'number' && !isNaN(value))) && value !== currentValue) {
15612 elem.attr(name, value);
15613 } else if (value && type === 'boolean') {
15614 elem.attr(name, name);
15615 } else if (!value) {
15616 elem.removeAttr(name);
15620 Ember.View.states = states;
15629 @submodule ember-views
15632 var get = Ember.get, set = Ember.set;
15634 Ember.View.states._default = {
15635 // appendChild is only legal while rendering the buffer.
15636 appendChild: function() {
15637 throw "You can't use appendChild outside of the rendering process";
15644 getElement: function() {
15648 // Handle events from `Ember.EventDispatcher`
15649 handleEvent: function() {
15650 return true; // continue event propagation
15653 destroyElement: function(view) {
15654 set(view, 'element', null);
15655 if (view._scheduledInsert) {
15656 Ember.run.cancel(view._scheduledInsert);
15657 view._scheduledInsert = null;
15662 renderToBufferIfNeeded: function () {
15676 @submodule ember-views
15679 var preRender = Ember.View.states.preRender = Ember.create(Ember.View.states._default);
15681 Ember.merge(preRender, {
15682 // a view leaves the preRender state once its element has been
15683 // created (createElement).
15684 insertElement: function(view, fn) {
15685 view.createElement();
15686 view.triggerRecursively('willInsertElement');
15687 // after createElement, the view will be in the hasElement state.
15689 view.transitionTo('inDOM');
15690 view.triggerRecursively('didInsertElement');
15693 renderToBufferIfNeeded: function(view) {
15694 return view.renderToBuffer();
15699 setElement: function(view, value) {
15700 if (value !== null) {
15701 view.transitionTo('hasElement');
15714 @submodule ember-views
15717 var get = Ember.get, set = Ember.set, meta = Ember.meta;
15719 var inBuffer = Ember.View.states.inBuffer = Ember.create(Ember.View.states._default);
15721 Ember.merge(inBuffer, {
15722 $: function(view, sel) {
15723 // if we don't have an element yet, someone calling this.$() is
15724 // trying to update an element that isn't in the DOM. Instead,
15725 // rerender the view to allow the render method to reflect the
15731 // when a view is rendered in a buffer, rerendering it simply
15732 // replaces the existing buffer with a new one
15733 rerender: function(view) {
15734 throw new Ember.Error("Something you did caused a view to re-render after it rendered but before it was inserted into the DOM.");
15737 // when a view is rendered in a buffer, appending a child
15738 // view will render that view and append the resulting
15739 // buffer into its buffer.
15740 appendChild: function(view, childView, options) {
15741 var buffer = view.buffer;
15743 childView = view.createChildView(childView, options);
15744 view._childViews.push(childView);
15746 childView.renderToBuffer(buffer);
15748 view.propertyDidChange('childViews');
15753 // when a view is rendered in a buffer, destroying the
15754 // element will simply destroy the buffer and put the
15755 // state back into the preRender state.
15756 destroyElement: function(view) {
15757 view.clearBuffer();
15758 view._notifyWillDestroyElement();
15759 view.transitionTo('preRender');
15764 empty: function() {
15765 Ember.assert("Emptying a view in the inBuffer state is not allowed and should not happen under normal circumstances. Most likely there is a bug in your application. This may be due to excessive property change notifications.");
15768 renderToBufferIfNeeded: function (view) {
15769 return view.buffer;
15772 // It should be impossible for a rendered view to be scheduled for
15774 insertElement: function() {
15775 throw "You can't insert an element that has already been rendered";
15778 setElement: function(view, value) {
15779 if (value === null) {
15780 view.transitionTo('preRender');
15782 view.clearBuffer();
15783 view.transitionTo('hasElement');
15798 @submodule ember-views
15801 var get = Ember.get, set = Ember.set, meta = Ember.meta;
15803 var hasElement = Ember.View.states.hasElement = Ember.create(Ember.View.states._default);
15805 Ember.merge(hasElement, {
15806 $: function(view, sel) {
15807 var elem = get(view, 'element');
15808 return sel ? Ember.$(sel, elem) : Ember.$(elem);
15811 getElement: function(view) {
15812 var parent = get(view, 'parentView');
15813 if (parent) { parent = get(parent, 'element'); }
15814 if (parent) { return view.findElementInParentElement(parent); }
15815 return Ember.$("#" + get(view, 'elementId'))[0];
15818 setElement: function(view, value) {
15819 if (value === null) {
15820 view.transitionTo('preRender');
15822 throw "You cannot set an element to a non-null value when the element is already in the DOM.";
15828 // once the view has been inserted into the DOM, rerendering is
15829 // deferred to allow bindings to synchronize.
15830 rerender: function(view) {
15831 view.triggerRecursively('willClearRender');
15833 view.clearRenderedChildren();
15835 view.domManager.replace(view);
15839 // once the view is already in the DOM, destroying it removes it
15840 // from the DOM, nukes its element, and puts it back into the
15841 // preRender state if inDOM.
15843 destroyElement: function(view) {
15844 view._notifyWillDestroyElement();
15845 view.domManager.remove(view);
15846 set(view, 'element', null);
15847 if (view._scheduledInsert) {
15848 Ember.run.cancel(view._scheduledInsert);
15849 view._scheduledInsert = null;
15854 empty: function(view) {
15855 var _childViews = view._childViews, len, idx;
15857 len = _childViews.length;
15858 for (idx = 0; idx < len; idx++) {
15859 _childViews[idx]._notifyWillDestroyElement();
15862 view.domManager.empty(view);
15865 // Handle events from `Ember.EventDispatcher`
15866 handleEvent: function(view, eventName, evt) {
15867 if (view.has(eventName)) {
15868 // Handler should be able to re-dispatch events, so we don't
15869 // preventDefault or stopPropagation.
15870 return view.trigger(eventName, evt);
15872 return true; // continue event propagation
15877 var inDOM = Ember.View.states.inDOM = Ember.create(hasElement);
15879 Ember.merge(inDOM, {
15880 insertElement: function(view, fn) {
15881 throw "You can't insert an element into the DOM that has already been inserted";
15892 @submodule ember-views
15895 var destroyedError = "You can't call %@ on a destroyed view", fmt = Ember.String.fmt;
15897 var destroyed = Ember.View.states.destroyed = Ember.create(Ember.View.states._default);
15899 Ember.merge(destroyed, {
15900 appendChild: function() {
15901 throw fmt(destroyedError, ['appendChild']);
15903 rerender: function() {
15904 throw fmt(destroyedError, ['rerender']);
15906 destroyElement: function() {
15907 throw fmt(destroyedError, ['destroyElement']);
15909 empty: function() {
15910 throw fmt(destroyedError, ['empty']);
15913 setElement: function() {
15914 throw fmt(destroyedError, ["set('element', ...)"]);
15917 renderToBufferIfNeeded: function() {
15918 throw fmt(destroyedError, ["renderToBufferIfNeeded"]);
15921 // Since element insertion is scheduled, don't do anything if
15922 // the view has been destroyed between scheduling and execution
15923 insertElement: Ember.K
15932 Ember.View.cloneStates = function(from) {
15935 into._default = {};
15936 into.preRender = Ember.create(into._default);
15937 into.destroyed = Ember.create(into._default);
15938 into.inBuffer = Ember.create(into._default);
15939 into.hasElement = Ember.create(into._default);
15940 into.inDOM = Ember.create(into.hasElement);
15944 for (var stateName in from) {
15945 if (!from.hasOwnProperty(stateName)) { continue; }
15946 Ember.merge(into[stateName], from[stateName]);
15957 var states = Ember.View.cloneStates(Ember.View.states);
15961 @submodule ember-views
15964 var get = Ember.get, set = Ember.set, meta = Ember.meta;
15965 var forEach = Ember.EnumerableUtils.forEach;
15967 var childViewsProperty = Ember.computed(function() {
15968 return get(this, '_childViews');
15969 }).property('_childViews');
15972 A `ContainerView` is an `Ember.View` subclass that allows for manual or
15973 programatic management of a view's `childViews` array that will correctly
15974 update the `ContainerView` instance's rendered DOM representation.
15976 ## Setting Initial Child Views
15978 The initial array of child views can be set in one of two ways. You can
15979 provide a `childViews` property at creation time that contains instance of
15983 aContainer = Ember.ContainerView.create({
15984 childViews: [Ember.View.create(), Ember.View.create()]
15988 You can also provide a list of property names whose values are instances of
15992 aContainer = Ember.ContainerView.create({
15993 childViews: ['aView', 'bView', 'cView'],
15994 aView: Ember.View.create(),
15995 bView: Ember.View.create(),
15996 cView: Ember.View.create()
16000 The two strategies can be combined:
16003 aContainer = Ember.ContainerView.create({
16004 childViews: ['aView', Ember.View.create()],
16005 aView: Ember.View.create()
16009 Each child view's rendering will be inserted into the container's rendered
16010 HTML in the same order as its position in the `childViews` property.
16012 ## Adding and Removing Child Views
16014 The views in a container's `childViews` array should be added and removed by
16015 manipulating the `childViews` property directly.
16017 To remove a view pass that view into a `removeObject` call on the container's
16018 `childViews` property.
16020 Given an empty `<body>` the following code
16023 aContainer = Ember.ContainerView.create({
16024 classNames: ['the-container'],
16025 childViews: ['aView', 'bView'],
16026 aView: Ember.View.create({
16027 template: Ember.Handlebars.compile("A")
16029 bView: Ember.View.create({
16030 template: Ember.Handlebars.compile("B")
16034 aContainer.appendTo('body');
16037 Results in the HTML
16040 <div class="ember-view the-container">
16041 <div class="ember-view">A</div>
16042 <div class="ember-view">B</div>
16049 aContainer.get('childViews'); // [aContainer.aView, aContainer.bView]
16050 aContainer.get('childViews').removeObject(aContainer.get('bView'));
16051 aContainer.get('childViews'); // [aContainer.aView]
16054 Will result in the following HTML
16057 <div class="ember-view the-container">
16058 <div class="ember-view">A</div>
16062 Similarly, adding a child view is accomplished by adding `Ember.View` instances to the
16063 container's `childViews` property.
16065 Given an empty `<body>` the following code
16068 aContainer = Ember.ContainerView.create({
16069 classNames: ['the-container'],
16070 childViews: ['aView', 'bView'],
16071 aView: Ember.View.create({
16072 template: Ember.Handlebars.compile("A")
16074 bView: Ember.View.create({
16075 template: Ember.Handlebars.compile("B")
16079 aContainer.appendTo('body');
16082 Results in the HTML
16085 <div class="ember-view the-container">
16086 <div class="ember-view">A</div>
16087 <div class="ember-view">B</div>
16094 AnotherViewClass = Ember.View.extend({
16095 template: Ember.Handlebars.compile("Another view")
16098 aContainer.get('childViews'); // [aContainer.aView, aContainer.bView]
16099 aContainer.get('childViews').pushObject(AnotherViewClass.create());
16100 aContainer.get('childViews'); // [aContainer.aView, aContainer.bView, <AnotherViewClass instance>]
16103 Will result in the following HTML
16106 <div class="ember-view the-container">
16107 <div class="ember-view">A</div>
16108 <div class="ember-view">B</div>
16109 <div class="ember-view">Another view</div>
16113 Direct manipulation of `childViews` presence or absence in the DOM via calls
16114 to `remove` or `removeFromParent` or calls to a container's `removeChild` may
16115 not behave correctly.
16117 Calling `remove()` on a child view will remove the view's HTML, but it will
16118 remain as part of its container's `childView`s property.
16120 Calling `removeChild()` on the container will remove the passed view instance
16121 from the container's `childView`s but keep its HTML within the container's
16124 Calling `removeFromParent()` behaves as expected but should be avoided in
16125 favor of direct manipulation of a container's `childViews` property.
16128 aContainer = Ember.ContainerView.create({
16129 classNames: ['the-container'],
16130 childViews: ['aView', 'bView'],
16131 aView: Ember.View.create({
16132 template: Ember.Handlebars.compile("A")
16134 bView: Ember.View.create({
16135 template: Ember.Handlebars.compile("B")
16139 aContainer.appendTo('body');
16142 Results in the HTML
16145 <div class="ember-view the-container">
16146 <div class="ember-view">A</div>
16147 <div class="ember-view">B</div>
16151 Calling `aContainer.get('aView').removeFromParent()` will result in the
16155 <div class="ember-view the-container">
16156 <div class="ember-view">B</div>
16160 And the `Ember.View` instance stored in `aContainer.aView` will be removed from `aContainer`'s
16161 `childViews` array.
16163 ## Templates and Layout
16165 A `template`, `templateName`, `defaultTemplate`, `layout`, `layoutName` or
16166 `defaultLayout` property on a container view will not result in the template
16167 or layout being rendered. The HTML contents of a `Ember.ContainerView`'s DOM
16168 representation will only be the rendered HTML of its child views.
16170 ## Binding a View to Display
16172 If you would like to display a single view in your ContainerView, you can set
16173 its `currentView` property. When the `currentView` property is set to a view
16174 instance, it will be added to the ContainerView's `childViews` array. If the
16175 `currentView` property is later changed to a different view, the new view
16176 will replace the old view. If `currentView` is set to `null`, the last
16177 `currentView` will be removed.
16179 This functionality is useful for cases where you want to bind the display of
16180 a ContainerView to a controller or state manager. For example, you can bind
16181 the `currentView` of a container to a controller like this:
16184 App.appController = Ember.Object.create({
16185 view: Ember.View.create({
16186 templateName: 'person_template'
16192 {{view Ember.ContainerView currentViewBinding="App.appController.view"}}
16195 @class ContainerView
16197 @extends Ember.View
16200 Ember.ContainerView = Ember.View.extend({
16206 var childViews = get(this, 'childViews');
16207 Ember.defineProperty(this, 'childViews', childViewsProperty);
16209 var _childViews = this._childViews;
16211 forEach(childViews, function(viewName, idx) {
16214 if ('string' === typeof viewName) {
16215 view = get(this, viewName);
16216 view = this.createChildView(view);
16217 set(this, viewName, view);
16219 view = this.createChildView(viewName);
16222 _childViews[idx] = view;
16225 var currentView = get(this, 'currentView');
16226 if (currentView) _childViews.push(this.createChildView(currentView));
16228 // Make the _childViews array observable
16229 Ember.A(_childViews);
16231 // Sets up an array observer on the child views array. This
16232 // observer will detect when child views are added or removed
16233 // and update the DOM to reflect the mutation.
16234 get(this, 'childViews').addArrayObserver(this, {
16235 willChange: 'childViewsWillChange',
16236 didChange: 'childViewsDidChange'
16243 Instructs each child view to render to the passed render buffer.
16246 @param {Ember.RenderBuffer} buffer the buffer to render to
16248 render: function(buffer) {
16249 this.forEachChildView(function(view) {
16250 view.renderToBuffer(buffer);
16254 instrumentName: 'render.container',
16259 When the container view is destroyed, tear down the child views
16262 @method willDestroy
16264 willDestroy: function() {
16265 get(this, 'childViews').removeArrayObserver(this, {
16266 willChange: 'childViewsWillChange',
16267 didChange: 'childViewsDidChange'
16276 When a child view is removed, destroy its element so that
16277 it is removed from the DOM.
16279 The array observer that triggers this action is set up in the
16280 `renderToBuffer` method.
16282 @method childViewsWillChange
16283 @param {Ember.Array} views the child views array before mutation
16284 @param {Number} start the start position of the mutation
16285 @param {Number} removed the number of child views removed
16287 childViewsWillChange: function(views, start, removed) {
16288 if (removed === 0) { return; }
16290 var changedViews = views.slice(start, start+removed);
16291 this.initializeViews(changedViews, null, null);
16293 this.currentState.childViewsWillChange(this, views, start, removed);
16299 When a child view is added, make sure the DOM gets updated appropriately.
16301 If the view has already rendered an element, we tell the child view to
16302 create an element and insert it into the DOM. If the enclosing container
16303 view has already written to a buffer, but not yet converted that buffer
16304 into an element, we insert the string representation of the child into the
16305 appropriate place in the buffer.
16307 @method childViewsDidChange
16308 @param {Ember.Array} views the array of child views afte the mutation has occurred
16309 @param {Number} start the start position of the mutation
16310 @param {Number} removed the number of child views removed
16311 @param {Number} the number of child views added
16313 childViewsDidChange: function(views, start, removed, added) {
16314 var len = get(views, 'length');
16316 // No new child views were added; bail out.
16317 if (added === 0) return;
16319 var changedViews = views.slice(start, start+added);
16320 this.initializeViews(changedViews, this, get(this, 'templateData'));
16322 // Let the current state handle the changes
16323 this.currentState.childViewsDidChange(this, views, start, added);
16326 initializeViews: function(views, parentView, templateData) {
16327 forEach(views, function(view) {
16328 set(view, '_parentView', parentView);
16330 if (!get(view, 'templateData')) {
16331 set(view, 'templateData', templateData);
16338 _currentViewWillChange: Ember.beforeObserver(function() {
16339 var childViews = get(this, 'childViews'),
16340 currentView = get(this, 'currentView');
16343 currentView.destroy();
16344 childViews.removeObject(currentView);
16348 _currentViewDidChange: Ember.observer(function() {
16349 var childViews = get(this, 'childViews'),
16350 currentView = get(this, 'currentView');
16353 childViews.pushObject(currentView);
16357 _ensureChildrenAreInDOM: function () {
16358 this.currentState.ensureChildrenAreInDOM(this);
16362 Ember.merge(states._default, {
16363 childViewsWillChange: Ember.K,
16364 childViewsDidChange: Ember.K,
16365 ensureChildrenAreInDOM: Ember.K
16368 Ember.merge(states.inBuffer, {
16369 childViewsDidChange: function(parentView, views, start, added) {
16370 throw new Error('You cannot modify child views while in the inBuffer state');
16374 Ember.merge(states.hasElement, {
16375 childViewsWillChange: function(view, views, start, removed) {
16376 for (var i=start; i<start+removed; i++) {
16381 childViewsDidChange: function(view, views, start, added) {
16382 Ember.run.scheduleOnce('render', view, '_ensureChildrenAreInDOM');
16385 ensureChildrenAreInDOM: function(view) {
16386 var childViews = view.get('childViews'), i, len, childView, previous, buffer;
16387 for (i = 0, len = childViews.length; i < len; i++) {
16388 childView = childViews[i];
16389 buffer = childView.renderToBufferIfNeeded();
16391 childView.triggerRecursively('willInsertElement');
16393 previous.domManager.after(previous, buffer.string());
16395 view.domManager.prepend(view, buffer.string());
16397 childView.transitionTo('inDOM');
16398 childView.propertyDidChange('element');
16399 childView.triggerRecursively('didInsertElement');
16401 previous = childView;
16413 @submodule ember-views
16416 var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
16419 `Ember.CollectionView` is an `Ember.View` descendent responsible for managing
16420 a collection (an array or array-like object) by maintaing a child view object
16421 and associated DOM representation for each item in the array and ensuring
16422 that child views and their associated rendered HTML are updated when items in
16423 the array are added, removed, or replaced.
16427 The managed collection of objects is referenced as the `Ember.CollectionView`
16428 instance's `content` property.
16431 someItemsView = Ember.CollectionView.create({
16432 content: ['A', 'B','C']
16436 The view for each item in the collection will have its `content` property set
16439 ## Specifying itemViewClass
16441 By default the view class for each item in the managed collection will be an
16442 instance of `Ember.View`. You can supply a different class by setting the
16443 `CollectionView`'s `itemViewClass` property.
16445 Given an empty `<body>` and the following code:
16448 someItemsView = Ember.CollectionView.create({
16449 classNames: ['a-collection'],
16450 content: ['A','B','C'],
16451 itemViewClass: Ember.View.extend({
16452 template: Ember.Handlebars.compile("the letter: {{view.content}}")
16456 someItemsView.appendTo('body');
16459 Will result in the following HTML structure
16462 <div class="ember-view a-collection">
16463 <div class="ember-view">the letter: A</div>
16464 <div class="ember-view">the letter: B</div>
16465 <div class="ember-view">the letter: C</div>
16469 ## Automatic matching of parent/child tagNames
16471 Setting the `tagName` property of a `CollectionView` to any of
16472 "ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result
16473 in the item views receiving an appropriately matched `tagName` property.
16475 Given an empty `<body>` and the following code:
16478 anUndorderedListView = Ember.CollectionView.create({
16480 content: ['A','B','C'],
16481 itemViewClass: Ember.View.extend({
16482 template: Ember.Handlebars.compile("the letter: {{view.content}}")
16486 anUndorderedListView.appendTo('body');
16489 Will result in the following HTML structure
16492 <ul class="ember-view a-collection">
16493 <li class="ember-view">the letter: A</li>
16494 <li class="ember-view">the letter: B</li>
16495 <li class="ember-view">the letter: C</li>
16499 Additional `tagName` pairs can be provided by adding to
16500 `Ember.CollectionView.CONTAINER_MAP `
16503 Ember.CollectionView.CONTAINER_MAP['article'] = 'section'
16506 ## Programatic creation of child views
16508 For cases where additional customization beyond the use of a single
16509 `itemViewClass` or `tagName` matching is required CollectionView's
16510 `createChildView` method can be overidden:
16513 CustomCollectionView = Ember.CollectionView.extend({
16514 createChildView: function(viewClass, attrs) {
16515 if (attrs.content.kind == 'album') {
16516 viewClass = App.AlbumView;
16518 viewClass = App.SongView;
16520 this._super(viewClass, attrs);
16527 You can provide an `Ember.View` subclass to the `Ember.CollectionView`
16528 instance as its `emptyView` property. If the `content` property of a
16529 `CollectionView` is set to `null` or an empty array, an instance of this view
16530 will be the `CollectionView`s only child.
16533 aListWithNothing = Ember.CollectionView.create({
16534 classNames: ['nothing']
16536 emptyView: Ember.View.extend({
16537 template: Ember.Handlebars.compile("The collection is empty")
16541 aListWithNothing.appendTo('body');
16544 Will result in the following HTML structure
16547 <div class="ember-view nothing">
16548 <div class="ember-view">
16549 The collection is empty
16554 ## Adding and Removing items
16556 The `childViews` property of a `CollectionView` should not be directly
16557 manipulated. Instead, add, remove, replace items from its `content` property.
16558 This will trigger appropriate changes to its rendered HTML.
16560 ## Use in templates via the `{{collection}}` `Ember.Handlebars` helper
16562 `Ember.Handlebars` provides a helper specifically for adding
16563 `CollectionView`s to templates. See `Ember.Handlebars.collection` for more
16566 @class CollectionView
16568 @extends Ember.ContainerView
16571 Ember.CollectionView = Ember.ContainerView.extend(
16572 /** @scope Ember.CollectionView.prototype */ {
16575 A list of items to be displayed by the `Ember.CollectionView`.
16586 This provides metadata about what kind of empty view class this
16587 collection would like if it is being instantiated from another
16588 system (like Handlebars)
16590 @property emptyViewClass
16592 emptyViewClass: Ember.View,
16595 An optional view to display if content is set to an empty array.
16597 @property emptyView
16604 @property itemViewClass
16606 @default Ember.View
16608 itemViewClass: Ember.View,
16611 var ret = this._super();
16612 this._contentDidChange();
16616 _contentWillChange: Ember.beforeObserver(function() {
16617 var content = this.get('content');
16619 if (content) { content.removeArrayObserver(this); }
16620 var len = content ? get(content, 'length') : 0;
16621 this.arrayWillChange(content, 0, len);
16627 Check to make sure that the content has changed, and if so,
16628 update the children directly. This is always scheduled
16629 asynchronously, to allow the element to be created before
16630 bindings have synchronized and vice versa.
16632 @method _contentDidChange
16634 _contentDidChange: Ember.observer(function() {
16635 var content = get(this, 'content');
16638 Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content));
16639 content.addArrayObserver(this);
16642 var len = content ? get(content, 'length') : 0;
16643 this.arrayDidChange(content, 0, null, len);
16646 willDestroy: function() {
16647 var content = get(this, 'content');
16648 if (content) { content.removeArrayObserver(this); }
16653 arrayWillChange: function(content, start, removedCount) {
16654 // If the contents were empty before and this template collection has an
16655 // empty view remove it now.
16656 var emptyView = get(this, 'emptyView');
16657 if (emptyView && emptyView instanceof Ember.View) {
16658 emptyView.removeFromParent();
16661 // Loop through child views that correspond with the removed items.
16662 // Note that we loop from the end of the array to the beginning because
16663 // we are mutating it as we go.
16664 var childViews = get(this, 'childViews'), childView, idx, len;
16666 len = get(childViews, 'length');
16668 var removingAll = removedCount === len;
16671 this.currentState.empty(this);
16674 for (idx = start + removedCount - 1; idx >= start; idx--) {
16675 childView = childViews[idx];
16676 if (removingAll) { childView.removedFromDOM = true; }
16677 childView.destroy();
16682 Called when a mutation to the underlying content array occurs.
16684 This method will replay that mutation against the views that compose the
16685 `Ember.CollectionView`, ensuring that the view reflects the model.
16687 This array observer is added in `contentDidChange`.
16689 @method arrayDidChange
16690 @param {Array} addedObjects the objects that were added to the content
16691 @param {Array} removedObjects the objects that were removed from the content
16692 @param {Number} changeIndex the index at which the changes occurred
16694 arrayDidChange: function(content, start, removed, added) {
16695 var itemViewClass = get(this, 'itemViewClass'),
16696 childViews = get(this, 'childViews'),
16697 addedViews = [], view, item, idx, len, itemTagName;
16699 if ('string' === typeof itemViewClass) {
16700 itemViewClass = get(itemViewClass);
16703 Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@", [itemViewClass]), Ember.View.detect(itemViewClass));
16705 len = content ? get(content, 'length') : 0;
16707 for (idx = start; idx < start+added; idx++) {
16708 item = content.objectAt(idx);
16710 view = this.createChildView(itemViewClass, {
16715 addedViews.push(view);
16718 var emptyView = get(this, 'emptyView');
16719 if (!emptyView) { return; }
16721 emptyView = this.createChildView(emptyView);
16722 addedViews.push(emptyView);
16723 set(this, 'emptyView', emptyView);
16725 childViews.replace(start, 0, addedViews);
16728 createChildView: function(view, attrs) {
16729 view = this._super(view, attrs);
16731 var itemTagName = get(view, 'tagName');
16732 var tagName = (itemTagName === null || itemTagName === undefined) ? Ember.CollectionView.CONTAINER_MAP[get(this, 'tagName')] : itemTagName;
16734 set(view, 'tagName', tagName);
16741 A map of parent tags to their default child tags. You can add
16742 additional parent tags if you want collection views that use
16743 a particular parent tag to default to a child tag.
16745 @property CONTAINER_MAP
16750 Ember.CollectionView.CONTAINER_MAP = {
16777 @submodule ember-views
16778 @requires ember-runtime
16785 define("metamorph",
16789 // ==========================================================================
16790 // Project: metamorph
16791 // Copyright: ©2011 My Company Inc. All rights reserved.
16792 // ==========================================================================
16794 var K = function(){},
16796 document = window.document,
16798 // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges
16799 supportsRange = ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
16801 // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
16802 // is a "zero-scope" element. This problem can be worked around by making
16803 // the first node an invisible text node. We, like Modernizr, use ­
16804 needsShy = (function(){
16805 var testEl = document.createElement('div');
16806 testEl.innerHTML = "<div></div>";
16807 testEl.firstChild.innerHTML = "<script></script>";
16808 return testEl.firstChild.innerHTML === '';
16812 // IE 8 (and likely earlier) likes to move whitespace preceeding
16813 // a script tag to appear after it. This means that we can
16814 // accidentally remove whitespace when updating a morph.
16815 movesWhitespace = (function() {
16816 var testEl = document.createElement('div');
16817 testEl.innerHTML = "Test: <script type='text/x-placeholder'></script>Value";
16818 return testEl.childNodes[0].nodeValue === 'Test:' &&
16819 testEl.childNodes[2].nodeValue === ' Value';
16822 // Constructor that supports either Metamorph('foo') or new
16823 // Metamorph('foo');
16825 // Takes a string of HTML as the argument.
16827 var Metamorph = function(html) {
16830 if (this instanceof Metamorph) {
16836 self.innerHTML = html;
16837 var myGuid = 'metamorph-'+(guid++);
16838 self.start = myGuid + '-start';
16839 self.end = myGuid + '-end';
16844 K.prototype = Metamorph.prototype;
16846 var rangeFor, htmlFunc, removeFunc, outerHTMLFunc, appendToFunc, afterFunc, prependFunc, startTagFunc, endTagFunc;
16848 outerHTMLFunc = function() {
16849 return this.startTag() + this.innerHTML + this.endTag();
16852 startTagFunc = function() {
16854 * We replace chevron by its hex code in order to prevent escaping problems.
16855 * Check this thread for more explaination:
16856 * http://stackoverflow.com/questions/8231048/why-use-x3c-instead-of-when-generating-html-from-javascript
16858 return "<script id='" + this.start + "' type='text/x-placeholder'>\x3C/script>";
16861 endTagFunc = function() {
16863 * We replace chevron by its hex code in order to prevent escaping problems.
16864 * Check this thread for more explaination:
16865 * http://stackoverflow.com/questions/8231048/why-use-x3c-instead-of-when-generating-html-from-javascript
16867 return "<script id='" + this.end + "' type='text/x-placeholder'>\x3C/script>";
16870 // If we have the W3C range API, this process is relatively straight forward.
16871 if (supportsRange) {
16873 // Get a range for the current morph. Optionally include the starting and
16874 // ending placeholders.
16875 rangeFor = function(morph, outerToo) {
16876 var range = document.createRange();
16877 var before = document.getElementById(morph.start);
16878 var after = document.getElementById(morph.end);
16881 range.setStartBefore(before);
16882 range.setEndAfter(after);
16884 range.setStartAfter(before);
16885 range.setEndBefore(after);
16891 htmlFunc = function(html, outerToo) {
16892 // get a range for the current metamorph object
16893 var range = rangeFor(this, outerToo);
16895 // delete the contents of the range, which will be the
16896 // nodes between the starting and ending placeholder.
16897 range.deleteContents();
16899 // create a new document fragment for the HTML
16900 var fragment = range.createContextualFragment(html);
16902 // insert the fragment into the range
16903 range.insertNode(fragment);
16906 removeFunc = function() {
16907 // get a range for the current metamorph object including
16908 // the starting and ending placeholders.
16909 var range = rangeFor(this, true);
16911 // delete the entire range.
16912 range.deleteContents();
16915 appendToFunc = function(node) {
16916 var range = document.createRange();
16917 range.setStart(node);
16918 range.collapse(false);
16919 var frag = range.createContextualFragment(this.outerHTML());
16920 node.appendChild(frag);
16923 afterFunc = function(html) {
16924 var range = document.createRange();
16925 var after = document.getElementById(this.end);
16927 range.setStartAfter(after);
16928 range.setEndAfter(after);
16930 var fragment = range.createContextualFragment(html);
16931 range.insertNode(fragment);
16934 prependFunc = function(html) {
16935 var range = document.createRange();
16936 var start = document.getElementById(this.start);
16938 range.setStartAfter(start);
16939 range.setEndAfter(start);
16941 var fragment = range.createContextualFragment(html);
16942 range.insertNode(fragment);
16947 * This code is mostly taken from jQuery, with one exception. In jQuery's case, we
16948 * have some HTML and we need to figure out how to convert it into some nodes.
16950 * In this case, jQuery needs to scan the HTML looking for an opening tag and use
16951 * that as the key for the wrap map. In our case, we know the parent node, and
16952 * can use its type as the key for the wrap map.
16955 select: [ 1, "<select multiple='multiple'>", "</select>" ],
16956 fieldset: [ 1, "<fieldset>", "</fieldset>" ],
16957 table: [ 1, "<table>", "</table>" ],
16958 tbody: [ 2, "<table><tbody>", "</tbody></table>" ],
16959 tr: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
16960 colgroup: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
16961 map: [ 1, "<map>", "</map>" ],
16962 _default: [ 0, "", "" ]
16965 var findChildById = function(element, id) {
16966 if (element.getAttribute('id') === id) { return element; }
16968 var len = element.childNodes.length, idx, node, found;
16969 for (idx=0; idx<len; idx++) {
16970 node = element.childNodes[idx];
16971 found = node.nodeType === 1 && findChildById(node, id);
16972 if (found) { return found; }
16976 var setInnerHTML = function(element, html) {
16978 if (movesWhitespace) {
16979 // Right now we only check for script tags with ids with the
16980 // goal of targeting morphs.
16981 html = html.replace(/(\s+)(<script id='([^']+)')/g, function(match, spaces, tag, id) {
16982 matches.push([id, spaces]);
16987 element.innerHTML = html;
16989 // If we have to do any whitespace adjustments do them now
16990 if (matches.length > 0) {
16991 var len = matches.length, idx;
16992 for (idx=0; idx<len; idx++) {
16993 var script = findChildById(element, matches[idx][0]),
16994 node = document.createTextNode(matches[idx][1]);
16995 script.parentNode.insertBefore(node, script);
17001 * Given a parent node and some HTML, generate a set of nodes. Return the first
17002 * node, which will allow us to traverse the rest using nextSibling.
17004 * We need to do this because innerHTML in IE does not really parse the nodes.
17006 var firstNodeFor = function(parentNode, html) {
17007 var arr = wrapMap[parentNode.tagName.toLowerCase()] || wrapMap._default;
17008 var depth = arr[0], start = arr[1], end = arr[2];
17010 if (needsShy) { html = '­'+html; }
17012 var element = document.createElement('div');
17014 setInnerHTML(element, start + html + end);
17016 for (var i=0; i<=depth; i++) {
17017 element = element.firstChild;
17020 // Look for ­ to remove it.
17022 var shyElement = element;
17024 // Sometimes we get nameless elements with the shy inside
17025 while (shyElement.nodeType === 1 && !shyElement.nodeName) {
17026 shyElement = shyElement.firstChild;
17029 // At this point it's the actual unicode character.
17030 if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") {
17031 shyElement.nodeValue = shyElement.nodeValue.slice(1);
17039 * In some cases, Internet Explorer can create an anonymous node in
17040 * the hierarchy with no tagName. You can create this scenario via:
17042 * div = document.createElement("div");
17043 * div.innerHTML = "<table>­<script></script><tr><td>hi</td></tr></table>";
17044 * div.firstChild.firstChild.tagName //=> ""
17046 * If our script markers are inside such a node, we need to find that
17047 * node and use *it* as the marker.
17049 var realNode = function(start) {
17050 while (start.parentNode.tagName === "") {
17051 start = start.parentNode;
17058 * When automatically adding a tbody, Internet Explorer inserts the
17059 * tbody immediately before the first <tr>. Other browsers create it
17060 * before the first node, no matter what.
17062 * This means the the following code:
17064 * div = document.createElement("div");
17065 * div.innerHTML = "<table><script id='first'></script><tr><td>hi</td></tr><script id='last'></script></table>
17067 * Generates the following DOM in IE:
17071 * - script id='first'
17076 * - script id='last'
17078 * Which means that the two script tags, even though they were
17079 * inserted at the same point in the hierarchy in the original
17080 * HTML, now have different parents.
17082 * This code reparents the first script tag by making it the tbody's
17085 var fixParentage = function(start, end) {
17086 if (start.parentNode !== end.parentNode) {
17087 end.parentNode.insertBefore(start, end.parentNode.firstChild);
17091 htmlFunc = function(html, outerToo) {
17092 // get the real starting node. see realNode for details.
17093 var start = realNode(document.getElementById(this.start));
17094 var end = document.getElementById(this.end);
17095 var parentNode = end.parentNode;
17096 var node, nextSibling, last;
17098 // make sure that the start and end nodes share the same
17099 // parent. If not, fix it.
17100 fixParentage(start, end);
17102 // remove all of the nodes after the starting placeholder and
17103 // before the ending placeholder.
17104 node = start.nextSibling;
17106 nextSibling = node.nextSibling;
17107 last = node === end;
17109 // if this is the last node, and we want to remove it as well,
17110 // set the `end` node to the next sibling. This is because
17111 // for the rest of the function, we insert the new nodes
17112 // before the end (note that insertBefore(node, null) is
17113 // the same as appendChild(node)).
17115 // if we do not want to remove it, just break.
17117 if (outerToo) { end = node.nextSibling; } else { break; }
17120 node.parentNode.removeChild(node);
17122 // if this is the last node and we didn't break before
17123 // (because we wanted to remove the outer nodes), break
17125 if (last) { break; }
17127 node = nextSibling;
17130 // get the first node for the HTML string, even in cases like
17131 // tables and lists where a simple innerHTML on a div would
17132 // swallow some of the content.
17133 node = firstNodeFor(start.parentNode, html);
17135 // copy the nodes for the HTML between the starting and ending
17138 nextSibling = node.nextSibling;
17139 parentNode.insertBefore(node, end);
17140 node = nextSibling;
17144 // remove the nodes in the DOM representing this metamorph.
17146 // this includes the starting and ending placeholders.
17147 removeFunc = function() {
17148 var start = realNode(document.getElementById(this.start));
17149 var end = document.getElementById(this.end);
17152 start.parentNode.removeChild(start);
17153 end.parentNode.removeChild(end);
17156 appendToFunc = function(parentNode) {
17157 var node = firstNodeFor(parentNode, this.outerHTML());
17161 nextSibling = node.nextSibling;
17162 parentNode.appendChild(node);
17163 node = nextSibling;
17167 afterFunc = function(html) {
17168 // get the real starting node. see realNode for details.
17169 var end = document.getElementById(this.end);
17170 var insertBefore = end.nextSibling;
17171 var parentNode = end.parentNode;
17175 // get the first node for the HTML string, even in cases like
17176 // tables and lists where a simple innerHTML on a div would
17177 // swallow some of the content.
17178 node = firstNodeFor(parentNode, html);
17180 // copy the nodes for the HTML between the starting and ending
17183 nextSibling = node.nextSibling;
17184 parentNode.insertBefore(node, insertBefore);
17185 node = nextSibling;
17189 prependFunc = function(html) {
17190 var start = document.getElementById(this.start);
17191 var parentNode = start.parentNode;
17195 node = firstNodeFor(parentNode, html);
17196 var insertBefore = start.nextSibling;
17199 nextSibling = node.nextSibling;
17200 parentNode.insertBefore(node, insertBefore);
17201 node = nextSibling;
17206 Metamorph.prototype.html = function(html) {
17207 this.checkRemoved();
17208 if (html === undefined) { return this.innerHTML; }
17210 htmlFunc.call(this, html);
17212 this.innerHTML = html;
17215 Metamorph.prototype.replaceWith = function(html) {
17216 this.checkRemoved();
17217 htmlFunc.call(this, html, true);
17220 Metamorph.prototype.remove = removeFunc;
17221 Metamorph.prototype.outerHTML = outerHTMLFunc;
17222 Metamorph.prototype.appendTo = appendToFunc;
17223 Metamorph.prototype.after = afterFunc;
17224 Metamorph.prototype.prepend = prependFunc;
17225 Metamorph.prototype.startTag = startTagFunc;
17226 Metamorph.prototype.endTag = endTagFunc;
17228 Metamorph.prototype.isRemoved = function() {
17229 var before = document.getElementById(this.start);
17230 var after = document.getElementById(this.end);
17232 return !before || !after;
17235 Metamorph.prototype.checkRemoved = function() {
17236 if (this.isRemoved()) {
17237 throw new Error("Cannot perform operations on a Metamorph that is not in the DOM.");
17249 @submodule ember-handlebars
17252 // Eliminate dependency on any Ember to simplify precompilation workflow
17253 var objectCreate = Object.create || function(parent) {
17255 F.prototype = parent;
17259 var Handlebars = this.Handlebars || Ember.imports.Handlebars;
17260 Ember.assert("Ember Handlebars requires Handlebars 1.0.beta.5 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.beta\.[56789]$|^1\.0\.rc\.[123456789]+/));
17263 Prepares the Handlebars templating library for use inside Ember's view
17266 The `Ember.Handlebars` object is the standard Handlebars library, extended to
17267 use Ember's `get()` method instead of direct property access, which allows
17268 computed properties to be used inside templates.
17270 To create an `Ember.Handlebars` template, call `Ember.Handlebars.compile()`.
17271 This will return a function that can be used by `Ember.View` for rendering.
17276 Ember.Handlebars = objectCreate(Handlebars);
17280 @namespace Ember.Handlebars
17282 Ember.Handlebars.helpers = objectCreate(Handlebars.helpers);
17285 Override the the opcode compiler and JavaScript compiler for Handlebars.
17288 @namespace Ember.Handlebars
17292 Ember.Handlebars.Compiler = function() {};
17294 // Handlebars.Compiler doesn't exist in runtime-only
17295 if (Handlebars.Compiler) {
17296 Ember.Handlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
17299 Ember.Handlebars.Compiler.prototype.compiler = Ember.Handlebars.Compiler;
17302 @class JavaScriptCompiler
17303 @namespace Ember.Handlebars
17307 Ember.Handlebars.JavaScriptCompiler = function() {};
17309 // Handlebars.JavaScriptCompiler doesn't exist in runtime-only
17310 if (Handlebars.JavaScriptCompiler) {
17311 Ember.Handlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
17312 Ember.Handlebars.JavaScriptCompiler.prototype.compiler = Ember.Handlebars.JavaScriptCompiler;
17316 Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars";
17319 Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() {
17326 Override the default buffer for Ember Handlebars. By default, Handlebars
17327 creates an empty String at the beginning of each invocation and appends to
17328 it. Ember's Handlebars overrides this to append to a single shared buffer.
17330 @method appendToBuffer
17331 @param string {String}
17333 Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
17334 return "data.buffer.push("+string+");";
17340 Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that
17341 all simple mustaches in Ember's Handlebars will also set up an observer to
17342 keep the DOM up to date when the underlying property changes.
17345 @for Ember.Handlebars.Compiler
17348 Ember.Handlebars.Compiler.prototype.mustache = function(mustache) {
17349 if (mustache.params.length || mustache.hash) {
17350 return Handlebars.Compiler.prototype.mustache.call(this, mustache);
17352 var id = new Handlebars.AST.IdNode(['_triageMustache']);
17354 // Update the mustache node to include a hash value indicating whether the original node
17355 // was escaped. This will allow us to properly escape values when the underlying value
17356 // changes and we need to re-render the value.
17357 if(!mustache.escaped) {
17358 mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
17359 mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
17361 mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
17362 return Handlebars.Compiler.prototype.mustache.call(this, mustache);
17367 Used for precompilation of Ember Handlebars templates. This will not be used
17368 during normal app execution.
17371 @for Ember.Handlebars
17373 @param {String} string The template to precompile
17375 Ember.Handlebars.precompile = function(string) {
17376 var ast = Handlebars.parse(string);
17385 _triageMustache: true
17391 var environment = new Ember.Handlebars.Compiler().compile(ast, options);
17392 return new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
17395 // We don't support this for Handlebars runtime-only
17396 if (Handlebars.compile) {
17398 The entry point for Ember Handlebars. This replaces the default
17399 `Handlebars.compile` and turns on template-local data and String
17403 @for Ember.Handlebars
17405 @param {String} string The template to compile
17408 Ember.Handlebars.compile = function(string) {
17409 var ast = Handlebars.parse(string);
17410 var options = { data: true, stringParams: true };
17411 var environment = new Ember.Handlebars.Compiler().compile(ast, options);
17412 var templateSpec = new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
17414 return Ember.Handlebars.template(templateSpec);
17425 If a path starts with a reserved keyword, returns the root
17426 that should be used.
17428 @method normalizePath
17430 @param root {Object}
17431 @param path {String}
17434 var normalizePath = Ember.Handlebars.normalizePath = function(root, path, data) {
17435 var keywords = (data && data.keywords) || {},
17436 keyword, isKeyword;
17438 // Get the first segment of the path. For example, if the
17439 // path is "foo.bar.baz", returns "foo".
17440 keyword = path.split('.', 1)[0];
17442 // Test to see if the first path is a keyword that has been
17443 // passed along in the view's data hash. If so, we will treat
17444 // that object as the new root.
17445 if (keywords.hasOwnProperty(keyword)) {
17446 // Look up the value in the template's data hash.
17447 root = keywords[keyword];
17450 // Handle cases where the entire path is the reserved
17451 // word. In that case, return the object itself.
17452 if (path === keyword) {
17455 // Strip the keyword from the path and look up
17456 // the remainder from the newly found root.
17457 path = path.substr(keyword.length+1);
17461 return { root: root, path: path, isKeyword: isKeyword };
17466 Lookup both on root and on window. If the path starts with
17467 a keyword, the corresponding object will be looked up in the
17468 template's data hash and used to resolve the path.
17471 @for Ember.Handlebars
17472 @param {Object} root The object to look up the property on
17473 @param {String} path The path to be lookedup
17474 @param {Object} options The template's option hash
17476 Ember.Handlebars.get = function(root, path, options) {
17477 var data = options && options.data,
17478 normalizedPath = normalizePath(root, path, data),
17481 // In cases where the path begins with a keyword, change the
17482 // root to the value represented by that keyword, and ensure
17483 // the path is relative to it.
17484 root = normalizedPath.root;
17485 path = normalizedPath.path;
17487 value = Ember.get(root, path);
17489 // If the path starts with a capital letter, look it up on Ember.lookup,
17490 // which defaults to the `window` object in browsers.
17491 if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) {
17492 value = Ember.get(Ember.lookup, path);
17496 Ember.Handlebars.getPath = Ember.deprecateFunc('`Ember.Handlebars.getPath` has been changed to `Ember.Handlebars.get` for consistency.', Ember.Handlebars.get);
17501 Registers a helper in Handlebars that will be called if no property with the
17502 given name can be found on the current context object, and no helper with
17503 that name is registered.
17505 This throws an exception with a more helpful error message so the user can
17506 track down where the problem is happening.
17508 @method helperMissing
17509 @for Ember.Handlebars.helpers
17510 @param {String} path
17511 @param {Hash} options
17513 Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
17514 var error, view = "";
17516 error = "%@ Handlebars error: Could not find property '%@' on object %@.";
17518 view = options.data.view;
17520 throw new Ember.Error(Ember.String.fmt(error, [view, path, this]));
17524 Register a bound handlebars helper. Bound helpers behave similarly to regular
17525 handlebars helpers, with the added ability to re-render when the underlying data
17531 Ember.Handlebars.registerBoundHelper('capitalize', function(value) {
17532 return value.toUpperCase();
17536 The above bound helper can be used inside of templates as follows:
17539 {{capitalize name}}
17542 In this case, when the `name` property of the template's context changes,
17543 the rendered value of the helper will update to reflect this change.
17545 ## Example with options
17547 Like normal handlebars helpers, bound helpers have access to the options
17548 passed into the helper call.
17551 Ember.Handlebars.registerBoundHelper('repeat', function(value, options) {
17552 var count = options.hash.count;
17554 while(a.length < count){
17561 This helper could be used in a template as follows:
17564 {{repeat text count=3}}
17567 ## Example with extra dependencies
17569 The `Ember.Handlebars.registerBoundHelper` method takes a variable length
17570 third parameter which indicates extra dependencies on the passed in value.
17571 This allows the handlebars helper to update when these dependencies change.
17574 Ember.Handlebars.registerBoundHelper('capitalizeName', function(value) {
17575 return value.get('name').toUpperCase();
17579 @method registerBoundHelper
17580 @for Ember.Handlebars
17581 @param {String} name
17582 @param {Function} function
17583 @param {String} dependentKeys*
17585 Ember.Handlebars.registerBoundHelper = function(name, fn) {
17586 var dependentKeys = Array.prototype.slice.call(arguments, 2);
17587 Ember.Handlebars.registerHelper(name, function(property, options) {
17588 var data = options.data,
17590 currentContext = (options.contexts && options.contexts[0]) || this,
17591 pathRoot, path, normalized,
17594 normalized = Ember.Handlebars.normalizePath(currentContext, property, data);
17596 pathRoot = normalized.root;
17597 path = normalized.path;
17599 var bindView = new Ember._SimpleHandlebarsView(
17600 path, pathRoot, !options.hash.unescaped, options.data
17603 bindView.normalizedValue = function() {
17604 var value = Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView);
17605 return fn.call(view, value, options);
17608 view.appendChild(bindView);
17610 observer = function() {
17611 Ember.run.scheduleOnce('render', bindView, 'rerender');
17614 Ember.addObserver(pathRoot, path, observer);
17616 while(loc < dependentKeys.length) {
17617 Ember.addObserver(pathRoot, path + '.' + dependentKeys[loc], observer);
17621 view.one('willClearRender', function() {
17622 Ember.removeObserver(pathRoot, path, observer);
17624 while(loc < dependentKeys.length) {
17625 Ember.removeObserver(pathRoot, path + '.' + dependentKeys[loc], observer);
17635 Overrides Handlebars.template so that we can distinguish
17636 user-created, top-level templates from inner contexts.
17639 @for Ember.Handlebars
17640 @param {String} template spec
17642 Ember.Handlebars.template = function(spec){
17643 var t = Handlebars.template(spec);
17659 Ember.String.htmlSafe = function(str) {
17660 return new Handlebars.SafeString(str);
17663 var htmlSafe = Ember.String.htmlSafe;
17665 if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
17668 See {{#crossLink "Ember.String/htmlSafe"}}{{/crossLink}}
17673 String.prototype.htmlSafe = function() {
17674 return htmlSafe(this);
17683 Ember.Handlebars.resolvePaths = function(options) {
17685 contexts = options.contexts,
17686 roots = options.roots,
17687 data = options.data;
17689 for (var i=0, l=contexts.length; i<l; i++) {
17690 ret.push( Ember.Handlebars.get(roots[i], contexts[i], { data: data }) );
17701 /*jshint newcap:false*/
17704 @submodule ember-handlebars
17707 var set = Ember.set, get = Ember.get;
17708 var Metamorph = requireModule('metamorph');
17710 // DOMManager should just abstract dom manipulation between jquery and metamorph
17712 remove: function(view) {
17713 view.morph.remove();
17716 prepend: function(view, html) {
17717 view.morph.prepend(html);
17720 after: function(view, html) {
17721 view.morph.after(html);
17724 html: function(view, html) {
17725 view.morph.html(html);
17728 // This is messed up.
17729 replace: function(view) {
17730 var morph = view.morph;
17732 view.transitionTo('preRender');
17734 Ember.run.schedule('render', this, function() {
17735 if (get(view, 'isDestroyed')) { return; }
17737 view.clearRenderedChildren();
17738 var buffer = view.renderToBuffer();
17740 view.invokeRecursively(function(view) {
17741 view.propertyDidChange('element');
17744 view.triggerRecursively('willInsertElement');
17745 morph.replaceWith(buffer.string());
17746 view.transitionTo('inDOM');
17747 view.triggerRecursively('didInsertElement');
17751 empty: function(view) {
17752 view.morph.html("");
17756 // The `morph` and `outerHTML` properties are internal only
17757 // and not observable.
17762 @extends Ember.Mixin
17765 Ember._Metamorph = Ember.Mixin.create({
17769 instrumentName: 'render.metamorph',
17773 this.morph = Metamorph();
17776 beforeRender: function(buffer) {
17777 buffer.push(this.morph.startTag());
17778 buffer.pushOpeningTag();
17781 afterRender: function(buffer) {
17782 buffer.pushClosingTag();
17783 buffer.push(this.morph.endTag());
17786 createElement: function() {
17787 var buffer = this.renderToBuffer();
17788 this.outerHTML = buffer.string();
17789 this.clearBuffer();
17792 domManager: DOMManager
17796 @class _MetamorphView
17798 @extends Ember.View
17799 @uses Ember._Metamorph
17802 Ember._MetamorphView = Ember.View.extend(Ember._Metamorph);
17805 @class _SimpleMetamorphView
17807 @extends Ember.View
17808 @uses Ember._Metamorph
17811 Ember._SimpleMetamorphView = Ember.CoreView.extend(Ember._Metamorph);
17819 /*globals Handlebars */
17820 /*jshint newcap:false*/
17823 @submodule ember-handlebars
17826 var get = Ember.get, set = Ember.set, handlebarsGet = Ember.Handlebars.get;
17827 var Metamorph = requireModule('metamorph');
17828 function SimpleHandlebarsView(path, pathRoot, isEscaped, templateData) {
17830 this.pathRoot = pathRoot;
17831 this.isEscaped = isEscaped;
17832 this.templateData = templateData;
17834 this.morph = Metamorph();
17835 this.state = 'preRender';
17836 this.updateId = null;
17839 Ember._SimpleHandlebarsView = SimpleHandlebarsView;
17841 SimpleHandlebarsView.prototype = {
17845 destroy: function () {
17846 if (this.updateId) {
17847 Ember.run.cancel(this.updateId);
17848 this.updateId = null;
17853 propertyDidChange: Ember.K,
17855 normalizedValue: function() {
17856 var path = this.path,
17857 pathRoot = this.pathRoot,
17858 result, templateData;
17860 // Use the pathRoot as the result if no path is provided. This
17861 // happens if the path is `this`, which gets normalized into
17862 // a `pathRoot` of the current Handlebars context and a path
17867 templateData = this.templateData;
17868 result = handlebarsGet(pathRoot, path, { data: templateData });
17874 renderToBuffer: function(buffer) {
17877 string += this.morph.startTag();
17878 string += this.render();
17879 string += this.morph.endTag();
17881 buffer.push(string);
17884 render: function() {
17885 // If not invoked via a triple-mustache ({{{foo}}}), escape
17886 // the content of the template.
17887 var escape = this.isEscaped;
17888 var result = this.normalizedValue();
17890 if (result === null || result === undefined) {
17892 } else if (!(result instanceof Handlebars.SafeString)) {
17893 result = String(result);
17896 if (escape) { result = Handlebars.Utils.escapeExpression(result); }
17900 rerender: function() {
17901 switch(this.state) {
17906 throw new Ember.Error("Something you did tried to replace an {{expression}} before it was inserted into the DOM.");
17909 this.updateId = Ember.run.scheduleOnce('render', this, 'update');
17916 update: function () {
17917 this.updateId = null;
17918 this.morph.html(this.render());
17921 transitionTo: function(state) {
17922 this.state = state;
17926 var states = Ember.View.cloneStates(Ember.View.states), merge = Ember.merge;
17928 merge(states._default, {
17929 rerenderIfNeeded: Ember.K
17932 merge(states.inDOM, {
17933 rerenderIfNeeded: function(view) {
17934 if (get(view, 'normalizedValue') !== view._lastNormalizedValue) {
17941 `Ember._HandlebarsBoundView` is a private view created by the Handlebars
17942 `{{bind}}` helpers that is used to keep track of bound properties.
17944 Every time a property is bound using a `{{mustache}}`, an anonymous subclass
17945 of `Ember._HandlebarsBoundView` is created with the appropriate sub-template
17946 and context set up. When the associated property changes, just the template
17947 for this view will re-render.
17949 @class _HandlebarsBoundView
17951 @extends Ember._MetamorphView
17954 Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
17955 instrumentName: 'render.boundHandlebars',
17959 The function used to determine if the `displayTemplate` or
17960 `inverseTemplate` should be rendered. This should be a function that takes
17961 a value and returns a Boolean.
17963 @property shouldDisplayFunc
17967 shouldDisplayFunc: null,
17970 Whether the template rendered by this view gets passed the context object
17971 of its parent template, or gets passed the value of retrieving `path`
17972 from the `pathRoot`.
17974 For example, this is true when using the `{{#if}}` helper, because the
17975 template inside the helper should look up properties relative to the same
17976 object as outside the block. This would be `false` when used with `{{#with
17977 foo}}` because the template should receive the object found by evaluating
17980 @property preserveContext
17984 preserveContext: false,
17987 If `preserveContext` is true, this is the object that will be used
17988 to render the template.
17990 @property previousContext
17993 previousContext: null,
17996 The template to render when `shouldDisplayFunc` evaluates to `true`.
17998 @property displayTemplate
18002 displayTemplate: null,
18005 The template to render when `shouldDisplayFunc` evaluates to `false`.
18007 @property inverseTemplate
18011 inverseTemplate: null,
18015 The path to look up on `pathRoot` that is passed to
18016 `shouldDisplayFunc` to determine which template to render.
18018 In addition, if `preserveContext` is `false,` the object at this path will
18019 be passed to the template when rendering.
18028 The object from which the `path` will be looked up. Sometimes this is the
18029 same as the `previousContext`, but in cases where this view has been
18030 generated for paths that start with a keyword such as `view` or
18031 `controller`, the path root will be that resolved object.
18038 normalizedValue: Ember.computed(function() {
18039 var path = get(this, 'path'),
18040 pathRoot = get(this, 'pathRoot'),
18041 valueNormalizer = get(this, 'valueNormalizerFunc'),
18042 result, templateData;
18044 // Use the pathRoot as the result if no path is provided. This
18045 // happens if the path is `this`, which gets normalized into
18046 // a `pathRoot` of the current Handlebars context and a path
18051 templateData = get(this, 'templateData');
18052 result = handlebarsGet(pathRoot, path, { data: templateData });
18055 return valueNormalizer ? valueNormalizer(result) : result;
18056 }).property('path', 'pathRoot', 'valueNormalizerFunc').volatile(),
18058 rerenderIfNeeded: function() {
18059 this.currentState.rerenderIfNeeded(this);
18063 Determines which template to invoke, sets up the correct state based on
18064 that logic, then invokes the default `Ember.View` `render` implementation.
18066 This method will first look up the `path` key on `pathRoot`,
18067 then pass that value to the `shouldDisplayFunc` function. If that returns
18068 `true,` the `displayTemplate` function will be rendered to DOM. Otherwise,
18069 `inverseTemplate`, if specified, will be rendered.
18071 For example, if this `Ember._HandlebarsBoundView` represented the `{{#with
18072 foo}}` helper, it would look up the `foo` property of its context, and
18073 `shouldDisplayFunc` would always return true. The object found by looking
18074 up `foo` would be passed to `displayTemplate`.
18077 @param {Ember.RenderBuffer} buffer
18079 render: function(buffer) {
18080 // If not invoked via a triple-mustache ({{{foo}}}), escape
18081 // the content of the template.
18082 var escape = get(this, 'isEscaped');
18084 var shouldDisplay = get(this, 'shouldDisplayFunc'),
18085 preserveContext = get(this, 'preserveContext'),
18086 context = get(this, 'previousContext');
18088 var inverseTemplate = get(this, 'inverseTemplate'),
18089 displayTemplate = get(this, 'displayTemplate');
18091 var result = get(this, 'normalizedValue');
18092 this._lastNormalizedValue = result;
18094 // First, test the conditional to see if we should
18095 // render the template or not.
18096 if (shouldDisplay(result)) {
18097 set(this, 'template', displayTemplate);
18099 // If we are preserving the context (for example, if this
18100 // is an #if block, call the template with the same object.
18101 if (preserveContext) {
18102 set(this, '_context', context);
18104 // Otherwise, determine if this is a block bind or not.
18105 // If so, pass the specified object to the template
18106 if (displayTemplate) {
18107 set(this, '_context', result);
18109 // This is not a bind block, just push the result of the
18110 // expression to the render context and return.
18111 if (result === null || result === undefined) {
18113 } else if (!(result instanceof Handlebars.SafeString)) {
18114 result = String(result);
18117 if (escape) { result = Handlebars.Utils.escapeExpression(result); }
18118 buffer.push(result);
18122 } else if (inverseTemplate) {
18123 set(this, 'template', inverseTemplate);
18125 if (preserveContext) {
18126 set(this, '_context', context);
18128 set(this, '_context', result);
18131 set(this, 'template', function() { return ''; });
18134 return this._super(buffer);
18145 @submodule ember-handlebars
18148 var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
18149 var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
18150 var forEach = Ember.ArrayPolyfills.forEach;
18152 var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers;
18154 // Binds a property into the DOM. This will create a hook in DOM that the
18155 // KVO system will look for and update if the property changes.
18156 function bind(property, options, preserveContext, shouldDisplay, valueNormalizer) {
18157 var data = options.data,
18159 inverse = options.inverse,
18161 currentContext = this,
18162 pathRoot, path, normalized,
18165 normalized = normalizePath(currentContext, property, data);
18167 pathRoot = normalized.root;
18168 path = normalized.path;
18170 // Set up observers for observable objects
18171 if ('object' === typeof this) {
18172 if (data.insideGroup) {
18173 observer = function() {
18174 Ember.run.once(view, 'rerender');
18177 var template, context, result = handlebarsGet(pathRoot, path, options);
18179 result = valueNormalizer(result);
18181 context = preserveContext ? currentContext : result;
18182 if (shouldDisplay(result)) {
18184 } else if (inverse) {
18185 template = inverse;
18188 template(context, { data: options.data });
18190 // Create the view that will wrap the output of this template/property
18191 // and add it to the nearest view's childViews array.
18192 // See the documentation of Ember._HandlebarsBoundView for more.
18193 var bindView = view.createChildView(Ember._HandlebarsBoundView, {
18194 preserveContext: preserveContext,
18195 shouldDisplayFunc: shouldDisplay,
18196 valueNormalizerFunc: valueNormalizer,
18197 displayTemplate: fn,
18198 inverseTemplate: inverse,
18200 pathRoot: pathRoot,
18201 previousContext: currentContext,
18202 isEscaped: !options.hash.unescaped,
18203 templateData: options.data
18206 view.appendChild(bindView);
18208 observer = function() {
18209 Ember.run.scheduleOnce('render', bindView, 'rerenderIfNeeded');
18213 // Observes the given property on the context and
18214 // tells the Ember._HandlebarsBoundView to re-render. If property
18215 // is an empty string, we are printing the current context
18216 // object ({{this}}) so updating it is not our responsibility.
18218 Ember.addObserver(pathRoot, path, observer);
18220 view.one('willClearRender', function() {
18221 Ember.removeObserver(pathRoot, path, observer);
18225 // The object is not observable, so just render it out and
18226 // be done with it.
18227 data.buffer.push(handlebarsGet(pathRoot, path, options));
18231 function simpleBind(property, options) {
18232 var data = options.data,
18234 currentContext = this,
18235 pathRoot, path, normalized,
18238 normalized = normalizePath(currentContext, property, data);
18240 pathRoot = normalized.root;
18241 path = normalized.path;
18243 // Set up observers for observable objects
18244 if ('object' === typeof this) {
18245 if (data.insideGroup) {
18246 observer = function() {
18247 Ember.run.once(view, 'rerender');
18250 var result = handlebarsGet(pathRoot, path, options);
18251 if (result === null || result === undefined) { result = ""; }
18252 data.buffer.push(result);
18254 var bindView = new Ember._SimpleHandlebarsView(
18255 path, pathRoot, !options.hash.unescaped, options.data
18258 bindView._parentView = view;
18259 view.appendChild(bindView);
18261 observer = function() {
18262 Ember.run.scheduleOnce('render', bindView, 'rerender');
18266 // Observes the given property on the context and
18267 // tells the Ember._HandlebarsBoundView to re-render. If property
18268 // is an empty string, we are printing the current context
18269 // object ({{this}}) so updating it is not our responsibility.
18271 Ember.addObserver(pathRoot, path, observer);
18273 view.one('willClearRender', function() {
18274 Ember.removeObserver(pathRoot, path, observer);
18278 // The object is not observable, so just render it out and
18279 // be done with it.
18280 data.buffer.push(handlebarsGet(pathRoot, path, options));
18287 '_triageMustache' is used internally select between a binding and helper for
18288 the given context. Until this point, it would be hard to determine if the
18289 mustache is a property reference or a regular helper reference. This triage
18290 helper resolves that.
18292 This would not be typically invoked by directly.
18294 @method _triageMustache
18295 @for Ember.Handlebars.helpers
18296 @param {String} property Property/helperID to triage
18297 @param {Function} fn Context to provide for rendering
18298 @return {String} HTML string
18300 EmberHandlebars.registerHelper('_triageMustache', function(property, fn) {
18301 Ember.assert("You cannot pass more than one argument to the _triageMustache helper", arguments.length <= 2);
18302 if (helpers[property]) {
18303 return helpers[property].call(this, fn);
18306 return helpers.bind.apply(this, arguments);
18313 `bind` can be used to display a value, then update that value if it
18314 changes. For example, if you wanted to print the `title` property of
18318 {{bind "content.title"}}
18321 This will return the `title` property as a string, then create a new observer
18322 at the specified path. If it changes, it will update the value in DOM. Note
18323 that if you need to support IE7 and IE8 you must modify the model objects
18324 properties using `Ember.get()` and `Ember.set()` for this to work as it
18325 relies on Ember's KVO system. For all other browsers this will be handled for
18329 @for Ember.Handlebars.helpers
18330 @param {String} property Property to bind
18331 @param {Function} fn Context to provide for rendering
18332 @return {String} HTML string
18334 EmberHandlebars.registerHelper('bind', function(property, options) {
18335 Ember.assert("You cannot pass more than one argument to the bind helper", arguments.length <= 2);
18337 var context = (options.contexts && options.contexts[0]) || this;
18340 return simpleBind.call(context, property, options);
18343 return bind.call(context, property, options, false, function(result) {
18344 return !Ember.isNone(result);
18351 Use the `boundIf` helper to create a conditional that re-evaluates
18352 whenever the truthiness of the bound value changes.
18355 {{#boundIf "content.shouldDisplayTitle"}}
18361 @for Ember.Handlebars.helpers
18362 @param {String} property Property to bind
18363 @param {Function} fn Context to provide for rendering
18364 @return {String} HTML string
18366 EmberHandlebars.registerHelper('boundIf', function(property, fn) {
18367 var context = (fn.contexts && fn.contexts[0]) || this;
18368 var func = function(result) {
18369 if (Ember.typeOf(result) === 'array') {
18370 return get(result, 'length') !== 0;
18376 return bind.call(context, property, fn, true, func, func);
18381 @for Ember.Handlebars.helpers
18382 @param {Function} context
18383 @param {Hash} options
18384 @return {String} HTML string
18386 EmberHandlebars.registerHelper('with', function(context, options) {
18387 if (arguments.length === 4) {
18388 var keywordName, path, rootPath, normalized;
18390 Ember.assert("If you pass more than one argument to the with helper, it must be in the form #with foo as bar", arguments[1] === "as");
18391 options = arguments[3];
18392 keywordName = arguments[2];
18393 path = arguments[0];
18395 Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
18397 if (Ember.isGlobalPath(path)) {
18398 Ember.bind(options.data.keywords, keywordName, path);
18400 normalized = normalizePath(this, path, options.data);
18401 path = normalized.path;
18402 rootPath = normalized.root;
18404 // This is a workaround for the fact that you cannot bind separate objects
18405 // together. When we implement that functionality, we should use it here.
18406 var contextKey = Ember.$.expando + Ember.guidFor(rootPath);
18407 options.data.keywords[contextKey] = rootPath;
18409 // if the path is '' ("this"), just bind directly to the current context
18410 var contextPath = path ? contextKey + '.' + path : contextKey;
18411 Ember.bind(options.data.keywords, keywordName, contextPath);
18414 return bind.call(this, path, options, true, function(result) {
18415 return !Ember.isNone(result);
18418 Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2);
18419 Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
18420 return helpers.bind.call(options.contexts[0], context, options);
18429 @for Ember.Handlebars.helpers
18430 @param {Function} context
18431 @param {Hash} options
18432 @return {String} HTML string
18434 EmberHandlebars.registerHelper('if', function(context, options) {
18435 Ember.assert("You must pass exactly one argument to the if helper", arguments.length === 2);
18436 Ember.assert("You must pass a block to the if helper", options.fn && options.fn !== Handlebars.VM.noop);
18438 return helpers.boundIf.call(options.contexts[0], context, options);
18443 @for Ember.Handlebars.helpers
18444 @param {Function} context
18445 @param {Hash} options
18446 @return {String} HTML string
18448 EmberHandlebars.registerHelper('unless', function(context, options) {
18449 Ember.assert("You must pass exactly one argument to the unless helper", arguments.length === 2);
18450 Ember.assert("You must pass a block to the unless helper", options.fn && options.fn !== Handlebars.VM.noop);
18452 var fn = options.fn, inverse = options.inverse;
18454 options.fn = inverse;
18455 options.inverse = fn;
18457 return helpers.boundIf.call(options.contexts[0], context, options);
18461 `bindAttr` allows you to create a binding between DOM element attributes and
18462 Ember objects. For example:
18465 <img {{bindAttr src="imageUrl" alt="imageTitle"}}>
18468 The above handlebars template will fill the `<img>`'s `src` attribute will
18469 the value of the property referenced with `"imageUrl"` and its `alt`
18470 attribute with the value of the property referenced with `"imageTitle"`.
18472 If the rendering context of this template is the following object:
18476 imageUrl: 'http://lolcats.info/haz-a-funny',
18477 imageTitle: 'A humorous image of a cat'
18481 The resulting HTML output will be:
18484 <img src="http://lolcats.info/haz-a-funny" alt="A humorous image of a cat">
18487 `bindAttr` cannot redeclare existing DOM element attributes. The use of `src`
18488 in the following `bindAttr` example will be ignored and the hard coded value
18489 of `src="/failwhale.gif"` will take precedence:
18492 <img src="/failwhale.gif" {{bindAttr src="imageUrl" alt="imageTitle"}}>
18495 ### `bindAttr` and the `class` attribute
18497 `bindAttr` supports a special syntax for handling a number of cases unique
18498 to the `class` DOM element attribute. The `class` attribute combines
18499 multiple discreet values into a single attribute as a space-delimited
18500 list of strings. Each string can be:
18502 * a string return value of an object's property.
18503 * a boolean return value of an object's property
18504 * a hard-coded value
18506 A string return value works identically to other uses of `bindAttr`. The
18507 return value of the property will become the value of the attribute. For
18508 example, the following view and template:
18511 AView = Ember.View.extend({
18512 someProperty: function(){
18519 <img {{bindAttr class="view.someProperty}}>
18522 Result in the following rendered output:
18525 <img class="aValue">
18528 A boolean return value will insert a specified class name if the property
18529 returns `true` and remove the class name if the property returns `false`.
18531 A class name is provided via the syntax
18532 `somePropertyName:class-name-if-true`.
18535 AView = Ember.View.extend({
18541 <img {{bindAttr class="view.someBool:class-name-if-true"}}>
18544 Result in the following rendered output:
18547 <img class="class-name-if-true">
18550 An additional section of the binding can be provided if you want to
18551 replace the existing class instead of removing it when the boolean
18555 <img {{bindAttr class="view.someBool:class-name-if-true:class-name-if-false"}}>
18558 A hard-coded value can be used by prepending `:` to the desired
18559 class name: `:class-name-to-always-apply`.
18562 <img {{bindAttr class=":class-name-to-always-apply"}}>
18565 Results in the following rendered output:
18568 <img class=":class-name-to-always-apply">
18571 All three strategies - string return value, boolean return value, and
18572 hard-coded value – can be combined in a single declaration:
18575 <img {{bindAttr class=":class-name-to-always-apply view.someBool:class-name-if-true view.someProperty"}}>
18579 @for Ember.Handlebars.helpers
18580 @param {Hash} options
18581 @return {String} HTML string
18583 EmberHandlebars.registerHelper('bindAttr', function(options) {
18585 var attrs = options.hash;
18587 Ember.assert("You must specify at least one hash argument to bindAttr", !!Ember.keys(attrs).length);
18589 var view = options.data.view;
18593 // Generate a unique id for this element. This will be added as a
18594 // data attribute to the element so it can be looked up when
18595 // the bound property changes.
18596 var dataId = ++Ember.uuid;
18598 // Handle classes differently, as we can bind multiple classes
18599 var classBindings = attrs['class'];
18600 if (classBindings !== null && classBindings !== undefined) {
18601 var classResults = EmberHandlebars.bindClasses(this, classBindings, view, dataId, options);
18603 ret.push('class="' + Handlebars.Utils.escapeExpression(classResults.join(' ')) + '"');
18604 delete attrs['class'];
18607 var attrKeys = Ember.keys(attrs);
18609 // For each attribute passed, create an observer and emit the
18610 // current value of the property as an attribute.
18611 forEach.call(attrKeys, function(attr) {
18612 var path = attrs[attr],
18613 pathRoot, normalized;
18615 Ember.assert(fmt("You must provide a String for a bound attribute, not %@", [path]), typeof path === 'string');
18617 normalized = normalizePath(ctx, path, options.data);
18619 pathRoot = normalized.root;
18620 path = normalized.path;
18622 var value = (path === 'this') ? pathRoot : handlebarsGet(pathRoot, path, options),
18623 type = Ember.typeOf(value);
18625 Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [value]), value === null || value === undefined || type === 'number' || type === 'string' || type === 'boolean');
18627 var observer, invoker;
18629 observer = function observer() {
18630 var result = handlebarsGet(pathRoot, path, options);
18632 Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [result]), result === null || result === undefined || typeof result === 'number' || typeof result === 'string' || typeof result === 'boolean');
18634 var elem = view.$("[data-bindattr-" + dataId + "='" + dataId + "']");
18636 // If we aren't able to find the element, it means the element
18637 // to which we were bound has been removed from the view.
18638 // In that case, we can assume the template has been re-rendered
18639 // and we need to clean up the observer.
18640 if (!elem || elem.length === 0) {
18641 Ember.removeObserver(pathRoot, path, invoker);
18645 Ember.View.applyAttributeBindings(elem, attr, result);
18648 invoker = function() {
18649 Ember.run.scheduleOnce('render', observer);
18652 // Add an observer to the view for when the property changes.
18653 // When the observer fires, find the element using the
18654 // unique data id and update the attribute to the new value.
18655 if (path !== 'this') {
18656 Ember.addObserver(pathRoot, path, invoker);
18658 view.one('willClearRender', function() {
18659 Ember.removeObserver(pathRoot, path, invoker);
18663 // if this changes, also change the logic in ember-views/lib/views/view.js
18664 if ((type === 'string' || (type === 'number' && !isNaN(value)))) {
18665 ret.push(attr + '="' + Handlebars.Utils.escapeExpression(value) + '"');
18666 } else if (value && type === 'boolean') {
18667 // The developer controls the attr name, so it should always be safe
18668 ret.push(attr + '="' + attr + '"');
18672 // Add the unique identifier
18673 // NOTE: We use all lower-case since Firefox has problems with mixed case in SVG
18674 ret.push('data-bindattr-' + dataId + '="' + dataId + '"');
18675 return new EmberHandlebars.SafeString(ret.join(' '));
18681 Helper that, given a space-separated string of property paths and a context,
18682 returns an array of class names. Calling this method also has the side
18683 effect of setting up observers at those property paths, such that if they
18684 change, the correct class name will be reapplied to the DOM element.
18686 For example, if you pass the string "fooBar", it will first look up the
18687 "fooBar" value of the context. If that value is true, it will add the
18688 "foo-bar" class to the current element (i.e., the dasherized form of
18689 "fooBar"). If the value is a string, it will add that string as the class.
18690 Otherwise, it will not add any new class name.
18692 @method bindClasses
18693 @for Ember.Handlebars
18694 @param {Ember.Object} context The context from which to lookup properties
18695 @param {String} classBindings A string, space-separated, of class bindings
18697 @param {Ember.View} view The view in which observers should look for the
18699 @param {Srting} bindAttrId Optional bindAttr id used to lookup elements
18700 @return {Array} An array of class names to add
18702 EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, options) {
18703 var ret = [], newClass, value, elem;
18705 // Helper method to retrieve the property from the context and
18706 // determine which class string to return, based on whether it is
18707 // a Boolean or not.
18708 var classStringForPath = function(root, parsedPath, options) {
18710 path = parsedPath.path;
18712 if (path === 'this') {
18714 } else if (path === '') {
18717 val = handlebarsGet(root, path, options);
18720 return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
18723 // For each property passed, loop through and setup
18725 forEach.call(classBindings.split(' '), function(binding) {
18727 // Variable in which the old class value is saved. The observer function
18728 // closes over this variable, so it knows which string to remove when
18729 // the property changes.
18732 var observer, invoker;
18734 var parsedPath = Ember.View._parsePropertyPath(binding),
18735 path = parsedPath.path,
18736 pathRoot = context,
18739 if (path !== '' && path !== 'this') {
18740 normalized = normalizePath(context, path, options.data);
18742 pathRoot = normalized.root;
18743 path = normalized.path;
18746 // Set up an observer on the context. If the property changes, toggle the
18748 observer = function() {
18749 // Get the current value of the property
18750 newClass = classStringForPath(pathRoot, parsedPath, options);
18751 elem = bindAttrId ? view.$("[data-bindattr-" + bindAttrId + "='" + bindAttrId + "']") : view.$();
18753 // If we can't find the element anymore, a parent template has been
18754 // re-rendered and we've been nuked. Remove the observer.
18755 if (!elem || elem.length === 0) {
18756 Ember.removeObserver(pathRoot, path, invoker);
18758 // If we had previously added a class to the element, remove it.
18760 elem.removeClass(oldClass);
18763 // If necessary, add a new class. Make sure we keep track of it so
18764 // it can be removed in the future.
18766 elem.addClass(newClass);
18767 oldClass = newClass;
18774 invoker = function() {
18775 Ember.run.scheduleOnce('render', observer);
18778 if (path !== '' && path !== 'this') {
18779 Ember.addObserver(pathRoot, path, invoker);
18781 view.one('willClearRender', function() {
18782 Ember.removeObserver(pathRoot, path, invoker);
18786 // We've already setup the observer; now we just need to figure out the
18787 // correct behavior right now on the first pass through.
18788 value = classStringForPath(pathRoot, parsedPath, options);
18793 // Make sure we save the current value so that it can be removed if the
18808 /*globals Handlebars */
18810 // TODO: Don't require the entire module
18813 @submodule ember-handlebars
18816 var get = Ember.get, set = Ember.set;
18817 var PARENT_VIEW_PATH = /^parentView\./;
18818 var EmberHandlebars = Ember.Handlebars;
18820 EmberHandlebars.ViewHelper = Ember.Object.create({
18822 propertiesFromHTMLOptions: function(options, thisContext) {
18823 var hash = options.hash, data = options.data;
18824 var extensions = {},
18825 classes = hash['class'],
18829 extensions.elementId = hash.id;
18834 classes = classes.split(' ');
18835 extensions.classNames = classes;
18839 if (hash.classBinding) {
18840 extensions.classNameBindings = hash.classBinding.split(' ');
18844 if (hash.classNameBindings) {
18845 if (extensions.classNameBindings === undefined) extensions.classNameBindings = [];
18846 extensions.classNameBindings = extensions.classNameBindings.concat(hash.classNameBindings.split(' '));
18850 if (hash.attributeBindings) {
18851 Ember.assert("Setting 'attributeBindings' via Handlebars is not allowed. Please subclass Ember.View and set it there instead.");
18852 extensions.attributeBindings = null;
18857 hash = Ember.$.extend({}, hash);
18859 delete hash['class'];
18860 delete hash.classBinding;
18863 // Set the proper context for all bindings passed to the helper. This applies to regular attribute bindings
18864 // as well as class name bindings. If the bindings are local, make them relative to the current context
18865 // instead of the view.
18868 // Evaluate the context of regular attribute bindings:
18869 for (var prop in hash) {
18870 if (!hash.hasOwnProperty(prop)) { continue; }
18872 // Test if the property ends in "Binding"
18873 if (Ember.IS_BINDING.test(prop) && typeof hash[prop] === 'string') {
18874 path = this.contextualizeBindingPath(hash[prop], data);
18875 if (path) { hash[prop] = path; }
18879 // Evaluate the context of class name bindings:
18880 if (extensions.classNameBindings) {
18881 for (var b in extensions.classNameBindings) {
18882 var full = extensions.classNameBindings[b];
18883 if (typeof full === 'string') {
18884 // Contextualize the path of classNameBinding so this:
18886 // classNameBinding="isGreen:green"
18888 // is converted to this:
18890 // classNameBinding="_parentView.context.isGreen:green"
18891 var parsedPath = Ember.View._parsePropertyPath(full);
18892 path = this.contextualizeBindingPath(parsedPath.path, data);
18893 if (path) { extensions.classNameBindings[b] = path + parsedPath.classNames; }
18898 return Ember.$.extend(hash, extensions);
18901 // Transform bindings from the current context to a context that can be evaluated within the view.
18902 // Returns null if the path shouldn't be changed.
18904 // TODO: consider the addition of a prefix that would allow this method to return `path`.
18905 contextualizeBindingPath: function(path, data) {
18906 var normalized = Ember.Handlebars.normalizePath(null, path, data);
18907 if (normalized.isKeyword) {
18908 return 'templateData.keywords.' + path;
18909 } else if (Ember.isGlobalPath(path)) {
18911 } else if (path === 'this') {
18912 return '_parentView.context';
18914 return '_parentView.context.' + path;
18918 helper: function(thisContext, path, options) {
18919 var inverse = options.inverse,
18920 data = options.data,
18923 hash = options.hash,
18926 if ('string' === typeof path) {
18927 newView = EmberHandlebars.get(thisContext, path, options);
18928 Ember.assert("Unable to find view at path '" + path + "'", !!newView);
18933 Ember.assert(Ember.String.fmt('You must pass a view to the #view helper, not %@ (%@)', [path, newView]), Ember.View.detect(newView) || Ember.View.detectInstance(newView));
18935 var viewOptions = this.propertiesFromHTMLOptions(options, thisContext);
18936 var currentView = data.view;
18937 viewOptions.templateData = options.data;
18938 var newViewProto = newView.proto ? newView.proto() : newView;
18941 Ember.assert("You cannot provide a template block if you also specified a templateName", !get(viewOptions, 'templateName') && !get(newViewProto, 'templateName'));
18942 viewOptions.template = fn;
18945 // We only want to override the `_context` computed property if there is
18946 // no specified controller. See View#_context for more information.
18947 if (!newViewProto.controller && !newViewProto.controllerBinding && !viewOptions.controller && !viewOptions.controllerBinding) {
18948 viewOptions._context = thisContext;
18951 currentView.appendChild(newView, viewOptions);
18956 `{{view}}` inserts a new instance of `Ember.View` into a template passing its
18957 options to the `Ember.View`'s `create` method and using the supplied block as
18958 the view's own template.
18960 An empty `<body>` and the following template:
18964 {{#view tagName="span"}}
18969 Will result in HTML structure:
18973 <!-- Note: the handlebars template script
18974 also results in a rendered Ember.View
18975 which is the outer <div> here -->
18977 <div class="ember-view">
18979 <span id="ember1" class="ember-view">
18986 ### `parentView` setting
18988 The `parentView` property of the new `Ember.View` instance created through
18989 `{{view}}` will be set to the `Ember.View` instance of the template where
18990 `{{view}}` was called.
18993 aView = Ember.View.create({
18994 template: Ember.Handlebars.compile("{{#view}} my parent: {{parentView.elementId}} {{/view}}")
18997 aView.appendTo('body');
19000 Will result in HTML structure:
19003 <div id="ember1" class="ember-view">
19004 <div id="ember2" class="ember-view">
19010 ### Setting CSS id and class attributes
19012 The HTML `id` attribute can be set on the `{{view}}`'s resulting element with
19013 the `id` option. This option will _not_ be passed to `Ember.View.create`.
19016 {{#view tagName="span" id="a-custom-id"}}
19021 Results in the following HTML structure:
19024 <div class="ember-view">
19025 <span id="a-custom-id" class="ember-view">
19031 The HTML `class` attribute can be set on the `{{view}}`'s resulting element
19032 with the `class` or `classNameBindings` options. The `class` option will
19033 directly set the CSS `class` attribute and will not be passed to
19034 `Ember.View.create`. `classNameBindings` will be passed to `create` and use
19035 `Ember.View`'s class name binding functionality:
19038 {{#view tagName="span" class="a-custom-class"}}
19043 Results in the following HTML structure:
19046 <div class="ember-view">
19047 <span id="ember2" class="ember-view a-custom-class">
19053 ### Supplying a different view class
19055 `{{view}}` can take an optional first argument before its supplied options to
19056 specify a path to a custom view class.
19059 {{#view "MyApp.CustomView"}}
19064 The first argument can also be a relative path. Ember will search for the
19065 view class starting at the `Ember.View` of the template where `{{view}}` was
19066 used as the root object:
19069 MyApp = Ember.Application.create({});
19070 MyApp.OuterView = Ember.View.extend({
19071 innerViewClass: Ember.View.extend({
19072 classNames: ['a-custom-view-class-as-property']
19074 template: Ember.Handlebars.compile('{{#view "innerViewClass"}} hi {{/view}}')
19077 MyApp.OuterView.create().appendTo('body');
19080 Will result in the following HTML:
19083 <div id="ember1" class="ember-view">
19084 <div id="ember2" class="ember-view a-custom-view-class-as-property">
19092 If you supply a custom `Ember.View` subclass that specifies its own template
19093 or provide a `templateName` option to `{{view}}` it can be used without
19094 supplying a block. Attempts to use both a `templateName` option and supply a
19095 block will throw an error.
19098 {{view "MyApp.ViewWithATemplateDefined"}}
19101 ### `viewName` property
19103 You can supply a `viewName` option to `{{view}}`. The `Ember.View` instance
19104 will be referenced as a property of its parent view by this name.
19107 aView = Ember.View.create({
19108 template: Ember.Handlebars.compile('{{#view viewName="aChildByName"}} hi {{/view}}')
19111 aView.appendTo('body');
19112 aView.get('aChildByName') // the instance of Ember.View created by {{view}} helper
19116 @for Ember.Handlebars.helpers
19117 @param {String} path
19118 @param {Hash} options
19119 @return {String} HTML string
19121 EmberHandlebars.registerHelper('view', function(path, options) {
19122 Ember.assert("The view helper only takes a single argument", arguments.length <= 2);
19124 // If no path is provided, treat path param as options.
19125 if (path && path.data && path.data.isRenderData) {
19127 path = "Ember.View";
19130 return EmberHandlebars.ViewHelper.helper(this, path, options);
19139 /*globals Handlebars */
19141 // TODO: Don't require all of this module
19144 @submodule ember-handlebars
19147 var get = Ember.get, handlebarsGet = Ember.Handlebars.get, fmt = Ember.String.fmt;
19150 `{{collection}}` is a `Ember.Handlebars` helper for adding instances of
19151 `Ember.CollectionView` to a template. See `Ember.CollectionView` for
19152 additional information on how a `CollectionView` functions.
19154 `{{collection}}`'s primary use is as a block helper with a `contentBinding`
19155 option pointing towards an `Ember.Array`-compatible object. An `Ember.View`
19156 instance will be created for each item in its `content` property. Each view
19157 will have its own `content` property set to the appropriate item in the
19160 The provided block will be applied as the template for each item's view.
19162 Given an empty `<body>` the following template:
19165 {{#collection contentBinding="App.items"}}
19166 Hi {{view.content.name}}
19170 And the following application code
19173 App = Ember.Application.create()
19175 Ember.Object.create({name: 'Dave'}),
19176 Ember.Object.create({name: 'Mary'}),
19177 Ember.Object.create({name: 'Sara'})
19181 Will result in the HTML structure below
19184 <div class="ember-view">
19185 <div class="ember-view">Hi Dave</div>
19186 <div class="ember-view">Hi Mary</div>
19187 <div class="ember-view">Hi Sara</div>
19193 If you provide an `itemViewClass` option that has its own `template` you can
19196 The following template:
19199 {{collection contentBinding="App.items" itemViewClass="App.AnItemView"}}
19202 And application code
19205 App = Ember.Application.create();
19207 Ember.Object.create({name: 'Dave'}),
19208 Ember.Object.create({name: 'Mary'}),
19209 Ember.Object.create({name: 'Sara'})
19212 App.AnItemView = Ember.View.extend({
19213 template: Ember.Handlebars.compile("Greetings {{view.content.name}}")
19217 Will result in the HTML structure below
19220 <div class="ember-view">
19221 <div class="ember-view">Greetings Dave</div>
19222 <div class="ember-view">Greetings Mary</div>
19223 <div class="ember-view">Greetings Sara</div>
19227 ### Specifying a CollectionView subclass
19229 By default the `{{collection}}` helper will create an instance of
19230 `Ember.CollectionView`. You can supply a `Ember.CollectionView` subclass to
19231 the helper by passing it as the first argument:
19234 {{#collection App.MyCustomCollectionClass contentBinding="App.items"}}
19235 Hi {{view.content.name}}
19239 ### Forwarded `item.*`-named Options
19241 As with the `{{view}}`, helper options passed to the `{{collection}}` will be
19242 set on the resulting `Ember.CollectionView` as properties. Additionally,
19243 options prefixed with `item` will be applied to the views rendered for each
19244 item (note the camelcasing):
19247 {{#collection contentBinding="App.items"
19249 itemClassNames="greeting"}}
19250 Howdy {{view.content.name}}
19254 Will result in the following HTML structure:
19257 <div class="ember-view">
19258 <p class="ember-view greeting">Howdy Dave</p>
19259 <p class="ember-view greeting">Howdy Mary</p>
19260 <p class="ember-view greeting">Howdy Sara</p>
19265 @for Ember.Handlebars.helpers
19266 @param {String} path
19267 @param {Hash} options
19268 @return {String} HTML string
19269 @deprecated Use `{{each}}` helper instead.
19271 Ember.Handlebars.registerHelper('collection', function(path, options) {
19272 Ember.deprecate("Using the {{collection}} helper without specifying a class has been deprecated as the {{each}} helper now supports the same functionality.", path !== 'collection');
19274 // If no path is provided, treat path param as options.
19275 if (path && path.data && path.data.isRenderData) {
19278 Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 1);
19280 Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 2);
19283 var fn = options.fn;
19284 var data = options.data;
19285 var inverse = options.inverse;
19286 var view = options.data.view;
19288 // If passed a path string, convert that into an object.
19289 // Otherwise, just default to the standard class.
19290 var collectionClass;
19291 collectionClass = path ? handlebarsGet(this, path, options) : Ember.CollectionView;
19292 Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass);
19294 var hash = options.hash, itemHash = {}, match;
19296 // Extract item view class if provided else default to the standard class
19297 var itemViewClass, itemViewPath = hash.itemViewClass;
19298 var collectionPrototype = collectionClass.proto();
19299 delete hash.itemViewClass;
19300 itemViewClass = itemViewPath ? handlebarsGet(collectionPrototype, itemViewPath, options) : collectionPrototype.itemViewClass;
19301 Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewPath]), !!itemViewClass);
19303 // Go through options passed to the {{collection}} helper and extract options
19304 // that configure item views instead of the collection itself.
19305 for (var prop in hash) {
19306 if (hash.hasOwnProperty(prop)) {
19307 match = prop.match(/^item(.)(.*)$/);
19310 // Convert itemShouldFoo -> shouldFoo
19311 itemHash[match[1].toLowerCase() + match[2]] = hash[prop];
19312 // Delete from hash as this will end up getting passed to the
19313 // {{view}} helper method.
19319 var tagName = hash.tagName || collectionPrototype.tagName;
19322 itemHash.template = fn;
19326 var emptyViewClass;
19327 if (inverse && inverse !== Handlebars.VM.noop) {
19328 emptyViewClass = get(collectionPrototype, 'emptyViewClass');
19329 emptyViewClass = emptyViewClass.extend({
19331 tagName: itemHash.tagName
19333 } else if (hash.emptyViewClass) {
19334 emptyViewClass = handlebarsGet(this, hash.emptyViewClass, options);
19336 hash.emptyView = emptyViewClass;
19338 if (hash.eachHelper === 'each') {
19339 itemHash._context = Ember.computed(function() {
19340 return get(this, 'content');
19341 }).property('content');
19342 delete hash.eachHelper;
19345 var viewString = view.toString();
19347 var viewOptions = Ember.Handlebars.ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this);
19348 hash.itemViewClass = itemViewClass.extend(viewOptions);
19350 return Ember.Handlebars.helpers.view.call(this, collectionClass, options);
19359 /*globals Handlebars */
19362 @submodule ember-handlebars
19365 var handlebarsGet = Ember.Handlebars.get;
19368 `unbound` allows you to output a property without binding. *Important:* The
19369 output will not be updated if the property changes. Use with caution.
19372 <div>{{unbound somePropertyThatDoesntChange}}</div>
19376 @for Ember.Handlebars.helpers
19377 @param {String} property
19378 @return {String} HTML string
19380 Ember.Handlebars.registerHelper('unbound', function(property, fn) {
19381 var context = (fn.contexts && fn.contexts[0]) || this;
19382 return handlebarsGet(context, property, fn);
19390 /*jshint debug:true*/
19393 @submodule ember-handlebars
19396 var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
19399 `log` allows you to output the value of a value in the current rendering
19407 @for Ember.Handlebars.helpers
19408 @param {String} property
19410 Ember.Handlebars.registerHelper('log', function(property, options) {
19411 var context = (options.contexts && options.contexts[0]) || this,
19412 normalized = normalizePath(context, property, options.data),
19413 pathRoot = normalized.root,
19414 path = normalized.path,
19415 value = (path === 'this') ? pathRoot : handlebarsGet(pathRoot, path, options);
19416 Ember.Logger.log(value);
19420 Execute the `debugger` statement in the current context.
19427 @for Ember.Handlebars.helpers
19428 @param {String} property
19430 Ember.Handlebars.registerHelper('debugger', function() {
19441 @submodule ember-handlebars
19444 var get = Ember.get, set = Ember.set;
19446 Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, {
19447 itemViewClass: Ember._MetamorphView,
19448 emptyViewClass: Ember._MetamorphView,
19450 createChildView: function(view, attrs) {
19451 view = this._super(view, attrs);
19453 // At the moment, if a container view subclass wants
19454 // to insert keywords, it is responsible for cloning
19455 // the keywords hash. This will be fixed momentarily.
19456 var keyword = get(this, 'keyword');
19459 var data = get(view, 'templateData');
19461 data = Ember.copy(data);
19462 data.keywords = view.cloneKeywords();
19463 set(view, 'templateData', data);
19465 var content = get(view, 'content');
19467 // In this case, we do not bind, because the `content` of
19468 // a #each item cannot change.
19469 data.keywords[keyword] = content;
19476 var GroupedEach = Ember.Handlebars.GroupedEach = function(context, path, options) {
19478 normalized = Ember.Handlebars.normalizePath(context, path, options.data);
19480 this.context = context;
19482 this.options = options;
19483 this.template = options.fn;
19484 this.containingView = options.data.view;
19485 this.normalizedRoot = normalized.root;
19486 this.normalizedPath = normalized.path;
19487 this.content = this.lookupContent();
19489 this.addContentObservers();
19490 this.addArrayObservers();
19492 this.containingView.on('willClearRender', function() {
19497 GroupedEach.prototype = {
19498 contentWillChange: function() {
19499 this.removeArrayObservers();
19502 contentDidChange: function() {
19503 this.content = this.lookupContent();
19504 this.addArrayObservers();
19505 this.rerenderContainingView();
19508 contentArrayWillChange: Ember.K,
19510 contentArrayDidChange: function() {
19511 this.rerenderContainingView();
19514 lookupContent: function() {
19515 return Ember.Handlebars.get(this.normalizedRoot, this.normalizedPath, this.options);
19518 addArrayObservers: function() {
19519 this.content.addArrayObserver(this, {
19520 willChange: 'contentArrayWillChange',
19521 didChange: 'contentArrayDidChange'
19525 removeArrayObservers: function() {
19526 this.content.removeArrayObserver(this, {
19527 willChange: 'contentArrayWillChange',
19528 didChange: 'contentArrayDidChange'
19532 addContentObservers: function() {
19533 Ember.addBeforeObserver(this.normalizedRoot, this.normalizedPath, this, this.contentWillChange);
19534 Ember.addObserver(this.normalizedRoot, this.normalizedPath, this, this.contentDidChange);
19537 removeContentObservers: function() {
19538 Ember.removeBeforeObserver(this.normalizedRoot, this.normalizedPath, this.contentWillChange);
19539 Ember.removeObserver(this.normalizedRoot, this.normalizedPath, this.contentDidChange);
19542 render: function() {
19543 var content = this.content,
19544 contentLength = get(content, 'length'),
19545 data = this.options.data,
19546 template = this.template;
19548 data.insideEach = true;
19549 for (var i = 0; i < contentLength; i++) {
19550 template(content.objectAt(i), { data: data });
19554 rerenderContainingView: function() {
19555 Ember.run.scheduleOnce('render', this.containingView, 'rerender');
19558 destroy: function() {
19559 this.removeContentObservers();
19560 this.removeArrayObservers();
19565 The `{{#each}}` helper loops over elements in a collection, rendering its
19566 block once for each item:
19569 Developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}];
19573 {{#each Developers}}
19578 `{{each}}` supports an alternative syntax with element naming:
19581 {{#each person in Developers}}
19586 When looping over objects that do not have properties, `{{this}}` can be used
19587 to render the object:
19590 DeveloperNames = ['Yehuda', 'Tom', 'Paul']
19594 {{#each DeveloperNames}}
19601 If you provide an `itemViewClass` option that has its own `template` you can
19602 omit the block in a similar way to how it can be done with the collection
19605 The following template:
19608 {{#view App.MyView }}
19609 {{each view.items itemViewClass="App.AnItemView"}}
19613 And application code
19616 App = Ember.Application.create({
19617 MyView: Ember.View.extend({
19619 Ember.Object.create({name: 'Dave'}),
19620 Ember.Object.create({name: 'Mary'}),
19621 Ember.Object.create({name: 'Sara'})
19626 App.AnItemView = Ember.View.extend({
19627 template: Ember.Handlebars.compile("Greetings {{name}}")
19633 Will result in the HTML structure below
19636 <div class="ember-view">
19637 <div class="ember-view">Greetings Dave</div>
19638 <div class="ember-view">Greetings Mary</div>
19639 <div class="ember-view">Greetings Sara</div>
19644 @for Ember.Handlebars.helpers
19645 @param [name] {String} name for item (used with `in`)
19646 @param path {String} path
19648 Ember.Handlebars.registerHelper('each', function(path, options) {
19649 if (arguments.length === 4) {
19650 Ember.assert("If you pass more than one argument to the each helper, it must be in the form #each foo in bar", arguments[1] === "in");
19652 var keywordName = arguments[0];
19654 options = arguments[3];
19655 path = arguments[2];
19656 if (path === '') { path = "this"; }
19658 options.hash.keyword = keywordName;
19660 options.hash.eachHelper = 'each';
19663 options.hash.contentBinding = path;
19664 // Set up emptyView as a metamorph with no tag
19665 //options.hash.emptyViewClass = Ember._MetamorphView;
19667 if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) {
19668 new Ember.Handlebars.GroupedEach(this, path, options).render();
19670 return Ember.Handlebars.helpers.collection.call(this, 'Ember.Handlebars.EachView', options);
19681 @submodule ember-handlebars
19685 `template` allows you to render a template from inside another template.
19686 This allows you to re-use the same template in multiple places. For example:
19689 <script type="text/x-handlebars" data-template-name="logged_in_user">
19690 {{#with loggedInUser}}
19691 Last Login: {{lastLogin}}
19692 User Info: {{template "user_info"}}
19698 <script type="text/x-handlebars" data-template-name="user_info">
19699 Name: <em>{{name}}</em>
19700 Karma: <em>{{karma}}</em>
19704 This helper looks for templates in the global `Ember.TEMPLATES` hash. If you
19705 add `<script>` tags to your page with the `data-template-name` attribute set,
19706 they will be compiled and placed in this hash automatically.
19708 You can also manually register templates by adding them to the hash:
19711 Ember.TEMPLATES["my_cool_template"] = Ember.Handlebars.compile('<b>{{user}}</b>');
19715 @for Ember.Handlebars.helpers
19716 @param {String} templateName the template to render
19719 Ember.Handlebars.registerHelper('template', function(name, options) {
19720 var template = Ember.TEMPLATES[name];
19722 Ember.assert("Unable to find template with name '"+name+"'.", !!template);
19724 Ember.TEMPLATES[name](this, { data: options.data });
19727 Ember.Handlebars.registerHelper('partial', function(name, options) {
19728 var nameParts = name.split("/"),
19729 lastPart = nameParts[nameParts.length - 1];
19731 nameParts[nameParts.length - 1] = "_" + lastPart;
19733 var underscoredName = nameParts.join("/");
19735 var template = Ember.TEMPLATES[underscoredName],
19736 deprecatedTemplate = Ember.TEMPLATES[name];
19738 Ember.deprecate("You tried to render the partial " + name + ", which should be at '" + underscoredName + "', but Ember found '" + name + "'. Please use a leading underscore in your partials", template);
19739 Ember.assert("Unable to find partial with name '"+name+"'.", template || deprecatedTemplate);
19741 template = template || deprecatedTemplate;
19743 template(this, { data: options.data });
19753 @submodule ember-handlebars
19756 var get = Ember.get, set = Ember.set;
19759 When used in a Handlebars template that is assigned to an `Ember.View`
19760 instance's `layout` property Ember will render the layout template first,
19761 inserting the view's own rendered output at the `{{yield}}` location.
19763 An empty `<body>` and the following application code:
19766 AView = Ember.View.extend({
19767 classNames: ['a-view-with-layout'],
19768 layout: Ember.Handlebars.compile('<div class="wrapper">{{yield}}</div>'),
19769 template: Ember.Handlebars.compile('<span>I am wrapped</span>')
19772 aView = AView.create();
19773 aView.appendTo('body');
19776 Will result in the following HTML output:
19780 <div class='ember-view a-view-with-layout'>
19781 <div class="wrapper">
19782 <span>I am wrapped</span>
19788 The `yield` helper cannot be used outside of a template assigned to an
19789 `Ember.View`'s `layout` property and will throw an error if attempted.
19792 BView = Ember.View.extend({
19793 classNames: ['a-view-with-layout'],
19794 template: Ember.Handlebars.compile('{{yield}}')
19797 bView = BView.create();
19798 bView.appendTo('body');
19801 // Uncaught Error: assertion failed: You called yield in a template that was not a layout
19805 @for Ember.Handlebars.helpers
19806 @param {Hash} options
19807 @return {String} HTML string
19809 Ember.Handlebars.registerHelper('yield', function(options) {
19810 var view = options.data.view, template;
19812 while (view && !get(view, 'layout')) {
19813 view = get(view, 'parentView');
19816 Ember.assert("You called yield in a template that was not a layout", !!view);
19818 template = get(view, 'template');
19820 if (template) { template(this, options); }
19842 @submodule ember-handlebars
19845 var set = Ember.set, get = Ember.get;
19848 The `Ember.Checkbox` view class renders a checkbox
19849 [input](https://developer.mozilla.org/en/HTML/Element/Input) element. It
19850 allows for binding an Ember property (`checked`) to the status of the
19856 {{view Ember.Checkbox checkedBinding="receiveEmail"}}
19859 You can add a `label` tag yourself in the template where the `Ember.Checkbox`
19864 {{view Ember.Checkbox classNames="applicaton-specific-checkbox"}}
19869 The `checked` attribute of an `Ember.Checkbox` object should always be set
19870 through the Ember object or by interacting with its rendered element
19871 representation via the mouse, keyboard, or touch. Updating the value of the
19872 checkbox via jQuery will result in the checked value of the object and its
19873 element losing synchronization.
19875 ## Layout and LayoutName properties
19877 Because HTML `input` elements are self closing `layout` and `layoutName`
19878 properties will not be applied. See `Ember.View`'s layout section for more
19883 @extends Ember.View
19885 Ember.Checkbox = Ember.View.extend({
19886 classNames: ['ember-checkbox'],
19890 attributeBindings: ['type', 'checked', 'disabled', 'tabindex'],
19898 this.on("change", this, this._updateElementValue);
19901 _updateElementValue: function() {
19902 set(this, 'checked', this.$().prop('checked'));
19913 @submodule ember-handlebars
19916 var get = Ember.get, set = Ember.set;
19919 Shared mixin used by `Ember.TextField` and `Ember.TextArea`.
19923 @extends Ember.Mixin
19926 Ember.TextSupport = Ember.Mixin.create({
19929 attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex'],
19934 insertNewline: Ember.K,
19939 this.on("focusOut", this, this._elementValueDidChange);
19940 this.on("change", this, this._elementValueDidChange);
19941 this.on("paste", this, this._elementValueDidChange);
19942 this.on("cut", this, this._elementValueDidChange);
19943 this.on("input", this, this._elementValueDidChange);
19944 this.on("keyUp", this, this.interpretKeyEvents);
19947 interpretKeyEvents: function(event) {
19948 var map = Ember.TextSupport.KEY_EVENTS;
19949 var method = map[event.keyCode];
19951 this._elementValueDidChange();
19952 if (method) { return this[method](event); }
19955 _elementValueDidChange: function() {
19956 set(this, 'value', this.$().val());
19961 Ember.TextSupport.KEY_EVENTS = {
19962 13: 'insertNewline',
19973 @submodule ember-handlebars
19976 var get = Ember.get, set = Ember.set;
19979 The `Ember.TextField` view class renders a text
19980 [input](https://developer.mozilla.org/en/HTML/Element/Input) element. It
19981 allows for binding Ember properties to the text field contents (`value`),
19982 live-updating as the user inputs text.
19987 {{view Ember.TextField valueBinding="firstName"}}
19990 ## Layout and LayoutName properties
19992 Because HTML `input` elements are self closing `layout` and `layoutName`
19993 properties will not be applied. See `Ember.View`'s layout section for more
19998 By default `Ember.TextField` provides support for `type`, `value`, `size`,
19999 `placeholder`, `disabled`, `maxlength` and `tabindex` attributes on a
20000 test field. If you need to support more attributes have a look at the
20001 `attributeBindings` property in `Ember.View`'s HTML Attributes section.
20003 To globally add support for additional attributes you can reopen
20004 `Ember.TextField` or `Ember.TextSupport`.
20007 Ember.TextSupport.reopen({
20008 attributeBindings: ["required"]
20014 @extends Ember.View
20015 @uses Ember.TextSupport
20017 Ember.TextField = Ember.View.extend(Ember.TextSupport,
20018 /** @scope Ember.TextField.prototype */ {
20020 classNames: ['ember-text-field'],
20022 attributeBindings: ['type', 'value', 'size'],
20025 The `value` attribute of the input element. As the user inputs text, this
20026 property is updated live.
20035 The `type` attribute of the input element.
20044 The `size` of the text field in characters.
20053 The action to be sent when the user presses the return key.
20055 This is similar to the `{{action}}` helper, but is fired when
20056 the user presses the return key when editing a text field, and sends
20057 the value of the field as the context.
20065 insertNewline: function() {
20066 var controller = get(this, 'controller'),
20067 action = get(this, 'action');
20070 controller.send(action, get(this, 'value'));
20084 @submodule ember-handlebars
20087 var get = Ember.get, set = Ember.set;
20092 @extends Ember.View
20093 @uses Ember.TargetActionSupport
20096 Ember.Button = Ember.View.extend(Ember.TargetActionSupport, {
20097 classNames: ['ember-button'],
20098 classNameBindings: ['isActive'],
20102 propagateEvents: false,
20104 attributeBindings: ['type', 'disabled', 'href', 'tabindex'],
20109 Overrides `TargetActionSupport`'s `targetObject` computed
20110 property to use Handlebars-specific path resolution.
20112 @property targetObject
20114 targetObject: Ember.computed(function() {
20115 var target = get(this, 'target'),
20116 root = get(this, 'context'),
20117 data = get(this, 'templateData');
20119 if (typeof target !== 'string') { return target; }
20121 return Ember.Handlebars.get(root, target, { data: data });
20122 }).property('target'),
20124 // Defaults to 'button' if tagName is 'input' or 'button'
20125 type: Ember.computed(function(key) {
20126 var tagName = this.tagName;
20127 if (tagName === 'input' || tagName === 'button') { return 'button'; }
20132 // Allow 'a' tags to act like buttons
20133 href: Ember.computed(function() {
20134 return this.tagName === 'a' ? '#' : null;
20137 mouseDown: function() {
20138 if (!get(this, 'disabled')) {
20139 set(this, 'isActive', true);
20140 this._mouseDown = true;
20141 this._mouseEntered = true;
20143 return get(this, 'propagateEvents');
20146 mouseLeave: function() {
20147 if (this._mouseDown) {
20148 set(this, 'isActive', false);
20149 this._mouseEntered = false;
20153 mouseEnter: function() {
20154 if (this._mouseDown) {
20155 set(this, 'isActive', true);
20156 this._mouseEntered = true;
20160 mouseUp: function(event) {
20161 if (get(this, 'isActive')) {
20162 // Actually invoke the button's target and action.
20163 // This method comes from the Ember.TargetActionSupport mixin.
20164 this.triggerAction();
20165 set(this, 'isActive', false);
20168 this._mouseDown = false;
20169 this._mouseEntered = false;
20170 return get(this, 'propagateEvents');
20173 keyDown: function(event) {
20174 // Handle space or enter
20175 if (event.keyCode === 13 || event.keyCode === 32) {
20180 keyUp: function(event) {
20181 // Handle space or enter
20182 if (event.keyCode === 13 || event.keyCode === 32) {
20187 // TODO: Handle proper touch behavior. Including should make inactive when
20188 // finger moves more than 20x outside of the edge of the button (vs mouse
20189 // which goes inactive as soon as mouse goes out of edges.)
20191 touchStart: function(touch) {
20192 return this.mouseDown(touch);
20195 touchEnd: function(touch) {
20196 return this.mouseUp(touch);
20200 Ember.deprecate("Ember.Button is deprecated and will be removed from future releases. Consider using the `{{action}}` helper.");
20212 @submodule ember-handlebars
20215 var get = Ember.get, set = Ember.set;
20218 The `Ember.TextArea` view class renders a
20219 [textarea](https://developer.mozilla.org/en/HTML/Element/textarea) element.
20220 It allows for binding Ember properties to the text area contents (`value`),
20221 live-updating as the user inputs text.
20223 ## Layout and LayoutName properties
20225 Because HTML `textarea` elements do not contain inner HTML the `layout` and
20226 `layoutName` properties will not be applied. See `Ember.View`'s layout
20227 section for more information.
20231 By default `Ember.TextArea` provides support for `rows`, `cols`,
20232 `placeholder`, `disabled`, `maxlength` and `tabindex` attributes on a
20233 textarea. If you need to support more attributes have a look at the
20234 `attributeBindings` property in `Ember.View`'s HTML Attributes section.
20236 To globally add support for additional attributes you can reopen
20237 `Ember.TextArea` or `Ember.TextSupport`.
20240 Ember.TextSupport.reopen({
20241 attributeBindings: ["required"]
20247 @extends Ember.View
20248 @uses Ember.TextSupport
20250 Ember.TextArea = Ember.View.extend(Ember.TextSupport, {
20251 classNames: ['ember-text-area'],
20253 tagName: "textarea",
20254 attributeBindings: ['rows', 'cols'],
20258 _updateElementValue: Ember.observer(function() {
20259 // We do this check so cursor position doesn't get affected in IE
20260 var value = get(this, 'value'),
20262 if ($el && value !== $el.val()) {
20269 this.on("didInsertElement", this, this._updateElementValue);
20279 /*jshint eqeqeq:false */
20283 @submodule ember-handlebars
20286 var set = Ember.set,
20288 indexOf = Ember.EnumerableUtils.indexOf,
20289 indexesOf = Ember.EnumerableUtils.indexesOf,
20290 replace = Ember.EnumerableUtils.replace,
20291 isArray = Ember.isArray,
20292 precompileTemplate = Ember.Handlebars.compile;
20295 The `Ember.Select` view class renders a
20296 [select](https://developer.mozilla.org/en/HTML/Element/select) HTML element,
20297 allowing the user to choose from a list of options.
20299 The text and `value` property of each `<option>` element within the
20300 `<select>` element are populated from the objects in the `Element.Select`'s
20301 `content` property. The underlying data object of the selected `<option>` is
20302 stored in the `Element.Select`'s `value` property.
20304 ### `content` as an array of Strings
20306 The simplest version of an `Ember.Select` takes an array of strings as its
20307 `content` property. The string will be used as both the `value` property and
20308 the inner text of each `<option>` element inside the rendered `<select>`.
20313 App.names = ["Yehuda", "Tom"];
20317 {{view Ember.Select contentBinding="App.names"}}
20320 Would result in the following HTML:
20323 <select class="ember-select">
20324 <option value="Yehuda">Yehuda</option>
20325 <option value="Tom">Tom</option>
20329 You can control which `<option>` is selected through the `Ember.Select`'s
20330 `value` property directly or as a binding:
20333 App.names = Ember.Object.create({
20335 content: ["Yehuda", "Tom"]
20340 {{view Ember.Select
20341 contentBinding="App.names.content"
20342 valueBinding="App.names.selected"
20346 Would result in the following HTML with the `<option>` for 'Tom' selected:
20349 <select class="ember-select">
20350 <option value="Yehuda">Yehuda</option>
20351 <option value="Tom" selected="selected">Tom</option>
20355 A user interacting with the rendered `<select>` to choose "Yehuda" would
20356 update the value of `App.names.selected` to "Yehuda".
20358 ### `content` as an Array of Objects
20360 An `Ember.Select` can also take an array of JavaScript or Ember objects as
20361 its `content` property.
20363 When using objects you need to tell the `Ember.Select` which property should
20364 be accessed on each object to supply the `value` attribute of the `<option>`
20365 and which property should be used to supply the element text.
20367 The `optionValuePath` option is used to specify the path on each object to
20368 the desired property for the `value` attribute. The `optionLabelPath`
20369 specifies the path on each object to the desired property for the
20370 element's text. Both paths must reference each object itself as `content`:
20373 App.programmers = [
20374 Ember.Object.create({firstName: "Yehuda", id: 1}),
20375 Ember.Object.create({firstName: "Tom", id: 2})
20380 {{view Ember.Select
20381 contentBinding="App.programmers"
20382 optionValuePath="content.id"
20383 optionLabelPath="content.firstName"}}
20386 Would result in the following HTML:
20389 <select class="ember-select">
20390 <option value>Please Select</option>
20391 <option value="1">Yehuda</option>
20392 <option value="2">Tom</option>
20396 The `value` attribute of the selected `<option>` within an `Ember.Select`
20397 can be bound to a property on another object by providing a
20398 `valueBinding` option:
20401 App.programmers = [
20402 Ember.Object.create({firstName: "Yehuda", id: 1}),
20403 Ember.Object.create({firstName: "Tom", id: 2})
20406 App.currentProgrammer = Ember.Object.create({
20412 {{view Ember.Select
20413 contentBinding="App.programmers"
20414 optionValuePath="content.id"
20415 optionLabelPath="content.firstName"
20416 valueBinding="App.currentProgrammer.id"}}
20419 Would result in the following HTML with a selected option:
20422 <select class="ember-select">
20423 <option value>Please Select</option>
20424 <option value="1">Yehuda</option>
20425 <option value="2" selected="selected">Tom</option>
20429 Interacting with the rendered element by selecting the first option
20430 ('Yehuda') will update the `id` value of `App.currentProgrammer`
20431 to match the `value` property of the newly selected `<option>`.
20433 Alternatively, you can control selection through the underlying objects
20434 used to render each object providing a `selectionBinding`. When the selected
20435 `<option>` is changed, the property path provided to `selectionBinding`
20436 will be updated to match the content object of the rendered `<option>`
20440 App.controller = Ember.Object.create({
20441 selectedPerson: null,
20443 Ember.Object.create({firstName: "Yehuda", id: 1}),
20444 Ember.Object.create({firstName: "Tom", id: 2})
20450 {{view Ember.Select
20451 contentBinding="App.controller.content"
20452 optionValuePath="content.id"
20453 optionLabelPath="content.firstName"
20454 selectionBinding="App.controller.selectedPerson"}}
20457 Would result in the following HTML with a selected option:
20460 <select class="ember-select">
20461 <option value>Please Select</option>
20462 <option value="1">Yehuda</option>
20463 <option value="2" selected="selected">Tom</option>
20467 Interacting with the rendered element by selecting the first option
20468 ('Yehuda') will update the `selectedPerson` value of `App.controller`
20469 to match the content object of the newly selected `<option>`. In this
20470 case it is the first object in the `App.content.content`
20472 ### Supplying a Prompt
20474 A `null` value for the `Ember.Select`'s `value` or `selection` property
20475 results in there being no `<option>` with a `selected` attribute:
20478 App.controller = Ember.Object.create({
20488 {{view Ember.Select
20489 contentBinding="App.controller.content"
20490 valueBinding="App.controller.selected"
20494 Would result in the following HTML:
20497 <select class="ember-select">
20498 <option value="Yehuda">Yehuda</option>
20499 <option value="Tom">Tom</option>
20503 Although `App.controller.selected` is `null` and no `<option>`
20504 has a `selected` attribute the rendered HTML will display the
20505 first item as though it were selected. You can supply a string
20506 value for the `Ember.Select` to display when there is no selection
20507 with the `prompt` option:
20510 App.controller = Ember.Object.create({
20520 {{view Ember.Select
20521 contentBinding="App.controller.content"
20522 valueBinding="App.controller.selected"
20523 prompt="Please select a name"
20527 Would result in the following HTML:
20530 <select class="ember-select">
20531 <option>Please select a name</option>
20532 <option value="Yehuda">Yehuda</option>
20533 <option value="Tom">Tom</option>
20539 @extends Ember.View
20541 Ember.Select = Ember.View.extend(
20542 /** @scope Ember.Select.prototype */ {
20545 classNames: ['ember-select'],
20546 defaultTemplate: Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) {
20547 helpers = helpers || Ember.Handlebars.helpers; data = data || {};
20548 var buffer = '', stack1, escapeExpression=this.escapeExpression, self=this;
20550 function program1(depth0,data) {
20552 var buffer = '', stack1;
20553 data.buffer.push("<option value=\"\">");
20554 stack1 = helpers._triageMustache.call(depth0, "view.prompt", {hash:{},contexts:[depth0],data:data});
20555 data.buffer.push(escapeExpression(stack1) + "</option>");
20558 function program3(depth0,data) {
20562 stack1['contentBinding'] = "this";
20563 stack1 = helpers.view.call(depth0, "Ember.SelectOption", {hash:stack1,contexts:[depth0],data:data});
20564 data.buffer.push(escapeExpression(stack1));}
20566 stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],data:data});
20567 if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
20568 stack1 = helpers.each.call(depth0, "view.content", {hash:{},inverse:self.noop,fn:self.program(3, program3, data),contexts:[depth0],data:data});
20569 if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
20572 attributeBindings: ['multiple', 'disabled', 'tabindex'],
20575 The `multiple` attribute of the select element. Indicates whether multiple
20576 options can be selected.
20587 The list of options.
20589 If `optionLabelPath` and `optionValuePath` are not overridden, this should
20590 be a list of strings, which will serve simultaneously as labels and values.
20592 Otherwise, this should be a list of objects. For instance:
20595 Ember.Select.create({
20597 { id: 1, firstName: 'Yehuda' },
20598 { id: 2, firstName: 'Tom' }
20600 optionLabelPath: 'content.firstName',
20601 optionValuePath: 'content.id'
20612 When `multiple` is `false`, the element of `content` that is currently
20615 When `multiple` is `true`, an array of such elements.
20617 @property selection
20618 @type Object or Array
20624 In single selection mode (when `multiple` is `false`), value can be used to
20625 get the current selection's value or set the selection by it's value.
20627 It is not currently supported in multiple selection mode.
20633 value: Ember.computed(function(key, value) {
20634 if (arguments.length === 2) { return value; }
20635 var valuePath = get(this, 'optionValuePath').replace(/^content\.?/, '');
20636 return valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection');
20637 }).property('selection'),
20640 If given, a top-most dummy option will be rendered to serve as a user
20650 The path of the option labels. See `content`.
20652 @property optionLabelPath
20656 optionLabelPath: 'content',
20659 The path of the option values. See `content`.
20661 @property optionValuePath
20665 optionValuePath: 'content',
20667 _change: function() {
20668 if (get(this, 'multiple')) {
20669 this._changeMultiple();
20671 this._changeSingle();
20675 selectionDidChange: Ember.observer(function() {
20676 var selection = get(this, 'selection');
20677 if (get(this, 'multiple')) {
20678 if (!isArray(selection)) {
20679 set(this, 'selection', Ember.A([selection]));
20682 this._selectionDidChangeMultiple();
20684 this._selectionDidChangeSingle();
20686 }, 'selection.@each'),
20688 valueDidChange: Ember.observer(function() {
20689 var content = get(this, 'content'),
20690 value = get(this, 'value'),
20691 valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''),
20692 selectedValue = (valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection')),
20695 if (value !== selectedValue) {
20696 selection = content.find(function(obj) {
20697 return value === (valuePath ? get(obj, valuePath) : obj);
20700 this.set('selection', selection);
20705 _triggerChange: function() {
20706 var selection = get(this, 'selection');
20707 var value = get(this, 'value');
20709 if (selection) { this.selectionDidChange(); }
20710 if (value) { this.valueDidChange(); }
20715 _changeSingle: function() {
20716 var selectedIndex = this.$()[0].selectedIndex,
20717 content = get(this, 'content'),
20718 prompt = get(this, 'prompt');
20720 if (!get(content, 'length')) { return; }
20721 if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; }
20723 if (prompt) { selectedIndex -= 1; }
20724 set(this, 'selection', content.objectAt(selectedIndex));
20728 _changeMultiple: function() {
20729 var options = this.$('option:selected'),
20730 prompt = get(this, 'prompt'),
20731 offset = prompt ? 1 : 0,
20732 content = get(this, 'content'),
20733 selection = get(this, 'selection');
20735 if (!content){ return; }
20737 var selectedIndexes = options.map(function(){
20738 return this.index - offset;
20740 var newSelection = content.objectsAt(selectedIndexes);
20742 if (isArray(selection)) {
20743 replace(selection, 0, get(selection, 'length'), newSelection);
20745 set(this, 'selection', newSelection);
20750 _selectionDidChangeSingle: function() {
20751 var el = this.get('element');
20752 if (!el) { return; }
20754 var content = get(this, 'content'),
20755 selection = get(this, 'selection'),
20756 selectionIndex = content ? indexOf(content, selection) : -1,
20757 prompt = get(this, 'prompt');
20759 if (prompt) { selectionIndex += 1; }
20760 if (el) { el.selectedIndex = selectionIndex; }
20763 _selectionDidChangeMultiple: function() {
20764 var content = get(this, 'content'),
20765 selection = get(this, 'selection'),
20766 selectedIndexes = content ? indexesOf(content, selection) : [-1],
20767 prompt = get(this, 'prompt'),
20768 offset = prompt ? 1 : 0,
20769 options = this.$('option'),
20773 options.each(function() {
20774 adjusted = this.index > -1 ? this.index - offset : -1;
20775 this.selected = indexOf(selectedIndexes, adjusted) > -1;
20782 this.on("didInsertElement", this, this._triggerChange);
20783 this.on("change", this, this._change);
20787 Ember.SelectOption = Ember.View.extend({
20789 attributeBindings: ['value', 'selected'],
20791 defaultTemplate: function(context, options) {
20792 options = { data: options.data, hash: {} };
20793 Ember.Handlebars.helpers.bind.call(context, "view.label", options);
20797 this.labelPathDidChange();
20798 this.valuePathDidChange();
20803 selected: Ember.computed(function() {
20804 var content = get(this, 'content'),
20805 selection = get(this, 'parentView.selection');
20806 if (get(this, 'parentView.multiple')) {
20807 return selection && indexOf(selection, content.valueOf()) > -1;
20809 // Primitives get passed through bindings as objects... since
20810 // `new Number(4) !== 4`, we use `==` below
20811 return content == selection;
20813 }).property('content', 'parentView.selection').volatile(),
20815 labelPathDidChange: Ember.observer(function() {
20816 var labelPath = get(this, 'parentView.optionLabelPath');
20818 if (!labelPath) { return; }
20820 Ember.defineProperty(this, 'label', Ember.computed(function() {
20821 return get(this, labelPath);
20822 }).property(labelPath));
20823 }, 'parentView.optionLabelPath'),
20825 valuePathDidChange: Ember.observer(function() {
20826 var valuePath = get(this, 'parentView.optionValuePath');
20828 if (!valuePath) { return; }
20830 Ember.defineProperty(this, 'value', Ember.computed(function() {
20831 return get(this, valuePath);
20832 }).property(valuePath));
20833 }, 'parentView.optionValuePath')
20847 /*globals Handlebars */
20850 @submodule ember-handlebars
20856 Find templates stored in the head tag as script tags and make them available
20857 to `Ember.CoreView` in the global `Ember.TEMPLATES` object. This will be run
20858 as as jQuery DOM-ready callback.
20860 Script tags with `text/x-handlebars` will be compiled
20861 with Ember's Handlebars and are suitable for use as a view's template.
20862 Those with type `text/x-raw-handlebars` will be compiled with regular
20863 Handlebars and are suitable for use in views' computed properties.
20866 @for Ember.Handlebars
20870 Ember.Handlebars.bootstrap = function(ctx) {
20871 var selectors = 'script[type="text/x-handlebars"], script[type="text/x-raw-handlebars"]';
20873 Ember.$(selectors, ctx)
20875 // Get a reference to the script tag
20876 var script = Ember.$(this),
20877 type = script.attr('type');
20879 var compile = (script.attr('type') === 'text/x-raw-handlebars') ?
20880 Ember.$.proxy(Handlebars.compile, Handlebars) :
20881 Ember.$.proxy(Ember.Handlebars.compile, Ember.Handlebars),
20882 // Get the name of the script, used by Ember.View's templateName property.
20883 // First look for data-template-name attribute, then fall back to its
20884 // id if no name is found.
20885 templateName = script.attr('data-template-name') || script.attr('id') || 'application',
20886 template = compile(script.html());
20888 // For templates which have a name, we save them and then remove them from the DOM
20889 Ember.TEMPLATES[templateName] = template;
20891 // Remove script tag from DOM
20896 function bootstrap() {
20897 Ember.Handlebars.bootstrap( Ember.$(document) );
20901 We tie this to application.load to ensure that we've at least
20902 attempted to bootstrap at the point that the application is loaded.
20904 We also tie this to document ready since we're guaranteed that all
20905 the inline templates are present at this point.
20907 There's no harm to running this twice, since we remove the templates
20908 from the DOM after processing.
20911 Ember.onLoad('application', bootstrap);
20922 @submodule ember-handlebars
20923 @requires ember-views
20926 Ember.runLoadHooks('Ember.Handlebars', Ember.Handlebars);
20931 define("route-recognizer",
20936 '/', '.', '*', '+', '?', '|',
20937 '(', ')', '[', ']', '{', '}', '\\'
20940 var escapeRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
20942 // A Segment represents a segment in the original route description.
20943 // Each Segment type provides an `eachChar` and `regex` method.
20945 // The `eachChar` method invokes the callback with one or more character
20946 // specifications. A character specification consumes one or more input
20949 // The `regex` method returns a regex fragment for the segment. If the
20950 // segment is a dynamic of star segment, the regex fragment also includes
20953 // A character specification contains:
20955 // * `validChars`: a String with a list of all valid characters, or
20956 // * `invalidChars`: a String with a list of all invalid characters
20957 // * `repeat`: true if the character specification can repeat
20959 function StaticSegment(string) { this.string = string; }
20960 StaticSegment.prototype = {
20961 eachChar: function(callback) {
20962 var string = this.string, char;
20964 for (var i=0, l=string.length; i<l; i++) {
20965 char = string.charAt(i);
20966 callback({ validChars: char });
20970 regex: function() {
20971 return this.string.replace(escapeRegex, '\\$1');
20974 generate: function() {
20975 return this.string;
20979 function DynamicSegment(name) { this.name = name; }
20980 DynamicSegment.prototype = {
20981 eachChar: function(callback) {
20982 callback({ invalidChars: "/", repeat: true });
20985 regex: function() {
20989 generate: function(params) {
20990 return params[this.name];
20994 function StarSegment(name) { this.name = name; }
20995 StarSegment.prototype = {
20996 eachChar: function(callback) {
20997 callback({ invalidChars: "", repeat: true });
21000 regex: function() {
21004 generate: function(params) {
21005 return params[this.name];
21009 function EpsilonSegment() {}
21010 EpsilonSegment.prototype = {
21011 eachChar: function() {},
21012 regex: function() { return ""; },
21013 generate: function() { return ""; }
21016 function parse(route, names, types) {
21017 // normalize route as not starting with a "/". Recognition will
21019 if (route.charAt(0) === "/") { route = route.substr(1); }
21021 var segments = route.split("/"), results = [];
21023 for (var i=0, l=segments.length; i<l; i++) {
21024 var segment = segments[i], match;
21026 if (match = segment.match(/^:([^\/]+)$/)) {
21027 results.push(new DynamicSegment(match[1]));
21028 names.push(match[1]);
21030 } else if (match = segment.match(/^\*([^\/]+)$/)) {
21031 results.push(new StarSegment(match[1]));
21032 names.push(match[1]);
21034 } else if(segment === "") {
21035 results.push(new EpsilonSegment());
21037 results.push(new StaticSegment(segment));
21045 // A State has a character specification and (`charSpec`) and a list of possible
21046 // subsequent states (`nextStates`).
21048 // If a State is an accepting state, it will also have several additional
21051 // * `regex`: A regular expression that is used to extract parameters from paths
21052 // that reached this accepting state.
21053 // * `handlers`: Information on how to convert the list of captures into calls
21054 // to registered handlers with the specified parameters
21055 // * `types`: How many static, dynamic or star segments in this route. Used to
21056 // decide which route to use if multiple registered routes match a path.
21058 // Currently, State is implemented naively by looping over `nextStates` and
21059 // comparing a character specification against a character. A more efficient
21060 // implementation would use a hash of keys pointing at one or more next states.
21062 function State(charSpec) {
21063 this.charSpec = charSpec;
21064 this.nextStates = [];
21067 State.prototype = {
21068 get: function(charSpec) {
21069 var nextStates = this.nextStates;
21071 for (var i=0, l=nextStates.length; i<l; i++) {
21072 var child = nextStates[i];
21074 var isEqual = child.charSpec.validChars === charSpec.validChars;
21075 isEqual = isEqual && child.charSpec.invalidChars === charSpec.invalidChars;
21077 if (isEqual) { return child; }
21081 put: function(charSpec) {
21084 // If the character specification already exists in a child of the current
21085 // state, just return that state.
21086 if (state = this.get(charSpec)) { return state; }
21088 // Make a new state for the character spec
21089 state = new State(charSpec);
21091 // Insert the new state as a child of the current state
21092 this.nextStates.push(state);
21094 // If this character specification repeats, insert the new state as a child
21095 // of itself. Note that this will not trigger an infinite loop because each
21096 // transition during recognition consumes a character.
21097 if (charSpec.repeat) {
21098 state.nextStates.push(state);
21101 // Return the new state
21105 // Find a list of child states matching the next character
21106 match: function(char) {
21107 // DEBUG "Processing `" + char + "`:"
21108 var nextStates = this.nextStates,
21109 child, charSpec, chars;
21111 // DEBUG " " + debugState(this)
21114 for (var i=0, l=nextStates.length; i<l; i++) {
21115 child = nextStates[i];
21117 charSpec = child.charSpec;
21119 if (typeof (chars = charSpec.validChars) !== 'undefined') {
21120 if (chars.indexOf(char) !== -1) { returned.push(child); }
21121 } else if (typeof (chars = charSpec.invalidChars) !== 'undefined') {
21122 if (chars.indexOf(char) === -1) { returned.push(child); }
21130 , debug: function() {
21131 var charSpec = this.charSpec,
21133 chars = charSpec.validChars || charSpec.invalidChars;
21135 if (charSpec.invalidChars) { debug += "^"; }
21139 if (charSpec.repeat) { debug += "+"; }
21147 function debug(log) {
21151 function debugState(state) {
21152 return state.nextStates.map(function(n) {
21153 if (n.nextStates.length === 0) { return "( " + n.debug() + " [accepting] )"; }
21154 return "( " + n.debug() + " <then> " + n.nextStates.map(function(s) { return s.debug() }).join(" or ") + " )";
21159 // This is a somewhat naive strategy, but should work in a lot of cases
21160 // A better strategy would properly resolve /posts/:id/new and /posts/edit/:id
21161 function sortSolutions(states) {
21162 return states.sort(function(a, b) {
21163 if (a.types.stars !== b.types.stars) { return a.types.stars - b.types.stars; }
21164 if (a.types.dynamics !== b.types.dynamics) { return a.types.dynamics - b.types.dynamics; }
21165 if (a.types.statics !== b.types.statics) { return a.types.statics - b.types.statics; }
21171 function recognizeChar(states, char) {
21172 var nextStates = [];
21174 for (var i=0, l=states.length; i<l; i++) {
21175 var state = states[i];
21177 nextStates = nextStates.concat(state.match(char));
21183 function findHandler(state, path) {
21184 var handlers = state.handlers, regex = state.regex;
21185 var captures = path.match(regex), currentCapture = 1;
21188 for (var i=0, l=handlers.length; i<l; i++) {
21189 var handler = handlers[i], names = handler.names, params = {};
21191 for (var j=0, m=names.length; j<m; j++) {
21192 params[names[j]] = captures[currentCapture++];
21195 result.push({ handler: handler.handler, params: params, isDynamic: !!names.length });
21201 function addSegment(currentState, segment) {
21202 segment.eachChar(function(char) {
21205 currentState = currentState.put(char);
21208 return currentState;
21211 // The main interface
21213 var RouteRecognizer = function() {
21214 this.rootState = new State();
21219 RouteRecognizer.prototype = {
21220 add: function(routes, options) {
21221 var currentState = this.rootState, regex = "^",
21222 types = { statics: 0, dynamics: 0, stars: 0 },
21223 handlers = [], allSegments = [], name;
21225 var isEmpty = true;
21227 for (var i=0, l=routes.length; i<l; i++) {
21228 var route = routes[i], names = [];
21230 var segments = parse(route.path, names, types);
21232 allSegments = allSegments.concat(segments);
21234 for (var j=0, m=segments.length; j<m; j++) {
21235 var segment = segments[j];
21237 if (segment instanceof EpsilonSegment) { continue; }
21241 // Add a "/" for the new segment
21242 currentState = currentState.put({ validChars: "/" });
21245 // Add a representation of the segment to the NFA and regex
21246 currentState = addSegment(currentState, segment);
21247 regex += segment.regex();
21250 handlers.push({ handler: route.handler, names: names });
21254 currentState = currentState.put({ validChars: "/" });
21258 currentState.handlers = handlers;
21259 currentState.regex = new RegExp(regex + "$");
21260 currentState.types = types;
21262 if (name = options && options.as) {
21263 this.names[name] = {
21264 segments: allSegments,
21270 handlersFor: function(name) {
21271 var route = this.names[name], result = [];
21272 if (!route) { throw new Error("There is no route named " + name); }
21274 for (var i=0, l=route.handlers.length; i<l; i++) {
21275 result.push(route.handlers[i]);
21281 hasRoute: function(name) {
21282 return !!this.names[name];
21285 generate: function(name, params) {
21286 var route = this.names[name], output = "";
21287 if (!route) { throw new Error("There is no route named " + name); }
21289 var segments = route.segments;
21291 for (var i=0, l=segments.length; i<l; i++) {
21292 var segment = segments[i];
21294 if (segment instanceof EpsilonSegment) { continue; }
21297 output += segment.generate(params);
21300 if (output.charAt(0) !== '/') { output = '/' + output; }
21305 recognize: function(path) {
21306 var states = [ this.rootState ], i, l;
21308 // DEBUG GROUP path
21310 var pathLen = path.length;
21312 if (path.charAt(0) !== "/") { path = "/" + path; }
21314 if (pathLen > 1 && path.charAt(pathLen - 1) === "/") {
21315 path = path.substr(0, pathLen - 1);
21318 for (i=0, l=path.length; i<l; i++) {
21319 states = recognizeChar(states, path.charAt(i));
21320 if (!states.length) { break; }
21325 var solutions = [];
21326 for (i=0, l=states.length; i<l; i++) {
21327 if (states[i].handlers) { solutions.push(states[i]); }
21330 states = sortSolutions(solutions);
21332 var state = solutions[0];
21334 if (state && state.handlers) {
21335 return findHandler(state, path);
21340 function Target(path, matcher, delegate) {
21342 this.matcher = matcher;
21343 this.delegate = delegate;
21346 Target.prototype = {
21347 to: function(target, callback) {
21348 var delegate = this.delegate;
21350 if (delegate && delegate.willAddRoute) {
21351 target = delegate.willAddRoute(this.matcher.target, target);
21354 this.matcher.add(this.path, target);
21357 if (callback.length === 0) { throw new Error("You must have an argument in the function passed to `to`"); }
21358 this.matcher.addChild(this.path, target, callback, this.delegate);
21363 function Matcher(target) {
21365 this.children = {};
21366 this.target = target;
21369 Matcher.prototype = {
21370 add: function(path, handler) {
21371 this.routes[path] = handler;
21374 addChild: function(path, target, callback, delegate) {
21375 var matcher = new Matcher(target);
21376 this.children[path] = matcher;
21378 var match = generateMatch(path, matcher, delegate);
21380 if (delegate && delegate.contextEntered) {
21381 delegate.contextEntered(target, match);
21388 function generateMatch(startingPath, matcher, delegate) {
21389 return function(path, nestedCallback) {
21390 var fullPath = startingPath + path;
21392 if (nestedCallback) {
21393 nestedCallback(generateMatch(fullPath, matcher, delegate));
21395 return new Target(startingPath + path, matcher, delegate);
21400 function addRoute(routeArray, path, handler) {
21402 for (var i=0, l=routeArray.length; i<l; i++) {
21403 len += routeArray[i].path.length;
21406 path = path.substr(len);
21407 routeArray.push({ path: path, handler: handler });
21410 function eachRoute(baseRoute, matcher, callback, binding) {
21411 var routes = matcher.routes;
21413 for (var path in routes) {
21414 if (routes.hasOwnProperty(path)) {
21415 var routeArray = baseRoute.slice();
21416 addRoute(routeArray, path, routes[path]);
21418 if (matcher.children[path]) {
21419 eachRoute(routeArray, matcher.children[path], callback, binding);
21421 callback.call(binding, routeArray);
21427 RouteRecognizer.prototype.map = function(callback, addRouteCallback) {
21428 var matcher = new Matcher();
21430 callback(generateMatch("", matcher, this.delegate));
21432 eachRoute([], matcher, function(route) {
21433 if (addRouteCallback) { addRouteCallback(this, route); }
21434 else { this.add(route); }
21437 return RouteRecognizer;
21446 ["route-recognizer"],
21447 function(RouteRecognizer) {
21452 This file references several internal structures:
21454 ## `RecognizedHandler`
21456 * `{String} handler`: A handler name
21457 * `{Object} params`: A hash of recognized parameters
21459 ## `UnresolvedHandlerInfo`
21461 * `{Boolean} isDynamic`: whether a handler has any dynamic segments
21462 * `{String} name`: the name of a handler
21463 * `{Object} context`: the active context for the handler
21467 * `{Boolean} isDynamic`: whether a handler has any dynamic segments
21468 * `{String} name`: the original unresolved handler name
21469 * `{Object} handler`: a handler object
21470 * `{Object} context`: the active context for the handler
21474 function Router() {
21475 this.recognizer = new RouteRecognizer();
21479 Router.prototype = {
21481 The main entry point into the router. The API is essentially
21482 the same as the `map` method in `route-recognizer`.
21484 This method extracts the String handler at the last `.to()`
21485 call and uses it as the name of the whole route.
21487 @param {Function} callback
21489 map: function(callback) {
21490 this.recognizer.delegate = this.delegate;
21492 this.recognizer.map(callback, function(recognizer, route) {
21493 var lastHandler = route[route.length - 1].handler;
21494 var args = [route, { as: lastHandler }];
21495 recognizer.add.apply(recognizer, args);
21499 hasRoute: function(route) {
21500 return this.recognizer.hasRoute(route);
21504 The entry point for handling a change to the URL (usually
21505 via the back and forward button).
21507 Returns an Array of handlers and the parameters associated
21508 with those parameters.
21510 @param {String} url a URL to process
21512 @return {Array} an Array of `[handler, parameter]` tuples
21514 handleURL: function(url) {
21515 var results = this.recognizer.recognize(url),
21519 throw new Error("No route matched the URL '" + url + "'");
21522 collectObjects(this, results, 0, []);
21526 Hook point for updating the URL.
21528 @param {String} url a URL to update to
21530 updateURL: function() {
21531 throw "updateURL is not implemented";
21535 Hook point for replacing the current URL, i.e. with replaceState
21537 By default this behaves the same as `updateURL`
21539 @param {String} url a URL to update to
21541 replaceURL: function(url) {
21542 this.updateURL(url);
21546 Transition into the specified named route.
21548 If necessary, trigger the exit callback on any handlers
21549 that are no longer represented by the target route.
21551 @param {String} name the name of the route
21553 transitionTo: function(name) {
21554 var args = Array.prototype.slice.call(arguments, 1);
21555 doTransition(this, name, this.updateURL, args);
21559 Identical to `transitionTo` except that the current URL will be replaced
21562 This method is intended primarily for use with `replaceState`.
21564 @param {String} name the name of the route
21566 replaceWith: function(name) {
21567 var args = Array.prototype.slice.call(arguments, 1);
21568 doTransition(this, name, this.replaceURL, args);
21574 This method takes a handler name and a list of contexts and returns
21575 a serialized parameter hash suitable to pass to `recognizer.generate()`.
21577 @param {String} handlerName
21578 @param {Array[Object]} contexts
21579 @return {Object} a serialized parameter hash
21581 paramsForHandler: function(handlerName, callback) {
21582 var output = this._paramsForHandler(handlerName, [].slice.call(arguments, 1));
21583 return output.params;
21587 Take a named route and context objects and generate a
21590 @param {String} name the name of the route to generate
21592 @param {...Object} objects a list of objects to serialize
21594 @return {String} a URL
21596 generate: function(handlerName) {
21597 var params = this.paramsForHandler.apply(this, arguments);
21598 return this.recognizer.generate(handlerName, params);
21604 Used internally by `generate` and `transitionTo`.
21606 _paramsForHandler: function(handlerName, objects, doUpdate) {
21607 var handlers = this.recognizer.handlersFor(handlerName),
21610 startIdx = handlers.length,
21611 objectsToMatch = objects.length,
21612 object, objectChanged, handlerObj, handler, names, i, len;
21614 // Find out which handler to start matching at
21615 for (i=handlers.length-1; i>=0 && objectsToMatch>0; i--) {
21616 if (handlers[i].names.length) {
21622 if (objectsToMatch > 0) {
21623 throw "More objects were passed than dynamic segments";
21626 // Connect the objects to the routes
21627 for (i=0, len=handlers.length; i<len; i++) {
21628 handlerObj = handlers[i];
21629 handler = this.getHandler(handlerObj.handler);
21630 names = handlerObj.names;
21631 objectChanged = false;
21633 // If it's a dynamic segment
21634 if (names.length) {
21635 // If we have objects, use them
21636 if (i >= startIdx) {
21637 object = objects.shift();
21638 objectChanged = true;
21639 // Otherwise use existing context
21641 object = handler.context;
21644 // Serialize to generate params
21645 if (handler.serialize) {
21646 merge(params, handler.serialize(object, names));
21648 // If it's not a dynamic segment and we're updating
21649 } else if (doUpdate) {
21650 // If we've passed the match point we need to deserialize again
21651 // or if we never had a context
21652 if (i > startIdx || !handler.hasOwnProperty('context')) {
21653 if (handler.deserialize) {
21654 object = handler.deserialize({});
21655 objectChanged = true;
21657 // Otherwise use existing context
21659 object = handler.context;
21663 // Make sure that we update the context here so it's available to
21664 // subsequent deserialize calls
21665 if (doUpdate && objectChanged) {
21666 // TODO: It's a bit awkward to set the context twice, see if we can DRY things up
21667 setContext(handler, object);
21671 isDynamic: !!handlerObj.names.length,
21672 handler: handlerObj.handler,
21673 name: handlerObj.name,
21678 return { params: params, toSetup: toSetup };
21681 isActive: function(handlerName) {
21682 var contexts = [].slice.call(arguments, 1);
21684 var currentHandlerInfos = this.currentHandlerInfos,
21685 found = false, names, object, handlerInfo, handlerObj;
21687 for (var i=currentHandlerInfos.length-1; i>=0; i--) {
21688 handlerInfo = currentHandlerInfos[i];
21689 if (handlerInfo.name === handlerName) { found = true; }
21692 if (contexts.length === 0) { break; }
21694 if (handlerInfo.isDynamic) {
21695 object = contexts.pop();
21696 if (handlerInfo.context !== object) { return false; }
21701 return contexts.length === 0 && found;
21704 trigger: function(name) {
21705 var args = [].slice.call(arguments);
21706 trigger(this, args);
21710 function merge(hash, other) {
21711 for (var prop in other) {
21712 if (other.hasOwnProperty(prop)) { hash[prop] = other[prop]; }
21716 function isCurrent(currentHandlerInfos, handlerName) {
21717 return currentHandlerInfos[currentHandlerInfos.length - 1].name === handlerName;
21723 This function is called the first time the `collectObjects`
21724 function encounters a promise while converting URL parameters
21727 It triggers the `enter` and `setup` methods on the `loading`
21730 @param {Router} router
21732 function loading(router) {
21733 if (!router.isLoading) {
21734 router.isLoading = true;
21735 var handler = router.getHandler('loading');
21738 if (handler.enter) { handler.enter(); }
21739 if (handler.setup) { handler.setup(); }
21747 This function is called if a promise was previously
21748 encountered once all promises are resolved.
21750 It triggers the `exit` method on the `loading` handler.
21752 @param {Router} router
21754 function loaded(router) {
21755 router.isLoading = false;
21756 var handler = router.getHandler('loading');
21757 if (handler && handler.exit) { handler.exit(); }
21763 This function is called if any encountered promise
21766 It triggers the `exit` method on the `loading` handler,
21767 the `enter` method on the `failure` handler, and the
21768 `setup` method on the `failure` handler with the
21771 @param {Router} router
21772 @param {Object} error the reason for the promise
21773 rejection, to pass into the failure handler's
21776 function failure(router, error) {
21778 var handler = router.getHandler('failure');
21779 if (handler && handler.setup) { handler.setup(error); }
21785 function doTransition(router, name, method, args) {
21786 var output = router._paramsForHandler(name, args, true);
21787 var params = output.params, toSetup = output.toSetup;
21789 var url = router.recognizer.generate(name, params);
21790 method.call(router, url);
21792 setupContexts(router, toSetup);
21798 This function is called after a URL change has been handled
21799 by `router.handleURL`.
21801 Takes an Array of `RecognizedHandler`s, and converts the raw
21802 params hashes into deserialized objects by calling deserialize
21803 on the handlers. This process builds up an Array of
21804 `HandlerInfo`s. It then calls `setupContexts` with the Array.
21806 If the `deserialize` method on a handler returns a promise
21807 (i.e. has a method called `then`), this function will pause
21808 building up the `HandlerInfo` Array until the promise is
21809 resolved. It will use the resolved value as the context of
21812 function collectObjects(router, results, index, objects) {
21813 if (results.length === index) {
21815 setupContexts(router, objects);
21819 var result = results[index];
21820 var handler = router.getHandler(result.handler);
21821 var object = handler.deserialize && handler.deserialize(result.params);
21823 if (object && typeof object.then === 'function') {
21826 // The chained `then` means that we can also catch errors that happen in `proceed`
21827 object.then(proceed).then(null, function(error) {
21828 failure(router, error);
21834 function proceed(value) {
21835 if (handler.context !== object) {
21836 setContext(handler, object);
21839 var updatedObjects = objects.concat([{
21841 handler: result.handler,
21842 isDynamic: result.isDynamic
21844 collectObjects(router, results, index + 1, updatedObjects);
21851 Takes an Array of `UnresolvedHandlerInfo`s, resolves the handler names
21852 into handlers, and then figures out what to do with each of the handlers.
21854 For example, consider the following tree of handlers. Each handler is
21855 followed by the URL segment it handles.
21859 | |~posts ("/posts")
21860 | | |-showPost ("/:id")
21861 | | |-newPost ("/new")
21862 | | |-editPost ("/edit")
21863 | |~about ("/about/:id")
21866 Consider the following transitions:
21868 1. A URL transition to `/posts/1`.
21869 1. Triggers the `deserialize` callback on the
21870 `index`, `posts`, and `showPost` handlers
21871 2. Triggers the `enter` callback on the same
21872 3. Triggers the `setup` callback on the same
21873 2. A direct transition to `newPost`
21874 1. Triggers the `exit` callback on `showPost`
21875 2. Triggers the `enter` callback on `newPost`
21876 3. Triggers the `setup` callback on `newPost`
21877 3. A direct transition to `about` with a specified
21879 1. Triggers the `exit` callback on `newPost`
21881 2. Triggers the `serialize` callback on `about`
21882 3. Triggers the `enter` callback on `about`
21883 4. Triggers the `setup` callback on `about`
21885 @param {Router} router
21886 @param {Array[UnresolvedHandlerInfo]} handlerInfos
21888 function setupContexts(router, handlerInfos) {
21889 resolveHandlers(router, handlerInfos);
21892 partitionHandlers(router.currentHandlerInfos || [], handlerInfos);
21894 router.currentHandlerInfos = handlerInfos;
21896 eachHandler(partition.exited, function(handler, context) {
21897 delete handler.context;
21898 if (handler.exit) { handler.exit(); }
21901 eachHandler(partition.updatedContext, function(handler, context) {
21902 setContext(handler, context);
21903 if (handler.setup) { handler.setup(context); }
21906 eachHandler(partition.entered, function(handler, context) {
21907 if (handler.enter) { handler.enter(); }
21908 setContext(handler, context);
21909 if (handler.setup) { handler.setup(context); }
21912 if (router.didTransition) {
21913 router.didTransition(handlerInfos);
21920 Iterates over an array of `HandlerInfo`s, passing the handler
21921 and context into the callback.
21923 @param {Array[HandlerInfo]} handlerInfos
21924 @param {Function(Object, Object)} callback
21926 function eachHandler(handlerInfos, callback) {
21927 for (var i=0, l=handlerInfos.length; i<l; i++) {
21928 var handlerInfo = handlerInfos[i],
21929 handler = handlerInfo.handler,
21930 context = handlerInfo.context;
21932 callback(handler, context);
21939 Updates the `handler` field in each element in an Array of
21940 `UnresolvedHandlerInfo`s from a handler name to a resolved handler.
21942 When done, the Array will contain `HandlerInfo` structures.
21944 @param {Router} router
21945 @param {Array[UnresolvedHandlerInfo]} handlerInfos
21947 function resolveHandlers(router, handlerInfos) {
21950 for (var i=0, l=handlerInfos.length; i<l; i++) {
21951 handlerInfo = handlerInfos[i];
21953 handlerInfo.name = handlerInfo.handler;
21954 handlerInfo.handler = router.getHandler(handlerInfo.handler);
21961 This function is called when transitioning from one URL to
21962 another to determine which handlers are not longer active,
21963 which handlers are newly active, and which handlers remain
21964 active but have their context changed.
21966 Take a list of old handlers and new handlers and partition
21967 them into four buckets:
21969 * unchanged: the handler was active in both the old and
21970 new URL, and its context remains the same
21971 * updated context: the handler was active in both the
21972 old and new URL, but its context changed. The handler's
21973 `setup` method, if any, will be called with the new
21975 * exited: the handler was active in the old URL, but is
21977 * entered: the handler was not active in the old URL, but
21980 The PartitionedHandlers structure has three fields:
21982 * `updatedContext`: a list of `HandlerInfo` objects that
21983 represent handlers that remain active but have a changed
21985 * `entered`: a list of `HandlerInfo` objects that represent
21986 handlers that are newly active
21987 * `exited`: a list of `HandlerInfo` objects that are no
21990 @param {Array[HandlerInfo]} oldHandlers a list of the handler
21991 information for the previous URL (or `[]` if this is the
21992 first handled transition)
21993 @param {Array[HandlerInfo]} newHandlers a list of the handler
21994 information for the new URL
21996 @return {Partition}
21998 function partitionHandlers(oldHandlers, newHandlers) {
22000 updatedContext: [],
22005 var handlerChanged, contextChanged, i, l;
22007 for (i=0, l=newHandlers.length; i<l; i++) {
22008 var oldHandler = oldHandlers[i], newHandler = newHandlers[i];
22010 if (!oldHandler || oldHandler.handler !== newHandler.handler) {
22011 handlerChanged = true;
22014 if (handlerChanged) {
22015 handlers.entered.push(newHandler);
22016 if (oldHandler) { handlers.exited.unshift(oldHandler); }
22017 } else if (contextChanged || oldHandler.context !== newHandler.context) {
22018 contextChanged = true;
22019 handlers.updatedContext.push(newHandler);
22023 for (i=newHandlers.length, l=oldHandlers.length; i<l; i++) {
22024 handlers.exited.unshift(oldHandlers[i]);
22030 function trigger(router, args) {
22031 var currentHandlerInfos = router.currentHandlerInfos;
22033 if (!currentHandlerInfos) {
22034 throw new Error("Could not trigger event. There are no active handlers");
22037 var name = args.shift();
22039 for (var i=currentHandlerInfos.length-1; i>=0; i--) {
22040 var handlerInfo = currentHandlerInfos[i],
22041 handler = handlerInfo.handler;
22043 if (handler.events && handler.events[name]) {
22044 handler.events[name].apply(handler, args);
22050 function setContext(handler, context) {
22051 handler.context = context;
22052 if (handler.contextDidChange) { handler.contextDidChange(); }
22064 @submodule ember-routing
22067 function DSL(name) {
22068 this.parent = name;
22073 resource: function(name, options, callback) {
22074 if (arguments.length === 2 && typeof options === 'function') {
22075 callback = options;
22079 if (arguments.length === 1) {
22083 if (typeof options.path !== 'string') {
22084 options.path = "/" + name;
22088 var dsl = new DSL(name);
22089 callback.call(dsl);
22090 this.push(options.path, name, dsl.generate());
22092 this.push(options.path, name);
22096 push: function(url, name, callback) {
22097 if (url === "" || url === "/") { this.explicitIndex = true; }
22099 this.matches.push([url, name, callback]);
22102 route: function(name, options) {
22103 Ember.assert("You must use `this.resource` to nest", typeof options !== 'function');
22105 options = options || {};
22107 if (typeof options.path !== 'string') {
22108 options.path = "/" + name;
22111 if (this.parent && this.parent !== 'application') {
22112 name = this.parent + "." + name;
22115 this.push(options.path, name);
22118 generate: function() {
22119 var dslMatches = this.matches;
22121 if (!this.explicitIndex) {
22122 this.route("index", { path: "/" });
22125 return function(match) {
22126 for (var i=0, l=dslMatches.length; i<l; i++) {
22127 var dslMatch = dslMatches[i];
22128 match(dslMatch[0]).to(dslMatch[1], dslMatch[2]);
22134 DSL.map = function(callback) {
22135 var dsl = new DSL();
22136 callback.call(dsl);
22140 Ember.RouterDSL = DSL;
22149 @submodule ember-routing
22152 Ember.controllerFor = function(container, controllerName, context) {
22153 return container.lookup('controller:' + controllerName) ||
22154 Ember.generateController(container, controllerName, context);
22157 Ember.generateController = function(container, controllerName, context) {
22160 if (context && Ember.isArray(context)) {
22161 controller = Ember.ArrayController.extend({
22164 } else if (context) {
22165 controller = Ember.ObjectController.extend({
22169 controller = Ember.Controller.extend();
22172 controller.toString = function() {
22173 return "(generated " + controllerName + " controller)";
22176 container.register('controller', controllerName, controller);
22177 return container.lookup('controller:' + controllerName);
22187 @submodule ember-routing
22190 var Router = requireModule("router");
22191 var get = Ember.get, set = Ember.set, classify = Ember.String.classify;
22193 var DefaultView = Ember.View.extend(Ember._Metamorph);
22194 function setupLocation(router) {
22195 var location = get(router, 'location'),
22196 rootURL = get(router, 'rootURL');
22198 if ('string' === typeof location) {
22199 location = set(router, 'location', Ember.Location.create({
22200 implementation: location
22203 if (typeof rootURL === 'string') {
22204 set(location, 'rootURL', rootURL);
22209 Ember.Router = Ember.Object.extend({
22213 this.router = this.constructor.router;
22214 this._activeViews = {};
22215 setupLocation(this);
22218 startRouting: function() {
22219 this.router = this.router || this.constructor.map(Ember.K);
22221 var router = this.router,
22222 location = get(this, 'location'),
22223 container = this.container,
22226 setupRouter(this, router, location);
22228 container.register('view', 'default', DefaultView);
22229 container.register('view', 'toplevel', Ember.View.extend());
22231 router.handleURL(location.getURL());
22232 location.onUpdateURL(function(url) {
22233 router.handleURL(url);
22237 didTransition: function(infos) {
22238 // Don't do any further action here if we redirected
22239 if (infos[infos.length-1].handler.transitioned) { return; }
22241 var appController = this.container.lookup('controller:application'),
22242 path = routePath(infos);
22244 set(appController, 'currentPath', path);
22245 this.notifyPropertyChange('url');
22247 if (get(this, 'namespace').LOG_TRANSITIONS) {
22248 Ember.Logger.log("Transitioned into '" + path + "'");
22252 handleURL: function(url) {
22253 this.router.handleURL(url);
22254 this.notifyPropertyChange('url');
22257 transitionTo: function(passedName) {
22258 var args = [].slice.call(arguments), name;
22260 if (!this.router.hasRoute(passedName)) {
22261 name = args[0] = passedName + '.index';
22266 Ember.assert("The route " + passedName + " was not found", this.router.hasRoute(name));
22268 this.router.transitionTo.apply(this.router, args);
22269 this.notifyPropertyChange('url');
22272 replaceWith: function() {
22273 this.router.replaceWith.apply(this.router, arguments);
22274 this.notifyPropertyChange('url');
22277 generate: function() {
22278 var url = this.router.generate.apply(this.router, arguments);
22279 return this.location.formatURL(url);
22282 isActive: function(routeName) {
22283 var router = this.router;
22284 return router.isActive.apply(router, arguments);
22287 send: function(name, context) {
22288 if (Ember.$ && context instanceof Ember.$.Event) {
22289 context = context.context;
22292 this.router.trigger(name, context);
22295 hasRoute: function(route) {
22296 return this.router.hasRoute(route);
22299 _lookupActiveView: function(templateName) {
22300 var active = this._activeViews[templateName];
22301 return active && active[0];
22304 _connectActiveView: function(templateName, view) {
22305 var existing = this._activeViews[templateName];
22308 existing[0].off('willDestroyElement', this, existing[1]);
22311 var disconnect = function() {
22312 delete this._activeViews[templateName];
22315 this._activeViews[templateName] = [view, disconnect];
22316 view.one('willDestroyElement', this, disconnect);
22320 Ember.Router.reopenClass({
22321 defaultFailureHandler: {
22322 setup: function(error) {
22323 Ember.Logger.error('Error while loading route:', error);
22325 // Using setTimeout allows us to escape from the Promise's try/catch block
22326 setTimeout(function() { throw error; });
22331 function getHandlerFunction(router) {
22332 var seen = {}, container = router.container;
22334 return function(name) {
22335 var handler = container.lookup('route:' + name);
22336 if (seen[name]) { return handler; }
22341 if (name === 'loading') { return {}; }
22342 if (name === 'failure') { return router.constructor.defaultFailureHandler; }
22344 container.register('route', name, Ember.Route.extend());
22345 handler = container.lookup('route:' + name);
22348 handler.routeName = name;
22353 function handlerIsActive(router, handlerName) {
22354 var handler = router.container.lookup('route:' + handlerName),
22355 currentHandlerInfos = router.router.currentHandlerInfos,
22358 for (var i=0, l=currentHandlerInfos.length; i<l; i++) {
22359 handlerInfo = currentHandlerInfos[i];
22360 if (handlerInfo.handler === handler) { return true; }
22366 function routePath(handlerInfos) {
22369 for (var i=1, l=handlerInfos.length; i<l; i++) {
22370 var name = handlerInfos[i].name,
22371 nameParts = name.split(".");
22373 path.push(nameParts[nameParts.length - 1]);
22376 return path.join(".");
22379 function setupRouter(emberRouter, router, location) {
22382 router.getHandler = getHandlerFunction(emberRouter);
22384 var doUpdateURL = function() {
22385 location.setURL(lastURL);
22388 router.updateURL = function(path) {
22390 Ember.run.once(doUpdateURL);
22393 if (location.replaceURL) {
22394 var doReplaceURL = function() {
22395 location.replaceURL(lastURL);
22398 router.replaceURL = function(path) {
22400 Ember.run.once(doReplaceURL);
22404 router.didTransition = function(infos) {
22405 emberRouter.didTransition(infos);
22409 Ember.Router.reopenClass({
22410 map: function(callback) {
22411 var router = this.router = new Router();
22413 var dsl = Ember.RouterDSL.map(function() {
22414 this.resource('application', { path: "/" }, function() {
22415 callback.call(this);
22419 router.map(dsl.generate());
22431 @submodule ember-routing
22434 var get = Ember.get, set = Ember.set,
22435 classify = Ember.String.classify,
22436 decamelize = Ember.String.decamelize;
22439 Ember.Route = Ember.Object.extend({
22441 teardownView(this);
22445 Transition into another route. Optionally supply a model for the
22446 route in question. The model will be serialized into the URL
22447 using the `serialize` hook.
22449 @method transitionTo
22450 @param {String} name the name of the route
22451 @param {...Object} models the
22453 transitionTo: function() {
22454 this.transitioned = true;
22455 return this.router.transitionTo.apply(this.router, arguments);
22459 Transition into another route while replacing the current URL if
22460 possible. Identical to `transitionTo` in all other respects.
22462 @method replaceWith
22463 @param {String} name the name of the route
22464 @param {...Object} models the
22466 replaceWith: function() {
22467 this.transitioned = true;
22468 return this.router.replaceWith.apply(this.router, arguments);
22472 return this.router.send.apply(this.router, arguments);
22478 This hook is the entry point for router.js
22482 setup: function(context) {
22483 this.transitioned = false;
22484 this.redirect(context);
22486 if (this.transitioned) { return; }
22488 var controller = this.controllerFor(this.routeName, context);
22491 set(controller, 'model', context);
22494 if (this.setupControllers) {
22495 Ember.deprecate("Ember.Route.setupControllers is deprecated. Please use Ember.Route.setupController(controller, model) instead.");
22496 this.setupControllers(controller, context);
22498 this.setupController(controller, context);
22501 if (this.renderTemplates) {
22502 Ember.deprecate("Ember.Route.renderTemplates is deprecated. Please use Ember.Route.renderTemplate(controller, model) instead.");
22503 this.renderTemplates(context);
22505 this.renderTemplate(controller, context);
22510 A hook you can implement to optionally redirect to another route.
22512 If you call `this.transitionTo` from inside of this hook, this route
22513 will not be entered in favor of the other hook.
22516 @param {Object} model the model for this route
22523 The hook called by `router.js` to convert parameters into the context
22524 for this handler. The public Ember hook is `model`.
22526 @method deserialize
22528 deserialize: function(params) {
22529 var model = this.model(params);
22530 return this.currentModel = model;
22536 Called when the context is changed by router.js.
22538 contextDidChange: function() {
22539 this.currentModel = this.context;
22543 A hook you can implement to convert the URL into the model for
22547 App.Route.map(function(match) {
22548 match("/posts/:post_id").to("post");
22552 The model for the `post` route is `App.Post.find(params.post_id)`.
22554 By default, if your route has a dynamic segment ending in `_id`:
22556 * The model class is determined from the segment (`post_id`'s
22557 class is `App.Post`)
22558 * The find method is called on the model class with the value of
22559 the dynamic segment.
22562 @param {Object} params the parameters extracted from the URL
22564 model: function(params) {
22565 var match, name, value;
22567 for (var prop in params) {
22568 if (match = prop.match(/^(.*)_id$/)) {
22570 value = params[prop];
22574 if (!name) { return; }
22576 var className = classify(name),
22577 namespace = this.router.namespace,
22578 modelClass = namespace[className];
22580 Ember.assert("You used the dynamic segment " + name + "_id in your router, but " + namespace + "." + className + " did not exist and you did not override your state's `model` hook.", modelClass);
22581 return modelClass.find(value);
22585 A hook you can implement to convert the route's model into parameters
22589 App.Route.map(function(match) {
22590 match("/posts/:post_id").to("post");
22593 App.PostRoute = Ember.Route.extend({
22594 model: function(params) {
22595 // the server returns `{ id: 12 }`
22596 return jQuery.getJSON("/posts/" + params.post_id);
22599 serialize: function(model) {
22600 // this will make the URL `/posts/12`
22601 return { post_id: model.id };
22606 The default `serialize` method inserts the model's `id` into the
22607 route's dynamic segment (in this case, `:post_id`).
22609 This method is called when `transitionTo` is called with a context
22610 in order to populate the URL.
22613 @param {Object} model the route's model
22614 @param {Array} params an Array of parameter names for the current
22615 route (in the example, `['post_id']`.
22616 @return {Object} the serialized parameters
22618 serialize: function(model, params) {
22619 if (params.length !== 1) { return; }
22621 var name = params[0], object = {};
22622 object[name] = get(model, 'id');
22628 A hook you can use to setup the controller for the current route.
22630 This method is called with the controller for the current route and the
22631 model supplied by the `model` hook.
22634 App.Route.map(function(match) {
22635 match("/posts/:post_id").to("post");
22639 For the `post` route, the controller is `App.PostController`.
22641 By default, the `setupController` hook sets the `content` property of
22642 the controller to the `model`.
22644 If no explicit controller is defined, the route will automatically create
22645 an appropriate controller for the model:
22647 * if the model is an `Ember.Array` (including record arrays from Ember
22648 Data), the controller is an `Ember.ArrayController`.
22649 * otherwise, the controller is an `Ember.ObjectController`.
22651 This means that your template will get a proxy for the model as its
22652 context, and you can act as though the model itself was the context.
22654 @method setupController
22656 setupController: Ember.K,
22659 Returns the controller for a particular route.
22662 App.PostRoute = Ember.Route.extend({
22663 setupController: function(controller, post) {
22664 this._super(controller, post);
22665 this.controllerFor('posts').set('currentPost', post);
22670 By default, the controller for `post` is the shared instance of
22671 `App.PostController`.
22673 @method controllerFor
22674 @param {String} name the name of the route
22675 @param {Object} model the model associated with the route (optional)
22676 @return {Ember.Controller}
22678 controllerFor: function(name, model) {
22679 var container = this.router.container,
22680 controller = container.lookup('controller:' + name);
22683 model = model || this.modelFor(name);
22685 Ember.assert("You are trying to look up a controller that you did not define, and for which Ember does not know the model.\n\nThis is not a controller for a route, so you must explicitly define the controller ("+this.router.namespace.toString() + "." + Ember.String.capitalize(Ember.String.camelize(name))+"Controller) or pass a model as the second parameter to `controllerFor`, so that Ember knows which type of controller to create for you.", model || this.container.lookup('route:' + name));
22687 controller = Ember.generateController(container, name, model);
22694 Returns the current model for a given route.
22696 This is the object returned by the `model` hook of the route
22700 @param {String} name the name of the route
22701 @return {Object} the model object
22703 modelFor: function(name) {
22704 var route = this.container.lookup('route:' + name);
22705 return route && route.currentModel;
22709 A hook you can use to render the template for the current route.
22711 This method is called with the controller for the current route and the
22712 model supplied by the `model` hook. By default, it renders the route's
22713 template, configured with the controller for the route.
22715 This method can be overridden to set up and render additional or
22716 alternative templates.
22718 @method renderTemplate
22719 @param {Object} controller the route's controller
22720 @param {Object} model the route's model
22722 renderTemplate: function(controller, model) {
22727 Renders a template into an outlet.
22729 This method has a number of defaults, based on the name of the
22730 route specified in the router.
22735 App.Router.map(function(match) {
22736 match("/").to("index");
22737 match("/posts/:post_id").to("post");
22740 App.PostRoute = App.Route.extend({
22741 renderTemplate: function() {
22747 The name of the `PostRoute`, as defined by the router, is `post`.
22749 By default, render will:
22751 * render the `post` template
22752 * with the `post` view (`PostView`) for event handling, if one exists
22753 * and the `post` controller (`PostController`), if one exists
22754 * into the `main` outlet of the `application` template
22756 You can override this behavior:
22759 App.PostRoute = App.Route.extend({
22760 renderTemplate: function() {
22761 this.render('myPost', { // the template to render
22762 into: 'index', // the template to render into
22763 outlet: 'detail', // the name of the outlet in that template
22764 controller: 'blogPost' // the controller to use for the template
22770 Remember that the controller's `content` will be the route's model. In
22771 this case, the default model will be `App.Post.find(params.post_id)`.
22774 @param {String} name the name of the template to render
22775 @param {Object} options the options
22777 render: function(name, options) {
22778 if (typeof name === 'object' && !options) {
22780 name = this.routeName;
22783 name = name ? name.replace(/\//g, '.') : this.routeName;
22785 var container = this.container,
22786 view = container.lookup('view:' + name),
22787 template = container.lookup('template:' + name);
22789 if (!view && !template) { return; }
22791 this.lastRenderedTemplate = name;
22793 options = normalizeOptions(this, name, template, options);
22794 view = setupView(view, container, options);
22796 appendView(this, view, options);
22800 function parentRoute(route) {
22801 var handlerInfos = route.router.router.currentHandlerInfos;
22803 var parent, current;
22805 for (var i=0, l=handlerInfos.length; i<l; i++) {
22806 current = handlerInfos[i].handler;
22807 if (current === route) { return parent; }
22812 function parentTemplate(route) {
22813 var parent = parentRoute(route), template;
22815 if (!parent) { return; }
22817 if (template = parent.lastRenderedTemplate) {
22820 return parentTemplate(parent);
22824 function normalizeOptions(route, name, template, options) {
22825 options = options || {};
22826 options.into = options.into ? options.into.replace(/\//g, '.') : parentTemplate(route);
22827 options.outlet = options.outlet || 'main';
22828 options.name = name;
22829 options.template = template;
22831 var controller = options.controller, namedController;
22833 if (options.controller) {
22834 controller = options.controller;
22835 } else if (namedController = route.container.lookup('controller:' + name)) {
22836 controller = namedController;
22838 controller = route.routeName;
22841 if (typeof controller === 'string') {
22842 controller = route.container.lookup('controller:' + controller);
22845 options.controller = controller;
22850 function setupView(view, container, options) {
22851 var defaultView = options.into ? 'view:default' : 'view:toplevel';
22853 view = view || container.lookup(defaultView);
22855 if (!get(view, 'templateName')) {
22856 set(view, 'template', options.template);
22859 set(view, 'renderedName', options.name);
22860 set(view, 'controller', options.controller);
22865 function appendView(route, view, options) {
22866 if (options.into) {
22867 var parentView = route.router._lookupActiveView(options.into);
22868 route.teardownView = teardownOutlet(parentView, options.outlet);
22869 parentView.connectOutlet(options.outlet, view);
22871 var rootElement = get(route, 'router.namespace.rootElement');
22872 route.router._connectActiveView(options.name, view);
22873 route.teardownView = teardownTopLevel(view);
22874 view.appendTo(rootElement);
22878 function teardownTopLevel(view) {
22879 return function() { view.remove(); };
22882 function teardownOutlet(parentView, outlet) {
22883 return function() { parentView.disconnectOutlet(outlet); };
22886 function teardownView(route) {
22887 if (route.teardownView) { route.teardownView(); }
22889 delete route.teardownView;
22890 delete route.lastRenderedTemplate;
22904 var get = Ember.get, set = Ember.set;
22905 Ember.onLoad('Ember.Handlebars', function(Handlebars) {
22907 var resolvePaths = Ember.Handlebars.resolvePaths,
22908 isSimpleClick = Ember.ViewUtils.isSimpleClick;
22910 function fullRouteName(router, name) {
22911 if (!router.hasRoute(name)) {
22912 name = name + '.index';
22918 function resolvedPaths(linkView) {
22919 return resolvePaths(linkView.parameters);
22922 function args(linkView, router, route) {
22923 var passedRouteName = route || linkView.namedRoute, routeName;
22925 routeName = fullRouteName(router, passedRouteName);
22927 Ember.assert("The route " + passedRouteName + " was not found", router.hasRoute(routeName));
22929 var ret = [ routeName ];
22930 return ret.concat(resolvePaths(linkView.parameters));
22933 var LinkView = Ember.View.extend({
22938 activeClass: 'active',
22940 attributeBindings: ['href', 'title'],
22941 classNameBindings: 'active',
22943 active: Ember.computed(function() {
22944 var router = this.get('router'),
22945 params = resolvedPaths(this),
22946 currentWithIndex = this.currentWhen + '.index',
22947 isActive = router.isActive.apply(router, [this.currentWhen].concat(params)) ||
22948 router.isActive.apply(router, [currentWithIndex].concat(params));
22950 if (isActive) { return get(this, 'activeClass'); }
22951 }).property('namedRoute', 'router.url'),
22953 router: Ember.computed(function() {
22954 return this.get('controller').container.lookup('router:main');
22957 click: function(event) {
22958 if (!isSimpleClick(event)) { return true; }
22960 event.preventDefault();
22961 if (this.bubbles === false) { event.stopPropagation(); }
22963 var router = this.get('router');
22965 if (this.get('replace')) {
22966 router.replaceWith.apply(router, args(this, router));
22968 router.transitionTo.apply(router, args(this, router));
22972 href: Ember.computed(function() {
22973 var router = this.get('router');
22974 return router.generate.apply(router, args(this, router));
22978 LinkView.toString = function() { return "LinkView"; };
22980 Ember.Handlebars.registerHelper('linkTo', function(name) {
22981 var options = [].slice.call(arguments, -1)[0];
22982 var contexts = [].slice.call(arguments, 1, -1);
22984 var hash = options.hash;
22986 hash.namedRoute = name;
22987 hash.currentWhen = hash.currentWhen || name;
22989 hash.parameters = {
22990 data: options.data,
22991 contexts: contexts,
22992 roots: options.contexts
22995 return Ember.Handlebars.helpers.view.call(this, LinkView, options);
23008 @submodule ember-routing
23011 var get = Ember.get, set = Ember.set;
23012 Ember.onLoad('Ember.Handlebars', function(Handlebars) {
23015 @submodule ember-handlebars
23018 Handlebars.OutletView = Ember.ContainerView.extend(Ember._Metamorph);
23021 The `outlet` helper allows you to specify that the current
23022 view's controller will fill in the view for a given area.
23028 By default, when the the current controller's `view` property changes, the
23029 outlet will replace its current view with the new view. You can set the
23030 `view` property directly, but it's normally best to use `connectOutlet`.
23033 # Instantiate App.PostsView and assign to `view`, so as to render into outlet.
23034 controller.connectOutlet('posts');
23037 You can also specify a particular name other than `view`:
23040 {{outlet masterView}}
23041 {{outlet detailView}}
23044 Then, you can control several outlets from a single controller.
23047 # Instantiate App.PostsView and assign to controller.masterView.
23048 controller.connectOutlet('masterView', 'posts');
23049 # Also, instantiate App.PostInfoView and assign to controller.detailView.
23050 controller.connectOutlet('detailView', 'postInfo');
23054 @for Ember.Handlebars.helpers
23055 @param {String} property the property on the controller
23056 that holds the view for this outlet
23058 Handlebars.registerHelper('outlet', function(property, options) {
23061 if (property && property.data && property.data.isRenderData) {
23062 options = property;
23066 outletSource = options.data.view;
23067 while (!(outletSource.get('template.isTop'))){
23068 outletSource = outletSource.get('_parentView');
23071 options.data.view.set('outletSource', outletSource);
23072 options.hash.currentViewBinding = '_view.outletSource._outlets.' + property;
23074 return Handlebars.helpers.view.call(this, Handlebars.OutletView, options);
23077 Ember.View.reopen({
23079 set(this, '_outlets', {});
23083 connectOutlet: function(outletName, view) {
23084 var outlets = get(this, '_outlets'),
23085 container = get(this, 'container'),
23086 router = container && container.lookup('router:main'),
23087 oldView = get(outlets, outletName),
23088 renderedName = get(view, 'renderedName');
23090 set(outlets, outletName, view);
23094 router._disconnectActiveView(oldView);
23096 if (renderedName) {
23097 router._connectActiveView(renderedName, view);
23102 disconnectOutlet: function(outletName) {
23103 var outlets = get(this, '_outlets'),
23104 container = get(this, 'container'),
23105 router = container && container.lookup('router:main'),
23106 view = get(outlets, outletName);
23108 set(outlets, outletName, null);
23110 if (router && view) {
23111 router._disconnectActiveView(view);
23124 @submodule ember-routing
23127 var get = Ember.get, set = Ember.set;
23128 Ember.onLoad('Ember.Handlebars', function(Handlebars) {
23130 Ember.Handlebars.registerHelper('render', function(name, context, options) {
23131 Ember.assert("You must pass a template to render", arguments.length >= 2);
23132 var container, router, controller, view;
23134 if (arguments.length === 2) {
23136 context = undefined;
23139 if (typeof context === 'string') {
23140 context = Ember.Handlebars.get(options.contexts[1], context, options);
23143 name = name.replace(/\//g, '.');
23144 container = options.data.keywords.controller.container;
23145 router = container.lookup('router:main');
23147 Ember.assert("This view is already rendered", !router || !router._lookupActiveView(name));
23149 view = container.lookup('view:' + name) || container.lookup('view:default');
23151 if (controller = options.hash.controller) {
23152 controller = container.lookup('controller:' + controller);
23154 controller = Ember.controllerFor(container, name, context);
23157 if (controller && context) {
23158 controller.set('model', context);
23161 controller.set('target', options.data.keywords.controller);
23163 options.hash.viewName = Ember.String.camelize(name);
23164 options.hash.template = container.lookup('template:' + name);
23165 options.hash.controller = controller;
23168 router._connectActiveView(name, view);
23171 Ember.Handlebars.helpers.view.call(this, view, options);
23183 @submodule ember-routing
23185 Ember.onLoad('Ember.Handlebars', function(Handlebars) {
23187 var resolvePaths = Ember.Handlebars.resolvePaths,
23188 isSimpleClick = Ember.ViewUtils.isSimpleClick;
23190 var EmberHandlebars = Ember.Handlebars,
23191 handlebarsGet = EmberHandlebars.get,
23192 SafeString = EmberHandlebars.SafeString,
23194 a_slice = Array.prototype.slice;
23196 function args(options, actionName) {
23198 if (actionName) { ret.push(actionName); }
23199 return ret.concat(resolvePaths(options.parameters));
23202 var ActionHelper = EmberHandlebars.ActionHelper = {
23203 registeredActions: {}
23206 ActionHelper.registerAction = function(actionName, options) {
23207 var actionId = (++Ember.uuid).toString();
23209 ActionHelper.registeredActions[actionId] = {
23210 eventName: options.eventName,
23211 handler: function(event) {
23212 if (!isSimpleClick(event)) { return true; }
23213 event.preventDefault();
23215 if (options.bubbles === false) {
23216 event.stopPropagation();
23219 var view = options.view,
23220 contexts = options.contexts,
23221 target = options.target;
23224 return target.send.apply(target, args(options, actionName));
23226 Ember.assert("The action '" + actionName + "' did not exist on " + target, typeof target[actionName] === 'function');
23227 return target[actionName].apply(target, args(options));
23232 options.view.on('willClearRender', function() {
23233 delete ActionHelper.registeredActions[actionId];
23240 The `{{action}}` helper registers an HTML element within a template for DOM
23241 event handling and forwards that interaction to the view's controller
23242 or supplied `target` option (see 'Specifying a Target').
23244 If the view's controller does not implement the event, the event is sent
23245 to the current route, and it bubbles up the route hierarchy from there.
23247 User interaction with that element will invoke the supplied action name on
23248 the appropriate target.
23250 Given the following Handlebars template on the page
23253 <script type="text/x-handlebars" data-template-name='a-template'>
23254 <div {{action anActionName}}>
23260 And application code
23263 AController = Ember.Controller.extend({
23264 anActionName: function() {}
23267 AView = Ember.View.extend({
23268 controller: AController.create(),
23269 templateName: 'a-template'
23272 aView = AView.create();
23273 aView.appendTo('body');
23276 Will results in the following rendered HTML
23279 <div class="ember-view">
23280 <div data-ember-action="1">
23286 Clicking "click me" will trigger the `anActionName` method of the
23287 `AController`. In this case, no additional parameters will be passed.
23289 If you provide additional parameters to the helper:
23292 <button {{action 'edit' post}}>Edit</button>
23295 Those parameters will be passed along as arguments to the JavaScript
23296 function implementing the action.
23298 ### Event Propagation
23300 Events triggered through the action helper will automatically have
23301 `.preventDefault()` called on them. You do not need to do so in your event
23304 To also disable bubbling, pass `bubbles=false` to the helper:
23307 <button {{action 'edit' post bubbles=false}}>Edit</button>
23310 If you need the default handler to trigger you should either register your
23311 own event handler, or use event methods on your view class. See `Ember.View`
23312 'Responding to Browser Events' for more information.
23314 ### Specifying DOM event type
23316 By default the `{{action}}` helper registers for DOM `click` events. You can
23317 supply an `on` option to the helper to specify a different DOM event name:
23320 <script type="text/x-handlebars" data-template-name='a-template'>
23321 <div {{action anActionName on="doubleClick"}}>
23327 See `Ember.View` 'Responding to Browser Events' for a list of
23328 acceptable DOM event names.
23330 NOTE: Because `{{action}}` depends on Ember's event dispatch system it will
23331 only function if an `Ember.EventDispatcher` instance is available. An
23332 `Ember.EventDispatcher` instance will be created when a new `Ember.Application`
23333 is created. Having an instance of `Ember.Application` will satisfy this
23336 ### Specifying a Target
23338 There are several possible target objects for `{{action}}` helpers:
23340 In a typical Ember application, where views are managed through use of the
23341 `{{outlet}}` helper, actions will bubble to the current controller, then
23342 to the current route, and then up the route hierarchy.
23344 Alternatively, a `target` option can be provided to the helper to change
23345 which object will receive the method call. This option must be a path
23346 path to an object, accessible in the current context:
23349 <script type="text/x-handlebars" data-template-name='a-template'>
23350 <div {{action anActionName target="MyApplication.someObject"}}>
23356 Clicking "click me" in the rendered HTML of the above template will trigger
23357 the `anActionName` method of the object at `MyApplication.someObject`.
23359 If an action's target does not implement a method that matches the supplied
23360 action name an error will be thrown.
23363 <script type="text/x-handlebars" data-template-name='a-template'>
23364 <div {{action aMethodNameThatIsMissing}}>
23370 With the following application code
23373 AView = Ember.View.extend({
23374 templateName; 'a-template',
23375 // note: no method 'aMethodNameThatIsMissing'
23376 anActionName: function(event) {}
23379 aView = AView.create();
23380 aView.appendTo('body');
23383 Will throw `Uncaught TypeError: Cannot call method 'call' of undefined` when
23384 "click me" is clicked.
23386 ### Additional Parameters
23388 You may specify additional parameters to the `{{action}}` helper. These
23389 parameters are passed along as the arguments to the JavaScript function
23390 implementing the action.
23393 <script type="text/x-handlebars" data-template-name='a-template'>
23394 {{#each person in people}}
23395 <div {{action edit person}}>
23402 Clicking "click me" will trigger the `edit` method on the current view's
23403 controller with the current person as a parameter.
23406 @for Ember.Handlebars.helpers
23407 @param {String} actionName
23408 @param {Object...} contexts
23409 @param {Hash} options
23411 EmberHandlebars.registerHelper('action', function(actionName) {
23412 var options = arguments[arguments.length - 1],
23413 contexts = a_slice.call(arguments, 1, -1);
23415 var hash = options.hash,
23416 view = options.data.view,
23417 target, controller, link;
23419 // create a hash to pass along to registerAction
23421 eventName: hash.on || "click"
23424 action.parameters = {
23425 data: options.data,
23426 contexts: contexts,
23427 roots: options.contexts
23430 action.view = view = get(view, 'concreteView');
23433 target = handlebarsGet(this, hash.target, options);
23434 } else if (controller = options.data.keywords.controller) {
23435 target = controller;
23438 action.target = target;
23439 action.bubbles = hash.bubbles;
23441 var actionId = ActionHelper.registerAction(actionName, action);
23442 return new SafeString('data-ember-action="' + actionId + '"');
23460 @submodule ember-routing
23463 var get = Ember.get, set = Ember.set;
23464 var ControllersProxy = Ember.Object.extend({
23467 unknownProperty: function(controllerName) {
23468 var controller = get(this, 'controller'),
23469 needs = get(controller, 'needs'),
23472 for (var i=0, l=needs.length; i<l; i++) {
23473 dependency = needs[i];
23474 if (dependency === controllerName) {
23475 return controller.controllerFor(controllerName);
23481 Ember.ControllerMixin.reopen({
23482 concatenatedProperties: ['needs'],
23486 this._super.apply(this, arguments);
23488 // Structure asserts to still do verification but not string concat in production
23489 if(!verifyDependencies(this)) {
23490 Ember.assert("Missing dependencies", false);
23494 transitionToRoute: function() {
23495 var target = get(this, 'target');
23497 return target.transitionTo.apply(target, arguments);
23500 // TODO: Deprecate this, see https://github.com/emberjs/ember.js/issues/1785
23501 transitionTo: function() {
23502 return this.transitionToRoute.apply(this, arguments);
23505 replaceRoute: function() {
23506 var target = get(this, 'target');
23508 return target.replaceWith.apply(target, arguments);
23511 // TODO: Deprecate this, see https://github.com/emberjs/ember.js/issues/1785
23512 replaceWith: function() {
23513 return this.replaceRoute.apply(this, arguments);
23516 controllerFor: function(controllerName) {
23517 var container = get(this, 'container');
23518 return container.lookup('controller:' + controllerName);
23521 model: Ember.computed(function(key, value) {
23522 if (arguments.length > 1) {
23523 return set(this, 'content', value);
23525 return get(this, 'content');
23527 }).property('content'),
23529 controllers: Ember.computed(function() {
23530 return ControllersProxy.create({ controller: this });
23534 function verifyDependencies(controller) {
23535 var needs = get(controller, 'needs'),
23536 container = get(controller, 'container'),
23537 dependency, satisfied = true;
23539 for (var i=0, l=needs.length; i<l; i++) {
23540 dependency = needs[i];
23541 if (dependency.indexOf(':') === -1) {
23542 dependency = "controller:" + dependency;
23545 if (!container.has(dependency)) {
23547 Ember.assert(controller + " needs " + dependency + " but it does not exist", false);
23561 @submodule ember-routing
23564 var get = Ember.get, set = Ember.set;
23566 Ember.View.reopen({
23568 set(this, '_outlets', {});
23572 connectOutlet: function(outletName, view) {
23573 var outlets = get(this, '_outlets'),
23574 container = get(this, 'container'),
23575 router = container && container.lookup('router:main'),
23576 renderedName = get(view, 'renderedName');
23578 set(outlets, outletName, view);
23580 if (router && renderedName) {
23581 router._connectActiveView(renderedName, view);
23585 disconnectOutlet: function(outletName) {
23586 var outlets = get(this, '_outlets');
23588 set(outlets, outletName, null);
23605 @submodule ember-routing
23608 var get = Ember.get, set = Ember.set;
23611 This file implements the `location` API used by Ember's router.
23615 getURL: returns the current URL
23616 setURL(path): sets the current URL
23617 replaceURL(path): replace the current URL (optional)
23618 onUpdateURL(callback): triggers the callback when the URL changes
23619 formatURL(url): formats `url` to be placed into `href` attribute
23621 Calling setURL or replaceURL will not trigger onUpdateURL callbacks.
23623 TODO: This should perhaps be moved so that it's visible in the doc output.
23627 Ember.Location returns an instance of the correct implementation of
23628 the `location` API.
23630 You can pass it a `implementation` ('hash', 'history', 'none') to force a
23631 particular implementation.
23638 create: function(options) {
23639 var implementation = options && options.implementation;
23640 Ember.assert("Ember.Location.create: you must specify a 'implementation' option", !!implementation);
23642 var implementationClass = this.implementations[implementation];
23643 Ember.assert("Ember.Location.create: " + implementation + " is not a valid implementation", !!implementationClass);
23645 return implementationClass.create.apply(implementationClass, arguments);
23648 registerImplementation: function(name, implementation) {
23649 this.implementations[name] = implementation;
23652 implementations: {}
23662 @submodule ember-routing
23665 var get = Ember.get, set = Ember.set;
23668 Ember.NoneLocation does not interact with the browser. It is useful for
23669 testing, or when you need to manage state with your Router, but temporarily
23670 don't want it to muck with the URL (for example when you embed your
23671 application in a larger page).
23673 @class NoneLocation
23675 @extends Ember.Object
23677 Ember.NoneLocation = Ember.Object.extend({
23680 getURL: function() {
23681 return get(this, 'path');
23684 setURL: function(path) {
23685 set(this, 'path', path);
23688 onUpdateURL: function(callback) {
23689 // We are not wired up to the browser, so we'll never trigger the callback.
23692 formatURL: function(url) {
23693 // The return value is not overly meaningful, but we do not want to throw
23694 // errors when test code renders templates containing {{action href=true}}
23700 Ember.Location.registerImplementation('none', Ember.NoneLocation);
23709 @submodule ember-routing
23712 var get = Ember.get, set = Ember.set;
23715 Ember.HashLocation implements the location API using the browser's
23716 hash. At present, it relies on a hashchange event existing in the
23719 @class HashLocation
23721 @extends Ember.Object
23723 Ember.HashLocation = Ember.Object.extend({
23726 set(this, 'location', get(this, 'location') || window.location);
23732 Returns the current `location.hash`, minus the '#' at the front.
23736 getURL: function() {
23737 return get(this, 'location').hash.substr(1);
23743 Set the `location.hash` and remembers what was set. This prevents
23744 `onUpdateURL` callbacks from triggering when the hash was set by
23748 @param path {String}
23750 setURL: function(path) {
23751 get(this, 'location').hash = path;
23752 set(this, 'lastSetURL', path);
23758 Register a callback to be invoked when the hash changes. These
23759 callbacks will execute when the user presses the back or forward
23760 button, but not after `setURL` is invoked.
23762 @method onUpdateURL
23763 @param callback {Function}
23765 onUpdateURL: function(callback) {
23767 var guid = Ember.guidFor(this);
23769 Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
23770 var path = location.hash.substr(1);
23771 if (get(self, 'lastSetURL') === path) { return; }
23773 set(self, 'lastSetURL', null);
23775 callback(location.hash.substr(1));
23782 Given a URL, formats it to be placed into the page as part
23783 of an element's `href` attribute.
23785 This is used, for example, when using the {{action}} helper
23786 to generate a URL based on an event.
23789 @param url {String}
23791 formatURL: function(url) {
23795 willDestroy: function() {
23796 var guid = Ember.guidFor(this);
23798 Ember.$(window).unbind('hashchange.ember-location-'+guid);
23802 Ember.Location.registerImplementation('hash', Ember.HashLocation);
23811 @submodule ember-routing
23814 var get = Ember.get, set = Ember.set;
23815 var popstateReady = false;
23818 Ember.HistoryLocation implements the location API using the browser's
23819 history.pushState API.
23821 @class HistoryLocation
23823 @extends Ember.Object
23825 Ember.HistoryLocation = Ember.Object.extend({
23828 set(this, 'location', get(this, 'location') || window.location);
23835 Used to set state on first call to setURL
23839 initState: function() {
23840 this.replaceState(get(this, 'location').pathname);
23841 set(this, 'history', window.history);
23845 Will be pre-pended to path upon state change
23855 Returns the current `location.pathname`.
23859 getURL: function() {
23860 return get(this, 'location').pathname;
23866 Uses `history.pushState` to update the url without a page reload.
23869 @param path {String}
23871 setURL: function(path) {
23872 path = this.formatURL(path);
23874 if (this.getState() && this.getState().path !== path) {
23875 popstateReady = true;
23876 this.pushState(path);
23883 Uses `history.replaceState` to update the url without a page reload
23884 or history modification.
23887 @param path {String}
23889 replaceURL: function(path) {
23890 path = this.formatURL(path);
23892 if (this.getState() && this.getState().path !== path) {
23893 popstateReady = true;
23894 this.replaceState(path);
23901 Get the current `history.state`
23905 getState: function() {
23906 return get(this, 'history').state;
23915 @param path {String}
23917 pushState: function(path) {
23918 window.history.pushState({ path: path }, null, path);
23924 Replaces the current state
23926 @method replaceState
23927 @param path {String}
23929 replaceState: function(path) {
23930 window.history.replaceState({ path: path }, null, path);
23936 Register a callback to be invoked whenever the browser
23937 history changes, including using forward and back buttons.
23939 @method onUpdateURL
23940 @param callback {Function}
23942 onUpdateURL: function(callback) {
23943 var guid = Ember.guidFor(this);
23945 Ember.$(window).bind('popstate.ember-location-'+guid, function(e) {
23946 if(!popstateReady) {
23949 callback(location.pathname);
23956 Used when using `{{action}}` helper. The url is always appended to the rootURL.
23959 @param url {String}
23961 formatURL: function(url) {
23962 var rootURL = get(this, 'rootURL');
23965 rootURL = rootURL.replace(/\/$/, '');
23968 return rootURL + url;
23971 willDestroy: function() {
23972 var guid = Ember.guidFor(this);
23974 Ember.$(window).unbind('popstate.ember-location-'+guid);
23978 Ember.Location.registerImplementation('history', Ember.HistoryLocation);
23995 @submodule ember-routing
23996 @requires ember-states
23997 @requires ember-views
24003 function visit(vertex, fn, visited, path) {
24004 var name = vertex.name,
24005 vertices = vertex.incoming,
24006 names = vertex.incomingNames,
24007 len = names.length,
24015 if (visited.hasOwnProperty(name)) {
24019 visited[name] = true;
24020 for (i = 0; i < len; i++) {
24021 visit(vertices[names[i]], fn, visited, path);
24029 this.vertices = {};
24032 DAG.prototype.add = function(name) {
24033 if (!name) { return; }
24034 if (this.vertices.hasOwnProperty(name)) {
24035 return this.vertices[name];
24038 name: name, incoming: {}, incomingNames: [], hasOutgoing: false, value: null
24040 this.vertices[name] = vertex;
24041 this.names.push(name);
24045 DAG.prototype.map = function(name, value) {
24046 this.add(name).value = value;
24049 DAG.prototype.addEdge = function(fromName, toName) {
24050 if (!fromName || !toName || fromName === toName) {
24053 var from = this.add(fromName), to = this.add(toName);
24054 if (to.incoming.hasOwnProperty(fromName)) {
24057 function checkCycle(vertex, path) {
24058 if (vertex.name === toName) {
24059 throw new Error("cycle detected: " + toName + " <- " + path.join(" <- "));
24062 visit(from, checkCycle);
24063 from.hasOutgoing = true;
24064 to.incoming[fromName] = from;
24065 to.incomingNames.push(fromName);
24068 DAG.prototype.topsort = function(fn) {
24070 vertices = this.vertices,
24071 names = this.names,
24072 len = names.length,
24074 for (i = 0; i < len; i++) {
24075 vertex = vertices[names[i]];
24076 if (!vertex.hasOutgoing) {
24077 visit(vertex, fn, visited);
24082 DAG.prototype.addEdges = function(name, value, before, after) {
24084 this.map(name, value);
24086 if (typeof before === 'string') {
24087 this.addEdge(name, before);
24089 for (i = 0; i < before.length; i++) {
24090 this.addEdge(name, before[i]);
24095 if (typeof after === 'string') {
24096 this.addEdge(after, name);
24098 for (i = 0; i < after.length; i++) {
24099 this.addEdge(after[i], name);
24114 @submodule ember-application
24117 var get = Ember.get, set = Ember.set,
24118 classify = Ember.String.classify,
24119 decamelize = Ember.String.decamelize;
24122 An instance of `Ember.Application` is the starting point for every Ember
24123 application. It helps to instantiate, initialize and coordinate the many
24124 objects that make up your app.
24126 Each Ember app has one and only one `Ember.Application` object. In fact, the
24127 very first thing you should do in your application is create the instance:
24130 window.App = Ember.Application.create();
24133 Typically, the application object is the only global variable. All other
24134 classes in your app should be properties on the `Ember.Application` instance,
24135 which highlights its first role: a global namespace.
24137 For example, if you define a view class, it might look like this:
24140 App.MyView = Ember.View.extend();
24143 By default, calling `Ember.Application.create()` will automatically initialize
24144 your application by calling the `Ember.Application.initialize()` method. If
24145 you need to delay initialization, you can call your app's `deferReadiness()`
24146 method. When you are ready for your app to be initialized, call its
24147 `advanceReadiness()` method.
24149 Because `Ember.Application` inherits from `Ember.Namespace`, any classes
24150 you create will have useful string representations when calling `toString()`.
24151 See the `Ember.Namespace` documentation for more information.
24153 While you can think of your `Ember.Application` as a container that holds the
24154 other classes in your application, there are several other responsibilities
24155 going on under-the-hood that you may want to understand.
24157 ### Event Delegation
24159 Ember uses a technique called _event delegation_. This allows the framework
24160 to set up a global, shared event listener instead of requiring each view to
24161 do it manually. For example, instead of each view registering its own
24162 `mousedown` listener on its associated element, Ember sets up a `mousedown`
24163 listener on the `body`.
24165 If a `mousedown` event occurs, Ember will look at the target of the event and
24166 start walking up the DOM node tree, finding corresponding views and invoking
24167 their `mouseDown` method as it goes.
24169 `Ember.Application` has a number of default events that it listens for, as
24170 well as a mapping from lowercase events to camel-cased view method names. For
24171 example, the `keypress` event causes the `keyPress` method on the view to be
24172 called, the `dblclick` event causes `doubleClick` to be called, and so on.
24174 If there is a browser event that Ember does not listen for by default, you
24175 can specify custom events and their corresponding view method names by
24176 setting the application's `customEvents` property:
24179 App = Ember.Application.create({
24181 // add support for the loadedmetadata media
24183 'loadedmetadata': "loadedMetadata"
24188 By default, the application sets up these event listeners on the document
24189 body. However, in cases where you are embedding an Ember application inside
24190 an existing page, you may want it to set up the listeners on an element
24193 For example, if only events inside a DOM element with the ID of `ember-app`
24194 should be delegated, set your application's `rootElement` property:
24197 window.App = Ember.Application.create({
24198 rootElement: '#ember-app'
24202 The `rootElement` can be either a DOM element or a jQuery-compatible selector
24203 string. Note that *views appended to the DOM outside the root element will
24204 not receive events.* If you specify a custom root element, make sure you only
24205 append views inside it!
24207 To learn more about the advantages of event delegation and the Ember view
24208 layer, and a list of the event listeners that are setup by default, visit the
24209 [Ember View Layer guide](http://emberjs.com/guides/view_layer#toc_event-delegation).
24211 ### Dependency Injection
24213 One thing you may have noticed while using Ember is that you define
24214 *classes*, not *instances*. When your application loads, all of the instances
24215 are created for you. Creating these instances is the responsibility of
24216 `Ember.Application`.
24218 When the `Ember.Application` initializes, it will look for an `Ember.Router`
24219 class defined on the applications's `Router` property, like this:
24222 App.Router = Ember.Router.extend({
24227 If found, the router is instantiated and saved on the application's `router`
24228 property (note the lowercase 'r'). While you should *not* reference this
24229 router instance directly from your application code, having access to
24230 `App.router` from the console can be useful during debugging.
24232 After the router is created, the application loops through all of the
24233 registered _injections_ and invokes them once for each property on the
24234 `Ember.Application` object.
24236 An injection is a function that is responsible for instantiating objects from
24237 classes defined on the application. By default, the only injection registered
24238 instantiates controllers and makes them available on the router.
24240 For example, if you define a controller class:
24243 App.MyController = Ember.Controller.extend({
24248 Your router will receive an instance of `App.MyController` saved on its
24249 `myController` property.
24251 Libraries on top of Ember can register additional injections. For example,
24252 if your application is using Ember Data, it registers an injection that
24253 instantiates `DS.Store`:
24256 Ember.Application.registerInjection({
24258 before: 'controllers',
24260 injection: function(app, router, property) {
24261 if (property === 'Store') {
24262 set(router, 'store', app[property].create());
24270 In addition to creating your application's router, `Ember.Application` is
24271 also responsible for telling the router when to start routing.
24273 By default, the router will begin trying to translate the current URL into
24274 application state once the browser emits the `DOMContentReady` event. If you
24275 need to defer routing, you can call the application's `deferReadiness()`
24276 method. Once routing can begin, call the `advanceReadiness()` method.
24278 If there is any setup required before routing begins, you can implement a
24279 `ready()` method on your app that will be invoked immediately before routing
24283 window.App = Ember.Application.create({
24284 ready: function() {
24285 this.set('router.enableLogging', true);
24289 To begin routing, you must have at a minimum a top-level controller and view.
24290 You define these as `App.ApplicationController` and `App.ApplicationView`,
24291 respectively. Your application will not work if you do not define these two
24292 mandatory classes. For example:
24295 App.ApplicationView = Ember.View.extend({
24296 templateName: 'application'
24298 App.ApplicationController = Ember.Controller.extend();
24303 @extends Ember.Namespace
24305 var Application = Ember.Application = Ember.Namespace.extend(
24306 /** @scope Ember.Application.prototype */{
24309 The root DOM element of the Application. This can be specified as an
24311 [jQuery-compatible selector string](http://api.jquery.com/category/selectors/).
24313 This is the element that will be passed to the Application's,
24314 `eventDispatcher`, which sets up the listeners for event delegation. Every
24315 view in your application should be a child of the element you specify here.
24317 @property rootElement
24321 rootElement: 'body',
24324 The `Ember.EventDispatcher` responsible for delegating events to this
24325 application's views.
24327 The event dispatcher is created by the application at initialization time
24328 and sets up event listeners on the DOM element described by the
24329 application's `rootElement` property.
24331 See the documentation for `Ember.EventDispatcher` for more information.
24333 @property eventDispatcher
24334 @type Ember.EventDispatcher
24337 eventDispatcher: null,
24340 The DOM events for which the event dispatcher should listen.
24342 By default, the application's `Ember.EventDispatcher` listens
24343 for a set of standard DOM events, such as `mousedown` and
24344 `keyup`, and delegates them to your application's `Ember.View`
24347 If you would like additional events to be delegated to your
24348 views, set your `Ember.Application`'s `customEvents` property
24349 to a hash containing the DOM event name as the key and the
24350 corresponding view method name as the value. For example:
24353 App = Ember.Application.create({
24355 // add support for the loadedmetadata media
24357 'loadedmetadata': "loadedMetadata"
24362 @property customEvents
24366 customEvents: null,
24368 isInitialized: false,
24370 // Start off the number of deferrals at 1. This will be
24371 // decremented by the Application's own `initialize` method.
24372 _readinessDeferrals: 1,
24375 if (!this.$) { this.$ = Ember.$; }
24376 this.__container__ = this.buildContainer();
24378 this.Router = this.Router || this.defaultRouter();
24379 if (this.Router) { this.Router.namespace = this; }
24383 this.deferUntilDOMReady();
24384 this.scheduleInitialize();
24390 Build the container for the current application.
24392 Also register a default application view in case the application
24395 @method buildContainer
24396 @return {Ember.Container} the configured container
24398 buildContainer: function() {
24399 var container = this.__container__ = Application.buildContainer(this);
24407 If the application has not opted out of routing and has not explicitly
24408 defined a router, supply a default router for the application author
24411 This allows application developers to do:
24414 App = Ember.Application.create();
24416 App.Router.map(function(match) {
24417 match("/").to("index");
24421 @method defaultRouter
24422 @return {Ember.Router} the default router
24424 defaultRouter: function() {
24425 // Create a default App.Router if one was not supplied to make
24426 // it possible to do App.Router.map(...) without explicitly
24427 // creating a router first.
24428 if (this.router === undefined) {
24429 return Ember.Router.extend();
24436 Defer Ember readiness until DOM readiness. By default, Ember
24437 will wait for both DOM readiness and application initialization,
24438 as well as any deferrals registered by initializers.
24440 @method deferUntilDOMReady
24442 deferUntilDOMReady: function() {
24443 this.deferReadiness();
24446 this.$().ready(function() {
24447 self.advanceReadiness();
24454 Automatically initialize the application once the DOM has
24457 The initialization itself is deferred using Ember.run.once,
24458 which ensures that application loading finishes before
24461 If you are asynchronously loading code, you should call
24462 `deferReadiness()` to defer booting, and then call
24463 `advanceReadiness()` once all of your code has finished
24466 @method scheduleInitialize
24468 scheduleInitialize: function() {
24470 this.$().ready(function() {
24471 if (self.isDestroyed || self.isInitialized) return;
24472 Ember.run.once(self, 'initialize');
24477 Use this to defer readiness until some condition is true.
24482 App = Ember.Application.create();
24483 App.deferReadiness();
24485 jQuery.getJSON("/auth-token", function(token) {
24487 App.advanceReadiness();
24491 This allows you to perform asynchronous setup logic and defer
24492 booting your application until the setup has finished.
24494 However, if the setup requires a loading UI, it might be better
24495 to use the router for this purpose.
24497 @method deferReadiness
24499 deferReadiness: function() {
24500 Ember.assert("You cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0);
24501 this._readinessDeferrals++;
24505 @method advanceReadiness
24506 @see {Ember.Application#deferReadiness}
24508 advanceReadiness: function() {
24509 this._readinessDeferrals--;
24511 if (this._readinessDeferrals === 0) {
24512 Ember.run.once(this, this.didBecomeReady);
24516 register: function() {
24517 var container = this.__container__;
24518 return container.register.apply(container, arguments);
24524 Initialize the application. This happens automatically.
24526 Run any injections and run the application load hook. These hooks may
24527 choose to defer readiness. For example, an authentication hook might want
24528 to defer readiness until the auth token has been retrieved.
24532 initialize: function() {
24533 Ember.assert("Application initialize may only be called once", !this.isInitialized);
24534 Ember.assert("Cannot initialize a destroyed application", !this.isDestroyed);
24535 this.isInitialized = true;
24537 // At this point, the App.Router must already be assigned
24538 this.__container__.register('router', 'main', this.Router);
24540 // Run any injections and run the application load hook. These hooks may
24541 // choose to defer readiness. For example, an authentication hook might want
24542 // to defer readiness until the auth token has been retrieved.
24543 this.runInitializers();
24544 Ember.runLoadHooks('application', this);
24546 // At this point, any injections or load hooks that would have wanted
24547 // to defer readiness have fired. In general, advancing readiness here
24548 // will proceed to didBecomeReady.
24549 this.advanceReadiness();
24556 @method runInitializers
24558 runInitializers: function() {
24559 var initializers = get(this.constructor, 'initializers'),
24560 container = this.__container__,
24561 graph = new Ember.DAG(),
24563 properties, i, initializer;
24565 for (i=0; i<initializers.length; i++) {
24566 initializer = initializers[i];
24567 graph.addEdges(initializer.name, initializer.initialize, initializer.before, initializer.after);
24570 graph.topsort(function (vertex) {
24571 var initializer = vertex.value;
24572 initializer(container, namespace);
24578 @method didBecomeReady
24580 didBecomeReady: function() {
24581 this.setupEventDispatcher();
24582 this.ready(); // user hook
24583 this.startRouting();
24585 if (!Ember.testing) {
24586 // Eagerly name all classes that are already loaded
24587 Ember.Namespace.processAll();
24588 Ember.BOOTED = true;
24595 Setup up the event dispatcher to receive events on the
24596 application's `rootElement` with any registered
24599 @method setupEventDispatcher
24601 setupEventDispatcher: function() {
24602 var eventDispatcher = this.createEventDispatcher(),
24603 customEvents = get(this, 'customEvents');
24605 eventDispatcher.setup(customEvents);
24611 Create an event dispatcher for the application's `rootElement`.
24613 @method createEventDispatcher
24615 createEventDispatcher: function() {
24616 var rootElement = get(this, 'rootElement'),
24617 eventDispatcher = Ember.EventDispatcher.create({
24618 rootElement: rootElement
24621 set(this, 'eventDispatcher', eventDispatcher);
24622 return eventDispatcher;
24628 If the application has a router, use it to route to the current URL, and
24629 trigger a new call to `route` whenever the URL changes.
24631 @method startRouting
24632 @property router {Ember.Router}
24634 startRouting: function() {
24635 var router = this.__container__.lookup('router:main');
24636 if (!router) { return; }
24638 router.startRouting();
24642 Called when the Application has become ready.
24643 The call will be delayed until the DOM has become ready.
24649 willDestroy: function() {
24650 Ember.BOOTED = false;
24652 var eventDispatcher = get(this, 'eventDispatcher');
24653 if (eventDispatcher) { eventDispatcher.destroy(); }
24655 this.__container__.destroy();
24658 initializer: function(options) {
24659 this.constructor.initializer(options);
24663 Ember.Application.reopenClass({
24664 concatenatedProperties: ['initializers'],
24665 initializers: Ember.A(),
24666 initializer: function(initializer) {
24667 var initializers = get(this, 'initializers');
24669 Ember.assert("The initializer '" + initializer.name + "' has already been registered", !initializers.findProperty('name', initializers.name));
24670 Ember.assert("An injection cannot be registered with both a before and an after", !(initializer.before && initializer.after));
24671 Ember.assert("An injection cannot be registered without an injection function", Ember.canInvoke(initializer, 'initialize'));
24673 initializers.push(initializer);
24679 This creates a container with the default Ember naming conventions.
24681 It also configures the container:
24683 * registered views are created every time they are looked up (they are
24685 * registered templates are not factories; the registered value is
24687 * the router receives the application as its `namespace` property
24688 * all controllers receive the router as their `target` and `controllers`
24690 * all controllers receive the application as their `namespace` property
24691 * the application view receives the application controller as its
24692 `controller` property
24693 * the application view receives the application template as its
24694 `defaultTemplate` property
24696 @method buildContainer
24698 @param {Ember.Application} namespace the application to build the
24700 @return {Ember.Container} the built container
24702 buildContainer: function(namespace) {
24703 var container = new Ember.Container();
24704 Ember.Container.defaultContainer = container;
24706 container.set = Ember.set;
24707 container.resolver = resolverFor(namespace);
24708 container.optionsForType('view', { singleton: false });
24709 container.optionsForType('template', { instantiate: false });
24710 container.register('application', 'main', namespace, { instantiate: false });
24711 container.injection('router:main', 'namespace', 'application:main');
24713 container.typeInjection('controller', 'target', 'router:main');
24714 container.typeInjection('controller', 'namespace', 'application:main');
24716 container.typeInjection('route', 'router', 'router:main');
24725 This function defines the default lookup rules for container lookups:
24727 * templates are looked up on `Ember.TEMPLATES`
24728 * other names are looked up on the application after classifying the name.
24729 For example, `controller:post` looks up `App.PostController` by default.
24730 * if the default lookup fails, look for registered classes on the container
24732 This allows the application to register default injections in the container
24733 that could be overridden by the normal naming convention.
24735 @param {Ember.Namespace} namespace the namespace to look for classes
24736 @return {any} the resolved value for a given lookup
24738 function resolverFor(namespace) {
24739 return function(fullName) {
24740 var nameParts = fullName.split(":"),
24741 type = nameParts[0], name = nameParts[1];
24743 if (type === 'template') {
24744 var templateName = name.replace(/\./g, '/');
24745 if (Ember.TEMPLATES[templateName]) {
24746 return Ember.TEMPLATES[templateName];
24749 templateName = decamelize(templateName);
24750 if (Ember.TEMPLATES[templateName]) {
24751 return Ember.TEMPLATES[templateName];
24755 if (type === 'controller' || type === 'route' || type === 'view') {
24756 name = name.replace(/\./g, '_');
24759 var className = classify(name) + classify(type);
24760 var factory = get(namespace, className);
24762 if (factory) { return factory; }
24766 Ember.runLoadHooks('Ember.Application', Ember.Application);
24784 @submodule ember-application
24785 @requires ember-views, ember-states, ember-routing
24791 var get = Ember.get, set = Ember.set;
24795 @submodule ember-states
24801 @extends Ember.Object
24802 @uses Ember.Evented
24804 Ember.State = Ember.Object.extend(Ember.Evented,
24805 /** @scope Ember.State.prototype */{
24809 A reference to the parent state.
24811 @property parentState
24818 The name of this state.
24826 The full path to this state.
24831 path: Ember.computed(function() {
24832 var parentPath = get(this, 'parentState.path'),
24833 path = get(this, 'name');
24836 path = parentPath + '.' + path;
24845 Override the default event firing from `Ember.Evented` to
24846 also call methods with the given name.
24851 trigger: function(name) {
24853 this[name].apply(this, [].slice.call(arguments, 1));
24855 this._super.apply(this, arguments);
24859 var states = get(this, 'states'), foundStates;
24860 set(this, 'childStates', Ember.A());
24861 set(this, 'eventTransitions', get(this, 'eventTransitions') || {});
24863 var name, value, transitionTarget;
24865 // As a convenience, loop over the properties
24866 // of this state and look for any that are other
24867 // Ember.State instances or classes, and move them
24868 // to the `states` hash. This avoids having to
24869 // create an explicit separate hash.
24874 for (name in this) {
24875 if (name === "constructor") { continue; }
24877 if (value = this[name]) {
24878 if (transitionTarget = value.transitionTarget) {
24879 this.eventTransitions[name] = transitionTarget;
24882 this.setupChild(states, name, value);
24886 set(this, 'states', states);
24888 for (name in states) {
24889 this.setupChild(states, name, states[name]);
24893 set(this, 'pathsCache', {});
24894 set(this, 'pathsCacheNoContext', {});
24897 setupChild: function(states, name, value) {
24898 if (!value) { return false; }
24900 if (value.isState) {
24901 set(value, 'name', name);
24902 } else if (Ember.State.detect(value)) {
24903 value = value.create({
24908 if (value.isState) {
24909 set(value, 'parentState', this);
24910 get(this, 'childStates').pushObject(value);
24911 states[name] = value;
24916 lookupEventTransition: function(name) {
24917 var path, state = this;
24919 while(state && !path) {
24920 path = state.eventTransitions[name];
24921 state = state.get('parentState');
24928 A Boolean value indicating whether the state is a leaf state
24929 in the state hierarchy. This is `false` if the state has child
24930 states; otherwise it is true.
24935 isLeaf: Ember.computed(function() {
24936 return !get(this, 'childStates').length;
24940 A boolean value indicating whether the state takes a context.
24941 By default we assume all states take contexts.
24943 @property hasContext
24949 This is the default transition event.
24952 @param {Ember.StateManager} manager
24954 @see Ember.StateManager#transitionEvent
24959 This event fires when the state is entered.
24962 @param {Ember.StateManager} manager
24967 This event fires when the state is exited.
24970 @param {Ember.StateManager} manager
24975 Ember.State.reopenClass({
24978 Creates an action function for transitioning to the named state while
24979 preserving context.
24981 The following example StateManagers are equivalent:
24984 aManager = Ember.StateManager.create({
24985 stateOne: Ember.State.create({
24986 changeToStateTwo: Ember.State.transitionTo('stateTwo')
24988 stateTwo: Ember.State.create({})
24991 bManager = Ember.StateManager.create({
24992 stateOne: Ember.State.create({
24993 changeToStateTwo: function(manager, context){
24994 manager.transitionTo('stateTwo', context)
24997 stateTwo: Ember.State.create({})
25001 @method transitionTo
25003 @param {String} target
25006 transitionTo: function(target) {
25008 var transitionFunction = function(stateManager, contextOrEvent) {
25009 var contexts = [], transitionArgs,
25010 Event = Ember.$ && Ember.$.Event;
25012 if (contextOrEvent && (Event && contextOrEvent instanceof Event)) {
25013 if (contextOrEvent.hasOwnProperty('contexts')) {
25014 contexts = contextOrEvent.contexts.slice();
25018 contexts = [].slice.call(arguments, 1);
25021 contexts.unshift(target);
25022 stateManager.transitionTo.apply(stateManager, contexts);
25025 transitionFunction.transitionTarget = target;
25027 return transitionFunction;
25039 @submodule ember-states
25042 var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
25043 var arrayForEach = Ember.ArrayPolyfills.forEach;
25045 A Transition takes the enter, exit and resolve states and normalizes
25048 * takes any passed in contexts into consideration
25049 * adds in `initialState`s
25054 var Transition = function(raw) {
25055 this.enterStates = raw.enterStates.slice();
25056 this.exitStates = raw.exitStates.slice();
25057 this.resolveState = raw.resolveState;
25059 this.finalState = raw.enterStates[raw.enterStates.length - 1] || raw.resolveState;
25062 Transition.prototype = {
25064 Normalize the passed in enter, exit and resolve states.
25066 This process also adds `finalState` and `contexts` to the Transition object.
25069 @param {Ember.StateManager} manager the state manager running the transition
25070 @param {Array} contexts a list of contexts passed into `transitionTo`
25072 normalize: function(manager, contexts) {
25073 this.matchContextsToStates(contexts);
25074 this.addInitialStates();
25075 this.removeUnchangedContexts(manager);
25080 Match each of the contexts passed to `transitionTo` to a state.
25081 This process may also require adding additional enter and exit
25082 states if there are more contexts than enter states.
25084 @method matchContextsToStates
25085 @param {Array} contexts a list of contexts passed into `transitionTo`
25087 matchContextsToStates: function(contexts) {
25088 var stateIdx = this.enterStates.length - 1,
25089 matchedContexts = [],
25093 // Next, we will match the passed in contexts to the states they
25096 // First, assign a context to each enter state in reverse order. If
25097 // any contexts are left, add a parent state to the list of states
25098 // to enter and exit, and assign a context to the parent state.
25100 // If there are still contexts left when the state manager is
25101 // reached, raise an exception.
25103 // This allows the following:
25108 // | |- about (* current state)
25110 // For `transitionTo('post.comments', post, post.get('comments')`,
25111 // the first context (`post`) will be assigned to `root.post`, and
25112 // the second context (`post.get('comments')`) will be assigned
25113 // to `root.post.comments`.
25115 // For the following:
25119 // | | |- index (* current state)
25122 // For `transitionTo('post.comments', otherPost, otherPost.get('comments')`,
25123 // the `<root.post>` state will be added to the list of enter and exit
25124 // states because its context has changed.
25126 while (contexts.length > 0) {
25127 if (stateIdx >= 0) {
25128 state = this.enterStates[stateIdx--];
25130 if (this.enterStates.length) {
25131 state = get(this.enterStates[0], 'parentState');
25132 if (!state) { throw "Cannot match all contexts to states"; }
25134 // If re-entering the current state with a context, the resolve
25135 // state will be the current state.
25136 state = this.resolveState;
25139 this.enterStates.unshift(state);
25140 this.exitStates.unshift(state);
25143 // in routers, only states with dynamic segments have a context
25144 if (get(state, 'hasContext')) {
25145 context = contexts.pop();
25150 matchedContexts.unshift(context);
25153 this.contexts = matchedContexts;
25157 Add any `initialState`s to the list of enter states.
25159 @method addInitialStates
25161 addInitialStates: function() {
25162 var finalState = this.finalState, initialState;
25165 initialState = get(finalState, 'initialState') || 'start';
25166 finalState = get(finalState, 'states.' + initialState);
25168 if (!finalState) { break; }
25170 this.finalState = finalState;
25171 this.enterStates.push(finalState);
25172 this.contexts.push(undefined);
25177 Remove any states that were added because the number of contexts
25178 exceeded the number of explicit enter states, but the context has
25179 not changed since the last time the state was entered.
25181 @method removeUnchangedContexts
25182 @param {Ember.StateManager} manager passed in to look up the last
25183 context for a states
25185 removeUnchangedContexts: function(manager) {
25186 // Start from the beginning of the enter states. If the state was added
25187 // to the list during the context matching phase, make sure the context
25188 // has actually changed since the last time the state was entered.
25189 while (this.enterStates.length > 0) {
25190 if (this.enterStates[0] !== this.exitStates[0]) { break; }
25192 if (this.enterStates.length === this.contexts.length) {
25193 if (manager.getStateMeta(this.enterStates[0], 'context') !== this.contexts[0]) { break; }
25194 this.contexts.shift();
25197 this.resolveState = this.enterStates.shift();
25198 this.exitStates.shift();
25203 var sendRecursively = function(event, currentState, isUnhandledPass) {
25204 var log = this.enableLogging,
25205 eventName = isUnhandledPass ? 'unhandledEvent' : event,
25206 action = currentState[eventName],
25207 contexts, sendRecursiveArguments, actionArguments;
25209 contexts = [].slice.call(arguments, 3);
25211 // Test to see if the action is a method that
25212 // can be invoked. Don't blindly check just for
25213 // existence, because it is possible the state
25214 // manager has a child state of the given name,
25215 // and we should still raise an exception in that
25217 if (typeof action === 'function') {
25219 if (isUnhandledPass) {
25220 Ember.Logger.log(fmt("STATEMANAGER: Unhandled event '%@' being sent to state %@.", [event, get(currentState, 'path')]));
25222 Ember.Logger.log(fmt("STATEMANAGER: Sending event '%@' to state %@.", [event, get(currentState, 'path')]));
25226 actionArguments = contexts;
25227 if (isUnhandledPass) {
25228 actionArguments.unshift(event);
25230 actionArguments.unshift(this);
25232 return action.apply(currentState, actionArguments);
25234 var parentState = get(currentState, 'parentState');
25237 sendRecursiveArguments = contexts;
25238 sendRecursiveArguments.unshift(event, parentState, isUnhandledPass);
25240 return sendRecursively.apply(this, sendRecursiveArguments);
25241 } else if (!isUnhandledPass) {
25242 return sendEvent.call(this, event, contexts, true);
25247 var sendEvent = function(eventName, sendRecursiveArguments, isUnhandledPass) {
25248 sendRecursiveArguments.unshift(eventName, get(this, 'currentState'), isUnhandledPass);
25249 return sendRecursively.apply(this, sendRecursiveArguments);
25253 StateManager is part of Ember's implementation of a finite state machine. A
25254 StateManager instance manages a number of properties that are instances of
25256 tracks the current active state, and triggers callbacks when states have changed.
25260 The states of StateManager can be declared in one of two ways. First, you can
25261 define a `states` property that contains all the states:
25264 managerA = Ember.StateManager.create({
25266 stateOne: Ember.State.create(),
25267 stateTwo: Ember.State.create()
25271 managerA.get('states')
25273 // stateOne: Ember.State.create(),
25274 // stateTwo: Ember.State.create()
25278 You can also add instances of `Ember.State` (or an `Ember.State` subclass)
25279 directly as properties of a StateManager. These states will be collected into
25280 the `states` property for you.
25283 managerA = Ember.StateManager.create({
25284 stateOne: Ember.State.create(),
25285 stateTwo: Ember.State.create()
25288 managerA.get('states')
25290 // stateOne: Ember.State.create(),
25291 // stateTwo: Ember.State.create()
25295 ## The Initial State
25297 When created a StateManager instance will immediately enter into the state
25298 defined as its `start` property or the state referenced by name in its
25299 `initialState` property:
25302 managerA = Ember.StateManager.create({
25303 start: Ember.State.create({})
25306 managerA.get('currentState.name') // 'start'
25308 managerB = Ember.StateManager.create({
25309 initialState: 'beginHere',
25310 beginHere: Ember.State.create({})
25313 managerB.get('currentState.name') // 'beginHere'
25316 Because it is a property you may also provide a computed function if you wish
25317 to derive an `initialState` programmatically:
25320 managerC = Ember.StateManager.create({
25321 initialState: function(){
25328 active: Ember.State.create({}),
25329 passive: Ember.State.create({})
25333 ## Moving Between States
25335 A StateManager can have any number of `Ember.State` objects as properties
25336 and can have a single one of these states as its current state.
25338 Calling `transitionTo` transitions between states:
25341 robotManager = Ember.StateManager.create({
25342 initialState: 'poweredDown',
25343 poweredDown: Ember.State.create({}),
25344 poweredUp: Ember.State.create({})
25347 robotManager.get('currentState.name') // 'poweredDown'
25348 robotManager.transitionTo('poweredUp')
25349 robotManager.get('currentState.name') // 'poweredUp'
25352 Before transitioning into a new state the existing `currentState` will have
25353 its `exit` method called with the StateManager instance as its first argument
25354 and an object representing the transition as its second argument.
25356 After transitioning into a new state the new `currentState` will have its
25357 `enter` method called with the StateManager instance as its first argument
25358 and an object representing the transition as its second argument.
25361 robotManager = Ember.StateManager.create({
25362 initialState: 'poweredDown',
25363 poweredDown: Ember.State.create({
25364 exit: function(stateManager){
25365 console.log("exiting the poweredDown state")
25368 poweredUp: Ember.State.create({
25369 enter: function(stateManager){
25370 console.log("entering the poweredUp state. Destroy all humans.")
25375 robotManager.get('currentState.name') // 'poweredDown'
25376 robotManager.transitionTo('poweredUp')
25379 // 'exiting the poweredDown state'
25380 // 'entering the poweredUp state. Destroy all humans.'
25383 Once a StateManager is already in a state, subsequent attempts to enter that
25384 state will not trigger enter or exit method calls. Attempts to transition
25385 into a state that the manager does not have will result in no changes in the
25386 StateManager's current state:
25389 robotManager = Ember.StateManager.create({
25390 initialState: 'poweredDown',
25391 poweredDown: Ember.State.create({
25392 exit: function(stateManager){
25393 console.log("exiting the poweredDown state")
25396 poweredUp: Ember.State.create({
25397 enter: function(stateManager){
25398 console.log("entering the poweredUp state. Destroy all humans.")
25403 robotManager.get('currentState.name') // 'poweredDown'
25404 robotManager.transitionTo('poweredUp')
25406 // 'exiting the poweredDown state'
25407 // 'entering the poweredUp state. Destroy all humans.'
25408 robotManager.transitionTo('poweredUp') // no logging, no state change
25410 robotManager.transitionTo('someUnknownState') // silently fails
25411 robotManager.get('currentState.name') // 'poweredUp'
25414 Each state property may itself contain properties that are instances of
25415 `Ember.State`. The StateManager can transition to specific sub-states in a
25416 series of transitionTo method calls or via a single transitionTo with the
25417 full path to the specific state. The StateManager will also keep track of the
25418 full path to its currentState
25421 robotManager = Ember.StateManager.create({
25422 initialState: 'poweredDown',
25423 poweredDown: Ember.State.create({
25424 charging: Ember.State.create(),
25425 charged: Ember.State.create()
25427 poweredUp: Ember.State.create({
25428 mobile: Ember.State.create(),
25429 stationary: Ember.State.create()
25433 robotManager.get('currentState.name') // 'poweredDown'
25435 robotManager.transitionTo('poweredUp')
25436 robotManager.get('currentState.name') // 'poweredUp'
25438 robotManager.transitionTo('mobile')
25439 robotManager.get('currentState.name') // 'mobile'
25441 // transition via a state path
25442 robotManager.transitionTo('poweredDown.charging')
25443 robotManager.get('currentState.name') // 'charging'
25445 robotManager.get('currentState.path') // 'poweredDown.charging'
25448 Enter transition methods will be called for each state and nested child state
25449 in their hierarchical order. Exit methods will be called for each state and
25450 its nested states in reverse hierarchical order.
25452 Exit transitions for a parent state are not called when entering into one of
25453 its child states, only when transitioning to a new section of possible states
25457 robotManager = Ember.StateManager.create({
25458 initialState: 'poweredDown',
25459 poweredDown: Ember.State.create({
25460 enter: function(){},
25462 console.log("exited poweredDown state")
25464 charging: Ember.State.create({
25465 enter: function(){},
25468 charged: Ember.State.create({
25470 console.log("entered charged state")
25473 console.log("exited charged state")
25477 poweredUp: Ember.State.create({
25479 console.log("entered poweredUp state")
25481 exit: function(){},
25482 mobile: Ember.State.create({
25484 console.log("entered mobile state")
25488 stationary: Ember.State.create({
25489 enter: function(){},
25496 robotManager.get('currentState.path') // 'poweredDown'
25497 robotManager.transitionTo('charged')
25498 // logs 'entered charged state'
25499 // but does *not* log 'exited poweredDown state'
25500 robotManager.get('currentState.name') // 'charged
25502 robotManager.transitionTo('poweredUp.mobile')
25504 // 'exited charged state'
25505 // 'exited poweredDown state'
25506 // 'entered poweredUp state'
25507 // 'entered mobile state'
25510 During development you can set a StateManager's `enableLogging` property to
25511 `true` to receive console messages of state transitions.
25514 robotManager = Ember.StateManager.create({
25515 enableLogging: true
25519 ## Managing currentState with Actions
25521 To control which transitions are possible for a given state, and
25522 appropriately handle external events, the StateManager can receive and
25523 route action messages to its states via the `send` method. Calling to
25524 `send` with an action name will begin searching for a method with the same
25525 name starting at the current state and moving up through the parent states
25526 in a state hierarchy until an appropriate method is found or the StateManager
25527 instance itself is reached.
25529 If an appropriately named method is found it will be called with the state
25530 manager as the first argument and an optional `context` object as the second
25534 managerA = Ember.StateManager.create({
25535 initialState: 'stateOne.substateOne.subsubstateOne',
25536 stateOne: Ember.State.create({
25537 substateOne: Ember.State.create({
25538 anAction: function(manager, context){
25539 console.log("an action was called")
25541 subsubstateOne: Ember.State.create({})
25546 managerA.get('currentState.name') // 'subsubstateOne'
25547 managerA.send('anAction')
25548 // 'stateOne.substateOne.subsubstateOne' has no anAction method
25549 // so the 'anAction' method of 'stateOne.substateOne' is called
25550 // and logs "an action was called"
25551 // with managerA as the first argument
25552 // and no second argument
25555 managerA.send('anAction', someObject)
25556 // the 'anAction' method of 'stateOne.substateOne' is called again
25557 // with managerA as the first argument and
25558 // someObject as the second argument.
25561 If the StateManager attempts to send an action but does not find an appropriately named
25562 method in the current state or while moving upwards through the state hierarchy, it will
25563 repeat the process looking for a `unhandledEvent` method. If an `unhandledEvent` method is
25564 found, it will be called with the original event name as the second argument. If an
25565 `unhandledEvent` method is not found, the StateManager will throw a new Ember.Error.
25568 managerB = Ember.StateManager.create({
25569 initialState: 'stateOne.substateOne.subsubstateOne',
25570 stateOne: Ember.State.create({
25571 substateOne: Ember.State.create({
25572 subsubstateOne: Ember.State.create({}),
25573 unhandledEvent: function(manager, eventName, context) {
25574 console.log("got an unhandledEvent with name " + eventName);
25580 managerB.get('currentState.name') // 'subsubstateOne'
25581 managerB.send('anAction')
25582 // neither `stateOne.substateOne.subsubstateOne` nor any of it's
25583 // parent states have a handler for `anAction`. `subsubstateOne`
25584 // also does not have a `unhandledEvent` method, but its parent
25585 // state, `substateOne`, does, and it gets fired. It will log
25586 // "got an unhandledEvent with name anAction"
25589 Action detection only moves upwards through the state hierarchy from the current state.
25590 It does not search in other portions of the hierarchy.
25593 managerC = Ember.StateManager.create({
25594 initialState: 'stateOne.substateOne.subsubstateOne',
25595 stateOne: Ember.State.create({
25596 substateOne: Ember.State.create({
25597 subsubstateOne: Ember.State.create({})
25600 stateTwo: Ember.State.create({
25601 anAction: function(manager, context){
25602 // will not be called below because it is
25603 // not a parent of the current state
25608 managerC.get('currentState.name') // 'subsubstateOne'
25609 managerC.send('anAction')
25610 // Error: <Ember.StateManager:ember132> could not
25611 // respond to event anAction in state stateOne.substateOne.subsubstateOne.
25614 Inside of an action method the given state should delegate `transitionTo` calls on its
25618 robotManager = Ember.StateManager.create({
25619 initialState: 'poweredDown.charging',
25620 poweredDown: Ember.State.create({
25621 charging: Ember.State.create({
25622 chargeComplete: function(manager, context){
25623 manager.transitionTo('charged')
25626 charged: Ember.State.create({
25627 boot: function(manager, context){
25628 manager.transitionTo('poweredUp')
25632 poweredUp: Ember.State.create({
25633 beginExtermination: function(manager, context){
25634 manager.transitionTo('rampaging')
25636 rampaging: Ember.State.create()
25640 robotManager.get('currentState.name') // 'charging'
25641 robotManager.send('boot') // throws error, no boot action
25642 // in current hierarchy
25643 robotManager.get('currentState.name') // remains 'charging'
25645 robotManager.send('beginExtermination') // throws error, no beginExtermination
25646 // action in current hierarchy
25647 robotManager.get('currentState.name') // remains 'charging'
25649 robotManager.send('chargeComplete')
25650 robotManager.get('currentState.name') // 'charged'
25652 robotManager.send('boot')
25653 robotManager.get('currentState.name') // 'poweredUp'
25655 robotManager.send('beginExtermination', allHumans)
25656 robotManager.get('currentState.name') // 'rampaging'
25659 Transition actions can also be created using the `transitionTo` method of the `Ember.State` class. The
25660 following example StateManagers are equivalent:
25663 aManager = Ember.StateManager.create({
25664 stateOne: Ember.State.create({
25665 changeToStateTwo: Ember.State.transitionTo('stateTwo')
25667 stateTwo: Ember.State.create({})
25670 bManager = Ember.StateManager.create({
25671 stateOne: Ember.State.create({
25672 changeToStateTwo: function(manager, context){
25673 manager.transitionTo('stateTwo', context)
25676 stateTwo: Ember.State.create({})
25680 @class StateManager
25682 @extends Ember.State
25684 Ember.StateManager = Ember.State.extend({
25688 When creating a new statemanager, look for a default state to transition
25689 into. This state can either be named `start`, or can be specified using the
25690 `initialState` property.
25697 set(this, 'stateMeta', Ember.Map.create());
25699 var initialState = get(this, 'initialState');
25701 if (!initialState && get(this, 'states.start')) {
25702 initialState = 'start';
25705 if (initialState) {
25706 this.transitionTo(initialState);
25707 Ember.assert('Failed to transition to initial state "' + initialState + '"', !!get(this, 'currentState'));
25711 stateMetaFor: function(state) {
25712 var meta = get(this, 'stateMeta'),
25713 stateMeta = meta.get(state);
25717 meta.set(state, stateMeta);
25723 setStateMeta: function(state, key, value) {
25724 return set(this.stateMetaFor(state), key, value);
25727 getStateMeta: function(state, key) {
25728 return get(this.stateMetaFor(state), key);
25732 The current state from among the manager's possible states. This property should
25733 not be set directly. Use `transitionTo` to move between states by name.
25735 @property currentState
25738 currentState: null,
25741 The path of the current state. Returns a string representation of the current
25744 @property currentPath
25747 currentPath: Ember.computed('currentState', function() {
25748 return get(this, 'currentState.path');
25752 The name of transitionEvent that this stateManager will dispatch
25754 @property transitionEvent
25758 transitionEvent: 'setup',
25761 If set to true, `errorOnUnhandledEvents` will cause an exception to be
25762 raised if you attempt to send an event to a state manager that is not
25763 handled by the current state or any of its parent states.
25765 @property errorOnUnhandledEvents
25769 errorOnUnhandledEvent: true,
25771 send: function(event) {
25772 var contexts = [].slice.call(arguments, 1);
25773 Ember.assert('Cannot send event "' + event + '" while currentState is ' + get(this, 'currentState'), get(this, 'currentState'));
25774 return sendEvent.call(this, event, contexts, false);
25776 unhandledEvent: function(manager, event) {
25777 if (get(this, 'errorOnUnhandledEvent')) {
25778 throw new Ember.Error(this.toString() + " could not respond to event " + event + " in state " + get(this, 'currentState.path') + ".");
25783 Finds a state by its state path.
25788 manager = Ember.StateManager.create({
25789 root: Ember.State.create({
25790 dashboard: Ember.State.create()
25794 manager.getStateByPath(manager, "root.dashboard")
25796 // returns the dashboard state
25799 @method getStateByPath
25800 @param {Ember.State} root the state to start searching from
25801 @param {String} path the state path to follow
25802 @return {Ember.State} the state at the end of the path
25804 getStateByPath: function(root, path) {
25805 var parts = path.split('.'),
25808 for (var i=0, len=parts.length; i<len; i++) {
25809 state = get(get(state, 'states'), parts[i]);
25810 if (!state) { break; }
25816 findStateByPath: function(state, path) {
25819 while (!possible && state) {
25820 possible = this.getStateByPath(state, path);
25821 state = get(state, 'parentState');
25828 A state stores its child states in its `states` hash.
25829 This code takes a path like `posts.show` and looks
25830 up `root.states.posts.states.show`.
25832 It returns a list of all of the states from the
25833 root, which is the list of states to call `enter`
25836 @method getStatesInPath
25840 getStatesInPath: function(root, path) {
25841 if (!path || path === "") { return undefined; }
25842 var parts = path.split('.'),
25847 for (var i=0, len=parts.length; i<len; i++) {
25848 states = get(root, 'states');
25849 if (!states) { return undefined; }
25850 state = get(states, parts[i]);
25851 if (state) { root = state; result.push(state); }
25852 else { return undefined; }
25858 goToState: function() {
25859 // not deprecating this yet so people don't constantly need to
25860 // make trivial changes for little reason.
25861 return this.transitionTo.apply(this, arguments);
25864 transitionTo: function(path, context) {
25865 // XXX When is transitionTo called with no path
25866 if (Ember.isEmpty(path)) { return; }
25868 // The ES6 signature of this function is `path, ...contexts`
25869 var contexts = context ? Array.prototype.slice.call(arguments, 1) : [],
25870 currentState = get(this, 'currentState') || this;
25872 // First, get the enter, exit and resolve states for the current state
25873 // and specified path. If possible, use an existing cache.
25874 var hash = this.contextFreeTransition(currentState, path);
25876 // Next, process the raw state information for the contexts passed in.
25877 var transition = new Transition(hash).normalize(this, contexts);
25879 this.enterState(transition);
25880 this.triggerSetupContext(transition);
25883 contextFreeTransition: function(currentState, path) {
25884 var cache = currentState.pathsCache[path];
25885 if (cache) { return cache; }
25887 var enterStates = this.getStatesInPath(currentState, path),
25889 resolveState = currentState;
25891 // Walk up the states. For each state, check whether a state matching
25892 // the `path` is nested underneath. This will find the closest
25893 // parent state containing `path`.
25895 // This allows the user to pass in a relative path. For example, for
25896 // the following state hierarchy:
25900 // | | |- show (* current)
25904 // If the current state is `<root.posts.show>`, an attempt to
25905 // transition to `comments.show` will match `<root.comments.show>`.
25907 // First, this code will look for root.posts.show.comments.show.
25908 // Next, it will look for root.posts.comments.show. Finally,
25909 // it will look for `root.comments.show`, and find the state.
25911 // After this process, the following variables will exist:
25913 // * resolveState: a common parent state between the current
25914 // and target state. In the above example, `<root>` is the
25916 // * enterStates: a list of all of the states represented
25917 // by the path from the `resolveState`. For example, for
25918 // the path `root.comments.show`, `enterStates` would have
25919 // `[<root.comments>, <root.comments.show>]`
25920 // * exitStates: a list of all of the states from the
25921 // `resolveState` to the `currentState`. In the above
25922 // example, `exitStates` would have
25923 // `[<root.posts>`, `<root.posts.show>]`.
25924 while (resolveState && !enterStates) {
25925 exitStates.unshift(resolveState);
25927 resolveState = get(resolveState, 'parentState');
25928 if (!resolveState) {
25929 enterStates = this.getStatesInPath(this, path);
25930 if (!enterStates) {
25931 Ember.assert('Could not find state for path: "'+path+'"');
25935 enterStates = this.getStatesInPath(resolveState, path);
25938 // If the path contains some states that are parents of both the
25939 // current state and the target state, remove them.
25941 // For example, in the following hierarchy:
25945 // | | |- index (* current)
25948 // If the `path` is `root.post.show`, the three variables will
25951 // * resolveState: `<state manager>`
25952 // * enterStates: `[<root>, <root.post>, <root.post.show>]`
25953 // * exitStates: `[<root>, <root.post>, <root.post.index>]`
25955 // The goal of this code is to remove the common states, so we
25958 // * resolveState: `<root.post>`
25959 // * enterStates: `[<root.post.show>]`
25960 // * exitStates: `[<root.post.index>]`
25962 // This avoid unnecessary calls to the enter and exit transitions.
25963 while (enterStates.length > 0 && enterStates[0] === exitStates[0]) {
25964 resolveState = enterStates.shift();
25965 exitStates.shift();
25968 // Cache the enterStates, exitStates, and resolveState for the
25969 // current state and the `path`.
25970 var transitions = currentState.pathsCache[path] = {
25971 exitStates: exitStates,
25972 enterStates: enterStates,
25973 resolveState: resolveState
25976 return transitions;
25979 triggerSetupContext: function(transitions) {
25980 var contexts = transitions.contexts,
25981 offset = transitions.enterStates.length - contexts.length,
25982 enterStates = transitions.enterStates,
25983 transitionEvent = get(this, 'transitionEvent');
25985 Ember.assert("More contexts provided than states", offset >= 0);
25987 arrayForEach.call(enterStates, function(state, idx) {
25988 state.trigger(transitionEvent, this, contexts[idx-offset]);
25992 getState: function(name) {
25993 var state = get(this, name),
25994 parentState = get(this, 'parentState');
25998 } else if (parentState) {
25999 return parentState.getState(name);
26003 enterState: function(transition) {
26004 var log = this.enableLogging;
26006 var exitStates = transition.exitStates.slice(0).reverse();
26007 arrayForEach.call(exitStates, function(state) {
26008 state.trigger('exit', this);
26011 arrayForEach.call(transition.enterStates, function(state) {
26012 if (log) { Ember.Logger.log("STATEMANAGER: Entering " + get(state, 'path')); }
26013 state.trigger('enter', this);
26016 set(this, 'currentState', transition.finalState);
26029 @submodule ember-states
26030 @requires ember-runtime
26037 // Version: v1.0.0-pre.3-19-g015138e
26038 // Last commit: 015138e (2013-01-17 23:02:17 -0800)