1// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// ┌────────────────────────────────────────────────────────────┐ \\
15// │ Eve 0.5.0 - JavaScript Events Library                      │ \\
16// ├────────────────────────────────────────────────────────────┤ \\
17// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
18// └────────────────────────────────────────────────────────────┘ \\
19
20(function (glob) {
21    var version = "0.5.0",
22        has = "hasOwnProperty",
23        separator = /[\.\/]/,
24        comaseparator = /\s*,\s*/,
25        wildcard = "*",
26        fun = function () {},
27        numsort = function (a, b) {
28            return a - b;
29        },
30        current_event,
31        stop,
32        events = {n: {}},
33        firstDefined = function () {
34            for (var i = 0, ii = this.length; i < ii; i++) {
35                if (typeof this[i] != "undefined") {
36                    return this[i];
37                }
38            }
39        },
40        lastDefined = function () {
41            var i = this.length;
42            while (--i) {
43                if (typeof this[i] != "undefined") {
44                    return this[i];
45                }
46            }
47        },
48        objtos = Object.prototype.toString,
49        Str = String,
50        isArray = Array.isArray || function (ar) {
51            return ar instanceof Array || objtos.call(ar) == "[object Array]";
52        };
53    /*\
54     * eve
55     [ method ]
56
57     * Fires event with given `name`, given scope and other parameters.
58
59     > Arguments
60
61     - name (string) name of the *event*, dot (`.`) or slash (`/`) separated
62     - scope (object) context for the event handlers
63     - varargs (...) the rest of arguments will be sent to event handlers
64
65     = (object) array of returned values from the listeners. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value.
66    \*/
67        eve = function (name, scope) {
68            var e = events,
69                oldstop = stop,
70                args = Array.prototype.slice.call(arguments, 2),
71                listeners = eve.listeners(name),
72                z = 0,
73                f = false,
74                l,
75                indexed = [],
76                queue = {},
77                out = [],
78                ce = current_event,
79                errors = [];
80            out.firstDefined = firstDefined;
81            out.lastDefined = lastDefined;
82            current_event = name;
83            stop = 0;
84            for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
85                indexed.push(listeners[i].zIndex);
86                if (listeners[i].zIndex < 0) {
87                    queue[listeners[i].zIndex] = listeners[i];
88                }
89            }
90            indexed.sort(numsort);
91            while (indexed[z] < 0) {
92                l = queue[indexed[z++]];
93                out.push(l.apply(scope, args));
94                if (stop) {
95                    stop = oldstop;
96                    return out;
97                }
98            }
99            for (i = 0; i < ii; i++) {
100                l = listeners[i];
101                if ("zIndex" in l) {
102                    if (l.zIndex == indexed[z]) {
103                        out.push(l.apply(scope, args));
104                        if (stop) {
105                            break;
106                        }
107                        do {
108                            z++;
109                            l = queue[indexed[z]];
110                            l && out.push(l.apply(scope, args));
111                            if (stop) {
112                                break;
113                            }
114                        } while (l)
115                    } else {
116                        queue[l.zIndex] = l;
117                    }
118                } else {
119                    out.push(l.apply(scope, args));
120                    if (stop) {
121                        break;
122                    }
123                }
124            }
125            stop = oldstop;
126            current_event = ce;
127            return out;
128        };
129        // Undocumented. Debug only.
130        eve._events = events;
131    /*\
132     * eve.listeners
133     [ method ]
134
135     * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
136
137     > Arguments
138
139     - name (string) name of the event, dot (`.`) or slash (`/`) separated
140
141     = (array) array of event handlers
142    \*/
143    eve.listeners = function (name) {
144        var names = isArray(name) ? name : name.split(separator),
145            e = events,
146            item,
147            items,
148            k,
149            i,
150            ii,
151            j,
152            jj,
153            nes,
154            es = [e],
155            out = [];
156        for (i = 0, ii = names.length; i < ii; i++) {
157            nes = [];
158            for (j = 0, jj = es.length; j < jj; j++) {
159                e = es[j].n;
160                items = [e[names[i]], e[wildcard]];
161                k = 2;
162                while (k--) {
163                    item = items[k];
164                    if (item) {
165                        nes.push(item);
166                        out = out.concat(item.f || []);
167                    }
168                }
169            }
170            es = nes;
171        }
172        return out;
173    };
174    /*\
175     * eve.separator
176     [ method ]
177
178     * If for some reasons you don’t like default separators (`.` or `/`) you can specify yours
179     * here. Be aware that if you pass a string longer than one character it will be treated as
180     * a list of characters.
181
182     - separator (string) new separator. Empty string resets to default: `.` or `/`.
183    \*/
184    eve.separator = function (sep) {
185        if (sep) {
186            sep = Str(sep).replace(/(?=[\.\^\]\[\-])/g, "\\");
187            sep = "[" + sep + "]";
188            separator = new RegExp(sep);
189        } else {
190            separator = /[\.\/]/;
191        }
192    };
193    /*\
194     * eve.on
195     [ method ]
196     **
197     * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
198     | eve.on("*.under.*", f);
199     | eve("mouse.under.floor"); // triggers f
200     * Use @eve to trigger the listener.
201     **
202     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
203     - f (function) event handler function
204     **
205     - name (array) if you don’t want to use separators, you can use array of strings
206     - f (function) event handler function
207     **
208     = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment.
209     > Example:
210     | eve.on("mouse", eatIt)(2);
211     | eve.on("mouse", scream);
212     | eve.on("mouse", catchIt)(1);
213     * This will ensure that `catchIt` function will be called before `eatIt`.
214     *
215     * If you want to put your handler before non-indexed handlers, specify a negative value.
216     * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
217    \*/
218    eve.on = function (name, f) {
219        if (typeof f != "function") {
220            return function () {};
221        }
222        var names = isArray(name) ? (isArray(name[0]) ? name : [name]) : Str(name).split(comaseparator);
223        for (var i = 0, ii = names.length; i < ii; i++) {
224            (function (name) {
225                var names = isArray(name) ? name : Str(name).split(separator),
226                    e = events,
227                    exist;
228                for (var i = 0, ii = names.length; i < ii; i++) {
229                    e = e.n;
230                    e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}});
231                }
232                e.f = e.f || [];
233                for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
234                    exist = true;
235                    break;
236                }
237                !exist && e.f.push(f);
238            }(names[i]));
239        }
240        return function (zIndex) {
241            if (+zIndex == +zIndex) {
242                f.zIndex = +zIndex;
243            }
244        };
245    };
246    /*\
247     * eve.f
248     [ method ]
249     **
250     * Returns function that will fire given event with optional arguments.
251     * Arguments that will be passed to the result function will be also
252     * concated to the list of final arguments.
253     | el.onclick = eve.f("click", 1, 2);
254     | eve.on("click", function (a, b, c) {
255     |     console.log(a, b, c); // 1, 2, [event object]
256     | });
257     > Arguments
258     - event (string) event name
259     - varargs (…) and any other arguments
260     = (function) possible event handler function
261    \*/
262    eve.f = function (event) {
263        var attrs = [].slice.call(arguments, 1);
264        return function () {
265            eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0)));
266        };
267    };
268    /*\
269     * eve.stop
270     [ method ]
271     **
272     * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
273    \*/
274    eve.stop = function () {
275        stop = 1;
276    };
277    /*\
278     * eve.nt
279     [ method ]
280     **
281     * Could be used inside event handler to figure out actual name of the event.
282     **
283     > Arguments
284     **
285     - subname (string) #optional subname of the event
286     **
287     = (string) name of the event, if `subname` is not specified
288     * or
289     = (boolean) `true`, if current event’s name contains `subname`
290    \*/
291    eve.nt = function (subname) {
292        var cur = isArray(current_event) ? current_event.join(".") : current_event;
293        if (subname) {
294            return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(cur);
295        }
296        return cur;
297    };
298    /*\
299     * eve.nts
300     [ method ]
301     **
302     * Could be used inside event handler to figure out actual name of the event.
303     **
304     **
305     = (array) names of the event
306    \*/
307    eve.nts = function () {
308        return isArray(current_event) ? current_event : current_event.split(separator);
309    };
310    /*\
311     * eve.off
312     [ method ]
313     **
314     * Removes given function from the list of event listeners assigned to given name.
315     * If no arguments specified all the events will be cleared.
316     **
317     > Arguments
318     **
319     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
320     - f (function) event handler function
321    \*/
322    /*\
323     * eve.unbind
324     [ method ]
325     **
326     * See @eve.off
327    \*/
328    eve.off = eve.unbind = function (name, f) {
329        if (!name) {
330            eve._events = events = {n: {}};
331            return;
332        }
333        var names = isArray(name) ? (isArray(name[0]) ? name : [name]) : Str(name).split(comaseparator);
334        if (names.length > 1) {
335            for (var i = 0, ii = names.length; i < ii; i++) {
336                eve.off(names[i], f);
337            }
338            return;
339        }
340        names = isArray(name) ? name : Str(name).split(separator);
341        var e,
342            key,
343            splice,
344            i, ii, j, jj,
345            cur = [events];
346        for (i = 0, ii = names.length; i < ii; i++) {
347            for (j = 0; j < cur.length; j += splice.length - 2) {
348                splice = [j, 1];
349                e = cur[j].n;
350                if (names[i] != wildcard) {
351                    if (e[names[i]]) {
352                        splice.push(e[names[i]]);
353                    }
354                } else {
355                    for (key in e) if (e[has](key)) {
356                        splice.push(e[key]);
357                    }
358                }
359                cur.splice.apply(cur, splice);
360            }
361        }
362        for (i = 0, ii = cur.length; i < ii; i++) {
363            e = cur[i];
364            while (e.n) {
365                if (f) {
366                    if (e.f) {
367                        for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
368                            e.f.splice(j, 1);
369                            break;
370                        }
371                        !e.f.length && delete e.f;
372                    }
373                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
374                        var funcs = e.n[key].f;
375                        for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
376                            funcs.splice(j, 1);
377                            break;
378                        }
379                        !funcs.length && delete e.n[key].f;
380                    }
381                } else {
382                    delete e.f;
383                    for (key in e.n) if (e.n[has](key) && e.n[key].f) {
384                        delete e.n[key].f;
385                    }
386                }
387                e = e.n;
388            }
389        }
390    };
391    /*\
392     * eve.once
393     [ method ]
394     **
395     * Binds given event handler with a given name to only run once then unbind itself.
396     | eve.once("login", f);
397     | eve("login"); // triggers f
398     | eve("login"); // no listeners
399     * Use @eve to trigger the listener.
400     **
401     > Arguments
402     **
403     - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
404     - f (function) event handler function
405     **
406     = (function) same return function as @eve.on
407    \*/
408    eve.once = function (name, f) {
409        var f2 = function () {
410            eve.off(name, f2);
411            return f.apply(this, arguments);
412        };
413        return eve.on(name, f2);
414    };
415    /*\
416     * eve.version
417     [ property (string) ]
418     **
419     * Current version of the library.
420    \*/
421    eve.version = version;
422    eve.toString = function () {
423        return "You are running Eve " + version;
424    };
425    (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define === "function" && define.amd ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
426})(this);
427