Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions addon/-wait-for.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,32 @@ class WaitForEventYieldable {
}

[yieldableSymbol](taskInstance, resumeIndex) {
let unbind = () => {};
let fn = (event) => {
unbind();
taskInstance.proceed(resumeIndex, YIELDABLE_CONTINUE, event);
};
this.object.one(this.eventName, fn);
return () => {
this.object.off(this.eventName, fn);
};

if (typeof this.object.addEventListener === 'function') {
// assume that we're dealing with a DOM `EventTarget`.
this.object.addEventListener(this.eventName, fn);

// unfortunately this is required, because IE 11 does not support the
// `once` option: https://caniuse.com/#feat=once-event-listener
unbind = () => {
this.object.removeEventListener(this.eventName, fn);
};

return unbind;
} else {
// assume that we're dealing with either `Ember.Evented` or a compatible
// interface, like jQuery.
this.object.one(this.eventName, fn);

return () => {
this.object.off(this.eventName, fn);
};
}
}
}

Expand Down Expand Up @@ -82,7 +101,6 @@ export function waitForQueue(queueName) {
* @param {function} eventName the name of the event to wait for
*/
export function waitForEvent(object, eventName) {
assert(`${object} must include Ember.Evented (or support \`.on()\`, \`.one()\`, and \`.off()\`) to be able to use \`waitForEvent\``, isEventedObject(object));
assert(`${object} must include Ember.Evented (or support \`.one()\` and \`.off()\`) or DOM EventTarget (or support \`addEventListener\` and \`removeEventListener\`) to be able to use \`waitForEvent\``, isEventedObject(object));
return new WaitForEventYieldable(object, eventName);
}

8 changes: 4 additions & 4 deletions addon/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import ComputedProperty from '@ember/object/computed';
import Ember from 'ember';

export function isEventedObject(c) {
return (c &&
typeof c.one === 'function' &&
typeof c.off === 'function');
return (c && (
(typeof c.one === 'function' && typeof c.off === 'function') ||
(typeof c.addEventListener === 'function' && typeof c.removeEventListener === 'function')
));
}

export function Arguments(args, defer) {
Expand Down Expand Up @@ -145,4 +146,3 @@ export function rawTimeout(ms) {
}
};
}

2 changes: 1 addition & 1 deletion tests/dummy/app/docs/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default Controller.extend({
{ route: "docs.task-groups", title: "Task Groups" },
{ route: "docs.derived-state", title: "Derived State" },
{ route: "docs.encapsulated-task", title: "Encapsulated Tasks" },
{ route: "docs.events", title: "Ember / jQuery Events" },
{ route: "docs.events", title: "Ember & DOM / jQuery Events" },
{ route: "docs.testing-debugging", title: "Testing & Debugging" },
{ route: "docs.faq", title: "FAQ & Fact Sheet" },

Expand Down
13 changes: 10 additions & 3 deletions tests/dummy/app/docs/events/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@ import { task, waitForEvent, timeout } from 'ember-concurrency';

export default Controller.extend(Evented, {
// BEGIN-SNIPPET waitForEvent
domEvent: null,
domEventLoop: task(function * () {
while(true) {
let event = yield waitForEvent(document.body, 'click');
this.set('domEvent', event);
this.trigger('fooEvent', { v: Math.random() });
}
}).on('init'),

jQueryEvent: null,
jQueryEventLoop: task(function * () {
let $body = $('body');
while(true) {
let event = yield waitForEvent($body, 'click');
this.set('jQueryEvent', event);
this.trigger('fooEvent', { v: Math.random() });
}
}).on('init'),

Expand All @@ -34,9 +42,8 @@ export default Controller.extend(Evented, {
}).on('init'),

waiter: task(function * () {
let event = yield waitForEvent($('body'), 'click');
let event = yield waitForEvent(document.body, 'click');
return event;
}),
// END-SNIPPET
});

16 changes: 11 additions & 5 deletions tests/dummy/app/docs/events/template.hbs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<h3>Waiting for Ember / jQuery Events</h3>
<h3>Waiting for Ember & DOM / jQuery Events</h3>

<p>
You can use <code>waitForEvent</code> to pause your task until
an Ember.Evented or jQuery Event fires.
You can use <code>waitForEvent(object, eventName)</code> to pause your task until
an Ember.Evented or DOM / jQuery Event fires.
<code>object</code> must include Ember.Evented (or support <code>.one()</code>
and <code>.off()</code>) or be a valid DOM EventTarget (or support
<code>.addEventListener()</code> and <code>.removeEventListener()</code>).
</p>

<p>
Expand All @@ -15,11 +18,15 @@

<p>
Try clicking around the page; <code>waitForEvent</code> will install
handlers and wait for the specified Ember or jQuery event to fire;
handlers and wait for the specified Ember, DOM or jQuery event to fire;
the value returned from <code>yield</code> is the event that was fired.
</p>

{{!BEGIN-SNIPPET waitForEvent}}
<h4>
domEvent: (x={{domEvent.offsetX}}, y={{domEvent.offsetX}})
</h4>

<h4>
jqueryEvent: (x={{jQueryEvent.offsetX}}, y={{jQueryEvent.offsetX}})
</h4>
Expand Down Expand Up @@ -56,4 +63,3 @@

{{code-snippet name="waitForEvent-derived-state.js"}}
{{code-snippet name="waitForEvent-derived-state.hbs"}}

66 changes: 64 additions & 2 deletions tests/unit/wait-for-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ test('cancelling waitForQueue works', function(assert) {
assert.notOk(taskCompleted, 'Task should not have completed');
});

test('waitForEvent works', function(assert) {
test('waitForEvent works (`Ember.Evented` interface)', function(assert) {
assert.expect(4);

let taskCompleted = false;
Expand Down Expand Up @@ -77,7 +77,7 @@ test('waitForEvent works', function(assert) {
assert.ok(taskCompleted, 'Task should have completed');
});

test('canceling waitForEvent works', function(assert) {
test('canceling waitForEvent works (`Ember.Evented` interface)', function(assert) {
assert.expect(4);

let taskCompleted = false;
Expand Down Expand Up @@ -105,3 +105,65 @@ test('canceling waitForEvent works', function(assert) {
assert.notOk(obj.has('foo'), 'Object does not have the event listener');
assert.notOk(taskCompleted, 'Task should not have completed');
});

test('waitForEvent works (DOM `EventTarget` interface)', function(assert) {
assert.expect(3);

const element = document.createElement('button');
let taskCompleted = false;
let obj;

const Obj = EventedObject.extend({
task: task(function*() {
let { detail } = yield waitForEvent(element, 'foo');
assert.equal(detail, 123);
taskCompleted = true;
})
});

run(() => {
obj = Obj.create();
obj.get('task').perform();
});

run(() => {
assert.notOk(taskCompleted, 'Task should not have completed');
element.dispatchEvent(new CustomEvent('foo', { detail: 123 }));
});

assert.ok(taskCompleted, 'Task should have completed');
});

test('canceling waitForEvent works (DOM `EventTarget` interface)', function(assert) {
assert.expect(3);

const element = document.createElement('button');
element.removeEventListener = (...args) => {
removeEventListenerCalled = true;
return HTMLElement.prototype.removeEventListener.apply(element, args);
};
let taskCompleted = false;
let removeEventListenerCalled = false;
let obj;

const Obj = EventedObject.extend({
task: task(function*() {
yield waitForEvent(element, 'foo');
taskCompleted = true;
})
});

run(() => {
obj = Obj.create();
obj.get('task').perform();
});

run(() => {
assert.notOk(taskCompleted, 'Task should not have completed');
obj.get('task').cancelAll();
element.dispatchEvent(new CustomEvent('foo'));
});

assert.ok(removeEventListenerCalled, '`removeEventListener` was called');
assert.notOk(taskCompleted, 'Task should not have completed');
});