Skip to content

Commit

Permalink
observable title and titleToken
Browse files Browse the repository at this point in the history
  • Loading branch information
ssendev committed Sep 6, 2015
1 parent 8f987b4 commit 218540a
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 27 deletions.
34 changes: 34 additions & 0 deletions tests/acceptance/document-title-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,37 @@ test('title updates when you switch routes', function(assert) {
assert.equal(document.title, 'Ember is omakase - Posts - My Blog');
});
});

test('titleTokens are observed', function(assert) {
assert.expect(2);

visit('/followers');

andThen(function() {
assert.equal(document.title, 'Followers: 99');

click('.test-follow-button');

andThen(function() {
assert.equal(document.title, 'Followers: 100',
'`titleToken` is observed');
});
});
});

test('titles are observed', function(assert) {
assert.expect(2);

visit('/donations');

andThen(function() {
assert.equal(document.title, 'Donations: 999');

click('.test-donate-button');

andThen(function() {
assert.equal(document.title, 'Donations: 1000',
'`title` is observed');
});
});
});
11 changes: 11 additions & 0 deletions tests/dummy/app/controllers/followers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Ember from 'ember';

export default Ember.Controller.extend({
followers: 99,

actions: {
follow: function(){
this.incrementProperty('followers');
}
}
});
2 changes: 2 additions & 0 deletions tests/dummy/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Router.map(function() {
this.route('about');
this.route('team');
this.route('friendship-status', function() {});
this.route('followers');
this.route('donations');
});

export default Router;
19 changes: 19 additions & 0 deletions tests/dummy/app/routes/donations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Ember from 'ember';

export default Ember.Route.extend({
model: function() {
return Ember.Object.create({
donations: 999
});
},

title: Ember.computed('volatileCurrentModel.donations', function() {
return 'Donations: ' + this.get('volatileCurrentModel.donations');
}),

actions: {
donate: function() {
this.get('currentModel').incrementProperty('donations');
}
}
});
9 changes: 9 additions & 0 deletions tests/dummy/app/routes/followers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Ember from 'ember';

export default Ember.Route.extend({
titleToken: Ember.computed.alias('volatileController.followers'),

title: function(tokens) {
return 'Followers: ' + tokens[0];
}
});
1 change: 1 addition & 0 deletions tests/dummy/app/templates/donations.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<button class="test-donate-button" {{action 'donate'}}>Donate</button>
1 change: 1 addition & 0 deletions tests/dummy/app/templates/followers.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<button class="test-follow-button" {{action 'follow'}}>Follow</button>
115 changes: 88 additions & 27 deletions vendor/document-title/document-title.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,21 @@ var routeProps = {
// that will be the document title. The `collectTitleTokens` action
// stops bubbling once a route is encountered that has a `title`
// defined.
title: null
title: null,

// 'volatileController' must be used instead of `controller` when creating
// a computed property for `title` or `titleToken` because `controller`
// is not observable.
volatileController: Ember.computed(function() {
return this.get('controller');
}).volatile(),

// 'volatileCurrentModel' must be used instead of `currentModel` when
// creating a computed property for `title` or `titleToken` because
// `currentModel` is not observable.
volatileCurrentModel: Ember.computed(function() {
return this.get('currentModel');
}).volatile()
};

/*
Expand All @@ -35,33 +49,13 @@ var mergedActionPropertyName = (function() {
})();

routeProps[mergedActionPropertyName] = {
collectTitleTokens: function(tokens) {
var titleToken = get(this, 'titleToken');
if (typeof titleToken === 'function') {
titleToken = titleToken.call(this, get(this, 'currentModel'));
}
collectTitleTokens: function(routes) {
routes.push(this);

if (Ember.isArray(titleToken)) {
tokens.unshift.apply(this, titleToken);
} else if (titleToken) {
tokens.unshift(titleToken);
}

// If `title` exists, it signals the end of the
// token-collection, and the title is decided right here.
// If `title` exists, it signals the end of the token-collection.
var title = get(this, 'title');
if (title) {
var finalTitle;
if (typeof title === 'function') {
finalTitle = title.call(this, tokens);
} else {
// Tokens aren't even considered... a string
// title just sledgehammer overwrites any children tokens.
finalTitle = title;
}

// Stubbable fn that sets document.title
this.router.setTitle(finalTitle);
this.router.setTitleRoutes(routes);
} else {
// Continue bubbling.
return true;
Expand All @@ -72,10 +66,77 @@ routeProps[mergedActionPropertyName] = {
Ember.Route.reopen(routeProps);

Ember.Router.reopen({
updateTitle: Ember.on('didTransition', function() {
this.send('collectTitleTokens', []);

titleTokenRoutes: Ember.A(),

titleRoute: null,

updateTitleRoutes: Ember.on('didTransition', function() {
this.send('collectTitleTokens', Ember.A());
}),

setTitleRoutes: function(routes) {
// title observer
var newTitleRoute = routes[routes.length - 1];
if (this.titleRoute !== newTitleRoute) {
if (this.titleRoute) {
this.titleRoute.removeObserver('title', this, 'titleChanged');
}
this.titleRoute = newTitleRoute;
newTitleRoute.addObserver('title', this, 'titleChanged');
}

// titleToken observers
var removedRoutes = Ember.A(), keptRoutes = Ember.A();
this.titleTokenRoutes.forEach(function(route){
(routes.contains(route) ? keptRoutes : removedRoutes).push(route);
});

this.titleTokenRoutes = routes;

removedRoutes.forEach(function (route) {
route.removeObserver('titleToken', this, 'titleChanged');
}, this);

routes.forEach(function (route) {
if (!keptRoutes.contains(route)) {
route.addObserver('titleToken', this, 'titleChanged');
}
}, this);

// update title
this.computeTitle();
},

titleChanged: function() {
Ember.run.scheduleOnce('render', this, 'computeTitle');
},

computeTitle: function() {
var title = get(this.titleRoute, 'title');

if (typeof title === 'function') {
var tokens = Ember.A();
this.titleTokenRoutes.forEach(function (route) {
var titleToken = get(route, 'titleToken');
if (typeof titleToken === 'function') {
titleToken = titleToken.call(route, get(route, 'currentModel'));
}

if (Ember.isArray(titleToken)) {
tokens.unshift.apply(route, titleToken);
} else if (titleToken) {
tokens.unshift(titleToken);
}
}, this);

title = title.call(this.titleRoute, tokens);
}

// Stubbable fn that sets document.title
this.setTitle(title);
},

setTitle: function(title) {
var renderer = this.container.lookup('renderer:-dom');

Expand Down

0 comments on commit 218540a

Please sign in to comment.