Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MrChocolatine/76f658283a74a083048d3b40f1ff0875 to your computer and use it in GitHub Desktop.
Save MrChocolatine/76f658283a74a083048d3b40f1ff0875 to your computer and use it in GitHub Desktop.
Ember.js – Routes decorator `@breadcrumb`
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 }
})
})
}
}
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