Instantly share code, notes, and snippets.
Created
November 11, 2021 09:53
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save MrChocolatine/76f658283a74a083048d3b40f1ff0875 to your computer and use it in GitHub Desktop.
Ember.js – Routes decorator `@breadcrumb`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { inject as service } from '@ember/service' | |
import { computed } from '@ember/object' | |
/** | |
* Decorator `@breadcrumb()` | |
* | |
* Allow to customise the breadcrumb of your application, for a specific Route, with an automatic | |
* translation based on its `routeName` or other custom parameters. | |
* | |
* ## Usage | |
* | |
* **Note:** | |
* While the recommended way to use it is with Class-based components, you can still use it with | |
* Classic Components by using the following syntax: | |
* | |
* *You can also have a look at its tests file.* | |
* | |
* ```js | |
* import Route from '@ember/routing/route' | |
* | |
* // Without argument | |
* export default breadcrumb()( | |
* Route.extend({ ... }) | |
* ) | |
* | |
* // With an argument | |
* export default breadcrumb(null)( | |
* Route.extend({ ... }) | |
* ) | |
* ``` | |
* | |
* | |
* ### Basic usage | |
* | |
* Consider a Route `/app/routes/aa/bb.js`, the translation path used will be `aa.bb.breadCrumb`. | |
* | |
* ```js | |
* import Route from '@ember/routing/route' | |
* | |
* @breadcrumb() | |
* export default class MyRoute extends Route { ... } | |
* ``` | |
* | |
* | |
* ### Disable the breadcrumb | |
* | |
* Pass `null` to disable the breadcrumb for a Route (no text will be generated). | |
* | |
* ```js | |
* import Route from '@ember/routing/route' | |
* | |
* @breadcrumb(null) | |
* export default class MyRoute extends Route { ... } | |
* ``` | |
* | |
* | |
* ### Use a custom string | |
* | |
* Pass a string (that is not a translation path) to show it in the breadcrumb. | |
* | |
* ```js | |
* import Route from '@ember/routing/route' | |
* | |
* @breadcrumb('My text in the breadcrumb') | |
* export default class MyRoute extends Route { ... } | |
* ``` | |
* | |
* | |
* ### Use a custom translation path | |
* | |
* Pass an existing translation path to use it in the breadcrumb. | |
* | |
* ```yaml | |
* my: | |
* custom-l10n: | |
* path: Hello | |
* ``` | |
* | |
* ```js | |
* import Route from '@ember/routing/route' | |
* | |
* @breadcrumb('my.custom-l10n.path') // will show "Hello" in the breadcrumb | |
* export default class MyRoute extends Route { ... } | |
* ``` | |
* | |
* You can also take advantage from the automatic translation based on the Route's | |
* [`routeName`](https://api.emberjs.com/ember/3.16/classes/Route/properties/routeName?anchor=routeName) | |
* using the wildcard `@@`. Use this marker anywhere in your translation path to get it replaced by | |
* your Route's `routeName`. | |
* | |
* Consider a Route `/app/routes/aa/bb.js`, the translation path used will be `some.aa.bb.path`. | |
* | |
* ```js | |
* import Route from '@ember/routing/route' | |
* | |
* @breadcrumb('some.@@.path') | |
* export default class MyRoute extends Route { ... } | |
* ``` | |
* | |
* | |
* ### **Reuse data from the Route** | |
* | |
* Pass a function to get access to the Route iself. From there you can target any property of the | |
* Route you want. See usage "additional dependent keys" for another advanced use case. | |
* | |
* ```js | |
* import Route from '@ember/routing/route' | |
* | |
* @breadcrumb((route) => route.donald) // will show "duck" in the breadcrumb | |
* export default class MyRoute extends Route { | |
* | |
* donald = 'duck'; | |
* | |
* } | |
* ``` | |
* | |
* | |
* ### Recompute on additional dependent keys | |
* | |
* You can make the breadcrumb property to recompute on extra dependent keys. Simply pass the key(s) | |
* as the second argument, the same way you declare your dependent keys with | |
* [`@computed()`](https://api.emberjs.com/ember/3.16/functions/@ember%2Fobject/computed). | |
* | |
* You can combine this feature with the one for reusing data from the Route : | |
* | |
* ```js | |
* import Route from '@ember/routing/route' | |
* | |
* @breadcrumb( | |
* (route) => route.controller.model.id, | |
* 'controller.model.id', | |
* ) | |
* export default class MyRoute extends Route { | |
* | |
* // Consider a basic scenario: | |
* // 1. The model will be set on the Controller but you don't know when. | |
* // 2. Here we want to reuse a property of the model, while asking the decorator | |
* // to recompute whenever this property becomes available. | |
* | |
* } | |
* ``` | |
* | |
* @param {undefined|null|String|Function} customValue The custom value to be used | |
* @param {...String} dependentKeys Array of additional dependent keys to recompute on | |
* @return {Function} Function that will augment the target class | |
*/ | |
export const breadcrumb = function breadcrumbUtil(customValue, ...dependentKeys) { | |
return function breadcrumbDecorator(TargetClass) { | |
return TargetClass.reopen({ | |
intl: service(), | |
breadCrumb: computed('intl.locale', ...dependentKeys, function () { | |
if (customValue === null) { | |
return null | |
} | |
let title = customValue | |
if (customValue === undefined) { | |
title = this.intl.t(`${this.routeName}.breadCrumb`) | |
} else if (typeof customValue === 'function') { | |
title = customValue(this) | |
} else if (typeof customValue === 'string') { | |
// build the "real path" of the translation path | |
let realPath = customValue.replace(/@@/, this.routeName) | |
title = this.intl.exists(realPath) | |
? this.intl.t(realPath) | |
: customValue | |
} | |
return { title } | |
}) | |
}) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { breadcrumb } from 'dummy/utils/decorators/routes/breadcrumb' | |
import { module, test } from 'qunit' | |
import Route from '@ember/routing/route' | |
module('Unit | Utility | decorators/routes/breadcrumb', function(hooks) { | |
hooks.beforeEach(function() { | |
this.MyRoute = class extends Route {} | |
}) | |
hooks.afterEach(function() { | |
this.MyRoute = undefined | |
}) | |
test('it can set the breadcrumb using the route\'s name', function(assert) { | |
assert.expect(4) | |
// Pure native way to call the decorator without `@` | |
let instance = breadcrumb()(this.MyRoute).create({ | |
routeName: 'my.super.route', | |
intl: { | |
t(translationPath) { | |
assert.strictEqual( | |
translationPath, | |
'my.super.route.breadCrumb', | |
'The translation path is as expected' | |
) | |
return translationPath | |
} | |
} | |
}) | |
assert.deepEqual( | |
instance.breadCrumb, | |
{ title: 'my.super.route.breadCrumb' }, | |
'The property for the breadcrumb is as expected' | |
) | |
// --- | |
let route = Route.extend({ | |
routeName: 'my.super.route' | |
}) | |
instance = breadcrumb()(route).create({ | |
intl: { | |
t(translationPath) { | |
assert.strictEqual( | |
translationPath, | |
'my.super.route.breadCrumb', | |
'Classic Components - The translation path is as expected' | |
) | |
return translationPath | |
} | |
} | |
}) | |
assert.deepEqual( | |
instance.breadCrumb, | |
{ title: 'my.super.route.breadCrumb' }, | |
'Classic Components - The property for the breadcrumb is as expected' | |
) | |
}) | |
test('it can disable the breadcrumb', function(assert) { | |
let instance = breadcrumb(null)(this.MyRoute).create() | |
assert.strictEqual(instance.breadCrumb, null) | |
}) | |
test('it can set the breadcrumb using a custom string', function(assert) { | |
assert.expect(2) | |
let instance = breadcrumb('my custom value')(this.MyRoute).create({ | |
intl: { | |
exists(translationPath) { | |
assert.strictEqual( | |
translationPath, | |
'my custom value', | |
'The passed value is not a translation path' | |
) | |
return false | |
} | |
} | |
}) | |
assert.deepEqual( | |
instance.breadCrumb, | |
{ title: 'my custom value' }, | |
'The property for the breadcrumb is set with the custom value' | |
) | |
}) | |
test('it can set the breadcrumb using a custom translation path', function(assert) { | |
assert.expect(3) | |
let instance = breadcrumb('foo.@@.bar')(this.MyRoute).create({ | |
routeName: 'my.route-xyz.awesome', | |
intl: { | |
exists(translationPath) { | |
assert.strictEqual( | |
translationPath, | |
'foo.my.route-xyz.awesome.bar', | |
'The passed value is a translation path' | |
) | |
return true | |
}, | |
t(translationPath) { | |
assert.strictEqual( | |
translationPath, | |
'foo.my.route-xyz.awesome.bar', | |
'The translation path is as expected' | |
) | |
return translationPath | |
} | |
} | |
}) | |
assert.deepEqual( | |
instance.breadCrumb, | |
{ title: 'foo.my.route-xyz.awesome.bar' }, | |
'The property for the breadcrumb is as expected' | |
) | |
}) | |
test('it can set the breadcrumb using a property from the Route', function(assert) { | |
let instance = breadcrumb((route) => route.donald)(this.MyRoute).create({ | |
donald: 'DUCKYYY' | |
}) | |
assert.deepEqual( | |
instance.breadCrumb, | |
{ title: 'DUCKYYY' }, | |
'The property for the breadcrumb is set with a property from the Route' | |
) | |
}) | |
test('it can recompute on additional dependent keys', function(assert) { | |
let instance = breadcrumb( | |
(route) => route.controller.model, | |
'controller.model' | |
)(this.MyRoute).create({ | |
controller: { | |
model: 'version-alpha' | |
} | |
}) | |
assert.deepEqual( | |
instance.breadCrumb, | |
{ title: 'version-alpha' }, | |
'The property for the breadcrumb is set with the first value' | |
) | |
instance.set('controller.model', 'version-beta') | |
assert.deepEqual( | |
instance.breadCrumb, | |
{ title: 'version-beta' }, | |
'The property for the breadcrumb now reflects the new value' | |
) | |
}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment