Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE ember-eager-update-url] Eagerly update link-to urls #4122

Merged
merged 1 commit into from
Jan 11, 2014
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
@@ -110,3 +110,9 @@ for a detailed explanation.
a whitespace string.

Added in [#4049](https://github.com/emberjs/ember.js/pull/4049).

* `ember-eager-url-update`
Invoking (clicking) `link-to` tags will immediately update the URL
instead of waiting for the transition to run to completion, unless
the transition was aborted/redirected within the same run loop.

3 changes: 2 additions & 1 deletion features.json
Original file line number Diff line number Diff line change
@@ -14,7 +14,8 @@
"computed-read-only": null,
"composable-computed-properties": true,
"ember-routing-drop-deprecated-action-style": null,
"ember-metal-is-blank": null
"ember-metal-is-blank": null,
"ember-eager-url-update": null
},
"debugStatements": ["Ember.warn", "Ember.assert", "Ember.deprecate", "Ember.debug", "Ember.Logger.info"]
}
33 changes: 31 additions & 2 deletions packages/ember-routing/lib/helpers/link_to.js
Original file line number Diff line number Diff line change
@@ -354,11 +354,40 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) {
var router = get(this, 'router'),
routeArgs = get(this, 'routeArgs');

var transition;
if (get(this, 'replace')) {
router.replaceWith.apply(router, routeArgs);
transition = router.replaceWith.apply(router, routeArgs);
} else {
router.transitionTo.apply(router, routeArgs);
transition = router.transitionTo.apply(router, routeArgs);
}

// Schedule eager URL update, but after we've given the transition
// a chance to synchronously redirect.
if (Ember.FEATURES.isEnabled("ember-eager-url-update")) {
Ember.run.scheduleOnce('routerTransitions', this, this._eagerUpdateUrl, transition, this.get('href'));
}
},

/**
@private
*/
_eagerUpdateUrl: function(transition, href) {
if (!transition.isActive || !transition.urlMethod) {
// transition was aborted, already ran to completion,
// or it has a null url-updated method.
return;
}

// Re-use the routerjs hooks set up by the Ember router.
var routerjs = get(this, 'router.router');
if (transition.urlMethod === 'update') {
routerjs.updateURL(href);
} else if (transition.urlMethod === 'replace') {
routerjs.replaceURL(href);
}

// Prevent later update url refire.
transition.method(null);
},

/**
4 changes: 2 additions & 2 deletions packages/ember-routing/lib/system/router.js
Original file line number Diff line number Diff line change
@@ -336,7 +336,7 @@ Ember.Router = Ember.Object.extend(Ember.Evented, {
var transitionPromise = this.router[method].apply(this.router, args);

transitionPromise.then(null, function(error) {
if (error.name === "UnrecognizedURLError") {
if (error && error.name === "UnrecognizedURLError") {
Ember.assert("The URL '" + error.message + "' did not match any routes in your application");
}
}, 'Ember: Check for Router unrecognized URL error');
@@ -517,7 +517,7 @@ var defaultActionHandlers = {
return;
}

Ember.Logger.error('Error while loading route: ' + error.stack);
Ember.Logger.error('Error while loading route: ' + (error && error.stack));
},

loading: function(transition, originRoute) {
176 changes: 123 additions & 53 deletions packages/ember/tests/helpers/link_to_test.js
Original file line number Diff line number Diff line change
@@ -29,21 +29,45 @@ function checkActive(selector, active) {

}

var updateCount, replaceCount;

function sharedSetup() {
App = Ember.Application.create({
name: "App",
rootElement: '#qunit-fixture'
});

App.deferReadiness();

updateCount = replaceCount = 0;
App.Router.reopen({
location: Ember.NoneLocation.createWithMixins({
setURL: function(path) {
updateCount++;
set(this, 'path', path);
},

replaceURL: function(path) {
replaceCount++;
set(this, 'path', path);
}
})
});

Router = App.Router;
container = App.__container__;
}

function sharedTeardown() {
Ember.run(function() { App.destroy(); });
Ember.TEMPLATES = {};
}

module("The {{link-to}} helper", {
setup: function() {
Ember.run(function() {
App = Ember.Application.create({
name: "App",
rootElement: '#qunit-fixture'
});

App.deferReadiness();

App.Router.reopen({
location: 'none'
});

Router = App.Router;
sharedSetup();

Ember.TEMPLATES.app = Ember.Handlebars.compile("{{outlet}}");
Ember.TEMPLATES.index = Ember.Handlebars.compile("<h3>Home</h3>{{#link-to 'about' id='about-link'}}About{{/link-to}}{{#link-to 'index' id='self-link'}}Self{{/link-to}}");
@@ -54,17 +78,12 @@ module("The {{link-to}} helper", {
templateName: 'app'
});

container = App.__container__;

container.register('view:app', AppView);
container.register('router:main', Router);
});
},

teardown: function() {
Ember.run(function() { App.destroy(); });
Ember.TEMPLATES = {};
}
teardown: sharedTeardown
});

test("The {{link-to}} helper moves into the named route", function() {
@@ -92,25 +111,9 @@ test("The {{link-to}} helper moves into the named route", function() {
});

test("The {{link-to}} helper supports URL replacement", function() {
var setCount = 0,
replaceCount = 0;

Ember.TEMPLATES.index = Ember.Handlebars.compile("<h3>Home</h3>{{#link-to 'about' id='about-link' replace=true}}About{{/link-to}}");

Router.reopen({
location: Ember.NoneLocation.createWithMixins({
setURL: function(path) {
setCount++;
set(this, 'path', path);
},

replaceURL: function(path) {
replaceCount++;
set(this, 'path', path);
}
})
});

Router.map(function() {
this.route("about");
});
@@ -121,14 +124,14 @@ test("The {{link-to}} helper supports URL replacement", function() {
router.handleURL("/");
});

equal(setCount, 0, 'precond: setURL has not been called');
equal(updateCount, 0, 'precond: setURL has not been called');
equal(replaceCount, 0, 'precond: replaceURL has not been called');

Ember.run(function() {
Ember.$('#about-link', '#qunit-fixture').click();
});

equal(setCount, 0, 'setURL should not be called');
equal(updateCount, 0, 'setURL should not be called');
equal(replaceCount, 1, 'replaceURL should be called once');
});

@@ -1047,16 +1050,7 @@ if (Ember.FEATURES.isEnabled("query-params-new")) {
module("The {{link-to}} helper: invoking with query params", {
setup: function() {
Ember.run(function() {
App = Ember.Application.create({
name: "App",
rootElement: '#qunit-fixture'
});

App.deferReadiness();

App.Router.reopen({
location: 'none'
});
sharedSetup();

App.IndexController = Ember.Controller.extend({
queryParams: ['foo', 'bar'],
@@ -1071,18 +1065,11 @@ if (Ember.FEATURES.isEnabled("query-params-new")) {
bat: 'borf'
});

Router = App.Router;

container = App.__container__;

container.register('router:main', Router);
});
},

teardown: function() {
Ember.run(function() { App.destroy(); });
Ember.TEMPLATES = {};
}
teardown: sharedTeardown
});

test("doesn't update controller QP properties on current route when invoked", function() {
@@ -1288,3 +1275,86 @@ if (Ember.FEATURES.isEnabled("query-params-new")) {

});
}

if (Ember.FEATURES.isEnabled("ember-eager-url-update")) {
var aboutDefer;
module("The {{link-to}} helper: eager URL updating", {
setup: function() {
Ember.run(function() {
sharedSetup();

container.register('router:main', Router);

Router.map(function() {
this.route('about');
});

App.AboutRoute = Ember.Route.extend({
model: function() {
aboutDefer = Ember.RSVP.defer();
return aboutDefer.promise;
}
});

Ember.TEMPLATES.application = Ember.Handlebars.compile("{{outlet}}{{link-to 'Index' 'index' id='index-link'}}{{link-to 'About' 'about' id='about-link'}}");
});
},

teardown: function() {
sharedTeardown();
aboutDefer = null;
}
});

test("invoking a link-to with a slow promise eager updates url", function() {
bootApplication();
equal(updateCount, 0);
Ember.run(Ember.$('#about-link'), 'click');

// URL should be eagerly updated now
equal(updateCount, 1);
equal(router.get('location.path'), '/about');

// Resolve the promise.
Ember.run(aboutDefer, 'resolve');
equal(router.get('location.path'), '/about');

// Shouldn't have called update url again.
equal(updateCount, 1);
equal(router.get('location.path'), '/about');
});

test("invoking a link-to with a promise that rejects on the run loop doesn't update url", function() {
App.AboutRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.reject();
}
});

bootApplication();
Ember.run(Ember.$('#about-link'), 'click');

// Shouldn't have called update url.
equal(updateCount, 0);
equal(router.get('location.path'), '', 'url was not updated');
});

test("invoking a link-to whose transition gets aborted in will transition doesn't update the url", function() {
App.IndexRoute = Ember.Route.extend({
actions: {
willTransition: function(transition) {
ok(true, "aborting transition");
transition.abort();
}
}
});

bootApplication();
Ember.run(Ember.$('#about-link'), 'click');

// Shouldn't have called update url.
equal(updateCount, 0);
equal(router.get('location.path'), '', 'url was not updated');
});
}