Composable helpers for Ember
npm install @nullvoxpopuli/ember-composable-helpersA modern fork of the original utility library.
> [!TIP]
> If you're starting a brand new project, you probably don't need (or want) this library. You can use regular JS functions in gjs and gts files.
template tag gts/gjs example:
``gjs
// note that importing from this file includes all helpers
import { mapBy, pipe, mut } from '@nullvoxpopuli/ember-composable-helpers';
{{#each (mapBy "fullName" users) as |fullName|}}
{{/each}}
`
If you only want one (or a few) utilit(y|ies), you may be interested in the individual exports instead:
`js`
import { mapBy } from '@nullvoxpopuli/ember-composable-helpers/helpers/map-by';ember-composable-helpers
these are all the same paths as provided by the original
class example:
`hbs`
{{#each (map-by "fullName" users) as |fullName|}}
{{/each}}
To install:
Ember 3.28+:
`no-highlight`
npm add @nullvoxpopuli/ember-composable-helpers
If you're using ember-source < 4.5, you may need a polyfill.
To use in an existing project, to replace the original ember-composable-helpers:
under webpack, configure an alias:
`js`
resolve: {
alias: {
"ember-composable-helpers": "@nullvoxpopuli/ember-composable-helpers",
},
},
under classic builds, using ember-auto-import, the alias would be configured this way:
`js`
// ember-cli-build.js
autoImport: {
alias: {
"ember-composable-helpers": "@nullvoxpopuli/ember-composable-helpers",
},
},
Note that under auto-import, all helpers will be included.
Under embroider, when static helpers/components are turned on, only what you use will be in the build output.
For Assessing impact, the whole library is 38K (no min, no gzip or brotly)
`bash`
❯ du --depth 1 --reverse --apparent-size --no-percent-bars --filter ".js$" ember-composable-helpers/dist/
38K └─┬ dist
26K ├── helpers
4.4K ├── utils
3.8K ├── _app_
2.5K ├── index.js
1003B └── -private
Running thruogh terser, the whole library becames 24K
`bash`
❯ du --depth 1 --reverse --apparent-size --no-percent-bars --filter ".min$" ember-composable-helpers/dist/
24K └─┬ dist
15K ├── helpers
3.5K ├── _app_
2.6K ├── utils
2.2K ├── index.js.min
658B └── -private
And then running through gzip (build -> min -> gzip)
`bash`
❯ du --depth 1 --reverse --apparent-size --no-percent-bars --filter ".gz$" ember-composable-helpers/dist/
17K └─┬ dist
10K ├── helpers
4.7K ├── _app_
1.3K ├── utils
443B ├── -private
416B └── index.js.min.gzip
And then running through brotly (build -> min -> brotli)
`bash`
❯ du --depth 1 --reverse --apparent-size --no-percent-bars --filter ".br$" ember-composable-helpers/dist/
13K └─┬ dist
8.3K ├── helpers
3.4K ├── _app_
1.0K ├── utils
370B ├── index.js.min.br
295B └── -private
* [ember-source][gh-ember-source] v3.28+
* [typescript][gh-typescript] v4.8+
* [ember-auto-import][gh-ember-auto-import] v2+
This addon is built with _composability_ in mind, and in order to faciliate that,
the ordering of arguments is somewhat different then you might be used to.
For all non-unary helpers, the subject of the helper function will always be the last argument.
This way the arguments are better readable if you compose together multiple helpers:
`hbs`
{{take 5 (sort-by "lastName" "firstName" (filter-by "active" array))}}
For action helpers, this will mean better currying semantics:
`hbs`
#### pipe
Pipes the return values of actions in a sequence of actions. This is useful to compose a pipeline of actions, so each action can do only one thing.
`hbs`
The pipe helper is Promise-aware, meaning that if any action in the pipeline returns a Promise, its return value will be piped into the next action. If the Promise rejects, the rest of the pipeline will be aborted.
The pipe helper can also be used directly as a closure action (using pipe-action) when being passed into a Component, which provides an elegant syntax for composing actions:
`hbs`
{{foo-bar
addAndSquare=(pipe-action (action "add") (action "square"))
multiplyAndSquare=(pipe-action (action "multiply") (action "square"))
}}
`hbs`
{{! foo-bar/template.hbs }}
#### call
Calls the given function with arguments
`hbs`
{{#each (call (fn this.callMeWith @daysInMonth) as |week|}}
{{#each week as |day|}}
{{day}}
{{/each}}
{{/each}}
#### compute
Calls an action as a template helper.
`hbs`
The square of 4 is {{compute (action "square") 4}}
#### toggle
Toggles a boolean value.
`hbs`
toggle can also be used directly as a closure action using toggle-action:
`hbs`
{{foo-bar
toggleIsExpanded=(toggle-action "isExpanded" this)
toggleIsSelected=(toggle-action "isSelected" this)
}}
`hbs`
{{! foo-bar/template.hbs }}
toggle also accepts optional values to rotate through:
`hbs`
#### noop
Returns an empty function.
`hbs`Some content
#### optional
Allows for the passed in action to not exist.
`hbs`
#### queue
Like pipe, this helper runs actions in a sequence (from left-to-right). The
difference is that this helper passes the original arguments to each action, not
the result of the previous action in the sequence.
If one of the actions in the sequence returns a promise, then it will wait for
that promise to resolve before calling the next action in the sequence. If a
promise is rejected it will stop the sequence and no further actions will be
called.
`hbs`
---
#### map
Maps a callback on an array.
`hbs`
{{#each (map (action "getName") users) as |fullName|}}
{{fullName}}
{{/each}}
#### map-by
Maps an array on a property.
`hbs`
{{#each (map-by "fullName" users) as |fullName|}}
{{fullName}}
{{/each}}
#### sort-by
Sort an array by given properties.
`hbs`
{{#each (sort-by "lastName" "firstName" users) as |user|}}
{{user.lastName}}, {{user.firstName}}
{{/each}}
You can append :desc to properties to sort in reverse order.
`hbs`
{{#each (sort-by "age:desc" users) as |user|}}
{{user.firstName}} {{user.lastName}} ({{user.age}})
{{/each}}
You can also pass a method as the first argument:
`hbs`
{{#each (sort-by (action "mySortAction") users) as |user|}}
{{user.firstName}} {{user.lastName}} ({{user.age}})
{{/each}}
#### filter
Filters an array by a callback.
`hbs`
{{#each (filter (action "isActive") users) as |user|}}
{{user.name}} is active!
{{/each}}
#### filter-by
Filters an array by a property.
`hbs`
{{#each (filter-by "isActive" true users) as |user|}}
{{user.name}} is active!
{{/each}}
If you omit the second argument it will test if the property is truthy.
`hbs`
{{#each (filter-by "address" users) as |user|}}
{{user.name}} has an address specified!
{{/each}}
You can also pass an action as second argument:
`hbs`
{{#each (filter-by "age" (action "olderThan" 18) users) as |user|}}
{{user.name}} is older than eighteen!
{{/each}}
#### reject-by
The inverse of filter by.
`hbs`
{{#each (reject-by "isActive" true users) as |user|}}
{{user.name}} is not active!
{{/each}}
If you omit the third argument it will test if the property is falsey.
`hbs`
{{#each (reject-by "address" users) as |user|}}
{{user.name}} does not have an address specified!
{{/each}}
You can also pass an action as third argument:
`hbs`
{{#each (reject-by "age" (action "youngerThan" 18) users) as |user|}}
{{user.name}} is older than eighteen!
{{/each}}
#### find-by
Returns the first entry matching the given value.
`hbs`
{{#let (find-by 'name' lookupName people) as |person|}}
{{#if person}}
{{#link-to 'person' person}}
Click here to see {{person.name}}'s details
{{/link-to}}
{{/if}}
{{/let}}
#### intersect
Creates an array of unique values that are included in all given arrays.
`hbs`Matching skills
{{#each (intersect desiredSkills currentSkills) as |skill|}}
{{skill.name}}
{{/each}}
#### invoke
Invokes a method on an object, or on each object of an array.
`hbs`
#### union
Joins arrays to create an array of unique values. When applied to a single array, has the same behavior as uniq.
`hbs`
{{#each (union cartA cartB cartC) as |cartItem|}}
{{cartItem.price}} x {{cartItem.quantity}} for {{cartItem.name}}
{{/each}}
#### taken
Returns the first entries of a given array.
`hbs`Top 3:
{{#each (take 3 contestants) as |contestant|}}
{{contestant.rank}}. {{contestant.name}}
{{/each}}
#### dropn
Returns an array with the first entries omitted.
`hbs`Other contestants:
{{#each (drop 3 contestants) as |contestant|}}
{{contestant.rank}}. {{contestant.name}}
{{/each}}
#### reduce
Reduce an array to a value.
`hbs`
{{reduce (action "sum") 0 (array 1 2 3)}}
The last argument is initial value. If you omit it, undefined will be used.
#### repeatn
Repeats times. This can be useful for making an n-length arbitrary list for iterating upon (you can think of this form as a times helper, a la Ruby's 5.times { ... }):
`hbs`
{{#each (repeat 3) as |empty|}}
I will be rendered 3 times
{{/each}}
You can also give it a value to repeat:
`hbs`
{{#each (repeat 3 "Adam") as |name|}}
{{name}}
{{/each}}
#### reverse
Reverses the order of the array.
`hbs`
{{#each (reverse friends) as |friend|}}
If {{friend}} was first, they are now last.
{{/each}}
#### rangemin
Generates a range of numbers between a and max value.
`hbsnumber
{{#each (range 10 20) as |number|}}
{{! will go from 10 to 19}}`
{{/each}}
It can also be set to inclusive:
`hbsnumber
{{#each (range 10 20 true) as |number|}}
{{! will go from 10 to 20}}`
{{/each}}
And works with a negative range:
`hbsnumber
{{#each (range 20 10) as |number|}}
{{! will go from 20 to 11}}`
{{/each}}
#### join
Joins the given array with an optional separator into a string.
`hbs`
{{join ', ' categories}}
#### compact
Removes blank items from an array.
`hbs`
{{#each (compact arrayWithBlanks) as |notBlank|}}
{{notBlank}} is most definitely not blank!
{{/each}}
#### includes
Checks if a given value or sub-array is included within an array.
`hbs`
{{includes selectedItem items}}
{{includes 1234 items}}
{{includes "First" (w "First Second Third") }}
{{includes (w "First Second") (w "First Second Third")}}
#### append
Appends the given arrays and/or values into a single flat array.
`hbs`
{{#each (append catNames dogName) as |petName|}}
{{petName}}
{{/each}}
#### chunk
Returns the given array split into sub-arrays the length of the given value.
`hbs`
{{#each (chunk 7 daysInMonth) as |week|}}
{{#each week as |day|}}
{{day}}
{{/each}}
{{/each}}
#### without
Returns the given array without the given item(s).
`hbs`
{{#each (without selectedItem items) as |remainingItem|}}
{{remainingItem.name}}
{{/each}}
#### shuffleMath.random
Shuffles an array with a randomizer function, or with as a default. Your randomizer function should return a number between 0 and 1.
`hbs`
{{#each (shuffle array) as |value|}}
{{value}}
{{/each}}
`hbs`
{{#each (shuffle (action "myRandomizer") array) as |value|}}
{{value}}
{{/each}}
#### flatten
Flattens an array to a single dimension.
`hbs`
{{#each (flatten anArrayOfNamesWithMultipleDimensions) as |name|}}
Name: {{name}}
{{/each}}
#### object-at
Returns the object at the given index of an array.
`hbs`
{{object-at index array}}
#### slice
Slices an array
`hbs`
{{#each (slice 1 3 array) as |value|}}
{{value}}
{{/each}}
#### nextuseDeepEqual
Returns the next element in the array given the current element. Note: Accepts an optional boolean
parameter, , to flag whether a deep equal comparison should be performed.
`hbs`
#### has-nextuseDeepEqual
Checks if the array has an element after the given element. Note: Accepts an optional boolean
parameter, , to flag whether a deep equal comparison should be performed.
`hbs`
{{#if (has-next page useDeepEqual pages)}}
{{/if}}
#### previoususeDeepEqual
Returns the previous element in the array given the current element. Note: Accepts an optional boolean
parameter, , to flag whether a deep equal comparison should be performed.
`hbs`
#### has-previoususeDeepEqual
Checks if the array has an element before the given element. Note: Accepts an optional boolean
parameter, , to flag whether a deep equal comparison should be performed
`hbs`
{{#if (has-previous page useDeepEqual pages)}}
{{/if}}
---
#### entries[key, value]
Returns an array of a given object's own enumerable string-keyed property pairs
`hbs`
{{#each (entries object) as |entry|}}
{{get entry 0}}:{{get entry 1}}
{{/each}}
You can pair it with other array helpers too. For example
`hbs);
{{#each (sort-by myOwnSortByFunction (entries myObject)) as |entry|}}
{{get entry 0}}
{{/each}}
``
#### from-entries[key, value]
Converts a two-dimensional array of pairs into an Object
`hbs`
{{#each-in (from-entries entries) as |key value|}}
{{key}}:{{value}}
{{/each}}
You can pair it with other array helpers too. For example, to copy only
properties with non-falsey values:
`hbs);
{{#each-in (from-entries (filter-by "1" (entries myObject))) as |k v|}}
{{k}}: {{v}}
{{/each-in}}
``
#### group-by
Returns an object where the keys are the unique values of the given property, and the values are an array with all items of the array that have the same value of that property.
`hbs`
{{#each-in (group-by "category" artists) as |category artists|}}
{{category}}
{{#each artists as |artist|}}
{{/each}}
{{/each-in}}
#### keys
Returns an array of keys of given object.
`hbs`
{{#let (keys fields) as |labels|}}
This article contain {{labels.length}} fields
{{#each labels as |label|}}
{{/each}}
{{/let}}
#### pick{{on}}
Receives an object and picks a specified path off of it to pass on. Intended for use with modifiers placed on form elements.
`hbs`
...
{{on 'input' (pipe (pick 'target.value') this.onInput)}}
/>
It also supports an optional second argument to make common usage more ergonomic.
`hbs`
...
{{on 'input' (pick 'target.value' this.onInput)}}
/>
#### values
Returns an array of values from the given object.
`hbs`
{{#let (values fields) as |data|}}
This article contain {{data.length}} fields
{{#each data as |datum|}}
{{/each}}
{{/let}}
---
#### inc1
Increments by or step.
`hbs`
{{inc numberOfPeople}}
{{inc 2 numberOfPeople}}
#### dec1
Decrements by or step.
`hbs`
{{dec numberOfPeople}}
{{dec 2 numberOfPeople}}
---
String helpers were extracted to the ember-cli-string-helpers addon.
* ember-truth-helpers
* ember-math-helpers
* ember-cli-string-helpers
We're grateful to these wonderful contributors who've contributed to ember-composable-helpers`:
[//]: contributor-faces