1 // Version: v1.0.pre-160-g7d62790
2 // Last commit: 7d62790 (2012-09-26 15:59:36 -0700)
12 @submodule ember-debug
19 if ('undefined' === typeof Ember) {
22 if ('undefined' !== typeof window) {
23 window.Em = window.Ember = Em = Ember;
27 Ember.ENV = 'undefined' === typeof ENV ? {} : ENV;
29 if (!('MANDATORY_SETTER' in Ember.ENV)) {
30 Ember.ENV.MANDATORY_SETTER = true; // default to true for debug dist
34 Define an assertion that will throw an exception if the condition is not
35 met. Ember build tools will remove any calls to Ember.assert() when
36 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')
44 @param {String} desc A description of the assertion. This will become
45 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);
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);
143 // Version: v1.0.pre-160-g7d62790
144 // Last commit: 7d62790 (2012-09-26 15:59:36 -0700)
148 /*globals Em:true ENV */
152 @submodule ember-metal
156 All Ember methods and functions are defined inside of this namespace.
157 You generally should not add new properties to this namespace as it may be
158 overwritten by future versions of Ember.
160 You can also use the shorthand "Em" instead of "Ember".
162 Ember-Runtime is a framework that provides core functions for
163 Ember including cross-platform functions, support for property
164 observing and objects. Its focus is on small size and performance. You can
165 use this in place of or along-side other cross-platform libraries such as
168 The core Runtime framework is based on the jQuery API with a number of
169 performance optimizations.
176 if ('undefined' === typeof Ember) {
177 // Create core object. Make it act like an instance of Ember.Namespace so that
178 // objects assigned to it are given a sane string representation.
182 // aliases needed to keep minifiers from removing the global context
183 if ('undefined' !== typeof window) {
184 window.Em = window.Ember = Em = Ember;
187 // Make sure these are set whether Ember was already defined or not
189 Ember.isNamespace = true;
191 Ember.toString = function() { return "Ember"; };
200 Ember.VERSION = '1.0.pre';
203 Standard environmental variables. You can define these in a global `ENV`
204 variable before loading Ember to control various configuration
210 Ember.ENV = Ember.ENV || ('undefined' === typeof ENV ? {} : ENV);
212 Ember.config = Ember.config || {};
214 // ..........................................................
219 Determines whether Ember should enhances some built-in object
220 prototypes to provide a more friendly API. If enabled, a few methods
221 will be added to Function, String, and Array. Object.prototype will not be
222 enhanced, which is the one that causes most troubles for people.
224 In general we recommend leaving this option set to true since it rarely
225 conflicts with other code. If you need to turn it off however, you can
226 define an ENV.EXTEND_PROTOTYPES config to disable it.
228 @property EXTEND_PROTOTYPES
232 Ember.EXTEND_PROTOTYPES = (Ember.ENV.EXTEND_PROTOTYPES !== false);
235 Determines whether Ember logs a full stack trace during deprecation warnings
237 @property LOG_STACKTRACE_ON_DEPRECATION
241 Ember.LOG_STACKTRACE_ON_DEPRECATION = (Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION !== false);
244 Determines whether Ember should add ECMAScript 5 shims to older browsers.
248 @default Ember.EXTEND_PROTOTYPES
250 Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES;
254 Determines whether computed properties are cacheable by default.
255 This option will be removed for the 1.1 release.
257 When caching is enabled by default, you can use `volatile()` to disable
258 caching on individual computed properties.
260 @property CP_DEFAULT_CACHEABLE
264 Ember.CP_DEFAULT_CACHEABLE = (Ember.ENV.CP_DEFAULT_CACHEABLE !== false);
267 Determines whether views render their templates using themselves
268 as the context, or whether it is inherited from the parent. This option
269 will be removed in the 1.1 release.
271 If you need to update your application to use the new context rules, simply
272 prefix property access with `view.`:
277 {{#each App.photosController}}
278 Photo Title: {{title}}
279 {{#view App.InfoView contentBinding="this"}}
281 {{content.cameraType}}
282 {{otherViewProperty}}
290 {{#each App.photosController}}
291 Photo Title: {{title}}
292 {{#view App.InfoView}}
295 {{view.otherViewProperty}}
300 @property VIEW_PRESERVES_CONTEXT
304 Ember.VIEW_PRESERVES_CONTEXT = (Ember.ENV.VIEW_PRESERVES_CONTEXT !== false);
307 Empty function. Useful for some operations.
313 Ember.K = function() { return this; };
316 // Stub out the methods defined by the ember-debug package in case it's not loaded
318 if ('undefined' === typeof Ember.assert) { Ember.assert = Ember.K; }
319 if ('undefined' === typeof Ember.warn) { Ember.warn = Ember.K; }
320 if ('undefined' === typeof Ember.deprecate) { Ember.deprecate = Ember.K; }
321 if ('undefined' === typeof Ember.deprecateFunc) {
322 Ember.deprecateFunc = function(_, func) { return func; };
325 // These are deprecated but still supported
327 if ('undefined' === typeof ember_assert) { window.ember_assert = Ember.K; }
328 if ('undefined' === typeof ember_warn) { window.ember_warn = Ember.K; }
329 if ('undefined' === typeof ember_deprecate) { window.ember_deprecate = Ember.K; }
330 if ('undefined' === typeof ember_deprecateFunc) {
331 window.ember_deprecateFunc = function(_, func) { return func; };
335 // ..........................................................
340 Inside Ember-Metal, simply uses the window.console object.
341 Override this to provide more robust logging functionality.
346 Ember.Logger = window.console || { log: Ember.K, warn: Ember.K, error: Ember.K, info: Ember.K, debug: Ember.K };
349 // ..........................................................
354 A function may be assigned to `Ember.onerror` to be called when Ember internals encounter an error.
355 This is useful for specialized error handling and reporting code.
359 @param {Exception} error the error object
361 Ember.onerror = null;
366 Wrap code block in a try/catch if {{#crossLink "Ember/onerror"}}{{/crossLink}} is set.
370 @param {Function} func
373 Ember.handleErrors = function(func, context) {
374 // Unfortunately in some browsers we lose the backtrace if we rethrow the existing error,
375 // so in the event that we don't have an `onerror` handler we don't wrap in a try/catch
376 if ('function' === typeof Ember.onerror) {
378 return func.apply(context || this);
380 Ember.onerror(error);
383 return func.apply(context || this);
392 /*jshint newcap:false*/
398 // NOTE: There is a bug in jshint that doesn't recognize `Object()` without `new`
399 // as being ok unless both `newcap:false` and not `use strict`.
400 // https://github.com/jshint/jshint/issues/392
402 // Testing this is not ideal, but we want to use native functions
403 // if available, but not to use versions created by libraries like Prototype
404 var isNativeFunc = function(func) {
405 // This should probably work in all browsers likely to have ES5 array methods
406 return func && Function.prototype.toString.call(func).indexOf('[native code]') > -1;
409 // From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map
410 var arrayMap = isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun /*, thisp */) {
413 if (this === void 0 || this === null) {
414 throw new TypeError();
417 var t = Object(this);
418 var len = t.length >>> 0;
419 if (typeof fun !== "function") {
420 throw new TypeError();
423 var res = new Array(len);
424 var thisp = arguments[1];
425 for (var i = 0; i < len; i++) {
427 res[i] = fun.call(thisp, t[i], i, t);
434 // From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
435 var arrayForEach = isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun /*, thisp */) {
438 if (this === void 0 || this === null) {
439 throw new TypeError();
442 var t = Object(this);
443 var len = t.length >>> 0;
444 if (typeof fun !== "function") {
445 throw new TypeError();
448 var thisp = arguments[1];
449 for (var i = 0; i < len; i++) {
451 fun.call(thisp, t[i], i, t);
456 var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj, fromIndex) {
457 if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; }
458 else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); }
459 for (var i = fromIndex, j = this.length; i < j; i++) {
460 if (this[i] === obj) { return i; }
465 Ember.ArrayPolyfills = {
467 forEach: arrayForEach,
468 indexOf: arrayIndexOf
471 var utils = Ember.EnumerableUtils = {
472 map: function(obj, callback, thisArg) {
473 return obj.map ? obj.map.call(obj, callback, thisArg) : arrayMap.call(obj, callback, thisArg);
476 forEach: function(obj, callback, thisArg) {
477 return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : arrayForEach.call(obj, callback, thisArg);
480 indexOf: function(obj, element, index) {
481 return obj.indexOf ? obj.indexOf.call(obj, element, index) : arrayIndexOf.call(obj, element, index);
484 indexesOf: function(obj, elements) {
485 return elements === undefined ? [] : utils.map(elements, function(item) {
486 return utils.indexOf(obj, item);
490 removeObject: function(array, item) {
491 var index = utils.indexOf(array, item);
492 if (index !== -1) { array.splice(index, 1); }
497 if (Ember.SHIM_ES5) {
498 if (!Array.prototype.map) {
499 Array.prototype.map = arrayMap;
502 if (!Array.prototype.forEach) {
503 Array.prototype.forEach = arrayForEach;
506 if (!Array.prototype.indexOf) {
507 Array.prototype.indexOf = arrayIndexOf;
522 Platform specific methods and feature detectors needed by the framework.
528 var platform = Ember.platform = {};
532 Identical to Object.create(). Implements if not available natively.
536 Ember.create = Object.create;
539 var K = function() {};
541 Ember.create = function(obj, props) {
546 for (var prop in props) {
547 K.prototype[prop] = props[prop].value;
556 Ember.create.isSimulated = true;
559 var defineProperty = Object.defineProperty;
560 var canRedefineProperties, canDefinePropertyOnDOM;
562 // Catch IE8 where Object.defineProperty exists but only works on DOM elements
563 if (defineProperty) {
565 defineProperty({}, 'a',{get:function(){}});
567 defineProperty = null;
571 if (defineProperty) {
572 // Detects a bug in Android <3.2 where you cannot redefine a property using
573 // Object.defineProperty once accessors have already been set.
574 canRedefineProperties = (function() {
577 defineProperty(obj, 'a', {
584 defineProperty(obj, 'a', {
591 return obj.a === true;
594 // This is for Safari 5.0, which supports Object.defineProperty, but not
596 canDefinePropertyOnDOM = (function(){
598 defineProperty(document.createElement('div'), 'definePropertyOnDOM', {});
605 if (!canRedefineProperties) {
606 defineProperty = null;
607 } else if (!canDefinePropertyOnDOM) {
608 defineProperty = function(obj, keyName, desc){
611 if (typeof Node === "object") {
612 isNode = obj instanceof Node;
614 isNode = typeof obj === "object" && typeof obj.nodeType === "number" && typeof obj.nodeName === "string";
618 // TODO: Should we have a warning here?
619 return (obj[keyName] = desc.value);
621 return Object.defineProperty(obj, keyName, desc);
633 Identical to Object.defineProperty(). Implements as much functionality
634 as possible if not available natively.
636 @method defineProperty
637 @param {Object} obj The object to modify
638 @param {String} keyName property name to modify
639 @param {Object} desc descriptor hash
642 platform.defineProperty = defineProperty;
645 Set to true if the platform supports native getters and setters.
647 @property hasPropertyAccessors
650 platform.hasPropertyAccessors = true;
652 if (!platform.defineProperty) {
653 platform.hasPropertyAccessors = false;
655 platform.defineProperty = function(obj, keyName, desc) {
656 if (!desc.get) { obj[keyName] = desc.value; }
659 platform.defineProperty.isSimulated = true;
662 if (Ember.ENV.MANDATORY_SETTER && !platform.hasPropertyAccessors) {
663 Ember.ENV.MANDATORY_SETTER = false;
675 var o_defineProperty = Ember.platform.defineProperty,
676 o_create = Ember.create,
677 // Used for guid generation...
678 GUID_KEY = '__ember'+ (+ new Date()),
683 var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
688 A unique key used to assign guids and other private metadata to objects.
689 If you inspect an object in your browser debugger you will often see these.
690 They can be safely ignored.
692 On browsers that support it, these properties are added with enumeration
693 disabled so they won't show up when you iterate over your properties.
700 Ember.GUID_KEY = GUID_KEY;
712 Generates a new guid, optionally saving the guid to the object that you
713 pass in. You will rarely need to use this method. Instead you should
714 call Ember.guidFor(obj), which return an existing guid if available.
718 @param {Object} [obj] Object the guid will be used for. If passed in, the guid will
719 be saved on the object and reused whenever you pass the same object
722 If no object is passed, just generate a new guid.
724 @param {String} [prefix] Prefix to place in front of the guid. Useful when you want to
725 separate the guid into separate namespaces.
727 @return {String} the guid
729 Ember.generateGuid = function generateGuid(obj, prefix) {
730 if (!prefix) prefix = 'ember';
731 var ret = (prefix + (uuid++));
733 GUID_DESC.value = ret;
734 o_defineProperty(obj, GUID_KEY, GUID_DESC);
742 Returns a unique id for the object. If the object does not yet have
743 a guid, one will be assigned to it. You can call this on any object,
744 Ember.Object-based or not, but be aware that it will add a _guid property.
746 You can also use this method on DOM Element objects.
750 @param obj {Object} any object, string, number, Element, or primitive
751 @return {String} the unique guid for this instance.
753 Ember.guidFor = function guidFor(obj) {
755 // special cases where we don't want to add a key to object
756 if (obj === undefined) return "(undefined)";
757 if (obj === null) return "(null)";
760 var type = typeof obj;
762 // Don't allow prototype changes to String etc. to change the guidFor
765 ret = numberCache[obj];
766 if (!ret) ret = numberCache[obj] = 'nu'+obj;
770 ret = stringCache[obj];
771 if (!ret) ret = stringCache[obj] = 'st'+(uuid++);
775 return obj ? '(true)' : '(false)';
778 if (obj[GUID_KEY]) return obj[GUID_KEY];
779 if (obj === Object) return '(Object)';
780 if (obj === Array) return '(Array)';
781 ret = 'ember'+(uuid++);
782 GUID_DESC.value = ret;
783 o_defineProperty(obj, GUID_KEY, GUID_DESC);
788 // ..........................................................
799 var META_KEY = Ember.GUID_KEY+'_meta';
802 The key used to store meta information on object for property observing.
810 Ember.META_KEY = META_KEY;
812 // Placeholder for non-writable metas.
818 if (MANDATORY_SETTER) { EMPTY_META.values = {}; }
820 Ember.EMPTY_META = EMPTY_META;
822 if (Object.freeze) Object.freeze(EMPTY_META);
824 var isDefinePropertySimulated = Ember.platform.defineProperty.isSimulated;
833 if (isDefinePropertySimulated) {
834 // on platforms that don't support enumerable false
835 // make meta fail jQuery.isPlainObject() to hide from
836 // jQuery.extend() by having a property that fails
837 // hasOwnProperty check.
838 Meta.prototype.__preventPlainObject__ = true;
842 Retrieves the meta hash for an object. If 'writable' is true ensures the
843 hash is writable for this object as well.
845 The meta object contains information about computed property descriptors as
846 well as any watched properties and other information. You generally will
847 not access this information directly but instead work with higher level
848 methods that manipulate this hash indirectly.
854 @param {Object} obj The object to retrieve meta for
856 @param {Boolean} [writable=true] Pass false if you do not intend to modify
857 the meta hash, allowing the method to avoid making an unnecessary copy.
861 Ember.meta = function meta(obj, writable) {
863 var ret = obj[META_KEY];
864 if (writable===false) return ret || EMPTY_META;
867 if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC);
871 if (MANDATORY_SETTER) { ret.values = {}; }
875 // make sure we don't accidentally try to create constructor like desc
876 ret.descs.constructor = null;
878 } else if (ret.source !== obj) {
879 if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC);
882 ret.descs = o_create(ret.descs);
883 ret.watching = o_create(ret.watching);
887 if (MANDATORY_SETTER) { ret.values = o_create(ret.values); }
894 Ember.getMeta = function getMeta(obj, property) {
895 var meta = Ember.meta(obj, false);
896 return meta[property];
899 Ember.setMeta = function setMeta(obj, property, value) {
900 var meta = Ember.meta(obj, true);
901 meta[property] = value;
908 In order to store defaults for a class, a prototype may need to create
909 a default meta object, which will be inherited by any objects instantiated
910 from the class's constructor.
912 However, the properties of that meta object are only shallow-cloned,
913 so if a property is a hash (like the event system's `listeners` hash),
914 it will by default be shared across all instances of that class.
916 This method allows extensions to deeply clone a series of nested hashes or
917 other complex objects. For instance, the event system might pass
918 ['listeners', 'foo:change', 'ember157'] to `prepareMetaPath`, which will
919 walk down the keys provided.
921 For each key, if the key does not exist, it is created. If it already
922 exists and it was inherited from its constructor, the constructor's
925 You can also pass false for `writable`, which will simply return
926 undefined if `prepareMetaPath` discovers any part of the path that
931 @param {Object} obj The object whose meta we are examining
932 @param {Array} path An array of keys to walk down
933 @param {Boolean} writable whether or not to create a new meta
934 (or meta property) if one does not already exist or if it's
935 shared with its constructor
937 Ember.metaPath = function metaPath(obj, path, writable) {
938 var meta = Ember.meta(obj, writable), keyName, value;
940 for (var i=0, l=path.length; i<l; i++) {
942 value = meta[keyName];
945 if (!writable) { return undefined; }
946 value = meta[keyName] = { __ember_source__: obj };
947 } else if (value.__ember_source__ !== obj) {
948 if (!writable) { return undefined; }
949 value = meta[keyName] = o_create(value);
950 value.__ember_source__ = obj;
962 Wraps the passed function so that `this._super` will point to the superFunc
963 when the function is invoked. This is the primitive we use to implement
968 @param {Function} func The function to call
969 @param {Function} superFunc The super function.
970 @return {Function} wrapped function.
972 Ember.wrap = function(func, superFunc) {
976 var newFunc = function() {
977 var ret, sup = this._super;
978 this._super = superFunc || K;
979 ret = func.apply(this, arguments);
989 Returns true if the passed object is an array or Array-like.
991 Ember Array Protocol:
993 - the object has an objectAt property
994 - the object is a native Array
995 - the object is an Object, and has a length property
997 Unlike Ember.typeOf this method returns true even if the passed object is
998 not formally array but appears to be array-like (i.e. implements Ember.Array)
1000 Ember.isArray(); // false
1001 Ember.isArray([]); // true
1002 Ember.isArray( Ember.ArrayProxy.create({ content: [] }) ); // true
1006 @param {Object} obj The object to test
1009 Ember.isArray = function(obj) {
1010 if (!obj || obj.setInterval) { return false; }
1011 if (Array.isArray && Array.isArray(obj)) { return true; }
1012 if (Ember.Array && Ember.Array.detect(obj)) { return true; }
1013 if ((obj.length !== undefined) && 'object'===typeof obj) { return true; }
1018 Forces the passed object to be part of an array. If the object is already
1019 an array or array-like, returns the object. Otherwise adds the object to
1020 an array. If obj is null or undefined, returns an empty array.
1022 Ember.makeArray(); => []
1023 Ember.makeArray(null); => []
1024 Ember.makeArray(undefined); => []
1025 Ember.makeArray('lindsay'); => ['lindsay']
1026 Ember.makeArray([1,2,42]); => [1,2,42]
1028 var controller = Ember.ArrayProxy.create({ content: [] });
1029 Ember.makeArray(controller) === controller; => true
1033 @param {Object} obj the object
1036 Ember.makeArray = function(obj) {
1037 if (obj === null || obj === undefined) { return []; }
1038 return Ember.isArray(obj) ? obj : [obj];
1041 function canInvoke(obj, methodName) {
1042 return !!(obj && typeof obj[methodName] === 'function');
1046 Checks to see if the `methodName` exists on the `obj`.
1050 @param {Object} obj The object to check for the method
1051 @param {String} methodName The method name to check for
1053 Ember.canInvoke = canInvoke;
1056 Checks to see if the `methodName` exists on the `obj`,
1057 and if it does, invokes it with the arguments passed.
1061 @param {Object} obj The object to check for the method
1062 @param {String} methodName The method name to check for
1063 @param {Array} [args] The arguments to pass to the method
1064 @return {anything} the return value of the invoked method or undefined if it cannot be invoked
1066 Ember.tryInvoke = function(obj, methodName, args) {
1067 if (canInvoke(obj, methodName)) {
1068 return obj[methodName].apply(obj, args || []);
1082 JavaScript (before ES6) does not have a Map implementation. Objects,
1083 which are often used as dictionaries, may only have Strings as keys.
1085 Because Ember has a way to get a unique identifier for every object
1086 via `Ember.guidFor`, we can implement a performant Map with arbitrary
1087 keys. Because it is commonly used in low-level bookkeeping, Map is
1088 implemented as a pure JavaScript object for performance.
1090 This implementation follows the current iteration of the ES6 proposal
1091 for maps (http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets),
1092 with two exceptions. First, because we need our implementation to be
1093 pleasant on older browsers, we do not use the `delete` name (using
1094 `remove` instead). Second, as we do not have the luxury of in-VM
1095 iteration, we implement a forEach method for iteration.
1097 Map is mocked out to look like an Ember object, so you can do
1098 `Ember.Map.create()` for symmetry with other Ember classes.
1100 var guidFor = Ember.guidFor,
1101 indexOf = Ember.ArrayPolyfills.indexOf;
1103 var copy = function(obj) {
1106 for (var prop in obj) {
1107 if (obj.hasOwnProperty(prop)) { output[prop] = obj[prop]; }
1113 var copyMap = function(original, newObject) {
1114 var keys = original.keys.copy(),
1115 values = copy(original.values);
1117 newObject.keys = keys;
1118 newObject.values = values;
1124 This class is used internally by Ember.js and Ember Data.
1125 Please do not use it at this time. We plan to clean it up
1126 and add many tests soon.
1133 var OrderedSet = Ember.OrderedSet = function() {
1140 @return {Ember.OrderedSet}
1142 OrderedSet.create = function() {
1143 return new OrderedSet();
1147 OrderedSet.prototype = {
1152 this.presenceSet = {};
1160 add: function(obj) {
1161 var guid = guidFor(obj),
1162 presenceSet = this.presenceSet,
1165 if (guid in presenceSet) { return; }
1167 presenceSet[guid] = true;
1175 remove: function(obj) {
1176 var guid = guidFor(obj),
1177 presenceSet = this.presenceSet,
1180 delete presenceSet[guid];
1182 var index = indexOf.call(list, obj);
1184 list.splice(index, 1);
1192 isEmpty: function() {
1193 return this.list.length === 0;
1201 has: function(obj) {
1202 var guid = guidFor(obj),
1203 presenceSet = this.presenceSet;
1205 return guid in presenceSet;
1210 @param {Function} function
1213 forEach: function(fn, self) {
1214 // allow mutation during iteration
1215 var list = this.list.slice();
1217 for (var i = 0, j = list.length; i < j; i++) {
1218 fn.call(self, list[i]);
1226 toArray: function() {
1227 return this.list.slice();
1232 @return {Ember.OrderedSet}
1235 var set = new OrderedSet();
1237 set.presenceSet = copy(this.presenceSet);
1238 set.list = this.list.slice();
1245 A Map stores values indexed by keys. Unlike JavaScript's
1246 default Objects, the keys of a Map can be any JavaScript
1249 Internally, a Map has two data structures:
1251 `keys`: an OrderedSet of all of the existing keys
1252 `values`: a JavaScript Object indexed by the
1255 When a key/value pair is added for the first time, we
1256 add the key to the `keys` OrderedSet, and create or
1257 replace an entry in `values`. When an entry is deleted,
1258 we delete its entry in `keys` and `values`.
1265 var Map = Ember.Map = function() {
1266 this.keys = Ember.OrderedSet.create();
1274 Map.create = function() {
1280 Retrieve the value associated with a given key.
1283 @param {anything} key
1284 @return {anything} the value associated with the key, or undefined
1286 get: function(key) {
1287 var values = this.values,
1288 guid = guidFor(key);
1290 return values[guid];
1294 Adds a value to the map. If a value for the given key has already been
1295 provided, the new value will replace the old value.
1298 @param {anything} key
1299 @param {anything} value
1301 set: function(key, value) {
1302 var keys = this.keys,
1303 values = this.values,
1304 guid = guidFor(key);
1307 values[guid] = value;
1311 Removes a value from the map for an associated key.
1314 @param {anything} key
1315 @return {Boolean} true if an item was removed, false otherwise
1317 remove: function(key) {
1318 // don't use ES6 "delete" because it will be annoying
1319 // to use in browsers that are not ES6 friendly;
1320 var keys = this.keys,
1321 values = this.values,
1322 guid = guidFor(key),
1325 if (values.hasOwnProperty(guid)) {
1327 value = values[guid];
1328 delete values[guid];
1336 Check whether a key is present.
1339 @param {anything} key
1340 @return {Boolean} true if the item was present, false otherwise
1342 has: function(key) {
1343 var values = this.values,
1344 guid = guidFor(key);
1346 return values.hasOwnProperty(guid);
1350 Iterate over all the keys and values. Calls the function once
1351 for each key, passing in the key and value, in that order.
1353 The keys are guaranteed to be iterated over in insertion order.
1356 @param {Function} callback
1357 @param {anything} self if passed, the `this` value inside the
1358 callback. By default, `this` is the map.
1360 forEach: function(callback, self) {
1361 var keys = this.keys,
1362 values = this.values;
1364 keys.forEach(function(key) {
1365 var guid = guidFor(key);
1366 callback.call(self, key, values[guid]);
1375 return copyMap(this, new Map());
1380 @class MapWithDefault
1386 @param {anything} [options.defaultValue]
1388 var MapWithDefault = Ember.MapWithDefault = function(options) {
1390 this.defaultValue = options.defaultValue;
1397 @param {anything} [options.defaultValue]
1398 @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns Ember.MapWithDefault otherwise returns Ember.Map
1400 MapWithDefault.create = function(options) {
1402 return new MapWithDefault(options);
1408 MapWithDefault.prototype = Ember.create(Map.prototype);
1411 Retrieve the value associated with a given key.
1414 @param {anything} key
1415 @return {anything} the value associated with the key, or the default value
1417 MapWithDefault.prototype.get = function(key) {
1418 var hasValue = this.has(key);
1421 return Map.prototype.get.call(this, key);
1423 var defaultValue = this.defaultValue(key);
1424 this.set(key, defaultValue);
1425 return defaultValue;
1431 @return {Ember.MapWithDefault}
1433 MapWithDefault.prototype.copy = function() {
1434 return copyMap(this, new MapWithDefault({
1435 defaultValue: this.defaultValue
1448 var META_KEY = Ember.META_KEY, get, set;
1450 var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
1452 var IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
1453 var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/;
1454 var HAS_THIS = /^this[\.\*]/;
1455 var FIRST_KEY = /^([^\.\*]+)/;
1457 // ..........................................................
1460 // If we are on a platform that supports accessors we can get use those.
1461 // Otherwise simulate accessors by looking up the property directly on the
1465 Gets the value of a property on an object. If the property is computed,
1466 the function will be invoked. If the property is not defined but the
1467 object implements the unknownProperty() method then that will be invoked.
1469 If you plan to run on IE8 and older browsers then you should use this
1470 method anytime you want to retrieve a property on an object that you don't
1471 know for sure is private. (My convention only properties beginning with
1472 an underscore '_' are considered private.)
1474 On all newer browsers, you only need to use this method to retrieve
1475 properties if the property might not be defined on the object and you want
1476 to respect the unknownProperty() handler. Otherwise you can ignore this
1479 Note that if the obj itself is null, this method will simply return
1484 @param {Object} obj The object to retrieve from.
1485 @param {String} keyName The property key to retrieve
1486 @return {Object} the property value or null.
1488 get = function get(obj, keyName) {
1489 // Helpers that operate with 'this' within an #each
1490 if (keyName === '') {
1494 if (!keyName && 'string'===typeof obj) {
1499 if (!obj || keyName.indexOf('.') !== -1) {
1500 return getPath(obj, keyName);
1503 Ember.assert("You need to provide an object and key to `get`.", !!obj && keyName);
1505 var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret;
1507 return desc.get(obj, keyName);
1509 if (MANDATORY_SETTER && meta && meta.watching[keyName] > 0) {
1510 ret = meta.values[keyName];
1515 if (ret === undefined &&
1516 'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) {
1517 return obj.unknownProperty(keyName);
1525 Sets the value of a property on an object, respecting computed properties
1526 and notifying observers and other listeners of the change. If the
1527 property is not defined but the object implements the unknownProperty()
1528 method then that will be invoked as well.
1530 If you plan to run on IE8 and older browsers then you should use this
1531 method anytime you want to set a property on an object that you don't
1532 know for sure is private. (My convention only properties beginning with
1533 an underscore '_' are considered private.)
1535 On all newer browsers, you only need to use this method to set
1536 properties if the property might not be defined on the object and you want
1537 to respect the unknownProperty() handler. Otherwise you can ignore this
1542 @param {Object} obj The object to modify.
1543 @param {String} keyName The property key to set
1544 @param {Object} value The value to set
1545 @return {Object} the passed value.
1547 set = function set(obj, keyName, value, tolerant) {
1548 if (typeof obj === 'string') {
1549 Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
1555 if (!obj || keyName.indexOf('.') !== -1) {
1556 return setPath(obj, keyName, value, tolerant);
1559 Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
1560 Ember.assert('calling set on destroyed object', !obj.isDestroyed);
1562 var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
1563 isUnknown, currentValue;
1565 desc.set(obj, keyName, value);
1568 isUnknown = 'object' === typeof obj && !(keyName in obj);
1570 // setUnknownProperty is called if `obj` is an object,
1571 // the property does not already exist, and the
1572 // `setUnknownProperty` method exists on the object
1573 if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
1574 obj.setUnknownProperty(keyName, value);
1575 } else if (meta && meta.watching[keyName] > 0) {
1576 if (MANDATORY_SETTER) {
1577 currentValue = meta.values[keyName];
1579 currentValue = obj[keyName];
1581 // only trigger a change if the value has changed
1582 if (value !== currentValue) {
1583 Ember.propertyWillChange(obj, keyName);
1584 if (MANDATORY_SETTER) {
1585 if (currentValue === undefined && !(keyName in obj)) {
1586 Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
1588 meta.values[keyName] = value;
1591 obj[keyName] = value;
1593 Ember.propertyDidChange(obj, keyName);
1596 obj[keyName] = value;
1602 function firstKey(path) {
1603 return path.match(FIRST_KEY)[0];
1606 // assumes path is already normalized
1607 function normalizeTuple(target, path) {
1608 var hasThis = HAS_THIS.test(path),
1609 isGlobal = !hasThis && IS_GLOBAL_PATH.test(path),
1612 if (!target || isGlobal) target = window;
1613 if (hasThis) path = path.slice(5);
1615 if (target === window) {
1616 key = firstKey(path);
1617 target = get(target, key);
1618 path = path.slice(key.length+1);
1621 // must return some kind of path to be valid else other things will break.
1622 if (!path || path.length===0) throw new Error('Invalid Path');
1624 return [ target, path ];
1627 function getPath(root, path) {
1628 var hasThis, parts, tuple, idx, len;
1630 // If there is no root and path is a key name, return that
1631 // property from the global object.
1632 // E.g. get('Ember') -> Ember
1633 if (root === null && path.indexOf('.') === -1) { return get(window, path); }
1635 // detect complicated paths and normalize them
1636 hasThis = HAS_THIS.test(path);
1638 if (!root || hasThis) {
1639 tuple = normalizeTuple(root, path);
1645 parts = path.split(".");
1647 for (idx=0; root && idx<len; idx++) {
1648 root = get(root, parts[idx], true);
1649 if (root && root.isDestroyed) { return undefined; }
1654 function setPath(root, path, value, tolerant) {
1657 // get the last part of the path
1658 keyName = path.slice(path.lastIndexOf('.') + 1);
1660 // get the first part of the part
1661 path = path.slice(0, path.length-(keyName.length+1));
1663 // unless the path is this, look up the first part to
1665 if (path !== 'this') {
1666 root = getPath(root, path);
1669 if (!keyName || keyName.length === 0) {
1670 throw new Error('You passed an empty path');
1674 if (tolerant) { return; }
1675 else { throw new Error('Object in path '+path+' could not be found or was destroyed.'); }
1678 return set(root, keyName, value);
1684 Normalizes a target/path pair to reflect that actual target/path that should
1685 be observed, etc. This takes into account passing in global property
1686 paths (i.e. a path beginning with a captial letter not defined on the
1687 target) and * separators.
1689 @method normalizeTuple
1691 @param {Object} target The current target. May be null.
1692 @param {String} path A path on the target or a global property path.
1693 @return {Array} a temporary array with the normalized target/path pair.
1695 Ember.normalizeTuple = function(target, path) {
1696 return normalizeTuple(target, path);
1699 Ember.getWithDefault = function(root, key, defaultValue) {
1700 var value = get(root, key);
1702 if (value === undefined) { return defaultValue; }
1708 Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get);
1711 Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now supports paths', Ember.set);
1714 Error-tolerant form of Ember.set. Will not blow up if any part of the
1715 chain is undefined, null, or destroyed.
1717 This is primarily used when syncing bindings, which may try to update after
1718 an object has been destroyed.
1722 @param {Object} obj The object to modify.
1723 @param {String} keyName The property key to set
1724 @param {Object} value The value to set
1726 Ember.trySet = function(root, path, value) {
1727 return set(root, path, value, true);
1729 Ember.trySetPath = Ember.deprecateFunc('trySetPath has been renamed to trySet', Ember.trySet);
1732 Returns true if the provided path is global (e.g., "MyApp.fooController.bar")
1733 instead of local ("foo.bar.baz").
1735 @method isGlobalPath
1738 @param {String} path
1741 Ember.isGlobalPath = function(path) {
1742 return IS_GLOBAL.test(path);
1747 if (Ember.config.overrideAccessors) {
1748 Ember.config.overrideAccessors();
1762 var GUID_KEY = Ember.GUID_KEY,
1763 META_KEY = Ember.META_KEY,
1764 EMPTY_META = Ember.EMPTY_META,
1765 metaFor = Ember.meta,
1766 o_create = Ember.create,
1767 objectDefineProperty = Ember.platform.defineProperty;
1769 var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
1771 // ..........................................................
1776 Objects of this type can implement an interface to responds requests to
1777 get and set. The default implementation handles simple properties.
1779 You generally won't need to create or subclass this directly.
1786 var Descriptor = Ember.Descriptor = function() {};
1788 // ..........................................................
1789 // DEFINING PROPERTIES API
1795 NOTE: This is a low-level method used by other parts of the API. You almost
1796 never want to call this method directly. Instead you should use Ember.mixin()
1797 to define new properties.
1799 Defines a property on an object. This method works much like the ES5
1800 Object.defineProperty() method except that it can also accept computed
1801 properties and other special descriptors.
1803 Normally this method takes only three parameters. However if you pass an
1804 instance of Ember.Descriptor as the third param then you can pass an optional
1805 value as the fourth parameter. This is often more efficient than creating
1806 new descriptor hashes for each property.
1810 // ES5 compatible mode
1811 Ember.defineProperty(contact, 'firstName', {
1813 configurable: false,
1818 // define a simple property
1819 Ember.defineProperty(contact, 'lastName', undefined, 'Jolley');
1821 // define a computed property
1822 Ember.defineProperty(contact, 'fullName', Ember.computed(function() {
1823 return this.firstName+' '+this.lastName;
1824 }).property('firstName', 'lastName').cacheable());
1826 @method defineProperty
1828 @param {Object} obj the object to define this property on. This may be a prototype.
1829 @param {String} keyName the name of the property
1830 @param {Ember.Descriptor} [desc] an instance of Ember.Descriptor (typically a
1831 computed property) or an ES5 descriptor.
1832 You must provide this or `data` but not both.
1833 @param {anything} [data] something other than a descriptor, that will
1834 become the explicit value of this property.
1836 Ember.defineProperty = function(obj, keyName, desc, data, meta) {
1837 var descs, existingDesc, watching, value;
1839 if (!meta) meta = metaFor(obj);
1841 existingDesc = meta.descs[keyName];
1842 watching = meta.watching[keyName] > 0;
1844 if (existingDesc instanceof Ember.Descriptor) {
1845 existingDesc.teardown(obj, keyName);
1848 if (desc instanceof Ember.Descriptor) {
1851 descs[keyName] = desc;
1852 if (MANDATORY_SETTER && watching) {
1853 objectDefineProperty(obj, keyName, {
1857 value: undefined // make enumerable
1860 obj[keyName] = undefined; // make enumerable
1862 desc.setup(obj, keyName);
1864 descs[keyName] = undefined; // shadow descriptor in proto
1868 if (MANDATORY_SETTER && watching) {
1869 meta.values[keyName] = data;
1870 objectDefineProperty(obj, keyName, {
1874 Ember.assert('Must use Ember.set() to access this property', false);
1877 var meta = this[META_KEY];
1878 return meta && meta.values[keyName];
1882 obj[keyName] = data;
1887 // compatibility with ES5
1888 objectDefineProperty(obj, keyName, desc);
1892 // if key is being watched, override chains that
1893 // were initialized with the prototype
1894 if (watching) { Ember.overrideChains(obj, keyName, meta); }
1896 // The `value` passed to the `didDefineProperty` hook is
1897 // either the descriptor or data, whichever was passed.
1898 if (obj.didDefineProperty) { obj.didDefineProperty(obj, keyName, value); }
1913 var AFTER_OBSERVERS = ':change';
1914 var BEFORE_OBSERVERS = ':before';
1915 var guidFor = Ember.guidFor;
1918 var array_Slice = [].slice;
1920 var ObserverSet = function () {
1921 this.targetSet = {};
1923 ObserverSet.prototype.add = function (target, path) {
1924 var targetSet = this.targetSet,
1925 targetGuid = Ember.guidFor(target),
1926 pathSet = targetSet[targetGuid];
1928 targetSet[targetGuid] = pathSet = {};
1930 if (pathSet[path]) {
1933 return pathSet[path] = true;
1936 ObserverSet.prototype.clear = function () {
1937 this.targetSet = {};
1940 var DeferredEventQueue = function() {
1941 this.targetSet = {};
1945 DeferredEventQueue.prototype.push = function(target, eventName, keyName) {
1946 var targetSet = this.targetSet,
1948 targetGuid = Ember.guidFor(target),
1949 eventNameSet = targetSet[targetGuid],
1952 if (!eventNameSet) {
1953 targetSet[targetGuid] = eventNameSet = {};
1955 index = eventNameSet[eventName];
1956 if (index === undefined) {
1957 eventNameSet[eventName] = queue.push(Ember.deferEvent(target, eventName, [target, keyName])) - 1;
1959 queue[index] = Ember.deferEvent(target, eventName, [target, keyName]);
1963 DeferredEventQueue.prototype.flush = function() {
1964 var queue = this.queue;
1966 this.targetSet = {};
1967 for (var i=0, len=queue.length; i < len; ++i) {
1972 var queue = new DeferredEventQueue(), beforeObserverSet = new ObserverSet();
1974 function notifyObservers(obj, eventName, keyName, forceNotification) {
1975 if (deferred && !forceNotification) {
1976 queue.push(obj, eventName, keyName);
1978 Ember.sendEvent(obj, eventName, [obj, keyName]);
1982 function flushObserverQueue() {
1983 beforeObserverSet.clear();
1989 @method beginPropertyChanges
1992 Ember.beginPropertyChanges = function() {
1998 @method endPropertyChanges
2000 Ember.endPropertyChanges = function() {
2002 if (deferred<=0) flushObserverQueue();
2006 Make a series of property changes together in an
2009 Ember.changeProperties(function() {
2010 obj1.set('foo', mayBlowUpWhenSet);
2011 obj2.set('bar', baz);
2014 @method changeProperties
2015 @param {Function} callback
2018 Ember.changeProperties = function(cb, binding){
2019 Ember.beginPropertyChanges();
2023 Ember.endPropertyChanges();
2028 Set a list of properties on an object. These properties are set inside
2029 a single `beginPropertyChanges` and `endPropertyChanges` batch, so
2030 observers will be buffered.
2032 @method setProperties
2034 @param {Hash} properties
2037 Ember.setProperties = function(self, hash) {
2038 Ember.changeProperties(function(){
2039 for(var prop in hash) {
2040 if (hash.hasOwnProperty(prop)) Ember.set(self, prop, hash[prop]);
2047 function changeEvent(keyName) {
2048 return keyName+AFTER_OBSERVERS;
2051 function beforeEvent(keyName) {
2052 return keyName+BEFORE_OBSERVERS;
2058 @param {String} path
2059 @param {Object|Function} targetOrMethod
2060 @param {Function|String} [method]
2062 Ember.addObserver = function(obj, path, target, method) {
2063 Ember.addListener(obj, changeEvent(path), target, method);
2064 Ember.watch(obj, path);
2068 Ember.observersFor = function(obj, path) {
2069 return Ember.listenersFor(obj, changeEvent(path));
2073 @method removeObserver
2075 @param {String} path
2076 @param {Object|Function} targetOrMethod
2077 @param {Function|String} [method]
2079 Ember.removeObserver = function(obj, path, target, method) {
2080 Ember.unwatch(obj, path);
2081 Ember.removeListener(obj, changeEvent(path), target, method);
2086 @method addBeforeObserver
2088 @param {String} path
2089 @param {Object|Function} targetOrMethod
2090 @param {Function|String} [method]
2092 Ember.addBeforeObserver = function(obj, path, target, method) {
2093 Ember.addListener(obj, beforeEvent(path), target, method);
2094 Ember.watch(obj, path);
2098 // Suspend observer during callback.
2100 // This should only be used by the target of the observer
2101 // while it is setting the observed path.
2102 Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
2103 return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
2106 Ember._suspendObserver = function(obj, path, target, method, callback) {
2107 return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
2110 var map = Ember.ArrayPolyfills.map;
2112 Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
2113 var events = map.call(paths, beforeEvent);
2114 return Ember._suspendListeners(obj, events, target, method, callback);
2117 Ember._suspendObservers = function(obj, paths, target, method, callback) {
2118 var events = map.call(paths, changeEvent);
2119 return Ember._suspendListeners(obj, events, target, method, callback);
2122 Ember.beforeObserversFor = function(obj, path) {
2123 return Ember.listenersFor(obj, beforeEvent(path));
2127 @method removeBeforeObserver
2129 @param {String} path
2130 @param {Object|Function} targetOrMethod
2131 @param {Function|String} [method]
2133 Ember.removeBeforeObserver = function(obj, path, target, method) {
2134 Ember.unwatch(obj, path);
2135 Ember.removeListener(obj, beforeEvent(path), target, method);
2139 Ember.notifyObservers = function(obj, keyName) {
2140 if (obj.isDestroying) { return; }
2142 notifyObservers(obj, changeEvent(keyName), keyName);
2145 Ember.notifyBeforeObservers = function(obj, keyName) {
2146 if (obj.isDestroying) { return; }
2148 var guid, set, forceNotification = false;
2151 if (beforeObserverSet.add(obj, keyName)) {
2152 forceNotification = true;
2158 notifyObservers(obj, beforeEvent(keyName), keyName, forceNotification);
2171 var guidFor = Ember.guidFor, // utils.js
2172 metaFor = Ember.meta, // utils.js
2173 get = Ember.get, // accessors.js
2174 set = Ember.set, // accessors.js
2175 normalizeTuple = Ember.normalizeTuple, // accessors.js
2176 GUID_KEY = Ember.GUID_KEY, // utils.js
2177 META_KEY = Ember.META_KEY, // utils.js
2178 // circular reference observer depends on Ember.watch
2179 // we should move change events to this file or its own property_events.js
2180 notifyObservers = Ember.notifyObservers, // observer.js
2181 forEach = Ember.ArrayPolyfills.forEach, // array.js
2182 FIRST_KEY = /^([^\.\*]+)/,
2185 var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
2186 o_defineProperty = Ember.platform.defineProperty;
2188 function firstKey(path) {
2189 return path.match(FIRST_KEY)[0];
2192 // returns true if the passed path is just a keyName
2193 function isKeyName(path) {
2194 return path==='*' || !IS_PATH.test(path);
2197 // ..........................................................
2201 var DEP_SKIP = { __emberproto__: true }; // skip some keys and toString
2203 function iterDeps(method, obj, depKey, seen, meta) {
2205 var guid = guidFor(obj);
2206 if (!seen[guid]) seen[guid] = {};
2207 if (seen[guid][depKey]) return;
2208 seen[guid][depKey] = true;
2210 var deps = meta.deps;
2211 deps = deps && deps[depKey];
2213 for(var key in deps) {
2214 if (DEP_SKIP[key]) continue;
2215 var desc = meta.descs[key];
2216 if (desc && desc._suspended === obj) continue;
2223 var WILL_SEEN, DID_SEEN;
2225 // called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...)
2226 function dependentKeysWillChange(obj, depKey, meta) {
2227 if (obj.isDestroying) { return; }
2229 var seen = WILL_SEEN, top = !seen;
2230 if (top) { seen = WILL_SEEN = {}; }
2231 iterDeps(propertyWillChange, obj, depKey, seen, meta);
2232 if (top) { WILL_SEEN = null; }
2235 // called whenever a property has just changed to update dependent keys
2236 function dependentKeysDidChange(obj, depKey, meta) {
2237 if (obj.isDestroying) { return; }
2239 var seen = DID_SEEN, top = !seen;
2240 if (top) { seen = DID_SEEN = {}; }
2241 iterDeps(propertyDidChange, obj, depKey, seen, meta);
2242 if (top) { DID_SEEN = null; }
2245 // ..........................................................
2249 function addChainWatcher(obj, keyName, node) {
2250 if (!obj || ('object' !== typeof obj)) return; // nothing to do
2251 var m = metaFor(obj);
2252 var nodes = m.chainWatchers;
2253 if (!nodes || nodes.__emberproto__ !== obj) {
2254 nodes = m.chainWatchers = { __emberproto__: obj };
2257 if (!nodes[keyName]) { nodes[keyName] = {}; }
2258 nodes[keyName][guidFor(node)] = node;
2259 Ember.watch(obj, keyName);
2262 function removeChainWatcher(obj, keyName, node) {
2263 if (!obj || 'object' !== typeof obj) { return; } // nothing to do
2264 var m = metaFor(obj, false),
2265 nodes = m.chainWatchers;
2266 if (!nodes || nodes.__emberproto__ !== obj) { return; } //nothing to do
2267 if (nodes[keyName]) { delete nodes[keyName][guidFor(node)]; }
2268 Ember.unwatch(obj, keyName);
2271 var pendingQueue = [];
2273 // attempts to add the pendingQueue chains again. If some of them end up
2274 // back in the queue and reschedule is true, schedules a timeout to try
2276 function flushPendingChains() {
2277 if (pendingQueue.length === 0) { return; } // nothing to do
2279 var queue = pendingQueue;
2282 forEach.call(queue, function(q) { q[0].add(q[1]); });
2284 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);
2287 function isProto(pvalue) {
2288 return metaFor(pvalue, false).proto === pvalue;
2291 // A ChainNode watches a single key on an object. If you provide a starting
2292 // value for the key then the node won't actually watch it. For a root node
2293 // pass null for parent and key and object for value.
2294 var ChainNode = function(parent, key, value, separator) {
2296 this._parent = parent;
2299 // _watching is true when calling get(this._parent, this._key) will
2300 // return the value of this node.
2302 // It is false for the root of a chain (because we have no parent)
2303 // and for global paths (because the parent node is the object with
2304 // the observer on it)
2305 this._watching = value===undefined;
2307 this._value = value;
2308 this._separator = separator || '.';
2310 if (this._watching) {
2311 this._object = parent.value();
2312 if (this._object) { addChainWatcher(this._object, this._key, this); }
2315 // Special-case: the EachProxy relies on immediate evaluation to
2316 // establish its observers.
2318 // TODO: Replace this with an efficient callback that the EachProxy
2320 if (this._parent && this._parent._key === '@each') {
2325 var ChainNodePrototype = ChainNode.prototype;
2327 ChainNodePrototype.value = function() {
2328 if (this._value === undefined && this._watching) {
2329 var obj = this._parent.value();
2330 this._value = (obj && !isProto(obj)) ? get(obj, this._key) : undefined;
2335 ChainNodePrototype.destroy = function() {
2336 if (this._watching) {
2337 var obj = this._object;
2338 if (obj) { removeChainWatcher(obj, this._key, this); }
2339 this._watching = false; // so future calls do nothing
2343 // copies a top level object only
2344 ChainNodePrototype.copy = function(obj) {
2345 var ret = new ChainNode(null, null, obj, this._separator),
2346 paths = this._paths, path;
2347 for (path in paths) {
2348 if (paths[path] <= 0) { continue; } // this check will also catch non-number vals.
2354 // called on the root node of a chain to setup watchers on the specified
2356 ChainNodePrototype.add = function(path) {
2357 var obj, tuple, key, src, separator, paths;
2359 paths = this._paths;
2360 paths[path] = (paths[path] || 0) + 1;
2363 tuple = normalizeTuple(obj, path);
2365 // the path was a local path
2366 if (tuple[0] && tuple[0] === obj) {
2368 key = firstKey(path);
2369 path = path.slice(key.length+1);
2371 // global path, but object does not exist yet.
2372 // put into a queue and try to connect later.
2373 } else if (!tuple[0]) {
2374 pendingQueue.push([this, path]);
2378 // global path, and object already exists
2381 key = path.slice(0, 0-(tuple[1].length+1));
2382 separator = path.slice(key.length, key.length+1);
2387 this.chain(key, path, src, separator);
2390 // called on the root node of a chain to teardown watcher on the specified
2392 ChainNodePrototype.remove = function(path) {
2393 var obj, tuple, key, src, paths;
2395 paths = this._paths;
2396 if (paths[path] > 0) { paths[path]--; }
2399 tuple = normalizeTuple(obj, path);
2400 if (tuple[0] === obj) {
2402 key = firstKey(path);
2403 path = path.slice(key.length+1);
2406 key = path.slice(0, 0-(tuple[1].length+1));
2411 this.unchain(key, path);
2414 ChainNodePrototype.count = 0;
2416 ChainNodePrototype.chain = function(key, path, src, separator) {
2417 var chains = this._chains, node;
2418 if (!chains) { chains = this._chains = {}; }
2421 if (!node) { node = chains[key] = new ChainNode(this, key, src, separator); }
2422 node.count++; // count chains...
2424 // chain rest of path if there is one
2425 if (path && path.length>0) {
2426 key = firstKey(path);
2427 path = path.slice(key.length+1);
2428 node.chain(key, path); // NOTE: no src means it will observe changes...
2432 ChainNodePrototype.unchain = function(key, path) {
2433 var chains = this._chains, node = chains[key];
2435 // unchain rest of path first...
2436 if (path && path.length>1) {
2437 key = firstKey(path);
2438 path = path.slice(key.length+1);
2439 node.unchain(key, path);
2442 // delete node if needed.
2444 if (node.count<=0) {
2445 delete chains[node._key];
2451 ChainNodePrototype.willChange = function() {
2452 var chains = this._chains;
2454 for(var key in chains) {
2455 if (!chains.hasOwnProperty(key)) { continue; }
2456 chains[key].willChange();
2460 if (this._parent) { this._parent.chainWillChange(this, this._key, 1); }
2463 ChainNodePrototype.chainWillChange = function(chain, path, depth) {
2464 if (this._key) { path = this._key + this._separator + path; }
2467 this._parent.chainWillChange(this, path, depth+1);
2469 if (depth > 1) { Ember.propertyWillChange(this.value(), path); }
2470 path = 'this.' + path;
2471 if (this._paths[path] > 0) { Ember.propertyWillChange(this.value(), path); }
2475 ChainNodePrototype.chainDidChange = function(chain, path, depth) {
2476 if (this._key) { path = this._key + this._separator + path; }
2478 this._parent.chainDidChange(this, path, depth+1);
2480 if (depth > 1) { Ember.propertyDidChange(this.value(), path); }
2481 path = 'this.' + path;
2482 if (this._paths[path] > 0) { Ember.propertyDidChange(this.value(), path); }
2486 ChainNodePrototype.didChange = function(suppressEvent) {
2487 // invalidate my own value first.
2488 if (this._watching) {
2489 var obj = this._parent.value();
2490 if (obj !== this._object) {
2491 removeChainWatcher(this._object, this._key, this);
2493 addChainWatcher(obj, this._key, this);
2495 this._value = undefined;
2497 // Special-case: the EachProxy relies on immediate evaluation to
2498 // establish its observers.
2499 if (this._parent && this._parent._key === '@each')
2503 // then notify chains...
2504 var chains = this._chains;
2506 for(var key in chains) {
2507 if (!chains.hasOwnProperty(key)) { continue; }
2508 chains[key].didChange(suppressEvent);
2512 if (suppressEvent) { return; }
2514 // and finally tell parent about my path changing...
2515 if (this._parent) { this._parent.chainDidChange(this, this._key, 1); }
2518 // get the chains for the current object. If the current object has
2519 // chains inherited from the proto they will be cloned and reconfigured for
2520 // the current object.
2521 function chainsFor(obj) {
2522 var m = metaFor(obj), ret = m.chains;
2524 ret = m.chains = new ChainNode(null, null, obj);
2525 } else if (ret.value() !== obj) {
2526 ret = m.chains = ret.copy(obj);
2531 function notifyChains(obj, m, keyName, methodName, arg) {
2532 var nodes = m.chainWatchers;
2534 if (!nodes || nodes.__emberproto__ !== obj) { return; } // nothing to do
2536 nodes = nodes[keyName];
2537 if (!nodes) { return; }
2539 for(var key in nodes) {
2540 if (!nodes.hasOwnProperty(key)) { continue; }
2541 nodes[key][methodName](arg);
2545 Ember.overrideChains = function(obj, keyName, m) {
2546 notifyChains(obj, m, keyName, 'didChange', true);
2549 function chainsWillChange(obj, keyName, m) {
2550 notifyChains(obj, m, keyName, 'willChange');
2553 function chainsDidChange(obj, keyName, m) {
2554 notifyChains(obj, m, keyName, 'didChange');
2557 // ..........................................................
2564 Starts watching a property on an object. Whenever the property changes,
2565 invokes Ember.propertyWillChange and Ember.propertyDidChange. This is the
2566 primitive used by observers and dependent keys; usually you will never call
2567 this method directly but instead use higher level methods like
2568 Ember.addObserver().
2573 @param {String} keyName
2575 Ember.watch = function(obj, keyName) {
2576 // can't watch length on Array - it is special...
2577 if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
2579 var m = metaFor(obj), watching = m.watching, desc;
2581 // activate watching first time
2582 if (!watching[keyName]) {
2583 watching[keyName] = 1;
2584 if (isKeyName(keyName)) {
2585 desc = m.descs[keyName];
2586 if (desc && desc.willWatch) { desc.willWatch(obj, keyName); }
2588 if ('function' === typeof obj.willWatchProperty) {
2589 obj.willWatchProperty(keyName);
2592 if (MANDATORY_SETTER && keyName in obj) {
2593 m.values[keyName] = obj[keyName];
2594 o_defineProperty(obj, keyName, {
2598 Ember.assert('Must use Ember.set() to access this property', false);
2601 var meta = this[META_KEY];
2602 return meta && meta.values[keyName];
2607 chainsFor(obj).add(keyName);
2611 watching[keyName] = (watching[keyName] || 0) + 1;
2616 Ember.isWatching = function isWatching(obj, key) {
2617 var meta = obj[META_KEY];
2618 return (meta && meta.watching[key]) > 0;
2621 Ember.watch.flushPending = flushPendingChains;
2623 Ember.unwatch = function(obj, keyName) {
2624 // can't watch length on Array - it is special...
2625 if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
2627 var m = metaFor(obj), watching = m.watching, desc;
2629 if (watching[keyName] === 1) {
2630 watching[keyName] = 0;
2632 if (isKeyName(keyName)) {
2633 desc = m.descs[keyName];
2634 if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); }
2636 if ('function' === typeof obj.didUnwatchProperty) {
2637 obj.didUnwatchProperty(keyName);
2640 if (MANDATORY_SETTER && keyName in obj) {
2641 o_defineProperty(obj, keyName, {
2645 value: m.values[keyName]
2647 delete m.values[keyName];
2650 chainsFor(obj).remove(keyName);
2653 } else if (watching[keyName]>1) {
2654 watching[keyName]--;
2663 Call on an object when you first beget it from another object. This will
2664 setup any chained watchers on the object instance as needed. This method is
2665 safe to call multiple times.
2671 Ember.rewatch = function(obj) {
2672 var m = metaFor(obj, false), chains = m.chains;
2674 // make sure the object has its own guid.
2675 if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
2676 Ember.generateGuid(obj, 'ember');
2679 // make sure any chained watchers update.
2680 if (chains && chains.value() !== obj) {
2681 m.chains = chains.copy(obj);
2687 Ember.finishChains = function(obj) {
2688 var m = metaFor(obj, false), chains = m.chains;
2690 if (chains.value() !== obj) {
2691 m.chains = chains = chains.copy(obj);
2693 chains.didChange(true);
2697 // ..........................................................
2702 This function is called just before an object property is about to change.
2703 It will notify any before observers and prepare caches among other things.
2705 Normally you will not need to call this method directly but if for some
2706 reason you can't directly watch a property you can invoke this method
2707 manually along with `Ember.propertyDidChange()` which you should call just
2708 after the property value changes.
2710 @method propertyWillChange
2712 @param {Object} obj The object with the property that will change
2713 @param {String} keyName The property key (or path) that will change.
2716 function propertyWillChange(obj, keyName, value) {
2717 var m = metaFor(obj, false),
2718 watching = m.watching[keyName] > 0 || keyName === 'length',
2720 desc = m.descs[keyName];
2722 if (!watching) { return; }
2723 if (proto === obj) { return; }
2724 if (desc && desc.willChange) { desc.willChange(obj, keyName); }
2725 dependentKeysWillChange(obj, keyName, m);
2726 chainsWillChange(obj, keyName, m);
2727 Ember.notifyBeforeObservers(obj, keyName);
2730 Ember.propertyWillChange = propertyWillChange;
2733 This function is called just after an object property has changed.
2734 It will notify any observers and clear caches among other things.
2736 Normally you will not need to call this method directly but if for some
2737 reason you can't directly watch a property you can invoke this method
2738 manually along with `Ember.propertyWilLChange()` which you should call just
2739 before the property value changes.
2741 @method propertyDidChange
2743 @param {Object} obj The object with the property that will change
2744 @param {String} keyName The property key (or path) that will change.
2747 function propertyDidChange(obj, keyName) {
2748 var m = metaFor(obj, false),
2749 watching = m.watching[keyName] > 0 || keyName === 'length',
2751 desc = m.descs[keyName];
2753 if (proto === obj) { return; }
2755 // shouldn't this mean that we're watching this key?
2756 if (desc && desc.didChange) { desc.didChange(obj, keyName); }
2757 if (!watching && keyName !== 'length') { return; }
2759 dependentKeysDidChange(obj, keyName, m);
2760 chainsDidChange(obj, keyName, m);
2761 Ember.notifyObservers(obj, keyName);
2764 Ember.propertyDidChange = propertyDidChange;
2766 var NODE_STACK = [];
2769 Tears down the meta on an object so that it can be garbage collected.
2770 Multiple calls will have no effect.
2774 @param {Object} obj the object to destroy
2777 Ember.destroy = function (obj) {
2778 var meta = obj[META_KEY], node, nodes, key, nodeObject;
2780 obj[META_KEY] = null;
2781 // remove chainWatchers to remove circular references that would prevent GC
2784 NODE_STACK.push(node);
2786 while (NODE_STACK.length > 0) {
2787 node = NODE_STACK.pop();
2789 nodes = node._chains;
2791 for (key in nodes) {
2792 if (nodes.hasOwnProperty(key)) {
2793 NODE_STACK.push(nodes[key]);
2797 // remove chainWatcher in node object
2798 if (node._watching) {
2799 nodeObject = node._object;
2801 removeChainWatcher(nodeObject, node._key, node);
2818 Ember.warn("Computed properties will soon be cacheable by default. To enable this in your app, set `ENV.CP_DEFAULT_CACHEABLE = true`.", Ember.CP_DEFAULT_CACHEABLE);
2821 var get = Ember.get,
2822 metaFor = Ember.meta,
2823 guidFor = Ember.guidFor,
2825 o_create = Ember.create,
2826 META_KEY = Ember.META_KEY,
2827 watch = Ember.watch,
2828 unwatch = Ember.unwatch;
2830 // ..........................................................
2837 // 'keyName': count,
2838 // __emberproto__: SRC_OBJ [to detect clones]
2840 // __emberproto__: SRC_OBJ
2844 This function returns a map of unique dependencies for a
2845 given object and key.
2847 function keysForDep(obj, depsMeta, depKey) {
2848 var keys = depsMeta[depKey];
2850 // if there are no dependencies yet for a the given key
2851 // create a new empty list of dependencies for the key
2852 keys = depsMeta[depKey] = { __emberproto__: obj };
2853 } else if (keys.__emberproto__ !== obj) {
2854 // otherwise if the dependency list is inherited from
2855 // a superclass, clone the hash
2856 keys = depsMeta[depKey] = o_create(keys);
2857 keys.__emberproto__ = obj;
2862 /* return obj[META_KEY].deps */
2863 function metaForDeps(obj, meta) {
2864 var deps = meta.deps;
2865 // If the current object has no dependencies...
2867 // initialize the dependencies with a pointer back to
2868 // the current object
2869 deps = meta.deps = { __emberproto__: obj };
2870 } else if (deps.__emberproto__ !== obj) {
2871 // otherwise if the dependencies are inherited from the
2872 // object's superclass, clone the deps
2873 deps = meta.deps = o_create(deps);
2874 deps.__emberproto__ = obj;
2879 function addDependentKeys(desc, obj, keyName, meta) {
2880 // the descriptor has a list of dependent keys, so
2881 // add all of its dependent keys.
2882 var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
2883 if (!depKeys) return;
2885 depsMeta = metaForDeps(obj, meta);
2887 for(idx = 0, len = depKeys.length; idx < len; idx++) {
2888 depKey = depKeys[idx];
2889 // Lookup keys meta for depKey
2890 keys = keysForDep(obj, depsMeta, depKey);
2891 // Increment the number of times depKey depends on keyName.
2892 keys[keyName] = (keys[keyName] || 0) + 1;
2898 function removeDependentKeys(desc, obj, keyName, meta) {
2899 // the descriptor has a list of dependent keys, so
2900 // add all of its dependent keys.
2901 var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
2902 if (!depKeys) return;
2904 depsMeta = metaForDeps(obj, meta);
2906 for(idx = 0, len = depKeys.length; idx < len; idx++) {
2907 depKey = depKeys[idx];
2908 // Lookup keys meta for depKey
2909 keys = keysForDep(obj, depsMeta, depKey);
2910 // Increment the number of times depKey depends on keyName.
2911 keys[keyName] = (keys[keyName] || 0) - 1;
2913 unwatch(obj, depKey);
2917 // ..........................................................
2918 // COMPUTED PROPERTY
2922 @class ComputedProperty
2924 @extends Ember.Descriptor
2927 function ComputedProperty(func, opts) {
2929 this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : Ember.CP_DEFAULT_CACHEABLE;
2930 this._dependentKeys = opts && opts.dependentKeys;
2933 Ember.ComputedProperty = ComputedProperty;
2934 ComputedProperty.prototype = new Ember.Descriptor();
2936 var ComputedPropertyPrototype = ComputedProperty.prototype;
2939 Call on a computed property to set it into cacheable mode. When in this
2940 mode the computed property will automatically cache the return value of
2941 your function until one of the dependent keys changes.
2943 MyApp.president = Ember.Object.create({
2944 fullName: function() {
2945 return this.get('firstName') + ' ' + this.get('lastName');
2947 // After calculating the value of this function, Ember.js will
2948 // return that value without re-executing this function until
2949 // one of the dependent properties change.
2950 }.property('firstName', 'lastName').cacheable()
2953 Properties are cacheable by default.
2956 @param {Boolean} aFlag optional set to false to disable caching
2959 ComputedPropertyPrototype.cacheable = function(aFlag) {
2960 this._cacheable = aFlag !== false;
2965 Call on a computed property to set it into non-cached mode. When in this
2966 mode the computed property will not automatically cache the return value.
2968 MyApp.outsideService = Ember.Object.create({
2970 return OutsideService.getValue();
2971 }.property().volatile()
2977 ComputedPropertyPrototype.volatile = function() {
2978 return this.cacheable(false);
2982 Sets the dependent keys on this computed property. Pass any number of
2983 arguments containing key paths that this computed property depends on.
2985 MyApp.president = Ember.Object.create({
2986 fullName: Ember.computed(function() {
2987 return this.get('firstName') + ' ' + this.get('lastName');
2989 // Tell Ember.js that this computed property depends on firstName
2991 }).property('firstName', 'lastName')
2995 @param {String} path* zero or more property paths
2998 ComputedPropertyPrototype.property = function() {
3000 for (var i = 0, l = arguments.length; i < l; i++) {
3001 args.push(arguments[i]);
3003 this._dependentKeys = args;
3008 In some cases, you may want to annotate computed properties with additional
3009 metadata about how they function or what values they operate on. For example,
3010 computed property functions may close over variables that are then no longer
3011 available for introspection.
3013 You can pass a hash of these values to a computed property like this:
3015 person: function() {
3016 var personId = this.get('personId');
3017 return App.Person.create({ id: personId });
3018 }.property().meta({ type: App.Person })
3020 The hash that you pass to the `meta()` function will be saved on the
3021 computed property descriptor under the `_meta` key. Ember runtime
3022 exposes a public API for retrieving these values from classes,
3023 via the `metaForProperty()` function.
3030 ComputedPropertyPrototype.meta = function(meta) {
3031 if (arguments.length === 0) {
3032 return this._meta || {};
3039 /* impl descriptor API */
3040 ComputedPropertyPrototype.willWatch = function(obj, keyName) {
3041 // watch already creates meta for this instance
3042 var meta = obj[META_KEY];
3043 Ember.assert('watch should have setup meta to be writable', meta.source === obj);
3044 if (!(keyName in meta.cache)) {
3045 addDependentKeys(this, obj, keyName, meta);
3049 ComputedPropertyPrototype.didUnwatch = function(obj, keyName) {
3050 var meta = obj[META_KEY];
3051 Ember.assert('unwatch should have setup meta to be writable', meta.source === obj);
3052 if (!(keyName in meta.cache)) {
3053 // unwatch already creates meta for this instance
3054 removeDependentKeys(this, obj, keyName, meta);
3058 /* impl descriptor API */
3059 ComputedPropertyPrototype.didChange = function(obj, keyName) {
3060 // _suspended is set via a CP.set to ensure we don't clear
3061 // the cached value set by the setter
3062 if (this._cacheable && this._suspended !== obj) {
3063 var meta = metaFor(obj);
3064 if (keyName in meta.cache) {
3065 delete meta.cache[keyName];
3066 if (!meta.watching[keyName]) {
3067 removeDependentKeys(this, obj, keyName, meta);
3073 /* impl descriptor API */
3074 ComputedPropertyPrototype.get = function(obj, keyName) {
3075 var ret, cache, meta;
3076 if (this._cacheable) {
3077 meta = metaFor(obj);
3079 if (keyName in cache) { return cache[keyName]; }
3080 ret = cache[keyName] = this.func.call(obj, keyName);
3081 if (!meta.watching[keyName]) {
3082 addDependentKeys(this, obj, keyName, meta);
3085 ret = this.func.call(obj, keyName);
3090 /* impl descriptor API */
3091 ComputedPropertyPrototype.set = function(obj, keyName, value) {
3092 var cacheable = this._cacheable,
3093 meta = metaFor(obj, cacheable),
3094 watched = meta.watching[keyName],
3095 oldSuspended = this._suspended,
3096 hadCachedValue = false,
3098 this._suspended = obj;
3100 ret = this.func.call(obj, keyName, value);
3102 if (cacheable && keyName in meta.cache) {
3103 if (meta.cache[keyName] === ret) {
3106 hadCachedValue = true;
3109 if (watched) { Ember.propertyWillChange(obj, keyName); }
3111 if (cacheable && hadCachedValue) {
3112 delete meta.cache[keyName];
3116 if (!watched && !hadCachedValue) {
3117 addDependentKeys(this, obj, keyName, meta);
3119 meta.cache[keyName] = ret;
3122 if (watched) { Ember.propertyDidChange(obj, keyName); }
3124 this._suspended = oldSuspended;
3129 /* called when property is defined */
3130 ComputedPropertyPrototype.setup = function(obj, keyName) {
3131 var meta = obj[META_KEY];
3132 if (meta && meta.watching[keyName]) {
3133 addDependentKeys(this, obj, keyName, metaFor(obj));
3137 /* called before property is overridden */
3138 ComputedPropertyPrototype.teardown = function(obj, keyName) {
3139 var meta = metaFor(obj);
3141 if (meta.watching[keyName] || keyName in meta.cache) {
3142 removeDependentKeys(this, obj, keyName, meta);
3145 if (this._cacheable) { delete meta.cache[keyName]; }
3147 return null; // no value to restore
3152 This helper returns a new property descriptor that wraps the passed
3153 computed property function. You can use this helper to define properties
3154 with mixins or via Ember.defineProperty().
3156 The function you pass will be used to both get and set property values.
3157 The function should accept two parameters, key and value. If value is not
3158 undefined you should set the value first. In either case return the
3159 current value of the property.
3163 @param {Function} func The computed property function.
3164 @return {Ember.ComputedProperty} property descriptor instance
3166 Ember.computed = function(func) {
3169 if (arguments.length > 1) {
3170 args = a_slice.call(arguments, 0, -1);
3171 func = a_slice.call(arguments, -1)[0];
3174 var cp = new ComputedProperty(func);
3177 cp.property.apply(cp, args);
3184 Returns the cached value for a property, if one exists.
3185 This can be useful for peeking at the value of a computed
3186 property that is generated lazily, without accidentally causing
3191 @param {Object} obj the object whose property you want to check
3192 @param {String} key the name of the property whose cached value you want
3195 Ember.cacheFor = function cacheFor(obj, key) {
3196 var cache = metaFor(obj, false).cache;
3198 if (cache && key in cache) {
3204 @method computed.not
3206 @param {String} dependentKey
3208 Ember.computed.not = function(dependentKey) {
3209 return Ember.computed(dependentKey, function(key) {
3210 return !get(this, dependentKey);
3215 @method computed.empty
3217 @param {String} dependentKey
3219 Ember.computed.empty = function(dependentKey) {
3220 return Ember.computed(dependentKey, function(key) {
3221 var val = get(this, dependentKey);
3222 return val === undefined || val === null || val === '' || (Ember.isArray(val) && get(val, 'length') === 0);
3227 @method computed.bool
3229 @param {String} dependentKey
3231 Ember.computed.bool = function(dependentKey) {
3232 return Ember.computed(dependentKey, function(key) {
3233 return !!get(this, dependentKey);
3246 var o_create = Ember.create,
3248 metaPath = Ember.metaPath,
3249 guidFor = Ember.guidFor,
3253 The event system uses a series of nested hashes to store listeners on an
3254 object. When a listener is registered, or when an event arrives, these
3255 hashes are consulted to determine which target and action pair to invoke.
3257 The hashes are stored in the object's meta hash, and look like this:
3259 // Object's meta hash
3261 listeners: { // variable name: `listenerSet`
3262 "foo:changed": { // variable name: `targetSet`
3263 [targetGuid]: { // variable name: `actionSet`
3264 [methodGuid]: { // variable name: `action`
3265 target: [Object object],
3266 method: [Function function]
3275 // Gets the set of all actions, keyed on the guid of each action's
3277 function actionSetFor(obj, eventName, target, writable) {
3278 return metaPath(obj, ['listeners', eventName, guidFor(target)], writable);
3281 // Gets the set of all targets, keyed on the guid of each action's
3283 function targetSetFor(obj, eventName) {
3284 var listenerSet = meta(obj, false).listeners;
3285 if (!listenerSet) { return false; }
3287 return listenerSet[eventName] || false;
3290 // TODO: This knowledge should really be a part of the
3292 var SKIP_PROPERTIES = { __ember_source__: true };
3294 function iterateSet(obj, eventName, callback, params) {
3295 var targetSet = targetSetFor(obj, eventName);
3296 if (!targetSet) { return false; }
3297 // Iterate through all elements of the target set
3298 for(var targetGuid in targetSet) {
3299 if (SKIP_PROPERTIES[targetGuid]) { continue; }
3301 var actionSet = targetSet[targetGuid];
3303 // Iterate through the elements of the action set
3304 for(var methodGuid in actionSet) {
3305 if (SKIP_PROPERTIES[methodGuid]) { continue; }
3307 var action = actionSet[methodGuid];
3309 if (callback(action, params, obj) === true) {
3319 function invokeAction(action, params, sender) {
3320 var method = action.method, target = action.target;
3321 // If there is no target, the target is the object
3322 // on which the event was fired.
3323 if (!target) { target = sender; }
3324 if ('string' === typeof method) { method = target[method]; }
3326 method.apply(target, params);
3328 method.apply(target);
3333 Add an event listener
3338 @param {String} eventName
3339 @param {Object|Function} targetOrMethod A target object or a function
3340 @param {Function|String} method A function or the name of a function to be called on `target`
3342 function addListener(obj, eventName, target, method) {
3343 Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
3345 if (!method && 'function' === typeof target) {
3350 var actionSet = actionSetFor(obj, eventName, target, true),
3351 methodGuid = guidFor(method);
3353 if (!actionSet[methodGuid]) {
3354 actionSet[methodGuid] = { target: target, method: method };
3357 if ('function' === typeof obj.didAddListener) {
3358 obj.didAddListener(eventName, target, method);
3363 Remove an event listener
3365 Arguments should match those passed to {{#crossLink "Ember/addListener"}}{{/crossLink}}
3367 @method removeListener
3370 @param {String} eventName
3371 @param {Object|Function} targetOrMethod A target object or a function
3372 @param {Function|String} method A function or the name of a function to be called on `target`
3374 function removeListener(obj, eventName, target, method) {
3375 Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
3377 if (!method && 'function' === typeof target) {
3382 var actionSet = actionSetFor(obj, eventName, target, true),
3383 methodGuid = guidFor(method);
3385 // we can't simply delete this parameter, because if we do, we might
3386 // re-expose the property from the prototype chain.
3387 if (actionSet && actionSet[methodGuid]) { actionSet[methodGuid] = null; }
3389 if ('function' === typeof obj.didRemoveListener) {
3390 obj.didRemoveListener(eventName, target, method);
3397 Suspend listener during callback.
3399 This should only be used by the target of the event listener
3400 when it is taking an action that would cause the event, e.g.
3401 an object might suspend its property change listener while it is
3402 setting that property.
3404 @method suspendListener
3407 @param {String} eventName
3408 @param {Object|Function} targetOrMethod A target object or a function
3409 @param {Function|String} method A function or the name of a function to be called on `target`
3410 @param {Function} callback
3412 function suspendListener(obj, eventName, target, method, callback) {
3413 if (!method && 'function' === typeof target) {
3418 var actionSet = actionSetFor(obj, eventName, target, true),
3419 methodGuid = guidFor(method),
3420 action = actionSet && actionSet[methodGuid];
3422 actionSet[methodGuid] = null;
3424 return callback.call(target);
3426 actionSet[methodGuid] = action;
3433 Suspend listener during callback.
3435 This should only be used by the target of the event listener
3436 when it is taking an action that would cause the event, e.g.
3437 an object might suspend its property change listener while it is
3438 setting that property.
3440 @method suspendListener
3443 @param {Array} eventName Array of event names
3444 @param {Object|Function} targetOrMethod A target object or a function
3445 @param {Function|String} method A function or the name of a function to be called on `target`
3446 @param {Function} callback
3448 function suspendListeners(obj, eventNames, target, method, callback) {
3449 if (!method && 'function' === typeof target) {
3454 var oldActions = [],
3456 eventName, actionSet, methodGuid, action, i, l;
3458 for (i=0, l=eventNames.length; i<l; i++) {
3459 eventName = eventNames[i];
3460 actionSet = actionSetFor(obj, eventName, target, true),
3461 methodGuid = guidFor(method);
3463 oldActions.push(actionSet && actionSet[methodGuid]);
3464 actionSets.push(actionSet);
3466 actionSet[methodGuid] = null;
3470 return callback.call(target);
3472 for (i=0, l=oldActions.length; i<l; i++) {
3473 eventName = eventNames[i];
3474 actionSets[i][methodGuid] = oldActions[i];
3482 Return a list of currently watched events
3484 @method watchedEvents
3488 function watchedEvents(obj) {
3489 var listeners = meta(obj, false).listeners, ret = [];
3492 for(var eventName in listeners) {
3493 if (!SKIP_PROPERTIES[eventName] && listeners[eventName]) {
3494 ret.push(eventName);
3505 @param {String} eventName
3506 @param {Array} params
3509 function sendEvent(obj, eventName, params) {
3510 // first give object a chance to handle it
3511 if (obj !== Ember && 'function' === typeof obj.sendEvent) {
3512 obj.sendEvent(eventName, params);
3515 iterateSet(obj, eventName, invokeAction, params);
3524 @param {String} eventName
3525 @param {Array} params
3527 function deferEvent(obj, eventName, params) {
3529 iterateSet(obj, eventName, function (action) {
3530 actions.push(action);
3534 if (obj.isDestroyed) { return; }
3536 if (obj !== Ember && 'function' === typeof obj.sendEvent) {
3537 obj.sendEvent(eventName, params);
3540 for (var i=0, len=actions.length; i < len; ++i) {
3541 invokeAction(actions[i], params, obj);
3548 @method hasListeners
3551 @param {String} eventName
3553 function hasListeners(obj, eventName) {
3554 if (iterateSet(obj, eventName, function() { return true; })) {
3558 // no listeners! might as well clean this up so it is faster later.
3559 var set = metaPath(obj, ['listeners'], true);
3560 set[eventName] = null;
3567 @method listenersFor
3570 @param {String} eventName
3572 function listenersFor(obj, eventName) {
3574 iterateSet(obj, eventName, function (action) {
3575 ret.push([action.target, action.method]);
3580 Ember.addListener = addListener;
3581 Ember.removeListener = removeListener;
3582 Ember._suspendListener = suspendListener;
3583 Ember._suspendListeners = suspendListeners;
3584 Ember.sendEvent = sendEvent;
3585 Ember.hasListeners = hasListeners;
3586 Ember.watchedEvents = watchedEvents;
3587 Ember.listenersFor = listenersFor;
3588 Ember.deferEvent = deferEvent;
3596 // Ember.watch.flushPending
3597 // Ember.beginPropertyChanges, Ember.endPropertyChanges
3604 // ..........................................................
3608 var slice = [].slice,
3609 forEach = Ember.ArrayPolyfills.forEach;
3611 // invokes passed params - normalizing so you can pass target/func,
3612 // target/string or just func
3613 function invoke(target, method, args, ignore) {
3615 if (method === undefined) {
3620 if ('string' === typeof method) { method = target[method]; }
3621 if (args && ignore > 0) {
3622 args = args.length > ignore ? slice.call(args, ignore) : null;
3625 return Ember.handleErrors(function() {
3626 // IE8's Function.prototype.apply doesn't accept undefined/null arguments.
3627 return method.apply(target || this, args || []);
3632 // ..........................................................
3636 var timerMark; // used by timers...
3639 Ember RunLoop (Private)
3646 var RunLoop = function(prev) {
3647 this._prev = prev || null;
3648 this.onceTimers = {};
3651 RunLoop.prototype = {
3666 // ..........................................................
3672 @param {String} queueName
3676 schedule: function(queueName, target, method) {
3677 var queues = this._queues, queue;
3678 if (!queues) { queues = this._queues = {}; }
3679 queue = queues[queueName];
3680 if (!queue) { queue = queues[queueName] = []; }
3682 var args = arguments.length > 3 ? slice.call(arguments, 3) : null;
3683 queue.push({ target: target, method: method, args: args });
3689 @param {String} queueName
3691 flush: function(queueName) {
3692 var queueNames, idx, len, queue, log;
3694 if (!this._queues) { return this; } // nothing to do
3696 function iter(item) {
3697 invoke(item.target, item.method, item.args);
3700 Ember.watch.flushPending(); // make sure all chained watchers are setup
3703 while (this._queues && (queue = this._queues[queueName])) {
3704 this._queues[queueName] = null;
3706 // the sync phase is to allow property changes to propagate. don't
3707 // invoke observers until that is finished.
3708 if (queueName === 'sync') {
3709 log = Ember.LOG_BINDINGS;
3710 if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
3712 Ember.beginPropertyChanges();
3714 forEach.call(queue, iter);
3716 Ember.endPropertyChanges();
3719 if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
3722 forEach.call(queue, iter);
3727 queueNames = Ember.run.queues;
3728 len = queueNames.length;
3733 queueName = queueNames[idx];
3734 queue = this._queues && this._queues[queueName];
3735 delete this._queues[queueName];
3738 // the sync phase is to allow property changes to propagate. don't
3739 // invoke observers until that is finished.
3740 if (queueName === 'sync') {
3741 log = Ember.LOG_BINDINGS;
3742 if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
3744 Ember.beginPropertyChanges();
3746 forEach.call(queue, iter);
3748 Ember.endPropertyChanges();
3751 if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
3753 forEach.call(queue, iter);
3757 // Loop through prior queues
3758 for (var i = 0; i <= idx; i++) {
3759 if (this._queues && this._queues[queueNames[i]]) {
3760 // Start over at the first queue with contents
3777 Ember.RunLoop = RunLoop;
3779 // ..........................................................
3780 // Ember.run - this is ideally the only public API the dev sees
3784 Runs the passed target and method inside of a RunLoop, ensuring any
3785 deferred actions including bindings and views updates are flushed at the
3788 Normally you should not need to invoke this method yourself. However if
3789 you are implementing raw event handlers when interfacing with other
3790 libraries or plugins, you should probably wrap all of your code inside this
3793 Ember.run(function(){
3794 // code to be execute within a RunLoop
3801 @param {Object} [target] target of method to call
3802 @param {Function|String} method Method to invoke.
3803 May be a function or a string. If you pass a string
3804 then it will be looked up on the passed target.
3805 @param {Object} [args*] Any additional arguments you wish to pass to the method.
3806 @return {Object} return value from invoking the passed function.
3808 Ember.run = function(target, method) {
3812 if (target || method) { ret = invoke(target, method, arguments, 2); }
3819 var run = Ember.run;
3823 Begins a new RunLoop. Any deferred actions invoked after the begin will
3824 be buffered until you invoke a matching call to Ember.run.end(). This is
3825 an lower-level way to use a RunLoop instead of using Ember.run().
3828 // code to be execute within a RunLoop
3834 Ember.run.begin = function() {
3835 run.currentRunLoop = new RunLoop(run.currentRunLoop);
3839 Ends a RunLoop. This must be called sometime after you call Ember.run.begin()
3840 to flush any deferred actions. This is a lower-level way to use a RunLoop
3841 instead of using Ember.run().
3844 // code to be execute within a RunLoop
3850 Ember.run.end = function() {
3851 Ember.assert('must have a current run loop', run.currentRunLoop);
3853 run.currentRunLoop.end();
3856 run.currentRunLoop = run.currentRunLoop.prev();
3861 Array of named queues. This array determines the order in which queues
3862 are flushed at the end of the RunLoop. You can define your own queues by
3863 simply adding the queue name to this array. Normally you should not need
3864 to inspect or modify this property.
3868 @default ['sync', 'actions', 'destroy', 'timers']
3870 Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
3873 Adds the passed target/method and any optional arguments to the named
3874 queue to be executed at the end of the RunLoop. If you have not already
3875 started a RunLoop when calling this method one will be started for you
3878 At the end of a RunLoop, any methods scheduled in this way will be invoked.
3879 Methods will be invoked in an order matching the named queues defined in
3880 the run.queues property.
3882 Ember.run.schedule('timers', this, function(){
3883 // this will be executed at the end of the RunLoop, when timers are run
3884 console.log("scheduled on timers queue");
3886 Ember.run.schedule('sync', this, function(){
3887 // this will be executed at the end of the RunLoop, when bindings are synced
3888 console.log("scheduled on sync queue");
3890 // Note the functions will be run in order based on the run queues order. Output would be:
3891 // scheduled on sync queue
3892 // scheduled on timers queue
3895 @param {String} queue The name of the queue to schedule against.
3896 Default queues are 'sync' and 'actions'
3898 @param {Object} [target] target object to use as the context when invoking a method.
3900 @param {String|Function} method The method to invoke. If you pass a string it
3901 will be resolved on the target object at the time the scheduled item is
3902 invoked allowing you to change the target function.
3904 @param {Object} [arguments*] Optional arguments to be passed to the queued method.
3908 Ember.run.schedule = function(queue, target, method) {
3909 var loop = run.autorun();
3910 loop.schedule.apply(loop, arguments);
3913 var scheduledAutorun;
3914 function autorun() {
3915 scheduledAutorun = null;
3916 if (run.currentRunLoop) { run.end(); }
3919 // Used by global test teardown
3920 Ember.run.hasScheduledTimers = function() {
3921 return !!(scheduledAutorun || scheduledLater || scheduledNext);
3924 // Used by global test teardown
3925 Ember.run.cancelTimers = function () {
3926 if (scheduledAutorun) {
3927 clearTimeout(scheduledAutorun);
3928 scheduledAutorun = null;
3930 if (scheduledLater) {
3931 clearTimeout(scheduledLater);
3932 scheduledLater = null;
3934 if (scheduledNext) {
3935 clearTimeout(scheduledNext);
3936 scheduledNext = null;
3942 Begins a new RunLoop if necessary and schedules a timer to flush the
3943 RunLoop at a later time. This method is used by parts of Ember to
3944 ensure the RunLoop always finishes. You normally do not need to call this
3945 method directly. Instead use Ember.run().
3950 Ember.run.autorun();
3951 @return {Ember.RunLoop} the new current RunLoop
3953 Ember.run.autorun = function() {
3954 if (!run.currentRunLoop) {
3955 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);
3959 if (!scheduledAutorun) {
3960 scheduledAutorun = setTimeout(autorun, 1);
3964 return run.currentRunLoop;
3968 Immediately flushes any events scheduled in the 'sync' queue. Bindings
3969 use this queue so this method is a useful way to immediately force all
3970 bindings in the application to sync.
3972 You should call this method anytime you need any changed state to propagate
3973 throughout the app immediately without repainting the UI.
3980 Ember.run.sync = function() {
3982 run.currentRunLoop.flush('sync');
3985 // ..........................................................
3989 var timers = {}; // active timers...
3992 function invokeLaterTimers() {
3993 scheduledLater = null;
3994 var now = (+ new Date()), earliest = -1;
3995 for (var key in timers) {
3996 if (!timers.hasOwnProperty(key)) { continue; }
3997 var timer = timers[key];
3998 if (timer && timer.expires) {
3999 if (now >= timer.expires) {
4001 invoke(timer.target, timer.method, timer.args, 2);
4003 if (earliest<0 || (timer.expires < earliest)) earliest=timer.expires;
4008 // schedule next timeout to fire...
4009 if (earliest > 0) { scheduledLater = setTimeout(invokeLaterTimers, earliest-(+ new Date())); }
4013 Invokes the passed target/method and optional arguments after a specified
4014 period if time. The last parameter of this method must always be a number
4017 You should use this method whenever you need to run some action after a
4018 period of time instead of using setTimeout(). This method will ensure that
4019 items that expire during the same script execution cycle all execute
4020 together, which is often more efficient than using a real setTimeout.
4022 Ember.run.later(myContext, function(){
4023 // code here will execute within a RunLoop in about 500ms with this == myContext
4027 @param {Object} [target] target of method to invoke
4029 @param {Function|String} method The method to invoke.
4030 If you pass a string it will be resolved on the
4031 target at the time the method is invoked.
4033 @param {Object} [args*] Optional arguments to pass to the timeout.
4035 @param {Number} wait
4036 Number of milliseconds to wait.
4038 @return {String} a string you can use to cancel the timer in
4039 {{#crossLink "Ember/run.cancel"}}{{/crossLink}} later.
4041 Ember.run.later = function(target, method) {
4042 var args, expires, timer, guid, wait;
4044 // setTimeout compatibility...
4045 if (arguments.length===2 && 'function' === typeof target) {
4049 args = [target, method];
4051 args = slice.call(arguments);
4055 expires = (+ new Date()) + wait;
4056 timer = { target: target, method: method, expires: expires, args: args };
4057 guid = Ember.guidFor(timer);
4058 timers[guid] = timer;
4059 run.once(timers, invokeLaterTimers);
4063 function invokeOnceTimer(guid, onceTimers) {
4064 if (onceTimers[this.tguid]) { delete onceTimers[this.tguid][this.mguid]; }
4065 if (timers[guid]) { invoke(this.target, this.method, this.args); }
4066 delete timers[guid];
4069 function scheduleOnce(queue, target, method, args) {
4070 var tguid = Ember.guidFor(target),
4071 mguid = Ember.guidFor(method),
4072 onceTimers = run.autorun().onceTimers,
4073 guid = onceTimers[tguid] && onceTimers[tguid][mguid],
4076 if (guid && timers[guid]) {
4077 timers[guid].args = args; // replace args
4087 guid = Ember.guidFor(timer);
4088 timers[guid] = timer;
4089 if (!onceTimers[tguid]) { onceTimers[tguid] = {}; }
4090 onceTimers[tguid][mguid] = guid; // so it isn't scheduled more than once
4092 run.schedule(queue, timer, invokeOnceTimer, guid, onceTimers);
4099 Schedules an item to run one time during the current RunLoop. Calling
4100 this method with the same target/method combination will have no effect.
4102 Note that although you can pass optional arguments these will not be
4103 considered when looking for duplicates. New arguments will replace previous
4106 Ember.run(function(){
4107 var doFoo = function() { foo(); }
4108 Ember.run.once(myContext, doFoo);
4109 Ember.run.once(myContext, doFoo);
4110 // doFoo will only be executed once at the end of the RunLoop
4114 @param {Object} [target] target of method to invoke
4116 @param {Function|String} method The method to invoke.
4117 If you pass a string it will be resolved on the
4118 target at the time the method is invoked.
4120 @param {Object} [args*] Optional arguments to pass to the timeout.
4123 @return {Object} timer
4125 Ember.run.once = function(target, method) {
4126 return scheduleOnce('actions', target, method, slice.call(arguments, 2));
4129 Ember.run.scheduleOnce = function(queue, target, method, args) {
4130 return scheduleOnce(queue, target, method, slice.call(arguments, 3));
4134 function invokeNextTimers() {
4135 scheduledNext = null;
4136 for(var key in timers) {
4137 if (!timers.hasOwnProperty(key)) { continue; }
4138 var timer = timers[key];
4141 invoke(timer.target, timer.method, timer.args, 2);
4147 Schedules an item to run after control has been returned to the system.
4148 This is often equivalent to calling setTimeout(function...,1).
4150 Ember.run.next(myContext, function(){
4151 // code to be executed in the next RunLoop, which will be scheduled after the current one
4155 @param {Object} [target] target of method to invoke
4157 @param {Function|String} method The method to invoke.
4158 If you pass a string it will be resolved on the
4159 target at the time the method is invoked.
4161 @param {Object} [args*] Optional arguments to pass to the timeout.
4163 @return {Object} timer
4165 Ember.run.next = function(target, method) {
4170 args: slice.call(arguments),
4174 guid = Ember.guidFor(timer);
4175 timers[guid] = timer;
4177 if (!scheduledNext) { scheduledNext = setTimeout(invokeNextTimers, 1); }
4182 Cancels a scheduled item. Must be a value returned by `Ember.run.later()`,
4183 `Ember.run.once()`, or `Ember.run.next()`.
4185 var runNext = Ember.run.next(myContext, function(){
4186 // will not be executed
4188 Ember.run.cancel(runNext);
4190 var runLater = Ember.run.later(myContext, function(){
4191 // will not be executed
4193 Ember.run.cancel(runLater);
4195 var runOnce = Ember.run.once(myContext, function(){
4196 // will not be executed
4198 Ember.run.cancel(runOnce);
4201 @param {Object} timer Timer object to cancel
4204 Ember.run.cancel = function(timer) {
4205 delete timers[timer];
4215 // guidFor, isArray, meta
4216 // addObserver, removeObserver
4217 // Ember.run.schedule
4222 // ..........................................................
4227 Debug parameter you can turn on. This will log all bindings that fire to
4228 the console. This should be disabled in production code. Note that you
4229 can also enable this from the console or temporarily.
4231 @property LOG_BINDINGS
4236 Ember.LOG_BINDINGS = false || !!Ember.ENV.LOG_BINDINGS;
4238 var get = Ember.get,
4240 guidFor = Ember.guidFor,
4241 isGlobalPath = Ember.isGlobalPath;
4244 function getWithGlobals(obj, path) {
4245 return get(isGlobalPath(path) ? window : obj, path);
4248 // ..........................................................
4252 var Binding = function(toPath, fromPath) {
4253 this._direction = 'fwd';
4254 this._from = fromPath;
4256 this._directionMap = Ember.Map.create();
4264 Binding.prototype = {
4266 This copies the Binding so it can be connected to another object.
4269 @return {Ember.Binding}
4272 var copy = new Binding(this._to, this._from);
4273 if (this._oneWay) { copy._oneWay = true; }
4277 // ..........................................................
4282 This will set "from" property path to the specified value. It will not
4283 attempt to resolve this property path to an actual object until you
4284 connect the binding.
4286 The binding will search for the property path starting at the root object
4287 you pass when you connect() the binding. It follows the same rules as
4288 `get()` - see that method for more information.
4291 @param {String} propertyPath the property path to connect to
4292 @return {Ember.Binding} receiver
4294 from: function(path) {
4300 This will set the "to" property path to the specified value. It will not
4301 attempt to resolve this property path to an actual object until you
4302 connect the binding.
4304 The binding will search for the property path starting at the root object
4305 you pass when you connect() the binding. It follows the same rules as
4306 `get()` - see that method for more information.
4309 @param {String|Tuple} propertyPath A property path or tuple
4310 @return {Ember.Binding} this
4312 to: function(path) {
4318 Configures the binding as one way. A one-way binding will relay changes
4319 on the "from" side to the "to" side, but not the other way around. This
4320 means that if you change the "to" side directly, the "from" side may have
4324 @return {Ember.Binding} receiver
4326 oneWay: function() {
4327 this._oneWay = true;
4331 toString: function() {
4332 var oneWay = this._oneWay ? '[oneWay]' : '';
4333 return "Ember.Binding<" + guidFor(this) + ">(" + this._from + " -> " + this._to + ")" + oneWay;
4336 // ..........................................................
4341 Attempts to connect this binding instance so that it can receive and relay
4342 changes. This method will raise an exception if you have not set the
4343 from/to properties yet.
4346 @param {Object} obj The root object for this binding.
4347 @return {Ember.Binding} this
4349 connect: function(obj) {
4350 Ember.assert('Must pass a valid object to Ember.Binding.connect()', !!obj);
4352 var fromPath = this._from, toPath = this._to;
4353 Ember.trySet(obj, toPath, getWithGlobals(obj, fromPath));
4355 // add an observer on the object to be notified when the binding should be updated
4356 Ember.addObserver(obj, fromPath, this, this.fromDidChange);
4358 // if the binding is a two-way binding, also set up an observer on the target
4359 if (!this._oneWay) { Ember.addObserver(obj, toPath, this, this.toDidChange); }
4361 this._readyToSync = true;
4367 Disconnects the binding instance. Changes will no longer be relayed. You
4368 will not usually need to call this method.
4371 @param {Object} obj The root object you passed when connecting the binding.
4372 @return {Ember.Binding} this
4374 disconnect: function(obj) {
4375 Ember.assert('Must pass a valid object to Ember.Binding.disconnect()', !!obj);
4377 var twoWay = !this._oneWay;
4379 // remove an observer on the object so we're no longer notified of
4380 // changes that should update bindings.
4381 Ember.removeObserver(obj, this._from, this, this.fromDidChange);
4383 // if the binding is two-way, remove the observer from the target as well
4384 if (twoWay) { Ember.removeObserver(obj, this._to, this, this.toDidChange); }
4386 this._readyToSync = false; // disable scheduled syncs...
4390 // ..........................................................
4394 /* called when the from side changes */
4395 fromDidChange: function(target) {
4396 this._scheduleSync(target, 'fwd');
4399 /* called when the to side changes */
4400 toDidChange: function(target) {
4401 this._scheduleSync(target, 'back');
4404 _scheduleSync: function(obj, dir) {
4405 var directionMap = this._directionMap;
4406 var existingDir = directionMap.get(obj);
4408 // if we haven't scheduled the binding yet, schedule it
4410 Ember.run.schedule('sync', this, this._sync, obj);
4411 directionMap.set(obj, dir);
4414 // If both a 'back' and 'fwd' sync have been scheduled on the same object,
4415 // default to a 'fwd' sync so that it remains deterministic.
4416 if (existingDir === 'back' && dir === 'fwd') {
4417 directionMap.set(obj, 'fwd');
4421 _sync: function(obj) {
4422 var log = Ember.LOG_BINDINGS;
4424 // don't synchronize destroyed objects or disconnected bindings
4425 if (obj.isDestroyed || !this._readyToSync) { return; }
4427 // get the direction of the binding for the object we are
4428 // synchronizing from
4429 var directionMap = this._directionMap;
4430 var direction = directionMap.get(obj);
4432 var fromPath = this._from, toPath = this._to;
4434 directionMap.remove(obj);
4436 // if we're synchronizing from the remote object...
4437 if (direction === 'fwd') {
4438 var fromValue = getWithGlobals(obj, this._from);
4440 Ember.Logger.log(' ', this.toString(), '->', fromValue, obj);
4443 Ember.trySet(obj, toPath, fromValue);
4445 Ember._suspendObserver(obj, toPath, this, this.toDidChange, function () {
4446 Ember.trySet(obj, toPath, fromValue);
4449 // if we're synchronizing *to* the remote object
4450 } else if (direction === 'back') {
4451 var toValue = get(obj, this._to);
4453 Ember.Logger.log(' ', this.toString(), '<-', toValue, obj);
4455 Ember._suspendObserver(obj, fromPath, this, this.fromDidChange, function () {
4456 Ember.trySet(Ember.isGlobalPath(fromPath) ? window : obj, fromPath, toValue);
4463 function mixinProperties(to, from) {
4464 for (var key in from) {
4465 if (from.hasOwnProperty(key)) {
4466 to[key] = from[key];
4471 mixinProperties(Binding, {
4474 See {{#crossLink "Ember.Binding/from"}}{{/crossLink}}
4480 var C = this, binding = new C();
4481 return binding.from.apply(binding, arguments);
4485 See {{#crossLink "Ember.Binding/to"}}{{/crossLink}}
4491 var C = this, binding = new C();
4492 return binding.to.apply(binding, arguments);
4496 Creates a new Binding instance and makes it apply in a single direction.
4497 A one-way binding will relay changes on the "from" side object (supplied
4498 as the `from` argument) the "to" side, but not the other way around.
4499 This means that if you change the "to" side directly, the "from" side may have
4502 See {{#crossLink "Binding/oneWay"}}{{/crossLink}}
4505 @param {String} from from path.
4506 @param {Boolean} [flag] (Optional) passing nothing here will make the binding oneWay. You can
4507 instead pass false to disable oneWay, making the binding two way again.
4509 oneWay: function(from, flag) {
4510 var C = this, binding = new C(null, from);
4511 return binding.oneWay(flag);
4517 An Ember.Binding connects the properties of two objects so that whenever the
4518 value of one property changes, the other property will be changed also.
4520 ## Automatic Creation of Bindings with `/^*Binding/`-named Properties
4521 You do not usually create Binding objects directly but instead describe
4522 bindings in your class or object definition using automatic binding detection.
4524 Properties ending in a `Binding` suffix will be converted to Ember.Binding instances.
4525 The value of this property should be a string representing a path to another object or
4526 a custom binding instanced created using Binding helpers (see "Customizing Your Bindings"):
4528 valueBinding: "MyApp.someController.title"
4530 This will create a binding from `MyApp.someController.title` to the `value`
4531 property of your object instance automatically. Now the two values will be
4536 One especially useful binding customization you can use is the `oneWay()`
4537 helper. This helper tells Ember that you are only interested in
4538 receiving changes on the object you are binding from. For example, if you
4539 are binding to a preference and you want to be notified if the preference
4540 has changed, but your object will not be changing the preference itself, you
4543 bigTitlesBinding: Ember.Binding.oneWay("MyApp.preferencesController.bigTitles")
4545 This way if the value of MyApp.preferencesController.bigTitles changes the
4546 "bigTitles" property of your object will change also. However, if you
4547 change the value of your "bigTitles" property, it will not update the
4548 preferencesController.
4550 One way bindings are almost twice as fast to setup and twice as fast to
4551 execute because the binding only has to worry about changes to one side.
4553 You should consider using one way bindings anytime you have an object that
4554 may be created frequently and you do not intend to change a property; only
4555 to monitor it for changes. (such as in the example above).
4557 ## Adding Bindings Manually
4559 All of the examples above show you how to configure a custom binding, but
4560 the result of these customizations will be a binding template, not a fully
4561 active Binding instance. The binding will actually become active only when you
4562 instantiate the object the binding belongs to. It is useful however, to
4563 understand what actually happens when the binding is activated.
4565 For a binding to function it must have at least a "from" property and a "to"
4566 property. The from property path points to the object/key that you want to
4567 bind from while the to path points to the object/key you want to bind to.
4569 When you define a custom binding, you are usually describing the property
4570 you want to bind from (such as "MyApp.someController.value" in the examples
4571 above). When your object is created, it will automatically assign the value
4572 you want to bind "to" based on the name of your binding key. In the
4573 examples above, during init, Ember objects will effectively call
4574 something like this on your binding:
4576 binding = Ember.Binding.from(this.valueBinding).to("value");
4578 This creates a new binding instance based on the template you provide, and
4579 sets the to path to the "value" property of the new object. Now that the
4580 binding is fully configured with a "from" and a "to", it simply needs to be
4581 connected to become active. This is done through the connect() method:
4583 binding.connect(this);
4585 Note that when you connect a binding you pass the object you want it to be
4586 connected to. This object will be used as the root for both the from and
4587 to side of the binding when inspecting relative paths. This allows the
4588 binding to be automatically inherited by subclassed objects as well.
4590 Now that the binding is connected, it will observe both the from and to side
4593 If you ever needed to do so (you almost never will, but it is useful to
4594 understand this anyway), you could manually create an active binding by
4595 using the Ember.bind() helper method. (This is the same method used by
4596 to setup your bindings on objects):
4598 Ember.bind(MyApp.anotherObject, "value", "MyApp.someController.value");
4600 Both of these code fragments have the same effect as doing the most friendly
4601 form of binding creation like so:
4603 MyApp.anotherObject = Ember.Object.create({
4604 valueBinding: "MyApp.someController.value",
4606 // OTHER CODE FOR THIS OBJECT...
4610 Ember's built in binding creation method makes it easy to automatically
4611 create bindings for you. You should always use the highest-level APIs
4612 available, even if you understand how it works underneath.
4618 Ember.Binding = Binding;
4622 Global helper method to create a new binding. Just pass the root object
4623 along with a to and from path to create and connect the binding.
4627 @param {Object} obj The root object of the transform.
4629 @param {String} to The path to the 'to' side of the binding.
4630 Must be relative to obj.
4632 @param {String} from The path to the 'from' side of the binding.
4633 Must be relative to obj or a global path.
4635 @return {Ember.Binding} binding instance
4637 Ember.bind = function(obj, to, from) {
4638 return new Ember.Binding(to, from).connect(obj);
4644 @param {Object} obj The root object of the transform.
4646 @param {String} to The path to the 'to' side of the binding.
4647 Must be relative to obj.
4649 @param {String} from The path to the 'from' side of the binding.
4650 Must be relative to obj or a global path.
4652 @return {Ember.Binding} binding instance
4654 Ember.oneWay = function(obj, to, from) {
4655 return new Ember.Binding(to, from).oneWay().connect(obj);
4667 var Mixin, REQUIRED, Alias,
4668 classToString, superClassString,
4669 a_map = Ember.ArrayPolyfills.map,
4670 a_indexOf = Ember.ArrayPolyfills.indexOf,
4671 a_forEach = Ember.ArrayPolyfills.forEach,
4673 EMPTY_META = {}, // dummy for non-writable meta
4674 META_SKIP = { __emberproto__: true, __ember_count__: true },
4675 o_create = Ember.create,
4676 defineProperty = Ember.defineProperty,
4677 guidFor = Ember.guidFor;
4679 function mixinsMeta(obj) {
4680 var m = Ember.meta(obj, true), ret = m.mixins;
4682 ret = m.mixins = { __emberproto__: obj };
4683 } else if (ret.__emberproto__ !== obj) {
4684 ret = m.mixins = o_create(ret);
4685 ret.__emberproto__ = obj;
4690 function initMixin(mixin, args) {
4691 if (args && args.length > 0) {
4692 mixin.mixins = a_map.call(args, function(x) {
4693 if (x instanceof Mixin) { return x; }
4695 // Note: Manually setup a primitive mixin here. This is the only
4696 // way to actually get a primitive mixin. This way normal creation
4697 // of mixins will give you combined mixins...
4698 var mixin = new Mixin();
4699 mixin.properties = x;
4706 function isMethod(obj) {
4707 return 'function' === typeof obj &&
4708 obj.isMethod !== false &&
4709 obj !== Boolean && obj !== Object && obj !== Number && obj !== Array && obj !== Date && obj !== String;
4712 function mergeMixins(mixins, m, descs, values, base) {
4713 var len = mixins.length, idx, mixin, guid, props, value, key, ovalue, concats;
4715 function removeKeys(keyName) {
4716 delete descs[keyName];
4717 delete values[keyName];
4720 for(idx=0; idx < len; idx++) {
4721 mixin = mixins[idx];
4722 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]');
4724 if (mixin instanceof Mixin) {
4725 guid = guidFor(mixin);
4726 if (m[guid]) { continue; }
4728 props = mixin.properties;
4730 props = mixin; // apply anonymous mixin properties
4734 // reset before adding each new mixin to pickup concats from previous
4735 concats = values.concatenatedProperties || base.concatenatedProperties;
4736 if (props.concatenatedProperties) {
4737 concats = concats ? concats.concat(props.concatenatedProperties) : props.concatenatedProperties;
4740 for (key in props) {
4741 if (!props.hasOwnProperty(key)) { continue; }
4743 if (value instanceof Ember.Descriptor) {
4744 if (value === REQUIRED && descs[key]) { continue; }
4747 values[key] = undefined;
4749 // impl super if needed...
4750 if (isMethod(value)) {
4751 ovalue = descs[key] === undefined && values[key];
4752 if (!ovalue) { ovalue = base[key]; }
4753 if ('function' !== typeof ovalue) { ovalue = null; }
4755 var o = value.__ember_observes__, ob = value.__ember_observesBefore__;
4756 value = Ember.wrap(value, ovalue);
4757 value.__ember_observes__ = o;
4758 value.__ember_observesBefore__ = ob;
4760 } else if ((concats && a_indexOf.call(concats, key) >= 0) || key === 'concatenatedProperties') {
4761 var baseValue = values[key] || base[key];
4762 value = baseValue ? baseValue.concat(value) : Ember.makeArray(value);
4765 descs[key] = undefined;
4766 values[key] = value;
4770 // manually copy toString() because some JS engines do not enumerate it
4771 if (props.hasOwnProperty('toString')) {
4772 base.toString = props.toString;
4775 } else if (mixin.mixins) {
4776 mergeMixins(mixin.mixins, m, descs, values, base);
4777 if (mixin._without) { a_forEach.call(mixin._without, removeKeys); }
4782 function writableReq(obj) {
4783 var m = Ember.meta(obj), req = m.required;
4784 if (!req || req.__emberproto__ !== obj) {
4785 req = m.required = req ? o_create(req) : { __ember_count__: 0 };
4786 req.__emberproto__ = obj;
4791 var IS_BINDING = Ember.IS_BINDING = /^.+Binding$/;
4793 function detectBinding(obj, key, value, m) {
4794 if (IS_BINDING.test(key)) {
4795 var bindings = m.bindings;
4797 bindings = m.bindings = { __emberproto__: obj };
4798 } else if (bindings.__emberproto__ !== obj) {
4799 bindings = m.bindings = o_create(m.bindings);
4800 bindings.__emberproto__ = obj;
4802 bindings[key] = value;
4806 function connectBindings(obj, m) {
4807 // TODO Mixin.apply(instance) should disconnect binding if exists
4808 var bindings = m.bindings, key, binding, to;
4810 for (key in bindings) {
4811 binding = key !== '__emberproto__' && bindings[key];
4813 to = key.slice(0, -7); // strip Binding off end
4814 if (binding instanceof Ember.Binding) {
4815 binding = binding.copy(); // copy prototypes' instance
4817 } else { // binding is string path
4818 binding = new Ember.Binding(to, binding);
4820 binding.connect(obj);
4825 m.bindings = { __emberproto__: obj };
4829 function finishPartial(obj, m) {
4830 connectBindings(obj, m || Ember.meta(obj));
4834 function applyMixin(obj, mixins, partial) {
4835 var descs = {}, values = {}, m = Ember.meta(obj), req = m.required,
4836 key, value, desc, prevValue, paths, len, idx;
4838 // Go through all mixins and hashes passed in, and:
4840 // * Handle concatenated properties
4841 // * Set up _super wrapping if necessary
4842 // * Set up computed property descriptors
4843 // * Copying `toString` in broken browsers
4844 mergeMixins(mixins, mixinsMeta(obj), descs, values, obj);
4846 for(key in values) {
4847 if (key === 'contructor') { continue; }
4848 if (!values.hasOwnProperty(key)) { continue; }
4851 value = values[key];
4853 if (desc === REQUIRED) {
4854 if (!(key in obj)) {
4855 Ember.assert('Required property not defined: '+key, !!partial);
4857 // for partial applies add to hash of required keys
4858 req = writableReq(obj);
4859 req.__ember_count__++;
4863 while (desc && desc instanceof Alias) {
4864 var altKey = desc.methodName;
4865 if (descs[altKey] || values[altKey]) {
4866 value = values[altKey];
4867 desc = descs[altKey];
4868 } else if (m.descs[altKey]) {
4869 desc = m.descs[altKey];
4873 value = obj[altKey];
4877 if (desc === undefined && value === undefined) { continue; }
4879 prevValue = obj[key];
4881 if ('function' === typeof prevValue) {
4882 if ((paths = prevValue.__ember_observesBefore__)) {
4884 for (idx=0; idx < len; idx++) {
4885 Ember.removeBeforeObserver(obj, paths[idx], null, key);
4887 } else if ((paths = prevValue.__ember_observes__)) {
4889 for (idx=0; idx < len; idx++) {
4890 Ember.removeObserver(obj, paths[idx], null, key);
4895 detectBinding(obj, key, value, m);
4897 defineProperty(obj, key, desc, value, m);
4899 if ('function' === typeof value) {
4900 if (paths = value.__ember_observesBefore__) {
4902 for (idx=0; idx < len; idx++) {
4903 Ember.addBeforeObserver(obj, paths[idx], null, key);
4905 } else if (paths = value.__ember_observes__) {
4907 for (idx=0; idx < len; idx++) {
4908 Ember.addObserver(obj, paths[idx], null, key);
4913 if (req && req[key]) {
4914 req = writableReq(obj);
4915 req.__ember_count__--;
4921 if (!partial) { // don't apply to prototype
4922 finishPartial(obj, m);
4925 // Make sure no required attrs remain
4926 if (!partial && req && req.__ember_count__>0) {
4929 if (META_SKIP[key]) { continue; }
4932 // TODO: Remove surrounding if clause from production build
4933 Ember.assert('Required properties not defined: '+keys.join(','));
4945 Ember.mixin = function(obj) {
4946 var args = a_slice.call(arguments, 1);
4947 applyMixin(obj, args, false);
4952 The `Ember.Mixin` class allows you to create mixins, whose properties can be
4953 added to other classes. For instance,
4955 App.Editable = Ember.Mixin.create({
4957 console.log('starting to edit');
4958 this.set('isEditing', true);
4963 // Mix mixins into classes by passing them as the first arguments to
4964 // .extend or .create.
4965 App.CommentView = Ember.View.extend(App.Editable, {
4966 template: Ember.Handlebars.compile('{{#if isEditing}}...{{else}}...{{/if}}')
4969 commentView = App.CommentView.create();
4970 commentView.edit(); // => outputs 'starting to edit'
4972 Note that Mixins are created with `Ember.Mixin.create`, not
4973 `Ember.Mixin.extend`.
4978 Ember.Mixin = function() { return initMixin(this, arguments); };
4980 Mixin = Ember.Mixin;
4982 Mixin._apply = applyMixin;
4984 Mixin.applyPartial = function(obj) {
4985 var args = a_slice.call(arguments, 1);
4986 return applyMixin(obj, args, true);
4989 Mixin.finishPartial = finishPartial;
4996 Mixin.create = function() {
4997 classToString.processed = false;
4999 return initMixin(new M(), arguments);
5002 var MixinPrototype = Mixin.prototype;
5008 MixinPrototype.reopen = function() {
5011 if (this.properties) {
5012 mixin = Mixin.create();
5013 mixin.properties = this.properties;
5014 delete this.properties;
5015 this.mixins = [mixin];
5016 } else if (!this.mixins) {
5020 var len = arguments.length, mixins = this.mixins, idx;
5022 for(idx=0; idx < len; idx++) {
5023 mixin = arguments[idx];
5024 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]');
5026 if (mixin instanceof Mixin) {
5029 tmp = Mixin.create();
5030 tmp.properties = mixin;
5041 @return applied object
5043 MixinPrototype.apply = function(obj) {
5044 return applyMixin(obj, [this], false);
5047 MixinPrototype.applyPartial = function(obj) {
5048 return applyMixin(obj, [this], true);
5051 function _detect(curMixin, targetMixin, seen) {
5052 var guid = guidFor(curMixin);
5054 if (seen[guid]) { return false; }
5057 if (curMixin === targetMixin) { return true; }
5058 var mixins = curMixin.mixins, loc = mixins ? mixins.length : 0;
5059 while (--loc >= 0) {
5060 if (_detect(mixins[loc], targetMixin, seen)) { return true; }
5070 MixinPrototype.detect = function(obj) {
5071 if (!obj) { return false; }
5072 if (obj instanceof Mixin) { return _detect(obj, this, {}); }
5073 var mixins = Ember.meta(obj, false).mixins;
5075 return !!mixins[guidFor(this)];
5080 MixinPrototype.without = function() {
5081 var ret = new Mixin(this);
5082 ret._without = a_slice.call(arguments);
5086 function _keys(ret, mixin, seen) {
5087 if (seen[guidFor(mixin)]) { return; }
5088 seen[guidFor(mixin)] = true;
5090 if (mixin.properties) {
5091 var props = mixin.properties;
5092 for (var key in props) {
5093 if (props.hasOwnProperty(key)) { ret[key] = true; }
5095 } else if (mixin.mixins) {
5096 a_forEach.call(mixin.mixins, function(x) { _keys(ret, x, seen); });
5100 MixinPrototype.keys = function() {
5101 var keys = {}, seen = {}, ret = [];
5102 _keys(keys, this, seen);
5103 for(var key in keys) {
5104 if (keys.hasOwnProperty(key)) { ret.push(key); }
5109 /* make Mixins have nice displayNames */
5111 var NAME_KEY = Ember.GUID_KEY+'_name';
5112 var get = Ember.get;
5114 function processNames(paths, root, seen) {
5115 var idx = paths.length;
5116 for(var key in root) {
5117 if (!root.hasOwnProperty || !root.hasOwnProperty(key)) { continue; }
5118 var obj = root[key];
5121 if (obj && obj.toString === classToString) {
5122 obj[NAME_KEY] = paths.join('.');
5123 } else if (obj && get(obj, 'isNamespace')) {
5124 if (seen[guidFor(obj)]) { continue; }
5125 seen[guidFor(obj)] = true;
5126 processNames(paths, obj, seen);
5129 paths.length = idx; // cut out last item
5132 function findNamespaces() {
5133 var Namespace = Ember.Namespace, obj, isNamespace;
5135 if (Namespace.PROCESSED) { return; }
5137 for (var prop in window) {
5138 // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox.
5139 // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage
5140 if (prop === "globalStorage" && window.StorageList && window.globalStorage instanceof window.StorageList) { continue; }
5141 // Unfortunately, some versions of IE don't support window.hasOwnProperty
5142 if (window.hasOwnProperty && !window.hasOwnProperty(prop)) { continue; }
5144 // At times we are not allowed to access certain properties for security reasons.
5145 // There are also times where even if we can access them, we are not allowed to access their properties.
5148 isNamespace = obj && get(obj, 'isNamespace');
5154 Ember.deprecate("Namespaces should not begin with lowercase.", /^[A-Z]/.test(prop));
5155 obj[NAME_KEY] = prop;
5162 @method identifyNamespaces
5165 Ember.identifyNamespaces = findNamespaces;
5167 superClassString = function(mixin) {
5168 var superclass = mixin.superclass;
5170 if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; }
5171 else { return superClassString(superclass); }
5177 classToString = function() {
5178 var Namespace = Ember.Namespace, namespace;
5180 // TODO: Namespace should really be in Metal
5182 if (!this[NAME_KEY] && !classToString.processed) {
5183 if (!Namespace.PROCESSED) {
5185 Namespace.PROCESSED = true;
5188 classToString.processed = true;
5190 var namespaces = Namespace.NAMESPACES;
5191 for (var i=0, l=namespaces.length; i<l; i++) {
5192 namespace = namespaces[i];
5193 processNames([namespace.toString()], namespace, {});
5198 if (this[NAME_KEY]) {
5199 return this[NAME_KEY];
5201 var str = superClassString(this);
5203 return "(subclass of " + str + ")";
5205 return "(unknown mixin)";
5210 MixinPrototype.toString = classToString;
5212 // returns the mixins currently applied to the specified object
5213 // TODO: Make Ember.mixin
5214 Mixin.mixins = function(obj) {
5215 var ret = [], mixins = Ember.meta(obj, false).mixins, key, mixin;
5217 for(key in mixins) {
5218 if (META_SKIP[key]) { continue; }
5219 mixin = mixins[key];
5221 // skip primitive mixins since these are always anonymous
5222 if (!mixin.properties) { ret.push(mixins[key]); }
5228 REQUIRED = new Ember.Descriptor();
5229 REQUIRED.toString = function() { return '(Required Property)'; };
5232 Denotes a required property for a mixin
5237 Ember.required = function() {
5241 Alias = function(methodName) {
5242 this.methodName = methodName;
5244 Alias.prototype = new Ember.Descriptor();
5247 Makes a property or method available via an additional name.
5249 App.PaintSample = Ember.Object.extend({
5251 colour: Ember.alias('color'),
5255 moniker: Ember.alias("name")
5257 var paintSample = App.PaintSample.create()
5258 paintSample.get('colour'); //=> 'red'
5259 paintSample.moniker(); //=> 'Zed'
5263 @param {String} methodName name of the method or property to alias
5264 @return {Ember.Descriptor}
5266 Ember.alias = function(methodName) {
5267 return new Alias(methodName);
5270 // ..........................................................
5277 @param {Function} func
5278 @param {String} propertyNames*
5281 Ember.observer = function(func) {
5282 var paths = a_slice.call(arguments, 1);
5283 func.__ember_observes__ = paths;
5287 // If observers ever become asynchronous, Ember.immediateObserver
5288 // must remain synchronous.
5290 @method immediateObserver
5292 @param {Function} func
5293 @param {String} propertyNames*
5296 Ember.immediateObserver = function() {
5297 for (var i=0, l=arguments.length; i<l; i++) {
5298 var arg = arguments[i];
5299 Ember.assert("Immediate observers must observe internal properties only, not properties on other objects.", typeof arg !== "string" || arg.indexOf('.') === -1);
5302 return Ember.observer.apply(this, arguments);
5306 @method beforeObserver
5308 @param {Function} func
5309 @param {String} propertyNames*
5312 Ember.beforeObserver = function(func) {
5313 var paths = a_slice.call(arguments, 1);
5314 func.__ember_observesBefore__ = paths;
5327 @submodule ember-metal
5336 @submodule ember-runtime
5339 var indexOf = Ember.EnumerableUtils.indexOf;
5341 // ........................................
5342 // TYPING & ARRAY MESSAGING
5346 var t = "Boolean Number String Function Array Date RegExp Object".split(" ");
5347 Ember.ArrayPolyfills.forEach.call(t, function(name) {
5348 TYPE_MAP[ "[object " + name + "]" ] = name.toLowerCase();
5351 var toString = Object.prototype.toString;
5354 Returns a consistent type for the passed item.
5356 Use this instead of the built-in `typeof` to get the type of an item.
5357 It will return the same result across all browsers and includes a bit
5358 more detail. Here is what will be returned:
5360 | Return Value | Meaning |
5361 |---------------|------------------------------------------------------|
5362 | 'string' | String primitive |
5363 | 'number' | Number primitive |
5364 | 'boolean' | Boolean primitive |
5365 | 'null' | Null value |
5366 | 'undefined' | Undefined value |
5367 | 'function' | A function |
5368 | 'array' | An instance of Array |
5369 | 'class' | A Ember class (created using Ember.Object.extend()) |
5370 | 'instance' | A Ember object instance |
5371 | 'error' | An instance of the Error object |
5372 | 'object' | A JavaScript object not inheriting from Ember.Object |
5376 Ember.typeOf(); => 'undefined'
5377 Ember.typeOf(null); => 'null'
5378 Ember.typeOf(undefined); => 'undefined'
5379 Ember.typeOf('michael'); => 'string'
5380 Ember.typeOf(101); => 'number'
5381 Ember.typeOf(true); => 'boolean'
5382 Ember.typeOf(Ember.makeArray); => 'function'
5383 Ember.typeOf([1,2,90]); => 'array'
5384 Ember.typeOf(Ember.Object.extend()); => 'class'
5385 Ember.typeOf(Ember.Object.create()); => 'instance'
5386 Ember.typeOf(new Error('teamocil')); => 'error'
5388 // "normal" JavaScript object
5389 Ember.typeOf({a: 'b'}); => 'object'
5393 @param item {Object} the item to check
5394 @return {String} the type
5396 Ember.typeOf = function(item) {
5399 ret = (item === null || item === undefined) ? String(item) : TYPE_MAP[toString.call(item)] || 'object';
5401 if (ret === 'function') {
5402 if (Ember.Object && Ember.Object.detect(item)) ret = 'class';
5403 } else if (ret === 'object') {
5404 if (item instanceof Error) ret = 'error';
5405 else if (Ember.Object && item instanceof Ember.Object) ret = 'instance';
5406 else ret = 'object';
5413 Returns true if the passed value is null or undefined. This avoids errors
5414 from JSLint complaining about use of ==, which can be technically
5417 Ember.none(); => true
5418 Ember.none(null); => true
5419 Ember.none(undefined); => true
5420 Ember.none(''); => false
5421 Ember.none([]); => false
5422 Ember.none(function(){}); => false
5426 @param {Object} obj Value to test
5429 Ember.none = function(obj) {
5430 return obj === null || obj === undefined;
5434 Verifies that a value is null or an empty string | array | function.
5436 Constrains the rules on `Ember.none` by returning false for empty
5437 string and empty arrays.
5439 Ember.empty(); => true
5440 Ember.empty(null); => true
5441 Ember.empty(undefined); => true
5442 Ember.empty(''); => true
5443 Ember.empty([]); => true
5444 Ember.empty('tobias fünke'); => false
5445 Ember.empty([0,1,2]); => false
5449 @param {Object} obj Value to test
5452 Ember.empty = function(obj) {
5453 return obj === null || obj === undefined || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0);
5457 This will compare two javascript values of possibly different types.
5458 It will tell you which one is greater than the other by returning:
5460 - -1 if the first is smaller than the second,
5461 - 0 if both are equal,
5462 - 1 if the first is greater than the second.
5464 The order is calculated based on Ember.ORDER_DEFINITION, if types are different.
5465 In case they have the same type an appropriate comparison for this type is made.
5467 Ember.compare('hello', 'hello'); => 0
5468 Ember.compare('abc', 'dfg'); => -1
5469 Ember.compare(2, 1); => 1
5473 @param {Object} v First value to compare
5474 @param {Object} w Second value to compare
5475 @return {Number} -1 if v < w, 0 if v = w and 1 if v > w.
5477 Ember.compare = function compare(v, w) {
5478 if (v === w) { return 0; }
5480 var type1 = Ember.typeOf(v);
5481 var type2 = Ember.typeOf(w);
5483 var Comparable = Ember.Comparable;
5485 if (type1==='instance' && Comparable.detect(v.constructor)) {
5486 return v.constructor.compare(v, w);
5489 if (type2 === 'instance' && Comparable.detect(w.constructor)) {
5490 return 1-w.constructor.compare(w, v);
5494 // If we haven't yet generated a reverse-mapping of Ember.ORDER_DEFINITION,
5496 var mapping = Ember.ORDER_DEFINITION_MAPPING;
5498 var order = Ember.ORDER_DEFINITION;
5499 mapping = Ember.ORDER_DEFINITION_MAPPING = {};
5501 for (idx = 0, len = order.length; idx < len; ++idx) {
5502 mapping[order[idx]] = idx;
5505 // We no longer need Ember.ORDER_DEFINITION.
5506 delete Ember.ORDER_DEFINITION;
5509 var type1Index = mapping[type1];
5510 var type2Index = mapping[type2];
5512 if (type1Index < type2Index) { return -1; }
5513 if (type1Index > type2Index) { return 1; }
5515 // types are equal - so we have to check values now
5519 if (v < w) { return -1; }
5520 if (v > w) { return 1; }
5524 var comp = v.localeCompare(w);
5525 if (comp < 0) { return -1; }
5526 if (comp > 0) { return 1; }
5530 var vLen = v.length;
5531 var wLen = w.length;
5532 var l = Math.min(vLen, wLen);
5535 while (r === 0 && i < l) {
5536 r = compare(v[i],w[i]);
5539 if (r !== 0) { return r; }
5541 // all elements are equal now
5542 // shorter array should be ordered first
5543 if (vLen < wLen) { return -1; }
5544 if (vLen > wLen) { return 1; }
5545 // arrays are equal now
5549 if (Ember.Comparable && Ember.Comparable.detect(v)) {
5550 return v.compare(v, w);
5555 var vNum = v.getTime();
5556 var wNum = w.getTime();
5557 if (vNum < wNum) { return -1; }
5558 if (vNum > wNum) { return 1; }
5566 function _copy(obj, deep, seen, copies) {
5569 // primitive data types are immutable, just return them.
5570 if ('object' !== typeof obj || obj===null) return obj;
5572 // avoid cyclical loops
5573 if (deep && (loc=indexOf(seen, obj))>=0) return copies[loc];
5575 Ember.assert('Cannot clone an Ember.Object that does not implement Ember.Copyable', !(obj instanceof Ember.Object) || (Ember.Copyable && Ember.Copyable.detect(obj)));
5577 // IMPORTANT: this specific test will detect a native array only. Any other
5578 // object will need to implement Copyable.
5579 if (Ember.typeOf(obj) === 'array') {
5583 while(--loc>=0) ret[loc] = _copy(ret[loc], deep, seen, copies);
5585 } else if (Ember.Copyable && Ember.Copyable.detect(obj)) {
5586 ret = obj.copy(deep, seen, copies);
5590 if (!obj.hasOwnProperty(key)) continue;
5591 ret[key] = deep ? _copy(obj[key], deep, seen, copies) : obj[key];
5604 Creates a clone of the passed object. This function can take just about
5605 any type of object and create a clone of it, including primitive values
5606 (which are not actually cloned because they are immutable).
5608 If the passed object implements the clone() method, then this function
5609 will simply call that method and return the result.
5613 @param {Object} object The object to clone
5614 @param {Boolean} deep If true, a deep copy of the object is made
5615 @return {Object} The cloned object
5617 Ember.copy = function(obj, deep) {
5619 if ('object' !== typeof obj || obj===null) return obj; // can't copy primitives
5620 if (Ember.Copyable && Ember.Copyable.detect(obj)) return obj.copy(deep);
5621 return _copy(obj, deep, deep ? [] : null, deep ? [] : null);
5625 Convenience method to inspect an object. This method will attempt to
5626 convert the object into a useful string description.
5630 @param {Object} obj The object you want to inspect.
5631 @return {String} A description of the object
5633 Ember.inspect = function(obj) {
5635 for(var key in obj) {
5636 if (obj.hasOwnProperty(key)) {
5638 if (v === 'toString') { continue; } // ignore useless items
5639 if (Ember.typeOf(v) === 'function') { v = "function() { ... }"; }
5640 ret.push(key + ": " + v);
5643 return "{" + ret.join(" , ") + "}";
5647 Compares two objects, returning true if they are logically equal. This is
5648 a deeper comparison than a simple triple equal. For sets it will compare the
5649 internal objects. For any other object that implements `isEqual()` it will
5650 respect that method.
5652 Ember.isEqual('hello', 'hello'); => true
5653 Ember.isEqual(1, 2); => false
5654 Ember.isEqual([4,2], [4,2]); => false
5658 @param {Object} a first object to compare
5659 @param {Object} b second object to compare
5662 Ember.isEqual = function(a, b) {
5663 if (a && 'function'===typeof a.isEqual) return a.isEqual(b);
5667 // Used by Ember.compare
5668 Ember.ORDER_DEFINITION = Ember.ENV.ORDER_DEFINITION || [
5683 Returns all of the keys defined on an object or hash. This is useful
5684 when inspecting objects for debugging. On browsers that support it, this
5685 uses the native Object.keys implementation.
5690 @return {Array} Array containing keys of obj
5692 Ember.keys = Object.keys;
5695 Ember.keys = function(obj) {
5697 for(var key in obj) {
5698 if (obj.hasOwnProperty(key)) { ret.push(key); }
5704 // ..........................................................
5708 var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
5711 A subclass of the JavaScript Error object for use in Ember.
5718 Ember.Error = function() {
5719 var tmp = Error.prototype.constructor.apply(this, arguments);
5721 // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
5722 for (var idx = 0; idx < errorProps.length; idx++) {
5723 this[errorProps[idx]] = tmp[errorProps[idx]];
5727 Ember.Error.prototype = Ember.create(Error.prototype);
5736 @submodule ember-runtime
5739 var STRING_DASHERIZE_REGEXP = (/[ _]/g);
5740 var STRING_DASHERIZE_CACHE = {};
5741 var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g);
5742 var STRING_CAMELIZE_REGEXP = (/(\-|_|\s)+(.)?/g);
5743 var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g);
5744 var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g);
5747 Defines the hash of localized strings for the current language. Used by
5748 the `Ember.String.loc()` helper. To localize, add string values to this
5758 Defines string helper methods including string formatting and localization.
5759 Unless Ember.EXTEND_PROTOTYPES = false these methods will also be added to the
5760 String.prototype as well.
5769 Apply formatting options to the string. This will look for occurrences
5770 of %@ in your string and substitute them with the arguments you pass into
5771 this method. If you want to control the specific order of replacement,
5772 you can add a number after the key as well to indicate which argument
5775 Ordered insertions are most useful when building loc strings where values
5776 you need to insert may appear in different orders.
5778 "Hello %@ %@".fmt('John', 'Doe') => "Hello John Doe"
5779 "Hello %@2, %@1".fmt('John', 'Doe') => "Hello Doe, John"
5782 @param {Object...} [args]
5783 @return {String} formatted string
5785 fmt: function(str, formats) {
5786 // first, replace any ORDERED replacements.
5787 var idx = 0; // the current index for non-numerical replacements
5788 return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
5789 argIndex = (argIndex) ? parseInt(argIndex,0) - 1 : idx++ ;
5790 s = formats[argIndex];
5791 return ((s === null) ? '(null)' : (s === undefined) ? '' : s).toString();
5796 Formats the passed string, but first looks up the string in the localized
5797 strings hash. This is a convenient way to localize text. See
5798 `Ember.String.fmt()` for more information on formatting.
5800 Note that it is traditional but not required to prefix localized string
5801 keys with an underscore or other character so you can easily identify
5805 '_Hello World': 'Bonjour le monde',
5806 '_Hello %@ %@': 'Bonjour %@ %@'
5809 Ember.String.loc("_Hello World");
5810 => 'Bonjour le monde';
5812 Ember.String.loc("_Hello %@ %@", ["John", "Smith"]);
5813 => "Bonjour John Smith";
5816 @param {String} str The string to format
5817 @param {Array} formats Optional array of parameters to interpolate into string.
5818 @return {String} formatted string
5820 loc: function(str, formats) {
5821 str = Ember.STRINGS[str] || str;
5822 return Ember.String.fmt(str, formats) ;
5826 Splits a string into separate units separated by spaces, eliminating any
5827 empty strings in the process. This is a convenience method for split that
5828 is mostly useful when applied to the String.prototype.
5830 Ember.String.w("alpha beta gamma").forEach(function(key) {
5838 @param {String} str The string to split
5839 @return {String} split string
5841 w: function(str) { return str.split(/\s+/); },
5844 Converts a camelized string into all lower case separated by underscores.
5846 'innerHTML'.decamelize() => 'inner_html'
5847 'action_name'.decamelize() => 'action_name'
5848 'css-class-name'.decamelize() => 'css-class-name'
5849 'my favorite items'.decamelize() => 'my favorite items'
5852 @param {String} str The string to decamelize.
5853 @return {String} the decamelized string.
5855 decamelize: function(str) {
5856 return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();
5860 Replaces underscores or spaces with dashes.
5862 'innerHTML'.dasherize() => 'inner-html'
5863 'action_name'.dasherize() => 'action-name'
5864 'css-class-name'.dasherize() => 'css-class-name'
5865 'my favorite items'.dasherize() => 'my-favorite-items'
5868 @param {String} str The string to dasherize.
5869 @return {String} the dasherized string.
5871 dasherize: function(str) {
5872 var cache = STRING_DASHERIZE_CACHE,
5878 ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-');
5886 Returns the lowerCaseCamel form of a string.
5888 'innerHTML'.camelize() => 'innerHTML'
5889 'action_name'.camelize() => 'actionName'
5890 'css-class-name'.camelize() => 'cssClassName'
5891 'my favorite items'.camelize() => 'myFavoriteItems'
5894 @param {String} str The string to camelize.
5895 @return {String} the camelized string.
5897 camelize: function(str) {
5898 return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) {
5899 return chr ? chr.toUpperCase() : '';
5904 Returns the UpperCamelCase form of a string.
5906 'innerHTML'.classify() => 'InnerHTML'
5907 'action_name'.classify() => 'ActionName'
5908 'css-class-name'.classify() => 'CssClassName'
5909 'my favorite items'.classify() => 'MyFavoriteItems'
5912 @param {String} str the string to classify
5913 @return {String} the classified string
5915 classify: function(str) {
5916 var camelized = Ember.String.camelize(str);
5917 return camelized.charAt(0).toUpperCase() + camelized.substr(1);
5921 More general than decamelize. Returns the lower_case_and_underscored
5924 'innerHTML'.underscore() => 'inner_html'
5925 'action_name'.underscore() => 'action_name'
5926 'css-class-name'.underscore() => 'css_class_name'
5927 'my favorite items'.underscore() => 'my_favorite_items'
5929 @property underscore
5930 @param {String} str The string to underscore.
5931 @return {String} the underscored string.
5933 underscore: function(str) {
5934 return str.replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2').
5935 replace(STRING_UNDERSCORE_REGEXP_2, '_').toLowerCase();
5946 @submodule ember-runtime
5951 var fmt = Ember.String.fmt,
5953 loc = Ember.String.loc,
5954 camelize = Ember.String.camelize,
5955 decamelize = Ember.String.decamelize,
5956 dasherize = Ember.String.dasherize,
5957 underscore = Ember.String.underscore;
5959 if (Ember.EXTEND_PROTOTYPES) {
5962 See {{#crossLink "Ember.String/fmt"}}{{/crossLink}}
5967 String.prototype.fmt = function() {
5968 return fmt(this, arguments);
5972 See {{#crossLink "Ember.String/w"}}{{/crossLink}}
5977 String.prototype.w = function() {
5982 See {{#crossLink "Ember.String/loc"}}{{/crossLink}}
5987 String.prototype.loc = function() {
5988 return loc(this, arguments);
5992 See {{#crossLink "Ember.String/camelize"}}{{/crossLink}}
5997 String.prototype.camelize = function() {
5998 return camelize(this);
6002 See {{#crossLink "Ember.String/decamelize"}}{{/crossLink}}
6007 String.prototype.decamelize = function() {
6008 return decamelize(this);
6012 See {{#crossLink "Ember.String/dasherize"}}{{/crossLink}}
6017 String.prototype.dasherize = function() {
6018 return dasherize(this);
6022 See {{#crossLink "Ember.String/underscore"}}{{/crossLink}}
6027 String.prototype.underscore = function() {
6028 return underscore(this);
6041 @submodule ember-runtime
6044 var a_slice = Array.prototype.slice;
6046 if (Ember.EXTEND_PROTOTYPES) {
6049 The `property` extension of Javascript's Function prototype is available
6050 when Ember.EXTEND_PROTOTYPES is true, which is the default.
6052 Computed properties allow you to treat a function like a property:
6054 MyApp.president = Ember.Object.create({
6055 firstName: "Barack",
6058 fullName: function() {
6059 return this.get('firstName') + ' ' + this.get('lastName');
6061 // Call this flag to mark the function as a property
6065 MyApp.president.get('fullName'); => "Barack Obama"
6067 Treating a function like a property is useful because they can work with
6068 bindings, just like any other property.
6070 Many computed properties have dependencies on other properties. For
6071 example, in the above example, the `fullName` property depends on
6072 `firstName` and `lastName` to determine its value. You can tell Ember.js
6073 about these dependencies like this:
6075 MyApp.president = Ember.Object.create({
6076 firstName: "Barack",
6079 fullName: function() {
6080 return this.get('firstName') + ' ' + this.get('lastName');
6082 // Tell Ember.js that this computed property depends on firstName
6084 }.property('firstName', 'lastName')
6087 Make sure you list these dependencies so Ember.js knows when to update
6088 bindings that connect to a computed property. Changing a dependency
6089 will not immediately trigger an update of the computed property, but
6090 will instead clear the cache so that it is updated when the next `get`
6091 is called on the property.
6093 See {{#crossLink "Ember.ComputedProperty"}}{{/crossLink}},
6094 {{#crossLink "Ember/computed"}}{{/crossLink}}
6099 Function.prototype.property = function() {
6100 var ret = Ember.computed(this);
6101 return ret.property.apply(ret, arguments);
6105 The `observes` extension of Javascript's Function prototype is available
6106 when Ember.EXTEND_PROTOTYPES is true, which is the default.
6108 You can observe property changes simply by adding the `observes`
6109 call to the end of your method declarations in classes that you write.
6112 Ember.Object.create({
6113 valueObserver: function() {
6114 // Executes whenever the "value" property changes
6118 See {{#crossLink "Ember.Observable/observes"}}{{/crossLink}}
6123 Function.prototype.observes = function() {
6124 this.__ember_observes__ = a_slice.call(arguments);
6129 The `observesBefore` extension of Javascript's Function prototype is
6130 available when Ember.EXTEND_PROTOTYPES is true, which is the default.
6132 You can get notified when a property changes is about to happen by
6133 by adding the `observesBefore` call to the end of your method
6134 declarations in classes that you write. For example:
6136 Ember.Object.create({
6137 valueObserver: function() {
6138 // Executes whenever the "value" property is about to change
6139 }.observesBefore('value')
6142 See {{#crossLink "Ember.Observable/observesBefore"}}{{/crossLink}}
6144 @method observesBefore
6147 Function.prototype.observesBefore = function() {
6148 this.__ember_observesBefore__ = a_slice.call(arguments);
6168 @submodule ember-runtime
6171 // ..........................................................
6175 var get = Ember.get, set = Ember.set;
6176 var a_slice = Array.prototype.slice;
6177 var a_indexOf = Ember.EnumerableUtils.indexOf;
6182 return contexts.length===0 ? {} : contexts.pop();
6185 function pushCtx(ctx) {
6190 function iter(key, value) {
6191 var valueProvided = arguments.length === 2;
6194 var cur = get(item, key);
6195 return valueProvided ? value===cur : !!cur;
6201 This mixin defines the common interface implemented by enumerable objects
6202 in Ember. Most of these methods follow the standard Array iteration
6203 API defined up to JavaScript 1.8 (excluding language-specific features that
6204 cannot be emulated in older versions of JavaScript).
6206 This mixin is applied automatically to the Array class on page load, so you
6207 can use any of these methods on simple arrays. If Array already implements
6208 one of these methods, the mixin will not override them.
6210 h3. Writing Your Own Enumerable
6212 To make your own custom class enumerable, you need two items:
6214 1. You must have a length property. This property should change whenever
6215 the number of items in your enumerable object changes. If you using this
6216 with an Ember.Object subclass, you should be sure to change the length
6217 property using set().
6219 2. If you must implement nextObject(). See documentation.
6221 Once you have these two methods implement, apply the Ember.Enumerable mixin
6222 to your class and you will be able to enumerate the contents of your object
6223 like any other collection.
6225 h3. Using Ember Enumeration with Other Libraries
6227 Many other libraries provide some kind of iterator or enumeration like
6228 facility. This is often where the most common API conflicts occur.
6229 Ember's API is designed to be as friendly as possible with other
6230 libraries by implementing only methods that mostly correspond to the
6235 @extends Ember.Mixin
6238 Ember.Enumerable = Ember.Mixin.create(
6239 /** @scope Ember.Enumerable.prototype */ {
6245 Implement this method to make your class enumerable.
6247 This method will be call repeatedly during enumeration. The index value
6248 will always begin with 0 and increment monotonically. You don't have to
6249 rely on the index value to determine what object to return, but you should
6250 always check the value and start from the beginning when you see the
6251 requested index is 0.
6253 The previousObject is the object that was returned from the last call
6254 to nextObject for the current iteration. This is a useful way to
6255 manage iteration if you are tracing a linked list, for example.
6257 Finally the context parameter will always contain a hash you can use as
6258 a "scratchpad" to maintain any other state you need in order to iterate
6259 properly. The context object is reused and is not reset between
6260 iterations so make sure you setup the context with a fresh state whenever
6261 the index parameter is 0.
6263 Generally iterators will continue to call nextObject until the index
6264 reaches the your current length-1. If you run out of data before this
6265 time for some reason, you should simply return undefined.
6267 The default implementation of this method simply looks up the index.
6268 This works great on any Array-like objects.
6271 @param {Number} index the current index of the iteration
6272 @param {Object} previousObject the value returned by the last call to nextObject.
6273 @param {Object} context a context object you can use to maintain state.
6274 @return {Object} the next object in the iteration or undefined
6276 nextObject: Ember.required(Function),
6279 Helper method returns the first object from a collection. This is usually
6280 used by bindings and other parts of the framework to extract a single
6281 object if the enumerable contains only one item.
6283 If you override this method, you should implement it so that it will
6284 always return the same value each time it is called. If your enumerable
6285 contains only one object, this method should always return that object.
6286 If your enumerable is empty, this method should return undefined.
6288 var arr = ["a", "b", "c"];
6289 arr.firstObject(); => "a"
6292 arr.firstObject(); => undefined
6294 @property firstObject
6295 @return {Object} the object or undefined
6297 firstObject: Ember.computed(function() {
6298 if (get(this, 'length')===0) return undefined ;
6300 // handle generic enumerables
6301 var context = popCtx(), ret;
6302 ret = this.nextObject(0, null, context);
6305 }).property('[]').cacheable(),
6308 Helper method returns the last object from a collection. If your enumerable
6309 contains only one object, this method should always return that object.
6310 If your enumerable is empty, this method should return undefined.
6312 var arr = ["a", "b", "c"];
6313 arr.lastObject(); => "c"
6316 arr.lastObject(); => undefined
6318 @property lastObject
6319 @return {Object} the last object or undefined
6321 lastObject: Ember.computed(function() {
6322 var len = get(this, 'length');
6323 if (len===0) return undefined ;
6324 var context = popCtx(), idx=0, cur, last = null;
6327 cur = this.nextObject(idx++, last, context);
6328 } while (cur !== undefined);
6331 }).property('[]').cacheable(),
6334 Returns true if the passed object can be found in the receiver. The
6335 default version will iterate through the enumerable until the object
6336 is found. You may want to override this with a more efficient version.
6338 var arr = ["a", "b", "c"];
6339 arr.contains("a"); => true
6340 arr.contains("z"); => false
6343 @param {Object} obj The object to search for.
6344 @return {Boolean} true if object is found in enumerable.
6346 contains: function(obj) {
6347 return this.find(function(item) { return item===obj; }) !== undefined;
6351 Iterates through the enumerable, calling the passed function on each
6352 item. This method corresponds to the forEach() method defined in
6355 The callback method you provide should have the following signature (all
6356 parameters are optional):
6358 function(item, index, enumerable);
6360 - *item* is the current item in the iteration.
6361 - *index* is the current index in the iteration
6362 - *enumerable* is the enumerable object itself.
6364 Note that in addition to a callback, you can also pass an optional target
6365 object that will be set as "this" on the context. This is a good way
6366 to give your iterator function access to the current object.
6369 @param {Function} callback The callback to execute
6370 @param {Object} [target] The target object to use
6371 @return {Object} receiver
6373 forEach: function(callback, target) {
6374 if (typeof callback !== "function") throw new TypeError() ;
6375 var len = get(this, 'length'), last = null, context = popCtx();
6377 if (target === undefined) target = null;
6379 for(var idx=0;idx<len;idx++) {
6380 var next = this.nextObject(idx, last, context) ;
6381 callback.call(target, next, idx, this);
6385 context = pushCtx(context);
6390 Alias for mapProperty
6393 @param {String} key name of the property
6394 @return {Array} The mapped array.
6396 getEach: function(key) {
6397 return this.mapProperty(key);
6401 Sets the value on the named property for each member. This is more
6402 efficient than using other methods defined on this helper. If the object
6403 implements Ember.Observable, the value will be changed to set(), otherwise
6404 it will be set directly. null objects are skipped.
6407 @param {String} key The key to set
6408 @param {Object} value The object to set
6409 @return {Object} receiver
6411 setEach: function(key, value) {
6412 return this.forEach(function(item) {
6413 set(item, key, value);
6418 Maps all of the items in the enumeration to another value, returning
6419 a new array. This method corresponds to map() defined in JavaScript 1.6.
6421 The callback method you provide should have the following signature (all
6422 parameters are optional):
6424 function(item, index, enumerable);
6426 - *item* is the current item in the iteration.
6427 - *index* is the current index in the iteration
6428 - *enumerable* is the enumerable object itself.
6430 It should return the mapped value.
6432 Note that in addition to a callback, you can also pass an optional target
6433 object that will be set as "this" on the context. This is a good way
6434 to give your iterator function access to the current object.
6437 @param {Function} callback The callback to execute
6438 @param {Object} [target] The target object to use
6439 @return {Array} The mapped array.
6441 map: function(callback, target) {
6443 this.forEach(function(x, idx, i) {
6444 ret[idx] = callback.call(target, x, idx,i);
6450 Similar to map, this specialized function returns the value of the named
6451 property on all items in the enumeration.
6454 @param {String} key name of the property
6455 @return {Array} The mapped array.
6457 mapProperty: function(key) {
6458 return this.map(function(next) {
6459 return get(next, key);
6464 Returns an array with all of the items in the enumeration that the passed
6465 function returns true for. This method corresponds to filter() defined in
6468 The callback method you provide should have the following signature (all
6469 parameters are optional):
6471 function(item, index, enumerable);
6473 - *item* is the current item in the iteration.
6474 - *index* is the current index in the iteration
6475 - *enumerable* is the enumerable object itself.
6477 It should return the true to include the item in the results, false otherwise.
6479 Note that in addition to a callback, you can also pass an optional target
6480 object that will be set as "this" on the context. This is a good way
6481 to give your iterator function access to the current object.
6484 @param {Function} callback The callback to execute
6485 @param {Object} [target] The target object to use
6486 @return {Array} A filtered array.
6488 filter: function(callback, target) {
6490 this.forEach(function(x, idx, i) {
6491 if (callback.call(target, x, idx, i)) ret.push(x);
6497 Returns an array with just the items with the matched property. You
6498 can pass an optional second argument with the target value. Otherwise
6499 this will match any property that evaluates to true.
6501 @method filterProperty
6502 @param {String} key the property to test
6503 @param {String} [value] optional value to test against.
6504 @return {Array} filtered array
6506 filterProperty: function(key, value) {
6507 return this.filter(iter.apply(this, arguments));
6511 Returns the first item in the array for which the callback returns true.
6512 This method works similar to the filter() method defined in JavaScript 1.6
6513 except that it will stop working on the array once a match is found.
6515 The callback method you provide should have the following signature (all
6516 parameters are optional):
6518 function(item, index, enumerable);
6520 - *item* is the current item in the iteration.
6521 - *index* is the current index in the iteration
6522 - *enumerable* is the enumerable object itself.
6524 It should return the true to include the item in the results, false otherwise.
6526 Note that in addition to a callback, you can also pass an optional target
6527 object that will be set as "this" on the context. This is a good way
6528 to give your iterator function access to the current object.
6531 @param {Function} callback The callback to execute
6532 @param {Object} [target] The target object to use
6533 @return {Object} Found item or null.
6535 find: function(callback, target) {
6536 var len = get(this, 'length') ;
6537 if (target === undefined) target = null;
6539 var last = null, next, found = false, ret ;
6540 var context = popCtx();
6541 for(var idx=0;idx<len && !found;idx++) {
6542 next = this.nextObject(idx, last, context) ;
6543 if (found = callback.call(target, next, idx, this)) ret = next ;
6546 next = last = null ;
6547 context = pushCtx(context);
6552 Returns the first item with a property matching the passed value. You
6553 can pass an optional second argument with the target value. Otherwise
6554 this will match any property that evaluates to true.
6556 This method works much like the more generic find() method.
6558 @method findProperty
6559 @param {String} key the property to test
6560 @param {String} [value] optional value to test against.
6561 @return {Object} found item or null
6563 findProperty: function(key, value) {
6564 return this.find(iter.apply(this, arguments));
6568 Returns true if the passed function returns true for every item in the
6569 enumeration. This corresponds with the every() method in JavaScript 1.6.
6571 The callback method you provide should have the following signature (all
6572 parameters are optional):
6574 function(item, index, enumerable);
6576 - *item* is the current item in the iteration.
6577 - *index* is the current index in the iteration
6578 - *enumerable* is the enumerable object itself.
6580 It should return the true or false.
6582 Note that in addition to a callback, you can also pass an optional target
6583 object that will be set as "this" on the context. This is a good way
6584 to give your iterator function access to the current object.
6588 if (people.every(isEngineer)) { Paychecks.addBigBonus(); }
6591 @param {Function} callback The callback to execute
6592 @param {Object} [target] The target object to use
6595 every: function(callback, target) {
6596 return !this.find(function(x, idx, i) {
6597 return !callback.call(target, x, idx, i);
6602 Returns true if the passed property resolves to true for all items in the
6603 enumerable. This method is often simpler/faster than using a callback.
6605 @method everyProperty
6606 @param {String} key the property to test
6607 @param {String} [value] optional value to test against.
6608 @return {Array} filtered array
6610 everyProperty: function(key, value) {
6611 return this.every(iter.apply(this, arguments));
6616 Returns true if the passed function returns true for any item in the
6617 enumeration. This corresponds with the every() method in JavaScript 1.6.
6619 The callback method you provide should have the following signature (all
6620 parameters are optional):
6622 function(item, index, enumerable);
6624 - *item* is the current item in the iteration.
6625 - *index* is the current index in the iteration
6626 - *enumerable* is the enumerable object itself.
6628 It should return the true to include the item in the results, false otherwise.
6630 Note that in addition to a callback, you can also pass an optional target
6631 object that will be set as "this" on the context. This is a good way
6632 to give your iterator function access to the current object.
6636 if (people.some(isManager)) { Paychecks.addBiggerBonus(); }
6639 @param {Function} callback The callback to execute
6640 @param {Object} [target] The target object to use
6641 @return {Array} A filtered array.
6643 some: function(callback, target) {
6644 return !!this.find(function(x, idx, i) {
6645 return !!callback.call(target, x, idx, i);
6650 Returns true if the passed property resolves to true for any item in the
6651 enumerable. This method is often simpler/faster than using a callback.
6653 @method someProperty
6654 @param {String} key the property to test
6655 @param {String} [value] optional value to test against.
6656 @return {Boolean} true
6658 someProperty: function(key, value) {
6659 return this.some(iter.apply(this, arguments));
6663 This will combine the values of the enumerator into a single value. It
6664 is a useful way to collect a summary value from an enumeration. This
6665 corresponds to the reduce() method defined in JavaScript 1.8.
6667 The callback method you provide should have the following signature (all
6668 parameters are optional):
6670 function(previousValue, item, index, enumerable);
6672 - *previousValue* is the value returned by the last call to the iterator.
6673 - *item* is the current item in the iteration.
6674 - *index* is the current index in the iteration
6675 - *enumerable* is the enumerable object itself.
6677 Return the new cumulative value.
6679 In addition to the callback you can also pass an initialValue. An error
6680 will be raised if you do not pass an initial value and the enumerator is
6683 Note that unlike the other methods, this method does not allow you to
6684 pass a target object to set as this for the callback. It's part of the
6688 @param {Function} callback The callback to execute
6689 @param {Object} initialValue Initial value for the reduce
6690 @param {String} reducerProperty internal use only.
6691 @return {Object} The reduced value.
6693 reduce: function(callback, initialValue, reducerProperty) {
6694 if (typeof callback !== "function") { throw new TypeError(); }
6696 var ret = initialValue;
6698 this.forEach(function(item, i) {
6699 ret = callback.call(null, ret, item, i, this, reducerProperty);
6706 Invokes the named method on every object in the receiver that
6707 implements it. This method corresponds to the implementation in
6711 @param {String} methodName the name of the method
6712 @param {Object...} args optional arguments to pass as well.
6713 @return {Array} return values from calling invoke.
6715 invoke: function(methodName) {
6717 if (arguments.length>1) args = a_slice.call(arguments, 1);
6719 this.forEach(function(x, idx) {
6720 var method = x && x[methodName];
6721 if ('function' === typeof method) {
6722 ret[idx] = args ? method.apply(x, args) : method.call(x);
6730 Simply converts the enumerable into a genuine array. The order is not
6731 guaranteed. Corresponds to the method implemented by Prototype.
6734 @return {Array} the enumerable as an array.
6736 toArray: function() {
6738 this.forEach(function(o, idx) { ret[idx] = o; });
6743 Returns a copy of the array with all null elements removed.
6745 var arr = ["a", null, "c", null];
6746 arr.compact(); => ["a", "c"]
6749 @return {Array} the array without null elements.
6751 compact: function() { return this.without(null); },
6754 Returns a new enumerable that excludes the passed value. The default
6755 implementation returns an array regardless of the receiver type unless
6756 the receiver does not contain the value.
6758 var arr = ["a", "b", "a", "c"];
6759 arr.without("a"); => ["b", "c"]
6762 @param {Object} value
6763 @return {Ember.Enumerable}
6765 without: function(value) {
6766 if (!this.contains(value)) return this; // nothing to do
6768 this.forEach(function(k) {
6769 if (k !== value) ret[ret.length] = k;
6775 Returns a new enumerable that contains only unique values. The default
6776 implementation returns an array regardless of the receiver type.
6778 var arr = ["a", "a", "b", "b"];
6779 arr.uniq(); => ["a", "b"]
6782 @return {Ember.Enumerable}
6786 this.forEach(function(k){
6787 if (a_indexOf(ret, k)<0) ret.push(k);
6793 This property will trigger anytime the enumerable's content changes.
6794 You can observe this property to be notified of changes to the enumerables
6797 For plain enumerables, this property is read only. Ember.Array overrides
6803 '[]': Ember.computed(function(key, value) {
6805 }).property().cacheable(),
6807 // ..........................................................
6808 // ENUMERABLE OBSERVERS
6812 Registers an enumerable observer. Must implement Ember.EnumerableObserver
6815 @method addEnumerableObserver
6816 @param target {Object}
6819 addEnumerableObserver: function(target, opts) {
6820 var willChange = (opts && opts.willChange) || 'enumerableWillChange',
6821 didChange = (opts && opts.didChange) || 'enumerableDidChange';
6823 var hasObservers = get(this, 'hasEnumerableObservers');
6824 if (!hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
6825 Ember.addListener(this, '@enumerable:before', target, willChange);
6826 Ember.addListener(this, '@enumerable:change', target, didChange);
6827 if (!hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
6832 Removes a registered enumerable observer.
6834 @method removeEnumerableObserver
6835 @param target {Object}
6836 @param [opts] {Hash}
6838 removeEnumerableObserver: function(target, opts) {
6839 var willChange = (opts && opts.willChange) || 'enumerableWillChange',
6840 didChange = (opts && opts.didChange) || 'enumerableDidChange';
6842 var hasObservers = get(this, 'hasEnumerableObservers');
6843 if (hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
6844 Ember.removeListener(this, '@enumerable:before', target, willChange);
6845 Ember.removeListener(this, '@enumerable:change', target, didChange);
6846 if (hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
6851 Becomes true whenever the array currently has observers watching changes
6854 @property hasEnumerableObservers
6857 hasEnumerableObservers: Ember.computed(function() {
6858 return Ember.hasListeners(this, '@enumerable:change') || Ember.hasListeners(this, '@enumerable:before');
6859 }).property().cacheable(),
6863 Invoke this method just before the contents of your enumerable will
6864 change. You can either omit the parameters completely or pass the objects
6865 to be removed or added if available or just a count.
6867 @method enumerableContentWillChange
6868 @param {Ember.Enumerable|Number} removing An enumerable of the objects to
6869 be removed or the number of items to be removed.
6870 @param {Ember.Enumerable|Number} adding An enumerable of the objects to be
6871 added or the number of items to be added.
6874 enumerableContentWillChange: function(removing, adding) {
6876 var removeCnt, addCnt, hasDelta;
6878 if ('number' === typeof removing) removeCnt = removing;
6879 else if (removing) removeCnt = get(removing, 'length');
6880 else removeCnt = removing = -1;
6882 if ('number' === typeof adding) addCnt = adding;
6883 else if (adding) addCnt = get(adding,'length');
6884 else addCnt = adding = -1;
6886 hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
6888 if (removing === -1) removing = null;
6889 if (adding === -1) adding = null;
6891 Ember.propertyWillChange(this, '[]');
6892 if (hasDelta) Ember.propertyWillChange(this, 'length');
6893 Ember.sendEvent(this, '@enumerable:before', [this, removing, adding]);
6899 Invoke this method when the contents of your enumerable has changed.
6900 This will notify any observers watching for content changes. If your are
6901 implementing an ordered enumerable (such as an array), also pass the
6902 start and end values where the content changed so that it can be used to
6903 notify range observers.
6905 @method enumerableContentDidChange
6906 @param {Number} [start] optional start offset for the content change.
6907 For unordered enumerables, you should always pass -1.
6908 @param {Ember.Enumerable|Number} removing An enumerable of the objects to
6909 be removed or the number of items to be removed.
6910 @param {Ember.Enumerable|Number} adding An enumerable of the objects to
6911 be added or the number of items to be added.
6914 enumerableContentDidChange: function(removing, adding) {
6915 var notify = this.propertyDidChange, removeCnt, addCnt, hasDelta;
6917 if ('number' === typeof removing) removeCnt = removing;
6918 else if (removing) removeCnt = get(removing, 'length');
6919 else removeCnt = removing = -1;
6921 if ('number' === typeof adding) addCnt = adding;
6922 else if (adding) addCnt = get(adding, 'length');
6923 else addCnt = adding = -1;
6925 hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
6927 if (removing === -1) removing = null;
6928 if (adding === -1) adding = null;
6930 Ember.sendEvent(this, '@enumerable:change', [this, removing, adding]);
6931 if (hasDelta) Ember.propertyDidChange(this, 'length');
6932 Ember.propertyDidChange(this, '[]');
6949 @submodule ember-runtime
6952 // ..........................................................
6956 var get = Ember.get, set = Ember.set, meta = Ember.meta, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
6958 function none(obj) { return obj===null || obj===undefined; }
6960 // ..........................................................
6964 This module implements Observer-friendly Array-like behavior. This mixin is
6965 picked up by the Array class as well as other controllers, etc. that want to
6966 appear to be arrays.
6968 Unlike Ember.Enumerable, this mixin defines methods specifically for
6969 collections that provide index-ordered access to their contents. When you
6970 are designing code that needs to accept any kind of Array-like object, you
6971 should use these methods instead of Array primitives because these will
6972 properly notify observers of changes to the array.
6974 Although these methods are efficient, they do add a layer of indirection to
6975 your application so it is a good idea to use them only when you need the
6976 flexibility of using both true JavaScript arrays and "virtual" arrays such
6977 as controllers and collections.
6979 You can use the methods defined in this module to access and modify array
6980 contents in a KVO-friendly way. You can also be notified whenever the
6981 membership if an array changes by changing the syntax of the property to
6982 .observes('*myProperty.[]') .
6984 To support Ember.Array in your own class, you must override two
6985 primitives to use it: replace() and objectAt().
6987 Note that the Ember.Array mixin also incorporates the Ember.Enumerable mixin. All
6988 Ember.Array-like objects are also enumerable.
6992 @extends Ember.Mixin
6993 @uses Ember.Enumerable
6996 Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.prototype */ {
7002 Your array must support the length property. Your replace methods should
7003 set this property whenever it changes.
7005 @property {Number} length
7007 length: Ember.required(),
7010 Returns the object at the given index. If the given index is negative or
7011 is greater or equal than the array length, returns `undefined`.
7013 This is one of the primitives you must implement to support `Ember.Array`.
7014 If your object supports retrieving the value of an array item using `get()`
7015 (i.e. `myArray.get(0)`), then you do not need to implement this method
7018 var arr = ['a', 'b', 'c', 'd'];
7019 arr.objectAt(0); => "a"
7020 arr.objectAt(3); => "d"
7021 arr.objectAt(-1); => undefined
7022 arr.objectAt(4); => undefined
7023 arr.objectAt(5); => undefined
7027 The index of the item to return.
7029 objectAt: function(idx) {
7030 if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ;
7031 return get(this, idx);
7035 This returns the objects at the specified indexes, using `objectAt`.
7037 var arr =Â ['a', 'b', 'c', 'd'];
7038 arr.objectsAt([0, 1, 2]) => ["a", "b", "c"]
7039 arr.objectsAt([2, 3, 4]) => ["c", "d", undefined]
7042 @param {Array} indexes
7043 An array of indexes of items to return.
7045 objectsAt: function(indexes) {
7047 return map(indexes, function(idx){ return self.objectAt(idx); });
7050 // overrides Ember.Enumerable version
7051 nextObject: function(idx) {
7052 return this.objectAt(idx);
7056 This is the handler for the special array content property. If you get
7057 this property, it will return this. If you set this property it a new
7058 array, it will replace the current content.
7060 This property overrides the default property defined in Ember.Enumerable.
7064 '[]': Ember.computed(function(key, value) {
7065 if (value !== undefined) this.replace(0, get(this, 'length'), value) ;
7067 }).property().cacheable(),
7069 firstObject: Ember.computed(function() {
7070 return this.objectAt(0);
7071 }).property().cacheable(),
7073 lastObject: Ember.computed(function() {
7074 return this.objectAt(get(this, 'length')-1);
7075 }).property().cacheable(),
7077 // optimized version from Enumerable
7078 contains: function(obj){
7079 return this.indexOf(obj) >= 0;
7082 // Add any extra methods to Ember.Array that are native to the built-in Array.
7084 Returns a new array that is a slice of the receiver. This implementation
7085 uses the observable array methods to retrieve the objects for the new
7088 var arr = ['red', 'green', 'blue'];
7089 arr.slice(0); => ['red', 'green', 'blue']
7090 arr.slice(0, 2); => ['red', 'green']
7091 arr.slice(1, 100); => ['green', 'blue']
7094 @param beginIndex {Integer} (Optional) index to begin slicing from.
7095 @param endIndex {Integer} (Optional) index to end the slice at.
7096 @return {Array} New array with specified slice
7098 slice: function(beginIndex, endIndex) {
7100 var length = get(this, 'length') ;
7101 if (none(beginIndex)) beginIndex = 0 ;
7102 if (none(endIndex) || (endIndex > length)) endIndex = length ;
7103 while(beginIndex < endIndex) {
7104 ret[ret.length] = this.objectAt(beginIndex++) ;
7110 Returns the index of the given object's first occurrence.
7111 If no startAt argument is given, the starting location to
7112 search is 0. If it's negative, will count backward from
7113 the end of the array. Returns -1 if no match is found.
7115 var arr = ["a", "b", "c", "d", "a"];
7116 arr.indexOf("a"); => 0
7117 arr.indexOf("z"); => -1
7118 arr.indexOf("a", 2); => 4
7119 arr.indexOf("a", -1); => 4
7120 arr.indexOf("b", 3); => -1
7121 arr.indexOf("a", 100); => -1
7124 @param {Object} object the item to search for
7125 @param {Number} startAt optional starting location to search, default 0
7126 @return {Number} index or -1 if not found
7128 indexOf: function(object, startAt) {
7129 var idx, len = get(this, 'length');
7131 if (startAt === undefined) startAt = 0;
7132 if (startAt < 0) startAt += len;
7134 for(idx=startAt;idx<len;idx++) {
7135 if (this.objectAt(idx, true) === object) return idx ;
7141 Returns the index of the given object's last occurrence.
7142 If no startAt argument is given, the search starts from
7143 the last position. If it's negative, will count backward
7144 from the end of the array. Returns -1 if no match is found.
7146 var arr = ["a", "b", "c", "d", "a"];
7147 arr.lastIndexOf("a"); => 4
7148 arr.lastIndexOf("z"); => -1
7149 arr.lastIndexOf("a", 2); => 0
7150 arr.lastIndexOf("a", -1); => 4
7151 arr.lastIndexOf("b", 3); => 1
7152 arr.lastIndexOf("a", 100); => 4
7155 @param {Object} object the item to search for
7156 @param {Number} startAt optional starting location to search, default 0
7157 @return {Number} index or -1 if not found
7159 lastIndexOf: function(object, startAt) {
7160 var idx, len = get(this, 'length');
7162 if (startAt === undefined || startAt >= len) startAt = len-1;
7163 if (startAt < 0) startAt += len;
7165 for(idx=startAt;idx>=0;idx--) {
7166 if (this.objectAt(idx) === object) return idx ;
7171 // ..........................................................
7176 Adds an array observer to the receiving array. The array observer object
7177 normally must implement two methods:
7179 * `arrayWillChange(start, removeCount, addCount)` - This method will be
7180 called just before the array is modified.
7181 * `arrayDidChange(start, removeCount, addCount)` - This method will be
7182 called just after the array is modified.
7184 Both callbacks will be passed the starting index of the change as well a
7185 a count of the items to be removed and added. You can use these callbacks
7186 to optionally inspect the array during the change, clear caches, or do
7187 any other bookkeeping necessary.
7189 In addition to passing a target, you can also include an options hash
7190 which you can use to override the method names that will be invoked on the
7193 @method addArrayObserver
7194 @param {Object} target The observer object.
7195 @param {Hash} opts Optional hash of configuration options including
7196 willChange, didChange, and a context option.
7197 @return {Ember.Array} receiver
7199 addArrayObserver: function(target, opts) {
7200 var willChange = (opts && opts.willChange) || 'arrayWillChange',
7201 didChange = (opts && opts.didChange) || 'arrayDidChange';
7203 var hasObservers = get(this, 'hasArrayObservers');
7204 if (!hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
7205 Ember.addListener(this, '@array:before', target, willChange);
7206 Ember.addListener(this, '@array:change', target, didChange);
7207 if (!hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
7212 Removes an array observer from the object if the observer is current
7213 registered. Calling this method multiple times with the same object will
7216 @method removeArrayObserver
7217 @param {Object} target The object observing the array.
7218 @return {Ember.Array} receiver
7220 removeArrayObserver: function(target, opts) {
7221 var willChange = (opts && opts.willChange) || 'arrayWillChange',
7222 didChange = (opts && opts.didChange) || 'arrayDidChange';
7224 var hasObservers = get(this, 'hasArrayObservers');
7225 if (hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
7226 Ember.removeListener(this, '@array:before', target, willChange);
7227 Ember.removeListener(this, '@array:change', target, didChange);
7228 if (hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
7233 Becomes true whenever the array currently has observers watching changes
7238 hasArrayObservers: Ember.computed(function() {
7239 return Ember.hasListeners(this, '@array:change') || Ember.hasListeners(this, '@array:before');
7240 }).property().cacheable(),
7243 If you are implementing an object that supports Ember.Array, call this
7244 method just before the array content changes to notify any observers and
7245 invalidate any related properties. Pass the starting index of the change
7246 as well as a delta of the amounts to change.
7248 @method arrayContentWillChange
7249 @param {Number} startIdx The starting index in the array that will change.
7250 @param {Number} removeAmt The number of items that will be removed. If you pass null assumes 0
7251 @param {Number} addAmt The number of items that will be added. If you pass null assumes 0.
7252 @return {Ember.Array} receiver
7254 arrayContentWillChange: function(startIdx, removeAmt, addAmt) {
7256 // if no args are passed assume everything changes
7257 if (startIdx===undefined) {
7259 removeAmt = addAmt = -1;
7261 if (removeAmt === undefined) removeAmt=-1;
7262 if (addAmt === undefined) addAmt=-1;
7265 // Make sure the @each proxy is set up if anyone is observing @each
7266 if (Ember.isWatching(this, '@each')) { get(this, '@each'); }
7268 Ember.sendEvent(this, '@array:before', [this, startIdx, removeAmt, addAmt]);
7271 if (startIdx>=0 && removeAmt>=0 && get(this, 'hasEnumerableObservers')) {
7273 lim = startIdx+removeAmt;
7274 for(var idx=startIdx;idx<lim;idx++) removing.push(this.objectAt(idx));
7276 removing = removeAmt;
7279 this.enumerableContentWillChange(removing, addAmt);
7284 arrayContentDidChange: function(startIdx, removeAmt, addAmt) {
7286 // if no args are passed assume everything changes
7287 if (startIdx===undefined) {
7289 removeAmt = addAmt = -1;
7291 if (removeAmt === undefined) removeAmt=-1;
7292 if (addAmt === undefined) addAmt=-1;
7296 if (startIdx>=0 && addAmt>=0 && get(this, 'hasEnumerableObservers')) {
7298 lim = startIdx+addAmt;
7299 for(var idx=startIdx;idx<lim;idx++) adding.push(this.objectAt(idx));
7304 this.enumerableContentDidChange(removeAmt, adding);
7305 Ember.sendEvent(this, '@array:change', [this, startIdx, removeAmt, addAmt]);
7307 var length = get(this, 'length'),
7308 cachedFirst = cacheFor(this, 'firstObject'),
7309 cachedLast = cacheFor(this, 'lastObject');
7310 if (this.objectAt(0) !== cachedFirst) {
7311 Ember.propertyWillChange(this, 'firstObject');
7312 Ember.propertyDidChange(this, 'firstObject');
7314 if (this.objectAt(length-1) !== cachedLast) {
7315 Ember.propertyWillChange(this, 'lastObject');
7316 Ember.propertyDidChange(this, 'lastObject');
7322 // ..........................................................
7323 // ENUMERATED PROPERTIES
7327 Returns a special object that can be used to observe individual properties
7328 on the array. Just get an equivalent property on this object and it will
7329 return an enumerable that maps automatically to the named key on the
7334 '@each': Ember.computed(function() {
7335 if (!this.__each) this.__each = new Ember.EachProxy(this);
7337 }).property().cacheable()
7348 @submodule ember-runtime
7353 Implements some standard methods for comparing objects. Add this mixin to
7354 any class you create that can compare its instances.
7356 You should implement the compare() method.
7360 @extends Ember.Mixin
7363 Ember.Comparable = Ember.Mixin.create( /** @scope Ember.Comparable.prototype */{
7366 walk like a duck. Indicates that the object can be compared.
7368 @property isComparable
7375 Override to return the result of the comparison of the two parameters. The
7376 compare method should return:
7382 Default implementation raises an exception.
7385 @param a {Object} the first object to compare
7386 @param b {Object} the second object to compare
7387 @return {Integer} the result of the comparison
7389 compare: Ember.required(Function)
7401 @submodule ember-runtime
7406 var get = Ember.get, set = Ember.set;
7409 Implements some standard methods for copying an object. Add this mixin to
7410 any object you create that can create a copy of itself. This mixin is
7411 added automatically to the built-in array.
7413 You should generally implement the copy() method to return a copy of the
7416 Note that frozenCopy() will only work if you also implement Ember.Freezable.
7420 @extends Ember.Mixin
7423 Ember.Copyable = Ember.Mixin.create(
7424 /** @scope Ember.Copyable.prototype */ {
7427 Override to return a copy of the receiver. Default implementation raises
7431 @param deep {Boolean} if true, a deep copy of the object should be made
7432 @return {Object} copy of receiver
7434 copy: Ember.required(Function),
7437 If the object implements Ember.Freezable, then this will return a new copy
7438 if the object is not frozen and the receiver if the object is frozen.
7440 Raises an exception if you try to call this method on a object that does
7441 not support freezing.
7443 You should use this method whenever you want a copy of a freezable object
7444 since a freezable object can simply return itself without actually
7445 consuming more memory.
7448 @return {Object} copy of receiver or receiver
7450 frozenCopy: function() {
7451 if (Ember.Freezable && Ember.Freezable.detect(this)) {
7452 return get(this, 'isFrozen') ? this : this.copy().freeze();
7454 throw new Error(Ember.String.fmt("%@ does not support freezing", [this]));
7469 @submodule ember-runtime
7473 var get = Ember.get, set = Ember.set;
7476 The Ember.Freezable mixin implements some basic methods for marking an object
7477 as frozen. Once an object is frozen it should be read only. No changes
7478 may be made the internal state of the object.
7482 To fully support freezing in your subclass, you must include this mixin and
7483 override any method that might alter any property on the object to instead
7484 raise an exception. You can check the state of an object by checking the
7487 Although future versions of JavaScript may support language-level freezing
7488 object objects, that is not the case today. Even if an object is freezable,
7489 it is still technically possible to modify the object, even though it could
7490 break other parts of your application that do not expect a frozen object to
7491 change. It is, therefore, very important that you always respect the
7492 isFrozen property on all freezable objects.
7496 The example below shows a simple object that implement the Ember.Freezable
7499 Contact = Ember.Object.extend(Ember.Freezable, {
7506 swapNames: function() {
7507 if (this.get('isFrozen')) throw Ember.FROZEN_ERROR;
7508 var tmp = this.get('firstName');
7509 this.set('firstName', this.get('lastName'));
7510 this.set('lastName', tmp);
7516 c = Context.create({ firstName: "John", lastName: "Doe" });
7517 c.swapNames(); => returns c
7519 c.swapNames(); => EXCEPTION
7523 Usually the Ember.Freezable protocol is implemented in cooperation with the
7524 Ember.Copyable protocol, which defines a frozenCopy() method that will return
7525 a frozen object, if the object implements this method as well.
7529 @extends Ember.Mixin
7532 Ember.Freezable = Ember.Mixin.create(
7533 /** @scope Ember.Freezable.prototype */ {
7536 Set to true when the object is frozen. Use this property to detect whether
7537 your object is frozen or not.
7545 Freezes the object. Once this method has been called the object should
7546 no longer allow any properties to be edited.
7549 @return {Object} receiver
7551 freeze: function() {
7552 if (get(this, 'isFrozen')) return this;
7553 set(this, 'isFrozen', true);
7559 Ember.FROZEN_ERROR = "Frozen object cannot be modified.";
7568 @submodule ember-runtime
7571 var forEach = Ember.EnumerableUtils.forEach;
7574 This mixin defines the API for modifying generic enumerables. These methods
7575 can be applied to an object regardless of whether it is ordered or
7578 Note that an Enumerable can change even if it does not implement this mixin.
7579 For example, a MappedEnumerable cannot be directly modified but if its
7580 underlying enumerable changes, it will change also.
7584 To add an object to an enumerable, use the addObject() method. This
7585 method will only add the object to the enumerable if the object is not
7586 already present and the object if of a type supported by the enumerable.
7588 set.addObject(contact);
7592 To remove an object form an enumerable, use the removeObject() method. This
7593 will only remove the object if it is already in the enumerable, otherwise
7594 this method has no effect.
7596 set.removeObject(contact);
7598 ## Implementing In Your Own Code
7600 If you are implementing an object and want to support this API, just include
7601 this mixin in your class and implement the required methods. In your unit
7602 tests, be sure to apply the Ember.MutableEnumerableTests to your object.
7604 @class MutableEnumerable
7606 @extends Ember.Mixin
7607 @uses Ember.Enumerable
7609 Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable,
7610 /** @scope Ember.MutableEnumerable.prototype */ {
7613 __Required.__ You must implement this method to apply this mixin.
7615 Attempts to add the passed object to the receiver if the object is not
7616 already present in the collection. If the object is present, this method
7619 If the passed object is of a type not supported by the receiver
7620 then this method should raise an exception.
7623 @param {Object} object The object to add to the enumerable.
7624 @return {Object} the passed object
7626 addObject: Ember.required(Function),
7629 Adds each object in the passed enumerable to the receiver.
7632 @param {Ember.Enumerable} objects the objects to add.
7633 @return {Object} receiver
7635 addObjects: function(objects) {
7636 Ember.beginPropertyChanges(this);
7637 forEach(objects, function(obj) { this.addObject(obj); }, this);
7638 Ember.endPropertyChanges(this);
7643 __Required.__ You must implement this method to apply this mixin.
7645 Attempts to remove the passed object from the receiver collection if the
7646 object is in present in the collection. If the object is not present,
7647 this method has no effect.
7649 If the passed object is of a type not supported by the receiver
7650 then this method should raise an exception.
7652 @method removeObject
7653 @param {Object} object The object to remove from the enumerable.
7654 @return {Object} the passed object
7656 removeObject: Ember.required(Function),
7660 Removes each objects in the passed enumerable from the receiver.
7662 @method removeObjects
7663 @param {Ember.Enumerable} objects the objects to remove
7664 @return {Object} receiver
7666 removeObjects: function(objects) {
7667 Ember.beginPropertyChanges(this);
7668 forEach(objects, function(obj) { this.removeObject(obj); }, this);
7669 Ember.endPropertyChanges(this);
7682 @submodule ember-runtime
7684 // ..........................................................
7688 var OUT_OF_RANGE_EXCEPTION = "Index out of range" ;
7691 // ..........................................................
7695 var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
7698 This mixin defines the API for modifying array-like objects. These methods
7699 can be applied only to a collection that keeps its items in an ordered set.
7701 Note that an Array can change even if it does not implement this mixin.
7702 For example, one might implement a SparseArray that cannot be directly
7703 modified, but if its underlying enumerable changes, it will change also.
7707 @extends Ember.Mixin
7709 @uses Ember.MutableEnumerable
7711 Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
7712 /** @scope Ember.MutableArray.prototype */ {
7715 __Required.__ You must implement this method to apply this mixin.
7717 This is one of the primitives you must implement to support Ember.Array. You
7718 should replace amt objects started at idx with the objects in the passed
7719 array. You should also call this.enumerableContentDidChange() ;
7722 @param {Number} idx Starting index in the array to replace. If idx >= length,
7723 then append to the end of the array.
7724 @param {Number} amt Number of elements that should be removed from the array,
7726 @param {Array} objects An array of zero or more objects that should be inserted
7727 into the array at *idx*
7729 replace: Ember.required(),
7732 Remove all elements from self. This is useful if you
7733 want to reuse an existing array without having to recreate it.
7735 var colors = ["red", "green", "blue"];
7736 color.length(); => 3
7737 colors.clear(); => []
7738 colors.length(); => 0
7741 @return {Ember.Array} An empty Array.
7743 clear: function () {
7744 var len = get(this, 'length');
7745 if (len === 0) return this;
7746 this.replace(0, len, EMPTY);
7751 This will use the primitive replace() method to insert an object at the
7754 var colors = ["red", "green", "blue"];
7755 colors.insertAt(2, "yellow"); => ["red", "green", "yellow", "blue"]
7756 colors.insertAt(5, "orange"); => Error: Index out of range
7759 @param {Number} idx index of insert the object at.
7760 @param {Object} object object to insert
7762 insertAt: function(idx, object) {
7763 if (idx > get(this, 'length')) throw new Error(OUT_OF_RANGE_EXCEPTION) ;
7764 this.replace(idx, 0, [object]) ;
7769 Remove an object at the specified index using the replace() primitive
7770 method. You can pass either a single index, or a start and a length.
7772 If you pass a start and length that is beyond the
7773 length this method will throw an Ember.OUT_OF_RANGE_EXCEPTION
7775 var colors = ["red", "green", "blue", "yellow", "orange"];
7776 colors.removeAt(0); => ["green", "blue", "yellow", "orange"]
7777 colors.removeAt(2, 2); => ["green", "blue"]
7778 colors.removeAt(4, 2); => Error: Index out of range
7781 @param {Number} start index, start of range
7782 @param {Number} len length of passing range
7783 @return {Object} receiver
7785 removeAt: function(start, len) {
7786 if ('number' === typeof start) {
7788 if ((start < 0) || (start >= get(this, 'length'))) {
7789 throw new Error(OUT_OF_RANGE_EXCEPTION);
7793 if (len === undefined) len = 1;
7794 this.replace(start, len, EMPTY);
7801 Push the object onto the end of the array. Works just like push() but it
7804 var colors = ["red", "green", "blue"];
7805 colors.pushObject("black"); => ["red", "green", "blue", "black"]
7806 colors.pushObject(["yellow", "orange"]); => ["red", "green", "blue", "black", ["yellow", "orange"]]
7809 @param {anything} obj object to push
7811 pushObject: function(obj) {
7812 this.insertAt(get(this, 'length'), obj) ;
7817 Add the objects in the passed numerable to the end of the array. Defers
7818 notifying observers of the change until all objects are added.
7820 var colors = ["red", "green", "blue"];
7821 colors.pushObjects("black"); => ["red", "green", "blue", "black"]
7822 colors.pushObjects(["yellow", "orange"]); => ["red", "green", "blue", "black", "yellow", "orange"]
7825 @param {Ember.Enumerable} objects the objects to add
7826 @return {Ember.Array} receiver
7828 pushObjects: function(objects) {
7829 this.replace(get(this, 'length'), 0, objects);
7834 Pop object from array or nil if none are left. Works just like pop() but
7835 it is KVO-compliant.
7837 var colors = ["red", "green", "blue"];
7838 colors.popObject(); => "blue"
7839 console.log(colors); => ["red", "green"]
7844 popObject: function() {
7845 var len = get(this, 'length') ;
7846 if (len === 0) return null ;
7848 var ret = this.objectAt(len-1) ;
7849 this.removeAt(len-1, 1) ;
7854 Shift an object from start of array or nil if none are left. Works just
7855 like shift() but it is KVO-compliant.
7857 var colors = ["red", "green", "blue"];
7858 colors.shiftObject(); => "red"
7859 console.log(colors); => ["green", "blue"]
7864 shiftObject: function() {
7865 if (get(this, 'length') === 0) return null ;
7866 var ret = this.objectAt(0) ;
7872 Unshift an object to start of array. Works just like unshift() but it is
7875 var colors = ["red", "green", "blue"];
7876 colors.unshiftObject("yellow"); => ["yellow", "red", "green", "blue"]
7877 colors.unshiftObject(["black", "white"]); => [["black", "white"], "yellow", "red", "green", "blue"]
7879 @method unshiftObject
7880 @param {anything} obj object to unshift
7882 unshiftObject: function(obj) {
7883 this.insertAt(0, obj) ;
7888 Adds the named objects to the beginning of the array. Defers notifying
7889 observers until all objects have been added.
7891 var colors = ["red", "green", "blue"];
7892 colors.unshiftObjects(["black", "white"]); => ["black", "white", "red", "green", "blue"]
7893 colors.unshiftObjects("yellow"); => Type Error: 'undefined' is not a function
7895 @method unshiftObjects
7896 @param {Ember.Enumerable} objects the objects to add
7897 @return {Ember.Array} receiver
7899 unshiftObjects: function(objects) {
7900 this.replace(0, 0, objects);
7905 Reverse objects in the array. Works just like reverse() but it is
7908 @method reverseObjects
7909 @return {Ember.Array} receiver
7911 reverseObjects: function() {
7912 var len = get(this, 'length');
7913 if (len === 0) return this;
7914 var objects = this.toArray().reverse();
7915 this.replace(0, len, objects);
7920 Replace all the the receiver's content with content of the argument.
7921 If argument is an empty array receiver will be cleared.
7923 var colors = ["red", "green", "blue"];
7924 colors.setObjects(["black", "white"]); => ["black", "white"]
7925 colors.setObjects([]); => []
7928 @param {Ember.Array} objects array whose content will be used for replacing
7929 the content of the receiver
7930 @return {Ember.Array} receiver with the new content
7932 setObjects: function(objects) {
7933 if (objects.length === 0) return this.clear();
7935 var len = get(this, 'length');
7936 this.replace(0, len, objects);
7940 // ..........................................................
7941 // IMPLEMENT Ember.MutableEnumerable
7944 removeObject: function(obj) {
7945 var loc = get(this, 'length') || 0;
7947 var curObject = this.objectAt(loc) ;
7948 if (curObject === obj) this.removeAt(loc) ;
7953 addObject: function(obj) {
7954 if (!this.contains(obj)) this.pushObject(obj);
7968 @submodule ember-runtime
7971 var get = Ember.get, set = Ember.set, defineProperty = Ember.defineProperty;
7976 This mixin provides properties and property observing functionality, core
7977 features of the Ember object model.
7979 Properties and observers allow one object to observe changes to a
7980 property on another object. This is one of the fundamental ways that
7981 models, controllers and views communicate with each other in an Ember
7984 Any object that has this mixin applied can be used in observer
7985 operations. That includes Ember.Object and most objects you will
7986 interact with as you write your Ember application.
7988 Note that you will not generally apply this mixin to classes yourself,
7989 but you will use the features provided by this module frequently, so it
7990 is important to understand how to use it.
7992 ## Using get() and set()
7994 Because of Ember's support for bindings and observers, you will always
7995 access properties using the get method, and set properties using the
7996 set method. This allows the observing objects to be notified and
7997 computed properties to be handled properly.
7999 More documentation about `get` and `set` are below.
8001 ## Observing Property Changes
8003 You typically observe property changes simply by adding the `observes`
8004 call to the end of your method declarations in classes that you write.
8007 Ember.Object.create({
8008 valueObserver: function() {
8009 // Executes whenever the "value" property changes
8013 Although this is the most common way to add an observer, this capability
8014 is actually built into the Ember.Object class on top of two methods
8015 defined in this mixin: `addObserver` and `removeObserver`. You can use
8016 these two methods to add and remove observers yourself if you need to
8019 To add an observer for a property, call:
8021 object.addObserver('propertyKey', targetObject, targetAction)
8023 This will call the `targetAction` method on the `targetObject` to be called
8024 whenever the value of the `propertyKey` changes.
8026 Note that if `propertyKey` is a computed property, the observer will be
8027 called when any of the property dependencies are changed, even if the
8028 resulting value of the computed property is unchanged. This is necessary
8029 because computed properties are not computed until `get` is called.
8033 @extends Ember.Mixin
8035 Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
8038 isObserverable: true,
8041 Retrieves the value of a property from the object.
8043 This method is usually similar to using object[keyName] or object.keyName,
8044 however it supports both computed properties and the unknownProperty
8047 Because `get` unifies the syntax for accessing all these kinds
8048 of properties, it can make many refactorings easier, such as replacing a
8049 simple property with a computed property, or vice versa.
8051 ### Computed Properties
8053 Computed properties are methods defined with the `property` modifier
8054 declared at the end, such as:
8056 fullName: function() {
8057 return this.getEach('firstName', 'lastName').compact().join(' ');
8058 }.property('firstName', 'lastName')
8060 When you call `get` on a computed property, the function will be
8061 called and the return value will be returned instead of the function
8064 ### Unknown Properties
8066 Likewise, if you try to call `get` on a property whose value is
8067 undefined, the unknownProperty() method will be called on the object.
8068 If this method returns any value other than undefined, it will be returned
8069 instead. This allows you to implement "virtual" properties that are
8070 not defined upfront.
8073 @param {String} key The property to retrieve
8074 @return {Object} The property value or undefined.
8076 get: function(keyName) {
8077 return get(this, keyName);
8081 To get multiple properties at once, call getProperties
8082 with a list of strings or an array:
8084 record.getProperties('firstName', 'lastName', 'zipCode'); // => { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
8088 record.getProperties(['firstName', 'lastName', 'zipCode']); // => { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
8090 @method getProperties
8091 @param {String...|Array} list of keys to get
8094 getProperties: function() {
8096 var propertyNames = arguments;
8097 if (arguments.length === 1 && Ember.typeOf(arguments[0]) === 'array') {
8098 propertyNames = arguments[0];
8100 for(var i = 0; i < propertyNames.length; i++) {
8101 ret[propertyNames[i]] = get(this, propertyNames[i]);
8107 Sets the provided key or path to the value.
8109 This method is generally very similar to calling object[key] = value or
8110 object.key = value, except that it provides support for computed
8111 properties, the unknownProperty() method and property observers.
8113 ### Computed Properties
8115 If you try to set a value on a key that has a computed property handler
8116 defined (see the get() method for an example), then set() will call
8117 that method, passing both the value and key instead of simply changing
8118 the value itself. This is useful for those times when you need to
8119 implement a property that is composed of one or more member
8122 ### Unknown Properties
8124 If you try to set a value on a key that is undefined in the target
8125 object, then the unknownProperty() handler will be called instead. This
8126 gives you an opportunity to implement complex "virtual" properties that
8127 are not predefined on the object. If unknownProperty() returns
8128 undefined, then set() will simply set the value on the object.
8130 ### Property Observers
8132 In addition to changing the property, set() will also register a
8133 property change with the object. Unless you have placed this call
8134 inside of a beginPropertyChanges() and endPropertyChanges(), any "local"
8135 observers (i.e. observer methods declared on the same object), will be
8136 called immediately. Any "remote" observers (i.e. observer methods
8137 declared on another object) will be placed in a queue and called at a
8138 later time in a coalesced manner.
8142 In addition to property changes, set() returns the value of the object
8143 itself so you can do chaining like this:
8145 record.set('firstName', 'Charles').set('lastName', 'Jolley');
8148 @param {String} key The property to set
8149 @param {Object} value The value to set or null.
8150 @return {Ember.Observable}
8152 set: function(keyName, value) {
8153 set(this, keyName, value);
8158 To set multiple properties at once, call setProperties
8161 record.setProperties({ firstName: 'Charles', lastName: 'Jolley' });
8163 @method setProperties
8164 @param {Hash} hash the hash of keys and values to set
8165 @return {Ember.Observable}
8167 setProperties: function(hash) {
8168 return Ember.setProperties(this, hash);
8172 Begins a grouping of property changes.
8174 You can use this method to group property changes so that notifications
8175 will not be sent until the changes are finished. If you plan to make a
8176 large number of changes to an object at one time, you should call this
8177 method at the beginning of the changes to begin deferring change
8178 notifications. When you are done making changes, call endPropertyChanges()
8179 to deliver the deferred change notifications and end deferring.
8181 @method beginPropertyChanges
8182 @return {Ember.Observable}
8184 beginPropertyChanges: function() {
8185 Ember.beginPropertyChanges();
8190 Ends a grouping of property changes.
8192 You can use this method to group property changes so that notifications
8193 will not be sent until the changes are finished. If you plan to make a
8194 large number of changes to an object at one time, you should call
8195 beginPropertyChanges() at the beginning of the changes to defer change
8196 notifications. When you are done making changes, call this method to
8197 deliver the deferred change notifications and end deferring.
8199 @method endPropertyChanges
8200 @return {Ember.Observable}
8202 endPropertyChanges: function() {
8203 Ember.endPropertyChanges();
8208 Notify the observer system that a property is about to change.
8210 Sometimes you need to change a value directly or indirectly without
8211 actually calling get() or set() on it. In this case, you can use this
8212 method and propertyDidChange() instead. Calling these two methods
8213 together will notify all observers that the property has potentially
8216 Note that you must always call propertyWillChange and propertyDidChange as
8217 a pair. If you do not, it may get the property change groups out of order
8218 and cause notifications to be delivered more often than you would like.
8220 @method propertyWillChange
8221 @param {String} key The property key that is about to change.
8222 @return {Ember.Observable}
8224 propertyWillChange: function(keyName){
8225 Ember.propertyWillChange(this, keyName);
8230 Notify the observer system that a property has just changed.
8232 Sometimes you need to change a value directly or indirectly without
8233 actually calling get() or set() on it. In this case, you can use this
8234 method and propertyWillChange() instead. Calling these two methods
8235 together will notify all observers that the property has potentially
8238 Note that you must always call propertyWillChange and propertyDidChange as
8239 a pair. If you do not, it may get the property change groups out of order
8240 and cause notifications to be delivered more often than you would like.
8242 @method propertyDidChange
8243 @param {String} keyName The property key that has just changed.
8244 @return {Ember.Observable}
8246 propertyDidChange: function(keyName) {
8247 Ember.propertyDidChange(this, keyName);
8252 Convenience method to call `propertyWillChange` and `propertyDidChange` in
8255 @method notifyPropertyChange
8256 @param {String} keyName The property key to be notified about.
8257 @return {Ember.Observable}
8259 notifyPropertyChange: function(keyName) {
8260 this.propertyWillChange(keyName);
8261 this.propertyDidChange(keyName);
8265 addBeforeObserver: function(key, target, method) {
8266 Ember.addBeforeObserver(this, key, target, method);
8270 Adds an observer on a property.
8272 This is the core method used to register an observer for a property.
8274 Once you call this method, anytime the key's value is set, your observer
8275 will be notified. Note that the observers are triggered anytime the
8276 value is set, regardless of whether it has actually changed. Your
8277 observer should be prepared to handle that.
8279 You can also pass an optional context parameter to this method. The
8280 context will be passed to your observer method whenever it is triggered.
8281 Note that if you add the same target/method pair on a key multiple times
8282 with different context parameters, your observer will only be called once
8283 with the last context you passed.
8285 ### Observer Methods
8287 Observer methods you pass should generally have the following signature if
8288 you do not pass a "context" parameter:
8290 fooDidChange: function(sender, key, value, rev);
8292 The sender is the object that changed. The key is the property that
8293 changes. The value property is currently reserved and unused. The rev
8294 is the last property revision of the object when it changed, which you can
8295 use to detect if the key value has really changed or not.
8297 If you pass a "context" parameter, the context will be passed before the
8300 fooDidChange: function(sender, key, value, context, rev);
8302 Usually you will not need the value, context or revision parameters at
8303 the end. In this case, it is common to write observer methods that take
8304 only a sender and key value as parameters or, if you aren't interested in
8305 any of these values, to write an observer that has no parameters at all.
8308 @param {String} key The key to observer
8309 @param {Object} target The target object to invoke
8310 @param {String|Function} method The method to invoke.
8311 @return {Ember.Object} self
8313 addObserver: function(key, target, method) {
8314 Ember.addObserver(this, key, target, method);
8318 Remove an observer you have previously registered on this object. Pass
8319 the same key, target, and method you passed to addObserver() and your
8320 target will no longer receive notifications.
8322 @method removeObserver
8323 @param {String} key The key to observer
8324 @param {Object} target The target object to invoke
8325 @param {String|Function} method The method to invoke.
8326 @return {Ember.Observable} receiver
8328 removeObserver: function(key, target, method) {
8329 Ember.removeObserver(this, key, target, method);
8333 Returns true if the object currently has observers registered for a
8334 particular key. You can use this method to potentially defer performing
8335 an expensive action until someone begins observing a particular property
8338 @method hasObserverFor
8339 @param {String} key Key to check
8342 hasObserverFor: function(key) {
8343 return Ember.hasListeners(this, key+':change');
8347 This method will be called when a client attempts to get the value of a
8348 property that has not been defined in one of the typical ways. Override
8349 this method to create "virtual" properties.
8351 @method unknownProperty
8352 @param {String} key The name of the unknown property that was requested.
8353 @return {Object} The property value or undefined. Default is undefined.
8355 unknownProperty: function(key) {
8360 This method will be called when a client attempts to set the value of a
8361 property that has not been defined in one of the typical ways. Override
8362 this method to create "virtual" properties.
8364 @method setUnknownProperty
8365 @param {String} key The name of the unknown property to be set.
8366 @param {Object} value The value the unknown property is to be set to.
8368 setUnknownProperty: function(key, value) {
8369 defineProperty(this, key);
8370 set(this, key, value);
8376 @param {String} path The property path to retrieve
8377 @return {Object} The property value or undefined.
8379 getPath: function(path) {
8380 Ember.deprecate("getPath is deprecated since get now supports paths");
8381 return this.get(path);
8387 @param {String} path The path to the property that will be set
8388 @param {Object} value The value to set or null.
8389 @return {Ember.Observable}
8391 setPath: function(path, value) {
8392 Ember.deprecate("setPath is deprecated since set now supports paths");
8393 return this.set(path, value);
8397 Retrieves the value of a property, or a default value in the case that the property
8400 person.getWithDefault('lastName', 'Doe');
8402 @method getWithDefault
8403 @param {String} keyName The name of the property to retrieve
8404 @param {Object} defaultValue The value to return if the property value is undefined
8405 @return {Object} The property value or the defaultValue.
8407 getWithDefault: function(keyName, defaultValue) {
8408 return Ember.getWithDefault(this, keyName, defaultValue);
8412 Set the value of a property to the current value plus some amount.
8414 person.incrementProperty('age');
8415 team.incrementProperty('score', 2);
8417 @method incrementProperty
8418 @param {String} keyName The name of the property to increment
8419 @param {Object} increment The amount to increment by. Defaults to 1
8420 @return {Object} The new property value
8422 incrementProperty: function(keyName, increment) {
8423 if (!increment) { increment = 1; }
8424 set(this, keyName, (get(this, keyName) || 0)+increment);
8425 return get(this, keyName);
8429 Set the value of a property to the current value minus some amount.
8431 player.decrementProperty('lives');
8432 orc.decrementProperty('health', 5);
8434 @method decrementProperty
8435 @param {String} keyName The name of the property to decrement
8436 @param {Object} increment The amount to decrement by. Defaults to 1
8437 @return {Object} The new property value
8439 decrementProperty: function(keyName, increment) {
8440 if (!increment) { increment = 1; }
8441 set(this, keyName, (get(this, keyName) || 0)-increment);
8442 return get(this, keyName);
8446 Set the value of a boolean property to the opposite of it's
8449 starship.toggleProperty('warpDriveEnaged');
8451 @method toggleProperty
8452 @param {String} keyName The name of the property to toggle
8453 @return {Object} The new property value
8455 toggleProperty: function(keyName) {
8456 set(this, keyName, !get(this, keyName));
8457 return get(this, keyName);
8461 Returns the cached value of a computed property, if it exists.
8462 This allows you to inspect the value of a computed property
8463 without accidentally invoking it if it is intended to be
8467 @param {String} keyName
8468 @return {Object} The cached value of the computed property, if any
8470 cacheFor: function(keyName) {
8471 return Ember.cacheFor(this, keyName);
8474 // intended for debugging purposes
8475 observersForKey: function(keyName) {
8476 return Ember.observersFor(this, keyName);
8488 @submodule ember-runtime
8491 var get = Ember.get, set = Ember.set;
8494 @class TargetActionSupport
8496 @extends Ember.Mixin
8498 Ember.TargetActionSupport = Ember.Mixin.create({
8502 targetObject: Ember.computed(function() {
8503 var target = get(this, 'target');
8505 if (Ember.typeOf(target) === "string") {
8506 var value = get(this, target);
8507 if (value === undefined) { value = get(window, target); }
8512 }).property('target').cacheable(),
8514 triggerAction: function() {
8515 var action = get(this, 'action'),
8516 target = get(this, 'targetObject');
8518 if (target && action) {
8521 if (typeof target.send === 'function') {
8522 ret = target.send(action, this);
8524 if (typeof action === 'string') {
8525 action = target[action];
8527 ret = action.call(target, this);
8529 if (ret !== false) ret = true;
8545 @submodule ember-runtime
8551 @extends Ember.Mixin
8553 Ember.Evented = Ember.Mixin.create({
8554 on: function(name, target, method) {
8555 Ember.addListener(this, name, target, method);
8558 one: function(name, target, method) {
8565 var wrapped = function() {
8566 Ember.removeListener(self, name, target, wrapped);
8568 if ('string' === typeof method) { method = this[method]; }
8570 // Internally, a `null` target means that the target is
8571 // the first parameter to addListener. That means that
8572 // the `this` passed into this function is the target
8573 // determined by the event system.
8574 method.apply(this, arguments);
8577 this.on(name, target, wrapped);
8580 trigger: function(name) {
8581 var args = [], i, l;
8582 for (i = 1, l = arguments.length; i < l; i++) {
8583 args.push(arguments[i]);
8585 Ember.sendEvent(this, name, args);
8588 fire: function(name) {
8589 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.");
8590 this.trigger.apply(this, arguments);
8593 off: function(name, target, method) {
8594 Ember.removeListener(this, name, target, method);
8597 has: function(name) {
8598 return Ember.hasListeners(this, name);
8615 @submodule ember-runtime
8619 // NOTE: this object should never be included directly. Instead use Ember.
8620 // Ember.Object. We only define this separately so that Ember.Set can depend on it
8623 var set = Ember.set, get = Ember.get,
8624 o_create = Ember.create,
8625 o_defineProperty = Ember.platform.defineProperty,
8626 a_slice = Array.prototype.slice,
8627 GUID_KEY = Ember.GUID_KEY,
8628 guidFor = Ember.guidFor,
8629 generateGuid = Ember.generateGuid,
8631 rewatch = Ember.rewatch,
8632 finishChains = Ember.finishChains,
8633 destroy = Ember.destroy,
8634 schedule = Ember.run.schedule,
8635 Mixin = Ember.Mixin,
8636 applyMixin = Mixin._apply,
8637 finishPartial = Mixin.finishPartial,
8638 reopen = Mixin.prototype.reopen,
8639 classToString = Mixin.prototype.toString;
8641 var undefinedDescriptor = {
8648 function makeCtor() {
8650 // Note: avoid accessing any properties on the object since it makes the
8651 // method a lot faster. This is glue code so we want it to be as fast as
8654 var wasApplied = false, initMixins;
8656 var Class = function() {
8658 Class.proto(); // prepare prototype...
8660 o_defineProperty(this, GUID_KEY, undefinedDescriptor);
8661 o_defineProperty(this, '_super', undefinedDescriptor);
8665 this.reopen.apply(this, initMixins);
8668 finishPartial(this, m);
8671 this.init.apply(this, arguments);
8674 Class.toString = classToString;
8675 Class.willReopen = function() {
8677 Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
8682 Class._initMixins = function(args) { initMixins = args; };
8684 Class.proto = function() {
8685 var superclass = Class.superclass;
8686 if (superclass) { superclass.proto(); }
8690 Class.PrototypeMixin.applyPartial(Class.prototype);
8691 rewatch(Class.prototype);
8694 return this.prototype;
8701 var CoreObject = makeCtor();
8703 CoreObject.PrototypeMixin = Mixin.create({
8705 reopen: function() {
8706 applyMixin(this, arguments, true);
8712 init: function() {},
8715 @property isDestroyed
8721 @property isDestroying
8724 isDestroying: false,
8727 Destroys an object by setting the isDestroyed flag and removing its
8728 metadata, which effectively destroys observers and bindings.
8730 If you try to set a property on a destroyed object, an exception will be
8733 Note that destruction is scheduled for the end of the run loop and does not
8737 @return {Ember.Object} receiver
8739 destroy: function() {
8740 if (this.isDestroying) { return; }
8742 this.isDestroying = true;
8744 if (this.willDestroy) { this.willDestroy(); }
8746 set(this, 'isDestroyed', true);
8747 schedule('destroy', this, this._scheduledDestroy);
8754 Invoked by the run loop to actually destroy the object. This is
8755 scheduled for execution by the `destroy` method.
8757 @method _scheduledDestroy
8759 _scheduledDestroy: function() {
8761 if (this.didDestroy) { this.didDestroy(); }
8764 bind: function(to, from) {
8765 if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
8766 from.to(to).connect(this);
8770 toString: function() {
8771 return '<'+this.constructor.toString()+':'+guidFor(this)+'>';
8775 if (Ember.config.overridePrototypeMixin) {
8776 Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin);
8779 CoreObject.__super__ = null;
8781 var ClassMixin = Mixin.create({
8783 ClassMixin: Ember.required(),
8785 PrototypeMixin: Ember.required(),
8791 extend: function() {
8792 var Class = makeCtor(), proto;
8793 Class.ClassMixin = Mixin.create(this.ClassMixin);
8794 Class.PrototypeMixin = Mixin.create(this.PrototypeMixin);
8796 Class.ClassMixin.ownerConstructor = Class;
8797 Class.PrototypeMixin.ownerConstructor = Class;
8799 reopen.apply(Class.PrototypeMixin, arguments);
8801 Class.superclass = this;
8802 Class.__super__ = this.prototype;
8804 proto = Class.prototype = o_create(this.prototype);
8805 proto.constructor = Class;
8806 generateGuid(proto, 'ember');
8807 meta(proto).proto = proto; // this will disable observers on prototype
8809 Class.ClassMixin.apply(Class);
8813 create: function() {
8815 if (arguments.length>0) { this._initMixins(arguments); }
8819 reopen: function() {
8821 reopen.apply(this.PrototypeMixin, arguments);
8825 reopenClass: function() {
8826 reopen.apply(this.ClassMixin, arguments);
8827 applyMixin(this, arguments, false);
8831 detect: function(obj) {
8832 if ('function' !== typeof obj) { return false; }
8834 if (obj===this) { return true; }
8835 obj = obj.superclass;
8840 detectInstance: function(obj) {
8841 return obj instanceof this;
8845 In some cases, you may want to annotate computed properties with additional
8846 metadata about how they function or what values they operate on. For example,
8847 computed property functions may close over variables that are then no longer
8848 available for introspection.
8850 You can pass a hash of these values to a computed property like this:
8852 person: function() {
8853 var personId = this.get('personId');
8854 return App.Person.create({ id: personId });
8855 }.property().meta({ type: App.Person })
8857 Once you've done this, you can retrieve the values saved to the computed
8858 property from your class like this:
8860 MyClass.metaForProperty('person');
8862 This will return the original hash that was passed to `meta()`.
8864 @method metaForProperty
8865 @param key {String} property name
8867 metaForProperty: function(key) {
8868 var desc = meta(this.proto(), false).descs[key];
8870 Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
8871 return desc._meta || {};
8875 Iterate over each computed property for the class, passing its name
8876 and any associated metadata (see `metaForProperty`) to the callback.
8878 @method eachComputedProperty
8879 @param {Function} callback
8880 @param {Object} binding
8882 eachComputedProperty: function(callback, binding) {
8883 var proto = this.proto(),
8884 descs = meta(proto).descs,
8888 for (var name in descs) {
8889 property = descs[name];
8891 if (property instanceof Ember.ComputedProperty) {
8892 callback.call(binding || this, name, property._meta || empty);
8899 if (Ember.config.overrideClassMixin) {
8900 Ember.config.overrideClassMixin(ClassMixin);
8903 CoreObject.ClassMixin = ClassMixin;
8904 ClassMixin.apply(CoreObject);
8910 Ember.CoreObject = CoreObject;
8922 @submodule ember-runtime
8925 var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.none;
8928 An unordered collection of objects.
8930 A Set works a bit like an array except that its items are not ordered.
8931 You can create a set to efficiently test for membership for an object. You
8932 can also iterate through a set just like an array, even accessing objects
8933 by index, however there is no guarantee as to their order.
8935 All Sets are observable via the Enumerable Observer API - which works
8936 on any enumerable object including both Sets and Arrays.
8940 You can create a set like you would most objects using
8941 `new Ember.Set()`. Most new sets you create will be empty, but you can
8942 also initialize the set with some content by passing an array or other
8943 enumerable of objects to the constructor.
8945 Finally, you can pass in an existing set and the set will be copied. You
8946 can also create a copy of a set by calling `Ember.Set#copy()`.
8949 // creates a new empty set
8950 var foundNames = new Ember.Set();
8952 // creates a set with four names in it.
8953 var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P
8955 // creates a copy of the names set.
8956 var namesCopy = new Ember.Set(names);
8959 var anotherNamesCopy = names.copy();
8961 ## Adding/Removing Objects
8963 You generally add or remove objects from a set using `add()` or
8964 `remove()`. You can add any type of object including primitives such as
8965 numbers, strings, and booleans.
8967 Unlike arrays, objects can only exist one time in a set. If you call `add()`
8968 on a set with the same object multiple times, the object will only be added
8969 once. Likewise, calling `remove()` with the same object multiple times will
8970 remove the object the first time and have no effect on future calls until
8971 you add the object to the set again.
8973 NOTE: You cannot add/remove null or undefined to a set. Any attempt to do so
8976 In addition to add/remove you can also call `push()`/`pop()`. Push behaves
8977 just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary
8978 object, remove it and return it. This is a good way to use a set as a job
8979 queue when you don't care which order the jobs are executed in.
8981 ## Testing for an Object
8983 To test for an object's presence in a set you simply call
8984 `Ember.Set#contains()`.
8986 ## Observing changes
8988 When using `Ember.Set`, you can observe the `"[]"` property to be
8989 alerted whenever the content changes. You can also add an enumerable
8990 observer to the set to be notified of specific objects that are added and
8991 removed from the set. See `Ember.Enumerable` for more information on
8994 This is often unhelpful. If you are filtering sets of objects, for instance,
8995 it is very inefficient to re-filter all of the items each time the set
8996 changes. It would be better if you could just adjust the filtered set based
8997 on what was changed on the original set. The same issue applies to merging
9002 `Ember.Set` primary implements other mixin APIs. For a complete reference
9003 on the methods you will use with `Ember.Set`, please consult these mixins.
9004 The most useful ones will be `Ember.Enumerable` and
9005 `Ember.MutableEnumerable` which implement most of the common iterator
9006 methods you are used to on Array.
9008 Note that you can also use the `Ember.Copyable` and `Ember.Freezable`
9009 APIs on `Ember.Set` as well. Once a set is frozen it can no longer be
9010 modified. The benefit of this is that when you call frozenCopy() on it,
9011 Ember will avoid making copies of the set. This allows you to write
9012 code that can know with certainty when the underlying set data will or
9013 will not be modified.
9017 @extends Ember.CoreObject
9018 @uses Ember.MutableEnumerable
9019 @uses Ember.Copyable
9020 @uses Ember.Freezable
9023 Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable,
9024 /** @scope Ember.Set.prototype */ {
9026 // ..........................................................
9027 // IMPLEMENT ENUMERABLE APIS
9031 This property will change as the number of objects in the set changes.
9040 Clears the set. This is useful if you want to reuse an existing set
9041 without having to recreate it.
9043 var colors = new Ember.Set(["red", "green", "blue"]);
9049 @return {Ember.Set} An empty Set
9052 if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); }
9054 var len = get(this, 'length');
9055 if (len === 0) { return this; }
9059 this.enumerableContentWillChange(len, 0);
9060 Ember.propertyWillChange(this, 'firstObject');
9061 Ember.propertyWillChange(this, 'lastObject');
9063 for (var i=0; i < len; i++){
9064 guid = guidFor(this[i]);
9069 set(this, 'length', 0);
9071 Ember.propertyDidChange(this, 'firstObject');
9072 Ember.propertyDidChange(this, 'lastObject');
9073 this.enumerableContentDidChange(len, 0);
9079 Returns true if the passed object is also an enumerable that contains the
9080 same objects as the receiver.
9082 var colors = ["red", "green", "blue"],
9083 same_colors = new Ember.Set(colors);
9084 same_colors.isEqual(colors); => true
9085 same_colors.isEqual(["purple", "brown"]); => false
9088 @param {Ember.Set} obj the other object.
9091 isEqual: function(obj) {
9093 if (!Ember.Enumerable.detect(obj)) return false;
9095 var loc = get(this, 'length');
9096 if (get(obj, 'length') !== loc) return false;
9099 if (!obj.contains(this[loc])) return false;
9106 Adds an object to the set. Only non-null objects can be added to a set
9107 and those can only be added once. If the object is already in the set or
9108 the passed value is null this method will have no effect.
9110 This is an alias for `Ember.MutableEnumerable.addObject()`.
9112 var colors = new Ember.Set();
9113 colors.add("blue"); => ["blue"]
9114 colors.add("blue"); => ["blue"]
9115 colors.add("red"); => ["blue", "red"]
9116 colors.add(null); => ["blue", "red"]
9117 colors.add(undefined); => ["blue", "red"]
9120 @param {Object} obj The object to add.
9121 @return {Ember.Set} The set itself.
9123 add: Ember.alias('addObject'),
9126 Removes the object from the set if it is found. If you pass a null value
9127 or an object that is already not in the set, this method will have no
9128 effect. This is an alias for `Ember.MutableEnumerable.removeObject()`.
9130 var colors = new Ember.Set(["red", "green", "blue"]);
9131 colors.remove("red"); => ["blue", "green"]
9132 colors.remove("purple"); => ["blue", "green"]
9133 colors.remove(null); => ["blue", "green"]
9136 @param {Object} obj The object to remove
9137 @return {Ember.Set} The set itself.
9139 remove: Ember.alias('removeObject'),
9142 Removes the last element from the set and returns it, or null if it's empty.
9144 var colors = new Ember.Set(["green", "blue"]);
9145 colors.pop(); => "blue"
9146 colors.pop(); => "green"
9147 colors.pop(); => null
9150 @return {Object} The removed object from the set or null.
9153 if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
9154 var obj = this.length > 0 ? this[this.length-1] : null;
9160 Inserts the given object on to the end of the set. It returns
9163 This is an alias for `Ember.MutableEnumerable.addObject()`.
9165 var colors = new Ember.Set();
9166 colors.push("red"); => ["red"]
9167 colors.push("green"); => ["red", "green"]
9168 colors.push("blue"); => ["red", "green", "blue"]
9171 @return {Ember.Set} The set itself.
9173 push: Ember.alias('addObject'),
9176 Removes the last element from the set and returns it, or null if it's empty.
9178 This is an alias for `Ember.Set.pop()`.
9180 var colors = new Ember.Set(["green", "blue"]);
9181 colors.shift(); => "blue"
9182 colors.shift(); => "green"
9183 colors.shift(); => null
9186 @return {Object} The removed object from the set or null.
9188 shift: Ember.alias('pop'),
9191 Inserts the given object on to the end of the set. It returns
9194 This is an alias of `Ember.Set.push()`
9196 var colors = new Ember.Set();
9197 colors.unshift("red"); => ["red"]
9198 colors.unshift("green"); => ["red", "green"]
9199 colors.unshift("blue"); => ["red", "green", "blue"]
9202 @return {Ember.Set} The set itself.
9204 unshift: Ember.alias('push'),
9207 Adds each object in the passed enumerable to the set.
9209 This is an alias of `Ember.MutableEnumerable.addObjects()`
9211 var colors = new Ember.Set();
9212 colors.addEach(["red", "green", "blue"]); => ["red", "green", "blue"]
9215 @param {Ember.Enumerable} objects the objects to add.
9216 @return {Ember.Set} The set itself.
9218 addEach: Ember.alias('addObjects'),
9221 Removes each object in the passed enumerable to the set.
9223 This is an alias of `Ember.MutableEnumerable.removeObjects()`
9225 var colors = new Ember.Set(["red", "green", "blue"]);
9226 colors.removeEach(["red", "blue"]); => ["green"]
9229 @param {Ember.Enumerable} objects the objects to remove.
9230 @return {Ember.Set} The set itself.
9232 removeEach: Ember.alias('removeObjects'),
9234 // ..........................................................
9235 // PRIVATE ENUMERABLE SUPPORT
9238 init: function(items) {
9240 if (items) this.addObjects(items);
9243 // implement Ember.Enumerable
9244 nextObject: function(idx) {
9248 // more optimized version
9249 firstObject: Ember.computed(function() {
9250 return this.length > 0 ? this[0] : undefined;
9251 }).property().cacheable(),
9253 // more optimized version
9254 lastObject: Ember.computed(function() {
9255 return this.length > 0 ? this[this.length-1] : undefined;
9256 }).property().cacheable(),
9258 // implements Ember.MutableEnumerable
9259 addObject: function(obj) {
9260 if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
9261 if (none(obj)) return this; // nothing to do
9263 var guid = guidFor(obj),
9265 len = get(this, 'length'),
9268 if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added
9272 this.enumerableContentWillChange(null, added);
9273 Ember.propertyWillChange(this, 'lastObject');
9275 len = get(this, 'length');
9278 set(this, 'length', len+1);
9280 Ember.propertyDidChange(this, 'lastObject');
9281 this.enumerableContentDidChange(null, added);
9286 // implements Ember.MutableEnumerable
9287 removeObject: function(obj) {
9288 if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
9289 if (none(obj)) return this; // nothing to do
9291 var guid = guidFor(obj),
9293 len = get(this, 'length'),
9294 isFirst = idx === 0,
9295 isLast = idx === len-1,
9299 if (idx>=0 && idx<len && (this[idx] === obj)) {
9302 this.enumerableContentWillChange(removed, null);
9303 if (isFirst) { Ember.propertyWillChange(this, 'firstObject'); }
9304 if (isLast) { Ember.propertyWillChange(this, 'lastObject'); }
9306 // swap items - basically move the item to the end so it can be removed
9310 this[guidFor(last)] = idx;
9315 set(this, 'length', len-1);
9317 if (isFirst) { Ember.propertyDidChange(this, 'firstObject'); }
9318 if (isLast) { Ember.propertyDidChange(this, 'lastObject'); }
9319 this.enumerableContentDidChange(removed, null);
9325 // optimized version
9326 contains: function(obj) {
9327 return this[guidFor(obj)]>=0;
9331 var C = this.constructor, ret = new C(), loc = get(this, 'length');
9332 set(ret, 'length', loc);
9334 ret[loc] = this[loc];
9335 ret[guidFor(this[loc])] = loc;
9340 toString: function() {
9341 var len = this.length, idx, array = [];
9342 for(idx = 0; idx < len; idx++) {
9343 array[idx] = this[idx];
9345 return "Ember.Set<%@>".fmt(array.join(','));
9357 @submodule ember-runtime
9361 `Ember.Object` is the main base class for all Ember objects. It is a subclass
9362 of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
9363 see the documentation for each of these.
9367 @extends Ember.CoreObject
9368 @uses Ember.Observable
9370 Ember.Object = Ember.CoreObject.extend(Ember.Observable);
9379 @submodule ember-runtime
9382 var indexOf = Ember.ArrayPolyfills.indexOf;
9385 A Namespace is an object usually used to contain other objects or methods
9386 such as an application or framework. Create a namespace anytime you want
9387 to define one of these new containers.
9391 MyFramework = Ember.Namespace.create({
9397 @extends Ember.Object
9399 Ember.Namespace = Ember.Object.extend({
9403 Ember.Namespace.NAMESPACES.push(this);
9404 Ember.Namespace.PROCESSED = false;
9407 toString: function() {
9408 Ember.identifyNamespaces();
9409 return this[Ember.GUID_KEY+'_name'];
9412 destroy: function() {
9413 var namespaces = Ember.Namespace.NAMESPACES;
9414 window[this.toString()] = undefined;
9415 namespaces.splice(indexOf.call(namespaces, this), 1);
9420 Ember.Namespace.NAMESPACES = [Ember];
9421 Ember.Namespace.PROCESSED = false;
9430 @submodule ember-runtime
9434 Defines a namespace that will contain an executable application. This is
9435 very similar to a normal namespace except that it is expected to include at
9436 least a 'ready' function which can be run to initialize the application.
9438 Currently Ember.Application is very similar to Ember.Namespace. However, this
9439 class may be augmented by additional frameworks so it is important to use
9440 this instance when building new applications.
9444 MyApp = Ember.Application.create({
9446 store: Ember.Store.create().from(Ember.fixtures)
9449 MyApp.ready = function() {
9450 //..init code goes here...
9455 @extends Ember.Namespace
9457 Ember.Application = Ember.Namespace.extend();
9467 @submodule ember-runtime
9471 var get = Ember.get, set = Ember.set;
9474 An ArrayProxy wraps any other object that implements Ember.Array and/or
9475 Ember.MutableArray, forwarding all requests. This makes it very useful for
9476 a number of binding use cases or other cases where being able to swap
9477 out the underlying array is useful.
9479 A simple example of usage:
9481 var pets = ['dog', 'cat', 'fish'];
9482 var ap = Ember.ArrayProxy.create({ content: Ember.A(pets) });
9483 ap.get('firstObject'); // => 'dog'
9484 ap.set('content', ['amoeba', 'paramecium']);
9485 ap.get('firstObject'); // => 'amoeba'
9487 This class can also be useful as a layer to transform the contents of
9488 an array, as they are accessed. This can be done by overriding
9491 var pets = ['dog', 'cat', 'fish'];
9492 var ap = Ember.ArrayProxy.create({
9493 content: Ember.A(pets),
9494 objectAtContent: function(idx) {
9495 return this.get('content').objectAt(idx).toUpperCase();
9498 ap.get('firstObject'); // => 'DOG'
9503 @extends Ember.Object
9504 @uses Ember.MutableArray
9506 Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,
9507 /** @scope Ember.ArrayProxy.prototype */ {
9510 The content array. Must be an object that implements Ember.Array and/or
9519 The array that the proxy pretends to be. In the default `ArrayProxy`
9520 implementation, this and `content` are the same. Subclasses of `ArrayProxy`
9521 can override this property to provide things like sorting and filtering.
9523 @property arrangedContent
9525 arrangedContent: Ember.computed('content', function() {
9526 return get(this, 'content');
9530 Should actually retrieve the object at the specified index from the
9531 content. You can override this method in subclasses to transform the
9532 content item to something new.
9534 This method will only be called if content is non-null.
9536 @method objectAtContent
9537 @param {Number} idx The index to retrieve.
9538 @return {Object} the value or undefined if none found
9540 objectAtContent: function(idx) {
9541 return get(this, 'arrangedContent').objectAt(idx);
9545 Should actually replace the specified objects on the content array.
9546 You can override this method in subclasses to transform the content item
9549 This method will only be called if content is non-null.
9551 @method replaceContent
9552 @param {Number} idx The starting index
9553 @param {Number} amt The number of items to remove from the content.
9554 @param {Array} objects Optional array of objects to insert or null if no objects.
9557 replaceContent: function(idx, amt, objects) {
9558 get(this, 'arrangedContent').replace(idx, amt, objects);
9564 Invoked when the content property is about to change. Notifies observers that the
9565 entire array content will change.
9567 @method _contentWillChange
9569 _contentWillChange: Ember.beforeObserver(function() {
9570 this._teardownContent();
9573 _teardownContent: function() {
9574 var content = get(this, 'content');
9577 content.removeArrayObserver(this, {
9578 willChange: 'contentArrayWillChange',
9579 didChange: 'contentArrayDidChange'
9584 contentArrayWillChange: Ember.K,
9585 contentArrayDidChange: Ember.K,
9590 Invoked when the content property changes. Notifies observers that the
9591 entire array content has changed.
9593 @method _contentDidChange
9595 _contentDidChange: Ember.observer(function() {
9596 var content = get(this, 'content');
9598 Ember.assert("Can't set ArrayProxy's content to itself", content !== this);
9600 this._setupContent();
9603 _setupContent: function() {
9604 var content = get(this, 'content');
9607 content.addArrayObserver(this, {
9608 willChange: 'contentArrayWillChange',
9609 didChange: 'contentArrayDidChange'
9614 _arrangedContentWillChange: Ember.beforeObserver(function() {
9615 var arrangedContent = get(this, 'arrangedContent'),
9616 len = arrangedContent ? get(arrangedContent, 'length') : 0;
9618 this.arrangedContentArrayWillChange(this, 0, len, undefined);
9619 this.arrangedContentWillChange(this);
9621 this._teardownArrangedContent(arrangedContent);
9622 }, 'arrangedContent'),
9624 _arrangedContentDidChange: Ember.observer(function() {
9625 var arrangedContent = get(this, 'arrangedContent'),
9626 len = arrangedContent ? get(arrangedContent, 'length') : 0;
9628 Ember.assert("Can't set ArrayProxy's content to itself", arrangedContent !== this);
9630 this._setupArrangedContent();
9632 this.arrangedContentDidChange(this);
9633 this.arrangedContentArrayDidChange(this, 0, undefined, len);
9634 }, 'arrangedContent'),
9636 _setupArrangedContent: function() {
9637 var arrangedContent = get(this, 'arrangedContent');
9639 if (arrangedContent) {
9640 arrangedContent.addArrayObserver(this, {
9641 willChange: 'arrangedContentArrayWillChange',
9642 didChange: 'arrangedContentArrayDidChange'
9647 _teardownArrangedContent: function() {
9648 var arrangedContent = get(this, 'arrangedContent');
9650 if (arrangedContent) {
9651 arrangedContent.removeArrayObserver(this, {
9652 willChange: 'arrangedContentArrayWillChange',
9653 didChange: 'arrangedContentArrayDidChange'
9658 arrangedContentWillChange: Ember.K,
9659 arrangedContentDidChange: Ember.K,
9661 objectAt: function(idx) {
9662 return get(this, 'content') && this.objectAtContent(idx);
9665 length: Ember.computed(function() {
9666 var arrangedContent = get(this, 'arrangedContent');
9667 return arrangedContent ? get(arrangedContent, 'length') : 0;
9668 // No dependencies since Enumerable notifies length of change
9669 }).property().cacheable(),
9671 replace: function(idx, amt, objects) {
9672 if (get(this, 'content')) this.replaceContent(idx, amt, objects);
9676 arrangedContentArrayWillChange: function(item, idx, removedCnt, addedCnt) {
9677 this.arrayContentWillChange(idx, removedCnt, addedCnt);
9680 arrangedContentArrayDidChange: function(item, idx, removedCnt, addedCnt) {
9681 this.arrayContentDidChange(idx, removedCnt, addedCnt);
9686 this._setupContent();
9687 this._setupArrangedContent();
9690 willDestroy: function() {
9691 this._teardownArrangedContent();
9692 this._teardownContent();
9704 @submodule ember-runtime
9707 var get = Ember.get,
9709 fmt = Ember.String.fmt,
9710 addBeforeObserver = Ember.addBeforeObserver,
9711 addObserver = Ember.addObserver,
9712 removeBeforeObserver = Ember.removeBeforeObserver,
9713 removeObserver = Ember.removeObserver,
9714 propertyWillChange = Ember.propertyWillChange,
9715 propertyDidChange = Ember.propertyDidChange;
9717 function contentPropertyWillChange(content, contentKey) {
9718 var key = contentKey.slice(8); // remove "content."
9719 if (key in this) { return; } // if shadowed in proxy
9720 propertyWillChange(this, key);
9723 function contentPropertyDidChange(content, contentKey) {
9724 var key = contentKey.slice(8); // remove "content."
9725 if (key in this) { return; } // if shadowed in proxy
9726 propertyDidChange(this, key);
9730 `Ember.ObjectProxy` forwards all properties not defined by the proxy itself
9731 to a proxied `content` object.
9733 object = Ember.Object.create({
9736 proxy = Ember.ObjectProxy.create({
9740 // Access and change existing properties
9741 proxy.get('name') // => 'Foo'
9742 proxy.set('name', 'Bar');
9743 object.get('name') // => 'Bar'
9745 // Create new 'description' property on `object`
9746 proxy.set('description', 'Foo is a whizboo baz');
9747 object.get('description') // => 'Foo is a whizboo baz'
9749 While `content` is unset, setting a property to be delegated will throw an Error.
9751 proxy = Ember.ObjectProxy.create({
9755 proxy.set('flag', true);
9756 proxy.get('flag'); // => true
9757 proxy.get('foo'); // => undefined
9758 proxy.set('foo', 'data'); // throws Error
9760 Delegated properties can be bound to and will change when content is updated.
9762 Computed properties on the proxy itself can depend on delegated properties.
9764 ProxyWithComputedProperty = Ember.ObjectProxy.extend({
9765 fullName: function () {
9766 var firstName = this.get('firstName'),
9767 lastName = this.get('lastName');
9768 if (firstName && lastName) {
9769 return firstName + ' ' + lastName;
9771 return firstName || lastName;
9772 }.property('firstName', 'lastName')
9774 proxy = ProxyWithComputedProperty.create();
9775 proxy.get('fullName'); => undefined
9776 proxy.set('content', {
9777 firstName: 'Tom', lastName: 'Dale'
9778 }); // triggers property change for fullName on proxy
9779 proxy.get('fullName'); => 'Tom Dale'
9783 @extends Ember.Object
9785 Ember.ObjectProxy = Ember.Object.extend(
9786 /** @scope Ember.ObjectProxy.prototype */ {
9788 The object whose properties will be forwarded.
9795 _contentDidChange: Ember.observer(function() {
9796 Ember.assert("Can't set ObjectProxy's content to itself", this.get('content') !== this);
9799 willWatchProperty: function (key) {
9800 var contentKey = 'content.' + key;
9801 addBeforeObserver(this, contentKey, null, contentPropertyWillChange);
9802 addObserver(this, contentKey, null, contentPropertyDidChange);
9805 didUnwatchProperty: function (key) {
9806 var contentKey = 'content.' + key;
9807 removeBeforeObserver(this, contentKey, null, contentPropertyWillChange);
9808 removeObserver(this, contentKey, null, contentPropertyDidChange);
9811 unknownProperty: function (key) {
9812 var content = get(this, 'content');
9814 return get(content, key);
9818 setUnknownProperty: function (key, value) {
9819 var content = get(this, 'content');
9820 Ember.assert(fmt("Cannot delegate set('%@', %@) to the 'content' property of object proxy %@: its 'content' is undefined.", [key, value, this]), content);
9821 return set(content, key, value);
9832 @submodule ember-runtime
9836 var set = Ember.set, get = Ember.get, guidFor = Ember.guidFor;
9837 var forEach = Ember.EnumerableUtils.forEach;
9839 var EachArray = Ember.Object.extend(Ember.Array, {
9841 init: function(content, keyName, owner) {
9843 this._keyName = keyName;
9844 this._owner = owner;
9845 this._content = content;
9848 objectAt: function(idx) {
9849 var item = this._content.objectAt(idx);
9850 return item && get(item, this._keyName);
9853 length: Ember.computed(function() {
9854 var content = this._content;
9855 return content ? get(content, 'length') : 0;
9856 }).property().cacheable()
9860 var IS_OBSERVER = /^.+:(before|change)$/;
9862 function addObserverForContentKey(content, keyName, proxy, idx, loc) {
9863 var objects = proxy._objects, guid;
9864 if (!objects) objects = proxy._objects = {};
9867 var item = content.objectAt(loc);
9869 Ember.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
9870 Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange');
9872 // keep track of the indicies each item was found at so we can map
9873 // it back when the obj changes.
9874 guid = guidFor(item);
9875 if (!objects[guid]) objects[guid] = [];
9876 objects[guid].push(loc);
9881 function removeObserverForContentKey(content, keyName, proxy, idx, loc) {
9882 var objects = proxy._objects;
9883 if (!objects) objects = proxy._objects = {};
9887 var item = content.objectAt(loc);
9889 Ember.removeBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
9890 Ember.removeObserver(item, keyName, proxy, 'contentKeyDidChange');
9892 guid = guidFor(item);
9893 indicies = objects[guid];
9894 indicies[indicies.indexOf(loc)] = null;
9900 This is the object instance returned when you get the @each property on an
9901 array. It uses the unknownProperty handler to automatically create
9902 EachArray instances for property names.
9907 @extends Ember.Object
9909 Ember.EachProxy = Ember.Object.extend({
9911 init: function(content) {
9913 this._content = content;
9914 content.addArrayObserver(this);
9916 // in case someone is already observing some keys make sure they are
9918 forEach(Ember.watchedEvents(this), function(eventName) {
9919 this.didAddListener(eventName);
9924 You can directly access mapped properties by simply requesting them.
9925 The unknownProperty handler will generate an EachArray of each item.
9927 @method unknownProperty
9928 @param keyName {String}
9929 @param value {anything}
9931 unknownProperty: function(keyName, value) {
9933 ret = new EachArray(this._content, keyName, this);
9934 Ember.defineProperty(this, keyName, null, ret);
9935 this.beginObservingContentKey(keyName);
9939 // ..........................................................
9941 // Invokes whenever the content array itself changes.
9943 arrayWillChange: function(content, idx, removedCnt, addedCnt) {
9944 var keys = this._keys, key, array, lim;
9946 lim = removedCnt>0 ? idx+removedCnt : -1;
9947 Ember.beginPropertyChanges(this);
9950 if (!keys.hasOwnProperty(key)) { continue; }
9952 if (lim>0) removeObserverForContentKey(content, key, this, idx, lim);
9954 Ember.propertyWillChange(this, key);
9957 Ember.propertyWillChange(this._content, '@each');
9958 Ember.endPropertyChanges(this);
9961 arrayDidChange: function(content, idx, removedCnt, addedCnt) {
9962 var keys = this._keys, key, array, lim;
9964 lim = addedCnt>0 ? idx+addedCnt : -1;
9965 Ember.beginPropertyChanges(this);
9968 if (!keys.hasOwnProperty(key)) { continue; }
9970 if (lim>0) addObserverForContentKey(content, key, this, idx, lim);
9972 Ember.propertyDidChange(this, key);
9975 Ember.propertyDidChange(this._content, '@each');
9976 Ember.endPropertyChanges(this);
9979 // ..........................................................
9980 // LISTEN FOR NEW OBSERVERS AND OTHER EVENT LISTENERS
9981 // Start monitoring keys based on who is listening...
9983 didAddListener: function(eventName) {
9984 if (IS_OBSERVER.test(eventName)) {
9985 this.beginObservingContentKey(eventName.slice(0, -7));
9989 didRemoveListener: function(eventName) {
9990 if (IS_OBSERVER.test(eventName)) {
9991 this.stopObservingContentKey(eventName.slice(0, -7));
9995 // ..........................................................
9996 // CONTENT KEY OBSERVING
9997 // Actual watch keys on the source content.
9999 beginObservingContentKey: function(keyName) {
10000 var keys = this._keys;
10001 if (!keys) keys = this._keys = {};
10002 if (!keys[keyName]) {
10004 var content = this._content,
10005 len = get(content, 'length');
10006 addObserverForContentKey(content, keyName, this, 0, len);
10012 stopObservingContentKey: function(keyName) {
10013 var keys = this._keys;
10014 if (keys && (keys[keyName]>0) && (--keys[keyName]<=0)) {
10015 var content = this._content,
10016 len = get(content, 'length');
10017 removeObserverForContentKey(content, keyName, this, 0, len);
10021 contentKeyWillChange: function(obj, keyName) {
10022 Ember.propertyWillChange(this, keyName);
10025 contentKeyDidChange: function(obj, keyName) {
10026 Ember.propertyDidChange(this, keyName);
10040 @submodule ember-runtime
10044 var get = Ember.get, set = Ember.set;
10046 // Add Ember.Array to Array.prototype. Remove methods with native
10047 // implementations and supply some more optimized versions of generic methods
10048 // because they are so common.
10049 var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember.Copyable, {
10051 // because length is a built-in property we need to know to just get the
10052 // original property.
10053 get: function(key) {
10054 if (key==='length') return this.length;
10055 else if ('number' === typeof key) return this[key];
10056 else return this._super(key);
10059 objectAt: function(idx) {
10063 // primitive for array support.
10064 replace: function(idx, amt, objects) {
10066 if (this.isFrozen) throw Ember.FROZEN_ERROR ;
10068 // if we replaced exactly the same number of items, then pass only the
10069 // replaced range. Otherwise, pass the full remaining array length
10070 // since everything has shifted
10071 var len = objects ? get(objects, 'length') : 0;
10072 this.arrayContentWillChange(idx, amt, len);
10074 if (!objects || objects.length === 0) {
10075 this.splice(idx, amt) ;
10077 var args = [idx, amt].concat(objects) ;
10078 this.splice.apply(this,args) ;
10081 this.arrayContentDidChange(idx, amt, len);
10085 // If you ask for an unknown property, then try to collect the value
10086 // from member items.
10087 unknownProperty: function(key, value) {
10088 var ret;// = this.reducedProperty(key, value) ;
10089 if ((value !== undefined) && ret === undefined) {
10090 ret = this[key] = value;
10095 // If browser did not implement indexOf natively, then override with
10096 // specialized version
10097 indexOf: function(object, startAt) {
10098 var idx, len = this.length;
10100 if (startAt === undefined) startAt = 0;
10101 else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
10102 if (startAt < 0) startAt += len;
10104 for(idx=startAt;idx<len;idx++) {
10105 if (this[idx] === object) return idx ;
10110 lastIndexOf: function(object, startAt) {
10111 var idx, len = this.length;
10113 if (startAt === undefined) startAt = len-1;
10114 else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
10115 if (startAt < 0) startAt += len;
10117 for(idx=startAt;idx>=0;idx--) {
10118 if (this[idx] === object) return idx ;
10124 return this.slice();
10128 // Remove any methods implemented natively so we don't override them
10129 var ignore = ['length'];
10130 Ember.EnumerableUtils.forEach(NativeArray.keys(), function(methodName) {
10131 if (Array.prototype[methodName]) ignore.push(methodName);
10134 if (ignore.length>0) {
10135 NativeArray = NativeArray.without.apply(NativeArray, ignore);
10139 The NativeArray mixin contains the properties needed to to make the native
10140 Array support Ember.MutableArray and all of its dependent APIs. Unless you
10141 have Ember.EXTEND_PROTOTYPES set to false, this will be applied automatically.
10142 Otherwise you can apply the mixin at anytime by calling
10143 `Ember.NativeArray.activate`.
10147 @extends Ember.Mixin
10148 @uses Ember.MutableArray
10149 @uses Ember.MutableEnumerable
10150 @uses Ember.Copyable
10151 @uses Ember.Freezable
10153 Ember.NativeArray = NativeArray;
10156 Creates an Ember.NativeArray from an Array like object.
10157 Does not modify the original object.
10161 @return {Ember.NativeArray}
10163 Ember.A = function(arr){
10164 if (arr === undefined) { arr = []; }
10165 return Ember.NativeArray.apply(arr);
10169 Activates the mixin on the Array.prototype if not already applied. Calling
10170 this method more than once is safe.
10173 @for Ember.NativeArray
10177 Ember.NativeArray.activate = function() {
10178 NativeArray.apply(Array.prototype);
10180 Ember.A = function(arr) { return arr || []; };
10183 if (Ember.EXTEND_PROTOTYPES) Ember.NativeArray.activate();
10194 @submodule ember-runtime
10197 var get = Ember.get, set = Ember.set;
10199 Ember._PromiseChain = Ember.Object.extend({
10201 failureCallback: Ember.K,
10202 successCallback: Ember.K,
10203 abortCallback: Ember.K,
10204 promiseSuccessCallback: Ember.K,
10206 runNextPromise: function() {
10207 if (get(this, 'isDestroyed')) { return; }
10209 var item = get(this, 'promises').shiftObject();
10211 var promise = get(item, 'promise') || item;
10212 Ember.assert("Cannot find promise to invoke", Ember.canInvoke(promise, 'then'));
10216 var successCallback = function() {
10217 self.promiseSuccessCallback.call(this, item, arguments);
10218 self.runNextPromise();
10221 var failureCallback = get(self, 'failureCallback');
10223 promise.then(successCallback, failureCallback);
10225 this.successCallback();
10229 start: function() {
10230 this.runNextPromise();
10234 abort: function() {
10235 this.abortCallback();
10240 set(this, 'promises', Ember.A(get(this, 'promises')));
10253 @submodule ember-runtime
10256 var loadHooks = {};
10262 @param name {String} name of hook
10263 @param callback {Function} callback to be called
10265 Ember.onLoad = function(name, callback) {
10268 loadHooks[name] = loadHooks[name] || Ember.A();
10269 loadHooks[name].pushObject(callback);
10271 if (object = loaded[name]) {
10277 @method runLoadHooks
10279 @param name {String} name of hook
10280 @param object {Object} object to pass to callbacks
10282 Ember.runLoadHooks = function(name, object) {
10285 loaded[name] = object;
10287 if (hooks = loadHooks[name]) {
10288 loadHooks[name].forEach(function(callback) {
10307 @submodule ember-runtime
10311 Ember.ControllerMixin provides a standard interface for all classes
10312 that compose Ember's controller layer: Ember.Controller, Ember.ArrayController,
10313 and Ember.ObjectController.
10315 Within an Ember.Router-managed application single shared instaces of every
10316 Controller object in your application's namespace will be added to the
10317 application's Ember.Router instance. See `Ember.Application#initialize`
10318 for additional information.
10321 By default a controller instance will be the rendering context
10322 for its associated Ember.View. This connection is made during calls to
10323 `Ember.ControllerMixin#connectOutlet`.
10325 Within the view's template, the Ember.View instance can be accessed
10326 through the controller with `{{view}}`.
10328 ## Target Forwarding
10329 By default a controller will target your application's Ember.Router instance.
10330 Calls to `{{action}}` within the template of a controller's view are forwarded
10331 to the router. See `Ember.Handlebars.helpers.action` for additional information.
10333 @class ControllerMixin
10335 @extends Ember.Mixin
10337 Ember.ControllerMixin = Ember.Mixin.create({
10339 The object to which events from the view should be sent.
10341 For example, when a Handlebars template uses the `{{action}}` helper,
10342 it will attempt to send the event to the view's controller's `target`.
10344 By default, a controller's `target` is set to the router after it is
10345 instantiated by `Ember.Application#initialize`.
10358 @extends Ember.Object
10359 @uses Ember.ControllerMixin
10361 Ember.Controller = Ember.Object.extend(Ember.ControllerMixin);
10370 @submodule ember-runtime
10373 var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
10376 Ember.SortableMixin provides a standard interface for array proxies
10377 to specify a sort order and maintain this sorting when objects are added,
10378 removed, or updated without changing the implicit order of their underlying
10382 {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'},
10383 {trackNumber: 2, title: 'Back in the U.S.S.R.'},
10384 {trackNumber: 3, title: 'Glass Onion'},
10387 songsController = Ember.ArrayController.create({
10389 sortProperties: ['trackNumber']
10392 songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
10394 songsController.addObject({trackNumber: 1, title: 'Dear Prudence'});
10395 songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'}
10398 @class SortableMixin
10400 @extends Ember.Mixin
10401 @uses Ember.MutableEnumerable
10403 Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
10404 sortProperties: null,
10405 sortAscending: true,
10407 addObject: function(obj) {
10408 var content = get(this, 'content');
10409 content.pushObject(obj);
10412 removeObject: function(obj) {
10413 var content = get(this, 'content');
10414 content.removeObject(obj);
10417 orderBy: function(item1, item2) {
10419 sortProperties = get(this, 'sortProperties'),
10420 sortAscending = get(this, 'sortAscending');
10422 Ember.assert("you need to define `sortProperties`", !!sortProperties);
10424 forEach(sortProperties, function(propertyName) {
10425 if (result === 0) {
10426 result = Ember.compare(get(item1, propertyName), get(item2, propertyName));
10427 if ((result !== 0) && !sortAscending) {
10428 result = (-1) * result;
10436 destroy: function() {
10437 var content = get(this, 'content'),
10438 sortProperties = get(this, 'sortProperties');
10440 if (content && sortProperties) {
10441 forEach(content, function(item) {
10442 forEach(sortProperties, function(sortProperty) {
10443 Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
10448 return this._super();
10451 isSorted: Ember.computed('sortProperties', function() {
10452 return !!get(this, 'sortProperties');
10455 arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) {
10456 var content = get(this, 'content'),
10457 isSorted = get(this, 'isSorted'),
10458 sortProperties = get(this, 'sortProperties'),
10461 if (content && isSorted) {
10462 content = content.slice();
10463 content.sort(function(item1, item2) {
10464 return self.orderBy(item1, item2);
10466 forEach(content, function(item) {
10467 forEach(sortProperties, function(sortProperty) {
10468 Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
10471 return Ember.A(content);
10477 _contentWillChange: Ember.beforeObserver(function() {
10478 var content = get(this, 'content'),
10479 sortProperties = get(this, 'sortProperties');
10481 if (content && sortProperties) {
10482 forEach(content, function(item) {
10483 forEach(sortProperties, function(sortProperty) {
10484 Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
10492 sortAscendingWillChange: Ember.beforeObserver(function() {
10493 this._lastSortAscending = get(this, 'sortAscending');
10494 }, 'sortAscending'),
10496 sortAscendingDidChange: Ember.observer(function() {
10497 if (get(this, 'sortAscending') !== this._lastSortAscending) {
10498 var arrangedContent = get(this, 'arrangedContent');
10499 arrangedContent.reverseObjects();
10501 }, 'sortAscending'),
10503 contentArrayWillChange: function(array, idx, removedCount, addedCount) {
10504 var isSorted = get(this, 'isSorted');
10507 var arrangedContent = get(this, 'arrangedContent');
10508 var removedObjects = array.slice(idx, idx+removedCount);
10509 var sortProperties = get(this, 'sortProperties');
10511 forEach(removedObjects, function(item) {
10512 arrangedContent.removeObject(item);
10514 forEach(sortProperties, function(sortProperty) {
10515 Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
10520 return this._super(array, idx, removedCount, addedCount);
10523 contentArrayDidChange: function(array, idx, removedCount, addedCount) {
10524 var isSorted = get(this, 'isSorted'),
10525 sortProperties = get(this, 'sortProperties');
10528 var addedObjects = array.slice(idx, idx+addedCount);
10529 var arrangedContent = get(this, 'arrangedContent');
10531 forEach(addedObjects, function(item) {
10532 this.insertItemSorted(item);
10534 forEach(sortProperties, function(sortProperty) {
10535 Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
10540 return this._super(array, idx, removedCount, addedCount);
10543 insertItemSorted: function(item) {
10544 var arrangedContent = get(this, 'arrangedContent');
10545 var length = get(arrangedContent, 'length');
10547 var idx = this._binarySearch(item, 0, length);
10548 arrangedContent.insertAt(idx, item);
10551 contentItemSortPropertyDidChange: function(item) {
10552 var arrangedContent = get(this, 'arrangedContent'),
10553 oldIndex = arrangedContent.indexOf(item),
10554 newIndex = this._binarySearch(item, 0, get(arrangedContent, 'length'));
10556 if (newIndex !== oldIndex) {
10557 arrangedContent.removeObject(item);
10558 this.insertItemSorted(item);
10562 _binarySearch: function(item, low, high) {
10563 var mid, midItem, res, arrangedContent;
10565 if (low === high) {
10569 arrangedContent = get(this, 'arrangedContent');
10571 mid = low + Math.floor((high - low) / 2);
10572 midItem = arrangedContent.objectAt(mid);
10574 res = this.orderBy(midItem, item);
10577 return this._binarySearch(item, mid+1, high);
10578 } else if (res > 0) {
10579 return this._binarySearch(item, low, mid);
10593 @submodule ember-runtime
10596 var get = Ember.get, set = Ember.set;
10599 Ember.ArrayController provides a way for you to publish a collection of objects
10600 so that you can easily bind to the collection from a Handlebars #each helper,
10601 an Ember.CollectionView, or other controllers.
10603 The advantage of using an ArrayController is that you only have to set up
10604 your view bindings once; to change what's displayed, simply swap out the
10605 `content` property on the controller.
10607 For example, imagine you wanted to display a list of items fetched via an XHR
10608 request. Create an Ember.ArrayController and set its `content` property:
10611 MyApp.listController = Ember.ArrayController.create();
10613 $.get('people.json', function(data) {
10614 MyApp.listController.set('content', data);
10618 Then, create a view that binds to your new controller:
10621 {{#each MyApp.listController}}
10622 {{firstName}} {{lastName}}
10626 Although you are binding to the controller, the behavior of this controller
10627 is to pass through any methods or properties to the underlying array. This
10628 capability comes from `Ember.ArrayProxy`, which this class inherits from.
10630 Note: As of this writing, `ArrayController` does not add any functionality
10631 to its superclass, `ArrayProxy`. The Ember team plans to add additional
10632 controller-specific functionality in the future, e.g. single or multiple
10633 selection support. If you are creating something that is conceptually a
10634 controller, use this class.
10636 @class ArrayController
10638 @extends Ember.ArrayProxy
10639 @uses Ember.SortableMixin
10640 @uses Ember.ControllerMixin
10643 Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
10644 Ember.SortableMixin);
10653 @submodule ember-runtime
10657 Ember.ObjectController is part of Ember's Controller layer. A single
10658 shared instance of each Ember.ObjectController subclass in your application's
10659 namespace will be created at application initialization and be stored on your
10660 application's Ember.Router instance.
10662 Ember.ObjectController derives its functionality from its superclass
10663 Ember.ObjectProxy and the Ember.ControllerMixin mixin.
10665 @class ObjectController
10667 @extends Ember.ObjectProxy
10668 @uses Ember.ControllerMixin
10670 Ember.ObjectController = Ember.ObjectProxy.extend(Ember.ControllerMixin);
10687 @submodule ember-runtime
10688 @requires ember-metal
10694 function visit(vertex, fn, visited, path) {
10695 var name = vertex.name,
10696 vertices = vertex.incoming,
10697 names = vertex.incomingNames,
10698 len = names.length,
10706 if (visited.hasOwnProperty(name)) {
10710 visited[name] = true;
10711 for (i = 0; i < len; i++) {
10712 visit(vertices[names[i]], fn, visited, path);
10720 this.vertices = {};
10723 DAG.prototype.add = function(name) {
10724 if (!name) { return; }
10725 if (this.vertices.hasOwnProperty(name)) {
10726 return this.vertices[name];
10729 name: name, incoming: {}, incomingNames: [], hasOutgoing: false, value: null
10731 this.vertices[name] = vertex;
10732 this.names.push(name);
10736 DAG.prototype.map = function(name, value) {
10737 this.add(name).value = value;
10740 DAG.prototype.addEdge = function(fromName, toName) {
10741 if (!fromName || !toName || fromName === toName) {
10744 var from = this.add(fromName), to = this.add(toName);
10745 if (to.incoming.hasOwnProperty(fromName)) {
10748 function checkCycle(vertex, path) {
10749 if (vertex.name === toName) {
10750 throw new Error("cycle detected: " + toName + " <- " + path.join(" <- "));
10753 visit(from, checkCycle);
10754 from.hasOutgoing = true;
10755 to.incoming[fromName] = from;
10756 to.incomingNames.push(fromName);
10759 DAG.prototype.topsort = function(fn) {
10761 vertices = this.vertices,
10762 names = this.names,
10763 len = names.length,
10765 for (i = 0; i < len; i++) {
10766 vertex = vertices[names[i]];
10767 if (!vertex.hasOutgoing) {
10768 visit(vertex, fn, visited);
10773 DAG.prototype.addEdges = function(name, value, before, after) {
10775 this.map(name, value);
10777 if (typeof before === 'string') {
10778 this.addEdge(name, before);
10780 for (i = 0; i < before.length; i++) {
10781 this.addEdge(name, before[i]);
10786 if (typeof after === 'string') {
10787 this.addEdge(after, name);
10789 for (i = 0; i < after.length; i++) {
10790 this.addEdge(after[i], name);
10805 @submodule ember-application
10808 var get = Ember.get, set = Ember.set;
10811 An instance of `Ember.Application` is the starting point for every Ember.js
10812 application. It helps to instantiate, initialize and coordinate the many
10813 objects that make up your app.
10815 Each Ember.js app has one and only one `Ember.Application` object. In fact, the very
10816 first thing you should do in your application is create the instance:
10819 window.App = Ember.Application.create();
10822 Typically, the application object is the only global variable. All other
10823 classes in your app should be properties on the `Ember.Application` instance,
10824 which highlights its first role: a global namespace.
10826 For example, if you define a view class, it might look like this:
10829 App.MyView = Ember.View.extend();
10832 After all of your classes are defined, call `App.initialize()` to start the
10835 Because `Ember.Application` inherits from `Ember.Namespace`, any classes
10836 you create will have useful string representations when calling `toString()`;
10837 see the `Ember.Namespace` documentation for more information.
10839 While you can think of your `Ember.Application` as a container that holds the
10840 other classes in your application, there are several other responsibilities
10841 going on under-the-hood that you may want to understand.
10843 ### Event Delegation
10845 Ember.js uses a technique called _event delegation_. This allows the framework
10846 to set up a global, shared event listener instead of requiring each view to do
10847 it manually. For example, instead of each view registering its own `mousedown`
10848 listener on its associated element, Ember.js sets up a `mousedown` listener on
10851 If a `mousedown` event occurs, Ember.js will look at the target of the event and
10852 start walking up the DOM node tree, finding corresponding views and invoking their
10853 `mouseDown` method as it goes.
10855 `Ember.Application` has a number of default events that it listens for, as well
10856 as a mapping from lowercase events to camel-cased view method names. For
10857 example, the `keypress` event causes the `keyPress` method on the view to be
10858 called, the `dblclick` event causes `doubleClick` to be called, and so on.
10860 If there is a browser event that Ember.js does not listen for by default, you
10861 can specify custom events and their corresponding view method names by setting
10862 the application's `customEvents` property:
10865 App = Ember.Application.create({
10867 // add support for the loadedmetadata media
10869 'loadedmetadata': "loadedMetadata"
10874 By default, the application sets up these event listeners on the document body.
10875 However, in cases where you are embedding an Ember.js application inside an
10876 existing page, you may want it to set up the listeners on an element inside
10879 For example, if only events inside a DOM element with the ID of `ember-app` should
10880 be delegated, set your application's `rootElement` property:
10883 window.App = Ember.Application.create({
10884 rootElement: '#ember-app'
10888 The `rootElement` can be either a DOM element or a jQuery-compatible selector
10889 string. Note that *views appended to the DOM outside the root element will not
10890 receive events.* If you specify a custom root element, make sure you only append
10893 To learn more about the advantages of event delegation and the Ember.js view layer,
10894 and a list of the event listeners that are setup by default, visit the
10895 [Ember.js View Layer guide](http://emberjs.com/guides/view_layer#toc_event-delegation).
10897 ### Dependency Injection
10899 One thing you may have noticed while using Ember.js is that you define *classes*, not
10900 *instances*. When your application loads, all of the instances are created for you.
10901 Creating these instances is the responsibility of `Ember.Application`.
10903 When the `Ember.Application` initializes, it will look for an `Ember.Router` class
10904 defined on the applications's `Router` property, like this:
10907 App.Router = Ember.Router.extend({
10912 If found, the router is instantiated and saved on the application's `router`
10913 property (note the lowercase 'r'). While you should *not* reference this router
10914 instance directly from your application code, having access to `App.router`
10915 from the console can be useful during debugging.
10917 After the router is created, the application loops through all of the
10918 registered _injections_ and invokes them once for each property on the
10919 `Ember.Application` object.
10921 An injection is a function that is responsible for instantiating objects from
10922 classes defined on the application. By default, the only injection registered
10923 instantiates controllers and makes them available on the router.
10925 For example, if you define a controller class:
10928 App.MyController = Ember.Controller.extend({
10933 Your router will receive an instance of `App.MyController` saved on its
10934 `myController` property.
10936 Libraries on top of Ember.js can register additional injections. For example,
10937 if your application is using Ember Data, it registers an injection that
10938 instantiates `DS.Store`:
10941 Ember.Application.registerInjection({
10943 before: 'controllers',
10945 injection: function(app, router, property) {
10946 if (property === 'Store') {
10947 set(router, 'store', app[property].create());
10955 In addition to creating your application's router, `Ember.Application` is also
10956 responsible for telling the router when to start routing.
10958 By default, the router will begin trying to translate the current URL into
10959 application state once the browser emits the `DOMContentReady` event. If you
10960 need to defer routing, you can call the application's `deferReadiness()` method.
10961 Once routing can begin, call the `advanceReadiness()` method.
10963 If there is any setup required before routing begins, you can implement a `ready()`
10964 method on your app that will be invoked immediately before routing begins:
10967 window.App = Ember.Application.create({
10968 ready: function() {
10969 this.set('router.enableLogging', true);
10973 To begin routing, you must have at a minimum a top-level controller and view.
10974 You define these as `App.ApplicationController` and `App.ApplicationView`,
10975 respectively. Your application will not work if you do not define these two
10976 mandatory classes. For example:
10979 App.ApplicationView = Ember.View.extend({
10980 templateName: 'application'
10982 App.ApplicationController = Ember.Controller.extend();
10987 @extends Ember.Namespace
10989 Ember.Application = Ember.Namespace.extend(
10990 /** @scope Ember.Application.prototype */{
10993 The root DOM element of the Application. This can be specified as an
10995 [jQuery-compatible selector string](http://api.jquery.com/category/selectors/).
10997 This is the element that will be passed to the Application's,
10998 `eventDispatcher`, which sets up the listeners for event delegation. Every
10999 view in your application should be a child of the element you specify here.
11001 @property rootElement
11005 rootElement: 'body',
11008 The `Ember.EventDispatcher` responsible for delegating events to this
11009 application's views.
11011 The event dispatcher is created by the application at initialization time
11012 and sets up event listeners on the DOM element described by the
11013 application's `rootElement` property.
11015 See the documentation for `Ember.EventDispatcher` for more information.
11017 @property eventDispatcher
11018 @type Ember.EventDispatcher
11021 eventDispatcher: null,
11024 The DOM events for which the event dispatcher should listen.
11026 By default, the application's `Ember.EventDispatcher` listens
11027 for a set of standard DOM events, such as `mousedown` and
11028 `keyup`, and delegates them to your application's `Ember.View`
11031 If you would like additional events to be delegated to your
11032 views, set your `Ember.Application`'s `customEvents` property
11033 to a hash containing the DOM event name as the key and the
11034 corresponding view method name as the value. For example:
11036 App = Ember.Application.create({
11038 // add support for the loadedmetadata media
11040 'loadedmetadata': "loadedMetadata"
11044 @property customEvents
11048 customEvents: null,
11050 autoinit: !Ember.testing,
11052 isInitialized: false,
11055 if (!this.$) { this.$ = Ember.$; }
11059 this.createEventDispatcher();
11061 // Start off the number of deferrals at 1. This will be
11062 // decremented by the Application's own `initialize` method.
11063 this._readinessDeferrals = 1;
11065 this.waitForDOMContentLoaded();
11067 if (this.autoinit) {
11069 this.$().ready(function() {
11070 if (self.isDestroyed || self.isInitialized) return;
11077 createEventDispatcher: function() {
11078 var rootElement = get(this, 'rootElement'),
11079 eventDispatcher = Ember.EventDispatcher.create({
11080 rootElement: rootElement
11083 set(this, 'eventDispatcher', eventDispatcher);
11086 waitForDOMContentLoaded: function() {
11087 this.deferReadiness();
11090 this.$().ready(function() {
11091 self.advanceReadiness();
11095 deferReadiness: function() {
11096 Ember.assert("You cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0);
11097 this._readinessDeferrals++;
11100 advanceReadiness: function() {
11101 this._readinessDeferrals--;
11103 if (this._readinessDeferrals === 0) {
11104 Ember.run.once(this, this.didBecomeReady);
11109 Instantiate all controllers currently available on the namespace
11110 and inject them onto a router.
11114 App.PostsController = Ember.ArrayController.extend();
11115 App.CommentsController = Ember.ArrayController.extend();
11117 var router = Ember.Router.create({
11121 App.initialize(router);
11123 router.get('postsController') // <App.PostsController:ember1234>
11124 router.get('commentsController') // <App.CommentsController:ember1235>
11126 router.get('postsController.router') // router
11129 @param router {Ember.Router}
11131 initialize: function(router) {
11132 Ember.assert("Application initialize may only be call once", !this.isInitialized);
11133 Ember.assert("Application not destroyed", !this.isDestroyed);
11135 router = this.setupRouter(router);
11137 this.runInjections(router);
11139 Ember.runLoadHooks('application', this);
11141 this.isInitialized = true;
11143 // At this point, any injections or load hooks that would have wanted
11144 // to defer readiness have fired.
11145 this.advanceReadiness();
11151 runInjections: function(router) {
11152 var injections = get(this.constructor, 'injections'),
11153 graph = new Ember.DAG(),
11155 properties, i, injection;
11157 for (i=0; i<injections.length; i++) {
11158 injection = injections[i];
11159 graph.addEdges(injection.name, injection.injection, injection.before, injection.after);
11162 graph.topsort(function (vertex) {
11163 var injection = vertex.value,
11164 properties = Ember.A(Ember.keys(namespace));
11165 properties.forEach(function(property) {
11166 injection(namespace, router, property);
11172 setupRouter: function(router) {
11173 if (!router && Ember.Router.detect(this.Router)) {
11174 router = this.Router.create();
11175 this._createdRouter = router;
11179 set(this, 'router', router);
11181 // By default, the router's namespace is the current application.
11183 // This allows it to find model classes when a state has a
11184 // route like `/posts/:post_id`. In that case, it would first
11185 // convert `post_id` into `Post`, and then look it up on its
11187 set(router, 'namespace', this);
11194 didBecomeReady: function() {
11195 var eventDispatcher = get(this, 'eventDispatcher'),
11196 customEvents = get(this, 'customEvents'),
11199 eventDispatcher.setup(customEvents);
11204 router = get(this, 'router');
11206 this.createApplicationView(router);
11208 if (router && router instanceof Ember.Router) {
11209 this.startRouting(router);
11213 createApplicationView: function (router) {
11214 var rootElement = get(this, 'rootElement'),
11215 applicationViewOptions = {},
11216 applicationViewClass = this.ApplicationView,
11217 applicationTemplate = Ember.TEMPLATES.application,
11218 applicationController, applicationView;
11220 // don't do anything unless there is an ApplicationView or application template
11221 if (!applicationViewClass && !applicationTemplate) return;
11224 applicationController = get(router, 'applicationController');
11225 if (applicationController) {
11226 applicationViewOptions.controller = applicationController;
11230 if (applicationTemplate) {
11231 applicationViewOptions.template = applicationTemplate;
11234 if (!applicationViewClass) {
11235 applicationViewClass = Ember.View;
11238 applicationView = applicationViewClass.create(applicationViewOptions);
11240 this._createdApplicationView = applicationView;
11243 set(router, 'applicationView', applicationView);
11246 applicationView.appendTo(rootElement);
11252 If the application has a router, use it to route to the current URL, and
11253 trigger a new call to `route` whenever the URL changes.
11255 @method startRouting
11256 @property router {Ember.Router}
11258 startRouting: function(router) {
11259 var location = get(router, 'location');
11261 Ember.assert("You must have an application template or ApplicationView defined on your application", get(router, 'applicationView') );
11262 Ember.assert("You must have an ApplicationController defined on your application", get(router, 'applicationController') );
11264 router.route(location.getURL());
11265 location.onUpdateURL(function(url) {
11271 Called when the Application has become ready.
11272 The call will be delayed until the DOM has become ready.
11278 willDestroy: function() {
11279 get(this, 'eventDispatcher').destroy();
11280 if (this._createdRouter) { this._createdRouter.destroy(); }
11281 if (this._createdApplicationView) { this._createdApplicationView.destroy(); }
11284 registerInjection: function(options) {
11285 this.constructor.registerInjection(options);
11289 Ember.Application.reopenClass({
11290 concatenatedProperties: ['injections'],
11291 injections: Ember.A(),
11292 registerInjection: function(injection) {
11293 var injections = get(this, 'injections');
11295 Ember.assert("The injection '" + injection.name + "' has already been registered", !injections.findProperty('name', injection.name));
11296 Ember.assert("An injection cannot be registered with both a before and an after", !(injection.before && injection.after));
11297 Ember.assert("An injection cannot be registered without an injection function", Ember.canInvoke(injection, 'injection'));
11299 injections.push(injection);
11303 Ember.Application.registerInjection({
11304 name: 'controllers',
11305 injection: function(app, router, property) {
11306 if (!/^[A-Z].*Controller$/.test(property)) { return; }
11308 var name = property.charAt(0).toLowerCase() + property.substr(1),
11309 controllerClass = app[property], controller;
11311 if(!Ember.Object.detect(controllerClass)){ return; }
11312 controller = app[property].create();
11314 router.set(name, controller);
11316 controller.setProperties({
11318 controllers: router,
11324 Ember.runLoadHooks('Ember.Application', Ember.Application);
11342 @submodule ember-application
11343 @requires ember-views, ember-states, ember-routing
11351 @submodule ember-views
11354 Ember.assert("Ember Views require jQuery 1.7 or 1.8", window.jQuery && (window.jQuery().jquery.match(/^1\.[78](\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
11362 Ember.$ = window.jQuery;
11371 @submodule ember-views
11374 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
11375 var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
11377 // Copies the `dataTransfer` property from a browser event object onto the
11378 // jQuery event object for the specified events
11379 Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
11380 Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
11390 @submodule ember-views
11393 var get = Ember.get, set = Ember.set;
11394 var indexOf = Ember.ArrayPolyfills.indexOf;
11396 var ClassSet = function() {
11401 ClassSet.prototype = {
11402 add: function(string) {
11403 if (string in this.seen) { return; }
11404 this.seen[string] = true;
11406 this.list.push(string);
11409 toDOM: function() {
11410 return this.list.join(" ");
11415 Ember.RenderBuffer gathers information regarding the a view and generates the
11416 final representation. Ember.RenderBuffer will generate HTML which can be pushed
11419 @class RenderBuffer
11423 Ember.RenderBuffer = function(tagName) {
11424 return new Ember._RenderBuffer(tagName);
11427 Ember._RenderBuffer = function(tagName) {
11428 this.elementTag = tagName;
11429 this.childBuffers = [];
11432 Ember._RenderBuffer.prototype =
11433 /** @scope Ember.RenderBuffer.prototype */ {
11436 Array of class-names which will be applied in the class="" attribute
11438 You should not maintain this array yourself, rather, you should use
11439 the addClass() method of Ember.RenderBuffer.
11441 @property elementClasses
11445 elementClasses: null,
11448 The id in of the element, to be applied in the id="" attribute
11450 You should not set this property yourself, rather, you should use
11451 the id() method of Ember.RenderBuffer.
11453 @property elementId
11460 A hash keyed on the name of the attribute and whose value will be
11461 applied to that attribute. For example, if you wanted to apply a
11462 data-view="Foo.bar" property to an element, you would set the
11463 elementAttributes hash to {'data-view':'Foo.bar'}
11465 You should not maintain this hash yourself, rather, you should use
11466 the attr() method of Ember.RenderBuffer.
11468 @property elementAttributes
11472 elementAttributes: null,
11475 The tagname of the element an instance of Ember.RenderBuffer represents.
11477 Usually, this gets set as the first parameter to Ember.RenderBuffer. For
11478 example, if you wanted to create a `p` tag, then you would call
11480 Ember.RenderBuffer('p')
11482 @property elementTag
11489 A hash keyed on the name of the style attribute and whose value will
11490 be applied to that attribute. For example, if you wanted to apply a
11491 background-color:black;" style to an element, you would set the
11492 elementStyle hash to {'background-color':'black'}
11494 You should not maintain this hash yourself, rather, you should use
11495 the style() method of Ember.RenderBuffer.
11497 @property elementStyle
11501 elementStyle: null,
11504 Nested RenderBuffers will set this to their parent RenderBuffer
11507 @property parentBuffer
11508 @type Ember._RenderBuffer
11510 parentBuffer: null,
11513 Adds a string of HTML to the RenderBuffer.
11516 @param {String} string HTML to push into the buffer
11519 push: function(string) {
11520 this.childBuffers.push(String(string));
11525 Adds a class to the buffer, which will be rendered to the class attribute.
11528 @param {String} className Class name to add to the buffer
11531 addClass: function(className) {
11532 // lazily create elementClasses
11533 var elementClasses = this.elementClasses = (this.elementClasses || new ClassSet());
11534 this.elementClasses.add(className);
11540 Sets the elementID to be used for the element.
11547 this.elementId = id;
11551 // duck type attribute functionality like jQuery so a render buffer
11552 // can be used like a jQuery object in attribute binding scenarios.
11555 Adds an attribute which will be rendered to the element.
11558 @param {String} name The name of the attribute
11559 @param {String} value The value to add to the attribute
11561 @return {Ember.RenderBuffer|String} this or the current attribute value
11563 attr: function(name, value) {
11564 var attributes = this.elementAttributes = (this.elementAttributes || {});
11566 if (arguments.length === 1) {
11567 return attributes[name];
11569 attributes[name] = value;
11576 Remove an attribute from the list of attributes to render.
11579 @param {String} name The name of the attribute
11582 removeAttr: function(name) {
11583 var attributes = this.elementAttributes;
11584 if (attributes) { delete attributes[name]; }
11590 Adds a style to the style attribute which will be rendered to the element.
11593 @param {String} name Name of the style
11594 @param {String} value
11597 style: function(name, value) {
11598 var style = this.elementStyle = (this.elementStyle || {});
11600 this.elementStyle[name] = value;
11607 Create a new child render buffer from a parent buffer. Optionally set
11608 additional properties on the buffer. Optionally invoke a callback
11609 with the newly created buffer.
11611 This is a primitive method used by other public methods: `begin`,
11612 `prepend`, `replaceWith`, `insertAfter`.
11615 @param {String} tagName Tag name to use for the child buffer's element
11616 @param {Ember._RenderBuffer} parent The parent render buffer that this
11617 buffer should be appended to.
11618 @param {Function} fn A callback to invoke with the newly created buffer.
11619 @param {Object} other Additional properties to add to the newly created
11622 newBuffer: function(tagName, parent, fn, other) {
11623 var buffer = new Ember._RenderBuffer(tagName);
11624 buffer.parentBuffer = parent;
11626 if (other) { Ember.$.extend(buffer, other); }
11627 if (fn) { fn.call(this, buffer); }
11635 Replace the current buffer with a new buffer. This is a primitive
11636 used by `remove`, which passes `null` for `newBuffer`, and `replaceWith`,
11637 which passes the new buffer it created.
11639 @method replaceWithBuffer
11640 @param {Ember._RenderBuffer} buffer The buffer to insert in place of
11641 the existing buffer.
11643 replaceWithBuffer: function(newBuffer) {
11644 var parent = this.parentBuffer;
11645 if (!parent) { return; }
11647 var childBuffers = parent.childBuffers;
11649 var index = indexOf.call(childBuffers, this);
11652 childBuffers.splice(index, 1, newBuffer);
11654 childBuffers.splice(index, 1);
11659 Creates a new Ember.RenderBuffer object with the provided tagName as
11660 the element tag and with its parentBuffer property set to the current
11661 Ember.RenderBuffer.
11664 @param {String} tagName Tag name to use for the child buffer's element
11665 @return {Ember.RenderBuffer} A new RenderBuffer object
11667 begin: function(tagName) {
11668 return this.newBuffer(tagName, this, function(buffer) {
11669 this.childBuffers.push(buffer);
11674 Prepend a new child buffer to the current render buffer.
11677 @param {String} tagName Tag name to use for the child buffer's element
11679 prepend: function(tagName) {
11680 return this.newBuffer(tagName, this, function(buffer) {
11681 this.childBuffers.splice(0, 0, buffer);
11686 Replace the current buffer with a new render buffer.
11688 @method replaceWith
11689 @param {String} tagName Tag name to use for the new buffer's element
11691 replaceWith: function(tagName) {
11692 var parentBuffer = this.parentBuffer;
11694 return this.newBuffer(tagName, parentBuffer, function(buffer) {
11695 this.replaceWithBuffer(buffer);
11700 Insert a new render buffer after the current render buffer.
11702 @method insertAfter
11703 @param {String} tagName Tag name to use for the new buffer's element
11705 insertAfter: function(tagName) {
11706 var parentBuffer = get(this, 'parentBuffer');
11708 return this.newBuffer(tagName, parentBuffer, function(buffer) {
11709 var siblings = parentBuffer.childBuffers;
11710 var index = indexOf.call(siblings, this);
11711 siblings.splice(index + 1, 0, buffer);
11716 Closes the current buffer and adds its content to the parentBuffer.
11719 @return {Ember.RenderBuffer} The parentBuffer, if one exists. Otherwise, this
11722 var parent = this.parentBuffer;
11723 return parent || this;
11726 remove: function() {
11727 this.replaceWithBuffer(null);
11732 @return {DOMElement} The element corresponding to the generated HTML
11735 element: function() {
11736 return Ember.$(this.string())[0];
11740 Generates the HTML content for this buffer.
11743 @return {String} The generated HTMl
11745 string: function() {
11746 var content = '', tag = this.elementTag, openTag;
11749 var id = this.elementId,
11750 classes = this.elementClasses,
11751 attrs = this.elementAttributes,
11752 style = this.elementStyle,
11753 styleBuffer = '', prop;
11755 openTag = ["<" + tag];
11757 if (id) { openTag.push('id="' + this._escapeAttribute(id) + '"'); }
11758 if (classes) { openTag.push('class="' + this._escapeAttribute(classes.toDOM()) + '"'); }
11761 for (prop in style) {
11762 if (style.hasOwnProperty(prop)) {
11763 styleBuffer += (prop + ':' + this._escapeAttribute(style[prop]) + ';');
11767 openTag.push('style="' + styleBuffer + '"');
11771 for (prop in attrs) {
11772 if (attrs.hasOwnProperty(prop)) {
11773 openTag.push(prop + '="' + this._escapeAttribute(attrs[prop]) + '"');
11778 openTag = openTag.join(" ") + '>';
11781 var childBuffers = this.childBuffers;
11783 Ember.ArrayPolyfills.forEach.call(childBuffers, function(buffer) {
11784 var stringy = typeof buffer === 'string';
11785 content += (stringy ? buffer : buffer.string());
11789 return openTag + content + "</" + tag + ">";
11795 _escapeAttribute: function(value) {
11796 // Stolen shamelessly from Handlebars
11806 var badChars = /&(?!\w+;)|[<>"'`]/g;
11807 var possible = /[&<>"'`]/;
11809 var escapeChar = function(chr) {
11810 return escape[chr] || "&";
11813 var string = value.toString();
11815 if(!possible.test(string)) { return string; }
11816 return string.replace(badChars, escapeChar);
11828 @submodule ember-views
11831 var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
11834 Ember.EventDispatcher handles delegating browser events to their corresponding
11835 Ember.Views. For example, when you click on a view, Ember.EventDispatcher ensures
11836 that that view's `mouseDown` method gets called.
11838 @class EventDispatcher
11841 @extends Ember.Object
11843 Ember.EventDispatcher = Ember.Object.extend(
11844 /** @scope Ember.EventDispatcher.prototype */{
11849 The root DOM element to which event listeners should be attached. Event
11850 listeners will be attached to the document unless this is overridden.
11852 Can be specified as a DOMElement or a selector string.
11854 The default body is a string since this may be evaluated before document.body
11857 @property rootElement
11861 rootElement: 'body',
11866 Sets up event listeners for standard browser events.
11868 This will be called after the browser sends a DOMContentReady event. By
11869 default, it will set up all of the listeners on the document body. If you
11870 would like to register the listeners on a different element, set the event
11871 dispatcher's `root` property.
11874 @param addedEvents {Hash}
11876 setup: function(addedEvents) {
11877 var event, events = {
11878 touchstart : 'touchStart',
11879 touchmove : 'touchMove',
11880 touchend : 'touchEnd',
11881 touchcancel : 'touchCancel',
11882 keydown : 'keyDown',
11884 keypress : 'keyPress',
11885 mousedown : 'mouseDown',
11886 mouseup : 'mouseUp',
11887 contextmenu : 'contextMenu',
11889 dblclick : 'doubleClick',
11890 mousemove : 'mouseMove',
11891 focusin : 'focusIn',
11892 focusout : 'focusOut',
11893 mouseenter : 'mouseEnter',
11894 mouseleave : 'mouseLeave',
11898 dragstart : 'dragStart',
11900 dragenter : 'dragEnter',
11901 dragleave : 'dragLeave',
11902 dragover : 'dragOver',
11904 dragend : 'dragEnd'
11907 Ember.$.extend(events, addedEvents || {});
11909 var rootElement = Ember.$(get(this, 'rootElement'));
11911 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'));
11912 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);
11913 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);
11915 rootElement.addClass('ember-application');
11917 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'));
11919 for (event in events) {
11920 if (events.hasOwnProperty(event)) {
11921 this.setupHandler(rootElement, event, events[event]);
11929 Registers an event listener on the document. If the given event is
11930 triggered, the provided event handler will be triggered on the target
11933 If the target view does not implement the event handler, or if the handler
11934 returns false, the parent view will be called. The event will continue to
11935 bubble to each successive parent view until it reaches the top.
11937 For example, to have the `mouseDown` method called on the target view when
11938 a `mousedown` event is received from the browser, do the following:
11940 setupHandler('mousedown', 'mouseDown');
11942 @method setupHandler
11943 @param {Element} rootElement
11944 @param {String} event the browser-originated event to listen to
11945 @param {String} eventName the name of the method to call on the view
11947 setupHandler: function(rootElement, event, eventName) {
11950 rootElement.delegate('.ember-view', event + '.ember', function(evt, triggeringManager) {
11951 return Ember.handleErrors(function() {
11952 var view = Ember.View.views[this.id],
11953 result = true, manager = null;
11955 manager = self._findNearestEventManager(view,eventName);
11957 if (manager && manager !== triggeringManager) {
11958 result = self._dispatchEvent(manager, evt, eventName, view);
11960 result = self._bubbleEvent(view,evt,eventName);
11962 evt.stopPropagation();
11969 rootElement.delegate('[data-ember-action]', event + '.ember', function(evt) {
11970 return Ember.handleErrors(function() {
11971 var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'),
11972 action = Ember.Handlebars.ActionHelper.registeredActions[actionId],
11973 handler = action.handler;
11975 if (action.eventName === eventName) {
11976 return handler(evt);
11982 _findNearestEventManager: function(view, eventName) {
11983 var manager = null;
11986 manager = get(view, 'eventManager');
11987 if (manager && manager[eventName]) { break; }
11989 view = get(view, 'parentView');
11995 _dispatchEvent: function(object, evt, eventName, view) {
11998 var handler = object[eventName];
11999 if (Ember.typeOf(handler) === 'function') {
12000 result = handler.call(object, evt, view);
12001 // Do not preventDefault in eventManagers.
12002 evt.stopPropagation();
12005 result = this._bubbleEvent(view, evt, eventName);
12011 _bubbleEvent: function(view, evt, eventName) {
12012 return Ember.run(function() {
12013 return view.handleEvent(eventName, evt);
12017 destroy: function() {
12018 var rootElement = get(this, 'rootElement');
12019 Ember.$(rootElement).undelegate('.ember').removeClass('ember-application');
12020 return this._super();
12031 @submodule ember-views
12034 // Add a new named queue for rendering views that happens
12035 // after bindings have synced.
12036 var queues = Ember.run.queues;
12037 queues.splice(Ember.$.inArray('actions', queues)+1, 0, 'render');
12046 @submodule ember-views
12049 var get = Ember.get, set = Ember.set;
12051 // Original class declaration and documentation in runtime/lib/controllers/controller.js
12052 // NOTE: It may be possible with YUIDoc to combine docs in two locations
12055 Additional methods for the ControllerMixin
12057 @class ControllerMixin
12060 Ember.ControllerMixin.reopen({
12068 `connectOutlet` creates a new instance of a provided view
12069 class, wires it up to its associated controller, and
12070 assigns the new view to a property on the current controller.
12072 The purpose of this method is to enable views that use
12073 outlets to quickly assign new views for a given outlet.
12075 For example, an application view's template may look like
12083 The view for this outlet is specified by assigning a
12084 `view` property to the application's controller. The
12085 following code will assign a new `App.PostsView` to
12089 applicationController.connectOutlet('posts');
12092 In general, you will also want to assign a controller
12093 to the newly created view. By convention, a controller
12094 named `postsController` will be assigned as the view's
12097 In an application initialized using `app.initialize(router)`,
12098 `connectOutlet` will look for `postsController` on the
12099 router. The initialization process will automatically
12100 create an instance of `App.PostsController` called
12101 `postsController`, so you don't need to do anything
12102 beyond `connectOutlet` to assign your view and wire it
12103 up to its associated controller.
12105 You can supply a `content` for the controller by supplying
12106 a final argument after the view class:
12109 applicationController.connectOutlet('posts', App.Post.find());
12112 You can specify a particular outlet to use. For example, if your main
12113 template looks like:
12121 You can assign an `App.PostsView` to the master outlet:
12124 applicationController.connectOutlet({
12126 outletName: 'master',
12127 context: App.Post.find()
12131 You can write this as:
12134 applicationController.connectOutlet('master', 'posts', App.Post.find());
12138 @method connectOutlet
12139 @param {String} outletName a name for the outlet to set
12140 @param {String} name a view/controller pair name
12141 @param {Object} context a context object to assign to the
12142 controller's `content` property, if a controller can be
12145 connectOutlet: function(name, context) {
12146 // Normalize arguments. Supported arguments:
12150 // outletName, name
12151 // outletName, name, context
12154 // The options hash has the following keys:
12156 // name: the name of the controller and view
12157 // to use. If this is passed, the name
12158 // determines the view and controller.
12159 // outletName: the name of the outlet to
12160 // fill in. default: 'view'
12161 // viewClass: the class of the view to instantiate
12162 // controller: the controller instance to pass
12164 // context: an object that should become the
12165 // controller's `content` and thus the
12166 // template's context.
12168 var outletName, viewClass, view, controller, options;
12170 if (Ember.typeOf(context) === 'string') {
12173 context = arguments[2];
12176 if (arguments.length === 1) {
12177 if (Ember.typeOf(name) === 'object') {
12179 outletName = options.outletName;
12180 name = options.name;
12181 viewClass = options.viewClass;
12182 controller = options.controller;
12183 context = options.context;
12189 outletName = outletName || 'view';
12191 Ember.assert("The viewClass is either missing or the one provided did not resolve to a view", !!name || (!name && !!viewClass));
12193 Ember.assert("You must supply a name or a viewClass to connectOutlet, but not both", (!!name && !viewClass && !controller) || (!name && !!viewClass));
12196 var namespace = get(this, 'namespace'),
12197 controllers = get(this, 'controllers');
12199 var viewClassName = name.charAt(0).toUpperCase() + name.substr(1) + "View";
12200 viewClass = get(namespace, viewClassName);
12201 controller = get(controllers, name + 'Controller');
12203 Ember.assert("The name you supplied " + name + " did not resolve to a view " + viewClassName, !!viewClass);
12204 Ember.assert("The name you supplied " + name + " did not resolve to a controller " + name + 'Controller', (!!controller && !!context) || !context);
12207 if (controller && context) { set(controller, 'content', context); }
12209 view = this.createOutletView(outletName, viewClass);
12211 if (controller) { set(view, 'controller', controller); }
12212 set(this, outletName, view);
12218 Convenience method to connect controllers. This method makes other controllers
12219 available on the controller the method was invoked on.
12221 For example, to make the `personController` and the `postController` available
12222 on the `overviewController`, you would call:
12224 overviewController.connectControllers('person', 'post');
12226 @method connectControllers
12227 @param {String...} controllerNames the controllers to make available
12229 connectControllers: function() {
12230 var controllers = get(this, 'controllers'),
12231 controllerNames = Array.prototype.slice.apply(arguments),
12234 for (var i=0, l=controllerNames.length; i<l; i++) {
12235 controllerName = controllerNames[i] + 'Controller';
12236 set(this, controllerName, get(controllers, controllerName));
12241 `disconnectOutlet` removes previously attached view from given outlet.
12243 @method disconnectOutlet
12244 @param {String} outletName the outlet name. (optional)
12246 disconnectOutlet: function(outletName) {
12247 outletName = outletName || 'view';
12249 set(this, outletName, null);
12253 `createOutletView` is a hook you may want to override if you need to do
12254 something special with the view created for the outlet. For example
12255 you may want to implement views sharing across outlets.
12257 @method createOutletView
12258 @param outletName {String}
12259 @param viewClass {Ember.View}
12261 createOutletView: function(outletName, viewClass) {
12262 return viewClass.create();
12279 @submodule ember-views
12282 var get = Ember.get, set = Ember.set, addObserver = Ember.addObserver, removeObserver = Ember.removeObserver;
12283 var meta = Ember.meta, fmt = Ember.String.fmt;
12284 var a_slice = [].slice;
12285 var a_forEach = Ember.EnumerableUtils.forEach;
12287 var childViewsProperty = Ember.computed(function() {
12288 var childViews = this._childViews;
12290 var ret = Ember.A();
12292 a_forEach(childViews, function(view) {
12293 if (view.isVirtual) {
12294 ret.pushObjects(get(view, 'childViews'));
12301 }).property().cacheable();
12303 var VIEW_PRESERVES_CONTEXT = Ember.VIEW_PRESERVES_CONTEXT;
12304 Ember.warn("The way that the {{view}} helper affects templates is about to change. Previously, templates inside child views would use the new view as the context. Soon, views will preserve their parent context when rendering their template. You can opt-in early to the new behavior by setting `ENV.VIEW_PRESERVES_CONTEXT = true`. For more information, see https://gist.github.com/2494968. You should update your templates as soon as possible; this default will change soon, and the option will be eliminated entirely before the 1.0 release.", VIEW_PRESERVES_CONTEXT);
12307 Global hash of shared templates. This will automatically be populated
12308 by the build tools so that you can store your Handlebars templates in
12309 separate files that get loaded into JavaScript at buildtime.
12311 @property TEMPLATES
12315 Ember.TEMPLATES = {};
12317 var invokeForState = {
12326 `Ember.View` is the class in Ember responsible for encapsulating templates of HTML
12327 content, combining templates with data to render as sections of a page's DOM, and
12328 registering and responding to user-initiated events.
12331 The default HTML tag name used for a view's DOM representation is `div`. This can be
12332 customized by setting the `tagName` property. The following view class:
12335 ParagraphView = Ember.View.extend({
12340 Would result in instances with the following HTML:
12343 <em id="ember1" class="ember-view"></em>
12346 ## HTML `class` Attribute
12347 The HTML `class` attribute of a view's tag can be set by providing a `classNames` property
12348 that is set to an array of strings:
12351 MyView = Ember.View.extend({
12352 classNames: ['my-class', 'my-other-class']
12356 Will result in view instances with an HTML representation of:
12359 <div id="ember1" class="ember-view my-class my-other-class"></div>
12362 `class` attribute values can also be set by providing a `classNameBindings` property
12363 set to an array of properties names for the view. The return value of these properties
12364 will be added as part of the value for the view's `class` attribute. These properties
12365 can be computed properties:
12368 MyView = Ember.View.extend({
12369 classNameBindings: ['propertyA', 'propertyB'],
12370 propertyA: 'from-a',
12371 propertyB: function(){
12372 if(someLogic){ return 'from-b'; }
12377 Will result in view instances with an HTML representation of:
12380 <div id="ember1" class="ember-view from-a from-b"></div>
12383 If the value of a class name binding returns a boolean the property name itself
12384 will be used as the class name if the property is true. The class name will
12385 not be added if the value is `false` or `undefined`.
12388 MyView = Ember.View.extend({
12389 classNameBindings: ['hovered'],
12394 Will result in view instances with an HTML representation of:
12397 <div id="ember1" class="ember-view hovered"></div>
12400 When using boolean class name bindings you can supply a string value other than the
12401 property name for use as the `class` HTML attribute by appending the preferred value after
12402 a ":" character when defining the binding:
12405 MyView = Ember.View.extend({
12406 classNameBindings: ['awesome:so-very-cool'],
12411 Will result in view instances with an HTML representation of:
12414 <div id="ember1" class="ember-view so-very-cool"></div>
12418 Boolean value class name bindings whose property names are in a camelCase-style
12419 format will be converted to a dasherized format:
12422 MyView = Ember.View.extend({
12423 classNameBindings: ['isUrgent'],
12428 Will result in view instances with an HTML representation of:
12431 <div id="ember1" class="ember-view is-urgent"></div>
12435 Class name bindings can also refer to object values that are found by
12436 traversing a path relative to the view itself:
12439 MyView = Ember.View.extend({
12440 classNameBindings: ['messages.empty']
12441 messages: Ember.Object.create({
12447 Will result in view instances with an HTML representation of:
12450 <div id="ember1" class="ember-view empty"></div>
12454 If you want to add a class name for a property which evaluates to true and
12455 and a different class name if it evaluates to false, you can pass a binding
12459 // Applies 'enabled' class when isEnabled is true and 'disabled' when isEnabled is false
12460 Ember.View.create({
12461 classNameBindings: ['isEnabled:enabled:disabled']
12466 Will result in view instances with an HTML representation of:
12469 <div id="ember1" class="ember-view enabled"></div>
12472 When isEnabled is `false`, the resulting HTML reprensentation looks like this:
12475 <div id="ember1" class="ember-view disabled"></div>
12478 This syntax offers the convenience to add a class if a property is `false`:
12481 // Applies no class when isEnabled is true and class 'disabled' when isEnabled is false
12482 Ember.View.create({
12483 classNameBindings: ['isEnabled::disabled']
12488 Will result in view instances with an HTML representation of:
12491 <div id="ember1" class="ember-view"></div>
12494 When the `isEnabled` property on the view is set to `false`, it will result
12495 in view instances with an HTML representation of:
12498 <div id="ember1" class="ember-view disabled"></div>
12501 Updates to the the value of a class name binding will result in automatic update
12502 of the HTML `class` attribute in the view's rendered HTML representation.
12503 If the value becomes `false` or `undefined` the class name will be removed.
12505 Both `classNames` and `classNameBindings` are concatenated properties.
12506 See `Ember.Object` documentation for more information about concatenated properties.
12510 The HTML attribute section of a view's tag can be set by providing an `attributeBindings`
12511 property set to an array of property names on the view. The return value of these properties
12512 will be used as the value of the view's HTML associated attribute:
12515 AnchorView = Ember.View.extend({
12517 attributeBindings: ['href'],
12518 href: 'http://google.com'
12522 Will result in view instances with an HTML representation of:
12525 <a id="ember1" class="ember-view" href="http://google.com"></a>
12528 If the return value of an `attributeBindings` monitored property is a boolean
12529 the property will follow HTML's pattern of repeating the attribute's name as
12533 MyTextInput = Ember.View.extend({
12535 attributeBindings: ['disabled'],
12540 Will result in view instances with an HTML representation of:
12543 <input id="ember1" class="ember-view" disabled="disabled" />
12546 `attributeBindings` can refer to computed properties:
12549 MyTextInput = Ember.View.extend({
12551 attributeBindings: ['disabled'],
12552 disabled: function(){
12562 Updates to the the property of an attribute binding will result in automatic update
12563 of the HTML attribute in the view's rendered HTML representation.
12565 `attributeBindings` is a concatenated property. See `Ember.Object` documentation
12566 for more information about concatenated properties.
12569 The HTML contents of a view's rendered representation are determined by its template.
12570 Templates can be any function that accepts an optional context parameter and returns
12571 a string of HTML that will be inserted within the view's tag. Most
12572 typically in Ember this function will be a compiled Ember.Handlebars template.
12575 AView = Ember.View.extend({
12576 template: Ember.Handlebars.compile('I am the template')
12580 Will result in view instances with an HTML representation of:
12583 <div id="ember1" class="ember-view">I am the template</div>
12586 The default context of the compiled template will be the view instance itself:
12589 AView = Ember.View.extend({
12590 template: Ember.Handlebars.compile('Hello {{excitedGreeting}}')
12593 aView = AView.create({
12594 content: Ember.Object.create({
12597 excitedGreeting: function(){
12598 return this.get("content.firstName") + "!!!"
12603 Will result in an HTML representation of:
12606 <div id="ember1" class="ember-view">Hello Barry!!!</div>
12609 Within an Ember application is more common to define a Handlebars templates as
12613 <script type='text/x-handlebars' data-template-name='some-template'>
12618 And associate it by name using a view's `templateName` property:
12621 AView = Ember.View.extend({
12622 templateName: 'some-template'
12626 Using a value for `templateName` that does not have a Handlebars template with a
12627 matching `data-template-name` attribute will throw an error.
12629 Assigning a value to both `template` and `templateName` properties will throw an error.
12631 For views classes that may have a template later defined (e.g. as the block portion of a `{{view}}`
12632 Handlebars helper call in another template or in a subclass), you can provide a `defaultTemplate`
12633 property set to compiled template function. If a template is not later provided for the view
12634 instance the `defaultTemplate` value will be used:
12637 AView = Ember.View.extend({
12638 defaultTemplate: Ember.Handlebars.compile('I was the default'),
12644 Will result in instances with an HTML representation of:
12647 <div id="ember1" class="ember-view">I was the default</div>
12650 If a `template` or `templateName` is provided it will take precedence over `defaultTemplate`:
12653 AView = Ember.View.extend({
12654 defaultTemplate: Ember.Handlebars.compile('I was the default')
12657 aView = AView.create({
12658 template: Ember.Handlebars.compile('I was the template, not default')
12662 Will result in the following HTML representation when rendered:
12665 <div id="ember1" class="ember-view">I was the template, not default</div>
12670 Views can have a secondary template that wraps their main template. Like
12671 primary templates, layouts can be any function that accepts an optional context
12672 parameter and returns a string of HTML that will be inserted inside view's tag. Views whose HTML
12673 element is self closing (e.g. `<input />`) cannot have a layout and this property will be ignored.
12675 Most typically in Ember a layout will be a compiled Ember.Handlebars template.
12677 A view's layout can be set directly with the `layout` property or reference an
12678 existing Handlebars template by name with the `layoutName` property.
12680 A template used as a layout must contain a single use of the Handlebars `{{yield}}`
12681 helper. The HTML contents of a view's rendered `template` will be inserted at this location:
12684 AViewWithLayout = Ember.View.extend({
12685 layout: Ember.Handlebars.compile("<div class='my-decorative-class'>{{yield}}</div>")
12686 template: Ember.Handlebars.compile("I got wrapped"),
12690 Will result in view instances with an HTML representation of:
12693 <div id="ember1" class="ember-view">
12694 <div class="my-decorative-class">
12700 See `Handlebars.helpers.yield` for more information.
12702 ## Responding to Browser Events
12704 Views can respond to user-initiated events in one of three ways: method implementation,
12705 through an event manager, and through `{{action}}` helper use in their template or layout.
12707 ### Method Implementation
12709 Views can respond to user-initiated events by implementing a method that matches the
12710 event name. A `jQuery.Event` object will be passed as the argument to this method.
12713 AView = Ember.View.extend({
12714 click: function(event){
12715 // will be called when when an instance's
12716 // rendered element is clicked
12723 Views can define an object as their `eventManager` property. This object can then
12724 implement methods that match the desired event names. Matching events that occur
12725 on the view's rendered HTML or the rendered HTML of any of its DOM descendants
12726 will trigger this method. A `jQuery.Event` object will be passed as the first
12727 argument to the method and an `Ember.View` object as the second. The `Ember.View`
12728 will be the view whose rendered HTML was interacted with. This may be the view with
12729 the `eventManager` property or one of its descendent views.
12732 AView = Ember.View.extend({
12733 eventManager: Ember.Object.create({
12734 doubleClick: function(event, view){
12735 // will be called when when an instance's
12736 // rendered element or any rendering
12737 // of this views's descendent
12738 // elements is clicked
12745 An event defined for an event manager takes precedence over events of the same
12746 name handled through methods on the view.
12749 AView = Ember.View.extend({
12750 mouseEnter: function(event){
12751 // will never trigger.
12753 eventManager: Ember.Object.create({
12754 mouseEnter: function(event, view){
12755 // takes presedence over AView#mouseEnter
12761 Similarly a view's event manager will take precedence for events of any views
12762 rendered as a descendent. A method name that matches an event name will not be called
12763 if the view instance was rendered inside the HTML representation of a view that has
12764 an `eventManager` property defined that handles events of the name. Events not handled
12765 by the event manager will still trigger method calls on the descendent.
12768 OuterView = Ember.View.extend({
12769 template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"),
12770 eventManager: Ember.Object.create({
12771 mouseEnter: function(event, view){
12772 // view might be instance of either
12773 // OutsideView or InnerView depending on
12774 // where on the page the user interaction occured
12779 InnerView = Ember.View.extend({
12780 click: function(event){
12781 // will be called if rendered inside
12782 // an OuterView because OuterView's
12783 // eventManager doesn't handle click events
12785 mouseEnter: function(event){
12786 // will never be called if rendered inside
12792 ### Handlebars `{{action}}` Helper
12794 See `Handlebars.helpers.action`.
12798 Possible events names for any of the responding approaches described above are:
12800 Touch events: 'touchStart', 'touchMove', 'touchEnd', 'touchCancel'
12802 Keyboard events: 'keyDown', 'keyUp', 'keyPress'
12804 Mouse events: 'mouseDown', 'mouseUp', 'contextMenu', 'click', 'doubleClick', 'mouseMove',
12805 'focusIn', 'focusOut', 'mouseEnter', 'mouseLeave'
12807 Form events: 'submit', 'change', 'focusIn', 'focusOut', 'input'
12809 HTML5 drag and drop events: 'dragStart', 'drag', 'dragEnter', 'dragLeave', 'drop', 'dragEnd'
12811 ## Handlebars `{{view}}` Helper
12813 Other `Ember.View` instances can be included as part of a view's template by using the `{{view}}`
12814 Handlebars helper. See `Handlebars.helpers.view` for additional information.
12818 @extends Ember.Object
12819 @uses Ember.Evented
12821 Ember.View = Ember.Object.extend(Ember.Evented,
12822 /** @scope Ember.View.prototype */ {
12824 concatenatedProperties: ['classNames', 'classNameBindings', 'attributeBindings'],
12834 // ..........................................................
12835 // TEMPLATE SUPPORT
12839 The name of the template to lookup if no template is provided.
12841 Ember.View will look for a template with this name in this view's
12842 `templates` object. By default, this will be a global object
12843 shared in `Ember.TEMPLATES`.
12845 @property templateName
12849 templateName: null,
12852 The name of the layout to lookup if no layout is provided.
12854 Ember.View will look for a template with this name in this view's
12855 `templates` object. By default, this will be a global object
12856 shared in `Ember.TEMPLATES`.
12858 @property layoutName
12865 The hash in which to look for `templateName`.
12867 @property templates
12869 @default Ember.TEMPLATES
12871 templates: Ember.TEMPLATES,
12874 The template used to render the view. This should be a function that
12875 accepts an optional context parameter and returns a string of HTML that
12876 will be inserted into the DOM relative to its parent view.
12878 In general, you should set the `templateName` property instead of setting
12879 the template yourself.
12884 template: Ember.computed(function(key, value) {
12885 if (value !== undefined) { return value; }
12887 var templateName = get(this, 'templateName'),
12888 template = this.templateForName(templateName, 'template');
12890 return template || get(this, 'defaultTemplate');
12891 }).property('templateName').cacheable(),
12894 The controller managing this view. If this property is set, it will be
12895 made available for use by the template.
12897 @property controller
12900 controller: Ember.computed(function(key, value) {
12903 if (arguments.length === 2) {
12906 parentView = get(this, 'parentView');
12907 return parentView ? get(parentView, 'controller') : null;
12909 }).property().cacheable(),
12912 A view may contain a layout. A layout is a regular template but
12913 supersedes the `template` property during rendering. It is the
12914 responsibility of the layout template to retrieve the `template`
12915 property from the view (or alternatively, call `Handlebars.helpers.yield`,
12916 `{{yield}}`) to render it in the correct location.
12918 This is useful for a view that has a shared wrapper, but which delegates
12919 the rendering of the contents of the wrapper to the `template` property
12925 layout: Ember.computed(function(key, value) {
12926 if (arguments.length === 2) { return value; }
12928 var layoutName = get(this, 'layoutName'),
12929 layout = this.templateForName(layoutName, 'layout');
12931 return layout || get(this, 'defaultLayout');
12932 }).property('layoutName').cacheable(),
12934 templateForName: function(name, type) {
12935 if (!name) { return; }
12937 var templates = get(this, 'templates'),
12938 template = get(templates, name);
12941 throw new Ember.Error(fmt('%@ - Unable to find %@ "%@".', [this, type, name]));
12948 The object from which templates should access properties.
12950 This object will be passed to the template function each time the render
12951 method is called, but it is up to the individual function to decide what
12954 By default, this will be the view itself.
12959 context: Ember.computed(function(key, value) {
12960 if (arguments.length === 2) {
12961 set(this, '_context', value);
12964 return get(this, '_context');
12971 Private copy of the view's template context. This can be set directly
12972 by Handlebars without triggering the observer that causes the view
12975 The context of a view is looked up as follows:
12977 1. Supplied context (usually by Handlebars)
12978 2. Specified controller
12979 3. `parentView`'s context (for a child of a ContainerView)
12981 The code in Handlebars that overrides the `_context` property first
12982 checks to see whether the view has a specified controller. This is
12983 something of a hack and should be revisited.
12987 _context: Ember.computed(function(key, value) {
12988 var parentView, controller;
12990 if (arguments.length === 2) {
12994 if (VIEW_PRESERVES_CONTEXT) {
12995 if (controller = get(this, 'controller')) {
12999 parentView = get(this, '_parentView');
13001 return get(parentView, '_context');
13011 If a value that affects template rendering changes, the view should be
13012 re-rendered to reflect the new value.
13014 @method _displayPropertyDidChange
13016 _displayPropertyDidChange: Ember.observer(function() {
13018 }, 'context', 'controller'),
13021 If the view is currently inserted into the DOM of a parent view, this
13022 property will point to the parent of the view.
13024 @property parentView
13028 parentView: Ember.computed(function() {
13029 var parent = get(this, '_parentView');
13031 if (parent && parent.isVirtual) {
13032 return get(parent, 'parentView');
13036 }).property('_parentView').volatile(),
13040 // return the current view, not including virtual views
13041 concreteView: Ember.computed(function() {
13042 if (!this.isVirtual) { return this; }
13043 else { return get(this, 'parentView'); }
13044 }).property('_parentView').volatile(),
13047 If false, the view will appear hidden in DOM.
13049 @property isVisible
13058 Array of child views. You should never edit this array directly.
13059 Instead, use appendChild and removeFromParent.
13061 @property childViews
13065 childViews: childViewsProperty,
13069 // When it's a virtual view, we need to notify the parent that their
13070 // childViews will change.
13071 _childViewsWillChange: Ember.beforeObserver(function() {
13072 if (this.isVirtual) {
13073 var parentView = get(this, 'parentView');
13074 if (parentView) { Ember.propertyWillChange(parentView, 'childViews'); }
13078 // When it's a virtual view, we need to notify the parent that their
13079 // childViews did change.
13080 _childViewsDidChange: Ember.observer(function() {
13081 if (this.isVirtual) {
13082 var parentView = get(this, 'parentView');
13083 if (parentView) { Ember.propertyDidChange(parentView, 'childViews'); }
13088 Return the nearest ancestor that is an instance of the provided
13091 @property nearestInstanceOf
13092 @param {Class} klass Subclass of Ember.View (or Ember.View itself)
13095 nearestInstanceOf: function(klass) {
13096 var view = get(this, 'parentView');
13099 if(view instanceof klass) { return view; }
13100 view = get(view, 'parentView');
13105 Return the nearest ancestor that has a given property.
13107 @property nearestWithProperty
13108 @param {String} property A property name
13111 nearestWithProperty: function(property) {
13112 var view = get(this, 'parentView');
13115 if (property in view) { return view; }
13116 view = get(view, 'parentView');
13121 Return the nearest ancestor whose parent is an instance of
13124 @property nearestChildOf
13125 @param {Class} klass Subclass of Ember.View (or Ember.View itself)
13128 nearestChildOf: function(klass) {
13129 var view = get(this, 'parentView');
13132 if(get(view, 'parentView') instanceof klass) { return view; }
13133 view = get(view, 'parentView');
13138 Return the nearest ancestor that is an Ember.CollectionView
13140 @property collectionView
13141 @return Ember.CollectionView
13143 collectionView: Ember.computed(function() {
13144 return this.nearestInstanceOf(Ember.CollectionView);
13148 Return the nearest ancestor that is a direct child of
13149 an Ember.CollectionView
13154 itemView: Ember.computed(function() {
13155 return this.nearestChildOf(Ember.CollectionView);
13159 Return the nearest ancestor that has the property
13162 @property contentView
13165 contentView: Ember.computed(function() {
13166 return this.nearestWithProperty('content');
13172 When the parent view changes, recursively invalidate
13173 collectionView, itemView, and contentView
13175 @method _parentViewDidChange
13177 _parentViewDidChange: Ember.observer(function() {
13178 if (this.isDestroying) { return; }
13180 this.invokeRecursively(function(view) {
13181 view.propertyDidChange('collectionView');
13182 view.propertyDidChange('itemView');
13183 view.propertyDidChange('contentView');
13186 if (get(this, 'parentView.controller') && !get(this, 'controller')) {
13187 this.notifyPropertyChange('controller');
13191 _controllerDidChange: Ember.observer(function() {
13192 if (this.isDestroying) { return; }
13194 this.forEachChildView(function(view) {
13195 view.propertyDidChange('controller');
13199 cloneKeywords: function() {
13200 var templateData = get(this, 'templateData');
13202 var keywords = templateData ? Ember.copy(templateData.keywords) : {};
13203 set(keywords, 'view', get(this, 'concreteView'));
13204 set(keywords, 'controller', get(this, 'controller'));
13210 Called on your view when it should push strings of HTML into a
13211 Ember.RenderBuffer. Most users will want to override the `template`
13212 or `templateName` properties instead of this method.
13214 By default, Ember.View will look for a function in the `template`
13215 property and invoke it with the value of `context`. The value of
13216 `context` will be the view's controller unless you override it.
13219 @param {Ember.RenderBuffer} buffer The render buffer
13221 render: function(buffer) {
13222 // If this view has a layout, it is the responsibility of the
13223 // the layout to render the view's template. Otherwise, render the template
13225 var template = get(this, 'layout') || get(this, 'template');
13228 var context = get(this, 'context');
13229 var keywords = this.cloneKeywords();
13234 isRenderData: true,
13238 // Invoke the template with the provided template context, which
13239 // is the view by default. A hash of data is also passed that provides
13240 // the template with access to the view and render buffer.
13242 Ember.assert('template must be a function. Did you mean to call Ember.Handlebars.compile("...") or specify templateName instead?', typeof template === 'function');
13243 // The template should write directly to the render buffer instead
13244 // of returning a string.
13245 var output = template(context, { data: data });
13247 // If the template returned a string instead of writing to the buffer,
13248 // push the string onto the buffer.
13249 if (output !== undefined) { buffer.push(output); }
13253 invokeForState: function(name) {
13254 var stateName = this.state, args, fn;
13256 // try to find the function for the state in the cache
13257 if (fn = invokeForState[stateName][name]) {
13258 args = a_slice.call(arguments);
13261 return fn.apply(this, args);
13264 // otherwise, find and cache the function for this state
13265 var parent = this, states = parent.states, state;
13268 state = states[stateName];
13274 invokeForState[stateName][name] = fn;
13276 args = a_slice.call(arguments, 1);
13277 args.unshift(this);
13279 return fn.apply(this, args);
13282 state = state.parentState;
13285 states = states.parent;
13290 Renders the view again. This will work regardless of whether the
13291 view is already in the DOM or not. If the view is in the DOM, the
13292 rendering process will be deferred to give bindings a chance
13295 If children were added during the rendering process using `appendChild`,
13296 `rerender` will remove them, because they will be added again
13297 if needed by the next `render`.
13299 In general, if the display of your view changes, you should modify
13300 the DOM element directly instead of manually calling `rerender`, which can
13305 rerender: function() {
13306 return this.invokeForState('rerender');
13309 clearRenderedChildren: function() {
13310 var lengthBefore = this.lengthBeforeRender,
13311 lengthAfter = this.lengthAfterRender;
13313 // If there were child views created during the last call to render(),
13314 // remove them under the assumption that they will be re-created when
13317 // VIEW-TODO: Unit test this path.
13318 var childViews = this._childViews;
13319 for (var i=lengthAfter-1; i>=lengthBefore; i--) {
13320 if (childViews[i]) { childViews[i].destroy(); }
13327 Iterates over the view's `classNameBindings` array, inserts the value
13328 of the specified property into the `classNames` array, then creates an
13329 observer to update the view's element if the bound property ever changes
13332 @method _applyClassNameBindings
13334 _applyClassNameBindings: function() {
13335 var classBindings = get(this, 'classNameBindings'),
13336 classNames = get(this, 'classNames'),
13337 elem, newClass, dasherizedClass;
13339 if (!classBindings) { return; }
13341 // Loop through all of the configured bindings. These will be either
13342 // property names ('isUrgent') or property paths relative to the view
13343 // ('content.isUrgent')
13344 a_forEach(classBindings, function(binding) {
13346 // Variable in which the old class value is saved. The observer function
13347 // closes over this variable, so it knows which string to remove when
13348 // the property changes.
13350 // Extract just the property name from bindings like 'foo:bar'
13351 var parsedPath = Ember.View._parsePropertyPath(binding);
13353 // Set up an observer on the context. If the property changes, toggle the
13355 var observer = function() {
13356 // Get the current value of the property
13357 newClass = this._classStringForProperty(binding);
13360 removeObserver(this, parsedPath.path, observer);
13364 // If we had previously added a class to the element, remove it.
13366 elem.removeClass(oldClass);
13367 // Also remove from classNames so that if the view gets rerendered,
13368 // the class doesn't get added back to the DOM.
13369 classNames.removeObject(oldClass);
13372 // If necessary, add a new class. Make sure we keep track of it so
13373 // it can be removed in the future.
13375 elem.addClass(newClass);
13376 oldClass = newClass;
13382 // Get the class name for the property at its current value
13383 dasherizedClass = this._classStringForProperty(binding);
13385 if (dasherizedClass) {
13386 // Ensure that it gets into the classNames array
13387 // so it is displayed when we render.
13388 classNames.push(dasherizedClass);
13390 // Save a reference to the class name so we can remove it
13391 // if the observer fires. Remember that this variable has
13392 // been closed over by the observer.
13393 oldClass = dasherizedClass;
13396 addObserver(this, parsedPath.path, observer);
13403 Iterates through the view's attribute bindings, sets up observers for each,
13404 then applies the current value of the attributes to the passed render buffer.
13406 @method _applyAttributeBindings
13407 @param {Ember.RenderBuffer} buffer
13409 _applyAttributeBindings: function(buffer) {
13410 var attributeBindings = get(this, 'attributeBindings'),
13411 attributeValue, elem, type;
13413 if (!attributeBindings) { return; }
13415 a_forEach(attributeBindings, function(binding) {
13416 var split = binding.split(':'),
13417 property = split[0],
13418 attributeName = split[1] || property;
13420 // Create an observer to add/remove/change the attribute if the
13421 // JavaScript property changes.
13422 var observer = function() {
13424 if (!elem) { return; }
13426 attributeValue = get(this, property);
13428 Ember.View.applyAttributeBindings(elem, attributeName, attributeValue);
13431 addObserver(this, property, observer);
13433 // Determine the current value and add it to the render buffer
13435 attributeValue = get(this, property);
13436 Ember.View.applyAttributeBindings(buffer, attributeName, attributeValue);
13443 Given a property name, returns a dasherized version of that
13444 property name if the property evaluates to a non-falsy value.
13446 For example, if the view has property `isUrgent` that evaluates to true,
13447 passing `isUrgent` to this method will return `"is-urgent"`.
13449 @method _classStringForProperty
13452 _classStringForProperty: function(property) {
13453 var parsedPath = Ember.View._parsePropertyPath(property);
13454 var path = parsedPath.path;
13456 var val = get(this, path);
13457 if (val === undefined && Ember.isGlobalPath(path)) {
13458 val = get(window, path);
13461 return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
13464 // ..........................................................
13469 Returns the current DOM element for the view.
13474 element: Ember.computed(function(key, value) {
13475 if (value !== undefined) {
13476 return this.invokeForState('setElement', value);
13478 return this.invokeForState('getElement');
13480 }).property('_parentView').cacheable(),
13483 Returns a jQuery object for this view's element. If you pass in a selector
13484 string, this method will return a jQuery object, using the current element
13487 For example, calling `view.$('li')` will return a jQuery object containing
13488 all of the `li` elements inside the DOM element of this view.
13491 @param {String} [selector] a jQuery-compatible selector string
13492 @return {jQuery} the CoreQuery object for the DOM node
13495 return this.invokeForState('$', sel);
13498 mutateChildViews: function(callback) {
13499 var childViews = this._childViews,
13500 idx = childViews.length,
13503 while(--idx >= 0) {
13504 view = childViews[idx];
13505 callback.call(this, view, idx);
13511 forEachChildView: function(callback) {
13512 var childViews = this._childViews;
13514 if (!childViews) { return this; }
13516 var len = childViews.length,
13519 for(idx = 0; idx < len; idx++) {
13520 view = childViews[idx];
13521 callback.call(this, view);
13528 Appends the view's element to the specified parent element.
13530 If the view does not have an HTML representation yet, `createElement()`
13531 will be called automatically.
13533 Note that this method just schedules the view to be appended; the DOM
13534 element will not be appended to the given element until all bindings have
13535 finished synchronizing.
13537 This is not typically a function that you will need to call directly
13538 when building your application. You might consider using Ember.ContainerView
13539 instead. If you do need to use appendTo, be sure that the target element you
13540 are providing is associated with an Ember.Application and does not have an
13541 ancestor element that is associated with an Ember view.
13544 @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object
13545 @return {Ember.View} receiver
13547 appendTo: function(target) {
13548 // Schedule the DOM element to be created and appended to the given
13549 // element after bindings have synchronized.
13550 this._insertElementLater(function() {
13551 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'));
13552 this.$().appendTo(target);
13559 Replaces the content of the specified parent element with this view's element.
13560 If the view does not have an HTML representation yet, `createElement()`
13561 will be called automatically.
13563 Note that this method just schedules the view to be appended; the DOM
13564 element will not be appended to the given element until all bindings have
13565 finished synchronizing
13568 @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object
13569 @return {Ember.View} received
13571 replaceIn: function(target) {
13572 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'));
13574 this._insertElementLater(function() {
13575 Ember.$(target).empty();
13576 this.$().appendTo(target);
13585 Schedules a DOM operation to occur during the next render phase. This
13586 ensures that all bindings have finished synchronizing before the view is
13589 To use, pass a function that performs a DOM operation..
13591 Before your function is called, this view and all child views will receive
13592 the `willInsertElement` event. After your function is invoked, this view
13593 and all of its child views will receive the `didInsertElement` event.
13595 view._insertElementLater(function() {
13596 this.createElement();
13597 this.$().appendTo('body');
13600 @method _insertElementLater
13601 @param {Function} fn the function that inserts the element into the DOM
13603 _insertElementLater: function(fn) {
13604 this._scheduledInsert = Ember.run.scheduleOnce('render', this, '_insertElement', fn);
13610 _insertElement: function (fn) {
13611 this._scheduledInsert = null;
13612 this.invokeForState('insertElement', fn);
13616 Appends the view's element to the document body. If the view does
13617 not have an HTML representation yet, `createElement()` will be called
13620 Note that this method just schedules the view to be appended; the DOM
13621 element will not be appended to the document body until all bindings have
13622 finished synchronizing.
13625 @return {Ember.View} receiver
13627 append: function() {
13628 return this.appendTo(document.body);
13632 Removes the view's element from the element to which it is attached.
13635 @return {Ember.View} receiver
13637 remove: function() {
13638 // What we should really do here is wait until the end of the run loop
13639 // to determine if the element has been re-appended to a different
13641 // In the interim, we will just re-render if that happens. It is more
13642 // important than elements get garbage collected.
13643 this.destroyElement();
13644 this.invokeRecursively(function(view) {
13645 view.clearRenderedChildren();
13650 The ID to use when trying to locate the element in the DOM. If you do not
13651 set the elementId explicitly, then the view's GUID will be used instead.
13652 This ID must be set at the time the view is created.
13654 @property elementId
13657 elementId: Ember.computed(function(key, value) {
13658 return value !== undefined ? value : Ember.guidFor(this);
13661 // TODO: Perhaps this should be removed from the production build somehow.
13662 _elementIdDidChange: Ember.beforeObserver(function() {
13663 throw "Changing a view's elementId after creation is not allowed.";
13667 Attempts to discover the element in the parent element. The default
13668 implementation looks for an element with an ID of elementId (or the view's
13669 guid if elementId is null). You can override this method to provide your
13670 own form of lookup. For example, if you want to discover your element
13671 using a CSS class name instead of an ID.
13673 @method findElementInParentElement
13674 @param {DOMElement} parentElement The parent's DOM element
13675 @return {DOMElement} The discovered element
13677 findElementInParentElement: function(parentElem) {
13678 var id = "#" + get(this, 'elementId');
13679 return Ember.$(id)[0] || Ember.$(id, parentElem)[0];
13683 Creates a new renderBuffer with the passed tagName. You can override this
13684 method to provide further customization to the buffer if needed. Normally
13685 you will not need to call or override this method.
13687 @method renderBuffer
13688 @param [tagName] {String}
13689 @return {Ember.RenderBuffer}
13691 renderBuffer: function(tagName) {
13692 tagName = tagName || get(this, 'tagName');
13694 // Explicitly check for null or undefined, as tagName
13695 // may be an empty string, which would evaluate to false.
13696 if (tagName === null || tagName === undefined) {
13700 return Ember.RenderBuffer(tagName);
13704 Creates a DOM representation of the view and all of its
13705 child views by recursively calling the `render()` method.
13707 After the element has been created, `didInsertElement` will
13708 be called on this view and all of its child views.
13710 @method createElement
13711 @return {Ember.View} receiver
13713 createElement: function() {
13714 if (get(this, 'element')) { return this; }
13716 var buffer = this.renderToBuffer();
13717 set(this, 'element', buffer.element());
13723 Called when a view is going to insert an element into the DOM.
13725 @event willInsertElement
13727 willInsertElement: Ember.K,
13730 Called when the element of the view has been inserted into the DOM.
13731 Override this function to do any set up that requires an element in the
13734 @event didInsertElement
13736 didInsertElement: Ember.K,
13739 Called when the view is about to rerender, but before anything has
13740 been torn down. This is a good opportunity to tear down any manual
13741 observers you have installed based on the DOM state
13743 @event willRerender
13745 willRerender: Ember.K,
13750 Run this callback on the current view and recursively on child views.
13752 @method invokeRecursively
13753 @param fn {Function}
13755 invokeRecursively: function(fn) {
13756 fn.call(this, this);
13758 this.forEachChildView(function(view) {
13759 view.invokeRecursively(fn);
13764 Invalidates the cache for a property on all child views.
13766 @method invalidateRecursively
13768 invalidateRecursively: function(key) {
13769 this.forEachChildView(function(view) {
13770 view.propertyDidChange(key);
13777 Invokes the receiver's willInsertElement() method if it exists and then
13778 invokes the same on all child views.
13780 NOTE: In some cases this was called when the element existed. This no longer
13781 works so we let people know. We can remove this warning code later.
13783 @method _notifyWillInsertElement
13785 _notifyWillInsertElement: function() {
13786 this.invokeRecursively(function(view) {
13787 view.trigger('willInsertElement');
13794 Invokes the receiver's didInsertElement() method if it exists and then
13795 invokes the same on all child views.
13797 @method _notifyDidInsertElement
13799 _notifyDidInsertElement: function() {
13800 this.invokeRecursively(function(view) {
13801 view.trigger('didInsertElement');
13808 Invokes the receiver's willRerender() method if it exists and then
13809 invokes the same on all child views.
13811 @method _notifyWillRerender
13813 _notifyWillRerender: function() {
13814 this.invokeRecursively(function(view) {
13815 view.trigger('willRerender');
13820 Destroys any existing element along with the element for any child views
13821 as well. If the view does not currently have a element, then this method
13824 If you implement willDestroyElement() on your view, then this method will
13825 be invoked on your view before your element is destroyed to give you a
13826 chance to clean up any event handlers, etc.
13828 If you write a willDestroyElement() handler, you can assume that your
13829 didInsertElement() handler was called earlier for the same element.
13831 Normally you will not call or override this method yourself, but you may
13832 want to implement the above callbacks when it is run.
13834 @method destroyElement
13835 @return {Ember.View} receiver
13837 destroyElement: function() {
13838 return this.invokeForState('destroyElement');
13842 Called when the element of the view is going to be destroyed. Override
13843 this function to do any teardown that requires an element, like removing
13846 @event willDestroyElement
13848 willDestroyElement: function() {},
13853 Invokes the `willDestroyElement` callback on the view and child views.
13855 @method _notifyWillDestroyElement
13857 _notifyWillDestroyElement: function() {
13858 this.invokeRecursively(function(view) {
13859 view.trigger('willDestroyElement');
13863 _elementWillChange: Ember.beforeObserver(function() {
13864 this.forEachChildView(function(view) {
13865 Ember.propertyWillChange(view, 'element');
13872 If this view's element changes, we need to invalidate the caches of our
13873 child views so that we do not retain references to DOM elements that are
13876 @method _elementDidChange
13878 _elementDidChange: Ember.observer(function() {
13879 this.forEachChildView(function(view) {
13880 Ember.propertyDidChange(view, 'element');
13885 Called when the parentView property has changed.
13887 @event parentViewDidChange
13889 parentViewDidChange: Ember.K,
13894 Invoked by the view system when this view needs to produce an HTML
13895 representation. This method will create a new render buffer, if needed,
13896 then apply any default attributes, such as class names and visibility.
13897 Finally, the `render()` method is invoked, which is responsible for
13898 doing the bulk of the rendering.
13900 You should not need to override this method; instead, implement the
13901 `template` property, or if you need more control, override the `render`
13904 @method renderToBuffer
13905 @param {Ember.RenderBuffer} buffer the render buffer. If no buffer is
13906 passed, a default buffer, using the current view's `tagName`, will
13909 renderToBuffer: function(parentBuffer, bufferOperation) {
13914 // Determine where in the parent buffer to start the new buffer.
13915 // By default, a new buffer will be appended to the parent buffer.
13916 // The buffer operation may be changed if the child views array is
13917 // mutated by Ember.ContainerView.
13918 bufferOperation = bufferOperation || 'begin';
13920 // If this is the top-most view, start a new buffer. Otherwise,
13921 // create a new buffer relative to the original using the
13922 // provided buffer operation (for example, `insertAfter` will
13923 // insert a new buffer after the "parent buffer").
13924 if (parentBuffer) {
13925 var tagName = get(this, 'tagName');
13926 if (tagName === null || tagName === undefined) {
13930 buffer = parentBuffer[bufferOperation](tagName);
13932 buffer = this.renderBuffer();
13935 this.buffer = buffer;
13936 this.transitionTo('inBuffer', false);
13938 this.lengthBeforeRender = this._childViews.length;
13940 this.beforeRender(buffer);
13941 this.render(buffer);
13942 this.afterRender(buffer);
13944 this.lengthAfterRender = this._childViews.length;
13949 renderToBufferIfNeeded: function () {
13950 return this.invokeForState('renderToBufferIfNeeded', this);
13953 beforeRender: function(buffer) {
13954 this.applyAttributesToBuffer(buffer);
13957 afterRender: Ember.K,
13959 applyAttributesToBuffer: function(buffer) {
13960 // Creates observers for all registered class name and attribute bindings,
13961 // then adds them to the element.
13962 this._applyClassNameBindings();
13964 // Pass the render buffer so the method can apply attributes directly.
13965 // This isn't needed for class name bindings because they use the
13966 // existing classNames infrastructure.
13967 this._applyAttributeBindings(buffer);
13970 a_forEach(get(this, 'classNames'), function(name){ buffer.addClass(name); });
13971 buffer.id(get(this, 'elementId'));
13973 var role = get(this, 'ariaRole');
13975 buffer.attr('role', role);
13978 if (get(this, 'isVisible') === false) {
13979 buffer.style('display', 'none');
13983 // ..........................................................
13984 // STANDARD RENDER PROPERTIES
13988 Tag name for the view's outer element. The tag name is only used when
13989 an element is first created. If you change the tagName for an element, you
13990 must destroy and recreate the view element.
13992 By default, the render buffer will use a `<div>` tag for views.
13999 // We leave this null by default so we can tell the difference between
14000 // the default case and a user-specified tag.
14004 The WAI-ARIA role of the control represented by this view. For example, a
14005 button may have a role of type 'button', or a pane may have a role of
14006 type 'alertdialog'. This property is used by assistive software to help
14007 visually challenged users navigate rich web applications.
14009 The full list of valid WAI-ARIA roles is available at:
14010 http://www.w3.org/TR/wai-aria/roles#roles_categorization
14019 Standard CSS class names to apply to the view's outer element. This
14020 property automatically inherits any class names defined by the view's
14021 superclasses as well.
14023 @property classNames
14025 @default ['ember-view']
14027 classNames: ['ember-view'],
14030 A list of properties of the view to apply as class names. If the property
14031 is a string value, the value of that string will be applied as a class
14034 // Applies the 'high' class to the view element
14035 Ember.View.create({
14036 classNameBindings: ['priority']
14040 If the value of the property is a Boolean, the name of that property is
14041 added as a dasherized class name.
14043 // Applies the 'is-urgent' class to the view element
14044 Ember.View.create({
14045 classNameBindings: ['isUrgent']
14049 If you would prefer to use a custom value instead of the dasherized
14050 property name, you can pass a binding like this:
14052 // Applies the 'urgent' class to the view element
14053 Ember.View.create({
14054 classNameBindings: ['isUrgent:urgent']
14058 This list of properties is inherited from the view's superclasses as well.
14060 @property classNameBindings
14064 classNameBindings: [],
14067 A list of properties of the view to apply as attributes. If the property is
14068 a string value, the value of that string will be applied as the attribute.
14070 // Applies the type attribute to the element
14071 // with the value "button", like <div type="button">
14072 Ember.View.create({
14073 attributeBindings: ['type'],
14077 If the value of the property is a Boolean, the name of that property is
14078 added as an attribute.
14080 // Renders something like <div enabled="enabled">
14081 Ember.View.create({
14082 attributeBindings: ['enabled'],
14086 @property attributeBindings
14088 attributeBindings: [],
14090 state: 'preRender',
14092 // .......................................................
14093 // CORE DISPLAY METHODS
14099 Setup a view, but do not finish waking it up.
14100 - configure childViews
14101 - register the view with the global views hash, which is used for event
14109 // Register the view for event handling. This hash is used by
14110 // Ember.EventDispatcher to dispatch incoming events.
14111 if (!this.isVirtual) Ember.View.views[get(this, 'elementId')] = this;
14113 // setup child views. be sure to clone the child views array first
14114 this._childViews = this._childViews.slice();
14116 Ember.assert("Only arrays are allowed for 'classNameBindings'", Ember.typeOf(this.classNameBindings) === 'array');
14117 this.classNameBindings = Ember.A(this.classNameBindings.slice());
14119 Ember.assert("Only arrays are allowed for 'classNames'", Ember.typeOf(this.classNames) === 'array');
14120 this.classNames = Ember.A(this.classNames.slice());
14122 var viewController = get(this, 'viewController');
14123 if (viewController) {
14124 viewController = get(viewController);
14125 if (viewController) {
14126 set(viewController, 'view', this);
14131 appendChild: function(view, options) {
14132 return this.invokeForState('appendChild', view, options);
14136 Removes the child view from the parent view.
14138 @method removeChild
14139 @param {Ember.View} view
14140 @return {Ember.View} receiver
14142 removeChild: function(view) {
14143 // If we're destroying, the entire subtree will be
14144 // freed, and the DOM will be handled separately,
14145 // so no need to mess with childViews.
14146 if (this.isDestroying) { return; }
14148 // update parent node
14149 set(view, '_parentView', null);
14151 // remove view from childViews array.
14152 var childViews = this._childViews;
14154 Ember.EnumerableUtils.removeObject(childViews, view);
14156 this.propertyDidChange('childViews'); // HUH?! what happened to will change?
14162 Removes all children from the parentView.
14164 @method removeAllChildren
14165 @return {Ember.View} receiver
14167 removeAllChildren: function() {
14168 return this.mutateChildViews(function(view) {
14169 this.removeChild(view);
14173 destroyAllChildren: function() {
14174 return this.mutateChildViews(function(view) {
14180 Removes the view from its parentView, if one is found. Otherwise
14183 @method removeFromParent
14184 @return {Ember.View} receiver
14186 removeFromParent: function() {
14187 var parent = get(this, '_parentView');
14189 // Remove DOM element from parent
14192 if (parent) { parent.removeChild(this); }
14197 You must call `destroy` on a view to destroy the view (and all of its
14198 child views). This will remove the view from any parent node, then make
14199 sure that the DOM element managed by the view can be released by the
14202 @method willDestroy
14204 willDestroy: function() {
14205 // calling this._super() will nuke computed properties and observers,
14206 // so collect any information we need before calling super.
14207 var childViews = this._childViews,
14208 parent = get(this, '_parentView'),
14211 // destroy the element -- this will avoid each child view destroying
14212 // the element over and over again...
14213 if (!this.removedFromDOM) { this.destroyElement(); }
14215 // remove from non-virtual parent view if viewName was specified
14216 if (this.viewName) {
14217 var nonVirtualParentView = get(this, 'parentView');
14218 if (nonVirtualParentView) {
14219 set(nonVirtualParentView, this.viewName, null);
14223 // remove from parent if found. Don't call removeFromParent,
14224 // as removeFromParent will try to remove the element from
14226 if (parent) { parent.removeChild(this); }
14228 this.state = 'destroyed';
14230 childLen = childViews.length;
14231 for (var i=childLen-1; i>=0; i--) {
14232 childViews[i].removedFromDOM = true;
14233 childViews[i].destroy();
14236 // next remove view from global hash
14237 if (!this.isVirtual) delete Ember.View.views[get(this, 'elementId')];
14241 Instantiates a view to be added to the childViews array during view
14242 initialization. You generally will not call this method directly unless
14243 you are overriding createChildViews(). Note that this method will
14244 automatically configure the correct settings on the new view instance to
14245 act as a child of the parent.
14247 @method createChildView
14248 @param {Class} viewClass
14249 @param {Hash} [attrs] Attributes to add
14250 @return {Ember.View} new instance
14252 createChildView: function(view, attrs) {
14253 if (Ember.View.detect(view)) {
14254 attrs = attrs || {};
14255 attrs._parentView = this;
14256 attrs.templateData = attrs.templateData || get(this, 'templateData');
14258 view = view.create(attrs);
14260 // don't set the property on a virtual view, as they are invisible to
14261 // consumers of the view API
14262 if (view.viewName) { set(get(this, 'concreteView'), view.viewName, view); }
14264 Ember.assert('You must pass instance or subclass of View', view instanceof Ember.View);
14265 Ember.assert("You can only pass attributes when a class is provided", !attrs);
14267 if (!get(view, 'templateData')) {
14268 set(view, 'templateData', get(this, 'templateData'));
14271 set(view, '_parentView', this);
14277 becameVisible: Ember.K,
14278 becameHidden: Ember.K,
14283 When the view's `isVisible` property changes, toggle the visibility
14284 element of the actual DOM element.
14286 @method _isVisibleDidChange
14288 _isVisibleDidChange: Ember.observer(function() {
14289 var $el = this.$();
14290 if (!$el) { return; }
14292 var isVisible = get(this, 'isVisible');
14294 $el.toggle(isVisible);
14296 if (this._isAncestorHidden()) { return; }
14299 this._notifyBecameVisible();
14301 this._notifyBecameHidden();
14305 _notifyBecameVisible: function() {
14306 this.trigger('becameVisible');
14308 this.forEachChildView(function(view) {
14309 var isVisible = get(view, 'isVisible');
14311 if (isVisible || isVisible === null) {
14312 view._notifyBecameVisible();
14317 _notifyBecameHidden: function() {
14318 this.trigger('becameHidden');
14319 this.forEachChildView(function(view) {
14320 var isVisible = get(view, 'isVisible');
14322 if (isVisible || isVisible === null) {
14323 view._notifyBecameHidden();
14328 _isAncestorHidden: function() {
14329 var parent = get(this, 'parentView');
14332 if (get(parent, 'isVisible') === false) { return true; }
14334 parent = get(parent, 'parentView');
14340 clearBuffer: function() {
14341 this.invokeRecursively(function(view) {
14342 this.buffer = null;
14346 transitionTo: function(state, children) {
14347 this.state = state;
14349 if (children !== false) {
14350 this.forEachChildView(function(view) {
14351 view.transitionTo(state);
14359 Override the default event firing from Ember.Evented to
14360 also call methods with the given name.
14363 @param name {String}
14365 trigger: function(name) {
14366 this._super.apply(this, arguments);
14367 var method = this[name];
14369 var args = [], i, l;
14370 for (i = 1, l = arguments.length; i < l; i++) {
14371 args.push(arguments[i]);
14373 return method.apply(this, args);
14377 has: function(name) {
14378 return Ember.typeOf(this[name]) === 'function' || this._super(name);
14381 // .......................................................
14388 Handle events from `Ember.EventDispatcher`
14390 @method handleEvent
14391 @param eventName {String}
14394 handleEvent: function(eventName, evt) {
14395 return this.invokeForState('handleEvent', eventName, evt);
14401 Describe how the specified actions should behave in the various
14402 states that a view can exist in. Possible states:
14404 * preRender: when a view is first instantiated, and after its
14405 element was destroyed, it is in the preRender state
14406 * inBuffer: once a view has been rendered, but before it has
14407 been inserted into the DOM, it is in the inBuffer state
14408 * inDOM: once a view has been inserted into the DOM it is in
14409 the inDOM state. A view spends the vast majority of its
14410 existence in this state.
14411 * destroyed: once a view has been destroyed (using the destroy
14412 method), it is in this state. No further actions can be invoked
14413 on a destroyed view.
14416 // in the destroyed state, everything is illegal
14418 // before rendering has begun, all legal manipulations are noops.
14420 // inside the buffer, legal manipulations are done on the buffer
14422 // once the view has been inserted into the DOM, legal manipulations
14423 // are done on the DOM element.
14426 prepend: function(view, html) {
14427 view.$().prepend(html);
14430 after: function(view, html) {
14431 view.$().after(html);
14434 html: function(view, html) {
14435 view.$().html(html);
14438 replace: function(view) {
14439 var element = get(view, 'element');
14441 set(view, 'element', null);
14443 view._insertElementLater(function() {
14444 Ember.$(element).replaceWith(get(view, 'element'));
14448 remove: function(view) {
14452 empty: function(view) {
14457 Ember.View.reopen({
14458 states: Ember.View.states,
14459 domManager: DOMManager
14462 Ember.View.reopenClass({
14467 Parse a path and return an object which holds the parsed properties.
14469 For example a path like "content.isEnabled:enabled:disabled" wil return the
14473 path: "content.isEnabled",
14474 className: "enabled",
14475 falsyClassName: "disabled",
14476 classNames: ":enabled:disabled"
14479 @method _parsePropertyPath
14482 _parsePropertyPath: function(path) {
14483 var split = path.split(':'),
14484 propertyPath = split[0],
14489 // check if the property is defined as prop:class or prop:trueClass:falseClass
14490 if (split.length > 1) {
14491 className = split[1];
14492 if (split.length === 3) { falsyClassName = split[2]; }
14494 classNames = ':' + className;
14495 if (falsyClassName) { classNames += ":" + falsyClassName; }
14499 path: propertyPath,
14500 classNames: classNames,
14501 className: (className === '') ? undefined : className,
14502 falsyClassName: falsyClassName
14509 Get the class name for a given value, based on the path, optional className
14510 and optional falsyClassName.
14512 - if a className or falsyClassName has been specified:
14513 - if the value is truthy and className has been specified, className is returned
14514 - if the value is falsy and falsyClassName has been specified, falsyClassName is returned
14515 - otherwise null is returned
14516 - if the value is true, the dasherized last part of the supplied path is returned
14517 - if the value is not false, undefined or null, the value is returned
14518 - if none of the above rules apply, null is returned
14520 @method _classStringForValue
14524 @param falsyClassName
14527 _classStringForValue: function(path, val, className, falsyClassName) {
14528 // When using the colon syntax, evaluate the truthiness or falsiness
14529 // of the value to determine which className to return
14530 if (className || falsyClassName) {
14531 if (className && !!val) {
14534 } else if (falsyClassName && !val) {
14535 return falsyClassName;
14541 // If value is a Boolean and true, return the dasherized property
14543 } else if (val === true) {
14544 // Normalize property path to be suitable for use
14545 // as a class name. For exaple, content.foo.barBaz
14546 // becomes bar-baz.
14547 var parts = path.split('.');
14548 return Ember.String.dasherize(parts[parts.length-1]);
14550 // If the value is not false, undefined, or null, return the current
14551 // value of the property.
14552 } else if (val !== false && val !== undefined && val !== null) {
14555 // Nothing to display. Return null so that the old class is removed
14556 // but no new class is added.
14570 Ember.View.views = {};
14572 // If someone overrides the child views computed property when
14573 // defining their class, we want to be able to process the user's
14574 // supplied childViews and then restore the original computed property
14575 // at view initialization time. This happens in Ember.ContainerView's init
14577 Ember.View.childViewsProperty = childViewsProperty;
14579 Ember.View.applyAttributeBindings = function(elem, name, value) {
14580 var type = Ember.typeOf(value);
14581 var currentValue = elem.attr(name);
14583 // if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js
14584 if ((type === 'string' || (type === 'number' && !isNaN(value))) && value !== currentValue) {
14585 elem.attr(name, value);
14586 } else if (value && type === 'boolean') {
14587 elem.attr(name, name);
14588 } else if (!value) {
14589 elem.removeAttr(name);
14600 @submodule ember-views
14603 var get = Ember.get, set = Ember.set;
14605 Ember.View.states = {
14607 // appendChild is only legal while rendering the buffer.
14608 appendChild: function() {
14609 throw "You can't use appendChild outside of the rendering process";
14616 getElement: function() {
14620 // Handle events from `Ember.EventDispatcher`
14621 handleEvent: function() {
14622 return true; // continue event propagation
14625 destroyElement: function(view) {
14626 set(view, 'element', null);
14627 if (view._scheduledInsert) {
14628 Ember.run.cancel(view._scheduledInsert);
14629 view._scheduledInsert = null;
14634 renderToBufferIfNeeded: function () {
14640 Ember.View.reopen({
14641 states: Ember.View.states
14651 @submodule ember-views
14654 Ember.View.states.preRender = {
14655 parentState: Ember.View.states._default,
14657 // a view leaves the preRender state once its element has been
14658 // created (createElement).
14659 insertElement: function(view, fn) {
14660 view.createElement();
14661 view._notifyWillInsertElement();
14662 // after createElement, the view will be in the hasElement state.
14664 view.transitionTo('inDOM');
14665 view._notifyDidInsertElement();
14668 renderToBufferIfNeeded: function(view) {
14669 return view.renderToBuffer();
14674 setElement: function(view, value) {
14675 if (value !== null) {
14676 view.transitionTo('hasElement');
14689 @submodule ember-views
14692 var get = Ember.get, set = Ember.set, meta = Ember.meta;
14694 Ember.View.states.inBuffer = {
14695 parentState: Ember.View.states._default,
14697 $: function(view, sel) {
14698 // if we don't have an element yet, someone calling this.$() is
14699 // trying to update an element that isn't in the DOM. Instead,
14700 // rerender the view to allow the render method to reflect the
14706 // when a view is rendered in a buffer, rerendering it simply
14707 // replaces the existing buffer with a new one
14708 rerender: function(view) {
14709 Ember.deprecate("Something you did caused a view to re-render after it rendered but before it was inserted into the DOM. Because this is avoidable and the cause of significant performance issues in applications, this behavior is deprecated. If you want to use the debugger to find out what caused this, you can set ENV.RAISE_ON_DEPRECATION to true.");
14711 view._notifyWillRerender();
14713 view.clearRenderedChildren();
14714 view.renderToBuffer(view.buffer, 'replaceWith');
14717 // when a view is rendered in a buffer, appending a child
14718 // view will render that view and append the resulting
14719 // buffer into its buffer.
14720 appendChild: function(view, childView, options) {
14721 var buffer = view.buffer;
14723 childView = this.createChildView(childView, options);
14724 view._childViews.push(childView);
14726 childView.renderToBuffer(buffer);
14728 view.propertyDidChange('childViews');
14733 // when a view is rendered in a buffer, destroying the
14734 // element will simply destroy the buffer and put the
14735 // state back into the preRender state.
14736 destroyElement: function(view) {
14737 view.clearBuffer();
14738 view._notifyWillDestroyElement();
14739 view.transitionTo('preRender');
14744 empty: function() {
14745 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.");
14748 renderToBufferIfNeeded: function (view) {
14749 return view.buffer;
14752 // It should be impossible for a rendered view to be scheduled for
14754 insertElement: function() {
14755 throw "You can't insert an element that has already been rendered";
14758 setElement: function(view, value) {
14759 if (value === null) {
14760 view.transitionTo('preRender');
14762 view.clearBuffer();
14763 view.transitionTo('hasElement');
14778 @submodule ember-views
14781 var get = Ember.get, set = Ember.set, meta = Ember.meta;
14783 Ember.View.states.hasElement = {
14784 parentState: Ember.View.states._default,
14786 $: function(view, sel) {
14787 var elem = get(view, 'element');
14788 return sel ? Ember.$(sel, elem) : Ember.$(elem);
14791 getElement: function(view) {
14792 var parent = get(view, 'parentView');
14793 if (parent) { parent = get(parent, 'element'); }
14794 if (parent) { return view.findElementInParentElement(parent); }
14795 return Ember.$("#" + get(view, 'elementId'))[0];
14798 setElement: function(view, value) {
14799 if (value === null) {
14800 view.transitionTo('preRender');
14802 throw "You cannot set an element to a non-null value when the element is already in the DOM.";
14808 // once the view has been inserted into the DOM, rerendering is
14809 // deferred to allow bindings to synchronize.
14810 rerender: function(view) {
14811 view._notifyWillRerender();
14813 view.clearRenderedChildren();
14815 view.domManager.replace(view);
14819 // once the view is already in the DOM, destroying it removes it
14820 // from the DOM, nukes its element, and puts it back into the
14821 // preRender state if inDOM.
14823 destroyElement: function(view) {
14824 view._notifyWillDestroyElement();
14825 view.domManager.remove(view);
14826 set(view, 'element', null);
14827 if (view._scheduledInsert) {
14828 Ember.run.cancel(view._scheduledInsert);
14829 view._scheduledInsert = null;
14834 empty: function(view) {
14835 var _childViews = view._childViews, len, idx;
14837 len = _childViews.length;
14838 for (idx = 0; idx < len; idx++) {
14839 _childViews[idx]._notifyWillDestroyElement();
14842 view.domManager.empty(view);
14845 // Handle events from `Ember.EventDispatcher`
14846 handleEvent: function(view, eventName, evt) {
14847 if (view.has(eventName)) {
14848 // Handler should be able to re-dispatch events, so we don't
14849 // preventDefault or stopPropagation.
14850 return view.trigger(eventName, evt);
14852 return true; // continue event propagation
14857 Ember.View.states.inDOM = {
14858 parentState: Ember.View.states.hasElement,
14860 insertElement: function(view, fn) {
14861 throw "You can't insert an element into the DOM that has already been inserted";
14872 @submodule ember-views
14875 var destroyedError = "You can't call %@ on a destroyed view", fmt = Ember.String.fmt;
14877 Ember.View.states.destroyed = {
14878 parentState: Ember.View.states._default,
14880 appendChild: function() {
14881 throw fmt(destroyedError, ['appendChild']);
14883 rerender: function() {
14884 throw fmt(destroyedError, ['rerender']);
14886 destroyElement: function() {
14887 throw fmt(destroyedError, ['destroyElement']);
14889 empty: function() {
14890 throw fmt(destroyedError, ['empty']);
14893 setElement: function() {
14894 throw fmt(destroyedError, ["set('element', ...)"]);
14897 renderToBufferIfNeeded: function() {
14898 throw fmt(destroyedError, ["renderToBufferIfNeeded"]);
14901 // Since element insertion is scheduled, don't do anything if
14902 // the view has been destroyed between scheduling and execution
14903 insertElement: Ember.K
14920 @submodule ember-views
14923 var get = Ember.get, set = Ember.set, meta = Ember.meta;
14924 var forEach = Ember.EnumerableUtils.forEach;
14926 var childViewsProperty = Ember.computed(function() {
14927 return get(this, '_childViews');
14928 }).property('_childViews').cacheable();
14931 A `ContainerView` is an `Ember.View` subclass that allows for manual or programatic
14932 management of a view's `childViews` array that will correctly update the `ContainerView`
14933 instance's rendered DOM representation.
14935 ## Setting Initial Child Views
14936 The initial array of child views can be set in one of two ways. You can provide
14937 a `childViews` property at creation time that contains instance of `Ember.View`:
14940 aContainer = Ember.ContainerView.create({
14941 childViews: [Ember.View.create(), Ember.View.create()]
14945 You can also provide a list of property names whose values are instances of `Ember.View`:
14948 aContainer = Ember.ContainerView.create({
14949 childViews: ['aView', 'bView', 'cView'],
14950 aView: Ember.View.create(),
14951 bView: Ember.View.create()
14952 cView: Ember.View.create()
14956 The two strategies can be combined:
14959 aContainer = Ember.ContainerView.create({
14960 childViews: ['aView', Ember.View.create()],
14961 aView: Ember.View.create()
14965 Each child view's rendering will be inserted into the container's rendered HTML in the same
14966 order as its position in the `childViews` property.
14968 ## Adding and Removing Child Views
14969 The views in a container's `childViews` array should be added and removed by manipulating
14970 the `childViews` property directly.
14972 To remove a view pass that view into a `removeObject` call on the container's `childViews` property.
14974 Given an empty `<body>` the following code
14977 aContainer = Ember.ContainerView.create({
14978 classNames: ['the-container'],
14979 childViews: ['aView', 'bView'],
14980 aView: Ember.View.create({
14981 template: Ember.Handlebars.compile("A")
14983 bView: Ember.View.create({
14984 template: Ember.Handlebars.compile("B")
14988 aContainer.appendTo('body');
14991 Results in the HTML
14994 <div class="ember-view the-container">
14995 <div class="ember-view">A</div>
14996 <div class="ember-view">B</div>
15003 aContainer.get('childViews'); // [aContainer.aView, aContainer.bView]
15004 aContainer.get('childViews').removeObject(aContainer.get('bView'));
15005 aContainer.get('childViews'); // [aContainer.aView]
15008 Will result in the following HTML
15011 <div class="ember-view the-container">
15012 <div class="ember-view">A</div>
15017 Similarly, adding a child view is accomplished by adding `Ember.View` instances to the
15018 container's `childViews` property.
15020 Given an empty `<body>` the following code
15023 aContainer = Ember.ContainerView.create({
15024 classNames: ['the-container'],
15025 childViews: ['aView', 'bView'],
15026 aView: Ember.View.create({
15027 template: Ember.Handlebars.compile("A")
15029 bView: Ember.View.create({
15030 template: Ember.Handlebars.compile("B")
15034 aContainer.appendTo('body');
15037 Results in the HTML
15040 <div class="ember-view the-container">
15041 <div class="ember-view">A</div>
15042 <div class="ember-view">B</div>
15049 AnotherViewClass = Ember.View.extend({
15050 template: Ember.Handlebars.compile("Another view")
15053 aContainer.get('childViews'); // [aContainer.aView, aContainer.bView]
15054 aContainer.get('childViews').pushObject(AnotherViewClass.create());
15055 aContainer.get('childViews'); // [aContainer.aView, aContainer.bView, <AnotherViewClass instance>]
15058 Will result in the following HTML
15061 <div class="ember-view the-container">
15062 <div class="ember-view">A</div>
15063 <div class="ember-view">B</div>
15064 <div class="ember-view">Another view</div>
15069 Direct manipulation of childViews presence or absence in the DOM via calls to
15070 `remove` or `removeFromParent` or calls to a container's `removeChild` may not behave
15073 Calling `remove()` on a child view will remove the view's HTML, but it will remain as part of its
15074 container's `childView`s property.
15076 Calling `removeChild()` on the container will remove the passed view instance from the container's
15077 `childView`s but keep its HTML within the container's rendered view.
15079 Calling `removeFromParent()` behaves as expected but should be avoided in favor of direct
15080 manipulation of a container's `childViews` property.
15083 aContainer = Ember.ContainerView.create({
15084 classNames: ['the-container'],
15085 childViews: ['aView', 'bView'],
15086 aView: Ember.View.create({
15087 template: Ember.Handlebars.compile("A")
15089 bView: Ember.View.create({
15090 template: Ember.Handlebars.compile("B")
15094 aContainer.appendTo('body');
15097 Results in the HTML
15100 <div class="ember-view the-container">
15101 <div class="ember-view">A</div>
15102 <div class="ember-view">B</div>
15106 Calling `aContainer.get('aView').removeFromParent()` will result in the following HTML
15109 <div class="ember-view the-container">
15110 <div class="ember-view">B</div>
15114 And the `Ember.View` instance stored in `aContainer.aView` will be removed from `aContainer`'s
15115 `childViews` array.
15117 ## Templates and Layout
15119 A `template`, `templateName`, `defaultTemplate`, `layout`, `layoutName` or `defaultLayout`
15120 property on a container view will not result in the template or layout being rendered.
15121 The HTML contents of a `Ember.ContainerView`'s DOM representation will only be the rendered HTML
15122 of its child views.
15124 ## Binding a View to Display
15126 If you would like to display a single view in your ContainerView, you can set its `currentView`
15127 property. When the `currentView` property is set to a view instance, it will be added to the
15128 ContainerView's `childViews` array. If the `currentView` property is later changed to a
15129 different view, the new view will replace the old view. If `currentView` is set to `null`, the
15130 last `currentView` will be removed.
15132 This functionality is useful for cases where you want to bind the display of a ContainerView to
15133 a controller or state manager. For example, you can bind the `currentView` of a container to
15134 a controller like this:
15137 App.appController = Ember.Object.create({
15138 view: Ember.View.create({
15139 templateName: 'person_template'
15145 {{view Ember.ContainerView currentViewBinding="App.appController.view"}}
15148 @class ContainerView
15150 @extends Ember.View
15153 Ember.ContainerView = Ember.View.extend({
15158 var childViews = get(this, 'childViews');
15159 Ember.defineProperty(this, 'childViews', childViewsProperty);
15161 var _childViews = this._childViews;
15163 forEach(childViews, function(viewName, idx) {
15166 if ('string' === typeof viewName) {
15167 view = get(this, viewName);
15168 view = this.createChildView(view);
15169 set(this, viewName, view);
15171 view = this.createChildView(viewName);
15174 _childViews[idx] = view;
15177 var currentView = get(this, 'currentView');
15178 if (currentView) _childViews.push(this.createChildView(currentView));
15180 // Make the _childViews array observable
15181 Ember.A(_childViews);
15183 // Sets up an array observer on the child views array. This
15184 // observer will detect when child views are added or removed
15185 // and update the DOM to reflect the mutation.
15186 get(this, 'childViews').addArrayObserver(this, {
15187 willChange: 'childViewsWillChange',
15188 didChange: 'childViewsDidChange'
15195 Instructs each child view to render to the passed render buffer.
15198 @param {Ember.RenderBuffer} buffer the buffer to render to
15200 render: function(buffer) {
15201 this.forEachChildView(function(view) {
15202 view.renderToBuffer(buffer);
15209 When the container view is destroyed, tear down the child views
15212 @method willDestroy
15214 willDestroy: function() {
15215 get(this, 'childViews').removeArrayObserver(this, {
15216 willChange: 'childViewsWillChange',
15217 didChange: 'childViewsDidChange'
15226 When a child view is removed, destroy its element so that
15227 it is removed from the DOM.
15229 The array observer that triggers this action is set up in the
15230 `renderToBuffer` method.
15232 @method childViewsWillChange
15233 @param {Ember.Array} views the child views array before mutation
15234 @param {Number} start the start position of the mutation
15235 @param {Number} removed the number of child views removed
15237 childViewsWillChange: function(views, start, removed) {
15238 if (removed === 0) { return; }
15240 var changedViews = views.slice(start, start+removed);
15241 this.initializeViews(changedViews, null, null);
15243 this.invokeForState('childViewsWillChange', views, start, removed);
15249 When a child view is added, make sure the DOM gets updated appropriately.
15251 If the view has already rendered an element, we tell the child view to
15252 create an element and insert it into the DOM. If the enclosing container view
15253 has already written to a buffer, but not yet converted that buffer into an
15254 element, we insert the string representation of the child into the appropriate
15255 place in the buffer.
15257 @method childViewsDidChange
15258 @param {Ember.Array} views the array of child views afte the mutation has occurred
15259 @param {Number} start the start position of the mutation
15260 @param {Number} removed the number of child views removed
15261 @param {Number} the number of child views added
15263 childViewsDidChange: function(views, start, removed, added) {
15264 var len = get(views, 'length');
15266 // No new child views were added; bail out.
15267 if (added === 0) return;
15269 var changedViews = views.slice(start, start+added);
15270 this.initializeViews(changedViews, this, get(this, 'templateData'));
15272 // Let the current state handle the changes
15273 this.invokeForState('childViewsDidChange', views, start, added);
15276 initializeViews: function(views, parentView, templateData) {
15277 forEach(views, function(view) {
15278 set(view, '_parentView', parentView);
15280 if (!get(view, 'templateData')) {
15281 set(view, 'templateData', templateData);
15288 _currentViewWillChange: Ember.beforeObserver(function() {
15289 var childViews = get(this, 'childViews'),
15290 currentView = get(this, 'currentView');
15293 childViews.removeObject(currentView);
15294 currentView.destroy();
15298 _currentViewDidChange: Ember.observer(function() {
15299 var childViews = get(this, 'childViews'),
15300 currentView = get(this, 'currentView');
15303 childViews.pushObject(currentView);
15307 _ensureChildrenAreInDOM: function () {
15308 this.invokeForState('ensureChildrenAreInDOM', this);
15312 // Ember.ContainerView extends the default view states to provide different
15313 // behavior for childViewsWillChange and childViewsDidChange.
15314 Ember.ContainerView.states = {
15315 parent: Ember.View.states,
15318 childViewsDidChange: function(parentView, views, start, added) {
15319 var buffer = parentView.buffer,
15320 startWith, prev, prevBuffer, view;
15322 // Determine where to begin inserting the child view(s) in the
15325 // If views were inserted at the beginning, prepend the first
15326 // view to the render buffer, then begin inserting any
15327 // additional views at the beginning.
15328 view = views[start];
15329 startWith = start + 1;
15330 view.renderToBuffer(buffer, 'prepend');
15332 // Otherwise, just insert them at the same place as the child
15334 view = views[start - 1];
15338 for (var i=startWith; i<start+added; i++) {
15341 prevBuffer = prev.buffer;
15342 view.renderToBuffer(prevBuffer, 'insertAfter');
15348 childViewsWillChange: function(view, views, start, removed) {
15349 for (var i=start; i<start+removed; i++) {
15354 childViewsDidChange: function(view, views, start, added) {
15355 Ember.run.scheduleOnce('render', this, '_ensureChildrenAreInDOM');
15358 ensureChildrenAreInDOM: function(view) {
15359 var childViews = view.get('childViews'), i, len, childView, previous, buffer;
15360 for (i = 0, len = childViews.length; i < len; i++) {
15361 childView = childViews[i];
15362 buffer = childView.renderToBufferIfNeeded();
15364 childView._notifyWillInsertElement();
15366 previous.domManager.after(previous, buffer.string());
15368 view.domManager.prepend(view, buffer.string());
15370 childView.transitionTo('inDOM');
15371 childView.propertyDidChange('element');
15372 childView._notifyDidInsertElement();
15374 previous = childView;
15380 Ember.ContainerView.states.inDOM = {
15381 parentState: Ember.ContainerView.states.hasElement
15384 Ember.ContainerView.reopen({
15385 states: Ember.ContainerView.states
15395 @submodule ember-views
15398 var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
15401 `Ember.CollectionView` is an `Ember.View` descendent responsible for managing a
15402 collection (an array or array-like object) by maintaing a child view object and
15403 associated DOM representation for each item in the array and ensuring that child
15404 views and their associated rendered HTML are updated when items in the array
15405 are added, removed, or replaced.
15408 The managed collection of objects is referenced as the `Ember.CollectionView` instance's
15409 `content` property.
15412 someItemsView = Ember.CollectionView.create({
15413 content: ['A', 'B','C']
15417 The view for each item in the collection will have its `content` property set
15420 ## Specifying itemViewClass
15421 By default the view class for each item in the managed collection will be an instance
15422 of `Ember.View`. You can supply a different class by setting the `CollectionView`'s
15423 `itemViewClass` property.
15425 Given an empty `<body>` and the following code:
15428 someItemsView = Ember.CollectionView.create({
15429 classNames: ['a-collection'],
15430 content: ['A','B','C'],
15431 itemViewClass: Ember.View.extend({
15432 template: Ember.Handlebars.compile("the letter: {{view.content}}")
15436 someItemsView.appendTo('body');
15439 Will result in the following HTML structure
15442 <div class="ember-view a-collection">
15443 <div class="ember-view">the letter: A</div>
15444 <div class="ember-view">the letter: B</div>
15445 <div class="ember-view">the letter: C</div>
15449 ## Automatic matching of parent/child tagNames
15451 Setting the `tagName` property of a `CollectionView` to any of
15452 "ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result
15453 in the item views receiving an appropriately matched `tagName` property.
15456 Given an empty `<body>` and the following code:
15459 anUndorderedListView = Ember.CollectionView.create({
15461 content: ['A','B','C'],
15462 itemViewClass: Ember.View.extend({
15463 template: Ember.Handlebars.compile("the letter: {{view.content}}")
15467 anUndorderedListView.appendTo('body');
15470 Will result in the following HTML structure
15473 <ul class="ember-view a-collection">
15474 <li class="ember-view">the letter: A</li>
15475 <li class="ember-view">the letter: B</li>
15476 <li class="ember-view">the letter: C</li>
15480 Additional tagName pairs can be provided by adding to `Ember.CollectionView.CONTAINER_MAP `
15483 Ember.CollectionView.CONTAINER_MAP['article'] = 'section'
15488 You can provide an `Ember.View` subclass to the `Ember.CollectionView` instance as its
15489 `emptyView` property. If the `content` property of a `CollectionView` is set to `null`
15490 or an empty array, an instance of this view will be the `CollectionView`s only child.
15493 aListWithNothing = Ember.CollectionView.create({
15494 classNames: ['nothing']
15496 emptyView: Ember.View.extend({
15497 template: Ember.Handlebars.compile("The collection is empty")
15501 aListWithNothing.appendTo('body');
15504 Will result in the following HTML structure
15507 <div class="ember-view nothing">
15508 <div class="ember-view">
15509 The collection is empty
15514 ## Adding and Removing items
15515 The `childViews` property of a `CollectionView` should not be directly manipulated. Instead,
15516 add, remove, replace items from its `content` property. This will trigger
15517 appropriate changes to its rendered HTML.
15519 ## Use in templates via the `{{collection}}` Ember.Handlebars helper
15520 Ember.Handlebars provides a helper specifically for adding `CollectionView`s to templates.
15521 See `Ember.Handlebars.collection` for more details
15523 @class CollectionView
15525 @extends Ember.ContainerView
15528 Ember.CollectionView = Ember.ContainerView.extend(
15529 /** @scope Ember.CollectionView.prototype */ {
15532 A list of items to be displayed by the Ember.CollectionView.
15543 This provides metadata about what kind of empty view class this
15544 collection would like if it is being instantiated from another
15545 system (like Handlebars)
15547 @property emptyViewClass
15549 emptyViewClass: Ember.View,
15552 An optional view to display if content is set to an empty array.
15554 @property emptyView
15561 @property itemViewClass
15563 @default Ember.View
15565 itemViewClass: Ember.View,
15568 var ret = this._super();
15569 this._contentDidChange();
15573 _contentWillChange: Ember.beforeObserver(function() {
15574 var content = this.get('content');
15576 if (content) { content.removeArrayObserver(this); }
15577 var len = content ? get(content, 'length') : 0;
15578 this.arrayWillChange(content, 0, len);
15584 Check to make sure that the content has changed, and if so,
15585 update the children directly. This is always scheduled
15586 asynchronously, to allow the element to be created before
15587 bindings have synchronized and vice versa.
15589 @method _contentDidChange
15591 _contentDidChange: Ember.observer(function() {
15592 var content = get(this, 'content');
15595 Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content));
15596 content.addArrayObserver(this);
15599 var len = content ? get(content, 'length') : 0;
15600 this.arrayDidChange(content, 0, null, len);
15603 willDestroy: function() {
15604 var content = get(this, 'content');
15605 if (content) { content.removeArrayObserver(this); }
15610 arrayWillChange: function(content, start, removedCount) {
15611 // If the contents were empty before and this template collection has an
15612 // empty view remove it now.
15613 var emptyView = get(this, 'emptyView');
15614 if (emptyView && emptyView instanceof Ember.View) {
15615 emptyView.removeFromParent();
15618 // Loop through child views that correspond with the removed items.
15619 // Note that we loop from the end of the array to the beginning because
15620 // we are mutating it as we go.
15621 var childViews = get(this, 'childViews'), childView, idx, len;
15623 len = get(childViews, 'length');
15625 var removingAll = removedCount === len;
15628 this.invokeForState('empty');
15631 for (idx = start + removedCount - 1; idx >= start; idx--) {
15632 childView = childViews[idx];
15633 if (removingAll) { childView.removedFromDOM = true; }
15634 childView.destroy();
15639 Called when a mutation to the underlying content array occurs.
15641 This method will replay that mutation against the views that compose the
15642 Ember.CollectionView, ensuring that the view reflects the model.
15644 This array observer is added in contentDidChange.
15646 @method arrayDidChange
15647 @param {Array} addedObjects the objects that were added to the content
15648 @param {Array} removedObjects the objects that were removed from the content
15649 @param {Number} changeIndex the index at which the changes occurred
15651 arrayDidChange: function(content, start, removed, added) {
15652 var itemViewClass = get(this, 'itemViewClass'),
15653 childViews = get(this, 'childViews'),
15654 addedViews = [], view, item, idx, len, itemTagName;
15656 if ('string' === typeof itemViewClass) {
15657 itemViewClass = get(itemViewClass);
15660 Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@", [itemViewClass]), Ember.View.detect(itemViewClass));
15662 len = content ? get(content, 'length') : 0;
15664 for (idx = start; idx < start+added; idx++) {
15665 item = content.objectAt(idx);
15667 view = this.createChildView(itemViewClass, {
15672 addedViews.push(view);
15675 var emptyView = get(this, 'emptyView');
15676 if (!emptyView) { return; }
15678 emptyView = this.createChildView(emptyView);
15679 addedViews.push(emptyView);
15680 set(this, 'emptyView', emptyView);
15682 childViews.replace(start, 0, addedViews);
15685 createChildView: function(view, attrs) {
15686 view = this._super(view, attrs);
15688 var itemTagName = get(view, 'tagName');
15689 var tagName = (itemTagName === null || itemTagName === undefined) ? Ember.CollectionView.CONTAINER_MAP[get(this, 'tagName')] : itemTagName;
15691 set(view, 'tagName', tagName);
15698 A map of parent tags to their default child tags. You can add
15699 additional parent tags if you want collection views that use
15700 a particular parent tag to default to a child tag.
15702 @property CONTAINER_MAP
15707 Ember.CollectionView.CONTAINER_MAP = {
15734 @submodule ember-views
15735 @require ember-runtime
15742 var get = Ember.get, set = Ember.set;
15746 @submodule ember-states
15752 @extends Ember.Object
15753 @uses Ember.Evented
15755 Ember.State = Ember.Object.extend(Ember.Evented,
15756 /** @scope Ember.State.prototype */{
15760 A reference to the parent state.
15762 @property parentState
15769 The name of this state.
15777 The full path to this state.
15782 path: Ember.computed(function() {
15783 var parentPath = get(this, 'parentState.path'),
15784 path = get(this, 'name');
15787 path = parentPath + '.' + path;
15791 }).property().cacheable(),
15796 Override the default event firing from Ember.Evented to
15797 also call methods with the given name.
15802 trigger: function(name) {
15804 this[name].apply(this, [].slice.call(arguments, 1));
15806 this._super.apply(this, arguments);
15810 var states = get(this, 'states'), foundStates;
15811 set(this, 'childStates', Ember.A());
15812 set(this, 'eventTransitions', get(this, 'eventTransitions') || {});
15814 var name, value, transitionTarget;
15816 // As a convenience, loop over the properties
15817 // of this state and look for any that are other
15818 // Ember.State instances or classes, and move them
15819 // to the `states` hash. This avoids having to
15820 // create an explicit separate hash.
15825 for (name in this) {
15826 if (name === "constructor") { continue; }
15828 if (value = this[name]) {
15829 if (transitionTarget = value.transitionTarget) {
15830 this.eventTransitions[name] = transitionTarget;
15833 this.setupChild(states, name, value);
15837 set(this, 'states', states);
15839 for (name in states) {
15840 this.setupChild(states, name, states[name]);
15844 set(this, 'pathsCache', {});
15845 set(this, 'pathsCacheNoContext', {});
15848 setupChild: function(states, name, value) {
15849 if (!value) { return false; }
15851 if (value.isState) {
15852 set(value, 'name', name);
15853 } else if (Ember.State.detect(value)) {
15854 value = value.create({
15859 if (value.isState) {
15860 set(value, 'parentState', this);
15861 get(this, 'childStates').pushObject(value);
15862 states[name] = value;
15867 lookupEventTransition: function(name) {
15868 var path, state = this;
15870 while(state && !path) {
15871 path = state.eventTransitions[name];
15872 state = state.get('parentState');
15879 A Boolean value indicating whether the state is a leaf state
15880 in the state hierarchy. This is false if the state has child
15881 states; otherwise it is true.
15886 isLeaf: Ember.computed(function() {
15887 return !get(this, 'childStates').length;
15891 A boolean value indicating whether the state takes a context.
15892 By default we assume all states take contexts.
15894 @property hasContext
15900 This is the default transition event.
15903 @param {Ember.StateManager} manager
15905 @see Ember.StateManager#transitionEvent
15910 This event fires when the state is entered.
15913 @param {Ember.StateManager} manager
15918 This event fires when the state is exited.
15921 @param {Ember.StateManager} manager
15926 var Event = Ember.$ && Ember.$.Event;
15928 Ember.State.reopenClass(
15929 /** @scope Ember.State */{
15932 Creates an action function for transitioning to the named state while preserving context.
15934 The following example StateManagers are equivalent:
15936 aManager = Ember.StateManager.create({
15937 stateOne: Ember.State.create({
15938 changeToStateTwo: Ember.State.transitionTo('stateTwo')
15940 stateTwo: Ember.State.create({})
15943 bManager = Ember.StateManager.create({
15944 stateOne: Ember.State.create({
15945 changeToStateTwo: function(manager, context){
15946 manager.transitionTo('stateTwo', context)
15949 stateTwo: Ember.State.create({})
15952 @method transitionTo
15954 @param {String} target
15956 transitionTo: function(target) {
15957 var event = function(stateManager, context) {
15958 if (Event && context instanceof Event) {
15959 if (context.hasOwnProperty('context')) {
15960 context = context.context;
15962 // If we received an event and it doesn't contain
15963 // a context, don't pass along a superfluous
15964 // context to the target of the event.
15965 return stateManager.transitionTo(target);
15969 stateManager.transitionTo(target, context);
15972 event.transitionTarget = target;
15985 @submodule ember-states
15988 var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
15989 var arrayForEach = Ember.ArrayPolyfills.forEach;
15991 A Transition takes the enter, exit and resolve states and normalizes
15994 * takes any passed in contexts into consideration
15995 * adds in `initialState`s
16000 var Transition = function(raw) {
16001 this.enterStates = raw.enterStates.slice();
16002 this.exitStates = raw.exitStates.slice();
16003 this.resolveState = raw.resolveState;
16005 this.finalState = raw.enterStates[raw.enterStates.length - 1] || raw.resolveState;
16008 Transition.prototype = {
16010 Normalize the passed in enter, exit and resolve states.
16012 This process also adds `finalState` and `contexts` to the Transition object.
16015 @param {Ember.StateManager} manager the state manager running the transition
16016 @param {Array} contexts a list of contexts passed into `transitionTo`
16018 normalize: function(manager, contexts) {
16019 this.matchContextsToStates(contexts);
16020 this.addInitialStates();
16021 this.removeUnchangedContexts(manager);
16026 Match each of the contexts passed to `transitionTo` to a state.
16027 This process may also require adding additional enter and exit
16028 states if there are more contexts than enter states.
16030 @method matchContextsToStates
16031 @param {Array} contexts a list of contexts passed into `transitionTo`
16033 matchContextsToStates: function(contexts) {
16034 var stateIdx = this.enterStates.length - 1,
16035 matchedContexts = [],
16039 // Next, we will match the passed in contexts to the states they
16042 // First, assign a context to each enter state in reverse order. If
16043 // any contexts are left, add a parent state to the list of states
16044 // to enter and exit, and assign a context to the parent state.
16046 // If there are still contexts left when the state manager is
16047 // reached, raise an exception.
16049 // This allows the following:
16054 // | |- about (* current state)
16056 // For `transitionTo('post.comments', post, post.get('comments')`,
16057 // the first context (`post`) will be assigned to `root.post`, and
16058 // the second context (`post.get('comments')`) will be assigned
16059 // to `root.post.comments`.
16061 // For the following:
16065 // | | |- index (* current state)
16068 // For `transitionTo('post.comments', otherPost, otherPost.get('comments')`,
16069 // the `<root.post>` state will be added to the list of enter and exit
16070 // states because its context has changed.
16072 while (contexts.length > 0) {
16073 if (stateIdx >= 0) {
16074 state = this.enterStates[stateIdx--];
16076 if (this.enterStates.length) {
16077 state = get(this.enterStates[0], 'parentState');
16078 if (!state) { throw "Cannot match all contexts to states"; }
16080 // If re-entering the current state with a context, the resolve
16081 // state will be the current state.
16082 state = this.resolveState;
16085 this.enterStates.unshift(state);
16086 this.exitStates.unshift(state);
16089 // in routers, only states with dynamic segments have a context
16090 if (get(state, 'hasContext')) {
16091 context = contexts.pop();
16096 matchedContexts.unshift(context);
16099 this.contexts = matchedContexts;
16103 Add any `initialState`s to the list of enter states.
16105 @method addInitialStates
16107 addInitialStates: function() {
16108 var finalState = this.finalState, initialState;
16111 initialState = get(finalState, 'initialState') || 'start';
16112 finalState = get(finalState, 'states.' + initialState);
16114 if (!finalState) { break; }
16116 this.finalState = finalState;
16117 this.enterStates.push(finalState);
16118 this.contexts.push(undefined);
16123 Remove any states that were added because the number of contexts
16124 exceeded the number of explicit enter states, but the context has
16125 not changed since the last time the state was entered.
16127 @method removeUnchangedContexts
16128 @param {Ember.StateManager} manager passed in to look up the last
16129 context for a states
16131 removeUnchangedContexts: function(manager) {
16132 // Start from the beginning of the enter states. If the state was added
16133 // to the list during the context matching phase, make sure the context
16134 // has actually changed since the last time the state was entered.
16135 while (this.enterStates.length > 0) {
16136 if (this.enterStates[0] !== this.exitStates[0]) { break; }
16138 if (this.enterStates.length === this.contexts.length) {
16139 if (manager.getStateMeta(this.enterStates[0], 'context') !== this.contexts[0]) { break; }
16140 this.contexts.shift();
16143 this.resolveState = this.enterStates.shift();
16144 this.exitStates.shift();
16150 StateManager is part of Ember's implementation of a finite state machine. A StateManager
16151 instance manages a number of properties that are instances of `Ember.State`,
16152 tracks the current active state, and triggers callbacks when states have changed.
16156 The states of StateManager can be declared in one of two ways. First, you can define
16157 a `states` property that contains all the states:
16159 managerA = Ember.StateManager.create({
16161 stateOne: Ember.State.create(),
16162 stateTwo: Ember.State.create()
16166 managerA.get('states')
16168 // stateOne: Ember.State.create(),
16169 // stateTwo: Ember.State.create()
16172 You can also add instances of `Ember.State` (or an `Ember.State` subclass) directly as properties
16173 of a StateManager. These states will be collected into the `states` property for you.
16175 managerA = Ember.StateManager.create({
16176 stateOne: Ember.State.create(),
16177 stateTwo: Ember.State.create()
16180 managerA.get('states')
16182 // stateOne: Ember.State.create(),
16183 // stateTwo: Ember.State.create()
16186 ## The Initial State
16187 When created a StateManager instance will immediately enter into the state
16188 defined as its `start` property or the state referenced by name in its
16189 `initialState` property:
16191 managerA = Ember.StateManager.create({
16192 start: Ember.State.create({})
16195 managerA.get('currentState.name') // 'start'
16197 managerB = Ember.StateManager.create({
16198 initialState: 'beginHere',
16199 beginHere: Ember.State.create({})
16202 managerB.get('currentState.name') // 'beginHere'
16204 Because it is a property you may also provide a computed function if you wish to derive
16205 an `initialState` programmatically:
16207 managerC = Ember.StateManager.create({
16208 initialState: function(){
16215 active: Ember.State.create({}),
16216 passive: Ember.State.create({})
16219 ## Moving Between States
16220 A StateManager can have any number of Ember.State objects as properties
16221 and can have a single one of these states as its current state.
16223 Calling `transitionTo` transitions between states:
16225 robotManager = Ember.StateManager.create({
16226 initialState: 'poweredDown',
16227 poweredDown: Ember.State.create({}),
16228 poweredUp: Ember.State.create({})
16231 robotManager.get('currentState.name') // 'poweredDown'
16232 robotManager.transitionTo('poweredUp')
16233 robotManager.get('currentState.name') // 'poweredUp'
16235 Before transitioning into a new state the existing `currentState` will have its
16236 `exit` method called with the StateManager instance as its first argument and
16237 an object representing the transition as its second argument.
16239 After transitioning into a new state the new `currentState` will have its
16240 `enter` method called with the StateManager instance as its first argument and
16241 an object representing the transition as its second argument.
16243 robotManager = Ember.StateManager.create({
16244 initialState: 'poweredDown',
16245 poweredDown: Ember.State.create({
16246 exit: function(stateManager){
16247 console.log("exiting the poweredDown state")
16250 poweredUp: Ember.State.create({
16251 enter: function(stateManager){
16252 console.log("entering the poweredUp state. Destroy all humans.")
16257 robotManager.get('currentState.name') // 'poweredDown'
16258 robotManager.transitionTo('poweredUp')
16260 // 'exiting the poweredDown state'
16261 // 'entering the poweredUp state. Destroy all humans.'
16264 Once a StateManager is already in a state, subsequent attempts to enter that state will
16265 not trigger enter or exit method calls. Attempts to transition into a state that the
16266 manager does not have will result in no changes in the StateManager's current state:
16268 robotManager = Ember.StateManager.create({
16269 initialState: 'poweredDown',
16270 poweredDown: Ember.State.create({
16271 exit: function(stateManager){
16272 console.log("exiting the poweredDown state")
16275 poweredUp: Ember.State.create({
16276 enter: function(stateManager){
16277 console.log("entering the poweredUp state. Destroy all humans.")
16282 robotManager.get('currentState.name') // 'poweredDown'
16283 robotManager.transitionTo('poweredUp')
16285 // 'exiting the poweredDown state'
16286 // 'entering the poweredUp state. Destroy all humans.'
16287 robotManager.transitionTo('poweredUp') // no logging, no state change
16289 robotManager.transitionTo('someUnknownState') // silently fails
16290 robotManager.get('currentState.name') // 'poweredUp'
16293 Each state property may itself contain properties that are instances of Ember.State.
16294 The StateManager can transition to specific sub-states in a series of transitionTo method calls or
16295 via a single transitionTo with the full path to the specific state. The StateManager will also
16296 keep track of the full path to its currentState
16298 robotManager = Ember.StateManager.create({
16299 initialState: 'poweredDown',
16300 poweredDown: Ember.State.create({
16301 charging: Ember.State.create(),
16302 charged: Ember.State.create()
16304 poweredUp: Ember.State.create({
16305 mobile: Ember.State.create(),
16306 stationary: Ember.State.create()
16310 robotManager.get('currentState.name') // 'poweredDown'
16312 robotManager.transitionTo('poweredUp')
16313 robotManager.get('currentState.name') // 'poweredUp'
16315 robotManager.transitionTo('mobile')
16316 robotManager.get('currentState.name') // 'mobile'
16318 // transition via a state path
16319 robotManager.transitionTo('poweredDown.charging')
16320 robotManager.get('currentState.name') // 'charging'
16322 robotManager.get('currentState.path') // 'poweredDown.charging'
16324 Enter transition methods will be called for each state and nested child state in their
16325 hierarchical order. Exit methods will be called for each state and its nested states in
16326 reverse hierarchical order.
16328 Exit transitions for a parent state are not called when entering into one of its child states,
16329 only when transitioning to a new section of possible states in the hierarchy.
16331 robotManager = Ember.StateManager.create({
16332 initialState: 'poweredDown',
16333 poweredDown: Ember.State.create({
16334 enter: function(){},
16336 console.log("exited poweredDown state")
16338 charging: Ember.State.create({
16339 enter: function(){},
16342 charged: Ember.State.create({
16344 console.log("entered charged state")
16347 console.log("exited charged state")
16351 poweredUp: Ember.State.create({
16353 console.log("entered poweredUp state")
16355 exit: function(){},
16356 mobile: Ember.State.create({
16358 console.log("entered mobile state")
16362 stationary: Ember.State.create({
16363 enter: function(){},
16370 robotManager.get('currentState.path') // 'poweredDown'
16371 robotManager.transitionTo('charged')
16372 // logs 'entered charged state'
16373 // but does *not* log 'exited poweredDown state'
16374 robotManager.get('currentState.name') // 'charged
16376 robotManager.transitionTo('poweredUp.mobile')
16378 // 'exited charged state'
16379 // 'exited poweredDown state'
16380 // 'entered poweredUp state'
16381 // 'entered mobile state'
16383 During development you can set a StateManager's `enableLogging` property to `true` to
16384 receive console messages of state transitions.
16386 robotManager = Ember.StateManager.create({
16387 enableLogging: true
16390 ## Managing currentState with Actions
16391 To control which transitions between states are possible for a given state, StateManager
16392 can receive and route action messages to its states via the `send` method. Calling to `send` with
16393 an action name will begin searching for a method with the same name starting at the current state
16394 and moving up through the parent states in a state hierarchy until an appropriate method is found
16395 or the StateManager instance itself is reached.
16397 If an appropriately named method is found it will be called with the state manager as the first
16398 argument and an optional `context` object as the second argument.
16400 managerA = Ember.StateManager.create({
16401 initialState: 'stateOne.substateOne.subsubstateOne',
16402 stateOne: Ember.State.create({
16403 substateOne: Ember.State.create({
16404 anAction: function(manager, context){
16405 console.log("an action was called")
16407 subsubstateOne: Ember.State.create({})
16412 managerA.get('currentState.name') // 'subsubstateOne'
16413 managerA.send('anAction')
16414 // 'stateOne.substateOne.subsubstateOne' has no anAction method
16415 // so the 'anAction' method of 'stateOne.substateOne' is called
16416 // and logs "an action was called"
16417 // with managerA as the first argument
16418 // and no second argument
16421 managerA.send('anAction', someObject)
16422 // the 'anAction' method of 'stateOne.substateOne' is called again
16423 // with managerA as the first argument and
16424 // someObject as the second argument.
16427 If the StateManager attempts to send an action but does not find an appropriately named
16428 method in the current state or while moving upwards through the state hierarchy
16429 it will throw a new Ember.Error. Action detection only moves upwards through the state hierarchy
16430 from the current state. It does not search in other portions of the hierarchy.
16432 managerB = Ember.StateManager.create({
16433 initialState: 'stateOne.substateOne.subsubstateOne',
16434 stateOne: Ember.State.create({
16435 substateOne: Ember.State.create({
16436 subsubstateOne: Ember.State.create({})
16439 stateTwo: Ember.State.create({
16440 anAction: function(manager, context){
16441 // will not be called below because it is
16442 // not a parent of the current state
16447 managerB.get('currentState.name') // 'subsubstateOne'
16448 managerB.send('anAction')
16449 // Error: <Ember.StateManager:ember132> could not
16450 // respond to event anAction in state stateOne.substateOne.subsubstateOne.
16452 Inside of an action method the given state should delegate `transitionTo` calls on its
16455 robotManager = Ember.StateManager.create({
16456 initialState: 'poweredDown.charging',
16457 poweredDown: Ember.State.create({
16458 charging: Ember.State.create({
16459 chargeComplete: function(manager, context){
16460 manager.transitionTo('charged')
16463 charged: Ember.State.create({
16464 boot: function(manager, context){
16465 manager.transitionTo('poweredUp')
16469 poweredUp: Ember.State.create({
16470 beginExtermination: function(manager, context){
16471 manager.transitionTo('rampaging')
16473 rampaging: Ember.State.create()
16477 robotManager.get('currentState.name') // 'charging'
16478 robotManager.send('boot') // throws error, no boot action
16479 // in current hierarchy
16480 robotManager.get('currentState.name') // remains 'charging'
16482 robotManager.send('beginExtermination') // throws error, no beginExtermination
16483 // action in current hierarchy
16484 robotManager.get('currentState.name') // remains 'charging'
16486 robotManager.send('chargeComplete')
16487 robotManager.get('currentState.name') // 'charged'
16489 robotManager.send('boot')
16490 robotManager.get('currentState.name') // 'poweredUp'
16492 robotManager.send('beginExtermination', allHumans)
16493 robotManager.get('currentState.name') // 'rampaging'
16495 Transition actions can also be created using the `transitionTo` method of the Ember.State class. The
16496 following example StateManagers are equivalent:
16498 aManager = Ember.StateManager.create({
16499 stateOne: Ember.State.create({
16500 changeToStateTwo: Ember.State.transitionTo('stateTwo')
16502 stateTwo: Ember.State.create({})
16505 bManager = Ember.StateManager.create({
16506 stateOne: Ember.State.create({
16507 changeToStateTwo: function(manager, context){
16508 manager.transitionTo('stateTwo', context)
16511 stateTwo: Ember.State.create({})
16514 @class StateManager
16516 @extends Ember.State
16518 Ember.StateManager = Ember.State.extend({
16522 When creating a new statemanager, look for a default state to transition
16523 into. This state can either be named `start`, or can be specified using the
16524 `initialState` property.
16531 set(this, 'stateMeta', Ember.Map.create());
16533 var initialState = get(this, 'initialState');
16535 if (!initialState && get(this, 'states.start')) {
16536 initialState = 'start';
16539 if (initialState) {
16540 this.transitionTo(initialState);
16541 Ember.assert('Failed to transition to initial state "' + initialState + '"', !!get(this, 'currentState'));
16545 stateMetaFor: function(state) {
16546 var meta = get(this, 'stateMeta'),
16547 stateMeta = meta.get(state);
16551 meta.set(state, stateMeta);
16557 setStateMeta: function(state, key, value) {
16558 return set(this.stateMetaFor(state), key, value);
16561 getStateMeta: function(state, key) {
16562 return get(this.stateMetaFor(state), key);
16566 The current state from among the manager's possible states. This property should
16567 not be set directly. Use `transitionTo` to move between states by name.
16569 @property currentState
16572 currentState: null,
16575 The path of the current state. Returns a string representation of the current
16578 @property currentPath
16581 currentPath: Ember.computed('currentState', function() {
16582 return get(this, 'currentState.path');
16586 The name of transitionEvent that this stateManager will dispatch
16588 @property transitionEvent
16592 transitionEvent: 'setup',
16595 If set to true, `errorOnUnhandledEvents` will cause an exception to be
16596 raised if you attempt to send an event to a state manager that is not
16597 handled by the current state or any of its parent states.
16599 @property errorOnUnhandledEvents
16603 errorOnUnhandledEvent: true,
16605 send: function(event, context) {
16606 Ember.assert('Cannot send event "' + event + '" while currentState is ' + get(this, 'currentState'), get(this, 'currentState'));
16607 return this.sendRecursively(event, get(this, 'currentState'), context);
16610 sendRecursively: function(event, currentState, context) {
16611 var log = this.enableLogging,
16612 action = currentState[event];
16614 // Test to see if the action is a method that
16615 // can be invoked. Don't blindly check just for
16616 // existence, because it is possible the state
16617 // manager has a child state of the given name,
16618 // and we should still raise an exception in that
16620 if (typeof action === 'function') {
16621 if (log) { Ember.Logger.log(fmt("STATEMANAGER: Sending event '%@' to state %@.", [event, get(currentState, 'path')])); }
16622 return action.call(currentState, this, context);
16624 var parentState = get(currentState, 'parentState');
16626 return this.sendRecursively(event, parentState, context);
16627 } else if (get(this, 'errorOnUnhandledEvent')) {
16628 throw new Ember.Error(this.toString() + " could not respond to event " + event + " in state " + get(this, 'currentState.path') + ".");
16634 Finds a state by its state path.
16638 manager = Ember.StateManager.create({
16639 root: Ember.State.create({
16640 dashboard: Ember.State.create()
16644 manager.getStateByPath(manager, "root.dashboard")
16646 // returns the dashboard state
16648 @method getStateByPath
16649 @param {Ember.State} root the state to start searching from
16650 @param {String} path the state path to follow
16651 @return {Ember.State} the state at the end of the path
16653 getStateByPath: function(root, path) {
16654 var parts = path.split('.'),
16657 for (var i=0, l=parts.length; i<l; i++) {
16658 state = get(get(state, 'states'), parts[i]);
16659 if (!state) { break; }
16665 findStateByPath: function(state, path) {
16668 while (!possible && state) {
16669 possible = this.getStateByPath(state, path);
16670 state = get(state, 'parentState');
16677 A state stores its child states in its `states` hash.
16678 This code takes a path like `posts.show` and looks
16679 up `origin.states.posts.states.show`.
16681 It returns a list of all of the states from the
16682 origin, which is the list of states to call `enter`
16685 @method findStateByPath
16689 findStatesByPath: function(origin, path) {
16690 if (!path || path === "") { return undefined; }
16691 var r = path.split('.'),
16694 for (var i=0, len = r.length; i < len; i++) {
16695 var states = get(origin, 'states');
16697 if (!states) { return undefined; }
16699 var s = get(states, r[i]);
16700 if (s) { origin = s; ret.push(s); }
16701 else { return undefined; }
16707 goToState: function() {
16708 // not deprecating this yet so people don't constantly need to
16709 // make trivial changes for little reason.
16710 return this.transitionTo.apply(this, arguments);
16713 transitionTo: function(path, context) {
16714 // XXX When is transitionTo called with no path
16715 if (Ember.empty(path)) { return; }
16717 // The ES6 signature of this function is `path, ...contexts`
16718 var contexts = context ? Array.prototype.slice.call(arguments, 1) : [],
16719 currentState = get(this, 'currentState') || this;
16721 // First, get the enter, exit and resolve states for the current state
16722 // and specified path. If possible, use an existing cache.
16723 var hash = this.contextFreeTransition(currentState, path);
16725 // Next, process the raw state information for the contexts passed in.
16726 var transition = new Transition(hash).normalize(this, contexts);
16728 this.enterState(transition);
16729 this.triggerSetupContext(transition);
16732 contextFreeTransition: function(currentState, path) {
16733 var cache = currentState.pathsCache[path];
16734 if (cache) { return cache; }
16736 var enterStates = this.findStatesByPath(currentState, path),
16738 resolveState = currentState;
16740 // Walk up the states. For each state, check whether a state matching
16741 // the `path` is nested underneath. This will find the closest
16742 // parent state containing `path`.
16744 // This allows the user to pass in a relative path. For example, for
16745 // the following state hierarchy:
16749 // | | |- show (* current)
16753 // If the current state is `<root.posts.show>`, an attempt to
16754 // transition to `comments.show` will match `<root.comments.show>`.
16756 // First, this code will look for root.posts.show.comments.show.
16757 // Next, it will look for root.posts.comments.show. Finally,
16758 // it will look for `root.comments.show`, and find the state.
16760 // After this process, the following variables will exist:
16762 // * resolveState: a common parent state between the current
16763 // and target state. In the above example, `<root>` is the
16765 // * enterStates: a list of all of the states represented
16766 // by the path from the `resolveState`. For example, for
16767 // the path `root.comments.show`, `enterStates` would have
16768 // `[<root.comments>, <root.comments.show>]`
16769 // * exitStates: a list of all of the states from the
16770 // `resolveState` to the `currentState`. In the above
16771 // example, `exitStates` would have
16772 // `[<root.posts>`, `<root.posts.show>]`.
16773 while (resolveState && !enterStates) {
16774 exitStates.unshift(resolveState);
16776 resolveState = get(resolveState, 'parentState');
16777 if (!resolveState) {
16778 enterStates = this.findStatesByPath(this, path);
16779 if (!enterStates) {
16780 Ember.assert('Could not find state for path: "'+path+'"');
16784 enterStates = this.findStatesByPath(resolveState, path);
16787 // If the path contains some states that are parents of both the
16788 // current state and the target state, remove them.
16790 // For example, in the following hierarchy:
16794 // | | |- index (* current)
16797 // If the `path` is `root.post.show`, the three variables will
16800 // * resolveState: `<state manager>`
16801 // * enterStates: `[<root>, <root.post>, <root.post.show>]`
16802 // * exitStates: `[<root>, <root.post>, <root.post.index>]`
16804 // The goal of this code is to remove the common states, so we
16807 // * resolveState: `<root.post>`
16808 // * enterStates: `[<root.post.show>]`
16809 // * exitStates: `[<root.post.index>]`
16811 // This avoid unnecessary calls to the enter and exit transitions.
16812 while (enterStates.length > 0 && enterStates[0] === exitStates[0]) {
16813 resolveState = enterStates.shift();
16814 exitStates.shift();
16817 // Cache the enterStates, exitStates, and resolveState for the
16818 // current state and the `path`.
16819 var transitions = currentState.pathsCache[path] = {
16820 exitStates: exitStates,
16821 enterStates: enterStates,
16822 resolveState: resolveState
16825 return transitions;
16828 triggerSetupContext: function(transitions) {
16829 var contexts = transitions.contexts,
16830 offset = transitions.enterStates.length - contexts.length,
16831 enterStates = transitions.enterStates,
16832 transitionEvent = get(this, 'transitionEvent');
16834 Ember.assert("More contexts provided than states", offset >= 0);
16836 arrayForEach.call(enterStates, function(state, idx) {
16837 state.trigger(transitionEvent, this, contexts[idx-offset]);
16841 getState: function(name) {
16842 var state = get(this, name),
16843 parentState = get(this, 'parentState');
16847 } else if (parentState) {
16848 return parentState.getState(name);
16852 enterState: function(transition) {
16853 var log = this.enableLogging;
16855 var exitStates = transition.exitStates.slice(0).reverse();
16856 arrayForEach.call(exitStates, function(state) {
16857 state.trigger('exit', this);
16860 arrayForEach.call(transition.enterStates, function(state) {
16861 if (log) { Ember.Logger.log("STATEMANAGER: Entering " + get(state, 'path')); }
16862 state.trigger('enter', this);
16865 set(this, 'currentState', transition.finalState);
16878 @submodule ember-states
16879 @requires ember-runtime
16885 var get = Ember.get;
16887 Ember._ResolvedState = Ember.Object.extend({
16892 object: Ember.computed(function(key, value) {
16893 if (arguments.length === 2) {
16894 this._object = value;
16897 if (this._object) {
16898 return this._object;
16900 var state = get(this, 'state'),
16901 match = get(this, 'match'),
16902 manager = get(this, 'manager');
16903 return state.deserialize(manager, match.hash);
16908 hasPromise: Ember.computed(function() {
16909 return Ember.canInvoke(get(this, 'object'), 'then');
16910 }).property('object'),
16912 promise: Ember.computed(function() {
16913 var object = get(this, 'object');
16914 if (Ember.canInvoke(object, 'then')) {
16918 then: function(success) { success(object); }
16921 }).property('object'),
16923 transition: function() {
16924 var manager = get(this, 'manager'),
16925 path = get(this, 'state.path'),
16926 object = get(this, 'object');
16927 manager.transitionTo(path, object);
16938 @submodule ember-routing
16941 var get = Ember.get;
16943 // The Ember Routable mixin assumes the existance of a simple
16944 // routing shim that supports the following three behaviors:
16946 // * .getURL() - this is called when the page loads
16947 // * .setURL(newURL) - this is called from within the state
16948 // manager when the state changes to a routable state
16949 // * .onURLChange(callback) - this happens when the user presses
16950 // the back or forward button
16952 var paramForClass = function(classObject) {
16953 var className = classObject.toString(),
16954 parts = className.split("."),
16955 last = parts[parts.length - 1];
16957 return Ember.String.underscore(last) + "_id";
16960 var merge = function(original, hash) {
16961 for (var prop in hash) {
16962 if (!hash.hasOwnProperty(prop)) { continue; }
16963 if (original.hasOwnProperty(prop)) { continue; }
16965 original[prop] = hash[prop];
16972 @extends Ember.Mixin
16974 Ember.Routable = Ember.Mixin.create({
16977 this.on('setup', this, this.stashContext);
16979 if (redirection = get(this, 'redirectsTo')) {
16980 Ember.assert("You cannot use `redirectsTo` if you already have a `connectOutlets` method", this.connectOutlets === Ember.K);
16982 this.connectOutlets = function(router) {
16983 router.transitionTo(redirection);
16987 // normalize empty route to '/'
16988 var route = get(this, 'route');
16989 if (route === '') {
16995 Ember.assert("You cannot use `redirectsTo` on a state that has child states", !redirection || (!!redirection && !!get(this, 'isLeaf')));
16998 setup: function() {
16999 return this.connectOutlets.apply(this, arguments);
17005 Whenever a routable state is entered, the context it was entered with
17006 is stashed so that we can regenerate the state's `absoluteURL` on
17009 @method stashContext
17010 @param manager {Ember.StateManager}
17013 stashContext: function(manager, context) {
17014 this.router = manager;
17016 var serialized = this.serialize(manager, context);
17017 Ember.assert('serialize must return a hash', !serialized || typeof serialized === 'object');
17019 manager.setStateMeta(this, 'context', context);
17020 manager.setStateMeta(this, 'serialized', serialized);
17022 if (get(this, 'isRoutable') && !get(manager, 'isRouting')) {
17023 this.updateRoute(manager, get(manager, 'location'));
17030 Whenever a routable state is entered, the router's location object
17031 is notified to set the URL to the current absolute path.
17033 In general, this will update the browser's URL.
17035 @method updateRoute
17036 @param manager {Ember.StateManager}
17037 @param location {Ember.Location}
17039 updateRoute: function(manager, location) {
17040 if (get(this, 'isLeafRoute')) {
17041 var path = this.absoluteRoute(manager);
17042 location.setURL(path);
17049 Get the absolute route for the current state and a given
17052 This method is private, as it expects a serialized hash,
17053 not the original context object.
17055 @method absoluteRoute
17056 @param manager {Ember.StateManager}
17059 absoluteRoute: function(manager, hash) {
17060 var parentState = get(this, 'parentState');
17061 var path = '', generated;
17063 // If the parent state is routable, use its current path
17064 // as this route's prefix.
17065 if (get(parentState, 'isRoutable')) {
17066 path = parentState.absoluteRoute(manager, hash);
17069 var matcher = get(this, 'routeMatcher'),
17070 serialized = manager.getStateMeta(this, 'serialized');
17072 // merge the existing serialized object in with the passed
17075 merge(hash, serialized);
17077 generated = matcher && matcher.generate(hash);
17080 path = path + '/' + generated;
17089 At the moment, a state is routable if it has a string `route`
17090 property. This heuristic may change.
17092 @property isRoutable
17095 isRoutable: Ember.computed(function() {
17096 return typeof get(this, 'route') === 'string';
17102 Determine if this is the last routeable state
17104 @property isLeafRoute
17107 isLeafRoute: Ember.computed(function() {
17108 if (get(this, 'isLeaf')) { return true; }
17109 return !get(this, 'childStates').findProperty('isRoutable');
17115 A _RouteMatcher object generated from the current route's `route`
17118 @property routeMatcher
17119 @type Ember._RouteMatcher
17121 routeMatcher: Ember.computed(function() {
17122 var route = get(this, 'route');
17124 return Ember._RouteMatcher.create({ route: route });
17131 Check whether the route has dynamic segments and therefore takes
17134 @property hasContext
17137 hasContext: Ember.computed(function() {
17138 var routeMatcher = get(this, 'routeMatcher');
17139 if (routeMatcher) {
17140 return routeMatcher.identifiers.length > 0;
17147 The model class associated with the current state. This property
17148 uses the `modelType` property, in order to allow it to be
17149 specified as a String.
17151 @property modelClass
17154 modelClass: Ember.computed(function() {
17155 var modelType = get(this, 'modelType');
17157 if (typeof modelType === 'string') {
17158 return Ember.get(window, modelType);
17167 Get the model class for the state. The heuristic is:
17169 * The state must have a single dynamic segment
17170 * The dynamic segment must end in `_id`
17171 * A dynamic segment like `blog_post_id` is converted into `BlogPost`
17172 * The name is then looked up on the passed in namespace
17174 The process of initializing an application with a router will
17175 pass the application's namespace into the router, which will be
17178 @method modelClassFor
17179 @param namespace {Ember.Namespace}
17181 modelClassFor: function(namespace) {
17182 var modelClass, routeMatcher, identifiers, match, className;
17184 // if an explicit modelType was specified, use that
17185 if (modelClass = get(this, 'modelClass')) { return modelClass; }
17187 // if the router has no lookup namespace, we won't be able to guess
17189 if (!namespace) { return; }
17191 // make sure this state is actually a routable state
17192 routeMatcher = get(this, 'routeMatcher');
17193 if (!routeMatcher) { return; }
17195 // only guess modelType for states with a single dynamic segment
17196 // (no more, no fewer)
17197 identifiers = routeMatcher.identifiers;
17198 if (identifiers.length !== 2) { return; }
17200 // extract the `_id` from the end of the dynamic segment; if the
17201 // dynamic segment does not end in `_id`, we can't guess the
17203 match = identifiers[1].match(/^(.*)_id$/);
17204 if (!match) { return; }
17206 // convert the underscored type into a class form and look it up
17207 // on the router's namespace
17208 className = Ember.String.classify(match[1]);
17209 return get(namespace, className);
17213 The default method that takes a `params` object and converts
17216 By default, a params hash that looks like `{ post_id: 1 }`
17217 will be looked up as `namespace.Post.find(1)`. This is
17218 designed to work seamlessly with Ember Data, but will work
17219 fine with any class that has a `find` method.
17221 @method deserialize
17222 @param manager {Ember.StateManager}
17223 @param params {Hash}
17225 deserialize: function(manager, params) {
17226 var modelClass, routeMatcher, param;
17228 if (modelClass = this.modelClassFor(get(manager, 'namespace'))) {
17229 Ember.assert("Expected "+modelClass.toString()+" to implement `find` for use in '"+this.get('path')+"' `deserialize`. Please implement the `find` method or overwrite `deserialize`.", modelClass.find);
17230 return modelClass.find(params[paramForClass(modelClass)]);
17237 The default method that takes an object and converts it into
17240 By default, if there is a single dynamic segment named
17241 `blog_post_id` and the object is a `BlogPost` with an
17242 `id` of `12`, the serialize method will produce:
17244 { blog_post_id: 12 }
17247 @param manager {Ember.StateManager}
17250 serialize: function(manager, context) {
17251 var modelClass, routeMatcher, namespace, param, id;
17253 if (Ember.empty(context)) { return ''; }
17255 if (modelClass = this.modelClassFor(get(manager, 'namespace'))) {
17256 param = paramForClass(modelClass);
17257 id = get(context, 'id');
17259 context[param] = id;
17267 @method resolvePath
17268 @param manager {Ember.StateManager}
17269 @param path {String}
17271 resolvePath: function(manager, path) {
17272 if (get(this, 'isLeafRoute')) { return Ember.A(); }
17274 var childStates = get(this, 'childStates'), match;
17276 childStates = Ember.A(childStates.filterProperty('isRoutable'));
17278 childStates = childStates.sort(function(a, b) {
17279 var aDynamicSegments = get(a, 'routeMatcher.identifiers.length'),
17280 bDynamicSegments = get(b, 'routeMatcher.identifiers.length'),
17281 aRoute = get(a, 'route'),
17282 bRoute = get(b, 'route');
17284 if (aRoute.indexOf(bRoute) === 0) {
17286 } else if (bRoute.indexOf(aRoute) === 0) {
17290 if (aDynamicSegments !== bDynamicSegments) {
17291 return aDynamicSegments - bDynamicSegments;
17294 return get(b, 'route.length') - get(a, 'route.length');
17297 var state = childStates.find(function(state) {
17298 var matcher = get(state, 'routeMatcher');
17299 if (match = matcher.match(path)) { return true; }
17302 Ember.assert("Could not find state for path " + path, !!state);
17304 var resolvedState = Ember._ResolvedState.create({
17310 var states = state.resolvePath(manager, match.remaining);
17312 return Ember.A([resolvedState]).pushObjects(states);
17318 Once `unroute` has finished unwinding, `routePath` will be called
17319 with the remainder of the route.
17321 For example, if you were in the /posts/1/comments state, and you
17322 moved into the /posts/2/comments state, `routePath` will be called
17323 on the state whose path is `/posts` with the path `/2/comments`.
17326 @param manager {Ember.StateManager}
17327 @param path {String}
17329 routePath: function(manager, path) {
17330 if (get(this, 'isLeafRoute')) { return; }
17332 var resolvedStates = this.resolvePath(manager, path),
17333 hasPromises = resolvedStates.some(function(s) { return get(s, 'hasPromise'); });
17335 function runTransition() {
17336 resolvedStates.forEach(function(rs) { rs.transition(); });
17340 manager.transitionTo('loading');
17342 Ember.assert('Loading state should be the child of a route', Ember.Routable.detect(get(manager, 'currentState.parentState')));
17343 Ember.assert('Loading state should not be a route', !Ember.Routable.detect(get(manager, 'currentState')));
17345 manager.handleStatePromises(resolvedStates, runTransition);
17354 When you move to a new route by pressing the back
17355 or forward button, this method is called first.
17357 Its job is to move the state manager into a parent
17358 state of the state it will eventually move into.
17360 @method unroutePath
17361 @param router {Ember.Router}
17362 @param path {String}
17364 unroutePath: function(router, path) {
17365 var parentState = get(this, 'parentState');
17367 // If we're at the root state, we're done
17368 if (parentState === router) {
17372 path = path.replace(/^(?=[^\/])/, "/");
17373 var absolutePath = this.absoluteRoute(router);
17375 var route = get(this, 'route');
17377 // If the current path is empty, move up one state,
17378 // because the index ('/') state must be a leaf node.
17379 if (route !== '/') {
17380 // If the current path is a prefix of the path we're trying
17381 // to go to, we're done.
17382 var index = path.indexOf(absolutePath),
17383 next = path.charAt(absolutePath.length);
17385 if (index === 0 && (next === "/" || next === "")) {
17390 // Transition to the parent and call unroute again.
17391 router.enterState({
17392 exitStates: [this],
17394 finalState: parentState
17397 router.send('unroutePath', path);
17400 parentTemplate: Ember.computed(function() {
17401 var state = this, parentState, template;
17403 while (state = get(state, 'parentState')) {
17404 if (template = get(state, 'template')) {
17409 return 'application';
17412 _template: Ember.computed(function(key, value) {
17413 if (arguments.length > 1) { return value; }
17415 if (value = get(this, 'template')) {
17419 // If no template was explicitly supplied convert
17420 // the class name into a template name. For example,
17421 // App.PostRoute will return `post`.
17422 var className = this.constructor.toString(), baseName;
17423 if (/^[^\[].*Route$/.test(className)) {
17424 baseName = className.match(/([^\.]+\.)*([^\.]+)/)[2];
17425 baseName = baseName.replace(/Route$/, '');
17426 return baseName.charAt(0).toLowerCase() + baseName.substr(1);
17430 render: function(options) {
17431 options = options || {};
17433 var template = options.template || get(this, '_template'),
17434 parentTemplate = options.into || get(this, 'parentTemplate'),
17435 controller = get(this.router, parentTemplate + "Controller");
17437 var viewName = Ember.String.classify(template) + "View",
17438 viewClass = get(get(this.router, 'namespace'), viewName);
17440 viewClass = (viewClass || Ember.View).extend({
17441 templateName: template
17444 controller.set('view', viewClass.create());
17448 The `connectOutlets` event will be triggered once a
17449 state has been entered. It will be called with the
17452 @event connectOutlets
17453 @param router {Ember.Router}
17456 connectOutlets: Ember.K,
17459 The `navigateAway` event will be triggered when the
17460 URL changes due to the back/forward button
17462 @event navigateAway
17464 navigateAway: Ember.K
17474 @submodule ember-routing
17480 @extends Ember.State
17481 @uses Ember.Routable
17483 Ember.Route = Ember.State.extend(Ember.Routable);
17490 var escapeForRegex = function(text) {
17491 return text.replace(/[\-\[\]{}()*+?.,\\\^\$|#\s]/g, "\\$&");
17495 @class _RouteMatcher
17498 @extends Ember.Object
17500 Ember._RouteMatcher = Ember.Object.extend({
17504 var route = this.route,
17509 // Strip off leading slash if present
17510 if (route.charAt(0) === '/') {
17511 route = this.route = route.substr(1);
17514 escaped = escapeForRegex(route);
17516 var regex = escaped.replace(/:([a-z_]+)(?=$|\/)/gi, function(match, id) {
17517 identifiers[count++] = id;
17521 this.identifiers = identifiers;
17522 this.regex = new RegExp("^/?" + regex);
17525 match: function(path) {
17526 var match = path.match(this.regex);
17529 var identifiers = this.identifiers,
17532 for (var i=1, l=identifiers.length; i<l; i++) {
17533 hash[identifiers[i]] = match[i];
17537 remaining: path.substr(match[0].length),
17538 hash: identifiers.length > 0 ? hash : null
17543 generate: function(hash) {
17544 var identifiers = this.identifiers, route = this.route, id;
17545 for (var i=1, l=identifiers.length; i<l; i++) {
17546 id = identifiers[i];
17547 route = route.replace(new RegExp(":" + id), hash[id]);
17560 @submodule ember-routing
17563 var get = Ember.get, set = Ember.set;
17566 This file implements the `location` API used by Ember's router.
17570 getURL: returns the current URL
17571 setURL(path): sets the current URL
17572 onUpdateURL(callback): triggers the callback when the URL changes
17573 formatURL(url): formats `url` to be placed into `href` attribute
17575 Calling setURL will not trigger onUpdateURL callbacks.
17577 TODO: This should perhaps be moved so that it's visible in the doc output.
17581 Ember.Location returns an instance of the correct implementation of
17582 the `location` API.
17584 You can pass it a `implementation` ('hash', 'history', 'none') to force a
17585 particular implementation.
17592 create: function(options) {
17593 var implementation = options && options.implementation;
17594 Ember.assert("Ember.Location.create: you must specify a 'implementation' option", !!implementation);
17596 var implementationClass = this.implementations[implementation];
17597 Ember.assert("Ember.Location.create: " + implementation + " is not a valid implementation", !!implementationClass);
17599 return implementationClass.create.apply(implementationClass, arguments);
17602 registerImplementation: function(name, implementation) {
17603 this.implementations[name] = implementation;
17606 implementations: {}
17616 @submodule ember-routing
17619 var get = Ember.get, set = Ember.set;
17622 Ember.NoneLocation does not interact with the browser. It is useful for
17623 testing, or when you need to manage state with your Router, but temporarily
17624 don't want it to muck with the URL (for example when you embed your
17625 application in a larger page).
17627 @class NoneLocation
17629 @extends Ember.Object
17631 Ember.NoneLocation = Ember.Object.extend({
17634 getURL: function() {
17635 return get(this, 'path');
17638 setURL: function(path) {
17639 set(this, 'path', path);
17642 onUpdateURL: function(callback) {
17643 // We are not wired up to the browser, so we'll never trigger the callback.
17646 formatURL: function(url) {
17647 // The return value is not overly meaningful, but we do not want to throw
17648 // errors when test code renders templates containing {{action href=true}}
17654 Ember.Location.registerImplementation('none', Ember.NoneLocation);
17663 @submodule ember-routing
17666 var get = Ember.get, set = Ember.set;
17669 Ember.HashLocation implements the location API using the browser's
17670 hash. At present, it relies on a hashchange event existing in the
17673 @class HashLocation
17675 @extends Ember.Object
17677 Ember.HashLocation = Ember.Object.extend({
17680 set(this, 'location', get(this, 'location') || window.location);
17686 Returns the current `location.hash`, minus the '#' at the front.
17690 getURL: function() {
17691 return get(this, 'location').hash.substr(1);
17697 Set the `location.hash` and remembers what was set. This prevents
17698 `onUpdateURL` callbacks from triggering when the hash was set by
17702 @param path {String}
17704 setURL: function(path) {
17705 get(this, 'location').hash = path;
17706 set(this, 'lastSetURL', path);
17712 Register a callback to be invoked when the hash changes. These
17713 callbacks will execute when the user presses the back or forward
17714 button, but not after `setURL` is invoked.
17716 @method onUpdateURL
17717 @param callback {Function}
17719 onUpdateURL: function(callback) {
17721 var guid = Ember.guidFor(this);
17723 Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
17724 var path = location.hash.substr(1);
17725 if (get(self, 'lastSetURL') === path) { return; }
17727 set(self, 'lastSetURL', null);
17729 callback(location.hash.substr(1));
17736 Given a URL, formats it to be placed into the page as part
17737 of an element's `href` attribute.
17739 This is used, for example, when using the {{action}} helper
17740 to generate a URL based on an event.
17743 @param url {String}
17745 formatURL: function(url) {
17749 willDestroy: function() {
17750 var guid = Ember.guidFor(this);
17752 Ember.$(window).unbind('hashchange.ember-location-'+guid);
17756 Ember.Location.registerImplementation('hash', Ember.HashLocation);
17765 @submodule ember-routing
17768 var get = Ember.get, set = Ember.set;
17771 Ember.HistoryLocation implements the location API using the browser's
17772 history.pushState API.
17774 @class HistoryLocation
17776 @extends Ember.Object
17778 Ember.HistoryLocation = Ember.Object.extend({
17781 set(this, 'location', get(this, 'location') || window.location);
17782 set(this, '_initialURL', get(this, 'location').pathname);
17786 Will be pre-pended to path upon state change
17796 Used to give history a starting reference
17798 @property _initialURL
17806 Returns the current `location.pathname`.
17810 getURL: function() {
17811 return get(this, 'location').pathname;
17817 Uses `history.pushState` to update the url without a page reload.
17820 @param path {String}
17822 setURL: function(path) {
17823 var state = window.history.state,
17824 initialURL = get(this, '_initialURL');
17826 path = this.formatURL(path);
17828 if ((initialURL !== path && !state) || (state && state.path !== path)) {
17829 window.history.pushState({ path: path }, null, path);
17836 Register a callback to be invoked whenever the browser
17837 history changes, including using forward and back buttons.
17839 @method onUpdateURL
17840 @param callback {Function}
17842 onUpdateURL: function(callback) {
17843 var guid = Ember.guidFor(this);
17845 Ember.$(window).bind('popstate.ember-location-'+guid, function(e) {
17846 callback(location.pathname);
17853 Used when using `{{action}}` helper. The url is always appended to the rootURL.
17856 @param url {String}
17858 formatURL: function(url) {
17859 var rootURL = get(this, 'rootURL');
17862 rootURL = rootURL.replace(/\/$/, '');
17865 return rootURL + url;
17868 willDestroy: function() {
17869 var guid = Ember.guidFor(this);
17871 Ember.$(window).unbind('popstate.ember-location-'+guid);
17875 Ember.Location.registerImplementation('history', Ember.HistoryLocation);
17890 @submodule ember-routing
17893 var get = Ember.get, set = Ember.set;
17895 var merge = function(original, hash) {
17896 for (var prop in hash) {
17897 if (!hash.hasOwnProperty(prop)) { continue; }
17898 if (original.hasOwnProperty(prop)) { continue; }
17900 original[prop] = hash[prop];
17905 `Ember.Router` is the subclass of `Ember.StateManager` responsible for providing URL-based
17906 application state detection. The `Ember.Router` instance of an application detects the browser URL
17907 at application load time and attempts to match it to a specific application state. Additionally
17908 the router will update the URL to reflect an application's state changes over time.
17910 ## Adding a Router Instance to Your Application
17911 An instance of Ember.Router can be associated with an instance of Ember.Application in one of two ways:
17913 You can provide a subclass of Ember.Router as the `Router` property of your application. An instance
17914 of this Router class will be instantiated and route detection will be enabled when the application's
17915 `initialize` method is called. The Router instance will be available as the `router` property
17916 of the application:
17918 App = Ember.Application.create({
17919 Router: Ember.Router.extend({ ... })
17923 App.get('router') // an instance of App.Router
17925 If you want to define a Router instance elsewhere, you can pass the instance to the application's
17926 `initialize` method:
17928 App = Ember.Application.create();
17929 aRouter = Ember.Router.create({ ... });
17931 App.initialize(aRouter);
17932 App.get('router') // aRouter
17934 ## Adding Routes to a Router
17935 The `initialState` property of Ember.Router instances is named `root`. The state stored in this
17936 property must be a subclass of Ember.Route. The `root` route acts as the container for the
17937 set of routable states but is not routable itself. It should have states that are also subclasses
17938 of Ember.Route which each have a `route` property describing the URL pattern you would like to detect.
17940 App = Ember.Application.create({
17941 Router: Ember.Router.extend({
17942 root: Ember.Route.extend({
17943 index: Ember.Route.extend({
17946 ... additional Ember.Routes ...
17953 When an application loads, Ember will parse the URL and attempt to find an Ember.Route within
17954 the application's states that matches. (The example URL-matching below will use the default
17955 'hash syntax' provided by `Ember.HashLocation`.)
17957 In the following route structure:
17959 App = Ember.Application.create({
17960 Router: Ember.Router.extend({
17961 root: Ember.Route.extend({
17962 aRoute: Ember.Route.extend({
17965 bRoute: Ember.Route.extend({
17966 route: '/alphabeta'
17973 Loading the page at the URL '#/' will detect the route property of 'root.aRoute' ('/') and
17974 transition the router first to the state named 'root' and then to the substate 'aRoute'.
17976 Respectively, loading the page at the URL '#/alphabeta' would detect the route property of
17977 'root.bRoute' ('/alphabeta') and transition the router first to the state named 'root' and
17978 then to the substate 'bRoute'.
17980 ## Adding Nested Routes to a Router
17981 Routes can contain nested subroutes each with their own `route` property describing the nested
17982 portion of the URL they would like to detect and handle. Router, like all instances of StateManager,
17983 cannot call `transitonTo` with an intermediary state. To avoid transitioning the Router into an
17984 intermediary state when detecting URLs, a Route with nested routes must define both a base `route`
17985 property for itself and a child Route with a `route` property of `'/'` which will be transitioned
17986 to when the base route is detected in the URL:
17988 Given the following application code:
17990 App = Ember.Application.create({
17991 Router: Ember.Router.extend({
17992 root: Ember.Route.extend({
17993 aRoute: Ember.Route.extend({
17994 route: '/theBaseRouteForThisSet',
17996 indexSubRoute: Ember.Route.extend({
18000 subRouteOne: Ember.Route.extend({
18001 route: '/subroute1'
18004 subRouteTwo: Ember.Route.extend({
18005 route: '/subRoute2'
18014 When the application is loaded at '/theBaseRouteForThisSet' the Router will transition to the route
18015 at path 'root.aRoute' and then transition to state 'indexSubRoute'.
18017 When the application is loaded at '/theBaseRouteForThisSet/subRoute1' the Router will transition to
18018 the route at path 'root.aRoute' and then transition to state 'subRouteOne'.
18020 ## Route Transition Events
18021 Transitioning between Ember.Route instances (including the transition into the detected
18022 route when loading the application) triggers the same transition events as state transitions for
18023 base `Ember.State`s. However, the default `setup` transition event is named `connectOutlets` on
18024 Ember.Router instances (see 'Changing View Hierarchy in Response To State Change').
18026 The following route structure when loaded with the URL "#/"
18028 App = Ember.Application.create({
18029 Router: Ember.Router.extend({
18030 root: Ember.Route.extend({
18031 aRoute: Ember.Route.extend({
18033 enter: function(router) {
18034 console.log("entering root.aRoute from", router.get('currentState.name'));
18036 connectOutlets: function(router) {
18037 console.log("entered root.aRoute, fully transitioned to", router.get('currentState.path'));
18045 Will result in console output of:
18047 'entering root.aRoute from root'
18048 'entered root.aRoute, fully transitioned to root.aRoute '
18050 Ember.Route has two additional callbacks for handling URL serialization and deserialization. See
18051 'Serializing/Deserializing URLs'
18053 ## Routes With Dynamic Segments
18054 An Ember.Route's `route` property can reference dynamic sections of the URL by prefacing a URL segment
18055 with the ':' character. The values of these dynamic segments will be passed as a hash to the
18056 `deserialize` method of the matching Route (see 'Serializing/Deserializing URLs').
18058 ## Serializing/Deserializing URLs
18059 Ember.Route has two callbacks for associating a particular object context with a URL: `serialize`
18060 for converting an object into a parameters hash to fill dynamic segments of a URL and `deserialize`
18061 for converting a hash of dynamic segments from the URL into the appropriate object.
18063 ### Deserializing A URL's Dynamic Segments
18064 When an application is first loaded or the URL is changed manually (e.g. through the browser's
18065 back button) the `deserialize` method of the URL's matching Ember.Route will be called with
18066 the application's router as its first argument and a hash of the URL's dynamic segments and values
18067 as its second argument.
18069 The following route structure when loaded with the URL "#/fixed/thefirstvalue/anotherFixed/thesecondvalue":
18071 App = Ember.Application.create({
18072 Router: Ember.Router.extend({
18073 root: Ember.Route.extend({
18074 aRoute: Ember.Route.extend({
18075 route: '/fixed/:dynamicSectionA/anotherFixed/:dynamicSectionB',
18076 deserialize: function(router, params) {}
18083 Will call the 'deserialize' method of the Route instance at the path 'root.aRoute' with the
18084 following hash as its second argument:
18087 dynamicSectionA: 'thefirstvalue',
18088 dynamicSectionB: 'thesecondvalue'
18091 Within `deserialize` you should use this information to retrieve or create an appropriate context
18092 object for the given URL (e.g. by loading from a remote API or accessing the browser's
18093 `localStorage`). This object must be the `return` value of `deserialize` and will be
18094 passed to the Route's `connectOutlets` and `serialize` methods.
18096 When an application's state is changed from within the application itself, the context provided for
18097 the transition will be passed and `deserialize` is not called (see 'Transitions Between States').
18099 ### Serializing An Object For URLs with Dynamic Segments
18100 When transitioning into a Route whose `route` property contains dynamic segments the Route's
18101 `serialize` method is called with the Route's router as the first argument and the Route's
18102 context as the second argument. The return value of `serialize` will be used to populate the
18103 dynamic segments and should be an object with keys that match the names of the dynamic sections.
18105 Given the following route structure:
18107 App = Ember.Application.create({
18108 Router: Ember.Router.extend({
18109 root: Ember.Route.extend({
18110 aRoute: Ember.Route.extend({
18113 bRoute: Ember.Route.extend({
18114 route: '/staticSection/:someDynamicSegment',
18115 serialize: function(router, context) {
18117 someDynamicSegment: context.get('name')
18127 Transitioning to "root.bRoute" with a context of `Object.create({name: 'Yehuda'})` will call
18128 the Route's `serialize` method with the context as its second argument and update the URL to
18129 '#/staticSection/Yehuda'.
18131 ## Transitions Between States
18132 Once a routed application has initialized its state based on the entry URL, subsequent transitions to other
18133 states will update the URL if the entered Route has a `route` property. Given the following route structure
18134 loaded at the URL '#/':
18136 App = Ember.Application.create({
18137 Router: Ember.Router.extend({
18138 root: Ember.Route.extend({
18139 aRoute: Ember.Route.extend({
18141 moveElsewhere: Ember.Route.transitionTo('bRoute')
18143 bRoute: Ember.Route.extend({
18144 route: '/someOtherLocation'
18151 And application code:
18153 App.get('router').send('moveElsewhere');
18155 Will transition the application's state to 'root.bRoute' and trigger an update of the URL to
18156 '#/someOtherLocation'.
18158 For URL patterns with dynamic segments a context can be supplied as the second argument to `send`.
18159 The router will match dynamic segments names to keys on this object and fill in the URL with the
18160 supplied values. Given the following state structure loaded at the URL '#/':
18162 App = Ember.Application.create({
18163 Router: Ember.Router.extend({
18164 root: Ember.Route.extend({
18165 aRoute: Ember.Route.extend({
18167 moveElsewhere: Ember.Route.transitionTo('bRoute')
18169 bRoute: Ember.Route.extend({
18170 route: '/a/route/:dynamicSection/:anotherDynamicSection',
18171 connectOutlets: function(router, context) {},
18178 And application code:
18180 App.get('router').send('moveElsewhere', {
18181 dynamicSection: '42',
18182 anotherDynamicSection: 'Life'
18185 Will transition the application's state to 'root.bRoute' and trigger an update of the URL to
18186 '#/a/route/42/Life'.
18188 The context argument will also be passed as the second argument to the `serialize` method call.
18190 ## Injection of Controller Singletons
18191 During application initialization Ember will detect properties of the application ending in 'Controller',
18192 create singleton instances of each class, and assign them as properties on the router. The property name
18193 will be the UpperCamel name converted to lowerCamel format. These controller classes should be subclasses
18194 of Ember.ObjectController, Ember.ArrayController, Ember.Controller, or a custom Ember.Object that includes the
18195 Ember.ControllerMixin mixin.
18198 App = Ember.Application.create({
18199 FooController: Ember.Object.create(Ember.ControllerMixin),
18200 Router: Ember.Router.extend({ ... })
18203 App.get('router.fooController'); // instance of App.FooController
18206 The controller singletons will have their `namespace` property set to the application and their `target`
18207 property set to the application's router singleton for easy integration with Ember's user event system.
18208 See 'Changing View Hierarchy in Response To State Change' and 'Responding to User-initiated Events.'
18210 ## Responding to User-initiated Events
18211 Controller instances injected into the router at application initialization have their `target` property
18212 set to the application's router instance. These controllers will also be the default `context` for their
18213 associated views. Uses of the `{{action}}` helper will automatically target the application's router.
18215 Given the following application entered at the URL '#/':
18218 App = Ember.Application.create({
18219 Router: Ember.Router.extend({
18220 root: Ember.Route.extend({
18221 aRoute: Ember.Route.extend({
18223 anActionOnTheRouter: function(router, context) {
18224 router.transitionTo('anotherState', context);
18227 anotherState: Ember.Route.extend({
18228 route: '/differentUrl',
18229 connectOutlets: function(router, context) {
18239 The following template:
18242 <script type="text/x-handlebars" data-template-name="aView">
18243 <h1><a {{action anActionOnTheRouter}}>{{title}}</a></h1>
18247 Will delegate `click` events on the rendered `h1` to the application's router instance. In this case the
18248 `anActionOnTheRouter` method of the state at 'root.aRoute' will be called with the view's controller
18249 as the context argument. This context will be passed to the `connectOutlets` as its second argument.
18251 Different `context` can be supplied from within the `{{action}}` helper, allowing specific context passing
18252 between application states:
18255 <script type="text/x-handlebars" data-template-name="photos">
18256 {{#each photo in controller}}
18257 <h1><a {{action showPhoto photo}}>{{title}}</a></h1>
18262 See `Handlebars.helpers.action` for additional usage examples.
18265 ## Changing View Hierarchy in Response To State Change
18267 Changes in application state that change the URL should be accompanied by associated changes in view
18268 hierarchy. This can be accomplished by calling 'connectOutlet' on the injected controller singletons from
18269 within the 'connectOutlets' event of an Ember.Route:
18272 App = Ember.Application.create({
18273 OneController: Ember.ObjectController.extend(),
18274 OneView: Ember.View.extend(),
18276 AnotherController: Ember.ObjectController.extend(),
18277 AnotherView: Ember.View.extend(),
18279 Router: Ember.Router.extend({
18280 root: Ember.Route.extend({
18281 aRoute: Ember.Route.extend({
18283 connectOutlets: function(router, context) {
18284 router.get('oneController').connectOutlet('another');
18294 This will detect the '{{outlet}}' portion of `oneController`'s view (an instance of `App.OneView`) and
18295 fill it with a rendered instance of `App.AnotherView` whose `context` will be the single instance of
18296 `App.AnotherController` stored on the router in the `anotherController` property.
18298 For more information about Outlets, see `Ember.Handlebars.helpers.outlet`. For additional information on
18299 the `connectOutlet` method, see `Ember.Controller.connectOutlet`. For more information on
18300 controller injections, see `Ember.Application#initialize()`. For additional information about view context,
18305 @extends Ember.StateManager
18307 Ember.Router = Ember.StateManager.extend(
18308 /** @scope Ember.Router.prototype */ {
18311 @property initialState
18315 initialState: 'root',
18318 The `Ember.Location` implementation to be used to manage the application
18319 URL state. The following values are supported:
18321 * 'hash': Uses URL fragment identifiers (like #/blog/1) for routing.
18322 * 'history': Uses the browser's history.pushstate API for routing. Only works in
18323 modern browsers with pushstate support.
18324 * 'none': Does not read or set the browser URL, but still allows for
18325 routing to happen. Useful for testing.
18334 This is only used when a history location is used so that applications that
18335 don't live at the root of the domain can append paths to their root.
18344 transitionTo: function() {
18345 this.abortRoutingPromises();
18346 this._super.apply(this, arguments);
18349 route: function(path) {
18350 this.abortRoutingPromises();
18352 set(this, 'isRouting', true);
18357 path = path.replace(get(this, 'rootURL'), '');
18358 path = path.replace(/^(?=[^\/])/, "/");
18360 this.send('navigateAway');
18361 this.send('unroutePath', path);
18363 routableState = get(this, 'currentState');
18364 while (routableState && !routableState.get('isRoutable')) {
18365 routableState = get(routableState, 'parentState');
18367 var currentURL = routableState ? routableState.absoluteRoute(this) : '';
18368 var rest = path.substr(currentURL.length);
18370 this.send('routePath', rest);
18372 set(this, 'isRouting', false);
18375 routableState = get(this, 'currentState');
18376 while (routableState && !routableState.get('isRoutable')) {
18377 routableState = get(routableState, 'parentState');
18380 if (routableState) {
18381 routableState.updateRoute(this, get(this, 'location'));
18385 urlFor: function(path, hash) {
18386 var currentState = get(this, 'currentState') || this,
18387 state = this.findStateByPath(currentState, path);
18389 Ember.assert(Ember.String.fmt("Could not find route with path '%@'", [path]), !!state);
18390 Ember.assert("To get a URL for a state, it must have a `route` property.", !!get(state, 'routeMatcher'));
18392 var location = get(this, 'location'),
18393 absoluteRoute = state.absoluteRoute(this, hash);
18395 return location.formatURL(absoluteRoute);
18398 urlForEvent: function(eventName) {
18399 var contexts = Array.prototype.slice.call(arguments, 1);
18400 var currentState = get(this, 'currentState');
18401 var targetStateName = currentState.lookupEventTransition(eventName);
18403 Ember.assert(Ember.String.fmt("You must specify a target state for event '%@' in order to link to it in the current state '%@'.", [eventName, get(currentState, 'path')]), !!targetStateName);
18405 var targetState = this.findStateByPath(currentState, targetStateName);
18407 Ember.assert("Your target state name " + targetStateName + " for event " + eventName + " did not resolve to a state", !!targetState);
18409 var hash = this.serializeRecursively(targetState, contexts, {});
18411 return this.urlFor(targetStateName, hash);
18414 serializeRecursively: function(state, contexts, hash) {
18416 context = get(state, 'hasContext') ? contexts.pop() : null;
18417 merge(hash, state.serialize(this, context));
18418 parentState = state.get("parentState");
18419 if (parentState && parentState instanceof Ember.Route) {
18420 return this.serializeRecursively(parentState, contexts, hash);
18426 abortRoutingPromises: function() {
18427 if (this._routingPromises) {
18428 this._routingPromises.abort();
18429 this._routingPromises = null;
18433 handleStatePromises: function(states, complete) {
18434 this.abortRoutingPromises();
18436 this.set('isLocked', true);
18438 var manager = this;
18440 this._routingPromises = Ember._PromiseChain.create({
18441 promises: states.slice(),
18443 successCallback: function() {
18444 manager.set('isLocked', false);
18448 failureCallback: function() {
18449 throw "Unable to load object";
18452 promiseSuccessCallback: function(item, args) {
18453 set(item, 'object', args[0]);
18456 abortCallback: function() {
18457 manager.set('isLocked', false);
18465 var location = get(this, 'location'),
18466 rootURL = get(this, 'rootURL');
18468 if ('string' === typeof location) {
18469 set(this, 'location', Ember.Location.create({
18470 implementation: location,
18475 this.assignRouter(this, this);
18478 assignRouter: function(state, router) {
18479 state.router = router;
18481 var childStates = state.states;
18484 for (var stateName in childStates) {
18485 if (!childStates.hasOwnProperty(stateName)) { continue; }
18486 this.assignRouter(childStates[stateName], router);
18491 willDestroy: function() {
18492 get(this, 'location').destroy();
18505 @submodule ember-routing
18506 @requires ember-states
18512 // ==========================================================================
18513 // Project: metamorph
18514 // Copyright: ©2011 My Company Inc. All rights reserved.
18515 // ==========================================================================
18517 (function(window) {
18519 var K = function(){},
18521 document = window.document,
18523 // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges
18524 supportsRange = ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
18526 // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
18527 // is a "zero-scope" element. This problem can be worked around by making
18528 // the first node an invisible text node. We, like Modernizr, use ­
18529 needsShy = (function(){
18530 var testEl = document.createElement('div');
18531 testEl.innerHTML = "<div></div>";
18532 testEl.firstChild.innerHTML = "<script></script>";
18533 return testEl.firstChild.innerHTML === '';
18536 // Constructor that supports either Metamorph('foo') or new
18537 // Metamorph('foo');
18539 // Takes a string of HTML as the argument.
18541 var Metamorph = function(html) {
18544 if (this instanceof Metamorph) {
18550 self.innerHTML = html;
18551 var myGuid = 'metamorph-'+(guid++);
18552 self.start = myGuid + '-start';
18553 self.end = myGuid + '-end';
18558 K.prototype = Metamorph.prototype;
18560 var rangeFor, htmlFunc, removeFunc, outerHTMLFunc, appendToFunc, afterFunc, prependFunc, startTagFunc, endTagFunc;
18562 outerHTMLFunc = function() {
18563 return this.startTag() + this.innerHTML + this.endTag();
18566 startTagFunc = function() {
18567 return "<script id='" + this.start + "' type='text/x-placeholder'></script>";
18570 endTagFunc = function() {
18571 return "<script id='" + this.end + "' type='text/x-placeholder'></script>";
18574 // If we have the W3C range API, this process is relatively straight forward.
18575 if (supportsRange) {
18577 // Get a range for the current morph. Optionally include the starting and
18578 // ending placeholders.
18579 rangeFor = function(morph, outerToo) {
18580 var range = document.createRange();
18581 var before = document.getElementById(morph.start);
18582 var after = document.getElementById(morph.end);
18585 range.setStartBefore(before);
18586 range.setEndAfter(after);
18588 range.setStartAfter(before);
18589 range.setEndBefore(after);
18595 htmlFunc = function(html, outerToo) {
18596 // get a range for the current metamorph object
18597 var range = rangeFor(this, outerToo);
18599 // delete the contents of the range, which will be the
18600 // nodes between the starting and ending placeholder.
18601 range.deleteContents();
18603 // create a new document fragment for the HTML
18604 var fragment = range.createContextualFragment(html);
18606 // insert the fragment into the range
18607 range.insertNode(fragment);
18610 removeFunc = function() {
18611 // get a range for the current metamorph object including
18612 // the starting and ending placeholders.
18613 var range = rangeFor(this, true);
18615 // delete the entire range.
18616 range.deleteContents();
18619 appendToFunc = function(node) {
18620 var range = document.createRange();
18621 range.setStart(node);
18622 range.collapse(false);
18623 var frag = range.createContextualFragment(this.outerHTML());
18624 node.appendChild(frag);
18627 afterFunc = function(html) {
18628 var range = document.createRange();
18629 var after = document.getElementById(this.end);
18631 range.setStartAfter(after);
18632 range.setEndAfter(after);
18634 var fragment = range.createContextualFragment(html);
18635 range.insertNode(fragment);
18638 prependFunc = function(html) {
18639 var range = document.createRange();
18640 var start = document.getElementById(this.start);
18642 range.setStartAfter(start);
18643 range.setEndAfter(start);
18645 var fragment = range.createContextualFragment(html);
18646 range.insertNode(fragment);
18651 * This code is mostly taken from jQuery, with one exception. In jQuery's case, we
18652 * have some HTML and we need to figure out how to convert it into some nodes.
18654 * In this case, jQuery needs to scan the HTML looking for an opening tag and use
18655 * that as the key for the wrap map. In our case, we know the parent node, and
18656 * can use its type as the key for the wrap map.
18659 select: [ 1, "<select multiple='multiple'>", "</select>" ],
18660 fieldset: [ 1, "<fieldset>", "</fieldset>" ],
18661 table: [ 1, "<table>", "</table>" ],
18662 tbody: [ 2, "<table><tbody>", "</tbody></table>" ],
18663 tr: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
18664 colgroup: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
18665 map: [ 1, "<map>", "</map>" ],
18666 _default: [ 0, "", "" ]
18670 * Given a parent node and some HTML, generate a set of nodes. Return the first
18671 * node, which will allow us to traverse the rest using nextSibling.
18673 * We need to do this because innerHTML in IE does not really parse the nodes.
18675 var firstNodeFor = function(parentNode, html) {
18676 var arr = wrapMap[parentNode.tagName.toLowerCase()] || wrapMap._default;
18677 var depth = arr[0], start = arr[1], end = arr[2];
18679 if (needsShy) { html = '­'+html; }
18681 var element = document.createElement('div');
18682 element.innerHTML = start + html + end;
18684 for (var i=0; i<=depth; i++) {
18685 element = element.firstChild;
18688 // Look for ­ to remove it.
18690 var shyElement = element;
18692 // Sometimes we get nameless elements with the shy inside
18693 while (shyElement.nodeType === 1 && !shyElement.nodeName) {
18694 shyElement = shyElement.firstChild;
18697 // At this point it's the actual unicode character.
18698 if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") {
18699 shyElement.nodeValue = shyElement.nodeValue.slice(1);
18707 * In some cases, Internet Explorer can create an anonymous node in
18708 * the hierarchy with no tagName. You can create this scenario via:
18710 * div = document.createElement("div");
18711 * div.innerHTML = "<table>­<script></script><tr><td>hi</td></tr></table>";
18712 * div.firstChild.firstChild.tagName //=> ""
18714 * If our script markers are inside such a node, we need to find that
18715 * node and use *it* as the marker.
18717 var realNode = function(start) {
18718 while (start.parentNode.tagName === "") {
18719 start = start.parentNode;
18726 * When automatically adding a tbody, Internet Explorer inserts the
18727 * tbody immediately before the first <tr>. Other browsers create it
18728 * before the first node, no matter what.
18730 * This means the the following code:
18732 * div = document.createElement("div");
18733 * div.innerHTML = "<table><script id='first'></script><tr><td>hi</td></tr><script id='last'></script></table>
18735 * Generates the following DOM in IE:
18739 * - script id='first'
18744 * - script id='last'
18746 * Which means that the two script tags, even though they were
18747 * inserted at the same point in the hierarchy in the original
18748 * HTML, now have different parents.
18750 * This code reparents the first script tag by making it the tbody's
18753 var fixParentage = function(start, end) {
18754 if (start.parentNode !== end.parentNode) {
18755 end.parentNode.insertBefore(start, end.parentNode.firstChild);
18759 htmlFunc = function(html, outerToo) {
18760 // get the real starting node. see realNode for details.
18761 var start = realNode(document.getElementById(this.start));
18762 var end = document.getElementById(this.end);
18763 var parentNode = end.parentNode;
18764 var node, nextSibling, last;
18766 // make sure that the start and end nodes share the same
18767 // parent. If not, fix it.
18768 fixParentage(start, end);
18770 // remove all of the nodes after the starting placeholder and
18771 // before the ending placeholder.
18772 node = start.nextSibling;
18774 nextSibling = node.nextSibling;
18775 last = node === end;
18777 // if this is the last node, and we want to remove it as well,
18778 // set the `end` node to the next sibling. This is because
18779 // for the rest of the function, we insert the new nodes
18780 // before the end (note that insertBefore(node, null) is
18781 // the same as appendChild(node)).
18783 // if we do not want to remove it, just break.
18785 if (outerToo) { end = node.nextSibling; } else { break; }
18788 node.parentNode.removeChild(node);
18790 // if this is the last node and we didn't break before
18791 // (because we wanted to remove the outer nodes), break
18793 if (last) { break; }
18795 node = nextSibling;
18798 // get the first node for the HTML string, even in cases like
18799 // tables and lists where a simple innerHTML on a div would
18800 // swallow some of the content.
18801 node = firstNodeFor(start.parentNode, html);
18803 // copy the nodes for the HTML between the starting and ending
18806 nextSibling = node.nextSibling;
18807 parentNode.insertBefore(node, end);
18808 node = nextSibling;
18812 // remove the nodes in the DOM representing this metamorph.
18814 // this includes the starting and ending placeholders.
18815 removeFunc = function() {
18816 var start = realNode(document.getElementById(this.start));
18817 var end = document.getElementById(this.end);
18820 start.parentNode.removeChild(start);
18821 end.parentNode.removeChild(end);
18824 appendToFunc = function(parentNode) {
18825 var node = firstNodeFor(parentNode, this.outerHTML());
18828 nextSibling = node.nextSibling;
18829 parentNode.appendChild(node);
18830 node = nextSibling;
18834 afterFunc = function(html) {
18835 // get the real starting node. see realNode for details.
18836 var end = document.getElementById(this.end);
18837 var insertBefore = end.nextSibling;
18838 var parentNode = end.parentNode;
18842 // get the first node for the HTML string, even in cases like
18843 // tables and lists where a simple innerHTML on a div would
18844 // swallow some of the content.
18845 node = firstNodeFor(parentNode, html);
18847 // copy the nodes for the HTML between the starting and ending
18850 nextSibling = node.nextSibling;
18851 parentNode.insertBefore(node, insertBefore);
18852 node = nextSibling;
18856 prependFunc = function(html) {
18857 var start = document.getElementById(this.start);
18858 var parentNode = start.parentNode;
18862 node = firstNodeFor(parentNode, html);
18863 var insertBefore = start.nextSibling;
18866 nextSibling = node.nextSibling;
18867 parentNode.insertBefore(node, insertBefore);
18868 node = nextSibling;
18873 Metamorph.prototype.html = function(html) {
18874 this.checkRemoved();
18875 if (html === undefined) { return this.innerHTML; }
18877 htmlFunc.call(this, html);
18879 this.innerHTML = html;
18882 Metamorph.prototype.replaceWith = function(html) {
18883 this.checkRemoved();
18884 htmlFunc.call(this, html, true);
18887 Metamorph.prototype.remove = removeFunc;
18888 Metamorph.prototype.outerHTML = outerHTMLFunc;
18889 Metamorph.prototype.appendTo = appendToFunc;
18890 Metamorph.prototype.after = afterFunc;
18891 Metamorph.prototype.prepend = prependFunc;
18892 Metamorph.prototype.startTag = startTagFunc;
18893 Metamorph.prototype.endTag = endTagFunc;
18895 Metamorph.prototype.isRemoved = function() {
18896 var before = document.getElementById(this.start);
18897 var after = document.getElementById(this.end);
18899 return !before || !after;
18902 Metamorph.prototype.checkRemoved = function() {
18903 if (this.isRemoved()) {
18904 throw new Error("Cannot perform operations on a Metamorph that is not in the DOM.");
18908 window.Metamorph = Metamorph;
18915 /*globals Handlebars */
18918 @submodule ember-handlebars
18921 var objectCreate = Ember.create;
18924 Ember.assert("Ember Handlebars requires Handlebars 1.0.beta.5 or greater", window.Handlebars && window.Handlebars.VERSION.match(/^1\.0\.beta\.[56789]$|^1\.0\.rc\.[123456789]+/));
18927 Prepares the Handlebars templating library for use inside Ember's view
18930 The Ember.Handlebars object is the standard Handlebars library, extended to use
18931 Ember's get() method instead of direct property access, which allows
18932 computed properties to be used inside templates.
18934 To create an Ember.Handlebars template, call Ember.Handlebars.compile(). This will
18935 return a function that can be used by Ember.View for rendering.
18940 Ember.Handlebars = objectCreate(Handlebars);
18944 @namespace Ember.Handlebars
18946 Ember.Handlebars.helpers = objectCreate(Handlebars.helpers);
18949 Override the the opcode compiler and JavaScript compiler for Handlebars.
18952 @namespace Ember.Handlebars
18956 Ember.Handlebars.Compiler = function() {};
18958 // Handlebars.Compiler doesn't exist in runtime-only
18959 if (Handlebars.Compiler) {
18960 Ember.Handlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
18963 Ember.Handlebars.Compiler.prototype.compiler = Ember.Handlebars.Compiler;
18966 @class JavaScriptCompiler
18967 @namespace Ember.Handlebars
18971 Ember.Handlebars.JavaScriptCompiler = function() {};
18973 // Handlebars.JavaScriptCompiler doesn't exist in runtime-only
18974 if (Handlebars.JavaScriptCompiler) {
18975 Ember.Handlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
18976 Ember.Handlebars.JavaScriptCompiler.prototype.compiler = Ember.Handlebars.JavaScriptCompiler;
18980 Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars";
18983 Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() {
18990 Override the default buffer for Ember Handlebars. By default, Handlebars creates
18991 an empty String at the beginning of each invocation and appends to it. Ember's
18992 Handlebars overrides this to append to a single shared buffer.
18994 @method appendToBuffer
18995 @param string {String}
18997 Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
18998 return "data.buffer.push("+string+");";
19004 Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that all simple
19005 mustaches in Ember's Handlebars will also set up an observer to keep the DOM
19006 up to date when the underlying property changes.
19009 @for Ember.Handlebars.Compiler
19012 Ember.Handlebars.Compiler.prototype.mustache = function(mustache) {
19013 if (mustache.params.length || mustache.hash) {
19014 return Handlebars.Compiler.prototype.mustache.call(this, mustache);
19016 var id = new Handlebars.AST.IdNode(['_triageMustache']);
19018 // Update the mustache node to include a hash value indicating whether the original node
19019 // was escaped. This will allow us to properly escape values when the underlying value
19020 // changes and we need to re-render the value.
19021 if(!mustache.escaped) {
19022 mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
19023 mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
19025 mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
19026 return Handlebars.Compiler.prototype.mustache.call(this, mustache);
19031 Used for precompilation of Ember Handlebars templates. This will not be used during normal
19035 @for Ember.Handlebars
19037 @param {String} string The template to precompile
19039 Ember.Handlebars.precompile = function(string) {
19040 var ast = Handlebars.parse(string);
19049 _triageMustache: true
19055 var environment = new Ember.Handlebars.Compiler().compile(ast, options);
19056 return new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
19059 // We don't support this for Handlebars runtime-only
19060 if (Handlebars.compile) {
19062 The entry point for Ember Handlebars. This replaces the default Handlebars.compile and turns on
19063 template-local data and String parameters.
19066 @for Ember.Handlebars
19068 @param {String} string The template to compile
19071 Ember.Handlebars.compile = function(string) {
19072 var ast = Handlebars.parse(string);
19073 var options = { data: true, stringParams: true };
19074 var environment = new Ember.Handlebars.Compiler().compile(ast, options);
19075 var templateSpec = new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
19077 return Handlebars.template(templateSpec);
19084 If a path starts with a reserved keyword, returns the root
19085 that should be used.
19087 @method normalizePath
19089 @param root {Object}
19090 @param path {String}
19093 var normalizePath = Ember.Handlebars.normalizePath = function(root, path, data) {
19094 var keywords = (data && data.keywords) || {},
19095 keyword, isKeyword;
19097 // Get the first segment of the path. For example, if the
19098 // path is "foo.bar.baz", returns "foo".
19099 keyword = path.split('.', 1)[0];
19101 // Test to see if the first path is a keyword that has been
19102 // passed along in the view's data hash. If so, we will treat
19103 // that object as the new root.
19104 if (keywords.hasOwnProperty(keyword)) {
19105 // Look up the value in the template's data hash.
19106 root = keywords[keyword];
19109 // Handle cases where the entire path is the reserved
19110 // word. In that case, return the object itself.
19111 if (path === keyword) {
19114 // Strip the keyword from the path and look up
19115 // the remainder from the newly found root.
19116 path = path.substr(keyword.length+1);
19120 return { root: root, path: path, isKeyword: isKeyword };
19125 Lookup both on root and on window. If the path starts with
19126 a keyword, the corresponding object will be looked up in the
19127 template's data hash and used to resolve the path.
19130 @for Ember.Handlebars
19131 @param {Object} root The object to look up the property on
19132 @param {String} path The path to be lookedup
19133 @param {Object} options The template's option hash
19135 Ember.Handlebars.getPath = function(root, path, options) {
19136 var data = options && options.data,
19137 normalizedPath = normalizePath(root, path, data),
19140 // In cases where the path begins with a keyword, change the
19141 // root to the value represented by that keyword, and ensure
19142 // the path is relative to it.
19143 root = normalizedPath.root;
19144 path = normalizedPath.path;
19146 value = Ember.get(root, path);
19148 if (value === undefined && root !== window && Ember.isGlobalPath(path)) {
19149 value = Ember.get(window, path);
19157 Registers a helper in Handlebars that will be called if no property with the
19158 given name can be found on the current context object, and no helper with
19159 that name is registered.
19161 This throws an exception with a more helpful error message so the user can
19162 track down where the problem is happening.
19164 @method helperMissing
19165 @for Ember.Handlebars.helpers
19166 @param {String} path
19167 @param {Hash} options
19169 Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
19170 var error, view = "";
19172 error = "%@ Handlebars error: Could not find property '%@' on object %@.";
19174 view = options.data.view;
19176 throw new Ember.Error(Ember.String.fmt(error, [view, path, this]));
19190 Ember.String.htmlSafe = function(str) {
19191 return new Handlebars.SafeString(str);
19194 var htmlSafe = Ember.String.htmlSafe;
19196 if (Ember.EXTEND_PROTOTYPES) {
19199 See {{#crossLink "Ember.String/htmlSafe"}}{{/crossLink}}
19204 String.prototype.htmlSafe = function() {
19205 return htmlSafe(this);
19215 /*jshint newcap:false*/
19218 @submodule ember-handlebars
19221 var set = Ember.set, get = Ember.get;
19223 // DOMManager should just abstract dom manipulation between jquery and metamorph
19225 remove: function(view) {
19226 view.morph.remove();
19229 prepend: function(view, html) {
19230 view.morph.prepend(html);
19233 after: function(view, html) {
19234 view.morph.after(html);
19237 html: function(view, html) {
19238 view.morph.html(html);
19241 // This is messed up.
19242 replace: function(view) {
19243 var morph = view.morph;
19245 view.transitionTo('preRender');
19246 view.clearRenderedChildren();
19247 var buffer = view.renderToBuffer();
19249 Ember.run.schedule('render', this, function() {
19250 if (get(view, 'isDestroyed')) { return; }
19251 view.invalidateRecursively('element');
19252 view._notifyWillInsertElement();
19253 morph.replaceWith(buffer.string());
19254 view.transitionTo('inDOM');
19255 view._notifyDidInsertElement();
19259 empty: function(view) {
19260 view.morph.html("");
19264 // The `morph` and `outerHTML` properties are internal only
19265 // and not observable.
19270 @extends Ember.Mixin
19273 Ember._Metamorph = Ember.Mixin.create({
19279 this.morph = Metamorph();
19282 beforeRender: function(buffer) {
19283 buffer.push(this.morph.startTag());
19286 afterRender: function(buffer) {
19287 buffer.push(this.morph.endTag());
19290 createElement: function() {
19291 var buffer = this.renderToBuffer();
19292 this.outerHTML = buffer.string();
19293 this.clearBuffer();
19296 domManager: DOMManager
19300 @class _MetamorphView
19302 @extends Ember.View
19303 @uses Ember._Metamorph
19306 Ember._MetamorphView = Ember.View.extend(Ember._Metamorph);
19314 /*globals Handlebars */
19318 @submodule ember-handlebars
19321 var get = Ember.get, set = Ember.set, getPath = Ember.Handlebars.getPath;
19323 Ember._HandlebarsBoundView is a private view created by the Handlebars `{{bind}}`
19324 helpers that is used to keep track of bound properties.
19326 Every time a property is bound using a `{{mustache}}`, an anonymous subclass
19327 of Ember._HandlebarsBoundView is created with the appropriate sub-template and
19328 context set up. When the associated property changes, just the template for
19329 this view will re-render.
19331 @class _HandlebarsBoundView
19333 @extends Ember._MetamorphView
19336 Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
19339 The function used to determine if the `displayTemplate` or
19340 `inverseTemplate` should be rendered. This should be a function that takes
19341 a value and returns a Boolean.
19343 @property shouldDisplayFunc
19347 shouldDisplayFunc: null,
19350 Whether the template rendered by this view gets passed the context object
19351 of its parent template, or gets passed the value of retrieving `path`
19352 from the `pathRoot`.
19354 For example, this is true when using the `{{#if}}` helper, because the
19355 template inside the helper should look up properties relative to the same
19356 object as outside the block. This would be false when used with `{{#with
19357 foo}}` because the template should receive the object found by evaluating
19360 @property preserveContext
19364 preserveContext: false,
19367 If `preserveContext` is true, this is the object that will be used
19368 to render the template.
19370 @property previousContext
19373 previousContext: null,
19376 The template to render when `shouldDisplayFunc` evaluates to true.
19378 @property displayTemplate
19382 displayTemplate: null,
19385 The template to render when `shouldDisplayFunc` evaluates to false.
19387 @property inverseTemplate
19391 inverseTemplate: null,
19395 The path to look up on `pathRoot` that is passed to
19396 `shouldDisplayFunc` to determine which template to render.
19398 In addition, if `preserveContext` is false, the object at this path will
19399 be passed to the template when rendering.
19408 The object from which the `path` will be looked up. Sometimes this is the
19409 same as the `previousContext`, but in cases where this view has been generated
19410 for paths that start with a keyword such as `view` or `controller`, the
19411 path root will be that resolved object.
19418 normalizedValue: Ember.computed(function() {
19419 var path = get(this, 'path'),
19420 pathRoot = get(this, 'pathRoot'),
19421 valueNormalizer = get(this, 'valueNormalizerFunc'),
19422 result, templateData;
19424 // Use the pathRoot as the result if no path is provided. This
19425 // happens if the path is `this`, which gets normalized into
19426 // a `pathRoot` of the current Handlebars context and a path
19431 templateData = get(this, 'templateData');
19432 result = getPath(pathRoot, path, { data: templateData });
19435 return valueNormalizer ? valueNormalizer(result) : result;
19436 }).property('path', 'pathRoot', 'valueNormalizerFunc').volatile(),
19438 rerenderIfNeeded: function() {
19439 if (!get(this, 'isDestroyed') && get(this, 'normalizedValue') !== this._lastNormalizedValue) {
19445 Determines which template to invoke, sets up the correct state based on
19446 that logic, then invokes the default Ember.View `render` implementation.
19448 This method will first look up the `path` key on `pathRoot`,
19449 then pass that value to the `shouldDisplayFunc` function. If that returns
19450 true, the `displayTemplate` function will be rendered to DOM. Otherwise,
19451 `inverseTemplate`, if specified, will be rendered.
19453 For example, if this Ember._HandlebarsBoundView represented the `{{#with foo}}`
19454 helper, it would look up the `foo` property of its context, and
19455 `shouldDisplayFunc` would always return true. The object found by looking
19456 up `foo` would be passed to `displayTemplate`.
19459 @param {Ember.RenderBuffer} buffer
19461 render: function(buffer) {
19462 // If not invoked via a triple-mustache ({{{foo}}}), escape
19463 // the content of the template.
19464 var escape = get(this, 'isEscaped');
19466 var shouldDisplay = get(this, 'shouldDisplayFunc'),
19467 preserveContext = get(this, 'preserveContext'),
19468 context = get(this, 'previousContext');
19470 var inverseTemplate = get(this, 'inverseTemplate'),
19471 displayTemplate = get(this, 'displayTemplate');
19473 var result = get(this, 'normalizedValue');
19474 this._lastNormalizedValue = result;
19476 // First, test the conditional to see if we should
19477 // render the template or not.
19478 if (shouldDisplay(result)) {
19479 set(this, 'template', displayTemplate);
19481 // If we are preserving the context (for example, if this
19482 // is an #if block, call the template with the same object.
19483 if (preserveContext) {
19484 set(this, '_context', context);
19486 // Otherwise, determine if this is a block bind or not.
19487 // If so, pass the specified object to the template
19488 if (displayTemplate) {
19489 set(this, '_context', result);
19491 // This is not a bind block, just push the result of the
19492 // expression to the render context and return.
19493 if (result === null || result === undefined) {
19495 } else if (!(result instanceof Handlebars.SafeString)) {
19496 result = String(result);
19499 if (escape) { result = Handlebars.Utils.escapeExpression(result); }
19500 buffer.push(result);
19504 } else if (inverseTemplate) {
19505 set(this, 'template', inverseTemplate);
19507 if (preserveContext) {
19508 set(this, '_context', context);
19510 set(this, '_context', result);
19513 set(this, 'template', function() { return ''; });
19516 return this._super(buffer);
19527 @submodule ember-handlebars
19530 var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
19531 var getPath = Ember.Handlebars.getPath, normalizePath = Ember.Handlebars.normalizePath;
19532 var forEach = Ember.ArrayPolyfills.forEach;
19534 var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers;
19536 // Binds a property into the DOM. This will create a hook in DOM that the
19537 // KVO system will look for and update if the property changes.
19538 function bind(property, options, preserveContext, shouldDisplay, valueNormalizer) {
19539 var data = options.data,
19541 inverse = options.inverse,
19543 currentContext = this,
19544 pathRoot, path, normalized;
19546 normalized = normalizePath(currentContext, property, data);
19548 pathRoot = normalized.root;
19549 path = normalized.path;
19551 // Set up observers for observable objects
19552 if ('object' === typeof this) {
19553 // Create the view that will wrap the output of this template/property
19554 // and add it to the nearest view's childViews array.
19555 // See the documentation of Ember._HandlebarsBoundView for more.
19556 var bindView = view.createChildView(Ember._HandlebarsBoundView, {
19557 preserveContext: preserveContext,
19558 shouldDisplayFunc: shouldDisplay,
19559 valueNormalizerFunc: valueNormalizer,
19560 displayTemplate: fn,
19561 inverseTemplate: inverse,
19563 pathRoot: pathRoot,
19564 previousContext: currentContext,
19565 isEscaped: !options.hash.unescaped,
19566 templateData: options.data
19569 view.appendChild(bindView);
19571 var observer = function() {
19572 Ember.run.scheduleOnce('render', bindView, 'rerenderIfNeeded');
19575 // Observes the given property on the context and
19576 // tells the Ember._HandlebarsBoundView to re-render. If property
19577 // is an empty string, we are printing the current context
19578 // object ({{this}}) so updating it is not our responsibility.
19580 Ember.addObserver(pathRoot, path, observer);
19583 // The object is not observable, so just render it out and
19584 // be done with it.
19585 data.buffer.push(getPath(pathRoot, path, options));
19592 '_triageMustache' is used internally select between a binding and helper for
19593 the given context. Until this point, it would be hard to determine if the
19594 mustache is a property reference or a regular helper reference. This triage
19595 helper resolves that.
19597 This would not be typically invoked by directly.
19599 @method _triageMustache
19600 @for Ember.Handlebars.helpers
19601 @param {String} property Property/helperID to triage
19602 @param {Function} fn Context to provide for rendering
19603 @return {String} HTML string
19605 EmberHandlebars.registerHelper('_triageMustache', function(property, fn) {
19606 Ember.assert("You cannot pass more than one argument to the _triageMustache helper", arguments.length <= 2);
19607 if (helpers[property]) {
19608 return helpers[property].call(this, fn);
19611 return helpers.bind.apply(this, arguments);
19618 `bind` can be used to display a value, then update that value if it
19619 changes. For example, if you wanted to print the `title` property of
19623 {{bind "content.title"}}
19626 This will return the `title` property as a string, then create a new
19627 observer at the specified path. If it changes, it will update the value in
19628 DOM. Note that if you need to support IE7 and IE8 you must modify the
19629 model objects properties using Ember.get() and Ember.set() for this to work as
19630 it relies on Ember's KVO system. For all other browsers this will be handled
19631 for you automatically.
19634 @for Ember.Handlebars.helpers
19635 @param {String} property Property to bind
19636 @param {Function} fn Context to provide for rendering
19637 @return {String} HTML string
19639 EmberHandlebars.registerHelper('bind', function(property, fn) {
19640 Ember.assert("You cannot pass more than one argument to the bind helper", arguments.length <= 2);
19642 var context = (fn.contexts && fn.contexts[0]) || this;
19644 return bind.call(context, property, fn, false, function(result) {
19645 return !Ember.none(result);
19652 Use the `boundIf` helper to create a conditional that re-evaluates
19653 whenever the bound value changes.
19656 {{#boundIf "content.shouldDisplayTitle"}}
19662 @for Ember.Handlebars.helpers
19663 @param {String} property Property to bind
19664 @param {Function} fn Context to provide for rendering
19665 @return {String} HTML string
19667 EmberHandlebars.registerHelper('boundIf', function(property, fn) {
19668 var context = (fn.contexts && fn.contexts[0]) || this;
19669 var func = function(result) {
19670 if (Ember.typeOf(result) === 'array') {
19671 return get(result, 'length') !== 0;
19677 return bind.call(context, property, fn, true, func, func);
19682 @for Ember.Handlebars.helpers
19683 @param {Function} context
19684 @param {Hash} options
19685 @return {String} HTML string
19687 EmberHandlebars.registerHelper('with', function(context, options) {
19688 if (arguments.length === 4) {
19689 var keywordName, path, rootPath, normalized;
19691 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");
19692 options = arguments[3];
19693 keywordName = arguments[2];
19694 path = arguments[0];
19696 Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
19698 if (Ember.isGlobalPath(path)) {
19699 Ember.bind(options.data.keywords, keywordName, path);
19701 normalized = normalizePath(this, path, options.data);
19702 path = normalized.path;
19703 rootPath = normalized.root;
19705 // This is a workaround for the fact that you cannot bind separate objects
19706 // together. When we implement that functionality, we should use it here.
19707 var contextKey = Ember.$.expando + Ember.guidFor(rootPath);
19708 options.data.keywords[contextKey] = rootPath;
19710 // if the path is '' ("this"), just bind directly to the current context
19711 var contextPath = path ? contextKey + '.' + path : contextKey;
19712 Ember.bind(options.data.keywords, keywordName, contextPath);
19715 return bind.call(this, path, options, true, function(result) {
19716 return !Ember.none(result);
19719 Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2);
19720 Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
19721 return helpers.bind.call(options.contexts[0], context, options);
19728 @for Ember.Handlebars.helpers
19729 @param {Function} context
19730 @param {Hash} options
19731 @return {String} HTML string
19733 EmberHandlebars.registerHelper('if', function(context, options) {
19734 Ember.assert("You must pass exactly one argument to the if helper", arguments.length === 2);
19735 Ember.assert("You must pass a block to the if helper", options.fn && options.fn !== Handlebars.VM.noop);
19737 return helpers.boundIf.call(options.contexts[0], context, options);
19742 @for Ember.Handlebars.helpers
19743 @param {Function} context
19744 @param {Hash} options
19745 @return {String} HTML string
19747 EmberHandlebars.registerHelper('unless', function(context, options) {
19748 Ember.assert("You must pass exactly one argument to the unless helper", arguments.length === 2);
19749 Ember.assert("You must pass a block to the unless helper", options.fn && options.fn !== Handlebars.VM.noop);
19751 var fn = options.fn, inverse = options.inverse;
19753 options.fn = inverse;
19754 options.inverse = fn;
19756 return helpers.boundIf.call(options.contexts[0], context, options);
19760 `bindAttr` allows you to create a binding between DOM element attributes and
19761 Ember objects. For example:
19764 <img {{bindAttr src="imageUrl" alt="imageTitle"}}>
19768 @for Ember.Handlebars.helpers
19769 @param {Hash} options
19770 @return {String} HTML string
19772 EmberHandlebars.registerHelper('bindAttr', function(options) {
19774 var attrs = options.hash;
19776 Ember.assert("You must specify at least one hash argument to bindAttr", !!Ember.keys(attrs).length);
19778 var view = options.data.view;
19782 // Generate a unique id for this element. This will be added as a
19783 // data attribute to the element so it can be looked up when
19784 // the bound property changes.
19785 var dataId = ++Ember.$.uuid;
19787 // Handle classes differently, as we can bind multiple classes
19788 var classBindings = attrs['class'];
19789 if (classBindings !== null && classBindings !== undefined) {
19790 var classResults = EmberHandlebars.bindClasses(this, classBindings, view, dataId, options);
19791 ret.push('class="' + Handlebars.Utils.escapeExpression(classResults.join(' ')) + '"');
19792 delete attrs['class'];
19795 var attrKeys = Ember.keys(attrs);
19797 // For each attribute passed, create an observer and emit the
19798 // current value of the property as an attribute.
19799 forEach.call(attrKeys, function(attr) {
19800 var path = attrs[attr],
19801 pathRoot, normalized;
19803 Ember.assert(fmt("You must provide a String for a bound attribute, not %@", [path]), typeof path === 'string');
19805 normalized = normalizePath(ctx, path, options.data);
19807 pathRoot = normalized.root;
19808 path = normalized.path;
19810 var value = (path === 'this') ? pathRoot : getPath(pathRoot, path, options),
19811 type = Ember.typeOf(value);
19813 Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [value]), value === null || value === undefined || type === 'number' || type === 'string' || type === 'boolean');
19815 var observer, invoker;
19817 observer = function observer() {
19818 var result = getPath(pathRoot, path, options);
19820 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');
19822 var elem = view.$("[data-bindattr-" + dataId + "='" + dataId + "']");
19824 // If we aren't able to find the element, it means the element
19825 // to which we were bound has been removed from the view.
19826 // In that case, we can assume the template has been re-rendered
19827 // and we need to clean up the observer.
19828 if (!elem || elem.length === 0) {
19829 Ember.removeObserver(pathRoot, path, invoker);
19833 Ember.View.applyAttributeBindings(elem, attr, result);
19836 invoker = function() {
19837 Ember.run.scheduleOnce('render', observer);
19840 // Add an observer to the view for when the property changes.
19841 // When the observer fires, find the element using the
19842 // unique data id and update the attribute to the new value.
19843 if (path !== 'this') {
19844 Ember.addObserver(pathRoot, path, invoker);
19847 // if this changes, also change the logic in ember-views/lib/views/view.js
19848 if ((type === 'string' || (type === 'number' && !isNaN(value)))) {
19849 ret.push(attr + '="' + Handlebars.Utils.escapeExpression(value) + '"');
19850 } else if (value && type === 'boolean') {
19851 // The developer controls the attr name, so it should always be safe
19852 ret.push(attr + '="' + attr + '"');
19856 // Add the unique identifier
19857 // NOTE: We use all lower-case since Firefox has problems with mixed case in SVG
19858 ret.push('data-bindattr-' + dataId + '="' + dataId + '"');
19859 return new EmberHandlebars.SafeString(ret.join(' '));
19865 Helper that, given a space-separated string of property paths and a context,
19866 returns an array of class names. Calling this method also has the side
19867 effect of setting up observers at those property paths, such that if they
19868 change, the correct class name will be reapplied to the DOM element.
19870 For example, if you pass the string "fooBar", it will first look up the
19871 "fooBar" value of the context. If that value is true, it will add the
19872 "foo-bar" class to the current element (i.e., the dasherized form of
19873 "fooBar"). If the value is a string, it will add that string as the class.
19874 Otherwise, it will not add any new class name.
19876 @method bindClasses
19877 @for Ember.Handlebars
19878 @param {Ember.Object} context The context from which to lookup properties
19879 @param {String} classBindings A string, space-separated, of class bindings to use
19880 @param {Ember.View} view The view in which observers should look for the element to update
19881 @param {Srting} bindAttrId Optional bindAttr id used to lookup elements
19882 @return {Array} An array of class names to add
19884 EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, options) {
19885 var ret = [], newClass, value, elem;
19887 // Helper method to retrieve the property from the context and
19888 // determine which class string to return, based on whether it is
19889 // a Boolean or not.
19890 var classStringForPath = function(root, parsedPath, options) {
19892 path = parsedPath.path;
19894 if (path === 'this') {
19896 } else if (path === '') {
19899 val = getPath(root, path, options);
19902 return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
19905 // For each property passed, loop through and setup
19907 forEach.call(classBindings.split(' '), function(binding) {
19909 // Variable in which the old class value is saved. The observer function
19910 // closes over this variable, so it knows which string to remove when
19911 // the property changes.
19914 var observer, invoker;
19916 var parsedPath = Ember.View._parsePropertyPath(binding),
19917 path = parsedPath.path,
19918 pathRoot = context,
19921 if (path !== '' && path !== 'this') {
19922 normalized = normalizePath(context, path, options.data);
19924 pathRoot = normalized.root;
19925 path = normalized.path;
19928 // Set up an observer on the context. If the property changes, toggle the
19930 observer = function() {
19931 // Get the current value of the property
19932 newClass = classStringForPath(pathRoot, parsedPath, options);
19933 elem = bindAttrId ? view.$("[data-bindattr-" + bindAttrId + "='" + bindAttrId + "']") : view.$();
19935 // If we can't find the element anymore, a parent template has been
19936 // re-rendered and we've been nuked. Remove the observer.
19937 if (!elem || elem.length === 0) {
19938 Ember.removeObserver(pathRoot, path, invoker);
19940 // If we had previously added a class to the element, remove it.
19942 elem.removeClass(oldClass);
19945 // If necessary, add a new class. Make sure we keep track of it so
19946 // it can be removed in the future.
19948 elem.addClass(newClass);
19949 oldClass = newClass;
19956 invoker = function() {
19957 Ember.run.scheduleOnce('render', observer);
19960 if (path !== '' && path !== 'this') {
19961 Ember.addObserver(pathRoot, path, invoker);
19964 // We've already setup the observer; now we just need to figure out the
19965 // correct behavior right now on the first pass through.
19966 value = classStringForPath(pathRoot, parsedPath, options);
19971 // Make sure we save the current value so that it can be removed if the
19986 /*globals Handlebars */
19988 // TODO: Don't require the entire module
19991 @submodule ember-handlebars
19994 var get = Ember.get, set = Ember.set;
19995 var PARENT_VIEW_PATH = /^parentView\./;
19996 var EmberHandlebars = Ember.Handlebars;
19997 var VIEW_PRESERVES_CONTEXT = Ember.VIEW_PRESERVES_CONTEXT;
19999 EmberHandlebars.ViewHelper = Ember.Object.create({
20001 propertiesFromHTMLOptions: function(options, thisContext) {
20002 var hash = options.hash, data = options.data;
20003 var extensions = {},
20004 classes = hash['class'],
20008 extensions.elementId = hash.id;
20013 classes = classes.split(' ');
20014 extensions.classNames = classes;
20018 if (hash.classBinding) {
20019 extensions.classNameBindings = hash.classBinding.split(' ');
20023 if (hash.classNameBindings) {
20024 if (extensions.classNameBindings === undefined) extensions.classNameBindings = [];
20025 extensions.classNameBindings = extensions.classNameBindings.concat(hash.classNameBindings.split(' '));
20029 if (hash.attributeBindings) {
20030 Ember.assert("Setting 'attributeBindings' via Handlebars is not allowed. Please subclass Ember.View and set it there instead.");
20031 extensions.attributeBindings = null;
20036 hash = Ember.$.extend({}, hash);
20038 delete hash['class'];
20039 delete hash.classBinding;
20042 // Set the proper context for all bindings passed to the helper. This applies to regular attribute bindings
20043 // as well as class name bindings. If the bindings are local, make them relative to the current context
20044 // instead of the view.
20047 // Evaluate the context of regular attribute bindings:
20048 for (var prop in hash) {
20049 if (!hash.hasOwnProperty(prop)) { continue; }
20051 // Test if the property ends in "Binding"
20052 if (Ember.IS_BINDING.test(prop) && typeof hash[prop] === 'string') {
20053 path = this.contextualizeBindingPath(hash[prop], data);
20054 if (path) { hash[prop] = path; }
20058 // Evaluate the context of class name bindings:
20059 if (extensions.classNameBindings) {
20060 for (var b in extensions.classNameBindings) {
20061 var full = extensions.classNameBindings[b];
20062 if (typeof full === 'string') {
20063 // Contextualize the path of classNameBinding so this:
20065 // classNameBinding="isGreen:green"
20067 // is converted to this:
20069 // classNameBinding="bindingContext.isGreen:green"
20070 var parsedPath = Ember.View._parsePropertyPath(full);
20071 path = this.contextualizeBindingPath(parsedPath.path, data);
20072 if (path) { extensions.classNameBindings[b] = path + parsedPath.classNames; }
20077 // Make the current template context available to the view
20078 // for the bindings set up above.
20079 extensions.bindingContext = thisContext;
20081 return Ember.$.extend(hash, extensions);
20084 // Transform bindings from the current context to a context that can be evaluated within the view.
20085 // Returns null if the path shouldn't be changed.
20087 // TODO: consider the addition of a prefix that would allow this method to return `path`.
20088 contextualizeBindingPath: function(path, data) {
20089 var normalized = Ember.Handlebars.normalizePath(null, path, data);
20090 if (normalized.isKeyword) {
20091 return 'templateData.keywords.' + path;
20092 } else if (Ember.isGlobalPath(path)) {
20094 } else if (path === 'this') {
20095 return 'bindingContext';
20097 return 'bindingContext.' + path;
20101 helper: function(thisContext, path, options) {
20102 var inverse = options.inverse,
20103 data = options.data,
20106 hash = options.hash,
20109 if ('string' === typeof path) {
20110 newView = EmberHandlebars.getPath(thisContext, path, options);
20111 Ember.assert("Unable to find view at path '" + path + "'", !!newView);
20116 Ember.assert(Ember.String.fmt('You must pass a view class to the #view helper, not %@ (%@)', [path, newView]), Ember.View.detect(newView));
20118 var viewOptions = this.propertiesFromHTMLOptions(options, thisContext);
20119 var currentView = data.view;
20120 viewOptions.templateData = options.data;
20123 Ember.assert("You cannot provide a template block if you also specified a templateName", !get(viewOptions, 'templateName') && !get(newView.proto(), 'templateName'));
20124 viewOptions.template = fn;
20127 // We only want to override the `_context` computed property if there is
20128 // no specified controller. See View#_context for more information.
20129 if (VIEW_PRESERVES_CONTEXT && !newView.proto().controller && !newView.proto().controllerBinding && !viewOptions.controller && !viewOptions.controllerBinding) {
20130 viewOptions._context = thisContext;
20133 currentView.appendChild(newView, viewOptions);
20138 `{{view}}` inserts a new instance of `Ember.View` into a template passing its options
20139 to the `Ember.View`'s `create` method and using the supplied block as the view's own template.
20141 An empty `<body>` and the following template:
20144 <script type="text/x-handlebars">
20146 {{#view tagName="span"}}
20152 Will result in HTML structure:
20156 <!-- Note: the handlebars template script
20157 also results in a rendered Ember.View
20158 which is the outer <div> here -->
20160 <div class="ember-view">
20162 <span id="ember1" class="ember-view">
20169 ### parentView setting
20171 The `parentView` property of the new `Ember.View` instance created through `{{view}}`
20172 will be set to the `Ember.View` instance of the template where `{{view}}` was called.
20175 aView = Ember.View.create({
20176 template: Ember.Handlebars.compile("{{#view}} my parent: {{parentView.elementId}} {{/view}}")
20179 aView.appendTo('body');
20182 Will result in HTML structure:
20185 <div id="ember1" class="ember-view">
20186 <div id="ember2" class="ember-view">
20192 ### Setting CSS id and class attributes
20194 The HTML `id` attribute can be set on the `{{view}}`'s resulting element with the `id` option.
20195 This option will _not_ be passed to `Ember.View.create`.
20198 <script type="text/x-handlebars">
20199 {{#view tagName="span" id="a-custom-id"}}
20205 Results in the following HTML structure:
20208 <div class="ember-view">
20209 <span id="a-custom-id" class="ember-view">
20215 The HTML `class` attribute can be set on the `{{view}}`'s resulting element with
20216 the `class` or `classNameBindings` options. The `class` option
20217 will directly set the CSS `class` attribute and will not be passed to
20218 `Ember.View.create`. `classNameBindings` will be passed to `create` and use
20219 `Ember.View`'s class name binding functionality:
20222 <script type="text/x-handlebars">
20223 {{#view tagName="span" class="a-custom-class"}}
20229 Results in the following HTML structure:
20232 <div class="ember-view">
20233 <span id="ember2" class="ember-view a-custom-class">
20239 ### Supplying a different view class
20241 `{{view}}` can take an optional first argument before its supplied options to specify a
20242 path to a custom view class.
20245 <script type="text/x-handlebars">
20246 {{#view "MyApp.CustomView"}}
20252 The first argument can also be a relative path. Ember will search for the view class
20253 starting at the `Ember.View` of the template where `{{view}}` was used as the root object:
20256 MyApp = Ember.Application.create({});
20257 MyApp.OuterView = Ember.View.extend({
20258 innerViewClass: Ember.View.extend({
20259 classNames: ['a-custom-view-class-as-property']
20261 template: Ember.Handlebars.compile('{{#view "innerViewClass"}} hi {{/view}}')
20264 MyApp.OuterView.create().appendTo('body');
20267 Will result in the following HTML:
20270 <div id="ember1" class="ember-view">
20271 <div id="ember2" class="ember-view a-custom-view-class-as-property">
20279 If you supply a custom `Ember.View` subclass that specifies its own template
20280 or provide a `templateName` option to `{{view}}` it can be used without supplying a block.
20281 Attempts to use both a `templateName` option and supply a block will throw an error.
20284 <script type="text/x-handlebars">
20285 {{view "MyApp.ViewWithATemplateDefined"}}
20289 ### viewName property
20291 You can supply a `viewName` option to `{{view}}`. The `Ember.View` instance will
20292 be referenced as a property of its parent view by this name.
20295 aView = Ember.View.create({
20296 template: Ember.Handlebars.compile('{{#view viewName="aChildByName"}} hi {{/view}}')
20299 aView.appendTo('body');
20300 aView.get('aChildByName') // the instance of Ember.View created by {{view}} helper
20304 @for Ember.Handlebars.helpers
20305 @param {String} path
20306 @param {Hash} options
20307 @return {String} HTML string
20309 EmberHandlebars.registerHelper('view', function(path, options) {
20310 Ember.assert("The view helper only takes a single argument", arguments.length <= 2);
20312 // If no path is provided, treat path param as options.
20313 if (path && path.data && path.data.isRenderData) {
20315 path = "Ember.View";
20318 return EmberHandlebars.ViewHelper.helper(this, path, options);
20327 /*globals Handlebars */
20329 // TODO: Don't require all of this module
20332 @submodule ember-handlebars
20335 var get = Ember.get, getPath = Ember.Handlebars.getPath, fmt = Ember.String.fmt;
20338 `{{collection}}` is a `Ember.Handlebars` helper for adding instances of
20339 `Ember.CollectionView` to a template. See `Ember.CollectionView` for additional
20340 information on how a `CollectionView` functions.
20342 `{{collection}}`'s primary use is as a block helper with a `contentBinding` option
20343 pointing towards an `Ember.Array`-compatible object. An `Ember.View` instance will
20344 be created for each item in its `content` property. Each view will have its own
20345 `content` property set to the appropriate item in the collection.
20347 The provided block will be applied as the template for each item's view.
20349 Given an empty `<body>` the following template:
20352 <script type="text/x-handlebars">
20353 {{#collection contentBinding="App.items"}}
20354 Hi {{view.content.name}}
20359 And the following application code
20362 App = Ember.Application.create()
20364 Ember.Object.create({name: 'Dave'}),
20365 Ember.Object.create({name: 'Mary'}),
20366 Ember.Object.create({name: 'Sara'})
20370 Will result in the HTML structure below
20373 <div class="ember-view">
20374 <div class="ember-view">Hi Dave</div>
20375 <div class="ember-view">Hi Mary</div>
20376 <div class="ember-view">Hi Sara</div>
20381 If you provide an `itemViewClass` option that has its own `template` you can omit
20384 The following template:
20387 <script type="text/x-handlebars">
20388 {{collection contentBinding="App.items" itemViewClass="App.AnItemView"}}
20392 And application code
20395 App = Ember.Application.create();
20397 Ember.Object.create({name: 'Dave'}),
20398 Ember.Object.create({name: 'Mary'}),
20399 Ember.Object.create({name: 'Sara'})
20402 App.AnItemView = Ember.View.extend({
20403 template: Ember.Handlebars.compile("Greetings {{view.content.name}}")
20407 Will result in the HTML structure below
20410 <div class="ember-view">
20411 <div class="ember-view">Greetings Dave</div>
20412 <div class="ember-view">Greetings Mary</div>
20413 <div class="ember-view">Greetings Sara</div>
20417 ### Specifying a CollectionView subclass
20419 By default the `{{collection}}` helper will create an instance of `Ember.CollectionView`.
20420 You can supply a `Ember.CollectionView` subclass to the helper by passing it
20421 as the first argument:
20424 <script type="text/x-handlebars">
20425 {{#collection App.MyCustomCollectionClass contentBinding="App.items"}}
20426 Hi {{view.content.name}}
20432 ### Forwarded `item.*`-named Options
20434 As with the `{{view}}`, helper options passed to the `{{collection}}` will be set on
20435 the resulting `Ember.CollectionView` as properties. Additionally, options prefixed with
20436 `item` will be applied to the views rendered for each item (note the camelcasing):
20439 <script type="text/x-handlebars">
20440 {{#collection contentBinding="App.items"
20442 itemClassNames="greeting"}}
20443 Howdy {{view.content.name}}
20448 Will result in the following HTML structure:
20451 <div class="ember-view">
20452 <p class="ember-view greeting">Howdy Dave</p>
20453 <p class="ember-view greeting">Howdy Mary</p>
20454 <p class="ember-view greeting">Howdy Sara</p>
20459 @for Ember.Handlebars.helpers
20460 @param {String} path
20461 @param {Hash} options
20462 @return {String} HTML string
20464 Ember.Handlebars.registerHelper('collection', function(path, options) {
20465 // If no path is provided, treat path param as options.
20466 if (path && path.data && path.data.isRenderData) {
20469 Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 1);
20471 Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 2);
20474 var fn = options.fn;
20475 var data = options.data;
20476 var inverse = options.inverse;
20478 // If passed a path string, convert that into an object.
20479 // Otherwise, just default to the standard class.
20480 var collectionClass;
20481 collectionClass = path ? getPath(this, path, options) : Ember.CollectionView;
20482 Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass);
20484 var hash = options.hash, itemHash = {}, match;
20486 // Extract item view class if provided else default to the standard class
20487 var itemViewClass, itemViewPath = hash.itemViewClass;
20488 var collectionPrototype = collectionClass.proto();
20489 delete hash.itemViewClass;
20490 itemViewClass = itemViewPath ? getPath(collectionPrototype, itemViewPath, options) : collectionPrototype.itemViewClass;
20491 Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewPath]), !!itemViewClass);
20493 // Go through options passed to the {{collection}} helper and extract options
20494 // that configure item views instead of the collection itself.
20495 for (var prop in hash) {
20496 if (hash.hasOwnProperty(prop)) {
20497 match = prop.match(/^item(.)(.*)$/);
20500 // Convert itemShouldFoo -> shouldFoo
20501 itemHash[match[1].toLowerCase() + match[2]] = hash[prop];
20502 // Delete from hash as this will end up getting passed to the
20503 // {{view}} helper method.
20509 var tagName = hash.tagName || collectionPrototype.tagName;
20512 itemHash.template = fn;
20516 var emptyViewClass;
20517 if (inverse && inverse !== Handlebars.VM.noop) {
20518 emptyViewClass = get(collectionPrototype, 'emptyViewClass');
20519 emptyViewClass = emptyViewClass.extend({
20521 tagName: itemHash.tagName
20523 } else if (hash.emptyViewClass) {
20524 emptyViewClass = getPath(this, hash.emptyViewClass, options);
20526 hash.emptyView = emptyViewClass;
20528 if (hash.eachHelper === 'each') {
20529 itemHash._context = Ember.computed(function() {
20530 return get(this, 'content');
20531 }).property('content');
20532 delete hash.eachHelper;
20535 var viewOptions = Ember.Handlebars.ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this);
20536 hash.itemViewClass = itemViewClass.extend(viewOptions);
20538 return Ember.Handlebars.helpers.view.call(this, collectionClass, options);
20547 /*globals Handlebars */
20550 @submodule ember-handlebars
20553 var getPath = Ember.Handlebars.getPath;
20556 `unbound` allows you to output a property without binding. *Important:* The
20557 output will not be updated if the property changes. Use with caution.
20560 <div>{{unbound somePropertyThatDoesntChange}}</div>
20564 @for Ember.Handlebars.helpers
20565 @param {String} property
20566 @return {String} HTML string
20568 Ember.Handlebars.registerHelper('unbound', function(property, fn) {
20569 var context = (fn.contexts && fn.contexts[0]) || this;
20570 return getPath(context, property, fn);
20578 /*jshint debug:true*/
20581 @submodule ember-handlebars
20584 var getPath = Ember.Handlebars.getPath, normalizePath = Ember.Handlebars.normalizePath;
20587 `log` allows you to output the value of a value in the current rendering
20595 @for Ember.Handlebars.helpers
20596 @param {String} property
20598 Ember.Handlebars.registerHelper('log', function(property, options) {
20599 var context = (options.contexts && options.contexts[0]) || this,
20600 normalized = normalizePath(context, property, options.data),
20601 pathRoot = normalized.root,
20602 path = normalized.path,
20603 value = (path === 'this') ? pathRoot : getPath(pathRoot, path, options);
20604 Ember.Logger.log(value);
20608 The `debugger` helper executes the `debugger` statement in the current
20616 @for Ember.Handlebars.helpers
20617 @param {String} property
20619 Ember.Handlebars.registerHelper('debugger', function() {
20630 @submodule ember-handlebars
20633 var get = Ember.get, set = Ember.set;
20635 Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, {
20636 itemViewClass: Ember._MetamorphView,
20637 emptyViewClass: Ember._MetamorphView,
20639 createChildView: function(view, attrs) {
20640 view = this._super(view, attrs);
20642 // At the moment, if a container view subclass wants
20643 // to insert keywords, it is responsible for cloning
20644 // the keywords hash. This will be fixed momentarily.
20645 var keyword = get(this, 'keyword');
20648 var data = get(view, 'templateData');
20650 data = Ember.copy(data);
20651 data.keywords = view.cloneKeywords();
20652 set(view, 'templateData', data);
20654 var content = get(view, 'content');
20656 // In this case, we do not bind, because the `content` of
20657 // a #each item cannot change.
20658 data.keywords[keyword] = content;
20666 The `{{#each}}` helper loops over elements in a collection, rendering its block once for each item:
20669 Developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}];
20673 {{#each Developers}}
20678 `{{each}}` supports an alternative syntax with element naming:
20681 {{#each person in Developers}}
20686 When looping over objects that do not have properties, `{{this}}` can be used to render the object:
20689 DeveloperNames = ['Yehuda', 'Tom', 'Paul']
20693 {{#each DeveloperNames}}
20700 If you provide an `itemViewClass` option that has its own `template` you can omit
20701 the block in a similar way to how it can be done with the collection helper.
20703 The following template:
20706 <script type="text/x-handlebars">
20707 {{#view App.MyView }}
20708 {{each view.items itemViewClass="App.AnItemView"}}
20713 And application code
20716 App = Ember.Application.create({
20717 MyView: Ember.View.extend({
20719 Ember.Object.create({name: 'Dave'}),
20720 Ember.Object.create({name: 'Mary'}),
20721 Ember.Object.create({name: 'Sara'})
20726 App.AnItemView = Ember.View.extend({
20727 template: Ember.Handlebars.compile("Greetings {{name}}")
20733 Will result in the HTML structure below
20736 <div class="ember-view">
20737 <div class="ember-view">Greetings Dave</div>
20738 <div class="ember-view">Greetings Mary</div>
20739 <div class="ember-view">Greetings Sara</div>
20745 @for Ember.Handlebars.helpers
20746 @param [name] {String} name for item (used with `in`)
20747 @param path {String} path
20749 Ember.Handlebars.registerHelper('each', function(path, options) {
20750 if (arguments.length === 4) {
20751 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");
20753 var keywordName = arguments[0];
20755 options = arguments[3];
20756 path = arguments[2];
20757 if (path === '') { path = "this"; }
20759 options.hash.keyword = keywordName;
20761 options.hash.eachHelper = 'each';
20764 options.hash.contentBinding = path;
20765 // Set up emptyView as a metamorph with no tag
20766 //options.hash.emptyViewClass = Ember._MetamorphView;
20768 return Ember.Handlebars.helpers.collection.call(this, 'Ember.Handlebars.EachView', options);
20778 @submodule ember-handlebars
20782 `template` allows you to render a template from inside another template.
20783 This allows you to re-use the same template in multiple places. For example:
20786 <script type="text/x-handlebars">
20787 {{#with loggedInUser}}
20788 Last Login: {{lastLogin}}
20789 User Info: {{template "user_info"}}
20793 <script type="text/x-handlebars" data-template-name="user_info">
20794 Name: <em>{{name}}</em>
20795 Karma: <em>{{karma}}</em>
20799 This helper looks for templates in the global Ember.TEMPLATES hash. If you
20800 add <script> tags to your page with the `data-template-name` attribute set,
20801 they will be compiled and placed in this hash automatically.
20803 You can also manually register templates by adding them to the hash:
20806 Ember.TEMPLATES["my_cool_template"] = Ember.Handlebars.compile('<b>{{user}}</b>');
20810 @for Ember.Handlebars.helpers
20811 @param {String} templateName the template to render
20814 Ember.Handlebars.registerHelper('template', function(name, options) {
20815 var template = Ember.TEMPLATES[name];
20817 Ember.assert("Unable to find template with name '"+name+"'.", !!template);
20819 Ember.TEMPLATES[name](this, { data: options.data });
20829 @submodule ember-handlebars
20832 var EmberHandlebars = Ember.Handlebars,
20833 getPath = EmberHandlebars.getPath,
20835 a_slice = Array.prototype.slice;
20837 var ActionHelper = EmberHandlebars.ActionHelper = {
20838 registeredActions: {}
20841 ActionHelper.registerAction = function(actionName, options) {
20842 var actionId = (++Ember.$.uuid).toString();
20844 ActionHelper.registeredActions[actionId] = {
20845 eventName: options.eventName,
20846 handler: function(event) {
20847 var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey,
20848 secondaryClick = event.which > 1, // IE9 may return undefined
20849 nonStandard = modifier || secondaryClick;
20851 if (options.link && nonStandard) {
20852 // Allow the browser to handle special link clicks normally
20856 event.preventDefault();
20858 event.view = options.view;
20860 if (options.hasOwnProperty('context')) {
20861 event.context = options.context;
20864 if (options.hasOwnProperty('contexts')) {
20865 event.contexts = options.contexts;
20868 var target = options.target;
20870 // Check for StateManager (or compatible object)
20871 if (target.isState && typeof target.send === 'function') {
20872 return target.send(actionName, event);
20874 Ember.assert(Ember.String.fmt('Target %@ does not have action %@', [target, actionName]), target[actionName]);
20875 return target[actionName].call(target, event);
20880 options.view.on('willRerender', function() {
20881 delete ActionHelper.registeredActions[actionId];
20888 The `{{action}}` helper registers an HTML element within a template for
20889 DOM event handling and forwards that interaction to the Application's router,
20890 the template's `Ember.View` instance, or supplied `target` option (see 'Specifying a Target').
20892 User interaction with that element will invoke the supplied action name on
20893 the appropriate target.
20895 Given the following Handlebars template on the page
20898 <script type="text/x-handlebars" data-template-name='a-template'>
20899 <div {{action anActionName}}>
20905 And application code
20908 AView = Ember.View.extend({
20909 templateName; 'a-template',
20910 anActionName: function(event){}
20913 aView = AView.create();
20914 aView.appendTo('body');
20917 Will results in the following rendered HTML
20920 <div class="ember-view">
20921 <div data-ember-action="1">
20927 Clicking "click me" will trigger the `anActionName` method of the `aView`
20928 object with a `jQuery.Event` object as its argument. The `jQuery.Event`
20929 object will be extended to include a `view` property that is set to the
20930 original view interacted with (in this case the `aView` object).
20932 ### Event Propagation
20934 Events triggered through the action helper will automatically have
20935 `.preventDefault()` called on them. You do not need to do so in your event
20936 handlers. To stop propagation of the event, simply return `false` from your
20939 If you need the default handler to trigger you should either register your
20940 own event handler, or use event methods on your view class. See Ember.View
20941 'Responding to Browser Events' for more information.
20943 ### Specifying DOM event type
20945 By default the `{{action}}` helper registers for DOM `click` events. You can
20946 supply an `on` option to the helper to specify a different DOM event name:
20949 <script type="text/x-handlebars" data-template-name='a-template'>
20950 <div {{action anActionName on="doubleClick"}}>
20956 See Ember.View 'Responding to Browser Events' for a list of
20957 acceptable DOM event names.
20959 Because `{{action}}` depends on Ember's event dispatch system it will only
20960 function if an `Ember.EventDispatcher` instance is available. An
20961 `Ember.EventDispatcher` instance will be created when a new
20962 `Ember.Application` is created. Having an instance of `Ember.Application`
20963 will satisfy this requirement.
20966 ### Specifying a Target
20967 There are several possible target objects for `{{action}}` helpers:
20969 In a typical `Ember.Router`-backed Application where views are managed
20970 through use of the `{{outlet}}` helper, actions will be forwarded to the
20971 current state of the Applications's Router. See Ember.Router 'Responding
20972 to User-initiated Events' for more information.
20974 If you manually set the `target` property on the controller of a template's
20975 `Ember.View` instance, the specifed `controller.target` will become the target
20976 for any actions. Likely custom values for a controller's `target` are the
20977 controller itself or a StateManager other than the Application's Router.
20979 If the templates's view lacks a controller property the view itself is the target.
20981 Finally, a `target` option can be provided to the helper to change which object
20982 will receive the method call. This option must be a string representing a
20986 <script type="text/x-handlebars" data-template-name='a-template'>
20987 <div {{action anActionName target="MyApplication.someObject"}}>
20993 Clicking "click me" in the rendered HTML of the above template will trigger
20994 the `anActionName` method of the object at `MyApplication.someObject`.
20995 The first argument to this method will be a `jQuery.Event` extended to
20996 include a `view` property that is set to the original view interacted with.
20998 A path relative to the template's `Ember.View` instance can also be used as
21002 <script type="text/x-handlebars" data-template-name='a-template'>
21003 <div {{action anActionName target="parentView"}}>
21009 Clicking "click me" in the rendered HTML of the above template will trigger
21010 the `anActionName` method of the view's parent view.
21012 The `{{action}}` helper is `Ember.StateManager` aware. If the target of the
21013 action is an `Ember.StateManager` instance `{{action}}` will use the `send`
21014 functionality of StateManagers. The documentation for `Ember.StateManager`
21015 has additional information about this use.
21017 If an action's target does not implement a method that matches the supplied
21018 action name an error will be thrown.
21021 <script type="text/x-handlebars" data-template-name='a-template'>
21022 <div {{action aMethodNameThatIsMissing}}>
21028 With the following application code
21031 AView = Ember.View.extend({
21032 templateName; 'a-template',
21033 // note: no method 'aMethodNameThatIsMissing'
21034 anActionName: function(event){}
21037 aView = AView.create();
21038 aView.appendTo('body');
21041 Will throw `Uncaught TypeError: Cannot call method 'call' of undefined` when
21042 "click me" is clicked.
21044 ### Specifying a context
21046 By default the `{{action}}` helper passes the current Handlebars context
21047 along in the `jQuery.Event` object. You may specify an alternate object to
21048 pass as the context by providing a property path:
21051 <script type="text/x-handlebars" data-template-name='a-template'>
21052 {{#each person in people}}
21053 <div {{action edit person}}>
21061 @for Ember.Handlebars.helpers
21062 @param {String} actionName
21063 @param {Object...} contexts
21064 @param {Hash} options
21066 EmberHandlebars.registerHelper('action', function(actionName) {
21067 var options = arguments[arguments.length - 1],
21068 contexts = a_slice.call(arguments, 1, -1);
21070 var hash = options.hash,
21071 view = options.data.view,
21072 target, controller, link;
21074 // create a hash to pass along to registerAction
21076 eventName: hash.on || "click"
21079 action.view = view = get(view, 'concreteView');
21082 target = getPath(this, hash.target, options);
21083 } else if (controller = options.data.keywords.controller) {
21084 target = get(controller, 'target');
21087 action.target = target = target || view;
21089 if (contexts.length) {
21090 action.contexts = contexts = Ember.EnumerableUtils.map(contexts, function(context) {
21091 return getPath(this, context, options);
21093 action.context = contexts[0];
21096 var output = [], url;
21098 if (hash.href && target.urlForEvent) {
21099 url = target.urlForEvent.apply(target, [actionName].concat(contexts));
21100 output.push('href="' + url + '"');
21101 action.link = true;
21104 var actionId = ActionHelper.registerAction(actionName, action);
21105 output.push('data-ember-action="' + actionId + '"');
21107 return new EmberHandlebars.SafeString(output.join(" "));
21117 @submodule ember-handlebars
21120 var get = Ember.get, set = Ember.set;
21124 When used in a Handlebars template that is assigned to an `Ember.View` instance's
21125 `layout` property Ember will render the layout template first, inserting the view's
21126 own rendered output at the `{{ yield }}` location.
21128 An empty `<body>` and the following application code:
21131 AView = Ember.View.extend({
21132 classNames: ['a-view-with-layout'],
21133 layout: Ember.Handlebars.compile('<div class="wrapper">{{ yield }}</div>'),
21134 template: Ember.Handlebars.compile('<span>I am wrapped</span>')
21137 aView = AView.create();
21138 aView.appendTo('body');
21141 Will result in the following HTML output:
21145 <div class='ember-view a-view-with-layout'>
21146 <div class="wrapper">
21147 <span>I am wrapped</span>
21153 The yield helper cannot be used outside of a template assigned to an `Ember.View`'s `layout` property
21154 and will throw an error if attempted.
21157 BView = Ember.View.extend({
21158 classNames: ['a-view-with-layout'],
21159 template: Ember.Handlebars.compile('{{yield}}')
21162 bView = BView.create();
21163 bView.appendTo('body');
21166 // Uncaught Error: assertion failed: You called yield in a template that was not a layout
21170 @for Ember.Handlebars.helpers
21171 @param {Hash} options
21172 @return {String} HTML string
21174 Ember.Handlebars.registerHelper('yield', function(options) {
21175 var view = options.data.view, template;
21177 while (view && !get(view, 'layout')) {
21178 view = get(view, 'parentView');
21181 Ember.assert("You called yield in a template that was not a layout", !!view);
21183 template = get(view, 'template');
21185 if (template) { template(this, options); }
21195 @submodule ember-handlebars
21198 Ember.Handlebars.OutletView = Ember.ContainerView.extend(Ember._Metamorph);
21201 The `outlet` helper allows you to specify that the current
21202 view's controller will fill in the view for a given area.
21208 By default, when the the current controller's `view`
21209 property changes, the outlet will replace its current
21210 view with the new view.
21213 controller.set('view', someView);
21216 You can also specify a particular name, other than view:
21219 {{outlet masterView}}
21220 {{outlet detailView}}
21223 Then, you can control several outlets from a single
21227 controller.set('masterView', postsView);
21228 controller.set('detailView', postView);
21232 @for Ember.Handlebars.helpers
21233 @param {String} property the property on the controller
21234 that holds the view for this outlet
21236 Ember.Handlebars.registerHelper('outlet', function(property, options) {
21237 if (property && property.data && property.data.isRenderData) {
21238 options = property;
21242 options.hash.currentViewBinding = "controller." + property;
21244 return Ember.Handlebars.helpers.view.call(this, Ember.Handlebars.OutletView, options);
21266 @submodule ember-handlebars
21269 var set = Ember.set, get = Ember.get;
21272 The `Ember.Checkbox` view class renders a checkbox [input](https://developer.mozilla.org/en/HTML/Element/Input)
21273 element. It allows for binding an Ember property (`checked`) to the status of the checkbox.
21278 {{view Ember.Checkbox checkedBinding="receiveEmail"}}
21281 You can add a `label` tag yourself in the template where the Ember.Checkbox is being used.
21285 {{view Ember.Checkbox classNames="applicaton-specific-checkbox"}}
21291 The `checked` attribute of an Ember.Checkbox object should always be set
21292 through the Ember object or by interacting with its rendered element representation
21293 via the mouse, keyboard, or touch. Updating the value of the checkbox via jQuery will
21294 result in the checked value of the object and its element losing synchronization.
21296 ## Layout and LayoutName properties
21297 Because HTML `input` elements are self closing `layout` and `layoutName` properties will
21298 not be applied. See `Ember.View`'s layout section for more information.
21302 @extends Ember.View
21304 Ember.Checkbox = Ember.View.extend({
21305 classNames: ['ember-checkbox'],
21309 attributeBindings: ['type', 'checked', 'disabled', 'tabindex'],
21317 this.on("change", this, this._updateElementValue);
21320 _updateElementValue: function() {
21321 set(this, 'checked', this.$().prop('checked'));
21332 @submodule ember-handlebars
21335 var get = Ember.get, set = Ember.set;
21338 Shared mixin used by Ember.TextField and Ember.TextArea.
21342 @extends Ember.Mixin
21345 Ember.TextSupport = Ember.Mixin.create({
21348 attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex'],
21353 insertNewline: Ember.K,
21358 this.on("focusOut", this, this._elementValueDidChange);
21359 this.on("change", this, this._elementValueDidChange);
21360 this.on("keyUp", this, this.interpretKeyEvents);
21363 interpretKeyEvents: function(event) {
21364 var map = Ember.TextSupport.KEY_EVENTS;
21365 var method = map[event.keyCode];
21367 this._elementValueDidChange();
21368 if (method) { return this[method](event); }
21371 _elementValueDidChange: function() {
21372 set(this, 'value', this.$().val());
21377 Ember.TextSupport.KEY_EVENTS = {
21378 13: 'insertNewline',
21389 @submodule ember-handlebars
21392 var get = Ember.get, set = Ember.set;
21395 The `Ember.TextField` view class renders a text
21396 [input](https://developer.mozilla.org/en/HTML/Element/Input) element. It
21397 allows for binding Ember properties to the text field contents (`value`),
21398 live-updating as the user inputs text.
21403 {{view Ember.TextField valueBinding="firstName"}}
21406 ## Layout and LayoutName properties
21407 Because HTML `input` elements are self closing `layout` and `layoutName` properties will
21408 not be applied. See `Ember.View`'s layout section for more information.
21412 @extends Ember.View
21413 @uses Ember.TextSupport
21415 Ember.TextField = Ember.View.extend(Ember.TextSupport,
21416 /** @scope Ember.TextField.prototype */ {
21418 classNames: ['ember-text-field'],
21420 attributeBindings: ['type', 'value', 'size'],
21423 The value attribute of the input element. As the user inputs text, this
21424 property is updated live.
21433 The type attribute of the input element.
21442 The size of the text field in characters.
21458 @submodule ember-handlebars
21461 var get = Ember.get, set = Ember.set;
21466 @extends Ember.View
21467 @uses Ember.TargetActionSupport
21470 Ember.Button = Ember.View.extend(Ember.TargetActionSupport, {
21471 classNames: ['ember-button'],
21472 classNameBindings: ['isActive'],
21476 propagateEvents: false,
21478 attributeBindings: ['type', 'disabled', 'href', 'tabindex'],
21483 Overrides TargetActionSupport's targetObject computed
21484 property to use Handlebars-specific path resolution.
21486 @property targetObject
21488 targetObject: Ember.computed(function() {
21489 var target = get(this, 'target'),
21490 root = get(this, 'context'),
21491 data = get(this, 'templateData');
21493 if (typeof target !== 'string') { return target; }
21495 return Ember.Handlebars.getPath(root, target, { data: data });
21496 }).property('target').cacheable(),
21498 // Defaults to 'button' if tagName is 'input' or 'button'
21499 type: Ember.computed(function(key, value) {
21500 var tagName = this.get('tagName');
21501 if (value !== undefined) { this._type = value; }
21502 if (this._type !== undefined) { return this._type; }
21503 if (tagName === 'input' || tagName === 'button') { return 'button'; }
21504 }).property('tagName').cacheable(),
21508 // Allow 'a' tags to act like buttons
21509 href: Ember.computed(function() {
21510 return this.get('tagName') === 'a' ? '#' : null;
21511 }).property('tagName').cacheable(),
21513 mouseDown: function() {
21514 if (!get(this, 'disabled')) {
21515 set(this, 'isActive', true);
21516 this._mouseDown = true;
21517 this._mouseEntered = true;
21519 return get(this, 'propagateEvents');
21522 mouseLeave: function() {
21523 if (this._mouseDown) {
21524 set(this, 'isActive', false);
21525 this._mouseEntered = false;
21529 mouseEnter: function() {
21530 if (this._mouseDown) {
21531 set(this, 'isActive', true);
21532 this._mouseEntered = true;
21536 mouseUp: function(event) {
21537 if (get(this, 'isActive')) {
21538 // Actually invoke the button's target and action.
21539 // This method comes from the Ember.TargetActionSupport mixin.
21540 this.triggerAction();
21541 set(this, 'isActive', false);
21544 this._mouseDown = false;
21545 this._mouseEntered = false;
21546 return get(this, 'propagateEvents');
21549 keyDown: function(event) {
21550 // Handle space or enter
21551 if (event.keyCode === 13 || event.keyCode === 32) {
21556 keyUp: function(event) {
21557 // Handle space or enter
21558 if (event.keyCode === 13 || event.keyCode === 32) {
21563 // TODO: Handle proper touch behavior. Including should make inactive when
21564 // finger moves more than 20x outside of the edge of the button (vs mouse
21565 // which goes inactive as soon as mouse goes out of edges.)
21567 touchStart: function(touch) {
21568 return this.mouseDown(touch);
21571 touchEnd: function(touch) {
21572 return this.mouseUp(touch);
21576 Ember.deprecate("Ember.Button is deprecated and will be removed from future releases. Consider using the `{{action}}` helper.");
21588 @submodule ember-handlebars
21591 var get = Ember.get, set = Ember.set;
21594 The `Ember.TextArea` view class renders a
21595 [textarea](https://developer.mozilla.org/en/HTML/Element/textarea) element.
21596 It allows for binding Ember properties to the text area contents (`value`),
21597 live-updating as the user inputs text.
21599 ## Layout and LayoutName properties
21601 Because HTML `textarea` elements do not contain inner HTML the `layout` and `layoutName`
21602 properties will not be applied. See `Ember.View`'s layout section for more information.
21606 @extends Ember.View
21607 @uses Ember.TextSupport
21609 Ember.TextArea = Ember.View.extend(Ember.TextSupport, {
21610 classNames: ['ember-text-area'],
21612 tagName: "textarea",
21613 attributeBindings: ['rows', 'cols'],
21617 _updateElementValue: Ember.observer(function() {
21618 // We do this check so cursor position doesn't get affected in IE
21619 var value = get(this, 'value'),
21621 if ($el && value !== $el.val()) {
21628 this.on("didInsertElement", this, this._updateElementValue);
21640 @submodule ember-handlebars
21644 @class TabContainerView
21647 @extends Ember.View
21649 Ember.TabContainerView = Ember.View.extend({
21651 Ember.deprecate("Ember.TabContainerView is deprecated and will be removed from future releases.");
21663 @submodule ember-handlebars
21666 var get = Ember.get;
21671 @extends Ember.View
21674 Ember.TabPaneView = Ember.View.extend({
21675 tabsContainer: Ember.computed(function() {
21676 return this.nearestInstanceOf(Ember.TabContainerView);
21677 }).property().volatile(),
21679 isVisible: Ember.computed(function() {
21680 return get(this, 'viewName') === get(this, 'tabsContainer.currentView');
21681 }).property('tabsContainer.currentView').volatile(),
21684 Ember.deprecate("Ember.TabPaneView is deprecated and will be removed from future releases.");
21696 @submodule ember-handlebars
21699 var get = Ember.get, setPath = Ember.setPath;
21704 @extends Ember.View
21707 Ember.TabView = Ember.View.extend({
21708 tabsContainer: Ember.computed(function() {
21709 return this.nearestInstanceOf(Ember.TabContainerView);
21710 }).property().volatile(),
21712 mouseUp: function() {
21713 setPath(this, 'tabsContainer.currentView', get(this, 'value'));
21717 Ember.deprecate("Ember.TabView is deprecated and will be removed from future releases.");
21733 /*jshint eqeqeq:false */
21737 @submodule ember-handlebars
21740 var set = Ember.set, get = Ember.get;
21741 var indexOf = Ember.EnumerableUtils.indexOf, indexesOf = Ember.EnumerableUtils.indexesOf;
21744 The Ember.Select view class renders a
21745 [select](https://developer.mozilla.org/en/HTML/Element/select) HTML element,
21746 allowing the user to choose from a list of options.
21748 The text and `value` property of each `<option>` element within the `<select>` element
21749 are populated from the objects in the Element.Select's `content` property. The
21750 underlying data object of the selected `<option>` is stored in the
21751 Element.Select's `value` property.
21753 ### `content` as an array of Strings
21754 The simplest version of an Ember.Select takes an array of strings as its `content` property.
21755 The string will be used as both the `value` property and the inner text of each `<option>`
21756 element inside the rendered `<select>`.
21761 App.Names = ["Yehuda", "Tom"];
21765 {{view Ember.Select contentBinding="App.Names"}}
21768 Would result in the following HTML:
21771 <select class="ember-select">
21772 <option value="Yehuda">Yehuda</option>
21773 <option value="Tom">Tom</option>
21777 You can control which `<option>` is selected through the Ember.Select's
21778 `value` property directly or as a binding:
21781 App.Names = Ember.Object.create({
21783 content: ["Yehuda", "Tom"]
21788 {{view Ember.Select
21789 contentBinding="App.controller.content"
21790 valueBinding="App.controller.selected"
21794 Would result in the following HTML with the `<option>` for 'Tom' selected:
21797 <select class="ember-select">
21798 <option value="Yehuda">Yehuda</option>
21799 <option value="Tom" selected="selected">Tom</option>
21803 A user interacting with the rendered `<select>` to choose "Yehuda" would update
21804 the value of `App.controller.selected` to "Yehuda".
21806 ### `content` as an Array of Objects
21807 An Ember.Select can also take an array of JavaScript or Ember objects
21808 as its `content` property.
21810 When using objects you need to tell the Ember.Select which property should be
21811 accessed on each object to supply the `value` attribute of the `<option>`
21812 and which property should be used to supply the element text.
21814 The `optionValuePath` option is used to specify the path on each object to
21815 the desired property for the `value` attribute. The `optionLabelPath`
21816 specifies the path on each object to the desired property for the
21817 element's text. Both paths must reference each object itself as 'content':
21820 App.Programmers = [
21821 Ember.Object.create({firstName: "Yehuda", id: 1}),
21822 Ember.Object.create({firstName: "Tom", id: 2})
21827 {{view Ember.Select
21828 contentBinding="App.Programmers"
21829 optionValuePath="content.id"
21830 optionLabelPath="content.firstName"}}
21833 Would result in the following HTML:
21836 <select class="ember-select">
21837 <option value>Please Select</option>
21838 <option value="1">Yehuda</option>
21839 <option value="2">Tom</option>
21844 The `value` attribute of the selected `<option>` within an Ember.Select
21845 can be bound to a property on another object by providing a
21846 `valueBinding` option:
21849 App.Programmers = [
21850 Ember.Object.create({firstName: "Yehuda", id: 1}),
21851 Ember.Object.create({firstName: "Tom", id: 2})
21854 App.currentProgrammer = Ember.Object.create({
21860 {{view Ember.Select
21861 contentBinding="App.controller.content"
21862 optionValuePath="content.id"
21863 optionLabelPath="content.firstName"
21864 valueBinding="App.currentProgrammer.id"}}
21867 Would result in the following HTML with a selected option:
21870 <select class="ember-select">
21871 <option value>Please Select</option>
21872 <option value="1">Yehuda</option>
21873 <option value="2" selected="selected">Tom</option>
21877 Interacting with the rendered element by selecting the first option
21878 ('Yehuda') will update the `id` value of `App.currentProgrammer`
21879 to match the `value` property of the newly selected `<option>`.
21881 Alternatively, you can control selection through the underlying objects
21882 used to render each object providing a `selectionBinding`. When the selected
21883 `<option>` is changed, the property path provided to `selectionBinding`
21884 will be updated to match the content object of the rendered `<option>`
21888 App.controller = Ember.Object.create({
21889 selectedPerson: null,
21891 Ember.Object.create({firstName: "Yehuda", id: 1}),
21892 Ember.Object.create({firstName: "Tom", id: 2})
21898 {{view Ember.Select
21899 contentBinding="App.controller.content"
21900 optionValuePath="content.id"
21901 optionLabelPath="content.firstName"
21902 selectionBinding="App.controller.selectedPerson"}}
21905 Would result in the following HTML with a selected option:
21908 <select class="ember-select">
21909 <option value>Please Select</option>
21910 <option value="1">Yehuda</option>
21911 <option value="2" selected="selected">Tom</option>
21916 Interacting with the rendered element by selecting the first option
21917 ('Yehuda') will update the `selectedPerson` value of `App.controller`
21918 to match the content object of the newly selected `<option>`. In this
21919 case it is the first object in the `App.content.content`
21921 ### Supplying a Prompt
21923 A `null` value for the Ember.Select's `value` or `selection` property
21924 results in there being no `<option>` with a `selected` attribute:
21927 App.controller = Ember.Object.create({
21937 {{view Ember.Select
21938 contentBinding="App.controller.content"
21939 valueBinding="App.controller.selected"
21943 Would result in the following HTML:
21946 <select class="ember-select">
21947 <option value="Yehuda">Yehuda</option>
21948 <option value="Tom">Tom</option>
21952 Although `App.controller.selected` is `null` and no `<option>`
21953 has a `selected` attribute the rendered HTML will display the
21954 first item as though it were selected. You can supply a string
21955 value for the Ember.Select to display when there is no selection
21956 with the `prompt` option:
21959 App.controller = Ember.Object.create({
21969 {{view Ember.Select
21970 contentBinding="App.controller.content"
21971 valueBinding="App.controller.selected"
21972 prompt="Please select a name"
21976 Would result in the following HTML:
21979 <select class="ember-select">
21980 <option>Please select a name</option>
21981 <option value="Yehuda">Yehuda</option>
21982 <option value="Tom">Tom</option>
21988 @extends Ember.View
21990 Ember.Select = Ember.View.extend(
21991 /** @scope Ember.Select.prototype */ {
21994 classNames: ['ember-select'],
21995 defaultTemplate: Ember.Handlebars.compile('{{#if view.prompt}}<option value>{{view.prompt}}</option>{{/if}}{{#each view.content}}{{view Ember.SelectOption contentBinding="this"}}{{/each}}'),
21996 attributeBindings: ['multiple', 'tabindex'],
21999 The `multiple` attribute of the select element. Indicates whether multiple
22000 options can be selected.
22009 The list of options.
22011 If `optionLabelPath` and `optionValuePath` are not overridden, this should
22012 be a list of strings, which will serve simultaneously as labels and values.
22014 Otherwise, this should be a list of objects. For instance:
22017 { id: 1, firstName: 'Yehuda' },
22018 { id: 2, firstName: 'Tom' }
22020 optionLabelPath: 'content.firstName',
22021 optionValuePath: 'content.id'
22030 When `multiple` is false, the element of `content` that is currently
22033 When `multiple` is true, an array of such elements.
22035 @property selection
22036 @type Object or Array
22042 In single selection mode (when `multiple` is false), value can be used to get
22043 the current selection's value or set the selection by it's value.
22045 It is not currently supported in multiple selection mode.
22051 value: Ember.computed(function(key, value) {
22052 if (arguments.length === 2) { return value; }
22054 var valuePath = get(this, 'optionValuePath').replace(/^content\.?/, '');
22055 return valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection');
22056 }).property('selection').cacheable(),
22059 If given, a top-most dummy option will be rendered to serve as a user
22069 The path of the option labels. See `content`.
22071 @property optionLabelPath
22075 optionLabelPath: 'content',
22078 The path of the option values. See `content`.
22080 @property optionValuePath
22084 optionValuePath: 'content',
22086 _change: function() {
22087 if (get(this, 'multiple')) {
22088 this._changeMultiple();
22090 this._changeSingle();
22094 selectionDidChange: Ember.observer(function() {
22095 var selection = get(this, 'selection'),
22096 isArray = Ember.isArray(selection);
22097 if (get(this, 'multiple')) {
22099 set(this, 'selection', Ember.A([selection]));
22102 this._selectionDidChangeMultiple();
22104 this._selectionDidChangeSingle();
22108 valueDidChange: Ember.observer(function() {
22109 var content = get(this, 'content'),
22110 value = get(this, 'value'),
22111 valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''),
22112 selectedValue = (valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection')),
22115 if (value !== selectedValue) {
22116 selection = content.find(function(obj) {
22117 return value === (valuePath ? get(obj, valuePath) : obj);
22120 this.set('selection', selection);
22125 _triggerChange: function() {
22126 var selection = get(this, 'selection');
22127 var value = get(this, 'value');
22129 if (selection) { this.selectionDidChange(); }
22130 if (value) { this.valueDidChange(); }
22135 _changeSingle: function() {
22136 var selectedIndex = this.$()[0].selectedIndex,
22137 content = get(this, 'content'),
22138 prompt = get(this, 'prompt');
22140 if (!content) { return; }
22141 if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; }
22143 if (prompt) { selectedIndex -= 1; }
22144 set(this, 'selection', content.objectAt(selectedIndex));
22147 _changeMultiple: function() {
22148 var options = this.$('option:selected'),
22149 prompt = get(this, 'prompt'),
22150 offset = prompt ? 1 : 0,
22151 content = get(this, 'content');
22153 if (!content){ return; }
22155 var selectedIndexes = options.map(function(){
22156 return this.index - offset;
22158 set(this, 'selection', content.objectsAt(selectedIndexes));
22162 _selectionDidChangeSingle: function() {
22163 var el = this.get('element');
22164 if (!el) { return; }
22166 var content = get(this, 'content'),
22167 selection = get(this, 'selection'),
22168 selectionIndex = content ? indexOf(content, selection) : -1,
22169 prompt = get(this, 'prompt');
22171 if (prompt) { selectionIndex += 1; }
22172 if (el) { el.selectedIndex = selectionIndex; }
22175 _selectionDidChangeMultiple: function() {
22176 var content = get(this, 'content'),
22177 selection = get(this, 'selection'),
22178 selectedIndexes = content ? indexesOf(content, selection) : [-1],
22179 prompt = get(this, 'prompt'),
22180 offset = prompt ? 1 : 0,
22181 options = this.$('option'),
22185 options.each(function() {
22186 adjusted = this.index > -1 ? this.index - offset : -1;
22187 this.selected = indexOf(selectedIndexes, adjusted) > -1;
22194 this.on("didInsertElement", this, this._triggerChange);
22195 this.on("change", this, this._change);
22199 Ember.SelectOption = Ember.View.extend({
22201 attributeBindings: ['value', 'selected'],
22203 defaultTemplate: function(context, options) {
22204 options = { data: options.data, hash: {} };
22205 Ember.Handlebars.helpers.bind.call(context, "view.label", options);
22209 this.labelPathDidChange();
22210 this.valuePathDidChange();
22215 selected: Ember.computed(function() {
22216 var content = get(this, 'content'),
22217 selection = get(this, 'parentView.selection');
22218 if (get(this, 'parentView.multiple')) {
22219 return selection && indexOf(selection, content.valueOf()) > -1;
22221 // Primitives get passed through bindings as objects... since
22222 // `new Number(4) !== 4`, we use `==` below
22223 return content == selection;
22225 }).property('content', 'parentView.selection').volatile(),
22227 labelPathDidChange: Ember.observer(function() {
22228 var labelPath = get(this, 'parentView.optionLabelPath');
22230 if (!labelPath) { return; }
22232 Ember.defineProperty(this, 'label', Ember.computed(function() {
22233 return get(this, labelPath);
22234 }).property(labelPath).cacheable());
22235 }, 'parentView.optionLabelPath'),
22237 valuePathDidChange: Ember.observer(function() {
22238 var valuePath = get(this, 'parentView.optionValuePath');
22240 if (!valuePath) { return; }
22242 Ember.defineProperty(this, 'value', Ember.computed(function() {
22243 return get(this, valuePath);
22244 }).property(valuePath).cacheable());
22245 }, 'parentView.optionValuePath')
22259 /*globals Handlebars */
22262 @submodule ember-handlebars
22268 Find templates stored in the head tag as script tags and make them available
22269 to Ember.CoreView in the global Ember.TEMPLATES object. This will be run as as
22270 jQuery DOM-ready callback.
22272 Script tags with "text/x-handlebars" will be compiled
22273 with Ember's Handlebars and are suitable for use as a view's template.
22274 Those with type="text/x-raw-handlebars" will be compiled with regular
22275 Handlebars and are suitable for use in views' computed properties.
22278 @for Ember.Handlebars
22282 Ember.Handlebars.bootstrap = function(ctx) {
22283 var selectors = 'script[type="text/x-handlebars"], script[type="text/x-raw-handlebars"]';
22285 Ember.$(selectors, ctx)
22287 // Get a reference to the script tag
22288 var script = Ember.$(this),
22289 type = script.attr('type');
22291 var compile = (script.attr('type') === 'text/x-raw-handlebars') ?
22292 Ember.$.proxy(Handlebars.compile, Handlebars) :
22293 Ember.$.proxy(Ember.Handlebars.compile, Ember.Handlebars),
22294 // Get the name of the script, used by Ember.View's templateName property.
22295 // First look for data-template-name attribute, then fall back to its
22296 // id if no name is found.
22297 templateName = script.attr('data-template-name') || script.attr('id') || 'application',
22298 template = compile(script.html());
22300 // For templates which have a name, we save them and then remove them from the DOM
22301 Ember.TEMPLATES[templateName] = template;
22303 // Remove script tag from DOM
22308 function bootstrap() {
22309 Ember.Handlebars.bootstrap( Ember.$(document) );
22313 We tie this to application.load to ensure that we've at least
22314 attempted to bootstrap at the point that the application is loaded.
22316 We also tie this to document ready since we're guaranteed that all
22317 the inline templates are present at this point.
22319 There's no harm to running this twice, since we remove the templates
22320 from the DOM after processing.
22323 Ember.onLoad('application', bootstrap);
22334 @submodule ember-handlebars
22335 @requires ember-views
22340 // Version: v1.0.pre-160-g7d62790
22341 // Last commit: 7d62790 (2012-09-26 15:59:36 -0700)