Commit 8c233266 authored by Sindre Sorhus's avatar Sindre Sorhus

Merge pull request #464 from trek/update-to-ember-1.0.rc1

Updates Ember.js example to 1.0.rc1
parents 3606cd38 65869839
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
<!-- /* Handlebars templates end */ --> <!-- /* Handlebars templates end */ -->
<script src="../../assets/base.js"></script> <script src="../../assets/base.js"></script>
<script src="../../assets/jquery.min.js"></script> <script src="../../assets/jquery.min.js"></script>
<script src="js/libs/handlebars-1.0.rc.2.js"></script> <script src="js/libs/handlebars-1.0.rc.3.js"></script>
<script src="js/libs/ember.js"></script> <script src="js/libs/ember.js"></script>
<script src="js/libs/ember-data.js"></script> <script src="js/libs/ember-data.js"></script>
<script src="js/libs/local_storage_adapter.js"></script> <script src="js/libs/local_storage_adapter.js"></script>
......
// Version: v1.0.0-pre.4-9-g6f709b0 // Version: v1.0.0-rc.1
// Last commit: 6f709b0 (2013-01-19 20:52:11 -0800) // Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
(function() { (function() {
...@@ -69,6 +69,21 @@ Ember.warn = function(message, test) { ...@@ -69,6 +69,21 @@ Ember.warn = function(message, test) {
} }
}; };
/**
Display a debug notice. Ember build tools will remove any calls to
`Ember.debug()` when doing a production build.
```javascript
Ember.debug("I'm a debug notice!");
```
@method debug
@param {String} message A debug message to display.
*/
Ember.debug = function(message) {
Ember.Logger.debug("DEBUG: "+message);
};
/** /**
Display a deprecation warning with the provided message and a stack trace Display a deprecation warning with the provided message and a stack trace
(Chrome and Firefox only). Ember build tools will remove any calls to (Chrome and Firefox only). Ember build tools will remove any calls to
...@@ -133,17 +148,10 @@ Ember.deprecateFunc = function(message, func) { ...@@ -133,17 +148,10 @@ Ember.deprecateFunc = function(message, func) {
}; };
}; };
if ('undefined' !== typeof window) {
window.ember_assert = Ember.deprecateFunc("ember_assert is deprecated. Please use Ember.assert instead.", Ember.assert);
window.ember_warn = Ember.deprecateFunc("ember_warn is deprecated. Please use Ember.warn instead.", Ember.warn);
window.ember_deprecate = Ember.deprecateFunc("ember_deprecate is deprecated. Please use Ember.deprecate instead.", Ember.deprecate);
window.ember_deprecateFunc = Ember.deprecateFunc("ember_deprecateFunc is deprecated. Please use Ember.deprecateFunc instead.", Ember.deprecateFunc);
}
})(); })();
// Version: v1.0.0-pre.4-45-gd5fb9c4 // Version: v1.0.0-rc.1
// Last commit: d5fb9c4 (2013-01-25 23:22:15 -0800) // Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
(function() { (function() {
...@@ -203,7 +211,7 @@ var define, requireModule; ...@@ -203,7 +211,7 @@ var define, requireModule;
@class Ember @class Ember
@static @static
@version 1.0.0-pre.4 @version 1.0.0-rc.1
*/ */
if ('undefined' === typeof Ember) { if ('undefined' === typeof Ember) {
...@@ -230,10 +238,10 @@ Ember.toString = function() { return "Ember"; }; ...@@ -230,10 +238,10 @@ Ember.toString = function() { return "Ember"; };
/** /**
@property VERSION @property VERSION
@type String @type String
@default '1.0.0-pre.4' @default '1.0.0-rc.1'
@final @final
*/ */
Ember.VERSION = '1.0.0-pre.4'; Ember.VERSION = '1.0.0-rc.1';
/** /**
Standard environmental variables. You can define these in a global `ENV` Standard environmental variables. You can define these in a global `ENV`
...@@ -303,20 +311,12 @@ Ember.K = function() { return this; }; ...@@ -303,20 +311,12 @@ Ember.K = function() { return this; };
if ('undefined' === typeof Ember.assert) { Ember.assert = Ember.K; } if ('undefined' === typeof Ember.assert) { Ember.assert = Ember.K; }
if ('undefined' === typeof Ember.warn) { Ember.warn = Ember.K; } if ('undefined' === typeof Ember.warn) { Ember.warn = Ember.K; }
if ('undefined' === typeof Ember.debug) { Ember.debug = Ember.K; }
if ('undefined' === typeof Ember.deprecate) { Ember.deprecate = Ember.K; } if ('undefined' === typeof Ember.deprecate) { Ember.deprecate = Ember.K; }
if ('undefined' === typeof Ember.deprecateFunc) { if ('undefined' === typeof Ember.deprecateFunc) {
Ember.deprecateFunc = function(_, func) { return func; }; Ember.deprecateFunc = function(_, func) { return func; };
} }
// These are deprecated but still supported
if ('undefined' === typeof ember_assert) { exports.ember_assert = Ember.K; }
if ('undefined' === typeof ember_warn) { exports.ember_warn = Ember.K; }
if ('undefined' === typeof ember_deprecate) { exports.ember_deprecate = Ember.K; }
if ('undefined' === typeof ember_deprecateFunc) {
exports.ember_deprecateFunc = function(_, func) { return func; };
}
/** /**
Previously we used `Ember.$.uuid`, however `$.uuid` has been removed from Previously we used `Ember.$.uuid`, however `$.uuid` has been removed from
jQuery master. We'll just bootstrap our own uuid now. jQuery master. We'll just bootstrap our own uuid now.
...@@ -331,14 +331,36 @@ Ember.uuid = 0; ...@@ -331,14 +331,36 @@ Ember.uuid = 0;
// LOGGER // LOGGER
// //
function consoleMethod(name) {
if (imports.console && imports.console[name]) {
// Older IE doesn't support apply, but Chrome needs it
if (imports.console[name].apply) {
return function() {
imports.console[name].apply(imports.console, arguments);
};
} else {
return function() {
var message = Array.prototype.join.call(arguments, ', ');
imports.console[name](message);
};
}
}
}
/** /**
Inside Ember-Metal, simply uses the `imports.console` object. Inside Ember-Metal, simply uses the methods from `imports.console`.
Override this to provide more robust logging functionality. Override this to provide more robust logging functionality.
@class Logger @class Logger
@namespace Ember @namespace Ember
*/ */
Ember.Logger = imports.console || { log: Ember.K, warn: Ember.K, error: Ember.K, info: Ember.K, debug: Ember.K }; Ember.Logger = {
log: consoleMethod('log') || Ember.K,
warn: consoleMethod('warn') || Ember.K,
error: consoleMethod('error') || Ember.K,
info: consoleMethod('info') || Ember.K,
debug: consoleMethod('debug') || consoleMethod('info') || Ember.K
};
// .......................................................... // ..........................................................
...@@ -1257,8 +1279,63 @@ Ember.subscribe = Ember.Instrumentation.subscribe; ...@@ -1257,8 +1279,63 @@ Ember.subscribe = Ember.Instrumentation.subscribe;
(function() { (function() {
/*jshint newcap:false*/ var utils = Ember.EnumerableUtils = {
map: function(obj, callback, thisArg) {
return obj.map ? obj.map.call(obj, callback, thisArg) : Array.prototype.map.call(obj, callback, thisArg);
},
forEach: function(obj, callback, thisArg) {
return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : Array.prototype.forEach.call(obj, callback, thisArg);
},
indexOf: function(obj, element, index) {
return obj.indexOf ? obj.indexOf.call(obj, element, index) : Array.prototype.indexOf.call(obj, element, index);
},
indexesOf: function(obj, elements) {
return elements === undefined ? [] : utils.map(elements, function(item) {
return utils.indexOf(obj, item);
});
},
addObject: function(array, item) {
var index = utils.indexOf(array, item);
if (index === -1) { array.push(item); }
},
removeObject: function(array, item) {
var index = utils.indexOf(array, item);
if (index !== -1) { array.splice(index, 1); }
},
replace: function(array, idx, amt, objects) {
if (array.replace) {
return array.replace(idx, amt, objects);
} else {
var args = Array.prototype.concat.apply([idx, amt], objects);
return array.splice.apply(array, args);
}
},
intersection: function(array1, array2) {
var intersection = [];
array1.forEach(function(element) {
if (array2.indexOf(element) >= 0) {
intersection.push(element);
}
});
return intersection;
}
};
})();
(function() {
/*jshint newcap:false*/
/** /**
@module ember-metal @module ember-metal
*/ */
...@@ -1336,46 +1413,6 @@ Ember.ArrayPolyfills = { ...@@ -1336,46 +1413,6 @@ Ember.ArrayPolyfills = {
indexOf: arrayIndexOf indexOf: arrayIndexOf
}; };
var utils = Ember.EnumerableUtils = {
map: function(obj, callback, thisArg) {
return obj.map ? obj.map.call(obj, callback, thisArg) : arrayMap.call(obj, callback, thisArg);
},
forEach: function(obj, callback, thisArg) {
return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : arrayForEach.call(obj, callback, thisArg);
},
indexOf: function(obj, element, index) {
return obj.indexOf ? obj.indexOf.call(obj, element, index) : arrayIndexOf.call(obj, element, index);
},
indexesOf: function(obj, elements) {
return elements === undefined ? [] : utils.map(elements, function(item) {
return utils.indexOf(obj, item);
});
},
addObject: function(array, item) {
var index = utils.indexOf(array, item);
if (index === -1) { array.push(item); }
},
removeObject: function(array, item) {
var index = utils.indexOf(array, item);
if (index !== -1) { array.splice(index, 1); }
},
replace: function(array, idx, amt, objects) {
if (array.replace) {
return array.replace(idx, amt, objects);
} else {
var args = Array.prototype.concat.apply([idx, amt], objects);
return array.splice.apply(array, args);
}
}
};
if (Ember.SHIM_ES5) { if (Ember.SHIM_ES5) {
if (!Array.prototype.map) { if (!Array.prototype.map) {
Array.prototype.map = arrayMap; Array.prototype.map = arrayMap;
...@@ -2112,6 +2149,17 @@ var Descriptor = Ember.Descriptor = function() {}; ...@@ -2112,6 +2149,17 @@ var Descriptor = Ember.Descriptor = function() {};
// DEFINING PROPERTIES API // DEFINING PROPERTIES API
// //
var MANDATORY_SETTER_FUNCTION = Ember.MANDATORY_SETTER_FUNCTION = function(value) {
Ember.assert("You must use Ember.set() to access this property (of " + this + ")", false);
};
var DEFAULT_GETTER_FUNCTION = Ember.DEFAULT_GETTER_FUNCTION = function(name) {
return function() {
var meta = this[META_KEY];
return meta && meta.values[name];
};
};
/** /**
@private @private
...@@ -2195,13 +2243,8 @@ Ember.defineProperty = function(obj, keyName, desc, data, meta) { ...@@ -2195,13 +2243,8 @@ Ember.defineProperty = function(obj, keyName, desc, data, meta) {
objectDefineProperty(obj, keyName, { objectDefineProperty(obj, keyName, {
configurable: true, configurable: true,
enumerable: true, enumerable: true,
set: function() { set: MANDATORY_SETTER_FUNCTION,
Ember.assert('Must use Ember.set() to access this property', false); get: DEFAULT_GETTER_FUNCTION(keyName)
},
get: function() {
var meta = this[META_KEY];
return meta && meta.values[keyName];
}
}); });
} else { } else {
obj[keyName] = data; obj[keyName] = data;
...@@ -2926,13 +2969,8 @@ Ember.watch = function(obj, keyName) { ...@@ -2926,13 +2969,8 @@ Ember.watch = function(obj, keyName) {
o_defineProperty(obj, keyName, { o_defineProperty(obj, keyName, {
configurable: true, configurable: true,
enumerable: true, enumerable: true,
set: function() { set: Ember.MANDATORY_SETTER_FUNCTION,
Ember.assert('Must use Ember.set() to access this property', false); get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
},
get: function() {
var meta = this[META_KEY];
return meta && meta.values[keyName];
}
}); });
} }
} else { } else {
...@@ -5845,7 +5883,8 @@ define("rsvp", ...@@ -5845,7 +5883,8 @@ define("rsvp",
callbacks, callbackTuple, callback, binding, event; callbacks, callbackTuple, callback, binding, event;
if (callbacks = allCallbacks[eventName]) { if (callbacks = allCallbacks[eventName]) {
for (var i=0, l=callbacks.length; i<l; i++) { // Don't cache the callbacks.length since it may grow
for (var i=0; i<callbacks.length; i++) {
callbackTuple = callbacks[i]; callbackTuple = callbacks[i];
callback = callbackTuple[0]; callback = callbackTuple[0];
binding = callbackTuple[1]; binding = callbackTuple[1];
...@@ -5963,9 +6002,41 @@ define("rsvp", ...@@ -5963,9 +6002,41 @@ define("rsvp",
}); });
} }
function all(promises) {
var i, results = [];
var allPromise = new Promise();
var remaining = promises.length;
if (remaining === 0) {
allPromise.resolve([]);
}
var resolver = function(index) {
return function(value) {
resolve(index, value);
};
};
var resolve = function(index, value) {
results[index] = value;
if (--remaining === 0) {
allPromise.resolve(results);
}
};
var reject = function(error) {
allPromise.reject(error);
};
for (i = 0; i < remaining; i++) {
promises[i].then(resolver(i), reject);
}
return allPromise;
}
EventTarget.mixin(Promise.prototype); EventTarget.mixin(Promise.prototype);
RSVP = { async: async, Promise: Promise, Event: Event, EventTarget: EventTarget }; RSVP = { async: async, Promise: Promise, Event: Event, EventTarget: EventTarget, all: all, raiseOnUncaughtExceptions: true };
return RSVP; return RSVP;
}); });
...@@ -6036,10 +6107,10 @@ define("container", ...@@ -6036,10 +6107,10 @@ define("container",
this.resolver = parent && parent.resolver || function() {}; this.resolver = parent && parent.resolver || function() {};
this.registry = new InheritingDict(parent && parent.registry); this.registry = new InheritingDict(parent && parent.registry);
this.cache = new InheritingDict(parent && parent.cache); this.cache = new InheritingDict(parent && parent.cache);
this.typeInjections = {}; this.typeInjections = new InheritingDict(parent && parent.typeInjections);
this.injections = {}; this.injections = {};
this.options = {}; this._options = new InheritingDict(parent && parent._options);
this.typeOptions = {}; this._typeOptions = new InheritingDict(parent && parent._typeOptions);
} }
Container.prototype = { Container.prototype = {
...@@ -6054,8 +6125,20 @@ define("container", ...@@ -6054,8 +6125,20 @@ define("container",
}, },
register: function(type, name, factory, options) { register: function(type, name, factory, options) {
this.registry.set(type + ":" + name, factory); var fullName;
this.options[type + ":" + name] = options || {};
if (type.indexOf(':') !== -1){
options = factory;
factory = name;
fullName = type;
} else {
Ember.deprecate('register("'+type +'", "'+ name+'") is now deprecated in-favour of register("'+type+':'+name+'");', true);
fullName = type + ":" + name;
}
this.registry.set(fullName, factory);
this._options.set(fullName, options || {});
}, },
resolve: function(fullName) { resolve: function(fullName) {
...@@ -6089,19 +6172,31 @@ define("container", ...@@ -6089,19 +6172,31 @@ define("container",
optionsForType: function(type, options) { optionsForType: function(type, options) {
if (this.parent) { illegalChildOperation('optionsForType'); } if (this.parent) { illegalChildOperation('optionsForType'); }
this.typeOptions[type] = options; this._typeOptions.set(type, options);
},
options: function(type, options) {
this.optionsForType(type, options);
}, },
typeInjection: function(type, property, fullName) { typeInjection: function(type, property, fullName) {
if (this.parent) { illegalChildOperation('typeInjection'); } if (this.parent) { illegalChildOperation('typeInjection'); }
var injections = this.typeInjections[type] = this.typeInjections[type] || []; var injections = this.typeInjections.get(type);
if (!injections) {
injections = [];
this.typeInjections.set(type, injections);
}
injections.push({ property: property, fullName: fullName }); injections.push({ property: property, fullName: fullName });
}, },
injection: function(factoryName, property, injectionName) { injection: function(factoryName, property, injectionName) {
if (this.parent) { illegalChildOperation('injection'); } if (this.parent) { illegalChildOperation('injection'); }
if (factoryName.indexOf(':') === -1) {
return this.typeInjection(factoryName, property, injectionName);
}
var injections = this.injections[factoryName] = this.injections[factoryName] || []; var injections = this.injections[factoryName] = this.injections[factoryName] || [];
injections.push({ property: property, fullName: injectionName }); injections.push({ property: property, fullName: injectionName });
}, },
...@@ -6162,14 +6257,14 @@ define("container", ...@@ -6162,14 +6257,14 @@ define("container",
} }
function option(container, fullName, optionName) { function option(container, fullName, optionName) {
var options = container.options[fullName]; var options = container._options.get(fullName);
if (options && options[optionName] !== undefined) { if (options && options[optionName] !== undefined) {
return options[optionName]; return options[optionName];
} }
var type = fullName.split(":")[0]; var type = fullName.split(":")[0];
options = container.typeOptions[type]; options = container._typeOptions.get(type);
if (options) { if (options) {
return options[optionName]; return options[optionName];
...@@ -6193,11 +6288,12 @@ define("container", ...@@ -6193,11 +6288,12 @@ define("container",
if (factory) { if (factory) {
var injections = []; var injections = [];
injections = injections.concat(container.typeInjections[type] || []); injections = injections.concat(container.typeInjections.get(type) || []);
injections = injections.concat(container.injections[fullName] || []); injections = injections.concat(container.injections[fullName] || []);
var hash = buildInjections(container, injections); var hash = buildInjections(container, injections);
hash.container = container; hash.container = container;
hash._debugContainerKey = fullName;
value = factory.create(hash); value = factory.create(hash);
...@@ -8107,7 +8203,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot ...@@ -8107,7 +8203,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot
This returns the objects at the specified indexes, using `objectAt`. This returns the objects at the specified indexes, using `objectAt`.
```javascript ```javascript
var arr = ['a', 'b', 'c', 'd']; var arr = ['a', 'b', 'c', 'd'];
arr.objectsAt([0, 1, 2]); // ["a", "b", "c"] arr.objectsAt([0, 1, 2]); // ["a", "b", "c"]
arr.objectsAt([2, 3, 4]); // ["c", "d", undefined] arr.objectsAt([2, 3, 4]); // ["c", "d", undefined]
``` ```
...@@ -9727,7 +9823,7 @@ Ember.Evented = Ember.Mixin.create({ ...@@ -9727,7 +9823,7 @@ Ember.Evented = Ember.Mixin.create({
event. event.
```javascript ```javascript
person.on('didEat', food) { person.on('didEat', function(food) {
console.log('person ate some ' + food); console.log('person ate some ' + food);
}); });
...@@ -10007,9 +10103,9 @@ function makeCtor() { ...@@ -10007,9 +10103,9 @@ function makeCtor() {
} }
var CoreObject = makeCtor(); var CoreObject = makeCtor();
CoreObject.toString = function() { return "Ember.CoreObject"; };
CoreObject.PrototypeMixin = Mixin.create({ CoreObject.PrototypeMixin = Mixin.create({
reopen: function() { reopen: function() {
applyMixin(this, arguments, true); applyMixin(this, arguments, true);
return this; return this;
...@@ -10110,9 +10206,10 @@ CoreObject.PrototypeMixin = Mixin.create({ ...@@ -10110,9 +10206,10 @@ CoreObject.PrototypeMixin = Mixin.create({
@return {Ember.Object} receiver @return {Ember.Object} receiver
*/ */
destroy: function() { destroy: function() {
if (this.isDestroying) { return; } if (this._didCallDestroy) { return; }
this.isDestroying = true; this.isDestroying = true;
this._didCallDestroy = true;
if (this.willDestroy) { this.willDestroy(); } if (this.willDestroy) { this.willDestroy(); }
...@@ -10180,6 +10277,8 @@ CoreObject.PrototypeMixin = Mixin.create({ ...@@ -10180,6 +10277,8 @@ CoreObject.PrototypeMixin = Mixin.create({
} }
}); });
CoreObject.PrototypeMixin.ownerConstructor = CoreObject;
function makeToString(ret) { function makeToString(ret) {
return function() { return ret; }; return function() { return ret; };
} }
...@@ -10318,6 +10417,8 @@ var ClassMixin = Mixin.create({ ...@@ -10318,6 +10417,8 @@ var ClassMixin = Mixin.create({
}); });
ClassMixin.ownerConstructor = CoreObject;
if (Ember.config.overrideClassMixin) { if (Ember.config.overrideClassMixin) {
Ember.config.overrideClassMixin(ClassMixin); Ember.config.overrideClassMixin(ClassMixin);
} }
...@@ -10809,6 +10910,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb ...@@ -10809,6 +10910,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb
@uses Ember.Observable @uses Ember.Observable
*/ */
Ember.Object = Ember.CoreObject.extend(Ember.Observable); Ember.Object = Ember.CoreObject.extend(Ember.Observable);
Ember.Object.toString = function() { return "Ember.Object"; };
})(); })();
...@@ -10869,16 +10971,28 @@ var Namespace = Ember.Namespace = Ember.Object.extend({ ...@@ -10869,16 +10971,28 @@ var Namespace = Ember.Namespace = Ember.Object.extend({
Namespace.reopenClass({ Namespace.reopenClass({
NAMESPACES: [Ember], NAMESPACES: [Ember],
NAMESPACES_BY_ID: {},
PROCESSED: false, PROCESSED: false,
processAll: processAllNamespaces processAll: processAllNamespaces,
byName: function(name) {
if (!Ember.BOOTED) {
processAllNamespaces();
}
return NAMESPACES_BY_ID[name];
}
}); });
var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID;
var hasOwnProp = ({}).hasOwnProperty, var hasOwnProp = ({}).hasOwnProperty,
guidFor = Ember.guidFor; guidFor = Ember.guidFor;
function processNamespace(paths, root, seen) { function processNamespace(paths, root, seen) {
var idx = paths.length; var idx = paths.length;
NAMESPACES_BY_ID[paths.join('.')] = root;
// Loop over all of the keys in the namespace, looking for classes // Loop over all of the keys in the namespace, looking for classes
for(var key in root) { for(var key in root) {
if (!hasOwnProp.call(root, key)) { continue; } if (!hasOwnProp.call(root, key)) { continue; }
...@@ -10978,12 +11092,15 @@ function classToString() { ...@@ -10978,12 +11092,15 @@ function classToString() {
} }
function processAllNamespaces() { function processAllNamespaces() {
if (!Namespace.PROCESSED) { var unprocessedNamespaces = !Namespace.PROCESSED,
unprocessedMixins = Ember.anyUnprocessedMixins;
if (unprocessedNamespaces) {
findNamespaces(); findNamespaces();
Namespace.PROCESSED = true; Namespace.PROCESSED = true;
} }
if (Ember.anyUnprocessedMixins) { if (unprocessedNamespaces || unprocessedMixins) {
var namespaces = Namespace.NAMESPACES, namespace; var namespaces = Namespace.NAMESPACES, namespace;
for (var i=0, l=namespaces.length; i<l; i++) { for (var i=0, l=namespaces.length; i<l; i++) {
namespace = namespaces[i]; namespace = namespaces[i];
...@@ -11109,9 +11226,7 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray, ...@@ -11109,9 +11226,7 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,
@property arrangedContent @property arrangedContent
*/ */
arrangedContent: Ember.computed('content', function() { arrangedContent: Ember.computed.alias('content'),
return get(this, 'content');
}),
/** /**
Should actually retrieve the object at the specified index from the Should actually retrieve the object at the specified index from the
...@@ -11396,9 +11511,9 @@ Ember.ObjectProxy = Ember.Object.extend( ...@@ -11396,9 +11511,9 @@ Ember.ObjectProxy = Ember.Object.extend(
Ember.assert("Can't set ObjectProxy's content to itself", this.get('content') !== this); Ember.assert("Can't set ObjectProxy's content to itself", this.get('content') !== this);
}, 'content'), }, 'content'),
isTruthy: Ember.computed(function() { isTruthy: Ember.computed.bool('content'),
return !!get(this, 'content');
}).property('content'), _debugContainerKey: null,
willWatchProperty: function (key) { willWatchProperty: function (key) {
var contentKey = 'content.' + key; var contentKey = 'content.' + key;
...@@ -11932,6 +12047,8 @@ Ember.ControllerMixin = Ember.Mixin.create({ ...@@ -11932,6 +12047,8 @@ Ember.ControllerMixin = Ember.Mixin.create({
store: null, store: null,
model: Ember.computed.alias('content'),
send: function(actionName) { send: function(actionName) {
var args = [].slice.call(arguments, 1), target; var args = [].slice.call(arguments, 1), target;
...@@ -11980,7 +12097,8 @@ var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach; ...@@ -11980,7 +12097,8 @@ var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
songsController = Ember.ArrayController.create({ songsController = Ember.ArrayController.create({
content: songs, content: songs,
sortProperties: ['trackNumber'] sortProperties: ['trackNumber'],
sortAscending: true
}); });
songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'} songsController.get('firstObject'); // {trackNumber: 2, title: 'Back in the U.S.S.R.'}
...@@ -11995,7 +12113,19 @@ var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach; ...@@ -11995,7 +12113,19 @@ var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach;
@uses Ember.MutableEnumerable @uses Ember.MutableEnumerable
*/ */
Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
/**
Specifies which properties dictate the arrangedContent's sort order.
@property {Array} sortProperties
*/
sortProperties: null, sortProperties: null,
/**
Specifies the arrangedContent's sort direction
@property {Boolean} sortAscending
*/
sortAscending: true, sortAscending: true,
orderBy: function(item1, item2) { orderBy: function(item1, item2) {
...@@ -12032,9 +12162,7 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, { ...@@ -12032,9 +12162,7 @@ Ember.SortableMixin = Ember.Mixin.create(Ember.MutableEnumerable, {
return this._super(); return this._super();
}, },
isSorted: Ember.computed('sortProperties', function() { isSorted: Ember.computed.bool('sortProperties'),
return !!get(this, 'sortProperties');
}),
arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) { arrangedContent: Ember.computed('content', 'sortProperties.@each', function(key, value) {
var content = get(this, 'content'), var content = get(this, 'content'),
...@@ -12309,20 +12437,22 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, ...@@ -12309,20 +12437,22 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
objectAtContent: function(idx) { objectAtContent: function(idx) {
var length = get(this, 'length'), var length = get(this, 'length'),
object = get(this,'arrangedContent').objectAt(idx), object = get(this,'arrangedContent').objectAt(idx);
controllerClass = this.lookupItemController(object);
if (controllerClass && idx < length) { if (idx >= 0 && idx < length) {
var controllerClass = this.lookupItemController(object);
if (controllerClass) {
return this.controllerAt(idx, object, controllerClass); return this.controllerAt(idx, object, controllerClass);
} else {
// When controllerClass is falsy we have not opted in to using item
// controllers, so return the object directly. However, when
// controllerClass is defined but the index is out of range, we want to
// return the "out of range" value, whatever that might be. Rather than
// make assumptions (e.g. guessing `null` or `undefined`) we defer this to
// `arrangedContent`.
return object;
} }
}
// When `controllerClass` is falsy, we have not opted in to using item
// controllers, so return the object directly.
// When the index is out of range, we want to return the "out of range"
// value, whatever that might be. Rather than make assumptions
// (e.g. guessing `null` or `undefined`) we defer this to `arrangedContent`.
return object;
}, },
arrangedContentDidChange: function() { arrangedContentDidChange: function() {
...@@ -12348,6 +12478,7 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, ...@@ -12348,6 +12478,7 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
init: function() { init: function() {
this._super(); this._super();
if (!this.get('content')) { this.set('content', Ember.A()); }
this._resetSubContainers(); this._resetSubContainers();
}, },
...@@ -12441,7 +12572,7 @@ Ember Runtime ...@@ -12441,7 +12572,7 @@ Ember Runtime
*/ */
var jQuery = Ember.imports.jQuery; var jQuery = Ember.imports.jQuery;
Ember.assert("Ember Views require jQuery 1.7 (>= 1.7.2), 1.8 or 1.9", jQuery && (jQuery().jquery.match(/^1\.(7(?!$)(?!\.[01])|8|9)(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY)); Ember.assert("Ember Views require jQuery 1.8 or 1.9", jQuery && (jQuery().jquery.match(/^1\.(8|9)(\.\d+)?(pre|rc\d?)?/) || Ember.ENV.FORCE_JQUERY));
/** /**
Alias for jQuery Alias for jQuery
...@@ -12720,6 +12851,21 @@ Ember._RenderBuffer.prototype = ...@@ -12720,6 +12851,21 @@ Ember._RenderBuffer.prototype =
*/ */
elementAttributes: null, elementAttributes: null,
/**
A hash keyed on the name of the properties and whose value will be
applied to that property. For example, if you wanted to apply a
`checked=true` property to an element, you would set the
elementProperties hash to `{'checked':true}`.
You should not maintain this hash yourself, rather, you should use
the `prop()` method of `Ember.RenderBuffer`.
@property elementProperties
@type Hash
@default {}
*/
elementProperties: null,
/** /**
The tagname of the element an instance of `Ember.RenderBuffer` represents. The tagname of the element an instance of `Ember.RenderBuffer` represents.
...@@ -12842,6 +12988,41 @@ Ember._RenderBuffer.prototype = ...@@ -12842,6 +12988,41 @@ Ember._RenderBuffer.prototype =
return this; return this;
}, },
/**
Adds an property which will be rendered to the element.
@method prop
@param {String} name The name of the property
@param {String} value The value to add to the property
@chainable
@return {Ember.RenderBuffer|String} this or the current property value
*/
prop: function(name, value) {
var properties = this.elementProperties = (this.elementProperties || {});
if (arguments.length === 1) {
return properties[name];
} else {
properties[name] = value;
}
return this;
},
/**
Remove an property from the list of properties to render.
@method removeProp
@param {String} name The name of the property
@chainable
*/
removeProp: function(name) {
var properties = this.elementProperties;
if (properties) { delete properties[name]; }
return this;
},
/** /**
Adds a style to the style attribute which will be rendered to the element. Adds a style to the style attribute which will be rendered to the element.
...@@ -12875,8 +13056,9 @@ Ember._RenderBuffer.prototype = ...@@ -12875,8 +13056,9 @@ Ember._RenderBuffer.prototype =
id = this.elementId, id = this.elementId,
classes = this.classes, classes = this.classes,
attrs = this.elementAttributes, attrs = this.elementAttributes,
props = this.elementProperties,
style = this.elementStyle, style = this.elementStyle,
prop; attr, prop;
buffer.push('<' + tagName); buffer.push('<' + tagName);
...@@ -12904,15 +13086,32 @@ Ember._RenderBuffer.prototype = ...@@ -12904,15 +13086,32 @@ Ember._RenderBuffer.prototype =
} }
if (attrs) { if (attrs) {
for (prop in attrs) { for (attr in attrs) {
if (attrs.hasOwnProperty(prop)) { if (attrs.hasOwnProperty(attr)) {
buffer.push(' ' + prop + '="' + this._escapeAttribute(attrs[prop]) + '"'); buffer.push(' ' + attr + '="' + this._escapeAttribute(attrs[attr]) + '"');
} }
} }
this.elementAttributes = null; this.elementAttributes = null;
} }
if (props) {
for (prop in props) {
if (props.hasOwnProperty(prop)) {
var value = props[prop];
if (value || typeof(value) === 'number') {
if (value === true) {
buffer.push(' ' + prop + '="' + prop + '"');
} else {
buffer.push(' ' + prop + '="' + this._escapeAttribute(props[prop]) + '"');
}
}
}
}
this.elementProperties = null;
}
buffer.push('>'); buffer.push('>');
}, },
...@@ -12932,8 +13131,9 @@ Ember._RenderBuffer.prototype = ...@@ -12932,8 +13131,9 @@ Ember._RenderBuffer.prototype =
id = this.elementId, id = this.elementId,
classes = this.classes, classes = this.classes,
attrs = this.elementAttributes, attrs = this.elementAttributes,
props = this.elementProperties,
style = this.elementStyle, style = this.elementStyle,
styleBuffer = '', prop; styleBuffer = '', attr, prop;
if (id) { if (id) {
$element.attr('id', id); $element.attr('id', id);
...@@ -12957,15 +13157,25 @@ Ember._RenderBuffer.prototype = ...@@ -12957,15 +13157,25 @@ Ember._RenderBuffer.prototype =
} }
if (attrs) { if (attrs) {
for (prop in attrs) { for (attr in attrs) {
if (attrs.hasOwnProperty(prop)) { if (attrs.hasOwnProperty(attr)) {
$element.attr(prop, attrs[prop]); $element.attr(attr, attrs[attr]);
} }
} }
this.elementAttributes = null; this.elementAttributes = null;
} }
if (props) {
for (prop in props) {
if (props.hasOwnProperty(prop)) {
$element.prop(prop, props[prop]);
}
}
this.elementProperties = null;
}
return element; return element;
}, },
...@@ -13181,11 +13391,13 @@ Ember.EventDispatcher = Ember.Object.extend( ...@@ -13181,11 +13391,13 @@ Ember.EventDispatcher = Ember.Object.extend(
rootElement.delegate('[data-ember-action]', event + '.ember', function(evt) { rootElement.delegate('[data-ember-action]', event + '.ember', function(evt) {
return Ember.handleErrors(function() { return Ember.handleErrors(function() {
var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'), var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'),
action = Ember.Handlebars.ActionHelper.registeredActions[actionId], action = Ember.Handlebars.ActionHelper.registeredActions[actionId];
handler = action.handler;
if (action.eventName === eventName) { // We have to check for action here since in some cases, jQuery will trigger
return handler(evt); // an event on `removeChild` (i.e. focusout) after we've already torn down the
// action handlers for the view.
if (action && action.eventName === eventName) {
return action.handler(evt);
} }
}, this); }, this);
}); });
...@@ -13274,7 +13486,25 @@ Ember.ControllerMixin.reopen({ ...@@ -13274,7 +13486,25 @@ Ember.ControllerMixin.reopen({
target: null, target: null,
namespace: null, namespace: null,
view: null, view: null,
container: null container: null,
_childContainers: null,
init: function() {
this._super();
set(this, '_childContainers', {});
},
_modelDidChange: Ember.observer(function() {
var containers = get(this, '_childContainers'),
container;
for (var prop in containers) {
if (!containers.hasOwnProperty(prop)) { continue; }
containers[prop].destroy();
}
set(this, '_childContainers', {});
}, 'model')
}); });
})(); })();
...@@ -13302,9 +13532,7 @@ var a_forEach = Ember.EnumerableUtils.forEach; ...@@ -13302,9 +13532,7 @@ var a_forEach = Ember.EnumerableUtils.forEach;
var a_addObject = Ember.EnumerableUtils.addObject; var a_addObject = Ember.EnumerableUtils.addObject;
var childViewsProperty = Ember.computed(function() { var childViewsProperty = Ember.computed(function() {
var childViews = this._childViews; var childViews = this._childViews, ret = Ember.A(), view = this;
var ret = Ember.A();
a_forEach(childViews, function(view) { a_forEach(childViews, function(view) {
if (view.isVirtual) { if (view.isVirtual) {
...@@ -13314,6 +13542,14 @@ var childViewsProperty = Ember.computed(function() { ...@@ -13314,6 +13542,14 @@ var childViewsProperty = Ember.computed(function() {
} }
}); });
ret.replace = function (idx, removedCount, addedViews) {
if (view instanceof Ember.ContainerView) {
Ember.deprecate("Manipulating a Ember.ContainerView through its childViews property is deprecated. Please use the ContainerView instance itself as an Ember.MutableArray.");
return view.replace(idx, removedCount, addedViews);
}
throw new Error("childViews is immutable");
};
return ret; return ret;
}); });
...@@ -13340,7 +13576,10 @@ Ember.CoreView = Ember.Object.extend(Ember.Evented, { ...@@ -13340,7 +13576,10 @@ Ember.CoreView = Ember.Object.extend(Ember.Evented, {
// Register the view for event handling. This hash is used by // Register the view for event handling. This hash is used by
// Ember.EventDispatcher to dispatch incoming events. // Ember.EventDispatcher to dispatch incoming events.
if (!this.isVirtual) Ember.View.views[this.elementId] = this; if (!this.isVirtual) {
Ember.assert("Attempted to register a view with an id already in use: "+this.elementId, !Ember.View.views[this.elementId]);
Ember.View.views[this.elementId] = this;
}
this.addBeforeObserver('elementId', function() { this.addBeforeObserver('elementId', function() {
throw new Error("Changing a view's elementId after creation is not allowed"); throw new Error("Changing a view's elementId after creation is not allowed");
...@@ -14107,6 +14346,8 @@ Ember.View = Ember.CoreView.extend( ...@@ -14107,6 +14346,8 @@ Ember.View = Ember.CoreView.extend(
var templateName = get(this, 'templateName'), var templateName = get(this, 'templateName'),
template = this.templateForName(templateName, 'template'); template = this.templateForName(templateName, 'template');
Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || template);
return template || get(this, 'defaultTemplate'); return template || get(this, 'defaultTemplate');
}).property('templateName'), }).property('templateName'),
...@@ -14148,6 +14389,8 @@ Ember.View = Ember.CoreView.extend( ...@@ -14148,6 +14389,8 @@ Ember.View = Ember.CoreView.extend(
var layoutName = get(this, 'layoutName'), var layoutName = get(this, 'layoutName'),
layout = this.templateForName(layoutName, 'layout'); layout = this.templateForName(layoutName, 'layout');
Ember.assert("You specified the layoutName " + layoutName + " for " + this + ", but it did not exist.", !layoutName || layout);
return layout || get(this, 'defaultLayout'); return layout || get(this, 'defaultLayout');
}).property('layoutName'), }).property('layoutName'),
...@@ -14527,6 +14770,15 @@ Ember.View = Ember.CoreView.extend( ...@@ -14527,6 +14770,15 @@ Ember.View = Ember.CoreView.extend(
} }
this.registerObserver(this, parsedPath.path, observer); this.registerObserver(this, parsedPath.path, observer);
// Remove className so when the view is rerendered,
// the className is added based on binding reevaluation
this.one('willClearRender', function() {
if (oldClass) {
classNames.removeObject(oldClass);
oldClass = null;
}
});
}, this); }, this);
}, },
...@@ -14769,7 +15021,7 @@ Ember.View = Ember.CoreView.extend( ...@@ -14769,7 +15021,7 @@ Ember.View = Ember.CoreView.extend(
// element. // element.
// In the interim, we will just re-render if that happens. It is more // In the interim, we will just re-render if that happens. It is more
// important than elements get garbage collected. // important than elements get garbage collected.
this.destroyElement(); if (!this.removedFromDOM) { this.destroyElement(); }
this.invokeRecursively(function(view) { this.invokeRecursively(function(view) {
if (view.clearRenderedChildren) { view.clearRenderedChildren(); } if (view.clearRenderedChildren) { view.clearRenderedChildren(); }
}); });
...@@ -15244,12 +15496,17 @@ Ember.View = Ember.CoreView.extend( ...@@ -15244,12 +15496,17 @@ Ember.View = Ember.CoreView.extend(
// so collect any information we need before calling super. // so collect any information we need before calling super.
var childViews = this._childViews, var childViews = this._childViews,
parent = this._parentView, parent = this._parentView,
childLen; childLen, i;
// destroy the element -- this will avoid each child view destroying // destroy the element -- this will avoid each child view destroying
// the element over and over again... // the element over and over again...
if (!this.removedFromDOM) { this.destroyElement(); } if (!this.removedFromDOM) { this.destroyElement(); }
childLen = childViews.length;
for (i=childLen-1; i>=0; i--) {
childViews[i].removedFromDOM = true;
}
// remove from non-virtual parent view if viewName was specified // remove from non-virtual parent view if viewName was specified
if (this.viewName) { if (this.viewName) {
var nonVirtualParentView = get(this, 'parentView'); var nonVirtualParentView = get(this, 'parentView');
...@@ -15266,8 +15523,7 @@ Ember.View = Ember.CoreView.extend( ...@@ -15266,8 +15523,7 @@ Ember.View = Ember.CoreView.extend(
this.transitionTo('destroyed'); this.transitionTo('destroyed');
childLen = childViews.length; childLen = childViews.length;
for (var i=childLen-1; i>=0; i--) { for (i=childLen-1; i>=0; i--) {
childViews[i].removedFromDOM = true;
childViews[i].destroy(); childViews[i].destroy();
} }
...@@ -15414,11 +15670,11 @@ Ember.View = Ember.CoreView.extend( ...@@ -15414,11 +15670,11 @@ Ember.View = Ember.CoreView.extend(
return this.currentState.handleEvent(this, eventName, evt); return this.currentState.handleEvent(this, eventName, evt);
}, },
registerObserver: function(root, path, observer) { registerObserver: function(root, path, target, observer) {
Ember.addObserver(root, path, observer); Ember.addObserver(root, path, target, observer);
this.one('willClearRender', function() { this.one('willClearRender', function() {
Ember.removeObserver(root, path, observer); Ember.removeObserver(root, path, target, observer);
}); });
} }
...@@ -15610,13 +15866,17 @@ Ember.View.childViewsProperty = childViewsProperty; ...@@ -15610,13 +15866,17 @@ Ember.View.childViewsProperty = childViewsProperty;
Ember.View.applyAttributeBindings = function(elem, name, value) { Ember.View.applyAttributeBindings = function(elem, name, value) {
var type = Ember.typeOf(value); var type = Ember.typeOf(value);
var currentValue = elem.attr(name);
// if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js // if this changes, also change the logic in ember-handlebars/lib/helpers/binding.js
if ((type === 'string' || (type === 'number' && !isNaN(value))) && value !== currentValue) { if (name !== 'value' && (type === 'string' || (type === 'number' && !isNaN(value)))) {
if (value !== elem.attr(name)) {
elem.attr(name, value); elem.attr(name, value);
} else if (value && type === 'boolean') { }
elem.attr(name, name); } else if (name === 'value' || type === 'boolean') {
if (value !== elem.prop(name)) {
// value and booleans should always be properties
elem.prop(name, value);
}
} else if (!value) { } else if (!value) {
elem.removeAttr(name); elem.removeAttr(name);
} }
...@@ -15969,14 +16229,9 @@ var states = Ember.View.cloneStates(Ember.View.states); ...@@ -15969,14 +16229,9 @@ var states = Ember.View.cloneStates(Ember.View.states);
var get = Ember.get, set = Ember.set, meta = Ember.meta; var get = Ember.get, set = Ember.set, meta = Ember.meta;
var forEach = Ember.EnumerableUtils.forEach; var forEach = Ember.EnumerableUtils.forEach;
var childViewsProperty = Ember.computed(function() {
return get(this, '_childViews');
}).property('_childViews');
/** /**
A `ContainerView` is an `Ember.View` subclass that allows for manual or A `ContainerView` is an `Ember.View` subclass that implements `Ember.MutableArray`
programatic management of a view's `childViews` array that will correctly allowing programatic management of its child views.
update the `ContainerView` instance's rendered DOM representation.
## Setting Initial Child Views ## Setting Initial Child Views
...@@ -16016,11 +16271,9 @@ var childViewsProperty = Ember.computed(function() { ...@@ -16016,11 +16271,9 @@ var childViewsProperty = Ember.computed(function() {
## Adding and Removing Child Views ## Adding and Removing Child Views
The views in a container's `childViews` array should be added and removed by The container view implements `Ember.MutableArray` allowing programatic management of its child views.
manipulating the `childViews` property directly.
To remove a view pass that view into a `removeObject` call on the container's To remove a view, pass that view into a `removeObject` call on the container view.
`childViews` property.
Given an empty `<body>` the following code Given an empty `<body>` the following code
...@@ -16051,9 +16304,9 @@ var childViewsProperty = Ember.computed(function() { ...@@ -16051,9 +16304,9 @@ var childViewsProperty = Ember.computed(function() {
Removing a view Removing a view
```javascript ```javascript
aContainer.get('childViews'); // [aContainer.aView, aContainer.bView] aContainer.toArray(); // [aContainer.aView, aContainer.bView]
aContainer.get('childViews').removeObject(aContainer.get('bView')); aContainer.removeObject(aContainer.get('bView'));
aContainer.get('childViews'); // [aContainer.aView] aContainer.toArray(); // [aContainer.aView]
``` ```
Will result in the following HTML Will result in the following HTML
...@@ -16065,7 +16318,7 @@ var childViewsProperty = Ember.computed(function() { ...@@ -16065,7 +16318,7 @@ var childViewsProperty = Ember.computed(function() {
``` ```
Similarly, adding a child view is accomplished by adding `Ember.View` instances to the Similarly, adding a child view is accomplished by adding `Ember.View` instances to the
container's `childViews` property. container view.
Given an empty `<body>` the following code Given an empty `<body>` the following code
...@@ -16100,9 +16353,9 @@ var childViewsProperty = Ember.computed(function() { ...@@ -16100,9 +16353,9 @@ var childViewsProperty = Ember.computed(function() {
template: Ember.Handlebars.compile("Another view") template: Ember.Handlebars.compile("Another view")
}); });
aContainer.get('childViews'); // [aContainer.aView, aContainer.bView] aContainer.toArray(); // [aContainer.aView, aContainer.bView]
aContainer.get('childViews').pushObject(AnotherViewClass.create()); aContainer.pushObject(AnotherViewClass.create());
aContainer.get('childViews'); // [aContainer.aView, aContainer.bView, <AnotherViewClass instance>] aContainer.toArray(); // [aContainer.aView, aContainer.bView, <AnotherViewClass instance>]
``` ```
Will result in the following HTML Will result in the following HTML
...@@ -16115,57 +16368,7 @@ var childViewsProperty = Ember.computed(function() { ...@@ -16115,57 +16368,7 @@ var childViewsProperty = Ember.computed(function() {
</div> </div>
``` ```
Direct manipulation of `childViews` presence or absence in the DOM via calls ## Templates and Layout
to `remove` or `removeFromParent` or calls to a container's `removeChild` may
not behave correctly.
Calling `remove()` on a child view will remove the view's HTML, but it will
remain as part of its container's `childView`s property.
Calling `removeChild()` on the container will remove the passed view instance
from the container's `childView`s but keep its HTML within the container's
rendered view.
Calling `removeFromParent()` behaves as expected but should be avoided in
favor of direct manipulation of a container's `childViews` property.
```javascript
aContainer = Ember.ContainerView.create({
classNames: ['the-container'],
childViews: ['aView', 'bView'],
aView: Ember.View.create({
template: Ember.Handlebars.compile("A")
}),
bView: Ember.View.create({
template: Ember.Handlebars.compile("B")
})
});
aContainer.appendTo('body');
```
Results in the HTML
```html
<div class="ember-view the-container">
<div class="ember-view">A</div>
<div class="ember-view">B</div>
</div>
```
Calling `aContainer.get('aView').removeFromParent()` will result in the
following HTML
```html
<div class="ember-view the-container">
<div class="ember-view">B</div>
</div>
```
And the `Ember.View` instance stored in `aContainer.aView` will be removed from `aContainer`'s
`childViews` array.
## Templates and Layout
A `template`, `templateName`, `defaultTemplate`, `layout`, `layoutName` or A `template`, `templateName`, `defaultTemplate`, `layout`, `layoutName` or
`defaultLayout` property on a container view will not result in the template `defaultLayout` property on a container view will not result in the template
...@@ -16176,10 +16379,9 @@ var childViewsProperty = Ember.computed(function() { ...@@ -16176,10 +16379,9 @@ var childViewsProperty = Ember.computed(function() {
If you would like to display a single view in your ContainerView, you can set If you would like to display a single view in your ContainerView, you can set
its `currentView` property. When the `currentView` property is set to a view its `currentView` property. When the `currentView` property is set to a view
instance, it will be added to the ContainerView's `childViews` array. If the instance, it will be added to the ContainerView. If the `currentView` property
`currentView` property is later changed to a different view, the new view is later changed to a different view, the new view will replace the old view.
will replace the old view. If `currentView` is set to `null`, the last If `currentView` is set to `null`, the last `currentView` will be removed.
`currentView` will be removed.
This functionality is useful for cases where you want to bind the display of This functionality is useful for cases where you want to bind the display of
a ContainerView to a controller or state manager. For example, you can bind a ContainerView to a controller or state manager. For example, you can bind
...@@ -16201,15 +16403,16 @@ var childViewsProperty = Ember.computed(function() { ...@@ -16201,15 +16403,16 @@ var childViewsProperty = Ember.computed(function() {
@namespace Ember @namespace Ember
@extends Ember.View @extends Ember.View
*/ */
Ember.ContainerView = Ember.View.extend(Ember.MutableArray, {
Ember.ContainerView = Ember.View.extend({
states: states, states: states,
init: function() { init: function() {
this._super(); this._super();
var childViews = get(this, 'childViews'); var childViews = get(this, 'childViews');
Ember.defineProperty(this, 'childViews', childViewsProperty);
// redefine view's childViews property that was obliterated
Ember.defineProperty(this, 'childViews', Ember.View.childViewsProperty);
var _childViews = this._childViews; var _childViews = this._childViews;
...@@ -16228,20 +16431,38 @@ Ember.ContainerView = Ember.View.extend({ ...@@ -16228,20 +16431,38 @@ Ember.ContainerView = Ember.View.extend({
}, this); }, this);
var currentView = get(this, 'currentView'); var currentView = get(this, 'currentView');
if (currentView) _childViews.push(this.createChildView(currentView)); if (currentView) {
_childViews.push(this.createChildView(currentView));
}
},
// Make the _childViews array observable replace: function(idx, removedCount, addedViews) {
Ember.A(_childViews); var addedCount = addedViews ? get(addedViews, 'length') : 0;
// Sets up an array observer on the child views array. This this.arrayContentWillChange(idx, removedCount, addedCount);
// observer will detect when child views are added or removed this.childViewsWillChange(this._childViews, idx, removedCount);
// and update the DOM to reflect the mutation.
get(this, 'childViews').addArrayObserver(this, { if (addedCount === 0) {
willChange: 'childViewsWillChange', this._childViews.splice(idx, removedCount) ;
didChange: 'childViewsDidChange' } else {
}); var args = [idx, removedCount].concat(addedViews);
this._childViews.splice.apply(this._childViews, args);
}
this.arrayContentDidChange(idx, removedCount, addedCount);
this.childViewsDidChange(this._childViews, idx, removedCount, addedCount);
return this;
}, },
objectAt: function(idx) {
return this._childViews[idx];
},
length: Ember.computed(function () {
return this._childViews.length;
}),
/** /**
@private @private
...@@ -16258,23 +16479,6 @@ Ember.ContainerView = Ember.View.extend({ ...@@ -16258,23 +16479,6 @@ Ember.ContainerView = Ember.View.extend({
instrumentName: 'render.container', instrumentName: 'render.container',
/**
@private
When the container view is destroyed, tear down the child views
array observer.
@method willDestroy
*/
willDestroy: function() {
get(this, 'childViews').removeArrayObserver(this, {
willChange: 'childViewsWillChange',
didChange: 'childViewsDidChange'
});
this._super();
},
/** /**
@private @private
...@@ -16290,12 +16494,19 @@ Ember.ContainerView = Ember.View.extend({ ...@@ -16290,12 +16494,19 @@ Ember.ContainerView = Ember.View.extend({
@param {Number} removed the number of child views removed @param {Number} removed the number of child views removed
**/ **/
childViewsWillChange: function(views, start, removed) { childViewsWillChange: function(views, start, removed) {
if (removed === 0) { return; } this.propertyWillChange('childViews');
if (removed > 0) {
var changedViews = views.slice(start, start+removed); var changedViews = views.slice(start, start+removed);
// transition to preRender before clearing parentView
this.currentState.childViewsWillChange(this, views, start, removed);
this.initializeViews(changedViews, null, null); this.initializeViews(changedViews, null, null);
}
},
this.currentState.childViewsWillChange(this, views, start, removed); removeChild: function(child) {
this.removeObject(child);
return this;
}, },
/** /**
...@@ -16316,16 +16527,12 @@ Ember.ContainerView = Ember.View.extend({ ...@@ -16316,16 +16527,12 @@ Ember.ContainerView = Ember.View.extend({
@param {Number} the number of child views added @param {Number} the number of child views added
*/ */
childViewsDidChange: function(views, start, removed, added) { childViewsDidChange: function(views, start, removed, added) {
var len = get(views, 'length'); if (added > 0) {
// No new child views were added; bail out.
if (added === 0) return;
var changedViews = views.slice(start, start+added); var changedViews = views.slice(start, start+added);
this.initializeViews(changedViews, this, get(this, 'templateData')); this.initializeViews(changedViews, this, get(this, 'templateData'));
// Let the current state handle the changes
this.currentState.childViewsDidChange(this, views, start, added); this.currentState.childViewsDidChange(this, views, start, added);
}
this.propertyDidChange('childViews');
}, },
initializeViews: function(views, parentView, templateData) { initializeViews: function(views, parentView, templateData) {
...@@ -16341,21 +16548,16 @@ Ember.ContainerView = Ember.View.extend({ ...@@ -16341,21 +16548,16 @@ Ember.ContainerView = Ember.View.extend({
currentView: null, currentView: null,
_currentViewWillChange: Ember.beforeObserver(function() { _currentViewWillChange: Ember.beforeObserver(function() {
var childViews = get(this, 'childViews'), var currentView = get(this, 'currentView');
currentView = get(this, 'currentView');
if (currentView) { if (currentView) {
currentView.destroy(); currentView.destroy();
childViews.removeObject(currentView);
} }
}, 'currentView'), }, 'currentView'),
_currentViewDidChange: Ember.observer(function() { _currentViewDidChange: Ember.observer(function() {
var childViews = get(this, 'childViews'), var currentView = get(this, 'currentView');
currentView = get(this, 'currentView');
if (currentView) { if (currentView) {
childViews.pushObject(currentView); this.pushObject(currentView);
} }
}, 'currentView'), }, 'currentView'),
...@@ -16388,7 +16590,7 @@ Ember.merge(states.hasElement, { ...@@ -16388,7 +16590,7 @@ Ember.merge(states.hasElement, {
}, },
ensureChildrenAreInDOM: function(view) { ensureChildrenAreInDOM: function(view) {
var childViews = view.get('childViews'), i, len, childView, previous, buffer; var childViews = view._childViews, i, len, childView, previous, buffer;
for (i = 0, len = childViews.length; i < len; i++) { for (i = 0, len = childViews.length; i < len; i++) {
childView = childViews[i]; childView = childViews[i];
buffer = childView.renderToBufferIfNeeded(); buffer = childView.renderToBufferIfNeeded();
...@@ -16653,6 +16855,10 @@ Ember.CollectionView = Ember.ContainerView.extend( ...@@ -16653,6 +16855,10 @@ Ember.CollectionView = Ember.ContainerView.extend(
if (content) { content.removeArrayObserver(this); } if (content) { content.removeArrayObserver(this); }
this._super(); this._super();
if (this._createdEmptyView) {
this._createdEmptyView.destroy();
}
}, },
arrayWillChange: function(content, start, removedCount) { arrayWillChange: function(content, start, removedCount) {
...@@ -16666,9 +16872,9 @@ Ember.CollectionView = Ember.ContainerView.extend( ...@@ -16666,9 +16872,9 @@ Ember.CollectionView = Ember.ContainerView.extend(
// Loop through child views that correspond with the removed items. // Loop through child views that correspond with the removed items.
// Note that we loop from the end of the array to the beginning because // Note that we loop from the end of the array to the beginning because
// we are mutating it as we go. // we are mutating it as we go.
var childViews = get(this, 'childViews'), childView, idx, len; var childViews = this._childViews, childView, idx, len;
len = get(childViews, 'length'); len = this._childViews.length;
var removingAll = removedCount === len; var removingAll = removedCount === len;
...@@ -16698,7 +16904,6 @@ Ember.CollectionView = Ember.ContainerView.extend( ...@@ -16698,7 +16904,6 @@ Ember.CollectionView = Ember.ContainerView.extend(
*/ */
arrayDidChange: function(content, start, removed, added) { arrayDidChange: function(content, start, removed, added) {
var itemViewClass = get(this, 'itemViewClass'), var itemViewClass = get(this, 'itemViewClass'),
childViews = get(this, 'childViews'),
addedViews = [], view, item, idx, len, itemTagName; addedViews = [], view, item, idx, len, itemTagName;
if ('string' === typeof itemViewClass) { if ('string' === typeof itemViewClass) {
...@@ -16723,11 +16928,15 @@ Ember.CollectionView = Ember.ContainerView.extend( ...@@ -16723,11 +16928,15 @@ Ember.CollectionView = Ember.ContainerView.extend(
var emptyView = get(this, 'emptyView'); var emptyView = get(this, 'emptyView');
if (!emptyView) { return; } if (!emptyView) { return; }
var isClass = Ember.CoreView.detect(emptyView);
emptyView = this.createChildView(emptyView); emptyView = this.createChildView(emptyView);
addedViews.push(emptyView); addedViews.push(emptyView);
set(this, 'emptyView', emptyView); set(this, 'emptyView', emptyView);
if (isClass) { this._createdEmptyView = emptyView; }
} }
childViews.replace(start, 0, addedViews); this.replace(start, 0, addedViews);
}, },
createChildView: function(view, attrs) { createChildView: function(view, attrs) {
...@@ -17262,7 +17471,7 @@ var objectCreate = Object.create || function(parent) { ...@@ -17262,7 +17471,7 @@ var objectCreate = Object.create || function(parent) {
}; };
var Handlebars = this.Handlebars || Ember.imports.Handlebars; var Handlebars = this.Handlebars || Ember.imports.Handlebars;
Ember.assert("Ember Handlebars requires Handlebars 1.0.rc.2 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.rc\.[23456789]+/)); Ember.assert("Ember Handlebars requires Handlebars 1.0.0-rc.3 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.[0-9](\.rc\.[23456789]+)?/));
/** /**
Prepares the Handlebars templating library for use inside Ember's view Prepares the Handlebars templating library for use inside Ember's view
...@@ -17339,6 +17548,8 @@ Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) ...@@ -17339,6 +17548,8 @@ Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string)
return "data.buffer.push("+string+");"; return "data.buffer.push("+string+");";
}; };
var prefix = "ember" + (+new Date()), incr = 1;
/** /**
@private @private
...@@ -17351,8 +17562,11 @@ Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) ...@@ -17351,8 +17562,11 @@ Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string)
@param mustache @param mustache
*/ */
Ember.Handlebars.Compiler.prototype.mustache = function(mustache) { Ember.Handlebars.Compiler.prototype.mustache = function(mustache) {
if (mustache.params.length || mustache.hash) { if (mustache.isHelper && mustache.id.string === 'control') {
return Handlebars.Compiler.prototype.mustache.call(this, mustache); mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
mustache.hash.pairs.push(["controlID", new Handlebars.AST.StringNode(prefix + incr++)]);
} else if (mustache.params.length || mustache.hash) {
// no changes required
} else { } else {
var id = new Handlebars.AST.IdNode(['_triageMustache']); var id = new Handlebars.AST.IdNode(['_triageMustache']);
...@@ -17364,8 +17578,9 @@ Ember.Handlebars.Compiler.prototype.mustache = function(mustache) { ...@@ -17364,8 +17578,9 @@ Ember.Handlebars.Compiler.prototype.mustache = function(mustache) {
mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]); mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
} }
mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped); mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
return Handlebars.Compiler.prototype.mustache.call(this, mustache);
} }
return Handlebars.Compiler.prototype.mustache.call(this, mustache);
}; };
/** /**
...@@ -17424,6 +17639,8 @@ if (Handlebars.compile) { ...@@ -17424,6 +17639,8 @@ if (Handlebars.compile) {
})(); })();
(function() { (function() {
var slice = Array.prototype.slice;
/** /**
@private @private
...@@ -17604,6 +17821,18 @@ Ember.Handlebars.registerHelper('helperMissing', function(path, options) { ...@@ -17604,6 +17821,18 @@ Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
{{repeat text count=3}} {{repeat text count=3}}
``` ```
## Example with bound options
Bound hash options are also supported. Example:
```handlebars
{{repeat text countBinding="numRepeats"}}
```
In this example, count will be bound to the value of
the `numRepeats` property on the context. If that property
changes, the helper will be re-rendered.
## Example with extra dependencies ## Example with extra dependencies
The `Ember.Handlebars.registerBoundHelper` method takes a variable length The `Ember.Handlebars.registerBoundHelper` method takes a variable length
...@@ -17616,6 +17845,37 @@ Ember.Handlebars.registerHelper('helperMissing', function(path, options) { ...@@ -17616,6 +17845,37 @@ Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
}, 'name'); }, 'name');
``` ```
## Example with multiple bound properties
`Ember.Handlebars.registerBoundHelper` supports binding to
multiple properties, e.g.:
```javascript
Ember.Handlebars.registerBoundHelper('concatenate', function() {
var values = arguments[arguments.length - 1];
return values.join('||');
});
```
Which allows for template syntax such as {{concatenate prop1 prop2}} or
{{concatenate prop1 prop2 prop3}}. If any of the properties change,
the helpr will re-render. Note that dependency keys cannot be
using in conjunction with multi-property helpers, since it is ambiguous
which property the dependent keys would belong to.
## Use with unbound helper
The {{unbound}} helper can be used with bound helper invocations
to render them in their unbound form, e.g.
```handlebars
{{unbound capitalize name}}
```
In this example, if the name property changes, the helper
will not re-render.
@method registerBoundHelper @method registerBoundHelper
@for Ember.Handlebars @for Ember.Handlebars
@param {String} name @param {String} name
...@@ -17623,15 +17883,50 @@ Ember.Handlebars.registerHelper('helperMissing', function(path, options) { ...@@ -17623,15 +17883,50 @@ Ember.Handlebars.registerHelper('helperMissing', function(path, options) {
@param {String} dependentKeys* @param {String} dependentKeys*
*/ */
Ember.Handlebars.registerBoundHelper = function(name, fn) { Ember.Handlebars.registerBoundHelper = function(name, fn) {
var dependentKeys = Array.prototype.slice.call(arguments, 2); var dependentKeys = slice.call(arguments, 2);
Ember.Handlebars.registerHelper(name, function(property, options) {
var data = options.data, function helper() {
var properties = slice.call(arguments, 0, -1),
numProperties = properties.length,
options = arguments[arguments.length - 1],
normalizedProperties = [],
data = options.data,
hash = options.hash,
view = data.view, view = data.view,
currentContext = (options.contexts && options.contexts[0]) || this, currentContext = (options.contexts && options.contexts[0]) || this,
pathRoot, path, normalized, normalized,
observer, loc; pathRoot, path,
loc, hashOption;
// Detect bound options (e.g. countBinding="otherCount")
hash.boundOptions = {};
for (hashOption in hash) {
if (!hash.hasOwnProperty(hashOption)) { continue; }
if (Ember.IS_BINDING.test(hashOption) && typeof hash[hashOption] === 'string') {
// Lop off 'Binding' suffix.
hash.boundOptions[hashOption.slice(0, -7)] = hash[hashOption];
}
}
// Expose property names on data.properties object.
data.properties = [];
for (loc = 0; loc < numProperties; ++loc) {
data.properties.push(properties[loc]);
normalizedProperties.push(normalizePath(currentContext, properties[loc], data));
}
if (data.isUnbound) {
return evaluateUnboundHelper(this, fn, normalizedProperties, options);
}
if (dependentKeys.length === 0) {
return evaluateMultiPropertyBoundHelper(currentContext, fn, normalizedProperties, options);
}
Ember.assert("Dependent keys can only be used with single-property helpers.", properties.length === 1);
normalized = Ember.Handlebars.normalizePath(currentContext, property, data); normalized = normalizedProperties[0];
pathRoot = normalized.root; pathRoot = normalized.root;
path = normalized.path; path = normalized.path;
...@@ -17647,18 +17942,116 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) { ...@@ -17647,18 +17942,116 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) {
view.appendChild(bindView); view.appendChild(bindView);
observer = function() { view.registerObserver(pathRoot, path, bindView, rerenderBoundHelperView);
Ember.run.scheduleOnce('render', bindView, 'rerender');
};
view.registerObserver(pathRoot, path, observer);
for (var i=0, l=dependentKeys.length; i<l; i++) { for (var i=0, l=dependentKeys.length; i<l; i++) {
view.registerObserver(pathRoot, path + '.' + dependentKeys[i], observer); view.registerObserver(pathRoot, path + '.' + dependentKeys[i], bindView, rerenderBoundHelperView);
} }
}); }
helper._rawFunction = fn;
Ember.Handlebars.registerHelper(name, helper);
}; };
/**
@private
Renders the unbound form of an otherwise bound helper function.
@param {Function} fn
@param {Object} context
@param {Array} normalizedProperties
@param {String} options
*/
function evaluateMultiPropertyBoundHelper(context, fn, normalizedProperties, options) {
var numProperties = normalizedProperties.length,
self = this,
data = options.data,
view = data.view,
hash = options.hash,
boundOptions = hash.boundOptions,
watchedProperties,
boundOption, bindView, loc, property, len;
bindView = new Ember._SimpleHandlebarsView(null, null, !hash.unescaped, data);
bindView.normalizedValue = function() {
var args = [], value, boundOption;
// Copy over bound options.
for (boundOption in boundOptions) {
if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
property = normalizePath(context, boundOptions[boundOption], data);
bindView.path = property.path;
bindView.pathRoot = property.root;
hash[boundOption] = Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView);
}
for (loc = 0; loc < numProperties; ++loc) {
property = normalizedProperties[loc];
bindView.path = property.path;
bindView.pathRoot = property.root;
args.push(Ember._SimpleHandlebarsView.prototype.normalizedValue.call(bindView));
}
args.push(options);
return fn.apply(context, args);
};
view.appendChild(bindView);
// Assemble liast of watched properties that'll re-render this helper.
watchedProperties = [];
for (boundOption in boundOptions) {
if (boundOptions.hasOwnProperty(boundOption)) {
watchedProperties.push(normalizePath(context, boundOptions[boundOption], data));
}
}
watchedProperties = watchedProperties.concat(normalizedProperties);
// Observe each property.
for (loc = 0, len = watchedProperties.length; loc < len; ++loc) {
property = watchedProperties[loc];
view.registerObserver(property.root, property.path, bindView, rerenderBoundHelperView);
}
}
/**
@private
An observer function used with bound helpers which
will schedule a re-render of the _SimpleHandlebarsView
connected with the helper.
*/
function rerenderBoundHelperView() {
Ember.run.scheduleOnce('render', this, 'rerender');
}
/**
@private
Renders the unbound form of an otherwise bound helper function.
@param {Function} fn
@param {Object} context
@param {Array} normalizedProperties
@param {String} options
*/
function evaluateUnboundHelper(context, fn, normalizedProperties, options) {
var args = [], hash = options.hash, boundOptions = hash.boundOptions, loc, len, property, boundOption;
for (boundOption in boundOptions) {
if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
hash[boundOption] = Ember.Handlebars.get(context, boundOptions[boundOption], options);
}
for(loc = 0, len = normalizedProperties.length; loc < len; ++loc) {
property = normalizedProperties[loc];
args.push(Ember.Handlebars.get(context, property.path, options));
}
args.push(options);
return fn.apply(context, args);
}
/** /**
@private @private
...@@ -18183,20 +18576,16 @@ var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers; ...@@ -18183,20 +18576,16 @@ var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers;
// Binds a property into the DOM. This will create a hook in DOM that the // Binds a property into the DOM. This will create a hook in DOM that the
// KVO system will look for and update if the property changes. // KVO system will look for and update if the property changes.
function bind(property, options, preserveContext, shouldDisplay, valueNormalizer) { function bind(property, options, preserveContext, shouldDisplay, valueNormalizer, childProperties) {
var data = options.data, var data = options.data,
fn = options.fn, fn = options.fn,
inverse = options.inverse, inverse = options.inverse,
view = data.view, view = data.view,
currentContext = this, currentContext = this,
pathRoot, path, normalized, normalized, observer, i;
observer;
normalized = normalizePath(currentContext, property, data); normalized = normalizePath(currentContext, property, data);
pathRoot = normalized.root;
path = normalized.path;
// Set up observers for observable objects // Set up observers for observable objects
if ('object' === typeof this) { if ('object' === typeof this) {
if (data.insideGroup) { if (data.insideGroup) {
...@@ -18204,7 +18593,7 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer ...@@ -18204,7 +18593,7 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer
Ember.run.once(view, 'rerender'); Ember.run.once(view, 'rerender');
}; };
var template, context, result = handlebarsGet(pathRoot, path, options); var template, context, result = handlebarsGet(currentContext, property, options);
result = valueNormalizer(result); result = valueNormalizer(result);
...@@ -18226,8 +18615,8 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer ...@@ -18226,8 +18615,8 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer
valueNormalizerFunc: valueNormalizer, valueNormalizerFunc: valueNormalizer,
displayTemplate: fn, displayTemplate: fn,
inverseTemplate: inverse, inverseTemplate: inverse,
path: path, path: property,
pathRoot: pathRoot, pathRoot: currentContext,
previousContext: currentContext, previousContext: currentContext,
isEscaped: !options.hash.unescaped, isEscaped: !options.hash.unescaped,
templateData: options.data templateData: options.data
...@@ -18244,13 +18633,18 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer ...@@ -18244,13 +18633,18 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer
// tells the Ember._HandlebarsBoundView to re-render. If property // tells the Ember._HandlebarsBoundView to re-render. If property
// is an empty string, we are printing the current context // is an empty string, we are printing the current context
// object ({{this}}) so updating it is not our responsibility. // object ({{this}}) so updating it is not our responsibility.
if (path !== '') { if (normalized.path !== '') {
view.registerObserver(pathRoot, path, observer); view.registerObserver(normalized.root, normalized.path, observer);
if (childProperties) {
for (i=0; i<childProperties.length; i++) {
view.registerObserver(normalized.root, normalized.path+'.'+childProperties[i], observer);
}
}
} }
} else { } else {
// The object is not observable, so just render it out and // The object is not observable, so just render it out and
// be done with it. // be done with it.
data.buffer.push(handlebarsGet(pathRoot, path, options)); data.buffer.push(handlebarsGet(currentContext, property, options));
} }
} }
...@@ -18258,14 +18652,10 @@ function simpleBind(property, options) { ...@@ -18258,14 +18652,10 @@ function simpleBind(property, options) {
var data = options.data, var data = options.data,
view = data.view, view = data.view,
currentContext = this, currentContext = this,
pathRoot, path, normalized, normalized, observer;
observer;
normalized = normalizePath(currentContext, property, data); normalized = normalizePath(currentContext, property, data);
pathRoot = normalized.root;
path = normalized.path;
// Set up observers for observable objects // Set up observers for observable objects
if ('object' === typeof this) { if ('object' === typeof this) {
if (data.insideGroup) { if (data.insideGroup) {
...@@ -18273,12 +18663,12 @@ function simpleBind(property, options) { ...@@ -18273,12 +18663,12 @@ function simpleBind(property, options) {
Ember.run.once(view, 'rerender'); Ember.run.once(view, 'rerender');
}; };
var result = handlebarsGet(pathRoot, path, options); var result = handlebarsGet(currentContext, property, options);
if (result === null || result === undefined) { result = ""; } if (result === null || result === undefined) { result = ""; }
data.buffer.push(result); data.buffer.push(result);
} else { } else {
var bindView = new Ember._SimpleHandlebarsView( var bindView = new Ember._SimpleHandlebarsView(
path, pathRoot, !options.hash.unescaped, options.data property, currentContext, !options.hash.unescaped, options.data
); );
bindView._parentView = view; bindView._parentView = view;
...@@ -18293,13 +18683,13 @@ function simpleBind(property, options) { ...@@ -18293,13 +18683,13 @@ function simpleBind(property, options) {
// tells the Ember._HandlebarsBoundView to re-render. If property // tells the Ember._HandlebarsBoundView to re-render. If property
// is an empty string, we are printing the current context // is an empty string, we are printing the current context
// object ({{this}}) so updating it is not our responsibility. // object ({{this}}) so updating it is not our responsibility.
if (path !== '') { if (normalized.path !== '') {
view.registerObserver(pathRoot, path, observer); view.registerObserver(normalized.root, normalized.path, observer);
} }
} else { } else {
// The object is not observable, so just render it out and // The object is not observable, so just render it out and
// be done with it. // be done with it.
data.buffer.push(handlebarsGet(pathRoot, path, options)); data.buffer.push(handlebarsGet(currentContext, property, options));
} }
} }
...@@ -18391,14 +18781,14 @@ EmberHandlebars.registerHelper('boundIf', function(property, fn) { ...@@ -18391,14 +18781,14 @@ EmberHandlebars.registerHelper('boundIf', function(property, fn) {
var truthy = result && get(result, 'isTruthy'); var truthy = result && get(result, 'isTruthy');
if (typeof truthy === 'boolean') { return truthy; } if (typeof truthy === 'boolean') { return truthy; }
if (Ember.typeOf(result) === 'array') { if (Ember.isArray(result)) {
return get(result, 'length') !== 0; return get(result, 'length') !== 0;
} else { } else {
return !!result; return !!result;
} }
}; };
return bind.call(context, property, fn, true, func, func); return bind.call(context, property, fn, true, func, func, ['isTruthy', 'length']);
}); });
/** /**
...@@ -18635,16 +19025,13 @@ EmberHandlebars.registerHelper('bindAttr', function(options) { ...@@ -18635,16 +19025,13 @@ EmberHandlebars.registerHelper('bindAttr', function(options) {
// current value of the property as an attribute. // current value of the property as an attribute.
forEach.call(attrKeys, function(attr) { forEach.call(attrKeys, function(attr) {
var path = attrs[attr], var path = attrs[attr],
pathRoot, normalized; normalized;
Ember.assert(fmt("You must provide a String for a bound attribute, not %@", [path]), typeof path === 'string'); Ember.assert(fmt("You must provide a String for a bound attribute, not %@", [path]), typeof path === 'string');
normalized = normalizePath(ctx, path, options.data); normalized = normalizePath(ctx, path, options.data);
pathRoot = normalized.root; var value = (path === 'this') ? normalized.root : handlebarsGet(ctx, path, options),
path = normalized.path;
var value = (path === 'this') ? pathRoot : handlebarsGet(pathRoot, path, options),
type = Ember.typeOf(value); type = Ember.typeOf(value);
Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [value]), value === null || value === undefined || type === 'number' || type === 'string' || type === 'boolean'); Ember.assert(fmt("Attributes must be numbers, strings or booleans, not %@", [value]), value === null || value === undefined || type === 'number' || type === 'string' || type === 'boolean');
...@@ -18652,7 +19039,7 @@ EmberHandlebars.registerHelper('bindAttr', function(options) { ...@@ -18652,7 +19039,7 @@ EmberHandlebars.registerHelper('bindAttr', function(options) {
var observer, invoker; var observer, invoker;
observer = function observer() { observer = function observer() {
var result = handlebarsGet(pathRoot, path, options); var result = handlebarsGet(ctx, path, options);
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'); 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');
...@@ -18663,7 +19050,7 @@ EmberHandlebars.registerHelper('bindAttr', function(options) { ...@@ -18663,7 +19050,7 @@ EmberHandlebars.registerHelper('bindAttr', function(options) {
// In that case, we can assume the template has been re-rendered // In that case, we can assume the template has been re-rendered
// and we need to clean up the observer. // and we need to clean up the observer.
if (!elem || elem.length === 0) { if (!elem || elem.length === 0) {
Ember.removeObserver(pathRoot, path, invoker); Ember.removeObserver(normalized.root, normalized.path, invoker);
return; return;
} }
...@@ -18678,7 +19065,7 @@ EmberHandlebars.registerHelper('bindAttr', function(options) { ...@@ -18678,7 +19065,7 @@ EmberHandlebars.registerHelper('bindAttr', function(options) {
// When the observer fires, find the element using the // When the observer fires, find the element using the
// unique data id and update the attribute to the new value. // unique data id and update the attribute to the new value.
if (path !== 'this') { if (path !== 'this') {
view.registerObserver(pathRoot, path, invoker); view.registerObserver(normalized.root, normalized.path, invoker);
} }
// if this changes, also change the logic in ember-views/lib/views/view.js // if this changes, also change the logic in ember-views/lib/views/view.js
...@@ -18768,7 +19155,7 @@ EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, ...@@ -18768,7 +19155,7 @@ EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId,
// class name. // class name.
observer = function() { observer = function() {
// Get the current value of the property // Get the current value of the property
newClass = classStringForPath(pathRoot, parsedPath, options); newClass = classStringForPath(context, parsedPath, options);
elem = bindAttrId ? view.$("[data-bindattr-" + bindAttrId + "='" + bindAttrId + "']") : view.$(); elem = bindAttrId ? view.$("[data-bindattr-" + bindAttrId + "='" + bindAttrId + "']") : view.$();
// If we can't find the element anymore, a parent template has been // If we can't find the element anymore, a parent template has been
...@@ -18802,7 +19189,7 @@ EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId, ...@@ -18802,7 +19189,7 @@ EmberHandlebars.bindClasses = function(context, classBindings, view, bindAttrId,
// We've already setup the observer; now we just need to figure out the // We've already setup the observer; now we just need to figure out the
// correct behavior right now on the first pass through. // correct behavior right now on the first pass through.
value = classStringForPath(pathRoot, parsedPath, options); value = classStringForPath(context, parsedPath, options);
if (value) { if (value) {
ret.push(value); ret.push(value);
...@@ -19350,13 +19737,10 @@ Ember.Handlebars.registerHelper('collection', function(path, options) { ...@@ -19350,13 +19737,10 @@ Ember.Handlebars.registerHelper('collection', function(path, options) {
} else if (hash.emptyViewClass) { } else if (hash.emptyViewClass) {
emptyViewClass = handlebarsGet(this, hash.emptyViewClass, options); emptyViewClass = handlebarsGet(this, hash.emptyViewClass, options);
} }
hash.emptyView = emptyViewClass; if (emptyViewClass) { hash.emptyView = emptyViewClass; }
if (hash.eachHelper === 'each') { if(!hash.keyword){
itemHash._context = Ember.computed(function() { itemHash._context = Ember.computed.alias('content');
return get(this, 'content');
}).property('content');
delete hash.eachHelper;
} }
var viewString = view.toString(); var viewString = view.toString();
...@@ -19389,13 +19773,31 @@ var handlebarsGet = Ember.Handlebars.get; ...@@ -19389,13 +19773,31 @@ var handlebarsGet = Ember.Handlebars.get;
<div>{{unbound somePropertyThatDoesntChange}}</div> <div>{{unbound somePropertyThatDoesntChange}}</div>
``` ```
`unbound` can also be used in conjunction with a bound helper to
render it in its unbound form:
```handlebars
<div>{{unbound helperName somePropertyThatDoesntChange}}</div>
```
@method unbound @method unbound
@for Ember.Handlebars.helpers @for Ember.Handlebars.helpers
@param {String} property @param {String} property
@return {String} HTML string @return {String} HTML string
*/ */
Ember.Handlebars.registerHelper('unbound', function(property, fn) { Ember.Handlebars.registerHelper('unbound', function(property, fn) {
var context = (fn.contexts && fn.contexts[0]) || this; var options = arguments[arguments.length - 1], helper, context, out;
if(arguments.length > 2) {
// Unbound helper call.
options.data.isUnbound = true;
helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helperMissing;
out = helper.apply(this, Array.prototype.slice.call(arguments, 1));
delete options.data.isUnbound;
return out;
}
context = (fn.contexts && fn.contexts[0]) || this;
return handlebarsGet(context, property, fn); return handlebarsGet(context, property, fn);
}); });
...@@ -19470,6 +19872,8 @@ Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, { ...@@ -19470,6 +19872,8 @@ Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, {
set(controller, 'itemController', itemController); set(controller, 'itemController', itemController);
set(controller, 'container', get(this, 'controller.container')); set(controller, 'container', get(this, 'controller.container'));
set(controller, '_eachView', this); set(controller, '_eachView', this);
set(controller, 'target', get(this, 'controller'));
this.disableContentObservers(function() { this.disableContentObservers(function() {
set(this, 'content', controller); set(this, 'content', controller);
binding = new Ember.Binding('content', '_eachView.dataSource').oneWay(); binding = new Ember.Binding('content', '_eachView.dataSource').oneWay();
...@@ -19631,7 +20035,8 @@ GroupedEach.prototype = { ...@@ -19631,7 +20035,8 @@ GroupedEach.prototype = {
/** /**
The `{{#each}}` helper loops over elements in a collection, rendering its The `{{#each}}` helper loops over elements in a collection, rendering its
block once for each item: block once for each item. It is an extension of the base Handlebars `{{#each}}`
helper:
```javascript ```javascript
Developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}]; Developers = [{name: 'Yehuda'},{name: 'Tom'}, {name: 'Paul'}];
...@@ -19663,12 +20068,20 @@ GroupedEach.prototype = { ...@@ -19663,12 +20068,20 @@ GroupedEach.prototype = {
{{this}} {{this}}
{{/each}} {{/each}}
``` ```
### {{else}} condition
`{{#each}}` can have a matching `{{else}}`. The contents of this block will render
if the collection is empty.
### Blockless Use ```
{{#each person in Developers}}
If you provide an `itemViewClass` option that has its own `template` you can {{person.name}}
omit the block in a similar way to how it can be done with the collection {{else}}
helper. <p>Sorry, nobody is available for this task.</p>
{{/each}}
```
### Specifying a View class for items
If you provide an `itemViewClass` option that references a view class
with its own `template` you can omit the block.
The following template: The following template:
...@@ -19694,8 +20107,6 @@ GroupedEach.prototype = { ...@@ -19694,8 +20107,6 @@ GroupedEach.prototype = {
App.AnItemView = Ember.View.extend({ App.AnItemView = Ember.View.extend({
template: Ember.Handlebars.compile("Greetings {{name}}") template: Ember.Handlebars.compile("Greetings {{name}}")
}); });
App.initialize();
``` ```
Will result in the HTML structure below Will result in the HTML structure below
...@@ -19708,10 +20119,38 @@ GroupedEach.prototype = { ...@@ -19708,10 +20119,38 @@ GroupedEach.prototype = {
</div> </div>
``` ```
### Representing each item with a Controller.
By default the controller lookup within an `{{#each}}` block will be
the controller of the template where the `{{#each}}` was used. If each
item needs to be presented by a custom controller you can provide a
`itemController` option which references a controller by lookup name.
Each item in the loop will be wrapped in an instance of this controller
and the item itself will be set to the `content` property of that controller.
This is useful in cases where properties of model objects need transformation
or synthesis for display:
```javascript
App.DeveloperController = Ember.ObjectController.extend({
isAvailableForHire: function(){
return !this.get('content.isEmployed') && this.get('content.isSeekingWork');
}.property('isEmployed', 'isSeekingWork')
})
```
```handlebars
{{#each person in Developers itemController="developer"}}
{{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}}
{{/each}}
```
@method each @method each
@for Ember.Handlebars.helpers @for Ember.Handlebars.helpers
@param [name] {String} name for item (used with `in`) @param [name] {String} name for item (used with `in`)
@param path {String} path @param path {String} path
@param [options] {Object} Handlebars key/value pairs of options
@param [options.itemViewClass] {String} a path to a view class used for each item
@param [options.itemController] {String} name of a controller to be created for each item
*/ */
Ember.Handlebars.registerHelper('each', function(path, options) { Ember.Handlebars.registerHelper('each', function(path, options) {
if (arguments.length === 4) { if (arguments.length === 4) {
...@@ -19724,8 +20163,6 @@ Ember.Handlebars.registerHelper('each', function(path, options) { ...@@ -19724,8 +20163,6 @@ Ember.Handlebars.registerHelper('each', function(path, options) {
if (path === '') { path = "this"; } if (path === '') { path = "this"; }
options.hash.keyword = keywordName; options.hash.keyword = keywordName;
} else {
options.hash.eachHelper = 'each';
} }
options.hash.dataSourceBinding = path; options.hash.dataSourceBinding = path;
...@@ -20087,7 +20524,7 @@ Ember.TextField = Ember.View.extend(Ember.TextSupport, ...@@ -20087,7 +20524,7 @@ Ember.TextField = Ember.View.extend(Ember.TextSupport,
classNames: ['ember-text-field'], classNames: ['ember-text-field'],
tagName: "input", tagName: "input",
attributeBindings: ['type', 'value', 'size'], attributeBindings: ['type', 'value', 'size', 'pattern'],
/** /**
The `value` attribute of the input element. As the user inputs text, this The `value` attribute of the input element. As the user inputs text, this
...@@ -20117,6 +20554,15 @@ Ember.TextField = Ember.View.extend(Ember.TextSupport, ...@@ -20117,6 +20554,15 @@ Ember.TextField = Ember.View.extend(Ember.TextSupport,
*/ */
size: null, size: null,
/**
The `pattern` the pattern attribute of input element.
@property pattern
@type String
@default null
*/
pattern: null,
/** /**
The action to be sent when the user presses the return key. The action to be sent when the user presses the return key.
...@@ -20130,15 +20576,34 @@ Ember.TextField = Ember.View.extend(Ember.TextSupport, ...@@ -20130,15 +20576,34 @@ Ember.TextField = Ember.View.extend(Ember.TextSupport,
*/ */
action: null, action: null,
insertNewline: function() { /**
Whether they `keyUp` event that triggers an `action` to be sent continues
propagating to other views.
By default, when the user presses the return key on their keyboard and
the text field has an `action` set, the action will be sent to the view's
controller and the key event will stop propagating.
If you would like parent views to receive the `keyUp` event even after an
action has been dispatched, set `bubbles` to true.
@property bubbles
@type Boolean
@default false
*/
bubbles: false,
insertNewline: function(event) {
var controller = get(this, 'controller'), var controller = get(this, 'controller'),
action = get(this, 'action'); action = get(this, 'action');
if (action) { if (action) {
controller.send(action, get(this, 'value')); controller.send(action, get(this, 'value'), this);
}
return false; if (!get(this, 'bubbles')) {
event.stopPropagation();
}
}
} }
}); });
...@@ -20612,38 +21077,37 @@ Ember.Select = Ember.View.extend( ...@@ -20612,38 +21077,37 @@ Ember.Select = Ember.View.extend(
tagName: 'select', tagName: 'select',
classNames: ['ember-select'], classNames: ['ember-select'],
defaultTemplate: Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { defaultTemplate: Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [2,'>= 1.0.0-rc.3'];
helpers = helpers || Ember.Handlebars.helpers; data = data || {}; helpers = helpers || Ember.Handlebars.helpers; data = data || {};
var buffer = '', stack1, hashTypes, escapeExpression=this.escapeExpression, self=this; var buffer = '', stack1, hashTypes, escapeExpression=this.escapeExpression, self=this;
function program1(depth0,data) { function program1(depth0,data) {
var buffer = '', stack1, hashTypes; var buffer = '', hashTypes;
data.buffer.push("<option value=\"\">"); data.buffer.push("<option value=\"\">");
stack1 = {};
hashTypes = {}; hashTypes = {};
stack1 = helpers._triageMustache.call(depth0, "view.prompt", {hash:stack1,contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data}); data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "view.prompt", {hash:{},contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data})));
data.buffer.push(escapeExpression(stack1) + "</option>"); data.buffer.push("</option>");
return buffer;} return buffer;
}
function program3(depth0,data) { function program3(depth0,data) {
var stack1, hashTypes; var hashTypes;
stack1 = {}; hashTypes = {'contentBinding': "STRING"};
hashTypes = {}; data.buffer.push(escapeExpression(helpers.view.call(depth0, "Ember.SelectOption", {hash:{
hashTypes['contentBinding'] = "STRING"; 'contentBinding': ("this")
stack1['contentBinding'] = "this"; },contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data})));
stack1 = helpers.view.call(depth0, "Ember.SelectOption", {hash:stack1,contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data}); }
data.buffer.push(escapeExpression(stack1));}
stack1 = {};
hashTypes = {}; hashTypes = {};
stack1 = helpers['if'].call(depth0, "view.prompt", {hash:stack1,inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data}); stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data});
if(stack1 || stack1 === 0) { data.buffer.push(stack1); } if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
stack1 = {};
hashTypes = {}; hashTypes = {};
stack1 = helpers.each.call(depth0, "view.content", {hash:stack1,inverse:self.noop,fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data}); stack1 = helpers.each.call(depth0, "view.content", {hash:{},inverse:self.noop,fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data});
if(stack1 || stack1 === 0) { data.buffer.push(stack1); } if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
return buffer; return buffer;
}), }),
attributeBindings: ['multiple', 'disabled', 'tabindex'], attributeBindings: ['multiple', 'disabled', 'tabindex'],
...@@ -22112,21 +22576,23 @@ define("router", ...@@ -22112,21 +22576,23 @@ define("router",
function trigger(router, args) { function trigger(router, args) {
var currentHandlerInfos = router.currentHandlerInfos; var currentHandlerInfos = router.currentHandlerInfos;
var name = args.shift();
if (!currentHandlerInfos) { if (!currentHandlerInfos) {
throw new Error("Could not trigger event. There are no active handlers"); throw new Error("Could not trigger event '" + name + "'. There are no active handlers");
} }
var name = args.shift();
for (var i=currentHandlerInfos.length-1; i>=0; i--) { for (var i=currentHandlerInfos.length-1; i>=0; i--) {
var handlerInfo = currentHandlerInfos[i], var handlerInfo = currentHandlerInfos[i],
handler = handlerInfo.handler; handler = handlerInfo.handler;
if (handler.events && handler.events[name]) { if (handler.events && handler.events[name]) {
handler.events[name].apply(handler, args); handler.events[name].apply(handler, args);
break; return;
} }
} }
throw new Error("Nothing handled the event '" + name + "'.");
} }
function setContext(handler, context) { function setContext(handler, context) {
...@@ -22272,7 +22738,7 @@ Ember.generateController = function(container, controllerName, context) { ...@@ -22272,7 +22738,7 @@ Ember.generateController = function(container, controllerName, context) {
var Router = requireModule("router"); var Router = requireModule("router");
var get = Ember.get, set = Ember.set, classify = Ember.String.classify; var get = Ember.get, set = Ember.set, classify = Ember.String.classify;
var DefaultView = Ember.View.extend(Ember._Metamorph); var DefaultView = Ember._MetamorphView;
function setupLocation(router) { function setupLocation(router) {
var location = get(router, 'location'), var location = get(router, 'location'),
rootURL = get(router, 'rootURL'); rootURL = get(router, 'rootURL');
...@@ -22288,6 +22754,14 @@ function setupLocation(router) { ...@@ -22288,6 +22754,14 @@ function setupLocation(router) {
} }
} }
/**
The `Ember.Router` class manages the application state and URLs. Refer to
the [routing guide](http://emberjs.com/guides/routing/) for documentation.
@class Router
@namespace Ember
@extends Ember.Object
*/
Ember.Router = Ember.Object.extend({ Ember.Router = Ember.Object.extend({
location: 'hash', location: 'hash',
...@@ -22297,6 +22771,10 @@ Ember.Router = Ember.Object.extend({ ...@@ -22297,6 +22771,10 @@ Ember.Router = Ember.Object.extend({
setupLocation(this); setupLocation(this);
}, },
url: Ember.computed(function() {
return get(this, 'location').getURL();
}),
startRouting: function() { startRouting: function() {
this.router = this.router || this.constructor.map(Ember.K); this.router = this.router || this.constructor.map(Ember.K);
...@@ -22310,16 +22788,17 @@ Ember.Router = Ember.Object.extend({ ...@@ -22310,16 +22788,17 @@ Ember.Router = Ember.Object.extend({
container.register('view', 'default', DefaultView); container.register('view', 'default', DefaultView);
container.register('view', 'toplevel', Ember.View.extend()); container.register('view', 'toplevel', Ember.View.extend());
this.handleURL(location.getURL());
location.onUpdateURL(function(url) { location.onUpdateURL(function(url) {
self.handleURL(url); self.handleURL(url);
}); });
this.handleURL(location.getURL());
}, },
didTransition: function(infos) { didTransition: function(infos) {
// Don't do any further action here if we redirected // Don't do any further action here if we redirected
for (var i=0, l=infos.length; i<l; i++) { for (var i=0, l=infos.length; i<l; i++) {
if (infos[i].handler.transitioned) { return; } if (infos[i].handler.redirected) { return; }
} }
var appController = this.container.lookup('controller:application'), var appController = this.container.lookup('controller:application'),
...@@ -22359,11 +22838,7 @@ Ember.Router = Ember.Object.extend({ ...@@ -22359,11 +22838,7 @@ Ember.Router = Ember.Object.extend({
}, },
send: function(name, context) { send: function(name, context) {
if (Ember.$ && context instanceof Ember.$.Event) { this.router.trigger.apply(this.router, arguments);
context = context.context;
}
this.router.trigger(name, context);
}, },
hasRoute: function(route) { hasRoute: function(route) {
...@@ -22524,12 +22999,70 @@ var get = Ember.get, set = Ember.set, ...@@ -22524,12 +22999,70 @@ var get = Ember.get, set = Ember.set,
classify = Ember.String.classify, classify = Ember.String.classify,
decamelize = Ember.String.decamelize; decamelize = Ember.String.decamelize;
/**
The `Ember.Route` class is used to define individual routes. Refer to
the [routing guide](http://emberjs.com/guides/routing/) for documentation.
@class Route
@namespace Ember
@extends Ember.Object
*/
Ember.Route = Ember.Object.extend({ Ember.Route = Ember.Object.extend({
/**
@private
@method exit
*/
exit: function() { exit: function() {
this.deactivate();
teardownView(this); teardownView(this);
}, },
/**
@private
@method enter
*/
enter: function() {
this.activate();
},
/**
The collection of functions keyed by name available on this route as
action targets.
These functions will be invoked when a matching `{{action}}` is triggered
from within a template and the application's current route is this route.
Events can also be invoked from other parts of your application via `Route#send`.
The context of event will be the this route.
@see {Ember.Route#send}
@see {Handlebars.helpers.action}
@property events
@type Hash
@default null
*/
events: null,
/**
This hook is executed when the router completely exits this route. It is
not executed when the model for the route changes.
@method deactivate
*/
deactivate: Ember.K,
/**
This hook is executed when the router enters the route for the first time.
It is not executed when the model for the route changes.
@method activate
*/
activate: Ember.K,
/** /**
Transition into another route. Optionally supply a model for the Transition into another route. Optionally supply a model for the
route in question. The model will be serialized into the URL route in question. The model will be serialized into the URL
...@@ -22540,7 +23073,7 @@ Ember.Route = Ember.Object.extend({ ...@@ -22540,7 +23073,7 @@ Ember.Route = Ember.Object.extend({
@param {...Object} models the @param {...Object} models the
*/ */
transitionTo: function() { transitionTo: function() {
this.transitioned = true; if (this._checkingRedirect) { this.redirected = true; }
return this.router.transitionTo.apply(this.router, arguments); return this.router.transitionTo.apply(this.router, arguments);
}, },
...@@ -22553,7 +23086,7 @@ Ember.Route = Ember.Object.extend({ ...@@ -22553,7 +23086,7 @@ Ember.Route = Ember.Object.extend({
@param {...Object} models the @param {...Object} models the
*/ */
replaceWith: function() { replaceWith: function() {
this.transitioned = true; if (this._checkingRedirect) { this.redirected = true; }
return this.router.replaceWith.apply(this.router, arguments); return this.router.replaceWith.apply(this.router, arguments);
}, },
...@@ -22569,14 +23102,18 @@ Ember.Route = Ember.Object.extend({ ...@@ -22569,14 +23102,18 @@ Ember.Route = Ember.Object.extend({
@method setup @method setup
*/ */
setup: function(context) { setup: function(context) {
this.transitioned = false; this.redirected = false;
this._checkingRedirect = true;
this.redirect(context); this.redirect(context);
if (this.transitioned) { return false; } this._checkingRedirect = false;
if (this.redirected) { return false; }
var controller = this.controllerFor(this.routeName, context); var controller = this.controllerFor(this.routeName, context);
if (controller) { if (controller) {
this.controller = controller;
set(controller, 'model', context); set(controller, 'model', context);
} }
...@@ -22890,6 +23427,10 @@ Ember.Route = Ember.Object.extend({ ...@@ -22890,6 +23427,10 @@ Ember.Route = Ember.Object.extend({
if (options.outlet === 'main') { this.lastRenderedTemplate = name; } if (options.outlet === 'main') { this.lastRenderedTemplate = name; }
appendView(this, view, options); appendView(this, view, options);
},
willDestroy: function() {
teardownView(this);
} }
}); });
...@@ -22905,15 +23446,17 @@ function parentRoute(route) { ...@@ -22905,15 +23446,17 @@ function parentRoute(route) {
} }
} }
function parentTemplate(route) { function parentTemplate(route, isRecursive) {
var parent = parentRoute(route), template; var parent = parentRoute(route), template;
if (!parent) { return; } if (!parent) { return; }
Ember.warn("The immediate parent route did not render into the main outlet and the default 'into' option may not be expected", !isRecursive);
if (template = parent.lastRenderedTemplate) { if (template = parent.lastRenderedTemplate) {
return template; return template;
} else { } else {
return parentTemplate(parent); return parentTemplate(parent, true);
} }
} }
...@@ -22924,6 +23467,8 @@ function normalizeOptions(route, name, template, options) { ...@@ -22924,6 +23467,8 @@ function normalizeOptions(route, name, template, options) {
options.name = name; options.name = name;
options.template = template; options.template = template;
Ember.assert("An outlet ("+options.outlet+") was specified but this view will render at the root level.", options.outlet === 'main' || options.into);
var controller = options.controller, namedController; var controller = options.controller, namedController;
if (options.controller) { if (options.controller) {
...@@ -22950,6 +23495,8 @@ function setupView(view, container, options) { ...@@ -22950,6 +23495,8 @@ function setupView(view, container, options) {
if (!get(view, 'templateName')) { if (!get(view, 'templateName')) {
set(view, 'template', options.template); set(view, 'template', options.template);
set(view, '_debugTemplateName', options.name);
} }
set(view, 'renderedName', options.name); set(view, 'renderedName', options.name);
...@@ -22972,7 +23519,7 @@ function appendView(route, view, options) { ...@@ -22972,7 +23519,7 @@ function appendView(route, view, options) {
} }
function teardownTopLevel(view) { function teardownTopLevel(view) {
return function() { view.remove(); }; return function() { view.destroy(); };
} }
function teardownOutlet(parentView, outlet) { function teardownOutlet(parentView, outlet) {
...@@ -22997,6 +23544,11 @@ function teardownView(route) { ...@@ -22997,6 +23544,11 @@ function teardownView(route) {
(function() { (function() {
/**
@module ember
@submodule ember-routing
*/
var get = Ember.get, set = Ember.set; var get = Ember.get, set = Ember.set;
Ember.onLoad('Ember.Handlebars', function(Handlebars) { Ember.onLoad('Ember.Handlebars', function(Handlebars) {
...@@ -23039,6 +23591,12 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ...@@ -23039,6 +23591,12 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
attributeBindings: ['href', 'title'], attributeBindings: ['href', 'title'],
classNameBindings: 'active', classNameBindings: 'active',
// Even though this isn't a virtual view, we want to treat it as if it is
// so that you can access the parent with {{view.prop}}
concreteView: Ember.computed(function() {
return get(this, 'parentView');
}).property('parentView').volatile(),
active: Ember.computed(function() { active: Ember.computed(function() {
var router = this.get('router'), var router = this.get('router'),
params = resolvedPaths(this.parameters), params = resolvedPaths(this.parameters),
...@@ -23076,6 +23634,13 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ...@@ -23076,6 +23634,13 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
LinkView.toString = function() { return "LinkView"; }; LinkView.toString = function() { return "LinkView"; };
/**
@method linkTo
@for Ember.Handlebars.helpers
@param {String} routeName
@param {Object} [context]*
@return {String} HTML string
*/
Ember.Handlebars.registerHelper('linkTo', function(name) { Ember.Handlebars.registerHelper('linkTo', function(name) {
var options = [].slice.call(arguments, -1)[0]; var options = [].slice.call(arguments, -1)[0];
var params = [].slice.call(arguments, 1, -1); var params = [].slice.call(arguments, 1, -1);
...@@ -23172,45 +23737,6 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ...@@ -23172,45 +23737,6 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
return Handlebars.helpers.view.call(this, Handlebars.OutletView, options); return Handlebars.helpers.view.call(this, Handlebars.OutletView, options);
}); });
Ember.View.reopen({
init: function() {
set(this, '_outlets', {});
this._super();
},
connectOutlet: function(outletName, view) {
var outlets = get(this, '_outlets'),
container = get(this, 'container'),
router = container && container.lookup('router:main'),
oldView = get(outlets, outletName),
renderedName = get(view, 'renderedName');
set(outlets, outletName, view);
if (router) {
if (oldView) {
router._disconnectActiveView(oldView);
}
if (renderedName) {
router._connectActiveView(renderedName, view);
}
}
},
disconnectOutlet: function(outletName) {
var outlets = get(this, '_outlets'),
container = get(this, 'container'),
router = container && container.lookup('router:main'),
view = get(outlets, outletName);
set(outlets, outletName, null);
if (router && view) {
router._disconnectActiveView(view);
}
}
});
}); });
})(); })();
...@@ -23226,17 +23752,34 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ...@@ -23226,17 +23752,34 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
var get = Ember.get, set = Ember.set; var get = Ember.get, set = Ember.set;
Ember.onLoad('Ember.Handlebars', function(Handlebars) { Ember.onLoad('Ember.Handlebars', function(Handlebars) {
Ember.Handlebars.registerHelper('render', function(name, context, options) { /**
Renders the named template in the current context using the singleton
instance of the same-named controller.
If a view class with the same name exists, uses the view class.
If a `model` is specified, it becomes the model for that controller.
The default target for `{{action}}`s in the rendered template is the
named controller.
@method action
@for Ember.Handlebars.helpers
@param {String} actionName
@param {Object?} model
@param {Hash} options
*/
Ember.Handlebars.registerHelper('render', function(name, contextString, options) {
Ember.assert("You must pass a template to render", arguments.length >= 2); Ember.assert("You must pass a template to render", arguments.length >= 2);
var container, router, controller, view; var container, router, controller, view, context;
if (arguments.length === 2) { if (arguments.length === 2) {
options = context; options = contextString;
context = undefined; contextString = undefined;
} }
if (typeof context === 'string') { if (typeof contextString === 'string') {
context = Ember.Handlebars.get(options.contexts[1], context, options); context = Ember.Handlebars.get(options.contexts[1], contextString, options);
} }
name = name.replace(/\//g, '.'); name = name.replace(/\//g, '.');
...@@ -23257,6 +23800,14 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ...@@ -23257,6 +23800,14 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
controller.set('model', context); controller.set('model', context);
} }
var root = options.contexts[1];
if (root) {
view.registerObserver(root, contextString, function() {
controller.set('model', Ember.Handlebars.get(root, contextString, options));
});
}
controller.set('target', options.data.keywords.controller); controller.set('target', options.data.keywords.controller);
options.hash.viewName = Ember.String.camelize(name); options.hash.viewName = Ember.String.camelize(name);
...@@ -23323,12 +23874,20 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ...@@ -23323,12 +23874,20 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
contexts = options.contexts, contexts = options.contexts,
target = options.target; target = options.target;
if (target.target) {
target = handlebarsGet(target.root, target.target, target.options);
} else {
target = target.root;
}
Ember.run(function() {
if (target.send) { if (target.send) {
return target.send.apply(target, args(options.parameters, actionName)); target.send.apply(target, args(options.parameters, actionName));
} else { } else {
Ember.assert("The action '" + actionName + "' did not exist on " + target, typeof target[actionName] === 'function'); Ember.assert("The action '" + actionName + "' did not exist on " + target, typeof target[actionName] === 'function');
return target[actionName].apply(target, args(options.parameters)); target[actionName].apply(target, args(options.parameters));
} }
});
} }
}; };
...@@ -23508,7 +24067,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ...@@ -23508,7 +24067,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
@method action @method action
@for Ember.Handlebars.helpers @for Ember.Handlebars.helpers
@param {String} actionName @param {String} actionName
@param {Object...} contexts @param {Object} [context]*
@param {Hash} options @param {Hash} options
*/ */
EmberHandlebars.registerHelper('action', function(actionName) { EmberHandlebars.registerHelper('action', function(actionName) {
...@@ -23517,7 +24076,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ...@@ -23517,7 +24076,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
var hash = options.hash, var hash = options.hash,
view = options.data.view, view = options.data.view,
target, controller, link; controller, link;
// create a hash to pass along to registerAction // create a hash to pass along to registerAction
var action = { var action = {
...@@ -23532,13 +24091,16 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ...@@ -23532,13 +24091,16 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
action.view = view = get(view, 'concreteView'); action.view = view = get(view, 'concreteView');
var root, target;
if (hash.target) { if (hash.target) {
target = handlebarsGet(this, hash.target, options); root = this;
target = hash.target;
} else if (controller = options.data.keywords.controller) { } else if (controller = options.data.keywords.controller) {
target = controller; root = controller;
} }
action.target = target; action.target = { root: root, target: target, options: options };
action.bubbles = hash.bubbles; action.bubbles = hash.bubbles;
var actionId = ActionHelper.registerAction(actionName, action); var actionId = ActionHelper.registerAction(actionName, action);
...@@ -23551,109 +24113,126 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ...@@ -23551,109 +24113,126 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
(function() {
})();
(function() { (function() {
/** /**
@module ember @module ember
@submodule ember-routing @submodule ember-routing
*/ */
var get = Ember.get, set = Ember.set; if (Ember.ENV.EXPERIMENTAL_CONTROL_HELPER) {
var ControllersProxy = Ember.Object.extend({ var get = Ember.get, set = Ember.set;
controller: null,
unknownProperty: function(controllerName) { /**
var controller = get(this, 'controller'), The control helper is currently under development and is considered experimental.
needs = get(controller, 'needs'), To enable it, set `ENV.EXPERIMENTAL_CONTROL_HELPER = true` before requiring Ember.
dependency;
for (var i=0, l=needs.length; i<l; i++) { @method control
dependency = needs[i]; @for Ember.Handlebars.helpers
if (dependency === controllerName) { @param {String} path
return controller.controllerFor(controllerName); @param {String} modelPath
@param {Hash} options
@return {String} HTML string
*/
Ember.Handlebars.registerHelper('control', function(path, modelPath, options) {
if (arguments.length === 2) {
options = modelPath;
modelPath = undefined;
} }
var model;
if (modelPath) {
model = Ember.Handlebars.get(this, modelPath, options);
} }
var controller = options.data.keywords.controller,
view = options.data.keywords.view,
children = get(controller, '_childContainers'),
controlID = options.hash.controlID,
container, subContainer;
if (children.hasOwnProperty(controlID)) {
subContainer = children[controlID];
} else {
container = get(controller, 'container'),
subContainer = container.child();
children[controlID] = subContainer;
} }
});
Ember.ControllerMixin.reopen({ var normalizedPath = path.replace(/\//g, '.');
concatenatedProperties: ['needs'],
needs: [],
init: function() { var childView = subContainer.lookup('view:' + normalizedPath) || subContainer.lookup('view:default'),
this._super.apply(this, arguments); childController = subContainer.lookup('controller:' + normalizedPath),
childTemplate = subContainer.lookup('template:' + path);
// Structure asserts to still do verification but not string concat in production Ember.assert("Could not find controller for path: " + normalizedPath, childController);
if(!verifyDependencies(this)) { Ember.assert("Could not find view for path: " + normalizedPath, childView);
Ember.assert("Missing dependencies", false);
set(childController, 'target', controller);
set(childController, 'model', model);
options.hash.template = childTemplate;
options.hash.controller = childController;
function observer() {
var model = Ember.Handlebars.get(this, modelPath, options);
set(childController, 'model', model);
childView.rerender();
} }
},
transitionToRoute: function() { Ember.addObserver(this, modelPath, observer);
var target = get(this, 'target'); childView.one('willDestroyElement', this, function() {
Ember.removeObserver(this, modelPath, observer);
});
Ember.Handlebars.helpers.view.call(this, childView, options);
});
}
})();
(function() {
})();
(function() {
/**
@module ember
@submodule ember-routing
*/
var get = Ember.get, set = Ember.set;
return target.transitionTo.apply(target, arguments); Ember.ControllerMixin.reopen({
transitionToRoute: function() {
// target may be either another controller or a router
var target = get(this, 'target'),
method = target.transitionToRoute || target.transitionTo;
return method.apply(target, arguments);
}, },
// TODO: Deprecate this, see https://github.com/emberjs/ember.js/issues/1785
transitionTo: function() { transitionTo: function() {
Ember.deprecate("transitionTo is deprecated. Please use transitionToRoute.");
return this.transitionToRoute.apply(this, arguments); return this.transitionToRoute.apply(this, arguments);
}, },
replaceRoute: function() { replaceRoute: function() {
var target = get(this, 'target'); // target may be either another controller or a router
var target = get(this, 'target'),
return target.replaceWith.apply(target, arguments); method = target.replaceRoute || target.replaceWith;
return method.apply(target, arguments);
}, },
// TODO: Deprecate this, see https://github.com/emberjs/ember.js/issues/1785
replaceWith: function() { replaceWith: function() {
Ember.deprecate("replaceWith is deprecated. Please use replaceRoute.");
return this.replaceRoute.apply(this, arguments); return this.replaceRoute.apply(this, arguments);
},
controllerFor: function(controllerName) {
var container = get(this, 'container');
return container.lookup('controller:' + controllerName);
},
model: Ember.computed(function(key, value) {
if (arguments.length > 1) {
return set(this, 'content', value);
} else {
return get(this, 'content');
} }
}).property('content'),
controllers: Ember.computed(function() {
return ControllersProxy.create({ controller: this });
})
}); });
function verifyDependencies(controller) {
var needs = get(controller, 'needs'),
container = get(controller, 'container'),
dependency, satisfied = true;
for (var i=0, l=needs.length; i<l; i++) {
dependency = needs[i];
if (dependency.indexOf(':') === -1) {
dependency = "controller:" + dependency;
}
if (!container.has(dependency)) {
satisfied = false;
Ember.assert(controller + " needs " + dependency + " but it does not exist", false);
}
}
return satisfied;
}
})(); })();
...@@ -23789,7 +24368,12 @@ Ember.NoneLocation = Ember.Object.extend({ ...@@ -23789,7 +24368,12 @@ Ember.NoneLocation = Ember.Object.extend({
}, },
onUpdateURL: function(callback) { onUpdateURL: function(callback) {
// We are not wired up to the browser, so we'll never trigger the callback. this.updateCallback = callback;
},
handleURL: function(url) {
set(this, 'path', url);
this.updateCallback(url);
}, },
formatURL: function(url) { formatURL: function(url) {
...@@ -23870,6 +24454,7 @@ Ember.HashLocation = Ember.Object.extend({ ...@@ -23870,6 +24454,7 @@ Ember.HashLocation = Ember.Object.extend({
var guid = Ember.guidFor(this); var guid = Ember.guidFor(this);
Ember.$(window).bind('hashchange.ember-location-'+guid, function() { Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
Ember.run(function() {
var path = location.hash.substr(1); var path = location.hash.substr(1);
if (get(self, 'lastSetURL') === path) { return; } if (get(self, 'lastSetURL') === path) { return; }
...@@ -23877,6 +24462,7 @@ Ember.HashLocation = Ember.Object.extend({ ...@@ -23877,6 +24462,7 @@ Ember.HashLocation = Ember.Object.extend({
callback(location.hash.substr(1)); callback(location.hash.substr(1));
}); });
});
}, },
/** /**
...@@ -24335,7 +24921,14 @@ var get = Ember.get, set = Ember.set, ...@@ -24335,7 +24921,14 @@ var get = Ember.get, set = Ember.set,
### Routing ### Routing
In addition to creating your application's router, `Ember.Application` is In addition to creating your application's router, `Ember.Application` is
also responsible for telling the router when to start routing. also responsible for telling the router when to start routing. Transitions
between routes can be logged with the LOG_TRANSITIONS flag:
```javascript
window.App = Ember.Application.create({
LOG_TRANSITIONS: true
});
```
By default, the router will begin trying to translate the current URL into By default, the router will begin trying to translate the current URL into
application state once the browser emits the `DOMContentReady` event. If you application state once the browser emits the `DOMContentReady` event. If you
...@@ -24344,14 +24937,7 @@ var get = Ember.get, set = Ember.set, ...@@ -24344,14 +24937,7 @@ var get = Ember.get, set = Ember.set,
If there is any setup required before routing begins, you can implement a If there is any setup required before routing begins, you can implement a
`ready()` method on your app that will be invoked immediately before routing `ready()` method on your app that will be invoked immediately before routing
begins: begins.
```javascript
window.App = Ember.Application.create({
ready: function() {
this.set('router.enableLogging', true);
}
});
To begin routing, you must have at a minimum a top-level controller and view. To begin routing, you must have at a minimum a top-level controller and view.
You define these as `App.ApplicationController` and `App.ApplicationView`, You define these as `App.ApplicationController` and `App.ApplicationView`,
...@@ -24369,8 +24955,7 @@ var get = Ember.get, set = Ember.set, ...@@ -24369,8 +24955,7 @@ var get = Ember.get, set = Ember.set,
@namespace Ember @namespace Ember
@extends Ember.Namespace @extends Ember.Namespace
*/ */
var Application = Ember.Application = Ember.Namespace.extend( var Application = Ember.Application = Ember.Namespace.extend({
/** @scope Ember.Application.prototype */{
/** /**
The root DOM element of the Application. This can be specified as an The root DOM element of the Application. This can be specified as an
...@@ -24449,6 +25034,12 @@ var Application = Ember.Application = Ember.Namespace.extend( ...@@ -24449,6 +25034,12 @@ var Application = Ember.Application = Ember.Namespace.extend(
this.deferUntilDOMReady(); this.deferUntilDOMReady();
this.scheduleInitialize(); this.scheduleInitialize();
Ember.debug('-------------------------------');
Ember.debug('Ember.VERSION : ' + Ember.VERSION);
Ember.debug('Handlebars.VERSION : ' + Ember.Handlebars.VERSION);
Ember.debug('jQuery.VERSION : ' + Ember.$().jquery);
Ember.debug('-------------------------------');
}, },
/** /**
...@@ -24580,9 +25171,52 @@ var Application = Ember.Application = Ember.Namespace.extend( ...@@ -24580,9 +25171,52 @@ var Application = Ember.Application = Ember.Namespace.extend(
} }
}, },
/**
registers a factory for later injection
Example:
```javascript
App = Ember.Application.create();
App.Person = Ember.Object.extend({});
App.Orange = Ember.Object.extend({});
App.Email = Ember.Object.extend({});
App.register('model:user', App.Person, {singleton: false });
App.register('fruit:favorite', App.Orange);
App.register('communication:main', App.Email, {singleton: false});
```
@method register
@param type {String}
@param name {String}
@param factory {String}
@param options {String} (optional)
**/
register: function() { register: function() {
var container = this.__container__; var container = this.__container__;
return container.register.apply(container, arguments); container.register.apply(container, arguments);
},
/**
defines an injection or typeInjection
Example:
```javascript
App.inject(<full_name or type>, <property name>, <full_name>)
App.inject('model:user', 'email', 'model:email')
App.inject('model', 'source', 'source:main')
```
@method inject
@param factoryNameOrType {String}
@param property {String}
@param injectionName {String}
**/
inject: function(){
var container = this.__container__;
container.injection.apply(container, arguments);
}, },
/** /**
...@@ -24615,6 +25249,15 @@ var Application = Ember.Application = Ember.Namespace.extend( ...@@ -24615,6 +25249,15 @@ var Application = Ember.Application = Ember.Namespace.extend(
return this; return this;
}, },
reset: function() {
get(this, '__container__').destroy();
this.buildContainer();
this.isInitialized = false;
this.initialize();
this.startRouting();
},
/** /**
@private @private
@method runInitializers @method runInitializers
...@@ -24702,6 +25345,12 @@ var Application = Ember.Application = Ember.Namespace.extend( ...@@ -24702,6 +25345,12 @@ var Application = Ember.Application = Ember.Namespace.extend(
router.startRouting(); router.startRouting();
}, },
handleURL: function(url) {
var router = this.__container__.lookup('router:main');
router.handleURL(url);
},
/** /**
Called when the Application has become ready. Called when the Application has become ready.
The call will be delayed until the DOM has become ready. The call will be delayed until the DOM has become ready.
...@@ -24716,7 +25365,7 @@ var Application = Ember.Application = Ember.Namespace.extend( ...@@ -24716,7 +25365,7 @@ var Application = Ember.Application = Ember.Namespace.extend(
var eventDispatcher = get(this, 'eventDispatcher'); var eventDispatcher = get(this, 'eventDispatcher');
if (eventDispatcher) { eventDispatcher.destroy(); } if (eventDispatcher) { eventDispatcher.destroy(); }
this.__container__.destroy(); get(this, '__container__').destroy();
}, },
initializer: function(options) { initializer: function(options) {
...@@ -24765,7 +25414,7 @@ Ember.Application.reopenClass({ ...@@ -24765,7 +25414,7 @@ Ember.Application.reopenClass({
*/ */
buildContainer: function(namespace) { buildContainer: function(namespace) {
var container = new Ember.Container(); var container = new Ember.Container();
Ember.Container.defaultContainer = container; Ember.Container.defaultContainer = Ember.Container.defaultContainer || container;
container.set = Ember.set; container.set = Ember.set;
container.resolver = resolverFor(namespace); container.resolver = resolverFor(namespace);
...@@ -24840,6 +25489,85 @@ Ember.runLoadHooks('Ember.Application', Ember.Application); ...@@ -24840,6 +25489,85 @@ Ember.runLoadHooks('Ember.Application', Ember.Application);
(function() {
/**
@module ember
@submodule ember-routing
*/
var get = Ember.get, set = Ember.set;
var ControllersProxy = Ember.Object.extend({
controller: null,
unknownProperty: function(controllerName) {
var controller = get(this, 'controller'),
needs = get(controller, 'needs'),
container = controller.get('container'),
dependency;
for (var i=0, l=needs.length; i<l; i++) {
dependency = needs[i];
if (dependency === controllerName) {
return container.lookup('controller:' + controllerName);
}
}
}
});
function verifyDependencies(controller) {
var needs = get(controller, 'needs'),
container = get(controller, 'container'),
dependency, satisfied = true;
for (var i=0, l=needs.length; i<l; i++) {
dependency = needs[i];
if (dependency.indexOf(':') === -1) {
dependency = "controller:" + dependency;
}
if (!container.has(dependency)) {
satisfied = false;
Ember.assert(controller + " needs " + dependency + " but it does not exist", false);
}
}
return satisfied;
}
Ember.ControllerMixin.reopen({
concatenatedProperties: ['needs'],
needs: [],
init: function() {
this._super.apply(this, arguments);
// Structure asserts to still do verification but not string concat in production
if(!verifyDependencies(this)) {
Ember.assert("Missing dependencies", false);
}
},
controllerFor: function(controllerName) {
Ember.deprecate("Controller#controllerFor is depcrecated, please use Controller#needs instead");
var container = get(this, 'container');
return container.lookup('controller:' + controllerName);
},
controllers: Ember.computed(function() {
return ControllersProxy.create({ controller: this });
})
});
})();
(function() {
})();
(function() { (function() {
/** /**
Ember Application Ember Application
...@@ -25808,9 +26536,7 @@ Ember.StateManager = Ember.State.extend({ ...@@ -25808,9 +26536,7 @@ Ember.StateManager = Ember.State.extend({
@property currentPath @property currentPath
@type String @type String
*/ */
currentPath: Ember.computed('currentState', function() { currentPath: Ember.computed.alias('currentState.path'),
return get(this, 'currentState.path');
}),
/** /**
The name of transitionEvent that this stateManager will dispatch The name of transitionEvent that this stateManager will dispatch
...@@ -26098,8 +26824,8 @@ Ember States ...@@ -26098,8 +26824,8 @@ Ember States
})(); })();
// Version: v1.0.0-pre.4-45-gd5fb9c4 // Version: v1.0.0-rc.1
// Last commit: d5fb9c4 (2013-01-25 23:22:15 -0800) // Last commit: 8b061b4 (2013-02-15 12:10:22 -0800)
(function() { (function() {
...@@ -26110,4 +26836,3 @@ Ember ...@@ -26110,4 +26836,3 @@ Ember
*/ */
})(); })();
/*
Copyright (C) 2011 by Yehuda Katz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// lib/handlebars/base.js // lib/handlebars/base.js
/*jshint eqnull:true*/ /*jshint eqnull:true*/
...@@ -5,7 +29,13 @@ this.Handlebars = {}; ...@@ -5,7 +29,13 @@ this.Handlebars = {};
(function(Handlebars) { (function(Handlebars) {
Handlebars.VERSION = "1.0.rc.2"; Handlebars.VERSION = "1.0.0-rc.3";
Handlebars.COMPILER_REVISION = 2;
Handlebars.REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '>= 1.0.0-rc.3'
};
Handlebars.helpers = {}; Handlebars.helpers = {};
Handlebars.partials = {}; Handlebars.partials = {};
...@@ -618,9 +648,13 @@ return new Parser; ...@@ -618,9 +648,13 @@ return new Parser;
// lib/handlebars/compiler/base.js // lib/handlebars/compiler/base.js
Handlebars.Parser = handlebars; Handlebars.Parser = handlebars;
Handlebars.parse = function(string) { Handlebars.parse = function(input) {
// Just return if an already-compile AST was passed in.
if(input.constructor === Handlebars.AST.ProgramNode) { return input; }
Handlebars.Parser.yy = Handlebars.AST; Handlebars.Parser.yy = Handlebars.AST;
return Handlebars.Parser.parse(string); return Handlebars.Parser.parse(input);
}; };
Handlebars.print = function(ast) { Handlebars.print = function(ast) {
...@@ -702,8 +736,11 @@ Handlebars.print = function(ast) { ...@@ -702,8 +736,11 @@ Handlebars.print = function(ast) {
for(var i=0,l=parts.length; i<l; i++) { for(var i=0,l=parts.length; i<l; i++) {
var part = parts[i]; var part = parts[i];
if(part === "..") { depth++; } if (part === ".." || part === "." || part === "this") {
else if(part === "." || part === "this") { this.isScoped = true; } if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); }
else if (part === "..") { depth++; }
else { this.isScoped = true; }
}
else { dig.push(part); } else { dig.push(part); }
} }
...@@ -853,6 +890,26 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -853,6 +890,26 @@ Handlebars.JavaScriptCompiler = function() {};
return out.join("\n"); return out.join("\n");
}, },
equals: function(other) {
var len = this.opcodes.length;
if (other.opcodes.length !== len) {
return false;
}
for (var i = 0; i < len; i++) {
var opcode = this.opcodes[i],
otherOpcode = other.opcodes[i];
if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
return false;
}
for (var j = 0; j < opcode.args.length; j++) {
if (opcode.args[j] !== otherOpcode.args[j]) {
return false;
}
}
}
return true;
},
guid: 0, guid: 0,
...@@ -944,7 +1001,7 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -944,7 +1001,7 @@ Handlebars.JavaScriptCompiler = function() {};
// evaluate it by executing `blockHelperMissing` // evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program); this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse); this.opcode('pushProgram', inverse);
this.opcode('pushHash'); this.opcode('emptyHash');
this.opcode('blockValue'); this.opcode('blockValue');
} else { } else {
this.ambiguousMustache(mustache, program, inverse); this.ambiguousMustache(mustache, program, inverse);
...@@ -953,7 +1010,7 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -953,7 +1010,7 @@ Handlebars.JavaScriptCompiler = function() {};
// evaluate it by executing `blockHelperMissing` // evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program); this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse); this.opcode('pushProgram', inverse);
this.opcode('pushHash'); this.opcode('emptyHash');
this.opcode('ambiguousBlockValue'); this.opcode('ambiguousBlockValue');
} }
...@@ -977,6 +1034,7 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -977,6 +1034,7 @@ Handlebars.JavaScriptCompiler = function() {};
this.opcode('assignToHash', pair[0]); this.opcode('assignToHash', pair[0]);
} }
this.opcode('popHash');
}, },
partial: function(partial) { partial: function(partial) {
...@@ -1017,17 +1075,19 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1017,17 +1075,19 @@ Handlebars.JavaScriptCompiler = function() {};
}, },
ambiguousMustache: function(mustache, program, inverse) { ambiguousMustache: function(mustache, program, inverse) {
var id = mustache.id, name = id.parts[0]; var id = mustache.id,
name = id.parts[0],
isBlock = program != null || inverse != null;
this.opcode('getContext', id.depth); this.opcode('getContext', id.depth);
this.opcode('pushProgram', program); this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse); this.opcode('pushProgram', inverse);
this.opcode('invokeAmbiguous', name); this.opcode('invokeAmbiguous', name, isBlock);
}, },
simpleMustache: function(mustache, program, inverse) { simpleMustache: function(mustache) {
var id = mustache.id; var id = mustache.id;
if (id.type === 'DATA') { if (id.type === 'DATA') {
...@@ -1158,7 +1218,7 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1158,7 +1218,7 @@ Handlebars.JavaScriptCompiler = function() {};
if(mustache.hash) { if(mustache.hash) {
this.hash(mustache.hash); this.hash(mustache.hash);
} else { } else {
this.opcode('pushHash'); this.opcode('emptyHash');
} }
return params; return params;
...@@ -1175,7 +1235,7 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1175,7 +1235,7 @@ Handlebars.JavaScriptCompiler = function() {};
if(mustache.hash) { if(mustache.hash) {
this.hash(mustache.hash); this.hash(mustache.hash);
} else { } else {
this.opcode('pushHash'); this.opcode('emptyHash');
} }
return params; return params;
...@@ -1189,7 +1249,7 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1189,7 +1249,7 @@ Handlebars.JavaScriptCompiler = function() {};
JavaScriptCompiler.prototype = { JavaScriptCompiler.prototype = {
// PUBLIC API: You can override these methods in a subclass to provide // PUBLIC API: You can override these methods in a subclass to provide
// alternative compiled forms for name lookup and buffering semantics // alternative compiled forms for name lookup and buffering semantics
nameLookup: function(parent, name, type) { nameLookup: function(parent, name /* , type*/) {
if (/^[0-9]+$/.test(name)) { if (/^[0-9]+$/.test(name)) {
return parent + "[" + name + "]"; return parent + "[" + name + "]";
} else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
...@@ -1204,7 +1264,11 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1204,7 +1264,11 @@ Handlebars.JavaScriptCompiler = function() {};
if (this.environment.isSimple) { if (this.environment.isSimple) {
return "return " + string + ";"; return "return " + string + ";";
} else { } else {
return "buffer += " + string + ";"; return {
appendToBuffer: true,
content: string,
toString: function() { return "buffer += " + string + ";"; }
};
} }
}, },
...@@ -1225,6 +1289,7 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1225,6 +1289,7 @@ Handlebars.JavaScriptCompiler = function() {};
this.isChild = !!context; this.isChild = !!context;
this.context = context || { this.context = context || {
programs: [], programs: [],
environments: [],
aliases: { } aliases: { }
}; };
...@@ -1234,6 +1299,7 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1234,6 +1299,7 @@ Handlebars.JavaScriptCompiler = function() {};
this.stackVars = []; this.stackVars = [];
this.registers = { list: [] }; this.registers = { list: [] };
this.compileStack = []; this.compileStack = [];
this.inlineStack = [];
this.compileChildren(environment, options); this.compileChildren(environment, options);
...@@ -1255,11 +1321,11 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1255,11 +1321,11 @@ Handlebars.JavaScriptCompiler = function() {};
}, },
nextOpcode: function() { nextOpcode: function() {
var opcodes = this.environment.opcodes, opcode = opcodes[this.i + 1]; var opcodes = this.environment.opcodes;
return opcodes[this.i + 1]; return opcodes[this.i + 1];
}, },
eat: function(opcode) { eat: function() {
this.i = this.i + 1; this.i = this.i + 1;
}, },
...@@ -1297,7 +1363,6 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1297,7 +1363,6 @@ Handlebars.JavaScriptCompiler = function() {};
// Generate minimizer alias mappings // Generate minimizer alias mappings
if (!this.isChild) { if (!this.isChild) {
var aliases = [];
for (var alias in this.context.aliases) { for (var alias in this.context.aliases) {
this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
} }
...@@ -1322,16 +1387,48 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1322,16 +1387,48 @@ Handlebars.JavaScriptCompiler = function() {};
params.push("depth" + this.environment.depths.list[i]); params.push("depth" + this.environment.depths.list[i]);
} }
// Perform a second pass over the output to merge content when possible
var source = this.mergeSource();
if (!this.isChild) {
var revision = Handlebars.COMPILER_REVISION,
versions = Handlebars.REVISION_CHANGES[revision];
source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
}
if (asObject) { if (asObject) {
params.push(this.source.join("\n ")); params.push(source);
return Function.apply(this, params); return Function.apply(this, params);
} else { } else {
var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + this.source.join("\n ") + '}'; var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}';
Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n"); Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
return functionSource; return functionSource;
} }
}, },
mergeSource: function() {
// WARN: We are not handling the case where buffer is still populated as the source should
// not have buffer append operations as their final action.
var source = '',
buffer;
for (var i = 0, len = this.source.length; i < len; i++) {
var line = this.source[i];
if (line.appendToBuffer) {
if (buffer) {
buffer = buffer + '\n + ' + line.content;
} else {
buffer = line.content;
}
} else {
if (buffer) {
source += 'buffer += ' + buffer + ';\n ';
buffer = undefined;
}
source += line + '\n ';
}
}
return source;
},
// [blockValue] // [blockValue]
// //
...@@ -1369,6 +1466,9 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1369,6 +1466,9 @@ Handlebars.JavaScriptCompiler = function() {};
var current = this.topStack(); var current = this.topStack();
params.splice(1, 0, current); params.splice(1, 0, current);
// Use the options value generated from the invocation
params[params.length-1] = 'options';
this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }"); this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
}, },
...@@ -1392,6 +1492,9 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1392,6 +1492,9 @@ Handlebars.JavaScriptCompiler = function() {};
// If `value` is truthy, or 0, it is coerced into a string and appended // If `value` is truthy, or 0, it is coerced into a string and appended
// Otherwise, the empty string is appended // Otherwise, the empty string is appended
append: function() { append: function() {
// Force anything that is inlined onto the stack so we don't have duplication
// when we examine local
this.flushInline();
var local = this.popStack(); var local = this.popStack();
this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }"); this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
if (this.environment.isSimple) { if (this.environment.isSimple) {
...@@ -1406,15 +1509,9 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1406,15 +1509,9 @@ Handlebars.JavaScriptCompiler = function() {};
// //
// Escape `value` and append it to the buffer // Escape `value` and append it to the buffer
appendEscaped: function() { appendEscaped: function() {
var opcode = this.nextOpcode(), extra = "";
this.context.aliases.escapeExpression = 'this.escapeExpression'; this.context.aliases.escapeExpression = 'this.escapeExpression';
if(opcode && opcode.opcode === 'appendContent') { this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
extra = " + " + this.quotedString(opcode.args[0]);
this.eat(opcode);
}
this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")" + extra));
}, },
// [getContext] // [getContext]
...@@ -1438,7 +1535,7 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1438,7 +1535,7 @@ Handlebars.JavaScriptCompiler = function() {};
// Looks up the value of `name` on the current context and pushes // Looks up the value of `name` on the current context and pushes
// it onto the stack. // it onto the stack.
lookupOnContext: function(name) { lookupOnContext: function(name) {
this.pushStack(this.nameLookup('depth' + this.lastContext, name, 'context')); this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
}, },
// [pushContext] // [pushContext]
...@@ -1486,7 +1583,7 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1486,7 +1583,7 @@ Handlebars.JavaScriptCompiler = function() {};
// //
// Push the result of looking up `id` on the current data // Push the result of looking up `id` on the current data
lookupData: function(id) { lookupData: function(id) {
this.pushStack(this.nameLookup('data', id, 'data')); this.push(this.nameLookup('data', id, 'data'));
}, },
// [pushStringParam] // [pushStringParam]
...@@ -1509,13 +1606,25 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1509,13 +1606,25 @@ Handlebars.JavaScriptCompiler = function() {};
} }
}, },
pushHash: function() { emptyHash: function() {
this.push('{}'); this.pushStackLiteral('{}');
if (this.options.stringParams) { if (this.options.stringParams) {
this.register('hashTypes', '{}'); this.register('hashTypes', '{}');
} }
}, },
pushHash: function() {
this.hash = {values: [], types: []};
},
popHash: function() {
var hash = this.hash;
this.hash = undefined;
if (this.options.stringParams) {
this.register('hashTypes', '{' + hash.types.join(',') + '}');
}
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
},
// [pushString] // [pushString]
// //
...@@ -1534,7 +1643,8 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1534,7 +1643,8 @@ Handlebars.JavaScriptCompiler = function() {};
// //
// Push an expression onto the stack // Push an expression onto the stack
push: function(expr) { push: function(expr) {
this.pushStack(expr); this.inlineStack.push(expr);
return expr;
}, },
// [pushLiteral] // [pushLiteral]
...@@ -1577,12 +1687,14 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1577,12 +1687,14 @@ Handlebars.JavaScriptCompiler = function() {};
invokeHelper: function(paramSize, name) { invokeHelper: function(paramSize, name) {
this.context.aliases.helperMissing = 'helpers.helperMissing'; this.context.aliases.helperMissing = 'helpers.helperMissing';
var helper = this.lastHelper = this.setupHelper(paramSize, name); var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
this.register('foundHelper', helper.name);
this.pushStack("foundHelper ? foundHelper.call(" + this.push(helper.name);
this.replaceStack(function(name) {
return name + ' ? ' + name + '.call(' +
helper.callParams + ") " + ": helperMissing.call(" + helper.callParams + ") " + ": helperMissing.call(" +
helper.helperMissingParams + ")"); helper.helperMissingParams + ")";
});
}, },
// [invokeKnownHelper] // [invokeKnownHelper]
...@@ -1594,7 +1706,7 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1594,7 +1706,7 @@ Handlebars.JavaScriptCompiler = function() {};
// so a `helperMissing` fallback is not required. // so a `helperMissing` fallback is not required.
invokeKnownHelper: function(paramSize, name) { invokeKnownHelper: function(paramSize, name) {
var helper = this.setupHelper(paramSize, name); var helper = this.setupHelper(paramSize, name);
this.pushStack(helper.name + ".call(" + helper.callParams + ")"); this.push(helper.name + ".call(" + helper.callParams + ")");
}, },
// [invokeAmbiguous] // [invokeAmbiguous]
...@@ -1609,19 +1721,18 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1609,19 +1721,18 @@ Handlebars.JavaScriptCompiler = function() {};
// This operation emits more code than the other options, // This operation emits more code than the other options,
// and can be avoided by passing the `knownHelpers` and // and can be avoided by passing the `knownHelpers` and
// `knownHelpersOnly` flags at compile-time. // `knownHelpersOnly` flags at compile-time.
invokeAmbiguous: function(name) { invokeAmbiguous: function(name, helperCall) {
this.context.aliases.functionType = '"function"'; this.context.aliases.functionType = '"function"';
this.pushStackLiteral('{}'); this.pushStackLiteral('{}'); // Hash value
var helper = this.setupHelper(0, name); var helper = this.setupHelper(0, name, helperCall);
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
this.register('foundHelper', helperName);
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context'); var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
var nextStack = this.nextStack(); var nextStack = this.nextStack();
this.source.push('if (foundHelper) { ' + nextStack + ' = foundHelper.call(' + helper.callParams + '); }'); this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }');
this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }'); this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }');
}, },
...@@ -1640,7 +1751,7 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1640,7 +1751,7 @@ Handlebars.JavaScriptCompiler = function() {};
} }
this.context.aliases.self = "this"; this.context.aliases.self = "this";
this.pushStack("self.invokePartial(" + params.join(", ") + ")"); this.push("self.invokePartial(" + params.join(", ") + ")");
}, },
// [assignToHash] // [assignToHash]
...@@ -1651,17 +1762,19 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1651,17 +1762,19 @@ Handlebars.JavaScriptCompiler = function() {};
// Pops a value and hash off the stack, assigns `hash[key] = value` // Pops a value and hash off the stack, assigns `hash[key] = value`
// and pushes the hash back onto the stack. // and pushes the hash back onto the stack.
assignToHash: function(key) { assignToHash: function(key) {
var value = this.popStack(); var value = this.popStack(),
type;
if (this.options.stringParams) { if (this.options.stringParams) {
var type = this.popStack(); type = this.popStack();
this.popStack(); this.popStack();
this.source.push("hashTypes['" + key + "'] = " + type + ";");
} }
var hash = this.topStack(); var hash = this.hash;
if (type) {
this.source.push(hash + "['" + key + "'] = " + value + ";"); hash.types.push("'" + key + "': " + type);
}
hash.values.push("'" + key + "': (" + value + ")");
}, },
// HELPERS // HELPERS
...@@ -1675,11 +1788,27 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1675,11 +1788,27 @@ Handlebars.JavaScriptCompiler = function() {};
child = children[i]; child = children[i];
compiler = new this.compiler(); compiler = new this.compiler();
var index = this.matchExistingProgram(child);
if (index == null) {
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
var index = this.context.programs.length; index = this.context.programs.length;
child.index = index; child.index = index;
child.name = 'program' + index; child.name = 'program' + index;
this.context.programs[index] = compiler.compile(child, options, this.context); this.context.programs[index] = compiler.compile(child, options, this.context);
this.context.environments[index] = child;
} else {
child.index = index;
child.name = 'program' + index;
}
}
},
matchExistingProgram: function(child) {
for (var i = 0, len = this.context.environments.length; i < len; i++) {
var environment = this.context.environments[i];
if (environment && environment.equals(child)) {
return i;
}
} }
}, },
...@@ -1723,57 +1852,111 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1723,57 +1852,111 @@ Handlebars.JavaScriptCompiler = function() {};
}, },
pushStackLiteral: function(item) { pushStackLiteral: function(item) {
this.compileStack.push(new Literal(item)); return this.push(new Literal(item));
return item;
}, },
pushStack: function(item) { pushStack: function(item) {
this.flushInline();
var stack = this.incrStack(); var stack = this.incrStack();
if (item) {
this.source.push(stack + " = " + item + ";"); this.source.push(stack + " = " + item + ";");
}
this.compileStack.push(stack); this.compileStack.push(stack);
return stack; return stack;
}, },
replaceStack: function(callback) { replaceStack: function(callback) {
var stack = this.topStack(), var prefix = '',
item = callback.call(this, stack); inline = this.isInline(),
stack;
// If we are currently inline then we want to merge the inline statement into the
// replacement statement via ','
if (inline) {
var top = this.popStack(true);
if (top instanceof Literal) {
// Literals do not need to be inlined
stack = top.value;
} else {
// Get or create the current stack name for use by the inline
var name = this.stackSlot ? this.topStackName() : this.incrStack();
prefix = '(' + this.push(name) + ' = ' + top + '),';
stack = this.topStack();
}
} else {
stack = this.topStack();
}
var item = callback.call(this, stack);
if (inline) {
if (this.inlineStack.length || this.compileStack.length) {
this.popStack();
}
this.push('(' + prefix + item + ')');
} else {
// Prevent modification of the context depth variable. Through replaceStack // Prevent modification of the context depth variable. Through replaceStack
if (/^depth/.test(stack)) { if (!/^stack/.test(stack)) {
stack = this.nextStack(); stack = this.nextStack();
} }
this.source.push(stack + " = " + item + ";"); this.source.push(stack + " = (" + prefix + item + ");");
}
return stack; return stack;
}, },
nextStack: function(skipCompileStack) { nextStack: function() {
var name = this.incrStack(); return this.pushStack();
this.compileStack.push(name);
return name;
}, },
incrStack: function() { incrStack: function() {
this.stackSlot++; this.stackSlot++;
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
return this.topStackName();
},
topStackName: function() {
return "stack" + this.stackSlot; return "stack" + this.stackSlot;
}, },
flushInline: function() {
var inlineStack = this.inlineStack;
if (inlineStack.length) {
this.inlineStack = [];
for (var i = 0, len = inlineStack.length; i < len; i++) {
var entry = inlineStack[i];
if (entry instanceof Literal) {
this.compileStack.push(entry);
} else {
this.pushStack(entry);
}
}
}
},
isInline: function() {
return this.inlineStack.length;
},
popStack: function() { popStack: function(wrapped) {
var item = this.compileStack.pop(); var inline = this.isInline(),
item = (inline ? this.inlineStack : this.compileStack).pop();
if (item instanceof Literal) { if (!wrapped && (item instanceof Literal)) {
return item.value; return item.value;
} else { } else {
if (!inline) {
this.stackSlot--; this.stackSlot--;
}
return item; return item;
} }
}, },
topStack: function() { topStack: function(wrapped) {
var item = this.compileStack[this.compileStack.length - 1]; var stack = (this.isInline() ? this.inlineStack : this.compileStack),
item = stack[stack.length - 1];
if (item instanceof Literal) { if (!wrapped && (item instanceof Literal)) {
return item.value; return item.value;
} else { } else {
return item; return item;
...@@ -1788,22 +1971,22 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1788,22 +1971,22 @@ Handlebars.JavaScriptCompiler = function() {};
.replace(/\r/g, '\\r') + '"'; .replace(/\r/g, '\\r') + '"';
}, },
setupHelper: function(paramSize, name) { setupHelper: function(paramSize, name, missingParams) {
var params = []; var params = [];
this.setupParams(paramSize, params); this.setupParams(paramSize, params, missingParams);
var foundHelper = this.nameLookup('helpers', name, 'helper'); var foundHelper = this.nameLookup('helpers', name, 'helper');
return { return {
params: params, params: params,
name: foundHelper, name: foundHelper,
callParams: ["depth0"].concat(params).join(", "), callParams: ["depth0"].concat(params).join(", "),
helperMissingParams: ["depth0", this.quotedString(name)].concat(params).join(", ") helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
}; };
}, },
// the params and contexts arguments are passed in arrays // the params and contexts arguments are passed in arrays
// to fill in // to fill in
setupParams: function(paramSize, params) { setupParams: function(paramSize, params, useRegister) {
var options = [], contexts = [], types = [], param, inverse, program; var options = [], contexts = [], types = [], param, inverse, program;
options.push("hash:" + this.popStack()); options.push("hash:" + this.popStack());
...@@ -1848,7 +2031,13 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1848,7 +2031,13 @@ Handlebars.JavaScriptCompiler = function() {};
options.push("data:data"); options.push("data:data");
} }
params.push("{" + options.join(",") + "}"); options = "{" + options.join(",") + "}";
if (useRegister) {
this.register('options', options);
params.push('options');
} else {
params.push(options);
}
return params.join(", "); return params.join(", ");
} }
}; };
...@@ -1886,23 +2075,23 @@ Handlebars.JavaScriptCompiler = function() {}; ...@@ -1886,23 +2075,23 @@ Handlebars.JavaScriptCompiler = function() {};
})(Handlebars.Compiler, Handlebars.JavaScriptCompiler); })(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
Handlebars.precompile = function(string, options) { Handlebars.precompile = function(input, options) {
if (typeof string !== 'string') { if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
throw new Handlebars.Exception("You must pass a string to Handlebars.compile. You passed " + string); throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
} }
options = options || {}; options = options || {};
if (!('data' in options)) { if (!('data' in options)) {
options.data = true; options.data = true;
} }
var ast = Handlebars.parse(string); var ast = Handlebars.parse(input);
var environment = new Handlebars.Compiler().compile(ast, options); var environment = new Handlebars.Compiler().compile(ast, options);
return new Handlebars.JavaScriptCompiler().compile(environment, options); return new Handlebars.JavaScriptCompiler().compile(environment, options);
}; };
Handlebars.compile = function(string, options) { Handlebars.compile = function(input, options) {
if (typeof string !== 'string') { if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
throw new Handlebars.Exception("You must pass a string to Handlebars.compile. You passed " + string); throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
} }
options = options || {}; options = options || {};
...@@ -1911,7 +2100,7 @@ Handlebars.compile = function(string, options) { ...@@ -1911,7 +2100,7 @@ Handlebars.compile = function(string, options) {
} }
var compiled; var compiled;
function compile() { function compile() {
var ast = Handlebars.parse(string); var ast = Handlebars.parse(input);
var environment = new Handlebars.Compiler().compile(ast, options); var environment = new Handlebars.Compiler().compile(ast, options);
var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true); var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
return Handlebars.template(templateSpec); return Handlebars.template(templateSpec);
...@@ -1946,12 +2135,32 @@ Handlebars.VM = { ...@@ -1946,12 +2135,32 @@ Handlebars.VM = {
} }
}, },
programWithDepth: Handlebars.VM.programWithDepth, programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop noop: Handlebars.VM.noop,
compilerInfo: null
}; };
return function(context, options) { return function(context, options) {
options = options || {}; options = options || {};
return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
var compilerInfo = container.compilerInfo || [],
compilerRevision = compilerInfo[0] || 1,
currentRevision = Handlebars.COMPILER_REVISION;
if (compilerRevision !== currentRevision) {
if (compilerRevision < currentRevision) {
var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
}
}
return result;
}; };
}, },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment