From b50c204a6b8b155519beb3332fd4ffe23b708830 Mon Sep 17 00:00:00 2001 From: poohitan Date: Mon, 4 Sep 2017 22:42:34 +0300 Subject: [PATCH] Add support for promise titleTokens (#55) * Add support for promise titleTokens * Add documentation for promise titleTokens feature --- README.md | 44 +++++++++++++++++++++++++ tests/acceptance/document-title-test.js | 15 +++++++++ tests/dummy/app/router.js | 1 + tests/dummy/app/routes/promise.js | 11 +++++++ vendor/document-title/document-title.js | 31 +++++++++++------ 5 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 tests/dummy/app/routes/promise.js diff --git a/README.md b/README.md index 1691732..6a6484c 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/tests/acceptance/document-title-test.js b/tests/acceptance/document-title-test.js index f7ce671..8296e82 100644 --- a/tests/acceptance/document-title-test.js +++ b/tests/acceptance/document-title-test.js @@ -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); + }); +}); diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js index 0d41d85..cb1c7d8 100644 --- a/tests/dummy/app/router.js +++ b/tests/dummy/app/router.js @@ -11,6 +11,7 @@ Router.map(function() { this.route('team'); this.route('candy'); this.route('friendship-status', function() {}); + this.route('promise'); }); export default Router; diff --git a/tests/dummy/app/routes/promise.js b/tests/dummy/app/routes/promise.js new file mode 100644 index 0000000..8bfd0ea --- /dev/null +++ b/tests/dummy/app/routes/promise.js @@ -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); + }); + } +}); diff --git a/vendor/document-title/document-title.js b/vendor/document-title/document-title.js index e7469ba..394cdf8 100644 --- a/vendor/document-title/document-title.js +++ b/vendor/document-title/document-title.js @@ -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 @@ -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;