• Stars
    star
    631
  • Rank 68,729 (Top 2 %)
  • Language
    JavaScript
  • License
    Other
  • Created about 8 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Composable helpers for declarative templating in Ember

ember-composable-helpers

Download count all time CircleCI npm version Ember Observer Score

ember-composable-helpers is built and maintained by DockYard, contact us for expert Ember.js consulting.

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 3.13+:

ember install ember-composable-helpers

Ember 3.12 and below:

ember install ember-composable-helpers@^2.4.0

Watch a free video overview presented by EmberMap:

Table of Contents

Configuration

If you don't need all the helpers, you can specify which to include or remove from your build using only or except within your ember-cli-build.js:

module.exports = function(defaults) {
  var app = new EmberApp(defaults, {
    'ember-composable-helpers': {
      only: ['inc', 'dec', 'pipe'],
      except: ['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`

Argument ordering

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-by "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>

Upgrade Guide

For help upgrading between major versions, check out the upgrading documentation.

Available helpers

Action helpers

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.

<button {{action (pipe (action 'addToCart') (action 'purchase') (action '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

call

Calls the given function with arguments

{{#each (call (fn this.callMeWith @daysInMonth) as |week|}}
  {{#each week as |day|}}
    {{day}}
  {{/each}}
{{/each}}

⬆️ back to top

compute

Calls an action as a template helper.

The square of 4 is {{compute (action "square") 4}}

⬆️ back to top

toggle

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

noop

Returns an empty function.

<div {{on "mouseenter" (if @isLoading (noop) @sendTrackingEvent))}}>Some content</div>

⬆️ back to top

optional

Allows for the passed in action to not exist.

<button {{action (optional handleClick)}}>Click Me</button>

⬆️ back to top

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.

<button {{action (queue (action "backupData") (action "unsafeOperation") (action "restoreBackup"))}} />

⬆️ back to top


Array helpers

map

Maps a callback on an array.

{{#each (map (action "getName") users) as |fullName|}}
  {{fullName}}
{{/each}}

⬆️ back to top

map-by

Maps an array on a property.

{{#each (map-by "fullName" users) as |fullName|}}
  {{fullName}}
{{/each}}

sort-by

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}}

You can also pass a method as the first argument:

{{#each (sort-by (action "mySortAction") users) as |user|}}
  {{user.firstName}} {{user.lastName}} ({{user.age}})
{{/each}}

⬆️ back to top

filter

Filters an array by a callback.

{{#each (filter (action "isActive") users) as |user|}}
  {{user.name}} is active!
{{/each}}

filter-by

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

reject-by

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

find-by

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

intersect

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

invoke

Invokes a method on an object, or on each object of an array.

<div id="popup">
  {{#each people as |person|}}
    <button {{action (invoke "rollbackAttributes" person)}}>
      Undo
    </button>
  {{/each}}
  <a {{action (invoke "save" people)}}>Save</a>
</div>

⬆️ back to top

union

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

take

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

drop

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

reduce

Reduce an array to a value.

{{reduce (action "sum") 0 (array 1 2 3)}}

The last argument is initial value. If you omit it, undefined will be used.

repeat

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

reverse

Reverses the order of the array.

{{#each (reverse friends) as |friend|}}
  If {{friend}} was first, they are now last.
{{/each}}

⬆️ back to top

range

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

join

Joins the given array with an optional separator into a string.

{{join ', ' categories}}

⬆️ back to top

compact

Removes blank items from an array.

{{#each (compact arrayWithBlanks) as |notBlank|}}
  {{notBlank}} is most definitely not blank!
{{/each}}

⬆️ back to top

includes

Checks if a given value or sub-array is included within an array.

{{includes selectedItem items}}
{{includes 1234 items}}
{{includes "First" (w "First Second Third") }}
{{includes (w "First Second") (w "First Second Third")}}

⬆️ back to top

append

Appends the given arrays and/or values into a single flat array.

{{#each (append catNames dogName) as |petName|}}
  {{petName}}
{{/each}}

⬆️ back to top

chunk

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

without

Returns the given array without the given item(s).

{{#each (without selectedItem items) as |remainingItem|}}
  {{remainingItem.name}}
{{/each}}

⬆️ back to top

shuffle

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

flatten

Flattens an array to a single dimension.

{{#each (flatten anArrayOfNamesWithMultipleDimensions) as |name|}}
  Name: {{name}}
{{/each}}

⬆️ back to top

object-at

Returns the object at the given index of an array.

{{object-at index array}}

⬆️ back to top

slice

Slices an array

{{#each (slice 1 3 array) as |value|}}
  {{value}}
{{/each}}

⬆️ back to top

next

Returns the next element in the array given the current element. Note: Accepts an optional boolean parameter, useDeepEqual, to flag whether a deep equal comparison should be performed.

<button onclick={{action (mut selectedItem) (next selectedItem useDeepEqual items)}}>Next</button>

⬆️ back to top

has-next

Checks if the array has an element after the given element. Note: Accepts an optional boolean parameter, useDeepEqual, to flag whether a deep equal comparison should be performed.

{{#if (has-next page useDeepEqual pages)}}
  <button>Next</button>
{{/if}}

⬆️ back to top

previous

Returns the previous element in the array given the current element. Note: Accepts an optional boolean parameter, useDeepEqual, to flag whether a deep equal comparison should be performed.

<button onclick={{action (mut selectedItem) (previous selectedItem useDeepEqual items)}}>Previous</button>

⬆️ back to top

has-previous

Checks if the array has an element before the given element. Note: Accepts an optional boolean parameter, useDeepEqual, to flag whether a deep equal comparison should be performed

{{#if (has-previous page useDeepEqual pages)}}
  <button>Previous</button>
{{/if}}

⬆️ back to top


Object helpers

entries

Returns an array of a given object's own enumerable string-keyed property [key, value] pairs

  {{#each (entries object) as |entry|}}
    {{get entry 0}}:{{get entry 1}}
  {{/each}}

You can pair it with other array helpers too. For example

  {{#each (sort-by myOwnSortByFunction (entries myObject)) as |entry|}}
    {{get entry 0}}
  {{/each}}`);

⬆️ back to top

from-entries

Converts a two-dimensional array of [key, value] pairs into an Object

  {{#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:

  {{#each-in (from-entries (filter-by "1" (entries myObject))) as |k v|}}
    {{k}}: {{v}}
  {{/each-in}}`);

⬆️ back to top

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.

{{#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

keys

Returns an array of keys of given object.

{{#with (keys fields) as |labels|}}
  <h3>This article contain {{labels.length}} fields</h3>
  <ul>
    {{#each labels as |label|}}
      <li>{{label}}</li>
    {{/each}}
  </ul>
{{/with}}

⬆️ back to top

pick

Receives an object and picks a specified path off of it to pass on. Intended for use with {{on}} modifiers placed on form elements.

  <input
    ...
    {{on 'input' (pipe (pick 'target.value') this.onInput)}}
  />

It also supports an optional second argument to make common usage more ergonomic.

  <input
    ...
    {{on 'input' (pick 'target.value' this.onInput)}}
  />

*⬆️ back to top

values

Returns an array of values from the given object.

{{#with (values fields) as |data|}}
  <h3>This article contain {{data.length}} fields</h3>
  <ul>
    {{#each data as |datum|}}
      <li>{{datum}}</li>
    {{/each}}
  </ul>
{{/with}}

*⬆️ back to top


Math helpers

inc

Increments by 1 or step.

{{inc numberOfPeople}}
{{inc 2 numberOfPeople}}

⬆️ back to top

dec

Decrements by 1 or step.

{{dec numberOfPeople}}
{{dec 2 numberOfPeople}}

⬆️ back to top


String helpers

String helpers were extracted to the ember-cli-string-helpers addon.

See also:

Legal

DockYard, Inc © 2016

@dockyard

Licensed under the MIT license

Contributors

We're grateful to these wonderful contributors who've contributed to ember-composable-helpers:

More Repositories

1

elixir-mail

Build composable mail messages
Elixir
379
star
2

ember-route-action-helper

Bubble closure actions in routes
JavaScript
331
star
3

ember-in-viewport

Detect if an Ember View or Component is in the viewport @ 60FPS
JavaScript
247
star
4

ember-admin

Admin backend for ember-cli projects
JavaScript
243
star
5

ember-service-worker

A pluggable approach to Service Workers for Ember.js
JavaScript
238
star
6

flame_on

Flame Graph LiveView Component and LiveDashboard plugin
Elixir
207
star
7

ember-router-scroll

🗔 Scroll to top with preserved browser history scroll position.
JavaScript
204
star
8

ember-async-button

Async Button Component for Ember CLI apps
JavaScript
173
star
9

ecto_fixtures

Fixtures for Elixir apps
Elixir
169
star
10

inquisitor

Composable query builder for Ecto
Elixir
169
star
11

openid_connect

Elixir
65
star
12

eslint-plugin-ember-suave

DockYard's ESLint plugin for Ember apps
JavaScript
53
star
13

courier

Elixir
53
star
14

ember-cart

Shopping cart primitives for Ember
JavaScript
53
star
15

valid_field

Elixir
48
star
16

rein

Reinforcement Learning tooling built with Nx
Elixir
40
star
17

json_api_assert

Composable assertions for JSON API payload
Elixir
36
star
18

live_view_demo

Forkable repo for entries in Phoenix Phrenzy (https://phoenixphrenzy.com/)
Elixir
33
star
19

ember-service-worker-asset-cache

JavaScript
28
star
20

svelte-inline-compile

JavaScript
27
star
21

ember-cli-custom-assertions

Add custom QUnit assertions to your ember-cli test suite
JavaScript
26
star
22

design-sprints

HTML
23
star
23

ember-app-shell

JavaScript
23
star
24

easing

Elixir
22
star
25

ember-i18n-to-intl-migrator

Migrate ember-i18n to ember-intl
JavaScript
20
star
26

ember-service-worker-index

An Ember Service Worker plugin that caches the index.html file
JavaScript
20
star
27

ember-cli-deploy-compress

Compress your assets automatically choosing the best compression available for your browser targets
JavaScript
18
star
28

laptop-install

Shell
17
star
29

narwin-pack

Package of PostCSS plugins DockYard utilizes for PostCSS based projects!
JavaScript
16
star
30

ember-maybe-in-element

Conditionally render content elsewhere using #-in-element on ember apps
JavaScript
15
star
31

ember-service-worker-cache-fallback

JavaScript
15
star
32

inquisitor_jsonapi

JSON API Matchers for Inquisitor
Elixir
14
star
33

ember-one-way-select

JavaScript
10
star
34

svelte-inline-component

Utility and vite plugin to allow to create your own inline svelte components in tests
JavaScript
9
star
35

canon

All the must-read articles and must-watch videos for the DockYard engineering team.
8
star
36

ember-service-worker-cache-first

JavaScript
7
star
37

qunit-notifications

Web Notifications support for QUnit in-browser test suites
JavaScript
6
star
38

netcdf

Elixir NetCDF Bindings
Rust
6
star
39

auth_test_support

Authentication and authorization test functions
Elixir
4
star
40

plausible_proxy

An Elixir Plug to proxy calls to Plausible through your server
Elixir
3
star
41

broccoli-json-concat

JavaScript
3
star
42

boat-tracker

Elixir
3
star
43

ember-load-css

Ember CLI wrapper for loadCSS
JavaScript
3
star
44

drive-in-privacy-policy

2
star
45

ketch

Simple proof-of-concept web application built with Next.js, Storybook, and Firebase.
JavaScript
1
star
46

courier_web

JavaScript
1
star
47

stylelint-config-narwin

DockYard stylelint configuration
JavaScript
1
star
48

liveview_tailwind_demo

Demo showing TailWind 3 integration in a Phoenix LiveView project
Elixir
1
star
49

ember-qunit-notifications

tomster-ified qunit-notifications
1
star
50

boston_elixir

LiveView Native Workshop for Boston Elixir
Elixir
1
star