Skip to content

Commit

Permalink
More helpers
Browse files Browse the repository at this point in the history
- add peek-record helper
- add peek-all helper
- add query helper
- add async-all helper
- add async-hash helper
  • Loading branch information
dknutsen committed Mar 23, 2019
1 parent 9fe0367 commit 12628fe
Show file tree
Hide file tree
Showing 25 changed files with 320 additions and 78 deletions.
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
ember-needs-async
==============================================================================

[Short description of the addon.]
Lightweight provider component and helpers that allow declarative, composable async data fetching and async-aware rendering in just a template. The component waits for an async task and yields the results as well as loading and error states. It can also be used with any ember-concurrency task. The helpers provide shorthand concurrency task wrappers around common Ember Data operations. Example:

```
<div class="user-card">
<NeedsAsync @needs={{find-record "user" userId}} as |states|>
{{#states.loading}}
<LoadingSpinner/>
{{/states.loading}}
{{#states.error as |error|}}
There was an error loading the user
<div class="error-message">{{error}}</div>
{{/states.error}}
{{#states.loaded as |user|}}
<img class="user-card-avatar" src={{user.avatar}} alt="{{user.fullName}} profile picture">
<div class="user-card-name">{{user.firstName}} {{user.lastName}}</div>
<div class="user-card-occupation">{{user.jobTitle}} at {{user.company}}</div>
{{/states.loaded}}
</NeedsAsync>
</div>
```


Compatibility
Expand All @@ -19,12 +40,6 @@ ember install ember-needs-async
```


Usage
------------------------------------------------------------------------------

[Longer description of how to use the addon in apps.]


License
------------------------------------------------------------------------------

Expand Down
11 changes: 11 additions & 0 deletions addon/helpers/async-all.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Helper from '@ember/component/helper';
import { all, task } from 'ember-concurrency';

export default Helper.extend({
asyncAllTask: task(function * (tasks) {
return yield all(tasks);
}),
compute([tasks/*, ...rest*/]/*, hash*/) {
return this.asyncAllTask.perform(tasks);
}
});
11 changes: 11 additions & 0 deletions addon/helpers/async-hash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Helper from '@ember/component/helper';
import { hash, task } from 'ember-concurrency';

export default Helper.extend({
asyncHashTask: task(function * (tasks) {
return yield hash(tasks);
}),
compute([tasks/*, ...rest*/]/*, hash*/) {
return this.asyncHashTask.perform(tasks);
}
});
13 changes: 13 additions & 0 deletions addon/helpers/peek-all.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';

export default Helper.extend({
store: service(),
peekAllTask: task(function * (modelType) {
return yield this.store.peekAll(modelType);
}),
compute([modelType/*, ...rest*/]/*, hash*/) {
return this.peekAllTask.perform(modelType);
}
});
14 changes: 14 additions & 0 deletions addon/helpers/peek-record.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';

export default Helper.extend({
store: service(),
peekRecordTask: task(function * (modelType, id) {
return yield this.store.peekRecord(modelType, id);
}),
compute([modelType, id/*, ...rest*/]/*, hash*/) {
if(!id) return null;
return this.peekRecordTask.perform(modelType, id);
}
});
13 changes: 13 additions & 0 deletions addon/helpers/query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';

export default Helper.extend({
store: service(),
queryTask: task(function * (modelType, hash) {
return yield this.store.query(modelType, hash);
}),
compute([modelType, hash/*, ...rest*/]/*, hash*/) {
return this.queryTask.perform(modelType, hash);
}
});
1 change: 1 addition & 0 deletions app/helpers/async-all.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, asyncAll } from 'ember-needs-async/helpers/async-all';
1 change: 1 addition & 0 deletions app/helpers/async-hash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, asyncHash } from 'ember-needs-async/helpers/async-hash';
1 change: 1 addition & 0 deletions app/helpers/peek-all.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, peekAll } from 'ember-needs-async/helpers/peek-all';
1 change: 1 addition & 0 deletions app/helpers/peek-record.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, peekRecord } from 'ember-needs-async/helpers/peek-record';
1 change: 1 addition & 0 deletions app/helpers/query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, query } from 'ember-needs-async/helpers/query';
16 changes: 0 additions & 16 deletions tests/dummy/app/controllers/application.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,4 @@
import Controller from '@ember/controller';
import { task, timeout } from 'ember-concurrency';

export default Controller.extend({
askQuestion: task(function * () {
yield timeout(1000);
this.set('result', Math.random());
return this.result;
}).drop(),

result: null,

taskInstance: null,

actions: {
onClick() {
this.set('taskInstance', this.askQuestion.perform());
}
}
});
20 changes: 20 additions & 0 deletions tests/dummy/app/controllers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Controller from '@ember/controller';
import { task, timeout } from 'ember-concurrency';

export default Controller.extend({
askQuestion: task(function * () {
yield timeout(1000);
this.set('result', Math.random());
return this.result;
}).drop(),

result: null,

taskInstance: null,

actions: {
onClick() {
this.set('taskInstance', this.askQuestion.perform());
}
}
});
1 change: 1 addition & 0 deletions tests/dummy/app/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { computed } from '@ember/object';
export default DS.Model.extend({
firstName: DS.attr(),
lastName: DS.attr(),
avatar: DS.attr(),

friends: DS.hasMany('user'),

Expand Down
1 change: 1 addition & 0 deletions tests/dummy/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const Router = EmberRouter.extend({
});

Router.map(function() {
this.route('sandbox');
});

export default Router;
4 changes: 4 additions & 0 deletions tests/dummy/app/routes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Route from '@ember/routing/route';

export default Route.extend({
});
59 changes: 4 additions & 55 deletions tests/dummy/app/templates/application.hbs
Original file line number Diff line number Diff line change
@@ -1,57 +1,6 @@
<h2 id="title">Welcome to Ember</h2>
{{!--
<button class={{if askQuestion.isIdle "button-primary"}} onclick={{perform askQuestion}}>
--}}
<button onclick={{action "onClick"}}>
Button
{{#needs-async needs=this.taskInstance as |states|}}
{{#states.loading}}loading...{{/states.loading}}
{{#states.loaded as |value|}}{{value}}{{/states.loaded}}
{{#states.error as |error|}}{{error}}{{/states.error}}
{{/needs-async}}
</button>

<div>
<div>
{{input value=userid}}
</div>

<div>
<NeedsAsync @needs={{find-record "user" userid}} as |states|>
{{#states.loading}}LOADING THE USER{{/states.loading}}

{{#states.loaded as |user|}}
{{user.lastName}}, {{user.firstName}}
<div>
<NeedsAsync @needs={{belongs-to user "company"}} as |states|>
{{#states.loading}}LOADING COMPANY{{/states.loading}}
{{#states.loaded as |company|}}
<div>
<div>{{company.name}} - {{company.motto}}</div>
<div>{{company.street}} {{company.city}} {{company.state}} {{company.zip}}</div>
</div>
{{/states.loaded}}
</NeedsAsync>
<NeedsAsync @needs={{has-many user "friends"}} as |states|>
{{#states.loading}}LOADING FRIENDS{{/states.loading}}
{{#states.loaded as |friends|}}
<div>
{{#each friends as |friend|}}
{{friend.fullName}},
{{/each}}
</div>
{{/states.loaded}}
</NeedsAsync>
</div>
{{/states.loaded}}

{{#states.error as |error|}}
There was an error: {{error}}
{{/states.error}}
</NeedsAsync>
</div>


</div>
<nav class="w-full h-8 bg-grey-light">
{{#link-to 'index'}}Main{{/link-to}}
{{#link-to 'sandbox'}}Sandbox{{/link-to}}
</nav>

{{outlet}}
64 changes: 64 additions & 0 deletions tests/dummy/app/templates/index.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<h1>Main</h1>

<button onclick={{action "onClick"}}>
Button
{{#needs-async needs=this.taskInstance as |states|}}
{{#states.loading}}loading...{{/states.loading}}
{{#states.loaded as |value|}}{{value}}{{/states.loaded}}
{{#states.error as |error|}}{{error}}{{/states.error}}
{{/needs-async}}
</button>

<div>
<div>
{{input value=userid}}
</div>

<div>
<NeedsAsync @needs={{find-record "user" userid}} as |states|>
{{#states.loading}}LOADING THE USER{{/states.loading}}

{{#states.loaded as |user|}}
<img src={{user.avatar}} alt="{{user.fullName}} profile picture">
{{user.lastName}}, {{user.firstName}}
<div>
<NeedsAsync @needs={{belongs-to user "company"}} as |states|>
{{#states.loading}}LOADING COMPANY{{/states.loading}}
{{#states.loaded as |company|}}
<div>
<div>{{company.name}} - {{company.motto}}</div>
<div>{{company.street}} {{company.city}} {{company.state}} {{company.zip}}</div>
</div>
{{/states.loaded}}
</NeedsAsync>
<NeedsAsync @needs={{has-many user "friends"}} as |states|>
{{#states.loading}}LOADING FRIENDS{{/states.loading}}
{{#states.loaded as |friends|}}
<div>
{{#each friends as |friend|}}
{{friend.fullName}},
{{/each}}
</div>
{{/states.loaded}}
</NeedsAsync>
</div>
{{/states.loaded}}

{{#states.error as |error|}}
There was an error: {{error}}
{{/states.error}}
</NeedsAsync>
<div>
<NeedsAsync @needs={{peek-all "user"}} as |States|>
<#States.loaded as |users|>
Loaded users: {{users.length}}
</States.loaded>
</NeedsAsync>
</div>
</div>


</div>


{{outlet}}
51 changes: 51 additions & 0 deletions tests/dummy/app/templates/sandbox.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<h1>Sandbox</h1>

<NeedsAsync @needs={{async-all (array
(find-record "user" "1")
(find-record "user" "2")
(find-record "user" "3")
)}} as |States|>
<#States.loading>
Loading...
</States.loading>

<#States.loaded as |users|>
{{#each users as |user|}}
{{user.fullName}},
{{/each}}
</States.loaded>
</NeedsAsync>


<NeedsAsync @needs={{async-hash (hash
carol=(find-record "user" "1")
carl=(find-record "user" "2")
cambert=(find-record "user" "3")
)}} as |States|>
<#States.loading>
Loading...
</States.loading>

<#States.loaded as |users|>
{{#each-in users as |name user|}}
{{name}}:{{user.fullName}},
{{/each-in}}
</States.loaded>
</NeedsAsync>

<div>
QUERY:
<NeedsAsync @needs={{query "user" (hash page="2" size="5")}} as |States|>
<#States.loading>
Loading...
</States.loading>

<#States.loaded as |users|>
{{#each users as |user|}}
{{user.fullName}},
{{/each}}
</States.loaded>
</NeedsAsync>
</div>

{{outlet}}
1 change: 1 addition & 0 deletions tests/dummy/mirage/factories/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Factory, faker, trait } from 'ember-cli-mirage';
export default Factory.extend({
firstName: faker.name.firstName,
lastName: faker.name.lastName,
avatar: faker.image.avatar,

withCompany: trait({
afterCreate(user, server) {
Expand Down
17 changes: 17 additions & 0 deletions tests/integration/helpers/async-all-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';

module('Integration | Helper | async-all', function(hooks) {
setupRenderingTest(hooks);

// Replace this with your real tests.
test('it renders', async function(assert) {
this.set('inputValue', '1234');

await render(hbs`{{async-all inputValue}}`);

assert.equal(this.element.textContent.trim(), '1234');
});
});
Loading

0 comments on commit 12628fe

Please sign in to comment.