diff --git a/app/assets/javascripts/admin/models/api_key.js b/app/assets/javascripts/admin/models/api_key.js index a7cb6f2cd81..7f13ef49ee1 100644 --- a/app/assets/javascripts/admin/models/api_key.js +++ b/app/assets/javascripts/admin/models/api_key.js @@ -44,8 +44,8 @@ Discourse.ApiKey.reopenClass({ @param {Object} the properties to create @returns {Discourse.ApiKey} the ApiKey instance **/ - create: function(apiKey) { - var result = this._super(apiKey); + create: function() { + var result = this._super.apply(this, arguments); if (result.user) { result.user = Discourse.AdminUser.create(result.user); } diff --git a/app/assets/javascripts/admin/models/email_log.js b/app/assets/javascripts/admin/models/email_log.js index 15f282eae7a..28e7066173f 100644 --- a/app/assets/javascripts/admin/models/email_log.js +++ b/app/assets/javascripts/admin/models/email_log.js @@ -10,6 +10,8 @@ Discourse.EmailLog = Discourse.Model.extend({}); Discourse.EmailLog.reopenClass({ create: function(attrs) { + attrs = attrs || {}; + if (attrs.user) { attrs.user = Discourse.AdminUser.create(attrs.user); } diff --git a/app/assets/javascripts/admin/models/staff_action_log.js b/app/assets/javascripts/admin/models/staff_action_log.js index 8b6fe250ddc..122574f5c02 100644 --- a/app/assets/javascripts/admin/models/staff_action_log.js +++ b/app/assets/javascripts/admin/models/staff_action_log.js @@ -43,6 +43,8 @@ Discourse.StaffActionLog = Discourse.Model.extend({ Discourse.StaffActionLog.reopenClass({ create: function(attrs) { + attrs = attrs || {}; + if (attrs.acting_user) { attrs.acting_user = Discourse.AdminUser.create(attrs.acting_user); } diff --git a/app/assets/javascripts/discourse/models/invite.js b/app/assets/javascripts/discourse/models/invite.js index 52ed84f913c..503fc58df10 100644 --- a/app/assets/javascripts/discourse/models/invite.js +++ b/app/assets/javascripts/discourse/models/invite.js @@ -21,9 +21,8 @@ Discourse.Invite = Discourse.Model.extend({ Discourse.Invite.reopenClass({ - create: function(invite) { - var result; - result = this._super(invite); + create: function() { + var result = this._super.apply(this, arguments); if (result.user) { result.user = Discourse.User.create(result.user); } diff --git a/app/assets/javascripts/discourse/models/notification.js b/app/assets/javascripts/discourse/models/notification.js index fd2ce09b73a..8d55a6f19b3 100644 --- a/app/assets/javascripts/discourse/models/notification.js +++ b/app/assets/javascripts/discourse/models/notification.js @@ -30,12 +30,12 @@ Discourse.Notification = Discourse.Model.extend({ Discourse.Notification.reopenClass({ create: function(obj) { - var result; - result = this._super(obj); + obj = obj || {}; + if (obj.data) { - result.set('data', Em.Object.create(obj.data)); + obj.data = Em.Object.create(obj.data); } - return result; + return this._super(obj); } }); diff --git a/app/assets/javascripts/discourse/models/post.js b/app/assets/javascripts/discourse/models/post.js index 1927a769ed6..4696996ddab 100644 --- a/app/assets/javascripts/discourse/models/post.js +++ b/app/assets/javascripts/discourse/models/post.js @@ -378,7 +378,7 @@ Discourse.Post.reopenClass({ }, create: function(obj) { - var result = this._super(obj); + var result = this._super.apply(this, arguments); this.createActionSummary(result); if (obj && obj.reply_to_user) { result.set('reply_to_user', Discourse.User.create(obj.reply_to_user)); diff --git a/app/assets/javascripts/discourse/models/post_stream.js b/app/assets/javascripts/discourse/models/post_stream.js index 43819f614cd..722142db94d 100644 --- a/app/assets/javascripts/discourse/models/post_stream.js +++ b/app/assets/javascripts/discourse/models/post_stream.js @@ -237,7 +237,7 @@ Discourse.PostStream = Em.Object.extend({ var postWeWant = this.get('posts').findProperty('post_number', opts.nearPost); if (postWeWant) { Discourse.TopicView.jumpToPost(topic.get('id'), opts.nearPost); - return Ember.Deferred.create(function(p) { p.reject(); }); + return Ember.RSVP.reject(); } // TODO: if we have all the posts in the filter, don't go to the server for them. @@ -301,7 +301,7 @@ Discourse.PostStream = Em.Object.extend({ **/ prependMore: function() { var postStream = this, - rejectedPromise = Ember.Deferred.create(function(p) { p.reject(); }); + rejectedPromise = Ember.RSVP.reject(); // Make sure we can append more posts if (!postStream.get('canPrependMore')) { return rejectedPromise; } @@ -684,8 +684,8 @@ Discourse.PostStream = Em.Object.extend({ Discourse.PostStream.reopenClass({ - create: function(args) { - var postStream = this._super(args); + create: function() { + var postStream = this._super.apply(this, arguments); postStream.setProperties({ posts: Em.A(), stream: Em.A(), diff --git a/app/assets/javascripts/discourse/models/site.js b/app/assets/javascripts/discourse/models/site.js index 40956e5b388..e7bbcc72139 100644 --- a/app/assets/javascripts/discourse/models/site.js +++ b/app/assets/javascripts/discourse/models/site.js @@ -44,8 +44,8 @@ Discourse.Site.reopenClass(Discourse.Singleton, { return Discourse.Site.create(PreloadStore.get('site')); }, - create: function(obj) { - var result = this._super(obj); + create: function() { + var result = this._super.apply(this, arguments); if (result.categories) { var byId = {}; diff --git a/app/assets/javascripts/discourse/routes/user_topic_list_routes.js b/app/assets/javascripts/discourse/routes/user_topic_list_routes.js index e0529d550d9..21fb7c3bb8a 100644 --- a/app/assets/javascripts/discourse/routes/user_topic_list_routes.js +++ b/app/assets/javascripts/discourse/routes/user_topic_list_routes.js @@ -20,7 +20,7 @@ function createPMRoute(viewName, path, type) { }, setupController: function(controller, model) { - this._super(controller, model); + this._super.apply(this, arguments); controller.set('hideCategories', true); this.controllerFor('user').setProperties({ pmView: viewName, diff --git a/app/assets/javascripts/discourse/views/topic_view.js b/app/assets/javascripts/discourse/views/topic_view.js index 2d4f8e88a71..058b3417d3f 100644 --- a/app/assets/javascripts/discourse/views/topic_view.js +++ b/app/assets/javascripts/discourse/views/topic_view.js @@ -234,16 +234,16 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { var info = Discourse.Eyeline.analyze(rows); if(!info) { return; } - // We disable scrolling of the topic while performing initial positioning // This code needs to be refactored, the pipline for positioning posts is wack // Be sure to test on safari as well when playing with this if(!Discourse.TopicView.disableScroll) { + // are we scrolling upwards? if(info.top === 0 || info.onScreen[0] === 0 || info.bottom === 0) { - var $body = $('body'); - var $elem = $(rows[0]); - var distToElement = $body.scrollTop() - $elem.position().top; + var $body = $('body'), + $elem = $(rows[0]), + distToElement = $body.scrollTop() - $elem.position().top; this.get('postStream').prependMore().then(function() { Em.run.next(function () { $('html, body').scrollTop($elem.position().top + distToElement); @@ -252,6 +252,7 @@ Discourse.TopicView = Discourse.View.extend(Discourse.Scrolling, { } } + // are we scrolling down? var currentPost; if(info.bottom === rows.length-1) { diff --git a/test/javascripts/admin/models/api_key_test.js b/test/javascripts/admin/models/api_key_test.js index 1cb658a0896..fd26de7f95b 100644 --- a/test/javascripts/admin/models/api_key_test.js +++ b/test/javascripts/admin/models/api_key_test.js @@ -17,7 +17,7 @@ asyncTestDiscourse('find', function() { }); asyncTestDiscourse('generateMasterKey', function() { - this.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve([])); + this.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve({api_key: {}})); Discourse.ApiKey.generateMasterKey().then(function() { start(); ok(Discourse.ajax.calledWith("/admin/api/key", {type: 'POST'}), "it POSTs to create a master key"); diff --git a/test/javascripts/mixins/selected_posts_count_test.js b/test/javascripts/mixins/selected_posts_count_test.js index 313da9e6362..e448cff2427 100644 --- a/test/javascripts/mixins/selected_posts_count_test.js +++ b/test/javascripts/mixins/selected_posts_count_test.js @@ -14,7 +14,7 @@ test("without selectedPosts", function () { }); test("with some selectedPosts", function() { - var testObj = buildTestObj({ selectedPosts: [Discourse.Post.create()] }); + var testObj = buildTestObj({ selectedPosts: [Discourse.Post.create({id: 123})] }); equal(testObj.get('selectedPostsCount'), 1, "It returns the amount of posts"); }); diff --git a/test/javascripts/models/email_log_test.js b/test/javascripts/models/email_log_test.js new file mode 100644 index 00000000000..e86a0980c4a --- /dev/null +++ b/test/javascripts/models/email_log_test.js @@ -0,0 +1,5 @@ +module("Discourse.EmailLog"); + +test("create", function() { + ok(Discourse.EmailLog.create(), "it can be created without arguments"); +}); \ No newline at end of file diff --git a/test/javascripts/models/invite_test.js b/test/javascripts/models/invite_test.js new file mode 100644 index 00000000000..0989d0c3809 --- /dev/null +++ b/test/javascripts/models/invite_test.js @@ -0,0 +1,5 @@ +module("Discourse.Invite"); + +test("create", function() { + ok(Discourse.Invite.create(), "it can be created without arguments"); +}); \ No newline at end of file diff --git a/test/javascripts/models/notification_test.js b/test/javascripts/models/notification_test.js new file mode 100644 index 00000000000..830c97e5811 --- /dev/null +++ b/test/javascripts/models/notification_test.js @@ -0,0 +1,5 @@ +module("Discourse.Notification"); + +test("create", function() { + ok(Discourse.Notification.create(), "it can be created without arguments"); +}); \ No newline at end of file diff --git a/test/javascripts/models/post_stream_test.js b/test/javascripts/models/post_stream_test.js index 0a6355962a2..e46fe7e4971 100644 --- a/test/javascripts/models/post_stream_test.js +++ b/test/javascripts/models/post_stream_test.js @@ -11,6 +11,10 @@ var buildStream = function(id, stream) { var participant = {username: 'eviltrout'}; +test('create', function() { + ok(Discourse.PostStream.create(), 'it can be created with no parameters'); +}); + test('defaults', function() { var postStream = buildStream(1234); blank(postStream.get('posts'), "there are no posts in a stream by default"); diff --git a/test/javascripts/models/site_test.js b/test/javascripts/models/site_test.js index cc4fafa3483..e5e53935d51 100644 --- a/test/javascripts/models/site_test.js +++ b/test/javascripts/models/site_test.js @@ -1,6 +1,10 @@ module("Discourse.Site"); -test('instance', function(){ +test('create', function() { + ok(Discourse.Site.create(), 'it can create with no parameters'); +}); + +test('instance', function() { var site = Discourse.Site.current(); @@ -32,5 +36,4 @@ test('create categories', function() { present(subcategory, "it loaded the subcategory"); equal(subcategory.get('parentCategory'), parent, "it has associated the child with the parent"); - }); \ No newline at end of file diff --git a/test/javascripts/models/staff_action_log_test.js b/test/javascripts/models/staff_action_log_test.js new file mode 100644 index 00000000000..ba744748043 --- /dev/null +++ b/test/javascripts/models/staff_action_log_test.js @@ -0,0 +1,5 @@ +module("Discourse.StaffActionLog"); + +test("create", function() { + ok(Discourse.StaffActionLog.create(), "it can be created without arguments"); +}); \ No newline at end of file diff --git a/vendor/assets/javascripts/development/ember.js b/vendor/assets/javascripts/development/ember.js index 6eca812245a..466487d9c76 100755 --- a/vendor/assets/javascripts/development/ember.js +++ b/vendor/assets/javascripts/development/ember.js @@ -1,16 +1,4 @@ -// ========================================================================== -// Project: Ember - JavaScript Application Framework -// Copyright: ©2011-2013 Tilde Inc. and contributors -// Portions ©2006-2011 Strobe Inc. -// Portions ©2008-2011 Apple Inc. All rights reserved. -// License: Licensed under MIT license -// See https://raw.github.com/emberjs/ember.js/master/LICENSE -// ========================================================================== - - -// Version: v1.0.0-rc.6-733-gd034d11 -// Last commit: d034d11 (2013-09-16 00:44:21 -0700) - + // Version: 1.1.2 (function() { /*global __fail__*/ @@ -65,7 +53,7 @@ Ember.assert = function(desc, test) { if (Ember.testing && !test) { // when testing, ensure test failures when assertions fail - throw new Error("Assertion Failed: " + desc); + throw new Ember.Error("Assertion Failed: " + desc); } }; @@ -117,7 +105,7 @@ Ember.deprecate = function(message, test) { if (arguments.length === 1) { test = false; } if (test) { return; } - if (Ember.ENV.RAISE_ON_DEPRECATION) { throw new Error(message); } + if (Ember.ENV.RAISE_ON_DEPRECATION) { throw new Ember.Error(message); } var error; @@ -148,15 +136,21 @@ Ember.deprecate = function(message, test) { /** + Alias an old, deprecated method with its new counterpart. + Display a deprecation warning with the provided message and a stack trace - (Chrome and Firefox only) when the wrapped method is called. + (Chrome and Firefox only) when the assigned method is called. Ember build tools will not remove calls to `Ember.deprecateFunc()`, though no warnings will be shown in production. + ```javascript + Ember.oldMethod = Ember.deprecateFunc("Please use the new, updated method", Ember.newMethod); + ``` + @method deprecateFunc @param {String} message A description of the deprecation. - @param {Function} func The function to be deprecated. + @param {Function} func The new function called to replace its deprecated counterpart. @return {Function} a new function that wrapped the original function with a deprecation warning */ Ember.deprecateFunc = function(message, func) { @@ -180,19 +174,7 @@ if (!Ember.testing) { })(); -// ========================================================================== -// Project: Ember - JavaScript Application Framework -// Copyright: ©2011-2013 Tilde Inc. and contributors -// Portions ©2006-2011 Strobe Inc. -// Portions ©2008-2011 Apple Inc. All rights reserved. -// License: Licensed under MIT license -// See https://raw.github.com/emberjs/ember.js/master/LICENSE -// ========================================================================== - - -// Version: v1.0.0-rc.6-733-gd034d11 -// Last commit: d034d11 (2013-09-16 00:44:21 -0700) - + // Version: 1.1.2 (function() { var define, requireModule; @@ -257,7 +239,7 @@ var define, requireModule; @class Ember @static - @version 1.0.0 + @version 1.1.2 */ if ('undefined' === typeof Ember) { @@ -284,10 +266,10 @@ Ember.toString = function() { return "Ember"; }; /** @property VERSION @type String - @default '1.0.0' + @default '1.1.2' @final */ -Ember.VERSION = '1.0.0'; +Ember.VERSION = '1.1.2'; /** Standard environmental variables. You can define these in a global `ENV` @@ -2048,6 +2030,7 @@ Ember.getWithDefault = function(root, key, defaultValue) { Ember.get = get; +Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get); })(); @@ -2886,6 +2869,7 @@ function setPath(root, path, value, tolerant) { } Ember.set = set; +Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now supports paths', Ember.set); /** Error-tolerant form of `Ember.set`. Will not blow up if any part of the @@ -2903,6 +2887,7 @@ Ember.set = set; Ember.trySet = function(root, path, value) { return set(root, path, value, true); }; +Ember.trySetPath = Ember.deprecateFunc('trySetPath has been renamed to trySet', Ember.trySet); })(); @@ -4653,14 +4638,14 @@ function registerComputedWithProperties(name, macro) { property is null, an empty string, empty array, or empty function. Note: When using `Ember.computed.empty` to watch an array make sure to - use the `array.length` syntax so the computed can subscribe to transitions + use the `array.[]` syntax so the computed can subscribe to transitions from empty to non-empty states. Example ```javascript var ToDoList = Ember.Object.extend({ - done: Ember.computed.empty('todos.length') + done: Ember.computed.empty('todos.[]') // detect array changes }); var todoList = ToDoList.create({todos: ['Unit Test', 'Documentation', 'Release']}); todoList.get('done'); // false @@ -4780,7 +4765,7 @@ registerComputed('not', function(dependentKey) { @method computed.bool @for Ember @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which convert + @return {Ember.ComputedProperty} computed property which converts to boolean the original value for property */ registerComputed('bool', function(dependentKey) { @@ -4998,7 +4983,7 @@ registerComputedWithProperties('and', function(properties) { }); /** - A computed property that which performs a logical `or` on the + A computed property which performs a logical `or` on the original values for the provided dependent properties. Example @@ -5165,7 +5150,7 @@ Ember.computed.alias = function(dependentKey) { @method computed.oneWay @for Ember @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which creates an + @return {Ember.ComputedProperty} computed property which creates a one way computed property to the original value for property. */ Ember.computed.oneWay = function(dependentKey) { @@ -5177,7 +5162,7 @@ Ember.computed.oneWay = function(dependentKey) { /** A computed property that acts like a standard getter and setter, - but retruns the value at the provided `defaultPath` if the + but returns the value at the provided `defaultPath` if the property itself has not been set to a value Example @@ -5545,7 +5530,12 @@ define("backburner", debouncees = [], timers = [], autorun, laterTimer, laterTimerExpiresAt, - global = this; + global = this, + NUMBER = /\d+/; + + function isCoercableNumber(number) { + return typeof number === 'number' || NUMBER.test(number); + } function Backburner(queueNames, options) { this.queueNames = queueNames; @@ -5660,32 +5650,60 @@ define("backburner", }, setTimeout: function() { - var self = this, - wait = pop.call(arguments), - target = arguments[0], - method = arguments[1], - executeAt = (+new Date()) + wait; + var args = slice.call(arguments); + var length = args.length; + var method, wait, target; + var self = this; + var methodOrTarget, methodOrWait, methodOrArgs; - if (!method) { - method = target; - target = null; + if (length === 0) { + return; + } else if (length === 1) { + method = args.shift(); + wait = 0; + } else if (length === 2) { + methodOrTarget = args[0]; + methodOrWait = args[1]; + + if (typeof methodOrWait === 'function' || typeof methodOrTarget[methodOrWait] === 'function') { + target = args.shift(); + method = args.shift(); + wait = 0; + } else if (isCoercableNumber(methodOrWait)) { + method = args.shift(); + wait = args.shift(); + } else { + method = args.shift(); + wait = 0; + } + } else { + var last = args[args.length - 1]; + + if (isCoercableNumber(last)) { + wait = args.pop(); + } + + methodOrTarget = args[0]; + methodOrArgs = args[1]; + + if (typeof methodOrArgs === 'function' || (typeof methodOrArgs === 'string' && + methodOrTarget !== null && + methodOrArgs in methodOrTarget)) { + target = args.shift(); + method = args.shift(); + } else { + method = args.shift(); + } } + var executeAt = (+new Date()) + parseInt(wait, 10); + if (typeof method === 'string') { method = target[method]; } - var fn, args; - if (arguments.length > 2) { - args = slice.call(arguments, 2); - - fn = function() { - method.apply(target, args); - }; - } else { - fn = function() { - method.call(target); - }; + function fn() { + method.apply(target, args); } // find position to insert - TODO: binary search @@ -5886,6 +5904,7 @@ define("backburner", __exports__.Backburner = Backburner; }); + })(); @@ -7502,11 +7521,10 @@ Alias.prototype = new Ember.Descriptor(); @deprecated Use `Ember.aliasMethod` or `Ember.computed.alias` instead */ Ember.alias = function(methodName) { + Ember.deprecate("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead."); return new Alias(methodName); }; -Ember.alias = Ember.deprecateFunc("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead.", Ember.alias); - /** Makes a method available via an additional name. @@ -7650,6 +7668,8 @@ Ember.beforeObserver = function(func) { (function() { // Provides a way to register library versions with ember. +var forEach = Ember.EnumerableUtils.forEach, + indexOf = Ember.EnumerableUtils.indexOf; Ember.libraries = function() { var libraries = []; @@ -7677,14 +7697,15 @@ Ember.libraries = function() { libraries.deRegister = function(name) { var lib = getLibrary(name); - if (lib) libraries.splice(libraries.indexOf(lib), 1); + if (lib) libraries.splice(indexOf(libraries, lib), 1); }; libraries.each = function (callback) { - libraries.forEach(function(lib) { + forEach(libraries, function(lib) { callback(lib.name, lib.version); }); }; + return libraries; }(); @@ -8604,25 +8625,25 @@ define("container", ``` @method register - @param {String} type - @param {String} name + @param {String} fullName @param {Function} factory @param {Object} options */ - register: function(type, name, factory, options) { - var fullName; + register: function(fullName, factory, options) { + if (fullName.indexOf(':') === -1) { + throw new TypeError("malformed fullName, expected: `type:name` got: " + fullName + ""); + } - 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+'");', false); - fullName = type + ":" + name; + if (factory === undefined) { + throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`'); } var normalizedName = this.normalize(fullName); + if (this.cache.has(normalizedName)) { + throw new Error('Cannot re-register: `' + fullName +'`, as it has already been looked up.'); + } + this.registry.set(normalizedName, factory); this._options.set(normalizedName, options || {}); }, @@ -9514,16 +9535,39 @@ Ember.ORDER_DEFINITION = Ember.ENV.ORDER_DEFINITION || [ Ember.keys = Object.keys; if (!Ember.keys || Ember.create.isSimulated) { - Ember.keys = function(obj) { - var ret = []; - for(var key in obj) { - // Prevents browsers that don't respect non-enumerability from - // copying internal Ember properties - if (key.substring(0,2) === '__') continue; - if (key === '_super') continue; + var prototypeProperties = [ + 'constructor', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'valueOf', + 'toLocaleString', + 'toString' + ], + pushPropertyName = function(obj, array, key) { + // Prevents browsers that don't respect non-enumerability from + // copying internal Ember properties + if (key.substring(0,2) === '__') return; + if (key === '_super') return; + if (indexOf(array, key) >= 0) return; + if (!obj.hasOwnProperty(key)) return; - if (obj.hasOwnProperty(key)) { ret.push(key); } + array.push(key); + }; + + Ember.keys = function(obj) { + var ret = [], key; + for (key in obj) { + pushPropertyName(obj, ret, key); } + + // IE8 doesn't enumerate property that named the same as prototype properties. + for (var i = 0, l = prototypeProperties.length; i < l; i++) { + key = prototypeProperties[i]; + + pushPropertyName(obj, ret, key); + } + return ret; }; } @@ -10959,10 +11003,12 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot (function() { -var e_get = Ember.get, +var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, metaFor = Ember.meta, + propertyWillChange = Ember.propertyWillChange, + propertyDidChange = Ember.propertyDidChange, addBeforeObserver = Ember.addBeforeObserver, removeBeforeObserver = Ember.removeBeforeObserver, addObserver = Ember.addObserver, @@ -10976,16 +11022,6 @@ var e_get = Ember.get, eachPropertyPattern = /^(.*)\.@each\.(.*)/, doubleEachPropertyPattern = /(.*\.@each){2,}/; -function get(obj, key) { - if (Ember.FEATURES.isEnabled('reduceComputedSelf')) { - if (key === '@self') { - return obj; - } - } - - return e_get(obj, key); -} - /* Tracks changes to dependent arrays, as well as to properties of items in dependent arrays. @@ -11033,7 +11069,7 @@ function ItemPropertyObserverContext (dependentArray, index, trackedArray) { DependentArraysObserver.prototype = { setValue: function (newValue) { - this.instanceMeta.setValue(newValue); + this.instanceMeta.setValue(newValue, true); }, getValue: function () { return this.instanceMeta.getValue(); @@ -11134,14 +11170,14 @@ DependentArraysObserver.prototype = { this.trackedArraysByGuid[dependentKey] = new Ember.TrackedArray(observerContexts); }, - addTransformation: function (dependentKey, index, newItems) { + trackAdd: function (dependentKey, index, newItems) { var trackedArray = this.trackedArraysByGuid[dependentKey]; if (trackedArray) { trackedArray.addItems(index, newItems); } }, - removeTransformation: function (dependentKey, index, removedCount) { + trackRemove: function (dependentKey, index, removedCount) { var trackedArray = this.trackedArraysByGuid[dependentKey]; if (trackedArray) { @@ -11182,7 +11218,7 @@ DependentArraysObserver.prototype = { sliceIndex, observerContexts; - observerContexts = this.removeTransformation(dependentKey, index, removedCount); + observerContexts = this.trackRemove(dependentKey, index, removedCount); function removeObservers(propertyKey) { observerContexts[sliceIndex].destroyed = true; @@ -11227,7 +11263,7 @@ DependentArraysObserver.prototype = { this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); }, this); - this.addTransformation(dependentKey, index, observerContexts); + this.trackAdd(dependentKey, index, observerContexts); }, itemPropertyWillChange: function (obj, keyName, array, observerContext) { @@ -11246,7 +11282,7 @@ DependentArraysObserver.prototype = { }, itemPropertyDidChange: function(obj, keyName, array, observerContext) { - Ember.run.once(this, 'flushChanges'); + this.flushChanges(); }, flushChanges: function() { @@ -11328,11 +11364,21 @@ ReduceComputedPropertyInstanceMeta.prototype = { } }, - setValue: function(newValue) { + setValue: function(newValue, triggerObservers) { // This lets sugars force a recomputation, handy for very simple // implementations of eg max. if (newValue !== undefined) { + var fireObservers = triggerObservers && (newValue !== this.cache[this.propertyName]); + + if (fireObservers) { + propertyWillChange(this.context, this.propertyName); + } + this.cache[this.propertyName] = newValue; + + if (fireObservers) { + propertyDidChange(this.context, this.propertyName); + } } else { delete this.cache[this.propertyName]; } @@ -11458,13 +11504,11 @@ ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName }; ReduceComputedProperty.prototype.initialValue = function () { - switch (typeof this.options.initialValue) { - case 'undefined': - throw new Error("reduce computed properties require an initial value: did you forget to pass one to Ember.reduceComputed?"); - case 'function': - return this.options.initialValue(); - default: - return this.options.initialValue; + if (typeof this.options.initialValue === 'function') { + return this.options.initialValue(); + } + else { + return this.options.initialValue; } }; @@ -11487,25 +11531,25 @@ ReduceComputedProperty.prototype.clearItemPropertyKeys = function (dependentArra ReduceComputedProperty.prototype.property = function () { var cp = this, args = a_slice.call(arguments), - propertyArgs = [], + propertyArgs = new Ember.Set(), match, dependentArrayKey, itemPropertyKey; forEach(a_slice.call(arguments), function (dependentKey) { if (doubleEachPropertyPattern.test(dependentKey)) { - throw new Error("Nested @each properties not supported: " + dependentKey); + throw new Ember.Error("Nested @each properties not supported: " + dependentKey); } else if (match = eachPropertyPattern.exec(dependentKey)) { dependentArrayKey = match[1]; itemPropertyKey = match[2]; cp.itemPropertyKey(dependentArrayKey, itemPropertyKey); - propertyArgs.push(dependentArrayKey); + propertyArgs.add(dependentArrayKey); } else { - propertyArgs.push(dependentKey); + propertyArgs.add(dependentKey); } }); - return ComputedProperty.prototype.property.apply(this, propertyArgs); + return ComputedProperty.prototype.property.apply(this, propertyArgs.toArray()); }; /** @@ -11517,7 +11561,7 @@ ReduceComputedProperty.prototype.property = function () { If there are more than one arguments the first arguments are considered to be dependent property keys. The last argument is required to be an options object. The options object can have the - following four properties. + following four properties: `initialValue` - A value or function that will be used as the initial value for the computed. If this property is a function the result of calling @@ -11598,6 +11642,12 @@ ReduceComputedProperty.prototype.property = function () { to invalidate the computation. This is generally not a good idea for arrayComputed but it's used in eg max and min. + Note that observers will be fired if either of these functions return a value + that differs from the accumulated value. When returning an object that + mutates in response to array changes, for example an array that maps + everything from some other array (see `Ember.computed.map`), it is usually + important that the *same* array be returned to avoid accidentally triggering observers. + Example ```javascript @@ -11618,37 +11668,6 @@ ReduceComputedProperty.prototype.property = function () { }; ``` - Dependent keys may refer to `@self` to observe changes to the object itself, - which must be array-like, rather than a property of the object. This is - mostly useful for array proxies, to ensure objects are retrieved via - `objectAtContent`. This is how you could sort items by properties defined on an item controller. - - Example - - ```javascript - App.PeopleController = Ember.ArrayController.extend({ - itemController: 'person', - - sortedPeople: Ember.computed.sort('@self.@each.reversedName', function(personA, personB) { - // `reversedName` isn't defined on Person, but we have access to it via - // the item controller App.PersonController. If we'd used - // `content.@each.reversedName` above, we would be getting the objects - // directly and not have access to `reversedName`. - // - var reversedNameA = get(personA, 'reversedName'), - reversedNameB = get(personB, 'reversedName'); - - return Ember.compare(reversedNameA, reversedNameB); - }) - }); - - App.PersonController = Ember.ObjectController.extend({ - reversedName: function () { - return reverse(get(this, 'name')); - }.property('name') - }) - ``` - @method reduceComputed @for Ember @param {String} [dependentKeys*] @@ -11664,11 +11683,11 @@ Ember.reduceComputed = function (options) { } if (typeof options !== "object") { - throw new Error("Reduce Computed Property declared without an options hash"); + throw new Ember.Error("Reduce Computed Property declared without an options hash"); } - if (Ember.isNone(options.initialValue)) { - throw new Error("Reduce Computed Property declared without an initial value"); + if (!('initialValue' in options)) { + throw new Ember.Error("Reduce Computed Property declared without an initial value"); } var cp = new ReduceComputedProperty(options); @@ -11847,7 +11866,7 @@ Ember.arrayComputed = function (options) { } if (typeof options !== "object") { - throw new Error("Array Computed Property declared without an options hash"); + throw new Ember.Error("Array Computed Property declared without an options hash"); } var cp = new ArrayComputedProperty(options); @@ -12335,7 +12354,7 @@ Ember.computed.intersect = function () { */ Ember.computed.setDiff = function (setAProperty, setBProperty) { if (arguments.length !== 2) { - throw new Error("setDiff requires exactly two dependent arrays."); + throw new Ember.Error("setDiff requires exactly two dependent arrays."); } return Ember.arrayComputed.call(null, setAProperty, setBProperty, { addedItem: function (array, item, changeMeta, instanceMeta) { @@ -12450,7 +12469,7 @@ function binarySearch(array, item, low, high) { ]}); todoList.get('sortedTodos'); // [{name:'Documentation', priority:3}, {name:'Release', priority:1}, {name:'Unit Test', priority:2}] - todoList.get('priroityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}] + todoList.get('priorityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}] ``` @method computed.sort @@ -12591,7 +12610,7 @@ Ember.RSVP = requireModule('rsvp'); var STRING_DASHERIZE_REGEXP = (/[ _]/g); var STRING_DASHERIZE_CACHE = {}; -var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g); +var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([A-Z])/g); var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g); var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g); var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g); @@ -13258,7 +13277,7 @@ Ember.Copyable = Ember.Mixin.create(/** @scope Ember.Copyable.prototype */ { if (Ember.Freezable && Ember.Freezable.detect(this)) { return get(this, 'isFrozen') ? this : this.copy().freeze(); } else { - throw new Error(Ember.String.fmt("%@ does not support freezing", [this])); + throw new Ember.Error(Ember.String.fmt("%@ does not support freezing", [this])); } } }); @@ -13567,7 +13586,7 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,/** @return this */ insertAt: function(idx, object) { - if (idx > get(this, 'length')) throw new Error(OUT_OF_RANGE_EXCEPTION) ; + if (idx > get(this, 'length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION) ; this.replace(idx, 0, [object]) ; return this ; }, @@ -13595,7 +13614,7 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,/** if ('number' === typeof start) { if ((start < 0) || (start >= get(this, 'length'))) { - throw new Error(OUT_OF_RANGE_EXCEPTION); + throw new Ember.Error(OUT_OF_RANGE_EXCEPTION); } // fast case @@ -14182,6 +14201,29 @@ Ember.Observable = Ember.Mixin.create({ return Ember.hasListeners(this, key+':change'); }, + /** + @deprecated + @method getPath + @param {String} path The property path to retrieve + @return {Object} The property value or undefined. + */ + getPath: function(path) { + Ember.deprecate("getPath is deprecated since get now supports paths"); + return this.get(path); + }, + + /** + @deprecated + @method setPath + @param {String} path The path to the property that will be set + @param {Object} value The value to set or `null`. + @return {Ember.Observable} + */ + setPath: function(path, value) { + Ember.deprecate("setPath is deprecated since set now supports paths"); + return this.set(path, value); + }, + /** Retrieves the value of a property, or a default value in the case that the property returns `undefined`. @@ -14389,6 +14431,13 @@ Ember.TargetActionSupport = Ember.Mixin.create({ target = opts.target || get(this, 'targetObject'), actionContext = opts.actionContext; + function args(options, actionName) { + var ret = []; + if (actionName) { ret.push(actionName); } + + return ret.concat(options); + } + if (typeof actionContext === 'undefined') { actionContext = get(this, 'actionContextObject') || this; } @@ -14397,10 +14446,10 @@ Ember.TargetActionSupport = Ember.Mixin.create({ var ret; if (target.send) { - ret = target.send.apply(target, [action, actionContext]); + ret = target.send.apply(target, args(actionContext, action)); } else { Ember.assert("The action '" + action + "' did not exist on " + target, typeof target[action] === 'function'); - ret = target[action].apply(target, [actionContext]); + ret = target[action].apply(target, args(actionContext)); } if (ret !== false) ret = true; @@ -14815,7 +14864,7 @@ Ember.PromiseProxyMixin = Ember.Mixin.create({ installPromise(this, promise); return promise; } else { - throw new Error("PromiseProxy's promise must be set"); + throw new Ember.Error("PromiseProxy's promise must be set"); } }), @@ -14858,9 +14907,9 @@ Ember.TrackedArray = function (items) { var length = get(items, 'length'); if (length) { - this._content = [new ArrayOperation(RETAIN, length, items)]; + this._operations = [new ArrayOperation(RETAIN, length, items)]; } else { - this._content = []; + this._operations = []; } }; @@ -14878,8 +14927,10 @@ Ember.TrackedArray.prototype = { @param newItems */ addItems: function (index, newItems) { - var count = get(newItems, 'length'), - match = this._findArrayOperation(index), + var count = get(newItems, 'length'); + if (count < 1) { return; } + + var match = this._findArrayOperation(index), arrayOperation = match.operation, arrayOperationIndex = match.index, arrayOperationRangeStart = match.rangeStart, @@ -14894,7 +14945,7 @@ Ember.TrackedArray.prototype = { if (arrayOperation) { if (!match.split) { // insert left of arrayOperation - this._content.splice(arrayOperationIndex, 0, newArrayOperation); + this._operations.splice(arrayOperationIndex, 0, newArrayOperation); composeIndex = arrayOperationIndex; } else { this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); @@ -14902,7 +14953,7 @@ Ember.TrackedArray.prototype = { } } else { // insert at end - this._content.push(newArrayOperation); + this._operations.push(newArrayOperation); composeIndex = arrayOperationIndex; } @@ -14917,6 +14968,8 @@ Ember.TrackedArray.prototype = { @param count */ removeItems: function (index, count) { + if (count < 1) { return; } + var match = this._findArrayOperation(index), arrayOperation = match.operation, arrayOperationIndex = match.index, @@ -14927,7 +14980,7 @@ Ember.TrackedArray.prototype = { newArrayOperation = new ArrayOperation(DELETE, count); if (!match.split) { // insert left of arrayOperation - this._content.splice(arrayOperationIndex, 0, newArrayOperation); + this._operations.splice(arrayOperationIndex, 0, newArrayOperation); composeIndex = arrayOperationIndex; } else { this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); @@ -14942,12 +14995,11 @@ Ember.TrackedArray.prototype = { items in the array. `callback` will be called for each operation and will be passed the following arguments: - -* {array} items The items for the given operation -* {number} offset The computed offset of the items, ie the index in the -array of the first item for this operation. -* {string} operation The type of the operation. One of `Ember.TrackedArray.{RETAIN, DELETE, INSERT}` -* + * {array} items The items for the given operation + * {number} offset The computed offset of the items, ie the index in the + array of the first item for this operation. + * {string} operation The type of the operation. One of + `Ember.TrackedArray.{RETAIN, DELETE, INSERT}` @method apply @param {function} callback @@ -14956,16 +15008,16 @@ array of the first item for this operation. var items = [], offset = 0; - forEach(this._content, function (arrayOperation) { - callback(arrayOperation.items, offset, arrayOperation.operation); + forEach(this._operations, function (arrayOperation) { + callback(arrayOperation.items, offset, arrayOperation.type); - if (arrayOperation.operation !== DELETE) { + if (arrayOperation.type !== DELETE) { offset += arrayOperation.count; items = items.concat(arrayOperation.items); } }); - this._content = [new ArrayOperation(RETAIN, items.length, items)]; + this._operations = [new ArrayOperation(RETAIN, items.length, items)]; }, /** @@ -14987,10 +15039,10 @@ array of the first item for this operation. // OPTIMIZE: we could search these faster if we kept a balanced tree. // find leftmost arrayOperation to the right of `index` - for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._content.length; arrayOperationIndex < len; ++arrayOperationIndex) { - arrayOperation = this._content[arrayOperationIndex]; + for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._operations.length; arrayOperationIndex < len; ++arrayOperationIndex) { + arrayOperation = this._operations[arrayOperationIndex]; - if (arrayOperation.operation === DELETE) { continue; } + if (arrayOperation.type === DELETE) { continue; } arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1; @@ -15008,25 +15060,24 @@ array of the first item for this operation. }, _split: function (arrayOperationIndex, splitIndex, newArrayOperation) { - var arrayOperation = this._content[arrayOperationIndex], + var arrayOperation = this._operations[arrayOperationIndex], splitItems = arrayOperation.items.slice(splitIndex), - splitArrayOperation = new ArrayOperation(arrayOperation.operation, splitItems.length, splitItems); + splitArrayOperation = new ArrayOperation(arrayOperation.type, splitItems.length, splitItems); // truncate LHS arrayOperation.count = splitIndex; arrayOperation.items = arrayOperation.items.slice(0, splitIndex); - this._content.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation); + this._operations.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation); }, - // TODO: unify _composeInsert, _composeDelete // see SubArray for a better implementation. _composeInsert: function (index) { - var newArrayOperation = this._content[index], - leftArrayOperation = this._content[index-1], // may be undefined - rightArrayOperation = this._content[index+1], // may be undefined - leftOp = leftArrayOperation && leftArrayOperation.operation, - rightOp = rightArrayOperation && rightArrayOperation.operation; + var newArrayOperation = this._operations[index], + leftArrayOperation = this._operations[index-1], // may be undefined + rightArrayOperation = this._operations[index+1], // may be undefined + leftOp = leftArrayOperation && leftArrayOperation.type, + rightOp = rightArrayOperation && rightArrayOperation.type; if (leftOp === INSERT) { // merge left @@ -15034,30 +15085,31 @@ array of the first item for this operation. leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items); if (rightOp === INSERT) { - // also merge right + // also merge right (we have split an insert with an insert) leftArrayOperation.count += rightArrayOperation.count; leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items); - this._content.splice(index, 2); + this._operations.splice(index, 2); } else { // only merge left - this._content.splice(index, 1); + this._operations.splice(index, 1); } } else if (rightOp === INSERT) { // merge right newArrayOperation.count += rightArrayOperation.count; newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items); - this._content.splice(index + 1, 1); + this._operations.splice(index + 1, 1); } }, _composeDelete: function (index) { - var arrayOperation = this._content[index], + var arrayOperation = this._operations[index], deletesToGo = arrayOperation.count, - leftArrayOperation = this._content[index-1], // may be undefined - leftOp = leftArrayOperation && leftArrayOperation.operation, + leftArrayOperation = this._operations[index-1], // may be undefined + leftOp = leftArrayOperation && leftArrayOperation.type, nextArrayOperation, nextOp, nextCount, + removeNewAndNextOp = false, removedItems = []; if (leftOp === DELETE) { @@ -15066,8 +15118,8 @@ array of the first item for this operation. } for (var i = index + 1; deletesToGo > 0; ++i) { - nextArrayOperation = this._content[i]; - nextOp = nextArrayOperation.operation; + nextArrayOperation = this._operations[i]; + nextOp = nextArrayOperation.type; nextCount = nextArrayOperation.count; if (nextOp === DELETE) { @@ -15076,6 +15128,7 @@ array of the first item for this operation. } if (nextCount > deletesToGo) { + // d:2 {r,i}:5 we reduce the retain or insert, but it stays removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo)); nextArrayOperation.count -= deletesToGo; @@ -15087,29 +15140,57 @@ array of the first item for this operation. deletesToGo = 0; } else { + if (nextCount === deletesToGo) { + // Handle edge case of d:2 i:2 in which case both operations go away + // during composition. + removeNewAndNextOp = true; + } removedItems = removedItems.concat(nextArrayOperation.items); deletesToGo -= nextCount; } if (nextOp === INSERT) { + // d:2 i:3 will result in delete going away arrayOperation.count -= nextCount; } } if (arrayOperation.count > 0) { - this._content.splice(index+1, i-1-index); + // compose our new delete with possibly several operations to the right of + // disparate types + this._operations.splice(index+1, i-1-index); } else { // The delete operation can go away; it has merely reduced some other - // operation, as in D:3 I:4 - this._content.splice(index, 1); + // operation, as in d:3 i:4; it may also have eliminated that operation, + // as in d:3 i:3. + this._operations.splice(index, removeNewAndNextOp ? 2 : 1); } return removedItems; + }, + + toString: function () { + var str = ""; + forEach(this._operations, function (operation) { + str += " " + operation.type + ":" + operation.count; + }); + return str.substring(1); } }; +/** + Internal data structure to represent an array operation. + + @method ArrayOperation + @private + @property {string} type The type of the operation. One of + `Ember.TrackedArray.{RETAIN, INSERT, DELETE}` + @property {number} count The number of items in this operation. + @property {array} items The items of the operation, if included. RETAIN and + INSERT include their items, DELETE does not. +*/ function ArrayOperation (operation, count, items) { - this.operation = operation; // RETAIN | INSERT | DELETE + this.type = operation; // RETAIN | INSERT | DELETE this.count = count; this.items = items; } @@ -15247,6 +15328,8 @@ Ember.SubArray.prototype = { self._operations.splice(operationIndex, 1); self._composeAt(operationIndex); } + }, function() { + throw new Ember.Error("Can't remove an item that has never been added."); }); return returnValue; @@ -15293,6 +15376,7 @@ Ember.SubArray.prototype = { if (otherOp.type === op.type) { op.count += otherOp.count; this._operations.splice(index-1, 1); + --index; } } @@ -15389,7 +15473,14 @@ function makeCtor() { Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin)); - for (var keyName in properties) { + if (properties === null || typeof properties !== 'object') { + Ember.assert("Ember.Object.create only accepts objects."); + continue; + } + + var keyNames = Ember.keys(properties); + for (var j = 0, ll = keyNames.length; j < ll; j++) { + var keyName = keyNames[j]; if (!properties.hasOwnProperty(keyName)) { continue; } var value = properties[keyName], @@ -15577,7 +15668,7 @@ CoreObject.PrototypeMixin = Mixin.create({ This feature is available for you to use throughout the Ember object model, although typical app developers are likely to use it infrequently. Since it changes expectations about behavior of properties, you should properly - document its usage in each individual concatenated property (to not + document its usage in each individual concatenated property (to not mislead your users to think they can override the property in a subclass). @property concatenatedProperties @@ -15718,6 +15809,86 @@ var ClassMixin = Mixin.create({ isMethod: false, + /** + Creates a new subclass. + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + alert(thing); + } + }); + ``` + + This defines a new subclass of Ember.Object: `App.Person`. It contains one method: `say()`. + + You can also create a subclass from any existing class by calling its `extend()` method. For example, you might want to create a subclass of Ember's built-in `Ember.View` class: + + ```javascript + App.PersonView = Ember.View.extend({ + tagName: 'li', + classNameBindings: ['isAdministrator'] + }); + ``` + + When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special `_super()` method: + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + var name = this.get('name'); + alert(name + ' says: ' + thing); + } + }); + + App.Soldier = App.Person.extend({ + say: function(thing) { + this._super(thing + ", sir!"); + }, + march: function(numberOfHours) { + alert(this.get('name') + ' marches for ' + numberOfHours + ' hours.') + } + }); + + var yehuda = App.Soldier.create({ + name: "Yehuda Katz" + }); + + yehuda.say("Yes"); // alerts "Yehuda Katz says: Yes, sir!" + ``` + + The `create()` on line #17 creates an *instance* of the `App.Soldier` class. The `extend()` on line #8 creates a *subclass* of `App.Person`. Any instance of the `App.Person` class will *not* have the `march()` method. + + You can also pass `Ember.Mixin` classes to add additional properties to the subclass. + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + alert(this.get('name') + ' says: ' + thing); + } + }); + + App.SingingMixin = Ember.Mixin.create({ + sing: function(thing){ + alert(this.get('name') + ' sings: la la la ' + thing); + } + }); + + App.BroadwayStar = App.Person.extend(App.SingingMixin, { + dance: function() { + alert(this.get('name') + ' dances: tap tap tap tap '); + } + }); + ``` + + The `App.BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`. + + @method extend + @static + + @param {Ember.Mixin} [mixins]* One or more Ember.Mixin classes + @param {Object} [arguments]* Object containing values to use within the new class + */ extend: function() { var Class = makeCtor(), proto; Class.ClassMixin = Mixin.create(this.ClassMixin); @@ -15798,10 +15969,10 @@ var ClassMixin = Mixin.create({ }, /** - + Augments a constructor's prototype with additional properties and functions: - + ```javascript MyObject = Ember.Object.extend({ name: 'an object' @@ -15821,7 +15992,7 @@ var ClassMixin = Mixin.create({ o.say("goodbye"); // logs "goodbye" ``` - + To add functions and properties to the constructor itself, see `reopenClass` @@ -15835,7 +16006,7 @@ var ClassMixin = Mixin.create({ /** Augments a constructor's own properties and functions: - + ```javascript MyObject = Ember.Object.extend({ name: 'an object' @@ -15845,17 +16016,50 @@ var ClassMixin = Mixin.create({ MyObject.reopenClass({ canBuild: false }); - + MyObject.canBuild; // false o = MyObject.create(); ``` - + + In other words, this creates static properties and functions for the class. These are only available on the class + and not on any instance of that class. + + ```javascript + App.Person = Ember.Object.extend({ + name : "", + sayHello : function(){ + alert("Hello. My name is " + this.get('name')); + } + }); + + App.Person.reopenClass({ + species : "Homo sapiens", + createPerson: function(newPersonsName){ + return App.Person.create({ + name:newPersonsName + }); + } + }); + + var tom = App.Person.create({ + name : "Tom Dale" + }); + var yehuda = App.Person.createPerson("Yehuda Katz"); + + tom.sayHello(); // "Hello. My name is Tom Dale" + yehuda.sayHello(); // "Hello. My name is Yehuda Katz" + alert(App.Person.species); // "Homo sapiens" + ``` + + Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda` + variables. They are only valid on `App.Person`. + To add functions and properties to instances of a constructor by extending the constructor's prototype see `reopen` - + @method reopenClass - */ + */ reopenClass: function() { reopen.apply(this.ClassMixin, arguments); applyMixin(this, arguments, false); @@ -16414,7 +16618,7 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,/** @scope Ember.Array }, _insertAt: function(idx, object) { - if (idx > get(this, 'content.length')) throw new Error(OUT_OF_RANGE_EXCEPTION); + if (idx > get(this, 'content.length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION); this._replace(idx, 0, [object]); return this; }, @@ -16434,7 +16638,7 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,/** @scope Ember.Array indices = [], i; if ((start < 0) || (start >= get(this, 'length'))) { - throw new Error(OUT_OF_RANGE_EXCEPTION); + throw new Ember.Error(OUT_OF_RANGE_EXCEPTION); } if (len === undefined) len = 1; @@ -17201,7 +17405,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb @return {Ember.Set} An empty Set */ clear: function() { - if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); } + if (this.isFrozen) { throw new Ember.Error(Ember.FROZEN_ERROR); } var len = get(this, 'length'); if (len === 0) { return this; } @@ -17311,7 +17515,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb @return {Object} The removed object from the set or null. */ pop: function() { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR); var obj = this.length > 0 ? this[this.length-1] : null; this.remove(obj); return obj; @@ -17428,7 +17632,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb // implements Ember.MutableEnumerable addObject: function(obj) { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR); if (isNone(obj)) return this; // nothing to do var guid = guidFor(obj), @@ -17456,7 +17660,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb // implements Ember.MutableEnumerable removeObject: function(obj) { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR); if (isNone(obj)) return this; // nothing to do var guid = guidFor(obj), @@ -18163,7 +18367,7 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, fullName = "controller:" + controllerClass; if (!container.has(fullName)) { - throw new Error('Could not resolve itemController: "' + controllerClass + '"'); + throw new Ember.Error('Could not resolve itemController: "' + controllerClass + '"'); } subController = container.lookupFactory(fullName).create({ @@ -18485,6 +18689,19 @@ function escapeAttribute(value) { return string.replace(BAD_CHARS_REGEXP, escapeChar); } +// IE 6/7 have bugs arond setting names on inputs during creation. +// From http://msdn.microsoft.com/en-us/library/ie/ms536389(v=vs.85).aspx: +// "To include the NAME attribute at run time on objects created with the createElement method, use the eTag." +var canSetNameOnInputs = (function() { + var div = document.createElement('div'), + el = document.createElement('input'); + + el.setAttribute('name', 'foo'); + div.appendChild(el); + + return !!div.innerHTML.match('foo'); +})(); + /** `Ember.RenderBuffer` gathers information regarding the a view and generates the final representation. `Ember.RenderBuffer` will generate HTML which can be pushed @@ -18844,14 +19061,22 @@ Ember._RenderBuffer.prototype = generateElement: function() { var tagName = this.tagNames.pop(), // pop since we don't need to close - element = document.createElement(tagName), - $element = Ember.$(element), id = this.elementId, classes = this.classes, attrs = this.elementAttributes, props = this.elementProperties, style = this.elementStyle, - styleBuffer = '', attr, prop; + styleBuffer = '', attr, prop, tagString; + + if (attrs && attrs.name && !canSetNameOnInputs) { + // IE allows passing a tag to createElement. See note on `canSetNameOnInputs` above as well. + tagString = '<'+stripTagName(tagName)+' name="'+escapeAttribute(attrs.name)+'">'; + } else { + tagString = tagName; + } + + var element = document.createElement(tagString), + $element = Ember.$(element); if (id) { $element.attr('id', id); @@ -19267,7 +19492,7 @@ var childViewsProperty = Ember.computed(function() { Ember.deprecate("Manipulating an 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"); + throw new Ember.Error("childViews is immutable"); }; return ret; @@ -20957,8 +21182,8 @@ Ember.View = Ember.CoreView.extend( If you write a `willDestroyElement()` handler, you can assume that your `didInsertElement()` handler was called earlier for the same element. - Normally you will not call or override this method yourself, but you may - want to implement the above callbacks when it is run. + You should not call or override this method yourself, but you may + want to implement the above callbacks. @method destroyElement @return {Ember.View} receiver @@ -21486,6 +21711,10 @@ Ember.View = Ember.CoreView.extend( target = null; } + if (!root || typeof root !== 'object') { + return; + } + var view = this, stateCheckedObserver = function() { view.currentState.invokeObserver(this, observer); @@ -22022,7 +22251,7 @@ Ember.merge(inDOM, { } view.addBeforeObserver('elementId', function() { - throw new Error("Changing a view's elementId after creation is not allowed"); + throw new Ember.Error("Changing a view's elementId after creation is not allowed"); }); }, @@ -22262,30 +22491,6 @@ var ViewCollection = Ember._ViewCollection; or layout being rendered. The HTML contents of a `Ember.ContainerView`'s DOM representation will only be the rendered HTML of its child views. - ## Binding a View to Display - - 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 - instance, it will be added to the ContainerView. If the `currentView` property - is later changed to a different view, the new view will replace the old view. - If `currentView` is set to `null`, the last `currentView` will be removed. - - 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 - the `currentView` of a container to a controller like this: - - ```javascript - App.appController = Ember.Object.create({ - view: Ember.View.create({ - templateName: 'person_template' - }) - }); - ``` - - ```handlebars - {{view Ember.ContainerView currentViewBinding="App.appController.view"}} - ``` - @class ContainerView @namespace Ember @extends Ember.View @@ -22470,7 +22675,7 @@ Ember.merge(states._default, { Ember.merge(states.inBuffer, { childViewsDidChange: function(parentView, views, start, added) { - throw new Error('You cannot modify child views while in the inBuffer state'); + throw new Ember.Error('You cannot modify child views while in the inBuffer state'); } }); @@ -22962,7 +23167,9 @@ Ember.CollectionView.CONTAINER_MAP = { (function() { -var get = Ember.get, set = Ember.set, isNone = Ember.isNone; +var get = Ember.get, set = Ember.set, isNone = Ember.isNone, + a_slice = Array.prototype.slice; + /** @module ember @@ -23153,8 +23360,9 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { @param [action] {String} the action to trigger @param [context] {*} a context to send with the action */ - sendAction: function(action, context) { - var actionName; + sendAction: function(action) { + var actionName, + contexts = a_slice.call(arguments, 1); // Send the default action if (action === undefined) { @@ -23170,7 +23378,7 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { this.triggerAction({ action: actionName, - actionContext: context + actionContext: contexts }); } }); @@ -23769,20 +23977,6 @@ Ember.assert("Ember Handlebars requires Handlebars version 1.0.0, COMPILER_REVIS */ Ember.Handlebars = objectCreate(Handlebars); -function makeBindings(options) { - var hash = options.hash, - hashType = options.hashTypes; - - for (var prop in hash) { - if (hashType[prop] === 'ID') { - hash[prop + 'Binding'] = hash[prop]; - hashType[prop + 'Binding'] = 'STRING'; - delete hash[prop]; - delete hashType[prop]; - } - } -} - /** Register a bound helper or custom view helper. @@ -23842,7 +24036,6 @@ Ember.Handlebars.helper = function(name, value) { if (Ember.View.detect(value)) { Ember.Handlebars.registerHelper(name, function(options) { Ember.assert("You can only pass attributes (such as name=value) not bare values to a helper for a View", arguments.length < 2); - makeBindings(options); return Ember.Handlebars.helpers.view.call(this, value, options); }); } else { @@ -24078,6 +24271,7 @@ var handlebarsGet = Ember.Handlebars.get = function(root, path, options) { } return value; }; +Ember.Handlebars.getPath = Ember.deprecateFunc('`Ember.Handlebars.getPath` has been changed to `Ember.Handlebars.get` for consistency.', Ember.Handlebars.get); Ember.Handlebars.resolveParams = function(context, params, options) { var resolvedParams = [], types = options.types, param, type; @@ -24260,7 +24454,8 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) { data = options.data, hash = options.hash, view = data.view, - currentContext = (options.contexts && options.contexts[0]) || this, + contexts = options.contexts, + currentContext = (contexts && contexts.length) ? contexts[0] : this, prefixPathForDependentKeys = '', loc, len, hashOption, boundOption, property, @@ -24385,7 +24580,7 @@ function evaluateUnboundHelper(context, fn, normalizedProperties, options) { for(loc = 0, len = normalizedProperties.length; loc < len; ++loc) { property = normalizedProperties[loc]; - args.push(Ember.Handlebars.get(context, property.path, options)); + args.push(Ember.Handlebars.get(property.root, property.path, options)); } args.push(options); return fn.apply(context, args); @@ -25029,16 +25224,16 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer } } -function simpleBind(property, options) { +function simpleBind(currentContext, property, options) { var data = options.data, view = data.view, - currentContext = this, - normalized, observer; + normalized, observer, pathRoot, output; normalized = normalizePath(currentContext, property, data); + pathRoot = normalized.root; // Set up observers for observable objects - if ('object' === typeof this) { + if (pathRoot && ('object' === typeof pathRoot)) { if (data.insideGroup) { observer = function() { Ember.run.once(view, 'rerender'); @@ -25070,7 +25265,8 @@ function simpleBind(property, options) { } else { // The object is not observable, so just render it out and // be done with it. - data.buffer.push(handlebarsGet(currentContext, property, options)); + output = handlebarsGet(currentContext, property, options); + data.buffer.push((output === null || typeof output === 'undefined') ? '' : output); } } @@ -25127,10 +25323,10 @@ EmberHandlebars.registerHelper('_triageMustache', function(property, fn) { EmberHandlebars.registerHelper('bind', function(property, options) { Ember.assert("You cannot pass more than one argument to the bind helper", arguments.length <= 2); - var context = (options.contexts && options.contexts[0]) || this; + var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this; if (!options.fn) { - return simpleBind.call(context, property, options); + return simpleBind(context, property, options); } return bind.call(context, property, options, false, exists); @@ -25155,7 +25351,7 @@ EmberHandlebars.registerHelper('bind', function(property, options) { @return {String} HTML string */ EmberHandlebars.registerHelper('boundIf', function(property, fn) { - var context = (fn.contexts && fn.contexts[0]) || this; + var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this; var func = function(result) { var truthy = result && get(result, 'isTruthy'); if (typeof truthy === 'boolean') { return truthy; } @@ -25605,6 +25801,35 @@ var EmberHandlebars = Ember.Handlebars; var LOWERCASE_A_Z = /^[a-z]/; var VIEW_PREFIX = /^view\./; +function makeBindings(thisContext, options) { + var hash = options.hash, + hashType = options.hashTypes; + + for (var prop in hash) { + if (hashType[prop] === 'ID') { + + var value = hash[prop]; + + if (Ember.IS_BINDING.test(prop)) { + Ember.warn("You're attempting to render a view by passing " + prop + "=" + value + " to a view helper, but this syntax is ambiguous. You should either surround " + value + " in quotes or remove `Binding` from " + prop + "."); + } else { + hash[prop + 'Binding'] = value; + hashType[prop + 'Binding'] = 'STRING'; + delete hash[prop]; + delete hashType[prop]; + } + } + } + + if (hash.hasOwnProperty('idBinding')) { + // id can't be bound, so just perform one-time lookup. + hash.id = EmberHandlebars.get(thisContext, hash.idBinding, options); + hashType.id = 'STRING'; + delete hash.idBinding; + delete hashType.idBinding; + } +} + EmberHandlebars.ViewHelper = Ember.Object.create({ propertiesFromHTMLOptions: function(options, thisContext) { @@ -25714,6 +25939,8 @@ EmberHandlebars.ViewHelper = Ember.Object.create({ fn = options.fn, newView; + makeBindings(thisContext, options); + if ('string' === typeof path) { // TODO: this is a lame conditional, this should likely change @@ -26204,7 +26431,7 @@ Ember.Handlebars.registerHelper('unbound', function(property, fn) { return out; } - context = (fn.contexts && fn.contexts[0]) || this; + context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this; return handlebarsGet(context, property, fn); }); @@ -26234,7 +26461,7 @@ var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.norma @param {String} property */ Ember.Handlebars.registerHelper('log', function(property, options) { - var context = (options.contexts && options.contexts[0]) || this, + var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this, normalized = normalizePath(context, property, options.data), pathRoot = normalized.root, path = normalized.path, @@ -26848,11 +27075,11 @@ var get = Ember.get, set = Ember.set; <!-- application.hbs --> {{#labeled-textfield value=someProperty}} First name: - {{/my-component}} + {{/labeled-textfield}} ``` ```handlebars - <!-- components/my-component.hbs --> + <!-- components/labeled-textfield.hbs --> <label> {{yield}} {{input value=value}} </label> @@ -27016,7 +27243,7 @@ var get = Ember.get, set = Ember.set; Ember.TextSupport = Ember.Mixin.create({ value: "", - attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly'], + attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex'], placeholder: null, disabled: false, maxlength: null, @@ -27050,7 +27277,7 @@ Ember.TextSupport = Ember.Mixin.create({ Options are: * `enter`: the user pressed enter - * `keypress`: the user pressed a key + * `keyPress`: the user pressed a key @property onEvent @type String @@ -27093,7 +27320,7 @@ Ember.TextSupport = Ember.Mixin.create({ Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 13. Uses sendAction to send the `enter` action to the controller. - @method insertNewLine + @method insertNewline @param {Event} event */ insertNewline: function(event) { @@ -27104,8 +27331,8 @@ Ember.TextSupport = Ember.Mixin.create({ /** Called when the user hits escape. - Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 13. - Uses sendAction to send the `enter` action to the controller. + Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 27. + Uses sendAction to send the `escape-press` action to the controller. @method cancel @param {Event} event @@ -27541,7 +27768,7 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ``` ```handlebars - {{view Ember.Select contentBinding="names"}} + {{view Ember.Select content=names}} ``` Would result in the following HTML: @@ -27554,7 +27781,7 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ``` You can control which `<option>` is selected through the `Ember.Select`'s - `value` property directly or as a binding: + `value` property: ```javascript App.ApplicationController = Ember.Controller.extend({ @@ -27565,8 +27792,8 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ```handlebars {{view Ember.Select - contentBinding="names" - valueBinding="selectedName" + content=names + value=selectedName }} ``` @@ -27607,7 +27834,7 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ```handlebars {{view Ember.Select - contentBinding="programmers" + content=programmers optionValuePath="content.id" optionLabelPath="content.firstName"}} ``` @@ -27622,8 +27849,7 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ``` The `value` attribute of the selected `<option>` within an `Ember.Select` - can be bound to a property on another object by providing a - `valueBinding` option: + can be bound to a property on another object: ```javascript App.ApplicationController = Ember.Controller.extend({ @@ -27639,10 +27865,10 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ```handlebars {{view Ember.Select - contentBinding="programmers" + content=programmers optionValuePath="content.id" optionLabelPath="content.firstName" - valueBinding="currentProgrammer.id"}} + value=currentProgrammer.id}} ``` Would result in the following HTML with a selected option: @@ -27659,8 +27885,8 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ to match the `value` property of the newly selected `<option>`. Alternatively, you can control selection through the underlying objects - used to render each object providing a `selectionBinding`. When the selected - `<option>` is changed, the property path provided to `selectionBinding` + used to render each object by binding the `selection` option. When the selected + `<option>` is changed, the property path provided to `selection` will be updated to match the content object of the rendered `<option>` element: @@ -27676,10 +27902,10 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ```handlebars {{view Ember.Select - contentBinding="programmers" + content=programmers optionValuePath="content.id" optionLabelPath="content.firstName" - selectionBinding="selectedPerson"}} + selection=selectedPerson}} ``` Would result in the following HTML with a selected option: @@ -27692,7 +27918,7 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ``` Interacting with the rendered element by selecting the first option - ('Yehuda') will update the `selectedPerson` to match the object of + ('Yehuda') will update the `selectedPerson` to match the object of the newly selected `<option>`. In this case it is the first object in the `programmers` @@ -27713,8 +27939,8 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ``` handlebars {{view Ember.Select - contentBinding="programmers" - valueBinding="selectedProgrammer" + content=programmers + value=selectedProgrammer }} ``` @@ -27745,8 +27971,8 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ```handlebars {{view Ember.Select - contentBinding="programmers" - valueBinding="selectedProgrammer" + content=programmers + value=selectedProgrammer prompt="Please select a name" }} ``` @@ -27798,11 +28024,11 @@ function program3(depth0,data) { function program4(depth0,data) { var hashContexts, hashTypes; - hashContexts = {'contentBinding': depth0,'labelBinding': depth0}; - hashTypes = {'contentBinding': "ID",'labelBinding': "ID"}; + hashContexts = {'content': depth0,'label': depth0}; + hashTypes = {'content': "ID",'label': "ID"}; data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.groupView", {hash:{ - 'contentBinding': ("content"), - 'labelBinding': ("label") + 'content': ("content"), + 'label': ("label") },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); } @@ -27818,10 +28044,10 @@ function program6(depth0,data) { function program7(depth0,data) { var hashContexts, hashTypes; - hashContexts = {'contentBinding': depth0}; - hashTypes = {'contentBinding': "STRING"}; + hashContexts = {'content': depth0}; + hashTypes = {'content': "ID"}; data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.optionView", {hash:{ - 'contentBinding': ("this") + 'content': ("") },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); } @@ -27852,7 +28078,7 @@ function program7(depth0,data) { The `disabled` attribute of the select element. Indicates whether the element is disabled from interactions. - @property multiple + @property disabled @type Boolean @default false */ @@ -27961,8 +28187,9 @@ function program7(depth0,data) { groupedContent: Ember.computed(function() { var groupPath = get(this, 'optionGroupPath'); var groupedContent = Ember.A(); + var content = get(this, 'content') || []; - forEach(get(this, 'content'), function(item) { + forEach(content, function(item) { var label = get(item, groupPath); if (get(groupedContent, 'lastObject.label') !== label) { @@ -28117,15 +28344,6 @@ function program7(depth0,data) { @submodule ember-handlebars-compiler */ -function normalizeHash(hash, hashTypes) { - for (var prop in hash) { - if (hashTypes[prop] === 'ID') { - hash[prop + 'Binding'] = hash[prop]; - delete hash[prop]; - } - } -} - /** The `{{input}}` helper inserts an HTML `<input>` tag into the template, @@ -28213,7 +28431,6 @@ function normalizeHash(hash, hashTypes) { * `indeterminate` * `name` - When set to a quoted string, these values will be directly applied to the HTML element. When left unquoted, these values will be bound to a property on the template's current rendering context (most typically a controller instance). @@ -28275,8 +28492,6 @@ Ember.Handlebars.registerHelper('input', function(options) { delete hash.type; delete hash.on; - normalizeHash(hash, types); - if (inputType === 'checkbox') { return Ember.Handlebars.helpers.view.call(this, Ember.Checkbox, options); } else { @@ -28439,7 +28654,6 @@ Ember.Handlebars.registerHelper('textarea', function(options) { var hash = options.hash, types = options.hashTypes; - normalizeHash(hash, types); return Ember.Handlebars.helpers.view.call(this, Ember.TextArea, options); }); @@ -28490,7 +28704,7 @@ Ember.Handlebars.bootstrap = function(ctx) { // Check if template of same name already exists if (Ember.TEMPLATES[templateName] !== undefined) { - throw new Error('Template named "' + templateName + '" already exists.'); + throw new Ember.Error('Template named "' + templateName + '" already exists.'); } // For templates which have a name, we save them and then remove them from the DOM @@ -28679,7 +28893,7 @@ define("route-recognizer", results.push(new StarSegment(match[1])); names.push(match[1]); types.stars++; - } else if(segment === "") { + } else if (segment === "") { results.push(new EpsilonSegment()); } else { results.push(new StaticSegment(segment)); @@ -28828,31 +29042,19 @@ define("route-recognizer", return nextStates; } - function findHandler(state, path, queryParams) { + function findHandler(state, path) { var handlers = state.handlers, regex = state.regex; var captures = path.match(regex), currentCapture = 1; var result = []; for (var i=0, l=handlers.length; i<l; i++) { - var handler = handlers[i], names = handler.names, params = {}, - watchedQueryParams = handler.queryParams || [], - activeQueryParams = {}, - j, m; + var handler = handlers[i], names = handler.names, params = {}; - for (j=0, m=names.length; j<m; j++) { + for (var j=0, m=names.length; j<m; j++) { params[names[j]] = captures[currentCapture++]; } - for (j=0, m=watchedQueryParams.length; j < m; j++) { - var key = watchedQueryParams[j]; - if(queryParams[key]){ - activeQueryParams[key] = queryParams[key]; - } - } - var currentResult = { handler: handler.handler, params: params, isDynamic: !!names.length }; - if(watchedQueryParams && watchedQueryParams.length > 0) { - currentResult.queryParams = activeQueryParams; - } - result.push(currentResult); + + result.push({ handler: handler.handler, params: params, isDynamic: !!names.length }); } return result; @@ -28907,11 +29109,7 @@ define("route-recognizer", regex += segment.regex(); } - var handler = { handler: route.handler, names: names }; - if(route.queryParams) { - handler.queryParams = route.queryParams; - } - handlers.push(handler); + handlers.push({ handler: route.handler, names: names }); } if (isEmpty) { @@ -28963,61 +29161,12 @@ define("route-recognizer", if (output.charAt(0) !== '/') { output = '/' + output; } - if (params && params.queryParams) { - output += this.generateQueryString(params.queryParams, route.handlers); - } - return output; }, - generateQueryString: function(params, handlers) { - var pairs = [], allowedParams = []; - for(var i=0; i < handlers.length; i++) { - var currentParamList = handlers[i].queryParams; - if(currentParamList) { - allowedParams.push.apply(allowedParams, currentParamList); - } - } - for(var key in params) { - if (params.hasOwnProperty(key)) { - if(!~allowedParams.indexOf(key)) { - throw 'Query param "' + key + '" is not specified as a valid param for this route'; - } - var value = params[key]; - var pair = encodeURIComponent(key); - if(value !== true) { - pair += "=" + encodeURIComponent(value); - } - pairs.push(pair); - } - } - - if (pairs.length === 0) { return ''; } - - return "?" + pairs.join("&"); - }, - - parseQueryString: function(queryString) { - var pairs = queryString.split("&"), queryParams = {}; - for(var i=0; i < pairs.length; i++) { - var pair = pairs[i].split('='), - key = decodeURIComponent(pair[0]), - value = pair[1] ? decodeURIComponent(pair[1]) : true; - queryParams[key] = value; - } - return queryParams; - }, - recognize: function(path) { var states = [ this.rootState ], - pathLen, i, l, queryStart, queryParams = {}; - - queryStart = path.indexOf('?'); - if (~queryStart) { - var queryString = path.substr(queryStart + 1, path.length); - path = path.substr(0, queryStart); - queryParams = this.parseQueryString(queryString); - } + pathLen, i, l; // DEBUG GROUP path @@ -29045,7 +29194,7 @@ define("route-recognizer", var state = solutions[0]; if (state && state.handlers) { - return findHandler(state, path, queryParams); + return findHandler(state, path); } } }; @@ -29070,25 +29219,12 @@ define("route-recognizer", if (callback.length === 0) { throw new Error("You must have an argument in the function passed to `to`"); } this.matcher.addChild(this.path, target, callback, this.delegate); } - return this; - }, - - withQueryParams: function() { - if (arguments.length === 0) { throw new Error("you must provide arguments to the withQueryParams method"); } - for (var i = 0; i < arguments.length; i++) { - if (typeof arguments[i] !== "string") { - throw new Error('you should call withQueryParams with a list of strings, e.g. withQueryParams("foo", "bar")'); - } - } - var queryParams = [].slice.call(arguments); - this.matcher.addQueryParams(this.path, queryParams); } }; function Matcher(target) { this.routes = {}; this.children = {}; - this.queryParams = {}; this.target = target; } @@ -29097,10 +29233,6 @@ define("route-recognizer", this.routes[path] = handler; }, - addQueryParams: function(path, params) { - this.queryParams[path] = params; - }, - addChild: function(path, target, callback, delegate) { var matcher = new Matcher(target); this.children[path] = matcher; @@ -29127,26 +29259,23 @@ define("route-recognizer", }; } - function addRoute(routeArray, path, handler, queryParams) { + function addRoute(routeArray, path, handler) { var len = 0; for (var i=0, l=routeArray.length; i<l; i++) { len += routeArray[i].path.length; } path = path.substr(len); - var route = { path: path, handler: handler }; - if(queryParams) { route.queryParams = queryParams; } - routeArray.push(route); + routeArray.push({ path: path, handler: handler }); } function eachRoute(baseRoute, matcher, callback, binding) { var routes = matcher.routes; - var queryParams = matcher.queryParams; for (var path in routes) { if (routes.hasOwnProperty(path)) { var routeArray = baseRoute.slice(); - addRoute(routeArray, path, routes[path], queryParams[path]); + addRoute(routeArray, path, routes[path]); if (matcher.children[path]) { eachRoute(routeArray, matcher.children[path], callback, binding); @@ -29285,9 +29414,9 @@ define("router", */ retry: function() { this.abort(); + var recogHandlers = this.router.recognizer.handlersFor(this.targetName), - handlerInfos = generateHandlerInfosWithQueryParams(this.router, recogHandlers, this.queryParams), - newTransition = performTransition(this.router, handlerInfos, this.providedModelsArray, this.params, this.queryParams, this.data); + newTransition = performTransition(this.router, recogHandlers, this.providedModelsArray, this.params, this.data); return newTransition; }, @@ -29312,10 +29441,6 @@ define("router", method: function(method) { this.urlMethod = method; return this; - }, - - toString: function() { - return "Transition (sequence " + this.sequence + ")"; } }; @@ -29460,21 +29585,8 @@ define("router", @param {Array[Object]} contexts @return {Object} a serialized parameter hash */ - paramsForHandler: function(handlerName, contexts) { - var partitionedArgs = extractQueryParams(slice.call(arguments, 1)); - return paramsForHandler(this, handlerName, partitionedArgs[0], partitionedArgs[1]); - }, - - /** - This method takes a handler name and returns a list of query params - that are valid to pass to the handler or its parents - - @param {String} handlerName - @return {Array[String]} a list of query parameters - */ - queryParamsForHandler: function (handlerName) { - return queryParamsForHandler(this, handlerName); + return paramsForHandler(this, handlerName, slice.call(arguments, 1)); }, /** @@ -29488,41 +29600,12 @@ define("router", @return {String} a URL */ generate: function(handlerName) { - var partitionedArgs = extractQueryParams(slice.call(arguments, 1)), - suppliedParams = partitionedArgs[0], - queryParams = partitionedArgs[1]; - - var params = paramsForHandler(this, handlerName, suppliedParams, queryParams), - validQueryParams = queryParamsForHandler(this, handlerName); - - var missingParams = []; - - for (var key in queryParams) { - if (queryParams.hasOwnProperty(key) && !~validQueryParams.indexOf(key)) { - missingParams.push(key); - } - } - - if (missingParams.length > 0) { - var err = 'You supplied the params '; - err += missingParams.map(function(param) { - return '"' + param + "=" + queryParams[param] + '"'; - }).join(' and '); - - err += ' which are not valid for the "' + handlerName + '" handler or its parents'; - - throw new Error(err); - } - + var params = paramsForHandler(this, handlerName, slice.call(arguments, 1)); return this.recognizer.generate(handlerName, params); }, isActive: function(handlerName) { - var partitionedArgs = extractQueryParams(slice.call(arguments, 1)), - contexts = partitionedArgs[0], - queryParams = partitionedArgs[1], - activeQueryParams = {}, - effectiveQueryParams = {}; + var contexts = slice.call(arguments, 1); var targetHandlerInfos = this.targetHandlerInfos, found = false, names, object, handlerInfo, handlerObj; @@ -29530,24 +29613,19 @@ define("router", if (!targetHandlerInfos) { return false; } var recogHandlers = this.recognizer.handlersFor(targetHandlerInfos[targetHandlerInfos.length - 1].name); + for (var i=targetHandlerInfos.length-1; i>=0; i--) { handlerInfo = targetHandlerInfos[i]; if (handlerInfo.name === handlerName) { found = true; } if (found) { - var recogHandler = recogHandlers[i]; + if (contexts.length === 0) { break; } - merge(activeQueryParams, handlerInfo.queryParams); - if (queryParams !== false) { - merge(effectiveQueryParams, handlerInfo.queryParams); - mergeSomeKeys(effectiveQueryParams, queryParams, recogHandler.queryParams); - } - - if (handlerInfo.isDynamic && contexts.length > 0) { + if (handlerInfo.isDynamic) { object = contexts.pop(); if (isParam(object)) { - var name = recogHandler.names[0]; + var recogHandler = recogHandlers[i], name = recogHandler.names[0]; if ("" + object !== this.currentParams[name]) { return false; } } else if (handlerInfo.context !== object) { return false; @@ -29556,8 +29634,7 @@ define("router", } } - - return contexts.length === 0 && found && queryParamsEqual(activeQueryParams, effectiveQueryParams); + return contexts.length === 0 && found; }, trigger: function(name) { @@ -29580,7 +29657,7 @@ define("router", a shared pivot parent route and other data necessary to perform a transition. */ - function getMatchPoint(router, handlers, objects, inputParams, queryParams) { + function getMatchPoint(router, handlers, objects, inputParams) { var matchPoint = handlers.length, providedModels = {}, i, @@ -29635,12 +29712,6 @@ define("router", } } - // If there is an old handler, see if query params are the same. If there isn't an old handler, - // hasChanged will already be true here - if(oldHandlerInfo && !queryParamsEqual(oldHandlerInfo.queryParams, handlerObj.queryParams)) { - hasChanged = true; - } - if (hasChanged) { matchPoint = i; } } @@ -29674,28 +29745,6 @@ define("router", return (typeof object === "string" || object instanceof String || !isNaN(object)); } - - - /** - @private - - This method takes a handler name and returns a list of query params - that are valid to pass to the handler or its parents - - @param {Router} router - @param {String} handlerName - @return {Array[String]} a list of query parameters - */ - function queryParamsForHandler(router, handlerName) { - var handlers = router.recognizer.handlersFor(handlerName), - queryParams = []; - - for (var i = 0; i < handlers.length; i++) { - queryParams.push.apply(queryParams, handlers[i].queryParams || []); - } - - return queryParams; - } /** @private @@ -29707,17 +29756,13 @@ define("router", @param {Array[Object]} objects @return {Object} a serialized parameter hash */ - function paramsForHandler(router, handlerName, objects, queryParams) { + function paramsForHandler(router, handlerName, objects) { var handlers = router.recognizer.handlersFor(handlerName), params = {}, - handlerInfos = generateHandlerInfosWithQueryParams(router, handlers, queryParams), - matchPoint = getMatchPoint(router, handlerInfos, objects).matchPoint, - mergedQueryParams = {}, + matchPoint = getMatchPoint(router, handlers, objects).matchPoint, object, handlerObj, handler, names, i; - params.queryParams = {}; - for (i=0; i<handlers.length; i++) { handlerObj = handlers[i]; handler = router.getHandler(handlerObj.handler); @@ -29736,13 +29781,7 @@ define("router", // Serialize to generate params merge(params, serialize(handler, object, names)); } - if (queryParams !== false) { - mergeSomeKeys(params.queryParams, router.currentQueryParams, handlerObj.queryParams); - mergeSomeKeys(params.queryParams, queryParams, handlerObj.queryParams); - } } - - if (queryParamsEqual(params.queryParams, {})) { delete params.queryParams; } return params; } @@ -29752,84 +29791,24 @@ define("router", } } - function mergeSomeKeys(hash, other, keys) { - if (!other || !keys) { return; } - for(var i = 0; i < keys.length; i++) { - var key = keys[i], value; - if(other.hasOwnProperty(key)) { - value = other[key]; - if(value === null || value === false || typeof value === "undefined") { - delete hash[key]; - } else { - hash[key] = other[key]; - } - } - } - } - - /** - @private - */ - - function generateHandlerInfosWithQueryParams(router, handlers, queryParams) { - var handlerInfos = []; - - for (var i = 0; i < handlers.length; i++) { - var handler = handlers[i], - handlerInfo = { handler: handler.handler, names: handler.names, context: handler.context, isDynamic: handler.isDynamic }, - activeQueryParams = {}; - - if (queryParams !== false) { - mergeSomeKeys(activeQueryParams, router.currentQueryParams, handler.queryParams); - mergeSomeKeys(activeQueryParams, queryParams, handler.queryParams); - } - - if (handler.queryParams && handler.queryParams.length > 0) { - handlerInfo.queryParams = activeQueryParams; - } - - handlerInfos.push(handlerInfo); - } - - return handlerInfos; - } - - /** - @private - */ - function createQueryParamTransition(router, queryParams) { - var currentHandlers = router.currentHandlerInfos, - currentHandler = currentHandlers[currentHandlers.length - 1], - name = currentHandler.name; - - log(router, "Attempting query param transition"); - - return createNamedTransition(router, [name, queryParams]); - } - /** @private */ function createNamedTransition(router, args) { - var partitionedArgs = extractQueryParams(args), - pureArgs = partitionedArgs[0], - queryParams = partitionedArgs[1], - handlers = router.recognizer.handlersFor(pureArgs[0]), - handlerInfos = generateHandlerInfosWithQueryParams(router, handlers, queryParams); + var handlers = router.recognizer.handlersFor(args[0]); + log(router, "Attempting transition to " + args[0]); - log(router, "Attempting transition to " + pureArgs[0]); - - return performTransition(router, handlerInfos, slice.call(pureArgs, 1), router.currentParams, queryParams); + return performTransition(router, handlers, slice.call(args, 1), router.currentParams); } /** @private */ function createURLTransition(router, url) { + var results = router.recognizer.recognize(url), - currentHandlerInfos = router.currentHandlerInfos, - queryParams = {}; + currentHandlerInfos = router.currentHandlerInfos; log(router, "Attempting URL transition to " + url); @@ -29837,11 +29816,7 @@ define("router", return errorTransition(router, new Router.UnrecognizedURLError(url)); } - for(var i = 0; i < results.length; i++) { - merge(queryParams, results[i].queryParams); - } - - return performTransition(router, results, [], {}, queryParams); + return performTransition(router, results, [], {}); } @@ -29925,9 +29900,8 @@ define("router", checkAbort(transition); setContext(handler, context); - setQueryParams(handler, handlerInfo.queryParams); - if (handler.setup) { handler.setup(context, handlerInfo.queryParams); } + if (handler.setup) { handler.setup(context); } checkAbort(transition); } catch(e) { if (!(e instanceof Router.TransitionAborted)) { @@ -29958,29 +29932,6 @@ define("router", } } - /** - @private - - determines if two queryparam objects are the same or not - **/ - function queryParamsEqual(a, b) { - a = a || {}; - b = b || {}; - var checkedKeys = [], key; - for(key in a) { - if (!a.hasOwnProperty(key)) { continue; } - if(b[key] !== a[key]) { return false; } - checkedKeys.push(key); - } - for(key in b) { - if (!b.hasOwnProperty(key)) { continue; } - if (~checkedKeys.indexOf(key)) { continue; } - // b has a key not in a - return false; - } - return true; - } - /** @private @@ -30030,21 +29981,19 @@ define("router", unchanged: [] }; - var handlerChanged, contextChanged, queryParamsChanged, i, l; + var handlerChanged, contextChanged, i, l; for (i=0, l=newHandlers.length; i<l; i++) { var oldHandler = oldHandlers[i], newHandler = newHandlers[i]; if (!oldHandler || oldHandler.handler !== newHandler.handler) { handlerChanged = true; - } else if (!queryParamsEqual(oldHandler.queryParams, newHandler.queryParams)) { - queryParamsChanged = true; } if (handlerChanged) { handlers.entered.push(newHandler); if (oldHandler) { handlers.exited.unshift(oldHandler); } - } else if (contextChanged || oldHandler.context !== newHandler.context || queryParamsChanged) { + } else if (contextChanged || oldHandler.context !== newHandler.context) { contextChanged = true; handlers.updatedContext.push(newHandler); } else { @@ -30097,45 +30046,21 @@ define("router", if (handler.contextDidChange) { handler.contextDidChange(); } } - function setQueryParams(handler, queryParams) { - handler.queryParams = queryParams; - if (handler.queryParamsDidChange) { handler.queryParamsDidChange(); } - } - - - /** - @private - - Extracts query params from the end of an array - **/ - - function extractQueryParams(array) { - var len = (array && array.length), head, queryParams; - - if(len && len > 0 && array[len - 1] && array[len - 1].hasOwnProperty('queryParams')) { - queryParams = array[len - 1].queryParams; - head = slice.call(array, 0, len - 1); - return [head, queryParams]; - } else { - return [array, null]; - } - } - /** @private Creates, begins, and returns a Transition. */ - function performTransition(router, recogHandlers, providedModelsArray, params, queryParams, data) { + function performTransition(router, recogHandlers, providedModelsArray, params, data) { - var matchPointResults = getMatchPoint(router, recogHandlers, providedModelsArray, params, queryParams), + var matchPointResults = getMatchPoint(router, recogHandlers, providedModelsArray, params), targetName = recogHandlers[recogHandlers.length - 1].handler, wasTransitioning = false, currentHandlerInfos = router.currentHandlerInfos; // Check if there's already a transition underway. if (router.activeTransition) { - if (transitionsIdentical(router.activeTransition, targetName, providedModelsArray, queryParams)) { + if (transitionsIdentical(router.activeTransition, targetName, providedModelsArray)) { return router.activeTransition; } router.activeTransition.abort(); @@ -30150,7 +30075,6 @@ define("router", transition.providedModelsArray = providedModelsArray; transition.params = matchPointResults.params; transition.data = data || {}; - transition.queryParams = queryParams; router.activeTransition = transition; var handlerInfos = generateHandlerInfos(router, recogHandlers); @@ -30217,16 +30141,11 @@ define("router", var handlerObj = recogHandlers[i], isDynamic = handlerObj.isDynamic || (handlerObj.names && handlerObj.names.length); - - var handlerInfo = { + handlerInfos.push({ isDynamic: !!isDynamic, name: handlerObj.handler, handler: router.getHandler(handlerObj.handler) - }; - if(handlerObj.queryParams) { - handlerInfo.queryParams = handlerObj.queryParams; - } - handlerInfos.push(handlerInfo); + }); } return handlerInfos; } @@ -30234,7 +30153,7 @@ define("router", /** @private */ - function transitionsIdentical(oldTransition, targetName, providedModelsArray, queryParams) { + function transitionsIdentical(oldTransition, targetName, providedModelsArray) { if (oldTransition.targetName !== targetName) { return false; } @@ -30244,11 +30163,6 @@ define("router", for (var i = 0, len = oldModels.length; i < len; ++i) { if (oldModels[i] !== providedModelsArray[i]) { return false; } } - - if(!queryParamsEqual(oldTransition.queryParams, queryParams)) { - return false; - } - return true; } @@ -30262,12 +30176,11 @@ define("router", var router = transition.router, seq = transition.sequence, - handlerName = handlerInfos[handlerInfos.length - 1].name, - i; + handlerName = handlerInfos[handlerInfos.length - 1].name; // Collect params for URL. var objects = [], providedModels = transition.providedModelsArray.slice(); - for (i = handlerInfos.length - 1; i>=0; --i) { + for (var i = handlerInfos.length - 1; i>=0; --i) { var handlerInfo = handlerInfos[i]; if (handlerInfo.isDynamic) { var providedModel = providedModels.pop(); @@ -30275,18 +30188,10 @@ define("router", } } - var newQueryParams = {}; - for (i = handlerInfos.length - 1; i>=0; --i) { - merge(newQueryParams, handlerInfos[i].queryParams); - } - router.currentQueryParams = newQueryParams; - - - var params = paramsForHandler(router, handlerName, objects, transition.queryParams); + var params = paramsForHandler(router, handlerName, objects); router.currentParams = params; - var urlMethod = transition.urlMethod; if (urlMethod) { var url = router.recognizer.generate(handlerName, params); @@ -30377,20 +30282,13 @@ define("router", log(router, seq, handlerName + ": calling beforeModel hook"); - var args; - - if (handlerInfo.queryParams) { - args = [handlerInfo.queryParams, transition]; - } else { - args = [transition]; - } - - var p = handler.beforeModel && handler.beforeModel.apply(handler, args); + var p = handler.beforeModel && handler.beforeModel(transition); return (p instanceof Transition) ? null : p; } function model() { log(router, seq, handlerName + ": resolving model"); + var p = getModel(handlerInfo, transition, handlerParams[handlerName], index >= matchPoint); return (p instanceof Transition) ? null : p; } @@ -30405,15 +30303,7 @@ define("router", transition.resolvedModels[handlerInfo.name] = context; - var args; - - if (handlerInfo.queryParams) { - args = [context, handlerInfo.queryParams, transition]; - } else { - args = [context, transition]; - } - - var p = handler.afterModel && handler.afterModel.apply(handler, args); + var p = handler.afterModel && handler.afterModel(context, transition); return (p instanceof Transition) ? null : p; } @@ -30444,8 +30334,9 @@ define("router", or use one of the models provided to `transitionTo`. */ function getModel(handlerInfo, transition, handlerParams, needsUpdate) { + var handler = handlerInfo.handler, - handlerName = handlerInfo.name, args; + handlerName = handlerInfo.name; if (!needsUpdate && handler.hasOwnProperty('context')) { return handler.context; @@ -30456,13 +30347,7 @@ define("router", return typeof providedModel === 'function' ? providedModel() : providedModel; } - if (handlerInfo.queryParams) { - args = [handlerParams || {}, handlerInfo.queryParams, transition]; - } else { - args = [handlerParams || {}, transition, handlerInfo.queryParams]; - } - - return handler.model && handler.model.apply(handler, args); + return handler.model && handler.model(handlerParams || {}, transition); } /** @@ -30495,12 +30380,10 @@ define("router", // Normalize blank transitions to root URL transitions. var name = args[0] || '/'; - if(args.length === 1 && args[0].hasOwnProperty('queryParams')) { - return createQueryParamTransition(router, args[0]); - } else if (name.charAt(0) === '/') { + if (name.charAt(0) === '/') { return createURLTransition(router, name); } else { - return createNamedTransition(router, slice.call(args)); + return createNamedTransition(router, args); } } @@ -30578,17 +30461,17 @@ DSL.prototype = { if (callback) { var dsl = new DSL(name); callback.call(dsl); - this.push(options.path, name, dsl.generate(), options.queryParams); + this.push(options.path, name, dsl.generate()); } else { - this.push(options.path, name, null, options.queryParams); + this.push(options.path, name); } }, - push: function(url, name, callback, queryParams) { + push: function(url, name, callback) { var parts = name.split('.'); if (url === "" || url === "/" || parts[parts.length-1] === "index") { this.explicitIndex = true; } - this.matches.push([url, name, callback, queryParams]); + this.matches.push([url, name, callback]); }, route: function(name, options) { @@ -30604,7 +30487,7 @@ DSL.prototype = { name = this.parent + "." + name; } - this.push(options.path, name, null, options.queryParams); + this.push(options.path, name); }, generate: function() { @@ -30617,12 +30500,7 @@ DSL.prototype = { return function(match) { for (var i=0, l=dslMatches.length; i<l; i++) { var dslMatch = dslMatches[i]; - var matchObj = match(dslMatch[0]).to(dslMatch[1], dslMatch[2]); - if (Ember.FEATURES.isEnabled("query-params")) { - if(dslMatch[3]) { - matchObj.withQueryParams.apply(matchObj, dslMatch[3]); - } - } + match(dslMatch[0]).to(dslMatch[1], dslMatch[2]); } }; } @@ -30927,16 +30805,12 @@ Ember.Router = Ember.Object.extend({ args = [].slice.call(args); args[0] = args[0] || '/'; - var passedName = args[0], name, self = this, - isQueryParamsOnly = false; + var passedName = args[0], name, self = this; - if (Ember.FEATURES.isEnabled("query-params")) { - isQueryParamsOnly = (args.length === 1 && args[0].hasOwnProperty('queryParams')); - } - - if (!isQueryParamsOnly && passedName.charAt(0) === '/') { + if (passedName.charAt(0) === '/') { name = passedName; - } else if (!isQueryParamsOnly) { + } else { + if (!this.router.hasRoute(passedName)) { name = args[0] = passedName + '.index'; } else { @@ -31004,7 +30878,7 @@ function triggerEvent(handlerInfos, ignoreFailure, args) { if (!handlerInfos) { if (ignoreFailure) { return; } - throw new Error("Could not trigger event '" + name + "'. There are no active handlers"); + throw new Ember.Error("Could not trigger event '" + name + "'. There are no active handlers"); } var eventWasHandled = false; @@ -31030,7 +30904,7 @@ function triggerEvent(handlerInfos, ignoreFailure, args) { } if (!eventWasHandled && !ignoreFailure) { - throw new Error("Nothing handled the event '" + name + "'."); + throw new Ember.Error("Nothing handled the event '" + name + "'."); } } @@ -31372,11 +31246,13 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { activate: Ember.K, /** - Transition into another route. Optionally supply a model for the - route in question. The model will be serialized into the URL - using the `serialize` hook. + Transition into another route. Optionally supply model(s) for the + route in question. If multiple models are supplied they will be applied + last to first recursively up the resource tree (see Multiple Models Example + below). The model(s) will be serialized into the URL using the appropriate + route's `serialize` hook. See also 'replaceWith'. - Example + Simple Transition Example ```javascript App.Router.map(function() { @@ -31397,9 +31273,31 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { }); ``` + Multiple Models Example + + ```javascript + App.Router.map(function() { + this.route("index"); + this.resource('breakfast', {path:':breakfastId'}, function(){ + this.resource('cereal', {path: ':cerealId'}); + }); + }); + + App.IndexRoute = Ember.Route.extend({ + actions: { + moveToChocolateCereal: function(){ + var cereal = { cerealId: "ChocolateYumminess"}, + breakfast = {breakfastId: "CerealAndMilk"}; + + this.transitionTo('cereal', breakfast, cereal); + } + } + }); + @method transitionTo @param {String} name the name of the route - @param {...Object} models + @param {...Object} models the model(s) to be used while transitioning + to the route. */ transitionTo: function(name, context) { var router = this.router; @@ -31407,8 +31305,10 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { }, /** - Transition into another route while replacing the current URL if - possible. Identical to `transitionTo` in all other respects. + Transition into another route while replacing the current URL, if possible. + This will replace the current history entry instead of adding a new one. + Beside that, it is identical to `transitionTo` in all other respects. See + 'transitionTo' for additional information regarding multiple models. Example @@ -31429,7 +31329,8 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @method replaceWith @param {String} name the name of the route - @param {...Object} models + @param {...Object} models the model(s) to be used while transitioning + to the route. */ replaceWith: function() { var router = this.router; @@ -31481,7 +31382,7 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @method setup */ - setup: function(context, queryParams) { + setup: function(context) { var controllerName = this.controllerName || this.routeName, controller = this.controllerFor(controllerName, true); if (!controller) { @@ -31492,37 +31393,31 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { // referenced in action handlers this.controller = controller; - var args = [controller, context]; - - if (Ember.FEATURES.isEnabled("query-params")) { - args.push(queryParams); - } - if (this.setupControllers) { Ember.deprecate("Ember.Route.setupControllers is deprecated. Please use Ember.Route.setupController(controller, model) instead."); this.setupControllers(controller, context); } else { - this.setupController.apply(this, args); + this.setupController(controller, context); } if (this.renderTemplates) { Ember.deprecate("Ember.Route.renderTemplates is deprecated. Please use Ember.Route.renderTemplate(controller, model) instead."); this.renderTemplates(context); } else { - this.renderTemplate.apply(this, args); + this.renderTemplate(controller, context); } }, /** - @deprecated - A hook you can implement to optionally redirect to another route. If you call `this.transitionTo` from inside of this hook, this route will not be entered in favor of the other hook. - This hook is deprecated in favor of using the `afterModel` hook - for performing redirects after the model has resolved. + Note that this hook is called by the default implementation of + `afterModel`, so if you override `afterModel`, you must either + explicitly call `redirect` or just put your redirecting + `this.transitionTo()` call within `afterModel`. @method redirect @param {Object} model the model for this route @@ -31600,7 +31495,6 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @method beforeModel @param {Transition} transition - @param {Object} queryParams the active query params for this route @return {Promise} if the value returned from this hook is a promise, the transition will pause until the transition resolves. Otherwise, non-promise return values are not @@ -31634,13 +31528,12 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @param {Object} resolvedModel the value returned from `model`, or its resolved value if it was a promise @param {Transition} transition - @param {Object} queryParams the active query params for this handler @return {Promise} if the value returned from this hook is a promise, the transition will pause until the transition resolves. Otherwise, non-promise return values are not utilized in any way. */ - afterModel: function(resolvedModel, transition, queryParams) { + afterModel: function(resolvedModel, transition) { this.redirect(resolvedModel, transition); }, @@ -31700,7 +31593,6 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @method model @param {Object} params the parameters extracted from the URL @param {Transition} transition - @param {Object} queryParams the query params for this route @return {Object|Promise} the model for this route. If a promise is returned, the transition will pause until the promise resolves, and the resolved value of the promise @@ -32530,40 +32422,19 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { Ember.Handlebars.normalizePath(templateContext, path, helperParameters.options.data); this.registerObserver(normalizedPath.root, normalizedPath.path, this, this._paramsChanged); } - - - if (Ember.FEATURES.isEnabled("query-params")) { - var queryParams = get(this, '_potentialQueryParams') || []; - - for(i=0; i < queryParams.length; i++) { - this.registerObserver(this, queryParams[i], this, this._queryParamsChanged); - } - } }, /** @private This method is invoked by observers installed during `init` that fire - whenever the params change + whenever the helpers @method _paramsChanged */ _paramsChanged: function() { this.notifyPropertyChange('resolvedParams'); }, - - /** - @private - - This method is invoked by observers installed during `init` that fire - whenever the query params change - */ - _queryParamsChanged: function (object, path) { - this.notifyPropertyChange('queryParams'); - }, - - /** @private @@ -32699,6 +32570,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { @return {Array} An array with the route name and any dynamic segments */ routeArgs: Ember.computed(function() { + var resolvedParams = get(this, 'resolvedParams').slice(0), router = get(this, 'router'), namedRoute = resolvedParams[0]; @@ -32718,44 +32590,9 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { } } - if (Ember.FEATURES.isEnabled("query-params")) { - var queryParams = get(this, 'queryParams'); - - if (queryParams || queryParams === false) { resolvedParams.push({queryParams: queryParams}); } - } - return resolvedParams; - }).property('resolvedParams', 'queryParams', 'router.url'), - - - _potentialQueryParams: Ember.computed(function () { - var namedRoute = get(this, 'resolvedParams')[0]; - if (!namedRoute) { return null; } - var router = get(this, 'router'); - - namedRoute = fullRouteName(router, namedRoute); - - return router.router.queryParamsForHandler(namedRoute); }).property('resolvedParams'), - queryParams: Ember.computed(function () { - var self = this, - queryParams = null, - allowedQueryParams = get(this, '_potentialQueryParams'); - - if (!allowedQueryParams) { return null; } - allowedQueryParams.forEach(function (param) { - var value = get(self, param); - if (typeof value !== 'undefined') { - queryParams = queryParams || {}; - queryParams[param] = value; - } - }); - - - return queryParams; - }).property('_potentialQueryParams.[]'), - /** Sets the element's `href` attribute to the url for the `LinkView`'s targeted route. @@ -32766,7 +32603,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { @property href **/ href: Ember.computed(function() { - if (get(this, 'tagName') !== 'a') { return false; } + if (get(this, 'tagName') !== 'a') { return; } var router = get(this, 'router'), routeArgs = get(this, 'routeArgs'); @@ -32826,7 +32663,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { To override this option for your entire application, see "Overriding Application-wide Defaults". - ### Disabling the `link-to` heper + ### Disabling the `link-to` helper By default `{{link-to}}` is enabled. any passed value to `disabled` helper property will disable the `link-to` helper. @@ -33411,16 +33248,16 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { /** The `{{action}}` helper registers an HTML element within a template for DOM - event handling and forwards that interaction to the view's controller + event handling and forwards that interaction to the templates's controller or supplied `target` option (see 'Specifying a Target'). - If the view's controller does not implement the event, the event is sent + If the controller does not implement the event, the event is sent to the current route, and it bubbles up the route hierarchy from there. User interaction with that element will invoke the supplied action name on the appropriate target. - Given the following Handlebars template on the page + Given the following application Handlebars template on the page ```handlebars <div {{action 'anActionName'}}> @@ -33431,17 +33268,13 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { And application code ```javascript - AController = Ember.Controller.extend({ - anActionName: function() {} + App.ApplicationController = Ember.Controller.extend({ + actions: { + anActionName: function() { + + } + } }); - - AView = Ember.View.extend({ - controller: AController.create(), - templateName: 'a-template' - }); - - aView = AView.create(); - aView.appendTo('body'); ``` Will result in the following rendered HTML @@ -33454,8 +33287,8 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { </div> ``` - Clicking "click me" will trigger the `anActionName` method of the - `AController`. In this case, no additional parameters will be passed. + Clicking "click me" will trigger the `anActionName` action of the + `App.ApplicationController`. In this case, no additional parameters will be passed. If you provide additional parameters to the helper: @@ -33488,11 +33321,9 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { supply an `on` option to the helper to specify a different DOM event name: ```handlebars - <script type="text/x-handlebars" data-template-name='a-template'> - <div {{action 'anActionName' on="doubleClick"}}> - click me - </div> - </script> + <div {{action "anActionName" on="doubleClick"}}> + click me + </div> ``` See `Ember.View` 'Responding to Browser Events' for a list of @@ -33510,11 +33341,9 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { keys. You can supply an `allowedKeys` option to specify which keys should not be ignored. ```handlebars - <script type="text/x-handlebars" data-template-name='a-template'> - <div {{action 'anActionName' allowedKeys="alt"}}> - click me - </div> - </script> + <div {{action "anActionName" allowedKeys="alt"}}> + click me + </div> ``` This way the `{{action}}` will fire when clicking with the alt key pressed down. @@ -33522,11 +33351,9 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { Alternatively, supply "any" to the `allowedKeys` option to accept any combination of modifier keys. ```handlebars - <script type="text/x-handlebars" data-template-name='a-template'> - <div {{action 'anActionName' allowedKeys="any"}}> - click me with any key pressed - </div> - </script> + <div {{action "anActionName" allowedKeys="any"}}> + click me with any key pressed + </div> ``` ### Specifying a Target @@ -33542,43 +33369,21 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { to an object, accessible in the current context: ```handlebars - <script type="text/x-handlebars" data-template-name='a-template'> - <div {{action 'anActionName' target="MyApplication.someObject"}}> - click me - </div> - </script> + {{! the application template }} + <div {{action "anActionName" target=view}}> + click me + </div> ``` - Clicking "click me" in the rendered HTML of the above template will trigger - the `anActionName` method of the object at `MyApplication.someObject`. - - If an action's target does not implement a method that matches the supplied - action name an error will be thrown. - - ```handlebars - <script type="text/x-handlebars" data-template-name='a-template'> - <div {{action 'aMethodNameThatIsMissing'}}> - click me - </div> - </script> - ``` - - With the following application code - ```javascript - AView = Ember.View.extend({ - templateName; 'a-template', - // note: no method 'aMethodNameThatIsMissing' - anActionName: function(event) {} + App.ApplicationView = Ember.View.extend({ + actions: { + anActionName: function(){} + } }); - aView = AView.create(); - aView.appendTo('body'); ``` - Will throw `Uncaught TypeError: Cannot call method 'call' of undefined` when - "click me" is clicked. - ### Additional Parameters You may specify additional parameters to the `{{action}}` helper. These @@ -33586,17 +33391,15 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { implementing the action. ```handlebars - <script type="text/x-handlebars" data-template-name='a-template'> - {{#each person in people}} - <div {{action 'edit' person}}> - click me - </div> - {{/each}} - </script> + {{#each person in people}} + <div {{action "edit" person}}> + click me + </div> + {{/each}} ``` - Clicking "click me" will trigger the `edit` method on the current view's - controller with the current person as a parameter. + Clicking "click me" will trigger the `edit` method on the current controller + with the value of `person` as a parameter. @method action @for Ember.Handlebars.helpers @@ -33781,8 +33584,23 @@ Ember.ControllerMixin.reopen({ aController.transitionToRoute('blogPost', aPost); ``` + Multiple models will be applied last to first recursively up the + resource tree. + + ```javascript + + this.resource('blogPost', {path:':blogPostId'}, function(){ + this.resource('blogComment', {path: ':blogCommentId'}); + }); + + aController.transitionToRoute('blogComment', aPost, aComment); + ``` + + See also 'replaceRoute'. + @param {String} name the name of the route - @param {...Object} models the + @param {...Object} models the model(s) to be used while transitioning + to the route. @for Ember.ControllerMixin @method transitionToRoute */ @@ -33804,8 +33622,9 @@ Ember.ControllerMixin.reopen({ }, /** - Alternative to `transitionToRoute`. Transition the application into another route. The route may - be either a single route or route path: + Transition into another route while replacing the current URL, if possible. + This will replace the current history entry instead of adding a new one. + Beside that, it is identical to `transitionToRoute` in all other respects. ```javascript aController.replaceRoute('blogPosts'); @@ -33820,8 +33639,21 @@ Ember.ControllerMixin.reopen({ aController.replaceRoute('blogPost', aPost); ``` + Multiple models will be applied last to first recursively up the + resource tree. + + ```javascript + + this.resource('blogPost', {path:':blogPostId'}, function(){ + this.resource('blogComment', {path: ':blogCommentId'}); + }); + + aController.replaceRoute('blogComment', aPost, aComment); + ``` + @param {String} name the name of the route - @param {...Object} models the + @param {...Object} models the model(s) to be used while transitioning + to the route. @for Ember.ControllerMixin @method replaceRoute */ @@ -34624,7 +34456,7 @@ DAG.prototype.addEdge = function(fromName, toName) { } function checkCycle(vertex, path) { if (vertex.name === toName) { - throw new Error("cycle detected: " + toName + " <- " + path.join(" <- ")); + throw new Ember.Error("cycle detected: " + toName + " <- " + path.join(" <- ")); } } visit(from, checkCycle); @@ -35065,16 +34897,15 @@ DeprecatedContainer.prototype = { example, the `keypress` event causes the `keyPress` method on the view to be called, the `dblclick` event causes `doubleClick` to be called, and so on. - If there is a browser event that Ember does not listen for by default, you - can specify custom events and their corresponding view method names by - setting the application's `customEvents` property: + If there is a bubbling browser event that Ember does not listen for by + default, you can specify custom events and their corresponding view method + names by setting the application's `customEvents` property: ```javascript App = Ember.Application.create({ customEvents: { - // add support for the loadedmetadata media - // player event - 'loadedmetadata': "loadedMetadata" + // add support for the paste event + 'paste: "paste" } }); ``` @@ -35187,7 +35018,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin `keyup`, and delegates them to your application's `Ember.View` instances. - If you would like additional events to be delegated to your + If you would like additional bubbling events to be delegated to your views, set your `Ember.Application`'s `customEvents` property to a hash containing the DOM event name as the key and the corresponding view method name as the value. For example: @@ -35195,9 +35026,8 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin ```javascript App = Ember.Application.create({ customEvents: { - // add support for the loadedmetadata media - // player event - 'loadedmetadata': "loadedMetadata" + // add support for the paste event + 'paste: "paste" } }); ``` @@ -35438,7 +35268,9 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin if (this.isDestroyed) { return; } // At this point, the App.Router must already be assigned - this.register('router:main', this.Router); + if (this.Router) { + this.register('router:main', this.Router); + } this.runInitializers(); Ember.runLoadHooks('application', this); @@ -35548,10 +35380,10 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin container = this.__container__, graph = new Ember.DAG(), namespace = this, - i, initializer; + name, initializer; - for (i=0; i<initializers.length; i++) { - initializer = initializers[i]; + for (name in initializers) { + initializer = initializers[name]; graph.addEdges(initializer.name, initializer.initialize, initializer.before, initializer.after); } @@ -35656,16 +35488,23 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin }); Ember.Application.reopenClass({ - concatenatedProperties: ['initializers'], - initializers: Ember.A(), + initializers: {}, initializer: function(initializer) { - var initializers = get(this, 'initializers'); + // If this is the first initializer being added to a subclass, we are going to reopen the class + // to make sure we have a new `initializers` object, which extends from the parent class' using + // prototypal inheritance. Without this, attempting to add initializers to the subclass would + // pollute the parent class as well as other subclasses. + if (this.superclass.initializers !== undefined && this.superclass.initializers === this.initializers) { + this.reopenClass({ + initializers: Ember.create(this.initializers) + }); + } - Ember.assert("The initializer '" + initializer.name + "' has already been registered", !initializers.findBy('name', initializers.name)); - Ember.assert("An injection cannot be registered with both a before and an after", !(initializer.before && initializer.after)); - Ember.assert("An injection cannot be registered without an injection function", Ember.canInvoke(initializer, 'initialize')); + Ember.assert("The initializer '" + initializer.name + "' has already been registered", !this.initializers[initializer.name]); + Ember.assert("An initializer cannot be registered with both a before and an after", !(initializer.before && initializer.after)); + Ember.assert("An initializer cannot be registered without an initialize function", Ember.canInvoke(initializer, 'initialize')); - initializers.push(initializer); + this.initializers[initializer.name] = initializer; }, /** @@ -35854,6 +35693,8 @@ Ember.ControllerMixin.reopen({ length = get(needs, 'length'); if (length > 0) { + Ember.assert(' `' + Ember.inspect(this) + ' specifies `needs`, but does not have a container. Please ensure this controller was instantiated with a container.', this.container); + verifyNeedsDependencies(this, this.container, needs); // if needs then initialize controllers proxy @@ -35863,6 +35704,11 @@ Ember.ControllerMixin.reopen({ this._super.apply(this, arguments); }, + /** + @method controllerFor + @see {Ember.Route#controllerFor} + @deprecated Use `needs` instead + */ controllerFor: function(controllerName) { Ember.deprecate("Controller#controllerFor is deprecated, please use Controller#needs instead"); return Ember.controllerFor(get(this, 'container'), controllerName); @@ -36654,7 +36500,7 @@ function testCheckboxClick(handler) { .css({ position: 'absolute', left: '-1000px', top: '-1000px' }) .appendTo('body') .on('click', handler) - .trigger('click') + .click() .remove(); } @@ -36793,6 +36639,7 @@ Test.onInjectHelpers(function() { }); Ember.$(document).ajaxStop(function() { + Ember.assert("An ajaxStop event which would cause the number of pending AJAX requests to be negative has been triggered. This is most likely caused by AJAX events that were started before calling `injectTestHelpers()`.", Test.pendingAjaxRequests !== 0); Test.pendingAjaxRequests--; }); }); @@ -36811,7 +36658,16 @@ function click(app, selector, context) { if ($el.is(':input')) { var type = $el.prop('type'); if (type !== 'checkbox' && type !== 'radio' && type !== 'hidden') { - Ember.run($el, 'focus'); + Ember.run($el, function(){ + // Firefox does not trigger the `focusin` event if the window + // does not have focus. If the document doesn't have focus just + // use trigger('focusin') instead. + if (!document.hasFocus || document.hasFocus()) { + this.focus(); + } else { + this.trigger('focusin'); + } + }); } } @@ -36850,7 +36706,7 @@ function fillIn(app, selector, context, text) { function findWithAssert(app, selector, context) { var $el = find(app, selector, context); if ($el.length === 0) { - throw new Error("Element " + selector + " not found."); + throw new Ember.Error("Element " + selector + " not found."); } return $el; } @@ -37106,7 +36962,7 @@ Ember function throwWithMessage(msg) { return function() { - throw new Error(msg); + throw new Ember.Error(msg); }; } diff --git a/vendor/assets/javascripts/production/ember.js b/vendor/assets/javascripts/production/ember.js index 209ece8f016..de9392edf35 100644 --- a/vendor/assets/javascripts/production/ember.js +++ b/vendor/assets/javascripts/production/ember.js @@ -1,17 +1,3 @@ -// ========================================================================== -// Project: Ember - JavaScript Application Framework -// Copyright: ©2011-2013 Tilde Inc. and contributors -// Portions ©2006-2011 Strobe Inc. -// Portions ©2008-2011 Apple Inc. All rights reserved. -// License: Licensed under MIT license -// See https://raw.github.com/emberjs/ember.js/master/LICENSE -// ========================================================================== - - -// Version: v1.0.0-rc.6-733-gd034d11 -// Last commit: d034d11 (2013-09-16 00:44:21 -0700) - - (function() { var define, requireModule; @@ -75,7 +61,7 @@ var define, requireModule; @class Ember @static - @version 1.0.0 + @version 1.1.2 */ if ('undefined' === typeof Ember) { @@ -102,10 +88,10 @@ Ember.toString = function() { return "Ember"; }; /** @property VERSION @type String - @default '1.0.0' + @default '1.1.2' @final */ -Ember.VERSION = '1.0.0'; +Ember.VERSION = '1.1.2'; /** Standard environmental variables. You can define these in a global `ENV` @@ -1865,6 +1851,7 @@ Ember.getWithDefault = function(root, key, defaultValue) { Ember.get = get; +Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get); })(); @@ -2701,6 +2688,7 @@ function setPath(root, path, value, tolerant) { } Ember.set = set; +Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now supports paths', Ember.set); /** Error-tolerant form of `Ember.set`. Will not blow up if any part of the @@ -2718,6 +2706,7 @@ Ember.set = set; Ember.trySet = function(root, path, value) { return set(root, path, value, true); }; +Ember.trySetPath = Ember.deprecateFunc('trySetPath has been renamed to trySet', Ember.trySet); })(); @@ -4467,14 +4456,14 @@ function registerComputedWithProperties(name, macro) { property is null, an empty string, empty array, or empty function. Note: When using `Ember.computed.empty` to watch an array make sure to - use the `array.length` syntax so the computed can subscribe to transitions + use the `array.[]` syntax so the computed can subscribe to transitions from empty to non-empty states. Example ```javascript var ToDoList = Ember.Object.extend({ - done: Ember.computed.empty('todos.length') + done: Ember.computed.empty('todos.[]') // detect array changes }); var todoList = ToDoList.create({todos: ['Unit Test', 'Documentation', 'Release']}); todoList.get('done'); // false @@ -4594,7 +4583,7 @@ registerComputed('not', function(dependentKey) { @method computed.bool @for Ember @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which convert + @return {Ember.ComputedProperty} computed property which converts to boolean the original value for property */ registerComputed('bool', function(dependentKey) { @@ -4812,7 +4801,7 @@ registerComputedWithProperties('and', function(properties) { }); /** - A computed property that which performs a logical `or` on the + A computed property which performs a logical `or` on the original values for the provided dependent properties. Example @@ -4979,7 +4968,7 @@ Ember.computed.alias = function(dependentKey) { @method computed.oneWay @for Ember @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which creates an + @return {Ember.ComputedProperty} computed property which creates a one way computed property to the original value for property. */ Ember.computed.oneWay = function(dependentKey) { @@ -4991,7 +4980,7 @@ Ember.computed.oneWay = function(dependentKey) { /** A computed property that acts like a standard getter and setter, - but retruns the value at the provided `defaultPath` if the + but returns the value at the provided `defaultPath` if the property itself has not been set to a value Example @@ -5359,7 +5348,12 @@ define("backburner", debouncees = [], timers = [], autorun, laterTimer, laterTimerExpiresAt, - global = this; + global = this, + NUMBER = /\d+/; + + function isCoercableNumber(number) { + return typeof number === 'number' || NUMBER.test(number); + } function Backburner(queueNames, options) { this.queueNames = queueNames; @@ -5474,32 +5468,60 @@ define("backburner", }, setTimeout: function() { - var self = this, - wait = pop.call(arguments), - target = arguments[0], - method = arguments[1], - executeAt = (+new Date()) + wait; + var args = slice.call(arguments); + var length = args.length; + var method, wait, target; + var self = this; + var methodOrTarget, methodOrWait, methodOrArgs; - if (!method) { - method = target; - target = null; + if (length === 0) { + return; + } else if (length === 1) { + method = args.shift(); + wait = 0; + } else if (length === 2) { + methodOrTarget = args[0]; + methodOrWait = args[1]; + + if (typeof methodOrWait === 'function' || typeof methodOrTarget[methodOrWait] === 'function') { + target = args.shift(); + method = args.shift(); + wait = 0; + } else if (isCoercableNumber(methodOrWait)) { + method = args.shift(); + wait = args.shift(); + } else { + method = args.shift(); + wait = 0; + } + } else { + var last = args[args.length - 1]; + + if (isCoercableNumber(last)) { + wait = args.pop(); + } + + methodOrTarget = args[0]; + methodOrArgs = args[1]; + + if (typeof methodOrArgs === 'function' || (typeof methodOrArgs === 'string' && + methodOrTarget !== null && + methodOrArgs in methodOrTarget)) { + target = args.shift(); + method = args.shift(); + } else { + method = args.shift(); + } } + var executeAt = (+new Date()) + parseInt(wait, 10); + if (typeof method === 'string') { method = target[method]; } - var fn, args; - if (arguments.length > 2) { - args = slice.call(arguments, 2); - - fn = function() { - method.apply(target, args); - }; - } else { - fn = function() { - method.call(target); - }; + function fn() { + method.apply(target, args); } // find position to insert - TODO: binary search @@ -5700,6 +5722,7 @@ define("backburner", __exports__.Backburner = Backburner; }); + })(); @@ -7316,11 +7339,10 @@ Alias.prototype = new Ember.Descriptor(); @deprecated Use `Ember.aliasMethod` or `Ember.computed.alias` instead */ Ember.alias = function(methodName) { + return new Alias(methodName); }; -Ember.alias = Ember.deprecateFunc("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead.", Ember.alias); - /** Makes a method available via an additional name. @@ -7464,6 +7486,8 @@ Ember.beforeObserver = function(func) { (function() { // Provides a way to register library versions with ember. +var forEach = Ember.EnumerableUtils.forEach, + indexOf = Ember.EnumerableUtils.indexOf; Ember.libraries = function() { var libraries = []; @@ -7491,14 +7515,15 @@ Ember.libraries = function() { libraries.deRegister = function(name) { var lib = getLibrary(name); - if (lib) libraries.splice(libraries.indexOf(lib), 1); + if (lib) libraries.splice(indexOf(libraries, lib), 1); }; libraries.each = function (callback) { - libraries.forEach(function(lib) { + forEach(libraries, function(lib) { callback(lib.name, lib.version); }); }; + return libraries; }(); @@ -8418,25 +8443,25 @@ define("container", ``` @method register - @param {String} type - @param {String} name + @param {String} fullName @param {Function} factory @param {Object} options */ - register: function(type, name, factory, options) { - var fullName; + register: function(fullName, factory, options) { + if (fullName.indexOf(':') === -1) { + throw new TypeError("malformed fullName, expected: `type:name` got: " + fullName + ""); + } - if (type.indexOf(':') !== -1) { - options = factory; - factory = name; - fullName = type; - } else { - - fullName = type + ":" + name; + if (factory === undefined) { + throw new TypeError('Attempting to register an unknown factory: `' + fullName + '`'); } var normalizedName = this.normalize(fullName); + if (this.cache.has(normalizedName)) { + throw new Error('Cannot re-register: `' + fullName +'`, as it has already been looked up.'); + } + this.registry.set(normalizedName, factory); this._options.set(normalizedName, options || {}); }, @@ -9327,16 +9352,39 @@ Ember.ORDER_DEFINITION = Ember.ENV.ORDER_DEFINITION || [ Ember.keys = Object.keys; if (!Ember.keys || Ember.create.isSimulated) { - Ember.keys = function(obj) { - var ret = []; - for(var key in obj) { - // Prevents browsers that don't respect non-enumerability from - // copying internal Ember properties - if (key.substring(0,2) === '__') continue; - if (key === '_super') continue; + var prototypeProperties = [ + 'constructor', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'valueOf', + 'toLocaleString', + 'toString' + ], + pushPropertyName = function(obj, array, key) { + // Prevents browsers that don't respect non-enumerability from + // copying internal Ember properties + if (key.substring(0,2) === '__') return; + if (key === '_super') return; + if (indexOf(array, key) >= 0) return; + if (!obj.hasOwnProperty(key)) return; - if (obj.hasOwnProperty(key)) { ret.push(key); } + array.push(key); + }; + + Ember.keys = function(obj) { + var ret = [], key; + for (key in obj) { + pushPropertyName(obj, ret, key); } + + // IE8 doesn't enumerate property that named the same as prototype properties. + for (var i = 0, l = prototypeProperties.length; i < l; i++) { + key = prototypeProperties[i]; + + pushPropertyName(obj, ret, key); + } + return ret; }; } @@ -10772,10 +10820,12 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot (function() { -var e_get = Ember.get, +var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, metaFor = Ember.meta, + propertyWillChange = Ember.propertyWillChange, + propertyDidChange = Ember.propertyDidChange, addBeforeObserver = Ember.addBeforeObserver, removeBeforeObserver = Ember.removeBeforeObserver, addObserver = Ember.addObserver, @@ -10789,16 +10839,6 @@ var e_get = Ember.get, eachPropertyPattern = /^(.*)\.@each\.(.*)/, doubleEachPropertyPattern = /(.*\.@each){2,}/; -function get(obj, key) { - if (Ember.FEATURES.isEnabled('reduceComputedSelf')) { - if (key === '@self') { - return obj; - } - } - - return e_get(obj, key); -} - /* Tracks changes to dependent arrays, as well as to properties of items in dependent arrays. @@ -10846,7 +10886,7 @@ function ItemPropertyObserverContext (dependentArray, index, trackedArray) { DependentArraysObserver.prototype = { setValue: function (newValue) { - this.instanceMeta.setValue(newValue); + this.instanceMeta.setValue(newValue, true); }, getValue: function () { return this.instanceMeta.getValue(); @@ -10947,14 +10987,14 @@ DependentArraysObserver.prototype = { this.trackedArraysByGuid[dependentKey] = new Ember.TrackedArray(observerContexts); }, - addTransformation: function (dependentKey, index, newItems) { + trackAdd: function (dependentKey, index, newItems) { var trackedArray = this.trackedArraysByGuid[dependentKey]; if (trackedArray) { trackedArray.addItems(index, newItems); } }, - removeTransformation: function (dependentKey, index, removedCount) { + trackRemove: function (dependentKey, index, removedCount) { var trackedArray = this.trackedArraysByGuid[dependentKey]; if (trackedArray) { @@ -10995,7 +11035,7 @@ DependentArraysObserver.prototype = { sliceIndex, observerContexts; - observerContexts = this.removeTransformation(dependentKey, index, removedCount); + observerContexts = this.trackRemove(dependentKey, index, removedCount); function removeObservers(propertyKey) { observerContexts[sliceIndex].destroyed = true; @@ -11040,7 +11080,7 @@ DependentArraysObserver.prototype = { this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); }, this); - this.addTransformation(dependentKey, index, observerContexts); + this.trackAdd(dependentKey, index, observerContexts); }, itemPropertyWillChange: function (obj, keyName, array, observerContext) { @@ -11059,7 +11099,7 @@ DependentArraysObserver.prototype = { }, itemPropertyDidChange: function(obj, keyName, array, observerContext) { - Ember.run.once(this, 'flushChanges'); + this.flushChanges(); }, flushChanges: function() { @@ -11141,11 +11181,21 @@ ReduceComputedPropertyInstanceMeta.prototype = { } }, - setValue: function(newValue) { + setValue: function(newValue, triggerObservers) { // This lets sugars force a recomputation, handy for very simple // implementations of eg max. if (newValue !== undefined) { + var fireObservers = triggerObservers && (newValue !== this.cache[this.propertyName]); + + if (fireObservers) { + propertyWillChange(this.context, this.propertyName); + } + this.cache[this.propertyName] = newValue; + + if (fireObservers) { + propertyDidChange(this.context, this.propertyName); + } } else { delete this.cache[this.propertyName]; } @@ -11271,13 +11321,11 @@ ReduceComputedProperty.prototype._instanceMeta = function (context, propertyName }; ReduceComputedProperty.prototype.initialValue = function () { - switch (typeof this.options.initialValue) { - case 'undefined': - throw new Error("reduce computed properties require an initial value: did you forget to pass one to Ember.reduceComputed?"); - case 'function': - return this.options.initialValue(); - default: - return this.options.initialValue; + if (typeof this.options.initialValue === 'function') { + return this.options.initialValue(); + } + else { + return this.options.initialValue; } }; @@ -11300,25 +11348,25 @@ ReduceComputedProperty.prototype.clearItemPropertyKeys = function (dependentArra ReduceComputedProperty.prototype.property = function () { var cp = this, args = a_slice.call(arguments), - propertyArgs = [], + propertyArgs = new Ember.Set(), match, dependentArrayKey, itemPropertyKey; forEach(a_slice.call(arguments), function (dependentKey) { if (doubleEachPropertyPattern.test(dependentKey)) { - throw new Error("Nested @each properties not supported: " + dependentKey); + throw new Ember.Error("Nested @each properties not supported: " + dependentKey); } else if (match = eachPropertyPattern.exec(dependentKey)) { dependentArrayKey = match[1]; itemPropertyKey = match[2]; cp.itemPropertyKey(dependentArrayKey, itemPropertyKey); - propertyArgs.push(dependentArrayKey); + propertyArgs.add(dependentArrayKey); } else { - propertyArgs.push(dependentKey); + propertyArgs.add(dependentKey); } }); - return ComputedProperty.prototype.property.apply(this, propertyArgs); + return ComputedProperty.prototype.property.apply(this, propertyArgs.toArray()); }; /** @@ -11330,7 +11378,7 @@ ReduceComputedProperty.prototype.property = function () { If there are more than one arguments the first arguments are considered to be dependent property keys. The last argument is required to be an options object. The options object can have the - following four properties. + following four properties: `initialValue` - A value or function that will be used as the initial value for the computed. If this property is a function the result of calling @@ -11411,6 +11459,12 @@ ReduceComputedProperty.prototype.property = function () { to invalidate the computation. This is generally not a good idea for arrayComputed but it's used in eg max and min. + Note that observers will be fired if either of these functions return a value + that differs from the accumulated value. When returning an object that + mutates in response to array changes, for example an array that maps + everything from some other array (see `Ember.computed.map`), it is usually + important that the *same* array be returned to avoid accidentally triggering observers. + Example ```javascript @@ -11431,37 +11485,6 @@ ReduceComputedProperty.prototype.property = function () { }; ``` - Dependent keys may refer to `@self` to observe changes to the object itself, - which must be array-like, rather than a property of the object. This is - mostly useful for array proxies, to ensure objects are retrieved via - `objectAtContent`. This is how you could sort items by properties defined on an item controller. - - Example - - ```javascript - App.PeopleController = Ember.ArrayController.extend({ - itemController: 'person', - - sortedPeople: Ember.computed.sort('@self.@each.reversedName', function(personA, personB) { - // `reversedName` isn't defined on Person, but we have access to it via - // the item controller App.PersonController. If we'd used - // `content.@each.reversedName` above, we would be getting the objects - // directly and not have access to `reversedName`. - // - var reversedNameA = get(personA, 'reversedName'), - reversedNameB = get(personB, 'reversedName'); - - return Ember.compare(reversedNameA, reversedNameB); - }) - }); - - App.PersonController = Ember.ObjectController.extend({ - reversedName: function () { - return reverse(get(this, 'name')); - }.property('name') - }) - ``` - @method reduceComputed @for Ember @param {String} [dependentKeys*] @@ -11477,11 +11500,11 @@ Ember.reduceComputed = function (options) { } if (typeof options !== "object") { - throw new Error("Reduce Computed Property declared without an options hash"); + throw new Ember.Error("Reduce Computed Property declared without an options hash"); } - if (Ember.isNone(options.initialValue)) { - throw new Error("Reduce Computed Property declared without an initial value"); + if (!('initialValue' in options)) { + throw new Ember.Error("Reduce Computed Property declared without an initial value"); } var cp = new ReduceComputedProperty(options); @@ -11660,7 +11683,7 @@ Ember.arrayComputed = function (options) { } if (typeof options !== "object") { - throw new Error("Array Computed Property declared without an options hash"); + throw new Ember.Error("Array Computed Property declared without an options hash"); } var cp = new ArrayComputedProperty(options); @@ -12148,7 +12171,7 @@ Ember.computed.intersect = function () { */ Ember.computed.setDiff = function (setAProperty, setBProperty) { if (arguments.length !== 2) { - throw new Error("setDiff requires exactly two dependent arrays."); + throw new Ember.Error("setDiff requires exactly two dependent arrays."); } return Ember.arrayComputed.call(null, setAProperty, setBProperty, { addedItem: function (array, item, changeMeta, instanceMeta) { @@ -12263,7 +12286,7 @@ function binarySearch(array, item, low, high) { ]}); todoList.get('sortedTodos'); // [{name:'Documentation', priority:3}, {name:'Release', priority:1}, {name:'Unit Test', priority:2}] - todoList.get('priroityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}] + todoList.get('priorityTodos'); // [{name:'Release', priority:1}, {name:'Unit Test', priority:2}, {name:'Documentation', priority:3}] ``` @method computed.sort @@ -12403,7 +12426,7 @@ Ember.RSVP = requireModule('rsvp'); var STRING_DASHERIZE_REGEXP = (/[ _]/g); var STRING_DASHERIZE_CACHE = {}; -var STRING_DECAMELIZE_REGEXP = (/([a-z])([A-Z])/g); +var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([A-Z])/g); var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g); var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g); var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g); @@ -13070,7 +13093,7 @@ Ember.Copyable = Ember.Mixin.create(/** @scope Ember.Copyable.prototype */ { if (Ember.Freezable && Ember.Freezable.detect(this)) { return get(this, 'isFrozen') ? this : this.copy().freeze(); } else { - throw new Error(Ember.String.fmt("%@ does not support freezing", [this])); + throw new Ember.Error(Ember.String.fmt("%@ does not support freezing", [this])); } } }); @@ -13379,7 +13402,7 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,/** @return this */ insertAt: function(idx, object) { - if (idx > get(this, 'length')) throw new Error(OUT_OF_RANGE_EXCEPTION) ; + if (idx > get(this, 'length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION) ; this.replace(idx, 0, [object]) ; return this ; }, @@ -13407,7 +13430,7 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,/** if ('number' === typeof start) { if ((start < 0) || (start >= get(this, 'length'))) { - throw new Error(OUT_OF_RANGE_EXCEPTION); + throw new Ember.Error(OUT_OF_RANGE_EXCEPTION); } // fast case @@ -13994,6 +14017,29 @@ Ember.Observable = Ember.Mixin.create({ return Ember.hasListeners(this, key+':change'); }, + /** + @deprecated + @method getPath + @param {String} path The property path to retrieve + @return {Object} The property value or undefined. + */ + getPath: function(path) { + + return this.get(path); + }, + + /** + @deprecated + @method setPath + @param {String} path The path to the property that will be set + @param {Object} value The value to set or `null`. + @return {Ember.Observable} + */ + setPath: function(path, value) { + + return this.set(path, value); + }, + /** Retrieves the value of a property, or a default value in the case that the property returns `undefined`. @@ -14201,6 +14247,13 @@ Ember.TargetActionSupport = Ember.Mixin.create({ target = opts.target || get(this, 'targetObject'), actionContext = opts.actionContext; + function args(options, actionName) { + var ret = []; + if (actionName) { ret.push(actionName); } + + return ret.concat(options); + } + if (typeof actionContext === 'undefined') { actionContext = get(this, 'actionContextObject') || this; } @@ -14209,10 +14262,10 @@ Ember.TargetActionSupport = Ember.Mixin.create({ var ret; if (target.send) { - ret = target.send.apply(target, [action, actionContext]); + ret = target.send.apply(target, args(actionContext, action)); } else { - ret = target[action].apply(target, [actionContext]); + ret = target[action].apply(target, args(actionContext)); } if (ret !== false) ret = true; @@ -14627,7 +14680,7 @@ Ember.PromiseProxyMixin = Ember.Mixin.create({ installPromise(this, promise); return promise; } else { - throw new Error("PromiseProxy's promise must be set"); + throw new Ember.Error("PromiseProxy's promise must be set"); } }), @@ -14670,9 +14723,9 @@ Ember.TrackedArray = function (items) { var length = get(items, 'length'); if (length) { - this._content = [new ArrayOperation(RETAIN, length, items)]; + this._operations = [new ArrayOperation(RETAIN, length, items)]; } else { - this._content = []; + this._operations = []; } }; @@ -14690,8 +14743,10 @@ Ember.TrackedArray.prototype = { @param newItems */ addItems: function (index, newItems) { - var count = get(newItems, 'length'), - match = this._findArrayOperation(index), + var count = get(newItems, 'length'); + if (count < 1) { return; } + + var match = this._findArrayOperation(index), arrayOperation = match.operation, arrayOperationIndex = match.index, arrayOperationRangeStart = match.rangeStart, @@ -14706,7 +14761,7 @@ Ember.TrackedArray.prototype = { if (arrayOperation) { if (!match.split) { // insert left of arrayOperation - this._content.splice(arrayOperationIndex, 0, newArrayOperation); + this._operations.splice(arrayOperationIndex, 0, newArrayOperation); composeIndex = arrayOperationIndex; } else { this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); @@ -14714,7 +14769,7 @@ Ember.TrackedArray.prototype = { } } else { // insert at end - this._content.push(newArrayOperation); + this._operations.push(newArrayOperation); composeIndex = arrayOperationIndex; } @@ -14729,6 +14784,8 @@ Ember.TrackedArray.prototype = { @param count */ removeItems: function (index, count) { + if (count < 1) { return; } + var match = this._findArrayOperation(index), arrayOperation = match.operation, arrayOperationIndex = match.index, @@ -14739,7 +14796,7 @@ Ember.TrackedArray.prototype = { newArrayOperation = new ArrayOperation(DELETE, count); if (!match.split) { // insert left of arrayOperation - this._content.splice(arrayOperationIndex, 0, newArrayOperation); + this._operations.splice(arrayOperationIndex, 0, newArrayOperation); composeIndex = arrayOperationIndex; } else { this._split(arrayOperationIndex, index - arrayOperationRangeStart, newArrayOperation); @@ -14754,12 +14811,11 @@ Ember.TrackedArray.prototype = { items in the array. `callback` will be called for each operation and will be passed the following arguments: - -* {array} items The items for the given operation -* {number} offset The computed offset of the items, ie the index in the -array of the first item for this operation. -* {string} operation The type of the operation. One of `Ember.TrackedArray.{RETAIN, DELETE, INSERT}` -* + * {array} items The items for the given operation + * {number} offset The computed offset of the items, ie the index in the + array of the first item for this operation. + * {string} operation The type of the operation. One of + `Ember.TrackedArray.{RETAIN, DELETE, INSERT}` @method apply @param {function} callback @@ -14768,16 +14824,16 @@ array of the first item for this operation. var items = [], offset = 0; - forEach(this._content, function (arrayOperation) { - callback(arrayOperation.items, offset, arrayOperation.operation); + forEach(this._operations, function (arrayOperation) { + callback(arrayOperation.items, offset, arrayOperation.type); - if (arrayOperation.operation !== DELETE) { + if (arrayOperation.type !== DELETE) { offset += arrayOperation.count; items = items.concat(arrayOperation.items); } }); - this._content = [new ArrayOperation(RETAIN, items.length, items)]; + this._operations = [new ArrayOperation(RETAIN, items.length, items)]; }, /** @@ -14799,10 +14855,10 @@ array of the first item for this operation. // OPTIMIZE: we could search these faster if we kept a balanced tree. // find leftmost arrayOperation to the right of `index` - for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._content.length; arrayOperationIndex < len; ++arrayOperationIndex) { - arrayOperation = this._content[arrayOperationIndex]; + for (arrayOperationIndex = arrayOperationRangeStart = 0, len = this._operations.length; arrayOperationIndex < len; ++arrayOperationIndex) { + arrayOperation = this._operations[arrayOperationIndex]; - if (arrayOperation.operation === DELETE) { continue; } + if (arrayOperation.type === DELETE) { continue; } arrayOperationRangeEnd = arrayOperationRangeStart + arrayOperation.count - 1; @@ -14820,25 +14876,24 @@ array of the first item for this operation. }, _split: function (arrayOperationIndex, splitIndex, newArrayOperation) { - var arrayOperation = this._content[arrayOperationIndex], + var arrayOperation = this._operations[arrayOperationIndex], splitItems = arrayOperation.items.slice(splitIndex), - splitArrayOperation = new ArrayOperation(arrayOperation.operation, splitItems.length, splitItems); + splitArrayOperation = new ArrayOperation(arrayOperation.type, splitItems.length, splitItems); // truncate LHS arrayOperation.count = splitIndex; arrayOperation.items = arrayOperation.items.slice(0, splitIndex); - this._content.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation); + this._operations.splice(arrayOperationIndex + 1, 0, newArrayOperation, splitArrayOperation); }, - // TODO: unify _composeInsert, _composeDelete // see SubArray for a better implementation. _composeInsert: function (index) { - var newArrayOperation = this._content[index], - leftArrayOperation = this._content[index-1], // may be undefined - rightArrayOperation = this._content[index+1], // may be undefined - leftOp = leftArrayOperation && leftArrayOperation.operation, - rightOp = rightArrayOperation && rightArrayOperation.operation; + var newArrayOperation = this._operations[index], + leftArrayOperation = this._operations[index-1], // may be undefined + rightArrayOperation = this._operations[index+1], // may be undefined + leftOp = leftArrayOperation && leftArrayOperation.type, + rightOp = rightArrayOperation && rightArrayOperation.type; if (leftOp === INSERT) { // merge left @@ -14846,30 +14901,31 @@ array of the first item for this operation. leftArrayOperation.items = leftArrayOperation.items.concat(newArrayOperation.items); if (rightOp === INSERT) { - // also merge right + // also merge right (we have split an insert with an insert) leftArrayOperation.count += rightArrayOperation.count; leftArrayOperation.items = leftArrayOperation.items.concat(rightArrayOperation.items); - this._content.splice(index, 2); + this._operations.splice(index, 2); } else { // only merge left - this._content.splice(index, 1); + this._operations.splice(index, 1); } } else if (rightOp === INSERT) { // merge right newArrayOperation.count += rightArrayOperation.count; newArrayOperation.items = newArrayOperation.items.concat(rightArrayOperation.items); - this._content.splice(index + 1, 1); + this._operations.splice(index + 1, 1); } }, _composeDelete: function (index) { - var arrayOperation = this._content[index], + var arrayOperation = this._operations[index], deletesToGo = arrayOperation.count, - leftArrayOperation = this._content[index-1], // may be undefined - leftOp = leftArrayOperation && leftArrayOperation.operation, + leftArrayOperation = this._operations[index-1], // may be undefined + leftOp = leftArrayOperation && leftArrayOperation.type, nextArrayOperation, nextOp, nextCount, + removeNewAndNextOp = false, removedItems = []; if (leftOp === DELETE) { @@ -14878,8 +14934,8 @@ array of the first item for this operation. } for (var i = index + 1; deletesToGo > 0; ++i) { - nextArrayOperation = this._content[i]; - nextOp = nextArrayOperation.operation; + nextArrayOperation = this._operations[i]; + nextOp = nextArrayOperation.type; nextCount = nextArrayOperation.count; if (nextOp === DELETE) { @@ -14888,6 +14944,7 @@ array of the first item for this operation. } if (nextCount > deletesToGo) { + // d:2 {r,i}:5 we reduce the retain or insert, but it stays removedItems = removedItems.concat(nextArrayOperation.items.splice(0, deletesToGo)); nextArrayOperation.count -= deletesToGo; @@ -14899,29 +14956,57 @@ array of the first item for this operation. deletesToGo = 0; } else { + if (nextCount === deletesToGo) { + // Handle edge case of d:2 i:2 in which case both operations go away + // during composition. + removeNewAndNextOp = true; + } removedItems = removedItems.concat(nextArrayOperation.items); deletesToGo -= nextCount; } if (nextOp === INSERT) { + // d:2 i:3 will result in delete going away arrayOperation.count -= nextCount; } } if (arrayOperation.count > 0) { - this._content.splice(index+1, i-1-index); + // compose our new delete with possibly several operations to the right of + // disparate types + this._operations.splice(index+1, i-1-index); } else { // The delete operation can go away; it has merely reduced some other - // operation, as in D:3 I:4 - this._content.splice(index, 1); + // operation, as in d:3 i:4; it may also have eliminated that operation, + // as in d:3 i:3. + this._operations.splice(index, removeNewAndNextOp ? 2 : 1); } return removedItems; + }, + + toString: function () { + var str = ""; + forEach(this._operations, function (operation) { + str += " " + operation.type + ":" + operation.count; + }); + return str.substring(1); } }; +/** + Internal data structure to represent an array operation. + + @method ArrayOperation + @private + @property {string} type The type of the operation. One of + `Ember.TrackedArray.{RETAIN, INSERT, DELETE}` + @property {number} count The number of items in this operation. + @property {array} items The items of the operation, if included. RETAIN and + INSERT include their items, DELETE does not. +*/ function ArrayOperation (operation, count, items) { - this.operation = operation; // RETAIN | INSERT | DELETE + this.type = operation; // RETAIN | INSERT | DELETE this.count = count; this.items = items; } @@ -15059,6 +15144,8 @@ Ember.SubArray.prototype = { self._operations.splice(operationIndex, 1); self._composeAt(operationIndex); } + }, function() { + throw new Ember.Error("Can't remove an item that has never been added."); }); return returnValue; @@ -15105,6 +15192,7 @@ Ember.SubArray.prototype = { if (otherOp.type === op.type) { op.count += otherOp.count; this._operations.splice(index-1, 1); + --index; } } @@ -15200,7 +15288,14 @@ function makeCtor() { var properties = props[i]; - for (var keyName in properties) { + if (properties === null || typeof properties !== 'object') { + + continue; + } + + var keyNames = Ember.keys(properties); + for (var j = 0, ll = keyNames.length; j < ll; j++) { + var keyName = keyNames[j]; if (!properties.hasOwnProperty(keyName)) { continue; } var value = properties[keyName], @@ -15387,7 +15482,7 @@ CoreObject.PrototypeMixin = Mixin.create({ This feature is available for you to use throughout the Ember object model, although typical app developers are likely to use it infrequently. Since it changes expectations about behavior of properties, you should properly - document its usage in each individual concatenated property (to not + document its usage in each individual concatenated property (to not mislead your users to think they can override the property in a subclass). @property concatenatedProperties @@ -15528,6 +15623,86 @@ var ClassMixin = Mixin.create({ isMethod: false, + /** + Creates a new subclass. + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + alert(thing); + } + }); + ``` + + This defines a new subclass of Ember.Object: `App.Person`. It contains one method: `say()`. + + You can also create a subclass from any existing class by calling its `extend()` method. For example, you might want to create a subclass of Ember's built-in `Ember.View` class: + + ```javascript + App.PersonView = Ember.View.extend({ + tagName: 'li', + classNameBindings: ['isAdministrator'] + }); + ``` + + When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special `_super()` method: + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + var name = this.get('name'); + alert(name + ' says: ' + thing); + } + }); + + App.Soldier = App.Person.extend({ + say: function(thing) { + this._super(thing + ", sir!"); + }, + march: function(numberOfHours) { + alert(this.get('name') + ' marches for ' + numberOfHours + ' hours.') + } + }); + + var yehuda = App.Soldier.create({ + name: "Yehuda Katz" + }); + + yehuda.say("Yes"); // alerts "Yehuda Katz says: Yes, sir!" + ``` + + The `create()` on line #17 creates an *instance* of the `App.Soldier` class. The `extend()` on line #8 creates a *subclass* of `App.Person`. Any instance of the `App.Person` class will *not* have the `march()` method. + + You can also pass `Ember.Mixin` classes to add additional properties to the subclass. + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + alert(this.get('name') + ' says: ' + thing); + } + }); + + App.SingingMixin = Ember.Mixin.create({ + sing: function(thing){ + alert(this.get('name') + ' sings: la la la ' + thing); + } + }); + + App.BroadwayStar = App.Person.extend(App.SingingMixin, { + dance: function() { + alert(this.get('name') + ' dances: tap tap tap tap '); + } + }); + ``` + + The `App.BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`. + + @method extend + @static + + @param {Ember.Mixin} [mixins]* One or more Ember.Mixin classes + @param {Object} [arguments]* Object containing values to use within the new class + */ extend: function() { var Class = makeCtor(), proto; Class.ClassMixin = Mixin.create(this.ClassMixin); @@ -15608,10 +15783,10 @@ var ClassMixin = Mixin.create({ }, /** - + Augments a constructor's prototype with additional properties and functions: - + ```javascript MyObject = Ember.Object.extend({ name: 'an object' @@ -15631,7 +15806,7 @@ var ClassMixin = Mixin.create({ o.say("goodbye"); // logs "goodbye" ``` - + To add functions and properties to the constructor itself, see `reopenClass` @@ -15645,7 +15820,7 @@ var ClassMixin = Mixin.create({ /** Augments a constructor's own properties and functions: - + ```javascript MyObject = Ember.Object.extend({ name: 'an object' @@ -15655,17 +15830,50 @@ var ClassMixin = Mixin.create({ MyObject.reopenClass({ canBuild: false }); - + MyObject.canBuild; // false o = MyObject.create(); ``` - + + In other words, this creates static properties and functions for the class. These are only available on the class + and not on any instance of that class. + + ```javascript + App.Person = Ember.Object.extend({ + name : "", + sayHello : function(){ + alert("Hello. My name is " + this.get('name')); + } + }); + + App.Person.reopenClass({ + species : "Homo sapiens", + createPerson: function(newPersonsName){ + return App.Person.create({ + name:newPersonsName + }); + } + }); + + var tom = App.Person.create({ + name : "Tom Dale" + }); + var yehuda = App.Person.createPerson("Yehuda Katz"); + + tom.sayHello(); // "Hello. My name is Tom Dale" + yehuda.sayHello(); // "Hello. My name is Yehuda Katz" + alert(App.Person.species); // "Homo sapiens" + ``` + + Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda` + variables. They are only valid on `App.Person`. + To add functions and properties to instances of a constructor by extending the constructor's prototype see `reopen` - + @method reopenClass - */ + */ reopenClass: function() { reopen.apply(this.ClassMixin, arguments); applyMixin(this, arguments, false); @@ -16221,7 +16429,7 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,/** @scope Ember.Array }, _insertAt: function(idx, object) { - if (idx > get(this, 'content.length')) throw new Error(OUT_OF_RANGE_EXCEPTION); + if (idx > get(this, 'content.length')) throw new Ember.Error(OUT_OF_RANGE_EXCEPTION); this._replace(idx, 0, [object]); return this; }, @@ -16241,7 +16449,7 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,/** @scope Ember.Array indices = [], i; if ((start < 0) || (start >= get(this, 'length'))) { - throw new Error(OUT_OF_RANGE_EXCEPTION); + throw new Ember.Error(OUT_OF_RANGE_EXCEPTION); } if (len === undefined) len = 1; @@ -17008,7 +17216,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb @return {Ember.Set} An empty Set */ clear: function() { - if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); } + if (this.isFrozen) { throw new Ember.Error(Ember.FROZEN_ERROR); } var len = get(this, 'length'); if (len === 0) { return this; } @@ -17118,7 +17326,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb @return {Object} The removed object from the set or null. */ pop: function() { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR); var obj = this.length > 0 ? this[this.length-1] : null; this.remove(obj); return obj; @@ -17235,7 +17443,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb // implements Ember.MutableEnumerable addObject: function(obj) { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR); if (isNone(obj)) return this; // nothing to do var guid = guidFor(obj), @@ -17263,7 +17471,7 @@ Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Emb // implements Ember.MutableEnumerable removeObject: function(obj) { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (get(this, 'isFrozen')) throw new Ember.Error(Ember.FROZEN_ERROR); if (isNone(obj)) return this; // nothing to do var guid = guidFor(obj), @@ -17969,7 +18177,7 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin, fullName = "controller:" + controllerClass; if (!container.has(fullName)) { - throw new Error('Could not resolve itemController: "' + controllerClass + '"'); + throw new Ember.Error('Could not resolve itemController: "' + controllerClass + '"'); } subController = container.lookupFactory(fullName).create({ @@ -18291,6 +18499,19 @@ function escapeAttribute(value) { return string.replace(BAD_CHARS_REGEXP, escapeChar); } +// IE 6/7 have bugs arond setting names on inputs during creation. +// From http://msdn.microsoft.com/en-us/library/ie/ms536389(v=vs.85).aspx: +// "To include the NAME attribute at run time on objects created with the createElement method, use the eTag." +var canSetNameOnInputs = (function() { + var div = document.createElement('div'), + el = document.createElement('input'); + + el.setAttribute('name', 'foo'); + div.appendChild(el); + + return !!div.innerHTML.match('foo'); +})(); + /** `Ember.RenderBuffer` gathers information regarding the a view and generates the final representation. `Ember.RenderBuffer` will generate HTML which can be pushed @@ -18650,14 +18871,22 @@ Ember._RenderBuffer.prototype = generateElement: function() { var tagName = this.tagNames.pop(), // pop since we don't need to close - element = document.createElement(tagName), - $element = Ember.$(element), id = this.elementId, classes = this.classes, attrs = this.elementAttributes, props = this.elementProperties, style = this.elementStyle, - styleBuffer = '', attr, prop; + styleBuffer = '', attr, prop, tagString; + + if (attrs && attrs.name && !canSetNameOnInputs) { + // IE allows passing a tag to createElement. See note on `canSetNameOnInputs` above as well. + tagString = '<'+stripTagName(tagName)+' name="'+escapeAttribute(attrs.name)+'">'; + } else { + tagString = tagName; + } + + var element = document.createElement(tagString), + $element = Ember.$(element); if (id) { $element.attr('id', id); @@ -19071,7 +19300,7 @@ var childViewsProperty = Ember.computed(function() { return view.replace(idx, removedCount, addedViews); } - throw new Error("childViews is immutable"); + throw new Ember.Error("childViews is immutable"); }; return ret; @@ -20757,8 +20986,8 @@ Ember.View = Ember.CoreView.extend( If you write a `willDestroyElement()` handler, you can assume that your `didInsertElement()` handler was called earlier for the same element. - Normally you will not call or override this method yourself, but you may - want to implement the above callbacks when it is run. + You should not call or override this method yourself, but you may + want to implement the above callbacks. @method destroyElement @return {Ember.View} receiver @@ -21283,6 +21512,10 @@ Ember.View = Ember.CoreView.extend( target = null; } + if (!root || typeof root !== 'object') { + return; + } + var view = this, stateCheckedObserver = function() { view.currentState.invokeObserver(this, observer); @@ -21819,7 +22052,7 @@ Ember.merge(inDOM, { } view.addBeforeObserver('elementId', function() { - throw new Error("Changing a view's elementId after creation is not allowed"); + throw new Ember.Error("Changing a view's elementId after creation is not allowed"); }); }, @@ -22059,30 +22292,6 @@ var ViewCollection = Ember._ViewCollection; or layout being rendered. The HTML contents of a `Ember.ContainerView`'s DOM representation will only be the rendered HTML of its child views. - ## Binding a View to Display - - 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 - instance, it will be added to the ContainerView. If the `currentView` property - is later changed to a different view, the new view will replace the old view. - If `currentView` is set to `null`, the last `currentView` will be removed. - - 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 - the `currentView` of a container to a controller like this: - - ```javascript - App.appController = Ember.Object.create({ - view: Ember.View.create({ - templateName: 'person_template' - }) - }); - ``` - - ```handlebars - {{view Ember.ContainerView currentViewBinding="App.appController.view"}} - ``` - @class ContainerView @namespace Ember @extends Ember.View @@ -22267,7 +22476,7 @@ Ember.merge(states._default, { Ember.merge(states.inBuffer, { childViewsDidChange: function(parentView, views, start, added) { - throw new Error('You cannot modify child views while in the inBuffer state'); + throw new Ember.Error('You cannot modify child views while in the inBuffer state'); } }); @@ -22758,7 +22967,9 @@ Ember.CollectionView.CONTAINER_MAP = { (function() { -var get = Ember.get, set = Ember.set, isNone = Ember.isNone; +var get = Ember.get, set = Ember.set, isNone = Ember.isNone, + a_slice = Array.prototype.slice; + /** @module ember @@ -22949,8 +23160,9 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { @param [action] {String} the action to trigger @param [context] {*} a context to send with the action */ - sendAction: function(action, context) { - var actionName; + sendAction: function(action) { + var actionName, + contexts = a_slice.call(arguments, 1); // Send the default action if (action === undefined) { @@ -22966,7 +23178,7 @@ Ember.Component = Ember.View.extend(Ember.TargetActionSupport, { this.triggerAction({ action: actionName, - actionContext: context + actionContext: contexts }); } }); @@ -23564,20 +23776,6 @@ if (!Handlebars && typeof require === 'function') { */ Ember.Handlebars = objectCreate(Handlebars); -function makeBindings(options) { - var hash = options.hash, - hashType = options.hashTypes; - - for (var prop in hash) { - if (hashType[prop] === 'ID') { - hash[prop + 'Binding'] = hash[prop]; - hashType[prop + 'Binding'] = 'STRING'; - delete hash[prop]; - delete hashType[prop]; - } - } -} - /** Register a bound helper or custom view helper. @@ -23637,7 +23835,6 @@ Ember.Handlebars.helper = function(name, value) { if (Ember.View.detect(value)) { Ember.Handlebars.registerHelper(name, function(options) { - makeBindings(options); return Ember.Handlebars.helpers.view.call(this, value, options); }); } else { @@ -23873,6 +24070,7 @@ var handlebarsGet = Ember.Handlebars.get = function(root, path, options) { } return value; }; +Ember.Handlebars.getPath = Ember.deprecateFunc('`Ember.Handlebars.getPath` has been changed to `Ember.Handlebars.get` for consistency.', Ember.Handlebars.get); Ember.Handlebars.resolveParams = function(context, params, options) { var resolvedParams = [], types = options.types, param, type; @@ -24055,7 +24253,8 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) { data = options.data, hash = options.hash, view = data.view, - currentContext = (options.contexts && options.contexts[0]) || this, + contexts = options.contexts, + currentContext = (contexts && contexts.length) ? contexts[0] : this, prefixPathForDependentKeys = '', loc, len, hashOption, boundOption, property, @@ -24179,7 +24378,7 @@ function evaluateUnboundHelper(context, fn, normalizedProperties, options) { for(loc = 0, len = normalizedProperties.length; loc < len; ++loc) { property = normalizedProperties[loc]; - args.push(Ember.Handlebars.get(context, property.path, options)); + args.push(Ember.Handlebars.get(property.root, property.path, options)); } args.push(options); return fn.apply(context, args); @@ -24823,16 +25022,16 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer } } -function simpleBind(property, options) { +function simpleBind(currentContext, property, options) { var data = options.data, view = data.view, - currentContext = this, - normalized, observer; + normalized, observer, pathRoot, output; normalized = normalizePath(currentContext, property, data); + pathRoot = normalized.root; // Set up observers for observable objects - if ('object' === typeof this) { + if (pathRoot && ('object' === typeof pathRoot)) { if (data.insideGroup) { observer = function() { Ember.run.once(view, 'rerender'); @@ -24864,7 +25063,8 @@ function simpleBind(property, options) { } else { // The object is not observable, so just render it out and // be done with it. - data.buffer.push(handlebarsGet(currentContext, property, options)); + output = handlebarsGet(currentContext, property, options); + data.buffer.push((output === null || typeof output === 'undefined') ? '' : output); } } @@ -24921,10 +25121,10 @@ EmberHandlebars.registerHelper('_triageMustache', function(property, fn) { EmberHandlebars.registerHelper('bind', function(property, options) { - var context = (options.contexts && options.contexts[0]) || this; + var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this; if (!options.fn) { - return simpleBind.call(context, property, options); + return simpleBind(context, property, options); } return bind.call(context, property, options, false, exists); @@ -24949,7 +25149,7 @@ EmberHandlebars.registerHelper('bind', function(property, options) { @return {String} HTML string */ EmberHandlebars.registerHelper('boundIf', function(property, fn) { - var context = (fn.contexts && fn.contexts[0]) || this; + var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this; var func = function(result) { var truthy = result && get(result, 'isTruthy'); if (typeof truthy === 'boolean') { return truthy; } @@ -25393,6 +25593,35 @@ var EmberHandlebars = Ember.Handlebars; var LOWERCASE_A_Z = /^[a-z]/; var VIEW_PREFIX = /^view\./; +function makeBindings(thisContext, options) { + var hash = options.hash, + hashType = options.hashTypes; + + for (var prop in hash) { + if (hashType[prop] === 'ID') { + + var value = hash[prop]; + + if (Ember.IS_BINDING.test(prop)) { + + } else { + hash[prop + 'Binding'] = value; + hashType[prop + 'Binding'] = 'STRING'; + delete hash[prop]; + delete hashType[prop]; + } + } + } + + if (hash.hasOwnProperty('idBinding')) { + // id can't be bound, so just perform one-time lookup. + hash.id = EmberHandlebars.get(thisContext, hash.idBinding, options); + hashType.id = 'STRING'; + delete hash.idBinding; + delete hashType.idBinding; + } +} + EmberHandlebars.ViewHelper = Ember.Object.create({ propertiesFromHTMLOptions: function(options, thisContext) { @@ -25502,6 +25731,8 @@ EmberHandlebars.ViewHelper = Ember.Object.create({ fn = options.fn, newView; + makeBindings(thisContext, options); + if ('string' === typeof path) { // TODO: this is a lame conditional, this should likely change @@ -25989,7 +26220,7 @@ Ember.Handlebars.registerHelper('unbound', function(property, fn) { return out; } - context = (fn.contexts && fn.contexts[0]) || this; + context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this; return handlebarsGet(context, property, fn); }); @@ -26019,7 +26250,7 @@ var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.norma @param {String} property */ Ember.Handlebars.registerHelper('log', function(property, options) { - var context = (options.contexts && options.contexts[0]) || this, + var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this, normalized = normalizePath(context, property, options.data), pathRoot = normalized.root, path = normalized.path, @@ -26632,11 +26863,11 @@ var get = Ember.get, set = Ember.set; <!-- application.hbs --> {{#labeled-textfield value=someProperty}} First name: - {{/my-component}} + {{/labeled-textfield}} ``` ```handlebars - <!-- components/my-component.hbs --> + <!-- components/labeled-textfield.hbs --> <label> {{yield}} {{input value=value}} </label> @@ -26799,7 +27030,7 @@ var get = Ember.get, set = Ember.set; Ember.TextSupport = Ember.Mixin.create({ value: "", - attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly'], + attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex'], placeholder: null, disabled: false, maxlength: null, @@ -26833,7 +27064,7 @@ Ember.TextSupport = Ember.Mixin.create({ Options are: * `enter`: the user pressed enter - * `keypress`: the user pressed a key + * `keyPress`: the user pressed a key @property onEvent @type String @@ -26876,7 +27107,7 @@ Ember.TextSupport = Ember.Mixin.create({ Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 13. Uses sendAction to send the `enter` action to the controller. - @method insertNewLine + @method insertNewline @param {Event} event */ insertNewline: function(event) { @@ -26887,8 +27118,8 @@ Ember.TextSupport = Ember.Mixin.create({ /** Called when the user hits escape. - Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 13. - Uses sendAction to send the `enter` action to the controller. + Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 27. + Uses sendAction to send the `escape-press` action to the controller. @method cancel @param {Event} event @@ -27324,7 +27555,7 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ``` ```handlebars - {{view Ember.Select contentBinding="names"}} + {{view Ember.Select content=names}} ``` Would result in the following HTML: @@ -27337,7 +27568,7 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ``` You can control which `<option>` is selected through the `Ember.Select`'s - `value` property directly or as a binding: + `value` property: ```javascript App.ApplicationController = Ember.Controller.extend({ @@ -27348,8 +27579,8 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ```handlebars {{view Ember.Select - contentBinding="names" - valueBinding="selectedName" + content=names + value=selectedName }} ``` @@ -27390,7 +27621,7 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ```handlebars {{view Ember.Select - contentBinding="programmers" + content=programmers optionValuePath="content.id" optionLabelPath="content.firstName"}} ``` @@ -27405,8 +27636,7 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ``` The `value` attribute of the selected `<option>` within an `Ember.Select` - can be bound to a property on another object by providing a - `valueBinding` option: + can be bound to a property on another object: ```javascript App.ApplicationController = Ember.Controller.extend({ @@ -27422,10 +27652,10 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ```handlebars {{view Ember.Select - contentBinding="programmers" + content=programmers optionValuePath="content.id" optionLabelPath="content.firstName" - valueBinding="currentProgrammer.id"}} + value=currentProgrammer.id}} ``` Would result in the following HTML with a selected option: @@ -27442,8 +27672,8 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ to match the `value` property of the newly selected `<option>`. Alternatively, you can control selection through the underlying objects - used to render each object providing a `selectionBinding`. When the selected - `<option>` is changed, the property path provided to `selectionBinding` + used to render each object by binding the `selection` option. When the selected + `<option>` is changed, the property path provided to `selection` will be updated to match the content object of the rendered `<option>` element: @@ -27459,10 +27689,10 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ```handlebars {{view Ember.Select - contentBinding="programmers" + content=programmers optionValuePath="content.id" optionLabelPath="content.firstName" - selectionBinding="selectedPerson"}} + selection=selectedPerson}} ``` Would result in the following HTML with a selected option: @@ -27475,7 +27705,7 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ``` Interacting with the rendered element by selecting the first option - ('Yehuda') will update the `selectedPerson` to match the object of + ('Yehuda') will update the `selectedPerson` to match the object of the newly selected `<option>`. In this case it is the first object in the `programmers` @@ -27496,8 +27726,8 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ``` handlebars {{view Ember.Select - contentBinding="programmers" - valueBinding="selectedProgrammer" + content=programmers + value=selectedProgrammer }} ``` @@ -27528,8 +27758,8 @@ Ember.SelectOptgroup = Ember.CollectionView.extend({ ```handlebars {{view Ember.Select - contentBinding="programmers" - valueBinding="selectedProgrammer" + content=programmers + value=selectedProgrammer prompt="Please select a name" }} ``` @@ -27581,11 +27811,11 @@ function program3(depth0,data) { function program4(depth0,data) { var hashContexts, hashTypes; - hashContexts = {'contentBinding': depth0,'labelBinding': depth0}; - hashTypes = {'contentBinding': "ID",'labelBinding': "ID"}; + hashContexts = {'content': depth0,'label': depth0}; + hashTypes = {'content': "ID",'label': "ID"}; data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.groupView", {hash:{ - 'contentBinding': ("content"), - 'labelBinding': ("label") + 'content': ("content"), + 'label': ("label") },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); } @@ -27601,10 +27831,10 @@ function program6(depth0,data) { function program7(depth0,data) { var hashContexts, hashTypes; - hashContexts = {'contentBinding': depth0}; - hashTypes = {'contentBinding': "STRING"}; + hashContexts = {'content': depth0}; + hashTypes = {'content': "ID"}; data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.optionView", {hash:{ - 'contentBinding': ("this") + 'content': ("") },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); } @@ -27635,7 +27865,7 @@ function program7(depth0,data) { The `disabled` attribute of the select element. Indicates whether the element is disabled from interactions. - @property multiple + @property disabled @type Boolean @default false */ @@ -27744,8 +27974,9 @@ function program7(depth0,data) { groupedContent: Ember.computed(function() { var groupPath = get(this, 'optionGroupPath'); var groupedContent = Ember.A(); + var content = get(this, 'content') || []; - forEach(get(this, 'content'), function(item) { + forEach(content, function(item) { var label = get(item, groupPath); if (get(groupedContent, 'lastObject.label') !== label) { @@ -27900,15 +28131,6 @@ function program7(depth0,data) { @submodule ember-handlebars-compiler */ -function normalizeHash(hash, hashTypes) { - for (var prop in hash) { - if (hashTypes[prop] === 'ID') { - hash[prop + 'Binding'] = hash[prop]; - delete hash[prop]; - } - } -} - /** The `{{input}}` helper inserts an HTML `<input>` tag into the template, @@ -27996,7 +28218,6 @@ function normalizeHash(hash, hashTypes) { * `indeterminate` * `name` - When set to a quoted string, these values will be directly applied to the HTML element. When left unquoted, these values will be bound to a property on the template's current rendering context (most typically a controller instance). @@ -28058,8 +28279,6 @@ Ember.Handlebars.registerHelper('input', function(options) { delete hash.type; delete hash.on; - normalizeHash(hash, types); - if (inputType === 'checkbox') { return Ember.Handlebars.helpers.view.call(this, Ember.Checkbox, options); } else { @@ -28222,7 +28441,6 @@ Ember.Handlebars.registerHelper('textarea', function(options) { var hash = options.hash, types = options.hashTypes; - normalizeHash(hash, types); return Ember.Handlebars.helpers.view.call(this, Ember.TextArea, options); }); @@ -28273,7 +28491,7 @@ Ember.Handlebars.bootstrap = function(ctx) { // Check if template of same name already exists if (Ember.TEMPLATES[templateName] !== undefined) { - throw new Error('Template named "' + templateName + '" already exists.'); + throw new Ember.Error('Template named "' + templateName + '" already exists.'); } // For templates which have a name, we save them and then remove them from the DOM @@ -28462,7 +28680,7 @@ define("route-recognizer", results.push(new StarSegment(match[1])); names.push(match[1]); types.stars++; - } else if(segment === "") { + } else if (segment === "") { results.push(new EpsilonSegment()); } else { results.push(new StaticSegment(segment)); @@ -28611,31 +28829,19 @@ define("route-recognizer", return nextStates; } - function findHandler(state, path, queryParams) { + function findHandler(state, path) { var handlers = state.handlers, regex = state.regex; var captures = path.match(regex), currentCapture = 1; var result = []; for (var i=0, l=handlers.length; i<l; i++) { - var handler = handlers[i], names = handler.names, params = {}, - watchedQueryParams = handler.queryParams || [], - activeQueryParams = {}, - j, m; + var handler = handlers[i], names = handler.names, params = {}; - for (j=0, m=names.length; j<m; j++) { + for (var j=0, m=names.length; j<m; j++) { params[names[j]] = captures[currentCapture++]; } - for (j=0, m=watchedQueryParams.length; j < m; j++) { - var key = watchedQueryParams[j]; - if(queryParams[key]){ - activeQueryParams[key] = queryParams[key]; - } - } - var currentResult = { handler: handler.handler, params: params, isDynamic: !!names.length }; - if(watchedQueryParams && watchedQueryParams.length > 0) { - currentResult.queryParams = activeQueryParams; - } - result.push(currentResult); + + result.push({ handler: handler.handler, params: params, isDynamic: !!names.length }); } return result; @@ -28690,11 +28896,7 @@ define("route-recognizer", regex += segment.regex(); } - var handler = { handler: route.handler, names: names }; - if(route.queryParams) { - handler.queryParams = route.queryParams; - } - handlers.push(handler); + handlers.push({ handler: route.handler, names: names }); } if (isEmpty) { @@ -28746,61 +28948,12 @@ define("route-recognizer", if (output.charAt(0) !== '/') { output = '/' + output; } - if (params && params.queryParams) { - output += this.generateQueryString(params.queryParams, route.handlers); - } - return output; }, - generateQueryString: function(params, handlers) { - var pairs = [], allowedParams = []; - for(var i=0; i < handlers.length; i++) { - var currentParamList = handlers[i].queryParams; - if(currentParamList) { - allowedParams.push.apply(allowedParams, currentParamList); - } - } - for(var key in params) { - if (params.hasOwnProperty(key)) { - if(!~allowedParams.indexOf(key)) { - throw 'Query param "' + key + '" is not specified as a valid param for this route'; - } - var value = params[key]; - var pair = encodeURIComponent(key); - if(value !== true) { - pair += "=" + encodeURIComponent(value); - } - pairs.push(pair); - } - } - - if (pairs.length === 0) { return ''; } - - return "?" + pairs.join("&"); - }, - - parseQueryString: function(queryString) { - var pairs = queryString.split("&"), queryParams = {}; - for(var i=0; i < pairs.length; i++) { - var pair = pairs[i].split('='), - key = decodeURIComponent(pair[0]), - value = pair[1] ? decodeURIComponent(pair[1]) : true; - queryParams[key] = value; - } - return queryParams; - }, - recognize: function(path) { var states = [ this.rootState ], - pathLen, i, l, queryStart, queryParams = {}; - - queryStart = path.indexOf('?'); - if (~queryStart) { - var queryString = path.substr(queryStart + 1, path.length); - path = path.substr(0, queryStart); - queryParams = this.parseQueryString(queryString); - } + pathLen, i, l; // DEBUG GROUP path @@ -28828,7 +28981,7 @@ define("route-recognizer", var state = solutions[0]; if (state && state.handlers) { - return findHandler(state, path, queryParams); + return findHandler(state, path); } } }; @@ -28853,25 +29006,12 @@ define("route-recognizer", if (callback.length === 0) { throw new Error("You must have an argument in the function passed to `to`"); } this.matcher.addChild(this.path, target, callback, this.delegate); } - return this; - }, - - withQueryParams: function() { - if (arguments.length === 0) { throw new Error("you must provide arguments to the withQueryParams method"); } - for (var i = 0; i < arguments.length; i++) { - if (typeof arguments[i] !== "string") { - throw new Error('you should call withQueryParams with a list of strings, e.g. withQueryParams("foo", "bar")'); - } - } - var queryParams = [].slice.call(arguments); - this.matcher.addQueryParams(this.path, queryParams); } }; function Matcher(target) { this.routes = {}; this.children = {}; - this.queryParams = {}; this.target = target; } @@ -28880,10 +29020,6 @@ define("route-recognizer", this.routes[path] = handler; }, - addQueryParams: function(path, params) { - this.queryParams[path] = params; - }, - addChild: function(path, target, callback, delegate) { var matcher = new Matcher(target); this.children[path] = matcher; @@ -28910,26 +29046,23 @@ define("route-recognizer", }; } - function addRoute(routeArray, path, handler, queryParams) { + function addRoute(routeArray, path, handler) { var len = 0; for (var i=0, l=routeArray.length; i<l; i++) { len += routeArray[i].path.length; } path = path.substr(len); - var route = { path: path, handler: handler }; - if(queryParams) { route.queryParams = queryParams; } - routeArray.push(route); + routeArray.push({ path: path, handler: handler }); } function eachRoute(baseRoute, matcher, callback, binding) { var routes = matcher.routes; - var queryParams = matcher.queryParams; for (var path in routes) { if (routes.hasOwnProperty(path)) { var routeArray = baseRoute.slice(); - addRoute(routeArray, path, routes[path], queryParams[path]); + addRoute(routeArray, path, routes[path]); if (matcher.children[path]) { eachRoute(routeArray, matcher.children[path], callback, binding); @@ -29068,9 +29201,9 @@ define("router", */ retry: function() { this.abort(); + var recogHandlers = this.router.recognizer.handlersFor(this.targetName), - handlerInfos = generateHandlerInfosWithQueryParams(this.router, recogHandlers, this.queryParams), - newTransition = performTransition(this.router, handlerInfos, this.providedModelsArray, this.params, this.queryParams, this.data); + newTransition = performTransition(this.router, recogHandlers, this.providedModelsArray, this.params, this.data); return newTransition; }, @@ -29095,10 +29228,6 @@ define("router", method: function(method) { this.urlMethod = method; return this; - }, - - toString: function() { - return "Transition (sequence " + this.sequence + ")"; } }; @@ -29243,21 +29372,8 @@ define("router", @param {Array[Object]} contexts @return {Object} a serialized parameter hash */ - paramsForHandler: function(handlerName, contexts) { - var partitionedArgs = extractQueryParams(slice.call(arguments, 1)); - return paramsForHandler(this, handlerName, partitionedArgs[0], partitionedArgs[1]); - }, - - /** - This method takes a handler name and returns a list of query params - that are valid to pass to the handler or its parents - - @param {String} handlerName - @return {Array[String]} a list of query parameters - */ - queryParamsForHandler: function (handlerName) { - return queryParamsForHandler(this, handlerName); + return paramsForHandler(this, handlerName, slice.call(arguments, 1)); }, /** @@ -29271,41 +29387,12 @@ define("router", @return {String} a URL */ generate: function(handlerName) { - var partitionedArgs = extractQueryParams(slice.call(arguments, 1)), - suppliedParams = partitionedArgs[0], - queryParams = partitionedArgs[1]; - - var params = paramsForHandler(this, handlerName, suppliedParams, queryParams), - validQueryParams = queryParamsForHandler(this, handlerName); - - var missingParams = []; - - for (var key in queryParams) { - if (queryParams.hasOwnProperty(key) && !~validQueryParams.indexOf(key)) { - missingParams.push(key); - } - } - - if (missingParams.length > 0) { - var err = 'You supplied the params '; - err += missingParams.map(function(param) { - return '"' + param + "=" + queryParams[param] + '"'; - }).join(' and '); - - err += ' which are not valid for the "' + handlerName + '" handler or its parents'; - - throw new Error(err); - } - + var params = paramsForHandler(this, handlerName, slice.call(arguments, 1)); return this.recognizer.generate(handlerName, params); }, isActive: function(handlerName) { - var partitionedArgs = extractQueryParams(slice.call(arguments, 1)), - contexts = partitionedArgs[0], - queryParams = partitionedArgs[1], - activeQueryParams = {}, - effectiveQueryParams = {}; + var contexts = slice.call(arguments, 1); var targetHandlerInfos = this.targetHandlerInfos, found = false, names, object, handlerInfo, handlerObj; @@ -29313,24 +29400,19 @@ define("router", if (!targetHandlerInfos) { return false; } var recogHandlers = this.recognizer.handlersFor(targetHandlerInfos[targetHandlerInfos.length - 1].name); + for (var i=targetHandlerInfos.length-1; i>=0; i--) { handlerInfo = targetHandlerInfos[i]; if (handlerInfo.name === handlerName) { found = true; } if (found) { - var recogHandler = recogHandlers[i]; + if (contexts.length === 0) { break; } - merge(activeQueryParams, handlerInfo.queryParams); - if (queryParams !== false) { - merge(effectiveQueryParams, handlerInfo.queryParams); - mergeSomeKeys(effectiveQueryParams, queryParams, recogHandler.queryParams); - } - - if (handlerInfo.isDynamic && contexts.length > 0) { + if (handlerInfo.isDynamic) { object = contexts.pop(); if (isParam(object)) { - var name = recogHandler.names[0]; + var recogHandler = recogHandlers[i], name = recogHandler.names[0]; if ("" + object !== this.currentParams[name]) { return false; } } else if (handlerInfo.context !== object) { return false; @@ -29339,8 +29421,7 @@ define("router", } } - - return contexts.length === 0 && found && queryParamsEqual(activeQueryParams, effectiveQueryParams); + return contexts.length === 0 && found; }, trigger: function(name) { @@ -29363,7 +29444,7 @@ define("router", a shared pivot parent route and other data necessary to perform a transition. */ - function getMatchPoint(router, handlers, objects, inputParams, queryParams) { + function getMatchPoint(router, handlers, objects, inputParams) { var matchPoint = handlers.length, providedModels = {}, i, @@ -29418,12 +29499,6 @@ define("router", } } - // If there is an old handler, see if query params are the same. If there isn't an old handler, - // hasChanged will already be true here - if(oldHandlerInfo && !queryParamsEqual(oldHandlerInfo.queryParams, handlerObj.queryParams)) { - hasChanged = true; - } - if (hasChanged) { matchPoint = i; } } @@ -29457,28 +29532,6 @@ define("router", return (typeof object === "string" || object instanceof String || !isNaN(object)); } - - - /** - @private - - This method takes a handler name and returns a list of query params - that are valid to pass to the handler or its parents - - @param {Router} router - @param {String} handlerName - @return {Array[String]} a list of query parameters - */ - function queryParamsForHandler(router, handlerName) { - var handlers = router.recognizer.handlersFor(handlerName), - queryParams = []; - - for (var i = 0; i < handlers.length; i++) { - queryParams.push.apply(queryParams, handlers[i].queryParams || []); - } - - return queryParams; - } /** @private @@ -29490,17 +29543,13 @@ define("router", @param {Array[Object]} objects @return {Object} a serialized parameter hash */ - function paramsForHandler(router, handlerName, objects, queryParams) { + function paramsForHandler(router, handlerName, objects) { var handlers = router.recognizer.handlersFor(handlerName), params = {}, - handlerInfos = generateHandlerInfosWithQueryParams(router, handlers, queryParams), - matchPoint = getMatchPoint(router, handlerInfos, objects).matchPoint, - mergedQueryParams = {}, + matchPoint = getMatchPoint(router, handlers, objects).matchPoint, object, handlerObj, handler, names, i; - params.queryParams = {}; - for (i=0; i<handlers.length; i++) { handlerObj = handlers[i]; handler = router.getHandler(handlerObj.handler); @@ -29519,13 +29568,7 @@ define("router", // Serialize to generate params merge(params, serialize(handler, object, names)); } - if (queryParams !== false) { - mergeSomeKeys(params.queryParams, router.currentQueryParams, handlerObj.queryParams); - mergeSomeKeys(params.queryParams, queryParams, handlerObj.queryParams); - } } - - if (queryParamsEqual(params.queryParams, {})) { delete params.queryParams; } return params; } @@ -29535,84 +29578,24 @@ define("router", } } - function mergeSomeKeys(hash, other, keys) { - if (!other || !keys) { return; } - for(var i = 0; i < keys.length; i++) { - var key = keys[i], value; - if(other.hasOwnProperty(key)) { - value = other[key]; - if(value === null || value === false || typeof value === "undefined") { - delete hash[key]; - } else { - hash[key] = other[key]; - } - } - } - } - - /** - @private - */ - - function generateHandlerInfosWithQueryParams(router, handlers, queryParams) { - var handlerInfos = []; - - for (var i = 0; i < handlers.length; i++) { - var handler = handlers[i], - handlerInfo = { handler: handler.handler, names: handler.names, context: handler.context, isDynamic: handler.isDynamic }, - activeQueryParams = {}; - - if (queryParams !== false) { - mergeSomeKeys(activeQueryParams, router.currentQueryParams, handler.queryParams); - mergeSomeKeys(activeQueryParams, queryParams, handler.queryParams); - } - - if (handler.queryParams && handler.queryParams.length > 0) { - handlerInfo.queryParams = activeQueryParams; - } - - handlerInfos.push(handlerInfo); - } - - return handlerInfos; - } - - /** - @private - */ - function createQueryParamTransition(router, queryParams) { - var currentHandlers = router.currentHandlerInfos, - currentHandler = currentHandlers[currentHandlers.length - 1], - name = currentHandler.name; - - log(router, "Attempting query param transition"); - - return createNamedTransition(router, [name, queryParams]); - } - /** @private */ function createNamedTransition(router, args) { - var partitionedArgs = extractQueryParams(args), - pureArgs = partitionedArgs[0], - queryParams = partitionedArgs[1], - handlers = router.recognizer.handlersFor(pureArgs[0]), - handlerInfos = generateHandlerInfosWithQueryParams(router, handlers, queryParams); + var handlers = router.recognizer.handlersFor(args[0]); + log(router, "Attempting transition to " + args[0]); - log(router, "Attempting transition to " + pureArgs[0]); - - return performTransition(router, handlerInfos, slice.call(pureArgs, 1), router.currentParams, queryParams); + return performTransition(router, handlers, slice.call(args, 1), router.currentParams); } /** @private */ function createURLTransition(router, url) { + var results = router.recognizer.recognize(url), - currentHandlerInfos = router.currentHandlerInfos, - queryParams = {}; + currentHandlerInfos = router.currentHandlerInfos; log(router, "Attempting URL transition to " + url); @@ -29620,11 +29603,7 @@ define("router", return errorTransition(router, new Router.UnrecognizedURLError(url)); } - for(var i = 0; i < results.length; i++) { - merge(queryParams, results[i].queryParams); - } - - return performTransition(router, results, [], {}, queryParams); + return performTransition(router, results, [], {}); } @@ -29708,9 +29687,8 @@ define("router", checkAbort(transition); setContext(handler, context); - setQueryParams(handler, handlerInfo.queryParams); - if (handler.setup) { handler.setup(context, handlerInfo.queryParams); } + if (handler.setup) { handler.setup(context); } checkAbort(transition); } catch(e) { if (!(e instanceof Router.TransitionAborted)) { @@ -29741,29 +29719,6 @@ define("router", } } - /** - @private - - determines if two queryparam objects are the same or not - **/ - function queryParamsEqual(a, b) { - a = a || {}; - b = b || {}; - var checkedKeys = [], key; - for(key in a) { - if (!a.hasOwnProperty(key)) { continue; } - if(b[key] !== a[key]) { return false; } - checkedKeys.push(key); - } - for(key in b) { - if (!b.hasOwnProperty(key)) { continue; } - if (~checkedKeys.indexOf(key)) { continue; } - // b has a key not in a - return false; - } - return true; - } - /** @private @@ -29813,21 +29768,19 @@ define("router", unchanged: [] }; - var handlerChanged, contextChanged, queryParamsChanged, i, l; + var handlerChanged, contextChanged, i, l; for (i=0, l=newHandlers.length; i<l; i++) { var oldHandler = oldHandlers[i], newHandler = newHandlers[i]; if (!oldHandler || oldHandler.handler !== newHandler.handler) { handlerChanged = true; - } else if (!queryParamsEqual(oldHandler.queryParams, newHandler.queryParams)) { - queryParamsChanged = true; } if (handlerChanged) { handlers.entered.push(newHandler); if (oldHandler) { handlers.exited.unshift(oldHandler); } - } else if (contextChanged || oldHandler.context !== newHandler.context || queryParamsChanged) { + } else if (contextChanged || oldHandler.context !== newHandler.context) { contextChanged = true; handlers.updatedContext.push(newHandler); } else { @@ -29880,45 +29833,21 @@ define("router", if (handler.contextDidChange) { handler.contextDidChange(); } } - function setQueryParams(handler, queryParams) { - handler.queryParams = queryParams; - if (handler.queryParamsDidChange) { handler.queryParamsDidChange(); } - } - - - /** - @private - - Extracts query params from the end of an array - **/ - - function extractQueryParams(array) { - var len = (array && array.length), head, queryParams; - - if(len && len > 0 && array[len - 1] && array[len - 1].hasOwnProperty('queryParams')) { - queryParams = array[len - 1].queryParams; - head = slice.call(array, 0, len - 1); - return [head, queryParams]; - } else { - return [array, null]; - } - } - /** @private Creates, begins, and returns a Transition. */ - function performTransition(router, recogHandlers, providedModelsArray, params, queryParams, data) { + function performTransition(router, recogHandlers, providedModelsArray, params, data) { - var matchPointResults = getMatchPoint(router, recogHandlers, providedModelsArray, params, queryParams), + var matchPointResults = getMatchPoint(router, recogHandlers, providedModelsArray, params), targetName = recogHandlers[recogHandlers.length - 1].handler, wasTransitioning = false, currentHandlerInfos = router.currentHandlerInfos; // Check if there's already a transition underway. if (router.activeTransition) { - if (transitionsIdentical(router.activeTransition, targetName, providedModelsArray, queryParams)) { + if (transitionsIdentical(router.activeTransition, targetName, providedModelsArray)) { return router.activeTransition; } router.activeTransition.abort(); @@ -29933,7 +29862,6 @@ define("router", transition.providedModelsArray = providedModelsArray; transition.params = matchPointResults.params; transition.data = data || {}; - transition.queryParams = queryParams; router.activeTransition = transition; var handlerInfos = generateHandlerInfos(router, recogHandlers); @@ -30000,16 +29928,11 @@ define("router", var handlerObj = recogHandlers[i], isDynamic = handlerObj.isDynamic || (handlerObj.names && handlerObj.names.length); - - var handlerInfo = { + handlerInfos.push({ isDynamic: !!isDynamic, name: handlerObj.handler, handler: router.getHandler(handlerObj.handler) - }; - if(handlerObj.queryParams) { - handlerInfo.queryParams = handlerObj.queryParams; - } - handlerInfos.push(handlerInfo); + }); } return handlerInfos; } @@ -30017,7 +29940,7 @@ define("router", /** @private */ - function transitionsIdentical(oldTransition, targetName, providedModelsArray, queryParams) { + function transitionsIdentical(oldTransition, targetName, providedModelsArray) { if (oldTransition.targetName !== targetName) { return false; } @@ -30027,11 +29950,6 @@ define("router", for (var i = 0, len = oldModels.length; i < len; ++i) { if (oldModels[i] !== providedModelsArray[i]) { return false; } } - - if(!queryParamsEqual(oldTransition.queryParams, queryParams)) { - return false; - } - return true; } @@ -30045,12 +29963,11 @@ define("router", var router = transition.router, seq = transition.sequence, - handlerName = handlerInfos[handlerInfos.length - 1].name, - i; + handlerName = handlerInfos[handlerInfos.length - 1].name; // Collect params for URL. var objects = [], providedModels = transition.providedModelsArray.slice(); - for (i = handlerInfos.length - 1; i>=0; --i) { + for (var i = handlerInfos.length - 1; i>=0; --i) { var handlerInfo = handlerInfos[i]; if (handlerInfo.isDynamic) { var providedModel = providedModels.pop(); @@ -30058,18 +29975,10 @@ define("router", } } - var newQueryParams = {}; - for (i = handlerInfos.length - 1; i>=0; --i) { - merge(newQueryParams, handlerInfos[i].queryParams); - } - router.currentQueryParams = newQueryParams; - - - var params = paramsForHandler(router, handlerName, objects, transition.queryParams); + var params = paramsForHandler(router, handlerName, objects); router.currentParams = params; - var urlMethod = transition.urlMethod; if (urlMethod) { var url = router.recognizer.generate(handlerName, params); @@ -30160,20 +30069,13 @@ define("router", log(router, seq, handlerName + ": calling beforeModel hook"); - var args; - - if (handlerInfo.queryParams) { - args = [handlerInfo.queryParams, transition]; - } else { - args = [transition]; - } - - var p = handler.beforeModel && handler.beforeModel.apply(handler, args); + var p = handler.beforeModel && handler.beforeModel(transition); return (p instanceof Transition) ? null : p; } function model() { log(router, seq, handlerName + ": resolving model"); + var p = getModel(handlerInfo, transition, handlerParams[handlerName], index >= matchPoint); return (p instanceof Transition) ? null : p; } @@ -30188,15 +30090,7 @@ define("router", transition.resolvedModels[handlerInfo.name] = context; - var args; - - if (handlerInfo.queryParams) { - args = [context, handlerInfo.queryParams, transition]; - } else { - args = [context, transition]; - } - - var p = handler.afterModel && handler.afterModel.apply(handler, args); + var p = handler.afterModel && handler.afterModel(context, transition); return (p instanceof Transition) ? null : p; } @@ -30227,8 +30121,9 @@ define("router", or use one of the models provided to `transitionTo`. */ function getModel(handlerInfo, transition, handlerParams, needsUpdate) { + var handler = handlerInfo.handler, - handlerName = handlerInfo.name, args; + handlerName = handlerInfo.name; if (!needsUpdate && handler.hasOwnProperty('context')) { return handler.context; @@ -30239,13 +30134,7 @@ define("router", return typeof providedModel === 'function' ? providedModel() : providedModel; } - if (handlerInfo.queryParams) { - args = [handlerParams || {}, handlerInfo.queryParams, transition]; - } else { - args = [handlerParams || {}, transition, handlerInfo.queryParams]; - } - - return handler.model && handler.model.apply(handler, args); + return handler.model && handler.model(handlerParams || {}, transition); } /** @@ -30278,12 +30167,10 @@ define("router", // Normalize blank transitions to root URL transitions. var name = args[0] || '/'; - if(args.length === 1 && args[0].hasOwnProperty('queryParams')) { - return createQueryParamTransition(router, args[0]); - } else if (name.charAt(0) === '/') { + if (name.charAt(0) === '/') { return createURLTransition(router, name); } else { - return createNamedTransition(router, slice.call(args)); + return createNamedTransition(router, args); } } @@ -30361,17 +30248,17 @@ DSL.prototype = { if (callback) { var dsl = new DSL(name); callback.call(dsl); - this.push(options.path, name, dsl.generate(), options.queryParams); + this.push(options.path, name, dsl.generate()); } else { - this.push(options.path, name, null, options.queryParams); + this.push(options.path, name); } }, - push: function(url, name, callback, queryParams) { + push: function(url, name, callback) { var parts = name.split('.'); if (url === "" || url === "/" || parts[parts.length-1] === "index") { this.explicitIndex = true; } - this.matches.push([url, name, callback, queryParams]); + this.matches.push([url, name, callback]); }, route: function(name, options) { @@ -30387,7 +30274,7 @@ DSL.prototype = { name = this.parent + "." + name; } - this.push(options.path, name, null, options.queryParams); + this.push(options.path, name); }, generate: function() { @@ -30400,12 +30287,7 @@ DSL.prototype = { return function(match) { for (var i=0, l=dslMatches.length; i<l; i++) { var dslMatch = dslMatches[i]; - var matchObj = match(dslMatch[0]).to(dslMatch[1], dslMatch[2]); - if (Ember.FEATURES.isEnabled("query-params")) { - if(dslMatch[3]) { - matchObj.withQueryParams.apply(matchObj, dslMatch[3]); - } - } + match(dslMatch[0]).to(dslMatch[1], dslMatch[2]); } }; } @@ -30710,16 +30592,12 @@ Ember.Router = Ember.Object.extend({ args = [].slice.call(args); args[0] = args[0] || '/'; - var passedName = args[0], name, self = this, - isQueryParamsOnly = false; + var passedName = args[0], name, self = this; - if (Ember.FEATURES.isEnabled("query-params")) { - isQueryParamsOnly = (args.length === 1 && args[0].hasOwnProperty('queryParams')); - } - - if (!isQueryParamsOnly && passedName.charAt(0) === '/') { + if (passedName.charAt(0) === '/') { name = passedName; - } else if (!isQueryParamsOnly) { + } else { + if (!this.router.hasRoute(passedName)) { name = args[0] = passedName + '.index'; } else { @@ -30786,7 +30664,7 @@ function triggerEvent(handlerInfos, ignoreFailure, args) { if (!handlerInfos) { if (ignoreFailure) { return; } - throw new Error("Could not trigger event '" + name + "'. There are no active handlers"); + throw new Ember.Error("Could not trigger event '" + name + "'. There are no active handlers"); } var eventWasHandled = false; @@ -30812,7 +30690,7 @@ function triggerEvent(handlerInfos, ignoreFailure, args) { } if (!eventWasHandled && !ignoreFailure) { - throw new Error("Nothing handled the event '" + name + "'."); + throw new Ember.Error("Nothing handled the event '" + name + "'."); } } @@ -31154,11 +31032,13 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { activate: Ember.K, /** - Transition into another route. Optionally supply a model for the - route in question. The model will be serialized into the URL - using the `serialize` hook. + Transition into another route. Optionally supply model(s) for the + route in question. If multiple models are supplied they will be applied + last to first recursively up the resource tree (see Multiple Models Example + below). The model(s) will be serialized into the URL using the appropriate + route's `serialize` hook. See also 'replaceWith'. - Example + Simple Transition Example ```javascript App.Router.map(function() { @@ -31179,9 +31059,31 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { }); ``` + Multiple Models Example + + ```javascript + App.Router.map(function() { + this.route("index"); + this.resource('breakfast', {path:':breakfastId'}, function(){ + this.resource('cereal', {path: ':cerealId'}); + }); + }); + + App.IndexRoute = Ember.Route.extend({ + actions: { + moveToChocolateCereal: function(){ + var cereal = { cerealId: "ChocolateYumminess"}, + breakfast = {breakfastId: "CerealAndMilk"}; + + this.transitionTo('cereal', breakfast, cereal); + } + } + }); + @method transitionTo @param {String} name the name of the route - @param {...Object} models + @param {...Object} models the model(s) to be used while transitioning + to the route. */ transitionTo: function(name, context) { var router = this.router; @@ -31189,8 +31091,10 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { }, /** - Transition into another route while replacing the current URL if - possible. Identical to `transitionTo` in all other respects. + Transition into another route while replacing the current URL, if possible. + This will replace the current history entry instead of adding a new one. + Beside that, it is identical to `transitionTo` in all other respects. See + 'transitionTo' for additional information regarding multiple models. Example @@ -31211,7 +31115,8 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @method replaceWith @param {String} name the name of the route - @param {...Object} models + @param {...Object} models the model(s) to be used while transitioning + to the route. */ replaceWith: function() { var router = this.router; @@ -31263,7 +31168,7 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @method setup */ - setup: function(context, queryParams) { + setup: function(context) { var controllerName = this.controllerName || this.routeName, controller = this.controllerFor(controllerName, true); if (!controller) { @@ -31274,37 +31179,31 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { // referenced in action handlers this.controller = controller; - var args = [controller, context]; - - if (Ember.FEATURES.isEnabled("query-params")) { - args.push(queryParams); - } - if (this.setupControllers) { this.setupControllers(controller, context); } else { - this.setupController.apply(this, args); + this.setupController(controller, context); } if (this.renderTemplates) { this.renderTemplates(context); } else { - this.renderTemplate.apply(this, args); + this.renderTemplate(controller, context); } }, /** - @deprecated - A hook you can implement to optionally redirect to another route. If you call `this.transitionTo` from inside of this hook, this route will not be entered in favor of the other hook. - This hook is deprecated in favor of using the `afterModel` hook - for performing redirects after the model has resolved. + Note that this hook is called by the default implementation of + `afterModel`, so if you override `afterModel`, you must either + explicitly call `redirect` or just put your redirecting + `this.transitionTo()` call within `afterModel`. @method redirect @param {Object} model the model for this route @@ -31382,7 +31281,6 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @method beforeModel @param {Transition} transition - @param {Object} queryParams the active query params for this route @return {Promise} if the value returned from this hook is a promise, the transition will pause until the transition resolves. Otherwise, non-promise return values are not @@ -31416,13 +31314,12 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @param {Object} resolvedModel the value returned from `model`, or its resolved value if it was a promise @param {Transition} transition - @param {Object} queryParams the active query params for this handler @return {Promise} if the value returned from this hook is a promise, the transition will pause until the transition resolves. Otherwise, non-promise return values are not utilized in any way. */ - afterModel: function(resolvedModel, transition, queryParams) { + afterModel: function(resolvedModel, transition) { this.redirect(resolvedModel, transition); }, @@ -31482,7 +31379,6 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @method model @param {Object} params the parameters extracted from the URL @param {Transition} transition - @param {Object} queryParams the query params for this route @return {Object|Promise} the model for this route. If a promise is returned, the transition will pause until the promise resolves, and the resolved value of the promise @@ -32310,40 +32206,19 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { Ember.Handlebars.normalizePath(templateContext, path, helperParameters.options.data); this.registerObserver(normalizedPath.root, normalizedPath.path, this, this._paramsChanged); } - - - if (Ember.FEATURES.isEnabled("query-params")) { - var queryParams = get(this, '_potentialQueryParams') || []; - - for(i=0; i < queryParams.length; i++) { - this.registerObserver(this, queryParams[i], this, this._queryParamsChanged); - } - } }, /** @private This method is invoked by observers installed during `init` that fire - whenever the params change + whenever the helpers @method _paramsChanged */ _paramsChanged: function() { this.notifyPropertyChange('resolvedParams'); }, - - /** - @private - - This method is invoked by observers installed during `init` that fire - whenever the query params change - */ - _queryParamsChanged: function (object, path) { - this.notifyPropertyChange('queryParams'); - }, - - /** @private @@ -32479,6 +32354,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { @return {Array} An array with the route name and any dynamic segments */ routeArgs: Ember.computed(function() { + var resolvedParams = get(this, 'resolvedParams').slice(0), router = get(this, 'router'), namedRoute = resolvedParams[0]; @@ -32497,44 +32373,9 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { } } - if (Ember.FEATURES.isEnabled("query-params")) { - var queryParams = get(this, 'queryParams'); - - if (queryParams || queryParams === false) { resolvedParams.push({queryParams: queryParams}); } - } - return resolvedParams; - }).property('resolvedParams', 'queryParams', 'router.url'), - - - _potentialQueryParams: Ember.computed(function () { - var namedRoute = get(this, 'resolvedParams')[0]; - if (!namedRoute) { return null; } - var router = get(this, 'router'); - - namedRoute = fullRouteName(router, namedRoute); - - return router.router.queryParamsForHandler(namedRoute); }).property('resolvedParams'), - queryParams: Ember.computed(function () { - var self = this, - queryParams = null, - allowedQueryParams = get(this, '_potentialQueryParams'); - - if (!allowedQueryParams) { return null; } - allowedQueryParams.forEach(function (param) { - var value = get(self, param); - if (typeof value !== 'undefined') { - queryParams = queryParams || {}; - queryParams[param] = value; - } - }); - - - return queryParams; - }).property('_potentialQueryParams.[]'), - /** Sets the element's `href` attribute to the url for the `LinkView`'s targeted route. @@ -32545,7 +32386,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { @property href **/ href: Ember.computed(function() { - if (get(this, 'tagName') !== 'a') { return false; } + if (get(this, 'tagName') !== 'a') { return; } var router = get(this, 'router'), routeArgs = get(this, 'routeArgs'); @@ -32605,7 +32446,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { To override this option for your entire application, see "Overriding Application-wide Defaults". - ### Disabling the `link-to` heper + ### Disabling the `link-to` helper By default `{{link-to}}` is enabled. any passed value to `disabled` helper property will disable the `link-to` helper. @@ -33189,16 +33030,16 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { /** The `{{action}}` helper registers an HTML element within a template for DOM - event handling and forwards that interaction to the view's controller + event handling and forwards that interaction to the templates's controller or supplied `target` option (see 'Specifying a Target'). - If the view's controller does not implement the event, the event is sent + If the controller does not implement the event, the event is sent to the current route, and it bubbles up the route hierarchy from there. User interaction with that element will invoke the supplied action name on the appropriate target. - Given the following Handlebars template on the page + Given the following application Handlebars template on the page ```handlebars <div {{action 'anActionName'}}> @@ -33209,17 +33050,13 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { And application code ```javascript - AController = Ember.Controller.extend({ - anActionName: function() {} + App.ApplicationController = Ember.Controller.extend({ + actions: { + anActionName: function() { + + } + } }); - - AView = Ember.View.extend({ - controller: AController.create(), - templateName: 'a-template' - }); - - aView = AView.create(); - aView.appendTo('body'); ``` Will result in the following rendered HTML @@ -33232,8 +33069,8 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { </div> ``` - Clicking "click me" will trigger the `anActionName` method of the - `AController`. In this case, no additional parameters will be passed. + Clicking "click me" will trigger the `anActionName` action of the + `App.ApplicationController`. In this case, no additional parameters will be passed. If you provide additional parameters to the helper: @@ -33266,11 +33103,9 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { supply an `on` option to the helper to specify a different DOM event name: ```handlebars - <script type="text/x-handlebars" data-template-name='a-template'> - <div {{action 'anActionName' on="doubleClick"}}> - click me - </div> - </script> + <div {{action "anActionName" on="doubleClick"}}> + click me + </div> ``` See `Ember.View` 'Responding to Browser Events' for a list of @@ -33288,11 +33123,9 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { keys. You can supply an `allowedKeys` option to specify which keys should not be ignored. ```handlebars - <script type="text/x-handlebars" data-template-name='a-template'> - <div {{action 'anActionName' allowedKeys="alt"}}> - click me - </div> - </script> + <div {{action "anActionName" allowedKeys="alt"}}> + click me + </div> ``` This way the `{{action}}` will fire when clicking with the alt key pressed down. @@ -33300,11 +33133,9 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { Alternatively, supply "any" to the `allowedKeys` option to accept any combination of modifier keys. ```handlebars - <script type="text/x-handlebars" data-template-name='a-template'> - <div {{action 'anActionName' allowedKeys="any"}}> - click me with any key pressed - </div> - </script> + <div {{action "anActionName" allowedKeys="any"}}> + click me with any key pressed + </div> ``` ### Specifying a Target @@ -33320,43 +33151,21 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { to an object, accessible in the current context: ```handlebars - <script type="text/x-handlebars" data-template-name='a-template'> - <div {{action 'anActionName' target="MyApplication.someObject"}}> - click me - </div> - </script> + {{! the application template }} + <div {{action "anActionName" target=view}}> + click me + </div> ``` - Clicking "click me" in the rendered HTML of the above template will trigger - the `anActionName` method of the object at `MyApplication.someObject`. - - If an action's target does not implement a method that matches the supplied - action name an error will be thrown. - - ```handlebars - <script type="text/x-handlebars" data-template-name='a-template'> - <div {{action 'aMethodNameThatIsMissing'}}> - click me - </div> - </script> - ``` - - With the following application code - ```javascript - AView = Ember.View.extend({ - templateName; 'a-template', - // note: no method 'aMethodNameThatIsMissing' - anActionName: function(event) {} + App.ApplicationView = Ember.View.extend({ + actions: { + anActionName: function(){} + } }); - aView = AView.create(); - aView.appendTo('body'); ``` - Will throw `Uncaught TypeError: Cannot call method 'call' of undefined` when - "click me" is clicked. - ### Additional Parameters You may specify additional parameters to the `{{action}}` helper. These @@ -33364,17 +33173,15 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { implementing the action. ```handlebars - <script type="text/x-handlebars" data-template-name='a-template'> - {{#each person in people}} - <div {{action 'edit' person}}> - click me - </div> - {{/each}} - </script> + {{#each person in people}} + <div {{action "edit" person}}> + click me + </div> + {{/each}} ``` - Clicking "click me" will trigger the `edit` method on the current view's - controller with the current person as a parameter. + Clicking "click me" will trigger the `edit` method on the current controller + with the value of `person` as a parameter. @method action @for Ember.Handlebars.helpers @@ -33558,8 +33365,23 @@ Ember.ControllerMixin.reopen({ aController.transitionToRoute('blogPost', aPost); ``` + Multiple models will be applied last to first recursively up the + resource tree. + + ```javascript + + this.resource('blogPost', {path:':blogPostId'}, function(){ + this.resource('blogComment', {path: ':blogCommentId'}); + }); + + aController.transitionToRoute('blogComment', aPost, aComment); + ``` + + See also 'replaceRoute'. + @param {String} name the name of the route - @param {...Object} models the + @param {...Object} models the model(s) to be used while transitioning + to the route. @for Ember.ControllerMixin @method transitionToRoute */ @@ -33581,8 +33403,9 @@ Ember.ControllerMixin.reopen({ }, /** - Alternative to `transitionToRoute`. Transition the application into another route. The route may - be either a single route or route path: + Transition into another route while replacing the current URL, if possible. + This will replace the current history entry instead of adding a new one. + Beside that, it is identical to `transitionToRoute` in all other respects. ```javascript aController.replaceRoute('blogPosts'); @@ -33597,8 +33420,21 @@ Ember.ControllerMixin.reopen({ aController.replaceRoute('blogPost', aPost); ``` + Multiple models will be applied last to first recursively up the + resource tree. + + ```javascript + + this.resource('blogPost', {path:':blogPostId'}, function(){ + this.resource('blogComment', {path: ':blogCommentId'}); + }); + + aController.replaceRoute('blogComment', aPost, aComment); + ``` + @param {String} name the name of the route - @param {...Object} models the + @param {...Object} models the model(s) to be used while transitioning + to the route. @for Ember.ControllerMixin @method replaceRoute */ @@ -34401,7 +34237,7 @@ DAG.prototype.addEdge = function(fromName, toName) { } function checkCycle(vertex, path) { if (vertex.name === toName) { - throw new Error("cycle detected: " + toName + " <- " + path.join(" <- ")); + throw new Ember.Error("cycle detected: " + toName + " <- " + path.join(" <- ")); } } visit(from, checkCycle); @@ -34839,16 +34675,15 @@ DeprecatedContainer.prototype = { example, the `keypress` event causes the `keyPress` method on the view to be called, the `dblclick` event causes `doubleClick` to be called, and so on. - If there is a browser event that Ember does not listen for by default, you - can specify custom events and their corresponding view method names by - setting the application's `customEvents` property: + If there is a bubbling browser event that Ember does not listen for by + default, you can specify custom events and their corresponding view method + names by setting the application's `customEvents` property: ```javascript App = Ember.Application.create({ customEvents: { - // add support for the loadedmetadata media - // player event - 'loadedmetadata': "loadedMetadata" + // add support for the paste event + 'paste: "paste" } }); ``` @@ -34961,7 +34796,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin `keyup`, and delegates them to your application's `Ember.View` instances. - If you would like additional events to be delegated to your + If you would like additional bubbling events to be delegated to your views, set your `Ember.Application`'s `customEvents` property to a hash containing the DOM event name as the key and the corresponding view method name as the value. For example: @@ -34969,9 +34804,8 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin ```javascript App = Ember.Application.create({ customEvents: { - // add support for the loadedmetadata media - // player event - 'loadedmetadata': "loadedMetadata" + // add support for the paste event + 'paste: "paste" } }); ``` @@ -35211,7 +35045,9 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin if (this.isDestroyed) { return; } // At this point, the App.Router must already be assigned - this.register('router:main', this.Router); + if (this.Router) { + this.register('router:main', this.Router); + } this.runInitializers(); Ember.runLoadHooks('application', this); @@ -35321,10 +35157,10 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin container = this.__container__, graph = new Ember.DAG(), namespace = this, - i, initializer; + name, initializer; - for (i=0; i<initializers.length; i++) { - initializer = initializers[i]; + for (name in initializers) { + initializer = initializers[name]; graph.addEdges(initializer.name, initializer.initialize, initializer.before, initializer.after); } @@ -35429,15 +35265,22 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin }); Ember.Application.reopenClass({ - concatenatedProperties: ['initializers'], - initializers: Ember.A(), + initializers: {}, initializer: function(initializer) { - var initializers = get(this, 'initializers'); + // If this is the first initializer being added to a subclass, we are going to reopen the class + // to make sure we have a new `initializers` object, which extends from the parent class' using + // prototypal inheritance. Without this, attempting to add initializers to the subclass would + // pollute the parent class as well as other subclasses. + if (this.superclass.initializers !== undefined && this.superclass.initializers === this.initializers) { + this.reopenClass({ + initializers: Ember.create(this.initializers) + }); + } - initializers.push(initializer); + this.initializers[initializer.name] = initializer; }, /** @@ -35626,6 +35469,8 @@ Ember.ControllerMixin.reopen({ length = get(needs, 'length'); if (length > 0) { + + verifyNeedsDependencies(this, this.container, needs); // if needs then initialize controllers proxy @@ -35635,6 +35480,11 @@ Ember.ControllerMixin.reopen({ this._super.apply(this, arguments); }, + /** + @method controllerFor + @see {Ember.Route#controllerFor} + @deprecated Use `needs` instead + */ controllerFor: function(controllerName) { return Ember.controllerFor(get(this, 'container'), controllerName);