npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ember-route-task-helper

v0.4.1

Published

Easily access tasks defined on a route in your templates

Downloads

870

Readme

ember-route-task-helper

Build Status npm version Download Total Ember Observer Score Greenkeeper badge dependencies Status devDependencies Status

The route-task template helper allows you to easily access tasks defined on a route in the currently active route hierarchy. Essentially this addon is just like ember-route-action-helper but for ember-concurrency tasks.

Installation

ember install ember-route-task-helper

This addon will work on Ember versions 2.4.x and up only, due to use of the new RouterService. If your Ember version does not natively support it yet, you need to install the polyfill:

ember install ember-router-service-polyfill

Of course, you need to have ember-concurrency installed. If you haven't already, run this command first:

ember install ember-concurrency

Minimum required version is 0.6.x.

Usage

Template Helper: (route-task taskName ...curryArguments)

Wherever in a template you would access a task by its name, replace it with (route-task "taskName") and move that task to a route. For instance:

{{task-button
  task=myTask
}}

{{!-- now becomes --}}

{{task-button
  task=(route-task "myTask")
}}

Notice the quotes around "myTask".

You can also curry your tasks:

{{task-button
  task=(task myTask "Freddie" "Morecurry")
}}

{{!-- now becomes --}}

{{task-button
  task=(route-task "myTask" "Freddie" "Morecurry")
}}

Exemplary Migration

Let's start with a traditional task defined on a component.

// app/components/delete-user.js
import Component from '@ember/component';
import { get } from '@ember/object';
import { task, timeout } from 'ember-concurrency';

export default Component.extends({
  /**
   * A User record.
   * @type {DS.Model}
   */
  user: null,

  /**
   * Deletes the user after a timeout of 5 seconds.
   * @type {Task}
   */
  deleteUser: task(function*() {
    timeout(5000); // give the user time to think about it
    yield get(this, 'user').destroyRecord();
  }).drop()
});
{{!-- app/templates/components/delete-user.hbs --}}

{{#if deleteUser.isIdle}}
  <button onclick={{perform deleteUser}}>
    Delete {{user.name}}
  </button>
{{else}}
  Deleting {{user.name}} in 5 seconds. You can still abort.

  <button onclick={{cancel-all deleteUser}}>
    Nah, spare their life.
  </button>
{{/if}}

And now we'll move it to a route, shall we?

// app/routes/user.js
import Route from '@ember/routing/route';
import { get } from '@ember/object';
import { task, timeout } from 'ember-concurrency';

export default Route.extends({
  /**
   * Deletes the current model after a timeout of 5 seconds.
   * @type {Task}
   */
  deleteUser: task(function*() {
    timeout(5000); // give the user time to think about it
    const user = this.modelFor(this.routeName);
    yield user.destroyRecord();
  }).drop()
});
{{!-- app/templates/user.hbs --}}

{{#if (get (route-task "deleteUser") "isIdle")}}
  <button onclick={{perform (route-task "deleteUser")}}>
    Delete {{model.name}}
  </button>
{{else}}
  Deleting {{model.name}} in 5 seconds. You can still abort.

  <button onclick={{cancel-all (route-task "deleteUser")}}>
    Nah, spare their life.
  </button>
{{/if}}

Currying

// app/routes/user.js
import Route from '@ember/routing/route';
import { get } from '@ember/object';
import { task, timeout } from 'ember-concurrency';

export default Route.extends({
  /**
   * Deletes the current model after a timeout of 5 seconds.
   * @type {Task}
   */
  deleteUser: task(function*(user) {
    timeout(5000); // give the user time to think about it
    yield user.destroyRecord();
  }).drop()
});
{{!-- app/templates/user.hbs --}}

{{task-button
  task=(route-task "deleteUser" model)
  idleLabel=(concat "Delete " model.name)
  runningLabel="Cancel deletion"
}}

Notes on Syntax

I personally dislike repeating (route-task) a bunch of times in my templates or even worse having to use the (get) helper to derive state from a task. You can avoid that by either only passing a task to a component (as shown in the {{task-button}} example above) or by using the {{with}} helper:

{{#with (route-task "deleteUser" model) as |deleteUser|}}
  {{#if deleteUser.isIdle}}
    <button onclick={{perform deleteUser}}>
      Delete {{model.name}}
    </button>
  {{else}}
    Deleting {{model.name}} in 5 seconds. You can still abort.

    <button onclick={{cancel-all deleteUser}}>
      Nah, spare their life.
    </button>
  {{/if}}
{{/with}}

Util: routeTask(context, taskName, ...curryArguments)

There also is a routeTask util, that's really similar to ember-invoke-action and might come in handy for JS heavy components.

// app/components/delete-user.js
import Component from '@ember/component';
import { get } from '@ember/object';
import { routeTask } from 'ember-route-task-helper';

export default Component.extends({
  /**
   * A User record.
   * @type {DS.Model}
   */
  user: null,

  /**
   * Deletes the user after a timeout of 5 seconds.
   */
  click() {
    const user = get(this, 'user');
    routeTask(this, 'deleteUser').perform(user);
  }
});

Currying

As with the (route-task) helper, you can curry the task with as many arguments as you like. So the above is interchangeable with:

click() {
  const user = get(this, 'user');
  routeTask(this, 'deleteUser', user).perform();
}

routeTaskFromRouter(router, taskName, ...curryArguments)

Internally routeTask performs a lookup for the Router everytime you call it. If you already happen to have the router instance available in your current scope, you could also pass it directly to skip the lookup:

// app/components/delete-user.js
import Component from '@ember/component';
import { get, computed } from '@ember/object';
import { getOwner } from '@ember/application';
import { routeTaskFromRouter } from 'ember-route-task-helper';

export default Component.extends({
  /**
   * A User record.
   * @type {DS.Model}
   */
  user: null,

  /**
   * The app's router instance.
   * @type {Ember.Router}
   */
  router: computed(function() {
    return getOwner(this).lookup('main:router');
  }).readOnly(),

  /**
   * Deletes the user after a timeout of 5 seconds.
   */
  click() {
    const router = get(this, 'router');
    const user = get(this, 'user');
    routeTaskFromRouter(router, 'deleteUser').perform(user);
  }
});

Notes on DDAU

In my opinion, using routeTask in components generally isn't a good design pattern. I would much rather prefer to explicitly pass the route task as an attribute:

{{my-component taskName=(route-task "taskName")}}

Just by looking at the component invocation in the template, the user should be able to judge what's going in and what's coming out of a component (DDAU). This way components remain completely agnostic and make no assumptions about the environment they are invoked in.

Calling routeTask inside a component is really non-transparent and promotes an unhealthy invisible entanglement.

On the other hand, you can already call (route-task) in the component's template.

I've implemented it for feature parity. But just because it's there, doesn't mean you have to use it. But don't let me stop you. :stuck_out_tongue_winking_eye:

Contributing

I sincerely hope this addon serves you well. Should you encounter a bug, have a great idea or just a question, please do open an issue and let me know. Even better yet, submit a PR yourself! :blush:

This addon is using the Prettier code formatter. It's embedded as a fixable eslint rule. If you're editor is set up to fix eslint errors on save, you're code is auto formatted. If not, please make sure you're not getting any linter errors.

Attribution

The original idea for this addon was brought up in machty/ember-concurrency#89 by @Luiz-N. This addon is in many ways a straight copy and paste from ember-route-action-helper by @DockYard.

A huge thank you to goes out to @machty for developing ember-concurrency in the first place. :heart: