Skip to content

Instantly share code, notes, and snippets.

@kfranqueiro
Last active August 29, 2015 14:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kfranqueiro/b61a0d4d1939063842b2 to your computer and use it in GitHub Desktop.
Save kfranqueiro/b61a0d4d1939063842b2 to your computer and use it in GitHub Desktop.
Hashing out various ways to approach what would be accomplished with extension events in Dojo 1.
// Dojo 2 examples are in JS so it can be somewhat easily plugged into any page with dojo-core / dojo-dom loaded
// (e.g. Intern's client.html...)
// 1. Dojo 1 style extension events:
require([ 'dojo/on' ], function (on) {
function activate(node, listener) {
return on(node, 'click,keyup', function (event) {
if (event.keyCode === 13 || !('keyCode' in event)) {
listener.call(this, event);
}
});
}
on(node, activate, ...);
});
// 2. Using on more directly
// Con: would need to do something separate or based on extra argument for delegation if delegate is its own API
// (assuming delegate implementation currently in https://github.com/dojo/dom/pull/16)
require([ 'dojo-core/on', 'dojo-dom/delegate' ], function (on) {
function onActivate(target, listener) {
return on(target, [ 'click', 'keyup' ], function (event) {
if (event.keyCode === 13 || !('keyCode' in event)) {
listener.call(this, event);
}
});
}
onActivate(node, ...);
function delegateActivate(target, selector, listener) {
return delegate(target, selector, [ 'click', 'keyup' ], function (event) {
if (event.keyCode === 13 || !('keyCode' in event)) {
listener.call(this, event);
}
});
}
delegateActivate(node, '.selector', ...);
});
// 3. Wrapping the handler only
// Pro: doesn't have the disadvantage in #2
// Con: you need to set the event type correctly yourself
require([ 'dojo-core/on', 'dojo-dom/delegate' ], function (on) {
function fireOnActivate(listener) {
if (event.keyCode === 13 || !('keyCode' in event)) {
listener.call(this, event);
}
}
on(node, [ 'click', 'keyup' ], fireOnActivate(...));
delegate(node, '.selector', [ 'click', 'keyup' ], fireOnActivate(...));
});

The main challenge with once and pausable is that they both need to reference both the listener function and the returned handle.

once:

  • Needs to wrap listener to remove handle when run
  • Needs handle to be referenced within wrapped listener

pausable:

  • Needs to wrap listener to check a flag
  • Needs to add pause/resume functions to handle

Dojo 1 of course tackles this by implementing once and pausable as APIs with the same signature as on itself, and they both call on internally. This would preclude easily reusing them for other callback/handler-based APIs (e.g. aspect).

These could ostensibly be written as Dojo 1 style extension events, but that would still not allow them to be used outside of on, and we've pretty much killed extension events for the time being otherwise.

Here are a few ideas:

  • Pass the listener API to the function, e.g. once(on, node, 'click', handler)
    • Terrible because there'd be no way to type-check once and pausable
  • Make APIs for each thing that should be once-able / pausable-able(-able-able...)
    • Terrible because Oprah syndrome (and YOU get once and pausable APIs! And YOU get once and pausable APIs! ...)
    • I guess this could be maybe not terrible if we limited it, e.g. to on.once, on.pausable, and aspect.once (presumably a once version of aspect.on, though maybe aspect.after is more useful).
    • For that matter, if we're thinking of replacing sinon with our own utilities, we could work one in for aspect.whatever and then just worry about on.once and on.pausable in core itself. Though then we're basically back to Dojo 1's way.
  • Make APIs capable of wrapping the original function and returning a once/pausable version?
    • I've started trying to throw together an example of this for once and it looks hairier than it's worth. Meanwhile I'm not sure it's feasible for pausable because the new function needs to return a different type of handle than the old function (with pause/resume).
// NOTE: This is totally rough and presently untested
import { Handle } from './interfaces';
function indexOfLastFunction(args: any[]) {
for (i = args.length; i--;) {
if (typeof args[i] === 'function') {
return i;
}
}
return -1;
}
export function createOnceFunction<T>(func: T): T {
return function (...args: any[]) {
const callbackIndex = indexOfLastFunction(args);
const callback = args[callbackIndex];
let handle: IHandle;
const newCallback = function () {
callback.apply(this, arguments);
handle.destroy();
};
args.splice(callbackIndex, 1, newCallback);
return func.apply(this, args);
};
}
@bitpshr
Copy link

bitpshr commented Jun 2, 2015

I think line 23 should be an array as well.

@kfranqueiro
Copy link
Author

Yup. Just fixed that (been a while since I had time to think about this) and added a possibly ridiculous idea for once/pausable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment