Skip to content

Commit

Permalink
Add support for promise titleTokens (#55)
Browse files Browse the repository at this point in the history
* Add support for promise titleTokens

* Add documentation for promise titleTokens feature
  • Loading branch information
poohitan authored and kimroen committed Sep 4, 2017
1 parent 9aa2f09 commit b50c204
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 10 deletions.
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,50 @@ This will result in these titles:
- On /posts - "Posts - My Blog"
- On /posts/1 - "Ember is Omakase - Posts - My Blog"

### Async titles using promises
In some cases you may need `titleToken` to be got in an asynchronous way - e.g. from an async relationship of your model or from IndexedDB. To achive this you can return a promise from the `titleToken()` function. This promise should resolve with a string value which will be used as `titleToken`.

Let's say we have these two models:

`models/user.js`
```js
export default DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
```

`models/post.js`
```js
export default DS.Model.extend({
name: DS.attr('string'),
author: DS.belongsTo('user', { async: true })
});
```

And we want to use both post name and it's author's name in the post title. As you can see `user` relationship is async, so `model.get('user')` will return a promise.

```js
// routes/post.js
export default Ember.Route.extend({
titleToken: function(model) {
var postName = model.get('name');

return model.get('user')
.then(function (user) {
var authorName = user.get('firstName') + user.get('lastName');

return postName + '(by' + authorName + ')';
});
}
});
```

With the same configuration for `Posts` and `Application` routes as in the previous example, this will result in this title:
- On /posts/1 - "Ember is Omakase (by John Smith) - Posts - My Blog"

Please pay attention, that page title will be unset until all promises from the `titleToken` chain resolve.

### Use with `ember-cli-head`

Using `ember-cli-document-title` with [ember-cli-head](https://github.com/ronco/ember-cli-head)
Expand Down
15 changes: 15 additions & 0 deletions tests/acceptance/document-title-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,18 @@ test('title updates when you switch routes', function(assert) {
assert.equal(document.title, 'Ember is omakase - Posts - My Blog');
});
});

test('promise title is set after promise is resolved', function(assert) {
assert.expect(1);

var done = assert.async();

visit('/promise');

andThen(function() {
setTimeout(function () {
assert.equal(document.title, 'This title is as async as possible - My Blog');
done();
}, 4000);
});
});
1 change: 1 addition & 0 deletions tests/dummy/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Router.map(function() {
this.route('team');
this.route('candy');
this.route('friendship-status', function() {});
this.route('promise');
});

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

export default Ember.Route.extend({
titleToken() {
return new Ember.RSVP.Promise(function(resolve) {
setTimeout(function () {
resolve('This title is as async as possible');
}, 3000);
});
}
});
31 changes: 21 additions & 10 deletions vendor/document-title/document-title.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var get = Ember.get;
var getOwner = Ember.getOwner;
var Promise = Ember.RSVP.Promise;

var routeProps = {
// `titleToken` can either be a static string or a function
Expand Down Expand Up @@ -52,17 +53,27 @@ routeProps[mergedActionPropertyName] = {
// token-collection, and the title is decided right here.
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;
}
var self = this;

// Stubbable fn that sets document.title
this.router.setTitle(finalTitle);
// Wrap in promise in case some tokens are asynchronous.
Promise.resolve()
.then(function() {
if (typeof title === 'function') {
// Wait for all tokens to resolve. It resolves immediately if all tokens are plain values (not promises).
return Promise.all(tokens)
.then(function(resolvedTokens) {
return title.call(self, resolvedTokens);
});
} else {
// Tokens aren't even considered... a string
// title just sledgehammer overwrites any children tokens.
return title;
}
})
.then(function(finalTitle) {
// Stubbable fn that sets document.title
self.router.setTitle(finalTitle);
});
} else {
// Continue bubbling.
return true;
Expand Down

0 comments on commit b50c204

Please sign in to comment.