SHOW:
|
|
- or go back to the newest paste.
1 | /** | |
2 | * Arranges loosely coupled interactions between components with a pipeline based on jQuery's promise implementation. | |
3 | * | |
4 | * Interactions are set up by building a chain of handling stages that are invoked in turn when the interaction is triggered. | |
5 | * Just like a promise, an interaction can be in one of three states: pending, resolved (successful) or rejected (failed). | |
6 | * Unlike promises however, once an interaction is triggered its state can change between resolved and rejected as a result | |
7 | * of each handling stage; if this happens, later stages will see the new state (possibly until it is again changed, etc). | |
8 | * All interactions are set up in the context of an object (typically this is a DOM element). Finally, just like promises | |
9 | * interactions carry a payload which is independent of their state and can also change between stages. | |
10 | * | |
11 | * To set up an interaction, call this function with the name of the interaction and one or more handler functions. This will | |
12 | * add one stage to the interaction each time and can be repeated as many times as desired. The aim of interactions is to | |
13 | * facilitate loosely coupled workflows, so typically stages are typically added in a bottom-up manner: the first stage is | |
14 | * added by the most specialized application component and higher-level components progressively build on top of that. | |
15 | * | |
16 | * When setting up the signature of this function is similar to <code>$.Deferred.pipe</code>: the first handler is invoked if | |
17 | * the interaction reaches the current stage in a resolved state, the second if the interaction is in a rejected state, and | |
18 | * the third receives progress notifications. If a handler is omitted then no processing is done for the current stage and | |
19 | * the interaction moves to the next stage in the same state as before. To create a stage with only a "rejected" handler | |
20 | * pass <code>null</code> as the value of the "resolved" handler, again just as when calling <code>$.Deferred.pipe</code>. | |
21 | * | |
22 | * All interaction handler functions have the same signature: <code>function handler(payload, isLastStage)</code>. The two | |
23 | * parameters provide the current payload and a boolean flag that lets each stage know if it is the last one in the chain; | |
24 | * the current context (<code>this</code>) is set to the object on which the interaction has been triggered; the state of the | |
25 | * interaction is not provided because it is implicit (a different handler is called for each possible state). Handlers | |
26 | * influence the interaction based on their return value: | |
27 | * | |
28 | * <ul> | |
29 | * <li> | |
30 | * returning anything that is not a promise results in the state of the interaction remaining unchanged but its payload | |
31 | * being modified (for example, a bare <code>return</code> results in the payload being changed to <code>undefined</code>, | |
32 | * while <code>return payload</code> results in both state and payload remaining unchanged | |
33 | * </li> | |
34 | * <li> | |
35 | * returning a resolved promise results in potentially both the state and the payload of the interaction being changed | |
36 | * before handling reaches the next stage (for example, returning <code>$.Deferred().reject(payload)</code> results in the | |
37 | * state being set to "rejected" while the payload remains unchanged) | |
38 | * </li> | |
39 | * <li> | |
40 | * returning an unresolved promise results in handling being "paused" until such time as the promise is fulfilled; when | |
41 | * this happens, handling will be resumed from the next stage with interaction state and payload being dictated by the | |
42 | * state and payload of the promise | |
43 | * </li> | |
44 | * </ul> | |
45 | * | |
46 | * To trigger an interaction, call this function with the name of the interaction and a parameter that determines the initial | |
47 | * state and payload of the interaction. The value of this parameter affects state and payload in the same manner as the | |
48 | * return value of handlers as documented above but with two differences: | |
49 | * | |
50 | * <ul> | |
51 | * <li> | |
52 | * if no value is provided then the result is the same as if the value were <code>undefined</code> (see next bullet) | |
53 | * </li> | |
54 | * <li> | |
55 | * a value that is not a promise is treated as a resolved promise with payload equal to the value | |
56 | * </li> | |
57 | * <li> | |
58 | * a value of <code>null</code> cannot be provided directly because this usage is ambiguous with setting up an additional | |
59 | * state without a "success" handler; if you want to trigger the interaction in a resolved state and with <code>null</code> | |
60 | * payload, pass <code>$.Deferred().resolve(null)</code> as the parameter | |
61 | * </li> | |
62 | * </ul> | |
63 | * | |
64 | * @since 1.8.0 | |
65 | */ | |
66 | $.fn.interaction = function(name /*, [payload | doneHandler], [failHandler], [progressHandler] */) { | |
67 | - | var dataKey = "inode.interactionQueue." + name, |
67 | + | var dataKey = "pj.interactionQueue." + name, |
68 | - | eventName = "event:" + name + ".interaction.inode", |
68 | + | eventName = "event:" + name + ".interaction.pj", |
69 | payload = arguments[1]; | |
70 | ||
71 | if (payload !== null && !$.isFunction(payload)) { | |
72 | // Trigger mode: if it's not a function, it's something we 'll use to trigger the interaction | |
73 | if (typeof payload === "undefined") { | |
74 | // no data provided; create a new Deferred and resolve it on the spot | |
75 | payload = $.Deferred().resolve(); | |
76 | } | |
77 | else if (!$.isFunction(payload.promise)) { | |
78 | // data provided but it's not a promise; create a fulfilled promise from it | |
79 | payload = $.Deferred().resolve(payload); | |
80 | } | |
81 | // .triggerHandler instead of .trigger because we don't want the event to bubble up -- this | |
82 | // would result in interactions with the same name on ancestors being spuriously fired. | |
83 | this.each(function() { | |
84 | $(this).triggerHandler(eventName, [payload]); | |
85 | }); | |
86 | return this; | |
87 | } | |
88 | ||
89 | // Setup mode: adds handlers to the interaction | |
90 | var handlers = Array.prototype.slice.call(arguments, 1); | |
91 | ||
92 | // Loop over each element in turn so that queues are independent of each other | |
93 | return this.each(function() { | |
94 | var queue = $(this).data(dataKey); | |
95 | if (typeof queue === "undefined") { | |
96 | var self = this; | |
97 | $(this) | |
98 | .data(dataKey, queue = []) | |
99 | .bind(eventName, function (ev, deferred) { | |
100 | $.each(queue, function (i) { | |
101 | deferred = deferred.pipe.apply(self, $.map(this, | |
102 | function(handlerToWrap) { | |
103 | return handlerToWrap === null | |
104 | ? [null] // $.map needs special treatment if you want to map to null or undefined! | |
105 | : function(result) {return handlerToWrap.call(self, result, i == queue.length - 1);}; | |
106 | })); | |
107 | }) | |
108 | }); | |
109 | } | |
110 | queue.push(handlers); | |
111 | }); | |
112 | }; |