Composable helpers for Ember that enables more declarative templating. These helpers can be composed together to form powerful ideas:
{{#each (map-by "fullName" users) as |fullName|}}
<input type="text" value={{fullName}} onchange={{action (mut newName)}}>
<button {{action (pipe updateFullName saveUser) newName}}>
Update and save {{fullName}} to {{newName}}
</button>
{{/each}}
To install:
ember install ember-composable-helpers
Watch a free video overview presented by EmberMap:
This addon performs optional tree-shaking – you can specify which helpers to whitelist or blacklist using only
or except
within your config/environment.js
:
module.exports = function(environment) {
var ENV = {
'ember-composable-helpers': {
only: ['inc', 'dec', 'pipe'],
except: ['pipe', 'filter-by']
}
};
Both only
and except
can be safely used together (the addon computes the diff), although it's best if you only use one for your own sanity.
except: ['pipe'] // imports all helpers except `pipe`
only: ['pipe'] // imports only `pipe`
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:
{{take 5 (sort "lastName" "firstName" (filter-by "active" array))}}
For action helpers, this will mean better currying semantics:
<button {{action (pipe (action "closePopover") (toggle "isExpanded")) this}}>
{{if isExpanded "I am expanded" "I am not"}}
</button>
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.
<button {{action (pipe addToCart purchase redirectToThankYouPage) item}}>
1-Click Buy
</button>
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:
{{foo-bar
addAndSquare=(pipe-action (action "add") (action "square"))
multiplyAndSquare=(pipe-action (action "multiply") (action "square"))
}}
{{! foo-bar/template.hbs }}
<button {{action addAndSquare 2 4}}>Add and Square</button>
<button {{action multiplyAndSquare 2 4}}>Multiply and Square</button>
⬆️️ back to top
Calls an action as a template helper.
The square of 4 is {{compute (action "square") 4}}
⬆️ back to top
Toggles a boolean value.
<button {{action (toggle "isExpanded" this)}}>
{{if isExpanded "I am expanded" "I am not"}}
</button>
toggle
can also be used directly as a closure action using toggle-action
:
{{foo-bar
toggleIsExpanded=(toggle-action "isExpanded" this)
toggleIsSelected=(toggle-action "isSelected" this)
}}
{{! foo-bar/template.hbs }}
<button {{action toggleIsExpanded}}>Open / Close</button>
<button {{action toggleIsSelected}}>Select / Deselect</button>
toggle
also accepts optional values to rotate through:
<button {{action (toggle "currentName" this "foo" "bar" "baz")}}>
{{currentName}}
</button>
⬆️ back to top
Allows for the passed in action to not exist.
<button {{action (optional handleClick)}}>Click Me</button>
⬆️ back to top
Camelizes a string using Ember.String.camelize
.
{{camelize "hello jim bob"}}
{{camelize stringWithDashes}}
⬆️ back to top
Capitalizes a string using Ember.String.capitalize
.
{{capitalize "hello jim bob"}}
{{capitalize fullName}}
⬆️ back to top
Classifies a string using Ember.String.classify
.
{{classify "hello jim bob"}}
{{classify stringWithDashes}}
⬆️ back to top
Capitalizes a string using Ember.String.dasherize
.
{{dasherize "whatsThat"}}
{{dasherize phrase}}
⬆️ back to top
Truncates a string with a characterLimit.
{{truncate "Lorem ipsum dolor sit amet, consectetur adipiscing elit." 20}}
{{truncate phrase}}
⬆️ back to top
Capitalizes a string using Ember.String.underscore
.
{{underscore "whatsThat"}}
{{underscore phrase}}
⬆️ back to top
Splits a string on whitespace and/or turns multiple words into an array
{{#each (w "First" "Second" "Last") as |rank|}}
Our {{rank}} place winner is ...
{{/each}}
or:
{{#each (w "First Second Last") as |rank|}}
Our {{rank}} place winner is ...
{{/each}}
See also: Ember w
documentation
⬆️ back to top
Maps an array on a property.
{{#each (map-by "fullName" users) as |fullName|}}
{{fullName}}
{{/each}}
⬆️ back to top
Sort an array by given properties.
{{#each (sort-by "lastName" "firstName" users) as |user|}}
{{user.lastName}}, {{user.firstName}}
{{/each}}
You can append :desc
to properties to sort in reverse order.
{{#each (sort-by "age:desc" users) as |user|}}
{{user.firstName}} {{user.lastName}} ({{user.age}})
{{/each}}
⬆️ back to top
Filters an array by a property.
{{#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.
{{#each (filter-by "address" users) as |user|}}
{{user.name}} has an address specified!
{{/each}}
You can also pass an action as second argument:
{{#each (filter-by "age" (action "olderThan" 18) users) as |user|}}
{{user.name}} is older than eighteen!
{{/each}}
⬆️ back to top
The inverse of filter by.
{{#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.
{{#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:
{{#each (reject-by "age" (action "youngerThan" 18) users) as |user|}}
{{user.name}} is older than eighteen!
{{/each}}
⬆️ back to top
Returns the first entry matching the given value.
{{#with (find-by 'name' lookupName people) as |person|}}
{{#if person}}
{{#link-to 'person' person}}
Click here to see {{person.name}}'s details
{{/link-to}}
{{/if}}
{{/with}}
⬆️ back to top
Creates an array of unique values that are included in all given arrays.
<h1>Matching skills</h1>
{{#each (intersect desiredSkills currentSkills) as |skill|}}
{{skill.name}}
{{/each}}
⬆️ back to top
Invokes a method on an object, or on each object of an array.
<div id="popup">
{{#each people as |person|}}
<button {{action (invoke "rollbackAttributes")}}>
Undo
</button>
{{/each}}
<a {{action (invoke "save" people)}}>Save</a>
</div>
⬆️ back to top
Joins arrays to create an array of unique values. When applied to a single array, has the same behavior as uniq
.
{{#each (union cartA cartB cartC) as |cartItem|}}
{{cartItem.price}} x {{cartItem.quantity}} for {{cartItem.name}}
{{/each}}
⬆️ back to top
Returns the first n
entries of a given array.
<h3>Top 3:</h3>
{{#each (take 3 contestants) as |contestant|}}
{{contestant.rank}}. {{contestant.name}}
{{/each}}
⬆️ back to top
Returns an array with the first n
entries omitted.
<h3>Other contestants:</h3>
{{#each (drop 3 contestants) as |contestant|}}
{{contestant.rank}}. {{contestant.name}}
{{/each}}
⬆️ back to top
Repeats n
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 { ... }
):
{{#each (repeat 3) as |empty|}}
I will be rendered 3 times
{{/each}}
You can also give it a value to repeat:
{{#each (repeat 3 "Adam") as |name|}}
{{name}}
{{/each}}
⬆️ back to top
Reverses the order of the array.
{{#each (reverse friends) as |friend|}}
If {{friend}} was first, they are now last.
{{/each}}
⬆️ back to top
Generates a range of numbers between a min
and max
value.
{{#each (range 10 20) as |number|}}
{{! `number` will go from 10 to 19}}
{{/each}}
It can also be set to inclusive
:
{{#each (range 10 20 true) as |number|}}
{{! `number` will go from 10 to 20}}
{{/each}}
And works with a negative range:
{{#each (range 20 10) as |number|}}
{{! `number` will go from 20 to 11}}
{{/each}}
⬆️ back to top
Joins the given array with an optional separator into a string.
⬆️ back to top
Removes blank items from an array.
{{#each (compact arrayWithBlanks) as |notBlank|}}
{{notBlank}} is most definitely not blank!
{{/each}}
⬆️ back to top
Checks if a given value or sub-array is contained within an array.
{{contains selectedItem items}}
{{contains 1234 items}}
{{contains "First" (w "First Second Third") }}
{{contains (w "First Second") (w "First Second Third")}}
⬆️ back to top
Appends the given arrays and/or values into a single flat array.
{{#each (append catNames dogName) as |petName|}}
{{petName}}
{{/each}}
⬆️ back to top
Returns the given array split into sub-arrays the length of the given value.
{{#each (chunk 7 daysInMonth) as |week|}}
{{#each week as |day|}}
{{day}}
{{/each}}
{{/each}}
⬆️ back to top
Returns the given array without the given item(s).
{{#each (without selectedItem items) as |remainingItem|}}
{{remainingItem.name}}
{{/each}}
⬆️ back to top
Similar to the hash
helper, this lets you compose arrays directly in the template:
{{#each (array 1 2 3) as |numbers|}}
{{numbers}}
{{/each}}
⬆️ back to top
Shuffles an array with a randomizer function, or with Math.random
as a default. Your randomizer function should return a number between 0 and 1.
{{#each (shuffle array) as |value|}}
{{value}}
{{/each}}
{{#each (shuffle (action "myRandomizer") array) as |value|}}
{{value}}
{{/each}}
⬆️ back to top
Flattens an array to a single dimension.
{{#each (flatten anArrayOfNamesWithMultipleDimensions) as |name|}}
Name: {{name}}
{{/each}}
⬆️ back to top
Returns the object at the given index of an array.
{{object-at index array}}
⬆️ back to top
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.
{{#each-in (group-by "category" artists) as |category artists|}}
<h3>{{category}}</h3>
<ul>
{{#each artists as |artist|}}
<li>{{artist.name}}</li>
{{/each}}
</ul>
{{/each-in}}
⬆️ back to top
Increments by 1
or step
.
{{inc numberOfPeople}}
{{inc 2 numberOfPeople}}
⬆️ back to top
Decrements by 1
or step
.
{{dec numberOfPeople}}
{{dec 2 numberOfPeople}}
⬆️ back to top
DockYard, Inc © 2016
@dockyard
Licensed under the MIT license