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