From 5689e314c5da61fe9d6fed727391145aee4f7a2b Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 29 Oct 2013 13:01:42 -0400 Subject: [PATCH] Upgrade Ember to 1.1.2 --- .../javascripts/admin/models/api_key.js | 4 +- .../javascripts/admin/models/email_log.js | 2 + .../admin/models/staff_action_log.js | 2 + .../javascripts/discourse/models/invite.js | 5 +- .../discourse/models/notification.js | 8 +- .../javascripts/discourse/models/post.js | 2 +- .../discourse/models/post_stream.js | 8 +- .../javascripts/discourse/models/site.js | 4 +- .../routes/user_topic_list_routes.js | 2 +- .../javascripts/discourse/views/topic_view.js | 9 +- test/javascripts/admin/models/api_key_test.js | 2 +- .../mixins/selected_posts_count_test.js | 2 +- test/javascripts/models/email_log_test.js | 5 + test/javascripts/models/invite_test.js | 5 + test/javascripts/models/notification_test.js | 5 + test/javascripts/models/post_stream_test.js | 4 + test/javascripts/models/site_test.js | 7 +- .../models/staff_action_log_test.js | 5 + .../assets/javascripts/development/ember.js | 1682 ++++++++--------- vendor/assets/javascripts/production/ember.js | 1630 ++++++++-------- 20 files changed, 1565 insertions(+), 1828 deletions(-) create mode 100644 test/javascripts/models/email_log_test.js create mode 100644 test/javascripts/models/invite_test.js create mode 100644 test/javascripts/models/notification_test.js create mode 100644 test/javascripts/models/staff_action_log_test.js 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; {{#labeled-textfield value=someProperty}} First name: - {{/my-component}} + {{/labeled-textfield}} ``` ```handlebars - + @@ -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 `