1 // Version: v1.0.0-pre.2-1-gd1a2e84
2 // Last commit: d1a2e84 (2012-10-25 13:51:08 -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.0-pre.2-1-gd1a2e84
144 // Last commit: d1a2e84 (2012-10-25 13:51:08 -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 // Default imports, exports and lookup to the global object;
183 var imports = Ember.imports = Ember.imports || this;
184 var exports = Ember.exports = Ember.exports || this;
185 var lookup = Ember.lookup = Ember.lookup || this;
187 // aliases needed to keep minifiers from removing the global context
188 exports.Em = exports.Ember = Em = Ember;
190 // Make sure these are set whether Ember was already defined or not
192 Ember.isNamespace = true;
194 Ember.toString = function() { return "Ember"; };
200 @default '1.0.0-pre.2'
203 Ember.VERSION = '1.0.0-pre.2';
206 Standard environmental variables. You can define these in a global `ENV`
207 variable before loading Ember to control various configuration
213 Ember.ENV = Ember.ENV || ('undefined' === typeof ENV ? {} : ENV);
215 Ember.config = Ember.config || {};
217 // ..........................................................
222 Determines whether Ember should enhances some built-in object
223 prototypes to provide a more friendly API. If enabled, a few methods
224 will be added to Function, String, and Array. Object.prototype will not be
225 enhanced, which is the one that causes most trouble for people.
227 In general we recommend leaving this option set to true since it rarely
228 conflicts with other code. If you need to turn it off however, you can
229 define an ENV.EXTEND_PROTOTYPES config to disable it.
231 @property EXTEND_PROTOTYPES
235 Ember.EXTEND_PROTOTYPES = Ember.ENV.EXTEND_PROTOTYPES;
237 if (typeof Ember.EXTEND_PROTOTYPES === 'undefined') {
238 Ember.EXTEND_PROTOTYPES = true;
242 Determines whether Ember logs a full stack trace during deprecation warnings
244 @property LOG_STACKTRACE_ON_DEPRECATION
248 Ember.LOG_STACKTRACE_ON_DEPRECATION = (Ember.ENV.LOG_STACKTRACE_ON_DEPRECATION !== false);
251 Determines whether Ember should add ECMAScript 5 shims to older browsers.
255 @default Ember.EXTEND_PROTOTYPES
257 Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES;
260 Empty function. Useful for some operations.
266 Ember.K = function() { return this; };
269 // Stub out the methods defined by the ember-debug package in case it's not loaded
271 if ('undefined' === typeof Ember.assert) { Ember.assert = Ember.K; }
272 if ('undefined' === typeof Ember.warn) { Ember.warn = Ember.K; }
273 if ('undefined' === typeof Ember.deprecate) { Ember.deprecate = Ember.K; }
274 if ('undefined' === typeof Ember.deprecateFunc) {
275 Ember.deprecateFunc = function(_, func) { return func; };
278 // These are deprecated but still supported
280 if ('undefined' === typeof ember_assert) { exports.ember_assert = Ember.K; }
281 if ('undefined' === typeof ember_warn) { exports.ember_warn = Ember.K; }
282 if ('undefined' === typeof ember_deprecate) { exports.ember_deprecate = Ember.K; }
283 if ('undefined' === typeof ember_deprecateFunc) {
284 exports.ember_deprecateFunc = function(_, func) { return func; };
288 Previously we used `Ember.$.uuid`, however `$.uuid` has been removed from jQuery master.
289 We'll just bootstrap our own uuid now.
297 // ..........................................................
302 Inside Ember-Metal, simply uses the imports.console object.
303 Override this to provide more robust logging functionality.
308 Ember.Logger = imports.console || { log: Ember.K, warn: Ember.K, error: Ember.K, info: Ember.K, debug: Ember.K };
311 // ..........................................................
316 A function may be assigned to `Ember.onerror` to be called when Ember internals encounter an error.
317 This is useful for specialized error handling and reporting code.
321 @param {Exception} error the error object
323 Ember.onerror = null;
328 Wrap code block in a try/catch if {{#crossLink "Ember/onerror"}}{{/crossLink}} is set.
332 @param {Function} func
335 Ember.handleErrors = function(func, context) {
336 // Unfortunately in some browsers we lose the backtrace if we rethrow the existing error,
337 // so in the event that we don't have an `onerror` handler we don't wrap in a try/catch
338 if ('function' === typeof Ember.onerror) {
340 return func.apply(context || this);
342 Ember.onerror(error);
345 return func.apply(context || this);
355 The purpose of the Ember Instrumentation module is
356 to provide efficient, general-purpose instrumentation
359 Subscribe to a listener by using `Ember.subscribe`:
361 Ember.subscribe("render", {
362 before: function(name, timestamp, payload) {
366 after: function(name, timestamp, payload) {
371 If you return a value from the `before` callback, that same
372 value will be passed as a fourth parameter to the `after`
375 Instrument a block of code by using `Ember.instrument`:
377 Ember.instrument("render.handlebars", payload, function() {
381 Event names passed to `Ember.instrument` are namespaced
382 by periods, from more general to more specific. Subscribers
383 can listen for events by whatever level of granularity they
386 In the above example, the event is `render.handlebars`,
387 and the subscriber listened for all events beginning with
388 `render`. It would receive callbacks for events named
389 `render`, `render.handlebars`, `render.container`, or
390 even `render.handlebars.layout`.
392 @class Instrumentation
396 Ember.Instrumentation = {};
398 var subscribers = [], cache = {};
400 var populateListeners = function(name) {
401 var listeners = [], subscriber;
403 for (var i=0, l=subscribers.length; i<l; i++) {
404 subscriber = subscribers[i];
405 if (subscriber.regex.test(name)) {
406 listeners.push(subscriber.object);
410 cache[name] = listeners;
414 var time = (function() {
415 var perf = window.performance || {};
416 var fn = perf.now || perf.mozNow || perf.webkitNow || perf.msNow || perf.oNow;
417 // fn.bind will be available in all the browsers that support the advanced window.performance... ;-)
418 return fn ? fn.bind(perf) : function() { return +new Date(); };
422 Ember.Instrumentation.instrument = function(name, payload, callback, binding) {
423 var listeners = cache[name];
426 listeners = populateListeners(name);
429 if (listeners.length === 0) { return callback.call(binding); }
431 var beforeValues = [], listener, ret, i, l;
434 for (i=0, l=listeners.length; i<l; i++) {
435 listener = listeners[i];
436 beforeValues[i] = listener.before(name, time(), payload);
439 ret = callback.call(binding);
441 payload = payload || {};
442 payload.exception = e;
444 for (i=0, l=listeners.length; i<l; i++) {
445 listener = listeners[i];
446 listener.after(name, time(), payload, beforeValues[i]);
453 Ember.Instrumentation.subscribe = function(pattern, object) {
454 var paths = pattern.split("."), path, regex = [];
456 for (var i=0, l=paths.length; i<l; i++) {
459 regex.push("[^\\.]*");
465 regex = regex.join("\\.");
466 regex = regex + "(\\..*)?";
470 regex: new RegExp("^" + regex + "$"),
474 subscribers.push(subscriber);
480 Ember.Instrumentation.unsubscribe = function(subscriber) {
483 for (var i=0, l=subscribers.length; i<l; i++) {
484 if (subscribers[i] === subscriber) {
489 subscribers.splice(index, 1);
493 Ember.Instrumentation.reset = function() {
498 Ember.instrument = Ember.Instrumentation.instrument;
499 Ember.subscribe = Ember.Instrumentation.subscribe;
506 /*jshint newcap:false*/
512 // NOTE: There is a bug in jshint that doesn't recognize `Object()` without `new`
513 // as being ok unless both `newcap:false` and not `use strict`.
514 // https://github.com/jshint/jshint/issues/392
516 // Testing this is not ideal, but we want to use native functions
517 // if available, but not to use versions created by libraries like Prototype
518 var isNativeFunc = function(func) {
519 // This should probably work in all browsers likely to have ES5 array methods
520 return func && Function.prototype.toString.call(func).indexOf('[native code]') > -1;
523 // From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map
524 var arrayMap = isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun /*, thisp */) {
527 if (this === void 0 || this === null) {
528 throw new TypeError();
531 var t = Object(this);
532 var len = t.length >>> 0;
533 if (typeof fun !== "function") {
534 throw new TypeError();
537 var res = new Array(len);
538 var thisp = arguments[1];
539 for (var i = 0; i < len; i++) {
541 res[i] = fun.call(thisp, t[i], i, t);
548 // From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
549 var arrayForEach = isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun /*, thisp */) {
552 if (this === void 0 || this === null) {
553 throw new TypeError();
556 var t = Object(this);
557 var len = t.length >>> 0;
558 if (typeof fun !== "function") {
559 throw new TypeError();
562 var thisp = arguments[1];
563 for (var i = 0; i < len; i++) {
565 fun.call(thisp, t[i], i, t);
570 var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj, fromIndex) {
571 if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; }
572 else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); }
573 for (var i = fromIndex, j = this.length; i < j; i++) {
574 if (this[i] === obj) { return i; }
579 Ember.ArrayPolyfills = {
581 forEach: arrayForEach,
582 indexOf: arrayIndexOf
585 var utils = Ember.EnumerableUtils = {
586 map: function(obj, callback, thisArg) {
587 return obj.map ? obj.map.call(obj, callback, thisArg) : arrayMap.call(obj, callback, thisArg);
590 forEach: function(obj, callback, thisArg) {
591 return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : arrayForEach.call(obj, callback, thisArg);
594 indexOf: function(obj, element, index) {
595 return obj.indexOf ? obj.indexOf.call(obj, element, index) : arrayIndexOf.call(obj, element, index);
598 indexesOf: function(obj, elements) {
599 return elements === undefined ? [] : utils.map(elements, function(item) {
600 return utils.indexOf(obj, item);
604 removeObject: function(array, item) {
605 var index = utils.indexOf(array, item);
606 if (index !== -1) { array.splice(index, 1); }
609 replace: function(array, idx, amt, objects) {
611 return array.replace(idx, amt, objects);
613 var args = Array.prototype.concat.apply([idx, amt], objects);
614 return array.splice.apply(array, args);
620 if (Ember.SHIM_ES5) {
621 if (!Array.prototype.map) {
622 Array.prototype.map = arrayMap;
625 if (!Array.prototype.forEach) {
626 Array.prototype.forEach = arrayForEach;
629 if (!Array.prototype.indexOf) {
630 Array.prototype.indexOf = arrayIndexOf;
645 Platform specific methods and feature detectors needed by the framework.
651 var platform = Ember.platform = {};
655 Identical to Object.create(). Implements if not available natively.
659 Ember.create = Object.create;
662 var K = function() {};
664 Ember.create = function(obj, props) {
669 for (var prop in props) {
670 K.prototype[prop] = props[prop].value;
679 Ember.create.isSimulated = true;
682 var defineProperty = Object.defineProperty;
683 var canRedefineProperties, canDefinePropertyOnDOM;
685 // Catch IE8 where Object.defineProperty exists but only works on DOM elements
686 if (defineProperty) {
688 defineProperty({}, 'a',{get:function(){}});
690 defineProperty = null;
694 if (defineProperty) {
695 // Detects a bug in Android <3.2 where you cannot redefine a property using
696 // Object.defineProperty once accessors have already been set.
697 canRedefineProperties = (function() {
700 defineProperty(obj, 'a', {
707 defineProperty(obj, 'a', {
714 return obj.a === true;
717 // This is for Safari 5.0, which supports Object.defineProperty, but not
719 canDefinePropertyOnDOM = (function(){
721 defineProperty(document.createElement('div'), 'definePropertyOnDOM', {});
728 if (!canRedefineProperties) {
729 defineProperty = null;
730 } else if (!canDefinePropertyOnDOM) {
731 defineProperty = function(obj, keyName, desc){
734 if (typeof Node === "object") {
735 isNode = obj instanceof Node;
737 isNode = typeof obj === "object" && typeof obj.nodeType === "number" && typeof obj.nodeName === "string";
741 // TODO: Should we have a warning here?
742 return (obj[keyName] = desc.value);
744 return Object.defineProperty(obj, keyName, desc);
756 Identical to Object.defineProperty(). Implements as much functionality
757 as possible if not available natively.
759 @method defineProperty
760 @param {Object} obj The object to modify
761 @param {String} keyName property name to modify
762 @param {Object} desc descriptor hash
765 platform.defineProperty = defineProperty;
768 Set to true if the platform supports native getters and setters.
770 @property hasPropertyAccessors
773 platform.hasPropertyAccessors = true;
775 if (!platform.defineProperty) {
776 platform.hasPropertyAccessors = false;
778 platform.defineProperty = function(obj, keyName, desc) {
779 if (!desc.get) { obj[keyName] = desc.value; }
782 platform.defineProperty.isSimulated = true;
785 if (Ember.ENV.MANDATORY_SETTER && !platform.hasPropertyAccessors) {
786 Ember.ENV.MANDATORY_SETTER = false;
798 var o_defineProperty = Ember.platform.defineProperty,
799 o_create = Ember.create,
800 // Used for guid generation...
801 GUID_KEY = '__ember'+ (+ new Date()),
806 var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
811 A unique key used to assign guids and other private metadata to objects.
812 If you inspect an object in your browser debugger you will often see these.
813 They can be safely ignored.
815 On browsers that support it, these properties are added with enumeration
816 disabled so they won't show up when you iterate over your properties.
823 Ember.GUID_KEY = GUID_KEY;
835 Generates a new guid, optionally saving the guid to the object that you
836 pass in. You will rarely need to use this method. Instead you should
837 call Ember.guidFor(obj), which return an existing guid if available.
841 @param {Object} [obj] Object the guid will be used for. If passed in, the guid will
842 be saved on the object and reused whenever you pass the same object
845 If no object is passed, just generate a new guid.
847 @param {String} [prefix] Prefix to place in front of the guid. Useful when you want to
848 separate the guid into separate namespaces.
850 @return {String} the guid
852 Ember.generateGuid = function generateGuid(obj, prefix) {
853 if (!prefix) prefix = 'ember';
854 var ret = (prefix + (uuid++));
856 GUID_DESC.value = ret;
857 o_defineProperty(obj, GUID_KEY, GUID_DESC);
865 Returns a unique id for the object. If the object does not yet have
866 a guid, one will be assigned to it. You can call this on any object,
867 Ember.Object-based or not, but be aware that it will add a _guid property.
869 You can also use this method on DOM Element objects.
873 @param obj {Object} any object, string, number, Element, or primitive
874 @return {String} the unique guid for this instance.
876 Ember.guidFor = function guidFor(obj) {
878 // special cases where we don't want to add a key to object
879 if (obj === undefined) return "(undefined)";
880 if (obj === null) return "(null)";
883 var type = typeof obj;
885 // Don't allow prototype changes to String etc. to change the guidFor
888 ret = numberCache[obj];
889 if (!ret) ret = numberCache[obj] = 'nu'+obj;
893 ret = stringCache[obj];
894 if (!ret) ret = stringCache[obj] = 'st'+(uuid++);
898 return obj ? '(true)' : '(false)';
901 if (obj[GUID_KEY]) return obj[GUID_KEY];
902 if (obj === Object) return '(Object)';
903 if (obj === Array) return '(Array)';
904 ret = 'ember'+(uuid++);
905 GUID_DESC.value = ret;
906 o_defineProperty(obj, GUID_KEY, GUID_DESC);
911 // ..........................................................
922 var META_KEY = Ember.GUID_KEY+'_meta';
925 The key used to store meta information on object for property observing.
933 Ember.META_KEY = META_KEY;
935 // Placeholder for non-writable metas.
941 if (MANDATORY_SETTER) { EMPTY_META.values = {}; }
943 Ember.EMPTY_META = EMPTY_META;
945 if (Object.freeze) Object.freeze(EMPTY_META);
947 var isDefinePropertySimulated = Ember.platform.defineProperty.isSimulated;
956 if (isDefinePropertySimulated) {
957 // on platforms that don't support enumerable false
958 // make meta fail jQuery.isPlainObject() to hide from
959 // jQuery.extend() by having a property that fails
960 // hasOwnProperty check.
961 Meta.prototype.__preventPlainObject__ = true;
965 Retrieves the meta hash for an object. If 'writable' is true ensures the
966 hash is writable for this object as well.
968 The meta object contains information about computed property descriptors as
969 well as any watched properties and other information. You generally will
970 not access this information directly but instead work with higher level
971 methods that manipulate this hash indirectly.
977 @param {Object} obj The object to retrieve meta for
979 @param {Boolean} [writable=true] Pass false if you do not intend to modify
980 the meta hash, allowing the method to avoid making an unnecessary copy.
984 Ember.meta = function meta(obj, writable) {
986 var ret = obj[META_KEY];
987 if (writable===false) return ret || EMPTY_META;
990 if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC);
994 if (MANDATORY_SETTER) { ret.values = {}; }
998 // make sure we don't accidentally try to create constructor like desc
999 ret.descs.constructor = null;
1001 } else if (ret.source !== obj) {
1002 if (!isDefinePropertySimulated) o_defineProperty(obj, META_KEY, META_DESC);
1004 ret = o_create(ret);
1005 ret.descs = o_create(ret.descs);
1006 ret.watching = o_create(ret.watching);
1010 if (MANDATORY_SETTER) { ret.values = o_create(ret.values); }
1012 obj[META_KEY] = ret;
1017 Ember.getMeta = function getMeta(obj, property) {
1018 var meta = Ember.meta(obj, false);
1019 return meta[property];
1022 Ember.setMeta = function setMeta(obj, property, value) {
1023 var meta = Ember.meta(obj, true);
1024 meta[property] = value;
1031 In order to store defaults for a class, a prototype may need to create
1032 a default meta object, which will be inherited by any objects instantiated
1033 from the class's constructor.
1035 However, the properties of that meta object are only shallow-cloned,
1036 so if a property is a hash (like the event system's `listeners` hash),
1037 it will by default be shared across all instances of that class.
1039 This method allows extensions to deeply clone a series of nested hashes or
1040 other complex objects. For instance, the event system might pass
1041 ['listeners', 'foo:change', 'ember157'] to `prepareMetaPath`, which will
1042 walk down the keys provided.
1044 For each key, if the key does not exist, it is created. If it already
1045 exists and it was inherited from its constructor, the constructor's
1048 You can also pass false for `writable`, which will simply return
1049 undefined if `prepareMetaPath` discovers any part of the path that
1050 shared or undefined.
1054 @param {Object} obj The object whose meta we are examining
1055 @param {Array} path An array of keys to walk down
1056 @param {Boolean} writable whether or not to create a new meta
1057 (or meta property) if one does not already exist or if it's
1058 shared with its constructor
1060 Ember.metaPath = function metaPath(obj, path, writable) {
1061 var meta = Ember.meta(obj, writable), keyName, value;
1063 for (var i=0, l=path.length; i<l; i++) {
1065 value = meta[keyName];
1068 if (!writable) { return undefined; }
1069 value = meta[keyName] = { __ember_source__: obj };
1070 } else if (value.__ember_source__ !== obj) {
1071 if (!writable) { return undefined; }
1072 value = meta[keyName] = o_create(value);
1073 value.__ember_source__ = obj;
1085 Wraps the passed function so that `this._super` will point to the superFunc
1086 when the function is invoked. This is the primitive we use to implement
1091 @param {Function} func The function to call
1092 @param {Function} superFunc The super function.
1093 @return {Function} wrapped function.
1095 Ember.wrap = function(func, superFunc) {
1099 var newFunc = function() {
1100 var ret, sup = this._super;
1101 this._super = superFunc || K;
1102 ret = func.apply(this, arguments);
1107 newFunc.base = func;
1112 Returns true if the passed object is an array or Array-like.
1114 Ember Array Protocol:
1116 - the object has an objectAt property
1117 - the object is a native Array
1118 - the object is an Object, and has a length property
1120 Unlike Ember.typeOf this method returns true even if the passed object is
1121 not formally array but appears to be array-like (i.e. implements Ember.Array)
1123 Ember.isArray(); // false
1124 Ember.isArray([]); // true
1125 Ember.isArray( Ember.ArrayProxy.create({ content: [] }) ); // true
1129 @param {Object} obj The object to test
1132 Ember.isArray = function(obj) {
1133 if (!obj || obj.setInterval) { return false; }
1134 if (Array.isArray && Array.isArray(obj)) { return true; }
1135 if (Ember.Array && Ember.Array.detect(obj)) { return true; }
1136 if ((obj.length !== undefined) && 'object'===typeof obj) { return true; }
1141 Forces the passed object to be part of an array. If the object is already
1142 an array or array-like, returns the object. Otherwise adds the object to
1143 an array. If obj is null or undefined, returns an empty array.
1145 Ember.makeArray(); => []
1146 Ember.makeArray(null); => []
1147 Ember.makeArray(undefined); => []
1148 Ember.makeArray('lindsay'); => ['lindsay']
1149 Ember.makeArray([1,2,42]); => [1,2,42]
1151 var controller = Ember.ArrayProxy.create({ content: [] });
1152 Ember.makeArray(controller) === controller; => true
1156 @param {Object} obj the object
1159 Ember.makeArray = function(obj) {
1160 if (obj === null || obj === undefined) { return []; }
1161 return Ember.isArray(obj) ? obj : [obj];
1164 function canInvoke(obj, methodName) {
1165 return !!(obj && typeof obj[methodName] === 'function');
1169 Checks to see if the `methodName` exists on the `obj`.
1173 @param {Object} obj The object to check for the method
1174 @param {String} methodName The method name to check for
1176 Ember.canInvoke = canInvoke;
1179 Checks to see if the `methodName` exists on the `obj`,
1180 and if it does, invokes it with the arguments passed.
1184 @param {Object} obj The object to check for the method
1185 @param {String} methodName The method name to check for
1186 @param {Array} [args] The arguments to pass to the method
1187 @return {anything} the return value of the invoked method or undefined if it cannot be invoked
1189 Ember.tryInvoke = function(obj, methodName, args) {
1190 if (canInvoke(obj, methodName)) {
1191 return obj[methodName].apply(obj, args || []);
1205 JavaScript (before ES6) does not have a Map implementation. Objects,
1206 which are often used as dictionaries, may only have Strings as keys.
1208 Because Ember has a way to get a unique identifier for every object
1209 via `Ember.guidFor`, we can implement a performant Map with arbitrary
1210 keys. Because it is commonly used in low-level bookkeeping, Map is
1211 implemented as a pure JavaScript object for performance.
1213 This implementation follows the current iteration of the ES6 proposal
1214 for maps (http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets),
1215 with two exceptions. First, because we need our implementation to be
1216 pleasant on older browsers, we do not use the `delete` name (using
1217 `remove` instead). Second, as we do not have the luxury of in-VM
1218 iteration, we implement a forEach method for iteration.
1220 Map is mocked out to look like an Ember object, so you can do
1221 `Ember.Map.create()` for symmetry with other Ember classes.
1223 var guidFor = Ember.guidFor,
1224 indexOf = Ember.ArrayPolyfills.indexOf;
1226 var copy = function(obj) {
1229 for (var prop in obj) {
1230 if (obj.hasOwnProperty(prop)) { output[prop] = obj[prop]; }
1236 var copyMap = function(original, newObject) {
1237 var keys = original.keys.copy(),
1238 values = copy(original.values);
1240 newObject.keys = keys;
1241 newObject.values = values;
1247 This class is used internally by Ember.js and Ember Data.
1248 Please do not use it at this time. We plan to clean it up
1249 and add many tests soon.
1256 var OrderedSet = Ember.OrderedSet = function() {
1263 @return {Ember.OrderedSet}
1265 OrderedSet.create = function() {
1266 return new OrderedSet();
1270 OrderedSet.prototype = {
1275 this.presenceSet = {};
1283 add: function(obj) {
1284 var guid = guidFor(obj),
1285 presenceSet = this.presenceSet,
1288 if (guid in presenceSet) { return; }
1290 presenceSet[guid] = true;
1298 remove: function(obj) {
1299 var guid = guidFor(obj),
1300 presenceSet = this.presenceSet,
1303 delete presenceSet[guid];
1305 var index = indexOf.call(list, obj);
1307 list.splice(index, 1);
1315 isEmpty: function() {
1316 return this.list.length === 0;
1324 has: function(obj) {
1325 var guid = guidFor(obj),
1326 presenceSet = this.presenceSet;
1328 return guid in presenceSet;
1333 @param {Function} function
1336 forEach: function(fn, self) {
1337 // allow mutation during iteration
1338 var list = this.list.slice();
1340 for (var i = 0, j = list.length; i < j; i++) {
1341 fn.call(self, list[i]);
1349 toArray: function() {
1350 return this.list.slice();
1355 @return {Ember.OrderedSet}
1358 var set = new OrderedSet();
1360 set.presenceSet = copy(this.presenceSet);
1361 set.list = this.list.slice();
1368 A Map stores values indexed by keys. Unlike JavaScript's
1369 default Objects, the keys of a Map can be any JavaScript
1372 Internally, a Map has two data structures:
1374 `keys`: an OrderedSet of all of the existing keys
1375 `values`: a JavaScript Object indexed by the
1378 When a key/value pair is added for the first time, we
1379 add the key to the `keys` OrderedSet, and create or
1380 replace an entry in `values`. When an entry is deleted,
1381 we delete its entry in `keys` and `values`.
1388 var Map = Ember.Map = function() {
1389 this.keys = Ember.OrderedSet.create();
1397 Map.create = function() {
1403 Retrieve the value associated with a given key.
1406 @param {anything} key
1407 @return {anything} the value associated with the key, or undefined
1409 get: function(key) {
1410 var values = this.values,
1411 guid = guidFor(key);
1413 return values[guid];
1417 Adds a value to the map. If a value for the given key has already been
1418 provided, the new value will replace the old value.
1421 @param {anything} key
1422 @param {anything} value
1424 set: function(key, value) {
1425 var keys = this.keys,
1426 values = this.values,
1427 guid = guidFor(key);
1430 values[guid] = value;
1434 Removes a value from the map for an associated key.
1437 @param {anything} key
1438 @return {Boolean} true if an item was removed, false otherwise
1440 remove: function(key) {
1441 // don't use ES6 "delete" because it will be annoying
1442 // to use in browsers that are not ES6 friendly;
1443 var keys = this.keys,
1444 values = this.values,
1445 guid = guidFor(key),
1448 if (values.hasOwnProperty(guid)) {
1450 value = values[guid];
1451 delete values[guid];
1459 Check whether a key is present.
1462 @param {anything} key
1463 @return {Boolean} true if the item was present, false otherwise
1465 has: function(key) {
1466 var values = this.values,
1467 guid = guidFor(key);
1469 return values.hasOwnProperty(guid);
1473 Iterate over all the keys and values. Calls the function once
1474 for each key, passing in the key and value, in that order.
1476 The keys are guaranteed to be iterated over in insertion order.
1479 @param {Function} callback
1480 @param {anything} self if passed, the `this` value inside the
1481 callback. By default, `this` is the map.
1483 forEach: function(callback, self) {
1484 var keys = this.keys,
1485 values = this.values;
1487 keys.forEach(function(key) {
1488 var guid = guidFor(key);
1489 callback.call(self, key, values[guid]);
1498 return copyMap(this, new Map());
1503 @class MapWithDefault
1509 @param {anything} [options.defaultValue]
1511 var MapWithDefault = Ember.MapWithDefault = function(options) {
1513 this.defaultValue = options.defaultValue;
1520 @param {anything} [options.defaultValue]
1521 @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns Ember.MapWithDefault otherwise returns Ember.Map
1523 MapWithDefault.create = function(options) {
1525 return new MapWithDefault(options);
1531 MapWithDefault.prototype = Ember.create(Map.prototype);
1534 Retrieve the value associated with a given key.
1537 @param {anything} key
1538 @return {anything} the value associated with the key, or the default value
1540 MapWithDefault.prototype.get = function(key) {
1541 var hasValue = this.has(key);
1544 return Map.prototype.get.call(this, key);
1546 var defaultValue = this.defaultValue(key);
1547 this.set(key, defaultValue);
1548 return defaultValue;
1554 @return {Ember.MapWithDefault}
1556 MapWithDefault.prototype.copy = function() {
1557 return copyMap(this, new MapWithDefault({
1558 defaultValue: this.defaultValue
1571 var META_KEY = Ember.META_KEY, get, set;
1573 var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
1575 var IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
1576 var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/;
1577 var HAS_THIS = /^this[\.\*]/;
1578 var FIRST_KEY = /^([^\.\*]+)/;
1580 // ..........................................................
1583 // If we are on a platform that supports accessors we can get use those.
1584 // Otherwise simulate accessors by looking up the property directly on the
1588 Gets the value of a property on an object. If the property is computed,
1589 the function will be invoked. If the property is not defined but the
1590 object implements the unknownProperty() method then that will be invoked.
1592 If you plan to run on IE8 and older browsers then you should use this
1593 method anytime you want to retrieve a property on an object that you don't
1594 know for sure is private. (My convention only properties beginning with
1595 an underscore '_' are considered private.)
1597 On all newer browsers, you only need to use this method to retrieve
1598 properties if the property might not be defined on the object and you want
1599 to respect the unknownProperty() handler. Otherwise you can ignore this
1602 Note that if the obj itself is null, this method will simply return
1607 @param {Object} obj The object to retrieve from.
1608 @param {String} keyName The property key to retrieve
1609 @return {Object} the property value or null.
1611 get = function get(obj, keyName) {
1612 // Helpers that operate with 'this' within an #each
1613 if (keyName === '') {
1617 if (!keyName && 'string'===typeof obj) {
1622 if (!obj || keyName.indexOf('.') !== -1) {
1623 return getPath(obj, keyName);
1626 Ember.assert("You need to provide an object and key to `get`.", !!obj && keyName);
1628 var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret;
1630 return desc.get(obj, keyName);
1632 if (MANDATORY_SETTER && meta && meta.watching[keyName] > 0) {
1633 ret = meta.values[keyName];
1638 if (ret === undefined &&
1639 'object' === typeof obj && !(keyName in obj) && 'function' === typeof obj.unknownProperty) {
1640 return obj.unknownProperty(keyName);
1648 Sets the value of a property on an object, respecting computed properties
1649 and notifying observers and other listeners of the change. If the
1650 property is not defined but the object implements the unknownProperty()
1651 method then that will be invoked as well.
1653 If you plan to run on IE8 and older browsers then you should use this
1654 method anytime you want to set a property on an object that you don't
1655 know for sure is private. (My convention only properties beginning with
1656 an underscore '_' are considered private.)
1658 On all newer browsers, you only need to use this method to set
1659 properties if the property might not be defined on the object and you want
1660 to respect the unknownProperty() handler. Otherwise you can ignore this
1665 @param {Object} obj The object to modify.
1666 @param {String} keyName The property key to set
1667 @param {Object} value The value to set
1668 @return {Object} the passed value.
1670 set = function set(obj, keyName, value, tolerant) {
1671 if (typeof obj === 'string') {
1672 Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
1678 if (!obj || keyName.indexOf('.') !== -1) {
1679 return setPath(obj, keyName, value, tolerant);
1682 Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
1683 Ember.assert('calling set on destroyed object', !obj.isDestroyed);
1685 var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
1686 isUnknown, currentValue;
1688 desc.set(obj, keyName, value);
1691 isUnknown = 'object' === typeof obj && !(keyName in obj);
1693 // setUnknownProperty is called if `obj` is an object,
1694 // the property does not already exist, and the
1695 // `setUnknownProperty` method exists on the object
1696 if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
1697 obj.setUnknownProperty(keyName, value);
1698 } else if (meta && meta.watching[keyName] > 0) {
1699 if (MANDATORY_SETTER) {
1700 currentValue = meta.values[keyName];
1702 currentValue = obj[keyName];
1704 // only trigger a change if the value has changed
1705 if (value !== currentValue) {
1706 Ember.propertyWillChange(obj, keyName);
1707 if (MANDATORY_SETTER) {
1708 if (currentValue === undefined && !(keyName in obj)) {
1709 Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
1711 meta.values[keyName] = value;
1714 obj[keyName] = value;
1716 Ember.propertyDidChange(obj, keyName);
1719 obj[keyName] = value;
1725 // Currently used only by Ember Data tests
1726 if (Ember.config.overrideAccessors) {
1729 Ember.config.overrideAccessors();
1734 function firstKey(path) {
1735 return path.match(FIRST_KEY)[0];
1738 // assumes path is already normalized
1739 function normalizeTuple(target, path) {
1740 var hasThis = HAS_THIS.test(path),
1741 isGlobal = !hasThis && IS_GLOBAL_PATH.test(path),
1744 if (!target || isGlobal) target = Ember.lookup;
1745 if (hasThis) path = path.slice(5);
1747 if (target === Ember.lookup) {
1748 key = firstKey(path);
1749 target = get(target, key);
1750 path = path.slice(key.length+1);
1753 // must return some kind of path to be valid else other things will break.
1754 if (!path || path.length===0) throw new Error('Invalid Path');
1756 return [ target, path ];
1759 function getPath(root, path) {
1760 var hasThis, parts, tuple, idx, len;
1762 // If there is no root and path is a key name, return that
1763 // property from the global object.
1764 // E.g. get('Ember') -> Ember
1765 if (root === null && path.indexOf('.') === -1) { return get(Ember.lookup, path); }
1767 // detect complicated paths and normalize them
1768 hasThis = HAS_THIS.test(path);
1770 if (!root || hasThis) {
1771 tuple = normalizeTuple(root, path);
1777 parts = path.split(".");
1779 for (idx=0; root && idx<len; idx++) {
1780 root = get(root, parts[idx], true);
1781 if (root && root.isDestroyed) { return undefined; }
1786 function setPath(root, path, value, tolerant) {
1789 // get the last part of the path
1790 keyName = path.slice(path.lastIndexOf('.') + 1);
1792 // get the first part of the part
1793 path = path.slice(0, path.length-(keyName.length+1));
1795 // unless the path is this, look up the first part to
1797 if (path !== 'this') {
1798 root = getPath(root, path);
1801 if (!keyName || keyName.length === 0) {
1802 throw new Error('You passed an empty path');
1806 if (tolerant) { return; }
1807 else { throw new Error('Object in path '+path+' could not be found or was destroyed.'); }
1810 return set(root, keyName, value);
1816 Normalizes a target/path pair to reflect that actual target/path that should
1817 be observed, etc. This takes into account passing in global property
1818 paths (i.e. a path beginning with a captial letter not defined on the
1819 target) and * separators.
1821 @method normalizeTuple
1823 @param {Object} target The current target. May be null.
1824 @param {String} path A path on the target or a global property path.
1825 @return {Array} a temporary array with the normalized target/path pair.
1827 Ember.normalizeTuple = function(target, path) {
1828 return normalizeTuple(target, path);
1831 Ember.getWithDefault = function(root, key, defaultValue) {
1832 var value = get(root, key);
1834 if (value === undefined) { return defaultValue; }
1840 Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get);
1843 Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now supports paths', Ember.set);
1846 Error-tolerant form of Ember.set. Will not blow up if any part of the
1847 chain is undefined, null, or destroyed.
1849 This is primarily used when syncing bindings, which may try to update after
1850 an object has been destroyed.
1854 @param {Object} obj The object to modify.
1855 @param {String} keyName The property key to set
1856 @param {Object} value The value to set
1858 Ember.trySet = function(root, path, value) {
1859 return set(root, path, value, true);
1861 Ember.trySetPath = Ember.deprecateFunc('trySetPath has been renamed to trySet', Ember.trySet);
1864 Returns true if the provided path is global (e.g., "MyApp.fooController.bar")
1865 instead of local ("foo.bar.baz").
1867 @method isGlobalPath
1870 @param {String} path
1873 Ember.isGlobalPath = function(path) {
1874 return IS_GLOBAL.test(path);
1887 var GUID_KEY = Ember.GUID_KEY,
1888 META_KEY = Ember.META_KEY,
1889 EMPTY_META = Ember.EMPTY_META,
1890 metaFor = Ember.meta,
1891 o_create = Ember.create,
1892 objectDefineProperty = Ember.platform.defineProperty;
1894 var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
1896 // ..........................................................
1901 Objects of this type can implement an interface to responds requests to
1902 get and set. The default implementation handles simple properties.
1904 You generally won't need to create or subclass this directly.
1911 var Descriptor = Ember.Descriptor = function() {};
1913 // ..........................................................
1914 // DEFINING PROPERTIES API
1920 NOTE: This is a low-level method used by other parts of the API. You almost
1921 never want to call this method directly. Instead you should use Ember.mixin()
1922 to define new properties.
1924 Defines a property on an object. This method works much like the ES5
1925 Object.defineProperty() method except that it can also accept computed
1926 properties and other special descriptors.
1928 Normally this method takes only three parameters. However if you pass an
1929 instance of Ember.Descriptor as the third param then you can pass an optional
1930 value as the fourth parameter. This is often more efficient than creating
1931 new descriptor hashes for each property.
1935 // ES5 compatible mode
1936 Ember.defineProperty(contact, 'firstName', {
1938 configurable: false,
1943 // define a simple property
1944 Ember.defineProperty(contact, 'lastName', undefined, 'Jolley');
1946 // define a computed property
1947 Ember.defineProperty(contact, 'fullName', Ember.computed(function() {
1948 return this.firstName+' '+this.lastName;
1949 }).property('firstName', 'lastName'));
1951 @method defineProperty
1953 @param {Object} obj the object to define this property on. This may be a prototype.
1954 @param {String} keyName the name of the property
1955 @param {Ember.Descriptor} [desc] an instance of Ember.Descriptor (typically a
1956 computed property) or an ES5 descriptor.
1957 You must provide this or `data` but not both.
1958 @param {anything} [data] something other than a descriptor, that will
1959 become the explicit value of this property.
1961 Ember.defineProperty = function(obj, keyName, desc, data, meta) {
1962 var descs, existingDesc, watching, value;
1964 if (!meta) meta = metaFor(obj);
1966 existingDesc = meta.descs[keyName];
1967 watching = meta.watching[keyName] > 0;
1969 if (existingDesc instanceof Ember.Descriptor) {
1970 existingDesc.teardown(obj, keyName);
1973 if (desc instanceof Ember.Descriptor) {
1976 descs[keyName] = desc;
1977 if (MANDATORY_SETTER && watching) {
1978 objectDefineProperty(obj, keyName, {
1982 value: undefined // make enumerable
1985 obj[keyName] = undefined; // make enumerable
1987 desc.setup(obj, keyName);
1989 descs[keyName] = undefined; // shadow descriptor in proto
1993 if (MANDATORY_SETTER && watching) {
1994 meta.values[keyName] = data;
1995 objectDefineProperty(obj, keyName, {
1999 Ember.assert('Must use Ember.set() to access this property', false);
2002 var meta = this[META_KEY];
2003 return meta && meta.values[keyName];
2007 obj[keyName] = data;
2012 // compatibility with ES5
2013 objectDefineProperty(obj, keyName, desc);
2017 // if key is being watched, override chains that
2018 // were initialized with the prototype
2019 if (watching) { Ember.overrideChains(obj, keyName, meta); }
2021 // The `value` passed to the `didDefineProperty` hook is
2022 // either the descriptor or data, whichever was passed.
2023 if (obj.didDefineProperty) { obj.didDefineProperty(obj, keyName, value); }
2038 var AFTER_OBSERVERS = ':change';
2039 var BEFORE_OBSERVERS = ':before';
2041 var guidFor = Ember.guidFor;
2046 this.observerSet = {
2047 [senderGuid]: { // variable name: `keySet`
2048 [keyName]: listIndex
2055 eventName: eventName,
2057 [targetGuid]: { // variable name: `actionSet`
2058 [methodGuid]: { // variable name: `action`
2059 target: [Object object],
2060 method: [Function function]
2068 function ObserverSet() {
2072 ObserverSet.prototype.add = function(sender, keyName, eventName) {
2073 var observerSet = this.observerSet,
2074 observers = this.observers,
2075 senderGuid = Ember.guidFor(sender),
2076 keySet = observerSet[senderGuid],
2080 observerSet[senderGuid] = keySet = {};
2082 index = keySet[keyName];
2083 if (index === undefined) {
2084 index = observers.push({
2087 eventName: eventName,
2090 keySet[keyName] = index;
2092 return observers[index].listeners;
2095 ObserverSet.prototype.flush = function() {
2096 var observers = this.observers, i, len, observer, sender;
2098 for (i=0, len=observers.length; i < len; ++i) {
2099 observer = observers[i];
2100 sender = observer.sender;
2101 if (sender.isDestroyed) { continue; }
2102 Ember.sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
2106 ObserverSet.prototype.clear = function() {
2107 this.observerSet = {};
2108 this.observers = [];
2111 var beforeObserverSet = new ObserverSet(), observerSet = new ObserverSet();
2114 @method beginPropertyChanges
2117 Ember.beginPropertyChanges = function() {
2122 @method endPropertyChanges
2124 Ember.endPropertyChanges = function() {
2127 beforeObserverSet.clear();
2128 observerSet.flush();
2133 Make a series of property changes together in an
2136 Ember.changeProperties(function() {
2137 obj1.set('foo', mayBlowUpWhenSet);
2138 obj2.set('bar', baz);
2141 @method changeProperties
2142 @param {Function} callback
2145 Ember.changeProperties = function(cb, binding){
2146 Ember.beginPropertyChanges();
2150 Ember.endPropertyChanges();
2155 Set a list of properties on an object. These properties are set inside
2156 a single `beginPropertyChanges` and `endPropertyChanges` batch, so
2157 observers will be buffered.
2159 @method setProperties
2161 @param {Hash} properties
2164 Ember.setProperties = function(self, hash) {
2165 Ember.changeProperties(function(){
2166 for(var prop in hash) {
2167 if (hash.hasOwnProperty(prop)) Ember.set(self, prop, hash[prop]);
2174 function changeEvent(keyName) {
2175 return keyName+AFTER_OBSERVERS;
2178 function beforeEvent(keyName) {
2179 return keyName+BEFORE_OBSERVERS;
2185 @param {String} path
2186 @param {Object|Function} targetOrMethod
2187 @param {Function|String} [method]
2189 Ember.addObserver = function(obj, path, target, method) {
2190 Ember.addListener(obj, changeEvent(path), target, method);
2191 Ember.watch(obj, path);
2195 Ember.observersFor = function(obj, path) {
2196 return Ember.listenersFor(obj, changeEvent(path));
2200 @method removeObserver
2202 @param {String} path
2203 @param {Object|Function} targetOrMethod
2204 @param {Function|String} [method]
2206 Ember.removeObserver = function(obj, path, target, method) {
2207 Ember.unwatch(obj, path);
2208 Ember.removeListener(obj, changeEvent(path), target, method);
2213 @method addBeforeObserver
2215 @param {String} path
2216 @param {Object|Function} targetOrMethod
2217 @param {Function|String} [method]
2219 Ember.addBeforeObserver = function(obj, path, target, method) {
2220 Ember.addListener(obj, beforeEvent(path), target, method);
2221 Ember.watch(obj, path);
2225 // Suspend observer during callback.
2227 // This should only be used by the target of the observer
2228 // while it is setting the observed path.
2229 Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
2230 return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
2233 Ember._suspendObserver = function(obj, path, target, method, callback) {
2234 return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
2237 var map = Ember.ArrayPolyfills.map;
2239 Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
2240 var events = map.call(paths, beforeEvent);
2241 return Ember._suspendListeners(obj, events, target, method, callback);
2244 Ember._suspendObservers = function(obj, paths, target, method, callback) {
2245 var events = map.call(paths, changeEvent);
2246 return Ember._suspendListeners(obj, events, target, method, callback);
2249 Ember.beforeObserversFor = function(obj, path) {
2250 return Ember.listenersFor(obj, beforeEvent(path));
2254 @method removeBeforeObserver
2256 @param {String} path
2257 @param {Object|Function} targetOrMethod
2258 @param {Function|String} [method]
2260 Ember.removeBeforeObserver = function(obj, path, target, method) {
2261 Ember.unwatch(obj, path);
2262 Ember.removeListener(obj, beforeEvent(path), target, method);
2266 Ember.notifyBeforeObservers = function(obj, keyName) {
2267 if (obj.isDestroying) { return; }
2269 var eventName = beforeEvent(keyName), listeners, listenersDiff;
2271 listeners = beforeObserverSet.add(obj, keyName, eventName);
2272 listenersDiff = Ember.listenersDiff(obj, eventName, listeners);
2273 Ember.sendEvent(obj, eventName, [obj, keyName], listenersDiff);
2275 Ember.sendEvent(obj, eventName, [obj, keyName]);
2279 Ember.notifyObservers = function(obj, keyName) {
2280 if (obj.isDestroying) { return; }
2282 var eventName = changeEvent(keyName), listeners;
2284 listeners = observerSet.add(obj, keyName, eventName);
2285 Ember.listenersUnion(obj, eventName, listeners);
2287 Ember.sendEvent(obj, eventName, [obj, keyName]);
2300 var guidFor = Ember.guidFor, // utils.js
2301 metaFor = Ember.meta, // utils.js
2302 get = Ember.get, // accessors.js
2303 set = Ember.set, // accessors.js
2304 normalizeTuple = Ember.normalizeTuple, // accessors.js
2305 GUID_KEY = Ember.GUID_KEY, // utils.js
2306 META_KEY = Ember.META_KEY, // utils.js
2307 // circular reference observer depends on Ember.watch
2308 // we should move change events to this file or its own property_events.js
2309 notifyObservers = Ember.notifyObservers, // observer.js
2310 forEach = Ember.ArrayPolyfills.forEach, // array.js
2311 FIRST_KEY = /^([^\.\*]+)/,
2314 var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
2315 o_defineProperty = Ember.platform.defineProperty;
2317 function firstKey(path) {
2318 return path.match(FIRST_KEY)[0];
2321 // returns true if the passed path is just a keyName
2322 function isKeyName(path) {
2323 return path==='*' || !IS_PATH.test(path);
2326 // ..........................................................
2330 var DEP_SKIP = { __emberproto__: true }; // skip some keys and toString
2332 function iterDeps(method, obj, depKey, seen, meta) {
2334 var guid = guidFor(obj);
2335 if (!seen[guid]) seen[guid] = {};
2336 if (seen[guid][depKey]) return;
2337 seen[guid][depKey] = true;
2339 var deps = meta.deps;
2340 deps = deps && deps[depKey];
2342 for(var key in deps) {
2343 if (DEP_SKIP[key]) continue;
2344 var desc = meta.descs[key];
2345 if (desc && desc._suspended === obj) continue;
2352 var WILL_SEEN, DID_SEEN;
2354 // called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...)
2355 function dependentKeysWillChange(obj, depKey, meta) {
2356 if (obj.isDestroying) { return; }
2358 var seen = WILL_SEEN, top = !seen;
2359 if (top) { seen = WILL_SEEN = {}; }
2360 iterDeps(propertyWillChange, obj, depKey, seen, meta);
2361 if (top) { WILL_SEEN = null; }
2364 // called whenever a property has just changed to update dependent keys
2365 function dependentKeysDidChange(obj, depKey, meta) {
2366 if (obj.isDestroying) { return; }
2368 var seen = DID_SEEN, top = !seen;
2369 if (top) { seen = DID_SEEN = {}; }
2370 iterDeps(propertyDidChange, obj, depKey, seen, meta);
2371 if (top) { DID_SEEN = null; }
2374 // ..........................................................
2378 function addChainWatcher(obj, keyName, node) {
2379 if (!obj || ('object' !== typeof obj)) return; // nothing to do
2380 var m = metaFor(obj);
2381 var nodes = m.chainWatchers;
2382 if (!nodes || nodes.__emberproto__ !== obj) {
2383 nodes = m.chainWatchers = { __emberproto__: obj };
2386 if (!nodes[keyName]) { nodes[keyName] = {}; }
2387 nodes[keyName][guidFor(node)] = node;
2388 Ember.watch(obj, keyName);
2391 function removeChainWatcher(obj, keyName, node) {
2392 if (!obj || 'object' !== typeof obj) { return; } // nothing to do
2393 var m = metaFor(obj, false),
2394 nodes = m.chainWatchers;
2395 if (!nodes || nodes.__emberproto__ !== obj) { return; } //nothing to do
2396 if (nodes[keyName]) { delete nodes[keyName][guidFor(node)]; }
2397 Ember.unwatch(obj, keyName);
2400 var pendingQueue = [];
2402 // attempts to add the pendingQueue chains again. If some of them end up
2403 // back in the queue and reschedule is true, schedules a timeout to try
2405 function flushPendingChains() {
2406 if (pendingQueue.length === 0) { return; } // nothing to do
2408 var queue = pendingQueue;
2411 forEach.call(queue, function(q) { q[0].add(q[1]); });
2413 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);
2416 function isProto(pvalue) {
2417 return metaFor(pvalue, false).proto === pvalue;
2420 // A ChainNode watches a single key on an object. If you provide a starting
2421 // value for the key then the node won't actually watch it. For a root node
2422 // pass null for parent and key and object for value.
2423 var ChainNode = function(parent, key, value, separator) {
2425 this._parent = parent;
2428 // _watching is true when calling get(this._parent, this._key) will
2429 // return the value of this node.
2431 // It is false for the root of a chain (because we have no parent)
2432 // and for global paths (because the parent node is the object with
2433 // the observer on it)
2434 this._watching = value===undefined;
2436 this._value = value;
2437 this._separator = separator || '.';
2439 if (this._watching) {
2440 this._object = parent.value();
2441 if (this._object) { addChainWatcher(this._object, this._key, this); }
2444 // Special-case: the EachProxy relies on immediate evaluation to
2445 // establish its observers.
2447 // TODO: Replace this with an efficient callback that the EachProxy
2449 if (this._parent && this._parent._key === '@each') {
2454 var ChainNodePrototype = ChainNode.prototype;
2456 ChainNodePrototype.value = function() {
2457 if (this._value === undefined && this._watching) {
2458 var obj = this._parent.value();
2459 this._value = (obj && !isProto(obj)) ? get(obj, this._key) : undefined;
2464 ChainNodePrototype.destroy = function() {
2465 if (this._watching) {
2466 var obj = this._object;
2467 if (obj) { removeChainWatcher(obj, this._key, this); }
2468 this._watching = false; // so future calls do nothing
2472 // copies a top level object only
2473 ChainNodePrototype.copy = function(obj) {
2474 var ret = new ChainNode(null, null, obj, this._separator),
2475 paths = this._paths, path;
2476 for (path in paths) {
2477 if (paths[path] <= 0) { continue; } // this check will also catch non-number vals.
2483 // called on the root node of a chain to setup watchers on the specified
2485 ChainNodePrototype.add = function(path) {
2486 var obj, tuple, key, src, separator, paths;
2488 paths = this._paths;
2489 paths[path] = (paths[path] || 0) + 1;
2492 tuple = normalizeTuple(obj, path);
2494 // the path was a local path
2495 if (tuple[0] && tuple[0] === obj) {
2497 key = firstKey(path);
2498 path = path.slice(key.length+1);
2500 // global path, but object does not exist yet.
2501 // put into a queue and try to connect later.
2502 } else if (!tuple[0]) {
2503 pendingQueue.push([this, path]);
2507 // global path, and object already exists
2510 key = path.slice(0, 0-(tuple[1].length+1));
2511 separator = path.slice(key.length, key.length+1);
2516 this.chain(key, path, src, separator);
2519 // called on the root node of a chain to teardown watcher on the specified
2521 ChainNodePrototype.remove = function(path) {
2522 var obj, tuple, key, src, paths;
2524 paths = this._paths;
2525 if (paths[path] > 0) { paths[path]--; }
2528 tuple = normalizeTuple(obj, path);
2529 if (tuple[0] === obj) {
2531 key = firstKey(path);
2532 path = path.slice(key.length+1);
2535 key = path.slice(0, 0-(tuple[1].length+1));
2540 this.unchain(key, path);
2543 ChainNodePrototype.count = 0;
2545 ChainNodePrototype.chain = function(key, path, src, separator) {
2546 var chains = this._chains, node;
2547 if (!chains) { chains = this._chains = {}; }
2550 if (!node) { node = chains[key] = new ChainNode(this, key, src, separator); }
2551 node.count++; // count chains...
2553 // chain rest of path if there is one
2554 if (path && path.length>0) {
2555 key = firstKey(path);
2556 path = path.slice(key.length+1);
2557 node.chain(key, path); // NOTE: no src means it will observe changes...
2561 ChainNodePrototype.unchain = function(key, path) {
2562 var chains = this._chains, node = chains[key];
2564 // unchain rest of path first...
2565 if (path && path.length>1) {
2566 key = firstKey(path);
2567 path = path.slice(key.length+1);
2568 node.unchain(key, path);
2571 // delete node if needed.
2573 if (node.count<=0) {
2574 delete chains[node._key];
2580 ChainNodePrototype.willChange = function() {
2581 var chains = this._chains;
2583 for(var key in chains) {
2584 if (!chains.hasOwnProperty(key)) { continue; }
2585 chains[key].willChange();
2589 if (this._parent) { this._parent.chainWillChange(this, this._key, 1); }
2592 ChainNodePrototype.chainWillChange = function(chain, path, depth) {
2593 if (this._key) { path = this._key + this._separator + path; }
2596 this._parent.chainWillChange(this, path, depth+1);
2598 if (depth > 1) { Ember.propertyWillChange(this.value(), path); }
2599 path = 'this.' + path;
2600 if (this._paths[path] > 0) { Ember.propertyWillChange(this.value(), path); }
2604 ChainNodePrototype.chainDidChange = function(chain, path, depth) {
2605 if (this._key) { path = this._key + this._separator + path; }
2607 this._parent.chainDidChange(this, path, depth+1);
2609 if (depth > 1) { Ember.propertyDidChange(this.value(), path); }
2610 path = 'this.' + path;
2611 if (this._paths[path] > 0) { Ember.propertyDidChange(this.value(), path); }
2615 ChainNodePrototype.didChange = function(suppressEvent) {
2616 // invalidate my own value first.
2617 if (this._watching) {
2618 var obj = this._parent.value();
2619 if (obj !== this._object) {
2620 removeChainWatcher(this._object, this._key, this);
2622 addChainWatcher(obj, this._key, this);
2624 this._value = undefined;
2626 // Special-case: the EachProxy relies on immediate evaluation to
2627 // establish its observers.
2628 if (this._parent && this._parent._key === '@each')
2632 // then notify chains...
2633 var chains = this._chains;
2635 for(var key in chains) {
2636 if (!chains.hasOwnProperty(key)) { continue; }
2637 chains[key].didChange(suppressEvent);
2641 if (suppressEvent) { return; }
2643 // and finally tell parent about my path changing...
2644 if (this._parent) { this._parent.chainDidChange(this, this._key, 1); }
2647 // get the chains for the current object. If the current object has
2648 // chains inherited from the proto they will be cloned and reconfigured for
2649 // the current object.
2650 function chainsFor(obj) {
2651 var m = metaFor(obj), ret = m.chains;
2653 ret = m.chains = new ChainNode(null, null, obj);
2654 } else if (ret.value() !== obj) {
2655 ret = m.chains = ret.copy(obj);
2660 function notifyChains(obj, m, keyName, methodName, arg) {
2661 var nodes = m.chainWatchers;
2663 if (!nodes || nodes.__emberproto__ !== obj) { return; } // nothing to do
2665 nodes = nodes[keyName];
2666 if (!nodes) { return; }
2668 for(var key in nodes) {
2669 if (!nodes.hasOwnProperty(key)) { continue; }
2670 nodes[key][methodName](arg);
2674 Ember.overrideChains = function(obj, keyName, m) {
2675 notifyChains(obj, m, keyName, 'didChange', true);
2678 function chainsWillChange(obj, keyName, m) {
2679 notifyChains(obj, m, keyName, 'willChange');
2682 function chainsDidChange(obj, keyName, m) {
2683 notifyChains(obj, m, keyName, 'didChange');
2686 // ..........................................................
2693 Starts watching a property on an object. Whenever the property changes,
2694 invokes Ember.propertyWillChange and Ember.propertyDidChange. This is the
2695 primitive used by observers and dependent keys; usually you will never call
2696 this method directly but instead use higher level methods like
2697 Ember.addObserver().
2702 @param {String} keyName
2704 Ember.watch = function(obj, keyName) {
2705 // can't watch length on Array - it is special...
2706 if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
2708 var m = metaFor(obj), watching = m.watching, desc;
2710 // activate watching first time
2711 if (!watching[keyName]) {
2712 watching[keyName] = 1;
2713 if (isKeyName(keyName)) {
2714 desc = m.descs[keyName];
2715 if (desc && desc.willWatch) { desc.willWatch(obj, keyName); }
2717 if ('function' === typeof obj.willWatchProperty) {
2718 obj.willWatchProperty(keyName);
2721 if (MANDATORY_SETTER && keyName in obj) {
2722 m.values[keyName] = obj[keyName];
2723 o_defineProperty(obj, keyName, {
2727 Ember.assert('Must use Ember.set() to access this property', false);
2730 var meta = this[META_KEY];
2731 return meta && meta.values[keyName];
2736 chainsFor(obj).add(keyName);
2740 watching[keyName] = (watching[keyName] || 0) + 1;
2745 Ember.isWatching = function isWatching(obj, key) {
2746 var meta = obj[META_KEY];
2747 return (meta && meta.watching[key]) > 0;
2750 Ember.watch.flushPending = flushPendingChains;
2752 Ember.unwatch = function(obj, keyName) {
2753 // can't watch length on Array - it is special...
2754 if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
2756 var m = metaFor(obj), watching = m.watching, desc;
2758 if (watching[keyName] === 1) {
2759 watching[keyName] = 0;
2761 if (isKeyName(keyName)) {
2762 desc = m.descs[keyName];
2763 if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); }
2765 if ('function' === typeof obj.didUnwatchProperty) {
2766 obj.didUnwatchProperty(keyName);
2769 if (MANDATORY_SETTER && keyName in obj) {
2770 o_defineProperty(obj, keyName, {
2774 value: m.values[keyName]
2776 delete m.values[keyName];
2779 chainsFor(obj).remove(keyName);
2782 } else if (watching[keyName]>1) {
2783 watching[keyName]--;
2792 Call on an object when you first beget it from another object. This will
2793 setup any chained watchers on the object instance as needed. This method is
2794 safe to call multiple times.
2800 Ember.rewatch = function(obj) {
2801 var m = metaFor(obj, false), chains = m.chains;
2803 // make sure the object has its own guid.
2804 if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
2805 Ember.generateGuid(obj, 'ember');
2808 // make sure any chained watchers update.
2809 if (chains && chains.value() !== obj) {
2810 m.chains = chains.copy(obj);
2816 Ember.finishChains = function(obj) {
2817 var m = metaFor(obj, false), chains = m.chains;
2819 if (chains.value() !== obj) {
2820 m.chains = chains = chains.copy(obj);
2822 chains.didChange(true);
2826 // ..........................................................
2831 This function is called just before an object property is about to change.
2832 It will notify any before observers and prepare caches among other things.
2834 Normally you will not need to call this method directly but if for some
2835 reason you can't directly watch a property you can invoke this method
2836 manually along with `Ember.propertyDidChange()` which you should call just
2837 after the property value changes.
2839 @method propertyWillChange
2841 @param {Object} obj The object with the property that will change
2842 @param {String} keyName The property key (or path) that will change.
2845 function propertyWillChange(obj, keyName, value) {
2846 var m = metaFor(obj, false),
2847 watching = m.watching[keyName] > 0 || keyName === 'length',
2849 desc = m.descs[keyName];
2851 if (!watching) { return; }
2852 if (proto === obj) { return; }
2853 if (desc && desc.willChange) { desc.willChange(obj, keyName); }
2854 dependentKeysWillChange(obj, keyName, m);
2855 chainsWillChange(obj, keyName, m);
2856 Ember.notifyBeforeObservers(obj, keyName);
2859 Ember.propertyWillChange = propertyWillChange;
2862 This function is called just after an object property has changed.
2863 It will notify any observers and clear caches among other things.
2865 Normally you will not need to call this method directly but if for some
2866 reason you can't directly watch a property you can invoke this method
2867 manually along with `Ember.propertyWilLChange()` which you should call just
2868 before the property value changes.
2870 @method propertyDidChange
2872 @param {Object} obj The object with the property that will change
2873 @param {String} keyName The property key (or path) that will change.
2876 function propertyDidChange(obj, keyName) {
2877 var m = metaFor(obj, false),
2878 watching = m.watching[keyName] > 0 || keyName === 'length',
2880 desc = m.descs[keyName];
2882 if (proto === obj) { return; }
2884 // shouldn't this mean that we're watching this key?
2885 if (desc && desc.didChange) { desc.didChange(obj, keyName); }
2886 if (!watching && keyName !== 'length') { return; }
2888 dependentKeysDidChange(obj, keyName, m);
2889 chainsDidChange(obj, keyName, m);
2890 Ember.notifyObservers(obj, keyName);
2893 Ember.propertyDidChange = propertyDidChange;
2895 var NODE_STACK = [];
2898 Tears down the meta on an object so that it can be garbage collected.
2899 Multiple calls will have no effect.
2903 @param {Object} obj the object to destroy
2906 Ember.destroy = function (obj) {
2907 var meta = obj[META_KEY], node, nodes, key, nodeObject;
2909 obj[META_KEY] = null;
2910 // remove chainWatchers to remove circular references that would prevent GC
2913 NODE_STACK.push(node);
2915 while (NODE_STACK.length > 0) {
2916 node = NODE_STACK.pop();
2918 nodes = node._chains;
2920 for (key in nodes) {
2921 if (nodes.hasOwnProperty(key)) {
2922 NODE_STACK.push(nodes[key]);
2926 // remove chainWatcher in node object
2927 if (node._watching) {
2928 nodeObject = node._object;
2930 removeChainWatcher(nodeObject, node._key, node);
2947 Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properties are always cached by default. Use `volatile` if you don't want caching.", Ember.ENV.CP_DEFAULT_CACHEABLE !== false);
2950 var get = Ember.get,
2951 metaFor = Ember.meta,
2952 guidFor = Ember.guidFor,
2954 o_create = Ember.create,
2955 META_KEY = Ember.META_KEY,
2956 watch = Ember.watch,
2957 unwatch = Ember.unwatch;
2959 // ..........................................................
2966 // 'keyName': count,
2967 // __emberproto__: SRC_OBJ [to detect clones]
2969 // __emberproto__: SRC_OBJ
2973 This function returns a map of unique dependencies for a
2974 given object and key.
2976 function keysForDep(obj, depsMeta, depKey) {
2977 var keys = depsMeta[depKey];
2979 // if there are no dependencies yet for a the given key
2980 // create a new empty list of dependencies for the key
2981 keys = depsMeta[depKey] = { __emberproto__: obj };
2982 } else if (keys.__emberproto__ !== obj) {
2983 // otherwise if the dependency list is inherited from
2984 // a superclass, clone the hash
2985 keys = depsMeta[depKey] = o_create(keys);
2986 keys.__emberproto__ = obj;
2991 /* return obj[META_KEY].deps */
2992 function metaForDeps(obj, meta) {
2993 var deps = meta.deps;
2994 // If the current object has no dependencies...
2996 // initialize the dependencies with a pointer back to
2997 // the current object
2998 deps = meta.deps = { __emberproto__: obj };
2999 } else if (deps.__emberproto__ !== obj) {
3000 // otherwise if the dependencies are inherited from the
3001 // object's superclass, clone the deps
3002 deps = meta.deps = o_create(deps);
3003 deps.__emberproto__ = obj;
3008 function addDependentKeys(desc, obj, keyName, meta) {
3009 // the descriptor has a list of dependent keys, so
3010 // add all of its dependent keys.
3011 var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
3012 if (!depKeys) return;
3014 depsMeta = metaForDeps(obj, meta);
3016 for(idx = 0, len = depKeys.length; idx < len; idx++) {
3017 depKey = depKeys[idx];
3018 // Lookup keys meta for depKey
3019 keys = keysForDep(obj, depsMeta, depKey);
3020 // Increment the number of times depKey depends on keyName.
3021 keys[keyName] = (keys[keyName] || 0) + 1;
3027 function removeDependentKeys(desc, obj, keyName, meta) {
3028 // the descriptor has a list of dependent keys, so
3029 // add all of its dependent keys.
3030 var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
3031 if (!depKeys) return;
3033 depsMeta = metaForDeps(obj, meta);
3035 for(idx = 0, len = depKeys.length; idx < len; idx++) {
3036 depKey = depKeys[idx];
3037 // Lookup keys meta for depKey
3038 keys = keysForDep(obj, depsMeta, depKey);
3039 // Increment the number of times depKey depends on keyName.
3040 keys[keyName] = (keys[keyName] || 0) - 1;
3042 unwatch(obj, depKey);
3046 // ..........................................................
3047 // COMPUTED PROPERTY
3051 @class ComputedProperty
3053 @extends Ember.Descriptor
3056 function ComputedProperty(func, opts) {
3058 this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
3059 this._dependentKeys = opts && opts.dependentKeys;
3062 Ember.ComputedProperty = ComputedProperty;
3063 ComputedProperty.prototype = new Ember.Descriptor();
3065 var ComputedPropertyPrototype = ComputedProperty.prototype;
3068 Call on a computed property to set it into cacheable mode. When in this
3069 mode the computed property will automatically cache the return value of
3070 your function until one of the dependent keys changes.
3072 MyApp.president = Ember.Object.create({
3073 fullName: function() {
3074 return this.get('firstName') + ' ' + this.get('lastName');
3076 // After calculating the value of this function, Ember.js will
3077 // return that value without re-executing this function until
3078 // one of the dependent properties change.
3079 }.property('firstName', 'lastName')
3082 Properties are cacheable by default.
3085 @param {Boolean} aFlag optional set to false to disable caching
3088 ComputedPropertyPrototype.cacheable = function(aFlag) {
3089 this._cacheable = aFlag !== false;
3094 Call on a computed property to set it into non-cached mode. When in this
3095 mode the computed property will not automatically cache the return value.
3097 MyApp.outsideService = Ember.Object.create({
3099 return OutsideService.getValue();
3100 }.property().volatile()
3106 ComputedPropertyPrototype.volatile = function() {
3107 return this.cacheable(false);
3111 Sets the dependent keys on this computed property. Pass any number of
3112 arguments containing key paths that this computed property depends on.
3114 MyApp.president = Ember.Object.create({
3115 fullName: Ember.computed(function() {
3116 return this.get('firstName') + ' ' + this.get('lastName');
3118 // Tell Ember.js that this computed property depends on firstName
3120 }).property('firstName', 'lastName')
3124 @param {String} path* zero or more property paths
3127 ComputedPropertyPrototype.property = function() {
3129 for (var i = 0, l = arguments.length; i < l; i++) {
3130 args.push(arguments[i]);
3132 this._dependentKeys = args;
3137 In some cases, you may want to annotate computed properties with additional
3138 metadata about how they function or what values they operate on. For example,
3139 computed property functions may close over variables that are then no longer
3140 available for introspection.
3142 You can pass a hash of these values to a computed property like this:
3144 person: function() {
3145 var personId = this.get('personId');
3146 return App.Person.create({ id: personId });
3147 }.property().meta({ type: App.Person })
3149 The hash that you pass to the `meta()` function will be saved on the
3150 computed property descriptor under the `_meta` key. Ember runtime
3151 exposes a public API for retrieving these values from classes,
3152 via the `metaForProperty()` function.
3159 ComputedPropertyPrototype.meta = function(meta) {
3160 if (arguments.length === 0) {
3161 return this._meta || {};
3168 /* impl descriptor API */
3169 ComputedPropertyPrototype.willWatch = function(obj, keyName) {
3170 // watch already creates meta for this instance
3171 var meta = obj[META_KEY];
3172 Ember.assert('watch should have setup meta to be writable', meta.source === obj);
3173 if (!(keyName in meta.cache)) {
3174 addDependentKeys(this, obj, keyName, meta);
3178 ComputedPropertyPrototype.didUnwatch = function(obj, keyName) {
3179 var meta = obj[META_KEY];
3180 Ember.assert('unwatch should have setup meta to be writable', meta.source === obj);
3181 if (!(keyName in meta.cache)) {
3182 // unwatch already creates meta for this instance
3183 removeDependentKeys(this, obj, keyName, meta);
3187 /* impl descriptor API */
3188 ComputedPropertyPrototype.didChange = function(obj, keyName) {
3189 // _suspended is set via a CP.set to ensure we don't clear
3190 // the cached value set by the setter
3191 if (this._cacheable && this._suspended !== obj) {
3192 var meta = metaFor(obj);
3193 if (keyName in meta.cache) {
3194 delete meta.cache[keyName];
3195 if (!meta.watching[keyName]) {
3196 removeDependentKeys(this, obj, keyName, meta);
3202 /* impl descriptor API */
3203 ComputedPropertyPrototype.get = function(obj, keyName) {
3204 var ret, cache, meta;
3205 if (this._cacheable) {
3206 meta = metaFor(obj);
3208 if (keyName in cache) { return cache[keyName]; }
3209 ret = cache[keyName] = this.func.call(obj, keyName);
3210 if (!meta.watching[keyName]) {
3211 addDependentKeys(this, obj, keyName, meta);
3214 ret = this.func.call(obj, keyName);
3219 /* impl descriptor API */
3220 ComputedPropertyPrototype.set = function(obj, keyName, value) {
3221 var cacheable = this._cacheable,
3222 meta = metaFor(obj, cacheable),
3223 watched = meta.watching[keyName],
3224 oldSuspended = this._suspended,
3225 hadCachedValue = false,
3227 this._suspended = obj;
3229 ret = this.func.call(obj, keyName, value);
3231 if (cacheable && keyName in meta.cache) {
3232 if (meta.cache[keyName] === ret) {
3235 hadCachedValue = true;
3238 if (watched) { Ember.propertyWillChange(obj, keyName); }
3240 if (cacheable && hadCachedValue) {
3241 delete meta.cache[keyName];
3245 if (!watched && !hadCachedValue) {
3246 addDependentKeys(this, obj, keyName, meta);
3248 meta.cache[keyName] = ret;
3251 if (watched) { Ember.propertyDidChange(obj, keyName); }
3253 this._suspended = oldSuspended;
3258 /* called when property is defined */
3259 ComputedPropertyPrototype.setup = function(obj, keyName) {
3260 var meta = obj[META_KEY];
3261 if (meta && meta.watching[keyName]) {
3262 addDependentKeys(this, obj, keyName, metaFor(obj));
3266 /* called before property is overridden */
3267 ComputedPropertyPrototype.teardown = function(obj, keyName) {
3268 var meta = metaFor(obj);
3270 if (meta.watching[keyName] || keyName in meta.cache) {
3271 removeDependentKeys(this, obj, keyName, meta);
3274 if (this._cacheable) { delete meta.cache[keyName]; }
3276 return null; // no value to restore
3281 This helper returns a new property descriptor that wraps the passed
3282 computed property function. You can use this helper to define properties
3283 with mixins or via Ember.defineProperty().
3285 The function you pass will be used to both get and set property values.
3286 The function should accept two parameters, key and value. If value is not
3287 undefined you should set the value first. In either case return the
3288 current value of the property.
3292 @param {Function} func The computed property function.
3293 @return {Ember.ComputedProperty} property descriptor instance
3295 Ember.computed = function(func) {
3298 if (arguments.length > 1) {
3299 args = a_slice.call(arguments, 0, -1);
3300 func = a_slice.call(arguments, -1)[0];
3303 var cp = new ComputedProperty(func);
3306 cp.property.apply(cp, args);
3313 Returns the cached value for a property, if one exists.
3314 This can be useful for peeking at the value of a computed
3315 property that is generated lazily, without accidentally causing
3320 @param {Object} obj the object whose property you want to check
3321 @param {String} key the name of the property whose cached value you want
3324 Ember.cacheFor = function cacheFor(obj, key) {
3325 var cache = metaFor(obj, false).cache;
3327 if (cache && key in cache) {
3333 @method computed.not
3335 @param {String} dependentKey
3337 Ember.computed.not = function(dependentKey) {
3338 return Ember.computed(dependentKey, function(key) {
3339 return !get(this, dependentKey);
3344 @method computed.empty
3346 @param {String} dependentKey
3348 Ember.computed.empty = function(dependentKey) {
3349 return Ember.computed(dependentKey, function(key) {
3350 var val = get(this, dependentKey);
3351 return val === undefined || val === null || val === '' || (Ember.isArray(val) && get(val, 'length') === 0);
3356 @method computed.bool
3358 @param {String} dependentKey
3360 Ember.computed.bool = function(dependentKey) {
3361 return Ember.computed(dependentKey, function(key) {
3362 return !!get(this, dependentKey);
3375 var o_create = Ember.create,
3377 metaPath = Ember.metaPath,
3378 guidFor = Ember.guidFor,
3382 The event system uses a series of nested hashes to store listeners on an
3383 object. When a listener is registered, or when an event arrives, these
3384 hashes are consulted to determine which target and action pair to invoke.
3386 The hashes are stored in the object's meta hash, and look like this:
3388 // Object's meta hash
3390 listeners: { // variable name: `listenerSet`
3391 "foo:changed": { // variable name: `targetSet`
3392 [targetGuid]: { // variable name: `actionSet`
3393 [methodGuid]: { // variable name: `action`
3394 target: [Object object],
3395 method: [Function function]
3404 // Gets the set of all actions, keyed on the guid of each action's
3406 function actionSetFor(obj, eventName, target, writable) {
3407 return metaPath(obj, ['listeners', eventName, guidFor(target)], writable);
3410 // Gets the set of all targets, keyed on the guid of each action's
3412 function targetSetFor(obj, eventName) {
3413 var listenerSet = meta(obj, false).listeners;
3414 if (!listenerSet) { return false; }
3416 return listenerSet[eventName] || false;
3419 // TODO: This knowledge should really be a part of the
3421 var SKIP_PROPERTIES = { __ember_source__: true };
3423 function iterateSet(targetSet, callback) {
3424 if (!targetSet) { return false; }
3425 // Iterate through all elements of the target set
3426 for(var targetGuid in targetSet) {
3427 if (SKIP_PROPERTIES[targetGuid]) { continue; }
3429 var actionSet = targetSet[targetGuid];
3431 // Iterate through the elements of the action set
3432 for(var methodGuid in actionSet) {
3433 if (SKIP_PROPERTIES[methodGuid]) { continue; }
3435 var action = actionSet[methodGuid];
3437 if (callback(action) === true) {
3447 function invokeAction(action, params, sender) {
3448 var method = action.method, target = action.target;
3449 // If there is no target, the target is the object
3450 // on which the event was fired.
3451 if (!target) { target = sender; }
3452 if ('string' === typeof method) { method = target[method]; }
3454 method.apply(target, params);
3456 method.apply(target);
3460 function targetSetUnion(obj, eventName, targetSet) {
3461 iterateSet(targetSetFor(obj, eventName), function (action) {
3462 var targetGuid = guidFor(action.target),
3463 methodGuid = guidFor(action.method),
3464 actionSet = targetSet[targetGuid];
3465 if (!actionSet) actionSet = targetSet[targetGuid] = {};
3466 actionSet[methodGuid] = action;
3470 function targetSetDiff(obj, eventName, targetSet) {
3471 var diffTargetSet = {};
3472 iterateSet(targetSetFor(obj, eventName), function (action) {
3473 var targetGuid = guidFor(action.target),
3474 methodGuid = guidFor(action.method),
3475 actionSet = targetSet[targetGuid],
3476 diffActionSet = diffTargetSet[targetGuid];
3477 if (!actionSet) actionSet = targetSet[targetGuid] = {};
3478 if (actionSet[methodGuid]) return;
3479 actionSet[methodGuid] = action;
3480 if (!diffActionSet) diffActionSet = diffTargetSet[targetGuid] = {};
3481 diffActionSet[methodGuid] = action;
3483 return diffTargetSet;
3487 Add an event listener
3492 @param {String} eventName
3493 @param {Object|Function} targetOrMethod A target object or a function
3494 @param {Function|String} method A function or the name of a function to be called on `target`
3496 function addListener(obj, eventName, target, method, guid) {
3497 Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
3499 if (!method && 'function' === typeof target) {
3504 var actionSet = actionSetFor(obj, eventName, target, true),
3505 // guid is used in case we wrapp given method to register
3506 // listener with method guid instead of the wrapper guid
3507 methodGuid = guid || guidFor(method);
3509 if (!actionSet[methodGuid]) {
3510 actionSet[methodGuid] = { target: target, method: method };
3513 if ('function' === typeof obj.didAddListener) {
3514 obj.didAddListener(eventName, target, method);
3519 Remove an event listener
3521 Arguments should match those passed to {{#crossLink "Ember/addListener"}}{{/crossLink}}
3523 @method removeListener
3526 @param {String} eventName
3527 @param {Object|Function} targetOrMethod A target object or a function
3528 @param {Function|String} method A function or the name of a function to be called on `target`
3530 function removeListener(obj, eventName, target, method) {
3531 Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
3533 if (!method && 'function' === typeof target) {
3538 function _removeListener(target, method) {
3539 var actionSet = actionSetFor(obj, eventName, target, true),
3540 methodGuid = guidFor(method);
3542 // we can't simply delete this parameter, because if we do, we might
3543 // re-expose the property from the prototype chain.
3544 if (actionSet && actionSet[methodGuid]) { actionSet[methodGuid] = null; }
3546 if ('function' === typeof obj.didRemoveListener) {
3547 obj.didRemoveListener(eventName, target, method);
3552 _removeListener(target, method);
3554 iterateSet(targetSetFor(obj, eventName), function(action) {
3555 _removeListener(action.target, action.method);
3563 Suspend listener during callback.
3565 This should only be used by the target of the event listener
3566 when it is taking an action that would cause the event, e.g.
3567 an object might suspend its property change listener while it is
3568 setting that property.
3570 @method suspendListener
3573 @param {String} eventName
3574 @param {Object|Function} targetOrMethod A target object or a function
3575 @param {Function|String} method A function or the name of a function to be called on `target`
3576 @param {Function} callback
3578 function suspendListener(obj, eventName, target, method, callback) {
3579 if (!method && 'function' === typeof target) {
3584 var actionSet = actionSetFor(obj, eventName, target, true),
3585 methodGuid = guidFor(method),
3586 action = actionSet && actionSet[methodGuid];
3588 actionSet[methodGuid] = null;
3590 return callback.call(target);
3592 actionSet[methodGuid] = action;
3599 Suspend listener during callback.
3601 This should only be used by the target of the event listener
3602 when it is taking an action that would cause the event, e.g.
3603 an object might suspend its property change listener while it is
3604 setting that property.
3606 @method suspendListener
3609 @param {Array} eventName Array of event names
3610 @param {Object|Function} targetOrMethod A target object or a function
3611 @param {Function|String} method A function or the name of a function to be called on `target`
3612 @param {Function} callback
3614 function suspendListeners(obj, eventNames, target, method, callback) {
3615 if (!method && 'function' === typeof target) {
3620 var oldActions = [],
3622 eventName, actionSet, methodGuid, action, i, l;
3624 for (i=0, l=eventNames.length; i<l; i++) {
3625 eventName = eventNames[i];
3626 actionSet = actionSetFor(obj, eventName, target, true),
3627 methodGuid = guidFor(method);
3629 oldActions.push(actionSet && actionSet[methodGuid]);
3630 actionSets.push(actionSet);
3632 actionSet[methodGuid] = null;
3636 return callback.call(target);
3638 for (i=0, l=oldActions.length; i<l; i++) {
3639 eventName = eventNames[i];
3640 actionSets[i][methodGuid] = oldActions[i];
3648 Return a list of currently watched events
3650 @method watchedEvents
3654 function watchedEvents(obj) {
3655 var listeners = meta(obj, false).listeners, ret = [];
3658 for(var eventName in listeners) {
3659 if (!SKIP_PROPERTIES[eventName] && listeners[eventName]) {
3660 ret.push(eventName);
3671 @param {String} eventName
3672 @param {Array} params
3675 function sendEvent(obj, eventName, params, targetSet) {
3676 // first give object a chance to handle it
3677 if (obj !== Ember && 'function' === typeof obj.sendEvent) {
3678 obj.sendEvent(eventName, params);
3681 if (!targetSet) targetSet = targetSetFor(obj, eventName);
3683 iterateSet(targetSet, function (action) {
3684 invokeAction(action, params, obj);
3691 @method hasListeners
3694 @param {String} eventName
3696 function hasListeners(obj, eventName) {
3697 if (iterateSet(targetSetFor(obj, eventName), function() { return true; })) {
3701 // no listeners! might as well clean this up so it is faster later.
3702 var set = metaPath(obj, ['listeners'], true);
3703 set[eventName] = null;
3710 @method listenersFor
3713 @param {String} eventName
3715 function listenersFor(obj, eventName) {
3717 iterateSet(targetSetFor(obj, eventName), function (action) {
3718 ret.push([action.target, action.method]);
3723 Ember.addListener = addListener;
3724 Ember.removeListener = removeListener;
3725 Ember._suspendListener = suspendListener;
3726 Ember._suspendListeners = suspendListeners;
3727 Ember.sendEvent = sendEvent;
3728 Ember.hasListeners = hasListeners;
3729 Ember.watchedEvents = watchedEvents;
3730 Ember.listenersFor = listenersFor;
3731 Ember.listenersDiff = targetSetDiff;
3732 Ember.listenersUnion = targetSetUnion;
3739 // Ember.watch.flushPending
3740 // Ember.beginPropertyChanges, Ember.endPropertyChanges
3747 // ..........................................................
3751 var slice = [].slice,
3752 forEach = Ember.ArrayPolyfills.forEach;
3754 // invokes passed params - normalizing so you can pass target/func,
3755 // target/string or just func
3756 function invoke(target, method, args, ignore) {
3758 if (method === undefined) {
3763 if ('string' === typeof method) { method = target[method]; }
3764 if (args && ignore > 0) {
3765 args = args.length > ignore ? slice.call(args, ignore) : null;
3768 return Ember.handleErrors(function() {
3769 // IE8's Function.prototype.apply doesn't accept undefined/null arguments.
3770 return method.apply(target || this, args || []);
3775 // ..........................................................
3779 var timerMark; // used by timers...
3782 Ember RunLoop (Private)
3789 var RunLoop = function(prev) {
3790 this._prev = prev || null;
3791 this.onceTimers = {};
3794 RunLoop.prototype = {
3809 // ..........................................................
3815 @param {String} queueName
3819 schedule: function(queueName, target, method) {
3820 var queues = this._queues, queue;
3821 if (!queues) { queues = this._queues = {}; }
3822 queue = queues[queueName];
3823 if (!queue) { queue = queues[queueName] = []; }
3825 var args = arguments.length > 3 ? slice.call(arguments, 3) : null;
3826 queue.push({ target: target, method: method, args: args });
3832 @param {String} queueName
3834 flush: function(queueName) {
3835 var queueNames, idx, len, queue, log;
3837 if (!this._queues) { return this; } // nothing to do
3839 function iter(item) {
3840 invoke(item.target, item.method, item.args);
3843 Ember.watch.flushPending(); // make sure all chained watchers are setup
3846 while (this._queues && (queue = this._queues[queueName])) {
3847 this._queues[queueName] = null;
3849 // the sync phase is to allow property changes to propagate. don't
3850 // invoke observers until that is finished.
3851 if (queueName === 'sync') {
3852 log = Ember.LOG_BINDINGS;
3853 if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
3855 Ember.beginPropertyChanges();
3857 forEach.call(queue, iter);
3859 Ember.endPropertyChanges();
3862 if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
3865 forEach.call(queue, iter);
3870 queueNames = Ember.run.queues;
3871 len = queueNames.length;
3876 queueName = queueNames[idx];
3877 queue = this._queues && this._queues[queueName];
3878 delete this._queues[queueName];
3881 // the sync phase is to allow property changes to propagate. don't
3882 // invoke observers until that is finished.
3883 if (queueName === 'sync') {
3884 log = Ember.LOG_BINDINGS;
3885 if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
3887 Ember.beginPropertyChanges();
3889 forEach.call(queue, iter);
3891 Ember.endPropertyChanges();
3894 if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
3896 forEach.call(queue, iter);
3900 // Loop through prior queues
3901 for (var i = 0; i <= idx; i++) {
3902 if (this._queues && this._queues[queueNames[i]]) {
3903 // Start over at the first queue with contents
3920 Ember.RunLoop = RunLoop;
3922 // ..........................................................
3923 // Ember.run - this is ideally the only public API the dev sees
3927 Runs the passed target and method inside of a RunLoop, ensuring any
3928 deferred actions including bindings and views updates are flushed at the
3931 Normally you should not need to invoke this method yourself. However if
3932 you are implementing raw event handlers when interfacing with other
3933 libraries or plugins, you should probably wrap all of your code inside this
3936 Ember.run(function(){
3937 // code to be execute within a RunLoop
3944 @param {Object} [target] target of method to call
3945 @param {Function|String} method Method to invoke.
3946 May be a function or a string. If you pass a string
3947 then it will be looked up on the passed target.
3948 @param {Object} [args*] Any additional arguments you wish to pass to the method.
3949 @return {Object} return value from invoking the passed function.
3951 Ember.run = function(target, method) {
3955 if (target || method) { ret = invoke(target, method, arguments, 2); }
3962 var run = Ember.run;
3966 Begins a new RunLoop. Any deferred actions invoked after the begin will
3967 be buffered until you invoke a matching call to Ember.run.end(). This is
3968 an lower-level way to use a RunLoop instead of using Ember.run().
3971 // code to be execute within a RunLoop
3977 Ember.run.begin = function() {
3978 run.currentRunLoop = new RunLoop(run.currentRunLoop);
3982 Ends a RunLoop. This must be called sometime after you call Ember.run.begin()
3983 to flush any deferred actions. This is a lower-level way to use a RunLoop
3984 instead of using Ember.run().
3987 // code to be execute within a RunLoop
3993 Ember.run.end = function() {
3994 Ember.assert('must have a current run loop', run.currentRunLoop);
3996 run.currentRunLoop.end();
3999 run.currentRunLoop = run.currentRunLoop.prev();
4004 Array of named queues. This array determines the order in which queues
4005 are flushed at the end of the RunLoop. You can define your own queues by
4006 simply adding the queue name to this array. Normally you should not need
4007 to inspect or modify this property.
4011 @default ['sync', 'actions', 'destroy', 'timers']
4013 Ember.run.queues = ['sync', 'actions', 'destroy', 'timers'];
4016 Adds the passed target/method and any optional arguments to the named
4017 queue to be executed at the end of the RunLoop. If you have not already
4018 started a RunLoop when calling this method one will be started for you
4021 At the end of a RunLoop, any methods scheduled in this way will be invoked.
4022 Methods will be invoked in an order matching the named queues defined in
4023 the run.queues property.
4025 Ember.run.schedule('timers', this, function(){
4026 // this will be executed at the end of the RunLoop, when timers are run
4027 console.log("scheduled on timers queue");
4029 Ember.run.schedule('sync', this, function(){
4030 // this will be executed at the end of the RunLoop, when bindings are synced
4031 console.log("scheduled on sync queue");
4033 // Note the functions will be run in order based on the run queues order. Output would be:
4034 // scheduled on sync queue
4035 // scheduled on timers queue
4038 @param {String} queue The name of the queue to schedule against.
4039 Default queues are 'sync' and 'actions'
4041 @param {Object} [target] target object to use as the context when invoking a method.
4043 @param {String|Function} method The method to invoke. If you pass a string it
4044 will be resolved on the target object at the time the scheduled item is
4045 invoked allowing you to change the target function.
4047 @param {Object} [arguments*] Optional arguments to be passed to the queued method.
4051 Ember.run.schedule = function(queue, target, method) {
4052 var loop = run.autorun();
4053 loop.schedule.apply(loop, arguments);
4056 var scheduledAutorun;
4057 function autorun() {
4058 scheduledAutorun = null;
4059 if (run.currentRunLoop) { run.end(); }
4062 // Used by global test teardown
4063 Ember.run.hasScheduledTimers = function() {
4064 return !!(scheduledAutorun || scheduledLater || scheduledNext);
4067 // Used by global test teardown
4068 Ember.run.cancelTimers = function () {
4069 if (scheduledAutorun) {
4070 clearTimeout(scheduledAutorun);
4071 scheduledAutorun = null;
4073 if (scheduledLater) {
4074 clearTimeout(scheduledLater);
4075 scheduledLater = null;
4077 if (scheduledNext) {
4078 clearTimeout(scheduledNext);
4079 scheduledNext = null;
4085 Begins a new RunLoop if necessary and schedules a timer to flush the
4086 RunLoop at a later time. This method is used by parts of Ember to
4087 ensure the RunLoop always finishes. You normally do not need to call this
4088 method directly. Instead use Ember.run().
4093 Ember.run.autorun();
4094 @return {Ember.RunLoop} the new current RunLoop
4096 Ember.run.autorun = function() {
4097 if (!run.currentRunLoop) {
4098 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);
4102 if (!scheduledAutorun) {
4103 scheduledAutorun = setTimeout(autorun, 1);
4107 return run.currentRunLoop;
4111 Immediately flushes any events scheduled in the 'sync' queue. Bindings
4112 use this queue so this method is a useful way to immediately force all
4113 bindings in the application to sync.
4115 You should call this method anytime you need any changed state to propagate
4116 throughout the app immediately without repainting the UI.
4123 Ember.run.sync = function() {
4125 run.currentRunLoop.flush('sync');
4128 // ..........................................................
4132 var timers = {}; // active timers...
4135 function invokeLaterTimers() {
4136 scheduledLater = null;
4137 var now = (+ new Date()), earliest = -1;
4138 for (var key in timers) {
4139 if (!timers.hasOwnProperty(key)) { continue; }
4140 var timer = timers[key];
4141 if (timer && timer.expires) {
4142 if (now >= timer.expires) {
4144 invoke(timer.target, timer.method, timer.args, 2);
4146 if (earliest<0 || (timer.expires < earliest)) earliest=timer.expires;
4151 // schedule next timeout to fire...
4152 if (earliest > 0) { scheduledLater = setTimeout(invokeLaterTimers, earliest-(+ new Date())); }
4156 Invokes the passed target/method and optional arguments after a specified
4157 period if time. The last parameter of this method must always be a number
4160 You should use this method whenever you need to run some action after a
4161 period of time instead of using setTimeout(). This method will ensure that
4162 items that expire during the same script execution cycle all execute
4163 together, which is often more efficient than using a real setTimeout.
4165 Ember.run.later(myContext, function(){
4166 // code here will execute within a RunLoop in about 500ms with this == myContext
4170 @param {Object} [target] target of method to invoke
4172 @param {Function|String} method The method to invoke.
4173 If you pass a string it will be resolved on the
4174 target at the time the method is invoked.
4176 @param {Object} [args*] Optional arguments to pass to the timeout.
4178 @param {Number} wait
4179 Number of milliseconds to wait.
4181 @return {String} a string you can use to cancel the timer in
4182 {{#crossLink "Ember/run.cancel"}}{{/crossLink}} later.
4184 Ember.run.later = function(target, method) {
4185 var args, expires, timer, guid, wait;
4187 // setTimeout compatibility...
4188 if (arguments.length===2 && 'function' === typeof target) {
4192 args = [target, method];
4194 args = slice.call(arguments);
4198 expires = (+ new Date()) + wait;
4199 timer = { target: target, method: method, expires: expires, args: args };
4200 guid = Ember.guidFor(timer);
4201 timers[guid] = timer;
4202 run.once(timers, invokeLaterTimers);
4206 function invokeOnceTimer(guid, onceTimers) {
4207 if (onceTimers[this.tguid]) { delete onceTimers[this.tguid][this.mguid]; }
4208 if (timers[guid]) { invoke(this.target, this.method, this.args); }
4209 delete timers[guid];
4212 function scheduleOnce(queue, target, method, args) {
4213 var tguid = Ember.guidFor(target),
4214 mguid = Ember.guidFor(method),
4215 onceTimers = run.autorun().onceTimers,
4216 guid = onceTimers[tguid] && onceTimers[tguid][mguid],
4219 if (guid && timers[guid]) {
4220 timers[guid].args = args; // replace args
4230 guid = Ember.guidFor(timer);
4231 timers[guid] = timer;
4232 if (!onceTimers[tguid]) { onceTimers[tguid] = {}; }
4233 onceTimers[tguid][mguid] = guid; // so it isn't scheduled more than once
4235 run.schedule(queue, timer, invokeOnceTimer, guid, onceTimers);
4242 Schedules an item to run one time during the current RunLoop. Calling
4243 this method with the same target/method combination will have no effect.
4245 Note that although you can pass optional arguments these will not be
4246 considered when looking for duplicates. New arguments will replace previous
4249 Ember.run(function(){
4250 var doFoo = function() { foo(); }
4251 Ember.run.once(myContext, doFoo);
4252 Ember.run.once(myContext, doFoo);
4253 // doFoo will only be executed once at the end of the RunLoop
4257 @param {Object} [target] target of method to invoke
4259 @param {Function|String} method The method to invoke.
4260 If you pass a string it will be resolved on the
4261 target at the time the method is invoked.
4263 @param {Object} [args*] Optional arguments to pass to the timeout.
4266 @return {Object} timer
4268 Ember.run.once = function(target, method) {
4269 return scheduleOnce('actions', target, method, slice.call(arguments, 2));
4272 Ember.run.scheduleOnce = function(queue, target, method, args) {
4273 return scheduleOnce(queue, target, method, slice.call(arguments, 3));
4277 function invokeNextTimers() {
4278 scheduledNext = null;
4279 for(var key in timers) {
4280 if (!timers.hasOwnProperty(key)) { continue; }
4281 var timer = timers[key];
4284 invoke(timer.target, timer.method, timer.args, 2);
4290 Schedules an item to run after control has been returned to the system.
4291 This is often equivalent to calling setTimeout(function...,1).
4293 Ember.run.next(myContext, function(){
4294 // code to be executed in the next RunLoop, which will be scheduled after the current one
4298 @param {Object} [target] target of method to invoke
4300 @param {Function|String} method The method to invoke.
4301 If you pass a string it will be resolved on the
4302 target at the time the method is invoked.
4304 @param {Object} [args*] Optional arguments to pass to the timeout.
4306 @return {Object} timer
4308 Ember.run.next = function(target, method) {
4313 args: slice.call(arguments),
4317 guid = Ember.guidFor(timer);
4318 timers[guid] = timer;
4320 if (!scheduledNext) { scheduledNext = setTimeout(invokeNextTimers, 1); }
4325 Cancels a scheduled item. Must be a value returned by `Ember.run.later()`,
4326 `Ember.run.once()`, or `Ember.run.next()`.
4328 var runNext = Ember.run.next(myContext, function(){
4329 // will not be executed
4331 Ember.run.cancel(runNext);
4333 var runLater = Ember.run.later(myContext, function(){
4334 // will not be executed
4336 Ember.run.cancel(runLater);
4338 var runOnce = Ember.run.once(myContext, function(){
4339 // will not be executed
4341 Ember.run.cancel(runOnce);
4344 @param {Object} timer Timer object to cancel
4347 Ember.run.cancel = function(timer) {
4348 delete timers[timer];
4358 // guidFor, isArray, meta
4359 // addObserver, removeObserver
4360 // Ember.run.schedule
4365 // ..........................................................
4370 Debug parameter you can turn on. This will log all bindings that fire to
4371 the console. This should be disabled in production code. Note that you
4372 can also enable this from the console or temporarily.
4374 @property LOG_BINDINGS
4379 Ember.LOG_BINDINGS = false || !!Ember.ENV.LOG_BINDINGS;
4381 var get = Ember.get,
4383 guidFor = Ember.guidFor,
4384 isGlobalPath = Ember.isGlobalPath;
4387 function getWithGlobals(obj, path) {
4388 return get(isGlobalPath(path) ? Ember.lookup : obj, path);
4391 // ..........................................................
4395 var Binding = function(toPath, fromPath) {
4396 this._direction = 'fwd';
4397 this._from = fromPath;
4399 this._directionMap = Ember.Map.create();
4407 Binding.prototype = {
4409 This copies the Binding so it can be connected to another object.
4412 @return {Ember.Binding}
4415 var copy = new Binding(this._to, this._from);
4416 if (this._oneWay) { copy._oneWay = true; }
4420 // ..........................................................
4425 This will set "from" property path to the specified value. It will not
4426 attempt to resolve this property path to an actual object until you
4427 connect the binding.
4429 The binding will search for the property path starting at the root object
4430 you pass when you connect() the binding. It follows the same rules as
4431 `get()` - see that method for more information.
4434 @param {String} propertyPath the property path to connect to
4435 @return {Ember.Binding} receiver
4437 from: function(path) {
4443 This will set the "to" property path to the specified value. It will not
4444 attempt to resolve this property path to an actual object until you
4445 connect the binding.
4447 The binding will search for the property path starting at the root object
4448 you pass when you connect() the binding. It follows the same rules as
4449 `get()` - see that method for more information.
4452 @param {String|Tuple} propertyPath A property path or tuple
4453 @return {Ember.Binding} this
4455 to: function(path) {
4461 Configures the binding as one way. A one-way binding will relay changes
4462 on the "from" side to the "to" side, but not the other way around. This
4463 means that if you change the "to" side directly, the "from" side may have
4467 @return {Ember.Binding} receiver
4469 oneWay: function() {
4470 this._oneWay = true;
4474 toString: function() {
4475 var oneWay = this._oneWay ? '[oneWay]' : '';
4476 return "Ember.Binding<" + guidFor(this) + ">(" + this._from + " -> " + this._to + ")" + oneWay;
4479 // ..........................................................
4484 Attempts to connect this binding instance so that it can receive and relay
4485 changes. This method will raise an exception if you have not set the
4486 from/to properties yet.
4489 @param {Object} obj The root object for this binding.
4490 @return {Ember.Binding} this
4492 connect: function(obj) {
4493 Ember.assert('Must pass a valid object to Ember.Binding.connect()', !!obj);
4495 var fromPath = this._from, toPath = this._to;
4496 Ember.trySet(obj, toPath, getWithGlobals(obj, fromPath));
4498 // add an observer on the object to be notified when the binding should be updated
4499 Ember.addObserver(obj, fromPath, this, this.fromDidChange);
4501 // if the binding is a two-way binding, also set up an observer on the target
4502 if (!this._oneWay) { Ember.addObserver(obj, toPath, this, this.toDidChange); }
4504 this._readyToSync = true;
4510 Disconnects the binding instance. Changes will no longer be relayed. You
4511 will not usually need to call this method.
4514 @param {Object} obj The root object you passed when connecting the binding.
4515 @return {Ember.Binding} this
4517 disconnect: function(obj) {
4518 Ember.assert('Must pass a valid object to Ember.Binding.disconnect()', !!obj);
4520 var twoWay = !this._oneWay;
4522 // remove an observer on the object so we're no longer notified of
4523 // changes that should update bindings.
4524 Ember.removeObserver(obj, this._from, this, this.fromDidChange);
4526 // if the binding is two-way, remove the observer from the target as well
4527 if (twoWay) { Ember.removeObserver(obj, this._to, this, this.toDidChange); }
4529 this._readyToSync = false; // disable scheduled syncs...
4533 // ..........................................................
4537 /* called when the from side changes */
4538 fromDidChange: function(target) {
4539 this._scheduleSync(target, 'fwd');
4542 /* called when the to side changes */
4543 toDidChange: function(target) {
4544 this._scheduleSync(target, 'back');
4547 _scheduleSync: function(obj, dir) {
4548 var directionMap = this._directionMap;
4549 var existingDir = directionMap.get(obj);
4551 // if we haven't scheduled the binding yet, schedule it
4553 Ember.run.schedule('sync', this, this._sync, obj);
4554 directionMap.set(obj, dir);
4557 // If both a 'back' and 'fwd' sync have been scheduled on the same object,
4558 // default to a 'fwd' sync so that it remains deterministic.
4559 if (existingDir === 'back' && dir === 'fwd') {
4560 directionMap.set(obj, 'fwd');
4564 _sync: function(obj) {
4565 var log = Ember.LOG_BINDINGS;
4567 // don't synchronize destroyed objects or disconnected bindings
4568 if (obj.isDestroyed || !this._readyToSync) { return; }
4570 // get the direction of the binding for the object we are
4571 // synchronizing from
4572 var directionMap = this._directionMap;
4573 var direction = directionMap.get(obj);
4575 var fromPath = this._from, toPath = this._to;
4577 directionMap.remove(obj);
4579 // if we're synchronizing from the remote object...
4580 if (direction === 'fwd') {
4581 var fromValue = getWithGlobals(obj, this._from);
4583 Ember.Logger.log(' ', this.toString(), '->', fromValue, obj);
4586 Ember.trySet(obj, toPath, fromValue);
4588 Ember._suspendObserver(obj, toPath, this, this.toDidChange, function () {
4589 Ember.trySet(obj, toPath, fromValue);
4592 // if we're synchronizing *to* the remote object
4593 } else if (direction === 'back') {
4594 var toValue = get(obj, this._to);
4596 Ember.Logger.log(' ', this.toString(), '<-', toValue, obj);
4598 Ember._suspendObserver(obj, fromPath, this, this.fromDidChange, function () {
4599 Ember.trySet(Ember.isGlobalPath(fromPath) ? Ember.lookup : obj, fromPath, toValue);
4606 function mixinProperties(to, from) {
4607 for (var key in from) {
4608 if (from.hasOwnProperty(key)) {
4609 to[key] = from[key];
4614 mixinProperties(Binding, {
4617 See {{#crossLink "Ember.Binding/from"}}{{/crossLink}}
4623 var C = this, binding = new C();
4624 return binding.from.apply(binding, arguments);
4628 See {{#crossLink "Ember.Binding/to"}}{{/crossLink}}
4634 var C = this, binding = new C();
4635 return binding.to.apply(binding, arguments);
4639 Creates a new Binding instance and makes it apply in a single direction.
4640 A one-way binding will relay changes on the "from" side object (supplied
4641 as the `from` argument) the "to" side, but not the other way around.
4642 This means that if you change the "to" side directly, the "from" side may have
4645 See {{#crossLink "Binding/oneWay"}}{{/crossLink}}
4648 @param {String} from from path.
4649 @param {Boolean} [flag] (Optional) passing nothing here will make the binding oneWay. You can
4650 instead pass false to disable oneWay, making the binding two way again.
4652 oneWay: function(from, flag) {
4653 var C = this, binding = new C(null, from);
4654 return binding.oneWay(flag);
4660 An Ember.Binding connects the properties of two objects so that whenever the
4661 value of one property changes, the other property will be changed also.
4663 ## Automatic Creation of Bindings with `/^*Binding/`-named Properties
4664 You do not usually create Binding objects directly but instead describe
4665 bindings in your class or object definition using automatic binding detection.
4667 Properties ending in a `Binding` suffix will be converted to Ember.Binding instances.
4668 The value of this property should be a string representing a path to another object or
4669 a custom binding instanced created using Binding helpers (see "Customizing Your Bindings"):
4671 valueBinding: "MyApp.someController.title"
4673 This will create a binding from `MyApp.someController.title` to the `value`
4674 property of your object instance automatically. Now the two values will be
4679 One especially useful binding customization you can use is the `oneWay()`
4680 helper. This helper tells Ember that you are only interested in
4681 receiving changes on the object you are binding from. For example, if you
4682 are binding to a preference and you want to be notified if the preference
4683 has changed, but your object will not be changing the preference itself, you
4686 bigTitlesBinding: Ember.Binding.oneWay("MyApp.preferencesController.bigTitles")
4688 This way if the value of MyApp.preferencesController.bigTitles changes the
4689 "bigTitles" property of your object will change also. However, if you
4690 change the value of your "bigTitles" property, it will not update the
4691 preferencesController.
4693 One way bindings are almost twice as fast to setup and twice as fast to
4694 execute because the binding only has to worry about changes to one side.
4696 You should consider using one way bindings anytime you have an object that
4697 may be created frequently and you do not intend to change a property; only
4698 to monitor it for changes. (such as in the example above).
4700 ## Adding Bindings Manually
4702 All of the examples above show you how to configure a custom binding, but
4703 the result of these customizations will be a binding template, not a fully
4704 active Binding instance. The binding will actually become active only when you
4705 instantiate the object the binding belongs to. It is useful however, to
4706 understand what actually happens when the binding is activated.
4708 For a binding to function it must have at least a "from" property and a "to"
4709 property. The from property path points to the object/key that you want to
4710 bind from while the to path points to the object/key you want to bind to.
4712 When you define a custom binding, you are usually describing the property
4713 you want to bind from (such as "MyApp.someController.value" in the examples
4714 above). When your object is created, it will automatically assign the value
4715 you want to bind "to" based on the name of your binding key. In the
4716 examples above, during init, Ember objects will effectively call
4717 something like this on your binding:
4719 binding = Ember.Binding.from(this.valueBinding).to("value");
4721 This creates a new binding instance based on the template you provide, and
4722 sets the to path to the "value" property of the new object. Now that the
4723 binding is fully configured with a "from" and a "to", it simply needs to be
4724 connected to become active. This is done through the connect() method:
4726 binding.connect(this);
4728 Note that when you connect a binding you pass the object you want it to be
4729 connected to. This object will be used as the root for both the from and
4730 to side of the binding when inspecting relative paths. This allows the
4731 binding to be automatically inherited by subclassed objects as well.
4733 Now that the binding is connected, it will observe both the from and to side
4736 If you ever needed to do so (you almost never will, but it is useful to
4737 understand this anyway), you could manually create an active binding by
4738 using the Ember.bind() helper method. (This is the same method used by
4739 to setup your bindings on objects):
4741 Ember.bind(MyApp.anotherObject, "value", "MyApp.someController.value");
4743 Both of these code fragments have the same effect as doing the most friendly
4744 form of binding creation like so:
4746 MyApp.anotherObject = Ember.Object.create({
4747 valueBinding: "MyApp.someController.value",
4749 // OTHER CODE FOR THIS OBJECT...
4753 Ember's built in binding creation method makes it easy to automatically
4754 create bindings for you. You should always use the highest-level APIs
4755 available, even if you understand how it works underneath.
4761 Ember.Binding = Binding;
4765 Global helper method to create a new binding. Just pass the root object
4766 along with a to and from path to create and connect the binding.
4770 @param {Object} obj The root object of the transform.
4772 @param {String} to The path to the 'to' side of the binding.
4773 Must be relative to obj.
4775 @param {String} from The path to the 'from' side of the binding.
4776 Must be relative to obj or a global path.
4778 @return {Ember.Binding} binding instance
4780 Ember.bind = function(obj, to, from) {
4781 return new Ember.Binding(to, from).connect(obj);
4787 @param {Object} obj The root object of the transform.
4789 @param {String} to The path to the 'to' side of the binding.
4790 Must be relative to obj.
4792 @param {String} from The path to the 'from' side of the binding.
4793 Must be relative to obj or a global path.
4795 @return {Ember.Binding} binding instance
4797 Ember.oneWay = function(obj, to, from) {
4798 return new Ember.Binding(to, from).oneWay().connect(obj);
4810 var Mixin, REQUIRED, Alias,
4811 classToString, superClassString,
4812 a_map = Ember.ArrayPolyfills.map,
4813 a_indexOf = Ember.ArrayPolyfills.indexOf,
4814 a_forEach = Ember.ArrayPolyfills.forEach,
4816 EMPTY_META = {}, // dummy for non-writable meta
4817 META_SKIP = { __emberproto__: true, __ember_count__: true },
4818 o_create = Ember.create,
4819 defineProperty = Ember.defineProperty,
4820 guidFor = Ember.guidFor;
4822 function mixinsMeta(obj) {
4823 var m = Ember.meta(obj, true), ret = m.mixins;
4825 ret = m.mixins = { __emberproto__: obj };
4826 } else if (ret.__emberproto__ !== obj) {
4827 ret = m.mixins = o_create(ret);
4828 ret.__emberproto__ = obj;
4833 function initMixin(mixin, args) {
4834 if (args && args.length > 0) {
4835 mixin.mixins = a_map.call(args, function(x) {
4836 if (x instanceof Mixin) { return x; }
4838 // Note: Manually setup a primitive mixin here. This is the only
4839 // way to actually get a primitive mixin. This way normal creation
4840 // of mixins will give you combined mixins...
4841 var mixin = new Mixin();
4842 mixin.properties = x;
4849 function isMethod(obj) {
4850 return 'function' === typeof obj &&
4851 obj.isMethod !== false &&
4852 obj !== Boolean && obj !== Object && obj !== Number && obj !== Array && obj !== Date && obj !== String;
4855 function mergeMixins(mixins, m, descs, values, base) {
4856 var len = mixins.length, idx, mixin, guid, props, value, key, ovalue, concats;
4858 function removeKeys(keyName) {
4859 delete descs[keyName];
4860 delete values[keyName];
4863 for(idx=0; idx < len; idx++) {
4864 mixin = mixins[idx];
4865 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]');
4867 if (mixin instanceof Mixin) {
4868 guid = guidFor(mixin);
4869 if (m[guid]) { continue; }
4871 props = mixin.properties;
4873 props = mixin; // apply anonymous mixin properties
4877 // reset before adding each new mixin to pickup concats from previous
4878 concats = values.concatenatedProperties || base.concatenatedProperties;
4879 if (props.concatenatedProperties) {
4880 concats = concats ? concats.concat(props.concatenatedProperties) : props.concatenatedProperties;
4883 for (key in props) {
4884 if (!props.hasOwnProperty(key)) { continue; }
4886 if (value instanceof Ember.Descriptor) {
4887 if (value === REQUIRED && descs[key]) { continue; }
4890 values[key] = undefined;
4892 // impl super if needed...
4893 if (isMethod(value)) {
4894 ovalue = descs[key] === undefined && values[key];
4895 if (!ovalue) { ovalue = base[key]; }
4896 if ('function' !== typeof ovalue) { ovalue = null; }
4898 var o = value.__ember_observes__, ob = value.__ember_observesBefore__;
4899 value = Ember.wrap(value, ovalue);
4900 value.__ember_observes__ = o;
4901 value.__ember_observesBefore__ = ob;
4903 } else if ((concats && a_indexOf.call(concats, key) >= 0) || key === 'concatenatedProperties') {
4904 var baseValue = values[key] || base[key];
4906 if ('function' === typeof baseValue.concat) {
4907 value = baseValue.concat(value);
4909 value = Ember.makeArray(baseValue).concat(value);
4912 value = Ember.makeArray(value);
4916 descs[key] = undefined;
4917 values[key] = value;
4921 // manually copy toString() because some JS engines do not enumerate it
4922 if (props.hasOwnProperty('toString')) {
4923 base.toString = props.toString;
4926 } else if (mixin.mixins) {
4927 mergeMixins(mixin.mixins, m, descs, values, base);
4928 if (mixin._without) { a_forEach.call(mixin._without, removeKeys); }
4933 function writableReq(obj) {
4934 var m = Ember.meta(obj), req = m.required;
4935 if (!req || req.__emberproto__ !== obj) {
4936 req = m.required = req ? o_create(req) : { __ember_count__: 0 };
4937 req.__emberproto__ = obj;
4942 var IS_BINDING = Ember.IS_BINDING = /^.+Binding$/;
4944 function detectBinding(obj, key, value, m) {
4945 if (IS_BINDING.test(key)) {
4946 var bindings = m.bindings;
4948 bindings = m.bindings = { __emberproto__: obj };
4949 } else if (bindings.__emberproto__ !== obj) {
4950 bindings = m.bindings = o_create(m.bindings);
4951 bindings.__emberproto__ = obj;
4953 bindings[key] = value;
4957 function connectBindings(obj, m) {
4958 // TODO Mixin.apply(instance) should disconnect binding if exists
4959 var bindings = m.bindings, key, binding, to;
4961 for (key in bindings) {
4962 binding = key !== '__emberproto__' && bindings[key];
4964 to = key.slice(0, -7); // strip Binding off end
4965 if (binding instanceof Ember.Binding) {
4966 binding = binding.copy(); // copy prototypes' instance
4968 } else { // binding is string path
4969 binding = new Ember.Binding(to, binding);
4971 binding.connect(obj);
4976 m.bindings = { __emberproto__: obj };
4980 function finishPartial(obj, m) {
4981 connectBindings(obj, m || Ember.meta(obj));
4985 function applyMixin(obj, mixins, partial) {
4986 var descs = {}, values = {}, m = Ember.meta(obj), req = m.required,
4987 key, value, desc, prevValue, paths, len, idx;
4989 // Go through all mixins and hashes passed in, and:
4991 // * Handle concatenated properties
4992 // * Set up _super wrapping if necessary
4993 // * Set up computed property descriptors
4994 // * Copying `toString` in broken browsers
4995 mergeMixins(mixins, mixinsMeta(obj), descs, values, obj);
4997 for(key in values) {
4998 if (key === 'contructor') { continue; }
4999 if (!values.hasOwnProperty(key)) { continue; }
5002 value = values[key];
5004 if (desc === REQUIRED) {
5005 if (!(key in obj)) {
5006 Ember.assert('Required property not defined: '+key, !!partial);
5008 // for partial applies add to hash of required keys
5009 req = writableReq(obj);
5010 req.__ember_count__++;
5014 while (desc && desc instanceof Alias) {
5015 var altKey = desc.methodName;
5016 if (descs[altKey] || values[altKey]) {
5017 value = values[altKey];
5018 desc = descs[altKey];
5019 } else if (m.descs[altKey]) {
5020 desc = m.descs[altKey];
5024 value = obj[altKey];
5028 if (desc === undefined && value === undefined) { continue; }
5030 prevValue = obj[key];
5032 if ('function' === typeof prevValue) {
5033 if ((paths = prevValue.__ember_observesBefore__)) {
5035 for (idx=0; idx < len; idx++) {
5036 Ember.removeBeforeObserver(obj, paths[idx], null, key);
5038 } else if ((paths = prevValue.__ember_observes__)) {
5040 for (idx=0; idx < len; idx++) {
5041 Ember.removeObserver(obj, paths[idx], null, key);
5046 detectBinding(obj, key, value, m);
5048 defineProperty(obj, key, desc, value, m);
5050 if ('function' === typeof value) {
5051 if (paths = value.__ember_observesBefore__) {
5053 for (idx=0; idx < len; idx++) {
5054 Ember.addBeforeObserver(obj, paths[idx], null, key);
5056 } else if (paths = value.__ember_observes__) {
5058 for (idx=0; idx < len; idx++) {
5059 Ember.addObserver(obj, paths[idx], null, key);
5064 if (req && req[key]) {
5065 req = writableReq(obj);
5066 req.__ember_count__--;
5072 if (!partial) { // don't apply to prototype
5073 finishPartial(obj, m);
5076 // Make sure no required attrs remain
5077 if (!partial && req && req.__ember_count__>0) {
5080 if (META_SKIP[key]) { continue; }
5083 // TODO: Remove surrounding if clause from production build
5084 Ember.assert('Required properties not defined: '+keys.join(','));
5096 Ember.mixin = function(obj) {
5097 var args = a_slice.call(arguments, 1);
5098 applyMixin(obj, args, false);
5103 The `Ember.Mixin` class allows you to create mixins, whose properties can be
5104 added to other classes. For instance,
5106 App.Editable = Ember.Mixin.create({
5108 console.log('starting to edit');
5109 this.set('isEditing', true);
5114 // Mix mixins into classes by passing them as the first arguments to
5115 // .extend or .create.
5116 App.CommentView = Ember.View.extend(App.Editable, {
5117 template: Ember.Handlebars.compile('{{#if isEditing}}...{{else}}...{{/if}}')
5120 commentView = App.CommentView.create();
5121 commentView.edit(); // => outputs 'starting to edit'
5123 Note that Mixins are created with `Ember.Mixin.create`, not
5124 `Ember.Mixin.extend`.
5129 Ember.Mixin = function() { return initMixin(this, arguments); };
5131 Mixin = Ember.Mixin;
5133 Mixin._apply = applyMixin;
5135 Mixin.applyPartial = function(obj) {
5136 var args = a_slice.call(arguments, 1);
5137 return applyMixin(obj, args, true);
5140 Mixin.finishPartial = finishPartial;
5147 Mixin.create = function() {
5148 classToString.processed = false;
5150 return initMixin(new M(), arguments);
5153 var MixinPrototype = Mixin.prototype;
5159 MixinPrototype.reopen = function() {
5162 if (this.properties) {
5163 mixin = Mixin.create();
5164 mixin.properties = this.properties;
5165 delete this.properties;
5166 this.mixins = [mixin];
5167 } else if (!this.mixins) {
5171 var len = arguments.length, mixins = this.mixins, idx;
5173 for(idx=0; idx < len; idx++) {
5174 mixin = arguments[idx];
5175 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]');
5177 if (mixin instanceof Mixin) {
5180 tmp = Mixin.create();
5181 tmp.properties = mixin;
5192 @return applied object
5194 MixinPrototype.apply = function(obj) {
5195 return applyMixin(obj, [this], false);
5198 MixinPrototype.applyPartial = function(obj) {
5199 return applyMixin(obj, [this], true);
5202 function _detect(curMixin, targetMixin, seen) {
5203 var guid = guidFor(curMixin);
5205 if (seen[guid]) { return false; }
5208 if (curMixin === targetMixin) { return true; }
5209 var mixins = curMixin.mixins, loc = mixins ? mixins.length : 0;
5210 while (--loc >= 0) {
5211 if (_detect(mixins[loc], targetMixin, seen)) { return true; }
5221 MixinPrototype.detect = function(obj) {
5222 if (!obj) { return false; }
5223 if (obj instanceof Mixin) { return _detect(obj, this, {}); }
5224 var mixins = Ember.meta(obj, false).mixins;
5226 return !!mixins[guidFor(this)];
5231 MixinPrototype.without = function() {
5232 var ret = new Mixin(this);
5233 ret._without = a_slice.call(arguments);
5237 function _keys(ret, mixin, seen) {
5238 if (seen[guidFor(mixin)]) { return; }
5239 seen[guidFor(mixin)] = true;
5241 if (mixin.properties) {
5242 var props = mixin.properties;
5243 for (var key in props) {
5244 if (props.hasOwnProperty(key)) { ret[key] = true; }
5246 } else if (mixin.mixins) {
5247 a_forEach.call(mixin.mixins, function(x) { _keys(ret, x, seen); });
5251 MixinPrototype.keys = function() {
5252 var keys = {}, seen = {}, ret = [];
5253 _keys(keys, this, seen);
5254 for(var key in keys) {
5255 if (keys.hasOwnProperty(key)) { ret.push(key); }
5260 /* make Mixins have nice displayNames */
5262 var NAME_KEY = Ember.GUID_KEY+'_name';
5263 var get = Ember.get;
5265 function processNames(paths, root, seen) {
5266 var idx = paths.length;
5267 for(var key in root) {
5268 if (!root.hasOwnProperty || !root.hasOwnProperty(key)) { continue; }
5269 var obj = root[key];
5272 if (obj && obj.toString === classToString) {
5273 obj[NAME_KEY] = paths.join('.');
5274 } else if (obj && get(obj, 'isNamespace')) {
5275 if (seen[guidFor(obj)]) { continue; }
5276 seen[guidFor(obj)] = true;
5277 processNames(paths, obj, seen);
5280 paths.length = idx; // cut out last item
5283 function findNamespaces() {
5284 var Namespace = Ember.Namespace, lookup = Ember.lookup, obj, isNamespace;
5286 if (Namespace.PROCESSED) { return; }
5288 for (var prop in lookup) {
5289 // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox.
5290 // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage
5291 if (prop === "globalStorage" && lookup.StorageList && lookup.globalStorage instanceof lookup.StorageList) { continue; }
5292 // Unfortunately, some versions of IE don't support window.hasOwnProperty
5293 if (lookup.hasOwnProperty && !lookup.hasOwnProperty(prop)) { continue; }
5295 // At times we are not allowed to access certain properties for security reasons.
5296 // There are also times where even if we can access them, we are not allowed to access their properties.
5298 obj = Ember.lookup[prop];
5299 isNamespace = obj && get(obj, 'isNamespace');
5305 Ember.deprecate("Namespaces should not begin with lowercase.", /^[A-Z]/.test(prop));
5306 obj[NAME_KEY] = prop;
5313 @method identifyNamespaces
5316 Ember.identifyNamespaces = findNamespaces;
5318 superClassString = function(mixin) {
5319 var superclass = mixin.superclass;
5321 if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; }
5322 else { return superClassString(superclass); }
5328 classToString = function() {
5329 var Namespace = Ember.Namespace, namespace;
5331 // TODO: Namespace should really be in Metal
5333 if (!this[NAME_KEY] && !classToString.processed) {
5334 if (!Namespace.PROCESSED) {
5336 Namespace.PROCESSED = true;
5339 classToString.processed = true;
5341 var namespaces = Namespace.NAMESPACES;
5342 for (var i=0, l=namespaces.length; i<l; i++) {
5343 namespace = namespaces[i];
5344 processNames([namespace.toString()], namespace, {});
5349 if (this[NAME_KEY]) {
5350 return this[NAME_KEY];
5352 var str = superClassString(this);
5354 return "(subclass of " + str + ")";
5356 return "(unknown mixin)";
5361 MixinPrototype.toString = classToString;
5363 // returns the mixins currently applied to the specified object
5364 // TODO: Make Ember.mixin
5365 Mixin.mixins = function(obj) {
5366 var ret = [], mixins = Ember.meta(obj, false).mixins, key, mixin;
5368 for(key in mixins) {
5369 if (META_SKIP[key]) { continue; }
5370 mixin = mixins[key];
5372 // skip primitive mixins since these are always anonymous
5373 if (!mixin.properties) { ret.push(mixins[key]); }
5379 REQUIRED = new Ember.Descriptor();
5380 REQUIRED.toString = function() { return '(Required Property)'; };
5383 Denotes a required property for a mixin
5388 Ember.required = function() {
5392 Alias = function(methodName) {
5393 this.methodName = methodName;
5395 Alias.prototype = new Ember.Descriptor();
5398 Makes a property or method available via an additional name.
5400 App.PaintSample = Ember.Object.extend({
5402 colour: Ember.alias('color'),
5406 moniker: Ember.alias("name")
5408 var paintSample = App.PaintSample.create()
5409 paintSample.get('colour'); //=> 'red'
5410 paintSample.moniker(); //=> 'Zed'
5414 @param {String} methodName name of the method or property to alias
5415 @return {Ember.Descriptor}
5417 Ember.alias = function(methodName) {
5418 return new Alias(methodName);
5421 // ..........................................................
5428 @param {Function} func
5429 @param {String} propertyNames*
5432 Ember.observer = function(func) {
5433 var paths = a_slice.call(arguments, 1);
5434 func.__ember_observes__ = paths;
5438 // If observers ever become asynchronous, Ember.immediateObserver
5439 // must remain synchronous.
5441 @method immediateObserver
5443 @param {Function} func
5444 @param {String} propertyNames*
5447 Ember.immediateObserver = function() {
5448 for (var i=0, l=arguments.length; i<l; i++) {
5449 var arg = arguments[i];
5450 Ember.assert("Immediate observers must observe internal properties only, not properties on other objects.", typeof arg !== "string" || arg.indexOf('.') === -1);
5453 return Ember.observer.apply(this, arguments);
5457 @method beforeObserver
5459 @param {Function} func
5460 @param {String} propertyNames*
5463 Ember.beforeObserver = function(func) {
5464 var paths = a_slice.call(arguments, 1);
5465 func.__ember_observesBefore__ = paths;
5478 @submodule ember-metal
5484 (function(exports) { "use strict";
5486 var browserGlobal = (typeof window !== 'undefined') ? window : {};
5488 var MutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
5491 if (typeof process !== 'undefined') {
5492 async = function(callback, binding) {
5493 process.nextTick(function() {
5494 callback.call(binding);
5497 } else if (MutationObserver) {
5500 var observer = new MutationObserver(function() {
5501 var toProcess = queue.slice();
5504 toProcess.forEach(function(tuple) {
5505 var callback = tuple[0], binding = tuple[1];
5506 callback.call(binding);
5510 var element = document.createElement('div');
5511 observer.observe(element, { attributes: true });
5513 async = function(callback, binding) {
5514 queue.push([callback, binding]);
5515 element.setAttribute('drainQueue', 'drainQueue');
5518 async = function(callback, binding) {
5519 setTimeout(function() {
5520 callback.call(binding);
5525 exports.async = async;
5527 var Event = exports.Event = function(type, options) {
5530 for (var option in options) {
5531 if (!options.hasOwnProperty(option)) { continue; }
5533 this[option] = options[option];
5537 var indexOf = function(callbacks, callback) {
5538 for (var i=0, l=callbacks.length; i<l; i++) {
5539 if (callbacks[i][0] === callback) { return i; }
5545 var callbacksFor = function(object) {
5546 var callbacks = object._promiseCallbacks;
5549 callbacks = object._promiseCallbacks = {};
5555 var EventTarget = exports.EventTarget = {
5556 mixin: function(object) {
5557 object.on = this.on;
5558 object.off = this.off;
5559 object.trigger = this.trigger;
5563 on: function(eventName, callback, binding) {
5564 var allCallbacks = callbacksFor(this), callbacks;
5565 binding = binding || this;
5567 callbacks = allCallbacks[eventName];
5570 callbacks = allCallbacks[eventName] = [];
5573 if (indexOf(callbacks, callback) === -1) {
5574 callbacks.push([callback, binding]);
5578 off: function(eventName, callback) {
5579 var allCallbacks = callbacksFor(this), callbacks;
5582 allCallbacks[eventName] = [];
5586 callbacks = allCallbacks[eventName];
5588 var index = indexOf(callbacks, callback);
5590 if (index !== -1) { callbacks.splice(index, 1); }
5593 trigger: function(eventName, options) {
5594 var allCallbacks = callbacksFor(this),
5595 callbacks, callbackTuple, callback, binding, event;
5597 if (callbacks = allCallbacks[eventName]) {
5598 for (var i=0, l=callbacks.length; i<l; i++) {
5599 callbackTuple = callbacks[i];
5600 callback = callbackTuple[0];
5601 binding = callbackTuple[1];
5603 if (typeof options !== 'object') {
5604 options = { detail: options };
5607 event = new Event(eventName, options);
5608 callback.call(binding, event);
5614 var Promise = exports.Promise = function() {
5615 this.on('promise:resolved', function(event) {
5616 this.trigger('success', { detail: event.detail });
5619 this.on('promise:failed', function(event) {
5620 this.trigger('error', { detail: event.detail });
5624 var noop = function() {};
5626 var invokeCallback = function(type, promise, callback, event) {
5631 value = callback(event.detail);
5636 value = event.detail;
5639 if (value instanceof Promise) {
5640 value.then(function(value) {
5641 promise.resolve(value);
5642 }, function(error) {
5643 promise.reject(error);
5645 } else if (callback && value) {
5646 promise.resolve(value);
5648 promise.reject(error);
5650 promise[type](value);
5654 Promise.prototype = {
5655 then: function(done, fail) {
5656 var thenPromise = new Promise();
5658 this.on('promise:resolved', function(event) {
5659 invokeCallback('resolve', thenPromise, done, event);
5662 this.on('promise:failed', function(event) {
5663 invokeCallback('reject', thenPromise, fail, event);
5669 resolve: function(value) {
5670 exports.async(function() {
5671 this.trigger('promise:resolved', { detail: value });
5672 this.isResolved = value;
5675 this.resolve = noop;
5679 reject: function(value) {
5680 exports.async(function() {
5681 this.trigger('promise:failed', { detail: value });
5682 this.isRejected = value;
5685 this.resolve = noop;
5690 EventTarget.mixin(Promise.prototype);
5691 })(window.RSVP = {});
5699 @submodule ember-runtime
5702 var indexOf = Ember.EnumerableUtils.indexOf;
5704 // ........................................
5705 // TYPING & ARRAY MESSAGING
5709 var t = "Boolean Number String Function Array Date RegExp Object".split(" ");
5710 Ember.ArrayPolyfills.forEach.call(t, function(name) {
5711 TYPE_MAP[ "[object " + name + "]" ] = name.toLowerCase();
5714 var toString = Object.prototype.toString;
5717 Returns a consistent type for the passed item.
5719 Use this instead of the built-in `typeof` to get the type of an item.
5720 It will return the same result across all browsers and includes a bit
5721 more detail. Here is what will be returned:
5723 | Return Value | Meaning |
5724 |---------------|------------------------------------------------------|
5725 | 'string' | String primitive |
5726 | 'number' | Number primitive |
5727 | 'boolean' | Boolean primitive |
5728 | 'null' | Null value |
5729 | 'undefined' | Undefined value |
5730 | 'function' | A function |
5731 | 'array' | An instance of Array |
5732 | 'class' | A Ember class (created using Ember.Object.extend()) |
5733 | 'instance' | A Ember object instance |
5734 | 'error' | An instance of the Error object |
5735 | 'object' | A JavaScript object not inheriting from Ember.Object |
5739 Ember.typeOf(); => 'undefined'
5740 Ember.typeOf(null); => 'null'
5741 Ember.typeOf(undefined); => 'undefined'
5742 Ember.typeOf('michael'); => 'string'
5743 Ember.typeOf(101); => 'number'
5744 Ember.typeOf(true); => 'boolean'
5745 Ember.typeOf(Ember.makeArray); => 'function'
5746 Ember.typeOf([1,2,90]); => 'array'
5747 Ember.typeOf(Ember.Object.extend()); => 'class'
5748 Ember.typeOf(Ember.Object.create()); => 'instance'
5749 Ember.typeOf(new Error('teamocil')); => 'error'
5751 // "normal" JavaScript object
5752 Ember.typeOf({a: 'b'}); => 'object'
5756 @param item {Object} the item to check
5757 @return {String} the type
5759 Ember.typeOf = function(item) {
5762 ret = (item === null || item === undefined) ? String(item) : TYPE_MAP[toString.call(item)] || 'object';
5764 if (ret === 'function') {
5765 if (Ember.Object && Ember.Object.detect(item)) ret = 'class';
5766 } else if (ret === 'object') {
5767 if (item instanceof Error) ret = 'error';
5768 else if (Ember.Object && item instanceof Ember.Object) ret = 'instance';
5769 else ret = 'object';
5776 Returns true if the passed value is null or undefined. This avoids errors
5777 from JSLint complaining about use of ==, which can be technically
5780 Ember.none(); => true
5781 Ember.none(null); => true
5782 Ember.none(undefined); => true
5783 Ember.none(''); => false
5784 Ember.none([]); => false
5785 Ember.none(function(){}); => false
5789 @param {Object} obj Value to test
5792 Ember.none = function(obj) {
5793 return obj === null || obj === undefined;
5797 Verifies that a value is null or an empty string | array | function.
5799 Constrains the rules on `Ember.none` by returning false for empty
5800 string and empty arrays.
5802 Ember.empty(); => true
5803 Ember.empty(null); => true
5804 Ember.empty(undefined); => true
5805 Ember.empty(''); => true
5806 Ember.empty([]); => true
5807 Ember.empty('tobias fünke'); => false
5808 Ember.empty([0,1,2]); => false
5812 @param {Object} obj Value to test
5815 Ember.empty = function(obj) {
5816 return obj === null || obj === undefined || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0);
5820 This will compare two javascript values of possibly different types.
5821 It will tell you which one is greater than the other by returning:
5823 - -1 if the first is smaller than the second,
5824 - 0 if both are equal,
5825 - 1 if the first is greater than the second.
5827 The order is calculated based on Ember.ORDER_DEFINITION, if types are different.
5828 In case they have the same type an appropriate comparison for this type is made.
5830 Ember.compare('hello', 'hello'); => 0
5831 Ember.compare('abc', 'dfg'); => -1
5832 Ember.compare(2, 1); => 1
5836 @param {Object} v First value to compare
5837 @param {Object} w Second value to compare
5838 @return {Number} -1 if v < w, 0 if v = w and 1 if v > w.
5840 Ember.compare = function compare(v, w) {
5841 if (v === w) { return 0; }
5843 var type1 = Ember.typeOf(v);
5844 var type2 = Ember.typeOf(w);
5846 var Comparable = Ember.Comparable;
5848 if (type1==='instance' && Comparable.detect(v.constructor)) {
5849 return v.constructor.compare(v, w);
5852 if (type2 === 'instance' && Comparable.detect(w.constructor)) {
5853 return 1-w.constructor.compare(w, v);
5857 // If we haven't yet generated a reverse-mapping of Ember.ORDER_DEFINITION,
5859 var mapping = Ember.ORDER_DEFINITION_MAPPING;
5861 var order = Ember.ORDER_DEFINITION;
5862 mapping = Ember.ORDER_DEFINITION_MAPPING = {};
5864 for (idx = 0, len = order.length; idx < len; ++idx) {
5865 mapping[order[idx]] = idx;
5868 // We no longer need Ember.ORDER_DEFINITION.
5869 delete Ember.ORDER_DEFINITION;
5872 var type1Index = mapping[type1];
5873 var type2Index = mapping[type2];
5875 if (type1Index < type2Index) { return -1; }
5876 if (type1Index > type2Index) { return 1; }
5878 // types are equal - so we have to check values now
5882 if (v < w) { return -1; }
5883 if (v > w) { return 1; }
5887 var comp = v.localeCompare(w);
5888 if (comp < 0) { return -1; }
5889 if (comp > 0) { return 1; }
5893 var vLen = v.length;
5894 var wLen = w.length;
5895 var l = Math.min(vLen, wLen);
5898 while (r === 0 && i < l) {
5899 r = compare(v[i],w[i]);
5902 if (r !== 0) { return r; }
5904 // all elements are equal now
5905 // shorter array should be ordered first
5906 if (vLen < wLen) { return -1; }
5907 if (vLen > wLen) { return 1; }
5908 // arrays are equal now
5912 if (Ember.Comparable && Ember.Comparable.detect(v)) {
5913 return v.compare(v, w);
5918 var vNum = v.getTime();
5919 var wNum = w.getTime();
5920 if (vNum < wNum) { return -1; }
5921 if (vNum > wNum) { return 1; }
5929 function _copy(obj, deep, seen, copies) {
5932 // primitive data types are immutable, just return them.
5933 if ('object' !== typeof obj || obj===null) return obj;
5935 // avoid cyclical loops
5936 if (deep && (loc=indexOf(seen, obj))>=0) return copies[loc];
5938 Ember.assert('Cannot clone an Ember.Object that does not implement Ember.Copyable', !(obj instanceof Ember.Object) || (Ember.Copyable && Ember.Copyable.detect(obj)));
5940 // IMPORTANT: this specific test will detect a native array only. Any other
5941 // object will need to implement Copyable.
5942 if (Ember.typeOf(obj) === 'array') {
5946 while(--loc>=0) ret[loc] = _copy(ret[loc], deep, seen, copies);
5948 } else if (Ember.Copyable && Ember.Copyable.detect(obj)) {
5949 ret = obj.copy(deep, seen, copies);
5953 if (!obj.hasOwnProperty(key)) continue;
5954 ret[key] = deep ? _copy(obj[key], deep, seen, copies) : obj[key];
5967 Creates a clone of the passed object. This function can take just about
5968 any type of object and create a clone of it, including primitive values
5969 (which are not actually cloned because they are immutable).
5971 If the passed object implements the clone() method, then this function
5972 will simply call that method and return the result.
5976 @param {Object} object The object to clone
5977 @param {Boolean} deep If true, a deep copy of the object is made
5978 @return {Object} The cloned object
5980 Ember.copy = function(obj, deep) {
5982 if ('object' !== typeof obj || obj===null) return obj; // can't copy primitives
5983 if (Ember.Copyable && Ember.Copyable.detect(obj)) return obj.copy(deep);
5984 return _copy(obj, deep, deep ? [] : null, deep ? [] : null);
5988 Convenience method to inspect an object. This method will attempt to
5989 convert the object into a useful string description.
5993 @param {Object} obj The object you want to inspect.
5994 @return {String} A description of the object
5996 Ember.inspect = function(obj) {
5998 for(var key in obj) {
5999 if (obj.hasOwnProperty(key)) {
6001 if (v === 'toString') { continue; } // ignore useless items
6002 if (Ember.typeOf(v) === 'function') { v = "function() { ... }"; }
6003 ret.push(key + ": " + v);
6006 return "{" + ret.join(" , ") + "}";
6010 Compares two objects, returning true if they are logically equal. This is
6011 a deeper comparison than a simple triple equal. For sets it will compare the
6012 internal objects. For any other object that implements `isEqual()` it will
6013 respect that method.
6015 Ember.isEqual('hello', 'hello'); => true
6016 Ember.isEqual(1, 2); => false
6017 Ember.isEqual([4,2], [4,2]); => false
6021 @param {Object} a first object to compare
6022 @param {Object} b second object to compare
6025 Ember.isEqual = function(a, b) {
6026 if (a && 'function'===typeof a.isEqual) return a.isEqual(b);
6030 // Used by Ember.compare
6031 Ember.ORDER_DEFINITION = Ember.ENV.ORDER_DEFINITION || [
6046 Returns all of the keys defined on an object or hash. This is useful
6047 when inspecting objects for debugging. On browsers that support it, this
6048 uses the native Object.keys implementation.
6053 @return {Array} Array containing keys of obj
6055 Ember.keys = Object.keys;
6058 Ember.keys = function(obj) {
6060 for(var key in obj) {
6061 if (obj.hasOwnProperty(key)) { ret.push(key); }
6067 // ..........................................................
6071 var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
6074 A subclass of the JavaScript Error object for use in Ember.
6081 Ember.Error = function() {
6082 var tmp = Error.prototype.constructor.apply(this, arguments);
6084 // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
6085 for (var idx = 0; idx < errorProps.length; idx++) {
6086 this[errorProps[idx]] = tmp[errorProps[idx]];
6090 Ember.Error.prototype = Ember.create(Error.prototype);
6099 @submodule ember-runtime
6102 var STRING_DASHERIZE_REGEXP = (/[ _]/g);
6103 var STRING_DASHERIZE_CACHE = {};
6104 var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g);
6105 var STRING_CAMELIZE_REGEXP = (/(\-|_|\s)+(.)?/g);
6106 var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g);
6107 var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g);
6110 Defines the hash of localized strings for the current language. Used by
6111 the `Ember.String.loc()` helper. To localize, add string values to this
6121 Defines string helper methods including string formatting and localization.
6122 Unless Ember.EXTEND_PROTOTYPES.String is false these methods will also be added
6123 to the String.prototype as well.
6132 Apply formatting options to the string. This will look for occurrences
6133 of %@ in your string and substitute them with the arguments you pass into
6134 this method. If you want to control the specific order of replacement,
6135 you can add a number after the key as well to indicate which argument
6138 Ordered insertions are most useful when building loc strings where values
6139 you need to insert may appear in different orders.
6141 "Hello %@ %@".fmt('John', 'Doe') => "Hello John Doe"
6142 "Hello %@2, %@1".fmt('John', 'Doe') => "Hello Doe, John"
6145 @param {Object...} [args]
6146 @return {String} formatted string
6148 fmt: function(str, formats) {
6149 // first, replace any ORDERED replacements.
6150 var idx = 0; // the current index for non-numerical replacements
6151 return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
6152 argIndex = (argIndex) ? parseInt(argIndex,0) - 1 : idx++ ;
6153 s = formats[argIndex];
6154 return ((s === null) ? '(null)' : (s === undefined) ? '' : s).toString();
6159 Formats the passed string, but first looks up the string in the localized
6160 strings hash. This is a convenient way to localize text. See
6161 `Ember.String.fmt()` for more information on formatting.
6163 Note that it is traditional but not required to prefix localized string
6164 keys with an underscore or other character so you can easily identify
6168 '_Hello World': 'Bonjour le monde',
6169 '_Hello %@ %@': 'Bonjour %@ %@'
6172 Ember.String.loc("_Hello World");
6173 => 'Bonjour le monde';
6175 Ember.String.loc("_Hello %@ %@", ["John", "Smith"]);
6176 => "Bonjour John Smith";
6179 @param {String} str The string to format
6180 @param {Array} formats Optional array of parameters to interpolate into string.
6181 @return {String} formatted string
6183 loc: function(str, formats) {
6184 str = Ember.STRINGS[str] || str;
6185 return Ember.String.fmt(str, formats) ;
6189 Splits a string into separate units separated by spaces, eliminating any
6190 empty strings in the process. This is a convenience method for split that
6191 is mostly useful when applied to the String.prototype.
6193 Ember.String.w("alpha beta gamma").forEach(function(key) {
6201 @param {String} str The string to split
6202 @return {String} split string
6204 w: function(str) { return str.split(/\s+/); },
6207 Converts a camelized string into all lower case separated by underscores.
6209 'innerHTML'.decamelize() => 'inner_html'
6210 'action_name'.decamelize() => 'action_name'
6211 'css-class-name'.decamelize() => 'css-class-name'
6212 'my favorite items'.decamelize() => 'my favorite items'
6215 @param {String} str The string to decamelize.
6216 @return {String} the decamelized string.
6218 decamelize: function(str) {
6219 return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase();
6223 Replaces underscores or spaces with dashes.
6225 'innerHTML'.dasherize() => 'inner-html'
6226 'action_name'.dasherize() => 'action-name'
6227 'css-class-name'.dasherize() => 'css-class-name'
6228 'my favorite items'.dasherize() => 'my-favorite-items'
6231 @param {String} str The string to dasherize.
6232 @return {String} the dasherized string.
6234 dasherize: function(str) {
6235 var cache = STRING_DASHERIZE_CACHE,
6241 ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-');
6249 Returns the lowerCaseCamel form of a string.
6251 'innerHTML'.camelize() => 'innerHTML'
6252 'action_name'.camelize() => 'actionName'
6253 'css-class-name'.camelize() => 'cssClassName'
6254 'my favorite items'.camelize() => 'myFavoriteItems'
6257 @param {String} str The string to camelize.
6258 @return {String} the camelized string.
6260 camelize: function(str) {
6261 return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) {
6262 return chr ? chr.toUpperCase() : '';
6267 Returns the UpperCamelCase form of a string.
6269 'innerHTML'.classify() => 'InnerHTML'
6270 'action_name'.classify() => 'ActionName'
6271 'css-class-name'.classify() => 'CssClassName'
6272 'my favorite items'.classify() => 'MyFavoriteItems'
6275 @param {String} str the string to classify
6276 @return {String} the classified string
6278 classify: function(str) {
6279 var camelized = Ember.String.camelize(str);
6280 return camelized.charAt(0).toUpperCase() + camelized.substr(1);
6284 More general than decamelize. Returns the lower_case_and_underscored
6287 'innerHTML'.underscore() => 'inner_html'
6288 'action_name'.underscore() => 'action_name'
6289 'css-class-name'.underscore() => 'css_class_name'
6290 'my favorite items'.underscore() => 'my_favorite_items'
6292 @property underscore
6293 @param {String} str The string to underscore.
6294 @return {String} the underscored string.
6296 underscore: function(str) {
6297 return str.replace(STRING_UNDERSCORE_REGEXP_1, '$1_$2').
6298 replace(STRING_UNDERSCORE_REGEXP_2, '_').toLowerCase();
6309 @submodule ember-runtime
6314 var fmt = Ember.String.fmt,
6316 loc = Ember.String.loc,
6317 camelize = Ember.String.camelize,
6318 decamelize = Ember.String.decamelize,
6319 dasherize = Ember.String.dasherize,
6320 underscore = Ember.String.underscore,
6321 classify = Ember.String.classify;
6323 if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
6326 See {{#crossLink "Ember.String/fmt"}}{{/crossLink}}
6331 String.prototype.fmt = function() {
6332 return fmt(this, arguments);
6336 See {{#crossLink "Ember.String/w"}}{{/crossLink}}
6341 String.prototype.w = function() {
6346 See {{#crossLink "Ember.String/loc"}}{{/crossLink}}
6351 String.prototype.loc = function() {
6352 return loc(this, arguments);
6356 See {{#crossLink "Ember.String/camelize"}}{{/crossLink}}
6361 String.prototype.camelize = function() {
6362 return camelize(this);
6366 See {{#crossLink "Ember.String/decamelize"}}{{/crossLink}}
6371 String.prototype.decamelize = function() {
6372 return decamelize(this);
6376 See {{#crossLink "Ember.String/dasherize"}}{{/crossLink}}
6381 String.prototype.dasherize = function() {
6382 return dasherize(this);
6386 See {{#crossLink "Ember.String/underscore"}}{{/crossLink}}
6391 String.prototype.underscore = function() {
6392 return underscore(this);
6396 See {{#crossLink "Ember.String/classify"}}{{/crossLink}}
6401 String.prototype.classify = function() {
6402 return classify(this);
6414 @submodule ember-runtime
6417 var a_slice = Array.prototype.slice;
6419 if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Function) {
6422 The `property` extension of Javascript's Function prototype is available
6423 when Ember.EXTEND_PROTOTYPES or Ember.EXTEND_PROTOTYPES.Function is true,
6424 which is the default.
6426 Computed properties allow you to treat a function like a property:
6428 MyApp.president = Ember.Object.create({
6429 firstName: "Barack",
6432 fullName: function() {
6433 return this.get('firstName') + ' ' + this.get('lastName');
6435 // Call this flag to mark the function as a property
6439 MyApp.president.get('fullName'); => "Barack Obama"
6441 Treating a function like a property is useful because they can work with
6442 bindings, just like any other property.
6444 Many computed properties have dependencies on other properties. For
6445 example, in the above example, the `fullName` property depends on
6446 `firstName` and `lastName` to determine its value. You can tell Ember.js
6447 about these dependencies like this:
6449 MyApp.president = Ember.Object.create({
6450 firstName: "Barack",
6453 fullName: function() {
6454 return this.get('firstName') + ' ' + this.get('lastName');
6456 // Tell Ember.js that this computed property depends on firstName
6458 }.property('firstName', 'lastName')
6461 Make sure you list these dependencies so Ember.js knows when to update
6462 bindings that connect to a computed property. Changing a dependency
6463 will not immediately trigger an update of the computed property, but
6464 will instead clear the cache so that it is updated when the next `get`
6465 is called on the property.
6467 See {{#crossLink "Ember.ComputedProperty"}}{{/crossLink}},
6468 {{#crossLink "Ember/computed"}}{{/crossLink}}
6473 Function.prototype.property = function() {
6474 var ret = Ember.computed(this);
6475 return ret.property.apply(ret, arguments);
6479 The `observes` extension of Javascript's Function prototype is available
6480 when Ember.EXTEND_PROTOTYPES or Ember.EXTEND_PROTOTYPES.Function is true,
6481 which is the default.
6483 You can observe property changes simply by adding the `observes`
6484 call to the end of your method declarations in classes that you write.
6487 Ember.Object.create({
6488 valueObserver: function() {
6489 // Executes whenever the "value" property changes
6493 See {{#crossLink "Ember.Observable/observes"}}{{/crossLink}}
6498 Function.prototype.observes = function() {
6499 this.__ember_observes__ = a_slice.call(arguments);
6504 The `observesBefore` extension of Javascript's Function prototype is available
6505 when Ember.EXTEND_PROTOTYPES or Ember.EXTEND_PROTOTYPES.Function is true,
6506 which is the default.
6508 You can get notified when a property changes is about to happen by
6509 by adding the `observesBefore` call to the end of your method
6510 declarations in classes that you write. For example:
6512 Ember.Object.create({
6513 valueObserver: function() {
6514 // Executes whenever the "value" property is about to change
6515 }.observesBefore('value')
6518 See {{#crossLink "Ember.Observable/observesBefore"}}{{/crossLink}}
6520 @method observesBefore
6523 Function.prototype.observesBefore = function() {
6524 this.__ember_observesBefore__ = a_slice.call(arguments);
6544 @submodule ember-runtime
6547 // ..........................................................
6551 var get = Ember.get, set = Ember.set;
6552 var a_slice = Array.prototype.slice;
6553 var a_indexOf = Ember.EnumerableUtils.indexOf;
6558 return contexts.length===0 ? {} : contexts.pop();
6561 function pushCtx(ctx) {
6566 function iter(key, value) {
6567 var valueProvided = arguments.length === 2;
6570 var cur = get(item, key);
6571 return valueProvided ? value===cur : !!cur;
6577 This mixin defines the common interface implemented by enumerable objects
6578 in Ember. Most of these methods follow the standard Array iteration
6579 API defined up to JavaScript 1.8 (excluding language-specific features that
6580 cannot be emulated in older versions of JavaScript).
6582 This mixin is applied automatically to the Array class on page load, so you
6583 can use any of these methods on simple arrays. If Array already implements
6584 one of these methods, the mixin will not override them.
6586 ## Writing Your Own Enumerable
6588 To make your own custom class enumerable, you need two items:
6590 1. You must have a length property. This property should change whenever
6591 the number of items in your enumerable object changes. If you using this
6592 with an Ember.Object subclass, you should be sure to change the length
6593 property using set().
6595 2. If you must implement nextObject(). See documentation.
6597 Once you have these two methods implement, apply the Ember.Enumerable mixin
6598 to your class and you will be able to enumerate the contents of your object
6599 like any other collection.
6601 ## Using Ember Enumeration with Other Libraries
6603 Many other libraries provide some kind of iterator or enumeration like
6604 facility. This is often where the most common API conflicts occur.
6605 Ember's API is designed to be as friendly as possible with other
6606 libraries by implementing only methods that mostly correspond to the
6611 @extends Ember.Mixin
6614 Ember.Enumerable = Ember.Mixin.create(
6615 /** @scope Ember.Enumerable.prototype */ {
6621 Implement this method to make your class enumerable.
6623 This method will be call repeatedly during enumeration. The index value
6624 will always begin with 0 and increment monotonically. You don't have to
6625 rely on the index value to determine what object to return, but you should
6626 always check the value and start from the beginning when you see the
6627 requested index is 0.
6629 The previousObject is the object that was returned from the last call
6630 to nextObject for the current iteration. This is a useful way to
6631 manage iteration if you are tracing a linked list, for example.
6633 Finally the context parameter will always contain a hash you can use as
6634 a "scratchpad" to maintain any other state you need in order to iterate
6635 properly. The context object is reused and is not reset between
6636 iterations so make sure you setup the context with a fresh state whenever
6637 the index parameter is 0.
6639 Generally iterators will continue to call nextObject until the index
6640 reaches the your current length-1. If you run out of data before this
6641 time for some reason, you should simply return undefined.
6643 The default implementation of this method simply looks up the index.
6644 This works great on any Array-like objects.
6647 @param {Number} index the current index of the iteration
6648 @param {Object} previousObject the value returned by the last call to nextObject.
6649 @param {Object} context a context object you can use to maintain state.
6650 @return {Object} the next object in the iteration or undefined
6652 nextObject: Ember.required(Function),
6655 Helper method returns the first object from a collection. This is usually
6656 used by bindings and other parts of the framework to extract a single
6657 object if the enumerable contains only one item.
6659 If you override this method, you should implement it so that it will
6660 always return the same value each time it is called. If your enumerable
6661 contains only one object, this method should always return that object.
6662 If your enumerable is empty, this method should return undefined.
6664 var arr = ["a", "b", "c"];
6665 arr.firstObject(); => "a"
6668 arr.firstObject(); => undefined
6670 @property firstObject
6671 @return {Object} the object or undefined
6673 firstObject: Ember.computed(function() {
6674 if (get(this, 'length')===0) return undefined ;
6676 // handle generic enumerables
6677 var context = popCtx(), ret;
6678 ret = this.nextObject(0, null, context);
6684 Helper method returns the last object from a collection. If your enumerable
6685 contains only one object, this method should always return that object.
6686 If your enumerable is empty, this method should return undefined.
6688 var arr = ["a", "b", "c"];
6689 arr.lastObject(); => "c"
6692 arr.lastObject(); => undefined
6694 @property lastObject
6695 @return {Object} the last object or undefined
6697 lastObject: Ember.computed(function() {
6698 var len = get(this, 'length');
6699 if (len===0) return undefined ;
6700 var context = popCtx(), idx=0, cur, last = null;
6703 cur = this.nextObject(idx++, last, context);
6704 } while (cur !== undefined);
6710 Returns true if the passed object can be found in the receiver. The
6711 default version will iterate through the enumerable until the object
6712 is found. You may want to override this with a more efficient version.
6714 var arr = ["a", "b", "c"];
6715 arr.contains("a"); => true
6716 arr.contains("z"); => false
6719 @param {Object} obj The object to search for.
6720 @return {Boolean} true if object is found in enumerable.
6722 contains: function(obj) {
6723 return this.find(function(item) { return item===obj; }) !== undefined;
6727 Iterates through the enumerable, calling the passed function on each
6728 item. This method corresponds to the forEach() method defined in
6731 The callback method you provide should have the following signature (all
6732 parameters are optional):
6734 function(item, index, enumerable);
6736 - *item* is the current item in the iteration.
6737 - *index* is the current index in the iteration
6738 - *enumerable* is the enumerable object itself.
6740 Note that in addition to a callback, you can also pass an optional target
6741 object that will be set as "this" on the context. This is a good way
6742 to give your iterator function access to the current object.
6745 @param {Function} callback The callback to execute
6746 @param {Object} [target] The target object to use
6747 @return {Object} receiver
6749 forEach: function(callback, target) {
6750 if (typeof callback !== "function") throw new TypeError() ;
6751 var len = get(this, 'length'), last = null, context = popCtx();
6753 if (target === undefined) target = null;
6755 for(var idx=0;idx<len;idx++) {
6756 var next = this.nextObject(idx, last, context) ;
6757 callback.call(target, next, idx, this);
6761 context = pushCtx(context);
6766 Alias for mapProperty
6769 @param {String} key name of the property
6770 @return {Array} The mapped array.
6772 getEach: function(key) {
6773 return this.mapProperty(key);
6777 Sets the value on the named property for each member. This is more
6778 efficient than using other methods defined on this helper. If the object
6779 implements Ember.Observable, the value will be changed to set(), otherwise
6780 it will be set directly. null objects are skipped.
6783 @param {String} key The key to set
6784 @param {Object} value The object to set
6785 @return {Object} receiver
6787 setEach: function(key, value) {
6788 return this.forEach(function(item) {
6789 set(item, key, value);
6794 Maps all of the items in the enumeration to another value, returning
6795 a new array. This method corresponds to map() defined in JavaScript 1.6.
6797 The callback method you provide should have the following signature (all
6798 parameters are optional):
6800 function(item, index, enumerable);
6802 - *item* is the current item in the iteration.
6803 - *index* is the current index in the iteration
6804 - *enumerable* is the enumerable object itself.
6806 It should return the mapped value.
6808 Note that in addition to a callback, you can also pass an optional target
6809 object that will be set as "this" on the context. This is a good way
6810 to give your iterator function access to the current object.
6813 @param {Function} callback The callback to execute
6814 @param {Object} [target] The target object to use
6815 @return {Array} The mapped array.
6817 map: function(callback, target) {
6819 this.forEach(function(x, idx, i) {
6820 ret[idx] = callback.call(target, x, idx,i);
6826 Similar to map, this specialized function returns the value of the named
6827 property on all items in the enumeration.
6830 @param {String} key name of the property
6831 @return {Array} The mapped array.
6833 mapProperty: function(key) {
6834 return this.map(function(next) {
6835 return get(next, key);
6840 Returns an array with all of the items in the enumeration that the passed
6841 function returns true for. This method corresponds to filter() defined in
6844 The callback method you provide should have the following signature (all
6845 parameters are optional):
6847 function(item, index, enumerable);
6849 - *item* is the current item in the iteration.
6850 - *index* is the current index in the iteration
6851 - *enumerable* is the enumerable object itself.
6853 It should return the true to include the item in the results, false otherwise.
6855 Note that in addition to a callback, you can also pass an optional target
6856 object that will be set as "this" on the context. This is a good way
6857 to give your iterator function access to the current object.
6860 @param {Function} callback The callback to execute
6861 @param {Object} [target] The target object to use
6862 @return {Array} A filtered array.
6864 filter: function(callback, target) {
6866 this.forEach(function(x, idx, i) {
6867 if (callback.call(target, x, idx, i)) ret.push(x);
6873 Returns an array with just the items with the matched property. You
6874 can pass an optional second argument with the target value. Otherwise
6875 this will match any property that evaluates to true.
6877 @method filterProperty
6878 @param {String} key the property to test
6879 @param {String} [value] optional value to test against.
6880 @return {Array} filtered array
6882 filterProperty: function(key, value) {
6883 return this.filter(iter.apply(this, arguments));
6887 Returns the first item in the array for which the callback returns true.
6888 This method works similar to the filter() method defined in JavaScript 1.6
6889 except that it will stop working on the array once a match is found.
6891 The callback method you provide should have the following signature (all
6892 parameters are optional):
6894 function(item, index, enumerable);
6896 - *item* is the current item in the iteration.
6897 - *index* is the current index in the iteration
6898 - *enumerable* is the enumerable object itself.
6900 It should return the true to include the item in the results, false otherwise.
6902 Note that in addition to a callback, you can also pass an optional target
6903 object that will be set as "this" on the context. This is a good way
6904 to give your iterator function access to the current object.
6907 @param {Function} callback The callback to execute
6908 @param {Object} [target] The target object to use
6909 @return {Object} Found item or null.
6911 find: function(callback, target) {
6912 var len = get(this, 'length') ;
6913 if (target === undefined) target = null;
6915 var last = null, next, found = false, ret ;
6916 var context = popCtx();
6917 for(var idx=0;idx<len && !found;idx++) {
6918 next = this.nextObject(idx, last, context) ;
6919 if (found = callback.call(target, next, idx, this)) ret = next ;
6922 next = last = null ;
6923 context = pushCtx(context);
6928 Returns the first item with a property matching the passed value. You
6929 can pass an optional second argument with the target value. Otherwise
6930 this will match any property that evaluates to true.
6932 This method works much like the more generic find() method.
6934 @method findProperty
6935 @param {String} key the property to test
6936 @param {String} [value] optional value to test against.
6937 @return {Object} found item or null
6939 findProperty: function(key, value) {
6940 return this.find(iter.apply(this, arguments));
6944 Returns true if the passed function returns true for every item in the
6945 enumeration. This corresponds with the every() method in JavaScript 1.6.
6947 The callback method you provide should have the following signature (all
6948 parameters are optional):
6950 function(item, index, enumerable);
6952 - *item* is the current item in the iteration.
6953 - *index* is the current index in the iteration
6954 - *enumerable* is the enumerable object itself.
6956 It should return the true or false.
6958 Note that in addition to a callback, you can also pass an optional target
6959 object that will be set as "this" on the context. This is a good way
6960 to give your iterator function access to the current object.
6964 if (people.every(isEngineer)) { Paychecks.addBigBonus(); }
6967 @param {Function} callback The callback to execute
6968 @param {Object} [target] The target object to use
6971 every: function(callback, target) {
6972 return !this.find(function(x, idx, i) {
6973 return !callback.call(target, x, idx, i);
6978 Returns true if the passed property resolves to true for all items in the
6979 enumerable. This method is often simpler/faster than using a callback.
6981 @method everyProperty
6982 @param {String} key the property to test
6983 @param {String} [value] optional value to test against.
6984 @return {Array} filtered array
6986 everyProperty: function(key, value) {
6987 return this.every(iter.apply(this, arguments));
6992 Returns true if the passed function returns true for any item in the
6993 enumeration. This corresponds with the every() method in JavaScript 1.6.
6995 The callback method you provide should have the following signature (all
6996 parameters are optional):
6998 function(item, index, enumerable);
7000 - *item* is the current item in the iteration.
7001 - *index* is the current index in the iteration
7002 - *enumerable* is the enumerable object itself.
7004 It should return the true to include the item in the results, false otherwise.
7006 Note that in addition to a callback, you can also pass an optional target
7007 object that will be set as "this" on the context. This is a good way
7008 to give your iterator function access to the current object.
7012 if (people.some(isManager)) { Paychecks.addBiggerBonus(); }
7015 @param {Function} callback The callback to execute
7016 @param {Object} [target] The target object to use
7017 @return {Array} A filtered array.
7019 some: function(callback, target) {
7020 return !!this.find(function(x, idx, i) {
7021 return !!callback.call(target, x, idx, i);
7026 Returns true if the passed property resolves to true for any item in the
7027 enumerable. This method is often simpler/faster than using a callback.
7029 @method someProperty
7030 @param {String} key the property to test
7031 @param {String} [value] optional value to test against.
7032 @return {Boolean} true
7034 someProperty: function(key, value) {
7035 return this.some(iter.apply(this, arguments));
7039 This will combine the values of the enumerator into a single value. It
7040 is a useful way to collect a summary value from an enumeration. This
7041 corresponds to the reduce() method defined in JavaScript 1.8.
7043 The callback method you provide should have the following signature (all
7044 parameters are optional):
7046 function(previousValue, item, index, enumerable);
7048 - *previousValue* is the value returned by the last call to the iterator.
7049 - *item* is the current item in the iteration.
7050 - *index* is the current index in the iteration
7051 - *enumerable* is the enumerable object itself.
7053 Return the new cumulative value.
7055 In addition to the callback you can also pass an initialValue. An error
7056 will be raised if you do not pass an initial value and the enumerator is
7059 Note that unlike the other methods, this method does not allow you to
7060 pass a target object to set as this for the callback. It's part of the
7064 @param {Function} callback The callback to execute
7065 @param {Object} initialValue Initial value for the reduce
7066 @param {String} reducerProperty internal use only.
7067 @return {Object} The reduced value.
7069 reduce: function(callback, initialValue, reducerProperty) {
7070 if (typeof callback !== "function") { throw new TypeError(); }
7072 var ret = initialValue;
7074 this.forEach(function(item, i) {
7075 ret = callback.call(null, ret, item, i, this, reducerProperty);
7082 Invokes the named method on every object in the receiver that
7083 implements it. This method corresponds to the implementation in
7087 @param {String} methodName the name of the method
7088 @param {Object...} args optional arguments to pass as well.
7089 @return {Array} return values from calling invoke.
7091 invoke: function(methodName) {
7093 if (arguments.length>1) args = a_slice.call(arguments, 1);
7095 this.forEach(function(x, idx) {
7096 var method = x && x[methodName];
7097 if ('function' === typeof method) {
7098 ret[idx] = args ? method.apply(x, args) : method.call(x);
7106 Simply converts the enumerable into a genuine array. The order is not
7107 guaranteed. Corresponds to the method implemented by Prototype.
7110 @return {Array} the enumerable as an array.
7112 toArray: function() {
7114 this.forEach(function(o, idx) { ret[idx] = o; });
7119 Returns a copy of the array with all null elements removed.
7121 var arr = ["a", null, "c", null];
7122 arr.compact(); => ["a", "c"]
7125 @return {Array} the array without null elements.
7127 compact: function() { return this.without(null); },
7130 Returns a new enumerable that excludes the passed value. The default
7131 implementation returns an array regardless of the receiver type unless
7132 the receiver does not contain the value.
7134 var arr = ["a", "b", "a", "c"];
7135 arr.without("a"); => ["b", "c"]
7138 @param {Object} value
7139 @return {Ember.Enumerable}
7141 without: function(value) {
7142 if (!this.contains(value)) return this; // nothing to do
7144 this.forEach(function(k) {
7145 if (k !== value) ret[ret.length] = k;
7151 Returns a new enumerable that contains only unique values. The default
7152 implementation returns an array regardless of the receiver type.
7154 var arr = ["a", "a", "b", "b"];
7155 arr.uniq(); => ["a", "b"]
7158 @return {Ember.Enumerable}
7162 this.forEach(function(k){
7163 if (a_indexOf(ret, k)<0) ret.push(k);
7169 This property will trigger anytime the enumerable's content changes.
7170 You can observe this property to be notified of changes to the enumerables
7173 For plain enumerables, this property is read only. Ember.Array overrides
7179 '[]': Ember.computed(function(key, value) {
7183 // ..........................................................
7184 // ENUMERABLE OBSERVERS
7188 Registers an enumerable observer. Must implement Ember.EnumerableObserver
7191 @method addEnumerableObserver
7192 @param target {Object}
7195 addEnumerableObserver: function(target, opts) {
7196 var willChange = (opts && opts.willChange) || 'enumerableWillChange',
7197 didChange = (opts && opts.didChange) || 'enumerableDidChange';
7199 var hasObservers = get(this, 'hasEnumerableObservers');
7200 if (!hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
7201 Ember.addListener(this, '@enumerable:before', target, willChange);
7202 Ember.addListener(this, '@enumerable:change', target, didChange);
7203 if (!hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
7208 Removes a registered enumerable observer.
7210 @method removeEnumerableObserver
7211 @param target {Object}
7212 @param [opts] {Hash}
7214 removeEnumerableObserver: function(target, opts) {
7215 var willChange = (opts && opts.willChange) || 'enumerableWillChange',
7216 didChange = (opts && opts.didChange) || 'enumerableDidChange';
7218 var hasObservers = get(this, 'hasEnumerableObservers');
7219 if (hasObservers) Ember.propertyWillChange(this, 'hasEnumerableObservers');
7220 Ember.removeListener(this, '@enumerable:before', target, willChange);
7221 Ember.removeListener(this, '@enumerable:change', target, didChange);
7222 if (hasObservers) Ember.propertyDidChange(this, 'hasEnumerableObservers');
7227 Becomes true whenever the array currently has observers watching changes
7230 @property hasEnumerableObservers
7233 hasEnumerableObservers: Ember.computed(function() {
7234 return Ember.hasListeners(this, '@enumerable:change') || Ember.hasListeners(this, '@enumerable:before');
7239 Invoke this method just before the contents of your enumerable will
7240 change. You can either omit the parameters completely or pass the objects
7241 to be removed or added if available or just a count.
7243 @method enumerableContentWillChange
7244 @param {Ember.Enumerable|Number} removing An enumerable of the objects to
7245 be removed or the number of items to be removed.
7246 @param {Ember.Enumerable|Number} adding An enumerable of the objects to be
7247 added or the number of items to be added.
7250 enumerableContentWillChange: function(removing, adding) {
7252 var removeCnt, addCnt, hasDelta;
7254 if ('number' === typeof removing) removeCnt = removing;
7255 else if (removing) removeCnt = get(removing, 'length');
7256 else removeCnt = removing = -1;
7258 if ('number' === typeof adding) addCnt = adding;
7259 else if (adding) addCnt = get(adding,'length');
7260 else addCnt = adding = -1;
7262 hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
7264 if (removing === -1) removing = null;
7265 if (adding === -1) adding = null;
7267 Ember.propertyWillChange(this, '[]');
7268 if (hasDelta) Ember.propertyWillChange(this, 'length');
7269 Ember.sendEvent(this, '@enumerable:before', [this, removing, adding]);
7275 Invoke this method when the contents of your enumerable has changed.
7276 This will notify any observers watching for content changes. If your are
7277 implementing an ordered enumerable (such as an array), also pass the
7278 start and end values where the content changed so that it can be used to
7279 notify range observers.
7281 @method enumerableContentDidChange
7282 @param {Number} [start] optional start offset for the content change.
7283 For unordered enumerables, you should always pass -1.
7284 @param {Ember.Enumerable|Number} removing An enumerable of the objects to
7285 be removed or the number of items to be removed.
7286 @param {Ember.Enumerable|Number} adding An enumerable of the objects to
7287 be added or the number of items to be added.
7290 enumerableContentDidChange: function(removing, adding) {
7291 var notify = this.propertyDidChange, removeCnt, addCnt, hasDelta;
7293 if ('number' === typeof removing) removeCnt = removing;
7294 else if (removing) removeCnt = get(removing, 'length');
7295 else removeCnt = removing = -1;
7297 if ('number' === typeof adding) addCnt = adding;
7298 else if (adding) addCnt = get(adding, 'length');
7299 else addCnt = adding = -1;
7301 hasDelta = addCnt<0 || removeCnt<0 || addCnt-removeCnt!==0;
7303 if (removing === -1) removing = null;
7304 if (adding === -1) adding = null;
7306 Ember.sendEvent(this, '@enumerable:change', [this, removing, adding]);
7307 if (hasDelta) Ember.propertyDidChange(this, 'length');
7308 Ember.propertyDidChange(this, '[]');
7325 @submodule ember-runtime
7328 // ..........................................................
7332 var get = Ember.get, set = Ember.set, meta = Ember.meta, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
7334 function none(obj) { return obj===null || obj===undefined; }
7336 // ..........................................................
7340 This module implements Observer-friendly Array-like behavior. This mixin is
7341 picked up by the Array class as well as other controllers, etc. that want to
7342 appear to be arrays.
7344 Unlike Ember.Enumerable, this mixin defines methods specifically for
7345 collections that provide index-ordered access to their contents. When you
7346 are designing code that needs to accept any kind of Array-like object, you
7347 should use these methods instead of Array primitives because these will
7348 properly notify observers of changes to the array.
7350 Although these methods are efficient, they do add a layer of indirection to
7351 your application so it is a good idea to use them only when you need the
7352 flexibility of using both true JavaScript arrays and "virtual" arrays such
7353 as controllers and collections.
7355 You can use the methods defined in this module to access and modify array
7356 contents in a KVO-friendly way. You can also be notified whenever the
7357 membership if an array changes by changing the syntax of the property to
7358 .observes('*myProperty.[]') .
7360 To support Ember.Array in your own class, you must override two
7361 primitives to use it: replace() and objectAt().
7363 Note that the Ember.Array mixin also incorporates the Ember.Enumerable mixin. All
7364 Ember.Array-like objects are also enumerable.
7368 @extends Ember.Mixin
7369 @uses Ember.Enumerable
7372 Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.prototype */ {
7378 Your array must support the length property. Your replace methods should
7379 set this property whenever it changes.
7381 @property {Number} length
7383 length: Ember.required(),
7386 Returns the object at the given index. If the given index is negative or
7387 is greater or equal than the array length, returns `undefined`.
7389 This is one of the primitives you must implement to support `Ember.Array`.
7390 If your object supports retrieving the value of an array item using `get()`
7391 (i.e. `myArray.get(0)`), then you do not need to implement this method
7394 var arr = ['a', 'b', 'c', 'd'];
7395 arr.objectAt(0); => "a"
7396 arr.objectAt(3); => "d"
7397 arr.objectAt(-1); => undefined
7398 arr.objectAt(4); => undefined
7399 arr.objectAt(5); => undefined
7403 The index of the item to return.
7405 objectAt: function(idx) {
7406 if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ;
7407 return get(this, idx);
7411 This returns the objects at the specified indexes, using `objectAt`.
7413 var arr =Â ['a', 'b', 'c', 'd'];
7414 arr.objectsAt([0, 1, 2]) => ["a", "b", "c"]
7415 arr.objectsAt([2, 3, 4]) => ["c", "d", undefined]
7418 @param {Array} indexes
7419 An array of indexes of items to return.
7421 objectsAt: function(indexes) {
7423 return map(indexes, function(idx){ return self.objectAt(idx); });
7426 // overrides Ember.Enumerable version
7427 nextObject: function(idx) {
7428 return this.objectAt(idx);
7432 This is the handler for the special array content property. If you get
7433 this property, it will return this. If you set this property it a new
7434 array, it will replace the current content.
7436 This property overrides the default property defined in Ember.Enumerable.
7440 '[]': Ember.computed(function(key, value) {
7441 if (value !== undefined) this.replace(0, get(this, 'length'), value) ;
7445 firstObject: Ember.computed(function() {
7446 return this.objectAt(0);
7449 lastObject: Ember.computed(function() {
7450 return this.objectAt(get(this, 'length')-1);
7453 // optimized version from Enumerable
7454 contains: function(obj){
7455 return this.indexOf(obj) >= 0;
7458 // Add any extra methods to Ember.Array that are native to the built-in Array.
7460 Returns a new array that is a slice of the receiver. This implementation
7461 uses the observable array methods to retrieve the objects for the new
7464 var arr = ['red', 'green', 'blue'];
7465 arr.slice(0); => ['red', 'green', 'blue']
7466 arr.slice(0, 2); => ['red', 'green']
7467 arr.slice(1, 100); => ['green', 'blue']
7470 @param beginIndex {Integer} (Optional) index to begin slicing from.
7471 @param endIndex {Integer} (Optional) index to end the slice at.
7472 @return {Array} New array with specified slice
7474 slice: function(beginIndex, endIndex) {
7476 var length = get(this, 'length') ;
7477 if (none(beginIndex)) beginIndex = 0 ;
7478 if (none(endIndex) || (endIndex > length)) endIndex = length ;
7479 while(beginIndex < endIndex) {
7480 ret[ret.length] = this.objectAt(beginIndex++) ;
7486 Returns the index of the given object's first occurrence.
7487 If no startAt argument is given, the starting location to
7488 search is 0. If it's negative, will count backward from
7489 the end of the array. Returns -1 if no match is found.
7491 var arr = ["a", "b", "c", "d", "a"];
7492 arr.indexOf("a"); => 0
7493 arr.indexOf("z"); => -1
7494 arr.indexOf("a", 2); => 4
7495 arr.indexOf("a", -1); => 4
7496 arr.indexOf("b", 3); => -1
7497 arr.indexOf("a", 100); => -1
7500 @param {Object} object the item to search for
7501 @param {Number} startAt optional starting location to search, default 0
7502 @return {Number} index or -1 if not found
7504 indexOf: function(object, startAt) {
7505 var idx, len = get(this, 'length');
7507 if (startAt === undefined) startAt = 0;
7508 if (startAt < 0) startAt += len;
7510 for(idx=startAt;idx<len;idx++) {
7511 if (this.objectAt(idx, true) === object) return idx ;
7517 Returns the index of the given object's last occurrence.
7518 If no startAt argument is given, the search starts from
7519 the last position. If it's negative, will count backward
7520 from the end of the array. Returns -1 if no match is found.
7522 var arr = ["a", "b", "c", "d", "a"];
7523 arr.lastIndexOf("a"); => 4
7524 arr.lastIndexOf("z"); => -1
7525 arr.lastIndexOf("a", 2); => 0
7526 arr.lastIndexOf("a", -1); => 4
7527 arr.lastIndexOf("b", 3); => 1
7528 arr.lastIndexOf("a", 100); => 4
7531 @param {Object} object the item to search for
7532 @param {Number} startAt optional starting location to search, default 0
7533 @return {Number} index or -1 if not found
7535 lastIndexOf: function(object, startAt) {
7536 var idx, len = get(this, 'length');
7538 if (startAt === undefined || startAt >= len) startAt = len-1;
7539 if (startAt < 0) startAt += len;
7541 for(idx=startAt;idx>=0;idx--) {
7542 if (this.objectAt(idx) === object) return idx ;
7547 // ..........................................................
7552 Adds an array observer to the receiving array. The array observer object
7553 normally must implement two methods:
7555 * `arrayWillChange(start, removeCount, addCount)` - This method will be
7556 called just before the array is modified.
7557 * `arrayDidChange(start, removeCount, addCount)` - This method will be
7558 called just after the array is modified.
7560 Both callbacks will be passed the starting index of the change as well a
7561 a count of the items to be removed and added. You can use these callbacks
7562 to optionally inspect the array during the change, clear caches, or do
7563 any other bookkeeping necessary.
7565 In addition to passing a target, you can also include an options hash
7566 which you can use to override the method names that will be invoked on the
7569 @method addArrayObserver
7570 @param {Object} target The observer object.
7571 @param {Hash} opts Optional hash of configuration options including
7572 willChange, didChange, and a context option.
7573 @return {Ember.Array} receiver
7575 addArrayObserver: function(target, opts) {
7576 var willChange = (opts && opts.willChange) || 'arrayWillChange',
7577 didChange = (opts && opts.didChange) || 'arrayDidChange';
7579 var hasObservers = get(this, 'hasArrayObservers');
7580 if (!hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
7581 Ember.addListener(this, '@array:before', target, willChange);
7582 Ember.addListener(this, '@array:change', target, didChange);
7583 if (!hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
7588 Removes an array observer from the object if the observer is current
7589 registered. Calling this method multiple times with the same object will
7592 @method removeArrayObserver
7593 @param {Object} target The object observing the array.
7594 @return {Ember.Array} receiver
7596 removeArrayObserver: function(target, opts) {
7597 var willChange = (opts && opts.willChange) || 'arrayWillChange',
7598 didChange = (opts && opts.didChange) || 'arrayDidChange';
7600 var hasObservers = get(this, 'hasArrayObservers');
7601 if (hasObservers) Ember.propertyWillChange(this, 'hasArrayObservers');
7602 Ember.removeListener(this, '@array:before', target, willChange);
7603 Ember.removeListener(this, '@array:change', target, didChange);
7604 if (hasObservers) Ember.propertyDidChange(this, 'hasArrayObservers');
7609 Becomes true whenever the array currently has observers watching changes
7614 hasArrayObservers: Ember.computed(function() {
7615 return Ember.hasListeners(this, '@array:change') || Ember.hasListeners(this, '@array:before');
7619 If you are implementing an object that supports Ember.Array, call this
7620 method just before the array content changes to notify any observers and
7621 invalidate any related properties. Pass the starting index of the change
7622 as well as a delta of the amounts to change.
7624 @method arrayContentWillChange
7625 @param {Number} startIdx The starting index in the array that will change.
7626 @param {Number} removeAmt The number of items that will be removed. If you pass null assumes 0
7627 @param {Number} addAmt The number of items that will be added. If you pass null assumes 0.
7628 @return {Ember.Array} receiver
7630 arrayContentWillChange: function(startIdx, removeAmt, addAmt) {
7632 // if no args are passed assume everything changes
7633 if (startIdx===undefined) {
7635 removeAmt = addAmt = -1;
7637 if (removeAmt === undefined) removeAmt=-1;
7638 if (addAmt === undefined) addAmt=-1;
7641 // Make sure the @each proxy is set up if anyone is observing @each
7642 if (Ember.isWatching(this, '@each')) { get(this, '@each'); }
7644 Ember.sendEvent(this, '@array:before', [this, startIdx, removeAmt, addAmt]);
7647 if (startIdx>=0 && removeAmt>=0 && get(this, 'hasEnumerableObservers')) {
7649 lim = startIdx+removeAmt;
7650 for(var idx=startIdx;idx<lim;idx++) removing.push(this.objectAt(idx));
7652 removing = removeAmt;
7655 this.enumerableContentWillChange(removing, addAmt);
7660 arrayContentDidChange: function(startIdx, removeAmt, addAmt) {
7662 // if no args are passed assume everything changes
7663 if (startIdx===undefined) {
7665 removeAmt = addAmt = -1;
7667 if (removeAmt === undefined) removeAmt=-1;
7668 if (addAmt === undefined) addAmt=-1;
7672 if (startIdx>=0 && addAmt>=0 && get(this, 'hasEnumerableObservers')) {
7674 lim = startIdx+addAmt;
7675 for(var idx=startIdx;idx<lim;idx++) adding.push(this.objectAt(idx));
7680 this.enumerableContentDidChange(removeAmt, adding);
7681 Ember.sendEvent(this, '@array:change', [this, startIdx, removeAmt, addAmt]);
7683 var length = get(this, 'length'),
7684 cachedFirst = cacheFor(this, 'firstObject'),
7685 cachedLast = cacheFor(this, 'lastObject');
7686 if (this.objectAt(0) !== cachedFirst) {
7687 Ember.propertyWillChange(this, 'firstObject');
7688 Ember.propertyDidChange(this, 'firstObject');
7690 if (this.objectAt(length-1) !== cachedLast) {
7691 Ember.propertyWillChange(this, 'lastObject');
7692 Ember.propertyDidChange(this, 'lastObject');
7698 // ..........................................................
7699 // ENUMERATED PROPERTIES
7703 Returns a special object that can be used to observe individual properties
7704 on the array. Just get an equivalent property on this object and it will
7705 return an enumerable that maps automatically to the named key on the
7710 '@each': Ember.computed(function() {
7711 if (!this.__each) this.__each = new Ember.EachProxy(this);
7724 @submodule ember-runtime
7729 Implements some standard methods for comparing objects. Add this mixin to
7730 any class you create that can compare its instances.
7732 You should implement the compare() method.
7736 @extends Ember.Mixin
7739 Ember.Comparable = Ember.Mixin.create( /** @scope Ember.Comparable.prototype */{
7742 walk like a duck. Indicates that the object can be compared.
7744 @property isComparable
7751 Override to return the result of the comparison of the two parameters. The
7752 compare method should return:
7758 Default implementation raises an exception.
7761 @param a {Object} the first object to compare
7762 @param b {Object} the second object to compare
7763 @return {Integer} the result of the comparison
7765 compare: Ember.required(Function)
7777 @submodule ember-runtime
7782 var get = Ember.get, set = Ember.set;
7785 Implements some standard methods for copying an object. Add this mixin to
7786 any object you create that can create a copy of itself. This mixin is
7787 added automatically to the built-in array.
7789 You should generally implement the copy() method to return a copy of the
7792 Note that frozenCopy() will only work if you also implement Ember.Freezable.
7796 @extends Ember.Mixin
7799 Ember.Copyable = Ember.Mixin.create(
7800 /** @scope Ember.Copyable.prototype */ {
7803 Override to return a copy of the receiver. Default implementation raises
7807 @param deep {Boolean} if true, a deep copy of the object should be made
7808 @return {Object} copy of receiver
7810 copy: Ember.required(Function),
7813 If the object implements Ember.Freezable, then this will return a new copy
7814 if the object is not frozen and the receiver if the object is frozen.
7816 Raises an exception if you try to call this method on a object that does
7817 not support freezing.
7819 You should use this method whenever you want a copy of a freezable object
7820 since a freezable object can simply return itself without actually
7821 consuming more memory.
7824 @return {Object} copy of receiver or receiver
7826 frozenCopy: function() {
7827 if (Ember.Freezable && Ember.Freezable.detect(this)) {
7828 return get(this, 'isFrozen') ? this : this.copy().freeze();
7830 throw new Error(Ember.String.fmt("%@ does not support freezing", [this]));
7845 @submodule ember-runtime
7849 var get = Ember.get, set = Ember.set;
7852 The Ember.Freezable mixin implements some basic methods for marking an object
7853 as frozen. Once an object is frozen it should be read only. No changes
7854 may be made the internal state of the object.
7858 To fully support freezing in your subclass, you must include this mixin and
7859 override any method that might alter any property on the object to instead
7860 raise an exception. You can check the state of an object by checking the
7863 Although future versions of JavaScript may support language-level freezing
7864 object objects, that is not the case today. Even if an object is freezable,
7865 it is still technically possible to modify the object, even though it could
7866 break other parts of your application that do not expect a frozen object to
7867 change. It is, therefore, very important that you always respect the
7868 isFrozen property on all freezable objects.
7872 The example below shows a simple object that implement the Ember.Freezable
7875 Contact = Ember.Object.extend(Ember.Freezable, {
7882 swapNames: function() {
7883 if (this.get('isFrozen')) throw Ember.FROZEN_ERROR;
7884 var tmp = this.get('firstName');
7885 this.set('firstName', this.get('lastName'));
7886 this.set('lastName', tmp);
7892 c = Context.create({ firstName: "John", lastName: "Doe" });
7893 c.swapNames(); => returns c
7895 c.swapNames(); => EXCEPTION
7899 Usually the Ember.Freezable protocol is implemented in cooperation with the
7900 Ember.Copyable protocol, which defines a frozenCopy() method that will return
7901 a frozen object, if the object implements this method as well.
7905 @extends Ember.Mixin
7908 Ember.Freezable = Ember.Mixin.create(
7909 /** @scope Ember.Freezable.prototype */ {
7912 Set to true when the object is frozen. Use this property to detect whether
7913 your object is frozen or not.
7921 Freezes the object. Once this method has been called the object should
7922 no longer allow any properties to be edited.
7925 @return {Object} receiver
7927 freeze: function() {
7928 if (get(this, 'isFrozen')) return this;
7929 set(this, 'isFrozen', true);
7935 Ember.FROZEN_ERROR = "Frozen object cannot be modified.";
7944 @submodule ember-runtime
7947 var forEach = Ember.EnumerableUtils.forEach;
7950 This mixin defines the API for modifying generic enumerables. These methods
7951 can be applied to an object regardless of whether it is ordered or
7954 Note that an Enumerable can change even if it does not implement this mixin.
7955 For example, a MappedEnumerable cannot be directly modified but if its
7956 underlying enumerable changes, it will change also.
7960 To add an object to an enumerable, use the addObject() method. This
7961 method will only add the object to the enumerable if the object is not
7962 already present and the object if of a type supported by the enumerable.
7964 set.addObject(contact);
7968 To remove an object form an enumerable, use the removeObject() method. This
7969 will only remove the object if it is already in the enumerable, otherwise
7970 this method has no effect.
7972 set.removeObject(contact);
7974 ## Implementing In Your Own Code
7976 If you are implementing an object and want to support this API, just include
7977 this mixin in your class and implement the required methods. In your unit
7978 tests, be sure to apply the Ember.MutableEnumerableTests to your object.
7980 @class MutableEnumerable
7982 @extends Ember.Mixin
7983 @uses Ember.Enumerable
7985 Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable,
7986 /** @scope Ember.MutableEnumerable.prototype */ {
7989 __Required.__ You must implement this method to apply this mixin.
7991 Attempts to add the passed object to the receiver if the object is not
7992 already present in the collection. If the object is present, this method
7995 If the passed object is of a type not supported by the receiver
7996 then this method should raise an exception.
7999 @param {Object} object The object to add to the enumerable.
8000 @return {Object} the passed object
8002 addObject: Ember.required(Function),
8005 Adds each object in the passed enumerable to the receiver.
8008 @param {Ember.Enumerable} objects the objects to add.
8009 @return {Object} receiver
8011 addObjects: function(objects) {
8012 Ember.beginPropertyChanges(this);
8013 forEach(objects, function(obj) { this.addObject(obj); }, this);
8014 Ember.endPropertyChanges(this);
8019 __Required.__ You must implement this method to apply this mixin.
8021 Attempts to remove the passed object from the receiver collection if the
8022 object is in present in the collection. If the object is not present,
8023 this method has no effect.
8025 If the passed object is of a type not supported by the receiver
8026 then this method should raise an exception.
8028 @method removeObject
8029 @param {Object} object The object to remove from the enumerable.
8030 @return {Object} the passed object
8032 removeObject: Ember.required(Function),
8036 Removes each objects in the passed enumerable from the receiver.
8038 @method removeObjects
8039 @param {Ember.Enumerable} objects the objects to remove
8040 @return {Object} receiver
8042 removeObjects: function(objects) {
8043 Ember.beginPropertyChanges(this);
8044 forEach(objects, function(obj) { this.removeObject(obj); }, this);
8045 Ember.endPropertyChanges(this);
8058 @submodule ember-runtime
8060 // ..........................................................
8064 var OUT_OF_RANGE_EXCEPTION = "Index out of range" ;
8067 // ..........................................................
8071 var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
8074 This mixin defines the API for modifying array-like objects. These methods
8075 can be applied only to a collection that keeps its items in an ordered set.
8077 Note that an Array can change even if it does not implement this mixin.
8078 For example, one might implement a SparseArray that cannot be directly
8079 modified, but if its underlying enumerable changes, it will change also.
8083 @extends Ember.Mixin
8085 @uses Ember.MutableEnumerable
8087 Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
8088 /** @scope Ember.MutableArray.prototype */ {
8091 __Required.__ You must implement this method to apply this mixin.
8093 This is one of the primitives you must implement to support Ember.Array. You
8094 should replace amt objects started at idx with the objects in the passed
8095 array. You should also call this.enumerableContentDidChange() ;
8098 @param {Number} idx Starting index in the array to replace. If idx >= length,
8099 then append to the end of the array.
8100 @param {Number} amt Number of elements that should be removed from the array,
8102 @param {Array} objects An array of zero or more objects that should be inserted
8103 into the array at *idx*
8105 replace: Ember.required(),
8108 Remove all elements from self. This is useful if you
8109 want to reuse an existing array without having to recreate it.
8111 var colors = ["red", "green", "blue"];
8112 color.length(); => 3
8113 colors.clear(); => []
8114 colors.length(); => 0
8117 @return {Ember.Array} An empty Array.
8119 clear: function () {
8120 var len = get(this, 'length');
8121 if (len === 0) return this;
8122 this.replace(0, len, EMPTY);
8127 This will use the primitive replace() method to insert an object at the
8130 var colors = ["red", "green", "blue"];
8131 colors.insertAt(2, "yellow"); => ["red", "green", "yellow", "blue"]
8132 colors.insertAt(5, "orange"); => Error: Index out of range
8135 @param {Number} idx index of insert the object at.
8136 @param {Object} object object to insert
8138 insertAt: function(idx, object) {
8139 if (idx > get(this, 'length')) throw new Error(OUT_OF_RANGE_EXCEPTION) ;
8140 this.replace(idx, 0, [object]) ;
8145 Remove an object at the specified index using the replace() primitive
8146 method. You can pass either a single index, or a start and a length.
8148 If you pass a start and length that is beyond the
8149 length this method will throw an Ember.OUT_OF_RANGE_EXCEPTION
8151 var colors = ["red", "green", "blue", "yellow", "orange"];
8152 colors.removeAt(0); => ["green", "blue", "yellow", "orange"]
8153 colors.removeAt(2, 2); => ["green", "blue"]
8154 colors.removeAt(4, 2); => Error: Index out of range
8157 @param {Number} start index, start of range
8158 @param {Number} len length of passing range
8159 @return {Object} receiver
8161 removeAt: function(start, len) {
8162 if ('number' === typeof start) {
8164 if ((start < 0) || (start >= get(this, 'length'))) {
8165 throw new Error(OUT_OF_RANGE_EXCEPTION);
8169 if (len === undefined) len = 1;
8170 this.replace(start, len, EMPTY);
8177 Push the object onto the end of the array. Works just like push() but it
8180 var colors = ["red", "green", "blue"];
8181 colors.pushObject("black"); => ["red", "green", "blue", "black"]
8182 colors.pushObject(["yellow", "orange"]); => ["red", "green", "blue", "black", ["yellow", "orange"]]
8185 @param {anything} obj object to push
8187 pushObject: function(obj) {
8188 this.insertAt(get(this, 'length'), obj) ;
8193 Add the objects in the passed numerable to the end of the array. Defers
8194 notifying observers of the change until all objects are added.
8196 var colors = ["red", "green", "blue"];
8197 colors.pushObjects("black"); => ["red", "green", "blue", "black"]
8198 colors.pushObjects(["yellow", "orange"]); => ["red", "green", "blue", "black", "yellow", "orange"]
8201 @param {Ember.Enumerable} objects the objects to add
8202 @return {Ember.Array} receiver
8204 pushObjects: function(objects) {
8205 this.replace(get(this, 'length'), 0, objects);
8210 Pop object from array or nil if none are left. Works just like pop() but
8211 it is KVO-compliant.
8213 var colors = ["red", "green", "blue"];
8214 colors.popObject(); => "blue"
8215 console.log(colors); => ["red", "green"]
8220 popObject: function() {
8221 var len = get(this, 'length') ;
8222 if (len === 0) return null ;
8224 var ret = this.objectAt(len-1) ;
8225 this.removeAt(len-1, 1) ;
8230 Shift an object from start of array or nil if none are left. Works just
8231 like shift() but it is KVO-compliant.
8233 var colors = ["red", "green", "blue"];
8234 colors.shiftObject(); => "red"
8235 console.log(colors); => ["green", "blue"]
8240 shiftObject: function() {
8241 if (get(this, 'length') === 0) return null ;
8242 var ret = this.objectAt(0) ;
8248 Unshift an object to start of array. Works just like unshift() but it is
8251 var colors = ["red", "green", "blue"];
8252 colors.unshiftObject("yellow"); => ["yellow", "red", "green", "blue"]
8253 colors.unshiftObject(["black", "white"]); => [["black", "white"], "yellow", "red", "green", "blue"]
8255 @method unshiftObject
8256 @param {anything} obj object to unshift
8258 unshiftObject: function(obj) {
8259 this.insertAt(0, obj) ;
8264 Adds the named objects to the beginning of the array. Defers notifying
8265 observers until all objects have been added.
8267 var colors = ["red", "green", "blue"];
8268 colors.unshiftObjects(["black", "white"]); => ["black", "white", "red", "green", "blue"]
8269 colors.unshiftObjects("yellow"); => Type Error: 'undefined' is not a function
8271 @method unshiftObjects
8272 @param {Ember.Enumerable} objects the objects to add
8273 @return {Ember.Array} receiver
8275 unshiftObjects: function(objects) {
8276 this.replace(0, 0, objects);
8281 Reverse objects in the array. Works just like reverse() but it is
8284 @method reverseObjects
8285 @return {Ember.Array} receiver
8287 reverseObjects: function() {
8288 var len = get(this, 'length');
8289 if (len === 0) return this;
8290 var objects = this.toArray().reverse();
8291 this.replace(0, len, objects);
8296 Replace all the the receiver's content with content of the argument.
8297 If argument is an empty array receiver will be cleared.
8299 var colors = ["red", "green", "blue"];
8300 colors.setObjects(["black", "white"]); => ["black", "white"]
8301 colors.setObjects([]); => []
8304 @param {Ember.Array} objects array whose content will be used for replacing
8305 the content of the receiver
8306 @return {Ember.Array} receiver with the new content
8308 setObjects: function(objects) {
8309 if (objects.length === 0) return this.clear();
8311 var len = get(this, 'length');
8312 this.replace(0, len, objects);
8316 // ..........................................................
8317 // IMPLEMENT Ember.MutableEnumerable
8320 removeObject: function(obj) {
8321 var loc = get(this, 'length') || 0;
8323 var curObject = this.objectAt(loc) ;
8324 if (curObject === obj) this.removeAt(loc) ;
8329 addObject: function(obj) {
8330 if (!this.contains(obj)) this.pushObject(obj);
8344 @submodule ember-runtime
8347 var get = Ember.get, set = Ember.set, defineProperty = Ember.defineProperty;
8352 This mixin provides properties and property observing functionality, core
8353 features of the Ember object model.
8355 Properties and observers allow one object to observe changes to a
8356 property on another object. This is one of the fundamental ways that
8357 models, controllers and views communicate with each other in an Ember
8360 Any object that has this mixin applied can be used in observer
8361 operations. That includes Ember.Object and most objects you will
8362 interact with as you write your Ember application.
8364 Note that you will not generally apply this mixin to classes yourself,
8365 but you will use the features provided by this module frequently, so it
8366 is important to understand how to use it.
8368 ## Using get() and set()
8370 Because of Ember's support for bindings and observers, you will always
8371 access properties using the get method, and set properties using the
8372 set method. This allows the observing objects to be notified and
8373 computed properties to be handled properly.
8375 More documentation about `get` and `set` are below.
8377 ## Observing Property Changes
8379 You typically observe property changes simply by adding the `observes`
8380 call to the end of your method declarations in classes that you write.
8383 Ember.Object.create({
8384 valueObserver: function() {
8385 // Executes whenever the "value" property changes
8389 Although this is the most common way to add an observer, this capability
8390 is actually built into the Ember.Object class on top of two methods
8391 defined in this mixin: `addObserver` and `removeObserver`. You can use
8392 these two methods to add and remove observers yourself if you need to
8395 To add an observer for a property, call:
8397 object.addObserver('propertyKey', targetObject, targetAction)
8399 This will call the `targetAction` method on the `targetObject` to be called
8400 whenever the value of the `propertyKey` changes.
8402 Note that if `propertyKey` is a computed property, the observer will be
8403 called when any of the property dependencies are changed, even if the
8404 resulting value of the computed property is unchanged. This is necessary
8405 because computed properties are not computed until `get` is called.
8409 @extends Ember.Mixin
8411 Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
8414 isObserverable: true,
8417 Retrieves the value of a property from the object.
8419 This method is usually similar to using object[keyName] or object.keyName,
8420 however it supports both computed properties and the unknownProperty
8423 Because `get` unifies the syntax for accessing all these kinds
8424 of properties, it can make many refactorings easier, such as replacing a
8425 simple property with a computed property, or vice versa.
8427 ### Computed Properties
8429 Computed properties are methods defined with the `property` modifier
8430 declared at the end, such as:
8432 fullName: function() {
8433 return this.getEach('firstName', 'lastName').compact().join(' ');
8434 }.property('firstName', 'lastName')
8436 When you call `get` on a computed property, the function will be
8437 called and the return value will be returned instead of the function
8440 ### Unknown Properties
8442 Likewise, if you try to call `get` on a property whose value is
8443 undefined, the unknownProperty() method will be called on the object.
8444 If this method returns any value other than undefined, it will be returned
8445 instead. This allows you to implement "virtual" properties that are
8446 not defined upfront.
8449 @param {String} key The property to retrieve
8450 @return {Object} The property value or undefined.
8452 get: function(keyName) {
8453 return get(this, keyName);
8457 To get multiple properties at once, call getProperties
8458 with a list of strings or an array:
8460 record.getProperties('firstName', 'lastName', 'zipCode'); // => { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
8464 record.getProperties(['firstName', 'lastName', 'zipCode']); // => { firstName: 'John', lastName: 'Doe', zipCode: '10011' }
8466 @method getProperties
8467 @param {String...|Array} list of keys to get
8470 getProperties: function() {
8472 var propertyNames = arguments;
8473 if (arguments.length === 1 && Ember.typeOf(arguments[0]) === 'array') {
8474 propertyNames = arguments[0];
8476 for(var i = 0; i < propertyNames.length; i++) {
8477 ret[propertyNames[i]] = get(this, propertyNames[i]);
8483 Sets the provided key or path to the value.
8485 This method is generally very similar to calling object[key] = value or
8486 object.key = value, except that it provides support for computed
8487 properties, the unknownProperty() method and property observers.
8489 ### Computed Properties
8491 If you try to set a value on a key that has a computed property handler
8492 defined (see the get() method for an example), then set() will call
8493 that method, passing both the value and key instead of simply changing
8494 the value itself. This is useful for those times when you need to
8495 implement a property that is composed of one or more member
8498 ### Unknown Properties
8500 If you try to set a value on a key that is undefined in the target
8501 object, then the unknownProperty() handler will be called instead. This
8502 gives you an opportunity to implement complex "virtual" properties that
8503 are not predefined on the object. If unknownProperty() returns
8504 undefined, then set() will simply set the value on the object.
8506 ### Property Observers
8508 In addition to changing the property, set() will also register a
8509 property change with the object. Unless you have placed this call
8510 inside of a beginPropertyChanges() and endPropertyChanges(), any "local"
8511 observers (i.e. observer methods declared on the same object), will be
8512 called immediately. Any "remote" observers (i.e. observer methods
8513 declared on another object) will be placed in a queue and called at a
8514 later time in a coalesced manner.
8518 In addition to property changes, set() returns the value of the object
8519 itself so you can do chaining like this:
8521 record.set('firstName', 'Charles').set('lastName', 'Jolley');
8524 @param {String} key The property to set
8525 @param {Object} value The value to set or null.
8526 @return {Ember.Observable}
8528 set: function(keyName, value) {
8529 set(this, keyName, value);
8534 To set multiple properties at once, call setProperties
8537 record.setProperties({ firstName: 'Charles', lastName: 'Jolley' });
8539 @method setProperties
8540 @param {Hash} hash the hash of keys and values to set
8541 @return {Ember.Observable}
8543 setProperties: function(hash) {
8544 return Ember.setProperties(this, hash);
8548 Begins a grouping of property changes.
8550 You can use this method to group property changes so that notifications
8551 will not be sent until the changes are finished. If you plan to make a
8552 large number of changes to an object at one time, you should call this
8553 method at the beginning of the changes to begin deferring change
8554 notifications. When you are done making changes, call endPropertyChanges()
8555 to deliver the deferred change notifications and end deferring.
8557 @method beginPropertyChanges
8558 @return {Ember.Observable}
8560 beginPropertyChanges: function() {
8561 Ember.beginPropertyChanges();
8566 Ends a grouping of property changes.
8568 You can use this method to group property changes so that notifications
8569 will not be sent until the changes are finished. If you plan to make a
8570 large number of changes to an object at one time, you should call
8571 beginPropertyChanges() at the beginning of the changes to defer change
8572 notifications. When you are done making changes, call this method to
8573 deliver the deferred change notifications and end deferring.
8575 @method endPropertyChanges
8576 @return {Ember.Observable}
8578 endPropertyChanges: function() {
8579 Ember.endPropertyChanges();
8584 Notify the observer system that a property is about to change.
8586 Sometimes you need to change a value directly or indirectly without
8587 actually calling get() or set() on it. In this case, you can use this
8588 method and propertyDidChange() instead. Calling these two methods
8589 together will notify all observers that the property has potentially
8592 Note that you must always call propertyWillChange and propertyDidChange as
8593 a pair. If you do not, it may get the property change groups out of order
8594 and cause notifications to be delivered more often than you would like.
8596 @method propertyWillChange
8597 @param {String} key The property key that is about to change.
8598 @return {Ember.Observable}
8600 propertyWillChange: function(keyName){
8601 Ember.propertyWillChange(this, keyName);
8606 Notify the observer system that a property has just changed.
8608 Sometimes you need to change a value directly or indirectly without
8609 actually calling get() or set() on it. In this case, you can use this
8610 method and propertyWillChange() instead. Calling these two methods
8611 together will notify all observers that the property has potentially
8614 Note that you must always call propertyWillChange and propertyDidChange as
8615 a pair. If you do not, it may get the property change groups out of order
8616 and cause notifications to be delivered more often than you would like.
8618 @method propertyDidChange
8619 @param {String} keyName The property key that has just changed.
8620 @return {Ember.Observable}
8622 propertyDidChange: function(keyName) {
8623 Ember.propertyDidChange(this, keyName);
8628 Convenience method to call `propertyWillChange` and `propertyDidChange` in
8631 @method notifyPropertyChange
8632 @param {String} keyName The property key to be notified about.
8633 @return {Ember.Observable}
8635 notifyPropertyChange: function(keyName) {
8636 this.propertyWillChange(keyName);
8637 this.propertyDidChange(keyName);
8641 addBeforeObserver: function(key, target, method) {
8642 Ember.addBeforeObserver(this, key, target, method);
8646 Adds an observer on a property.
8648 This is the core method used to register an observer for a property.
8650 Once you call this method, anytime the key's value is set, your observer
8651 will be notified. Note that the observers are triggered anytime the
8652 value is set, regardless of whether it has actually changed. Your
8653 observer should be prepared to handle that.
8655 You can also pass an optional context parameter to this method. The
8656 context will be passed to your observer method whenever it is triggered.
8657 Note that if you add the same target/method pair on a key multiple times
8658 with different context parameters, your observer will only be called once
8659 with the last context you passed.
8661 ### Observer Methods
8663 Observer methods you pass should generally have the following signature if
8664 you do not pass a "context" parameter:
8666 fooDidChange: function(sender, key, value, rev);
8668 The sender is the object that changed. The key is the property that
8669 changes. The value property is currently reserved and unused. The rev
8670 is the last property revision of the object when it changed, which you can
8671 use to detect if the key value has really changed or not.
8673 If you pass a "context" parameter, the context will be passed before the
8676 fooDidChange: function(sender, key, value, context, rev);
8678 Usually you will not need the value, context or revision parameters at
8679 the end. In this case, it is common to write observer methods that take
8680 only a sender and key value as parameters or, if you aren't interested in
8681 any of these values, to write an observer that has no parameters at all.
8684 @param {String} key The key to observer
8685 @param {Object} target The target object to invoke
8686 @param {String|Function} method The method to invoke.
8687 @return {Ember.Object} self
8689 addObserver: function(key, target, method) {
8690 Ember.addObserver(this, key, target, method);
8694 Remove an observer you have previously registered on this object. Pass
8695 the same key, target, and method you passed to addObserver() and your
8696 target will no longer receive notifications.
8698 @method removeObserver
8699 @param {String} key The key to observer
8700 @param {Object} target The target object to invoke
8701 @param {String|Function} method The method to invoke.
8702 @return {Ember.Observable} receiver
8704 removeObserver: function(key, target, method) {
8705 Ember.removeObserver(this, key, target, method);
8709 Returns true if the object currently has observers registered for a
8710 particular key. You can use this method to potentially defer performing
8711 an expensive action until someone begins observing a particular property
8714 @method hasObserverFor
8715 @param {String} key Key to check
8718 hasObserverFor: function(key) {
8719 return Ember.hasListeners(this, key+':change');
8723 This method will be called when a client attempts to get the value of a
8724 property that has not been defined in one of the typical ways. Override
8725 this method to create "virtual" properties.
8727 @method unknownProperty
8728 @param {String} key The name of the unknown property that was requested.
8729 @return {Object} The property value or undefined. Default is undefined.
8731 unknownProperty: function(key) {
8736 This method will be called when a client attempts to set the value of a
8737 property that has not been defined in one of the typical ways. Override
8738 this method to create "virtual" properties.
8740 @method setUnknownProperty
8741 @param {String} key The name of the unknown property to be set.
8742 @param {Object} value The value the unknown property is to be set to.
8744 setUnknownProperty: function(key, value) {
8745 defineProperty(this, key);
8746 set(this, key, value);
8752 @param {String} path The property path to retrieve
8753 @return {Object} The property value or undefined.
8755 getPath: function(path) {
8756 Ember.deprecate("getPath is deprecated since get now supports paths");
8757 return this.get(path);
8763 @param {String} path The path to the property that will be set
8764 @param {Object} value The value to set or null.
8765 @return {Ember.Observable}
8767 setPath: function(path, value) {
8768 Ember.deprecate("setPath is deprecated since set now supports paths");
8769 return this.set(path, value);
8773 Retrieves the value of a property, or a default value in the case that the property
8776 person.getWithDefault('lastName', 'Doe');
8778 @method getWithDefault
8779 @param {String} keyName The name of the property to retrieve
8780 @param {Object} defaultValue The value to return if the property value is undefined
8781 @return {Object} The property value or the defaultValue.
8783 getWithDefault: function(keyName, defaultValue) {
8784 return Ember.getWithDefault(this, keyName, defaultValue);
8788 Set the value of a property to the current value plus some amount.
8790 person.incrementProperty('age');
8791 team.incrementProperty('score', 2);
8793 @method incrementProperty
8794 @param {String} keyName The name of the property to increment
8795 @param {Object} increment The amount to increment by. Defaults to 1
8796 @return {Object} The new property value
8798 incrementProperty: function(keyName, increment) {
8799 if (!increment) { increment = 1; }
8800 set(this, keyName, (get(this, keyName) || 0)+increment);
8801 return get(this, keyName);
8805 Set the value of a property to the current value minus some amount.
8807 player.decrementProperty('lives');
8808 orc.decrementProperty('health', 5);
8810 @method decrementProperty
8811 @param {String} keyName The name of the property to decrement
8812 @param {Object} increment The amount to decrement by. Defaults to 1
8813 @return {Object} The new property value
8815 decrementProperty: function(keyName, increment) {
8816 if (!increment) { increment = 1; }
8817 set(this, keyName, (get(this, keyName) || 0)-increment);
8818 return get(this, keyName);
8822 Set the value of a boolean property to the opposite of it's
8825 starship.toggleProperty('warpDriveEnaged');
8827 @method toggleProperty
8828 @param {String} keyName The name of the property to toggle
8829 @return {Object} The new property value
8831 toggleProperty: function(keyName) {
8832 set(this, keyName, !get(this, keyName));
8833 return get(this, keyName);
8837 Returns the cached value of a computed property, if it exists.
8838 This allows you to inspect the value of a computed property
8839 without accidentally invoking it if it is intended to be
8843 @param {String} keyName
8844 @return {Object} The cached value of the computed property, if any
8846 cacheFor: function(keyName) {
8847 return Ember.cacheFor(this, keyName);
8850 // intended for debugging purposes
8851 observersForKey: function(keyName) {
8852 return Ember.observersFor(this, keyName);
8864 @submodule ember-runtime
8867 var get = Ember.get, set = Ember.set;
8870 @class TargetActionSupport
8872 @extends Ember.Mixin
8874 Ember.TargetActionSupport = Ember.Mixin.create({
8878 targetObject: Ember.computed(function() {
8879 var target = get(this, 'target');
8881 if (Ember.typeOf(target) === "string") {
8882 var value = get(this, target);
8883 if (value === undefined) { value = get(Ember.lookup, target); }
8888 }).property('target'),
8890 triggerAction: function() {
8891 var action = get(this, 'action'),
8892 target = get(this, 'targetObject');
8894 if (target && action) {
8897 if (typeof target.send === 'function') {
8898 ret = target.send(action, this);
8900 if (typeof action === 'string') {
8901 action = target[action];
8903 ret = action.call(target, this);
8905 if (ret !== false) ret = true;
8921 @submodule ember-runtime
8927 @extends Ember.Mixin
8929 Ember.Evented = Ember.Mixin.create({
8930 on: function(name, target, method) {
8931 Ember.addListener(this, name, target, method);
8934 one: function(name, target, method) {
8941 var wrapped = function() {
8942 Ember.removeListener(self, name, target, method);
8944 if ('string' === typeof method) { method = this[method]; }
8946 // Internally, a `null` target means that the target is
8947 // the first parameter to addListener. That means that
8948 // the `this` passed into this function is the target
8949 // determined by the event system.
8950 method.apply(this, arguments);
8953 Ember.addListener(this, name, target, wrapped, Ember.guidFor(method));
8956 trigger: function(name) {
8957 var args = [], i, l;
8958 for (i = 1, l = arguments.length; i < l; i++) {
8959 args.push(arguments[i]);
8961 Ember.sendEvent(this, name, args);
8964 fire: function(name) {
8965 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.");
8966 this.trigger.apply(this, arguments);
8969 off: function(name, target, method) {
8970 Ember.removeListener(this, name, target, method);
8973 has: function(name) {
8974 return Ember.hasListeners(this, name);
8985 @submodule ember-runtime
8988 var get = Ember.get,
8989 slice = Array.prototype.slice;
8994 @extends Ember.Mixin
8996 Ember.Deferred = Ember.Mixin.create({
8999 Add handlers to be called when the Deferred object is resolved or rejected.
9002 @param {Function} doneCallback a callback function to be called when done
9003 @param {Function} failCallback a callback function to be called when failed
9005 then: function(doneCallback, failCallback) {
9006 return get(this, 'promise').then(doneCallback, failCallback);
9010 Resolve a Deferred object and call any doneCallbacks with the given args.
9014 resolve: function(value) {
9015 get(this, 'promise').resolve(value);
9019 Reject a Deferred object and call any failCallbacks with the given args.
9023 reject: function(value) {
9024 get(this, 'promise').reject(value);
9027 promise: Ember.computed(function() {
9028 return new RSVP.Promise();
9045 @submodule ember-runtime
9049 // NOTE: this object should never be included directly. Instead use Ember.
9050 // Ember.Object. We only define this separately so that Ember.Set can depend on it
9053 var set = Ember.set, get = Ember.get,
9054 o_create = Ember.create,
9055 o_defineProperty = Ember.platform.defineProperty,
9056 a_slice = Array.prototype.slice,
9057 GUID_KEY = Ember.GUID_KEY,
9058 guidFor = Ember.guidFor,
9059 generateGuid = Ember.generateGuid,
9061 rewatch = Ember.rewatch,
9062 finishChains = Ember.finishChains,
9063 destroy = Ember.destroy,
9064 schedule = Ember.run.schedule,
9065 Mixin = Ember.Mixin,
9066 applyMixin = Mixin._apply,
9067 finishPartial = Mixin.finishPartial,
9068 reopen = Mixin.prototype.reopen,
9069 classToString = Mixin.prototype.toString;
9071 var undefinedDescriptor = {
9078 function makeCtor() {
9080 // Note: avoid accessing any properties on the object since it makes the
9081 // method a lot faster. This is glue code so we want it to be as fast as
9084 var wasApplied = false, initMixins;
9086 var Class = function() {
9088 Class.proto(); // prepare prototype...
9090 o_defineProperty(this, GUID_KEY, undefinedDescriptor);
9091 o_defineProperty(this, '_super', undefinedDescriptor);
9095 this.reopen.apply(this, initMixins);
9098 finishPartial(this, m);
9101 this.init.apply(this, arguments);
9104 Class.toString = classToString;
9105 Class.willReopen = function() {
9107 Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin);
9112 Class._initMixins = function(args) { initMixins = args; };
9114 Class.proto = function() {
9115 var superclass = Class.superclass;
9116 if (superclass) { superclass.proto(); }
9120 Class.PrototypeMixin.applyPartial(Class.prototype);
9121 rewatch(Class.prototype);
9124 return this.prototype;
9131 var CoreObject = makeCtor();
9133 CoreObject.PrototypeMixin = Mixin.create({
9135 reopen: function() {
9136 applyMixin(this, arguments, true);
9142 init: function() {},
9145 @property isDestroyed
9151 @property isDestroying
9154 isDestroying: false,
9157 Destroys an object by setting the isDestroyed flag and removing its
9158 metadata, which effectively destroys observers and bindings.
9160 If you try to set a property on a destroyed object, an exception will be
9163 Note that destruction is scheduled for the end of the run loop and does not
9167 @return {Ember.Object} receiver
9169 destroy: function() {
9170 if (this.isDestroying) { return; }
9172 this.isDestroying = true;
9174 if (this.willDestroy) { this.willDestroy(); }
9176 set(this, 'isDestroyed', true);
9177 schedule('destroy', this, this._scheduledDestroy);
9184 Invoked by the run loop to actually destroy the object. This is
9185 scheduled for execution by the `destroy` method.
9187 @method _scheduledDestroy
9189 _scheduledDestroy: function() {
9191 if (this.didDestroy) { this.didDestroy(); }
9194 bind: function(to, from) {
9195 if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
9196 from.to(to).connect(this);
9200 toString: function() {
9201 return '<'+this.constructor.toString()+':'+guidFor(this)+'>';
9205 if (Ember.config.overridePrototypeMixin) {
9206 Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin);
9209 CoreObject.__super__ = null;
9211 var ClassMixin = Mixin.create({
9213 ClassMixin: Ember.required(),
9215 PrototypeMixin: Ember.required(),
9221 extend: function() {
9222 var Class = makeCtor(), proto;
9223 Class.ClassMixin = Mixin.create(this.ClassMixin);
9224 Class.PrototypeMixin = Mixin.create(this.PrototypeMixin);
9226 Class.ClassMixin.ownerConstructor = Class;
9227 Class.PrototypeMixin.ownerConstructor = Class;
9229 reopen.apply(Class.PrototypeMixin, arguments);
9231 Class.superclass = this;
9232 Class.__super__ = this.prototype;
9234 proto = Class.prototype = o_create(this.prototype);
9235 proto.constructor = Class;
9236 generateGuid(proto, 'ember');
9237 meta(proto).proto = proto; // this will disable observers on prototype
9239 Class.ClassMixin.apply(Class);
9243 create: function() {
9245 if (arguments.length>0) { this._initMixins(arguments); }
9249 reopen: function() {
9251 reopen.apply(this.PrototypeMixin, arguments);
9255 reopenClass: function() {
9256 reopen.apply(this.ClassMixin, arguments);
9257 applyMixin(this, arguments, false);
9261 detect: function(obj) {
9262 if ('function' !== typeof obj) { return false; }
9264 if (obj===this) { return true; }
9265 obj = obj.superclass;
9270 detectInstance: function(obj) {
9271 return obj instanceof this;
9275 In some cases, you may want to annotate computed properties with additional
9276 metadata about how they function or what values they operate on. For example,
9277 computed property functions may close over variables that are then no longer
9278 available for introspection.
9280 You can pass a hash of these values to a computed property like this:
9282 person: function() {
9283 var personId = this.get('personId');
9284 return App.Person.create({ id: personId });
9285 }.property().meta({ type: App.Person })
9287 Once you've done this, you can retrieve the values saved to the computed
9288 property from your class like this:
9290 MyClass.metaForProperty('person');
9292 This will return the original hash that was passed to `meta()`.
9294 @method metaForProperty
9295 @param key {String} property name
9297 metaForProperty: function(key) {
9298 var desc = meta(this.proto(), false).descs[key];
9300 Ember.assert("metaForProperty() could not find a computed property with key '"+key+"'.", !!desc && desc instanceof Ember.ComputedProperty);
9301 return desc._meta || {};
9305 Iterate over each computed property for the class, passing its name
9306 and any associated metadata (see `metaForProperty`) to the callback.
9308 @method eachComputedProperty
9309 @param {Function} callback
9310 @param {Object} binding
9312 eachComputedProperty: function(callback, binding) {
9313 var proto = this.proto(),
9314 descs = meta(proto).descs,
9318 for (var name in descs) {
9319 property = descs[name];
9321 if (property instanceof Ember.ComputedProperty) {
9322 callback.call(binding || this, name, property._meta || empty);
9329 if (Ember.config.overrideClassMixin) {
9330 Ember.config.overrideClassMixin(ClassMixin);
9333 CoreObject.ClassMixin = ClassMixin;
9334 ClassMixin.apply(CoreObject);
9340 Ember.CoreObject = CoreObject;
9352 @submodule ember-runtime
9355 var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.none;
9358 An unordered collection of objects.
9360 A Set works a bit like an array except that its items are not ordered.
9361 You can create a set to efficiently test for membership for an object. You
9362 can also iterate through a set just like an array, even accessing objects
9363 by index, however there is no guarantee as to their order.
9365 All Sets are observable via the Enumerable Observer API - which works
9366 on any enumerable object including both Sets and Arrays.
9370 You can create a set like you would most objects using
9371 `new Ember.Set()`. Most new sets you create will be empty, but you can
9372 also initialize the set with some content by passing an array or other
9373 enumerable of objects to the constructor.
9375 Finally, you can pass in an existing set and the set will be copied. You
9376 can also create a copy of a set by calling `Ember.Set#copy()`.
9379 // creates a new empty set
9380 var foundNames = new Ember.Set();
9382 // creates a set with four names in it.
9383 var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P
9385 // creates a copy of the names set.
9386 var namesCopy = new Ember.Set(names);
9389 var anotherNamesCopy = names.copy();
9391 ## Adding/Removing Objects
9393 You generally add or remove objects from a set using `add()` or
9394 `remove()`. You can add any type of object including primitives such as
9395 numbers, strings, and booleans.
9397 Unlike arrays, objects can only exist one time in a set. If you call `add()`
9398 on a set with the same object multiple times, the object will only be added
9399 once. Likewise, calling `remove()` with the same object multiple times will
9400 remove the object the first time and have no effect on future calls until
9401 you add the object to the set again.
9403 NOTE: You cannot add/remove null or undefined to a set. Any attempt to do so
9406 In addition to add/remove you can also call `push()`/`pop()`. Push behaves
9407 just like `add()` but `pop()`, unlike `remove()` will pick an arbitrary
9408 object, remove it and return it. This is a good way to use a set as a job
9409 queue when you don't care which order the jobs are executed in.
9411 ## Testing for an Object
9413 To test for an object's presence in a set you simply call
9414 `Ember.Set#contains()`.
9416 ## Observing changes
9418 When using `Ember.Set`, you can observe the `"[]"` property to be
9419 alerted whenever the content changes. You can also add an enumerable
9420 observer to the set to be notified of specific objects that are added and
9421 removed from the set. See `Ember.Enumerable` for more information on
9424 This is often unhelpful. If you are filtering sets of objects, for instance,
9425 it is very inefficient to re-filter all of the items each time the set
9426 changes. It would be better if you could just adjust the filtered set based
9427 on what was changed on the original set. The same issue applies to merging
9432 `Ember.Set` primary implements other mixin APIs. For a complete reference
9433 on the methods you will use with `Ember.Set`, please consult these mixins.
9434 The most useful ones will be `Ember.Enumerable` and
9435 `Ember.MutableEnumerable` which implement most of the common iterator
9436 methods you are used to on Array.
9438 Note that you can also use the `Ember.Copyable` and `Ember.Freezable`
9439 APIs on `Ember.Set` as well. Once a set is frozen it can no longer be
9440 modified. The benefit of this is that when you call frozenCopy() on it,
9441 Ember will avoid making copies of the set. This allows you to write
9442 code that can know with certainty when the underlying set data will or
9443 will not be modified.
9447 @extends Ember.CoreObject
9448 @uses Ember.MutableEnumerable
9449 @uses Ember.Copyable
9450 @uses Ember.Freezable
9453 Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable,
9454 /** @scope Ember.Set.prototype */ {
9456 // ..........................................................
9457 // IMPLEMENT ENUMERABLE APIS
9461 This property will change as the number of objects in the set changes.
9470 Clears the set. This is useful if you want to reuse an existing set
9471 without having to recreate it.
9473 var colors = new Ember.Set(["red", "green", "blue"]);
9479 @return {Ember.Set} An empty Set
9482 if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); }
9484 var len = get(this, 'length');
9485 if (len === 0) { return this; }
9489 this.enumerableContentWillChange(len, 0);
9490 Ember.propertyWillChange(this, 'firstObject');
9491 Ember.propertyWillChange(this, 'lastObject');
9493 for (var i=0; i < len; i++){
9494 guid = guidFor(this[i]);
9499 set(this, 'length', 0);
9501 Ember.propertyDidChange(this, 'firstObject');
9502 Ember.propertyDidChange(this, 'lastObject');
9503 this.enumerableContentDidChange(len, 0);
9509 Returns true if the passed object is also an enumerable that contains the
9510 same objects as the receiver.
9512 var colors = ["red", "green", "blue"],
9513 same_colors = new Ember.Set(colors);
9514 same_colors.isEqual(colors); => true
9515 same_colors.isEqual(["purple", "brown"]); => false
9518 @param {Ember.Set} obj the other object.
9521 isEqual: function(obj) {
9523 if (!Ember.Enumerable.detect(obj)) return false;
9525 var loc = get(this, 'length');
9526 if (get(obj, 'length') !== loc) return false;
9529 if (!obj.contains(this[loc])) return false;
9536 Adds an object to the set. Only non-null objects can be added to a set
9537 and those can only be added once. If the object is already in the set or
9538 the passed value is null this method will have no effect.
9540 This is an alias for `Ember.MutableEnumerable.addObject()`.
9542 var colors = new Ember.Set();
9543 colors.add("blue"); => ["blue"]
9544 colors.add("blue"); => ["blue"]
9545 colors.add("red"); => ["blue", "red"]
9546 colors.add(null); => ["blue", "red"]
9547 colors.add(undefined); => ["blue", "red"]
9550 @param {Object} obj The object to add.
9551 @return {Ember.Set} The set itself.
9553 add: Ember.alias('addObject'),
9556 Removes the object from the set if it is found. If you pass a null value
9557 or an object that is already not in the set, this method will have no
9558 effect. This is an alias for `Ember.MutableEnumerable.removeObject()`.
9560 var colors = new Ember.Set(["red", "green", "blue"]);
9561 colors.remove("red"); => ["blue", "green"]
9562 colors.remove("purple"); => ["blue", "green"]
9563 colors.remove(null); => ["blue", "green"]
9566 @param {Object} obj The object to remove
9567 @return {Ember.Set} The set itself.
9569 remove: Ember.alias('removeObject'),
9572 Removes the last element from the set and returns it, or null if it's empty.
9574 var colors = new Ember.Set(["green", "blue"]);
9575 colors.pop(); => "blue"
9576 colors.pop(); => "green"
9577 colors.pop(); => null
9580 @return {Object} The removed object from the set or null.
9583 if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
9584 var obj = this.length > 0 ? this[this.length-1] : null;
9590 Inserts the given object on to the end of the set. It returns
9593 This is an alias for `Ember.MutableEnumerable.addObject()`.
9595 var colors = new Ember.Set();
9596 colors.push("red"); => ["red"]
9597 colors.push("green"); => ["red", "green"]
9598 colors.push("blue"); => ["red", "green", "blue"]
9601 @return {Ember.Set} The set itself.
9603 push: Ember.alias('addObject'),
9606 Removes the last element from the set and returns it, or null if it's empty.
9608 This is an alias for `Ember.Set.pop()`.
9610 var colors = new Ember.Set(["green", "blue"]);
9611 colors.shift(); => "blue"
9612 colors.shift(); => "green"
9613 colors.shift(); => null
9616 @return {Object} The removed object from the set or null.
9618 shift: Ember.alias('pop'),
9621 Inserts the given object on to the end of the set. It returns
9624 This is an alias of `Ember.Set.push()`
9626 var colors = new Ember.Set();
9627 colors.unshift("red"); => ["red"]
9628 colors.unshift("green"); => ["red", "green"]
9629 colors.unshift("blue"); => ["red", "green", "blue"]
9632 @return {Ember.Set} The set itself.
9634 unshift: Ember.alias('push'),
9637 Adds each object in the passed enumerable to the set.
9639 This is an alias of `Ember.MutableEnumerable.addObjects()`
9641 var colors = new Ember.Set();
9642 colors.addEach(["red", "green", "blue"]); => ["red", "green", "blue"]
9645 @param {Ember.Enumerable} objects the objects to add.
9646 @return {Ember.Set} The set itself.
9648 addEach: Ember.alias('addObjects'),
9651 Removes each object in the passed enumerable to the set.
9653 This is an alias of `Ember.MutableEnumerable.removeObjects()`
9655 var colors = new Ember.Set(["red", "green", "blue"]);
9656 colors.removeEach(["red", "blue"]); => ["green"]
9659 @param {Ember.Enumerable} objects the objects to remove.
9660 @return {Ember.Set} The set itself.
9662 removeEach: Ember.alias('removeObjects'),
9664 // ..........................................................
9665 // PRIVATE ENUMERABLE SUPPORT
9668 init: function(items) {
9670 if (items) this.addObjects(items);
9673 // implement Ember.Enumerable
9674 nextObject: function(idx) {
9678 // more optimized version
9679 firstObject: Ember.computed(function() {
9680 return this.length > 0 ? this[0] : undefined;
9683 // more optimized version
9684 lastObject: Ember.computed(function() {
9685 return this.length > 0 ? this[this.length-1] : undefined;
9688 // implements Ember.MutableEnumerable
9689 addObject: function(obj) {
9690 if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
9691 if (none(obj)) return this; // nothing to do
9693 var guid = guidFor(obj),
9695 len = get(this, 'length'),
9698 if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added
9702 this.enumerableContentWillChange(null, added);
9703 Ember.propertyWillChange(this, 'lastObject');
9705 len = get(this, 'length');
9708 set(this, 'length', len+1);
9710 Ember.propertyDidChange(this, 'lastObject');
9711 this.enumerableContentDidChange(null, added);
9716 // implements Ember.MutableEnumerable
9717 removeObject: function(obj) {
9718 if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
9719 if (none(obj)) return this; // nothing to do
9721 var guid = guidFor(obj),
9723 len = get(this, 'length'),
9724 isFirst = idx === 0,
9725 isLast = idx === len-1,
9729 if (idx>=0 && idx<len && (this[idx] === obj)) {
9732 this.enumerableContentWillChange(removed, null);
9733 if (isFirst) { Ember.propertyWillChange(this, 'firstObject'); }
9734 if (isLast) { Ember.propertyWillChange(this, 'lastObject'); }
9736 // swap items - basically move the item to the end so it can be removed
9740 this[guidFor(last)] = idx;
9745 set(this, 'length', len-1);
9747 if (isFirst) { Ember.propertyDidChange(this, 'firstObject'); }
9748 if (isLast) { Ember.propertyDidChange(this, 'lastObject'); }
9749 this.enumerableContentDidChange(removed, null);
9755 // optimized version
9756 contains: function(obj) {
9757 return this[guidFor(obj)]>=0;
9761 var C = this.constructor, ret = new C(), loc = get(this, 'length');
9762 set(ret, 'length', loc);
9764 ret[loc] = this[loc];
9765 ret[guidFor(this[loc])] = loc;
9770 toString: function() {
9771 var len = this.length, idx, array = [];
9772 for(idx = 0; idx < len; idx++) {
9773 array[idx] = this[idx];
9775 return "Ember.Set<%@>".fmt(array.join(','));
9787 @submodule ember-runtime
9791 `Ember.Object` is the main base class for all Ember objects. It is a subclass
9792 of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details,
9793 see the documentation for each of these.
9797 @extends Ember.CoreObject
9798 @uses Ember.Observable
9800 Ember.Object = Ember.CoreObject.extend(Ember.Observable);
9809 @submodule ember-runtime
9812 var indexOf = Ember.ArrayPolyfills.indexOf;
9815 A Namespace is an object usually used to contain other objects or methods
9816 such as an application or framework. Create a namespace anytime you want
9817 to define one of these new containers.
9821 MyFramework = Ember.Namespace.create({
9827 @extends Ember.Object
9829 Ember.Namespace = Ember.Object.extend({
9833 Ember.Namespace.NAMESPACES.push(this);
9834 Ember.Namespace.PROCESSED = false;
9837 toString: function() {
9838 Ember.identifyNamespaces();
9839 return this[Ember.GUID_KEY+'_name'];
9842 destroy: function() {
9843 var namespaces = Ember.Namespace.NAMESPACES;
9844 Ember.lookup[this.toString()] = undefined;
9845 namespaces.splice(indexOf.call(namespaces, this), 1);
9850 Ember.Namespace.NAMESPACES = [Ember];
9851 Ember.Namespace.PROCESSED = false;
9860 @submodule ember-runtime
9864 Defines a namespace that will contain an executable application. This is
9865 very similar to a normal namespace except that it is expected to include at
9866 least a 'ready' function which can be run to initialize the application.
9868 Currently Ember.Application is very similar to Ember.Namespace. However, this
9869 class may be augmented by additional frameworks so it is important to use
9870 this instance when building new applications.
9874 MyApp = Ember.Application.create({
9876 store: Ember.Store.create().from(Ember.fixtures)
9879 MyApp.ready = function() {
9880 //..init code goes here...
9885 @extends Ember.Namespace
9887 Ember.Application = Ember.Namespace.extend();
9897 @submodule ember-runtime
9901 var get = Ember.get, set = Ember.set;
9904 An ArrayProxy wraps any other object that implements Ember.Array and/or
9905 Ember.MutableArray, forwarding all requests. This makes it very useful for
9906 a number of binding use cases or other cases where being able to swap
9907 out the underlying array is useful.
9909 A simple example of usage:
9911 var pets = ['dog', 'cat', 'fish'];
9912 var ap = Ember.ArrayProxy.create({ content: Ember.A(pets) });
9913 ap.get('firstObject'); // => 'dog'
9914 ap.set('content', ['amoeba', 'paramecium']);
9915 ap.get('firstObject'); // => 'amoeba'
9917 This class can also be useful as a layer to transform the contents of
9918 an array, as they are accessed. This can be done by overriding
9921 var pets = ['dog', 'cat', 'fish'];
9922 var ap = Ember.ArrayProxy.create({
9923 content: Ember.A(pets),
9924 objectAtContent: function(idx) {
9925 return this.get('content').objectAt(idx).toUpperCase();
9928 ap.get('firstObject'); // => 'DOG'
9933 @extends Ember.Object
9934 @uses Ember.MutableArray
9936 Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,
9937 /** @scope Ember.ArrayProxy.prototype */ {
9940 The content array. Must be an object that implements Ember.Array and/or
9949 The array that the proxy pretends to be. In the default `ArrayProxy`
9950 implementation, this and `content` are the same. Subclasses of `ArrayProxy`
9951 can override this property to provide things like sorting and filtering.
9953 @property arrangedContent
9955 arrangedContent: Ember.computed('content', function() {
9956 return get(this, 'content');
9960 Should actually retrieve the object at the specified index from the
9961 content. You can override this method in subclasses to transform the
9962 content item to something new.
9964 This method will only be called if content is non-null.
9966 @method objectAtContent
9967 @param {Number} idx The index to retrieve.
9968 @return {Object} the value or undefined if none found
9970 objectAtContent: function(idx) {
9971 return get(this, 'arrangedContent').objectAt(idx);
9975 Should actually replace the specified objects on the content array.
9976 You can override this method in subclasses to transform the content item
9979 This method will only be called if content is non-null.
9981 @method replaceContent
9982 @param {Number} idx The starting index
9983 @param {Number} amt The number of items to remove from the content.
9984 @param {Array} objects Optional array of objects to insert or null if no objects.
9987 replaceContent: function(idx, amt, objects) {
9988 get(this, 'arrangedContent').replace(idx, amt, objects);
9994 Invoked when the content property is about to change. Notifies observers that the
9995 entire array content will change.
9997 @method _contentWillChange
9999 _contentWillChange: Ember.beforeObserver(function() {
10000 this._teardownContent();
10003 _teardownContent: function() {
10004 var content = get(this, 'content');
10007 content.removeArrayObserver(this, {
10008 willChange: 'contentArrayWillChange',
10009 didChange: 'contentArrayDidChange'
10014 contentArrayWillChange: Ember.K,
10015 contentArrayDidChange: Ember.K,
10020 Invoked when the content property changes. Notifies observers that the
10021 entire array content has changed.
10023 @method _contentDidChange
10025 _contentDidChange: Ember.observer(function() {
10026 var content = get(this, 'content');
10028 Ember.assert("Can't set ArrayProxy's content to itself", content !== this);
10030 this._setupContent();
10033 _setupContent: function() {
10034 var content = get(this, 'content');
10037 content.addArrayObserver(this, {
10038 willChange: 'contentArrayWillChange',
10039 didChange: 'contentArrayDidChange'
10044 _arrangedContentWillChange: Ember.beforeObserver(function() {
10045 var arrangedContent = get(this, 'arrangedContent'),
10046 len = arrangedContent ? get(arrangedContent, 'length') : 0;
10048 this.arrangedContentArrayWillChange(this, 0, len, undefined);
10049 this.arrangedContentWillChange(this);
10051 this._teardownArrangedContent(arrangedContent);
10052 }, 'arrangedContent'),
10054 _arrangedContentDidChange: Ember.observer(function() {
10055 var arrangedContent = get(this, 'arrangedContent'),
10056 len = arrangedContent ? get(arrangedContent, 'length') : 0;
10058 Ember.assert("Can't set ArrayProxy's content to itself", arrangedContent !== this);
10060 this._setupArrangedContent();
10062 this.arrangedContentDidChange(this);
10063 this.arrangedContentArrayDidChange(this, 0, undefined, len);
10064 }, 'arrangedContent'),
10066 _setupArrangedContent: function() {
10067 var arrangedContent = get(this, 'arrangedContent');
10069 if (arrangedContent) {
10070 arrangedContent.addArrayObserver(this, {
10071 willChange: 'arrangedContentArrayWillChange',
10072 didChange: 'arrangedContentArrayDidChange'
10077 _teardownArrangedContent: function() {
10078 var arrangedContent = get(this, 'arrangedContent');
10080 if (arrangedContent) {
10081 arrangedContent.removeArrayObserver(this, {
10082 willChange: 'arrangedContentArrayWillChange',
10083 didChange: 'arrangedContentArrayDidChange'
10088 arrangedContentWillChange: Ember.K,
10089 arrangedContentDidChange: Ember.K,
10091 objectAt: function(idx) {
10092 return get(this, 'content') && this.objectAtContent(idx);
10095 length: Ember.computed(function() {
10096 var arrangedContent = get(this, 'arrangedContent');
10097 return arrangedContent ? get(arrangedContent, 'length') : 0;
10098 // No dependencies since Enumerable notifies length of change
10101 replace: function(idx, amt, objects) {
10102 Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', this.get('content'));
10103 if (get(this, 'content')) this.replaceContent(idx, amt, objects);
10107 arrangedContentArrayWillChange: function(item, idx, removedCnt, addedCnt) {
10108 this.arrayContentWillChange(idx, removedCnt, addedCnt);
10111 arrangedContentArrayDidChange: function(item, idx, removedCnt, addedCnt) {
10112 this.arrayContentDidChange(idx, removedCnt, addedCnt);
10117 this._setupContent();
10118 this._setupArrangedContent();
10121 willDestroy: function() {
10122 this._teardownArrangedContent();
10123 this._teardownContent();
10135 @submodule ember-runtime
10138 var get = Ember.get,
10140 fmt = Ember.String.fmt,
10141 addBeforeObserver = Ember.addBeforeObserver,
10142 addObserver = Ember.addObserver,
10143 removeBeforeObserver = Ember.removeBeforeObserver,
10144 removeObserver = Ember.removeObserver,
10145 propertyWillChange = Ember.propertyWillChange,
10146 propertyDidChange = Ember.propertyDidChange;
10148 function contentPropertyWillChange(content, contentKey) {
10149 var key = contentKey.slice(8); // remove "content."
10150 if (key in this) { return; } // if shadowed in proxy
10151 propertyWillChange(this, key);
10154 function contentPropertyDidChange(content, contentKey) {
10155 var key = contentKey.slice(8); // remove "content."
10156 if (key in this) { return; } // if shadowed in proxy
10157 propertyDidChange(this, key);
10161 `Ember.ObjectProxy` forwards all properties not defined by the proxy itself
10162 to a proxied `content` object.
10164 object = Ember.Object.create({
10167 proxy = Ember.ObjectProxy.create({
10171 // Access and change existing properties
10172 proxy.get('name') // => 'Foo'
10173 proxy.set('name', 'Bar');
10174 object.get('name') // => 'Bar'
10176 // Create new 'description' property on `object`
10177 proxy.set('description', 'Foo is a whizboo baz');
10178 object.get('description') // => 'Foo is a whizboo baz'
10180 While `content` is unset, setting a property to be delegated will throw an Error.
10182 proxy = Ember.ObjectProxy.create({
10186 proxy.set('flag', true);
10187 proxy.get('flag'); // => true
10188 proxy.get('foo'); // => undefined
10189 proxy.set('foo', 'data'); // throws Error
10191 Delegated properties can be bound to and will change when content is updated.
10193 Computed properties on the proxy itself can depend on delegated properties.
10195 ProxyWithComputedProperty = Ember.ObjectProxy.extend({
10196 fullName: function () {
10197 var firstName = this.get('firstName'),
10198 lastName = this.get('lastName');
10199 if (firstName && lastName) {
10200 return firstName + ' ' + lastName;
10202 return firstName || lastName;
10203 }.property('firstName', 'lastName')
10205 proxy = ProxyWithComputedProperty.create();
10206 proxy.get('fullName'); => undefined
10207 proxy.set('content', {
10208 firstName: 'Tom', lastName: 'Dale'
10209 }); // triggers property change for fullName on proxy
10210 proxy.get('fullName'); => 'Tom Dale'
10214 @extends Ember.Object
10216 Ember.ObjectProxy = Ember.Object.extend(
10217 /** @scope Ember.ObjectProxy.prototype */ {
10219 The object whose properties will be forwarded.
10226 _contentDidChange: Ember.observer(function() {
10227 Ember.assert("Can't set ObjectProxy's content to itself", this.get('content') !== this);
10230 willWatchProperty: function (key) {
10231 var contentKey = 'content.' + key;
10232 addBeforeObserver(this, contentKey, null, contentPropertyWillChange);
10233 addObserver(this, contentKey, null, contentPropertyDidChange);
10236 didUnwatchProperty: function (key) {
10237 var contentKey = 'content.' + key;
10238 removeBeforeObserver(this, contentKey, null, contentPropertyWillChange);
10239 removeObserver(this, contentKey, null, contentPropertyDidChange);
10242 unknownProperty: function (key) {
10243 var content = get(this, 'content');
10245 return get(content, key);
10249 setUnknownProperty: function (key, value) {
10250 var content = get(this, 'content');
10251 Ember.assert(fmt("Cannot delegate set('%@', %@) to the 'content' property of object proxy %@: its 'content' is undefined.", [key, value, this]), content);
10252 return set(content, key, value);
10263 @submodule ember-runtime
10267 var set = Ember.set, get = Ember.get, guidFor = Ember.guidFor;
10268 var forEach = Ember.EnumerableUtils.forEach;
10270 var EachArray = Ember.Object.extend(Ember.Array, {
10272 init: function(content, keyName, owner) {
10274 this._keyName = keyName;
10275 this._owner = owner;
10276 this._content = content;
10279 objectAt: function(idx) {
10280 var item = this._content.objectAt(idx);
10281 return item && get(item, this._keyName);
10284 length: Ember.computed(function() {
10285 var content = this._content;
10286 return content ? get(content, 'length') : 0;
10291 var IS_OBSERVER = /^.+:(before|change)$/;
10293 function addObserverForContentKey(content, keyName, proxy, idx, loc) {
10294 var objects = proxy._objects, guid;
10295 if (!objects) objects = proxy._objects = {};
10297 while(--loc>=idx) {
10298 var item = content.objectAt(loc);
10300 Ember.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
10301 Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange');
10303 // keep track of the indicies each item was found at so we can map
10304 // it back when the obj changes.
10305 guid = guidFor(item);
10306 if (!objects[guid]) objects[guid] = [];
10307 objects[guid].push(loc);
10312 function removeObserverForContentKey(content, keyName, proxy, idx, loc) {
10313 var objects = proxy._objects;
10314 if (!objects) objects = proxy._objects = {};
10315 var indicies, guid;
10317 while(--loc>=idx) {
10318 var item = content.objectAt(loc);
10320 Ember.removeBeforeObserver(item, keyName, proxy, 'contentKeyWillChange');
10321 Ember.removeObserver(item, keyName, proxy, 'contentKeyDidChange');
10323 guid = guidFor(item);
10324 indicies = objects[guid];
10325 indicies[indicies.indexOf(loc)] = null;
10331 This is the object instance returned when you get the @each property on an
10332 array. It uses the unknownProperty handler to automatically create
10333 EachArray instances for property names.
10338 @extends Ember.Object
10340 Ember.EachProxy = Ember.Object.extend({
10342 init: function(content) {
10344 this._content = content;
10345 content.addArrayObserver(this);
10347 // in case someone is already observing some keys make sure they are
10349 forEach(Ember.watchedEvents(this), function(eventName) {
10350 this.didAddListener(eventName);
10355 You can directly access mapped properties by simply requesting them.
10356 The unknownProperty handler will generate an EachArray of each item.
10358 @method unknownProperty
10359 @param keyName {String}
10360 @param value {anything}
10362 unknownProperty: function(keyName, value) {
10364 ret = new EachArray(this._content, keyName, this);
10365 Ember.defineProperty(this, keyName, null, ret);
10366 this.beginObservingContentKey(keyName);
10370 // ..........................................................
10372 // Invokes whenever the content array itself changes.
10374 arrayWillChange: function(content, idx, removedCnt, addedCnt) {
10375 var keys = this._keys, key, array, lim;
10377 lim = removedCnt>0 ? idx+removedCnt : -1;
10378 Ember.beginPropertyChanges(this);
10381 if (!keys.hasOwnProperty(key)) { continue; }
10383 if (lim>0) removeObserverForContentKey(content, key, this, idx, lim);
10385 Ember.propertyWillChange(this, key);
10388 Ember.propertyWillChange(this._content, '@each');
10389 Ember.endPropertyChanges(this);
10392 arrayDidChange: function(content, idx, removedCnt, addedCnt) {
10393 var keys = this._keys, key, array, lim;
10395 lim = addedCnt>0 ? idx+addedCnt : -1;
10396 Ember.beginPropertyChanges(this);
10399 if (!keys.hasOwnProperty(key)) { continue; }
10401 if (lim>0) addObserverForContentKey(content, key, this, idx, lim);
10403 Ember.propertyDidChange(this, key);
10406 Ember.propertyDidChange(this._content, '@each');
10407 Ember.endPropertyChanges(this);
10410 // ..........................................................
10411 // LISTEN FOR NEW OBSERVERS AND OTHER EVENT LISTENERS
10412 // Start monitoring keys based on who is listening...
10414 didAddListener: function(eventName) {
10415 if (IS_OBSERVER.test(eventName)) {
10416 this.beginObservingContentKey(eventName.slice(0, -7));
10420 didRemoveListener: function(eventName) {
10421 if (IS_OBSERVER.test(eventName)) {
10422 this.stopObservingContentKey(eventName.slice(0, -7));
10426 // ..........................................................
10427 // CONTENT KEY OBSERVING
10428 // Actual watch keys on the source content.
10430 beginObservingContentKey: function(keyName) {
10431 var keys = this._keys;
10432 if (!keys) keys = this._keys = {};
10433 if (!keys[keyName]) {
10435 var content = this._content,
10436 len = get(content, 'length');
10437 addObserverForContentKey(content, keyName, this, 0, len);
10443 stopObservingContentKey: function(keyName) {
10444 var keys = this._keys;
10445 if (keys && (keys[keyName]>0) && (--keys[keyName]<=0)) {
10446 var content = this._content,
10447 len = get(content, 'length');
10448 removeObserverForContentKey(content, keyName, this, 0, len);
10452 contentKeyWillChange: function(obj, keyName) {
10453 Ember.propertyWillChange(this, keyName);
10456 contentKeyDidChange: function(obj, keyName) {
10457 Ember.propertyDidChange(this, keyName);
10471 @submodule ember-runtime
10475 var get = Ember.get, set = Ember.set;
10477 // Add Ember.Array to Array.prototype. Remove methods with native
10478 // implementations and supply some more optimized versions of generic methods
10479 // because they are so common.
10480 var NativeArray = Ember.Mixin.create(Ember.MutableArray, Ember.Observable, Ember.Copyable, {
10482 // because length is a built-in property we need to know to just get the
10483 // original property.
10484 get: function(key) {
10485 if (key==='length') return this.length;
10486 else if ('number' === typeof key) return this[key];
10487 else return this._super(key);
10490 objectAt: function(idx) {
10494 // primitive for array support.
10495 replace: function(idx, amt, objects) {
10497 if (this.isFrozen) throw Ember.FROZEN_ERROR ;
10499 // if we replaced exactly the same number of items, then pass only the
10500 // replaced range. Otherwise, pass the full remaining array length
10501 // since everything has shifted
10502 var len = objects ? get(objects, 'length') : 0;
10503 this.arrayContentWillChange(idx, amt, len);
10505 if (!objects || objects.length === 0) {
10506 this.splice(idx, amt) ;
10508 var args = [idx, amt].concat(objects) ;
10509 this.splice.apply(this,args) ;
10512 this.arrayContentDidChange(idx, amt, len);
10516 // If you ask for an unknown property, then try to collect the value
10517 // from member items.
10518 unknownProperty: function(key, value) {
10519 var ret;// = this.reducedProperty(key, value) ;
10520 if ((value !== undefined) && ret === undefined) {
10521 ret = this[key] = value;
10526 // If browser did not implement indexOf natively, then override with
10527 // specialized version
10528 indexOf: function(object, startAt) {
10529 var idx, len = this.length;
10531 if (startAt === undefined) startAt = 0;
10532 else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
10533 if (startAt < 0) startAt += len;
10535 for(idx=startAt;idx<len;idx++) {
10536 if (this[idx] === object) return idx ;
10541 lastIndexOf: function(object, startAt) {
10542 var idx, len = this.length;
10544 if (startAt === undefined) startAt = len-1;
10545 else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
10546 if (startAt < 0) startAt += len;
10548 for(idx=startAt;idx>=0;idx--) {
10549 if (this[idx] === object) return idx ;
10555 return this.slice();
10559 // Remove any methods implemented natively so we don't override them
10560 var ignore = ['length'];
10561 Ember.EnumerableUtils.forEach(NativeArray.keys(), function(methodName) {
10562 if (Array.prototype[methodName]) ignore.push(methodName);
10565 if (ignore.length>0) {
10566 NativeArray = NativeArray.without.apply(NativeArray, ignore);
10570 The NativeArray mixin contains the properties needed to to make the native
10571 Array support Ember.MutableArray and all of its dependent APIs. Unless you
10572 have Ember.EXTEND_PROTOTYPES or Ember.EXTEND_PROTOTYPES.Array set to false, this
10573 will be applied automatically. Otherwise you can apply the mixin at anytime by
10574 calling `Ember.NativeArray.activate`.
10578 @extends Ember.Mixin
10579 @uses Ember.MutableArray
10580 @uses Ember.MutableEnumerable
10581 @uses Ember.Copyable
10582 @uses Ember.Freezable
10584 Ember.NativeArray = NativeArray;
10587 Creates an Ember.NativeArray from an Array like object.
10588 Does not modify the original object.
10592 @return {Ember.NativeArray}
10594 Ember.A = function(arr){
10595 if (arr === undefined) { arr = []; }
10596 return Ember.NativeArray.apply(arr);
10600 Activates the mixin on the Array.prototype if not already applied. Calling
10601 this method more than once is safe.
10604 @for Ember.NativeArray
10608 Ember.NativeArray.activate = function() {
10609 NativeArray.apply(Array.prototype);
10611 Ember.A = function(arr) { return arr || []; };
10614 if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.Array) {
10615 Ember.NativeArray.activate();
10626 @submodule ember-runtime
10629 var get = Ember.get, set = Ember.set;
10631 Ember._PromiseChain = Ember.Object.extend({
10633 failureCallback: Ember.K,
10634 successCallback: Ember.K,
10635 abortCallback: Ember.K,
10636 promiseSuccessCallback: Ember.K,
10638 runNextPromise: function() {
10639 if (get(this, 'isDestroyed')) { return; }
10641 var item = get(this, 'promises').shiftObject();
10643 var promise = get(item, 'promise') || item;
10644 Ember.assert("Cannot find promise to invoke", Ember.canInvoke(promise, 'then'));
10648 var successCallback = function() {
10649 self.promiseSuccessCallback.call(this, item, arguments);
10650 self.runNextPromise();
10653 var failureCallback = get(self, 'failureCallback');
10655 promise.then(successCallback, failureCallback);
10657 this.successCallback();
10661 start: function() {
10662 this.runNextPromise();
10666 abort: function() {
10667 this.abortCallback();
10672 set(this, 'promises', Ember.A(get(this, 'promises')));
10685 @submodule ember-runtime
10688 var loadHooks = {};
10694 @param name {String} name of hook
10695 @param callback {Function} callback to be called
10697 Ember.onLoad = function(name, callback) {
10700 loadHooks[name] = loadHooks[name] || Ember.A();
10701 loadHooks[name].pushObject(callback);
10703 if (object = loaded[name]) {
10709 @method runLoadHooks
10711 @param name {String} name of hook
10712 @param object {Object} object to pass to callbacks
10714 Ember.runLoadHooks = function(name, object) {
10717 loaded[name] = object;
10719 if (hooks = loadHooks[name]) {
10720 loadHooks[name].forEach(function(callback) {
10739 @submodule ember-runtime
10743 Ember.ControllerMixin provides a standard interface for all classes
10744 that compose Ember's controller layer: Ember.Controller, Ember.ArrayController,
10745 and Ember.ObjectController.
10747 Within an Ember.Router-managed application single shared instaces of every
10748 Controller object in your application's namespace will be added to the
10749 application's Ember.Router instance. See `Ember.Application#initialize`
10750 for additional information.
10753 By default a controller instance will be the rendering context
10754 for its associated Ember.View. This connection is made during calls to
10755 `Ember.ControllerMixin#connectOutlet`.
10757 Within the view's template, the Ember.View instance can be accessed
10758 through the controller with `{{view}}`.
10760 ## Target Forwarding
10761 By default a controller will target your application's Ember.Router instance.
10762 Calls to `{{action}}` within the template of a controller's view are forwarded
10763 to the router. See `Ember.Handlebars.helpers.action` for additional information.
10765 @class ControllerMixin
10767 @extends Ember.Mixin
10769 Ember.ControllerMixin = Ember.Mixin.create({
10771 The object to which events from the view should be sent.
10773 For example, when a Handlebars template uses the `{{action}}` helper,
10774 it will attempt to send the event to the view's controller's `target`.
10776 By default, a controller's `target` is set to the router after it is
10777 instantiated by `Ember.Application#initialize`.
10790 @extends Ember.Object
10791 @uses Ember.ControllerMixin
10793 Ember.Controller = Ember.Object.extend(Ember.ControllerMixin);
10802 @submodule ember-runtime
10805 var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
10808 Ember.SortableMixin provides a standard interface for array proxies
10809 to specify a sort order and maintain this sorting when objects are added,
10810 removed, or updated without changing the implicit order of their underlying
10814 {trackNumber: 4, title: 'Ob-La-Di, Ob-La-Da'},
10815 {trackNumber: 2, title: 'Back in the U.S.S.R.'},
10816 {trackNumber: 3, title: 'Glass Onion'},
10819 songsController = Ember.ArrayController.create({
10821 sortProperties: ['trackNumber']
10824 songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
10826 songsController.addObject({trackNumber: 1, title: 'Dear Prudence'});
10827 songsController.get('firstObject'); // {trackNumber: 1, title: 'Dear Prudence'}
10830 @class SortableMixin
10832 @extends Ember.Mixin
10833 @uses Ember.MutableEnumerable
10835 Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
10836 sortProperties: null,
10837 sortAscending: true,
10839 addObject: function(obj) {
10840 var content = get(this, 'content');
10841 content.pushObject(obj);
10844 removeObject: function(obj) {
10845 var content = get(this, 'content');
10846 content.removeObject(obj);
10849 orderBy: function(item1, item2) {
10851 sortProperties = get(this, 'sortProperties'),
10852 sortAscending = get(this, 'sortAscending');
10854 Ember.assert("you need to define `sortProperties`", !!sortProperties);
10856 forEach(sortProperties, function(propertyName) {
10857 if (result === 0) {
10858 result = Ember.compare(get(item1, propertyName), get(item2, propertyName));
10859 if ((result !== 0) && !sortAscending) {
10860 result = (-1) * result;
10868 destroy: function() {
10869 var content = get(this, 'content'),
10870 sortProperties = get(this, 'sortProperties');
10872 if (content && sortProperties) {
10873 forEach(content, function(item) {
10874 forEach(sortProperties, function(sortProperty) {
10875 Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
10880 return this._super();
10883 isSorted: Ember.computed('sortProperties', function() {
10884 return !!get(this, 'sortProperties');
10887 arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) {
10888 var content = get(this, 'content'),
10889 isSorted = get(this, 'isSorted'),
10890 sortProperties = get(this, 'sortProperties'),
10893 if (content && isSorted) {
10894 content = content.slice();
10895 content.sort(function(item1, item2) {
10896 return self.orderBy(item1, item2);
10898 forEach(content, function(item) {
10899 forEach(sortProperties, function(sortProperty) {
10900 Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
10903 return Ember.A(content);
10909 _contentWillChange: Ember.beforeObserver(function() {
10910 var content = get(this, 'content'),
10911 sortProperties = get(this, 'sortProperties');
10913 if (content && sortProperties) {
10914 forEach(content, function(item) {
10915 forEach(sortProperties, function(sortProperty) {
10916 Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
10924 sortAscendingWillChange: Ember.beforeObserver(function() {
10925 this._lastSortAscending = get(this, 'sortAscending');
10926 }, 'sortAscending'),
10928 sortAscendingDidChange: Ember.observer(function() {
10929 if (get(this, 'sortAscending') !== this._lastSortAscending) {
10930 var arrangedContent = get(this, 'arrangedContent');
10931 arrangedContent.reverseObjects();
10933 }, 'sortAscending'),
10935 contentArrayWillChange: function(array, idx, removedCount, addedCount) {
10936 var isSorted = get(this, 'isSorted');
10939 var arrangedContent = get(this, 'arrangedContent');
10940 var removedObjects = array.slice(idx, idx+removedCount);
10941 var sortProperties = get(this, 'sortProperties');
10943 forEach(removedObjects, function(item) {
10944 arrangedContent.removeObject(item);
10946 forEach(sortProperties, function(sortProperty) {
10947 Ember.removeObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
10952 return this._super(array, idx, removedCount, addedCount);
10955 contentArrayDidChange: function(array, idx, removedCount, addedCount) {
10956 var isSorted = get(this, 'isSorted'),
10957 sortProperties = get(this, 'sortProperties');
10960 var addedObjects = array.slice(idx, idx+addedCount);
10961 var arrangedContent = get(this, 'arrangedContent');
10963 forEach(addedObjects, function(item) {
10964 this.insertItemSorted(item);
10966 forEach(sortProperties, function(sortProperty) {
10967 Ember.addObserver(item, sortProperty, this, 'contentItemSortPropertyDidChange');
10972 return this._super(array, idx, removedCount, addedCount);
10975 insertItemSorted: function(item) {
10976 var arrangedContent = get(this, 'arrangedContent');
10977 var length = get(arrangedContent, 'length');
10979 var idx = this._binarySearch(item, 0, length);
10980 arrangedContent.insertAt(idx, item);
10983 contentItemSortPropertyDidChange: function(item) {
10984 var arrangedContent = get(this, 'arrangedContent'),
10985 oldIndex = arrangedContent.indexOf(item),
10986 leftItem = arrangedContent.objectAt(oldIndex - 1),
10987 rightItem = arrangedContent.objectAt(oldIndex + 1),
10988 leftResult = leftItem && this.orderBy(item, leftItem),
10989 rightResult = rightItem && this.orderBy(item, rightItem);
10991 if (leftResult < 0 || rightResult > 0) {
10992 arrangedContent.removeObject(item);
10993 this.insertItemSorted(item);
10997 _binarySearch: function(item, low, high) {
10998 var mid, midItem, res, arrangedContent;
11000 if (low === high) {
11004 arrangedContent = get(this, 'arrangedContent');
11006 mid = low + Math.floor((high - low) / 2);
11007 midItem = arrangedContent.objectAt(mid);
11009 res = this.orderBy(midItem, item);
11012 return this._binarySearch(item, mid+1, high);
11013 } else if (res > 0) {
11014 return this._binarySearch(item, low, mid);
11028 @submodule ember-runtime
11031 var get = Ember.get, set = Ember.set;
11034 Ember.ArrayController provides a way for you to publish a collection of objects
11035 so that you can easily bind to the collection from a Handlebars #each helper,
11036 an Ember.CollectionView, or other controllers.
11038 The advantage of using an ArrayController is that you only have to set up
11039 your view bindings once; to change what's displayed, simply swap out the
11040 `content` property on the controller.
11042 For example, imagine you wanted to display a list of items fetched via an XHR
11043 request. Create an Ember.ArrayController and set its `content` property:
11046 MyApp.listController = Ember.ArrayController.create();
11048 $.get('people.json', function(data) {
11049 MyApp.listController.set('content', data);
11053 Then, create a view that binds to your new controller:
11056 {{#each MyApp.listController}}
11057 {{firstName}} {{lastName}}
11061 Although you are binding to the controller, the behavior of this controller
11062 is to pass through any methods or properties to the underlying array. This
11063 capability comes from `Ember.ArrayProxy`, which this class inherits from.
11065 Note: As of this writing, `ArrayController` does not add any functionality
11066 to its superclass, `ArrayProxy`. The Ember team plans to add additional
11067 controller-specific functionality in the future, e.g. single or multiple
11068 selection support. If you are creating something that is conceptually a
11069 controller, use this class.
11071 @class ArrayController
11073 @extends Ember.ArrayProxy
11074 @uses Ember.SortableMixin
11075 @uses Ember.ControllerMixin
11078 Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
11079 Ember.SortableMixin);
11088 @submodule ember-runtime
11092 Ember.ObjectController is part of Ember's Controller layer. A single
11093 shared instance of each Ember.ObjectController subclass in your application's
11094 namespace will be created at application initialization and be stored on your
11095 application's Ember.Router instance.
11097 Ember.ObjectController derives its functionality from its superclass
11098 Ember.ObjectProxy and the Ember.ControllerMixin mixin.
11100 @class ObjectController
11102 @extends Ember.ObjectProxy
11103 @uses Ember.ControllerMixin
11105 Ember.ObjectController = Ember.ObjectProxy.extend(Ember.ControllerMixin);
11122 @submodule ember-runtime
11123 @requires ember-metal
11129 function visit(vertex, fn, visited, path) {
11130 var name = vertex.name,
11131 vertices = vertex.incoming,
11132 names = vertex.incomingNames,
11133 len = names.length,
11141 if (visited.hasOwnProperty(name)) {
11145 visited[name] = true;
11146 for (i = 0; i < len; i++) {
11147 visit(vertices[names[i]], fn, visited, path);
11155 this.vertices = {};
11158 DAG.prototype.add = function(name) {
11159 if (!name) { return; }
11160 if (this.vertices.hasOwnProperty(name)) {
11161 return this.vertices[name];
11164 name: name, incoming: {}, incomingNames: [], hasOutgoing: false, value: null
11166 this.vertices[name] = vertex;
11167 this.names.push(name);
11171 DAG.prototype.map = function(name, value) {
11172 this.add(name).value = value;
11175 DAG.prototype.addEdge = function(fromName, toName) {
11176 if (!fromName || !toName || fromName === toName) {
11179 var from = this.add(fromName), to = this.add(toName);
11180 if (to.incoming.hasOwnProperty(fromName)) {
11183 function checkCycle(vertex, path) {
11184 if (vertex.name === toName) {
11185 throw new Error("cycle detected: " + toName + " <- " + path.join(" <- "));
11188 visit(from, checkCycle);
11189 from.hasOutgoing = true;
11190 to.incoming[fromName] = from;
11191 to.incomingNames.push(fromName);
11194 DAG.prototype.topsort = function(fn) {
11196 vertices = this.vertices,
11197 names = this.names,
11198 len = names.length,
11200 for (i = 0; i < len; i++) {
11201 vertex = vertices[names[i]];
11202 if (!vertex.hasOutgoing) {
11203 visit(vertex, fn, visited);
11208 DAG.prototype.addEdges = function(name, value, before, after) {
11210 this.map(name, value);
11212 if (typeof before === 'string') {
11213 this.addEdge(name, before);
11215 for (i = 0; i < before.length; i++) {
11216 this.addEdge(name, before[i]);
11221 if (typeof after === 'string') {
11222 this.addEdge(after, name);
11224 for (i = 0; i < after.length; i++) {
11225 this.addEdge(after[i], name);
11240 @submodule ember-application
11243 var get = Ember.get, set = Ember.set;
11246 An instance of `Ember.Application` is the starting point for every Ember.js
11247 application. It helps to instantiate, initialize and coordinate the many
11248 objects that make up your app.
11250 Each Ember.js app has one and only one `Ember.Application` object. In fact, the very
11251 first thing you should do in your application is create the instance:
11254 window.App = Ember.Application.create();
11257 Typically, the application object is the only global variable. All other
11258 classes in your app should be properties on the `Ember.Application` instance,
11259 which highlights its first role: a global namespace.
11261 For example, if you define a view class, it might look like this:
11264 App.MyView = Ember.View.extend();
11267 After all of your classes are defined, call `App.initialize()` to start the
11270 Because `Ember.Application` inherits from `Ember.Namespace`, any classes
11271 you create will have useful string representations when calling `toString()`;
11272 see the `Ember.Namespace` documentation for more information.
11274 While you can think of your `Ember.Application` as a container that holds the
11275 other classes in your application, there are several other responsibilities
11276 going on under-the-hood that you may want to understand.
11278 ### Event Delegation
11280 Ember.js uses a technique called _event delegation_. This allows the framework
11281 to set up a global, shared event listener instead of requiring each view to do
11282 it manually. For example, instead of each view registering its own `mousedown`
11283 listener on its associated element, Ember.js sets up a `mousedown` listener on
11286 If a `mousedown` event occurs, Ember.js will look at the target of the event and
11287 start walking up the DOM node tree, finding corresponding views and invoking their
11288 `mouseDown` method as it goes.
11290 `Ember.Application` has a number of default events that it listens for, as well
11291 as a mapping from lowercase events to camel-cased view method names. For
11292 example, the `keypress` event causes the `keyPress` method on the view to be
11293 called, the `dblclick` event causes `doubleClick` to be called, and so on.
11295 If there is a browser event that Ember.js does not listen for by default, you
11296 can specify custom events and their corresponding view method names by setting
11297 the application's `customEvents` property:
11300 App = Ember.Application.create({
11302 // add support for the loadedmetadata media
11304 'loadedmetadata': "loadedMetadata"
11309 By default, the application sets up these event listeners on the document body.
11310 However, in cases where you are embedding an Ember.js application inside an
11311 existing page, you may want it to set up the listeners on an element inside
11314 For example, if only events inside a DOM element with the ID of `ember-app` should
11315 be delegated, set your application's `rootElement` property:
11318 window.App = Ember.Application.create({
11319 rootElement: '#ember-app'
11323 The `rootElement` can be either a DOM element or a jQuery-compatible selector
11324 string. Note that *views appended to the DOM outside the root element will not
11325 receive events.* If you specify a custom root element, make sure you only append
11328 To learn more about the advantages of event delegation and the Ember.js view layer,
11329 and a list of the event listeners that are setup by default, visit the
11330 [Ember.js View Layer guide](http://emberjs.com/guides/view_layer#toc_event-delegation).
11332 ### Dependency Injection
11334 One thing you may have noticed while using Ember.js is that you define *classes*, not
11335 *instances*. When your application loads, all of the instances are created for you.
11336 Creating these instances is the responsibility of `Ember.Application`.
11338 When the `Ember.Application` initializes, it will look for an `Ember.Router` class
11339 defined on the applications's `Router` property, like this:
11342 App.Router = Ember.Router.extend({
11347 If found, the router is instantiated and saved on the application's `router`
11348 property (note the lowercase 'r'). While you should *not* reference this router
11349 instance directly from your application code, having access to `App.router`
11350 from the console can be useful during debugging.
11352 After the router is created, the application loops through all of the
11353 registered _injections_ and invokes them once for each property on the
11354 `Ember.Application` object.
11356 An injection is a function that is responsible for instantiating objects from
11357 classes defined on the application. By default, the only injection registered
11358 instantiates controllers and makes them available on the router.
11360 For example, if you define a controller class:
11363 App.MyController = Ember.Controller.extend({
11368 Your router will receive an instance of `App.MyController` saved on its
11369 `myController` property.
11371 Libraries on top of Ember.js can register additional injections. For example,
11372 if your application is using Ember Data, it registers an injection that
11373 instantiates `DS.Store`:
11376 Ember.Application.registerInjection({
11378 before: 'controllers',
11380 injection: function(app, router, property) {
11381 if (property === 'Store') {
11382 set(router, 'store', app[property].create());
11390 In addition to creating your application's router, `Ember.Application` is also
11391 responsible for telling the router when to start routing.
11393 By default, the router will begin trying to translate the current URL into
11394 application state once the browser emits the `DOMContentReady` event. If you
11395 need to defer routing, you can call the application's `deferReadiness()` method.
11396 Once routing can begin, call the `advanceReadiness()` method.
11398 If there is any setup required before routing begins, you can implement a `ready()`
11399 method on your app that will be invoked immediately before routing begins:
11402 window.App = Ember.Application.create({
11403 ready: function() {
11404 this.set('router.enableLogging', true);
11408 To begin routing, you must have at a minimum a top-level controller and view.
11409 You define these as `App.ApplicationController` and `App.ApplicationView`,
11410 respectively. Your application will not work if you do not define these two
11411 mandatory classes. For example:
11414 App.ApplicationView = Ember.View.extend({
11415 templateName: 'application'
11417 App.ApplicationController = Ember.Controller.extend();
11422 @extends Ember.Namespace
11424 Ember.Application = Ember.Namespace.extend(
11425 /** @scope Ember.Application.prototype */{
11428 The root DOM element of the Application. This can be specified as an
11430 [jQuery-compatible selector string](http://api.jquery.com/category/selectors/).
11432 This is the element that will be passed to the Application's,
11433 `eventDispatcher`, which sets up the listeners for event delegation. Every
11434 view in your application should be a child of the element you specify here.
11436 @property rootElement
11440 rootElement: 'body',
11443 The `Ember.EventDispatcher` responsible for delegating events to this
11444 application's views.
11446 The event dispatcher is created by the application at initialization time
11447 and sets up event listeners on the DOM element described by the
11448 application's `rootElement` property.
11450 See the documentation for `Ember.EventDispatcher` for more information.
11452 @property eventDispatcher
11453 @type Ember.EventDispatcher
11456 eventDispatcher: null,
11459 The DOM events for which the event dispatcher should listen.
11461 By default, the application's `Ember.EventDispatcher` listens
11462 for a set of standard DOM events, such as `mousedown` and
11463 `keyup`, and delegates them to your application's `Ember.View`
11466 If you would like additional events to be delegated to your
11467 views, set your `Ember.Application`'s `customEvents` property
11468 to a hash containing the DOM event name as the key and the
11469 corresponding view method name as the value. For example:
11471 App = Ember.Application.create({
11473 // add support for the loadedmetadata media
11475 'loadedmetadata': "loadedMetadata"
11479 @property customEvents
11483 customEvents: null,
11485 autoinit: !Ember.testing,
11487 isInitialized: false,
11490 if (!this.$) { this.$ = Ember.$; }
11494 this.createEventDispatcher();
11496 // Start off the number of deferrals at 1. This will be
11497 // decremented by the Application's own `initialize` method.
11498 this._readinessDeferrals = 1;
11500 this.waitForDOMContentLoaded();
11502 if (this.autoinit) {
11504 this.$().ready(function() {
11505 if (self.isDestroyed || self.isInitialized) return;
11512 createEventDispatcher: function() {
11513 var rootElement = get(this, 'rootElement'),
11514 eventDispatcher = Ember.EventDispatcher.create({
11515 rootElement: rootElement
11518 set(this, 'eventDispatcher', eventDispatcher);
11521 waitForDOMContentLoaded: function() {
11522 this.deferReadiness();
11525 this.$().ready(function() {
11526 self.advanceReadiness();
11530 deferReadiness: function() {
11531 Ember.assert("You cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0);
11532 this._readinessDeferrals++;
11535 advanceReadiness: function() {
11536 this._readinessDeferrals--;
11538 if (this._readinessDeferrals === 0) {
11539 Ember.run.once(this, this.didBecomeReady);
11544 Instantiate all controllers currently available on the namespace
11545 and inject them onto a router.
11549 App.PostsController = Ember.ArrayController.extend();
11550 App.CommentsController = Ember.ArrayController.extend();
11552 var router = Ember.Router.create({
11556 App.initialize(router);
11558 router.get('postsController') // <App.PostsController:ember1234>
11559 router.get('commentsController') // <App.CommentsController:ember1235>
11562 @param router {Ember.Router}
11564 initialize: function(router) {
11565 Ember.assert("Application initialize may only be call once", !this.isInitialized);
11566 Ember.assert("Application not destroyed", !this.isDestroyed);
11568 router = this.setupRouter(router);
11570 this.runInjections(router);
11572 Ember.runLoadHooks('application', this);
11574 this.isInitialized = true;
11576 // At this point, any injections or load hooks that would have wanted
11577 // to defer readiness have fired.
11578 this.advanceReadiness();
11584 runInjections: function(router) {
11585 var injections = get(this.constructor, 'injections'),
11586 graph = new Ember.DAG(),
11588 properties, i, injection;
11590 for (i=0; i<injections.length; i++) {
11591 injection = injections[i];
11592 graph.addEdges(injection.name, injection.injection, injection.before, injection.after);
11595 graph.topsort(function (vertex) {
11596 var injection = vertex.value,
11597 properties = Ember.A(Ember.keys(namespace));
11598 properties.forEach(function(property) {
11599 injection(namespace, router, property);
11605 setupRouter: function(router) {
11606 if (!router && Ember.Router.detect(this.Router)) {
11607 router = this.Router.create();
11608 this._createdRouter = router;
11612 set(this, 'router', router);
11614 // By default, the router's namespace is the current application.
11616 // This allows it to find model classes when a state has a
11617 // route like `/posts/:post_id`. In that case, it would first
11618 // convert `post_id` into `Post`, and then look it up on its
11620 set(router, 'namespace', this);
11627 didBecomeReady: function() {
11628 var eventDispatcher = get(this, 'eventDispatcher'),
11629 customEvents = get(this, 'customEvents'),
11632 eventDispatcher.setup(customEvents);
11637 router = get(this, 'router');
11639 this.createApplicationView(router);
11641 if (router && router instanceof Ember.Router) {
11642 this.startRouting(router);
11646 createApplicationView: function (router) {
11647 var rootElement = get(this, 'rootElement'),
11648 applicationViewOptions = {},
11649 applicationViewClass = this.ApplicationView,
11650 applicationTemplate = Ember.TEMPLATES.application,
11651 applicationController, applicationView;
11653 // don't do anything unless there is an ApplicationView or application template
11654 if (!applicationViewClass && !applicationTemplate) return;
11657 applicationController = get(router, 'applicationController');
11658 if (applicationController) {
11659 applicationViewOptions.controller = applicationController;
11663 if (applicationTemplate) {
11664 applicationViewOptions.template = applicationTemplate;
11667 if (!applicationViewClass) {
11668 applicationViewClass = Ember.View;
11671 applicationView = applicationViewClass.create(applicationViewOptions);
11673 this._createdApplicationView = applicationView;
11676 set(router, 'applicationView', applicationView);
11679 applicationView.appendTo(rootElement);
11685 If the application has a router, use it to route to the current URL, and
11686 trigger a new call to `route` whenever the URL changes.
11688 @method startRouting
11689 @property router {Ember.Router}
11691 startRouting: function(router) {
11692 var location = get(router, 'location');
11694 Ember.assert("You must have an application template or ApplicationView defined on your application", get(router, 'applicationView') );
11695 Ember.assert("You must have an ApplicationController defined on your application", get(router, 'applicationController') );
11697 router.route(location.getURL());
11698 location.onUpdateURL(function(url) {
11704 Called when the Application has become ready.
11705 The call will be delayed until the DOM has become ready.
11711 willDestroy: function() {
11712 get(this, 'eventDispatcher').destroy();
11713 if (this._createdRouter) { this._createdRouter.destroy(); }
11714 if (this._createdApplicationView) { this._createdApplicationView.destroy(); }
11717 registerInjection: function(options) {
11718 this.constructor.registerInjection(options);
11722 Ember.Application.reopenClass({
11723 concatenatedProperties: ['injections'],
11724 injections: Ember.A(),
11725 registerInjection: function(injection) {
11726 var injections = get(this, 'injections');
11728 Ember.assert("The injection '" + injection.name + "' has already been registered", !injections.findProperty('name', injection.name));
11729 Ember.assert("An injection cannot be registered with both a before and an after", !(injection.before && injection.after));
11730 Ember.assert("An injection cannot be registered without an injection function", Ember.canInvoke(injection, 'injection'));
11732 injections.push(injection);
11736 Ember.Application.registerInjection({
11737 name: 'controllers',
11738 injection: function(app, router, property) {
11739 if (!router) { return; }
11740 if (!/^[A-Z].*Controller$/.test(property)) { return; }
11742 var name = property.charAt(0).toLowerCase() + property.substr(1),
11743 controllerClass = app[property], controller;
11745 if(!Ember.Object.detect(controllerClass)){ return; }
11746 controller = app[property].create();
11748 router.set(name, controller);
11750 controller.setProperties({
11752 controllers: router,
11758 Ember.runLoadHooks('Ember.Application', Ember.Application);
11776 @submodule ember-application
11777 @requires ember-views, ember-states, ember-routing
11785 @submodule ember-views
11788 var jQuery = Ember.imports.jQuery;
11789 Ember.assert("Ember Views require jQuery 1.7 or 1.8", jQuery && (jQuery().jquery.match(/^1\.(7(?!$)(?!\.[01])|8)(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
11806 @submodule ember-views
11809 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dndevents
11810 var dragEvents = Ember.String.w('dragstart drag dragenter dragleave dragover drop dragend');
11812 // Copies the `dataTransfer` property from a browser event object onto the
11813 // jQuery event object for the specified events
11814 Ember.EnumerableUtils.forEach(dragEvents, function(eventName) {
11815 Ember.$.event.fixHooks[eventName] = { props: ['dataTransfer'] };
11825 @submodule ember-views
11828 var get = Ember.get, set = Ember.set;
11829 var indexOf = Ember.ArrayPolyfills.indexOf;
11831 var ClassSet = function() {
11836 ClassSet.prototype = {
11837 add: function(string) {
11838 if (string in this.seen) { return; }
11839 this.seen[string] = true;
11841 this.list.push(string);
11844 toDOM: function() {
11845 return this.list.join(" ");
11850 Ember.RenderBuffer gathers information regarding the a view and generates the
11851 final representation. Ember.RenderBuffer will generate HTML which can be pushed
11854 @class RenderBuffer
11858 Ember.RenderBuffer = function(tagName) {
11859 return new Ember._RenderBuffer(tagName);
11862 Ember._RenderBuffer = function(tagName) {
11863 this.elementTag = tagName;
11864 this.childBuffers = [];
11867 Ember._RenderBuffer.prototype =
11868 /** @scope Ember.RenderBuffer.prototype */ {
11871 Array of class-names which will be applied in the class="" attribute
11873 You should not maintain this array yourself, rather, you should use
11874 the addClass() method of Ember.RenderBuffer.
11876 @property elementClasses
11880 elementClasses: null,
11883 The id in of the element, to be applied in the id="" attribute
11885 You should not set this property yourself, rather, you should use
11886 the id() method of Ember.RenderBuffer.
11888 @property elementId
11895 A hash keyed on the name of the attribute and whose value will be
11896 applied to that attribute. For example, if you wanted to apply a
11897 data-view="Foo.bar" property to an element, you would set the
11898 elementAttributes hash to {'data-view':'Foo.bar'}
11900 You should not maintain this hash yourself, rather, you should use
11901 the attr() method of Ember.RenderBuffer.
11903 @property elementAttributes
11907 elementAttributes: null,
11910 The tagname of the element an instance of Ember.RenderBuffer represents.
11912 Usually, this gets set as the first parameter to Ember.RenderBuffer. For
11913 example, if you wanted to create a `p` tag, then you would call
11915 Ember.RenderBuffer('p')
11917 @property elementTag
11924 A hash keyed on the name of the style attribute and whose value will
11925 be applied to that attribute. For example, if you wanted to apply a
11926 background-color:black;" style to an element, you would set the
11927 elementStyle hash to {'background-color':'black'}
11929 You should not maintain this hash yourself, rather, you should use
11930 the style() method of Ember.RenderBuffer.
11932 @property elementStyle
11936 elementStyle: null,
11939 Nested RenderBuffers will set this to their parent RenderBuffer
11942 @property parentBuffer
11943 @type Ember._RenderBuffer
11945 parentBuffer: null,
11948 Adds a string of HTML to the RenderBuffer.
11951 @param {String} string HTML to push into the buffer
11954 push: function(string) {
11955 this.childBuffers.push(String(string));
11960 Adds a class to the buffer, which will be rendered to the class attribute.
11963 @param {String} className Class name to add to the buffer
11966 addClass: function(className) {
11967 // lazily create elementClasses
11968 var elementClasses = this.elementClasses = (this.elementClasses || new ClassSet());
11969 this.elementClasses.add(className);
11975 Sets the elementID to be used for the element.
11982 this.elementId = id;
11986 // duck type attribute functionality like jQuery so a render buffer
11987 // can be used like a jQuery object in attribute binding scenarios.
11990 Adds an attribute which will be rendered to the element.
11993 @param {String} name The name of the attribute
11994 @param {String} value The value to add to the attribute
11996 @return {Ember.RenderBuffer|String} this or the current attribute value
11998 attr: function(name, value) {
11999 var attributes = this.elementAttributes = (this.elementAttributes || {});
12001 if (arguments.length === 1) {
12002 return attributes[name];
12004 attributes[name] = value;
12011 Remove an attribute from the list of attributes to render.
12014 @param {String} name The name of the attribute
12017 removeAttr: function(name) {
12018 var attributes = this.elementAttributes;
12019 if (attributes) { delete attributes[name]; }
12025 Adds a style to the style attribute which will be rendered to the element.
12028 @param {String} name Name of the style
12029 @param {String} value
12032 style: function(name, value) {
12033 var style = this.elementStyle = (this.elementStyle || {});
12035 this.elementStyle[name] = value;
12042 Create a new child render buffer from a parent buffer. Optionally set
12043 additional properties on the buffer. Optionally invoke a callback
12044 with the newly created buffer.
12046 This is a primitive method used by other public methods: `begin`,
12047 `prepend`, `replaceWith`, `insertAfter`.
12050 @param {String} tagName Tag name to use for the child buffer's element
12051 @param {Ember._RenderBuffer} parent The parent render buffer that this
12052 buffer should be appended to.
12053 @param {Function} fn A callback to invoke with the newly created buffer.
12054 @param {Object} other Additional properties to add to the newly created
12057 newBuffer: function(tagName, parent, fn, other) {
12058 var buffer = new Ember._RenderBuffer(tagName);
12059 buffer.parentBuffer = parent;
12061 if (other) { Ember.$.extend(buffer, other); }
12062 if (fn) { fn.call(this, buffer); }
12070 Replace the current buffer with a new buffer. This is a primitive
12071 used by `remove`, which passes `null` for `newBuffer`, and `replaceWith`,
12072 which passes the new buffer it created.
12074 @method replaceWithBuffer
12075 @param {Ember._RenderBuffer} buffer The buffer to insert in place of
12076 the existing buffer.
12078 replaceWithBuffer: function(newBuffer) {
12079 var parent = this.parentBuffer;
12080 if (!parent) { return; }
12082 var childBuffers = parent.childBuffers;
12084 var index = indexOf.call(childBuffers, this);
12087 childBuffers.splice(index, 1, newBuffer);
12089 childBuffers.splice(index, 1);
12094 Creates a new Ember.RenderBuffer object with the provided tagName as
12095 the element tag and with its parentBuffer property set to the current
12096 Ember.RenderBuffer.
12099 @param {String} tagName Tag name to use for the child buffer's element
12100 @return {Ember.RenderBuffer} A new RenderBuffer object
12102 begin: function(tagName) {
12103 return this.newBuffer(tagName, this, function(buffer) {
12104 this.childBuffers.push(buffer);
12109 Prepend a new child buffer to the current render buffer.
12112 @param {String} tagName Tag name to use for the child buffer's element
12114 prepend: function(tagName) {
12115 return this.newBuffer(tagName, this, function(buffer) {
12116 this.childBuffers.splice(0, 0, buffer);
12121 Replace the current buffer with a new render buffer.
12123 @method replaceWith
12124 @param {String} tagName Tag name to use for the new buffer's element
12126 replaceWith: function(tagName) {
12127 var parentBuffer = this.parentBuffer;
12129 return this.newBuffer(tagName, parentBuffer, function(buffer) {
12130 this.replaceWithBuffer(buffer);
12135 Insert a new render buffer after the current render buffer.
12137 @method insertAfter
12138 @param {String} tagName Tag name to use for the new buffer's element
12140 insertAfter: function(tagName) {
12141 var parentBuffer = get(this, 'parentBuffer');
12143 return this.newBuffer(tagName, parentBuffer, function(buffer) {
12144 var siblings = parentBuffer.childBuffers;
12145 var index = indexOf.call(siblings, this);
12146 siblings.splice(index + 1, 0, buffer);
12151 Closes the current buffer and adds its content to the parentBuffer.
12154 @return {Ember.RenderBuffer} The parentBuffer, if one exists. Otherwise, this
12157 var parent = this.parentBuffer;
12158 return parent || this;
12161 remove: function() {
12162 this.replaceWithBuffer(null);
12167 @return {DOMElement} The element corresponding to the generated HTML
12170 element: function() {
12171 return Ember.$(this.string())[0];
12175 Generates the HTML content for this buffer.
12178 @return {String} The generated HTMl
12180 string: function() {
12181 var content = '', tag = this.elementTag, openTag;
12184 var id = this.elementId,
12185 classes = this.elementClasses,
12186 attrs = this.elementAttributes,
12187 style = this.elementStyle,
12188 styleBuffer = '', prop;
12190 openTag = ["<" + tag];
12192 if (id) { openTag.push('id="' + this._escapeAttribute(id) + '"'); }
12193 if (classes) { openTag.push('class="' + this._escapeAttribute(classes.toDOM()) + '"'); }
12196 for (prop in style) {
12197 if (style.hasOwnProperty(prop)) {
12198 styleBuffer += (prop + ':' + this._escapeAttribute(style[prop]) + ';');
12202 openTag.push('style="' + styleBuffer + '"');
12206 for (prop in attrs) {
12207 if (attrs.hasOwnProperty(prop)) {
12208 openTag.push(prop + '="' + this._escapeAttribute(attrs[prop]) + '"');
12213 openTag = openTag.join(" ") + '>';
12216 var childBuffers = this.childBuffers;
12218 Ember.ArrayPolyfills.forEach.call(childBuffers, function(buffer) {
12219 var stringy = typeof buffer === 'string';
12220 content += (stringy ? buffer : buffer.string());
12224 return openTag + content + "</" + tag + ">";
12230 _escapeAttribute: function(value) {
12231 // Stolen shamelessly from Handlebars
12241 var badChars = /&(?!\w+;)|[<>"'`]/g;
12242 var possible = /[&<>"'`]/;
12244 var escapeChar = function(chr) {
12245 return escape[chr] || "&";
12248 var string = value.toString();
12250 if(!possible.test(string)) { return string; }
12251 return string.replace(badChars, escapeChar);
12263 @submodule ember-views
12266 var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
12269 Ember.EventDispatcher handles delegating browser events to their corresponding
12270 Ember.Views. For example, when you click on a view, Ember.EventDispatcher ensures
12271 that that view's `mouseDown` method gets called.
12273 @class EventDispatcher
12276 @extends Ember.Object
12278 Ember.EventDispatcher = Ember.Object.extend(
12279 /** @scope Ember.EventDispatcher.prototype */{
12284 The root DOM element to which event listeners should be attached. Event
12285 listeners will be attached to the document unless this is overridden.
12287 Can be specified as a DOMElement or a selector string.
12289 The default body is a string since this may be evaluated before document.body
12292 @property rootElement
12296 rootElement: 'body',
12301 Sets up event listeners for standard browser events.
12303 This will be called after the browser sends a DOMContentReady event. By
12304 default, it will set up all of the listeners on the document body. If you
12305 would like to register the listeners on a different element, set the event
12306 dispatcher's `root` property.
12309 @param addedEvents {Hash}
12311 setup: function(addedEvents) {
12312 var event, events = {
12313 touchstart : 'touchStart',
12314 touchmove : 'touchMove',
12315 touchend : 'touchEnd',
12316 touchcancel : 'touchCancel',
12317 keydown : 'keyDown',
12319 keypress : 'keyPress',
12320 mousedown : 'mouseDown',
12321 mouseup : 'mouseUp',
12322 contextmenu : 'contextMenu',
12324 dblclick : 'doubleClick',
12325 mousemove : 'mouseMove',
12326 focusin : 'focusIn',
12327 focusout : 'focusOut',
12328 mouseenter : 'mouseEnter',
12329 mouseleave : 'mouseLeave',
12333 dragstart : 'dragStart',
12335 dragenter : 'dragEnter',
12336 dragleave : 'dragLeave',
12337 dragover : 'dragOver',
12339 dragend : 'dragEnd'
12342 Ember.$.extend(events, addedEvents || {});
12344 var rootElement = Ember.$(get(this, 'rootElement'));
12346 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'));
12347 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);
12348 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);
12350 rootElement.addClass('ember-application');
12352 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'));
12354 for (event in events) {
12355 if (events.hasOwnProperty(event)) {
12356 this.setupHandler(rootElement, event, events[event]);
12364 Registers an event listener on the document. If the given event is
12365 triggered, the provided event handler will be triggered on the target
12368 If the target view does not implement the event handler, or if the handler
12369 returns false, the parent view will be called. The event will continue to
12370 bubble to each successive parent view until it reaches the top.
12372 For example, to have the `mouseDown` method called on the target view when
12373 a `mousedown` event is received from the browser, do the following:
12375 setupHandler('mousedown', 'mouseDown');
12377 @method setupHandler
12378 @param {Element} rootElement
12379 @param {String} event the browser-originated event to listen to
12380 @param {String} eventName the name of the method to call on the view
12382 setupHandler: function(rootElement, event, eventName) {
12385 rootElement.delegate('.ember-view', event + '.ember', function(evt, triggeringManager) {
12386 return Ember.handleErrors(function() {
12387 var view = Ember.View.views[this.id],
12388 result = true, manager = null;
12390 manager = self._findNearestEventManager(view,eventName);
12392 if (manager && manager !== triggeringManager) {
12393 result = self._dispatchEvent(manager, evt, eventName, view);
12395 result = self._bubbleEvent(view,evt,eventName);
12397 evt.stopPropagation();
12404 rootElement.delegate('[data-ember-action]', event + '.ember', function(evt) {
12405 return Ember.handleErrors(function() {
12406 var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'),
12407 action = Ember.Handlebars.ActionHelper.registeredActions[actionId],
12408 handler = action.handler;
12410 if (action.eventName === eventName) {
12411 return handler(evt);
12417 _findNearestEventManager: function(view, eventName) {
12418 var manager = null;
12421 manager = get(view, 'eventManager');
12422 if (manager && manager[eventName]) { break; }
12424 view = get(view, 'parentView');
12430 _dispatchEvent: function(object, evt, eventName, view) {
12433 var handler = object[eventName];
12434 if (Ember.typeOf(handler) === 'function') {
12435 result = handler.call(object, evt, view);
12436 // Do not preventDefault in eventManagers.
12437 evt.stopPropagation();
12440 result = this._bubbleEvent(view, evt, eventName);
12446 _bubbleEvent: function(view, evt, eventName) {
12447 return Ember.run(function() {
12448 return view.handleEvent(eventName, evt);
12452 destroy: function() {
12453 var rootElement = get(this, 'rootElement');
12454 Ember.$(rootElement).undelegate('.ember').removeClass('ember-application');
12455 return this._super();
12466 @submodule ember-views
12469 // Add a new named queue for rendering views that happens
12470 // after bindings have synced.
12471 var queues = Ember.run.queues;
12472 queues.splice(Ember.$.inArray('actions', queues)+1, 0, 'render');
12481 @submodule ember-views
12484 var get = Ember.get, set = Ember.set;
12486 // Original class declaration and documentation in runtime/lib/controllers/controller.js
12487 // NOTE: It may be possible with YUIDoc to combine docs in two locations
12490 Additional methods for the ControllerMixin
12492 @class ControllerMixin
12495 Ember.ControllerMixin.reopen({
12503 `connectOutlet` creates a new instance of a provided view
12504 class, wires it up to its associated controller, and
12505 assigns the new view to a property on the current controller.
12507 The purpose of this method is to enable views that use
12508 outlets to quickly assign new views for a given outlet.
12510 For example, an application view's template may look like
12518 The view for this outlet is specified by assigning a
12519 `view` property to the application's controller. The
12520 following code will assign a new `App.PostsView` to
12524 applicationController.connectOutlet('posts');
12527 In general, you will also want to assign a controller
12528 to the newly created view. By convention, a controller
12529 named `postsController` will be assigned as the view's
12532 In an application initialized using `app.initialize(router)`,
12533 `connectOutlet` will look for `postsController` on the
12534 router. The initialization process will automatically
12535 create an instance of `App.PostsController` called
12536 `postsController`, so you don't need to do anything
12537 beyond `connectOutlet` to assign your view and wire it
12538 up to its associated controller.
12540 You can supply a `content` for the controller by supplying
12541 a final argument after the view class:
12544 applicationController.connectOutlet('posts', App.Post.find());
12547 You can specify a particular outlet to use. For example, if your main
12548 template looks like:
12552 {{outlet masterView}}
12553 {{outlet detailView}}
12556 You can assign an `App.PostsView` to the masterView outlet:
12559 applicationController.connectOutlet({
12560 outletName: 'masterView',
12562 context: App.Post.find()
12566 You can write this as:
12569 applicationController.connectOutlet('masterView', 'posts', App.Post.find());
12573 @method connectOutlet
12574 @param {String} outletName a name for the outlet to set
12575 @param {String} name a view/controller pair name
12576 @param {Object} context a context object to assign to the
12577 controller's `content` property, if a controller can be
12580 connectOutlet: function(name, context) {
12581 // Normalize arguments. Supported arguments:
12585 // outletName, name
12586 // outletName, name, context
12589 // The options hash has the following keys:
12591 // name: the name of the controller and view
12592 // to use. If this is passed, the name
12593 // determines the view and controller.
12594 // outletName: the name of the outlet to
12595 // fill in. default: 'view'
12596 // viewClass: the class of the view to instantiate
12597 // controller: the controller instance to pass
12599 // context: an object that should become the
12600 // controller's `content` and thus the
12601 // template's context.
12603 var outletName, viewClass, view, controller, options;
12605 if (Ember.typeOf(context) === 'string') {
12608 context = arguments[2];
12611 if (arguments.length === 1) {
12612 if (Ember.typeOf(name) === 'object') {
12614 outletName = options.outletName;
12615 name = options.name;
12616 viewClass = options.viewClass;
12617 controller = options.controller;
12618 context = options.context;
12624 outletName = outletName || 'view';
12626 Ember.assert("The viewClass is either missing or the one provided did not resolve to a view", !!name || (!name && !!viewClass));
12628 Ember.assert("You must supply a name or a viewClass to connectOutlet, but not both", (!!name && !viewClass && !controller) || (!name && !!viewClass));
12631 var namespace = get(this, 'namespace'),
12632 controllers = get(this, 'controllers');
12634 var viewClassName = name.charAt(0).toUpperCase() + name.substr(1) + "View";
12635 viewClass = get(namespace, viewClassName);
12636 controller = get(controllers, name + 'Controller');
12638 Ember.assert("The name you supplied " + name + " did not resolve to a view " + viewClassName, !!viewClass);
12639 Ember.assert("The name you supplied " + name + " did not resolve to a controller " + name + 'Controller', (!!controller && !!context) || !context);
12642 if (controller && context) { set(controller, 'content', context); }
12644 view = this.createOutletView(outletName, viewClass);
12646 if (controller) { set(view, 'controller', controller); }
12647 set(this, outletName, view);
12653 Convenience method to connect controllers. This method makes other controllers
12654 available on the controller the method was invoked on.
12656 For example, to make the `personController` and the `postController` available
12657 on the `overviewController`, you would call:
12659 overviewController.connectControllers('person', 'post');
12661 @method connectControllers
12662 @param {String...} controllerNames the controllers to make available
12664 connectControllers: function() {
12665 var controllers = get(this, 'controllers'),
12666 controllerNames = Array.prototype.slice.apply(arguments),
12669 for (var i=0, l=controllerNames.length; i<l; i++) {
12670 controllerName = controllerNames[i] + 'Controller';
12671 set(this, controllerName, get(controllers, controllerName));
12676 `disconnectOutlet` removes previously attached view from given outlet.
12678 @method disconnectOutlet
12679 @param {String} outletName the outlet name. (optional)
12681 disconnectOutlet: function(outletName) {
12682 outletName = outletName || 'view';
12684 set(this, outletName, null);
12688 `createOutletView` is a hook you may want to override if you need to do
12689 something special with the view created for the outlet. For example
12690 you may want to implement views sharing across outlets.
12692 @method createOutletView
12693 @param outletName {String}
12694 @param viewClass {Ember.View}
12696 createOutletView: function(outletName, viewClass) {
12697 return viewClass.create();
12714 @submodule ember-views
12717 var get = Ember.get, set = Ember.set, addObserver = Ember.addObserver, removeObserver = Ember.removeObserver;
12718 var meta = Ember.meta, fmt = Ember.String.fmt;
12719 var a_slice = [].slice;
12720 var a_forEach = Ember.EnumerableUtils.forEach;
12722 var childViewsProperty = Ember.computed(function() {
12723 var childViews = this._childViews;
12725 var ret = Ember.A();
12727 a_forEach(childViews, function(view) {
12728 if (view.isVirtual) {
12729 ret.pushObjects(get(view, 'childViews'));
12738 Ember.warn("The VIEW_PRESERVES_CONTEXT flag has been removed and the functionality can no longer be disabled.", Ember.ENV.VIEW_PRESERVES_CONTEXT !== false);
12741 Global hash of shared templates. This will automatically be populated
12742 by the build tools so that you can store your Handlebars templates in
12743 separate files that get loaded into JavaScript at buildtime.
12745 @property TEMPLATES
12749 Ember.TEMPLATES = {};
12751 var invokeForState = {
12759 Ember.CoreView = Ember.Object.extend(Ember.Evented, {
12763 // Register the view for event handling. This hash is used by
12764 // Ember.EventDispatcher to dispatch incoming events.
12765 if (!this.isVirtual) Ember.View.views[get(this, 'elementId')] = this;
12769 If the view is currently inserted into the DOM of a parent view, this
12770 property will point to the parent of the view.
12772 @property parentView
12776 parentView: Ember.computed(function() {
12777 var parent = get(this, '_parentView');
12779 if (parent && parent.isVirtual) {
12780 return get(parent, 'parentView');
12784 }).property('_parentView').volatile(),
12786 state: 'preRender',
12790 // return the current view, not including virtual views
12791 concreteView: Ember.computed(function() {
12792 if (!this.isVirtual) { return this; }
12793 else { return get(this, 'parentView'); }
12794 }).property('_parentView').volatile(),
12797 Creates a new renderBuffer with the passed tagName. You can override this
12798 method to provide further customization to the buffer if needed. Normally
12799 you will not need to call or override this method.
12801 @method renderBuffer
12802 @param [tagName] {String}
12803 @return {Ember.RenderBuffer}
12805 renderBuffer: function(tagName) {
12806 tagName = tagName || get(this, 'tagName');
12808 // Explicitly check for null or undefined, as tagName
12809 // may be an empty string, which would evaluate to false.
12810 if (tagName === null || tagName === undefined) {
12814 return Ember.RenderBuffer(tagName);
12817 instrumentName: 'render.core_view',
12819 instrumentDetails: function(hash) {
12820 hash.type = this.constructor.toString();
12826 Invoked by the view system when this view needs to produce an HTML
12827 representation. This method will create a new render buffer, if needed,
12828 then apply any default attributes, such as class names and visibility.
12829 Finally, the `render()` method is invoked, which is responsible for
12830 doing the bulk of the rendering.
12832 You should not need to override this method; instead, implement the
12833 `template` property, or if you need more control, override the `render`
12836 @method renderToBuffer
12837 @param {Ember.RenderBuffer} buffer the render buffer. If no buffer is
12838 passed, a default buffer, using the current view's `tagName`, will
12841 renderToBuffer: function(parentBuffer, bufferOperation) {
12842 var name = get(this, 'instrumentName'),
12845 this.instrumentDetails(details);
12847 return Ember.instrument(name, details, function() {
12848 return this._renderToBuffer(parentBuffer, bufferOperation);
12852 _renderToBuffer: function(parentBuffer, bufferOperation) {
12857 // Determine where in the parent buffer to start the new buffer.
12858 // By default, a new buffer will be appended to the parent buffer.
12859 // The buffer operation may be changed if the child views array is
12860 // mutated by Ember.ContainerView.
12861 bufferOperation = bufferOperation || 'begin';
12863 // If this is the top-most view, start a new buffer. Otherwise,
12864 // create a new buffer relative to the original using the
12865 // provided buffer operation (for example, `insertAfter` will
12866 // insert a new buffer after the "parent buffer").
12867 if (parentBuffer) {
12868 var tagName = get(this, 'tagName');
12869 if (tagName === null || tagName === undefined) {
12873 buffer = parentBuffer[bufferOperation](tagName);
12875 buffer = this.renderBuffer();
12878 this.buffer = buffer;
12879 this.transitionTo('inBuffer', false);
12881 this.beforeRender(buffer);
12882 this.render(buffer);
12883 this.afterRender(buffer);
12891 Override the default event firing from Ember.Evented to
12892 also call methods with the given name.
12895 @param name {String}
12897 trigger: function(name) {
12898 this._super.apply(this, arguments);
12899 var method = this[name];
12901 var args = [], i, l;
12902 for (i = 1, l = arguments.length; i < l; i++) {
12903 args.push(arguments[i]);
12905 return method.apply(this, args);
12909 has: function(name) {
12910 return Ember.typeOf(this[name]) === 'function' || this._super(name);
12913 willDestroy: function() {
12914 var parent = get(this, '_parentView');
12916 // destroy the element -- this will avoid each child view destroying
12917 // the element over and over again...
12918 if (!this.removedFromDOM) { this.destroyElement(); }
12920 // remove from parent if found. Don't call removeFromParent,
12921 // as removeFromParent will try to remove the element from
12923 if (parent) { parent.removeChild(this); }
12925 this.state = 'destroyed';
12927 // next remove view from global hash
12928 if (!this.isVirtual) delete Ember.View.views[get(this, 'elementId')];
12931 clearRenderedChildren: Ember.K,
12932 invokeRecursively: Ember.K,
12933 invalidateRecursively: Ember.K,
12934 transitionTo: Ember.K,
12935 destroyElement: Ember.K,
12936 _notifyWillInsertElement: Ember.K,
12937 _notifyDidInsertElement: Ember.K
12941 `Ember.View` is the class in Ember responsible for encapsulating templates of HTML
12942 content, combining templates with data to render as sections of a page's DOM, and
12943 registering and responding to user-initiated events.
12946 The default HTML tag name used for a view's DOM representation is `div`. This can be
12947 customized by setting the `tagName` property. The following view class:
12950 ParagraphView = Ember.View.extend({
12955 Would result in instances with the following HTML:
12958 <em id="ember1" class="ember-view"></em>
12961 ## HTML `class` Attribute
12962 The HTML `class` attribute of a view's tag can be set by providing a `classNames` property
12963 that is set to an array of strings:
12966 MyView = Ember.View.extend({
12967 classNames: ['my-class', 'my-other-class']
12971 Will result in view instances with an HTML representation of:
12974 <div id="ember1" class="ember-view my-class my-other-class"></div>
12977 `class` attribute values can also be set by providing a `classNameBindings` property
12978 set to an array of properties names for the view. The return value of these properties
12979 will be added as part of the value for the view's `class` attribute. These properties
12980 can be computed properties:
12983 MyView = Ember.View.extend({
12984 classNameBindings: ['propertyA', 'propertyB'],
12985 propertyA: 'from-a',
12986 propertyB: function(){
12987 if(someLogic){ return 'from-b'; }
12992 Will result in view instances with an HTML representation of:
12995 <div id="ember1" class="ember-view from-a from-b"></div>
12998 If the value of a class name binding returns a boolean the property name itself
12999 will be used as the class name if the property is true. The class name will
13000 not be added if the value is `false` or `undefined`.
13003 MyView = Ember.View.extend({
13004 classNameBindings: ['hovered'],
13009 Will result in view instances with an HTML representation of:
13012 <div id="ember1" class="ember-view hovered"></div>
13015 When using boolean class name bindings you can supply a string value other than the
13016 property name for use as the `class` HTML attribute by appending the preferred value after
13017 a ":" character when defining the binding:
13020 MyView = Ember.View.extend({
13021 classNameBindings: ['awesome:so-very-cool'],
13026 Will result in view instances with an HTML representation of:
13029 <div id="ember1" class="ember-view so-very-cool"></div>
13033 Boolean value class name bindings whose property names are in a camelCase-style
13034 format will be converted to a dasherized format:
13037 MyView = Ember.View.extend({
13038 classNameBindings: ['isUrgent'],
13043 Will result in view instances with an HTML representation of:
13046 <div id="ember1" class="ember-view is-urgent"></div>
13050 Class name bindings can also refer to object values that are found by
13051 traversing a path relative to the view itself:
13054 MyView = Ember.View.extend({
13055 classNameBindings: ['messages.empty']
13056 messages: Ember.Object.create({
13062 Will result in view instances with an HTML representation of:
13065 <div id="ember1" class="ember-view empty"></div>
13069 If you want to add a class name for a property which evaluates to true and
13070 and a different class name if it evaluates to false, you can pass a binding
13074 // Applies 'enabled' class when isEnabled is true and 'disabled' when isEnabled is false
13075 Ember.View.create({
13076 classNameBindings: ['isEnabled:enabled:disabled']
13081 Will result in view instances with an HTML representation of:
13084 <div id="ember1" class="ember-view enabled"></div>
13087 When isEnabled is `false`, the resulting HTML reprensentation looks like this:
13090 <div id="ember1" class="ember-view disabled"></div>
13093 This syntax offers the convenience to add a class if a property is `false`:
13096 // Applies no class when isEnabled is true and class 'disabled' when isEnabled is false
13097 Ember.View.create({
13098 classNameBindings: ['isEnabled::disabled']
13103 Will result in view instances with an HTML representation of:
13106 <div id="ember1" class="ember-view"></div>
13109 When the `isEnabled` property on the view is set to `false`, it will result
13110 in view instances with an HTML representation of:
13113 <div id="ember1" class="ember-view disabled"></div>
13116 Updates to the the value of a class name binding will result in automatic update
13117 of the HTML `class` attribute in the view's rendered HTML representation.
13118 If the value becomes `false` or `undefined` the class name will be removed.
13120 Both `classNames` and `classNameBindings` are concatenated properties.
13121 See `Ember.Object` documentation for more information about concatenated properties.
13125 The HTML attribute section of a view's tag can be set by providing an `attributeBindings`
13126 property set to an array of property names on the view. The return value of these properties
13127 will be used as the value of the view's HTML associated attribute:
13130 AnchorView = Ember.View.extend({
13132 attributeBindings: ['href'],
13133 href: 'http://google.com'
13137 Will result in view instances with an HTML representation of:
13140 <a id="ember1" class="ember-view" href="http://google.com"></a>
13143 If the return value of an `attributeBindings` monitored property is a boolean
13144 the property will follow HTML's pattern of repeating the attribute's name as
13148 MyTextInput = Ember.View.extend({
13150 attributeBindings: ['disabled'],
13155 Will result in view instances with an HTML representation of:
13158 <input id="ember1" class="ember-view" disabled="disabled" />
13161 `attributeBindings` can refer to computed properties:
13164 MyTextInput = Ember.View.extend({
13166 attributeBindings: ['disabled'],
13167 disabled: function(){
13177 Updates to the the property of an attribute binding will result in automatic update
13178 of the HTML attribute in the view's rendered HTML representation.
13180 `attributeBindings` is a concatenated property. See `Ember.Object` documentation
13181 for more information about concatenated properties.
13185 The HTML contents of a view's rendered representation are determined by its template.
13186 Templates can be any function that accepts an optional context parameter and returns
13187 a string of HTML that will be inserted within the view's tag. Most
13188 typically in Ember this function will be a compiled Ember.Handlebars template.
13191 AView = Ember.View.extend({
13192 template: Ember.Handlebars.compile('I am the template')
13196 Will result in view instances with an HTML representation of:
13199 <div id="ember1" class="ember-view">I am the template</div>
13202 Within an Ember application is more common to define a Handlebars templates as
13206 <script type='text/x-handlebars' data-template-name='some-template'>
13211 And associate it by name using a view's `templateName` property:
13214 AView = Ember.View.extend({
13215 templateName: 'some-template'
13219 Using a value for `templateName` that does not have a Handlebars template with a
13220 matching `data-template-name` attribute will throw an error.
13222 Assigning a value to both `template` and `templateName` properties will throw an error.
13224 For views classes that may have a template later defined (e.g. as the block portion of a `{{view}}`
13225 Handlebars helper call in another template or in a subclass), you can provide a `defaultTemplate`
13226 property set to compiled template function. If a template is not later provided for the view
13227 instance the `defaultTemplate` value will be used:
13230 AView = Ember.View.extend({
13231 defaultTemplate: Ember.Handlebars.compile('I was the default'),
13237 Will result in instances with an HTML representation of:
13240 <div id="ember1" class="ember-view">I was the default</div>
13243 If a `template` or `templateName` is provided it will take precedence over `defaultTemplate`:
13246 AView = Ember.View.extend({
13247 defaultTemplate: Ember.Handlebars.compile('I was the default')
13250 aView = AView.create({
13251 template: Ember.Handlebars.compile('I was the template, not default')
13255 Will result in the following HTML representation when rendered:
13258 <div id="ember1" class="ember-view">I was the template, not default</div>
13263 The default context of the compiled template is the view's controller:
13266 AView = Ember.View.extend({
13267 template: Ember.Handlebars.compile('Hello {{excitedGreeting}}')
13270 aController = Ember.Object.create({
13271 firstName: 'Barry',
13272 excitedGreeting: function(){
13273 return this.get("content.firstName") + "!!!"
13277 aView = AView.create({
13278 controller: aController,
13282 Will result in an HTML representation of:
13285 <div id="ember1" class="ember-view">Hello Barry!!!</div>
13288 A context can also be explicitly supplied through the view's `context` property.
13289 If the view has neither `context` nor `controller` properties, the parentView's
13290 context will be used.
13294 Views can have a secondary template that wraps their main template. Like
13295 primary templates, layouts can be any function that accepts an optional context
13296 parameter and returns a string of HTML that will be inserted inside view's tag. Views whose HTML
13297 element is self closing (e.g. `<input />`) cannot have a layout and this property will be ignored.
13299 Most typically in Ember a layout will be a compiled Ember.Handlebars template.
13301 A view's layout can be set directly with the `layout` property or reference an
13302 existing Handlebars template by name with the `layoutName` property.
13304 A template used as a layout must contain a single use of the Handlebars `{{yield}}`
13305 helper. The HTML contents of a view's rendered `template` will be inserted at this location:
13308 AViewWithLayout = Ember.View.extend({
13309 layout: Ember.Handlebars.compile("<div class='my-decorative-class'>{{yield}}</div>")
13310 template: Ember.Handlebars.compile("I got wrapped"),
13314 Will result in view instances with an HTML representation of:
13317 <div id="ember1" class="ember-view">
13318 <div class="my-decorative-class">
13324 See `Handlebars.helpers.yield` for more information.
13326 ## Responding to Browser Events
13328 Views can respond to user-initiated events in one of three ways: method implementation,
13329 through an event manager, and through `{{action}}` helper use in their template or layout.
13331 ### Method Implementation
13333 Views can respond to user-initiated events by implementing a method that matches the
13334 event name. A `jQuery.Event` object will be passed as the argument to this method.
13337 AView = Ember.View.extend({
13338 click: function(event){
13339 // will be called when when an instance's
13340 // rendered element is clicked
13347 Views can define an object as their `eventManager` property. This object can then
13348 implement methods that match the desired event names. Matching events that occur
13349 on the view's rendered HTML or the rendered HTML of any of its DOM descendants
13350 will trigger this method. A `jQuery.Event` object will be passed as the first
13351 argument to the method and an `Ember.View` object as the second. The `Ember.View`
13352 will be the view whose rendered HTML was interacted with. This may be the view with
13353 the `eventManager` property or one of its descendent views.
13356 AView = Ember.View.extend({
13357 eventManager: Ember.Object.create({
13358 doubleClick: function(event, view){
13359 // will be called when when an instance's
13360 // rendered element or any rendering
13361 // of this views's descendent
13362 // elements is clicked
13368 An event defined for an event manager takes precedence over events of the same
13369 name handled through methods on the view.
13372 AView = Ember.View.extend({
13373 mouseEnter: function(event){
13374 // will never trigger.
13376 eventManager: Ember.Object.create({
13377 mouseEnter: function(event, view){
13378 // takes presedence over AView#mouseEnter
13384 Similarly a view's event manager will take precedence for events of any views
13385 rendered as a descendent. A method name that matches an event name will not be called
13386 if the view instance was rendered inside the HTML representation of a view that has
13387 an `eventManager` property defined that handles events of the name. Events not handled
13388 by the event manager will still trigger method calls on the descendent.
13391 OuterView = Ember.View.extend({
13392 template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"),
13393 eventManager: Ember.Object.create({
13394 mouseEnter: function(event, view){
13395 // view might be instance of either
13396 // OutsideView or InnerView depending on
13397 // where on the page the user interaction occured
13402 InnerView = Ember.View.extend({
13403 click: function(event){
13404 // will be called if rendered inside
13405 // an OuterView because OuterView's
13406 // eventManager doesn't handle click events
13408 mouseEnter: function(event){
13409 // will never be called if rendered inside
13415 ### Handlebars `{{action}}` Helper
13417 See `Handlebars.helpers.action`.
13421 Possible events names for any of the responding approaches described above are:
13423 Touch events: 'touchStart', 'touchMove', 'touchEnd', 'touchCancel'
13425 Keyboard events: 'keyDown', 'keyUp', 'keyPress'
13427 Mouse events: 'mouseDown', 'mouseUp', 'contextMenu', 'click', 'doubleClick', 'mouseMove',
13428 'focusIn', 'focusOut', 'mouseEnter', 'mouseLeave'
13430 Form events: 'submit', 'change', 'focusIn', 'focusOut', 'input'
13432 HTML5 drag and drop events: 'dragStart', 'drag', 'dragEnter', 'dragLeave', 'drop', 'dragEnd'
13434 ## Handlebars `{{view}}` Helper
13436 Other `Ember.View` instances can be included as part of a view's template by using the `{{view}}`
13437 Handlebars helper. See `Handlebars.helpers.view` for additional information.
13441 @extends Ember.Object
13442 @uses Ember.Evented
13444 Ember.View = Ember.CoreView.extend(
13445 /** @scope Ember.View.prototype */ {
13447 concatenatedProperties: ['classNames', 'classNameBindings', 'attributeBindings'],
13457 // ..........................................................
13458 // TEMPLATE SUPPORT
13462 The name of the template to lookup if no template is provided.
13464 Ember.View will look for a template with this name in this view's
13465 `templates` object. By default, this will be a global object
13466 shared in `Ember.TEMPLATES`.
13468 @property templateName
13472 templateName: null,
13475 The name of the layout to lookup if no layout is provided.
13477 Ember.View will look for a template with this name in this view's
13478 `templates` object. By default, this will be a global object
13479 shared in `Ember.TEMPLATES`.
13481 @property layoutName
13488 The hash in which to look for `templateName`.
13490 @property templates
13492 @default Ember.TEMPLATES
13494 templates: Ember.TEMPLATES,
13497 The template used to render the view. This should be a function that
13498 accepts an optional context parameter and returns a string of HTML that
13499 will be inserted into the DOM relative to its parent view.
13501 In general, you should set the `templateName` property instead of setting
13502 the template yourself.
13507 template: Ember.computed(function(key, value) {
13508 if (value !== undefined) { return value; }
13510 var templateName = get(this, 'templateName'),
13511 template = this.templateForName(templateName, 'template');
13513 return template || get(this, 'defaultTemplate');
13514 }).property('templateName'),
13517 The controller managing this view. If this property is set, it will be
13518 made available for use by the template.
13520 @property controller
13523 controller: Ember.computed(function(key, value) {
13526 if (arguments.length === 2) {
13529 parentView = get(this, 'parentView');
13530 return parentView ? get(parentView, 'controller') : null;
13535 A view may contain a layout. A layout is a regular template but
13536 supersedes the `template` property during rendering. It is the
13537 responsibility of the layout template to retrieve the `template`
13538 property from the view (or alternatively, call `Handlebars.helpers.yield`,
13539 `{{yield}}`) to render it in the correct location.
13541 This is useful for a view that has a shared wrapper, but which delegates
13542 the rendering of the contents of the wrapper to the `template` property
13548 layout: Ember.computed(function(key, value) {
13549 if (arguments.length === 2) { return value; }
13551 var layoutName = get(this, 'layoutName'),
13552 layout = this.templateForName(layoutName, 'layout');
13554 return layout || get(this, 'defaultLayout');
13555 }).property('layoutName'),
13557 templateForName: function(name, type) {
13558 if (!name) { return; }
13560 var templates = get(this, 'templates'),
13561 template = get(templates, name);
13564 throw new Ember.Error(fmt('%@ - Unable to find %@ "%@".', [this, type, name]));
13571 The object from which templates should access properties.
13573 This object will be passed to the template function each time the render
13574 method is called, but it is up to the individual function to decide what
13577 By default, this will be the view's controller.
13582 context: Ember.computed(function(key, value) {
13583 if (arguments.length === 2) {
13584 set(this, '_context', value);
13587 return get(this, '_context');
13594 Private copy of the view's template context. This can be set directly
13595 by Handlebars without triggering the observer that causes the view
13598 The context of a view is looked up as follows:
13600 1. Supplied context (usually by Handlebars)
13601 2. Specified controller
13602 3. `parentView`'s context (for a child of a ContainerView)
13604 The code in Handlebars that overrides the `_context` property first
13605 checks to see whether the view has a specified controller. This is
13606 something of a hack and should be revisited.
13610 _context: Ember.computed(function(key, value) {
13611 var parentView, controller;
13613 if (arguments.length === 2) {
13617 if (controller = get(this, 'controller')) {
13621 parentView = get(this, '_parentView');
13623 return get(parentView, '_context');
13632 If a value that affects template rendering changes, the view should be
13633 re-rendered to reflect the new value.
13635 @method _displayPropertyDidChange
13637 _displayPropertyDidChange: Ember.observer(function() {
13639 }, 'context', 'controller'),
13642 If false, the view will appear hidden in DOM.
13644 @property isVisible
13653 Array of child views. You should never edit this array directly.
13654 Instead, use appendChild and removeFromParent.
13656 @property childViews
13660 childViews: childViewsProperty,
13664 // When it's a virtual view, we need to notify the parent that their
13665 // childViews will change.
13666 _childViewsWillChange: Ember.beforeObserver(function() {
13667 if (this.isVirtual) {
13668 var parentView = get(this, 'parentView');
13669 if (parentView) { Ember.propertyWillChange(parentView, 'childViews'); }
13673 // When it's a virtual view, we need to notify the parent that their
13674 // childViews did change.
13675 _childViewsDidChange: Ember.observer(function() {
13676 if (this.isVirtual) {
13677 var parentView = get(this, 'parentView');
13678 if (parentView) { Ember.propertyDidChange(parentView, 'childViews'); }
13683 Return the nearest ancestor that is an instance of the provided
13686 @property nearestInstanceOf
13687 @param {Class} klass Subclass of Ember.View (or Ember.View itself)
13691 nearestInstanceOf: function(klass) {
13692 Ember.deprecate("nearestInstanceOf is deprecated and will be removed from future releases. Use nearestOfType.");
13693 var view = get(this, 'parentView');
13696 if(view instanceof klass) { return view; }
13697 view = get(view, 'parentView');
13702 Return the nearest ancestor that is an instance of the provided
13705 @property nearestOfType
13706 @param {Class,Mixin} klass Subclass of Ember.View (or Ember.View itself),
13707 or an instance of Ember.Mixin.
13710 nearestOfType: function(klass) {
13711 var view = get(this, 'parentView'),
13712 isOfType = klass instanceof Ember.Mixin ?
13713 function(view) { return klass.detect(view); } :
13714 function(view) { return klass.detect(view.constructor); };
13717 if( isOfType(view) ) { return view; }
13718 view = get(view, 'parentView');
13723 Return the nearest ancestor that has a given property.
13725 @property nearestWithProperty
13726 @param {String} property A property name
13729 nearestWithProperty: function(property) {
13730 var view = get(this, 'parentView');
13733 if (property in view) { return view; }
13734 view = get(view, 'parentView');
13739 Return the nearest ancestor whose parent is an instance of
13742 @property nearestChildOf
13743 @param {Class} klass Subclass of Ember.View (or Ember.View itself)
13746 nearestChildOf: function(klass) {
13747 var view = get(this, 'parentView');
13750 if(get(view, 'parentView') instanceof klass) { return view; }
13751 view = get(view, 'parentView');
13756 Return the nearest ancestor that is an Ember.CollectionView
13758 @property collectionView
13759 @return Ember.CollectionView
13761 collectionView: Ember.computed(function() {
13762 return this.nearestOfType(Ember.CollectionView);
13766 Return the nearest ancestor that is a direct child of
13767 an Ember.CollectionView
13772 itemView: Ember.computed(function() {
13773 return this.nearestChildOf(Ember.CollectionView);
13777 Return the nearest ancestor that has the property
13780 @property contentView
13783 contentView: Ember.computed(function() {
13784 return this.nearestWithProperty('content');
13790 When the parent view changes, recursively invalidate
13791 collectionView, itemView, and contentView
13793 @method _parentViewDidChange
13795 _parentViewDidChange: Ember.observer(function() {
13796 if (this.isDestroying) { return; }
13798 this.invokeRecursively(function(view) {
13799 view.propertyDidChange('collectionView');
13800 view.propertyDidChange('itemView');
13801 view.propertyDidChange('contentView');
13804 if (get(this, 'parentView.controller') && !get(this, 'controller')) {
13805 this.notifyPropertyChange('controller');
13809 _controllerDidChange: Ember.observer(function() {
13810 if (this.isDestroying) { return; }
13812 this.forEachChildView(function(view) {
13813 view.propertyDidChange('controller');
13817 cloneKeywords: function() {
13818 var templateData = get(this, 'templateData');
13820 var keywords = templateData ? Ember.copy(templateData.keywords) : {};
13821 set(keywords, 'view', get(this, 'concreteView'));
13822 set(keywords, 'controller', get(this, 'controller'));
13828 Called on your view when it should push strings of HTML into a
13829 Ember.RenderBuffer. Most users will want to override the `template`
13830 or `templateName` properties instead of this method.
13832 By default, Ember.View will look for a function in the `template`
13833 property and invoke it with the value of `context`. The value of
13834 `context` will be the view's controller unless you override it.
13837 @param {Ember.RenderBuffer} buffer The render buffer
13839 render: function(buffer) {
13840 // If this view has a layout, it is the responsibility of the
13841 // the layout to render the view's template. Otherwise, render the template
13843 var template = get(this, 'layout') || get(this, 'template');
13846 var context = get(this, 'context');
13847 var keywords = this.cloneKeywords();
13852 isRenderData: true,
13856 // Invoke the template with the provided template context, which
13857 // is the view's controller by default. A hash of data is also passed that provides
13858 // the template with access to the view and render buffer.
13860 Ember.assert('template must be a function. Did you mean to call Ember.Handlebars.compile("...") or specify templateName instead?', typeof template === 'function');
13861 // The template should write directly to the render buffer instead
13862 // of returning a string.
13863 var output = template(context, { data: data });
13865 // If the template returned a string instead of writing to the buffer,
13866 // push the string onto the buffer.
13867 if (output !== undefined) { buffer.push(output); }
13871 invokeForState: function(name) {
13872 var stateName = this.state, args, fn;
13874 // try to find the function for the state in the cache
13875 if (fn = invokeForState[stateName][name]) {
13876 args = a_slice.call(arguments);
13879 return fn.apply(this, args);
13882 // otherwise, find and cache the function for this state
13883 var parent = this, states = parent.states, state;
13886 state = states[stateName];
13892 invokeForState[stateName][name] = fn;
13894 args = a_slice.call(arguments, 1);
13895 args.unshift(this);
13897 return fn.apply(this, args);
13900 state = state.parentState;
13903 states = states.parent;
13908 Renders the view again. This will work regardless of whether the
13909 view is already in the DOM or not. If the view is in the DOM, the
13910 rendering process will be deferred to give bindings a chance
13913 If children were added during the rendering process using `appendChild`,
13914 `rerender` will remove them, because they will be added again
13915 if needed by the next `render`.
13917 In general, if the display of your view changes, you should modify
13918 the DOM element directly instead of manually calling `rerender`, which can
13923 rerender: function() {
13924 return this.invokeForState('rerender');
13927 clearRenderedChildren: function() {
13928 var lengthBefore = this.lengthBeforeRender,
13929 lengthAfter = this.lengthAfterRender;
13931 // If there were child views created during the last call to render(),
13932 // remove them under the assumption that they will be re-created when
13935 // VIEW-TODO: Unit test this path.
13936 var childViews = this._childViews;
13937 for (var i=lengthAfter-1; i>=lengthBefore; i--) {
13938 if (childViews[i]) { childViews[i].destroy(); }
13945 Iterates over the view's `classNameBindings` array, inserts the value
13946 of the specified property into the `classNames` array, then creates an
13947 observer to update the view's element if the bound property ever changes
13950 @method _applyClassNameBindings
13952 _applyClassNameBindings: function() {
13953 var classBindings = get(this, 'classNameBindings'),
13954 classNames = get(this, 'classNames'),
13955 elem, newClass, dasherizedClass;
13957 if (!classBindings) { return; }
13959 // Loop through all of the configured bindings. These will be either
13960 // property names ('isUrgent') or property paths relative to the view
13961 // ('content.isUrgent')
13962 a_forEach(classBindings, function(binding) {
13964 // Variable in which the old class value is saved. The observer function
13965 // closes over this variable, so it knows which string to remove when
13966 // the property changes.
13968 // Extract just the property name from bindings like 'foo:bar'
13969 var parsedPath = Ember.View._parsePropertyPath(binding);
13971 // Set up an observer on the context. If the property changes, toggle the
13973 var observer = function() {
13974 // Get the current value of the property
13975 newClass = this._classStringForProperty(binding);
13978 removeObserver(this, parsedPath.path, observer);
13982 // If we had previously added a class to the element, remove it.
13984 elem.removeClass(oldClass);
13985 // Also remove from classNames so that if the view gets rerendered,
13986 // the class doesn't get added back to the DOM.
13987 classNames.removeObject(oldClass);
13990 // If necessary, add a new class. Make sure we keep track of it so
13991 // it can be removed in the future.
13993 elem.addClass(newClass);
13994 oldClass = newClass;
14000 // Get the class name for the property at its current value
14001 dasherizedClass = this._classStringForProperty(binding);
14003 if (dasherizedClass) {
14004 // Ensure that it gets into the classNames array
14005 // so it is displayed when we render.
14006 classNames.push(dasherizedClass);
14008 // Save a reference to the class name so we can remove it
14009 // if the observer fires. Remember that this variable has
14010 // been closed over by the observer.
14011 oldClass = dasherizedClass;
14014 addObserver(this, parsedPath.path, observer);
14016 this.one('willClearRender', function() {
14017 removeObserver(this, parsedPath.path, observer);
14025 Iterates through the view's attribute bindings, sets up observers for each,
14026 then applies the current value of the attributes to the passed render buffer.
14028 @method _applyAttributeBindings
14029 @param {Ember.RenderBuffer} buffer
14031 _applyAttributeBindings: function(buffer) {
14032 var attributeBindings = get(this, 'attributeBindings'),
14033 attributeValue, elem, type;
14035 if (!attributeBindings) { return; }
14037 a_forEach(attributeBindings, function(binding) {
14038 var split = binding.split(':'),
14039 property = split[0],
14040 attributeName = split[1] || property;
14042 // Create an observer to add/remove/change the attribute if the
14043 // JavaScript property changes.
14044 var observer = function() {
14046 if (!elem) { return; }
14048 attributeValue = get(this, property);
14050 Ember.View.applyAttributeBindings(elem, attributeName, attributeValue);
14053 addObserver(this, property, observer);
14055 this.one('willClearRender', function() {
14056 removeObserver(this, property, observer);
14059 // Determine the current value and add it to the render buffer
14061 attributeValue = get(this, property);
14062 Ember.View.applyAttributeBindings(buffer, attributeName, attributeValue);
14069 Given a property name, returns a dasherized version of that
14070 property name if the property evaluates to a non-falsy value.
14072 For example, if the view has property `isUrgent` that evaluates to true,
14073 passing `isUrgent` to this method will return `"is-urgent"`.
14075 @method _classStringForProperty
14078 _classStringForProperty: function(property) {
14079 var parsedPath = Ember.View._parsePropertyPath(property);
14080 var path = parsedPath.path;
14082 var val = get(this, path);
14083 if (val === undefined && Ember.isGlobalPath(path)) {
14084 val = get(Ember.lookup, path);
14087 return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
14090 // ..........................................................
14095 Returns the current DOM element for the view.
14100 element: Ember.computed(function(key, value) {
14101 if (value !== undefined) {
14102 return this.invokeForState('setElement', value);
14104 return this.invokeForState('getElement');
14106 }).property('_parentView'),
14109 Returns a jQuery object for this view's element. If you pass in a selector
14110 string, this method will return a jQuery object, using the current element
14113 For example, calling `view.$('li')` will return a jQuery object containing
14114 all of the `li` elements inside the DOM element of this view.
14117 @param {String} [selector] a jQuery-compatible selector string
14118 @return {jQuery} the CoreQuery object for the DOM node
14121 return this.invokeForState('$', sel);
14124 mutateChildViews: function(callback) {
14125 var childViews = this._childViews,
14126 idx = childViews.length,
14129 while(--idx >= 0) {
14130 view = childViews[idx];
14131 callback.call(this, view, idx);
14137 forEachChildView: function(callback) {
14138 var childViews = this._childViews;
14140 if (!childViews) { return this; }
14142 var len = childViews.length,
14145 for(idx = 0; idx < len; idx++) {
14146 view = childViews[idx];
14147 callback.call(this, view);
14154 Appends the view's element to the specified parent element.
14156 If the view does not have an HTML representation yet, `createElement()`
14157 will be called automatically.
14159 Note that this method just schedules the view to be appended; the DOM
14160 element will not be appended to the given element until all bindings have
14161 finished synchronizing.
14163 This is not typically a function that you will need to call directly
14164 when building your application. You might consider using Ember.ContainerView
14165 instead. If you do need to use appendTo, be sure that the target element you
14166 are providing is associated with an Ember.Application and does not have an
14167 ancestor element that is associated with an Ember view.
14170 @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object
14171 @return {Ember.View} receiver
14173 appendTo: function(target) {
14174 // Schedule the DOM element to be created and appended to the given
14175 // element after bindings have synchronized.
14176 this._insertElementLater(function() {
14177 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'));
14178 this.$().appendTo(target);
14185 Replaces the content of the specified parent element with this view's element.
14186 If the view does not have an HTML representation yet, `createElement()`
14187 will be called automatically.
14189 Note that this method just schedules the view to be appended; the DOM
14190 element will not be appended to the given element until all bindings have
14191 finished synchronizing
14194 @param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object
14195 @return {Ember.View} received
14197 replaceIn: function(target) {
14198 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'));
14200 this._insertElementLater(function() {
14201 Ember.$(target).empty();
14202 this.$().appendTo(target);
14211 Schedules a DOM operation to occur during the next render phase. This
14212 ensures that all bindings have finished synchronizing before the view is
14215 To use, pass a function that performs a DOM operation..
14217 Before your function is called, this view and all child views will receive
14218 the `willInsertElement` event. After your function is invoked, this view
14219 and all of its child views will receive the `didInsertElement` event.
14221 view._insertElementLater(function() {
14222 this.createElement();
14223 this.$().appendTo('body');
14226 @method _insertElementLater
14227 @param {Function} fn the function that inserts the element into the DOM
14229 _insertElementLater: function(fn) {
14230 this._scheduledInsert = Ember.run.scheduleOnce('render', this, '_insertElement', fn);
14236 _insertElement: function (fn) {
14237 this._scheduledInsert = null;
14238 this.invokeForState('insertElement', fn);
14242 Appends the view's element to the document body. If the view does
14243 not have an HTML representation yet, `createElement()` will be called
14246 Note that this method just schedules the view to be appended; the DOM
14247 element will not be appended to the document body until all bindings have
14248 finished synchronizing.
14251 @return {Ember.View} receiver
14253 append: function() {
14254 return this.appendTo(document.body);
14258 Removes the view's element from the element to which it is attached.
14261 @return {Ember.View} receiver
14263 remove: function() {
14264 // What we should really do here is wait until the end of the run loop
14265 // to determine if the element has been re-appended to a different
14267 // In the interim, we will just re-render if that happens. It is more
14268 // important than elements get garbage collected.
14269 this.destroyElement();
14270 this.invokeRecursively(function(view) {
14271 view.clearRenderedChildren();
14276 The ID to use when trying to locate the element in the DOM. If you do not
14277 set the elementId explicitly, then the view's GUID will be used instead.
14278 This ID must be set at the time the view is created.
14280 @property elementId
14283 elementId: Ember.computed(function(key, value) {
14284 return value !== undefined ? value : Ember.guidFor(this);
14287 // TODO: Perhaps this should be removed from the production build somehow.
14288 _elementIdDidChange: Ember.beforeObserver(function() {
14289 throw "Changing a view's elementId after creation is not allowed.";
14293 Attempts to discover the element in the parent element. The default
14294 implementation looks for an element with an ID of elementId (or the view's
14295 guid if elementId is null). You can override this method to provide your
14296 own form of lookup. For example, if you want to discover your element
14297 using a CSS class name instead of an ID.
14299 @method findElementInParentElement
14300 @param {DOMElement} parentElement The parent's DOM element
14301 @return {DOMElement} The discovered element
14303 findElementInParentElement: function(parentElem) {
14304 var id = "#" + get(this, 'elementId');
14305 return Ember.$(id)[0] || Ember.$(id, parentElem)[0];
14309 Creates a DOM representation of the view and all of its
14310 child views by recursively calling the `render()` method.
14312 After the element has been created, `didInsertElement` will
14313 be called on this view and all of its child views.
14315 @method createElement
14316 @return {Ember.View} receiver
14318 createElement: function() {
14319 if (get(this, 'element')) { return this; }
14321 var buffer = this.renderToBuffer();
14322 set(this, 'element', buffer.element());
14328 Called when a view is going to insert an element into the DOM.
14330 @event willInsertElement
14332 willInsertElement: Ember.K,
14335 Called when the element of the view has been inserted into the DOM.
14336 Override this function to do any set up that requires an element in the
14339 @event didInsertElement
14341 didInsertElement: Ember.K,
14344 Called when the view is about to rerender, but before anything has
14345 been torn down. This is a good opportunity to tear down any manual
14346 observers you have installed based on the DOM state
14348 @event willClearRender
14350 willClearRender: Ember.K,
14355 Run this callback on the current view and recursively on child views.
14357 @method invokeRecursively
14358 @param fn {Function}
14360 invokeRecursively: function(fn) {
14361 fn.call(this, this);
14363 this.forEachChildView(function(view) {
14364 view.invokeRecursively(fn);
14369 Invalidates the cache for a property on all child views.
14371 @method invalidateRecursively
14373 invalidateRecursively: function(key) {
14374 this.forEachChildView(function(view) {
14375 view.propertyDidChange(key);
14382 Invokes the receiver's willInsertElement() method if it exists and then
14383 invokes the same on all child views.
14385 NOTE: In some cases this was called when the element existed. This no longer
14386 works so we let people know. We can remove this warning code later.
14388 @method _notifyWillInsertElement
14390 _notifyWillInsertElement: function() {
14391 this.invokeRecursively(function(view) {
14392 view.trigger('willInsertElement');
14399 Invokes the receiver's didInsertElement() method if it exists and then
14400 invokes the same on all child views.
14402 @method _notifyDidInsertElement
14404 _notifyDidInsertElement: function() {
14405 this.invokeRecursively(function(view) {
14406 view.trigger('didInsertElement');
14413 Triggers the `willClearRender` event (which invokes the `willClearRender()`
14414 method if it exists) on this view and all child views.
14416 @method _notifyWillClearRender
14418 _notifyWillClearRender: function() {
14419 this.invokeRecursively(function(view) {
14420 view.trigger('willClearRender');
14425 Destroys any existing element along with the element for any child views
14426 as well. If the view does not currently have a element, then this method
14429 If you implement willDestroyElement() on your view, then this method will
14430 be invoked on your view before your element is destroyed to give you a
14431 chance to clean up any event handlers, etc.
14433 If you write a willDestroyElement() handler, you can assume that your
14434 didInsertElement() handler was called earlier for the same element.
14436 Normally you will not call or override this method yourself, but you may
14437 want to implement the above callbacks when it is run.
14439 @method destroyElement
14440 @return {Ember.View} receiver
14442 destroyElement: function() {
14443 return this.invokeForState('destroyElement');
14447 Called when the element of the view is going to be destroyed. Override
14448 this function to do any teardown that requires an element, like removing
14451 @event willDestroyElement
14453 willDestroyElement: function() {},
14458 Triggers the `willDestroyElement` event (which invokes the `willDestroyElement()`
14459 method if it exists) on this view and all child views.
14461 Before triggering `willDestroyElement`, it first triggers the `willClearRender`
14464 @method _notifyWillDestroyElement
14466 _notifyWillDestroyElement: function() {
14467 this._notifyWillClearRender();
14469 this.invokeRecursively(function(view) {
14470 view.trigger('willDestroyElement');
14474 _elementWillChange: Ember.beforeObserver(function() {
14475 this.forEachChildView(function(view) {
14476 Ember.propertyWillChange(view, 'element');
14483 If this view's element changes, we need to invalidate the caches of our
14484 child views so that we do not retain references to DOM elements that are
14487 @method _elementDidChange
14489 _elementDidChange: Ember.observer(function() {
14490 this.forEachChildView(function(view) {
14491 Ember.propertyDidChange(view, 'element');
14496 Called when the parentView property has changed.
14498 @event parentViewDidChange
14500 parentViewDidChange: Ember.K,
14502 instrumentName: 'render.view',
14504 instrumentDetails: function(hash) {
14505 hash.template = get(this, 'templateName');
14509 _renderToBuffer: function(parentBuffer, bufferOperation) {
14510 this.lengthBeforeRender = this._childViews.length;
14511 var buffer = this._super(parentBuffer, bufferOperation);
14512 this.lengthAfterRender = this._childViews.length;
14517 renderToBufferIfNeeded: function () {
14518 return this.invokeForState('renderToBufferIfNeeded', this);
14521 beforeRender: function(buffer) {
14522 this.applyAttributesToBuffer(buffer);
14525 afterRender: Ember.K,
14527 applyAttributesToBuffer: function(buffer) {
14528 // Creates observers for all registered class name and attribute bindings,
14529 // then adds them to the element.
14530 this._applyClassNameBindings();
14532 // Pass the render buffer so the method can apply attributes directly.
14533 // This isn't needed for class name bindings because they use the
14534 // existing classNames infrastructure.
14535 this._applyAttributeBindings(buffer);
14538 a_forEach(get(this, 'classNames'), function(name){ buffer.addClass(name); });
14539 buffer.id(get(this, 'elementId'));
14541 var role = get(this, 'ariaRole');
14543 buffer.attr('role', role);
14546 if (get(this, 'isVisible') === false) {
14547 buffer.style('display', 'none');
14551 // ..........................................................
14552 // STANDARD RENDER PROPERTIES
14556 Tag name for the view's outer element. The tag name is only used when
14557 an element is first created. If you change the tagName for an element, you
14558 must destroy and recreate the view element.
14560 By default, the render buffer will use a `<div>` tag for views.
14567 // We leave this null by default so we can tell the difference between
14568 // the default case and a user-specified tag.
14572 The WAI-ARIA role of the control represented by this view. For example, a
14573 button may have a role of type 'button', or a pane may have a role of
14574 type 'alertdialog'. This property is used by assistive software to help
14575 visually challenged users navigate rich web applications.
14577 The full list of valid WAI-ARIA roles is available at:
14578 http://www.w3.org/TR/wai-aria/roles#roles_categorization
14587 Standard CSS class names to apply to the view's outer element. This
14588 property automatically inherits any class names defined by the view's
14589 superclasses as well.
14591 @property classNames
14593 @default ['ember-view']
14595 classNames: ['ember-view'],
14598 A list of properties of the view to apply as class names. If the property
14599 is a string value, the value of that string will be applied as a class
14602 // Applies the 'high' class to the view element
14603 Ember.View.create({
14604 classNameBindings: ['priority']
14608 If the value of the property is a Boolean, the name of that property is
14609 added as a dasherized class name.
14611 // Applies the 'is-urgent' class to the view element
14612 Ember.View.create({
14613 classNameBindings: ['isUrgent']
14617 If you would prefer to use a custom value instead of the dasherized
14618 property name, you can pass a binding like this:
14620 // Applies the 'urgent' class to the view element
14621 Ember.View.create({
14622 classNameBindings: ['isUrgent:urgent']
14626 This list of properties is inherited from the view's superclasses as well.
14628 @property classNameBindings
14632 classNameBindings: [],
14635 A list of properties of the view to apply as attributes. If the property is
14636 a string value, the value of that string will be applied as the attribute.
14638 // Applies the type attribute to the element
14639 // with the value "button", like <div type="button">
14640 Ember.View.create({
14641 attributeBindings: ['type'],
14645 If the value of the property is a Boolean, the name of that property is
14646 added as an attribute.
14648 // Renders something like <div enabled="enabled">
14649 Ember.View.create({
14650 attributeBindings: ['enabled'],
14654 @property attributeBindings
14656 attributeBindings: [],
14658 // .......................................................
14659 // CORE DISPLAY METHODS
14665 Setup a view, but do not finish waking it up.
14666 - configure childViews
14667 - register the view with the global views hash, which is used for event
14675 // setup child views. be sure to clone the child views array first
14676 this._childViews = this._childViews.slice();
14678 Ember.assert("Only arrays are allowed for 'classNameBindings'", Ember.typeOf(this.classNameBindings) === 'array');
14679 this.classNameBindings = Ember.A(this.classNameBindings.slice());
14681 Ember.assert("Only arrays are allowed for 'classNames'", Ember.typeOf(this.classNames) === 'array');
14682 this.classNames = Ember.A(this.classNames.slice());
14684 var viewController = get(this, 'viewController');
14685 if (viewController) {
14686 viewController = get(viewController);
14687 if (viewController) {
14688 set(viewController, 'view', this);
14693 appendChild: function(view, options) {
14694 return this.invokeForState('appendChild', view, options);
14698 Removes the child view from the parent view.
14700 @method removeChild
14701 @param {Ember.View} view
14702 @return {Ember.View} receiver
14704 removeChild: function(view) {
14705 // If we're destroying, the entire subtree will be
14706 // freed, and the DOM will be handled separately,
14707 // so no need to mess with childViews.
14708 if (this.isDestroying) { return; }
14710 // update parent node
14711 set(view, '_parentView', null);
14713 // remove view from childViews array.
14714 var childViews = this._childViews;
14716 Ember.EnumerableUtils.removeObject(childViews, view);
14718 this.propertyDidChange('childViews'); // HUH?! what happened to will change?
14724 Removes all children from the parentView.
14726 @method removeAllChildren
14727 @return {Ember.View} receiver
14729 removeAllChildren: function() {
14730 return this.mutateChildViews(function(view) {
14731 this.removeChild(view);
14735 destroyAllChildren: function() {
14736 return this.mutateChildViews(function(view) {
14742 Removes the view from its parentView, if one is found. Otherwise
14745 @method removeFromParent
14746 @return {Ember.View} receiver
14748 removeFromParent: function() {
14749 var parent = get(this, '_parentView');
14751 // Remove DOM element from parent
14754 if (parent) { parent.removeChild(this); }
14759 You must call `destroy` on a view to destroy the view (and all of its
14760 child views). This will remove the view from any parent node, then make
14761 sure that the DOM element managed by the view can be released by the
14764 @method willDestroy
14766 willDestroy: function() {
14767 // calling this._super() will nuke computed properties and observers,
14768 // so collect any information we need before calling super.
14769 var childViews = this._childViews,
14770 parent = get(this, '_parentView'),
14773 // destroy the element -- this will avoid each child view destroying
14774 // the element over and over again...
14775 if (!this.removedFromDOM) { this.destroyElement(); }
14777 // remove from non-virtual parent view if viewName was specified
14778 if (this.viewName) {
14779 var nonVirtualParentView = get(this, 'parentView');
14780 if (nonVirtualParentView) {
14781 set(nonVirtualParentView, this.viewName, null);
14785 // remove from parent if found. Don't call removeFromParent,
14786 // as removeFromParent will try to remove the element from
14788 if (parent) { parent.removeChild(this); }
14790 this.state = 'destroyed';
14792 childLen = childViews.length;
14793 for (var i=childLen-1; i>=0; i--) {
14794 childViews[i].removedFromDOM = true;
14795 childViews[i].destroy();
14798 // next remove view from global hash
14799 if (!this.isVirtual) delete Ember.View.views[get(this, 'elementId')];
14803 Instantiates a view to be added to the childViews array during view
14804 initialization. You generally will not call this method directly unless
14805 you are overriding createChildViews(). Note that this method will
14806 automatically configure the correct settings on the new view instance to
14807 act as a child of the parent.
14809 @method createChildView
14810 @param {Class} viewClass
14811 @param {Hash} [attrs] Attributes to add
14812 @return {Ember.View} new instance
14814 createChildView: function(view, attrs) {
14815 if (Ember.CoreView.detect(view)) {
14816 attrs = attrs || {};
14817 attrs._parentView = this;
14818 attrs.templateData = attrs.templateData || get(this, 'templateData');
14820 view = view.create(attrs);
14822 // don't set the property on a virtual view, as they are invisible to
14823 // consumers of the view API
14824 if (view.viewName) { set(get(this, 'concreteView'), view.viewName, view); }
14826 Ember.assert('You must pass instance or subclass of View', view instanceof Ember.CoreView);
14827 Ember.assert("You can only pass attributes when a class is provided", !attrs);
14829 if (!get(view, 'templateData')) {
14830 set(view, 'templateData', get(this, 'templateData'));
14833 set(view, '_parentView', this);
14839 becameVisible: Ember.K,
14840 becameHidden: Ember.K,
14845 When the view's `isVisible` property changes, toggle the visibility
14846 element of the actual DOM element.
14848 @method _isVisibleDidChange
14850 _isVisibleDidChange: Ember.observer(function() {
14851 var $el = this.$();
14852 if (!$el) { return; }
14854 var isVisible = get(this, 'isVisible');
14856 $el.toggle(isVisible);
14858 if (this._isAncestorHidden()) { return; }
14861 this._notifyBecameVisible();
14863 this._notifyBecameHidden();
14867 _notifyBecameVisible: function() {
14868 this.trigger('becameVisible');
14870 this.forEachChildView(function(view) {
14871 var isVisible = get(view, 'isVisible');
14873 if (isVisible || isVisible === null) {
14874 view._notifyBecameVisible();
14879 _notifyBecameHidden: function() {
14880 this.trigger('becameHidden');
14881 this.forEachChildView(function(view) {
14882 var isVisible = get(view, 'isVisible');
14884 if (isVisible || isVisible === null) {
14885 view._notifyBecameHidden();
14890 _isAncestorHidden: function() {
14891 var parent = get(this, 'parentView');
14894 if (get(parent, 'isVisible') === false) { return true; }
14896 parent = get(parent, 'parentView');
14902 clearBuffer: function() {
14903 this.invokeRecursively(function(view) {
14904 this.buffer = null;
14908 transitionTo: function(state, children) {
14909 this.state = state;
14911 if (children !== false) {
14912 this.forEachChildView(function(view) {
14913 view.transitionTo(state);
14918 // .......................................................
14925 Handle events from `Ember.EventDispatcher`
14927 @method handleEvent
14928 @param eventName {String}
14931 handleEvent: function(eventName, evt) {
14932 return this.invokeForState('handleEvent', eventName, evt);
14938 Describe how the specified actions should behave in the various
14939 states that a view can exist in. Possible states:
14941 * preRender: when a view is first instantiated, and after its
14942 element was destroyed, it is in the preRender state
14943 * inBuffer: once a view has been rendered, but before it has
14944 been inserted into the DOM, it is in the inBuffer state
14945 * inDOM: once a view has been inserted into the DOM it is in
14946 the inDOM state. A view spends the vast majority of its
14947 existence in this state.
14948 * destroyed: once a view has been destroyed (using the destroy
14949 method), it is in this state. No further actions can be invoked
14950 on a destroyed view.
14953 // in the destroyed state, everything is illegal
14955 // before rendering has begun, all legal manipulations are noops.
14957 // inside the buffer, legal manipulations are done on the buffer
14959 // once the view has been inserted into the DOM, legal manipulations
14960 // are done on the DOM element.
14963 prepend: function(view, html) {
14964 view.$().prepend(html);
14967 after: function(view, html) {
14968 view.$().after(html);
14971 html: function(view, html) {
14972 view.$().html(html);
14975 replace: function(view) {
14976 var element = get(view, 'element');
14978 set(view, 'element', null);
14980 view._insertElementLater(function() {
14981 Ember.$(element).replaceWith(get(view, 'element'));
14985 remove: function(view) {
14989 empty: function(view) {
14994 Ember.View.reopen({
14995 states: Ember.View.states,
14996 domManager: DOMManager
14999 Ember.View.reopenClass({
15004 Parse a path and return an object which holds the parsed properties.
15006 For example a path like "content.isEnabled:enabled:disabled" wil return the
15010 path: "content.isEnabled",
15011 className: "enabled",
15012 falsyClassName: "disabled",
15013 classNames: ":enabled:disabled"
15016 @method _parsePropertyPath
15019 _parsePropertyPath: function(path) {
15020 var split = path.split(':'),
15021 propertyPath = split[0],
15026 // check if the property is defined as prop:class or prop:trueClass:falseClass
15027 if (split.length > 1) {
15028 className = split[1];
15029 if (split.length === 3) { falsyClassName = split[2]; }
15031 classNames = ':' + className;
15032 if (falsyClassName) { classNames += ":" + falsyClassName; }
15036 path: propertyPath,
15037 classNames: classNames,
15038 className: (className === '') ? undefined : className,
15039 falsyClassName: falsyClassName
15046 Get the class name for a given value, based on the path, optional className
15047 and optional falsyClassName.
15049 - if a className or falsyClassName has been specified:
15050 - if the value is truthy and className has been specified, className is returned
15051 - if the value is falsy and falsyClassName has been specified, falsyClassName is returned
15052 - otherwise null is returned
15053 - if the value is true, the dasherized last part of the supplied path is returned
15054 - if the value is not false, undefined or null, the value is returned
15055 - if none of the above rules apply, null is returned
15057 @method _classStringForValue
15061 @param falsyClassName
15064 _classStringForValue: function(path, val, className, falsyClassName) {
15065 // When using the colon syntax, evaluate the truthiness or falsiness
15066 // of the value to determine which className to return
15067 if (className || falsyClassName) {
15068 if (className && !!val) {
15071 } else if (falsyClassName && !val) {
15072 return falsyClassName;
15078 // If value is a Boolean and true, return the dasherized property
15080 } else if (val === true) {
15081 // Normalize property path to be suitable for use
15082 // as a class name. For exaple, content.foo.barBaz
15083 // becomes bar-baz.
15084 var parts = path.split('.');
15085 return Ember.String.dasherize(parts[parts.length-1]);
15087 // If the value is not false, undefined, or null, return the current
15088 // value of the property.
15089 } else if (val !== false && val !== undefined && val !== null) {
15092 // Nothing to display. Return null so that the old class is removed
15093 // but no new class is added.
15107 Ember.View.views = {};
15109 // If someone overrides the child views computed property when
15110 // defining their class, we want to be able to process the user's
15111 // supplied childViews and then restore the original computed property
15112 // at view initialization time. This happens in Ember.ContainerView's init
15114 Ember.View.childViewsProperty = childViewsProperty;
15116 Ember.View.applyAttributeBindings = function(elem, name, value) {
15117 var type = Ember.typeOf(value);
15118 var currentValue = elem.attr(name);
15120 // if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js
15121 if ((type === 'string' || (type === 'number' && !isNaN(value))) && value !== currentValue) {
15122 elem.attr(name, value);
15123 } else if (value && type === 'boolean') {
15124 elem.attr(name, name);
15125 } else if (!value) {
15126 elem.removeAttr(name);
15137 @submodule ember-views
15140 var get = Ember.get, set = Ember.set;
15142 Ember.View.states = {
15144 // appendChild is only legal while rendering the buffer.
15145 appendChild: function() {
15146 throw "You can't use appendChild outside of the rendering process";
15153 getElement: function() {
15157 // Handle events from `Ember.EventDispatcher`
15158 handleEvent: function() {
15159 return true; // continue event propagation
15162 destroyElement: function(view) {
15163 set(view, 'element', null);
15164 if (view._scheduledInsert) {
15165 Ember.run.cancel(view._scheduledInsert);
15166 view._scheduledInsert = null;
15171 renderToBufferIfNeeded: function () {
15177 Ember.View.reopen({
15178 states: Ember.View.states
15188 @submodule ember-views
15191 Ember.View.states.preRender = {
15192 parentState: Ember.View.states._default,
15194 // a view leaves the preRender state once its element has been
15195 // created (createElement).
15196 insertElement: function(view, fn) {
15197 view.createElement();
15198 view._notifyWillInsertElement();
15199 // after createElement, the view will be in the hasElement state.
15201 view.transitionTo('inDOM');
15202 view._notifyDidInsertElement();
15205 renderToBufferIfNeeded: function(view) {
15206 return view.renderToBuffer();
15211 setElement: function(view, value) {
15212 if (value !== null) {
15213 view.transitionTo('hasElement');
15226 @submodule ember-views
15229 var get = Ember.get, set = Ember.set, meta = Ember.meta;
15231 Ember.View.states.inBuffer = {
15232 parentState: Ember.View.states._default,
15234 $: function(view, sel) {
15235 // if we don't have an element yet, someone calling this.$() is
15236 // trying to update an element that isn't in the DOM. Instead,
15237 // rerender the view to allow the render method to reflect the
15243 // when a view is rendered in a buffer, rerendering it simply
15244 // replaces the existing buffer with a new one
15245 rerender: function(view) {
15246 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.");
15248 view._notifyWillClearRender();
15250 view.clearRenderedChildren();
15251 view.renderToBuffer(view.buffer, 'replaceWith');
15254 // when a view is rendered in a buffer, appending a child
15255 // view will render that view and append the resulting
15256 // buffer into its buffer.
15257 appendChild: function(view, childView, options) {
15258 var buffer = view.buffer;
15260 childView = this.createChildView(childView, options);
15261 view._childViews.push(childView);
15263 childView.renderToBuffer(buffer);
15265 view.propertyDidChange('childViews');
15270 // when a view is rendered in a buffer, destroying the
15271 // element will simply destroy the buffer and put the
15272 // state back into the preRender state.
15273 destroyElement: function(view) {
15274 view.clearBuffer();
15275 view._notifyWillDestroyElement();
15276 view.transitionTo('preRender');
15281 empty: function() {
15282 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.");
15285 renderToBufferIfNeeded: function (view) {
15286 return view.buffer;
15289 // It should be impossible for a rendered view to be scheduled for
15291 insertElement: function() {
15292 throw "You can't insert an element that has already been rendered";
15295 setElement: function(view, value) {
15296 if (value === null) {
15297 view.transitionTo('preRender');
15299 view.clearBuffer();
15300 view.transitionTo('hasElement');
15315 @submodule ember-views
15318 var get = Ember.get, set = Ember.set, meta = Ember.meta;
15320 Ember.View.states.hasElement = {
15321 parentState: Ember.View.states._default,
15323 $: function(view, sel) {
15324 var elem = get(view, 'element');
15325 return sel ? Ember.$(sel, elem) : Ember.$(elem);
15328 getElement: function(view) {
15329 var parent = get(view, 'parentView');
15330 if (parent) { parent = get(parent, 'element'); }
15331 if (parent) { return view.findElementInParentElement(parent); }
15332 return Ember.$("#" + get(view, 'elementId'))[0];
15335 setElement: function(view, value) {
15336 if (value === null) {
15337 view.transitionTo('preRender');
15339 throw "You cannot set an element to a non-null value when the element is already in the DOM.";
15345 // once the view has been inserted into the DOM, rerendering is
15346 // deferred to allow bindings to synchronize.
15347 rerender: function(view) {
15348 view._notifyWillClearRender();
15350 view.clearRenderedChildren();
15352 view.domManager.replace(view);
15356 // once the view is already in the DOM, destroying it removes it
15357 // from the DOM, nukes its element, and puts it back into the
15358 // preRender state if inDOM.
15360 destroyElement: function(view) {
15361 view._notifyWillDestroyElement();
15362 view.domManager.remove(view);
15363 set(view, 'element', null);
15364 if (view._scheduledInsert) {
15365 Ember.run.cancel(view._scheduledInsert);
15366 view._scheduledInsert = null;
15371 empty: function(view) {
15372 var _childViews = view._childViews, len, idx;
15374 len = _childViews.length;
15375 for (idx = 0; idx < len; idx++) {
15376 _childViews[idx]._notifyWillDestroyElement();
15379 view.domManager.empty(view);
15382 // Handle events from `Ember.EventDispatcher`
15383 handleEvent: function(view, eventName, evt) {
15384 if (view.has(eventName)) {
15385 // Handler should be able to re-dispatch events, so we don't
15386 // preventDefault or stopPropagation.
15387 return view.trigger(eventName, evt);
15389 return true; // continue event propagation
15394 Ember.View.states.inDOM = {
15395 parentState: Ember.View.states.hasElement,
15397 insertElement: function(view, fn) {
15398 throw "You can't insert an element into the DOM that has already been inserted";
15409 @submodule ember-views
15412 var destroyedError = "You can't call %@ on a destroyed view", fmt = Ember.String.fmt;
15414 Ember.View.states.destroyed = {
15415 parentState: Ember.View.states._default,
15417 appendChild: function() {
15418 throw fmt(destroyedError, ['appendChild']);
15420 rerender: function() {
15421 throw fmt(destroyedError, ['rerender']);
15423 destroyElement: function() {
15424 throw fmt(destroyedError, ['destroyElement']);
15426 empty: function() {
15427 throw fmt(destroyedError, ['empty']);
15430 setElement: function() {
15431 throw fmt(destroyedError, ["set('element', ...)"]);
15434 renderToBufferIfNeeded: function() {
15435 throw fmt(destroyedError, ["renderToBufferIfNeeded"]);
15438 // Since element insertion is scheduled, don't do anything if
15439 // the view has been destroyed between scheduling and execution
15440 insertElement: Ember.K
15457 @submodule ember-views
15460 var get = Ember.get, set = Ember.set, meta = Ember.meta;
15461 var forEach = Ember.EnumerableUtils.forEach;
15463 var childViewsProperty = Ember.computed(function() {
15464 return get(this, '_childViews');
15465 }).property('_childViews');
15468 A `ContainerView` is an `Ember.View` subclass that allows for manual or programatic
15469 management of a view's `childViews` array that will correctly update the `ContainerView`
15470 instance's rendered DOM representation.
15472 ## Setting Initial Child Views
15473 The initial array of child views can be set in one of two ways. You can provide
15474 a `childViews` property at creation time that contains instance of `Ember.View`:
15477 aContainer = Ember.ContainerView.create({
15478 childViews: [Ember.View.create(), Ember.View.create()]
15482 You can also provide a list of property names whose values are instances of `Ember.View`:
15485 aContainer = Ember.ContainerView.create({
15486 childViews: ['aView', 'bView', 'cView'],
15487 aView: Ember.View.create(),
15488 bView: Ember.View.create()
15489 cView: Ember.View.create()
15493 The two strategies can be combined:
15496 aContainer = Ember.ContainerView.create({
15497 childViews: ['aView', Ember.View.create()],
15498 aView: Ember.View.create()
15502 Each child view's rendering will be inserted into the container's rendered HTML in the same
15503 order as its position in the `childViews` property.
15505 ## Adding and Removing Child Views
15506 The views in a container's `childViews` array should be added and removed by manipulating
15507 the `childViews` property directly.
15509 To remove a view pass that view into a `removeObject` call on the container's `childViews` property.
15511 Given an empty `<body>` the following code
15514 aContainer = Ember.ContainerView.create({
15515 classNames: ['the-container'],
15516 childViews: ['aView', 'bView'],
15517 aView: Ember.View.create({
15518 template: Ember.Handlebars.compile("A")
15520 bView: Ember.View.create({
15521 template: Ember.Handlebars.compile("B")
15525 aContainer.appendTo('body');
15528 Results in the HTML
15531 <div class="ember-view the-container">
15532 <div class="ember-view">A</div>
15533 <div class="ember-view">B</div>
15540 aContainer.get('childViews'); // [aContainer.aView, aContainer.bView]
15541 aContainer.get('childViews').removeObject(aContainer.get('bView'));
15542 aContainer.get('childViews'); // [aContainer.aView]
15545 Will result in the following HTML
15548 <div class="ember-view the-container">
15549 <div class="ember-view">A</div>
15554 Similarly, adding a child view is accomplished by adding `Ember.View` instances to the
15555 container's `childViews` property.
15557 Given an empty `<body>` the following code
15560 aContainer = Ember.ContainerView.create({
15561 classNames: ['the-container'],
15562 childViews: ['aView', 'bView'],
15563 aView: Ember.View.create({
15564 template: Ember.Handlebars.compile("A")
15566 bView: Ember.View.create({
15567 template: Ember.Handlebars.compile("B")
15571 aContainer.appendTo('body');
15574 Results in the HTML
15577 <div class="ember-view the-container">
15578 <div class="ember-view">A</div>
15579 <div class="ember-view">B</div>
15586 AnotherViewClass = Ember.View.extend({
15587 template: Ember.Handlebars.compile("Another view")
15590 aContainer.get('childViews'); // [aContainer.aView, aContainer.bView]
15591 aContainer.get('childViews').pushObject(AnotherViewClass.create());
15592 aContainer.get('childViews'); // [aContainer.aView, aContainer.bView, <AnotherViewClass instance>]
15595 Will result in the following HTML
15598 <div class="ember-view the-container">
15599 <div class="ember-view">A</div>
15600 <div class="ember-view">B</div>
15601 <div class="ember-view">Another view</div>
15606 Direct manipulation of childViews presence or absence in the DOM via calls to
15607 `remove` or `removeFromParent` or calls to a container's `removeChild` may not behave
15610 Calling `remove()` on a child view will remove the view's HTML, but it will remain as part of its
15611 container's `childView`s property.
15613 Calling `removeChild()` on the container will remove the passed view instance from the container's
15614 `childView`s but keep its HTML within the container's rendered view.
15616 Calling `removeFromParent()` behaves as expected but should be avoided in favor of direct
15617 manipulation of a container's `childViews` property.
15620 aContainer = Ember.ContainerView.create({
15621 classNames: ['the-container'],
15622 childViews: ['aView', 'bView'],
15623 aView: Ember.View.create({
15624 template: Ember.Handlebars.compile("A")
15626 bView: Ember.View.create({
15627 template: Ember.Handlebars.compile("B")
15631 aContainer.appendTo('body');
15634 Results in the HTML
15637 <div class="ember-view the-container">
15638 <div class="ember-view">A</div>
15639 <div class="ember-view">B</div>
15643 Calling `aContainer.get('aView').removeFromParent()` will result in the following HTML
15646 <div class="ember-view the-container">
15647 <div class="ember-view">B</div>
15651 And the `Ember.View` instance stored in `aContainer.aView` will be removed from `aContainer`'s
15652 `childViews` array.
15654 ## Templates and Layout
15656 A `template`, `templateName`, `defaultTemplate`, `layout`, `layoutName` or `defaultLayout`
15657 property on a container view will not result in the template or layout being rendered.
15658 The HTML contents of a `Ember.ContainerView`'s DOM representation will only be the rendered HTML
15659 of its child views.
15661 ## Binding a View to Display
15663 If you would like to display a single view in your ContainerView, you can set its `currentView`
15664 property. When the `currentView` property is set to a view instance, it will be added to the
15665 ContainerView's `childViews` array. If the `currentView` property is later changed to a
15666 different view, the new view will replace the old view. If `currentView` is set to `null`, the
15667 last `currentView` will be removed.
15669 This functionality is useful for cases where you want to bind the display of a ContainerView to
15670 a controller or state manager. For example, you can bind the `currentView` of a container to
15671 a controller like this:
15674 App.appController = Ember.Object.create({
15675 view: Ember.View.create({
15676 templateName: 'person_template'
15682 {{view Ember.ContainerView currentViewBinding="App.appController.view"}}
15685 @class ContainerView
15687 @extends Ember.View
15690 Ember.ContainerView = Ember.View.extend({
15695 var childViews = get(this, 'childViews');
15696 Ember.defineProperty(this, 'childViews', childViewsProperty);
15698 var _childViews = this._childViews;
15700 forEach(childViews, function(viewName, idx) {
15703 if ('string' === typeof viewName) {
15704 view = get(this, viewName);
15705 view = this.createChildView(view);
15706 set(this, viewName, view);
15708 view = this.createChildView(viewName);
15711 _childViews[idx] = view;
15714 var currentView = get(this, 'currentView');
15715 if (currentView) _childViews.push(this.createChildView(currentView));
15717 // Make the _childViews array observable
15718 Ember.A(_childViews);
15720 // Sets up an array observer on the child views array. This
15721 // observer will detect when child views are added or removed
15722 // and update the DOM to reflect the mutation.
15723 get(this, 'childViews').addArrayObserver(this, {
15724 willChange: 'childViewsWillChange',
15725 didChange: 'childViewsDidChange'
15732 Instructs each child view to render to the passed render buffer.
15735 @param {Ember.RenderBuffer} buffer the buffer to render to
15737 render: function(buffer) {
15738 this.forEachChildView(function(view) {
15739 view.renderToBuffer(buffer);
15743 instrumentName: 'render.container',
15748 When the container view is destroyed, tear down the child views
15751 @method willDestroy
15753 willDestroy: function() {
15754 get(this, 'childViews').removeArrayObserver(this, {
15755 willChange: 'childViewsWillChange',
15756 didChange: 'childViewsDidChange'
15765 When a child view is removed, destroy its element so that
15766 it is removed from the DOM.
15768 The array observer that triggers this action is set up in the
15769 `renderToBuffer` method.
15771 @method childViewsWillChange
15772 @param {Ember.Array} views the child views array before mutation
15773 @param {Number} start the start position of the mutation
15774 @param {Number} removed the number of child views removed
15776 childViewsWillChange: function(views, start, removed) {
15777 if (removed === 0) { return; }
15779 var changedViews = views.slice(start, start+removed);
15780 this.initializeViews(changedViews, null, null);
15782 this.invokeForState('childViewsWillChange', views, start, removed);
15788 When a child view is added, make sure the DOM gets updated appropriately.
15790 If the view has already rendered an element, we tell the child view to
15791 create an element and insert it into the DOM. If the enclosing container view
15792 has already written to a buffer, but not yet converted that buffer into an
15793 element, we insert the string representation of the child into the appropriate
15794 place in the buffer.
15796 @method childViewsDidChange
15797 @param {Ember.Array} views the array of child views afte the mutation has occurred
15798 @param {Number} start the start position of the mutation
15799 @param {Number} removed the number of child views removed
15800 @param {Number} the number of child views added
15802 childViewsDidChange: function(views, start, removed, added) {
15803 var len = get(views, 'length');
15805 // No new child views were added; bail out.
15806 if (added === 0) return;
15808 var changedViews = views.slice(start, start+added);
15809 this.initializeViews(changedViews, this, get(this, 'templateData'));
15811 // Let the current state handle the changes
15812 this.invokeForState('childViewsDidChange', views, start, added);
15815 initializeViews: function(views, parentView, templateData) {
15816 forEach(views, function(view) {
15817 set(view, '_parentView', parentView);
15819 if (!get(view, 'templateData')) {
15820 set(view, 'templateData', templateData);
15827 _currentViewWillChange: Ember.beforeObserver(function() {
15828 var childViews = get(this, 'childViews'),
15829 currentView = get(this, 'currentView');
15832 childViews.removeObject(currentView);
15833 currentView.destroy();
15837 _currentViewDidChange: Ember.observer(function() {
15838 var childViews = get(this, 'childViews'),
15839 currentView = get(this, 'currentView');
15842 childViews.pushObject(currentView);
15846 _ensureChildrenAreInDOM: function () {
15847 this.invokeForState('ensureChildrenAreInDOM', this);
15851 // Ember.ContainerView extends the default view states to provide different
15852 // behavior for childViewsWillChange and childViewsDidChange.
15853 Ember.ContainerView.states = {
15854 parent: Ember.View.states,
15857 childViewsDidChange: function(parentView, views, start, added) {
15858 var buffer = parentView.buffer,
15859 startWith, prev, prevBuffer, view;
15861 // Determine where to begin inserting the child view(s) in the
15864 // If views were inserted at the beginning, prepend the first
15865 // view to the render buffer, then begin inserting any
15866 // additional views at the beginning.
15867 view = views[start];
15868 startWith = start + 1;
15869 view.renderToBuffer(buffer, 'prepend');
15871 // Otherwise, just insert them at the same place as the child
15873 view = views[start - 1];
15877 for (var i=startWith; i<start+added; i++) {
15880 prevBuffer = prev.buffer;
15881 view.renderToBuffer(prevBuffer, 'insertAfter');
15887 childViewsWillChange: function(view, views, start, removed) {
15888 for (var i=start; i<start+removed; i++) {
15893 childViewsDidChange: function(view, views, start, added) {
15894 Ember.run.scheduleOnce('render', this, '_ensureChildrenAreInDOM');
15897 ensureChildrenAreInDOM: function(view) {
15898 var childViews = view.get('childViews'), i, len, childView, previous, buffer;
15899 for (i = 0, len = childViews.length; i < len; i++) {
15900 childView = childViews[i];
15901 buffer = childView.renderToBufferIfNeeded();
15903 childView._notifyWillInsertElement();
15905 previous.domManager.after(previous, buffer.string());
15907 view.domManager.prepend(view, buffer.string());
15909 childView.transitionTo('inDOM');
15910 childView.propertyDidChange('element');
15911 childView._notifyDidInsertElement();
15913 previous = childView;
15919 Ember.ContainerView.states.inDOM = {
15920 parentState: Ember.ContainerView.states.hasElement
15923 Ember.ContainerView.reopen({
15924 states: Ember.ContainerView.states
15934 @submodule ember-views
15937 var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
15940 `Ember.CollectionView` is an `Ember.View` descendent responsible for managing a
15941 collection (an array or array-like object) by maintaing a child view object and
15942 associated DOM representation for each item in the array and ensuring that child
15943 views and their associated rendered HTML are updated when items in the array
15944 are added, removed, or replaced.
15947 The managed collection of objects is referenced as the `Ember.CollectionView` instance's
15948 `content` property.
15951 someItemsView = Ember.CollectionView.create({
15952 content: ['A', 'B','C']
15956 The view for each item in the collection will have its `content` property set
15959 ## Specifying itemViewClass
15960 By default the view class for each item in the managed collection will be an instance
15961 of `Ember.View`. You can supply a different class by setting the `CollectionView`'s
15962 `itemViewClass` property.
15964 Given an empty `<body>` and the following code:
15967 someItemsView = Ember.CollectionView.create({
15968 classNames: ['a-collection'],
15969 content: ['A','B','C'],
15970 itemViewClass: Ember.View.extend({
15971 template: Ember.Handlebars.compile("the letter: {{view.content}}")
15975 someItemsView.appendTo('body');
15978 Will result in the following HTML structure
15981 <div class="ember-view a-collection">
15982 <div class="ember-view">the letter: A</div>
15983 <div class="ember-view">the letter: B</div>
15984 <div class="ember-view">the letter: C</div>
15988 ## Automatic matching of parent/child tagNames
15990 Setting the `tagName` property of a `CollectionView` to any of
15991 "ul", "ol", "table", "thead", "tbody", "tfoot", "tr", or "select" will result
15992 in the item views receiving an appropriately matched `tagName` property.
15995 Given an empty `<body>` and the following code:
15998 anUndorderedListView = Ember.CollectionView.create({
16000 content: ['A','B','C'],
16001 itemViewClass: Ember.View.extend({
16002 template: Ember.Handlebars.compile("the letter: {{view.content}}")
16006 anUndorderedListView.appendTo('body');
16009 Will result in the following HTML structure
16012 <ul class="ember-view a-collection">
16013 <li class="ember-view">the letter: A</li>
16014 <li class="ember-view">the letter: B</li>
16015 <li class="ember-view">the letter: C</li>
16019 Additional tagName pairs can be provided by adding to `Ember.CollectionView.CONTAINER_MAP `
16022 Ember.CollectionView.CONTAINER_MAP['article'] = 'section'
16027 You can provide an `Ember.View` subclass to the `Ember.CollectionView` instance as its
16028 `emptyView` property. If the `content` property of a `CollectionView` is set to `null`
16029 or an empty array, an instance of this view will be the `CollectionView`s only child.
16032 aListWithNothing = Ember.CollectionView.create({
16033 classNames: ['nothing']
16035 emptyView: Ember.View.extend({
16036 template: Ember.Handlebars.compile("The collection is empty")
16040 aListWithNothing.appendTo('body');
16043 Will result in the following HTML structure
16046 <div class="ember-view nothing">
16047 <div class="ember-view">
16048 The collection is empty
16053 ## Adding and Removing items
16054 The `childViews` property of a `CollectionView` should not be directly manipulated. Instead,
16055 add, remove, replace items from its `content` property. This will trigger
16056 appropriate changes to its rendered HTML.
16058 ## Use in templates via the `{{collection}}` Ember.Handlebars helper
16059 Ember.Handlebars provides a helper specifically for adding `CollectionView`s to templates.
16060 See `Ember.Handlebars.collection` for more details
16062 @class CollectionView
16064 @extends Ember.ContainerView
16067 Ember.CollectionView = Ember.ContainerView.extend(
16068 /** @scope Ember.CollectionView.prototype */ {
16071 A list of items to be displayed by the Ember.CollectionView.
16082 This provides metadata about what kind of empty view class this
16083 collection would like if it is being instantiated from another
16084 system (like Handlebars)
16086 @property emptyViewClass
16088 emptyViewClass: Ember.View,
16091 An optional view to display if content is set to an empty array.
16093 @property emptyView
16100 @property itemViewClass
16102 @default Ember.View
16104 itemViewClass: Ember.View,
16107 var ret = this._super();
16108 this._contentDidChange();
16112 _contentWillChange: Ember.beforeObserver(function() {
16113 var content = this.get('content');
16115 if (content) { content.removeArrayObserver(this); }
16116 var len = content ? get(content, 'length') : 0;
16117 this.arrayWillChange(content, 0, len);
16123 Check to make sure that the content has changed, and if so,
16124 update the children directly. This is always scheduled
16125 asynchronously, to allow the element to be created before
16126 bindings have synchronized and vice versa.
16128 @method _contentDidChange
16130 _contentDidChange: Ember.observer(function() {
16131 var content = get(this, 'content');
16134 Ember.assert(fmt("an Ember.CollectionView's content must implement Ember.Array. You passed %@", [content]), Ember.Array.detect(content));
16135 content.addArrayObserver(this);
16138 var len = content ? get(content, 'length') : 0;
16139 this.arrayDidChange(content, 0, null, len);
16142 willDestroy: function() {
16143 var content = get(this, 'content');
16144 if (content) { content.removeArrayObserver(this); }
16149 arrayWillChange: function(content, start, removedCount) {
16150 // If the contents were empty before and this template collection has an
16151 // empty view remove it now.
16152 var emptyView = get(this, 'emptyView');
16153 if (emptyView && emptyView instanceof Ember.View) {
16154 emptyView.removeFromParent();
16157 // Loop through child views that correspond with the removed items.
16158 // Note that we loop from the end of the array to the beginning because
16159 // we are mutating it as we go.
16160 var childViews = get(this, 'childViews'), childView, idx, len;
16162 len = get(childViews, 'length');
16164 var removingAll = removedCount === len;
16167 this.invokeForState('empty');
16170 for (idx = start + removedCount - 1; idx >= start; idx--) {
16171 childView = childViews[idx];
16172 if (removingAll) { childView.removedFromDOM = true; }
16173 childView.destroy();
16178 Called when a mutation to the underlying content array occurs.
16180 This method will replay that mutation against the views that compose the
16181 Ember.CollectionView, ensuring that the view reflects the model.
16183 This array observer is added in contentDidChange.
16185 @method arrayDidChange
16186 @param {Array} addedObjects the objects that were added to the content
16187 @param {Array} removedObjects the objects that were removed from the content
16188 @param {Number} changeIndex the index at which the changes occurred
16190 arrayDidChange: function(content, start, removed, added) {
16191 var itemViewClass = get(this, 'itemViewClass'),
16192 childViews = get(this, 'childViews'),
16193 addedViews = [], view, item, idx, len, itemTagName;
16195 if ('string' === typeof itemViewClass) {
16196 itemViewClass = get(itemViewClass);
16199 Ember.assert(fmt("itemViewClass must be a subclass of Ember.View, not %@", [itemViewClass]), Ember.View.detect(itemViewClass));
16201 len = content ? get(content, 'length') : 0;
16203 for (idx = start; idx < start+added; idx++) {
16204 item = content.objectAt(idx);
16206 view = this.createChildView(itemViewClass, {
16211 addedViews.push(view);
16214 var emptyView = get(this, 'emptyView');
16215 if (!emptyView) { return; }
16217 emptyView = this.createChildView(emptyView);
16218 addedViews.push(emptyView);
16219 set(this, 'emptyView', emptyView);
16221 childViews.replace(start, 0, addedViews);
16224 createChildView: function(view, attrs) {
16225 view = this._super(view, attrs);
16227 var itemTagName = get(view, 'tagName');
16228 var tagName = (itemTagName === null || itemTagName === undefined) ? Ember.CollectionView.CONTAINER_MAP[get(this, 'tagName')] : itemTagName;
16230 set(view, 'tagName', tagName);
16237 A map of parent tags to their default child tags. You can add
16238 additional parent tags if you want collection views that use
16239 a particular parent tag to default to a child tag.
16241 @property CONTAINER_MAP
16246 Ember.CollectionView.CONTAINER_MAP = {
16273 @submodule ember-views
16274 @require ember-runtime
16281 var get = Ember.get, set = Ember.set;
16285 @submodule ember-states
16291 @extends Ember.Object
16292 @uses Ember.Evented
16294 Ember.State = Ember.Object.extend(Ember.Evented,
16295 /** @scope Ember.State.prototype */{
16299 A reference to the parent state.
16301 @property parentState
16308 The name of this state.
16316 The full path to this state.
16321 path: Ember.computed(function() {
16322 var parentPath = get(this, 'parentState.path'),
16323 path = get(this, 'name');
16326 path = parentPath + '.' + path;
16335 Override the default event firing from Ember.Evented to
16336 also call methods with the given name.
16341 trigger: function(name) {
16343 this[name].apply(this, [].slice.call(arguments, 1));
16345 this._super.apply(this, arguments);
16349 var states = get(this, 'states'), foundStates;
16350 set(this, 'childStates', Ember.A());
16351 set(this, 'eventTransitions', get(this, 'eventTransitions') || {});
16353 var name, value, transitionTarget;
16355 // As a convenience, loop over the properties
16356 // of this state and look for any that are other
16357 // Ember.State instances or classes, and move them
16358 // to the `states` hash. This avoids having to
16359 // create an explicit separate hash.
16364 for (name in this) {
16365 if (name === "constructor") { continue; }
16367 if (value = this[name]) {
16368 if (transitionTarget = value.transitionTarget) {
16369 this.eventTransitions[name] = transitionTarget;
16372 this.setupChild(states, name, value);
16376 set(this, 'states', states);
16378 for (name in states) {
16379 this.setupChild(states, name, states[name]);
16383 set(this, 'pathsCache', {});
16384 set(this, 'pathsCacheNoContext', {});
16387 setupChild: function(states, name, value) {
16388 if (!value) { return false; }
16390 if (value.isState) {
16391 set(value, 'name', name);
16392 } else if (Ember.State.detect(value)) {
16393 value = value.create({
16398 if (value.isState) {
16399 set(value, 'parentState', this);
16400 get(this, 'childStates').pushObject(value);
16401 states[name] = value;
16406 lookupEventTransition: function(name) {
16407 var path, state = this;
16409 while(state && !path) {
16410 path = state.eventTransitions[name];
16411 state = state.get('parentState');
16418 A Boolean value indicating whether the state is a leaf state
16419 in the state hierarchy. This is false if the state has child
16420 states; otherwise it is true.
16425 isLeaf: Ember.computed(function() {
16426 return !get(this, 'childStates').length;
16430 A boolean value indicating whether the state takes a context.
16431 By default we assume all states take contexts.
16433 @property hasContext
16439 This is the default transition event.
16442 @param {Ember.StateManager} manager
16444 @see Ember.StateManager#transitionEvent
16449 This event fires when the state is entered.
16452 @param {Ember.StateManager} manager
16457 This event fires when the state is exited.
16460 @param {Ember.StateManager} manager
16465 Ember.State.reopenClass({
16468 Creates an action function for transitioning to the named state while preserving context.
16470 The following example StateManagers are equivalent:
16472 aManager = Ember.StateManager.create({
16473 stateOne: Ember.State.create({
16474 changeToStateTwo: Ember.State.transitionTo('stateTwo')
16476 stateTwo: Ember.State.create({})
16479 bManager = Ember.StateManager.create({
16480 stateOne: Ember.State.create({
16481 changeToStateTwo: function(manager, context){
16482 manager.transitionTo('stateTwo', context)
16485 stateTwo: Ember.State.create({})
16488 @method transitionTo
16490 @param {String} target
16493 transitionTo: function(target) {
16495 var transitionFunction = function(stateManager, contextOrEvent) {
16496 var contexts = [], transitionArgs,
16497 Event = Ember.$ && Ember.$.Event;
16499 if (contextOrEvent && (Event && contextOrEvent instanceof Event)) {
16500 if (contextOrEvent.hasOwnProperty('contexts')) {
16501 contexts = contextOrEvent.contexts.slice();
16505 contexts = [].slice.call(arguments, 1);
16508 contexts.unshift(target);
16509 stateManager.transitionTo.apply(stateManager, contexts);
16512 transitionFunction.transitionTarget = target;
16514 return transitionFunction;
16526 @submodule ember-states
16529 var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
16530 var arrayForEach = Ember.ArrayPolyfills.forEach;
16532 A Transition takes the enter, exit and resolve states and normalizes
16535 * takes any passed in contexts into consideration
16536 * adds in `initialState`s
16541 var Transition = function(raw) {
16542 this.enterStates = raw.enterStates.slice();
16543 this.exitStates = raw.exitStates.slice();
16544 this.resolveState = raw.resolveState;
16546 this.finalState = raw.enterStates[raw.enterStates.length - 1] || raw.resolveState;
16549 Transition.prototype = {
16551 Normalize the passed in enter, exit and resolve states.
16553 This process also adds `finalState` and `contexts` to the Transition object.
16556 @param {Ember.StateManager} manager the state manager running the transition
16557 @param {Array} contexts a list of contexts passed into `transitionTo`
16559 normalize: function(manager, contexts) {
16560 this.matchContextsToStates(contexts);
16561 this.addInitialStates();
16562 this.removeUnchangedContexts(manager);
16567 Match each of the contexts passed to `transitionTo` to a state.
16568 This process may also require adding additional enter and exit
16569 states if there are more contexts than enter states.
16571 @method matchContextsToStates
16572 @param {Array} contexts a list of contexts passed into `transitionTo`
16574 matchContextsToStates: function(contexts) {
16575 var stateIdx = this.enterStates.length - 1,
16576 matchedContexts = [],
16580 // Next, we will match the passed in contexts to the states they
16583 // First, assign a context to each enter state in reverse order. If
16584 // any contexts are left, add a parent state to the list of states
16585 // to enter and exit, and assign a context to the parent state.
16587 // If there are still contexts left when the state manager is
16588 // reached, raise an exception.
16590 // This allows the following:
16595 // | |- about (* current state)
16597 // For `transitionTo('post.comments', post, post.get('comments')`,
16598 // the first context (`post`) will be assigned to `root.post`, and
16599 // the second context (`post.get('comments')`) will be assigned
16600 // to `root.post.comments`.
16602 // For the following:
16606 // | | |- index (* current state)
16609 // For `transitionTo('post.comments', otherPost, otherPost.get('comments')`,
16610 // the `<root.post>` state will be added to the list of enter and exit
16611 // states because its context has changed.
16613 while (contexts.length > 0) {
16614 if (stateIdx >= 0) {
16615 state = this.enterStates[stateIdx--];
16617 if (this.enterStates.length) {
16618 state = get(this.enterStates[0], 'parentState');
16619 if (!state) { throw "Cannot match all contexts to states"; }
16621 // If re-entering the current state with a context, the resolve
16622 // state will be the current state.
16623 state = this.resolveState;
16626 this.enterStates.unshift(state);
16627 this.exitStates.unshift(state);
16630 // in routers, only states with dynamic segments have a context
16631 if (get(state, 'hasContext')) {
16632 context = contexts.pop();
16637 matchedContexts.unshift(context);
16640 this.contexts = matchedContexts;
16644 Add any `initialState`s to the list of enter states.
16646 @method addInitialStates
16648 addInitialStates: function() {
16649 var finalState = this.finalState, initialState;
16652 initialState = get(finalState, 'initialState') || 'start';
16653 finalState = get(finalState, 'states.' + initialState);
16655 if (!finalState) { break; }
16657 this.finalState = finalState;
16658 this.enterStates.push(finalState);
16659 this.contexts.push(undefined);
16664 Remove any states that were added because the number of contexts
16665 exceeded the number of explicit enter states, but the context has
16666 not changed since the last time the state was entered.
16668 @method removeUnchangedContexts
16669 @param {Ember.StateManager} manager passed in to look up the last
16670 context for a states
16672 removeUnchangedContexts: function(manager) {
16673 // Start from the beginning of the enter states. If the state was added
16674 // to the list during the context matching phase, make sure the context
16675 // has actually changed since the last time the state was entered.
16676 while (this.enterStates.length > 0) {
16677 if (this.enterStates[0] !== this.exitStates[0]) { break; }
16679 if (this.enterStates.length === this.contexts.length) {
16680 if (manager.getStateMeta(this.enterStates[0], 'context') !== this.contexts[0]) { break; }
16681 this.contexts.shift();
16684 this.resolveState = this.enterStates.shift();
16685 this.exitStates.shift();
16691 StateManager is part of Ember's implementation of a finite state machine. A StateManager
16692 instance manages a number of properties that are instances of `Ember.State`,
16693 tracks the current active state, and triggers callbacks when states have changed.
16697 The states of StateManager can be declared in one of two ways. First, you can define
16698 a `states` property that contains all the states:
16700 managerA = Ember.StateManager.create({
16702 stateOne: Ember.State.create(),
16703 stateTwo: Ember.State.create()
16707 managerA.get('states')
16709 // stateOne: Ember.State.create(),
16710 // stateTwo: Ember.State.create()
16713 You can also add instances of `Ember.State` (or an `Ember.State` subclass) directly as properties
16714 of a StateManager. These states will be collected into the `states` property for you.
16716 managerA = Ember.StateManager.create({
16717 stateOne: Ember.State.create(),
16718 stateTwo: Ember.State.create()
16721 managerA.get('states')
16723 // stateOne: Ember.State.create(),
16724 // stateTwo: Ember.State.create()
16727 ## The Initial State
16728 When created a StateManager instance will immediately enter into the state
16729 defined as its `start` property or the state referenced by name in its
16730 `initialState` property:
16732 managerA = Ember.StateManager.create({
16733 start: Ember.State.create({})
16736 managerA.get('currentState.name') // 'start'
16738 managerB = Ember.StateManager.create({
16739 initialState: 'beginHere',
16740 beginHere: Ember.State.create({})
16743 managerB.get('currentState.name') // 'beginHere'
16745 Because it is a property you may also provide a computed function if you wish to derive
16746 an `initialState` programmatically:
16748 managerC = Ember.StateManager.create({
16749 initialState: function(){
16756 active: Ember.State.create({}),
16757 passive: Ember.State.create({})
16760 ## Moving Between States
16761 A StateManager can have any number of Ember.State objects as properties
16762 and can have a single one of these states as its current state.
16764 Calling `transitionTo` transitions between states:
16766 robotManager = Ember.StateManager.create({
16767 initialState: 'poweredDown',
16768 poweredDown: Ember.State.create({}),
16769 poweredUp: Ember.State.create({})
16772 robotManager.get('currentState.name') // 'poweredDown'
16773 robotManager.transitionTo('poweredUp')
16774 robotManager.get('currentState.name') // 'poweredUp'
16776 Before transitioning into a new state the existing `currentState` will have its
16777 `exit` method called with the StateManager instance as its first argument and
16778 an object representing the transition as its second argument.
16780 After transitioning into a new state the new `currentState` will have its
16781 `enter` method called with the StateManager instance as its first argument and
16782 an object representing the transition as its second argument.
16784 robotManager = Ember.StateManager.create({
16785 initialState: 'poweredDown',
16786 poweredDown: Ember.State.create({
16787 exit: function(stateManager){
16788 console.log("exiting the poweredDown state")
16791 poweredUp: Ember.State.create({
16792 enter: function(stateManager){
16793 console.log("entering the poweredUp state. Destroy all humans.")
16798 robotManager.get('currentState.name') // 'poweredDown'
16799 robotManager.transitionTo('poweredUp')
16801 // 'exiting the poweredDown state'
16802 // 'entering the poweredUp state. Destroy all humans.'
16805 Once a StateManager is already in a state, subsequent attempts to enter that state will
16806 not trigger enter or exit method calls. Attempts to transition into a state that the
16807 manager does not have will result in no changes in the StateManager's current state:
16809 robotManager = Ember.StateManager.create({
16810 initialState: 'poweredDown',
16811 poweredDown: Ember.State.create({
16812 exit: function(stateManager){
16813 console.log("exiting the poweredDown state")
16816 poweredUp: Ember.State.create({
16817 enter: function(stateManager){
16818 console.log("entering the poweredUp state. Destroy all humans.")
16823 robotManager.get('currentState.name') // 'poweredDown'
16824 robotManager.transitionTo('poweredUp')
16826 // 'exiting the poweredDown state'
16827 // 'entering the poweredUp state. Destroy all humans.'
16828 robotManager.transitionTo('poweredUp') // no logging, no state change
16830 robotManager.transitionTo('someUnknownState') // silently fails
16831 robotManager.get('currentState.name') // 'poweredUp'
16834 Each state property may itself contain properties that are instances of Ember.State.
16835 The StateManager can transition to specific sub-states in a series of transitionTo method calls or
16836 via a single transitionTo with the full path to the specific state. The StateManager will also
16837 keep track of the full path to its currentState
16839 robotManager = Ember.StateManager.create({
16840 initialState: 'poweredDown',
16841 poweredDown: Ember.State.create({
16842 charging: Ember.State.create(),
16843 charged: Ember.State.create()
16845 poweredUp: Ember.State.create({
16846 mobile: Ember.State.create(),
16847 stationary: Ember.State.create()
16851 robotManager.get('currentState.name') // 'poweredDown'
16853 robotManager.transitionTo('poweredUp')
16854 robotManager.get('currentState.name') // 'poweredUp'
16856 robotManager.transitionTo('mobile')
16857 robotManager.get('currentState.name') // 'mobile'
16859 // transition via a state path
16860 robotManager.transitionTo('poweredDown.charging')
16861 robotManager.get('currentState.name') // 'charging'
16863 robotManager.get('currentState.path') // 'poweredDown.charging'
16865 Enter transition methods will be called for each state and nested child state in their
16866 hierarchical order. Exit methods will be called for each state and its nested states in
16867 reverse hierarchical order.
16869 Exit transitions for a parent state are not called when entering into one of its child states,
16870 only when transitioning to a new section of possible states in the hierarchy.
16872 robotManager = Ember.StateManager.create({
16873 initialState: 'poweredDown',
16874 poweredDown: Ember.State.create({
16875 enter: function(){},
16877 console.log("exited poweredDown state")
16879 charging: Ember.State.create({
16880 enter: function(){},
16883 charged: Ember.State.create({
16885 console.log("entered charged state")
16888 console.log("exited charged state")
16892 poweredUp: Ember.State.create({
16894 console.log("entered poweredUp state")
16896 exit: function(){},
16897 mobile: Ember.State.create({
16899 console.log("entered mobile state")
16903 stationary: Ember.State.create({
16904 enter: function(){},
16911 robotManager.get('currentState.path') // 'poweredDown'
16912 robotManager.transitionTo('charged')
16913 // logs 'entered charged state'
16914 // but does *not* log 'exited poweredDown state'
16915 robotManager.get('currentState.name') // 'charged
16917 robotManager.transitionTo('poweredUp.mobile')
16919 // 'exited charged state'
16920 // 'exited poweredDown state'
16921 // 'entered poweredUp state'
16922 // 'entered mobile state'
16924 During development you can set a StateManager's `enableLogging` property to `true` to
16925 receive console messages of state transitions.
16927 robotManager = Ember.StateManager.create({
16928 enableLogging: true
16931 ## Managing currentState with Actions
16932 To control which transitions between states are possible for a given state, StateManager
16933 can receive and route action messages to its states via the `send` method. Calling to `send` with
16934 an action name will begin searching for a method with the same name starting at the current state
16935 and moving up through the parent states in a state hierarchy until an appropriate method is found
16936 or the StateManager instance itself is reached.
16938 If an appropriately named method is found it will be called with the state manager as the first
16939 argument and an optional `context` object as the second argument.
16941 managerA = Ember.StateManager.create({
16942 initialState: 'stateOne.substateOne.subsubstateOne',
16943 stateOne: Ember.State.create({
16944 substateOne: Ember.State.create({
16945 anAction: function(manager, context){
16946 console.log("an action was called")
16948 subsubstateOne: Ember.State.create({})
16953 managerA.get('currentState.name') // 'subsubstateOne'
16954 managerA.send('anAction')
16955 // 'stateOne.substateOne.subsubstateOne' has no anAction method
16956 // so the 'anAction' method of 'stateOne.substateOne' is called
16957 // and logs "an action was called"
16958 // with managerA as the first argument
16959 // and no second argument
16962 managerA.send('anAction', someObject)
16963 // the 'anAction' method of 'stateOne.substateOne' is called again
16964 // with managerA as the first argument and
16965 // someObject as the second argument.
16968 If the StateManager attempts to send an action but does not find an appropriately named
16969 method in the current state or while moving upwards through the state hierarchy
16970 it will throw a new Ember.Error. Action detection only moves upwards through the state hierarchy
16971 from the current state. It does not search in other portions of the hierarchy.
16973 managerB = Ember.StateManager.create({
16974 initialState: 'stateOne.substateOne.subsubstateOne',
16975 stateOne: Ember.State.create({
16976 substateOne: Ember.State.create({
16977 subsubstateOne: Ember.State.create({})
16980 stateTwo: Ember.State.create({
16981 anAction: function(manager, context){
16982 // will not be called below because it is
16983 // not a parent of the current state
16988 managerB.get('currentState.name') // 'subsubstateOne'
16989 managerB.send('anAction')
16990 // Error: <Ember.StateManager:ember132> could not
16991 // respond to event anAction in state stateOne.substateOne.subsubstateOne.
16993 Inside of an action method the given state should delegate `transitionTo` calls on its
16996 robotManager = Ember.StateManager.create({
16997 initialState: 'poweredDown.charging',
16998 poweredDown: Ember.State.create({
16999 charging: Ember.State.create({
17000 chargeComplete: function(manager, context){
17001 manager.transitionTo('charged')
17004 charged: Ember.State.create({
17005 boot: function(manager, context){
17006 manager.transitionTo('poweredUp')
17010 poweredUp: Ember.State.create({
17011 beginExtermination: function(manager, context){
17012 manager.transitionTo('rampaging')
17014 rampaging: Ember.State.create()
17018 robotManager.get('currentState.name') // 'charging'
17019 robotManager.send('boot') // throws error, no boot action
17020 // in current hierarchy
17021 robotManager.get('currentState.name') // remains 'charging'
17023 robotManager.send('beginExtermination') // throws error, no beginExtermination
17024 // action in current hierarchy
17025 robotManager.get('currentState.name') // remains 'charging'
17027 robotManager.send('chargeComplete')
17028 robotManager.get('currentState.name') // 'charged'
17030 robotManager.send('boot')
17031 robotManager.get('currentState.name') // 'poweredUp'
17033 robotManager.send('beginExtermination', allHumans)
17034 robotManager.get('currentState.name') // 'rampaging'
17036 Transition actions can also be created using the `transitionTo` method of the Ember.State class. The
17037 following example StateManagers are equivalent:
17039 aManager = Ember.StateManager.create({
17040 stateOne: Ember.State.create({
17041 changeToStateTwo: Ember.State.transitionTo('stateTwo')
17043 stateTwo: Ember.State.create({})
17046 bManager = Ember.StateManager.create({
17047 stateOne: Ember.State.create({
17048 changeToStateTwo: function(manager, context){
17049 manager.transitionTo('stateTwo', context)
17052 stateTwo: Ember.State.create({})
17055 @class StateManager
17057 @extends Ember.State
17059 Ember.StateManager = Ember.State.extend({
17063 When creating a new statemanager, look for a default state to transition
17064 into. This state can either be named `start`, or can be specified using the
17065 `initialState` property.
17072 set(this, 'stateMeta', Ember.Map.create());
17074 var initialState = get(this, 'initialState');
17076 if (!initialState && get(this, 'states.start')) {
17077 initialState = 'start';
17080 if (initialState) {
17081 this.transitionTo(initialState);
17082 Ember.assert('Failed to transition to initial state "' + initialState + '"', !!get(this, 'currentState'));
17086 stateMetaFor: function(state) {
17087 var meta = get(this, 'stateMeta'),
17088 stateMeta = meta.get(state);
17092 meta.set(state, stateMeta);
17098 setStateMeta: function(state, key, value) {
17099 return set(this.stateMetaFor(state), key, value);
17102 getStateMeta: function(state, key) {
17103 return get(this.stateMetaFor(state), key);
17107 The current state from among the manager's possible states. This property should
17108 not be set directly. Use `transitionTo` to move between states by name.
17110 @property currentState
17113 currentState: null,
17116 The path of the current state. Returns a string representation of the current
17119 @property currentPath
17122 currentPath: Ember.computed('currentState', function() {
17123 return get(this, 'currentState.path');
17127 The name of transitionEvent that this stateManager will dispatch
17129 @property transitionEvent
17133 transitionEvent: 'setup',
17136 If set to true, `errorOnUnhandledEvents` will cause an exception to be
17137 raised if you attempt to send an event to a state manager that is not
17138 handled by the current state or any of its parent states.
17140 @property errorOnUnhandledEvents
17144 errorOnUnhandledEvent: true,
17146 send: function(event) {
17147 var contexts, sendRecursiveArguments;
17149 Ember.assert('Cannot send event "' + event + '" while currentState is ' + get(this, 'currentState'), get(this, 'currentState'));
17151 contexts = [].slice.call(arguments, 1);
17152 sendRecursiveArguments = contexts;
17153 sendRecursiveArguments.unshift(event, get(this, 'currentState'));
17155 return this.sendRecursively.apply(this, sendRecursiveArguments);
17158 sendRecursively: function(event, currentState) {
17159 var log = this.enableLogging,
17160 action = currentState[event],
17161 contexts, sendRecursiveArguments, actionArguments;
17163 contexts = [].slice.call(arguments, 2);
17165 // Test to see if the action is a method that
17166 // can be invoked. Don't blindly check just for
17167 // existence, because it is possible the state
17168 // manager has a child state of the given name,
17169 // and we should still raise an exception in that
17171 if (typeof action === 'function') {
17172 if (log) { Ember.Logger.log(fmt("STATEMANAGER: Sending event '%@' to state %@.", [event, get(currentState, 'path')])); }
17174 actionArguments = contexts;
17175 actionArguments.unshift(this);
17177 return action.apply(currentState, actionArguments);
17179 var parentState = get(currentState, 'parentState');
17182 sendRecursiveArguments = contexts;
17183 sendRecursiveArguments.unshift(event, parentState);
17185 return this.sendRecursively.apply(this, sendRecursiveArguments);
17186 } else if (get(this, 'errorOnUnhandledEvent')) {
17187 throw new Ember.Error(this.toString() + " could not respond to event " + event + " in state " + get(this, 'currentState.path') + ".");
17193 Finds a state by its state path.
17197 manager = Ember.StateManager.create({
17198 root: Ember.State.create({
17199 dashboard: Ember.State.create()
17203 manager.getStateByPath(manager, "root.dashboard")
17205 // returns the dashboard state
17207 @method getStateByPath
17208 @param {Ember.State} root the state to start searching from
17209 @param {String} path the state path to follow
17210 @return {Ember.State} the state at the end of the path
17212 getStateByPath: function(root, path) {
17213 var parts = path.split('.'),
17216 for (var i=0, len=parts.length; i<len; i++) {
17217 state = get(get(state, 'states'), parts[i]);
17218 if (!state) { break; }
17224 findStateByPath: function(state, path) {
17227 while (!possible && state) {
17228 possible = this.getStateByPath(state, path);
17229 state = get(state, 'parentState');
17236 A state stores its child states in its `states` hash.
17237 This code takes a path like `posts.show` and looks
17238 up `root.states.posts.states.show`.
17240 It returns a list of all of the states from the
17241 root, which is the list of states to call `enter`
17244 @method getStatesInPath
17248 getStatesInPath: function(root, path) {
17249 if (!path || path === "") { return undefined; }
17250 var parts = path.split('.'),
17255 for (var i=0, len=parts.length; i<len; i++) {
17256 states = get(root, 'states');
17257 if (!states) { return undefined; }
17258 state = get(states, parts[i]);
17259 if (state) { root = state; result.push(state); }
17260 else { return undefined; }
17266 goToState: function() {
17267 // not deprecating this yet so people don't constantly need to
17268 // make trivial changes for little reason.
17269 return this.transitionTo.apply(this, arguments);
17272 transitionTo: function(path, context) {
17273 // XXX When is transitionTo called with no path
17274 if (Ember.empty(path)) { return; }
17276 // The ES6 signature of this function is `path, ...contexts`
17277 var contexts = context ? Array.prototype.slice.call(arguments, 1) : [],
17278 currentState = get(this, 'currentState') || this;
17280 // First, get the enter, exit and resolve states for the current state
17281 // and specified path. If possible, use an existing cache.
17282 var hash = this.contextFreeTransition(currentState, path);
17284 // Next, process the raw state information for the contexts passed in.
17285 var transition = new Transition(hash).normalize(this, contexts);
17287 this.enterState(transition);
17288 this.triggerSetupContext(transition);
17291 contextFreeTransition: function(currentState, path) {
17292 var cache = currentState.pathsCache[path];
17293 if (cache) { return cache; }
17295 var enterStates = this.getStatesInPath(currentState, path),
17297 resolveState = currentState;
17299 // Walk up the states. For each state, check whether a state matching
17300 // the `path` is nested underneath. This will find the closest
17301 // parent state containing `path`.
17303 // This allows the user to pass in a relative path. For example, for
17304 // the following state hierarchy:
17308 // | | |- show (* current)
17312 // If the current state is `<root.posts.show>`, an attempt to
17313 // transition to `comments.show` will match `<root.comments.show>`.
17315 // First, this code will look for root.posts.show.comments.show.
17316 // Next, it will look for root.posts.comments.show. Finally,
17317 // it will look for `root.comments.show`, and find the state.
17319 // After this process, the following variables will exist:
17321 // * resolveState: a common parent state between the current
17322 // and target state. In the above example, `<root>` is the
17324 // * enterStates: a list of all of the states represented
17325 // by the path from the `resolveState`. For example, for
17326 // the path `root.comments.show`, `enterStates` would have
17327 // `[<root.comments>, <root.comments.show>]`
17328 // * exitStates: a list of all of the states from the
17329 // `resolveState` to the `currentState`. In the above
17330 // example, `exitStates` would have
17331 // `[<root.posts>`, `<root.posts.show>]`.
17332 while (resolveState && !enterStates) {
17333 exitStates.unshift(resolveState);
17335 resolveState = get(resolveState, 'parentState');
17336 if (!resolveState) {
17337 enterStates = this.getStatesInPath(this, path);
17338 if (!enterStates) {
17339 Ember.assert('Could not find state for path: "'+path+'"');
17343 enterStates = this.getStatesInPath(resolveState, path);
17346 // If the path contains some states that are parents of both the
17347 // current state and the target state, remove them.
17349 // For example, in the following hierarchy:
17353 // | | |- index (* current)
17356 // If the `path` is `root.post.show`, the three variables will
17359 // * resolveState: `<state manager>`
17360 // * enterStates: `[<root>, <root.post>, <root.post.show>]`
17361 // * exitStates: `[<root>, <root.post>, <root.post.index>]`
17363 // The goal of this code is to remove the common states, so we
17366 // * resolveState: `<root.post>`
17367 // * enterStates: `[<root.post.show>]`
17368 // * exitStates: `[<root.post.index>]`
17370 // This avoid unnecessary calls to the enter and exit transitions.
17371 while (enterStates.length > 0 && enterStates[0] === exitStates[0]) {
17372 resolveState = enterStates.shift();
17373 exitStates.shift();
17376 // Cache the enterStates, exitStates, and resolveState for the
17377 // current state and the `path`.
17378 var transitions = currentState.pathsCache[path] = {
17379 exitStates: exitStates,
17380 enterStates: enterStates,
17381 resolveState: resolveState
17384 return transitions;
17387 triggerSetupContext: function(transitions) {
17388 var contexts = transitions.contexts,
17389 offset = transitions.enterStates.length - contexts.length,
17390 enterStates = transitions.enterStates,
17391 transitionEvent = get(this, 'transitionEvent');
17393 Ember.assert("More contexts provided than states", offset >= 0);
17395 arrayForEach.call(enterStates, function(state, idx) {
17396 state.trigger(transitionEvent, this, contexts[idx-offset]);
17400 getState: function(name) {
17401 var state = get(this, name),
17402 parentState = get(this, 'parentState');
17406 } else if (parentState) {
17407 return parentState.getState(name);
17411 enterState: function(transition) {
17412 var log = this.enableLogging;
17414 var exitStates = transition.exitStates.slice(0).reverse();
17415 arrayForEach.call(exitStates, function(state) {
17416 state.trigger('exit', this);
17419 arrayForEach.call(transition.enterStates, function(state) {
17420 if (log) { Ember.Logger.log("STATEMANAGER: Entering " + get(state, 'path')); }
17421 state.trigger('enter', this);
17424 set(this, 'currentState', transition.finalState);
17437 @submodule ember-states
17438 @requires ember-runtime
17444 var get = Ember.get;
17446 Ember._ResolvedState = Ember.Object.extend({
17451 object: Ember.computed(function(key, value) {
17452 if (arguments.length === 2) {
17453 this._object = value;
17456 if (this._object) {
17457 return this._object;
17459 var state = get(this, 'state'),
17460 match = get(this, 'match'),
17461 manager = get(this, 'manager');
17462 return state.deserialize(manager, match.hash);
17467 hasPromise: Ember.computed(function() {
17468 return Ember.canInvoke(get(this, 'object'), 'then');
17469 }).property('object'),
17471 promise: Ember.computed(function() {
17472 var object = get(this, 'object');
17473 if (Ember.canInvoke(object, 'then')) {
17477 then: function(success) { success(object); }
17480 }).property('object'),
17482 transition: function() {
17483 var manager = get(this, 'manager'),
17484 path = get(this, 'state.path'),
17485 object = get(this, 'object');
17486 manager.transitionTo(path, object);
17497 @submodule ember-routing
17500 var get = Ember.get;
17502 // The Ember Routable mixin assumes the existance of a simple
17503 // routing shim that supports the following three behaviors:
17505 // * .getURL() - this is called when the page loads
17506 // * .setURL(newURL) - this is called from within the state
17507 // manager when the state changes to a routable state
17508 // * .onURLChange(callback) - this happens when the user presses
17509 // the back or forward button
17511 var paramForClass = function(classObject) {
17512 var className = classObject.toString(),
17513 parts = className.split("."),
17514 last = parts[parts.length - 1];
17516 return Ember.String.underscore(last) + "_id";
17519 var merge = function(original, hash) {
17520 for (var prop in hash) {
17521 if (!hash.hasOwnProperty(prop)) { continue; }
17522 if (original.hasOwnProperty(prop)) { continue; }
17524 original[prop] = hash[prop];
17531 @extends Ember.Mixin
17533 Ember.Routable = Ember.Mixin.create({
17536 this.on('setup', this, this.stashContext);
17538 if (redirection = get(this, 'redirectsTo')) {
17539 Ember.assert("You cannot use `redirectsTo` if you already have a `connectOutlets` method", this.connectOutlets === Ember.K);
17541 this.connectOutlets = function(router) {
17542 router.transitionTo(redirection);
17546 // normalize empty route to '/'
17547 var route = get(this, 'route');
17548 if (route === '') {
17554 Ember.assert("You cannot use `redirectsTo` on a state that has child states", !redirection || (!!redirection && !!get(this, 'isLeaf')));
17557 setup: function() {
17558 return this.connectOutlets.apply(this, arguments);
17564 Whenever a routable state is entered, the context it was entered with
17565 is stashed so that we can regenerate the state's `absoluteURL` on
17568 @method stashContext
17569 @param manager {Ember.StateManager}
17572 stashContext: function(manager, context) {
17573 this.router = manager;
17575 var serialized = this.serialize(manager, context);
17576 Ember.assert('serialize must return a hash', !serialized || typeof serialized === 'object');
17578 manager.setStateMeta(this, 'context', context);
17579 manager.setStateMeta(this, 'serialized', serialized);
17581 if (get(this, 'isRoutable') && !get(manager, 'isRouting')) {
17582 this.updateRoute(manager, get(manager, 'location'));
17589 Whenever a routable state is entered, the router's location object
17590 is notified to set the URL to the current absolute path.
17592 In general, this will update the browser's URL.
17594 @method updateRoute
17595 @param manager {Ember.StateManager}
17596 @param location {Ember.Location}
17598 updateRoute: function(manager, location) {
17599 if (get(this, 'isLeafRoute')) {
17600 var path = this.absoluteRoute(manager);
17601 location.setURL(path);
17608 Get the absolute route for the current state and a given
17611 This method is private, as it expects a serialized hash,
17612 not the original context object.
17614 @method absoluteRoute
17615 @param manager {Ember.StateManager}
17618 absoluteRoute: function(manager, hash) {
17619 var parentState = get(this, 'parentState');
17620 var path = '', generated;
17622 // If the parent state is routable, use its current path
17623 // as this route's prefix.
17624 if (get(parentState, 'isRoutable')) {
17625 path = parentState.absoluteRoute(manager, hash);
17628 var matcher = get(this, 'routeMatcher'),
17629 serialized = manager.getStateMeta(this, 'serialized');
17631 // merge the existing serialized object in with the passed
17634 merge(hash, serialized);
17636 generated = matcher && matcher.generate(hash);
17639 path = path + '/' + generated;
17648 At the moment, a state is routable if it has a string `route`
17649 property. This heuristic may change.
17651 @property isRoutable
17654 isRoutable: Ember.computed(function() {
17655 return typeof get(this, 'route') === 'string';
17661 Determine if this is the last routeable state
17663 @property isLeafRoute
17666 isLeafRoute: Ember.computed(function() {
17667 if (get(this, 'isLeaf')) { return true; }
17668 return !get(this, 'childStates').findProperty('isRoutable');
17674 A _RouteMatcher object generated from the current route's `route`
17677 @property routeMatcher
17678 @type Ember._RouteMatcher
17680 routeMatcher: Ember.computed(function() {
17681 var route = get(this, 'route');
17683 return Ember._RouteMatcher.create({ route: route });
17690 Check whether the route has dynamic segments and therefore takes
17693 @property hasContext
17696 hasContext: Ember.computed(function() {
17697 var routeMatcher = get(this, 'routeMatcher');
17698 if (routeMatcher) {
17699 return routeMatcher.identifiers.length > 0;
17706 The model class associated with the current state. This property
17707 uses the `modelType` property, in order to allow it to be
17708 specified as a String.
17710 @property modelClass
17713 modelClass: Ember.computed(function() {
17714 var modelType = get(this, 'modelType');
17716 if (typeof modelType === 'string') {
17717 return Ember.get(Ember.lookup, modelType);
17726 Get the model class for the state. The heuristic is:
17728 * The state must have a single dynamic segment
17729 * The dynamic segment must end in `_id`
17730 * A dynamic segment like `blog_post_id` is converted into `BlogPost`
17731 * The name is then looked up on the passed in namespace
17733 The process of initializing an application with a router will
17734 pass the application's namespace into the router, which will be
17737 @method modelClassFor
17738 @param namespace {Ember.Namespace}
17740 modelClassFor: function(namespace) {
17741 var modelClass, routeMatcher, identifiers, match, className;
17743 // if an explicit modelType was specified, use that
17744 if (modelClass = get(this, 'modelClass')) { return modelClass; }
17746 // if the router has no lookup namespace, we won't be able to guess
17748 if (!namespace) { return; }
17750 // make sure this state is actually a routable state
17751 routeMatcher = get(this, 'routeMatcher');
17752 if (!routeMatcher) { return; }
17754 // only guess modelType for states with a single dynamic segment
17755 // (no more, no fewer)
17756 identifiers = routeMatcher.identifiers;
17757 if (identifiers.length !== 2) { return; }
17759 // extract the `_id` from the end of the dynamic segment; if the
17760 // dynamic segment does not end in `_id`, we can't guess the
17762 match = identifiers[1].match(/^(.*)_id$/);
17763 if (!match) { return; }
17765 // convert the underscored type into a class form and look it up
17766 // on the router's namespace
17767 className = Ember.String.classify(match[1]);
17768 return get(namespace, className);
17772 The default method that takes a `params` object and converts
17775 By default, a params hash that looks like `{ post_id: 1 }`
17776 will be looked up as `namespace.Post.find(1)`. This is
17777 designed to work seamlessly with Ember Data, but will work
17778 fine with any class that has a `find` method.
17780 @method deserialize
17781 @param manager {Ember.StateManager}
17782 @param params {Hash}
17784 deserialize: function(manager, params) {
17785 var modelClass, routeMatcher, param;
17787 if (modelClass = this.modelClassFor(get(manager, 'namespace'))) {
17788 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);
17789 return modelClass.find(params[paramForClass(modelClass)]);
17796 The default method that takes an object and converts it into
17799 By default, if there is a single dynamic segment named
17800 `blog_post_id` and the object is a `BlogPost` with an
17801 `id` of `12`, the serialize method will produce:
17803 { blog_post_id: 12 }
17806 @param manager {Ember.StateManager}
17809 serialize: function(manager, context) {
17810 var modelClass, routeMatcher, namespace, param, id;
17812 if (Ember.empty(context)) { return ''; }
17814 if (modelClass = this.modelClassFor(get(manager, 'namespace'))) {
17815 param = paramForClass(modelClass);
17816 id = get(context, 'id');
17818 context[param] = id;
17826 @method resolvePath
17827 @param manager {Ember.StateManager}
17828 @param path {String}
17830 resolvePath: function(manager, path) {
17831 if (get(this, 'isLeafRoute')) { return Ember.A(); }
17833 var childStates = get(this, 'childStates'), match;
17835 childStates = Ember.A(childStates.filterProperty('isRoutable'));
17837 childStates = childStates.sort(function(a, b) {
17838 var aDynamicSegments = get(a, 'routeMatcher.identifiers.length'),
17839 bDynamicSegments = get(b, 'routeMatcher.identifiers.length'),
17840 aRoute = get(a, 'route'),
17841 bRoute = get(b, 'route');
17843 if (aRoute.indexOf(bRoute) === 0) {
17845 } else if (bRoute.indexOf(aRoute) === 0) {
17849 if (aDynamicSegments !== bDynamicSegments) {
17850 return aDynamicSegments - bDynamicSegments;
17853 return get(b, 'route.length') - get(a, 'route.length');
17856 var state = childStates.find(function(state) {
17857 var matcher = get(state, 'routeMatcher');
17858 if (match = matcher.match(path)) { return true; }
17861 Ember.assert("Could not find state for path " + path, !!state);
17863 var resolvedState = Ember._ResolvedState.create({
17869 var states = state.resolvePath(manager, match.remaining);
17871 return Ember.A([resolvedState]).pushObjects(states);
17877 Once `unroute` has finished unwinding, `routePath` will be called
17878 with the remainder of the route.
17880 For example, if you were in the /posts/1/comments state, and you
17881 moved into the /posts/2/comments state, `routePath` will be called
17882 on the state whose path is `/posts` with the path `/2/comments`.
17885 @param manager {Ember.StateManager}
17886 @param path {String}
17888 routePath: function(manager, path) {
17889 if (get(this, 'isLeafRoute')) { return; }
17891 var resolvedStates = this.resolvePath(manager, path),
17892 hasPromises = resolvedStates.some(function(s) { return get(s, 'hasPromise'); });
17894 function runTransition() {
17895 resolvedStates.forEach(function(rs) { rs.transition(); });
17899 manager.transitionTo('loading');
17901 Ember.assert('Loading state should be the child of a route', Ember.Routable.detect(get(manager, 'currentState.parentState')));
17902 Ember.assert('Loading state should not be a route', !Ember.Routable.detect(get(manager, 'currentState')));
17904 manager.handleStatePromises(resolvedStates, runTransition);
17913 When you move to a new route by pressing the back
17914 or forward button, this method is called first.
17916 Its job is to move the state manager into a parent
17917 state of the state it will eventually move into.
17919 @method unroutePath
17920 @param router {Ember.Router}
17921 @param path {String}
17923 unroutePath: function(router, path) {
17924 var parentState = get(this, 'parentState');
17926 // If we're at the root state, we're done
17927 if (parentState === router) {
17931 path = path.replace(/^(?=[^\/])/, "/");
17932 var absolutePath = this.absoluteRoute(router);
17934 var route = get(this, 'route');
17936 // If the current path is empty, move up one state,
17937 // because the index ('/') state must be a leaf node.
17938 if (route !== '/') {
17939 // If the current path is a prefix of the path we're trying
17940 // to go to, we're done.
17941 var index = path.indexOf(absolutePath),
17942 next = path.charAt(absolutePath.length);
17944 if (index === 0 && (next === "/" || next === "")) {
17949 // Transition to the parent and call unroute again.
17950 router.enterState({
17951 exitStates: [this],
17953 finalState: parentState
17956 router.send('unroutePath', path);
17959 parentTemplate: Ember.computed(function() {
17960 var state = this, parentState, template;
17962 while (state = get(state, 'parentState')) {
17963 if (template = get(state, 'template')) {
17968 return 'application';
17971 _template: Ember.computed(function(key, value) {
17972 if (arguments.length > 1) { return value; }
17974 if (value = get(this, 'template')) {
17978 // If no template was explicitly supplied convert
17979 // the class name into a template name. For example,
17980 // App.PostRoute will return `post`.
17981 var className = this.constructor.toString(), baseName;
17982 if (/^[^\[].*Route$/.test(className)) {
17983 baseName = className.match(/([^\.]+\.)*([^\.]+)/)[2];
17984 baseName = baseName.replace(/Route$/, '');
17985 return baseName.charAt(0).toLowerCase() + baseName.substr(1);
17989 render: function(options) {
17990 options = options || {};
17992 var template = options.template || get(this, '_template'),
17993 parentTemplate = options.into || get(this, 'parentTemplate'),
17994 controller = get(this.router, parentTemplate + "Controller");
17996 var viewName = Ember.String.classify(template) + "View",
17997 viewClass = get(get(this.router, 'namespace'), viewName);
17999 viewClass = (viewClass || Ember.View).extend({
18000 templateName: template
18003 controller.set('view', viewClass.create());
18007 The `connectOutlets` event will be triggered once a
18008 state has been entered. It will be called with the
18011 @event connectOutlets
18012 @param router {Ember.Router}
18015 connectOutlets: Ember.K,
18018 The `navigateAway` event will be triggered when the
18019 URL changes due to the back/forward button
18021 @event navigateAway
18023 navigateAway: Ember.K
18033 @submodule ember-routing
18039 @extends Ember.State
18040 @uses Ember.Routable
18042 Ember.Route = Ember.State.extend(Ember.Routable);
18049 var escapeForRegex = function(text) {
18050 return text.replace(/[\-\[\]{}()*+?.,\\\^\$|#\s]/g, "\\$&");
18054 @class _RouteMatcher
18057 @extends Ember.Object
18059 Ember._RouteMatcher = Ember.Object.extend({
18063 var route = this.route,
18068 // Strip off leading slash if present
18069 if (route.charAt(0) === '/') {
18070 route = this.route = route.substr(1);
18073 escaped = escapeForRegex(route);
18075 var regex = escaped.replace(/(:|(?:\\\*))([a-z_]+)(?=$|\/)/gi, function(match, type, id) {
18076 identifiers[count++] = id;
18085 this.identifiers = identifiers;
18086 this.regex = new RegExp("^/?" + regex);
18089 match: function(path) {
18090 var match = path.match(this.regex);
18093 var identifiers = this.identifiers,
18096 for (var i=1, l=identifiers.length; i<l; i++) {
18097 hash[identifiers[i]] = match[i];
18101 remaining: path.substr(match[0].length),
18102 hash: identifiers.length > 0 ? hash : null
18107 generate: function(hash) {
18108 var identifiers = this.identifiers, route = this.route, id;
18109 for (var i=1, l=identifiers.length; i<l; i++) {
18110 id = identifiers[i];
18111 route = route.replace(new RegExp("(:|(\\*))" + id), hash[id]);
18124 @submodule ember-routing
18127 var get = Ember.get, set = Ember.set;
18130 This file implements the `location` API used by Ember's router.
18134 getURL: returns the current URL
18135 setURL(path): sets the current URL
18136 onUpdateURL(callback): triggers the callback when the URL changes
18137 formatURL(url): formats `url` to be placed into `href` attribute
18139 Calling setURL will not trigger onUpdateURL callbacks.
18141 TODO: This should perhaps be moved so that it's visible in the doc output.
18145 Ember.Location returns an instance of the correct implementation of
18146 the `location` API.
18148 You can pass it a `implementation` ('hash', 'history', 'none') to force a
18149 particular implementation.
18156 create: function(options) {
18157 var implementation = options && options.implementation;
18158 Ember.assert("Ember.Location.create: you must specify a 'implementation' option", !!implementation);
18160 var implementationClass = this.implementations[implementation];
18161 Ember.assert("Ember.Location.create: " + implementation + " is not a valid implementation", !!implementationClass);
18163 return implementationClass.create.apply(implementationClass, arguments);
18166 registerImplementation: function(name, implementation) {
18167 this.implementations[name] = implementation;
18170 implementations: {}
18180 @submodule ember-routing
18183 var get = Ember.get, set = Ember.set;
18186 Ember.NoneLocation does not interact with the browser. It is useful for
18187 testing, or when you need to manage state with your Router, but temporarily
18188 don't want it to muck with the URL (for example when you embed your
18189 application in a larger page).
18191 @class NoneLocation
18193 @extends Ember.Object
18195 Ember.NoneLocation = Ember.Object.extend({
18198 getURL: function() {
18199 return get(this, 'path');
18202 setURL: function(path) {
18203 set(this, 'path', path);
18206 onUpdateURL: function(callback) {
18207 // We are not wired up to the browser, so we'll never trigger the callback.
18210 formatURL: function(url) {
18211 // The return value is not overly meaningful, but we do not want to throw
18212 // errors when test code renders templates containing {{action href=true}}
18218 Ember.Location.registerImplementation('none', Ember.NoneLocation);
18227 @submodule ember-routing
18230 var get = Ember.get, set = Ember.set;
18233 Ember.HashLocation implements the location API using the browser's
18234 hash. At present, it relies on a hashchange event existing in the
18237 @class HashLocation
18239 @extends Ember.Object
18241 Ember.HashLocation = Ember.Object.extend({
18244 set(this, 'location', get(this, 'location') || window.location);
18250 Returns the current `location.hash`, minus the '#' at the front.
18254 getURL: function() {
18255 return get(this, 'location').hash.substr(1);
18261 Set the `location.hash` and remembers what was set. This prevents
18262 `onUpdateURL` callbacks from triggering when the hash was set by
18266 @param path {String}
18268 setURL: function(path) {
18269 get(this, 'location').hash = path;
18270 set(this, 'lastSetURL', path);
18276 Register a callback to be invoked when the hash changes. These
18277 callbacks will execute when the user presses the back or forward
18278 button, but not after `setURL` is invoked.
18280 @method onUpdateURL
18281 @param callback {Function}
18283 onUpdateURL: function(callback) {
18285 var guid = Ember.guidFor(this);
18287 Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
18288 var path = location.hash.substr(1);
18289 if (get(self, 'lastSetURL') === path) { return; }
18291 set(self, 'lastSetURL', null);
18293 callback(location.hash.substr(1));
18300 Given a URL, formats it to be placed into the page as part
18301 of an element's `href` attribute.
18303 This is used, for example, when using the {{action}} helper
18304 to generate a URL based on an event.
18307 @param url {String}
18309 formatURL: function(url) {
18313 willDestroy: function() {
18314 var guid = Ember.guidFor(this);
18316 Ember.$(window).unbind('hashchange.ember-location-'+guid);
18320 Ember.Location.registerImplementation('hash', Ember.HashLocation);
18329 @submodule ember-routing
18332 var get = Ember.get, set = Ember.set;
18333 var popstateReady = false;
18336 Ember.HistoryLocation implements the location API using the browser's
18337 history.pushState API.
18339 @class HistoryLocation
18341 @extends Ember.Object
18343 Ember.HistoryLocation = Ember.Object.extend({
18346 set(this, 'location', get(this, 'location') || window.location);
18353 Used to set state on first call to setURL
18357 initState: function() {
18358 this.replaceState(get(this, 'location').pathname);
18359 set(this, 'history', window.history);
18363 Will be pre-pended to path upon state change
18373 Returns the current `location.pathname`.
18377 getURL: function() {
18378 return get(this, 'location').pathname;
18384 Uses `history.pushState` to update the url without a page reload.
18387 @param path {String}
18389 setURL: function(path) {
18390 path = this.formatURL(path);
18392 if (this.getState().path !== path) {
18393 popstateReady = true;
18394 this.pushState(path);
18401 Get the current `history.state`
18405 getState: function() {
18406 return get(this, 'history').state;
18415 @param path {String}
18417 pushState: function(path) {
18418 window.history.pushState({ path: path }, null, path);
18424 Replaces the current state
18426 @method replaceState
18427 @param path {String}
18429 replaceState: function(path) {
18430 window.history.replaceState({ path: path }, null, path);
18436 Register a callback to be invoked whenever the browser
18437 history changes, including using forward and back buttons.
18439 @method onUpdateURL
18440 @param callback {Function}
18442 onUpdateURL: function(callback) {
18443 var guid = Ember.guidFor(this);
18445 Ember.$(window).bind('popstate.ember-location-'+guid, function(e) {
18446 if(!popstateReady) {
18449 callback(location.pathname);
18456 Used when using `{{action}}` helper. The url is always appended to the rootURL.
18459 @param url {String}
18461 formatURL: function(url) {
18462 var rootURL = get(this, 'rootURL');
18465 rootURL = rootURL.replace(/\/$/, '');
18468 return rootURL + url;
18471 willDestroy: function() {
18472 var guid = Ember.guidFor(this);
18474 Ember.$(window).unbind('popstate.ember-location-'+guid);
18478 Ember.Location.registerImplementation('history', Ember.HistoryLocation);
18493 @submodule ember-routing
18496 var get = Ember.get, set = Ember.set;
18498 var merge = function(original, hash) {
18499 for (var prop in hash) {
18500 if (!hash.hasOwnProperty(prop)) { continue; }
18501 if (original.hasOwnProperty(prop)) { continue; }
18503 original[prop] = hash[prop];
18508 `Ember.Router` is the subclass of `Ember.StateManager` responsible for providing URL-based
18509 application state detection. The `Ember.Router` instance of an application detects the browser URL
18510 at application load time and attempts to match it to a specific application state. Additionally
18511 the router will update the URL to reflect an application's state changes over time.
18513 ## Adding a Router Instance to Your Application
18514 An instance of Ember.Router can be associated with an instance of Ember.Application in one of two ways:
18516 You can provide a subclass of Ember.Router as the `Router` property of your application. An instance
18517 of this Router class will be instantiated and route detection will be enabled when the application's
18518 `initialize` method is called. The Router instance will be available as the `router` property
18519 of the application:
18521 App = Ember.Application.create({
18522 Router: Ember.Router.extend({ ... })
18526 App.get('router') // an instance of App.Router
18528 If you want to define a Router instance elsewhere, you can pass the instance to the application's
18529 `initialize` method:
18531 App = Ember.Application.create();
18532 aRouter = Ember.Router.create({ ... });
18534 App.initialize(aRouter);
18535 App.get('router') // aRouter
18537 ## Adding Routes to a Router
18538 The `initialState` property of Ember.Router instances is named `root`. The state stored in this
18539 property must be a subclass of Ember.Route. The `root` route acts as the container for the
18540 set of routable states but is not routable itself. It should have states that are also subclasses
18541 of Ember.Route which each have a `route` property describing the URL pattern you would like to detect.
18543 App = Ember.Application.create({
18544 Router: Ember.Router.extend({
18545 root: Ember.Route.extend({
18546 index: Ember.Route.extend({
18549 ... additional Ember.Routes ...
18556 When an application loads, Ember will parse the URL and attempt to find an Ember.Route within
18557 the application's states that matches. (The example URL-matching below will use the default
18558 'hash syntax' provided by `Ember.HashLocation`.)
18560 In the following route structure:
18562 App = Ember.Application.create({
18563 Router: Ember.Router.extend({
18564 root: Ember.Route.extend({
18565 aRoute: Ember.Route.extend({
18568 bRoute: Ember.Route.extend({
18569 route: '/alphabeta'
18576 Loading the page at the URL '#/' will detect the route property of 'root.aRoute' ('/') and
18577 transition the router first to the state named 'root' and then to the substate 'aRoute'.
18579 Respectively, loading the page at the URL '#/alphabeta' would detect the route property of
18580 'root.bRoute' ('/alphabeta') and transition the router first to the state named 'root' and
18581 then to the substate 'bRoute'.
18583 ## Adding Nested Routes to a Router
18584 Routes can contain nested subroutes each with their own `route` property describing the nested
18585 portion of the URL they would like to detect and handle. Router, like all instances of StateManager,
18586 cannot call `transitonTo` with an intermediary state. To avoid transitioning the Router into an
18587 intermediary state when detecting URLs, a Route with nested routes must define both a base `route`
18588 property for itself and a child Route with a `route` property of `'/'` which will be transitioned
18589 to when the base route is detected in the URL:
18591 Given the following application code:
18593 App = Ember.Application.create({
18594 Router: Ember.Router.extend({
18595 root: Ember.Route.extend({
18596 aRoute: Ember.Route.extend({
18597 route: '/theBaseRouteForThisSet',
18599 indexSubRoute: Ember.Route.extend({
18603 subRouteOne: Ember.Route.extend({
18604 route: '/subroute1'
18607 subRouteTwo: Ember.Route.extend({
18608 route: '/subRoute2'
18617 When the application is loaded at '/theBaseRouteForThisSet' the Router will transition to the route
18618 at path 'root.aRoute' and then transition to state 'indexSubRoute'.
18620 When the application is loaded at '/theBaseRouteForThisSet/subRoute1' the Router will transition to
18621 the route at path 'root.aRoute' and then transition to state 'subRouteOne'.
18623 ## Route Transition Events
18624 Transitioning between Ember.Route instances (including the transition into the detected
18625 route when loading the application) triggers the same transition events as state transitions for
18626 base `Ember.State`s. However, the default `setup` transition event is named `connectOutlets` on
18627 Ember.Router instances (see 'Changing View Hierarchy in Response To State Change').
18629 The following route structure when loaded with the URL "#/"
18631 App = Ember.Application.create({
18632 Router: Ember.Router.extend({
18633 root: Ember.Route.extend({
18634 aRoute: Ember.Route.extend({
18636 enter: function(router) {
18637 console.log("entering root.aRoute from", router.get('currentState.name'));
18639 connectOutlets: function(router) {
18640 console.log("entered root.aRoute, fully transitioned to", router.get('currentState.path'));
18648 Will result in console output of:
18650 'entering root.aRoute from root'
18651 'entered root.aRoute, fully transitioned to root.aRoute '
18653 Ember.Route has two additional callbacks for handling URL serialization and deserialization. See
18654 'Serializing/Deserializing URLs'
18656 ## Routes With Dynamic Segments
18657 An Ember.Route's `route` property can reference dynamic sections of the URL by prefacing a URL segment
18658 with the ':' character. The values of these dynamic segments will be passed as a hash to the
18659 `deserialize` method of the matching Route (see 'Serializing/Deserializing URLs').
18661 ## Serializing/Deserializing URLs
18662 Ember.Route has two callbacks for associating a particular object context with a URL: `serialize`
18663 for converting an object into a parameters hash to fill dynamic segments of a URL and `deserialize`
18664 for converting a hash of dynamic segments from the URL into the appropriate object.
18666 ### Deserializing A URL's Dynamic Segments
18667 When an application is first loaded or the URL is changed manually (e.g. through the browser's
18668 back button) the `deserialize` method of the URL's matching Ember.Route will be called with
18669 the application's router as its first argument and a hash of the URL's dynamic segments and values
18670 as its second argument.
18672 The following route structure when loaded with the URL "#/fixed/thefirstvalue/anotherFixed/thesecondvalue":
18674 App = Ember.Application.create({
18675 Router: Ember.Router.extend({
18676 root: Ember.Route.extend({
18677 aRoute: Ember.Route.extend({
18678 route: '/fixed/:dynamicSectionA/anotherFixed/:dynamicSectionB',
18679 deserialize: function(router, params) {}
18686 Will call the 'deserialize' method of the Route instance at the path 'root.aRoute' with the
18687 following hash as its second argument:
18690 dynamicSectionA: 'thefirstvalue',
18691 dynamicSectionB: 'thesecondvalue'
18694 Within `deserialize` you should use this information to retrieve or create an appropriate context
18695 object for the given URL (e.g. by loading from a remote API or accessing the browser's
18696 `localStorage`). This object must be the `return` value of `deserialize` and will be
18697 passed to the Route's `connectOutlets` and `serialize` methods.
18699 When an application's state is changed from within the application itself, the context provided for
18700 the transition will be passed and `deserialize` is not called (see 'Transitions Between States').
18702 ### Serializing An Object For URLs with Dynamic Segments
18703 When transitioning into a Route whose `route` property contains dynamic segments the Route's
18704 `serialize` method is called with the Route's router as the first argument and the Route's
18705 context as the second argument. The return value of `serialize` will be used to populate the
18706 dynamic segments and should be an object with keys that match the names of the dynamic sections.
18708 Given the following route structure:
18710 App = Ember.Application.create({
18711 Router: Ember.Router.extend({
18712 root: Ember.Route.extend({
18713 aRoute: Ember.Route.extend({
18716 bRoute: Ember.Route.extend({
18717 route: '/staticSection/:someDynamicSegment',
18718 serialize: function(router, context) {
18720 someDynamicSegment: context.get('name')
18730 Transitioning to "root.bRoute" with a context of `Object.create({name: 'Yehuda'})` will call
18731 the Route's `serialize` method with the context as its second argument and update the URL to
18732 '#/staticSection/Yehuda'.
18734 ## Transitions Between States
18735 Once a routed application has initialized its state based on the entry URL, subsequent transitions to other
18736 states will update the URL if the entered Route has a `route` property. Given the following route structure
18737 loaded at the URL '#/':
18739 App = Ember.Application.create({
18740 Router: Ember.Router.extend({
18741 root: Ember.Route.extend({
18742 aRoute: Ember.Route.extend({
18744 moveElsewhere: Ember.Route.transitionTo('bRoute')
18746 bRoute: Ember.Route.extend({
18747 route: '/someOtherLocation'
18754 And application code:
18756 App.get('router').send('moveElsewhere');
18758 Will transition the application's state to 'root.bRoute' and trigger an update of the URL to
18759 '#/someOtherLocation'.
18761 For URL patterns with dynamic segments a context can be supplied as the second argument to `send`.
18762 The router will match dynamic segments names to keys on this object and fill in the URL with the
18763 supplied values. Given the following state structure loaded at the URL '#/':
18765 App = Ember.Application.create({
18766 Router: Ember.Router.extend({
18767 root: Ember.Route.extend({
18768 aRoute: Ember.Route.extend({
18770 moveElsewhere: Ember.Route.transitionTo('bRoute')
18772 bRoute: Ember.Route.extend({
18773 route: '/a/route/:dynamicSection/:anotherDynamicSection',
18774 connectOutlets: function(router, context) {},
18781 And application code:
18783 App.get('router').send('moveElsewhere', {
18784 dynamicSection: '42',
18785 anotherDynamicSection: 'Life'
18788 Will transition the application's state to 'root.bRoute' and trigger an update of the URL to
18789 '#/a/route/42/Life'.
18791 The context argument will also be passed as the second argument to the `serialize` method call.
18793 ## Injection of Controller Singletons
18794 During application initialization Ember will detect properties of the application ending in 'Controller',
18795 create singleton instances of each class, and assign them as properties on the router. The property name
18796 will be the UpperCamel name converted to lowerCamel format. These controller classes should be subclasses
18797 of Ember.ObjectController, Ember.ArrayController, Ember.Controller, or a custom Ember.Object that includes the
18798 Ember.ControllerMixin mixin.
18801 App = Ember.Application.create({
18802 FooController: Ember.Object.create(Ember.ControllerMixin),
18803 Router: Ember.Router.extend({ ... })
18806 App.get('router.fooController'); // instance of App.FooController
18809 The controller singletons will have their `namespace` property set to the application and their `target`
18810 property set to the application's router singleton for easy integration with Ember's user event system.
18811 See 'Changing View Hierarchy in Response To State Change' and 'Responding to User-initiated Events.'
18813 ## Responding to User-initiated Events
18814 Controller instances injected into the router at application initialization have their `target` property
18815 set to the application's router instance. These controllers will also be the default `context` for their
18816 associated views. Uses of the `{{action}}` helper will automatically target the application's router.
18818 Given the following application entered at the URL '#/':
18821 App = Ember.Application.create({
18822 Router: Ember.Router.extend({
18823 root: Ember.Route.extend({
18824 aRoute: Ember.Route.extend({
18826 anActionOnTheRouter: function(router, context) {
18827 router.transitionTo('anotherState', context);
18830 anotherState: Ember.Route.extend({
18831 route: '/differentUrl',
18832 connectOutlets: function(router, context) {
18842 The following template:
18845 <script type="text/x-handlebars" data-template-name="aView">
18846 <h1><a {{action anActionOnTheRouter}}>{{title}}</a></h1>
18850 Will delegate `click` events on the rendered `h1` to the application's router instance. In this case the
18851 `anActionOnTheRouter` method of the state at 'root.aRoute' will be called with the view's controller
18852 as the context argument. This context will be passed to the `connectOutlets` as its second argument.
18854 Different `context` can be supplied from within the `{{action}}` helper, allowing specific context passing
18855 between application states:
18858 <script type="text/x-handlebars" data-template-name="photos">
18859 {{#each photo in controller}}
18860 <h1><a {{action showPhoto photo}}>{{title}}</a></h1>
18865 See `Handlebars.helpers.action` for additional usage examples.
18868 ## Changing View Hierarchy in Response To State Change
18870 Changes in application state that change the URL should be accompanied by associated changes in view
18871 hierarchy. This can be accomplished by calling 'connectOutlet' on the injected controller singletons from
18872 within the 'connectOutlets' event of an Ember.Route:
18875 App = Ember.Application.create({
18876 OneController: Ember.ObjectController.extend(),
18877 OneView: Ember.View.extend(),
18879 AnotherController: Ember.ObjectController.extend(),
18880 AnotherView: Ember.View.extend(),
18882 Router: Ember.Router.extend({
18883 root: Ember.Route.extend({
18884 aRoute: Ember.Route.extend({
18886 connectOutlets: function(router, context) {
18887 router.get('oneController').connectOutlet('another');
18897 This will detect the '{{outlet}}' portion of `oneController`'s view (an instance of `App.OneView`) and
18898 fill it with a rendered instance of `App.AnotherView` whose `context` will be the single instance of
18899 `App.AnotherController` stored on the router in the `anotherController` property.
18901 For more information about Outlets, see `Ember.Handlebars.helpers.outlet`. For additional information on
18902 the `connectOutlet` method, see `Ember.Controller.connectOutlet`. For more information on
18903 controller injections, see `Ember.Application#initialize()`. For additional information about view context,
18908 @extends Ember.StateManager
18910 Ember.Router = Ember.StateManager.extend(
18911 /** @scope Ember.Router.prototype */ {
18914 @property initialState
18918 initialState: 'root',
18921 The `Ember.Location` implementation to be used to manage the application
18922 URL state. The following values are supported:
18924 * 'hash': Uses URL fragment identifiers (like #/blog/1) for routing.
18925 * 'history': Uses the browser's history.pushstate API for routing. Only works in
18926 modern browsers with pushstate support.
18927 * 'none': Does not read or set the browser URL, but still allows for
18928 routing to happen. Useful for testing.
18937 This is only used when a history location is used so that applications that
18938 don't live at the root of the domain can append paths to their root.
18947 transitionTo: function() {
18948 this.abortRoutingPromises();
18949 this._super.apply(this, arguments);
18952 route: function(path) {
18953 this.abortRoutingPromises();
18955 set(this, 'isRouting', true);
18960 path = path.replace(get(this, 'rootURL'), '');
18961 path = path.replace(/^(?=[^\/])/, "/");
18963 this.send('navigateAway');
18964 this.send('unroutePath', path);
18966 routableState = get(this, 'currentState');
18967 while (routableState && !routableState.get('isRoutable')) {
18968 routableState = get(routableState, 'parentState');
18970 var currentURL = routableState ? routableState.absoluteRoute(this) : '';
18971 var rest = path.substr(currentURL.length);
18973 this.send('routePath', rest);
18975 set(this, 'isRouting', false);
18978 routableState = get(this, 'currentState');
18979 while (routableState && !routableState.get('isRoutable')) {
18980 routableState = get(routableState, 'parentState');
18983 if (routableState) {
18984 routableState.updateRoute(this, get(this, 'location'));
18988 urlFor: function(path, hash) {
18989 var currentState = get(this, 'currentState') || this,
18990 state = this.findStateByPath(currentState, path);
18992 Ember.assert(Ember.String.fmt("Could not find route with path '%@'", [path]), state);
18993 Ember.assert(Ember.String.fmt("To get a URL for the state '%@', it must have a `route` property.", [path]), get(state, 'routeMatcher'));
18995 var location = get(this, 'location'),
18996 absoluteRoute = state.absoluteRoute(this, hash);
18998 return location.formatURL(absoluteRoute);
19001 urlForEvent: function(eventName) {
19002 var contexts = Array.prototype.slice.call(arguments, 1);
19003 var currentState = get(this, 'currentState');
19004 var targetStateName = currentState.lookupEventTransition(eventName);
19006 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);
19008 var targetState = this.findStateByPath(currentState, targetStateName);
19010 Ember.assert("Your target state name " + targetStateName + " for event " + eventName + " did not resolve to a state", targetState);
19012 var hash = this.serializeRecursively(targetState, contexts, {});
19014 return this.urlFor(targetStateName, hash);
19017 serializeRecursively: function(state, contexts, hash) {
19019 context = get(state, 'hasContext') ? contexts.pop() : null;
19020 merge(hash, state.serialize(this, context));
19021 parentState = state.get("parentState");
19022 if (parentState && parentState instanceof Ember.Route) {
19023 return this.serializeRecursively(parentState, contexts, hash);
19029 abortRoutingPromises: function() {
19030 if (this._routingPromises) {
19031 this._routingPromises.abort();
19032 this._routingPromises = null;
19036 handleStatePromises: function(states, complete) {
19037 this.abortRoutingPromises();
19039 this.set('isLocked', true);
19041 var manager = this;
19043 this._routingPromises = Ember._PromiseChain.create({
19044 promises: states.slice(),
19046 successCallback: function() {
19047 manager.set('isLocked', false);
19051 failureCallback: function() {
19052 throw "Unable to load object";
19055 promiseSuccessCallback: function(item, args) {
19056 set(item, 'object', args[0]);
19059 abortCallback: function() {
19060 manager.set('isLocked', false);
19065 moveStatesIntoRoot: function() {
19066 this.root = Ember.Route.extend();
19068 for (var name in this) {
19069 if (name === "constructor") { continue; }
19071 var state = this[name];
19073 if (state instanceof Ember.Route || Ember.Route.detect(state)) {
19074 this.root[name] = state;
19082 this.moveStatesIntoRoot();
19087 var location = get(this, 'location'),
19088 rootURL = get(this, 'rootURL');
19090 if ('string' === typeof location) {
19091 set(this, 'location', Ember.Location.create({
19092 implementation: location,
19097 this.assignRouter(this, this);
19100 assignRouter: function(state, router) {
19101 state.router = router;
19103 var childStates = state.states;
19106 for (var stateName in childStates) {
19107 if (!childStates.hasOwnProperty(stateName)) { continue; }
19108 this.assignRouter(childStates[stateName], router);
19113 willDestroy: function() {
19114 get(this, 'location').destroy();
19127 @submodule ember-routing
19128 @requires ember-states
19134 // ==========================================================================
19135 // Project: metamorph
19136 // Copyright: ©2011 My Company Inc. All rights reserved.
19137 // ==========================================================================
19139 (function(window) {
19141 var K = function(){},
19143 document = window.document,
19145 // Feature-detect the W3C range API, the extended check is for IE9 which only partially supports ranges
19146 supportsRange = ('createRange' in document) && (typeof Range !== 'undefined') && Range.prototype.createContextualFragment,
19148 // Internet Explorer prior to 9 does not allow setting innerHTML if the first element
19149 // is a "zero-scope" element. This problem can be worked around by making
19150 // the first node an invisible text node. We, like Modernizr, use ­
19151 needsShy = (function(){
19152 var testEl = document.createElement('div');
19153 testEl.innerHTML = "<div></div>";
19154 testEl.firstChild.innerHTML = "<script></script>";
19155 return testEl.firstChild.innerHTML === '';
19158 // Constructor that supports either Metamorph('foo') or new
19159 // Metamorph('foo');
19161 // Takes a string of HTML as the argument.
19163 var Metamorph = function(html) {
19166 if (this instanceof Metamorph) {
19172 self.innerHTML = html;
19173 var myGuid = 'metamorph-'+(guid++);
19174 self.start = myGuid + '-start';
19175 self.end = myGuid + '-end';
19180 K.prototype = Metamorph.prototype;
19182 var rangeFor, htmlFunc, removeFunc, outerHTMLFunc, appendToFunc, afterFunc, prependFunc, startTagFunc, endTagFunc;
19184 outerHTMLFunc = function() {
19185 return this.startTag() + this.innerHTML + this.endTag();
19188 startTagFunc = function() {
19189 return "<script id='" + this.start + "' type='text/x-placeholder'></script>";
19192 endTagFunc = function() {
19193 return "<script id='" + this.end + "' type='text/x-placeholder'></script>";
19196 // If we have the W3C range API, this process is relatively straight forward.
19197 if (supportsRange) {
19199 // Get a range for the current morph. Optionally include the starting and
19200 // ending placeholders.
19201 rangeFor = function(morph, outerToo) {
19202 var range = document.createRange();
19203 var before = document.getElementById(morph.start);
19204 var after = document.getElementById(morph.end);
19207 range.setStartBefore(before);
19208 range.setEndAfter(after);
19210 range.setStartAfter(before);
19211 range.setEndBefore(after);
19217 htmlFunc = function(html, outerToo) {
19218 // get a range for the current metamorph object
19219 var range = rangeFor(this, outerToo);
19221 // delete the contents of the range, which will be the
19222 // nodes between the starting and ending placeholder.
19223 range.deleteContents();
19225 // create a new document fragment for the HTML
19226 var fragment = range.createContextualFragment(html);
19228 // insert the fragment into the range
19229 range.insertNode(fragment);
19232 removeFunc = function() {
19233 // get a range for the current metamorph object including
19234 // the starting and ending placeholders.
19235 var range = rangeFor(this, true);
19237 // delete the entire range.
19238 range.deleteContents();
19241 appendToFunc = function(node) {
19242 var range = document.createRange();
19243 range.setStart(node);
19244 range.collapse(false);
19245 var frag = range.createContextualFragment(this.outerHTML());
19246 node.appendChild(frag);
19249 afterFunc = function(html) {
19250 var range = document.createRange();
19251 var after = document.getElementById(this.end);
19253 range.setStartAfter(after);
19254 range.setEndAfter(after);
19256 var fragment = range.createContextualFragment(html);
19257 range.insertNode(fragment);
19260 prependFunc = function(html) {
19261 var range = document.createRange();
19262 var start = document.getElementById(this.start);
19264 range.setStartAfter(start);
19265 range.setEndAfter(start);
19267 var fragment = range.createContextualFragment(html);
19268 range.insertNode(fragment);
19273 * This code is mostly taken from jQuery, with one exception. In jQuery's case, we
19274 * have some HTML and we need to figure out how to convert it into some nodes.
19276 * In this case, jQuery needs to scan the HTML looking for an opening tag and use
19277 * that as the key for the wrap map. In our case, we know the parent node, and
19278 * can use its type as the key for the wrap map.
19281 select: [ 1, "<select multiple='multiple'>", "</select>" ],
19282 fieldset: [ 1, "<fieldset>", "</fieldset>" ],
19283 table: [ 1, "<table>", "</table>" ],
19284 tbody: [ 2, "<table><tbody>", "</tbody></table>" ],
19285 tr: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
19286 colgroup: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
19287 map: [ 1, "<map>", "</map>" ],
19288 _default: [ 0, "", "" ]
19292 * Given a parent node and some HTML, generate a set of nodes. Return the first
19293 * node, which will allow us to traverse the rest using nextSibling.
19295 * We need to do this because innerHTML in IE does not really parse the nodes.
19297 var firstNodeFor = function(parentNode, html) {
19298 var arr = wrapMap[parentNode.tagName.toLowerCase()] || wrapMap._default;
19299 var depth = arr[0], start = arr[1], end = arr[2];
19301 if (needsShy) { html = '­'+html; }
19303 var element = document.createElement('div');
19304 element.innerHTML = start + html + end;
19306 for (var i=0; i<=depth; i++) {
19307 element = element.firstChild;
19310 // Look for ­ to remove it.
19312 var shyElement = element;
19314 // Sometimes we get nameless elements with the shy inside
19315 while (shyElement.nodeType === 1 && !shyElement.nodeName) {
19316 shyElement = shyElement.firstChild;
19319 // At this point it's the actual unicode character.
19320 if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") {
19321 shyElement.nodeValue = shyElement.nodeValue.slice(1);
19329 * In some cases, Internet Explorer can create an anonymous node in
19330 * the hierarchy with no tagName. You can create this scenario via:
19332 * div = document.createElement("div");
19333 * div.innerHTML = "<table>­<script></script><tr><td>hi</td></tr></table>";
19334 * div.firstChild.firstChild.tagName //=> ""
19336 * If our script markers are inside such a node, we need to find that
19337 * node and use *it* as the marker.
19339 var realNode = function(start) {
19340 while (start.parentNode.tagName === "") {
19341 start = start.parentNode;
19348 * When automatically adding a tbody, Internet Explorer inserts the
19349 * tbody immediately before the first <tr>. Other browsers create it
19350 * before the first node, no matter what.
19352 * This means the the following code:
19354 * div = document.createElement("div");
19355 * div.innerHTML = "<table><script id='first'></script><tr><td>hi</td></tr><script id='last'></script></table>
19357 * Generates the following DOM in IE:
19361 * - script id='first'
19366 * - script id='last'
19368 * Which means that the two script tags, even though they were
19369 * inserted at the same point in the hierarchy in the original
19370 * HTML, now have different parents.
19372 * This code reparents the first script tag by making it the tbody's
19375 var fixParentage = function(start, end) {
19376 if (start.parentNode !== end.parentNode) {
19377 end.parentNode.insertBefore(start, end.parentNode.firstChild);
19381 htmlFunc = function(html, outerToo) {
19382 // get the real starting node. see realNode for details.
19383 var start = realNode(document.getElementById(this.start));
19384 var end = document.getElementById(this.end);
19385 var parentNode = end.parentNode;
19386 var node, nextSibling, last;
19388 // make sure that the start and end nodes share the same
19389 // parent. If not, fix it.
19390 fixParentage(start, end);
19392 // remove all of the nodes after the starting placeholder and
19393 // before the ending placeholder.
19394 node = start.nextSibling;
19396 nextSibling = node.nextSibling;
19397 last = node === end;
19399 // if this is the last node, and we want to remove it as well,
19400 // set the `end` node to the next sibling. This is because
19401 // for the rest of the function, we insert the new nodes
19402 // before the end (note that insertBefore(node, null) is
19403 // the same as appendChild(node)).
19405 // if we do not want to remove it, just break.
19407 if (outerToo) { end = node.nextSibling; } else { break; }
19410 node.parentNode.removeChild(node);
19412 // if this is the last node and we didn't break before
19413 // (because we wanted to remove the outer nodes), break
19415 if (last) { break; }
19417 node = nextSibling;
19420 // get the first node for the HTML string, even in cases like
19421 // tables and lists where a simple innerHTML on a div would
19422 // swallow some of the content.
19423 node = firstNodeFor(start.parentNode, html);
19425 // copy the nodes for the HTML between the starting and ending
19428 nextSibling = node.nextSibling;
19429 parentNode.insertBefore(node, end);
19430 node = nextSibling;
19434 // remove the nodes in the DOM representing this metamorph.
19436 // this includes the starting and ending placeholders.
19437 removeFunc = function() {
19438 var start = realNode(document.getElementById(this.start));
19439 var end = document.getElementById(this.end);
19442 start.parentNode.removeChild(start);
19443 end.parentNode.removeChild(end);
19446 appendToFunc = function(parentNode) {
19447 var node = firstNodeFor(parentNode, this.outerHTML());
19450 nextSibling = node.nextSibling;
19451 parentNode.appendChild(node);
19452 node = nextSibling;
19456 afterFunc = function(html) {
19457 // get the real starting node. see realNode for details.
19458 var end = document.getElementById(this.end);
19459 var insertBefore = end.nextSibling;
19460 var parentNode = end.parentNode;
19464 // get the first node for the HTML string, even in cases like
19465 // tables and lists where a simple innerHTML on a div would
19466 // swallow some of the content.
19467 node = firstNodeFor(parentNode, html);
19469 // copy the nodes for the HTML between the starting and ending
19472 nextSibling = node.nextSibling;
19473 parentNode.insertBefore(node, insertBefore);
19474 node = nextSibling;
19478 prependFunc = function(html) {
19479 var start = document.getElementById(this.start);
19480 var parentNode = start.parentNode;
19484 node = firstNodeFor(parentNode, html);
19485 var insertBefore = start.nextSibling;
19488 nextSibling = node.nextSibling;
19489 parentNode.insertBefore(node, insertBefore);
19490 node = nextSibling;
19495 Metamorph.prototype.html = function(html) {
19496 this.checkRemoved();
19497 if (html === undefined) { return this.innerHTML; }
19499 htmlFunc.call(this, html);
19501 this.innerHTML = html;
19504 Metamorph.prototype.replaceWith = function(html) {
19505 this.checkRemoved();
19506 htmlFunc.call(this, html, true);
19509 Metamorph.prototype.remove = removeFunc;
19510 Metamorph.prototype.outerHTML = outerHTMLFunc;
19511 Metamorph.prototype.appendTo = appendToFunc;
19512 Metamorph.prototype.after = afterFunc;
19513 Metamorph.prototype.prepend = prependFunc;
19514 Metamorph.prototype.startTag = startTagFunc;
19515 Metamorph.prototype.endTag = endTagFunc;
19517 Metamorph.prototype.isRemoved = function() {
19518 var before = document.getElementById(this.start);
19519 var after = document.getElementById(this.end);
19521 return !before || !after;
19524 Metamorph.prototype.checkRemoved = function() {
19525 if (this.isRemoved()) {
19526 throw new Error("Cannot perform operations on a Metamorph that is not in the DOM.");
19530 window.Metamorph = Metamorph;
19539 @submodule ember-handlebars
19542 var objectCreate = Ember.create;
19544 var Handlebars = Ember.imports.Handlebars;
19545 Ember.assert("Ember Handlebars requires Handlebars 1.0.beta.5 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.beta\.[56789]$|^1\.0\.rc\.[123456789]+/));
19548 Prepares the Handlebars templating library for use inside Ember's view
19551 The Ember.Handlebars object is the standard Handlebars library, extended to use
19552 Ember's get() method instead of direct property access, which allows
19553 computed properties to be used inside templates.
19555 To create an Ember.Handlebars template, call Ember.Handlebars.compile(). This will
19556 return a function that can be used by Ember.View for rendering.
19561 Ember.Handlebars = objectCreate(Handlebars);
19565 @namespace Ember.Handlebars
19567 Ember.Handlebars.helpers = objectCreate(Handlebars.helpers);
19570 Override the the opcode compiler and JavaScript compiler for Handlebars.
19573 @namespace Ember.Handlebars
19577 Ember.Handlebars.Compiler = function() {};
19579 // Handlebars.Compiler doesn't exist in runtime-only
19580 if (Handlebars.Compiler) {
19581 Ember.Handlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
19584 Ember.Handlebars.Compiler.prototype.compiler = Ember.Handlebars.Compiler;
19587 @class JavaScriptCompiler
19588 @namespace Ember.Handlebars
19592 Ember.Handlebars.JavaScriptCompiler = function() {};
19594 // Handlebars.JavaScriptCompiler doesn't exist in runtime-only
19595 if (Handlebars.JavaScriptCompiler) {
19596 Ember.Handlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
19597 Ember.Handlebars.JavaScriptCompiler.prototype.compiler = Ember.Handlebars.JavaScriptCompiler;
19601 Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars";
19604 Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() {
19611 Override the default buffer for Ember Handlebars. By default, Handlebars creates
19612 an empty String at the beginning of each invocation and appends to it. Ember's
19613 Handlebars overrides this to append to a single shared buffer.
19615 @method appendToBuffer
19616 @param string {String}
19618 Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
19619 return "data.buffer.push("+string+");";
19625 Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that all simple
19626 mustaches in Ember's Handlebars will also set up an observer to keep the DOM
19627 up to date when the underlying property changes.
19630 @for Ember.Handlebars.Compiler
19633 Ember.Handlebars.Compiler.prototype.mustache = function(mustache) {
19634 if (mustache.params.length || mustache.hash) {
19635 return Handlebars.Compiler.prototype.mustache.call(this, mustache);
19637 var id = new Handlebars.AST.IdNode(['_triageMustache']);
19639 // Update the mustache node to include a hash value indicating whether the original node
19640 // was escaped. This will allow us to properly escape values when the underlying value
19641 // changes and we need to re-render the value.
19642 if(!mustache.escaped) {
19643 mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
19644 mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
19646 mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
19647 return Handlebars.Compiler.prototype.mustache.call(this, mustache);
19652 Used for precompilation of Ember Handlebars templates. This will not be used during normal
19656 @for Ember.Handlebars
19658 @param {String} string The template to precompile
19660 Ember.Handlebars.precompile = function(string) {
19661 var ast = Handlebars.parse(string);
19670 _triageMustache: true
19676 var environment = new Ember.Handlebars.Compiler().compile(ast, options);
19677 return new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
19680 // We don't support this for Handlebars runtime-only
19681 if (Handlebars.compile) {
19683 The entry point for Ember Handlebars. This replaces the default Handlebars.compile and turns on
19684 template-local data and String parameters.
19687 @for Ember.Handlebars
19689 @param {String} string The template to compile
19692 Ember.Handlebars.compile = function(string) {
19693 var ast = Handlebars.parse(string);
19694 var options = { data: true, stringParams: true };
19695 var environment = new Ember.Handlebars.Compiler().compile(ast, options);
19696 var templateSpec = new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
19698 return Handlebars.template(templateSpec);
19705 If a path starts with a reserved keyword, returns the root
19706 that should be used.
19708 @method normalizePath
19710 @param root {Object}
19711 @param path {String}
19714 var normalizePath = Ember.Handlebars.normalizePath = function(root, path, data) {
19715 var keywords = (data && data.keywords) || {},
19716 keyword, isKeyword;
19718 // Get the first segment of the path. For example, if the
19719 // path is "foo.bar.baz", returns "foo".
19720 keyword = path.split('.', 1)[0];
19722 // Test to see if the first path is a keyword that has been
19723 // passed along in the view's data hash. If so, we will treat
19724 // that object as the new root.
19725 if (keywords.hasOwnProperty(keyword)) {
19726 // Look up the value in the template's data hash.
19727 root = keywords[keyword];
19730 // Handle cases where the entire path is the reserved
19731 // word. In that case, return the object itself.
19732 if (path === keyword) {
19735 // Strip the keyword from the path and look up
19736 // the remainder from the newly found root.
19737 path = path.substr(keyword.length+1);
19741 return { root: root, path: path, isKeyword: isKeyword };
19746 Lookup both on root and on window. If the path starts with
19747 a keyword, the corresponding object will be looked up in the
19748 template's data hash and used to resolve the path.
19751 @for Ember.Handlebars
19752 @param {Object} root The object to look up the property on
19753 @param {String} path The path to be lookedup
19754 @param {Object} options The template's option hash
19756 Ember.Handlebars.get = function(root, path, options) {
19757 var data = options && options.data,
19758 normalizedPath = normalizePath(root, path, data),
19761 // In cases where the path begins with a keyword, change the
19762 // root to the value represented by that keyword, and ensure
19763 // the path is relative to it.
19764 root = normalizedPath.root;
19765 path = normalizedPath.path;
19767 value = Ember.get(root, path);
19769 // If the path starts with a capital letter, look it up on Ember.lookup,
19770 // which defaults to the `window` object in browsers.
19771 if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) {
19772 value = Ember.get(Ember.lookup, path);
19776 Ember.Handlebars.getPath = Ember.deprecateFunc('`Ember.Handlebars.getPath` has been changed to `Ember.Handlebars.get` for consistency.', Ember.Handlebars.get);
19781 Registers a helper in Handlebars that will be called if no property with the
19782 given name can be found on the current context object, and no helper with
19783 that name is registered.
19785 This throws an exception with a more helpful error message so the user can
19786 track down where the problem is happening.
19788 @method helperMissing
19789 @for Ember.Handlebars.helpers
19790 @param {String} path
19791 @param {Hash} options
19793 Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
19794 var error, view = "";
19796 error = "%@ Handlebars error: Could not find property '%@' on object %@.";
19798 view = options.data.view;
19800 throw new Ember.Error(Ember.String.fmt(error, [view, path, this]));
19814 Ember.String.htmlSafe = function(str) {
19815 return new Handlebars.SafeString(str);
19818 var htmlSafe = Ember.String.htmlSafe;
19820 if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
19823 See {{#crossLink "Ember.String/htmlSafe"}}{{/crossLink}}
19828 String.prototype.htmlSafe = function() {
19829 return htmlSafe(this);
19838 /*jshint newcap:false*/
19841 @submodule ember-handlebars
19844 var set = Ember.set, get = Ember.get;
19846 // DOMManager should just abstract dom manipulation between jquery and metamorph
19848 remove: function(view) {
19849 view.morph.remove();
19852 prepend: function(view, html) {
19853 view.morph.prepend(html);
19856 after: function(view, html) {
19857 view.morph.after(html);
19860 html: function(view, html) {
19861 view.morph.html(html);
19864 // This is messed up.
19865 replace: function(view) {
19866 var morph = view.morph;
19868 view.transitionTo('preRender');
19869 view.clearRenderedChildren();
19870 var buffer = view.renderToBuffer();
19872 Ember.run.schedule('render', this, function() {
19873 if (get(view, 'isDestroyed')) { return; }
19874 view.invalidateRecursively('element');
19875 view._notifyWillInsertElement();
19876 morph.replaceWith(buffer.string());
19877 view.transitionTo('inDOM');
19878 view._notifyDidInsertElement();
19882 empty: function(view) {
19883 view.morph.html("");
19887 // The `morph` and `outerHTML` properties are internal only
19888 // and not observable.
19893 @extends Ember.Mixin
19896 Ember._Metamorph = Ember.Mixin.create({
19900 instrumentName: 'render.metamorph',
19904 this.morph = Metamorph();
19907 beforeRender: function(buffer) {
19908 buffer.push(this.morph.startTag());
19911 afterRender: function(buffer) {
19912 buffer.push(this.morph.endTag());
19915 createElement: function() {
19916 var buffer = this.renderToBuffer();
19917 this.outerHTML = buffer.string();
19918 this.clearBuffer();
19921 domManager: DOMManager
19925 @class _MetamorphView
19927 @extends Ember.View
19928 @uses Ember._Metamorph
19931 Ember._MetamorphView = Ember.View.extend(Ember._Metamorph);
19934 @class _SimpleMetamorphView
19936 @extends Ember.View
19937 @uses Ember._Metamorph
19940 Ember._SimpleMetamorphView = Ember.CoreView.extend(Ember._Metamorph);
19948 /*globals Handlebars */
19952 @submodule ember-handlebars
19955 var get = Ember.get, set = Ember.set, handlebarsGet = Ember.Handlebars.get;
19956 Ember._SimpleHandlebarsView = Ember._SimpleMetamorphView.extend({
19957 instrumentName: 'render.simpleHandlebars',
19959 normalizedValue: Ember.computed(function() {
19960 var path = get(this, 'path'),
19961 pathRoot = get(this, 'pathRoot'),
19962 result, templateData;
19964 // Use the pathRoot as the result if no path is provided. This
19965 // happens if the path is `this`, which gets normalized into
19966 // a `pathRoot` of the current Handlebars context and a path
19971 templateData = get(this, 'templateData');
19972 result = handlebarsGet(pathRoot, path, { data: templateData });
19976 }).property('path', 'pathRoot').volatile(),
19978 render: function(buffer) {
19979 // If not invoked via a triple-mustache ({{{foo}}}), escape
19980 // the content of the template.
19981 var escape = get(this, 'isEscaped');
19982 var result = get(this, 'normalizedValue');
19984 if (result === null || result === undefined) {
19986 } else if (!(result instanceof Handlebars.SafeString)) {
19987 result = String(result);
19990 if (escape) { result = Handlebars.Utils.escapeExpression(result); }
19991 buffer.push(result);
19995 rerender: function() {
19996 switch(this.state) {
20001 throw new Error("Something you did tried to replace an {{expression}} before it was inserted into the DOM.");
20004 this.domManager.replace(this);
20011 transitionTo: function(state) {
20012 this.state = state;
20017 Ember._HandlebarsBoundView is a private view created by the Handlebars `{{bind}}`
20018 helpers that is used to keep track of bound properties.
20020 Every time a property is bound using a `{{mustache}}`, an anonymous subclass
20021 of Ember._HandlebarsBoundView is created with the appropriate sub-template and
20022 context set up. When the associated property changes, just the template for
20023 this view will re-render.
20025 @class _HandlebarsBoundView
20027 @extends Ember._MetamorphView
20030 Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
20031 instrumentName: 'render.boundHandlebars',
20034 The function used to determine if the `displayTemplate` or
20035 `inverseTemplate` should be rendered. This should be a function that takes
20036 a value and returns a Boolean.
20038 @property shouldDisplayFunc
20042 shouldDisplayFunc: null,
20045 Whether the template rendered by this view gets passed the context object
20046 of its parent template, or gets passed the value of retrieving `path`
20047 from the `pathRoot`.
20049 For example, this is true when using the `{{#if}}` helper, because the
20050 template inside the helper should look up properties relative to the same
20051 object as outside the block. This would be false when used with `{{#with
20052 foo}}` because the template should receive the object found by evaluating
20055 @property preserveContext
20059 preserveContext: false,
20062 If `preserveContext` is true, this is the object that will be used
20063 to render the template.
20065 @property previousContext
20068 previousContext: null,
20071 The template to render when `shouldDisplayFunc` evaluates to true.
20073 @property displayTemplate
20077 displayTemplate: null,
20080 The template to render when `shouldDisplayFunc` evaluates to false.
20082 @property inverseTemplate
20086 inverseTemplate: null,
20090 The path to look up on `pathRoot` that is passed to
20091 `shouldDisplayFunc` to determine which template to render.
20093 In addition, if `preserveContext` is false, the object at this path will
20094 be passed to the template when rendering.
20103 The object from which the `path` will be looked up. Sometimes this is the
20104 same as the `previousContext`, but in cases where this view has been generated
20105 for paths that start with a keyword such as `view` or `controller`, the
20106 path root will be that resolved object.
20113 normalizedValue: Ember.computed(function() {
20114 var path = get(this, 'path'),
20115 pathRoot = get(this, 'pathRoot'),
20116 valueNormalizer = get(this, 'valueNormalizerFunc'),
20117 result, templateData;
20119 // Use the pathRoot as the result if no path is provided. This
20120 // happens if the path is `this`, which gets normalized into
20121 // a `pathRoot` of the current Handlebars context and a path
20126 templateData = get(this, 'templateData');
20127 result = handlebarsGet(pathRoot, path, { data: templateData });
20130 return valueNormalizer ? valueNormalizer(result) : result;
20131 }).property('path', 'pathRoot', 'valueNormalizerFunc').volatile(),
20133 rerenderIfNeeded: function() {
20134 if (!get(this, 'isDestroyed') && get(this, 'normalizedValue') !== this._lastNormalizedValue) {
20140 Determines which template to invoke, sets up the correct state based on
20141 that logic, then invokes the default Ember.View `render` implementation.
20143 This method will first look up the `path` key on `pathRoot`,
20144 then pass that value to the `shouldDisplayFunc` function. If that returns
20145 true, the `displayTemplate` function will be rendered to DOM. Otherwise,
20146 `inverseTemplate`, if specified, will be rendered.
20148 For example, if this Ember._HandlebarsBoundView represented the `{{#with foo}}`
20149 helper, it would look up the `foo` property of its context, and
20150 `shouldDisplayFunc` would always return true. The object found by looking
20151 up `foo` would be passed to `displayTemplate`.
20154 @param {Ember.RenderBuffer} buffer
20156 render: function(buffer) {
20157 // If not invoked via a triple-mustache ({{{foo}}}), escape
20158 // the content of the template.
20159 var escape = get(this, 'isEscaped');
20161 var shouldDisplay = get(this, 'shouldDisplayFunc'),
20162 preserveContext = get(this, 'preserveContext'),
20163 context = get(this, 'previousContext');
20165 var inverseTemplate = get(this, 'inverseTemplate'),
20166 displayTemplate = get(this, 'displayTemplate');
20168 var result = get(this, 'normalizedValue');
20169 this._lastNormalizedValue = result;
20171 // First, test the conditional to see if we should
20172 // render the template or not.
20173 if (shouldDisplay(result)) {
20174 set(this, 'template', displayTemplate);
20176 // If we are preserving the context (for example, if this
20177 // is an #if block, call the template with the same object.
20178 if (preserveContext) {
20179 set(this, '_context', context);
20181 // Otherwise, determine if this is a block bind or not.
20182 // If so, pass the specified object to the template
20183 if (displayTemplate) {
20184 set(this, '_context', result);
20186 // This is not a bind block, just push the result of the
20187 // expression to the render context and return.
20188 if (result === null || result === undefined) {
20190 } else if (!(result instanceof Handlebars.SafeString)) {
20191 result = String(result);
20194 if (escape) { result = Handlebars.Utils.escapeExpression(result); }
20195 buffer.push(result);
20199 } else if (inverseTemplate) {
20200 set(this, 'template', inverseTemplate);
20202 if (preserveContext) {
20203 set(this, '_context', context);
20205 set(this, '_context', result);
20208 set(this, 'template', function() { return ''; });
20211 return this._super(buffer);
20222 @submodule ember-handlebars
20225 var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
20226 var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
20227 var forEach = Ember.ArrayPolyfills.forEach;
20229 var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers;
20231 // Binds a property into the DOM. This will create a hook in DOM that the
20232 // KVO system will look for and update if the property changes.
20233 function bind(property, options, preserveContext, shouldDisplay, valueNormalizer) {
20234 var data = options.data,
20236 inverse = options.inverse,
20238 currentContext = this,
20239 pathRoot, path, normalized;
20241 normalized = normalizePath(currentContext, property, data);
20243 pathRoot = normalized.root;
20244 path = normalized.path;
20246 // Set up observers for observable objects
20247 if ('object' === typeof this) {
20248 // Create the view that will wrap the output of this template/property
20249 // and add it to the nearest view's childViews array.
20250 // See the documentation of Ember._HandlebarsBoundView for more.
20251 var bindView = view.createChildView(Ember._HandlebarsBoundView, {
20252 preserveContext: preserveContext,
20253 shouldDisplayFunc: shouldDisplay,
20254 valueNormalizerFunc: valueNormalizer,
20255 displayTemplate: fn,
20256 inverseTemplate: inverse,
20258 pathRoot: pathRoot,
20259 previousContext: currentContext,
20260 isEscaped: !options.hash.unescaped,
20261 templateData: options.data
20264 view.appendChild(bindView);
20266 var observer = function() {
20267 Ember.run.scheduleOnce('render', bindView, 'rerenderIfNeeded');
20270 // Observes the given property on the context and
20271 // tells the Ember._HandlebarsBoundView to re-render. If property
20272 // is an empty string, we are printing the current context
20273 // object ({{this}}) so updating it is not our responsibility.
20275 Ember.addObserver(pathRoot, path, observer);
20277 view.one('willClearRender', function() {
20278 Ember.removeObserver(pathRoot, path, observer);
20282 // The object is not observable, so just render it out and
20283 // be done with it.
20284 data.buffer.push(handlebarsGet(pathRoot, path, options));
20288 function simpleBind(property, options) {
20289 var data = options.data,
20291 currentContext = this,
20292 pathRoot, path, normalized;
20294 normalized = normalizePath(currentContext, property, data);
20296 pathRoot = normalized.root;
20297 path = normalized.path;
20299 // Set up observers for observable objects
20300 if ('object' === typeof this) {
20301 var bindView = Ember._SimpleHandlebarsView.create().setProperties({
20303 pathRoot: pathRoot,
20304 isEscaped: !options.hash.unescaped,
20305 previousContext: currentContext,
20306 templateData: options.data
20309 view.createChildView(bindView);
20310 view.appendChild(bindView);
20312 var observer = function() {
20313 Ember.run.scheduleOnce('render', bindView, 'rerender');
20316 // Observes the given property on the context and
20317 // tells the Ember._HandlebarsBoundView to re-render. If property
20318 // is an empty string, we are printing the current context
20319 // object ({{this}}) so updating it is not our responsibility.
20321 Ember.addObserver(pathRoot, path, observer);
20323 view.one('willClearRender', function() {
20324 Ember.removeObserver(pathRoot, path, observer);
20328 // The object is not observable, so just render it out and
20329 // be done with it.
20330 data.buffer.push(handlebarsGet(pathRoot, path, options));
20337 '_triageMustache' is used internally select between a binding and helper for
20338 the given context. Until this point, it would be hard to determine if the
20339 mustache is a property reference or a regular helper reference. This triage
20340 helper resolves that.
20342 This would not be typically invoked by directly.
20344 @method _triageMustache
20345 @for Ember.Handlebars.helpers
20346 @param {String} property Property/helperID to triage
20347 @param {Function} fn Context to provide for rendering
20348 @return {String} HTML string
20350 EmberHandlebars.registerHelper('_triageMustache', function(property, fn) {
20351 Ember.assert("You cannot pass more than one argument to the _triageMustache helper", arguments.length <= 2);
20352 if (helpers[property]) {
20353 return helpers[property].call(this, fn);
20356 return helpers.bind.apply(this, arguments);
20363 `bind` can be used to display a value, then update that value if it
20364 changes. For example, if you wanted to print the `title` property of
20368 {{bind "content.title"}}
20371 This will return the `title` property as a string, then create a new
20372 observer at the specified path. If it changes, it will update the value in
20373 DOM. Note that if you need to support IE7 and IE8 you must modify the
20374 model objects properties using Ember.get() and Ember.set() for this to work as
20375 it relies on Ember's KVO system. For all other browsers this will be handled
20376 for you automatically.
20379 @for Ember.Handlebars.helpers
20380 @param {String} property Property to bind
20381 @param {Function} fn Context to provide for rendering
20382 @return {String} HTML string
20384 EmberHandlebars.registerHelper('bind', function(property, options) {
20385 Ember.assert("You cannot pass more than one argument to the bind helper", arguments.length <= 2);
20387 var context = (options.contexts && options.contexts[0]) || this;
20390 return simpleBind.call(context, property, options);
20393 return bind.call(context, property, options, false, function(result) {
20394 return !Ember.none(result);
20401 Use the `boundIf` helper to create a conditional that re-evaluates
20402 whenever the bound value changes.
20405 {{#boundIf "content.shouldDisplayTitle"}}
20411 @for Ember.Handlebars.helpers
20412 @param {String} property Property to bind
20413 @param {Function} fn Context to provide for rendering
20414 @return {String} HTML string
20416 EmberHandlebars.registerHelper('boundIf', function(property, fn) {
20417 var context = (fn.contexts && fn.contexts[0]) || this;
20418 var func = function(result) {
20419 if (Ember.typeOf(result) === 'array') {
20420 return get(result, 'length') !== 0;
20426 return bind.call(context, property, fn, true, func, func);
20431 @for Ember.Handlebars.helpers
20432 @param {Function} context
20433 @param {Hash} options
20434 @return {String} HTML string
20436 EmberHandlebars.registerHelper('with', function(context, options) {
20437 if (arguments.length === 4) {
20438 var keywordName, path, rootPath, normalized;
20440 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");
20441 options = arguments[3];
20442 keywordName = arguments[2];
20443 path = arguments[0];
20445 Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
20447 if (Ember.isGlobalPath(path)) {
20448 Ember.bind(options.data.keywords, keywordName, path);
20450 normalized = normalizePath(this, path, options.data);
20451 path = normalized.path;
20452 rootPath = normalized.root;
20454 // This is a workaround for the fact that you cannot bind separate objects
20455 // together. When we implement that functionality, we should use it here.
20456 var contextKey = Ember.$.expando + Ember.guidFor(rootPath);
20457 options.data.keywords[contextKey] = rootPath;
20459 // if the path is '' ("this"), just bind directly to the current context
20460 var contextPath = path ? contextKey + '.' + path : contextKey;
20461 Ember.bind(options.data.keywords, keywordName, contextPath);
20464 return bind.call(this, path, options, true, function(result) {
20465 return !Ember.none(result);
20468 Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2);
20469 Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
20470 return helpers.bind.call(options.contexts[0], context, options);
20477 @for Ember.Handlebars.helpers
20478 @param {Function} context
20479 @param {Hash} options
20480 @return {String} HTML string
20482 EmberHandlebars.registerHelper('if', function(context, options) {
20483 Ember.assert("You must pass exactly one argument to the if helper", arguments.length === 2);
20484 Ember.assert("You must pass a block to the if helper", options.fn && options.fn !== Handlebars.VM.noop);
20486 return helpers.boundIf.call(options.contexts[0], context, options);
20491 @for Ember.Handlebars.helpers
20492 @param {Function} context
20493 @param {Hash} options
20494 @return {String} HTML string
20496 EmberHandlebars.registerHelper('unless', function(context, options) {
20497 Ember.assert("You must pass exactly one argument to the unless helper", arguments.length === 2);
20498 Ember.assert("You must pass a block to the unless helper", options.fn && options.fn !== Handlebars.VM.noop);
20500 var fn = options.fn, inverse = options.inverse;
20502 options.fn = inverse;
20503 options.inverse = fn;
20505 return helpers.boundIf.call(options.contexts[0], context, options);
20509 `bindAttr` allows you to create a binding between DOM element attributes and
20510 Ember objects. For example:
20513 <img {{bindAttr src="imageUrl" alt="imageTitle"}}>
20517 @for Ember.Handlebars.helpers
20518 @param {Hash} options
20519 @return {String} HTML string
20521 EmberHandlebars.registerHelper('bindAttr', function(options) {
20523 var attrs = options.hash;
20525 Ember.assert("You must specify at least one hash argument to bindAttr", !!Ember.keys(attrs).length);
20527 var view = options.data.view;
20531 // Generate a unique id for this element. This will be added as a
20532 // data attribute to the element so it can be looked up when
20533 // the bound property changes.
20534 var dataId = ++Ember.uuid;
20536 // Handle classes differently, as we can bind multiple classes
20537 var classBindings = attrs['class'];
20538 if (classBindings !== null && classBindings !== undefined) {
20539 var classResults = EmberHandlebars.bindClasses(this, classBindings, view, dataId, options);
20540 ret.push('class="' + Handlebars.Utils.escapeExpression(classResults.join(' ')) + '"');
20541 delete attrs['class'];
20544 var attrKeys = Ember.keys(attrs);
20546 // For each attribute passed, create an observer and emit the
20547 // current value of the property as an attribute.
20548 forEach.call(attrKeys, function(attr) {
20549 var path = attrs[attr],
20550 pathRoot, normalized;
20552 Ember.assert(fmt("You must provide a String for a bound attribute, not %@", [path]), typeof path === 'string');
20554 normalized = normalizePath(ctx, path, options.data);
20556 pathRoot = normalized.root;
20557 path = normalized.path;
20559 var value = (path === 'this') ? pathRoot : handlebarsGet(pathRoot, path, options),
20560 type = Ember.typeOf(value);
20562 Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [value]), value === null || value === undefined || type === 'number' || type === 'string' || type === 'boolean');
20564 var observer, invoker;
20566 observer = function observer() {
20567 var result = handlebarsGet(pathRoot, path, options);
20569 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');
20571 var elem = view.$("[data-bindattr-" + dataId + "='" + dataId + "']");
20573 // If we aren't able to find the element, it means the element
20574 // to which we were bound has been removed from the view.
20575 // In that case, we can assume the template has been re-rendered
20576 // and we need to clean up the observer.
20577 if (!elem || elem.length === 0) {
20578 Ember.removeObserver(pathRoot, path, invoker);
20582 Ember.View.applyAttributeBindings(elem, attr, result);
20585 invoker = function() {
20586 Ember.run.scheduleOnce('render', observer);
20589 // Add an observer to the view for when the property changes.
20590 // When the observer fires, find the element using the
20591 // unique data id and update the attribute to the new value.
20592 if (path !== 'this') {
20593 Ember.addObserver(pathRoot, path, invoker);
20595 view.one('willClearRender', function() {
20596 Ember.removeObserver(pathRoot, path, invoker);
20600 // if this changes, also change the logic in ember-views/lib/views/view.js
20601 if ((type === 'string' || (type === 'number' && !isNaN(value)))) {
20602 ret.push(attr + '="' + Handlebars.Utils.escapeExpression(value) + '"');
20603 } else if (value && type === 'boolean') {
20604 // The developer controls the attr name, so it should always be safe
20605 ret.push(attr + '="' + attr + '"');
20609 // Add the unique identifier
20610 // NOTE: We use all lower-case since Firefox has problems with mixed case in SVG
20611 ret.push('data-bindattr-' + dataId + '="' + dataId + '"');
20612 return new EmberHandlebars.SafeString(ret.join(' '));
20618 Helper that, given a space-separated string of property paths and a context,
20619 returns an array of class names. Calling this method also has the side
20620 effect of setting up observers at those property paths, such that if they
20621 change, the correct class name will be reapplied to the DOM element.
20623 For example, if you pass the string "fooBar", it will first look up the
20624 "fooBar" value of the context. If that value is true, it will add the
20625 "foo-bar" class to the current element (i.e., the dasherized form of
20626 "fooBar"). If the value is a string, it will add that string as the class.
20627 Otherwise, it will not add any new class name.
20629 @method bindClasses
20630 @for Ember.Handlebars
20631 @param {Ember.Object} context The context from which to lookup properties
20632 @param {String} classBindings A string, space-separated, of class bindings to use
20633 @param {Ember.View} view The view in which observers should look for the element to update
20634 @param {Srting} bindAttrId Optional bindAttr id used to lookup elements
20635 @return {Array} An array of class names to add
20637 EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, options) {
20638 var ret = [], newClass, value, elem;
20640 // Helper method to retrieve the property from the context and
20641 // determine which class string to return, based on whether it is
20642 // a Boolean or not.
20643 var classStringForPath = function(root, parsedPath, options) {
20645 path = parsedPath.path;
20647 if (path === 'this') {
20649 } else if (path === '') {
20652 val = handlebarsGet(root, path, options);
20655 return Ember.View._classStringForValue(path, val, parsedPath.className, parsedPath.falsyClassName);
20658 // For each property passed, loop through and setup
20660 forEach.call(classBindings.split(' '), function(binding) {
20662 // Variable in which the old class value is saved. The observer function
20663 // closes over this variable, so it knows which string to remove when
20664 // the property changes.
20667 var observer, invoker;
20669 var parsedPath = Ember.View._parsePropertyPath(binding),
20670 path = parsedPath.path,
20671 pathRoot = context,
20674 if (path !== '' && path !== 'this') {
20675 normalized = normalizePath(context, path, options.data);
20677 pathRoot = normalized.root;
20678 path = normalized.path;
20681 // Set up an observer on the context. If the property changes, toggle the
20683 observer = function() {
20684 // Get the current value of the property
20685 newClass = classStringForPath(pathRoot, parsedPath, options);
20686 elem = bindAttrId ? view.$("[data-bindattr-" + bindAttrId + "='" + bindAttrId + "']") : view.$();
20688 // If we can't find the element anymore, a parent template has been
20689 // re-rendered and we've been nuked. Remove the observer.
20690 if (!elem || elem.length === 0) {
20691 Ember.removeObserver(pathRoot, path, invoker);
20693 // If we had previously added a class to the element, remove it.
20695 elem.removeClass(oldClass);
20698 // If necessary, add a new class. Make sure we keep track of it so
20699 // it can be removed in the future.
20701 elem.addClass(newClass);
20702 oldClass = newClass;
20709 invoker = function() {
20710 Ember.run.scheduleOnce('render', observer);
20713 if (path !== '' && path !== 'this') {
20714 Ember.addObserver(pathRoot, path, invoker);
20716 view.one('willClearRender', function() {
20717 Ember.removeObserver(pathRoot, path, invoker);
20721 // We've already setup the observer; now we just need to figure out the
20722 // correct behavior right now on the first pass through.
20723 value = classStringForPath(pathRoot, parsedPath, options);
20728 // Make sure we save the current value so that it can be removed if the
20743 /*globals Handlebars */
20745 // TODO: Don't require the entire module
20748 @submodule ember-handlebars
20751 var get = Ember.get, set = Ember.set;
20752 var PARENT_VIEW_PATH = /^parentView\./;
20753 var EmberHandlebars = Ember.Handlebars;
20755 EmberHandlebars.ViewHelper = Ember.Object.create({
20757 propertiesFromHTMLOptions: function(options, thisContext) {
20758 var hash = options.hash, data = options.data;
20759 var extensions = {},
20760 classes = hash['class'],
20764 extensions.elementId = hash.id;
20769 classes = classes.split(' ');
20770 extensions.classNames = classes;
20774 if (hash.classBinding) {
20775 extensions.classNameBindings = hash.classBinding.split(' ');
20779 if (hash.classNameBindings) {
20780 if (extensions.classNameBindings === undefined) extensions.classNameBindings = [];
20781 extensions.classNameBindings = extensions.classNameBindings.concat(hash.classNameBindings.split(' '));
20785 if (hash.attributeBindings) {
20786 Ember.assert("Setting 'attributeBindings' via Handlebars is not allowed. Please subclass Ember.View and set it there instead.");
20787 extensions.attributeBindings = null;
20792 hash = Ember.$.extend({}, hash);
20794 delete hash['class'];
20795 delete hash.classBinding;
20798 // Set the proper context for all bindings passed to the helper. This applies to regular attribute bindings
20799 // as well as class name bindings. If the bindings are local, make them relative to the current context
20800 // instead of the view.
20803 // Evaluate the context of regular attribute bindings:
20804 for (var prop in hash) {
20805 if (!hash.hasOwnProperty(prop)) { continue; }
20807 // Test if the property ends in "Binding"
20808 if (Ember.IS_BINDING.test(prop) && typeof hash[prop] === 'string') {
20809 path = this.contextualizeBindingPath(hash[prop], data);
20810 if (path) { hash[prop] = path; }
20814 // Evaluate the context of class name bindings:
20815 if (extensions.classNameBindings) {
20816 for (var b in extensions.classNameBindings) {
20817 var full = extensions.classNameBindings[b];
20818 if (typeof full === 'string') {
20819 // Contextualize the path of classNameBinding so this:
20821 // classNameBinding="isGreen:green"
20823 // is converted to this:
20825 // classNameBinding="bindingContext.isGreen:green"
20826 var parsedPath = Ember.View._parsePropertyPath(full);
20827 path = this.contextualizeBindingPath(parsedPath.path, data);
20828 if (path) { extensions.classNameBindings[b] = path + parsedPath.classNames; }
20833 // Make the current template context available to the view
20834 // for the bindings set up above.
20835 extensions.bindingContext = thisContext;
20837 return Ember.$.extend(hash, extensions);
20840 // Transform bindings from the current context to a context that can be evaluated within the view.
20841 // Returns null if the path shouldn't be changed.
20843 // TODO: consider the addition of a prefix that would allow this method to return `path`.
20844 contextualizeBindingPath: function(path, data) {
20845 var normalized = Ember.Handlebars.normalizePath(null, path, data);
20846 if (normalized.isKeyword) {
20847 return 'templateData.keywords.' + path;
20848 } else if (Ember.isGlobalPath(path)) {
20850 } else if (path === 'this') {
20851 return 'bindingContext';
20853 return 'bindingContext.' + path;
20857 helper: function(thisContext, path, options) {
20858 var inverse = options.inverse,
20859 data = options.data,
20862 hash = options.hash,
20865 if ('string' === typeof path) {
20866 newView = EmberHandlebars.get(thisContext, path, options);
20867 Ember.assert("Unable to find view at path '" + path + "'", !!newView);
20872 Ember.assert(Ember.String.fmt('You must pass a view class to the #view helper, not %@ (%@)', [path, newView]), Ember.View.detect(newView));
20874 var viewOptions = this.propertiesFromHTMLOptions(options, thisContext);
20875 var currentView = data.view;
20876 viewOptions.templateData = options.data;
20879 Ember.assert("You cannot provide a template block if you also specified a templateName", !get(viewOptions, 'templateName') && !get(newView.proto(), 'templateName'));
20880 viewOptions.template = fn;
20883 // We only want to override the `_context` computed property if there is
20884 // no specified controller. See View#_context for more information.
20885 if (!newView.proto().controller && !newView.proto().controllerBinding && !viewOptions.controller && !viewOptions.controllerBinding) {
20886 viewOptions._context = thisContext;
20889 currentView.appendChild(newView, viewOptions);
20894 `{{view}}` inserts a new instance of `Ember.View` into a template passing its options
20895 to the `Ember.View`'s `create` method and using the supplied block as the view's own template.
20897 An empty `<body>` and the following template:
20900 <script type="text/x-handlebars">
20902 {{#view tagName="span"}}
20908 Will result in HTML structure:
20912 <!-- Note: the handlebars template script
20913 also results in a rendered Ember.View
20914 which is the outer <div> here -->
20916 <div class="ember-view">
20918 <span id="ember1" class="ember-view">
20925 ### parentView setting
20927 The `parentView` property of the new `Ember.View` instance created through `{{view}}`
20928 will be set to the `Ember.View` instance of the template where `{{view}}` was called.
20931 aView = Ember.View.create({
20932 template: Ember.Handlebars.compile("{{#view}} my parent: {{parentView.elementId}} {{/view}}")
20935 aView.appendTo('body');
20938 Will result in HTML structure:
20941 <div id="ember1" class="ember-view">
20942 <div id="ember2" class="ember-view">
20948 ### Setting CSS id and class attributes
20950 The HTML `id` attribute can be set on the `{{view}}`'s resulting element with the `id` option.
20951 This option will _not_ be passed to `Ember.View.create`.
20954 <script type="text/x-handlebars">
20955 {{#view tagName="span" id="a-custom-id"}}
20961 Results in the following HTML structure:
20964 <div class="ember-view">
20965 <span id="a-custom-id" class="ember-view">
20971 The HTML `class` attribute can be set on the `{{view}}`'s resulting element with
20972 the `class` or `classNameBindings` options. The `class` option
20973 will directly set the CSS `class` attribute and will not be passed to
20974 `Ember.View.create`. `classNameBindings` will be passed to `create` and use
20975 `Ember.View`'s class name binding functionality:
20978 <script type="text/x-handlebars">
20979 {{#view tagName="span" class="a-custom-class"}}
20985 Results in the following HTML structure:
20988 <div class="ember-view">
20989 <span id="ember2" class="ember-view a-custom-class">
20995 ### Supplying a different view class
20997 `{{view}}` can take an optional first argument before its supplied options to specify a
20998 path to a custom view class.
21001 <script type="text/x-handlebars">
21002 {{#view "MyApp.CustomView"}}
21008 The first argument can also be a relative path. Ember will search for the view class
21009 starting at the `Ember.View` of the template where `{{view}}` was used as the root object:
21012 MyApp = Ember.Application.create({});
21013 MyApp.OuterView = Ember.View.extend({
21014 innerViewClass: Ember.View.extend({
21015 classNames: ['a-custom-view-class-as-property']
21017 template: Ember.Handlebars.compile('{{#view "innerViewClass"}} hi {{/view}}')
21020 MyApp.OuterView.create().appendTo('body');
21023 Will result in the following HTML:
21026 <div id="ember1" class="ember-view">
21027 <div id="ember2" class="ember-view a-custom-view-class-as-property">
21035 If you supply a custom `Ember.View` subclass that specifies its own template
21036 or provide a `templateName` option to `{{view}}` it can be used without supplying a block.
21037 Attempts to use both a `templateName` option and supply a block will throw an error.
21040 <script type="text/x-handlebars">
21041 {{view "MyApp.ViewWithATemplateDefined"}}
21045 ### viewName property
21047 You can supply a `viewName` option to `{{view}}`. The `Ember.View` instance will
21048 be referenced as a property of its parent view by this name.
21051 aView = Ember.View.create({
21052 template: Ember.Handlebars.compile('{{#view viewName="aChildByName"}} hi {{/view}}')
21055 aView.appendTo('body');
21056 aView.get('aChildByName') // the instance of Ember.View created by {{view}} helper
21060 @for Ember.Handlebars.helpers
21061 @param {String} path
21062 @param {Hash} options
21063 @return {String} HTML string
21065 EmberHandlebars.registerHelper('view', function(path, options) {
21066 Ember.assert("The view helper only takes a single argument", arguments.length <= 2);
21068 // If no path is provided, treat path param as options.
21069 if (path && path.data && path.data.isRenderData) {
21071 path = "Ember.View";
21074 return EmberHandlebars.ViewHelper.helper(this, path, options);
21083 /*globals Handlebars */
21085 // TODO: Don't require all of this module
21088 @submodule ember-handlebars
21091 var get = Ember.get, handlebarsGet = Ember.Handlebars.get, fmt = Ember.String.fmt;
21094 `{{collection}}` is a `Ember.Handlebars` helper for adding instances of
21095 `Ember.CollectionView` to a template. See `Ember.CollectionView` for additional
21096 information on how a `CollectionView` functions.
21098 `{{collection}}`'s primary use is as a block helper with a `contentBinding` option
21099 pointing towards an `Ember.Array`-compatible object. An `Ember.View` instance will
21100 be created for each item in its `content` property. Each view will have its own
21101 `content` property set to the appropriate item in the collection.
21103 The provided block will be applied as the template for each item's view.
21105 Given an empty `<body>` the following template:
21108 <script type="text/x-handlebars">
21109 {{#collection contentBinding="App.items"}}
21110 Hi {{view.content.name}}
21115 And the following application code
21118 App = Ember.Application.create()
21120 Ember.Object.create({name: 'Dave'}),
21121 Ember.Object.create({name: 'Mary'}),
21122 Ember.Object.create({name: 'Sara'})
21126 Will result in the HTML structure below
21129 <div class="ember-view">
21130 <div class="ember-view">Hi Dave</div>
21131 <div class="ember-view">Hi Mary</div>
21132 <div class="ember-view">Hi Sara</div>
21137 If you provide an `itemViewClass` option that has its own `template` you can omit
21140 The following template:
21143 <script type="text/x-handlebars">
21144 {{collection contentBinding="App.items" itemViewClass="App.AnItemView"}}
21148 And application code
21151 App = Ember.Application.create();
21153 Ember.Object.create({name: 'Dave'}),
21154 Ember.Object.create({name: 'Mary'}),
21155 Ember.Object.create({name: 'Sara'})
21158 App.AnItemView = Ember.View.extend({
21159 template: Ember.Handlebars.compile("Greetings {{view.content.name}}")
21163 Will result in the HTML structure below
21166 <div class="ember-view">
21167 <div class="ember-view">Greetings Dave</div>
21168 <div class="ember-view">Greetings Mary</div>
21169 <div class="ember-view">Greetings Sara</div>
21173 ### Specifying a CollectionView subclass
21175 By default the `{{collection}}` helper will create an instance of `Ember.CollectionView`.
21176 You can supply a `Ember.CollectionView` subclass to the helper by passing it
21177 as the first argument:
21180 <script type="text/x-handlebars">
21181 {{#collection App.MyCustomCollectionClass contentBinding="App.items"}}
21182 Hi {{view.content.name}}
21188 ### Forwarded `item.*`-named Options
21190 As with the `{{view}}`, helper options passed to the `{{collection}}` will be set on
21191 the resulting `Ember.CollectionView` as properties. Additionally, options prefixed with
21192 `item` will be applied to the views rendered for each item (note the camelcasing):
21195 <script type="text/x-handlebars">
21196 {{#collection contentBinding="App.items"
21198 itemClassNames="greeting"}}
21199 Howdy {{view.content.name}}
21204 Will result in the following HTML structure:
21207 <div class="ember-view">
21208 <p class="ember-view greeting">Howdy Dave</p>
21209 <p class="ember-view greeting">Howdy Mary</p>
21210 <p class="ember-view greeting">Howdy Sara</p>
21215 @for Ember.Handlebars.helpers
21216 @param {String} path
21217 @param {Hash} options
21218 @return {String} HTML string
21219 @deprecated Use `{{each}}` helper instead.
21221 Ember.Handlebars.registerHelper('collection', function(path, options) {
21222 Ember.deprecate("Using the {{collection}} helper without specifying a class has been deprecated as the {{each}} helper now supports the same functionality.", path !== 'collection');
21224 // If no path is provided, treat path param as options.
21225 if (path && path.data && path.data.isRenderData) {
21228 Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 1);
21230 Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 2);
21233 var fn = options.fn;
21234 var data = options.data;
21235 var inverse = options.inverse;
21237 // If passed a path string, convert that into an object.
21238 // Otherwise, just default to the standard class.
21239 var collectionClass;
21240 collectionClass = path ? handlebarsGet(this, path, options) : Ember.CollectionView;
21241 Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass);
21243 var hash = options.hash, itemHash = {}, match;
21245 // Extract item view class if provided else default to the standard class
21246 var itemViewClass, itemViewPath = hash.itemViewClass;
21247 var collectionPrototype = collectionClass.proto();
21248 delete hash.itemViewClass;
21249 itemViewClass = itemViewPath ? handlebarsGet(collectionPrototype, itemViewPath, options) : collectionPrototype.itemViewClass;
21250 Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewPath]), !!itemViewClass);
21252 // Go through options passed to the {{collection}} helper and extract options
21253 // that configure item views instead of the collection itself.
21254 for (var prop in hash) {
21255 if (hash.hasOwnProperty(prop)) {
21256 match = prop.match(/^item(.)(.*)$/);
21259 // Convert itemShouldFoo -> shouldFoo
21260 itemHash[match[1].toLowerCase() + match[2]] = hash[prop];
21261 // Delete from hash as this will end up getting passed to the
21262 // {{view}} helper method.
21268 var tagName = hash.tagName || collectionPrototype.tagName;
21271 itemHash.template = fn;
21275 var emptyViewClass;
21276 if (inverse && inverse !== Handlebars.VM.noop) {
21277 emptyViewClass = get(collectionPrototype, 'emptyViewClass');
21278 emptyViewClass = emptyViewClass.extend({
21280 tagName: itemHash.tagName
21282 } else if (hash.emptyViewClass) {
21283 emptyViewClass = handlebarsGet(this, hash.emptyViewClass, options);
21285 hash.emptyView = emptyViewClass;
21287 if (hash.eachHelper === 'each') {
21288 itemHash._context = Ember.computed(function() {
21289 return get(this, 'content');
21290 }).property('content');
21291 delete hash.eachHelper;
21294 var viewOptions = Ember.Handlebars.ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this);
21295 hash.itemViewClass = itemViewClass.extend(viewOptions);
21297 return Ember.Handlebars.helpers.view.call(this, collectionClass, options);
21306 /*globals Handlebars */
21309 @submodule ember-handlebars
21312 var handlebarsGet = Ember.Handlebars.get;
21315 `unbound` allows you to output a property without binding. *Important:* The
21316 output will not be updated if the property changes. Use with caution.
21319 <div>{{unbound somePropertyThatDoesntChange}}</div>
21323 @for Ember.Handlebars.helpers
21324 @param {String} property
21325 @return {String} HTML string
21327 Ember.Handlebars.registerHelper('unbound', function(property, fn) {
21328 var context = (fn.contexts && fn.contexts[0]) || this;
21329 return handlebarsGet(context, property, fn);
21337 /*jshint debug:true*/
21340 @submodule ember-handlebars
21343 var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
21346 `log` allows you to output the value of a value in the current rendering
21354 @for Ember.Handlebars.helpers
21355 @param {String} property
21357 Ember.Handlebars.registerHelper('log', function(property, options) {
21358 var context = (options.contexts && options.contexts[0]) || this,
21359 normalized = normalizePath(context, property, options.data),
21360 pathRoot = normalized.root,
21361 path = normalized.path,
21362 value = (path === 'this') ? pathRoot : handlebarsGet(pathRoot, path, options);
21363 Ember.Logger.log(value);
21367 The `debugger` helper executes the `debugger` statement in the current
21375 @for Ember.Handlebars.helpers
21376 @param {String} property
21378 Ember.Handlebars.registerHelper('debugger', function() {
21389 @submodule ember-handlebars
21392 var get = Ember.get, set = Ember.set;
21394 Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, {
21395 itemViewClass: Ember._MetamorphView,
21396 emptyViewClass: Ember._MetamorphView,
21398 createChildView: function(view, attrs) {
21399 view = this._super(view, attrs);
21401 // At the moment, if a container view subclass wants
21402 // to insert keywords, it is responsible for cloning
21403 // the keywords hash. This will be fixed momentarily.
21404 var keyword = get(this, 'keyword');
21407 var data = get(view, 'templateData');
21409 data = Ember.copy(data);
21410 data.keywords = view.cloneKeywords();
21411 set(view, 'templateData', data);
21413 var content = get(view, 'content');
21415 // In this case, we do not bind, because the `content` of
21416 // a #each item cannot change.
21417 data.keywords[keyword] = content;
21425 The `{{#each}}` helper loops over elements in a collection, rendering its block once for each item:
21428 Developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}];
21432 {{#each Developers}}
21437 `{{each}}` supports an alternative syntax with element naming:
21440 {{#each person in Developers}}
21445 When looping over objects that do not have properties, `{{this}}` can be used to render the object:
21448 DeveloperNames = ['Yehuda', 'Tom', 'Paul']
21452 {{#each DeveloperNames}}
21459 If you provide an `itemViewClass` option that has its own `template` you can omit
21460 the block in a similar way to how it can be done with the collection helper.
21462 The following template:
21465 <script type="text/x-handlebars">
21466 {{#view App.MyView }}
21467 {{each view.items itemViewClass="App.AnItemView"}}
21472 And application code
21475 App = Ember.Application.create({
21476 MyView: Ember.View.extend({
21478 Ember.Object.create({name: 'Dave'}),
21479 Ember.Object.create({name: 'Mary'}),
21480 Ember.Object.create({name: 'Sara'})
21485 App.AnItemView = Ember.View.extend({
21486 template: Ember.Handlebars.compile("Greetings {{name}}")
21492 Will result in the HTML structure below
21495 <div class="ember-view">
21496 <div class="ember-view">Greetings Dave</div>
21497 <div class="ember-view">Greetings Mary</div>
21498 <div class="ember-view">Greetings Sara</div>
21504 @for Ember.Handlebars.helpers
21505 @param [name] {String} name for item (used with `in`)
21506 @param path {String} path
21508 Ember.Handlebars.registerHelper('each', function(path, options) {
21509 if (arguments.length === 4) {
21510 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");
21512 var keywordName = arguments[0];
21514 options = arguments[3];
21515 path = arguments[2];
21516 if (path === '') { path = "this"; }
21518 options.hash.keyword = keywordName;
21520 options.hash.eachHelper = 'each';
21523 options.hash.contentBinding = path;
21524 // Set up emptyView as a metamorph with no tag
21525 //options.hash.emptyViewClass = Ember._MetamorphView;
21527 return Ember.Handlebars.helpers.collection.call(this, 'Ember.Handlebars.EachView', options);
21537 @submodule ember-handlebars
21541 `template` allows you to render a template from inside another template.
21542 This allows you to re-use the same template in multiple places. For example:
21545 <script type="text/x-handlebars">
21546 {{#with loggedInUser}}
21547 Last Login: {{lastLogin}}
21548 User Info: {{template "user_info"}}
21552 <script type="text/x-handlebars" data-template-name="user_info">
21553 Name: <em>{{name}}</em>
21554 Karma: <em>{{karma}}</em>
21558 This helper looks for templates in the global Ember.TEMPLATES hash. If you
21559 add <script> tags to your page with the `data-template-name` attribute set,
21560 they will be compiled and placed in this hash automatically.
21562 You can also manually register templates by adding them to the hash:
21565 Ember.TEMPLATES["my_cool_template"] = Ember.Handlebars.compile('<b>{{user}}</b>');
21569 @for Ember.Handlebars.helpers
21570 @param {String} templateName the template to render
21573 Ember.Handlebars.registerHelper('template', function(name, options) {
21574 var template = Ember.TEMPLATES[name];
21576 Ember.assert("Unable to find template with name '"+name+"'.", !!template);
21578 Ember.TEMPLATES[name](this, { data: options.data });
21588 @submodule ember-handlebars
21591 var EmberHandlebars = Ember.Handlebars,
21592 handlebarsGet = EmberHandlebars.get,
21594 a_slice = Array.prototype.slice;
21596 var ActionHelper = EmberHandlebars.ActionHelper = {
21597 registeredActions: {}
21600 ActionHelper.registerAction = function(actionName, options) {
21601 var actionId = (++Ember.uuid).toString();
21603 ActionHelper.registeredActions[actionId] = {
21604 eventName: options.eventName,
21605 handler: function(event) {
21606 var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey,
21607 secondaryClick = event.which > 1, // IE9 may return undefined
21608 nonStandard = modifier || secondaryClick;
21610 if (options.link && nonStandard) {
21611 // Allow the browser to handle special link clicks normally
21615 event.preventDefault();
21617 event.view = options.view;
21619 if (options.hasOwnProperty('context')) {
21620 event.context = options.context;
21623 if (options.hasOwnProperty('contexts')) {
21624 event.contexts = options.contexts;
21627 var target = options.target;
21629 // Check for StateManager (or compatible object)
21630 if (target.isState && typeof target.send === 'function') {
21631 return target.send(actionName, event);
21633 Ember.assert(Ember.String.fmt('Target %@ does not have action %@', [target, actionName]), target[actionName]);
21634 return target[actionName].call(target, event);
21639 options.view.on('willClearRender', function() {
21640 delete ActionHelper.registeredActions[actionId];
21647 The `{{action}}` helper registers an HTML element within a template for
21648 DOM event handling and forwards that interaction to the view's `controller.target`
21649 or supplied `target` option (see 'Specifying a Target'). By default the
21650 `controller.target` is set to the Application's router.
21652 User interaction with that element will invoke the supplied action name on
21653 the appropriate target.
21655 Given the following Handlebars template on the page
21658 <script type="text/x-handlebars" data-template-name='a-template'>
21659 <div {{action anActionName target="view"}}>
21665 And application code
21668 AView = Ember.View.extend({
21669 templateName: 'a-template',
21670 anActionName: function(event){}
21673 aView = AView.create();
21674 aView.appendTo('body');
21677 Will results in the following rendered HTML
21680 <div class="ember-view">
21681 <div data-ember-action="1">
21687 Clicking "click me" will trigger the `anActionName` method of the `aView`
21688 object with a `jQuery.Event` object as its argument. The `jQuery.Event`
21689 object will be extended to include a `view` property that is set to the
21690 original view interacted with (in this case the `aView` object).
21692 ### Event Propagation
21694 Events triggered through the action helper will automatically have
21695 `.preventDefault()` called on them. You do not need to do so in your event
21696 handlers. To stop propagation of the event, simply return `false` from your
21699 If you need the default handler to trigger you should either register your
21700 own event handler, or use event methods on your view class. See Ember.View
21701 'Responding to Browser Events' for more information.
21703 ### Specifying DOM event type
21705 By default the `{{action}}` helper registers for DOM `click` events. You can
21706 supply an `on` option to the helper to specify a different DOM event name:
21709 <script type="text/x-handlebars" data-template-name='a-template'>
21710 <div {{action anActionName on="doubleClick"}}>
21716 See Ember.View 'Responding to Browser Events' for a list of
21717 acceptable DOM event names.
21719 Because `{{action}}` depends on Ember's event dispatch system it will only
21720 function if an `Ember.EventDispatcher` instance is available. An
21721 `Ember.EventDispatcher` instance will be created when a new
21722 `Ember.Application` is created. Having an instance of `Ember.Application`
21723 will satisfy this requirement.
21726 ### Specifying a Target
21728 There are several possible target objects for `{{action}}` helpers:
21730 In a typical `Ember.Router`-backed Application where views are managed
21731 through use of the `{{outlet}}` helper, actions will be forwarded to the
21732 current state of the Applications's Router. See Ember.Router 'Responding
21733 to User-initiated Events' for more information.
21735 If you manually set the `target` property on the controller of a template's
21736 `Ember.View` instance, the specifed `controller.target` will become the target
21737 for any actions. Likely custom values for a controller's `target` are the
21738 controller itself or a StateManager other than the Application's Router.
21740 If the templates's view lacks a controller property the view itself is the target.
21742 Finally, a `target` option can be provided to the helper to change which object
21743 will receive the method call. This option must be a string representing a
21747 <script type="text/x-handlebars" data-template-name='a-template'>
21748 <div {{action anActionName target="MyApplication.someObject"}}>
21754 Clicking "click me" in the rendered HTML of the above template will trigger
21755 the `anActionName` method of the object at `MyApplication.someObject`.
21756 The first argument to this method will be a `jQuery.Event` extended to
21757 include a `view` property that is set to the original view interacted with.
21759 A path relative to the template's `Ember.View` instance can also be used as
21763 <script type="text/x-handlebars" data-template-name='a-template'>
21764 <div {{action anActionName target="parentView"}}>
21770 Clicking "click me" in the rendered HTML of the above template will trigger
21771 the `anActionName` method of the view's parent view.
21773 The `{{action}}` helper is `Ember.StateManager` aware. If the target of the
21774 action is an `Ember.StateManager` instance `{{action}}` will use the `send`
21775 functionality of StateManagers. The documentation for `Ember.StateManager`
21776 has additional information about this use.
21778 If an action's target does not implement a method that matches the supplied
21779 action name an error will be thrown.
21782 <script type="text/x-handlebars" data-template-name='a-template'>
21783 <div {{action aMethodNameThatIsMissing}}>
21789 With the following application code
21792 AView = Ember.View.extend({
21793 templateName; 'a-template',
21794 // note: no method 'aMethodNameThatIsMissing'
21795 anActionName: function(event){}
21798 aView = AView.create();
21799 aView.appendTo('body');
21802 Will throw `Uncaught TypeError: Cannot call method 'call' of undefined` when
21803 "click me" is clicked.
21805 ### Specifying a context
21807 You may optionally specify objects to pass as contexts to the `{{action}}` helper
21808 by providing property paths as the subsequent parameters. These objects are made
21809 available as the `contexts` (also `context` if there is only one) properties in the
21810 `jQuery.Event` object:
21813 <script type="text/x-handlebars" data-template-name='a-template'>
21814 {{#each person in people}}
21815 <div {{action edit person}}>
21822 Clicking "click me" will trigger the `edit` method of the view's context with a
21823 `jQuery.Event` object containing the person object as its context.
21826 @for Ember.Handlebars.helpers
21827 @param {String} actionName
21828 @param {Object...} contexts
21829 @param {Hash} options
21831 EmberHandlebars.registerHelper('action', function(actionName) {
21832 var options = arguments[arguments.length - 1],
21833 contexts = a_slice.call(arguments, 1, -1);
21835 var hash = options.hash,
21836 view = options.data.view,
21837 target, controller, link;
21839 // create a hash to pass along to registerAction
21841 eventName: hash.on || "click"
21844 action.view = view = get(view, 'concreteView');
21847 target = handlebarsGet(this, hash.target, options);
21848 } else if (controller = options.data.keywords.controller) {
21849 target = get(controller, 'target');
21852 action.target = target = target || view;
21854 if (contexts.length) {
21855 action.contexts = contexts = Ember.EnumerableUtils.map(contexts, function(context) {
21856 return handlebarsGet(this, context, options);
21858 action.context = contexts[0];
21861 var output = [], url;
21863 if (hash.href && target.urlForEvent) {
21864 url = target.urlForEvent.apply(target, [actionName].concat(contexts));
21865 output.push('href="' + url + '"');
21866 action.link = true;
21869 var actionId = ActionHelper.registerAction(actionName, action);
21870 output.push('data-ember-action="' + actionId + '"');
21872 return new EmberHandlebars.SafeString(output.join(" "));
21882 @submodule ember-handlebars
21885 var get = Ember.get, set = Ember.set;
21889 When used in a Handlebars template that is assigned to an `Ember.View` instance's
21890 `layout` property Ember will render the layout template first, inserting the view's
21891 own rendered output at the `{{ yield }}` location.
21893 An empty `<body>` and the following application code:
21896 AView = Ember.View.extend({
21897 classNames: ['a-view-with-layout'],
21898 layout: Ember.Handlebars.compile('<div class="wrapper">{{ yield }}</div>'),
21899 template: Ember.Handlebars.compile('<span>I am wrapped</span>')
21902 aView = AView.create();
21903 aView.appendTo('body');
21906 Will result in the following HTML output:
21910 <div class='ember-view a-view-with-layout'>
21911 <div class="wrapper">
21912 <span>I am wrapped</span>
21918 The yield helper cannot be used outside of a template assigned to an `Ember.View`'s `layout` property
21919 and will throw an error if attempted.
21922 BView = Ember.View.extend({
21923 classNames: ['a-view-with-layout'],
21924 template: Ember.Handlebars.compile('{{yield}}')
21927 bView = BView.create();
21928 bView.appendTo('body');
21931 // Uncaught Error: assertion failed: You called yield in a template that was not a layout
21935 @for Ember.Handlebars.helpers
21936 @param {Hash} options
21937 @return {String} HTML string
21939 Ember.Handlebars.registerHelper('yield', function(options) {
21940 var view = options.data.view, template;
21942 while (view && !get(view, 'layout')) {
21943 view = get(view, 'parentView');
21946 Ember.assert("You called yield in a template that was not a layout", !!view);
21948 template = get(view, 'template');
21950 if (template) { template(this, options); }
21960 @submodule ember-handlebars
21963 Ember.Handlebars.OutletView = Ember.ContainerView.extend(Ember._Metamorph);
21966 The `outlet` helper allows you to specify that the current
21967 view's controller will fill in the view for a given area.
21973 By default, when the the current controller's `view` property changes, the
21974 outlet will replace its current view with the new view. You can set the
21975 `view` property directly, but it's normally best to use `connectOutlet`.
21978 # Instantiate App.PostsView and assign to `view`, so as to render into outlet.
21979 controller.connectOutlet('posts');
21982 You can also specify a particular name other than `view`:
21985 {{outlet masterView}}
21986 {{outlet detailView}}
21989 Then, you can control several outlets from a single controller.
21992 # Instantiate App.PostsView and assign to controller.masterView.
21993 controller.connectOutlet('masterView', 'posts');
21994 # Also, instantiate App.PostInfoView and assign to controller.detailView.
21995 controller.connectOutlet('detailView', 'postInfo');
21999 @for Ember.Handlebars.helpers
22000 @param {String} property the property on the controller
22001 that holds the view for this outlet
22003 Ember.Handlebars.registerHelper('outlet', function(property, options) {
22004 if (property && property.data && property.data.isRenderData) {
22005 options = property;
22009 options.hash.currentViewBinding = "view.context." + property;
22011 return Ember.Handlebars.helpers.view.call(this, Ember.Handlebars.OutletView, options);
22033 @submodule ember-handlebars
22036 var set = Ember.set, get = Ember.get;
22039 The `Ember.Checkbox` view class renders a checkbox [input](https://developer.mozilla.org/en/HTML/Element/Input)
22040 element. It allows for binding an Ember property (`checked`) to the status of the checkbox.
22045 {{view Ember.Checkbox checkedBinding="receiveEmail"}}
22048 You can add a `label` tag yourself in the template where the Ember.Checkbox is being used.
22052 {{view Ember.Checkbox classNames="applicaton-specific-checkbox"}}
22058 The `checked` attribute of an Ember.Checkbox object should always be set
22059 through the Ember object or by interacting with its rendered element representation
22060 via the mouse, keyboard, or touch. Updating the value of the checkbox via jQuery will
22061 result in the checked value of the object and its element losing synchronization.
22063 ## Layout and LayoutName properties
22064 Because HTML `input` elements are self closing `layout` and `layoutName` properties will
22065 not be applied. See `Ember.View`'s layout section for more information.
22069 @extends Ember.View
22071 Ember.Checkbox = Ember.View.extend({
22072 classNames: ['ember-checkbox'],
22076 attributeBindings: ['type', 'checked', 'disabled', 'tabindex'],
22084 this.on("change", this, this._updateElementValue);
22087 _updateElementValue: function() {
22088 set(this, 'checked', this.$().prop('checked'));
22099 @submodule ember-handlebars
22102 var get = Ember.get, set = Ember.set;
22105 Shared mixin used by Ember.TextField and Ember.TextArea.
22109 @extends Ember.Mixin
22112 Ember.TextSupport = Ember.Mixin.create({
22115 attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex'],
22120 insertNewline: Ember.K,
22125 this.on("focusOut", this, this._elementValueDidChange);
22126 this.on("change", this, this._elementValueDidChange);
22127 this.on("keyUp", this, this.interpretKeyEvents);
22130 interpretKeyEvents: function(event) {
22131 var map = Ember.TextSupport.KEY_EVENTS;
22132 var method = map[event.keyCode];
22134 this._elementValueDidChange();
22135 if (method) { return this[method](event); }
22138 _elementValueDidChange: function() {
22139 set(this, 'value', this.$().val());
22144 Ember.TextSupport.KEY_EVENTS = {
22145 13: 'insertNewline',
22156 @submodule ember-handlebars
22159 var get = Ember.get, set = Ember.set;
22162 The `Ember.TextField` view class renders a text
22163 [input](https://developer.mozilla.org/en/HTML/Element/Input) element. It
22164 allows for binding Ember properties to the text field contents (`value`),
22165 live-updating as the user inputs text.
22170 {{view Ember.TextField valueBinding="firstName"}}
22173 ## Layout and LayoutName properties
22174 Because HTML `input` elements are self closing `layout` and `layoutName` properties will
22175 not be applied. See `Ember.View`'s layout section for more information.
22179 By default `Ember.TextField` provides support for `type`, `value`, `size`, `placeholder`,
22180 `disabled`, `maxlength` and `tabindex` attributes on a textarea. If you need to support
22181 more attributes have a look at the `attributeBindings` property in `Ember.View`'s
22182 HTML Attributes section.
22184 To globally add support for additional attributes you can reopen `Ember.TextField` or
22185 `Ember.TextSupport`.
22188 Ember.TextSupport.reopen({
22189 attributeBindings: ["required"]
22195 @extends Ember.View
22196 @uses Ember.TextSupport
22198 Ember.TextField = Ember.View.extend(Ember.TextSupport,
22199 /** @scope Ember.TextField.prototype */ {
22201 classNames: ['ember-text-field'],
22203 attributeBindings: ['type', 'value', 'size'],
22206 The value attribute of the input element. As the user inputs text, this
22207 property is updated live.
22216 The type attribute of the input element.
22225 The size of the text field in characters.
22241 @submodule ember-handlebars
22244 var get = Ember.get, set = Ember.set;
22249 @extends Ember.View
22250 @uses Ember.TargetActionSupport
22253 Ember.Button = Ember.View.extend(Ember.TargetActionSupport, {
22254 classNames: ['ember-button'],
22255 classNameBindings: ['isActive'],
22259 propagateEvents: false,
22261 attributeBindings: ['type', 'disabled', 'href', 'tabindex'],
22266 Overrides TargetActionSupport's targetObject computed
22267 property to use Handlebars-specific path resolution.
22269 @property targetObject
22271 targetObject: Ember.computed(function() {
22272 var target = get(this, 'target'),
22273 root = get(this, 'context'),
22274 data = get(this, 'templateData');
22276 if (typeof target !== 'string') { return target; }
22278 return Ember.Handlebars.get(root, target, { data: data });
22279 }).property('target'),
22281 // Defaults to 'button' if tagName is 'input' or 'button'
22282 type: Ember.computed(function(key, value) {
22283 var tagName = this.get('tagName');
22284 if (value !== undefined) { this._type = value; }
22285 if (this._type !== undefined) { return this._type; }
22286 if (tagName === 'input' || tagName === 'button') { return 'button'; }
22287 }).property('tagName'),
22291 // Allow 'a' tags to act like buttons
22292 href: Ember.computed(function() {
22293 return this.get('tagName') === 'a' ? '#' : null;
22294 }).property('tagName'),
22296 mouseDown: function() {
22297 if (!get(this, 'disabled')) {
22298 set(this, 'isActive', true);
22299 this._mouseDown = true;
22300 this._mouseEntered = true;
22302 return get(this, 'propagateEvents');
22305 mouseLeave: function() {
22306 if (this._mouseDown) {
22307 set(this, 'isActive', false);
22308 this._mouseEntered = false;
22312 mouseEnter: function() {
22313 if (this._mouseDown) {
22314 set(this, 'isActive', true);
22315 this._mouseEntered = true;
22319 mouseUp: function(event) {
22320 if (get(this, 'isActive')) {
22321 // Actually invoke the button's target and action.
22322 // This method comes from the Ember.TargetActionSupport mixin.
22323 this.triggerAction();
22324 set(this, 'isActive', false);
22327 this._mouseDown = false;
22328 this._mouseEntered = false;
22329 return get(this, 'propagateEvents');
22332 keyDown: function(event) {
22333 // Handle space or enter
22334 if (event.keyCode === 13 || event.keyCode === 32) {
22339 keyUp: function(event) {
22340 // Handle space or enter
22341 if (event.keyCode === 13 || event.keyCode === 32) {
22346 // TODO: Handle proper touch behavior. Including should make inactive when
22347 // finger moves more than 20x outside of the edge of the button (vs mouse
22348 // which goes inactive as soon as mouse goes out of edges.)
22350 touchStart: function(touch) {
22351 return this.mouseDown(touch);
22354 touchEnd: function(touch) {
22355 return this.mouseUp(touch);
22359 Ember.deprecate("Ember.Button is deprecated and will be removed from future releases. Consider using the `{{action}}` helper.");
22371 @submodule ember-handlebars
22374 var get = Ember.get, set = Ember.set;
22377 The `Ember.TextArea` view class renders a
22378 [textarea](https://developer.mozilla.org/en/HTML/Element/textarea) element.
22379 It allows for binding Ember properties to the text area contents (`value`),
22380 live-updating as the user inputs text.
22382 ## Layout and LayoutName properties
22384 Because HTML `textarea` elements do not contain inner HTML the `layout` and `layoutName`
22385 properties will not be applied. See `Ember.View`'s layout section for more information.
22389 By default `Ember.TextArea` provides support for `rows`, `cols`, `placeholder`, `disabled`,
22390 `maxlength` and `tabindex` attributes on a textarea. If you need to support more
22391 attributes have a look at the `attributeBindings` property in `Ember.View`'s HTML Attributes section.
22393 To globally add support for additional attributes you can reopen `Ember.TextArea` or `Ember.TextSupport`.
22396 Ember.TextSupport.reopen({
22397 attributeBindings: ["required"]
22403 @extends Ember.View
22404 @uses Ember.TextSupport
22406 Ember.TextArea = Ember.View.extend(Ember.TextSupport, {
22407 classNames: ['ember-text-area'],
22409 tagName: "textarea",
22410 attributeBindings: ['rows', 'cols'],
22414 _updateElementValue: Ember.observer(function() {
22415 // We do this check so cursor position doesn't get affected in IE
22416 var value = get(this, 'value'),
22418 if ($el && value !== $el.val()) {
22425 this.on("didInsertElement", this, this._updateElementValue);
22437 @submodule ember-handlebars
22441 @class TabContainerView
22444 @extends Ember.View
22446 Ember.TabContainerView = Ember.View.extend({
22448 Ember.deprecate("Ember.TabContainerView is deprecated and will be removed from future releases.");
22460 @submodule ember-handlebars
22463 var get = Ember.get;
22468 @extends Ember.View
22471 Ember.TabPaneView = Ember.View.extend({
22472 tabsContainer: Ember.computed(function() {
22473 return this.nearestOfType(Ember.TabContainerView);
22474 }).property().volatile(),
22476 isVisible: Ember.computed(function() {
22477 return get(this, 'viewName') === get(this, 'tabsContainer.currentView');
22478 }).property('tabsContainer.currentView').volatile(),
22481 Ember.deprecate("Ember.TabPaneView is deprecated and will be removed from future releases.");
22493 @submodule ember-handlebars
22496 var get = Ember.get, setPath = Ember.setPath;
22501 @extends Ember.View
22504 Ember.TabView = Ember.View.extend({
22505 tabsContainer: Ember.computed(function() {
22506 return this.nearestInstanceOf(Ember.TabContainerView);
22507 }).property().volatile(),
22509 mouseUp: function() {
22510 setPath(this, 'tabsContainer.currentView', get(this, 'value'));
22514 Ember.deprecate("Ember.TabView is deprecated and will be removed from future releases.");
22530 /*jshint eqeqeq:false */
22534 @submodule ember-handlebars
22537 var set = Ember.set,
22539 indexOf = Ember.EnumerableUtils.indexOf,
22540 indexesOf = Ember.EnumerableUtils.indexesOf,
22541 replace = Ember.EnumerableUtils.replace,
22542 isArray = Ember.isArray;
22545 The Ember.Select view class renders a
22546 [select](https://developer.mozilla.org/en/HTML/Element/select) HTML element,
22547 allowing the user to choose from a list of options.
22549 The text and `value` property of each `<option>` element within the `<select>` element
22550 are populated from the objects in the Element.Select's `content` property. The
22551 underlying data object of the selected `<option>` is stored in the
22552 Element.Select's `value` property.
22554 ### `content` as an array of Strings
22555 The simplest version of an Ember.Select takes an array of strings as its `content` property.
22556 The string will be used as both the `value` property and the inner text of each `<option>`
22557 element inside the rendered `<select>`.
22562 App.names = ["Yehuda", "Tom"];
22566 {{view Ember.Select contentBinding="App.names"}}
22569 Would result in the following HTML:
22572 <select class="ember-select">
22573 <option value="Yehuda">Yehuda</option>
22574 <option value="Tom">Tom</option>
22578 You can control which `<option>` is selected through the Ember.Select's
22579 `value` property directly or as a binding:
22582 App.names = Ember.Object.create({
22584 content: ["Yehuda", "Tom"]
22589 {{view Ember.Select
22590 contentBinding="App.names.content"
22591 valueBinding="App.names.selected"
22595 Would result in the following HTML with the `<option>` for 'Tom' selected:
22598 <select class="ember-select">
22599 <option value="Yehuda">Yehuda</option>
22600 <option value="Tom" selected="selected">Tom</option>
22604 A user interacting with the rendered `<select>` to choose "Yehuda" would update
22605 the value of `App.names.selected` to "Yehuda".
22607 ### `content` as an Array of Objects
22608 An Ember.Select can also take an array of JavaScript or Ember objects
22609 as its `content` property.
22611 When using objects you need to tell the Ember.Select which property should be
22612 accessed on each object to supply the `value` attribute of the `<option>`
22613 and which property should be used to supply the element text.
22615 The `optionValuePath` option is used to specify the path on each object to
22616 the desired property for the `value` attribute. The `optionLabelPath`
22617 specifies the path on each object to the desired property for the
22618 element's text. Both paths must reference each object itself as 'content':
22621 App.programmers = [
22622 Ember.Object.create({firstName: "Yehuda", id: 1}),
22623 Ember.Object.create({firstName: "Tom", id: 2})
22628 {{view Ember.Select
22629 contentBinding="App.programmers"
22630 optionValuePath="content.id"
22631 optionLabelPath="content.firstName"}}
22634 Would result in the following HTML:
22637 <select class="ember-select">
22638 <option value>Please Select</option>
22639 <option value="1">Yehuda</option>
22640 <option value="2">Tom</option>
22645 The `value` attribute of the selected `<option>` within an Ember.Select
22646 can be bound to a property on another object by providing a
22647 `valueBinding` option:
22650 App.programmers = [
22651 Ember.Object.create({firstName: "Yehuda", id: 1}),
22652 Ember.Object.create({firstName: "Tom", id: 2})
22655 App.currentProgrammer = Ember.Object.create({
22661 {{view Ember.Select
22662 contentBinding="App.programmers"
22663 optionValuePath="content.id"
22664 optionLabelPath="content.firstName"
22665 valueBinding="App.currentProgrammer.id"}}
22668 Would result in the following HTML with a selected option:
22671 <select class="ember-select">
22672 <option value>Please Select</option>
22673 <option value="1">Yehuda</option>
22674 <option value="2" selected="selected">Tom</option>
22678 Interacting with the rendered element by selecting the first option
22679 ('Yehuda') will update the `id` value of `App.currentProgrammer`
22680 to match the `value` property of the newly selected `<option>`.
22682 Alternatively, you can control selection through the underlying objects
22683 used to render each object providing a `selectionBinding`. When the selected
22684 `<option>` is changed, the property path provided to `selectionBinding`
22685 will be updated to match the content object of the rendered `<option>`
22689 App.controller = Ember.Object.create({
22690 selectedPerson: null,
22692 Ember.Object.create({firstName: "Yehuda", id: 1}),
22693 Ember.Object.create({firstName: "Tom", id: 2})
22699 {{view Ember.Select
22700 contentBinding="App.controller.content"
22701 optionValuePath="content.id"
22702 optionLabelPath="content.firstName"
22703 selectionBinding="App.controller.selectedPerson"}}
22706 Would result in the following HTML with a selected option:
22709 <select class="ember-select">
22710 <option value>Please Select</option>
22711 <option value="1">Yehuda</option>
22712 <option value="2" selected="selected">Tom</option>
22717 Interacting with the rendered element by selecting the first option
22718 ('Yehuda') will update the `selectedPerson` value of `App.controller`
22719 to match the content object of the newly selected `<option>`. In this
22720 case it is the first object in the `App.content.content`
22722 ### Supplying a Prompt
22724 A `null` value for the Ember.Select's `value` or `selection` property
22725 results in there being no `<option>` with a `selected` attribute:
22728 App.controller = Ember.Object.create({
22738 {{view Ember.Select
22739 contentBinding="App.controller.content"
22740 valueBinding="App.controller.selected"
22744 Would result in the following HTML:
22747 <select class="ember-select">
22748 <option value="Yehuda">Yehuda</option>
22749 <option value="Tom">Tom</option>
22753 Although `App.controller.selected` is `null` and no `<option>`
22754 has a `selected` attribute the rendered HTML will display the
22755 first item as though it were selected. You can supply a string
22756 value for the Ember.Select to display when there is no selection
22757 with the `prompt` option:
22760 App.controller = Ember.Object.create({
22770 {{view Ember.Select
22771 contentBinding="App.controller.content"
22772 valueBinding="App.controller.selected"
22773 prompt="Please select a name"
22777 Would result in the following HTML:
22780 <select class="ember-select">
22781 <option>Please select a name</option>
22782 <option value="Yehuda">Yehuda</option>
22783 <option value="Tom">Tom</option>
22789 @extends Ember.View
22791 Ember.Select = Ember.View.extend(
22792 /** @scope Ember.Select.prototype */ {
22795 classNames: ['ember-select'],
22796 defaultTemplate: Ember.Handlebars.compile('{{#if view.prompt}}<option value>{{view.prompt}}</option>{{/if}}{{#each view.content}}{{view Ember.SelectOption contentBinding="this"}}{{/each}}'),
22797 attributeBindings: ['multiple', 'disabled', 'tabindex'],
22800 The `multiple` attribute of the select element. Indicates whether multiple
22801 options can be selected.
22812 The list of options.
22814 If `optionLabelPath` and `optionValuePath` are not overridden, this should
22815 be a list of strings, which will serve simultaneously as labels and values.
22817 Otherwise, this should be a list of objects. For instance:
22820 { id: 1, firstName: 'Yehuda' },
22821 { id: 2, firstName: 'Tom' }
22823 optionLabelPath: 'content.firstName',
22824 optionValuePath: 'content.id'
22833 When `multiple` is false, the element of `content` that is currently
22836 When `multiple` is true, an array of such elements.
22838 @property selection
22839 @type Object or Array
22845 In single selection mode (when `multiple` is false), value can be used to get
22846 the current selection's value or set the selection by it's value.
22848 It is not currently supported in multiple selection mode.
22854 value: Ember.computed(function(key, value) {
22855 if (arguments.length === 2) { return value; }
22857 var valuePath = get(this, 'optionValuePath').replace(/^content\.?/, '');
22858 return valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection');
22859 }).property('selection'),
22862 If given, a top-most dummy option will be rendered to serve as a user
22872 The path of the option labels. See `content`.
22874 @property optionLabelPath
22878 optionLabelPath: 'content',
22881 The path of the option values. See `content`.
22883 @property optionValuePath
22887 optionValuePath: 'content',
22889 _change: function() {
22890 if (get(this, 'multiple')) {
22891 this._changeMultiple();
22893 this._changeSingle();
22897 selectionDidChange: Ember.observer(function() {
22898 var selection = get(this, 'selection');
22899 if (get(this, 'multiple')) {
22900 if (!isArray(selection)) {
22901 set(this, 'selection', Ember.A([selection]));
22904 this._selectionDidChangeMultiple();
22906 this._selectionDidChangeSingle();
22908 }, 'selection.@each'),
22910 valueDidChange: Ember.observer(function() {
22911 var content = get(this, 'content'),
22912 value = get(this, 'value'),
22913 valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''),
22914 selectedValue = (valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection')),
22917 if (value !== selectedValue) {
22918 selection = content.find(function(obj) {
22919 return value === (valuePath ? get(obj, valuePath) : obj);
22922 this.set('selection', selection);
22927 _triggerChange: function() {
22928 var selection = get(this, 'selection');
22929 var value = get(this, 'value');
22931 if (selection) { this.selectionDidChange(); }
22932 if (value) { this.valueDidChange(); }
22937 _changeSingle: function() {
22938 var selectedIndex = this.$()[0].selectedIndex,
22939 content = get(this, 'content'),
22940 prompt = get(this, 'prompt');
22942 if (!content) { return; }
22943 if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; }
22945 if (prompt) { selectedIndex -= 1; }
22946 set(this, 'selection', content.objectAt(selectedIndex));
22950 _changeMultiple: function() {
22951 var options = this.$('option:selected'),
22952 prompt = get(this, 'prompt'),
22953 offset = prompt ? 1 : 0,
22954 content = get(this, 'content'),
22955 selection = get(this, 'selection');
22957 if (!content){ return; }
22959 var selectedIndexes = options.map(function(){
22960 return this.index - offset;
22962 var newSelection = content.objectsAt(selectedIndexes);
22964 if (isArray(selection)) {
22965 replace(selection, 0, get(selection, 'length'), newSelection);
22967 set(this, 'selection', newSelection);
22972 _selectionDidChangeSingle: function() {
22973 var el = this.get('element');
22974 if (!el) { return; }
22976 var content = get(this, 'content'),
22977 selection = get(this, 'selection'),
22978 selectionIndex = content ? indexOf(content, selection) : -1,
22979 prompt = get(this, 'prompt');
22981 if (prompt) { selectionIndex += 1; }
22982 if (el) { el.selectedIndex = selectionIndex; }
22985 _selectionDidChangeMultiple: function() {
22986 var content = get(this, 'content'),
22987 selection = get(this, 'selection'),
22988 selectedIndexes = content ? indexesOf(content, selection) : [-1],
22989 prompt = get(this, 'prompt'),
22990 offset = prompt ? 1 : 0,
22991 options = this.$('option'),
22995 options.each(function() {
22996 adjusted = this.index > -1 ? this.index - offset : -1;
22997 this.selected = indexOf(selectedIndexes, adjusted) > -1;
23004 this.on("didInsertElement", this, this._triggerChange);
23005 this.on("change", this, this._change);
23009 Ember.SelectOption = Ember.View.extend({
23011 attributeBindings: ['value', 'selected'],
23013 defaultTemplate: function(context, options) {
23014 options = { data: options.data, hash: {} };
23015 Ember.Handlebars.helpers.bind.call(context, "view.label", options);
23019 this.labelPathDidChange();
23020 this.valuePathDidChange();
23025 selected: Ember.computed(function() {
23026 var content = get(this, 'content'),
23027 selection = get(this, 'parentView.selection');
23028 if (get(this, 'parentView.multiple')) {
23029 return selection && indexOf(selection, content.valueOf()) > -1;
23031 // Primitives get passed through bindings as objects... since
23032 // `new Number(4) !== 4`, we use `==` below
23033 return content == selection;
23035 }).property('content', 'parentView.selection').volatile(),
23037 labelPathDidChange: Ember.observer(function() {
23038 var labelPath = get(this, 'parentView.optionLabelPath');
23040 if (!labelPath) { return; }
23042 Ember.defineProperty(this, 'label', Ember.computed(function() {
23043 return get(this, labelPath);
23044 }).property(labelPath));
23045 }, 'parentView.optionLabelPath'),
23047 valuePathDidChange: Ember.observer(function() {
23048 var valuePath = get(this, 'parentView.optionValuePath');
23050 if (!valuePath) { return; }
23052 Ember.defineProperty(this, 'value', Ember.computed(function() {
23053 return get(this, valuePath);
23054 }).property(valuePath));
23055 }, 'parentView.optionValuePath')
23069 /*globals Handlebars */
23072 @submodule ember-handlebars
23078 Find templates stored in the head tag as script tags and make them available
23079 to Ember.CoreView in the global Ember.TEMPLATES object. This will be run as as
23080 jQuery DOM-ready callback.
23082 Script tags with "text/x-handlebars" will be compiled
23083 with Ember's Handlebars and are suitable for use as a view's template.
23084 Those with type="text/x-raw-handlebars" will be compiled with regular
23085 Handlebars and are suitable for use in views' computed properties.
23088 @for Ember.Handlebars
23092 Ember.Handlebars.bootstrap = function(ctx) {
23093 var selectors = 'script[type="text/x-handlebars"], script[type="text/x-raw-handlebars"]';
23095 Ember.$(selectors, ctx)
23097 // Get a reference to the script tag
23098 var script = Ember.$(this),
23099 type = script.attr('type');
23101 var compile = (script.attr('type') === 'text/x-raw-handlebars') ?
23102 Ember.$.proxy(Handlebars.compile, Handlebars) :
23103 Ember.$.proxy(Ember.Handlebars.compile, Ember.Handlebars),
23104 // Get the name of the script, used by Ember.View's templateName property.
23105 // First look for data-template-name attribute, then fall back to its
23106 // id if no name is found.
23107 templateName = script.attr('data-template-name') || script.attr('id') || 'application',
23108 template = compile(script.html());
23110 // For templates which have a name, we save them and then remove them from the DOM
23111 Ember.TEMPLATES[templateName] = template;
23113 // Remove script tag from DOM
23118 function bootstrap() {
23119 Ember.Handlebars.bootstrap( Ember.$(document) );
23123 We tie this to application.load to ensure that we've at least
23124 attempted to bootstrap at the point that the application is loaded.
23126 We also tie this to document ready since we're guaranteed that all
23127 the inline templates are present at this point.
23129 There's no harm to running this twice, since we remove the templates
23130 from the DOM after processing.
23133 Ember.onLoad('application', bootstrap);
23144 @submodule ember-handlebars
23145 @requires ember-views
23150 // Version: v1.0.0-pre.2-1-gd1a2e84
23151 // Last commit: d1a2e84 (2012-10-25 13:51:08 -0700)