From 8fd57b354c0e46f2f732a1ff1d75a078200a6fb1 Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Tue, 26 Jun 2012 16:20:42 -0700 Subject: [PATCH 01/19] Support basic States inside of Routes --- packages/ember-routing/lib/routable.js | 16 +++++++- packages/ember-routing/lib/router.js | 17 +++++++- packages/ember-routing/tests/routable_test.js | 40 +++++++++++++++++++ packages/ember-routing/tests/router_test.js | 27 ++++++++++++- 4 files changed, 95 insertions(+), 5 deletions(-) diff --git a/packages/ember-routing/lib/routable.js b/packages/ember-routing/lib/routable.js index b5ee00df9b7..2b4f328a965 100644 --- a/packages/ember-routing/lib/routable.js +++ b/packages/ember-routing/lib/routable.js @@ -78,7 +78,7 @@ Ember.Routable = Ember.Mixin.create({ In general, this will update the browser's URL. */ updateRoute: function(manager, location) { - if (get(this, 'isLeaf')) { + if (get(this, 'isLeafRoute')) { var path = this.absoluteRoute(manager); location.setURL(path); } @@ -130,6 +130,16 @@ Ember.Routable = Ember.Mixin.create({ return typeof get(this, 'route') === 'string'; }).cacheable(), + /** + @private + + Determine if this is the last routeable state + */ + isLeafRoute: Ember.computed(function() { + if (get(this, 'isLeaf')) { return true; } + return !get(this, 'childStates').findProperty('isRoutable'); + }).cacheable(), + /** @private @@ -272,10 +282,12 @@ Ember.Routable = Ember.Mixin.create({ on the state whose path is `/posts` with the path `/2/comments`. */ routePath: function(manager, path) { - if (get(this, 'isLeaf')) { return; } + if (get(this, 'isLeafRoute')) { return; } var childStates = get(this, 'childStates'), match; + childStates = Ember.A(childStates.filterProperty('isRoutable')); + childStates = childStates.sort(function(a, b) { var aDynamicSegments = getPath(a, 'routeMatcher.identifiers.length'), bDynamicSegments = getPath(b, 'routeMatcher.identifiers.length'), diff --git a/packages/ember-routing/lib/router.js b/packages/ember-routing/lib/router.js index 318149c2cd7..7cfac7fd006 100644 --- a/packages/ember-routing/lib/router.js +++ b/packages/ember-routing/lib/router.js @@ -389,13 +389,19 @@ Ember.Router = Ember.StateManager.extend( route: function(path) { set(this, 'isRouting', true); + var routableState; + try { path = path.replace(/^(?=[^\/])/, "/"); this.send('navigateAway'); this.send('unroutePath', path); - var currentURL = get(this, 'currentState').absoluteRoute(this); + routableState = get(this, 'currentState'); + while (routableState && !routableState.get('isRoutable')) { + routableState = get(routableState, 'parentState'); + } + var currentURL = routableState ? routableState.absoluteRoute(this) : ''; var rest = path.substr(currentURL.length); this.send('routePath', rest); @@ -403,7 +409,14 @@ Ember.Router = Ember.StateManager.extend( set(this, 'isRouting', false); } - get(this, 'currentState').updateRoute(this, get(this, 'location')); + routableState = get(this, 'currentState'); + while (routableState && !routableState.get('isRoutable')) { + routableState = get(routableState, 'parentState'); + } + + if (routableState) { + routableState.updateRoute(this, get(this, 'location')); + } }, urlFor: function(path, hash) { diff --git a/packages/ember-routing/tests/routable_test.js b/packages/ember-routing/tests/routable_test.js index a81b769ea8a..8b5f18b29ca 100644 --- a/packages/ember-routing/tests/routable_test.js +++ b/packages/ember-routing/tests/routable_test.js @@ -142,6 +142,46 @@ test("when you descend into a state, the route is set", function() { router.send('ready'); }); +test("when you descend into a state, the route is set even when child states (not routes) are present", function() { + var state = Ember.Route.create({ + ready: function(manager) { + manager.transitionTo('fooChild.barChild.bazChild'); + }, + + fooChild: Ember.Route.create({ + route: 'foo', + + barChild: Ember.Route.create({ + route: 'bar', + + bazChild: Ember.Route.create({ + route: 'baz', + + basicState: Ember.State.create() + }) + }) + }) + }); + + var count = 0; + + var router = Ember.Router.create({ + root: state, + location: { + setURL: function(url) { + if (count === 0) { + equal(url, '/foo/bar/baz', "The current URL should be passed in"); + count++; + } else { + ok(false, "Should not get here"); + } + } + } + }); + + router.send('ready'); +}); + var router; var Post = { find: function(id) { diff --git a/packages/ember-routing/tests/router_test.js b/packages/ember-routing/tests/router_test.js index 6ec6034b197..7b92ff214de 100644 --- a/packages/ember-routing/tests/router_test.js +++ b/packages/ember-routing/tests/router_test.js @@ -334,7 +334,32 @@ test("should update route for redirections", function() { }) }); - router.route('/'); + Ember.run(function() { + router.route('/'); + }); equal(location.url, '/login'); }); + +test("respects initialState if leafRoute with child states", function() { + var router = Ember.Router.create({ + location: location, + namespace: namespace, + root: Ember.Route.create({ + foo: Ember.Route.create({ + route: '/foo', + + initialState: 'bar', + + bar: Ember.State.create() + }) + }) + }); + + Ember.run(function() { + router.route('/foo'); + }); + + equal(location.url, '/foo'); + equal(router.getPath('currentState.name'), 'bar'); +}); From dca2ca470ad30eaa83f43c454e19ba488fdd9b1b Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Wed, 27 Jun 2012 08:36:22 -0700 Subject: [PATCH 02/19] Don't push new history state unnecessarily - fixes #1068 --- .../lib/system/history_location.js | 2 +- .../tests/system/location_test.js | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/ember-application/lib/system/history_location.js b/packages/ember-application/lib/system/history_location.js index 827e3b58c4e..34dbdcf43b8 100644 --- a/packages/ember-application/lib/system/history_location.js +++ b/packages/ember-application/lib/system/history_location.js @@ -30,7 +30,7 @@ Ember.HistoryLocation = Ember.Object.extend({ // We only want pushState to be executed if we are passing // in a new path, otherwise a new state will be inserted // for the same path. - if (!state || (state && state.path !== path)) { + if ((!state && path !== '/') || (state && state.path !== path)) { window.history.pushState({ path: path }, null, path); } }, diff --git a/packages/ember-application/tests/system/location_test.js b/packages/ember-application/tests/system/location_test.js index e14f9feb29e..e7d314a7b1f 100644 --- a/packages/ember-application/tests/system/location_test.js +++ b/packages/ember-application/tests/system/location_test.js @@ -1,5 +1,5 @@ var locationObject; -var realPushState; +var realPushState, realHistoryState; module("Ember.Location, hash implementation", { setup: function() { @@ -64,6 +64,7 @@ test("if the URL is set, it doesn't trigger the hashchange event", function() { module("Ember.Location, history implementation", { setup: function() { + realHistoryState = window.history.state; realPushState = window.history.pushState; locationObject = Ember.Location.create({ implementation: 'history' @@ -75,6 +76,7 @@ module("Ember.Location, history implementation", { teardown: function() { window.history.pushState = realPushState; + window.history.state = realHistoryState; Ember.run(function() { locationObject.destroy(); }); @@ -130,3 +132,20 @@ test("if history is used, it triggers the popstate event", function() { window.history.back(); }); + +test("doesn't push a state if there is no state and path is '/'", function() { + expect(1); + stop(); + + var count = 0; + window.history.pushState = function(data, title, path) { + count++; + }; + + setTimeout(function() { + start(); + equal(count, 0, "pushState should not have been called"); + }, 100); + + locationObject.setURL('/'); +}); From 429fcd2b49773d537c51a09adcf545aa80df5e60 Mon Sep 17 00:00:00 2001 From: Tomhuda Katzdale Date: Wed, 27 Jun 2012 13:03:17 -0400 Subject: [PATCH 03/19] Test initial state edge cases --- .../ember-states/tests/state_manager_test.js | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/ember-states/tests/state_manager_test.js b/packages/ember-states/tests/state_manager_test.js index b7f5031e067..25311374076 100644 --- a/packages/ember-states/tests/state_manager_test.js +++ b/packages/ember-states/tests/state_manager_test.js @@ -192,6 +192,40 @@ test("it automatically transitions to a default substate specified using the ini ok(get(stateManager, 'currentState').isStart, "automatically transitions to beginning substate"); }); +test("it automatically synchronously transitions into initialState in an event", function() { + var count = 0; + + stateManager = Ember.StateManager.create({ + root: Ember.State.create({ + original: Ember.State.create({ + zomgAnEvent: function(manager) { + manager.transitionTo('nextState'); + manager.send('zomgAnotherEvent'); + } + }), + + nextState: Ember.State.create({ + initialState: 'begin', + + begin: Ember.State.create({ + zomgAnotherEvent: function(manager) { + count++; + } + }) + }) + }) + }); + + Ember.run(function() { + stateManager.transitionTo('root.original'); + }); + + Ember.run(function() { + stateManager.send('zomgAnEvent'); + equal(count, 1, "the initialState was synchronously effective"); + }); +}); + test("it automatically transitions to multiple substates specified using either start or initialState property", function() { stateManager = Ember.StateManager.create({ start: Ember.State.create({ From 50ff881bcab9e2507293b90eb9e1a73e1e328a7b Mon Sep 17 00:00:00 2001 From: gnikibog Date: Wed, 27 Jun 2012 15:27:56 -0300 Subject: [PATCH 04/19] Typo correction --- packages/ember-routing/lib/router.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ember-routing/lib/router.js b/packages/ember-routing/lib/router.js index 7cfac7fd006..0e34f3bec59 100644 --- a/packages/ember-routing/lib/router.js +++ b/packages/ember-routing/lib/router.js @@ -316,7 +316,7 @@ var get = Ember.get, getPath = Ember.getPath, set = Ember.set; {{/each}} - See Handlebars.helpers.actions for additional usage examples. + See Handlebars.helpers.action for additional usage examples. ## Changing View Hierarchy in Response To State Change From b5a5c115a83db849eb1a6bb4f022e45767ed4ab5 Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Wed, 27 Jun 2012 17:10:52 -0700 Subject: [PATCH 05/19] Fix Handlebars in builds --- Assetfile | 2 +- Rakefile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Assetfile b/Assetfile index 4a721af3080..c38d04562b0 100644 --- a/Assetfile +++ b/Assetfile @@ -85,7 +85,7 @@ end distros = { :runtime => %w(ember-metal ember-runtime), - :full => %w(handlebars ember-metal ember-runtime ember-application ember-views ember-states ember-routing ember-viewstates metamorph ember-handlebars) + :full => %w(ember-metal ember-runtime ember-application ember-views ember-states ember-routing ember-viewstates metamorph ember-handlebars) } output "dist" diff --git a/Rakefile b/Rakefile index bd7ec75546b..316f200f4fb 100644 --- a/Rakefile +++ b/Rakefile @@ -67,12 +67,12 @@ end desc "Clean build artifacts from previous builds" task :clean do puts "Cleaning build..." - pipeline.clean + rm_rf "dist" # Make sure even things RakeP doesn't know about are cleaned puts "Done" end desc "Upload latest Ember.js build to GitHub repository" -task :upload_latest => :dist do +task :upload_latest => [:clean, :dist] do uploader = setup_uploader # Upload minified first, so non-minified shows up on top From ca4476adf97e8d869609ded000e660938783040d Mon Sep 17 00:00:00 2001 From: Bradley Priest Date: Thu, 28 Jun 2012 19:12:37 +0800 Subject: [PATCH 06/19] Fix a stray variable missed in 3d5587 --- packages/ember-states/lib/state_manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ember-states/lib/state_manager.js b/packages/ember-states/lib/state_manager.js index a1d658c1258..efb024fbad0 100644 --- a/packages/ember-states/lib/state_manager.js +++ b/packages/ember-states/lib/state_manager.js @@ -593,7 +593,7 @@ Ember.StateManager = Ember.State.extend( exitStates.shift(); } - currentState.pathsCache[name] = { + currentState.pathsCache[path] = { exitStates: exitStates, enterStates: enterStates, resolveState: resolveState From 4ac56d07d602cf5281d9fd8a4f82a8fb4414b7ab Mon Sep 17 00:00:00 2001 From: Joshua Borton Date: Fri, 29 Jun 2012 14:32:22 -0400 Subject: [PATCH 07/19] Only push new history when initialURL has changed --- .../lib/system/history_location.js | 19 ++++++++++++++----- .../tests/system/location_test.js | 4 ++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/ember-application/lib/system/history_location.js b/packages/ember-application/lib/system/history_location.js index 34dbdcf43b8..e9219ed7840 100644 --- a/packages/ember-application/lib/system/history_location.js +++ b/packages/ember-application/lib/system/history_location.js @@ -7,9 +7,17 @@ var get = Ember.get, set = Ember.set; Ember.HistoryLocation = Ember.Object.extend({ init: function() { set(this, 'location', get(this, 'location') || window.location); + set(this, '_initialURL', get(this, 'location').pathname); set(this, 'callbacks', Ember.A()); }, + /** + @private + + Used to give history a starting reference + */ + _initialURL: null, + /** @private @@ -25,12 +33,13 @@ Ember.HistoryLocation = Ember.Object.extend({ Uses `history.pushState` to update the url without a page reload. */ setURL: function(path) { - var state = window.history.state; + var state = window.history.state, + initialURL = get(this, '_initialURL'); + if (path === "") { path = '/'; } - // We only want pushState to be executed if we are passing - // in a new path, otherwise a new state will be inserted - // for the same path. - if ((!state && path !== '/') || (state && state.path !== path)) { + + if ((initialURL && initialURL !== path) || (state && state.path !== path)) { + set(this, '_initialURL', null); window.history.pushState({ path: path }, null, path); } }, diff --git a/packages/ember-application/tests/system/location_test.js b/packages/ember-application/tests/system/location_test.js index e7d314a7b1f..8cc5bad425d 100644 --- a/packages/ember-application/tests/system/location_test.js +++ b/packages/ember-application/tests/system/location_test.js @@ -133,7 +133,7 @@ test("if history is used, it triggers the popstate event", function() { window.history.back(); }); -test("doesn't push a state if there is no state and path is '/'", function() { +test("doesn't push a state if path has not changed", function() { expect(1); stop(); @@ -147,5 +147,5 @@ test("doesn't push a state if there is no state and path is '/'", function() { equal(count, 0, "pushState should not have been called"); }, 100); - locationObject.setURL('/'); + locationObject.setURL(window.location.pathname); }); From 5a803bbc95691a8f3e79235af408325cd7ac7543 Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Fri, 29 Jun 2012 20:21:52 -0700 Subject: [PATCH 08/19] Fix issue with transitionTo context and initialStates --- packages/ember-routing/lib/routable.js | 5 ++- packages/ember-states/lib/state.js | 6 +++ packages/ember-states/lib/state_manager.js | 3 +- .../ember-states/tests/state_manager_test.js | 40 +++++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/packages/ember-routing/lib/routable.js b/packages/ember-routing/lib/routable.js index 2b4f328a965..312f7c4d6d0 100644 --- a/packages/ember-routing/lib/routable.js +++ b/packages/ember-routing/lib/routable.js @@ -156,9 +156,10 @@ Ember.Routable = Ember.Mixin.create({ /** @private - Check whether the route has dynamic segments + Check whether the route has dynamic segments and therefore takes + a context. */ - isDynamic: Ember.computed(function() { + hasContext: Ember.computed(function() { var routeMatcher = get(this, 'routeMatcher'); if (routeMatcher) { return routeMatcher.identifiers.length > 0; diff --git a/packages/ember-states/lib/state.js b/packages/ember-states/lib/state.js index b104b31145a..eae779e9090 100644 --- a/packages/ember-states/lib/state.js +++ b/packages/ember-states/lib/state.js @@ -135,6 +135,12 @@ Ember.State = Ember.Object.extend(Ember.Evented, return !get(this, 'childStates').length; }).cacheable(), + /** + A boolean value indicating whether the state takes a context. + By default we assume all states take contexts. + */ + hasContext: true, + /** This is the default transition event. diff --git a/packages/ember-states/lib/state_manager.js b/packages/ember-states/lib/state_manager.js index efb024fbad0..307ed8995d1 100644 --- a/packages/ember-states/lib/state_manager.js +++ b/packages/ember-states/lib/state_manager.js @@ -611,7 +611,7 @@ Ember.StateManager = Ember.State.extend( exitStates.unshift(state); } - useContext = context && (!get(state, 'isRoutable') || get(state, 'isDynamic')); + useContext = context && get(state, 'hasContext'); matchedContexts.unshift(useContext ? contexts.pop() : null); } @@ -623,6 +623,7 @@ Ember.StateManager = Ember.State.extend( state = getPath(state, 'states.'+initialState); if (!state) { break; } enterStates.push(state); + matchedContexts.push(undefined); } while (enterStates.length > 0) { diff --git a/packages/ember-states/tests/state_manager_test.js b/packages/ember-states/tests/state_manager_test.js index 25311374076..7e186b35288 100644 --- a/packages/ember-states/tests/state_manager_test.js +++ b/packages/ember-states/tests/state_manager_test.js @@ -590,6 +590,46 @@ test("multiple contexts can be provided in a single transitionTo", function() { stateManager.transitionTo('planters.nuts', { company: true }, { product: true }); }); +test("multiple contexts only apply to states that need them", function() { + expect(4); + + Ember.run(function() { + stateManager = Ember.StateManager.create({ + start: Ember.State.create(), + + parent: Ember.State.create({ + hasContext: false, + + setup: function(manager, context) { + equal(context, undefined); + }, + + child: Ember.State.create({ + setup: function(manager, context) { + equal(context, 'one'); + }, + + grandchild: Ember.State.create({ + initialState: 'greatGrandchild', + + setup: function(manager, context) { + equal(context, 'two'); + }, + + greatGrandchild: Ember.State.create({ + setup: function(manager, context) { + equal(context, undefined); + } + }) + }) + }) + }) + }); + }); + + stateManager.transitionTo('parent.child.grandchild', 'one', 'two'); +}); + test("transitionEvent is called for each nested state", function() { expect(4); From 541d085ae039a190d24213d1476de53029355bf9 Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Fri, 29 Jun 2012 20:54:45 -0700 Subject: [PATCH 09/19] Don't set empty context --- packages/ember-states/lib/state_manager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ember-states/lib/state_manager.js b/packages/ember-states/lib/state_manager.js index 307ed8995d1..c8a490f686d 100644 --- a/packages/ember-states/lib/state_manager.js +++ b/packages/ember-states/lib/state_manager.js @@ -444,7 +444,6 @@ Ember.StateManager = Ember.State.extend( send: function(event, context) { Ember.assert('Cannot send event "' + event + '" while currentState is ' + get(this, 'currentState'), get(this, 'currentState')); - if (arguments.length === 1) { context = {}; } return this.sendRecursively(event, get(this, 'currentState'), context); }, From 71d1e61c44bbc84ee2a29d7ffc1173406e83cd88 Mon Sep 17 00:00:00 2001 From: Jo Liss Date: Sat, 30 Jun 2012 21:05:45 +0200 Subject: [PATCH 10/19] Move redundant regex test into assert --- packages/ember-metal/lib/accessors.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ember-metal/lib/accessors.js b/packages/ember-metal/lib/accessors.js index 059501c4b6c..30efdd0ce5b 100644 --- a/packages/ember-metal/lib/accessors.js +++ b/packages/ember-metal/lib/accessors.js @@ -254,7 +254,8 @@ Ember.getPath = function(root, path) { Ember.setPath = function(root, path, value, tolerant) { var keyName; - if (typeof root === 'string' && IS_GLOBAL.test(root)) { + if (typeof root === 'string') { + Ember.assert("Path '" + root + "' must be global if no root is given.", IS_GLOBAL.test(root)); value = path; path = root; root = null; From 7f773de4af3f2613fa9ec86d1b0132c6bdd0fcda Mon Sep 17 00:00:00 2001 From: Jo Liss Date: Sat, 30 Jun 2012 21:07:02 +0200 Subject: [PATCH 11/19] Remove redundant check for !HAS_THIS Any string matching IS_GLOBAL cannot match HAS_THIS. --- packages/ember-metal/lib/accessors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ember-metal/lib/accessors.js b/packages/ember-metal/lib/accessors.js index 30efdd0ce5b..4f54de3faaa 100644 --- a/packages/ember-metal/lib/accessors.js +++ b/packages/ember-metal/lib/accessors.js @@ -309,5 +309,5 @@ Ember.trySetPath = function(root, path, value) { @returns Boolean */ Ember.isGlobalPath = function(path) { - return !HAS_THIS.test(path) && IS_GLOBAL.test(path); + return IS_GLOBAL.test(path); }; From 8517fe071366bf5fe97fdc957782f8d30c86ca7c Mon Sep 17 00:00:00 2001 From: Jo Liss Date: Sat, 30 Jun 2012 21:14:12 +0200 Subject: [PATCH 12/19] Remove redundant Ember.isGlobal function (use isGlobalPath instead) --- packages/ember-handlebars/lib/helpers/binding.js | 2 +- packages/ember-metal/lib/accessors.js | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/ember-handlebars/lib/helpers/binding.js b/packages/ember-handlebars/lib/helpers/binding.js index 2982952b0ca..78c8fe3e802 100644 --- a/packages/ember-handlebars/lib/helpers/binding.js +++ b/packages/ember-handlebars/lib/helpers/binding.js @@ -167,7 +167,7 @@ EmberHandlebars.registerHelper('with', function(context, options) { Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop); - if (Ember.isGlobal(path)) { + if (Ember.isGlobalPath(path)) { Ember.bind(options.data.keywords, keywordName, path); } else { normalized = normalizePath(this, path, options.data); diff --git a/packages/ember-metal/lib/accessors.js b/packages/ember-metal/lib/accessors.js index 4f54de3faaa..86529e0eca8 100644 --- a/packages/ember-metal/lib/accessors.js +++ b/packages/ember-metal/lib/accessors.js @@ -186,11 +186,6 @@ function normalizeTuple(target, path) { return TUPLE_RET; } -/** @private */ -Ember.isGlobal = function(path) { - return IS_GLOBAL.test(path); -}; - /** @private From 4484ae4987e645eba0efde56c44b87656eb0df03 Mon Sep 17 00:00:00 2001 From: Tom Dale Date: Tue, 3 Jul 2012 09:52:16 -0700 Subject: [PATCH 13/19] Views should inherit parent context if available This fixes an issue where views created inside templates were inheriting the nearest controller rather than the parent context. Now, they inherit context unless a controller is explicitly set. --- .../tests/helpers/each_test.js | 29 +++++++++++++++ packages/ember-views/lib/views/view.js | 35 ++++++++++--------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/packages/ember-handlebars/tests/helpers/each_test.js b/packages/ember-handlebars/tests/helpers/each_test.js index eac75615d66..49011be231b 100644 --- a/packages/ember-handlebars/tests/helpers/each_test.js +++ b/packages/ember-handlebars/tests/helpers/each_test.js @@ -191,4 +191,33 @@ if (Ember.VIEW_PRESERVES_CONTEXT) { equal(view.$().text(), "My Cool Each Test 1My Cool Each Test 2"); }); + test("views inside #each preserve the new context", function() { + var controller = Ember.A([ { name: "Adam" }, { name: "Steve" } ]); + + view = Ember.View.create({ + controller: controller, + template: templateFor('{{#each controller}}{{#view}}{{name}}{{/view}}{{/each}}') + }); + + append(view); + + equal(view.$().text(), "AdamSteve"); + }); + + test("views inside #each use a controller as their context if set explicitly", function() { + var controller = Ember.A([ { name: "Adam" }, { name: "Steve" } ]); + + view = Ember.View.create({ + controller: controller, + template: templateFor('{{#each controller}}{{#view view.itemView}}{{name}}{{/view}}{{/each}}'), + + itemView: Ember.View.extend({ + controller: { name: "Ryan" } + }) + }); + + append(view); + + equal(view.$().text(), "RyanRyan"); + }); } diff --git a/packages/ember-views/lib/views/view.js b/packages/ember-views/lib/views/view.js index 4bb34392fa0..883e34c4f00 100644 --- a/packages/ember-views/lib/views/view.js +++ b/packages/ember-views/lib/views/view.js @@ -27,6 +27,17 @@ var childViewsProperty = Ember.computed(function() { return ret; }).property().cacheable(); +var controllerProperty = Ember.computed(function(key, value) { + var parentView; + + if (arguments.length === 2) { + return value; + } else { + parentView = get(this, 'parentView'); + return parentView ? get(parentView, 'controller') : null; + } +}).property().cacheable(); + var VIEW_PRESERVES_CONTEXT = Ember.VIEW_PRESERVES_CONTEXT; Ember.warn("The way that the {{view}} helper affects templates is about to change. Previously, templates inside child views would use the new view as the context. Soon, views will preserve their parent context when rendering their template. You can opt-in early to the new behavior by setting `ENV.VIEW_PRESERVES_CONTEXT = true`. For more information, see https://gist.github.com/2494968. You should update your templates as soon as possible; this default will change soon, and the option will be eliminated entirely before the 1.0 release.", VIEW_PRESERVES_CONTEXT); @@ -495,17 +506,7 @@ Ember.View = Ember.Object.extend(Ember.Evented, @type Object */ - controller: Ember.computed(function(key, value) { - var parentView; - - if (arguments.length === 2) { - return value; - } else { - parentView = get(this, 'parentView'); - return parentView ? get(parentView, 'controller') : null; - } - }).property().cacheable(), - + controller: controllerProperty, /** A view may contain a layout. A layout is a regular template but supersedes the `template` property during rendering. It is the @@ -570,20 +571,22 @@ Ember.View = Ember.Object.extend(Ember.Evented, to be re-rendered. */ _context: Ember.computed(function(key, value) { - var parentView, controller; + var parentView, context; if (arguments.length === 2) { return value; } if (VIEW_PRESERVES_CONTEXT) { - if (controller = get(this, 'controller')) { - return controller; + if (Ember.meta(this).descs.controller !== controllerProperty) { + if (context = get(this, 'controller')) { + return context; + } } parentView = get(this, '_parentView'); - if (parentView) { - return get(parentView, '_context'); + if (parentView && (context = get(parentView, '_context'))) { + return context; } } From 3097ea8f12b53f6e34d559d22f85d81d2f857b1b Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Wed, 4 Jul 2012 14:42:40 -0700 Subject: [PATCH 14/19] Fix tests for git version of jQuery --- .../tests/views/collection_view_test.js | 16 ++++++++++++++-- .../tests/views/view/view_lifecycle_test.js | 7 +++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/ember-handlebars/tests/views/collection_view_test.js b/packages/ember-handlebars/tests/views/collection_view_test.js index cb043f084c1..7ccfcf0e1a5 100644 --- a/packages/ember-handlebars/tests/views/collection_view_test.js +++ b/packages/ember-handlebars/tests/views/collection_view_test.js @@ -172,7 +172,12 @@ test("a block passed to a collection helper defaults to the content property of view.appendTo('#qunit-fixture'); }); - equal(view.$('li:has(label:contains("foo")) + li:has(label:contains("bar")) + li:has(label:contains("baz"))').length, 1, 'one label element is created for each content item'); + equal(view.$('li:nth-child(1) label').length, 1); + equal(view.$('li:nth-child(1) label').text(), 'foo'); + equal(view.$('li:nth-child(2) label').length, 1); + equal(view.$('li:nth-child(2) label').text(), 'bar'); + equal(view.$('li:nth-child(3) label').length, 1); + equal(view.$('li:nth-child(3) label').text(), 'baz'); }); test("a block passed to a collection helper defaults to the view", function() { @@ -188,7 +193,14 @@ test("a block passed to a collection helper defaults to the view", function() { Ember.run(function() { view.appendTo('#qunit-fixture'); }); - equal(view.$('li:has(label:contains("foo")) + li:has(label:contains("bar")) + li:has(label:contains("baz"))').length, 1, 'precond - one aside element is created for each content item'); + + // Preconds + equal(view.$('li:nth-child(1) label').length, 1); + equal(view.$('li:nth-child(1) label').text(), 'foo'); + equal(view.$('li:nth-child(2) label').length, 1); + equal(view.$('li:nth-child(2) label').text(), 'bar'); + equal(view.$('li:nth-child(3) label').length, 1); + equal(view.$('li:nth-child(3) label').text(), 'baz'); Ember.run(function() { set(firstChild(view), 'content', Ember.A()); diff --git a/packages/ember-views/tests/views/view/view_lifecycle_test.js b/packages/ember-views/tests/views/view/view_lifecycle_test.js index f58ab54b312..eed60870d8c 100644 --- a/packages/ember-views/tests/views/view/view_lifecycle_test.js +++ b/packages/ember-views/tests/views/view/view_lifecycle_test.js @@ -151,8 +151,11 @@ test("rerender should work inside a template", function() { } finally { Ember.TESTING_DEPRECATION = false; } - ok(view.$('div:contains(2), div:contains(Inside child2').length === 2, - "Rerendering a view causes it to rerender"); + + equal(view.$('div:nth-child(1)').length, 1); + equal(view.$('div:nth-child(1)').text(), '2'); + equal(view.$('div:nth-child(2)').length, 1); + equal(view.$('div:nth-child(2)').text(), 'Inside child2'); }); module("views/view/view_lifecycle_test - in DOM", { From 4893a4ae84288f3d9a2ff3f3726ab089df54d3b1 Mon Sep 17 00:00:00 2001 From: pangratz Date: Wed, 4 Jul 2012 19:19:00 +0200 Subject: [PATCH 15/19] Remove data-tag-name "feature" from '); - - Ember.run(function() { - Ember.Handlebars.bootstrap(Ember.$('#qunit-fixture')); - Tobias = Ember.Object.create({ - firstName: 'Tobias', - drug: 'teamocil' - }); - }); - - equal(Ember.$('#qunit-fixture h1').text(), 'Tobias takes teamocil', 'template is rendered inside custom tag'); -}); - test('template with data-element-id should add an id attribute to the view', function() { Ember.$('#qunit-fixture').html(''); From a71efcd69b5a5bd6d202fcddfb89e539ae4803c1 Mon Sep 17 00:00:00 2001 From: Jo Liss Date: Sat, 30 Jun 2012 21:28:27 +0200 Subject: [PATCH 16/19] Remove old ember_assert call Ember is still undefined, so I'm deleting it instead of using Ember.assert. --- benchmarks/iframe_runner.js | 1 - 1 file changed, 1 deletion(-) diff --git a/benchmarks/iframe_runner.js b/benchmarks/iframe_runner.js index 9c08d0c1fd1..4c5bdbe6971 100644 --- a/benchmarks/iframe_runner.js +++ b/benchmarks/iframe_runner.js @@ -10,7 +10,6 @@ BenchWarmer.evalString = function(string, emberPath, logger, profile) { var benchWarmer = new BenchWarmer(emberPath, logger, profile); var bench = function(name, fn) { - ember_assert("Please pass in a name and function", arguments.length === 2); benchWarmer.bench(name, fn); }; From 51f7e5d38bf39f8b108df8f3dd47be5dccb9d8fd Mon Sep 17 00:00:00 2001 From: Jo Liss Date: Thu, 5 Jul 2012 01:37:16 +0200 Subject: [PATCH 17/19] Provide useful error message when {{action}} target is missing --- packages/ember-handlebars/lib/helpers/action.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ember-handlebars/lib/helpers/action.js b/packages/ember-handlebars/lib/helpers/action.js index 14e532aa9ca..7384ee1faac 100644 --- a/packages/ember-handlebars/lib/helpers/action.js +++ b/packages/ember-handlebars/lib/helpers/action.js @@ -19,6 +19,7 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont if (target.isState && typeof target.send === 'function') { return target.send(actionName, event); } else { + Ember.assert(Ember.String.fmt('Target %@ does not have action %@', [target, actionName]), target[actionName]); return target[actionName].call(target, event); } } From 1c6d434493d566f02cd89c871dd6b3ba3789ffde Mon Sep 17 00:00:00 2001 From: Jo Liss Date: Sat, 30 Jun 2012 21:37:59 +0200 Subject: [PATCH 18/19] Add README for how to run benchmarks --- benchmarks/README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 benchmarks/README.md diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 00000000000..fbd0bf97734 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,7 @@ +# Extremely simple Ember benchmarks + +To run the benchmarks, serve the repository root on a web server (`gem install +asdf; asdf`), run `rake` to build Ember, and open e.g. +`http://localhost:9292/benchmarks/index.html?suitePath=plain_object.js` to run +`benchmarks/suites/plain_object.js`. Run `cp -r dist distold` to benchmark +different versions against each other. From 3fb87cf07765307ab6082436abe35bd437135116 Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Thu, 5 Jul 2012 09:08:07 -0700 Subject: [PATCH 19/19] Prevent proxies from being set to themselves --- packages/ember-runtime/lib/system/array_proxy.js | 4 ++++ packages/ember-runtime/lib/system/object_proxy.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/ember-runtime/lib/system/array_proxy.js b/packages/ember-runtime/lib/system/array_proxy.js index 3639cd075d7..a0db21adfb8 100644 --- a/packages/ember-runtime/lib/system/array_proxy.js +++ b/packages/ember-runtime/lib/system/array_proxy.js @@ -130,6 +130,8 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray, var content = get(this, 'content'), len = content ? get(content, 'length') : 0; + Ember.assert("Can't set ArrayProxy's content to itself", content !== this); + if (content) { content.addArrayObserver(this, { willChange: 'contentArrayWillChange', @@ -156,6 +158,8 @@ Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray, var arrangedContent = get(this, 'arrangedContent'), len = arrangedContent ? get(arrangedContent, 'length') : 0; + Ember.assert("Can't set ArrayProxy's content to itself", arrangedContent !== this); + if (arrangedContent) { arrangedContent.addArrayObserver(this, { willChange: 'arrangedContentArrayWillChange', diff --git a/packages/ember-runtime/lib/system/object_proxy.js b/packages/ember-runtime/lib/system/object_proxy.js index e435ff2ada5..dee42655adb 100644 --- a/packages/ember-runtime/lib/system/object_proxy.js +++ b/packages/ember-runtime/lib/system/object_proxy.js @@ -141,6 +141,10 @@ Ember.ObjectProxy = Ember.Object.extend( */ content: null, /** @private */ + _contentDidChange: Ember.observer(function() { + Ember.assert("Can't set ObjectProxy's content to itself", this.get('content') !== this); + }, 'content'), + /** @private */ delegateGet: function (key) { var content = get(this, 'content'); if (content) {