├── .gitignore ├── .npmignore ├── tests ├── noconflict_fixture.js ├── benchmark │ ├── nano.jar │ ├── nwevents-pubsub.js │ ├── bean_04.js │ └── bean_05.js ├── noconflict-test.js ├── tests.html ├── fire-test.js ├── custom-test.js ├── common.js ├── clone-test.js ├── custom-types-test.js ├── support │ └── syn │ │ ├── browsers.js │ │ ├── mouse.js │ │ └── key.js ├── remove-test.js ├── delegate-test.js ├── add-test.js ├── namespace-test.js └── event-object-test.js ├── src ├── copyright.js └── ender.js ├── Makefile ├── component.json ├── buster.js ├── make ├── component.js └── build.js ├── package.json ├── .jshintrc ├── LICENSE ├── integration └── index.html ├── bean.min.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | node_modules 3 | /build 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | tests/benchmark/ 3 | integration/ -------------------------------------------------------------------------------- /tests/noconflict_fixture.js: -------------------------------------------------------------------------------- 1 | function bean() { return 'success' } 2 | bean.NOTBEAN = true -------------------------------------------------------------------------------- /tests/benchmark/nano.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fat/bean/HEAD/tests/benchmark/nano.jar -------------------------------------------------------------------------------- /src/copyright.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bean - copyright (c) Jacob Thornton 2011-2012 3 | * https://github.com/fat/bean 4 | * MIT license 5 | */ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: boosh component_json 2 | 3 | boosh: 4 | node make/build.js 5 | 6 | component_json: 7 | node make/component.js 8 | 9 | components: component_json 10 | component build 11 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bean", 3 | "description": "A small, fast, framework-agnostic event manager", 4 | "version": "1.0.14", 5 | "keywords": [ 6 | "ender", 7 | "events", 8 | "event", 9 | "dom" 10 | ], 11 | "main": "bean.js", 12 | "scripts": [ 13 | "bean.js" 14 | ], 15 | "repo": "https://github.com/fat/bean" 16 | } -------------------------------------------------------------------------------- /tests/noconflict-test.js: -------------------------------------------------------------------------------- 1 | /*global bean:true, buster:true, assert:true*/ 2 | 3 | buster.testCase('noConflict', { 4 | 5 | 'noConflict': function () { 6 | this.b = bean.noConflict() 7 | assert(this.b) 8 | assert.equals(bean(), 'success') 9 | } 10 | 11 | , 'tearDown': function () { 12 | window.bean = this.b // reset 13 | } 14 | 15 | }) -------------------------------------------------------------------------------- /buster.js: -------------------------------------------------------------------------------- 1 | var config = module.exports 2 | 3 | config['Bean Tests'] = { 4 | environment: 'browser' 5 | , sources: [ 6 | 'node_modules/qwery/qwery.js' 7 | , 'tests/support/syn/synthetic.js' 8 | , 'tests/support/syn/mouse.js' 9 | , 'tests/support/syn/browsers.js' 10 | , 'tests/support/syn/key.js' 11 | , 'tests/noconflict_fixture.js' 12 | , 'src/bean.js' 13 | , 'tests/common.js' 14 | ] 15 | , tests: [ 16 | 'tests/*-test.js' 17 | ] 18 | } -------------------------------------------------------------------------------- /make/component.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var data = fs.readFileSync("package.json", "utf8"); 3 | var packagejson = JSON.parse(data); 4 | fs.writeFileSync("component.json", JSON.stringify({ 5 | name: packagejson.name, 6 | description: packagejson.description, 7 | version: packagejson.version, 8 | keywords: packagejson.keywords, 9 | main: "bean.js", 10 | scripts: ["bean.js"], 11 | repo: "https://github.com/fat/bean" 12 | }, null, 2) 13 | ); 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bean", 3 | "description": "A small, fast, framework-agnostic event manager", 4 | "version": "1.0.14", 5 | "homepage": "https://github.com/fat/bean", 6 | "authors": [ 7 | "Jacob Thornton <@fat>", 8 | "Rod Vagg <@rvagg>", 9 | "Dustin Diaz <@ded>" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/fat/bean.git" 14 | }, 15 | "keywords": [ 16 | "ender", 17 | "events", 18 | "event", 19 | "dom" 20 | ], 21 | "main": "./bean.js", 22 | "devDependencies": { 23 | "buster": "~0.6.2", 24 | "smoosh": "~0.4.1", 25 | "qwery": "~3.3.11" 26 | }, 27 | "ender": "./src/ender.js" 28 | } 29 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ "assert", "refute", "define", "self" ] 3 | , "boss": true 4 | , "bitwise": false 5 | , "shadow": true 6 | , "trailing": true 7 | , "immed": true 8 | , "latedef": true 9 | , "forin": false 10 | , "curly": false 11 | , "debug": true 12 | , "devel": false 13 | , "evil": true 14 | , "regexp": false 15 | , "undef": true 16 | , "sub": true 17 | , "white": false 18 | , "asi": true 19 | , "laxbreak": true 20 | , "eqnull": true 21 | , "browser": true 22 | , "node": true 23 | , "laxcomma": true 24 | , "proto": true 25 | , "expr": true 26 | , "es5": true 27 | , "strict": false 28 | , "quotmark": true 29 | , "camelcase": true 30 | } -------------------------------------------------------------------------------- /make/build.js: -------------------------------------------------------------------------------- 1 | require("smoosh").config({ 2 | "JAVASCRIPT": { 3 | "DIST_DIR": "./" 4 | , "bean": [ 5 | "./src/copyright.js" 6 | , "./src/bean.js" 7 | ] 8 | } 9 | , "JSHINT_OPTS": { 10 | "predef": [ "assert", "refute", "define", "self" ] 11 | , "boss": true 12 | , "bitwise": false 13 | , "shadow": true 14 | , "trailing": true 15 | , "immed": true 16 | , "latedef": true 17 | , "forin": false 18 | , "curly": false 19 | , "debug": true 20 | , "devel": false 21 | , "evil": true 22 | , "regexp": false 23 | , "undef": true 24 | , "sub": true 25 | , "asi": true 26 | , "laxbreak": true 27 | , "eqnull": true 28 | , "browser": true 29 | , "node": true 30 | , "laxcomma": true 31 | , "proto": true 32 | , "expr": true 33 | , "es5": false 34 | , "strict": false 35 | , "quotmark": true 36 | , "camelcase": true 37 | } 38 | }).run().build().analyze() 39 | -------------------------------------------------------------------------------- /tests/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bean tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011-2012, Jacob Thornton (the "Original Author") 2 | All rights reserved. 3 | 4 | MIT 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the "Software"), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | Distributions of all or part of the Software intended to be used 19 | by the recipients as they would use the unmodified Software, 20 | containing modifications that substantially alter, remove, or 21 | disable functionality of the Software, outside of the documented 22 | configuration mechanisms provided by the Software, shall be 23 | modified such that the Original Author's bug reporting email 24 | addresses and urls are either replaced with the contact information 25 | of the parties responsible for the changes, or removed entirely. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 29 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 30 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 31 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 32 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 34 | OTHER DEALINGS IN THE SOFTWARE. 35 | 36 | 37 | Except where noted, this license applies to any and all software 38 | programs and associated documentation files created by the 39 | Original Author, when distributed with the Software. -------------------------------------------------------------------------------- /src/ender.js: -------------------------------------------------------------------------------- 1 | !function ($) { 2 | var b = require('bean') 3 | 4 | , integrate = function (method, type, method2) { 5 | var _args = type ? [type] : [] 6 | return function () { 7 | for (var i = 0, l = this.length; i < l; i++) { 8 | if (!arguments.length && method == 'on' && type) method = 'fire' 9 | b[method].apply(this, [this[i]].concat(_args, Array.prototype.slice.call(arguments, 0))) 10 | } 11 | return this 12 | } 13 | } 14 | 15 | , add = integrate('add') 16 | , on = integrate('on') 17 | , one = integrate('one') 18 | , off = integrate('off') 19 | , fire = integrate('fire') 20 | , clone = integrate('clone') 21 | 22 | , hover = function (enter, leave, i) { // i for internal 23 | for (i = this.length; i--;) { 24 | b['on'].call(this, this[i], 'mouseenter', enter) 25 | b['on'].call(this, this[i], 'mouseleave', leave) 26 | } 27 | return this 28 | } 29 | 30 | , methods = { 31 | 'on' : on 32 | , 'addListener' : on 33 | , 'bind' : on 34 | , 'listen' : on 35 | , 'delegate' : add // jQuery compat, same arg order as add() 36 | 37 | , 'one' : one 38 | 39 | , 'off' : off 40 | , 'unbind' : off 41 | , 'unlisten' : off 42 | , 'removeListener' : off 43 | , 'undelegate' : off 44 | 45 | , 'emit' : fire 46 | , 'trigger' : fire 47 | 48 | , 'cloneEvents' : clone 49 | 50 | , 'hover' : hover 51 | } 52 | 53 | , shortcuts = 54 | ('blur change click dblclick error focus focusin focusout keydown keypress ' 55 | + 'keyup load mousedown mouseenter mouseleave mouseout mouseover mouseup ' 56 | + 'mousemove resize scroll select submit unload').split(' ') 57 | 58 | for (var i = shortcuts.length; i--;) { 59 | methods[shortcuts[i]] = integrate('on', shortcuts[i]) 60 | } 61 | 62 | b['setSelectorEngine']($) 63 | 64 | $.ender(methods, true) 65 | }(ender); -------------------------------------------------------------------------------- /tests/fire-test.js: -------------------------------------------------------------------------------- 1 | /*global bean:true, buster:true, Syn:true, assert:true, defer:true, features:true, globalSetUp:true, globalTearDown:true*/ 2 | 3 | buster.testCase('fire', { 4 | 'setUp': globalSetUp 5 | , 'tearDown': globalTearDown 6 | 7 | , 'should be able to fire an event': function (done) { 8 | var el = this.byId('input') 9 | , trigger = this.trigger() 10 | , spy = this.spy() 11 | 12 | trigger.after(function () { 13 | assert(spy.calledOnce, 'fires an event') 14 | done() 15 | }) 16 | 17 | bean.on(el, 'click', trigger.wrap(spy)) 18 | bean.fire(el, 'click') 19 | } 20 | 21 | , 'should be able to fire multiple events by space seperation': function (done) { 22 | var el = this.byId('input') 23 | , trigger = this.trigger() 24 | , mouseDownSpy = this.spy() 25 | , mouseUpSpy = this.spy() 26 | 27 | trigger.after(function () { 28 | assert(mouseDownSpy.calledOnce, 'fires multiple events by space seperation (mousedown)') 29 | assert(mouseUpSpy.calledOnce , 'fires multiple events by space seperation (mouseup)') 30 | done() 31 | }) 32 | 33 | bean.on(el, 'mousedown', trigger.wrap(mouseDownSpy)) 34 | bean.on(el, 'mouseup', trigger.wrap(mouseUpSpy)) 35 | bean.fire(el, 'mousedown mouseup') 36 | } 37 | 38 | , 'should be able to pass multiple arguments to custom event': function (done) { 39 | // jquery like array syntax 40 | var el = this.byId('input') 41 | , trigger = this.trigger() 42 | , spy = this.spy() 43 | 44 | trigger.after(function () { 45 | assert.equals(spy.callCount, 1, 'single call') 46 | assert.equals(spy.firstCall.args.length, 3, 'called with 3 arguments') 47 | assert.equals(spy.firstCall.args[0], 1, 'called with correct argument 1') 48 | assert.equals(spy.firstCall.args[1], 2, 'called with correct argument 2') 49 | assert.equals(spy.firstCall.args[2], 3, 'called with correct argument 3') 50 | done() 51 | }) 52 | 53 | bean.on(el, 'foo', trigger.wrap(spy)) 54 | bean.fire(el, 'foo', [1, 2, 3]) 55 | } 56 | }) -------------------------------------------------------------------------------- /tests/custom-test.js: -------------------------------------------------------------------------------- 1 | /*global bean:true, buster:true, Syn:true, assert:true, defer:true, features:true, globalSetUp:true, globalTearDown:true*/ 2 | 3 | buster.testCase('custom', { 4 | 'setUp': globalSetUp 5 | , 'tearDown': globalTearDown 6 | 7 | , 'should be able to add single custom events': function (done) { 8 | var el = this.byId('input') 9 | , trigger = this.trigger() 10 | , spy = this.spy() 11 | 12 | trigger.after(function () { 13 | assert(spy.calledOnce, 'add single custom events') 14 | done() 15 | }) 16 | 17 | bean.add(el, 'partytime', trigger.wrap(spy)) 18 | bean.fire(el, 'partytime') 19 | } 20 | 21 | , 'should bubble up dom like traditional events': function (done) { 22 | if (features.w3c) { 23 | //dean edwards' onpropertychange hack doesn't bubble unfortunately :( 24 | var el1 = this.byId('foo') 25 | , el2 = this.byId('bar') 26 | , trigger = this.trigger() 27 | , spy = this.spy() 28 | 29 | trigger.after(function () { 30 | assert(spy.calledOnce, 'bubbles up dom like traditional events') 31 | done() 32 | }) 33 | 34 | bean.add(el1, 'partytime', trigger.wrap(spy)) 35 | bean.fire(el2, 'partytime') 36 | } else { 37 | assert(true, 'onpropertychange bubbling not supported by this browser, test bypassed') 38 | done() 39 | } 40 | } 41 | 42 | , 'should be able to add, fire and remove custom events to document': function (done) { 43 | var calls = 0 44 | , trigger = this.trigger() 45 | 46 | this.removables.push(document) 47 | 48 | trigger.after(function () { 49 | assert.equals(calls, 1, 'add custom events to document') 50 | done() 51 | }) 52 | 53 | bean.add(document, 'justlookatthat', trigger.wrap(function () { 54 | calls++ 55 | bean.remove(document, 'justlookatthat') 56 | })) 57 | 58 | bean.fire(document, 'justlookatthat') 59 | bean.fire(document, 'justlookatthat') 60 | } 61 | 62 | , 'should be able to add, fire and remove custom events to window': function (done) { 63 | var calls = 0 64 | , trigger = this.trigger() 65 | 66 | trigger.after(function () { 67 | assert.equals(calls, 1, 'add custom events to window') 68 | done() 69 | }) 70 | 71 | this.removables.push(window) 72 | 73 | bean.add(window, 'spiffy', trigger.wrap(function () { 74 | calls++ 75 | bean.remove(window, 'spiffy') 76 | })) 77 | 78 | bean.fire(window, 'spiffy') 79 | bean.fire(window, 'spiffy') 80 | } 81 | }) -------------------------------------------------------------------------------- /tests/common.js: -------------------------------------------------------------------------------- 1 | /*global bean:true, qwery:true*/ 2 | 3 | var fixturesHTML = 4 | '\n' + 5 | '\n' + 6 | '
\n' + 7 | '
\n' + 8 | '
\n' + 9 | '
\n' + 10 | '
\n' + 11 | '
\n' + 12 | '
\n' + 13 | ' \n' + 14 | '
\n' 15 | 16 | var features = { 17 | w3c: !!window.addEventListener 18 | , qSA: !!document.querySelectorAll 19 | , createEvent: (function () { 20 | try { 21 | document.createEvent('KeyEvents') 22 | return true 23 | } catch (e) { 24 | try { 25 | document.createEvent('TextEvent') 26 | return true 27 | } catch (e) { } 28 | } 29 | return false 30 | }()) 31 | , message: !!window.postMessage 32 | , history: !!window.history && !!window.history.pushState 33 | } 34 | , defer = function (fn, t) { 35 | setTimeout(fn, t || 1) 36 | } 37 | , insertFixtures = function () { 38 | document.body.appendChild((function() { 39 | var fixtures = document.createElement('div') 40 | fixtures.id = 'fixtures' 41 | fixtures.innerHTML = fixturesHTML 42 | fixtures.style.position = 'absolute' 43 | fixtures.style.left = '-999px' 44 | return fixtures 45 | }())) 46 | } 47 | , removeFixtures = function () { 48 | document.body.removeChild(document.getElementById('fixtures')) 49 | } 50 | , SpyTrigger = function () {} 51 | , globalSetUp = function () { 52 | var removables = this.removables = [] 53 | this.timeout = 1000 54 | 55 | this.byId = function (id) { 56 | var el = document.getElementById(id) 57 | if (el) { 58 | bean.remove(el) 59 | removables.push(el) // auto clean up 60 | } 61 | return el 62 | } 63 | 64 | this.newObj = function () { 65 | var obj = {} 66 | removables.push(obj) // auto clean up 67 | return obj 68 | } 69 | 70 | this.createElement = function (tag) { 71 | var el = document.createElement(tag) 72 | removables.push(el) 73 | return el 74 | } 75 | 76 | this.trigger = function () { 77 | return new SpyTrigger() 78 | } 79 | } 80 | , globalTearDown = function () { 81 | for (var i = 0; i < this.removables.length; i++) 82 | bean.remove(this.removables[i]) 83 | 84 | //removeFixtures() 85 | } 86 | 87 | SpyTrigger.prototype.after = function (fn, delay) { 88 | this._after = fn 89 | this._delay = delay || 1 90 | } 91 | SpyTrigger.prototype.wrap = function (spy) { 92 | if (spy._SpyTriggerWrap) 93 | return spy._SpyTriggerWrap 94 | 95 | var self = this 96 | , fn = function () { 97 | self._trigger() 98 | spy.apply(this, arguments) 99 | } 100 | fn.$ = spy 101 | spy._SpyTriggerWrap = fn 102 | return fn 103 | } 104 | SpyTrigger.prototype.wrapped = function (spy) { 105 | return spy._SpyTriggerWrap 106 | } 107 | SpyTrigger.prototype._trigger = function () { 108 | if (this._after) { 109 | this.reset() 110 | this._timeout = setTimeout(this._after, this._delay) 111 | } 112 | } 113 | SpyTrigger.prototype.reset = function () { 114 | if (this._timeout) clearTimeout(this._timeout) 115 | } 116 | 117 | if (!window.console) window.console = { log: function () {}} 118 | insertFixtures() -------------------------------------------------------------------------------- /tests/clone-test.js: -------------------------------------------------------------------------------- 1 | /*global bean:true, qwery:true, buster:true, Syn:true, assert:true, defer:true, features:true, globalSetUp:true, globalTearDown:true*/ 2 | 3 | buster.testCase('clone', { 4 | 'setUp': globalSetUp 5 | , 'tearDown': globalTearDown 6 | 7 | , 'should be able to clone events of a specific type from one element to another': function (done) { 8 | var el1 = this.byId('input') 9 | , el2 = this.byId('input2') 10 | , trigger = this.trigger() 11 | , spy1 = this.spy() 12 | , spy2 = this.spy() 13 | , spy3 = this.spy() 14 | 15 | trigger.after(function () { 16 | assert.equals(spy1.callCount, 1, 'cloned first click handler') 17 | assert.equals(spy2.callCount, 1, 'cloned second click handler') 18 | assert.equals(spy3.callCount, 0, 'should not clone non-click handler') 19 | done() 20 | }) 21 | 22 | bean.add(el2, 'click' , trigger.wrap(spy1)) 23 | bean.add(el2, 'click' , trigger.wrap(spy2)) 24 | bean.add(el2, 'keydown', trigger.wrap(spy3)) 25 | 26 | bean.clone(el1, el2, 'click') 27 | 28 | Syn.click(el1) 29 | Syn.key('j', el1) 30 | } 31 | 32 | , 'should be able to clone all events from one element to another': function (done) { 33 | var el1 = this.byId('input') 34 | , el2 = this.byId('input2') 35 | , trigger = this.trigger() 36 | , spy1 = this.spy() 37 | , spy2 = this.spy() 38 | , spy3 = this.spy() 39 | 40 | trigger.after(function () { 41 | assert.equals(spy1.callCount, 1, 'cloned first click handler') 42 | assert.equals(spy2.callCount, 1, 'cloned second click handler') 43 | assert.equals(spy3.callCount, 1, 'cloned keydown handler') 44 | done() 45 | }) 46 | 47 | bean.add(el2, 'click' , trigger.wrap(spy1)) 48 | bean.add(el2, 'click' , trigger.wrap(spy2)) 49 | bean.add(el2, 'keydown', trigger.wrap(spy3)) 50 | 51 | bean.clone(el1, el2) 52 | 53 | Syn.click(el1) 54 | Syn.key('j', el1) 55 | } 56 | 57 | , 'should fire cloned event in scope of new element': function (done) { 58 | var el1 = this.byId('input') 59 | , el2 = this.byId('input2') 60 | , trigger = this.trigger() 61 | , spy = this.spy() 62 | 63 | trigger.after(function () { 64 | assert.equals(spy.callCount, 1, 'cloned click handler') 65 | assert.same(spy.thisValues[0], el2, 'cloned handler gets correct context (this)') 66 | done() 67 | }) 68 | 69 | bean.add(el1, 'click', trigger.wrap(spy)) 70 | bean.clone(el2, el1) 71 | 72 | Syn.click(el2) 73 | } 74 | 75 | , 'should work with delegated events': function (done) { 76 | var foo = this.createElement('div') 77 | , realfoo = this.byId('foo') 78 | , bang = this.byId('bang') 79 | , trigger = this.trigger() 80 | , spy1 = this.spy() 81 | , spy2 = this.spy() 82 | 83 | trigger.after(function () { 84 | assert.equals(spy1.callCount, 1, 'cloned delegated event handler') 85 | assert.same(spy1.thisValues[0], bang, 'context (this) was set to delegated element') 86 | assert(spy1.firstCall.args[0], 'got an event object argument') 87 | assert.same(spy1.firstCall.args[0].currentTarget, bang, 'degated event has currentTarget property correctly set') 88 | assert.equals(spy2.callCount, 0, 'cloned delegated event handler retains delegation selector (should not call this)') 89 | done() 90 | }) 91 | 92 | bean.setSelectorEngine(qwery) 93 | 94 | bean.add(foo, '.bang', 'click', trigger.wrap(spy1), qwery) 95 | bean.add(foo, '.baz' , 'click', trigger.wrap(spy2), qwery) 96 | 97 | bean.clone(realfoo, foo) 98 | 99 | Syn.click(bang) 100 | } 101 | }) -------------------------------------------------------------------------------- /integration/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ender Integration Test 6 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /tests/benchmark/nwevents-pubsub.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2005-2012 Diego Perini 3 | * All rights reserved. 4 | * 5 | * PubSub extension for NWEvents 6 | * 7 | */ 8 | 9 | (function(global) { 10 | 11 | var doc = global.document, 12 | root = doc.documentElement, 13 | 14 | Event = typeof exports == 'object' ? exports : ( 15 | (global.NW || (global.NW = { })) && 16 | (global.NW.Event || (global.NW.Event = { }))), 17 | 18 | // event subscriptions register 19 | Subscriptions = { }, 20 | 21 | /* =========================== TRIGGER HANDLERS =========================== */ 22 | 23 | TRIGGER_EVENT = 'onhelp', 24 | 25 | triggerArguments = null, 26 | triggerCallback = null, 27 | triggerEnabled = false, 28 | 29 | triggerTarget = doc.createDocumentFragment && 30 | doc.createDocumentFragment(). 31 | appendChild(doc.createElement('div')), 32 | 33 | triggerEvent = Event.W3C_MODEL ? 34 | (function() { 35 | var event = doc.createEvent('Event'); 36 | event.initEvent(TRIGGER_EVENT, true, true); 37 | return event; 38 | })() : Event.MSIE_MODEL ? 39 | (function() { 40 | var event = doc.createEventObject(); 41 | event.type = 'onhelp'; 42 | event.bubbles = true; 43 | event.cancelable = true; 44 | return event; 45 | })() : 46 | null, 47 | 48 | triggerEnable = Event.W3C_MODEL ? 49 | function(enable) { 50 | if ((triggerEnabled = !!enable)) { 51 | triggerTarget.addEventListener(TRIGGER_EVENT, triggerExec, false); 52 | } else { 53 | triggerTarget.removeEventListener(TRIGGER_EVENT, triggerExec, false); 54 | } 55 | } : Event.MSIE_MODEL ? 56 | function(enable) { 57 | if ((triggerEnabled = !!enable)) { 58 | triggerTarget.attachEvent(TRIGGER_EVENT, triggerExec); 59 | } else { 60 | triggerTarget.detachEvent(TRIGGER_EVENT, triggerExec); 61 | } 62 | } : 63 | function(enable) { 64 | triggerEnabled = !!enable; 65 | }, 66 | 67 | triggerExec = 68 | function() { 69 | if (typeof triggerCallback == 'function') { 70 | triggerCallback.call(triggerArguments[0], triggerArguments[1]); 71 | } 72 | }, 73 | 74 | trigger = Event.W3C_MODEL ? 75 | function(callback, args) { 76 | triggerArguments = args; 77 | triggerCallback = callback; 78 | triggerEvent.initEvent(TRIGGER_EVENT, true, true); 79 | triggerTarget.dispatchEvent(triggerEvent); 80 | } : Event.MSIE_MODEL ? 81 | function(callback, args) { 82 | triggerArguments = args; 83 | triggerCallback = callback; 84 | triggerEvent.bubbles = true; 85 | triggerEvent.cancelable = true; 86 | triggerTarget.fireEvent(TRIGGER_EVENT, triggerEvent); 87 | } : 88 | function(callback, args) { 89 | callback.call(args[0], args[1]); 90 | }, 91 | 92 | // register a subscriber for event publication 93 | subscribe = 94 | function(object, type, callback, capture, options) { 95 | var k = Event.isRegistered(Subscriptions, object, type, callback, capture); 96 | if (k === false) Event.register(Subscriptions, object, type, callback, capture); 97 | }, 98 | 99 | // unregister a subscriber from event publication 100 | unsubscribe = 101 | function(object, type, callback, capture, options) { 102 | var k = Event.isRegistered(Subscriptions, object, type, callback, capture); 103 | if (k !== false) Event.unregister(Subscriptions, object, type, callback, capture, k); 104 | }, 105 | 106 | // publish an event to registered subscribers 107 | publish = 108 | function(object, type, data, capture, options) { 109 | var i, l, event, list = Subscriptions[type]; 110 | if (list) { 111 | for (i = 0, l = list.calls.length; l > i; i++) { 112 | event = Event.synthesize(object, type, list.parms[i], options); 113 | if (data) event.data = data; 114 | event.currentTarget = list.items[i]; 115 | if (triggerEnabled) { 116 | trigger(list.calls[i], [object, event]); 117 | } else { 118 | list.calls[i].call(object, event); 119 | } 120 | } 121 | } 122 | }; 123 | 124 | // exposed methods 125 | Event.publish = publish; 126 | Event.subscribe = subscribe; 127 | Event.unsubscribe = unsubscribe; 128 | 129 | // control DOM events triggering 130 | Event.triggerEnable = triggerEnable; 131 | 132 | // event subscriptions collection 133 | Event.Subscriptions = Subscriptions; 134 | 135 | // enable DOM events triggering 136 | triggerEnable(true); 137 | 138 | })(this); 139 | -------------------------------------------------------------------------------- /tests/custom-types-test.js: -------------------------------------------------------------------------------- 1 | /*global bean:true, qwery:true, buster:true, Syn:true, assert:true, defer:true, features:true, globalSetUp:true, globalTearDown:true*/ 2 | 3 | buster.testCase('custom types', { 4 | 'setUp': function () { 5 | var self = this 6 | 7 | globalSetUp.call(this) 8 | 9 | this.testRemove = function (done, removeFn) { 10 | var html = document.documentElement 11 | , foo = this.byId('foo') 12 | , trigger = self.trigger() 13 | , meSpy = self.spy() 14 | , mlSpy = self.spy() 15 | 16 | trigger.after(function () { 17 | assert.equals(meSpy.callCount, 1, 'removes mouseenter event') 18 | assert.equals(mlSpy.callCount, 1, 'removes mouseleave event') 19 | done() 20 | }) 21 | 22 | bean.add(foo, 'mouseenter', trigger.wrap(meSpy)) 23 | bean.add(foo, 'mouseleave', trigger.wrap(mlSpy)) 24 | 25 | Syn.trigger('mouseover', { relatedTarget: html }, foo) 26 | Syn.trigger('mouseout', { relatedTarget: html }, foo) 27 | 28 | removeFn(foo, trigger.wrapped(meSpy), trigger.wrapped(mlSpy)) 29 | 30 | Syn.trigger('mouseover', { relatedTarget: html }, foo) 31 | Syn.trigger('mouseout', { relatedTarget: html }, foo) 32 | } 33 | } 34 | 35 | , 'tearDown': globalTearDown 36 | 37 | , 'mouseenter/mouseleave should wrap simple mouseover/mouseout': function (done) { 38 | var html = document.documentElement 39 | , foo = this.byId('foo') 40 | , bar = this.byId('bar') 41 | , bang = this.byId('bang') 42 | , trigger = this.trigger() 43 | , meSpy = this.spy() 44 | , mlSpy = this.spy() 45 | 46 | trigger.after(function () { 47 | assert.equals(meSpy.callCount, 1, 'removes mouseenter event') 48 | assert.equals(mlSpy.callCount, 1, 'removes mouseleave event') 49 | assert(meSpy.firstCall.args[0], 'has event object argument') 50 | assert(mlSpy.firstCall.args[0], 'has event object argument') 51 | assert.same(meSpy.firstCall.args[0].currentTarget, foo, 'currentTarget property of event set correctly') 52 | assert.same(mlSpy.firstCall.args[0].currentTarget, foo, 'currentTarget property of event set correctly') 53 | done() 54 | }, 50) 55 | 56 | bean.add(foo, 'mouseenter', trigger.wrap(meSpy)) 57 | bean.add(foo, 'mouseleave', trigger.wrap(mlSpy)) 58 | 59 | // relatedTarget is where the mouse came from for mouseover and where it's going to in mouseout 60 | Syn.trigger('mouseover', { relatedTarget: html }, foo) 61 | Syn.trigger('mouseover', { relatedTarget: foo } , bar) 62 | Syn.trigger('mouseover', { relatedTarget: bar } , bang) 63 | Syn.trigger('mouseout' , { relatedTarget: bar } , bang) 64 | Syn.trigger('mouseout' , { relatedTarget: foo } , bar) 65 | Syn.trigger('mouseout' , { relatedTarget: html }, foo) 66 | } 67 | 68 | , 'custom events should be removable': function (done) { 69 | this.testRemove(done, function (foo, me, ml) { 70 | bean.remove(foo) 71 | }) 72 | } 73 | 74 | , 'custom events should be removable by type': function (done) { 75 | this.testRemove(done, function (foo, me, ml) { 76 | bean.remove(foo, 'mouseenter') 77 | bean.remove(foo, 'mouseleave') 78 | }) 79 | } 80 | 81 | , 'custom events should be removable by type+handler': function (done) { 82 | this.testRemove(done, function (foo, me, ml) { 83 | bean.remove(foo, 'mouseenter', me) 84 | bean.remove(foo, 'mouseleave', ml) 85 | }) 86 | } 87 | 88 | , 'custom events should work with delegate': function (done) { 89 | var html = document.documentElement 90 | , foo = this.byId('foo') 91 | , bar = this.byId('bar') 92 | , bang = this.byId('bang') 93 | , trigger = this.trigger() 94 | , meSpy = this.spy() 95 | , mlSpy = this.spy() 96 | 97 | trigger.after(function () { 98 | assert.equals(meSpy.callCount, 1, 'removes mouseenter event') 99 | assert.equals(mlSpy.callCount, 1, 'removes mouseleave event') 100 | assert(meSpy.firstCall.args[0], 'has event object argument') 101 | assert(mlSpy.firstCall.args[0], 'has event object argument') 102 | assert.same(meSpy.firstCall.args[0].currentTarget, bang, 'currentTarget property of event set correctly') 103 | assert.same(mlSpy.firstCall.args[0].currentTarget, bang, 'currentTarget property of event set correctly') 104 | done() 105 | }, 50) 106 | 107 | bean.setSelectorEngine(qwery) 108 | 109 | bean.add(foo, '.bang', 'mouseenter', trigger.wrap(meSpy), qwery) 110 | bean.add(foo, '.bang', 'mouseleave', trigger.wrap(mlSpy), qwery) 111 | 112 | Syn.trigger('mouseover', { relatedTarget: html }, foo) 113 | Syn.trigger('mouseover', { relatedTarget: foo } , bar) 114 | Syn.trigger('mouseover', { relatedTarget: bar } , bang) 115 | Syn.trigger('mouseout' , { relatedTarget: bar } , bang) 116 | Syn.trigger('mouseout' , { relatedTarget: foo } , bar) 117 | Syn.trigger('mouseout' , { relatedTarget: html }, foo) 118 | } 119 | }) -------------------------------------------------------------------------------- /tests/support/syn/browsers.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | Syn.key.browsers = { 3 | webkit : { 4 | 'prevent': 5 | {"keyup":[],"keydown":["char","keypress"],"keypress":["char"]}, 6 | 'character': 7 | {"keydown":[0,"key"],"keypress":["char","char"],"keyup":[0,"key"]}, 8 | 'specialChars': 9 | {"keydown":[0,"char"],"keyup":[0,"char"]}, 10 | 'navigation': 11 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 12 | 'special': 13 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 14 | 'tab': 15 | {"keydown":[0,"char"],"keyup":[0,"char"]}, 16 | 'pause-break': 17 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 18 | 'caps': 19 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 20 | 'escape': 21 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 22 | 'num-lock': 23 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 24 | 'scroll-lock': 25 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 26 | 'print': 27 | {"keyup":[0,"key"]}, 28 | 'function': 29 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 30 | '\r': 31 | {"keydown":[0,"key"],"keypress":["char","key"],"keyup":[0,"key"]} 32 | }, 33 | gecko : { 34 | 'prevent': 35 | {"keyup":[],"keydown":["char"],"keypress":["char"]}, 36 | 'character': 37 | {"keydown":[0,"key"],"keypress":["char",0],"keyup":[0,"key"]}, 38 | 'specialChars': 39 | {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, 40 | 'navigation': 41 | {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, 42 | 'special': 43 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 44 | '\t': 45 | {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, 46 | 'pause-break': 47 | {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, 48 | 'caps': 49 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 50 | 'escape': 51 | {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, 52 | 'num-lock': 53 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 54 | 'scroll-lock': 55 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 56 | 'print': 57 | {"keyup":[0,"key"]}, 58 | 'function': 59 | {"keydown":[0,"key"],"keyup":[0,"key"]}, 60 | '\r': 61 | {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]} 62 | }, 63 | msie : { 64 | 'prevent':{"keyup":[],"keydown":["char","keypress"],"keypress":["char"]}, 65 | 'character':{"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]}, 66 | 'specialChars':{"keydown":[null,"char"],"keyup":[null,"char"]}, 67 | 'navigation':{"keydown":[null,"key"],"keyup":[null,"key"]}, 68 | 'special':{"keydown":[null,"key"],"keyup":[null,"key"]}, 69 | 'tab':{"keydown":[null,"char"],"keyup":[null,"char"]}, 70 | 'pause-break':{"keydown":[null,"key"],"keyup":[null,"key"]}, 71 | 'caps':{"keydown":[null,"key"],"keyup":[null,"key"]}, 72 | 'escape':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, 73 | 'num-lock':{"keydown":[null,"key"],"keyup":[null,"key"]}, 74 | 'scroll-lock':{"keydown":[null,"key"],"keyup":[null,"key"]}, 75 | 'print':{"keyup":[null,"key"]}, 76 | 'function':{"keydown":[null,"key"],"keyup":[null,"key"]}, 77 | '\r':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]} 78 | }, 79 | opera : { 80 | 'prevent': 81 | {"keyup":[],"keydown":[],"keypress":["char"]}, 82 | 'character': 83 | {"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]}, 84 | 'specialChars': 85 | {"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]}, 86 | 'navigation': 87 | {"keydown":[null,"key"],"keypress":[null,"key"]}, 88 | 'special': 89 | {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, 90 | 'tab': 91 | {"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]}, 92 | 'pause-break': 93 | {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, 94 | 'caps': 95 | {"keydown":[null,"key"],"keyup":[null,"key"]}, 96 | 'escape': 97 | {"keydown":[null,"key"],"keypress":[null,"key"]}, 98 | 'num-lock': 99 | {"keyup":[null,"key"],"keydown":[null,"key"],"keypress":[null,"key"]}, 100 | 'scroll-lock': 101 | {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, 102 | 'print': 103 | {}, 104 | 'function': 105 | {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, 106 | '\r': 107 | {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]} 108 | } 109 | }; 110 | 111 | Syn.mouse.browsers = { 112 | webkit : {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}, 113 | "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}}, 114 | opera: {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3}}, 115 | "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}}, 116 | msie: { "right":{"mousedown":{"button":2},"mouseup":{"button":2},"contextmenu":{"button":0}}, 117 | "left":{"mousedown":{"button":1},"mouseup":{"button":1},"click":{"button":0}}}, 118 | chrome : {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}, 119 | "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}}, 120 | gecko: {"left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}, 121 | "right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}} 122 | } 123 | 124 | //set browser 125 | Syn.key.browser = 126 | (function(){ 127 | if(Syn.key.browsers[window.navigator.userAgent]){ 128 | return Syn.key.browsers[window.navigator.userAgent]; 129 | } 130 | for(var browser in Syn.browser){ 131 | if(Syn.browser[browser] && Syn.key.browsers[browser]){ 132 | return Syn.key.browsers[browser] 133 | } 134 | } 135 | return Syn.key.browsers.gecko; 136 | })(); 137 | 138 | Syn.mouse.browser = 139 | (function(){ 140 | if(Syn.mouse.browsers[window.navigator.userAgent]){ 141 | return Syn.mouse.browsers[window.navigator.userAgent]; 142 | } 143 | for(var browser in Syn.browser){ 144 | if(Syn.browser[browser] && Syn.mouse.browsers[browser]){ 145 | return Syn.mouse.browsers[browser] 146 | } 147 | } 148 | return Syn.mouse.browsers.gecko; 149 | })(); 150 | })() 151 | -------------------------------------------------------------------------------- /tests/remove-test.js: -------------------------------------------------------------------------------- 1 | /*global bean:true, buster:true, Syn:true, assert:true, defer:true, features:true, globalSetUp:true, globalTearDown:true*/ 2 | 3 | buster.testCase('remove', { 4 | 'setUp': globalSetUp 5 | , 'tearDown': globalTearDown 6 | 7 | , 'should return the element passed in': function () { 8 | var el = this.byId('foo') 9 | , handler = function () {} 10 | , returned 11 | 12 | bean.add(el, 'click', handler) 13 | returned = bean.remove(el, 'click', handler) 14 | 15 | assert.same(el, returned, 'returns the element passed in') 16 | } 17 | 18 | , 'should be able to remove a single event': function (done) { 19 | var el = this.byId('foo') 20 | , calls = 0 21 | , trigger = this.trigger() 22 | , handler = trigger.wrap(function () { 23 | calls++ 24 | bean.remove(el, 'click', handler) 25 | Syn.click(el) 26 | }) 27 | 28 | trigger.after(function () { 29 | assert.equals(calls, 1, 'remove a single event') 30 | done() 31 | }, 50) 32 | 33 | bean.add(el, 'click', handler) 34 | 35 | Syn.click(el) 36 | } 37 | 38 | , 'should be able to remove mulitple events with an object literal': function (done) { 39 | var el = this.byId('input') 40 | , calls = 0 41 | , trigger = this.trigger() 42 | , handler1 = function () { 43 | calls++ 44 | bean.remove(el, { 45 | click : trigger.wrapped(handler1) 46 | , keydown : trigger.wrapped(handler2) 47 | }) 48 | Syn.click(el) 49 | Syn.key('j', el) 50 | } 51 | , handler2 = this.spy() 52 | , handler3 = this.spy() 53 | 54 | trigger.after(function () { 55 | assert.equals(calls, 1, 'remove a events with object literal') 56 | refute(handler2.called, 'correct handler properly removed') 57 | assert.equals(handler3.callCount, 1, 'non-matching handler should not be removed') 58 | done() 59 | }, 50) 60 | 61 | bean.add(el, 'click' , trigger.wrap(handler1)) 62 | bean.add(el, 'keydown', trigger.wrap(handler2)) 63 | bean.add(el, 'keydown', trigger.wrap(handler3)) 64 | 65 | Syn.click(el) 66 | } 67 | 68 | , 'should be able to remove all events of a specific type': function (done) { 69 | var el = this.byId('input') 70 | , calls = 0 71 | , trigger = this.trigger() 72 | , handler1 = this.spy() 73 | , handler2 = function () { 74 | calls++ 75 | bean.remove(el, 'click') 76 | Syn.click(el) 77 | } 78 | 79 | trigger.after(function () { 80 | assert.equals(calls, 1, 'removes all events of a specific type') 81 | assert.equals(handler1.callCount, 1, 'removes all events of a specific type') 82 | done() 83 | }, 50) 84 | 85 | bean.add(el, 'click', trigger.wrap(handler1)) 86 | bean.add(el, 'click', trigger.wrap(handler2)) 87 | 88 | Syn.click(el) 89 | } 90 | 91 | , 'should be able to remove all events of a specific type (multiple)': function (done) { 92 | var el = this.byId('input') 93 | , calls = 0 94 | , trigger = this.trigger() 95 | , handler1 = this.spy() 96 | , handler2 = function () { 97 | calls++ 98 | bean.remove(el, 'mousedown mouseup') 99 | Syn.click(el) 100 | } 101 | 102 | trigger.after(function () { 103 | assert.equals(calls, 1, 'removes all events of a specific type') 104 | assert.equals(handler1.callCount, 1, 'removes all events of a specific type') 105 | done() 106 | }, 50) 107 | 108 | bean.add(el, 'mousedown', trigger.wrap(handler1)) 109 | bean.add(el, 'mouseup', trigger.wrap(handler2)) 110 | 111 | Syn.click(el) 112 | } 113 | 114 | , 'should be able to remove all events': function (done) { 115 | var el = this.byId('input') 116 | , calls = 0 117 | , trigger = this.trigger() 118 | , handler1 = function () { 119 | calls++ 120 | bean.remove(el) 121 | Syn.click(el) 122 | Syn.key('j', el) 123 | } 124 | , handler2 = this.spy() 125 | 126 | trigger.after(function () { 127 | assert.equals(calls, 1, 'removes all events') 128 | assert.equals(handler2.callCount, 0, 'removes all events') 129 | done() 130 | }, 50) 131 | 132 | bean.add(el, 'click', trigger.wrap(handler1)) 133 | bean.add(el, 'keydown', trigger.wrap(handler2)) 134 | 135 | Syn.click(el) 136 | } 137 | 138 | , 'should only remove events of specified type': function (done) { 139 | // testing that bean.remove(el, type) removes *only* of that type and no others 140 | var el = this.byId('input') 141 | , calls = 0 142 | , trigger = this.trigger() 143 | , handler1 = this.spy() 144 | , handler2 = function (e) { 145 | calls++ 146 | bean.remove(el, e.type) 147 | } 148 | 149 | trigger.after(function () { 150 | assert.equals(calls, 2, 'removes all events of a specific type') 151 | assert.equals(handler1.callCount, 2, 'removes all events of a specific type') 152 | done() 153 | }, 50) 154 | 155 | bean.add(el, 'click', trigger.wrap(handler1)) 156 | bean.add(el, 'keyup', trigger.wrap(handler1)) 157 | bean.add(el, 'click', trigger.wrap(handler2)) 158 | bean.add(el, 'keyup', trigger.wrap(handler2)) 159 | 160 | Syn.click(el) 161 | Syn.key(el, 'f') 162 | Syn.click(el) 163 | Syn.key(el, 'f') 164 | } 165 | 166 | , 'should only remove events for specified handler': function (done) { 167 | // testing that bean.remove(el, fn) removes *only* that handler and no others 168 | var el = this.byId('input') 169 | , trigger = this.trigger() 170 | , handler1 = this.spy() 171 | , handler2 = this.spy() 172 | 173 | trigger.after(function () { 174 | assert.equals(handler1.callCount, 0, 'removes all events of a specific handler') 175 | assert.equals(handler2.callCount, 2, 'removes all events of a specific handler') 176 | done() 177 | }, 50) 178 | 179 | bean.add(el, 'click', trigger.wrap(handler1)) 180 | bean.add(el, 'keyup', trigger.wrap(handler1)) 181 | bean.add(el, 'click', trigger.wrap(handler2)) 182 | bean.add(el, 'keyup', trigger.wrap(handler2)) 183 | bean.remove(el, trigger.wrapped(handler1)) 184 | 185 | Syn.click(el) 186 | Syn.key(el, 'f') 187 | } 188 | 189 | , 'should remove all events, including namespaced': function (done) { 190 | // testing that bean.remove(el, fn) removes *only* that handler and no others 191 | var el = this.byId('input') 192 | , handler1 = this.spy() 193 | , handler2 = this.spy() 194 | 195 | bean.add(el, 'click.foo', handler1) 196 | bean.add(el, 'click', handler1) 197 | bean.add(el, 'keyup.bar', handler2) 198 | bean.add(el, 'keyup', handler2) 199 | bean.remove(el) 200 | 201 | Syn.click(el) 202 | Syn.key(el, 'f') 203 | 204 | defer(function () { 205 | assert.equals(handler1.callCount, 0, 'removes all events') 206 | assert.equals(handler2.callCount, 0, 'removes all events') 207 | done() 208 | }, 100) 209 | } 210 | 211 | , 'should be able to remove all events of a certain namespace': function (done) { 212 | var el = this.byId('input') 213 | , calls = 0 214 | , trigger = this.trigger() 215 | , handler1 = function () { 216 | calls++ 217 | bean.remove(el, '.foo') 218 | Syn.click(el) 219 | Syn.key('j', el) 220 | } 221 | , handler2 = this.spy() 222 | , handler3 = this.spy() 223 | 224 | trigger.after(function () { 225 | assert.equals(calls, 1, 'removes all events of a certain namespace') 226 | assert.equals(handler2.callCount, 0, 'removes all events of a certain namespace') 227 | assert.equals(handler3.callCount, 2, 'removes all events of a certain namespace') 228 | done() 229 | }, 50) 230 | 231 | bean.add(el, 'click.foo', trigger.wrap(handler1)) 232 | bean.add(el, 'keydown.foo', trigger.wrap(handler2)) 233 | bean.add(el, 'click.bar', trigger.wrap(handler3)) 234 | 235 | Syn.click(el) 236 | } 237 | 238 | , 'should only remove event if the remove namespaces is within the event namespace or if the event namespace is within the remove namespace': function (done) { 239 | var el = this.byId('foo') 240 | , trigger = this.trigger() 241 | , spy = this.spy() 242 | 243 | trigger.after(function () { 244 | assert.equals(spy.callCount, 4, 'calls on appropriate namespaces') 245 | done() 246 | }) 247 | 248 | bean.remove(el) 249 | bean.add(el, 'fat.test1.foo.ded fat.test2.foo fat.test1.foo', trigger.wrap(spy)) 250 | bean.fire(el, 'fat.test1.ded', ['1']) 251 | bean.fire(el, 'fat.test2', ['2']) 252 | bean.remove(el, '.foo.ded') 253 | bean.fire(el, 'fat.foo', ['3']) 254 | } 255 | }) -------------------------------------------------------------------------------- /bean.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bean - copyright (c) Jacob Thornton 2011-2012 3 | * https://github.com/fat/bean 4 | * MIT license 5 | */ 6 | (function(e,t,n){typeof module!="undefined"&&module.exports?module.exports=n():typeof define=="function"&&define.amd?define(n):t[e]=n()})("bean",this,function(e,t){e=e||"bean",t=t||this;var n=window,r=t[e],i=/[^\.]*(?=\..*)\.|.*/,s=/\..*/,o="addEventListener",u="removeEventListener",a=document||{},f=a.documentElement||{},l=f[o],c=l?o:"attachEvent",h={},p=Array.prototype.slice,d=function(e,t){return e.split(t||" ")},v=function(e){return typeof e=="string"},m=function(e){return typeof e=="function"},g="click dblclick mouseup mousedown contextmenu mousewheel mousemultiwheel DOMMouseScroll mouseover mouseout mousemove selectstart selectend keydown keypress keyup orientationchange focus blur change reset select submit load unload beforeunload resize move DOMContentLoaded readystatechange message error abort scroll ",y="show input invalid touchstart touchmove touchend touchcancel gesturestart gesturechange gestureend textinput readystatechange pageshow pagehide popstate hashchange offline online afterprint beforeprint dragstart dragenter dragover dragleave drag drop dragend loadstart progress suspend emptied stalled loadmetadata loadeddata canplay canplaythrough playing waiting seeking seeked ended durationchange timeupdate play pause ratechange volumechange cuechange checking noupdate downloading cached updateready obsolete ",b=function(e,t,n){for(n=0;n0){t=d(t);for(f=t.length;f--;)D(e,t[f],n);return e}u=r&&t.replace(s,""),u&&w[u]&&(u=w[u].base);if(!t||r){if(a=r&&t.replace(i,""))a=d(a,".");O(e,u,n,a)}else if(m(t))O(e,null,t);else for(o in t)t.hasOwnProperty(o)&&D(e,o,t[o]);return e},P=function(e,t,n,r){var o,u,a,f,l,v,g;if(n===undefined&&typeof t=="object"){for(u in t)t.hasOwnProperty(u)&&P.call(this,e,u,t[u]);return}m(n)?(l=p.call(arguments,3),r=o=n):(o=r,l=p.call(arguments,4),r=M(n,o,N)),a=d(t),this===h&&(r=A(D,e,t,r,o));for(f=a.length;f--;)g=T.put(v=new x(e,a[f].replace(s,""),r,o,d(a[f].replace(i,""),"."),l,!1)),v[c]&&g&&L(e,v.eventType,!0,v.customType);return e},H=function(e,t,n,r){return P.apply(null,v(n)?[e,n,t,r].concat(arguments.length>3?p.call(arguments,5):[]):p.call(arguments))},B=function(){return P.apply(h,arguments)},j=function(e,t,n){var r=d(t),o,u,a,f,l;for(o=r.length;o--;){t=r[o].replace(s,"");if(f=r[o].replace(i,""))f=d(f,".");if(!f&&!n&&e[c])_(b[t],t,e);else{l=T.get(e,t,null,!1),n=[!1].concat(n);for(u=0,a=l.length;u" + "" + "" + "" + "" + "" + "" + "" + ""; 218 | document.documentElement.appendChild(div); 219 | form = div.firstChild 220 | checkbox = form.childNodes[0]; 221 | submit = form.childNodes[2]; 222 | select = form.getElementsByTagName('select')[0] 223 | 224 | checkbox.checked = false; 225 | checkbox.onchange = function() { 226 | Syn.support.clickChanges = true; 227 | } 228 | 229 | Syn.trigger("click", {}, checkbox) 230 | Syn.support.clickChecks = checkbox.checked; 231 | 232 | checkbox.checked = false; 233 | 234 | Syn.trigger("change", {}, checkbox); 235 | 236 | Syn.support.changeChecks = checkbox.checked; 237 | 238 | form.onsubmit = function( ev ) { 239 | if ( ev.preventDefault ) ev.preventDefault(); 240 | Syn.support.clickSubmits = true; 241 | return false; 242 | } 243 | Syn.trigger("click", {}, submit) 244 | 245 | 246 | 247 | form.childNodes[1].onchange = function() { 248 | Syn.support.radioClickChanges = true; 249 | } 250 | Syn.trigger("click", {}, form.childNodes[1]) 251 | 252 | 253 | Syn.bind(div, 'click', function() { 254 | Syn.support.optionClickBubbles = true; 255 | Syn.unbind(div, 'click', arguments.callee) 256 | }) 257 | Syn.trigger("click", {}, select.firstChild) 258 | 259 | 260 | Syn.support.changeBubbles = Syn.eventSupported('change'); 261 | 262 | //test if mousedown followed by mouseup causes click (opera), make sure there are no clicks after this 263 | var clicksCount = 0 264 | div.onclick = function() { 265 | Syn.support.mouseDownUpClicks = true; 266 | //we should use this to check for opera potentially, but would 267 | //be difficult to remove element correctly 268 | //Syn.support.mouseDownUpRepeatClicks = (2 == (++clicksCount)) 269 | } 270 | Syn.trigger("mousedown", {}, div) 271 | Syn.trigger("mouseup", {}, div) 272 | 273 | //setTimeout(function(){ 274 | // Syn.trigger("mousedown",{},div) 275 | // Syn.trigger("mouseup",{},div) 276 | //},1) 277 | 278 | document.documentElement.removeChild(div); 279 | 280 | //check stuff 281 | window.__synthTest = oldSynth; 282 | Syn.support.ready++; 283 | })(); 284 | })() 285 | -------------------------------------------------------------------------------- /tests/delegate-test.js: -------------------------------------------------------------------------------- 1 | /*global bean:true, qwery:true, buster:true, Syn:true, assert:true, defer:true, features:true, globalSetUp:true, globalTearDown:true*/ 2 | 3 | buster.testCase('delegate', { 4 | 'setUp': function () { 5 | globalSetUp.call(this) 6 | 7 | bean.setSelectorEngine(qwery) 8 | 9 | this.verifySimpleDelegateSpy = function (spy, target) { 10 | assert.equals(spy.callCount, 2, 'delegated on selector') 11 | assert.same(spy.thisValues[0], target, 'context (this) was set to delegated element') 12 | assert.same(spy.thisValues[1], target, 'context (this) was set to delegated element') 13 | assert(spy.firstCall.args[0], 'got an event object argument') 14 | assert(spy.secondCall && spy.secondCall.args[0], 'got an event object argument') 15 | assert.same(spy.firstCall.args[0].currentTarget, target, 'degated event has currentTarget property correctly set') 16 | assert.same(spy.secondCall && spy.secondCall.args[0].currentTarget, target, 'degated event has currentTarget property correctly set') 17 | } 18 | } 19 | 20 | , 'tearDown': function () { 21 | globalTearDown.call(this) 22 | bean.setSelectorEngine() // reset to default 23 | } 24 | 25 | , 'should be able to delegate on selectors': { 26 | 'setUp': function () { 27 | var self = this 28 | this.runTest = function (done, regFn) { 29 | var el1 = self.byId('foo') 30 | , el2 = self.byId('bar') 31 | , el3 = self.byId('baz') 32 | , el4 = self.byId('bang') 33 | , trigger = self.trigger() 34 | , spy = self.spy() 35 | 36 | trigger.after(function () { 37 | self.verifySimpleDelegateSpy(spy, el2) 38 | done() 39 | }) 40 | 41 | regFn(el1, trigger.wrap(spy)) 42 | 43 | Syn.click(el2) 44 | Syn.click(el3) 45 | Syn.click(el4) 46 | } 47 | } 48 | , 'on()': function (done) { 49 | this.runTest(done, function (el1, wrappedSpy) { 50 | bean.on(el1, 'click', '.bar', wrappedSpy) 51 | }) 52 | } 53 | , 'add()': function (done) { 54 | this.runTest(done, function (el1, wrappedSpy) { 55 | bean.add(el1, '.bar', 'click', wrappedSpy) 56 | }) 57 | } 58 | } 59 | 60 | , 'should be able to delegate multiple events': { 61 | 'setUp': function () { 62 | var self = this 63 | this.runTest = function (done, regFn) { 64 | var el1 = self.byId('foo') 65 | , el2 = self.byId('bar') 66 | , el3 = self.byId('baz') 67 | , trigger = self.trigger() 68 | , spy = self.spy() 69 | 70 | trigger.after(function () { 71 | self.verifySimpleDelegateSpy(spy, el2) 72 | done() 73 | }, 50) 74 | 75 | regFn(el1, trigger.wrap(spy)) 76 | 77 | Syn.click(el2) 78 | Syn.click(el3) 79 | } 80 | } 81 | , 'on()': function (done) { 82 | this.runTest(done, function (el1, wrappedSpy) { 83 | bean.on(el1, 'mouseup mousedown', '.bar', wrappedSpy) 84 | }) 85 | } 86 | , 'add()': function (done) { 87 | this.runTest(done, function (el1, wrappedSpy) { 88 | bean.add(el1, '.bar', 'mouseup mousedown', wrappedSpy) 89 | }) 90 | } 91 | } 92 | 93 | , 'should be able to delegate on array': { 94 | 'setUp': function () { 95 | var self = this 96 | this.runTest = function (done, regFn) { 97 | var el1 = self.byId('foo') 98 | , el2 = self.byId('bar') 99 | , el3 = self.byId('baz') 100 | , el4 = self.byId('bang') 101 | , trigger = self.trigger() 102 | , spy = self.spy() 103 | 104 | trigger.after(function () { 105 | self.verifySimpleDelegateSpy(spy, el2) 106 | done() 107 | }) 108 | 109 | regFn(el1, el2, trigger.wrap(spy)) 110 | 111 | Syn.click(el2) 112 | Syn.click(el3) 113 | Syn.click(el4) 114 | } 115 | } 116 | , 'on()': function (done) { 117 | this.runTest(done, function (el1, el2, wrappedSpy) { 118 | bean.on(el1, 'click', [el2], wrappedSpy) 119 | }) 120 | } 121 | , 'add()': function (done) { 122 | this.runTest(done, function (el1, el2, wrappedSpy) { 123 | bean.add(el1, [el2], 'click', wrappedSpy) 124 | }) 125 | } 126 | } 127 | 128 | , 'should be able to remove delegated handler': { 129 | 'setUp': function () { 130 | var self = this 131 | this.runTest = function (done, regFn) { 132 | var el1 = self.byId('foo') 133 | , el2 = self.byId('bar') 134 | , calls = 0 135 | , trigger = self.trigger(50) 136 | , fn = function () { 137 | calls++ 138 | bean.remove(el1, 'click', trigger.wrapped(fn)) 139 | } 140 | 141 | trigger.after(function () { 142 | assert.equals(calls, 1, 'degegated event triggered once') 143 | done() 144 | }) 145 | 146 | regFn(el1, trigger.wrap(fn)) 147 | 148 | Syn.click(el2) 149 | Syn.click(el2) 150 | } 151 | } 152 | , 'on()': function (done) { 153 | this.runTest(done, function (el1, wrappedSpy) { 154 | bean.on(el1, 'click', '.bar', wrappedSpy) 155 | }) 156 | } 157 | , 'add()': function (done) { 158 | this.runTest(done, function (el1, wrappedSpy) { 159 | bean.add(el1, '.bar', 'click', wrappedSpy) 160 | }) 161 | } 162 | } 163 | 164 | , 'should use qSA if available': { 165 | 'setUp': function () { 166 | var self = this 167 | this.runTest = function (done, regFn) { 168 | if (!features.qSA) { 169 | assert(true, 'qSA not available in this browser, skipping test') 170 | return done() 171 | } 172 | 173 | var el1 = self.byId('foo') 174 | , el2 = self.byId('bar') 175 | , el3 = self.byId('baz') 176 | , el4 = self.byId('bang') 177 | , trigger = self.trigger() 178 | , spy = self.spy() 179 | 180 | trigger.after(function () { 181 | self.verifySimpleDelegateSpy(spy, el2) 182 | done() 183 | }) 184 | 185 | bean.setSelectorEngine() // reset to default 186 | regFn(el1, trigger.wrap(spy)) 187 | 188 | Syn.click(el2) 189 | Syn.click(el3) 190 | Syn.click(el4) 191 | } 192 | } 193 | , 'on()': function (done) { 194 | this.runTest(done, function (el1, wrappedSpy) { 195 | bean.on(el1, 'click', '.bar', wrappedSpy) 196 | }) 197 | } 198 | , 'add()': function (done) { 199 | this.runTest(done, function (el1, wrappedSpy) { 200 | bean.add(el1, '.bar', 'click', wrappedSpy) 201 | }) 202 | } 203 | } 204 | 205 | , 'should throw error when no qSA available and no selector engine set': { 206 | 'setUp': function () { 207 | var self = this 208 | this.runTest = function (done, regFn) { 209 | if (features.qSA) { 210 | assert(true, 'qSA available in this browser, skipping test') 211 | return done() 212 | } 213 | 214 | var el1 = self.byId('foo') 215 | , el2 = self.byId('bar') 216 | , spy = self.spy() 217 | 218 | bean.setSelectorEngine() // reset to default 219 | regFn(el1, spy) 220 | 221 | window.onerror = function (e) { 222 | assert(e.toString(), /Bean/, 'threw Error on delegated event trigger without selector engine or qSA') 223 | window.onerror = null 224 | } 225 | 226 | Syn.click(el2) 227 | 228 | defer(function () { 229 | assert.equals(spy.callCount, 0, 'don\'t fire delegated event without selector engine or qSA') 230 | done() 231 | }) 232 | } 233 | } 234 | , 'on()': function (done) { 235 | this.runTest(done, function (el1, wrappedSpy) { 236 | bean.on(el1, 'click', '.bar', wrappedSpy) 237 | }) 238 | } 239 | , 'add()': function (done) { 240 | this.runTest(done, function (el1, wrappedSpy) { 241 | bean.add(el1, '.bar', 'click', wrappedSpy) 242 | }) 243 | } 244 | } 245 | 246 | , 'should be able to set a default selector engine': { 247 | 'setUp': function () { 248 | var self = this 249 | this.runTest = function (done, regFn) { 250 | var el1 = self.byId('foo') 251 | , el2 = self.byId('bar') 252 | , el3 = self.byId('baz') 253 | , el4 = self.byId('bang') 254 | , selector = 'SELECTOR? WE DON\'T NEED NO STINKIN\' SELECTOR!' 255 | , trigger = self.trigger() 256 | , stub = self.stub() 257 | , spy = self.spy() 258 | 259 | trigger.after(function () { 260 | // 6, see? lots of wasteful calls 261 | assert.equals(stub.callCount, 6, 'selector engine called') 262 | assert.same(stub.firstCall.args[0], selector, 'selector engine called with selector argument') 263 | assert.same(stub.firstCall.args[1], el1, 'selector engine called with root argument') 264 | self.verifySimpleDelegateSpy(spy, el2) 265 | bean.setSelectorEngine(null) 266 | done() 267 | }) 268 | 269 | stub.returns([el2]) 270 | // TODO: findTarget() is called for setting event.currentTarget as well as checking for a match 271 | // fix this so it's only called once, otherwise it's a waste 272 | bean.setSelectorEngine(stub) 273 | 274 | regFn(el1, selector, trigger.wrap(spy)) 275 | 276 | Syn.click(el2) 277 | Syn.click(el3) 278 | Syn.click(el4) 279 | } 280 | } 281 | , 'on()': function (done) { 282 | this.runTest(done, function (el1, selector, wrappedSpy) { 283 | bean.on(el1, 'click', selector, wrappedSpy) 284 | }) 285 | } 286 | , 'add()': function (done) { 287 | this.runTest(done, function (el1, selector, wrappedSpy) { 288 | bean.add(el1, selector, 'click', wrappedSpy) 289 | }) 290 | } 291 | } 292 | }) -------------------------------------------------------------------------------- /tests/add-test.js: -------------------------------------------------------------------------------- 1 | /*global bean:true, buster:true, Syn:true, assert:true, defer:true, features:true, globalSetUp:true, globalTearDown:true*/ 2 | 3 | buster.testCase('add', { 4 | 'setUp': globalSetUp 5 | , 'tearDown': globalTearDown 6 | 7 | , 'should return the element passed in': { 8 | 'setUp': function () { 9 | var self = this 10 | this.runTest = function (method) { 11 | var el = self.byId('input') 12 | , returned = bean[method](el, 'click', function () {}) 13 | 14 | assert.same(el, returned, 'returns the element passed in') 15 | } 16 | } 17 | , 'on()': function () { this.runTest('on') } 18 | , 'add()': function () { this.runTest('add') } 19 | } 20 | 21 | , 'should be able to add single events to elements': { 22 | 'setUp': function () { 23 | var self = this 24 | this.runTest = function (done, method) { 25 | var el = self.byId('input') 26 | , trigger = self.trigger() 27 | , spy = self.spy() 28 | 29 | trigger.after(function() { 30 | assert(spy.calledOnce, 'adds single events to elements ') 31 | done() 32 | }) 33 | 34 | bean[method](el, 'click', trigger.wrap(spy)) 35 | 36 | Syn.click(el) 37 | } 38 | } 39 | , 'on()': function (done) { this.runTest(done, 'on') } 40 | , 'add()': function (done) { this.runTest(done, 'add') } 41 | } 42 | 43 | , 'should be able to add single events to objects': { 44 | 'setUp': function () { 45 | var self = this 46 | this.runTest = function (done, method) { 47 | var obj = this.newObj() 48 | , trigger = self.trigger() 49 | , spy = self.spy() 50 | 51 | trigger.after(function () { 52 | assert(spy.calledOnce, 'adds single events to objects') 53 | done() 54 | }) 55 | 56 | bean[method](obj, 'complete', trigger.wrap(spy)) 57 | bean.fire(obj, 'complete') 58 | bean.remove(obj) 59 | bean.fire(obj, 'complete') 60 | } 61 | } 62 | , 'on()': function (done) { this.runTest(done, 'on') } 63 | , 'add()': function (done) { this.runTest(done, 'add') } 64 | } 65 | 66 | , 'scope should be equal to element': { 67 | 'setUp': function () { 68 | var self = this 69 | this.runTest = function (done, method) { 70 | var el = self.byId('input') 71 | , trigger = self.trigger() 72 | , spy = self.spy() 73 | 74 | trigger.after(function () { 75 | assert.equals(spy.callCount, 1, 'single call') 76 | assert(spy.calledOn(el), 'called with element as scope (this)') 77 | done() 78 | }) 79 | 80 | bean[method](el, 'click', trigger.wrap(spy)) 81 | 82 | Syn.click(el) 83 | } 84 | } 85 | , 'on()': function (done) { this.runTest(done, 'on') } 86 | , 'add()': function (done) { this.runTest(done, 'add') } 87 | } 88 | 89 | , 'should recieve an event method': { 90 | 'setUp': function () { 91 | var self = this 92 | this.runTest = function (done, method) { 93 | var el = self.byId('input') 94 | , trigger = self.trigger() 95 | , spy = self.spy() 96 | 97 | trigger.after(function () { 98 | assert(spy.calledOnce, 'single call') 99 | assert.equals(spy.firstCall.args.length, 1, 'called with an object') 100 | assert(!!spy.firstCall.args[0].stop, 'called with an event object') 101 | done() 102 | }) 103 | 104 | bean[method](el, 'click', trigger.wrap(spy)) 105 | 106 | Syn.click(el) 107 | } 108 | } 109 | , 'on()': function (done) { this.runTest(done, 'on') } 110 | , 'add()': function (done) { this.runTest(done, 'add') } 111 | } 112 | 113 | , 'should be able to pass x amount of additional arguments': { 114 | 'setUp': function () { 115 | var self = this 116 | this.runTest = function (done, method) { 117 | var el = self.byId('input') 118 | , trigger = self.trigger() 119 | , spy = self.spy() 120 | 121 | trigger.after(function () { 122 | assert(spy.calledOnce, 'single call') 123 | assert.equals(spy.firstCall.args.length, 4, 'called with an event object and 3 additional arguments') 124 | assert.equals(spy.firstCall.args[1], 1, 'called with correct argument') 125 | assert.equals(spy.firstCall.args[2], 2, 'called with correct argument') 126 | assert.equals(spy.firstCall.args[3], 3, 'called with correct argument') 127 | done() 128 | }) 129 | 130 | bean[method](el, 'click', trigger.wrap(spy), 1, 2, 3) 131 | 132 | Syn.click(el) 133 | } 134 | } 135 | , 'on()': function (done) { this.runTest(done, 'on') } 136 | , 'add()': function (done) { this.runTest(done, 'add') } 137 | } 138 | 139 | , 'should be able to add multiple events by space seperating them': { 140 | 'setUp': function () { 141 | var self = this 142 | this.runTest = function (done, method) { 143 | var el = self.byId('input') 144 | , trigger = self.trigger() 145 | , spy = self.spy() 146 | 147 | trigger.after(function () { 148 | assert.equals(spy.callCount, 2, 'adds multiple events by space seperating them') 149 | done() 150 | }) 151 | 152 | bean[method](el, 'click keypress', trigger.wrap(spy)) 153 | 154 | Syn.click(el).key('j') 155 | } 156 | } 157 | , 'on()': function (done) { this.runTest(done, 'on') } 158 | , 'add()': function (done) { this.runTest(done, 'add') } 159 | } 160 | 161 | , 'should be able to add multiple events of the same type': { 162 | 'setUp': function () { 163 | var self = this 164 | this.runTest = function (done, method) { 165 | var el = self.byId('input') 166 | , trigger = self.trigger() 167 | , spy1 = self.spy() 168 | , spy2 = self.spy() 169 | , spy3 = self.spy() 170 | 171 | trigger.after(function () { 172 | assert(spy1.calledOnce, 'adds multiple events of the same type (1)') 173 | assert(spy2.calledOnce, 'adds multiple events of the same type (2)') 174 | assert(spy3.calledOnce, 'adds multiple events of the same type (3)') 175 | done() 176 | }) 177 | 178 | bean[method](el, 'click', trigger.wrap(spy1)) 179 | bean[method](el, 'click', trigger.wrap(spy2)) 180 | bean[method](el, 'click', trigger.wrap(spy3)) 181 | 182 | Syn.click(el) 183 | } 184 | } 185 | , 'on()': function (done) { this.runTest(done, 'on') } 186 | , 'add()': function (done) { this.runTest(done, 'add') } 187 | } 188 | 189 | , 'should be able to add multiple events simultaneously with an object literal': { 190 | 'setUp': function () { 191 | var self = this 192 | this.runTest = function (done, method) { 193 | var el = self.byId('input') 194 | , trigger = self.trigger() 195 | , clickSpy = self.spy() 196 | , keydownSpy = self.spy() 197 | 198 | trigger.after(function () { 199 | assert.equals(clickSpy.callCount, 1, 'adds multiple events simultaneously with an object literal (click)') 200 | assert.equals(keydownSpy.callCount, 1, 'adds multiple events simultaneously with an object literal (keydown)') 201 | done() 202 | }) 203 | 204 | bean[method](el, { click: trigger.wrap(clickSpy), keydown: trigger.wrap(keydownSpy) }) 205 | 206 | Syn.click(el).key('j') 207 | } 208 | } 209 | , 'on()': function (done) { this.runTest(done, 'on') } 210 | , 'add()': function (done) { this.runTest(done, 'add') } 211 | } 212 | 213 | , 'should bubble up dom': { 214 | 'setUp': function () { 215 | var self = this 216 | this.runTest = function (done, method) { 217 | var el1 = self.byId('foo') 218 | , el2 = self.byId('bar') 219 | , trigger = self.trigger() 220 | , spy = self.spy() 221 | 222 | trigger.after(function () { 223 | assert(spy.calledOnce, 'bubbles up dom') 224 | done() 225 | }) 226 | 227 | bean[method](el1, 'click', trigger.wrap(spy)) 228 | 229 | Syn.click(el2) 230 | } 231 | } 232 | , 'on()': function (done) { this.runTest(done, 'on') } 233 | , 'add()': function (done) { this.runTest(done, 'add') } 234 | } 235 | 236 | , 'shouldn\'t trigger event when adding additional custom event listeners': { 237 | 'setUp': function () { 238 | var self = this 239 | this.runTest = function (done, method) { 240 | var el = self.byId('input') 241 | , spy = self.spy() 242 | 243 | bean[method](el, 'foo', spy) 244 | bean[method](el, 'foo', spy) 245 | 246 | defer(function () { 247 | refute(spy.called, 'additional custom event listeners trigger event') 248 | done() 249 | }) 250 | } 251 | } 252 | , 'on()': function (done) { this.runTest(done, 'on') } 253 | , 'add()': function (done) { this.runTest(done, 'add') } 254 | } 255 | 256 | , 'should bind onmessage to window': { 257 | 'setUp': function () { 258 | var self = this 259 | this.runTest = function (done, method) { 260 | if (features.message) { 261 | var calls = 0 262 | , trigger = self.trigger() 263 | 264 | this.removables.push(window) 265 | 266 | trigger.after(function () { 267 | assert.equals(calls, 1, 'message event activated') 268 | }) 269 | 270 | // unfortunately we can't use a spy here because we want to inspect the original event 271 | // object which isn't available in IE8 (and previous, but there is no postMessage in IE<8) 272 | // after a setTimeout() 273 | bean[method](window, 'message', trigger.wrap(function (event) { 274 | calls++ 275 | assert(event, 'has event object argument') 276 | assert.equals(event.data, 'hello there', 'data should be copied') 277 | assert.same(event.origin, event.originalEvent.origin, 'origin should be copied') 278 | assert.same(event.source, event.originalEvent.source, 'source should be copied') 279 | done() 280 | })) 281 | 282 | window.postMessage('hello there', '*') 283 | } else { 284 | assert(true, 'message events not supported by this browser, test bypassed') 285 | done() 286 | } 287 | } 288 | } 289 | , 'on()': function (done) { this.runTest(done, 'on') } 290 | , 'add()': function (done) { this.runTest(done, 'add') } 291 | } 292 | 293 | , 'one: should only trigger handler once': { 294 | 'setUp': function () { 295 | var self = this 296 | this.runTest = function (done) { 297 | var el = self.byId('input') 298 | , trigger = self.trigger() 299 | , spy = self.spy() 300 | 301 | trigger.after(function () { 302 | assert.equals(spy.callCount, 1, 'handler called exactly one time') 303 | done() 304 | }) 305 | 306 | bean.one(el, 'click', trigger.wrap(spy)) 307 | Syn.click(el) 308 | Syn.click(el) 309 | Syn.click(el) 310 | } 311 | } 312 | , 'on()': function (done) { this.runTest(done, 'on') } 313 | , 'add()': function (done) { this.runTest(done, 'add') } 314 | } 315 | 316 | , 'one: should be removable': { 317 | 'setUp': function () { 318 | var self = this 319 | this.runTest = function (done) { 320 | var el = self.byId('input') 321 | , spy = self.spy() 322 | 323 | bean.one(el, 'click', spy) 324 | bean.remove(el, 'click', spy) 325 | Syn.click(el) 326 | Syn.click(el) 327 | 328 | defer(function () { 329 | refute(spy.called, 'handler shouldn\'t be called') 330 | done() 331 | }) 332 | } 333 | } 334 | , 'on()': function (done) { this.runTest(done, 'on') } 335 | , 'add()': function (done) { this.runTest(done, 'add') } 336 | } 337 | }) -------------------------------------------------------------------------------- /tests/namespace-test.js: -------------------------------------------------------------------------------- 1 | /*global bean:true, buster:true, Syn:true, assert:true, defer:true, features:true, globalSetUp:true, globalTearDown:true*/ 2 | 3 | buster.testCase('namespaces', { 4 | 'setUp': globalSetUp 5 | , 'tearDown': globalTearDown 6 | 7 | , 'should be able to name handlers': { 8 | 'setUp': function () { 9 | var self = this 10 | this.runTest = function (done, regFn) { 11 | var el1 = self.byId('foo') 12 | , trigger = self.trigger() 13 | , spy = self.spy() 14 | 15 | trigger.after(function () { 16 | assert.equals(spy.callCount, 1, 'triggered click event') 17 | done() 18 | }) 19 | 20 | regFn(el1, trigger.wrap(spy)) 21 | 22 | Syn.click(el1) 23 | } 24 | } 25 | , 'on()': function (done) { 26 | this.runTest(done, function (el1, wrappedSpy) { 27 | bean.on(el1, 'click.fat', wrappedSpy) 28 | }) 29 | } 30 | , 'add()': function (done) { 31 | this.runTest(done, function (el1, wrappedSpy) { 32 | bean.add(el1, 'click.fat', wrappedSpy) 33 | }) 34 | } 35 | } 36 | 37 | , 'should be able to add multiple handlers under the same namespace to the same element': { 38 | 'setUp': function () { 39 | var self = this 40 | this.runTest = function (done, regFn) { 41 | var el1 = self.byId('foo') 42 | , trigger = self.trigger() 43 | , spy1 = self.spy() 44 | , spy2 = self.spy() 45 | 46 | trigger.after(function () { 47 | assert.equals(spy1.callCount, 1, 'triggered click event') 48 | assert.equals(spy2.callCount, 1, 'triggered click event') 49 | done() 50 | }) 51 | 52 | regFn(el1, trigger.wrap(spy1), trigger.wrap(spy2)) 53 | 54 | Syn.click(el1) 55 | } 56 | } 57 | , 'on()': function (done) { 58 | this.runTest(done, function (el1, wrappedSpy1, wrappedSpy2) { 59 | bean.on(el1, 'click.fat', wrappedSpy1) 60 | bean.on(el1, 'click.fat', wrappedSpy2) 61 | }) 62 | } 63 | , 'add()': function (done) { 64 | this.runTest(done, function (el1, wrappedSpy1, wrappedSpy2) { 65 | bean.add(el1, 'click.fat', wrappedSpy1) 66 | bean.add(el1, 'click.fat', wrappedSpy2) 67 | }) 68 | } 69 | } 70 | 71 | , 'should be able to fire an event without handlers': function () { 72 | var el1 = this.byId('foo') 73 | 74 | bean.fire(el1, 'click.fat') 75 | 76 | assert(true, 'fire namespaced event with no handlers (no exception)') 77 | } 78 | 79 | , 'should be able to target namespaced event handlers with fire': { 80 | 'setUp': function () { 81 | var self = this 82 | this.runTest = function (done, regFn) { 83 | var el1 = self.byId('foo') 84 | , trigger = self.trigger() 85 | , spy1 = self.spy() 86 | , spy2 = self.spy() 87 | 88 | trigger.after(function () { 89 | assert.equals(spy1.callCount, 1, 'triggered click event (namespaced)') 90 | assert.equals(spy2.callCount, 0, 'should not trigger click event (plain)') 91 | done() 92 | }) 93 | 94 | regFn(el1, trigger.wrap(spy1), trigger.wrap(spy2)) 95 | 96 | bean.fire(el1, 'click.fat') 97 | } 98 | } 99 | , 'on()': function (done) { 100 | this.runTest(done, function (el1, wrappedSpy1, wrappedSpy2) { 101 | bean.on(el1, 'click.fat', wrappedSpy1) 102 | bean.on(el1, 'click', wrappedSpy2) 103 | }) 104 | } 105 | , 'add()': function (done) { 106 | this.runTest(done, function (el1, wrappedSpy1, wrappedSpy2) { 107 | bean.add(el1, 'click.fat', wrappedSpy1) 108 | bean.add(el1, 'click', wrappedSpy2) 109 | }) 110 | } 111 | } 112 | 113 | // changed in 0.5 so this doesn't fire, namespaces need to match 114 | , 'should not be able to target multiple namespaced event handlers with fire': { 115 | 'setUp': function () { 116 | var self = this 117 | this.runTest = function (done, regFn) { 118 | var el1 = self.byId('foo') 119 | , spy1 = self.spy() 120 | , spy2 = self.spy() 121 | , spy3 = self.spy() 122 | 123 | regFn(el1, spy1, spy2, spy3) 124 | 125 | bean.fire(el1, 'click.fat.ded') 126 | 127 | defer(function () { 128 | assert.equals(spy1.callCount, 0, 'should not trigger click event (namespaced)') 129 | assert.equals(spy2.callCount, 0, 'should not trigger click event (namespaced)') 130 | assert.equals(spy3.callCount, 0, 'should not trigger click event (plain)') 131 | done() 132 | }) 133 | } 134 | } 135 | , 'on()': function (done) { 136 | this.runTest(done, function (el1, wrappedSpy1, wrappedSpy2, wrappedSpy3) { 137 | bean.on(el1, 'click.fat', wrappedSpy1) 138 | bean.on(el1, 'click.ded', wrappedSpy2) 139 | bean.on(el1, 'click', wrappedSpy3) 140 | }) 141 | } 142 | , 'add()': function (done) { 143 | this.runTest(done, function (el1, wrappedSpy1, wrappedSpy2, wrappedSpy3) { 144 | bean.add(el1, 'click.fat', wrappedSpy1) 145 | bean.add(el1, 'click.ded', wrappedSpy2) 146 | bean.add(el1, 'click', wrappedSpy3) 147 | }) 148 | } 149 | } 150 | 151 | , 'should be able to remove handlers based on name': { 152 | 'setUp': function () { 153 | var self = this 154 | this.runTest = function (done, regFn) { 155 | var el1 = self.byId('foo') 156 | , trigger = self.trigger() 157 | , spy1 = self.spy() 158 | , spy2 = self.spy() 159 | 160 | trigger.after(function () { 161 | assert.equals(spy1.callCount, 0, 'should not trigger click event (namespaced)') 162 | assert.equals(spy2.callCount, 1, 'triggered click event (plain)') 163 | done() 164 | }) 165 | 166 | regFn(el1, trigger.wrap(spy1), trigger.wrap(spy2)) 167 | 168 | bean.remove(el1, 'click.ded') 169 | 170 | Syn.click(el1) 171 | } 172 | } 173 | , 'on()': function (done) { 174 | this.runTest(done, function (el1, wrappedSpy1, wrappedSpy2) { 175 | bean.on(el1, 'click.ded', wrappedSpy1) 176 | bean.on(el1, 'click', wrappedSpy2) 177 | }) 178 | } 179 | , 'add()': function (done) { 180 | this.runTest(done, function (el1, wrappedSpy1, wrappedSpy2) { 181 | bean.add(el1, 'click.ded', wrappedSpy1) 182 | bean.add(el1, 'click', wrappedSpy2) 183 | }) 184 | } 185 | } 186 | 187 | // changed in 0.5 so this doesn't remove, namespaces need to match 188 | , 'should not be able to remove multiple handlers based on name': { 189 | 'setUp': function () { 190 | var self = this 191 | this.runTest = function (done, regFn) { 192 | var el1 = self.byId('foo') 193 | , trigger = self.trigger() 194 | , spy1 = self.spy() 195 | , spy2 = self.spy() 196 | , spy3 = self.spy() 197 | 198 | trigger.after(function () { 199 | assert.equals(spy1.callCount, 1, 'triggered click event (namespaced)') 200 | assert.equals(spy2.callCount, 1, 'triggered click event (namespaced)') 201 | assert.equals(spy3.callCount, 1, 'triggered click event (plain)') 202 | done() 203 | }) 204 | 205 | regFn(el1, trigger.wrap(spy1), trigger.wrap(spy2), trigger.wrap(spy3)) 206 | 207 | bean.remove(el1, 'click.ded.fat') 208 | 209 | Syn.click(el1) 210 | } 211 | } 212 | , 'on()': function (done) { 213 | this.runTest(done, function (el1, wrappedSpy1, wrappedSpy2, wrappedSpy3) { 214 | bean.on(el1, 'click.fat', wrappedSpy1) 215 | bean.on(el1, 'click.ded', wrappedSpy2) 216 | bean.on(el1, 'click', wrappedSpy3) 217 | }) 218 | } 219 | , 'add()': function (done) { 220 | this.runTest(done, function (el1, wrappedSpy1, wrappedSpy2, wrappedSpy3) { 221 | bean.add(el1, 'click.fat', wrappedSpy1) 222 | bean.add(el1, 'click.ded', wrappedSpy2) 223 | bean.add(el1, 'click', wrappedSpy3) 224 | }) 225 | } 226 | } 227 | 228 | , 'should be able to add multiple custom events to a single handler and call them individually': { 229 | 'setUp': function () { 230 | var self = this 231 | this.runTest = function (done, regFn) { 232 | var el1 = self.byId('foo') 233 | , trigger = self.trigger() 234 | , spy = self.spy() 235 | 236 | trigger.after(function () { 237 | assert.equals(spy.callCount, 2, 'triggered custom event') 238 | assert.equals(spy.firstCall.args[0], '1', 'expected array argument') 239 | assert.equals(spy.secondCall.args[0], '2', 'expected array argument') 240 | done() 241 | }) 242 | 243 | regFn(el1, trigger.wrap(spy)) 244 | 245 | bean.fire(el1, 'fat.test1', ['1']) 246 | bean.fire(el1, 'fat.test2', ['2']) 247 | } 248 | } 249 | , 'on()': function (done) { 250 | this.runTest(done, function (el1, wrappedSpy) { 251 | bean.on(el1, 'fat.test1 fat.test2', wrappedSpy) 252 | }) 253 | } 254 | , 'add()': function (done) { 255 | this.runTest(done, function (el1, wrappedSpy) { 256 | bean.add(el1, 'fat.test1 fat.test2', wrappedSpy) 257 | }) 258 | } 259 | } 260 | 261 | , 'should be able to fire an event if the fired namespace is within the event namespace range': { 262 | 'setUp': function () { 263 | var self = this 264 | this.runTest = function (done, regFn) { 265 | var el1 = self.byId('foo') 266 | , trigger = self.trigger() 267 | , spy = self.spy() 268 | 269 | trigger.after(function () { 270 | assert.equals(spy.callCount, 4, 'triggered custom event') 271 | assert.equals(spy.firstCall.args[0], '1', 'expected array argument') 272 | assert.equals(spy.secondCall.args[0], '2', 'expected array argument') 273 | assert.equals(spy.thirdCall.args[0], '3', 'expected array argument') 274 | assert.equals(spy.lastCall.args[0], '3', 'expected array argument') 275 | done() 276 | }) 277 | 278 | regFn(el1, trigger.wrap(spy)) 279 | 280 | bean.fire(el1, 'fat.test1', ['1']) 281 | bean.fire(el1, 'fat.test2', ['2']) 282 | bean.fire(el1, 'fat.foo', ['3']) 283 | } 284 | } 285 | , 'on()': function (done) { 286 | this.runTest(done, function (el1, wrappedSpy) { 287 | bean.on(el1, 'fat.test1.foo fat.test2.foo', wrappedSpy) 288 | }) 289 | } 290 | , 'add()': function (done) { 291 | this.runTest(done, function (el1, wrappedSpy) { 292 | bean.add(el1, 'fat.test1.foo fat.test2.foo', wrappedSpy) 293 | }) 294 | } 295 | } 296 | 297 | , 'should be able to fire multiple events and fire them regardless of the order of the namespaces': { 298 | 'setUp': function () { 299 | var self = this 300 | this.runTest = function (done, regFn) { 301 | var el1 = self.byId('foo') 302 | , trigger = self.trigger() 303 | , spy = self.spy() 304 | 305 | trigger.after(function () { 306 | assert.equals(spy.callCount, 4, 'triggered custom event') 307 | assert.equals(spy.firstCall.args[0], '1', 'expected array argument') 308 | assert.equals(spy.secondCall.args[0], '1', 'expected array argument') 309 | assert.equals(spy.thirdCall.args[0], '2', 'expected array argument') 310 | assert.equals(spy.lastCall.args[0], '2', 'expected array argument') 311 | done() 312 | }) 313 | 314 | regFn(el1, trigger.wrap(spy)) 315 | 316 | bean.fire(el1, 'fat.test.foo', ['1']) 317 | bean.fire(el1, 'fat.foo.test', ['2']) 318 | } 319 | } 320 | , 'on()': function (done) { 321 | this.runTest(done, function (el1, wrappedSpy) { 322 | bean.on(el1, 'fat.test.foo fat.foo.test', wrappedSpy) 323 | }) 324 | } 325 | , 'add()': function (done) { 326 | this.runTest(done, function (el1, wrappedSpy) { 327 | bean.add(el1, 'fat.test.foo fat.foo.test', wrappedSpy) 328 | }) 329 | } 330 | } 331 | 332 | , 'should only fire an event if the fired namespaces is within the event namespace or if the event namespace is within the fired namespace': { 333 | 'setUp': function () { 334 | var self = this 335 | this.runTest = function (done, regFn) { 336 | var el1 = self.byId('foo') 337 | , trigger = self.trigger() 338 | , spy = self.spy() 339 | 340 | trigger.after(function () { 341 | assert.equals(spy.callCount, 5, 'triggered custom event') 342 | assert.equals(spy.firstCall.args[0], '1', 'expected array argument') 343 | assert.equals(spy.secondCall.args[0], '1', 'expected array argument') 344 | assert.equals(spy.thirdCall.args[0], '2', 'expected array argument') 345 | assert.equals(spy.getCall(3).args[0], '2', 'expected array argument') 346 | assert.equals(spy.getCall(4).args[0], '3', 'expected array argument') 347 | done() 348 | }) 349 | 350 | regFn(el1, trigger.wrap(spy)) 351 | 352 | bean.fire(el1, 'fat.test.foo', ['1']) 353 | bean.fire(el1, 'fat.foo.test', ['2']) 354 | bean.fire(el1, 'fat.test.ded', ['3']) 355 | } 356 | } 357 | , 'on()': function (done) { 358 | this.runTest(done, function (el1, wrappedSpy) { 359 | bean.on(el1, 'fat.test.foo.ded fat.foo.test fat.ded', wrappedSpy) 360 | }) 361 | } 362 | , 'add()': function (done) { 363 | this.runTest(done, function (el1, wrappedSpy) { 364 | bean.add(el1, 'fat.test.foo.ded fat.foo.test fat.ded', wrappedSpy) 365 | }) 366 | } 367 | } 368 | }) -------------------------------------------------------------------------------- /tests/event-object-test.js: -------------------------------------------------------------------------------- 1 | /*global bean:true, qwery:true, buster:true, Syn:true, assert:true, features:true, globalSetUp:true, globalTearDown:true*/ 2 | 3 | buster.testCase('event object', { 4 | 'setUp': globalSetUp 5 | , 'tearDown': globalTearDown 6 | 7 | , 'should have correct target': function (done) { 8 | var el1 = this.byId('foo') 9 | , el2 = this.byId('bar') 10 | , trigger = this.trigger() 11 | , spy = this.spy() 12 | 13 | bean.on(el1, 'click', trigger.wrap(spy)) 14 | 15 | Syn.click(el2) 16 | 17 | trigger.after(function() { 18 | assert.equals(spy.callCount, 1, 'called once') 19 | assert(spy.firstCall.args.length, 'has argument') 20 | assert.same(spy.firstCall.args[0].target, el2, 'event object has correct property') 21 | done() 22 | }) 23 | } 24 | 25 | , 'event object': { 26 | 'setUp': function () { 27 | var self = this 28 | this.runTest = function (custom, done, verifyFn) { 29 | var el = self.byId('foo') 30 | , trigger = self.trigger() 31 | , spy = self.spy() 32 | 33 | trigger.after(function() { 34 | assert.equals(spy.callCount, 1, 'called once') 35 | assert(spy.firstCall.args.length, 'has argument') 36 | verifyFn(spy.firstCall.args[0]) 37 | done() 38 | }) 39 | 40 | bean.on(el, custom ? 'customEvent' : 'click', trigger.wrap(spy)) 41 | 42 | if (custom) 43 | bean.fire(el, 'customEvent') 44 | else 45 | Syn.click(el) 46 | } 47 | } 48 | 49 | , 'should have stopPropagation method': function (done) { 50 | this.runTest(false, done, function (event) { 51 | assert.isFunction(event.stopPropagation, 'event object has stopPropagation method') 52 | }) 53 | } 54 | 55 | , 'should have preventDefault method': function (done) { 56 | this.runTest(false, done, function (event) { 57 | assert.isFunction(event.preventDefault, 'event object has preventDefault method') 58 | }) 59 | } 60 | 61 | , 'should have stopImmediatePropagation method': function (done) { 62 | this.runTest(false, done, function (event) { 63 | assert.isFunction(event.stopImmediatePropagation, 'event object has stopImmediatePropagation method') 64 | }) 65 | } 66 | 67 | , 'should have stopPropagation method on custom event': function (done) { 68 | this.runTest(true, done, function (event) { 69 | assert.isFunction(event.stopPropagation, 'event object has stopPropagation method') 70 | }) 71 | } 72 | 73 | , 'should have preventDefault method on custom event': function (done) { 74 | this.runTest(true, done, function (event) { 75 | assert.isFunction(event.preventDefault, 'event object has preventDefault method') 76 | }) 77 | } 78 | 79 | , 'should have stopImmediatePropagation method on custom event': function (done) { 80 | this.runTest(true, done, function (event) { 81 | assert.isFunction(event.stopImmediatePropagation, 'event object has stopImmediatePropagation method') 82 | }) 83 | } 84 | } 85 | 86 | , 'stop()': { 87 | 'setUp': function () { 88 | var self = this 89 | 90 | this.runTest = function (delegate, done) { 91 | // we should be able to prevent a keypress and event propagation with stop() 92 | // on the keypress event, checking the parent doesn't receive the keypress 93 | // and then checking the input contents on a keyup, it should be empty. 94 | var txt = self.byId('txt') 95 | , parent = self.byId('stopper') 96 | , fixture = self.byId('fixtures') 97 | , trigger = self.trigger() 98 | , parentSpy = self.spy() 99 | , txtHandler = function (event) { 100 | event.stop() 101 | } 102 | 103 | trigger.after(function () { 104 | refute(parentSpy.called, 'parent should not receive event') 105 | refute(txt.value.length, 'input is has no text after keypress') 106 | done() 107 | }) 108 | 109 | bean.setSelectorEngine(qwery) 110 | 111 | txt.value = '' 112 | if (delegate) { 113 | bean.on(parent , 'keypress', '*', trigger.wrap(txtHandler)) 114 | bean.on(fixture , 'keypress' , trigger.wrap(parentSpy)) 115 | } else { 116 | bean.on(txt , 'keypress', trigger.wrap(txtHandler)) 117 | bean.on(parent, 'keypress', trigger.wrap(parentSpy)) 118 | } 119 | 120 | Syn.key(txt, 'f') 121 | } 122 | } 123 | 124 | , 'should preventDefault and stopPropagation': function(done) { 125 | this.runTest(false, done) 126 | } 127 | 128 | , 'should preventDefault and stopPropagation on delegated events': function (done) { 129 | this.runTest(true, done) 130 | } 131 | } 132 | 133 | , 'stopImmediatePropagation()': { 134 | 'setUp': function () { 135 | var self = this 136 | 137 | this.runTest = function (delegate, done) { 138 | // we should be able to prevent a keypress and event propagation with stop() 139 | // on the keypress event, checking the parent doesn't receive the keypress 140 | // and then checking the input contents on a keyup, it should be empty. 141 | var stopper = self.byId('stopper') 142 | , txt = self.byId('txt') 143 | , trigger = self.trigger() 144 | , spy1 = self.spy() 145 | , spy2 = self.spy() 146 | , spy3 = self.spy() 147 | , stopHandler = function (event) { 148 | event.stopImmediatePropagation() 149 | } 150 | 151 | trigger.after(function () { 152 | assert.equals(spy1.callCount, 1, 'first spy should be called') 153 | assert.equals(spy2.callCount, 0, 'second spy should not be called') 154 | assert.equals(spy3.callCount, 0, 'third spy should not be called') 155 | done() 156 | }) 157 | 158 | bean.setSelectorEngine(qwery) 159 | 160 | if (delegate) { 161 | bean.on(stopper , 'click', '[type=text]', trigger.wrap(spy1)) 162 | bean.on(stopper , 'click', '[type=text]', trigger.wrap(stopHandler)) 163 | bean.on(stopper , 'click', '[type=text]', trigger.wrap(spy2)) 164 | bean.on(stopper , 'click', '[type=text]', trigger.wrap(spy3)) 165 | Syn.click(txt) 166 | } else { 167 | bean.on(stopper , 'click', trigger.wrap(spy1)) 168 | bean.on(stopper , 'click', trigger.wrap(stopHandler)) 169 | bean.on(stopper , 'click', trigger.wrap(spy2)) 170 | bean.on(stopper , 'click', trigger.wrap(spy3)) 171 | Syn.click(stopper) 172 | } 173 | } 174 | } 175 | 176 | , 'should stop immediate propagation': function(done) { 177 | this.runTest(false, done) 178 | } 179 | 180 | , 'should stop immediate propagation on delegated events': function (done) { 181 | this.runTest(true, done) 182 | } 183 | } 184 | 185 | , 'should have keyCode': function (done) { 186 | var el = this.byId('input') 187 | , trigger = this.trigger() 188 | , spy = this.spy() 189 | 190 | trigger.after(function() { 191 | assert.equals(spy.callCount, 1, 'called once') 192 | assert(spy.firstCall.args.length, 'has argument') 193 | assert(spy.firstCall.args[0].keyCode, 'event object has keyCode') 194 | done() 195 | }) 196 | 197 | bean.on(el, 'keypress', trigger.wrap(spy)) 198 | 199 | Syn.key(el, 'f') 200 | } 201 | 202 | // the idea here is that we have a whitelist in bean.js for properties to copy over from the original 203 | // event object (if they exist) to the new synthetic one. But, there are a bunch of browser specific 204 | // properties we don't care about. We list those properties here and then we check to see if there are 205 | // any properties in source event objects that aren't being copied to the new event objects that we 206 | // haven't specifically listed as 'ignorable'. This way we should be able to pick up new event properties 207 | // browsers as they're implemented and then make a decision as to whether they should be copied or not 208 | , 'event object properties': { 209 | 'setUp': function () { 210 | var commonIgnorables = ('cancelBubble clipboardData defaultPrevented explicitOriginalTarget getPreventDefault initEvent initUIEvent isChar ' + 211 | 'originalTarget preventCapture preventBubble rangeOffset rangeParent returnValue stopImmediatePropagation synthetic initPopStateEvent ' + 212 | 'preventDefault stopPropagation').split(' ') 213 | // stuff from IE8 and below 214 | , oldIEIgnorables = ('recordset altLeft repeat reason data behaviorCookie source contentOverflow behaviorPart url shiftLeft dataFld ' + 215 | 'qualifier wheelDelta bookmarks srcFilter nextPage srcUrn origin boundElements propertyName ctrlLeft state').split(' ') 216 | , clickIgnorables = commonIgnorables.concat(oldIEIgnorables).concat(('charCode defaultPrevented initMouseEvent keyCode layerX layerY ' + 217 | 'initNSMouseEvent x y state webkitMovementY webkitMovementX').split(' ')) 218 | , oldIEKeyIgnorables = 'fromElement toElement dataTransfer button x y screenX screenY clientX clientY offsetX offsetY state'.split(' ') 219 | , keyIgnorables = this.keyIgnorables = commonIgnorables.concat(oldIEIgnorables).concat(oldIEKeyIgnorables).concat('initKeyEvent layerX layerY pageX pageY state'.split(' ')) 220 | 221 | , el = this.byId('input') 222 | 223 | , getEventObject = this.getEventObject = function (evType, elType, trigger, callback) { 224 | var handler = function (e) { 225 | bean.remove(el) 226 | callback(e) 227 | } 228 | el = elType === window ? elType : el; 229 | bean.on(el, evType, handler) 230 | trigger(el) 231 | } 232 | 233 | , contains = function (arr, e) { 234 | var i = arr.length 235 | while (i--) { 236 | if (arr[i] === e) return true 237 | } 238 | return false 239 | } 240 | 241 | , verifyEventObject = this.verifyEventObject = function (event, type, ignorables) { 242 | var p, orig = event.originalEvent 243 | 244 | assert(event, 'has event object') 245 | assert(event.originalEvent, 'has reference to originalEvent') 246 | assert.equals(event.type, type, 'correct event type') 247 | 248 | for (p in orig) { 249 | refute( 250 | !event.hasOwnProperty(p) 251 | && !contains(ignorables, p) 252 | && !/^[A-Z_\d]+$/.test(p) // STUFF_LIKE_THIS 253 | && !/^moz[A-Z]/.test(p) // Mozilla prefixed properties 254 | , 'additional, uncopied property: "' + p + '" (may need to be added to event-object-test.js)' 255 | ) 256 | } 257 | } 258 | 259 | this.testMouseEvent = function (type, syn, done) { 260 | getEventObject( 261 | type 262 | , 'button' 263 | , function (el) { Syn[syn || type](el) } 264 | , function (event) { 265 | verifyEventObject(event, type, clickIgnorables) 266 | done() 267 | } 268 | ) 269 | } 270 | 271 | this.testStateEvent = function (type, done) { 272 | if (!features.history) { 273 | assert(true, 'no history API in this browser, not testing state events') 274 | return done() 275 | } 276 | getEventObject( 277 | type 278 | , window 279 | , function () { 280 | window.history.pushState({}, 'test state', '#test-state') 281 | window.history.go(-1) 282 | } 283 | , function (event) { 284 | try { 285 | verifyEventObject(event, type, commonIgnorables) 286 | } catch (e) { } 287 | done() 288 | } 289 | ) 290 | } 291 | 292 | this.testKeyEvent = function (type, done) { 293 | getEventObject( 294 | type 295 | , 'input' 296 | , function (el) { Syn.key(el, 'f') } 297 | , function (event) { 298 | verifyEventObject(event, type, keyIgnorables) 299 | done() 300 | } 301 | ) 302 | } 303 | } 304 | 305 | , 'click: has correct properties': function (done) { 306 | this.testMouseEvent('click', null, done) 307 | } 308 | 309 | , 'dblclick: has correct properties': function (done) { 310 | this.testMouseEvent('dblclick', null, done) 311 | } 312 | 313 | , 'mousedown: has correct properties': function (done) { 314 | this.testMouseEvent('mousedown', 'click', done) 315 | } 316 | 317 | , 'mouseup: has correct properties': function (done) { 318 | this.testMouseEvent('mouseup', 'click', done) 319 | } 320 | 321 | , 'popstate: has correct properties': function(done) { 322 | this.testStateEvent('popstate', done) 323 | } 324 | 325 | , 'keyup: has correct properties': function (done) { 326 | this.testKeyEvent('keyup', done) 327 | } 328 | 329 | , 'keydown: has correct properties': function (done) { 330 | this.testKeyEvent('keydown', done) 331 | } 332 | 333 | , 'keypress: has correct properties': function (done) { 334 | this.testKeyEvent('keypress', done) 335 | } 336 | 337 | // see https://github.com/fat/bean/pull/61 & https://github.com/fat/bean/issues/76 338 | , 'key events prefer "keyCode" rather than "which"': function (done) { 339 | var verifyEventObject = this.verifyEventObject 340 | , keyIgnorables = this.keyIgnorables 341 | 342 | this.getEventObject( 343 | 'keyup' 344 | , 'input' 345 | , function (el) { Syn.trigger('keyup', { which: 'g', keyCode: 'f' }, el) } 346 | , function (event) { 347 | verifyEventObject(event, 'keyup', keyIgnorables) 348 | assert.equals(event.keyCode, 'f', 'correct keyCode') 349 | done() 350 | } 351 | ) 352 | } 353 | } 354 | }) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bean 2 | Bean is a small, fast, cross-platform, framework-agnostic event manager designed for desktop, mobile, and touch-based browsers. In its simplest form - it works like this: 3 | 4 | ```js 5 | bean.on(element, 'click', function (e) { 6 | console.log('hello'); 7 | }); 8 | ``` 9 | 10 | Bean is included in [Ender](http://enderjs.com/)'s *starter pack*, "The Jeesh". More details on the Ender interface [below](#ender). 11 | 12 | ## API 13 | 14 | Bean has five main methods, each packing quite a punch. 15 | 16 | * bean.on() 17 | * bean.one() 18 | * bean.off() 19 | * bean.clone() 20 | * bean.fire() 21 | 22 | -------------------------------------------------------- 23 | 24 | ### on(element, eventType[, selector], handler[, args ]) 25 | bean.on() lets you attach event listeners to both elements and objects. 26 | 27 | **Arguments** 28 | 29 | * **element / object** (DOM Element or Object) - an HTML DOM element or any JavaScript Object 30 | * **event type(s)** (String) - an event (or multiple events, space separated) to listen to 31 | * **selector** (optional String) - a CSS DOM Element selector string to bind the listener to child elements matching the selector 32 | * **handler** (Function) - the callback function 33 | * **args** (optional) - additional arguments to pas to the callback function when triggered 34 | 35 | Optionally, event types and handlers can be passed in an object of the form `{ 'eventType': handler }` as the second argument. 36 | 37 | **Examples** 38 | 39 | ```js 40 | // simple 41 | bean.on(element, 'click', handler); 42 | 43 | // optional arguments passed to handler 44 | bean.on(element, 'click', function(e, o1, o2) { 45 | console.log(o1, o2); 46 | }, 'fat', 'ded'); 47 | 48 | // multiple events 49 | bean.on(element, 'keydown keyup', handler); 50 | 51 | // multiple handlers 52 | bean.on(element, { 53 | click: function (e) {}, 54 | mouseover: function (e) {}, 55 | 'focus blur': function (e) {} 56 | }); 57 | ``` 58 | 59 | **Delegation** 60 | 61 | A String as the 3rd argument to `on()` will be interpreted as a selector for event delegation. Events for child elements will cause the element to be checked against the selector and the event to be fired if a match is found. The event behaves the same way as if you listened directly to the element it was fired on. 62 | 63 | ```js 64 | // event delegated events 65 | bean.on(element, 'click', '.content p', handler); 66 | 67 | // Alternatively, you can pass an array of elements. 68 | // This cuts down on selector engine work, and is a more performant means of 69 | // delegation if you know your DOM won't be changing: 70 | bean.on(element, 'click', [el, el2, el3], handler); 71 | bean.on(element, 'click', $('.myClass'), handler); 72 | ``` 73 | 74 | **Notes** 75 | 76 | * Prior to v1, Bean used `add()` as its primary handler-adding interface, it still exists but uses the original argument order for delegated events: `add(element[, selector], eventType, handler[, args ])`. This may be removed in future versions of Bean. 77 | 78 | * The focus, blur, and submit events will not delegate due to [vagaries](http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html) of the DOM model. This *may* be addressed in a future version of Bean. 79 | 80 | **Namespacing** 81 | 82 | Bean supports namespacing your events. This makes it much easier to target the handlers later when using `off()` or `fire()`, both of these methods match namespaced handlers in the same way. 83 | 84 | To namespace an event just add a dot followed by your unique name identifier: 85 | 86 | ```js 87 | bean.on(element, 'click.fat.foo', fn); // 1 88 | bean.on(element, 'click.ded', fn); // 2 89 | bean.on(element, 'click', fn); // 3 90 | 91 | // later: 92 | bean.fire(element, 'click.ded'); // trigger 2 93 | bean.fire(element, 'click.fat'); // trigger 1 94 | bean.off(element, 'click'); // remove 1, 2 & 3 95 | 96 | // fire() & off() match multiple namespaces with AND, not OR: 97 | bean.fire(element, 'click.fat.foo'); // trigger 1 98 | bean.off(element, 'click.fat.ded'); // remove nothing 99 | ``` 100 | 101 | **Notes** 102 | 103 | * Prior to v1, Bean matched multiple namespaces in `fire()` and `remove()` calls using OR rather than AND. 104 | 105 | -------------------------------------------------------- 106 | 107 | ### one(element, eventType[, selector], handler[, args ]) 108 | bean.one() is an alias for bean.on() except that the handler will only be executed once and then removed for the event type(s). 109 | 110 | **Notes** 111 | 112 | * Prior to v1, `one()` used the same argument ordering as `add()` (see note above), it now uses the new `on()` ordering. 113 | 114 | -------------------------------------------------------- 115 | 116 | ### off(element[, eventType[, handler ]]) 117 | bean.off() is how you get rid of handlers once you no longer want them active. It's also a good idea to call *off* on elements before you remove them from your DOM; this gives Bean a chance to clean up some things and prevents memory leaks. 118 | 119 | **Arguments** 120 | 121 | * **element / object** (DOM Element or Object) - an HTML DOM element or any JavaScript Object 122 | * **event type(s)** (optional String) - an event (or multiple events, space separated) to remove 123 | * **handler** (optional Function) - the specific callback function to remove 124 | 125 | Optionally, event types and handlers can be passed in an object of the form `{ 'eventType': handler }` as the second argument, just like `on()`. 126 | 127 | **Examples** 128 | 129 | ```js 130 | // remove a single event handlers 131 | bean.off(element, 'click', handler); 132 | 133 | // remove all click handlers 134 | bean.off(element, 'click'); 135 | 136 | // remove handler for all events 137 | bean.off(element, handler); 138 | 139 | // remove multiple events 140 | bean.off(element, 'mousedown mouseup'); 141 | 142 | // remove all events 143 | bean.off(element); 144 | 145 | // remove handlers for events using object literal 146 | bean.off(element, { click: clickHandler, keyup: keyupHandler }) 147 | ``` 148 | 149 | **Notes** 150 | 151 | * Prior to Bean v1, `remove()` was the primary removal interface. This is retained as an alias for backward compatibility but may eventually be removed. 152 | 153 | -------------------------------------------------------- 154 | 155 | ### clone(destElement, srcElement[, eventType ]) 156 | bean.clone() is a method for cloning events from one DOM element or object to another. 157 | 158 | **Examples** 159 | 160 | ```js 161 | // clone all events at once by doing this: 162 | bean.clone(toElement, fromElement); 163 | 164 | // clone events of a specific type 165 | bean.clone(toElement, fromElement, 'click'); 166 | ``` 167 | 168 | -------------------------------------------------------- 169 | 170 | ### fire(element, eventType[, args ]) 171 | bean.fire() gives you the ability to trigger events. 172 | 173 | **Examples** 174 | 175 | ```js 176 | // fire a single event on an element 177 | bean.fire(element, 'click'); 178 | 179 | // fire multiple types 180 | bean.fire(element, 'mousedown mouseup'); 181 | ``` 182 | 183 | **Notes** 184 | 185 | * An optional args array may be passed to `fire()` which will in turn be passed to the event handlers. Handlers will be triggered manually, outside of the DOM, even if you're trying to fire standard DOM events. 186 | 187 | 188 | -------------------------------------------------------- 189 | 190 | ### setSelectorEngine(selectorEngine) 191 | bean.setSelectorEngine() allows you to set a default selector engine for all your delegation needs. 192 | 193 | The selector engine simply needs to be a function that takes two arguments: a selector string and a root element, it should return an array of matched DOM elements. [Qwery](https://github.com/ded/qwery), [Sel](https://github.com/amccollum/sel), [Sizzle](https://github.com/jquery/sizzle), [NWMatcher](https://github.com/dperini/nwmatcher) and other selector engines should all be compatible with Bean. 194 | 195 | **Examples** 196 | 197 | ```js 198 | bean.setSelectorEngine(qwery); 199 | ``` 200 | 201 | **Notes** 202 | 203 | * `querySelectorAll()` is used as the default selector engine, this is available on most modern platforms such as mobile WebKit. To support event delegation on older browsers you will need to install a selector engine. 204 | 205 | ## The `Event` object 206 | 207 | Bean implements a variant of the standard DOM `Event` object, supplied as the argument to your DOM event handler functions. Bean wraps and *fixes* the native `Event` object where required, providing a consistent interface across browsers. 208 | 209 | ```js 210 | // prevent default behavior and propagation (even works on old IE) 211 | bean.on(el, 'click', function (event) { 212 | event.preventDefault(); 213 | event.stopPropagation(); 214 | }); 215 | 216 | // a simple shortcut version of the above code 217 | bean.on(el, 'click', function (event) { 218 | event.stop(); 219 | }); 220 | 221 | // prevent all subsequent handlers from being triggered for this particular event 222 | bean.on(el, 'click', function (event) { 223 | event.stopImmediatePropagation(); 224 | }); 225 | ``` 226 | 227 | **Notes** 228 | 229 | * Your mileage with the `Event` methods (`preventDefault` etc.) may vary with delegated events as the events are not intercepted at the element in question. 230 | 231 | ## Custom events 232 | 233 | Bean uses methods similar to [Dean Edwards' event model](http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/) to ensure custom events behave like real events, rather than just callbacks. 234 | 235 | For all intents and purposes, you can just think of them as native DOM events, which will bubble up and behave you would expect. 236 | 237 | **Examples** 238 | 239 | ```js 240 | bean.on(element, 'partytime', handler); 241 | bean.fire(element, 'partytime'); 242 | ``` 243 | 244 | ## mouseenter, mouseleave 245 | 246 | Bean provides you with two custom DOM events, *'mouseenter'* and *'mouseleave'*. They are essentially just helpers for making your mouseover / mouseout lives a bit easier. 247 | 248 | **Examples** 249 | 250 | ```js 251 | bean.on(element, 'mouseenter', enterHandler); 252 | bean.on(element, 'mouseleave', leaveHandler); 253 | ``` 254 | 255 | ## Object support 256 | 257 | Everything you can do in Bean with an element, you can also do with an object. This is particularly useful for working with classes or plugins. 258 | 259 | ```js 260 | var inst = new Klass(); 261 | bean.on(inst, 'complete', handler); 262 | 263 | //later on... 264 | bean.fire(inst, 'complete'); 265 | ``` 266 | 267 | 268 | ## Ender Integration API 269 | 270 | If you use Bean with Ender its API is greatly extended through its bridge file. This extension aims to give Bean the look and feel of jQuery. 271 | 272 | **Add events** 273 | 274 | + on - $(element).on('click', fn); 275 | + addListener - $(element).addListener('click', fn); 276 | + bind - $(element).bind('click', fn); 277 | + listen - $(element).listen('click', fn); 278 | 279 | **Remove events** 280 | 281 | + off - $(element).off('click'); 282 | + unbind - $(element).unbind('click'); 283 | + unlisten - $(element).unlisten('click'); 284 | + removeListener - $(element).removeListener('click'); 285 | 286 | **Delegate events** 287 | 288 | + on - $(element).on('click', '.foo', fn); 289 | + delegate - $(element).delegate('.foo', 'click', fn); 290 | + undelegate - $(element).undelegate('.foo', 'click'); 291 | 292 | **Clone events** 293 | 294 | + cloneEvents - $(element).cloneEvents('.foo', fn); 295 | 296 | **Custom events** 297 | 298 | + fire / emit / trigger - $(element).trigger('click') 299 | 300 | **Special events** 301 | 302 | + hover - $(element).hover(enterfn, leavefn); 303 | + blur - $(element).blur(fn); 304 | + change - $(element).change(fn); 305 | + click - $(element).click(fn); 306 | + dblclick - $(element).dblclick(fn); 307 | + focusin - $(element).focusin(fn); 308 | + focusout - $(element).focusout(fn); 309 | + keydown - $(element).keydown(fn); 310 | + keypress - $(element).keypress(fn); 311 | + keyup - $(element).keyup(fn); 312 | + mousedown - $(element).mousedown(fn); 313 | + mouseenter - $(element).mouseenter(fn); 314 | + mouseleave - $(element).mouseleave(fn); 315 | + mouseout - $(element).mouseout(fn); 316 | + mouseover - $(element).mouseover(fn); 317 | + mouseup - $(element).mouseup(fn); 318 | + mousemove - $(element).mousemove(fn); 319 | + resize - $(element).resize(fn); 320 | + scroll - $(element).scroll(fn); 321 | + select - $(element).select(fn); 322 | + submit - $(element).submit(fn); 323 | + unload - $(element).unload(fn); 324 | 325 | ## Browser support 326 | 327 | Bean passes our tests in all the following browsers. If you've found bugs in these browsers or others please let us know by submitting an issue on GitHub! 328 | 329 | - IE6+ 330 | - Chrome 1+ 331 | - Safari 4+ 332 | - Firefox 3.5+ 333 | - Opera 10+ 334 | 335 | ## Contributing 336 | 337 | Bean uses [BusterJS](http://busterjs.org/) for its unit tests. `npm install` will install Buster and other required development dependencies for you and then you can simply point your browser at *bean/tests/tests.html*. 338 | 339 | A Buster configuration file also exists so you can use `buster-server` to run a capture server to attach multiple browsers to and then `buster-test` to run the tests (if you don't have Buster installed globally, you can find the executables in *node_modules/.bin/*). 340 | 341 | We're more than happy to consider pull requests, however major features that have not been previously discussed may risk being rejected. Feel free to open an issue on GitHub for discussion or questions. 342 | 343 | Contributions should stick with Bean's coding style: comma-first, semicolon-free and two-space indenting. Non-trivial contributions should come with unit tests also, feel free to ask questions if you have trouble. 344 | 345 | Running `make` will assemble the *bean.js* file in the root of the repository. Please be aware that any contributions to bean should be in *src/bean.js* or they will be lost! 346 | 347 | ## Contributors 348 | 349 | * [Jacob Thornton](https://github.com/fat/bean/commits/master?author=fat) ([GitHub](https://github.com/fat) - [Twitter](https://twitter.com/fat)) 350 | * [Rod Vagg](https://github.com/fat/bean/commits/master?author=rvagg) ([GitHub](https://github.com/rvagg) - [Twitter](https://twitter.com/rvagg)) 351 | * [Dustin Diaz](https://github.com/fat/bean/commits/master?author=ded) ([GitHub](https://github.com/ded) - [Twitter](https://twitter.com/ded)) 352 | 353 | Special thanks to: 354 | 355 | * [Dean Edwards](http://dean.edwards.name/) 356 | * [Diego Perini](https://github.com/dperini/nwevents) 357 | * [The entire MooTools team](https://github.com/mootools/mootools-core) 358 | 359 | ## Licence & copyright 360 | 361 | Bean is copyright © 2011-2012 Jacob Thornton and licenced under the MIT licence. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details. 362 | -------------------------------------------------------------------------------- /tests/benchmark/bean_04.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * bean.js - copyright Jacob Thornton 2011 3 | * https://github.com/fat/bean 4 | * MIT License 5 | * special thanks to: 6 | * dean edwards: http://dean.edwards.name/ 7 | * dperini: https://github.com/dperini/nwevents 8 | * the entire mootools team: github.com/mootools/mootools-core 9 | */ 10 | !function (name, context, definition) { 11 | if (typeof module !== 'undefined') module.exports = definition(name, context); 12 | else if (typeof define === 'function' && typeof define.amd === 'object') define(definition); 13 | else context[name] = definition(name, context); 14 | }('bean', this, function (name, context) { 15 | var win = window 16 | , old = context[name] 17 | , overOut = /over|out/ 18 | , namespaceRegex = /[^\.]*(?=\..*)\.|.*/ 19 | , nameRegex = /\..*/ 20 | , addEvent = 'addEventListener' 21 | , attachEvent = 'attachEvent' 22 | , removeEvent = 'removeEventListener' 23 | , detachEvent = 'detachEvent' 24 | , ownerDocument = 'ownerDocument' 25 | , targetS = 'target' 26 | , qSA = 'querySelectorAll' 27 | , doc = document || {} 28 | , root = doc.documentElement || {} 29 | , W3C_MODEL = root[addEvent] 30 | , eventSupport = W3C_MODEL ? addEvent : attachEvent 31 | , slice = Array.prototype.slice 32 | , mouseTypeRegex = /click|mouse(?!(.*wheel|scroll))|menu|drag|drop/i 33 | , mouseWheelTypeRegex = /mouse.*(wheel|scroll)/i 34 | , textTypeRegex = /^text/i 35 | , touchTypeRegex = /^touch|^gesture/i 36 | , ONE = {} // singleton for quick matching making add() do one() 37 | 38 | , nativeEvents = (function (hash, events, i) { 39 | for (i = 0; i < events.length; i++) 40 | hash[events[i]] = 1 41 | return hash 42 | }({}, ( 43 | 'click dblclick mouseup mousedown contextmenu ' + // mouse buttons 44 | 'mousewheel mousemultiwheel DOMMouseScroll ' + // mouse wheel 45 | 'mouseover mouseout mousemove selectstart selectend ' + // mouse movement 46 | 'keydown keypress keyup ' + // keyboard 47 | 'orientationchange ' + // mobile 48 | 'focus blur change reset select submit ' + // form elements 49 | 'load unload beforeunload resize move DOMContentLoaded '+ // window 50 | 'readystatechange message ' + // window 51 | 'error abort scroll ' + // misc 52 | (W3C_MODEL ? // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event 53 | // that doesn't actually exist, so make sure we only do these on newer browsers 54 | 'show ' + // mouse buttons 55 | 'input invalid ' + // form elements 56 | 'touchstart touchmove touchend touchcancel ' + // touch 57 | 'gesturestart gesturechange gestureend ' + // gesture 58 | 'readystatechange pageshow pagehide popstate ' + // window 59 | 'hashchange offline online ' + // window 60 | 'afterprint beforeprint ' + // printing 61 | 'dragstart dragenter dragover dragleave drag drop dragend ' + // dnd 62 | 'loadstart progress suspend emptied stalled loadmetadata ' + // media 63 | 'loadeddata canplay canplaythrough playing waiting seeking ' + // media 64 | 'seeked ended durationchange timeupdate play pause ratechange ' + // media 65 | 'volumechange cuechange ' + // media 66 | 'checking noupdate downloading cached updateready obsolete ' + // appcache 67 | '' : '') 68 | ).split(' ') 69 | )) 70 | 71 | , customEvents = (function () { 72 | var cdp = 'compareDocumentPosition' 73 | , isAncestor = cdp in root 74 | ? function (element, container) { 75 | return container[cdp] && (container[cdp](element) & 16) === 16 76 | } 77 | : 'contains' in root 78 | ? function (element, container) { 79 | container = container.nodeType === 9 || container === window ? root : container 80 | return container !== element && container.contains(element) 81 | } 82 | : function (element, container) { 83 | while (element = element.parentNode) if (element === container) return 1 84 | return 0 85 | } 86 | 87 | function check(event) { 88 | var related = event.relatedTarget 89 | return !related 90 | ? related === null 91 | : (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString()) && !isAncestor(related, this)) 92 | } 93 | 94 | return { 95 | mouseenter: { base: 'mouseover', condition: check } 96 | , mouseleave: { base: 'mouseout', condition: check } 97 | , mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } 98 | } 99 | }()) 100 | 101 | , fixEvent = (function () { 102 | var commonProps = 'altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which'.split(' ') 103 | , mouseProps = commonProps.concat('button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement'.split(' ')) 104 | , mouseWheelProps = mouseProps.concat('wheelDelta wheelDeltaX wheelDeltaY wheelDeltaZ axis'.split(' ')) // 'axis' is FF specific 105 | , keyProps = commonProps.concat('char charCode key keyCode keyIdentifier keyLocation'.split(' ')) 106 | , textProps = commonProps.concat(['data']) 107 | , touchProps = commonProps.concat('touches targetTouches changedTouches scale rotation'.split(' ')) 108 | , messageProps = commonProps.concat(['data', 'origin', 'source']) 109 | , stateProps = commonProps.concat(['state']) 110 | , preventDefault = 'preventDefault' 111 | , createPreventDefault = function (event) { 112 | return function () { 113 | if (event[preventDefault]) 114 | event[preventDefault]() 115 | else 116 | event.returnValue = false 117 | } 118 | } 119 | , stopPropagation = 'stopPropagation' 120 | , createStopPropagation = function (event) { 121 | return function () { 122 | if (event[stopPropagation]) 123 | event[stopPropagation]() 124 | else 125 | event.cancelBubble = true 126 | } 127 | } 128 | , createStop = function (synEvent) { 129 | return function () { 130 | synEvent[preventDefault]() 131 | synEvent[stopPropagation]() 132 | synEvent.stopped = true 133 | } 134 | } 135 | , copyProps = function (event, result, props) { 136 | var i, p 137 | for (i = props.length; i--;) { 138 | p = props[i] 139 | if (!(p in result) && p in event) result[p] = event[p] 140 | } 141 | } 142 | 143 | return function (event, isNative) { 144 | var result = { originalEvent: event, isNative: isNative } 145 | if (!event) 146 | return result 147 | 148 | var props 149 | , type = event.type 150 | , target = event[targetS] || event.srcElement 151 | 152 | result[preventDefault] = createPreventDefault(event) 153 | result[stopPropagation] = createStopPropagation(event) 154 | result.stop = createStop(result) 155 | result[targetS] = target && target.nodeType === 3 ? target.parentNode : target 156 | if (isNative) { // we only need basic augmentation on custom events, the rest is too expensive 157 | if (type.indexOf('key') !== -1) { 158 | props = keyProps 159 | result.keyCode = event.keyCode || event.which 160 | } else if (mouseTypeRegex.test(type)) { 161 | props = mouseProps 162 | result.rightClick = event.which === 3 || event.button === 2 163 | result.pos = { x: 0, y: 0 } 164 | if (event.pageX || event.pageY) { 165 | result.clientX = event.pageX 166 | result.clientY = event.pageY 167 | } else if (event.clientX || event.clientY) { 168 | result.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft 169 | result.clientY = event.clientY + doc.body.scrollTop + root.scrollTop 170 | } 171 | if (overOut.test(type)) 172 | result.relatedTarget = event.relatedTarget || event[(type === 'mouseover' ? 'from' : 'to') + 'Element'] 173 | } else if (touchTypeRegex.test(type)) { 174 | props = touchProps 175 | } else if (mouseWheelTypeRegex.test(type)) { 176 | props = mouseWheelProps 177 | } else if (textTypeRegex.test(type)) { 178 | props = textProps 179 | } else if (type === 'message') { 180 | props = messageProps 181 | } else if (type === 'popstate') { 182 | props = stateProps; 183 | } 184 | copyProps(event, result, props || commonProps) 185 | } 186 | return result 187 | } 188 | }()) 189 | 190 | // if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both 191 | , targetElement = function (element, isNative) { 192 | return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element 193 | } 194 | 195 | // we use one of these per listener, of any type 196 | , RegEntry = (function () { 197 | function entry(element, type, handler, original, namespaces) { 198 | var isNative = this.isNative = nativeEvents[type] && element[eventSupport] 199 | this.element = element 200 | this.type = type 201 | this.handler = handler 202 | this.original = original 203 | this.namespaces = namespaces 204 | this.custom = customEvents[type] 205 | this.eventType = W3C_MODEL || isNative ? type : 'propertychange' 206 | this.customType = !W3C_MODEL && !isNative && type 207 | this[targetS] = targetElement(element, isNative) 208 | this[eventSupport] = this[targetS][eventSupport] 209 | } 210 | 211 | entry.prototype = { 212 | // given a list of namespaces, is our entry in any of them? 213 | inNamespaces: function (checkNamespaces) { 214 | var i, j 215 | if (!checkNamespaces) 216 | return true 217 | if (!this.namespaces) 218 | return false 219 | for (i = checkNamespaces.length; i--;) { 220 | for (j = this.namespaces.length; j--;) { 221 | if (checkNamespaces[i] === this.namespaces[j]) 222 | return true 223 | } 224 | } 225 | return false 226 | } 227 | 228 | // match by element, original fn (opt), handler fn (opt) 229 | , matches: function (checkElement, checkOriginal, checkHandler) { 230 | return this.element === checkElement && 231 | (!checkOriginal || this.original === checkOriginal) && 232 | (!checkHandler || this.handler === checkHandler) 233 | } 234 | } 235 | 236 | return entry 237 | }()) 238 | 239 | , registry = (function () { 240 | // our map stores arrays by event type, just because it's better than storing 241 | // everything in a single array. uses '$' as a prefix for the keys for safety 242 | var map = {} 243 | 244 | // generic functional search of our registry for matching listeners, 245 | // `fn` returns false to break out of the loop 246 | , forAll = function (element, type, original, handler, fn) { 247 | if (!type || type === '*') { 248 | // search the whole registry 249 | for (var t in map) { 250 | if (t.charAt(0) === '$') 251 | forAll(element, t.substr(1), original, handler, fn) 252 | } 253 | } else { 254 | var i = 0, l, list = map['$' + type], all = element === '*' 255 | if (!list) 256 | return 257 | for (l = list.length; i < l; i++) { 258 | if (all || list[i].matches(element, original, handler)) 259 | if (!fn(list[i], list, i, type)) 260 | return 261 | } 262 | } 263 | } 264 | 265 | , has = function (element, type, original) { 266 | // we're not using forAll here simply because it's a bit slower and this 267 | // needs to be fast 268 | var i, list = map['$' + type] 269 | if (list) { 270 | for (i = list.length; i--;) { 271 | if (list[i].matches(element, original, null)) 272 | return true 273 | } 274 | } 275 | return false 276 | } 277 | 278 | , get = function (element, type, original) { 279 | var entries = [] 280 | forAll(element, type, original, null, function (entry) { return entries.push(entry) }) 281 | return entries 282 | } 283 | 284 | , put = function (entry) { 285 | (map['$' + entry.type] || (map['$' + entry.type] = [])).push(entry) 286 | return entry 287 | } 288 | 289 | , del = function (entry) { 290 | forAll(entry.element, entry.type, null, entry.handler, function (entry, list, i) { 291 | list.splice(i, 1) 292 | if (list.length === 0) 293 | delete map['$' + entry.type] 294 | return false 295 | }) 296 | } 297 | 298 | // dump all entries, used for onunload 299 | , entries = function () { 300 | var t, entries = [] 301 | for (t in map) { 302 | if (t.charAt(0) === '$') 303 | entries = entries.concat(map[t]) 304 | } 305 | return entries 306 | } 307 | 308 | return { has: has, get: get, put: put, del: del, entries: entries } 309 | }()) 310 | 311 | , selectorEngine = doc[qSA] 312 | ? function (s, r) { 313 | return r[qSA](s) 314 | } 315 | : function () { 316 | throw new Error('Bean: No selector engine installed') // eeek 317 | } 318 | 319 | , setSelectorEngine = function (e) { 320 | selectorEngine = e 321 | } 322 | 323 | // add and remove listeners to DOM elements 324 | , listener = W3C_MODEL ? function (element, type, fn, add) { 325 | element[add ? addEvent : removeEvent](type, fn, false) 326 | } : function (element, type, fn, add, custom) { 327 | if (custom && add && element['_on' + custom] === null) 328 | element['_on' + custom] = 0 329 | element[add ? attachEvent : detachEvent]('on' + type, fn) 330 | } 331 | 332 | , nativeHandler = function (element, fn, args) { 333 | var beanDel = fn.__beanDel 334 | , handler = function (event) { 335 | event = fixEvent(event || ((this[ownerDocument] || this.document || this).parentWindow || win).event, true) 336 | if (beanDel) // delegated event, fix the fix 337 | event.currentTarget = beanDel.ft(event[targetS], element) 338 | return fn.apply(element, [event].concat(args)) 339 | } 340 | handler.__beanDel = beanDel 341 | return handler 342 | } 343 | 344 | , customHandler = function (element, fn, type, condition, args, isNative) { 345 | var beanDel = fn.__beanDel 346 | , handler = function (event) { 347 | var target = beanDel ? beanDel.ft(event[targetS], element) : this // deleated event 348 | if (condition ? condition.apply(target, arguments) : W3C_MODEL ? true : event && event.propertyName === '_on' + type || !event) { 349 | if (event) { 350 | event = fixEvent(event || ((this[ownerDocument] || this.document || this).parentWindow || win).event, isNative) 351 | event.currentTarget = target 352 | } 353 | fn.apply(element, event && (!args || args.length === 0) ? arguments : slice.call(arguments, event ? 0 : 1).concat(args)) 354 | } 355 | } 356 | handler.__beanDel = beanDel 357 | return handler 358 | } 359 | 360 | , once = function (rm, element, type, fn, originalFn) { 361 | // wrap the handler in a handler that does a remove as well 362 | return function () { 363 | rm(element, type, originalFn) 364 | fn.apply(this, arguments) 365 | } 366 | } 367 | 368 | , removeListener = function (element, orgType, handler, namespaces) { 369 | var i, l, entry 370 | , type = (orgType && orgType.replace(nameRegex, '')) 371 | , handlers = registry.get(element, type, handler) 372 | 373 | for (i = 0, l = handlers.length; i < l; i++) { 374 | if (handlers[i].inNamespaces(namespaces)) { 375 | if ((entry = handlers[i])[eventSupport]) 376 | listener(entry[targetS], entry.eventType, entry.handler, false, entry.type) 377 | // TODO: this is problematic, we have a registry.get() and registry.del() that 378 | // both do registry searches so we waste cycles doing this. Needs to be rolled into 379 | // a single registry.forAll(fn) that removes while finding, but the catch is that 380 | // we'll be splicing the arrays that we're iterating over. Needs extra tests to 381 | // make sure we don't screw it up. @rvagg 382 | registry.del(entry) 383 | } 384 | } 385 | } 386 | 387 | , addListener = function (element, orgType, fn, originalFn, args) { 388 | var entry 389 | , type = orgType.replace(nameRegex, '') 390 | , namespaces = orgType.replace(namespaceRegex, '').split('.') 391 | 392 | if (registry.has(element, type, fn)) 393 | return element // no dupe 394 | if (type === 'unload') 395 | fn = once(removeListener, element, type, fn, originalFn) // self clean-up 396 | if (customEvents[type]) { 397 | if (customEvents[type].condition) 398 | fn = customHandler(element, fn, type, customEvents[type].condition, args, true) 399 | type = customEvents[type].base || type 400 | } 401 | entry = registry.put(new RegEntry(element, type, fn, originalFn, namespaces[0] && namespaces)) 402 | entry.handler = entry.isNative ? 403 | nativeHandler(element, entry.handler, args) : 404 | customHandler(element, entry.handler, type, false, args, false) 405 | if (entry[eventSupport]) 406 | listener(entry[targetS], entry.eventType, entry.handler, true, entry.customType) 407 | } 408 | 409 | , del = function (selector, fn, $) { 410 | //TODO: findTarget (therefore $) is called twice, once for match and once for 411 | // setting e.currentTarget, fix this so it's only needed once 412 | var findTarget = function (target, root) { 413 | var i, array = typeof selector === 'string' ? $(selector, root) : selector 414 | for (; target && target !== root; target = target.parentNode) { 415 | for (i = array.length; i--;) { 416 | if (array[i] === target) 417 | return target 418 | } 419 | } 420 | } 421 | , handler = function (e) { 422 | var match = findTarget(e[targetS], this) 423 | match && fn.apply(match, arguments) 424 | } 425 | 426 | handler.__beanDel = { 427 | ft: findTarget // attach it here for customEvents to use too 428 | , selector: selector 429 | , $: $ 430 | } 431 | return handler 432 | } 433 | 434 | , remove = function (element, typeSpec, fn) { 435 | var k, type, namespaces, i 436 | , rm = removeListener 437 | , isString = typeSpec && typeof typeSpec === 'string' 438 | 439 | if (isString && typeSpec.indexOf(' ') > 0) { 440 | // remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3') 441 | typeSpec = typeSpec.split(' ') 442 | for (i = typeSpec.length; i--;) 443 | remove(element, typeSpec[i], fn) 444 | return element 445 | } 446 | type = isString && typeSpec.replace(nameRegex, '') 447 | if (type && customEvents[type]) 448 | type = customEvents[type].type 449 | if (!typeSpec || isString) { 450 | // remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3) 451 | if (namespaces = isString && typeSpec.replace(namespaceRegex, '')) 452 | namespaces = namespaces.split('.') 453 | rm(element, type, fn, namespaces) 454 | } else if (typeof typeSpec === 'function') { 455 | // remove(el, fn) 456 | rm(element, null, typeSpec) 457 | } else { 458 | // remove(el, { t1: fn1, t2, fn2 }) 459 | for (k in typeSpec) { 460 | if (typeSpec.hasOwnProperty(k)) 461 | remove(element, k, typeSpec[k]) 462 | } 463 | } 464 | return element 465 | } 466 | 467 | // 5th argument, $=selector engine, is deprecated and will be removed 468 | , add = function (element, events, fn, delfn, $) { 469 | var type, types, i, args 470 | , originalFn = fn 471 | , isDel = fn && typeof fn === 'string' 472 | 473 | if (events && !fn && typeof events === 'object') { 474 | for (type in events) { 475 | if (events.hasOwnProperty(type)) 476 | add.apply(this, [ element, type, events[type] ]) 477 | } 478 | } else { 479 | args = arguments.length > 3 ? slice.call(arguments, 3) : [] 480 | types = (isDel ? fn : events).split(' ') 481 | isDel && (fn = del(events, (originalFn = delfn), $ || selectorEngine)) && (args = slice.call(args, 1)) 482 | // special case for one() 483 | this === ONE && (fn = once(remove, element, events, fn, originalFn)) 484 | for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args) 485 | } 486 | return element 487 | } 488 | 489 | , one = function () { 490 | return add.apply(ONE, arguments) 491 | } 492 | 493 | , fireListener = W3C_MODEL ? function (isNative, type, element) { 494 | var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents') 495 | evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1) 496 | element.dispatchEvent(evt) 497 | } : function (isNative, type, element) { 498 | element = targetElement(element, isNative) 499 | // if not-native then we're using onpropertychange so we just increment a custom property 500 | isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++ 501 | } 502 | 503 | , fire = function (element, type, args) { 504 | var i, j, l, names, handlers 505 | , types = type.split(' ') 506 | 507 | for (i = types.length; i--;) { 508 | type = types[i].replace(nameRegex, '') 509 | if (names = types[i].replace(namespaceRegex, '')) 510 | names = names.split('.') 511 | if (!names && !args && element[eventSupport]) { 512 | fireListener(nativeEvents[type], type, element) 513 | } else { 514 | // non-native event, either because of a namespace, arguments or a non DOM element 515 | // iterate over all listeners and manually 'fire' 516 | handlers = registry.get(element, type) 517 | args = [false].concat(args) 518 | for (j = 0, l = handlers.length; j < l; j++) { 519 | if (handlers[j].inNamespaces(names)) 520 | handlers[j].handler.apply(element, args) 521 | } 522 | } 523 | } 524 | return element 525 | } 526 | 527 | , clone = function (element, from, type) { 528 | var i = 0 529 | , handlers = registry.get(from, type) 530 | , l = handlers.length 531 | , args, beanDel 532 | 533 | for (;i < l; i++) { 534 | if (handlers[i].original) { 535 | beanDel = handlers[i].handler.__beanDel 536 | if (beanDel) { 537 | args = [ element, beanDel.selector, handlers[i].type, handlers[i].original, beanDel.$] 538 | } else 539 | args = [ element, handlers[i].type, handlers[i].original ] 540 | add.apply(null, args) 541 | } 542 | } 543 | return element 544 | } 545 | 546 | , bean = { 547 | add: add 548 | , one: one 549 | , remove: remove 550 | , clone: clone 551 | , fire: fire 552 | , setSelectorEngine: setSelectorEngine 553 | , noConflict: function () { 554 | context[name] = old 555 | return this 556 | } 557 | } 558 | 559 | if (win[attachEvent]) { 560 | // for IE, clean up on unload to avoid leaks 561 | var cleanup = function () { 562 | var i, entries = registry.entries() 563 | for (i in entries) { 564 | if (entries[i].type && entries[i].type !== 'unload') 565 | remove(entries[i].element, entries[i].type) 566 | } 567 | win[detachEvent]('onunload', cleanup) 568 | win.CollectGarbage && win.CollectGarbage() 569 | } 570 | win[attachEvent]('onunload', cleanup) 571 | } 572 | 573 | return bean 574 | }) 575 | -------------------------------------------------------------------------------- /tests/support/syn/key.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var h = Syn.helpers, 3 | S = Syn, 4 | 5 | // gets the selection of an input or textarea 6 | getSelection = function( el ) { 7 | // use selectionStart if we can 8 | if ( el.selectionStart !== undefined ) { 9 | // this is for opera, so we don't have to focus to type how we think we would 10 | if ( document.activeElement && document.activeElement != el && el.selectionStart == el.selectionEnd && el.selectionStart == 0 ) { 11 | return { 12 | start: el.value.length, 13 | end: el.value.length 14 | }; 15 | } 16 | return { 17 | start: el.selectionStart, 18 | end: el.selectionEnd 19 | } 20 | } else { 21 | //check if we aren't focused 22 | try { 23 | //try 2 different methods that work differently (IE breaks depending on type) 24 | if ( el.nodeName.toLowerCase() == 'input' ) { 25 | var real = h.getWindow(el).document.selection.createRange(), 26 | r = el.createTextRange(); 27 | r.setEndPoint("EndToStart", real); 28 | 29 | var start = r.text.length 30 | return { 31 | start: start, 32 | end: start + real.text.length 33 | } 34 | } 35 | else { 36 | var real = h.getWindow(el).document.selection.createRange(), 37 | r = real.duplicate(), 38 | r2 = real.duplicate(), 39 | r3 = real.duplicate(); 40 | r2.collapse(); 41 | r3.collapse(false); 42 | r2.moveStart('character', -1) 43 | r3.moveStart('character', -1) 44 | //select all of our element 45 | r.moveToElementText(el) 46 | //now move our endpoint to the end of our real range 47 | r.setEndPoint('EndToEnd', real); 48 | var start = r.text.length - real.text.length, 49 | end = r.text.length; 50 | if ( start != 0 && r2.text == "" ) { 51 | start += 2; 52 | } 53 | if ( end != 0 && r3.text == "" ) { 54 | end += 2; 55 | } 56 | //if we aren't at the start, but previous is empty, we are at start of newline 57 | return { 58 | start: start, 59 | end: end 60 | } 61 | } 62 | } catch (e) { 63 | return { 64 | start: el.value.length, 65 | end: el.value.length 66 | }; 67 | } 68 | } 69 | }, 70 | // gets all focusable elements 71 | getFocusable = function( el ) { 72 | var document = h.getWindow(el).document, 73 | res = []; 74 | 75 | var els = document.getElementsByTagName('*'), 76 | len = els.length; 77 | 78 | for ( var i = 0; i < len; i++ ) { 79 | Syn.isFocusable(els[i]) && els[i] != document.documentElement && res.push(els[i]) 80 | } 81 | return res; 82 | 83 | 84 | }; 85 | 86 | /** 87 | * @add Syn static 88 | */ 89 | h.extend(Syn, { 90 | /** 91 | * @attribute 92 | * A list of the keys and their keycodes codes you can type. 93 | * You can add type keys with 94 | * @codestart 95 | * Syn('key','delete','title'); 96 | * 97 | * //or 98 | * 99 | * Syn('type','One Two Three[left][left][delete]','title') 100 | * @codeend 101 | * 102 | * The following are a list of keys you can type: 103 | * @codestart text 104 | * \b - backspace 105 | * \t - tab 106 | * \r - enter 107 | * ' ' - space 108 | * a-Z 0-9 - normal characters 109 | * /!@#$*,.? - All other typeable characters 110 | * page-up - scrolls up 111 | * page-down - scrolls down 112 | * end - scrolls to bottom 113 | * home - scrolls to top 114 | * insert - changes how keys are entered 115 | * delete - deletes the next character 116 | * left - moves cursor left 117 | * right - moves cursor right 118 | * up - moves the cursor up 119 | * down - moves the cursor down 120 | * f1-12 - function buttons 121 | * shift, ctrl, alt - special keys 122 | * pause-break - the pause button 123 | * scroll-lock - locks scrolling 124 | * caps - makes caps 125 | * escape - escape button 126 | * num-lock - allows numbers on keypad 127 | * print - screen capture 128 | * @codeend 129 | */ 130 | keycodes: { 131 | //backspace 132 | '\b': 8, 133 | 134 | //tab 135 | '\t': 9, 136 | 137 | //enter 138 | '\r': 13, 139 | 140 | //special 141 | 'shift': 16, 142 | 'ctrl': 17, 143 | 'alt': 18, 144 | 145 | //weird 146 | 'pause-break': 19, 147 | 'caps': 20, 148 | 'escape': 27, 149 | 'num-lock': 144, 150 | 'scroll-lock': 145, 151 | 'print': 44, 152 | 153 | //navigation 154 | 'page-up': 33, 155 | 'page-down': 34, 156 | 'end': 35, 157 | 'home': 36, 158 | 'left': 37, 159 | 'up': 38, 160 | 'right': 39, 161 | 'down': 40, 162 | 'insert': 45, 163 | 'delete': 46, 164 | 165 | //normal characters 166 | ' ': 32, 167 | '0': 48, 168 | '1': 49, 169 | '2': 50, 170 | '3': 51, 171 | '4': 52, 172 | '5': 53, 173 | '6': 54, 174 | '7': 55, 175 | '8': 56, 176 | '9': 57, 177 | 'a': 65, 178 | 'b': 66, 179 | 'c': 67, 180 | 'd': 68, 181 | 'e': 69, 182 | 'f': 70, 183 | 'g': 71, 184 | 'h': 72, 185 | 'i': 73, 186 | 'j': 74, 187 | 'k': 75, 188 | 'l': 76, 189 | 'm': 77, 190 | 'n': 78, 191 | 'o': 79, 192 | 'p': 80, 193 | 'q': 81, 194 | 'r': 82, 195 | 's': 83, 196 | 't': 84, 197 | 'u': 85, 198 | 'v': 86, 199 | 'w': 87, 200 | 'x': 88, 201 | 'y': 89, 202 | 'z': 90, 203 | //normal-characters, numpad 204 | 'num0': 96, 205 | 'num1': 97, 206 | 'num2': 98, 207 | 'num3': 99, 208 | 'num4': 100, 209 | 'num5': 101, 210 | 'num6': 102, 211 | 'num7': 103, 212 | 'num8': 104, 213 | 'num9': 105, 214 | '*': 106, 215 | '+': 107, 216 | '-': 109, 217 | '.': 110, 218 | //normal-characters, others 219 | '/': 111, 220 | ';': 186, 221 | '=': 187, 222 | ',': 188, 223 | '-': 189, 224 | '.': 190, 225 | '/': 191, 226 | '`': 192, 227 | '[': 219, 228 | '\\': 220, 229 | ']': 221, 230 | "'": 222, 231 | 232 | //ignore these, you shouldn't use them 233 | 'left window key': 91, 234 | 'right window key': 92, 235 | 'select key': 93, 236 | 237 | 238 | 'f1': 112, 239 | 'f2': 113, 240 | 'f3': 114, 241 | 'f4': 115, 242 | 'f5': 116, 243 | 'f6': 117, 244 | 'f7': 118, 245 | 'f8': 119, 246 | 'f9': 120, 247 | 'f10': 121, 248 | 'f11': 122, 249 | 'f12': 123 250 | }, 251 | 252 | // what we can type in 253 | typeable: /input|textarea/i, 254 | 255 | // selects text on an element 256 | selectText: function( el, start, end ) { 257 | if ( el.setSelectionRange ) { 258 | if (!end ) { 259 | el.focus(); 260 | el.setSelectionRange(start, start); 261 | } else { 262 | el.selectionStart = start; 263 | el.selectionEnd = end; 264 | } 265 | } else if ( el.createTextRange ) { 266 | //el.focus(); 267 | var r = el.createTextRange(); 268 | r.moveStart('character', start); 269 | end = end || start; 270 | r.moveEnd('character', end - el.value.length); 271 | 272 | r.select(); 273 | } 274 | }, 275 | getText: function( el ) { 276 | //first check if the el has anything selected .. 277 | if ( Syn.typeable.test(el.nodeName) ) { 278 | var sel = getSelection(el); 279 | return el.value.substring(sel.start, sel.end) 280 | } 281 | //otherwise get from page 282 | var win = Syn.helpers.getWindow(el); 283 | if ( win.getSelection ) { 284 | return win.getSelection().toString(); 285 | } 286 | else if ( win.document.getSelection ) { 287 | return win.document.getSelection().toString() 288 | } 289 | else { 290 | return win.document.selection.createRange().text; 291 | } 292 | }, 293 | getSelection: getSelection 294 | }); 295 | 296 | h.extend(Syn.key, { 297 | // retrieves a description of what events for this character should look like 298 | data: function( key ) { 299 | //check if it is described directly 300 | if ( S.key.browser[key] ) { 301 | return S.key.browser[key]; 302 | } 303 | for ( var kind in S.key.kinds ) { 304 | if ( h.inArray(key, S.key.kinds[kind]) > -1 ) { 305 | return S.key.browser[kind] 306 | } 307 | } 308 | return S.key.browser.character 309 | }, 310 | 311 | //returns the special key if special 312 | isSpecial: function( keyCode ) { 313 | var specials = S.key.kinds.special; 314 | for ( var i = 0; i < specials.length; i++ ) { 315 | if ( Syn.keycodes[specials[i]] == keyCode ) { 316 | return specials[i]; 317 | } 318 | } 319 | }, 320 | /** 321 | * @hide 322 | * gets the options for a key and event type ... 323 | * @param {Object} key 324 | * @param {Object} event 325 | */ 326 | options: function( key, event ) { 327 | var keyData = Syn.key.data(key); 328 | 329 | if (!keyData[event] ) { 330 | //we shouldn't be creating this event 331 | return null; 332 | } 333 | 334 | var charCode = keyData[event][0], 335 | keyCode = keyData[event][1], 336 | result = {}; 337 | 338 | if ( keyCode == 'key' ) { 339 | result.keyCode = Syn.keycodes[key] 340 | } else if ( keyCode == 'char' ) { 341 | result.keyCode = key.charCodeAt(0) 342 | } else { 343 | result.keyCode = keyCode; 344 | } 345 | 346 | if ( charCode == 'char' ) { 347 | result.charCode = key.charCodeAt(0) 348 | } else if ( charCode !== null ) { 349 | result.charCode = charCode; 350 | } 351 | 352 | // all current browsers have which property to normalize keyCode/charCode 353 | if(result.keyCode){ 354 | result.which = result.keyCode; 355 | } else { 356 | result.which = result.charCode; 357 | } 358 | 359 | 360 | return result 361 | }, 362 | //types of event keys 363 | kinds: { 364 | special: ["shift", 'ctrl', 'alt', 'caps'], 365 | specialChars: ["\b"], 366 | navigation: ["page-up", 'page-down', 'end', 'home', 'left', 'up', 'right', 'down', 'insert', 'delete'], 367 | 'function': ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12'] 368 | }, 369 | //returns the default function 370 | // some keys have default functions 371 | // some 'kinds' of keys have default functions 372 | getDefault: function( key ) { 373 | //check if it is described directly 374 | if ( Syn.key.defaults[key] ) { 375 | return Syn.key.defaults[key]; 376 | } 377 | for ( var kind in Syn.key.kinds ) { 378 | if ( h.inArray(key, Syn.key.kinds[kind]) > -1 && Syn.key.defaults[kind] ) { 379 | return Syn.key.defaults[kind]; 380 | } 381 | } 382 | return Syn.key.defaults.character 383 | }, 384 | // default behavior when typing 385 | defaults: { 386 | 'character': function( options, scope, key, force, sel ) { 387 | if (/num\d+/.test(key) ) { 388 | key = key.match(/\d+/)[0] 389 | } 390 | 391 | if ( force || (!S.support.keyCharacters && Syn.typeable.test(this.nodeName)) ) { 392 | var current = this.value, 393 | before = current.substr(0, sel.start), 394 | after = current.substr(sel.end), 395 | character = key; 396 | 397 | this.value = before + character + after; 398 | //handle IE inserting \r\n 399 | var charLength = character == "\n" && S.support.textareaCarriage ? 2 : character.length; 400 | Syn.selectText(this, before.length + charLength) 401 | } 402 | }, 403 | 'c': function( options, scope, key, force, sel ) { 404 | if ( Syn.key.ctrlKey ) { 405 | Syn.key.clipboard = Syn.getText(this) 406 | } else { 407 | Syn.key.defaults.character.apply(this, arguments); 408 | } 409 | }, 410 | 'v': function( options, scope, key, force, sel ) { 411 | if ( Syn.key.ctrlKey ) { 412 | Syn.key.defaults.character.call(this, options, scope, Syn.key.clipboard, true, sel); 413 | } else { 414 | Syn.key.defaults.character.apply(this, arguments); 415 | } 416 | }, 417 | 'a': function( options, scope, key, force, sel ) { 418 | if ( Syn.key.ctrlKey ) { 419 | Syn.selectText(this, 0, this.value.length) 420 | } else { 421 | Syn.key.defaults.character.apply(this, arguments); 422 | } 423 | }, 424 | 'home': function() { 425 | Syn.onParents(this, function( el ) { 426 | if ( el.scrollHeight != el.clientHeight ) { 427 | el.scrollTop = 0; 428 | return false; 429 | } 430 | }) 431 | }, 432 | 'end': function() { 433 | Syn.onParents(this, function( el ) { 434 | if ( el.scrollHeight != el.clientHeight ) { 435 | el.scrollTop = el.scrollHeight; 436 | return false; 437 | } 438 | }) 439 | }, 440 | 'page-down': function() { 441 | //find the first parent we can scroll 442 | Syn.onParents(this, function( el ) { 443 | if ( el.scrollHeight != el.clientHeight ) { 444 | var ch = el.clientHeight 445 | el.scrollTop += ch; 446 | return false; 447 | } 448 | }) 449 | }, 450 | 'page-up': function() { 451 | Syn.onParents(this, function( el ) { 452 | if ( el.scrollHeight != el.clientHeight ) { 453 | var ch = el.clientHeight 454 | el.scrollTop -= ch; 455 | return false; 456 | } 457 | }) 458 | }, 459 | '\b': function( options, scope, key, force, sel ) { 460 | //this assumes we are deleting from the end 461 | if (!S.support.backspaceWorks && Syn.typeable.test(this.nodeName) ) { 462 | var current = this.value, 463 | before = current.substr(0, sel.start), 464 | after = current.substr(sel.end); 465 | 466 | if ( sel.start == sel.end && sel.start > 0 ) { 467 | //remove a character 468 | this.value = before.substring(0, before.length - 1) + after 469 | Syn.selectText(this, sel.start - 1) 470 | } else { 471 | this.value = before + after; 472 | Syn.selectText(this, sel.start) 473 | } 474 | 475 | //set back the selection 476 | } 477 | }, 478 | 'delete': function( options, scope, key, force, sel ) { 479 | if (!S.support.backspaceWorks && Syn.typeable.test(this.nodeName) ) { 480 | var current = this.value, 481 | before = current.substr(0, sel.start), 482 | after = current.substr(sel.end); 483 | if ( sel.start == sel.end && sel.start <= this.value.length - 1 ) { 484 | this.value = before + after.substring(1) 485 | } else { 486 | this.value = before + after; 487 | 488 | } 489 | Syn.selectText(this, sel.start) 490 | } 491 | }, 492 | '\r': function( options, scope, key, force, sel ) { 493 | 494 | var nodeName = this.nodeName.toLowerCase() 495 | // submit a form 496 | if (!S.support.keypressSubmits && nodeName == 'input' ) { 497 | var form = Syn.closest(this, "form"); 498 | if ( form ) { 499 | Syn.trigger("submit", {}, form); 500 | } 501 | 502 | } 503 | //newline in textarea 504 | if (!S.support.keyCharacters && nodeName == 'textarea' ) { 505 | Syn.key.defaults.character.call(this, options, scope, "\n", undefined, sel) 506 | } 507 | // 'click' hyperlinks 508 | if (!S.support.keypressOnAnchorClicks && nodeName == 'a' ) { 509 | Syn.trigger("click", {}, this); 510 | } 511 | }, 512 | // 513 | // Gets all focusable elements. If the element (this) 514 | // doesn't have a tabindex, finds the next element after. 515 | // If the element (this) has a tabindex finds the element 516 | // with the next higher tabindex OR the element with the same 517 | // tabindex after it in the document. 518 | // @return the next element 519 | // 520 | '\t': function( options, scope ) { 521 | // focusable elements 522 | var focusEls = getFocusable(this), 523 | // the current element's tabindex 524 | tabIndex = Syn.tabIndex(this), 525 | // will be set to our guess for the next element 526 | current = null, 527 | // the next index we care about 528 | currentIndex = 1000000000, 529 | // set to true once we found 'this' element 530 | found = false, 531 | i = 0, 532 | el, 533 | //the tabindex of the tabable element we are looking at 534 | elIndex, firstNotIndexed, prev; 535 | orders = []; 536 | for (; i < focusEls.length; i++ ) { 537 | orders.push([focusEls[i], i]); 538 | } 539 | var sort = function( order1, order2 ) { 540 | var el1 = order1[0], 541 | el2 = order2[0], 542 | tab1 = Syn.tabIndex(el1) || 0, 543 | tab2 = Syn.tabIndex(el2) || 0; 544 | if ( tab1 == tab2 ) { 545 | return order1[1] - order2[1] 546 | } else { 547 | if ( tab1 == 0 ) { 548 | return 1; 549 | } else if ( tab2 == 0 ) { 550 | return -1; 551 | } else { 552 | return tab1 - tab2; 553 | } 554 | } 555 | } 556 | orders.sort(sort); 557 | //now find current 558 | for ( i = 0; i < orders.length; i++ ) { 559 | el = orders[i][0]; 560 | if ( this == el ) { 561 | if (!Syn.key.shiftKey ) { 562 | current = orders[i + 1][0]; 563 | if (!current ) { 564 | current = orders[0][0] 565 | } 566 | } else { 567 | current = orders[i - 1][0]; 568 | if (!current ) { 569 | current = orders[focusEls.length - 1][0] 570 | } 571 | } 572 | 573 | } 574 | } 575 | 576 | //restart if we didn't find anything 577 | if (!current ) { 578 | current = firstNotIndexed; 579 | } 580 | current && current.focus(); 581 | return current; 582 | }, 583 | 'left': function( options, scope, key, force, sel ) { 584 | if ( Syn.typeable.test(this.nodeName) ) { 585 | if ( Syn.key.shiftKey ) { 586 | Syn.selectText(this, sel.start == 0 ? 0 : sel.start - 1, sel.end) 587 | } else { 588 | Syn.selectText(this, sel.start == 0 ? 0 : sel.start - 1) 589 | } 590 | } 591 | }, 592 | 'right': function( options, scope, key, force, sel ) { 593 | if ( Syn.typeable.test(this.nodeName) ) { 594 | if ( Syn.key.shiftKey ) { 595 | Syn.selectText(this, sel.start, sel.end + 1 > this.value.length ? this.value.length : sel.end + 1) 596 | } else { 597 | Syn.selectText(this, sel.end + 1 > this.value.length ? this.value.length : sel.end + 1) 598 | } 599 | } 600 | }, 601 | 'up': function() { 602 | if (/select/i.test(this.nodeName) ) { 603 | 604 | this.selectedIndex = this.selectedIndex ? this.selectedIndex - 1 : 0; 605 | //set this to change on blur? 606 | } 607 | }, 608 | 'down': function() { 609 | if (/select/i.test(this.nodeName) ) { 610 | Syn.changeOnBlur(this, "selectedIndex", this.selectedIndex) 611 | this.selectedIndex = this.selectedIndex + 1; 612 | //set this to change on blur? 613 | } 614 | }, 615 | 'shift': function() { 616 | return null; 617 | } 618 | } 619 | }); 620 | 621 | 622 | h.extend(Syn.create, { 623 | keydown: { 624 | setup: function( type, options, element ) { 625 | if ( h.inArray(options, Syn.key.kinds.special) != -1 ) { 626 | Syn.key[options + "Key"] = element; 627 | } 628 | } 629 | }, 630 | keypress: { 631 | setup: function( type, options, element ) { 632 | // if this browsers supports writing keys on events 633 | // but doesn't write them if the element isn't focused 634 | // focus on the element (ignored if already focused) 635 | if ( S.support.keyCharacters && !S.support.keysOnNotFocused ) { 636 | element.focus() 637 | } 638 | } 639 | }, 640 | keyup: { 641 | setup: function( type, options, element ) { 642 | if ( h.inArray(options, Syn.key.kinds.special) != -1 ) { 643 | Syn.key[options + "Key"] = null; 644 | } 645 | } 646 | }, 647 | key: { 648 | // return the options for a key event 649 | options: function( type, options, element ) { 650 | //check if options is character or has character 651 | options = typeof options != "object" ? { 652 | character: options 653 | } : options; 654 | 655 | //don't change the orignial 656 | options = h.extend({}, options) 657 | if ( options.character ) { 658 | h.extend(options, S.key.options(options.character, type)); 659 | delete options.character; 660 | } 661 | 662 | options = h.extend({ 663 | ctrlKey: !! Syn.key.ctrlKey, 664 | altKey: !! Syn.key.altKey, 665 | shiftKey: !! Syn.key.shiftKey, 666 | metaKey: !! Syn.key.metaKey 667 | }, options) 668 | 669 | return options; 670 | }, 671 | // creates a key event 672 | event: function( type, options, element ) { //Everyone Else 673 | var doc = h.getWindow(element).document || document; 674 | if ( doc.createEvent ) { 675 | var event; 676 | 677 | try { 678 | 679 | event = doc.createEvent("KeyEvents"); 680 | event.initKeyEvent(type, true, true, window, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode); 681 | } 682 | catch (e) { 683 | event = h.createBasicStandardEvent(type, options, doc); 684 | } 685 | event.synthetic = true; 686 | return event; 687 | } 688 | else { 689 | var event; 690 | try { 691 | event = h.createEventObject.apply(this, arguments); 692 | h.extend(event, options) 693 | } 694 | catch (e) {} 695 | 696 | return event; 697 | } 698 | } 699 | } 700 | }); 701 | 702 | var convert = { 703 | "enter": "\r", 704 | "backspace": "\b", 705 | "tab": "\t", 706 | "space": " " 707 | } 708 | 709 | /** 710 | * @add Syn prototype 711 | */ 712 | h.extend(Syn.init.prototype, { 713 | /** 714 | * @function key 715 | * Types a single key. The key should be 716 | * a string that matches a 717 | * [Syn.static.keycodes]. 718 | * 719 | * The following sends a carridge return 720 | * to the 'name' element. 721 | * @codestart 722 | * Syn.key('\r','name') 723 | * @codeend 724 | * For each character, a keydown, keypress, and keyup is triggered if 725 | * appropriate. 726 | * @param {String} options 727 | * @param {HTMLElement} [element] 728 | * @param {Function} [callback] 729 | * @return {HTMLElement} the element currently focused. 730 | */ 731 | _key: function( options, element, callback ) { 732 | //first check if it is a special up 733 | if (/-up$/.test(options) && h.inArray(options.replace("-up", ""), Syn.key.kinds.special) != -1 ) { 734 | Syn.trigger('keyup', options.replace("-up", ""), element) 735 | callback(true, element); 736 | return; 737 | } 738 | 739 | // keep reference to current activeElement 740 | var activeElement = h.getWindow(element).document.activeElement, 741 | caret = Syn.typeable.test(element.nodeName) && getSelection(element), 742 | key = convert[options] || options, 743 | // should we run default events 744 | runDefaults = Syn.trigger('keydown', key, element), 745 | 746 | // a function that gets the default behavior for a key 747 | getDefault = Syn.key.getDefault, 748 | 749 | // how this browser handles preventing default events 750 | prevent = Syn.key.browser.prevent, 751 | 752 | // the result of the default event 753 | defaultResult, 754 | 755 | keypressOptions = Syn.key.options(key, 'keypress'); 756 | 757 | 758 | if ( runDefaults ) { 759 | //if the browser doesn't create keypresses for this key, run default 760 | if (!keypressOptions ) { 761 | defaultResult = getDefault(key).call(element, keypressOptions, h.getWindow(element), key, undefined, caret) 762 | } else { 763 | //do keypress 764 | // check if activeElement changed b/c someone called focus in keydown 765 | if( activeElement !== h.getWindow(element).document.activeElement ) { 766 | element = h.getWindow(element).document.activeElement; 767 | } 768 | 769 | runDefaults = Syn.trigger('keypress', keypressOptions, element) 770 | if ( runDefaults ) { 771 | defaultResult = getDefault(key).call(element, keypressOptions, h.getWindow(element), key, undefined, caret) 772 | } 773 | } 774 | } else { 775 | //canceled ... possibly don't run keypress 776 | if ( keypressOptions && h.inArray('keypress', prevent.keydown) == -1 ) { 777 | // check if activeElement changed b/c someone called focus in keydown 778 | if( activeElement !== h.getWindow(element).document.activeElement ) { 779 | element = h.getWindow(element).document.activeElement; 780 | } 781 | 782 | Syn.trigger('keypress', keypressOptions, element) 783 | } 784 | } 785 | if ( defaultResult && defaultResult.nodeName ) { 786 | element = defaultResult 787 | } 788 | 789 | if ( defaultResult !== null ) { 790 | setTimeout(function() { 791 | Syn.trigger('keyup', Syn.key.options(key, 'keyup'), element) 792 | callback(runDefaults, element) 793 | }, 1) 794 | } else { 795 | callback(runDefaults, element) 796 | } 797 | 798 | 799 | //do mouseup 800 | return element; 801 | // is there a keypress? .. if not , run default 802 | // yes -> did we prevent it?, if not run ... 803 | }, 804 | /** 805 | * @function type 806 | * Types sequence of [Syn.key key actions]. Each 807 | * character is typed, one at a type. 808 | * Multi-character keys like 'left' should be 809 | * enclosed in square brackents. 810 | * 811 | * The following types 'JavaScript MVC' then deletes the space. 812 | * @codestart 813 | * Syn.type('JavaScript MVC[left][left][left]\b','name') 814 | * @codeend 815 | * 816 | * Type is able to handle (and move with) tabs (\t). 817 | * The following simulates tabing and entering values in a form and 818 | * eventually submitting the form. 819 | * @codestart 820 | * Syn.type("Justin\tMeyer\t27\tjustinbmeyer@gmail.com\r") 821 | * @codeend 822 | * @param {String} options the text to type 823 | * @param {HTMLElement} [element] an element or an id of an element 824 | * @param {Function} [callback] a function to callback 825 | */ 826 | _type: function( options, element, callback ) { 827 | //break it up into parts ... 828 | //go through each type and run 829 | var parts = options.match(/(\[[^\]]+\])|([^\[])/g), 830 | self = this, 831 | runNextPart = function( runDefaults, el ) { 832 | var part = parts.shift(); 833 | if (!part ) { 834 | callback(runDefaults, el); 835 | return; 836 | } 837 | el = el || element; 838 | if ( part.length > 1 ) { 839 | part = part.substr(1, part.length - 2) 840 | } 841 | self._key(part, el, runNextPart) 842 | } 843 | 844 | runNextPart(); 845 | 846 | } 847 | }); 848 | 849 | 850 | //do support code 851 | (function() { 852 | if (!document.body ) { 853 | setTimeout(arguments.callee, 1) 854 | return; 855 | } 856 | 857 | var div = document.createElement("div"), 858 | checkbox, submit, form, input, submitted = false, 859 | anchor, textarea, inputter; 860 | 861 | div.innerHTML = "
" + 862 | "" + 863 | "" + 864 | "" + 865 | "" + 866 | "" + 867 | "" + 868 | "" + 869 | "" + 870 | "
"; 871 | 872 | document.documentElement.appendChild(div); 873 | form = div.firstChild; 874 | checkbox = form.childNodes[0]; 875 | submit = form.childNodes[2]; 876 | anchor = form.getElementsByTagName("a")[0]; 877 | textarea = form.getElementsByTagName("textarea")[0]; 878 | inputter = form.childNodes[3]; 879 | 880 | form.onsubmit = function( ev ) { 881 | if ( ev.preventDefault ) ev.preventDefault(); 882 | S.support.keypressSubmits = true; 883 | ev.returnValue = false; 884 | return false; 885 | }; 886 | // Firefox 4 won't write key events if the element isn't focused 887 | inputter.focus(); 888 | Syn.trigger("keypress", "\r", inputter); 889 | 890 | 891 | Syn.trigger("keypress", "a", inputter); 892 | S.support.keyCharacters = inputter.value == "a"; 893 | 894 | 895 | inputter.value = "a"; 896 | Syn.trigger("keypress", "\b", inputter); 897 | S.support.backspaceWorks = inputter.value == ""; 898 | 899 | 900 | 901 | inputter.onchange = function() { 902 | S.support.focusChanges = true; 903 | } 904 | inputter.focus(); 905 | Syn.trigger("keypress", "a", inputter); 906 | form.childNodes[5].focus(); // this will throw a change event 907 | Syn.trigger("keypress", "b", inputter); 908 | S.support.keysOnNotFocused = inputter.value == "ab"; 909 | 910 | //test keypress \r on anchor submits 911 | S.bind(anchor, "click", function( ev ) { 912 | if ( ev.preventDefault ) ev.preventDefault(); 913 | S.support.keypressOnAnchorClicks = true; 914 | ev.returnValue = false; 915 | return false; 916 | }) 917 | Syn.trigger("keypress", "\r", anchor); 918 | 919 | S.support.textareaCarriage = textarea.value.length == 4; 920 | 921 | document.documentElement.removeChild(div); 922 | 923 | S.support.ready++; 924 | })(); 925 | })() 926 | -------------------------------------------------------------------------------- /tests/benchmark/bean_05.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * bean.js - copyright Jacob Thornton 2011 3 | * https://github.com/fat/bean 4 | * MIT License 5 | * special thanks to: 6 | * dean edwards: http://dean.edwards.name/ 7 | * dperini: https://github.com/dperini/nwevents 8 | * the entire mootools team: github.com/mootools/mootools-core 9 | */ 10 | !(function (name, context, definition) { 11 | if (typeof module != 'undefined') module.exports = definition(name, context); 12 | else if (typeof define == 'function' && typeof define.amd == 'object') define(definition); 13 | else context[name] = definition(name, context); 14 | }('bean', this, function (name, context) { 15 | var win = window 16 | , old = context[name] 17 | , namespaceRegex = /[^\.]*(?=\..*)\.|.*/ 18 | , nameRegex = /\..*/ 19 | , addEvent = 'addEventListener' 20 | , attachEvent = 'attachEvent' 21 | , removeEvent = 'removeEventListener' 22 | , detachEvent = 'detachEvent' 23 | , ownerDocument = 'ownerDocument' 24 | , targetS = 'target' 25 | , qSA = 'querySelectorAll' 26 | , doc = document || {} 27 | , root = doc.documentElement || {} 28 | , W3C_MODEL = root[addEvent] 29 | , eventSupport = W3C_MODEL ? addEvent : attachEvent 30 | , slice = Array.prototype.slice 31 | , ONE = {} // singleton for quick matching making add() do one() 32 | , standardNativeEvents = 33 | 'click dblclick mouseup mousedown contextmenu ' + // mouse buttons 34 | 'mousewheel mousemultiwheel DOMMouseScroll ' + // mouse wheel 35 | 'mouseover mouseout mousemove selectstart selectend ' + // mouse movement 36 | 'keydown keypress keyup ' + // keyboard 37 | 'orientationchange ' + // mobile 38 | 'focus blur change reset select submit ' + // form elements 39 | 'load unload beforeunload resize move DOMContentLoaded ' + // window 40 | 'readystatechange message ' + // window 41 | 'error abort scroll ' // misc 42 | // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event 43 | // that doesn't actually exist, so make sure we only do these on newer browsers 44 | , w3cNativeEvents = 45 | 'show ' + // mouse buttons 46 | 'input invalid ' + // form elements 47 | 'touchstart touchmove touchend touchcancel ' + // touch 48 | 'gesturestart gesturechange gestureend ' + // gesture 49 | 'textinput' + // TextEvent 50 | 'readystatechange pageshow pagehide popstate ' + // window 51 | 'hashchange offline online ' + // window 52 | 'afterprint beforeprint ' + // printing 53 | 'dragstart dragenter dragover dragleave drag drop dragend ' + // dnd 54 | 'loadstart progress suspend emptied stalled loadmetadata ' + // media 55 | 'loadeddata canplay canplaythrough playing waiting seeking ' + // media 56 | 'seeked ended durationchange timeupdate play pause ratechange ' + // media 57 | 'volumechange cuechange ' + // media 58 | 'checking noupdate downloading cached updateready obsolete ' // appcache 59 | , str2arr = function (s, d) { return s.split(d || ' ') } 60 | , isString = function (o) { return typeof o == 'string' } 61 | , isFunction = function (o) { return typeof o == 'function' } 62 | 63 | , nativeEvents = (function (hash, events, i) { 64 | for (i = 0; i < events.length; i++) events[i] && (hash[events[i]] = 1) 65 | return hash 66 | }({}, str2arr(standardNativeEvents + (W3C_MODEL ? w3cNativeEvents : '')))) 67 | 68 | , customEvents = (function () { 69 | var cdp = 'compareDocumentPosition' 70 | , isAncestor = cdp in root 71 | ? function (element, container) { 72 | return container[cdp] && (container[cdp](element) & 16) === 16 73 | } 74 | : 'contains' in root 75 | ? function (element, container) { 76 | container = container.nodeType === 9 || container === window ? root : container 77 | return container !== element && container.contains(element) 78 | } 79 | : function (element, container) { 80 | while (element = element.parentNode) if (element === container) return 1 81 | return 0 82 | } 83 | , check = function (event) { 84 | var related = event.relatedTarget 85 | return !related 86 | ? related == null 87 | : (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString()) 88 | && !isAncestor(related, this)) 89 | } 90 | 91 | return { 92 | mouseenter: { base: 'mouseover', condition: check } 93 | , mouseleave: { base: 'mouseout', condition: check } 94 | , mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } 95 | } 96 | }()) 97 | 98 | , Event = (function () { 99 | var commonProps = str2arr('altKey attrChange attrName bubbles cancelable ctrlKey currentTarget ' + 100 | 'detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey ' + 101 | 'srcElement target timeStamp type view which') 102 | , mouseProps = commonProps.concat(str2arr('button buttons clientX clientY dataTransfer ' + 103 | 'fromElement offsetX offsetY pageX pageY screenX screenY toElement')) 104 | , mouseWheelProps = mouseProps.concat(str2arr('wheelDelta wheelDeltaX wheelDeltaY wheelDeltaZ ' + 105 | 'axis')) // 'axis' is FF specific 106 | , keyProps = commonProps.concat(str2arr('char charCode key keyCode keyIdentifier ' + 107 | 'keyLocation location')) 108 | , textProps = commonProps.concat(str2arr('data')) 109 | , touchProps = commonProps.concat(str2arr('touches targetTouches changedTouches scale rotation')) 110 | , messageProps = commonProps.concat(str2arr('data origin source')) 111 | , stateProps = commonProps.concat(str2arr('state')) 112 | , overOutRegex = /over|out/ 113 | // some event types need special handling and some need special properties, do that all here 114 | , typeFixers = [ 115 | { // key events 116 | reg: /key/i 117 | , fix: function (event, newEvent) { 118 | newEvent.keyCode = event.which || event.keyCode 119 | return keyProps 120 | } 121 | } 122 | , { // mouse events 123 | reg: /click|mouse(?!(.*wheel|scroll))|menu|drag|drop/i 124 | , fix: function (event, newEvent, type) { 125 | newEvent.rightClick = event.which === 3 || event.button === 2 126 | newEvent.pos = { x: 0, y: 0 } 127 | if (event.pageX || event.pageY) { 128 | newEvent.clientX = event.pageX 129 | newEvent.clientY = event.pageY 130 | } else if (event.clientX || event.clientY) { 131 | newEvent.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft 132 | newEvent.clientY = event.clientY + doc.body.scrollTop + root.scrollTop 133 | } 134 | if (overOutRegex.test(type)) { 135 | newEvent.relatedTarget = event.relatedTarget 136 | || event[(type == 'mouseover' ? 'from' : 'to') + 'Element'] 137 | } 138 | return mouseProps 139 | } 140 | } 141 | , { // mouse wheel events 142 | reg: /mouse.*(wheel|scroll)/i 143 | , fix: function () { return mouseWheelProps } 144 | } 145 | , { // TextEvent 146 | reg: /^text/i 147 | , fix: function () { return textProps } 148 | } 149 | , { // touch and gesture events 150 | reg: /^touch|^gesture/i 151 | , fix: function () { return touchProps } 152 | } 153 | , { // message events 154 | reg: /^message$/i 155 | , fix: function () { return messageProps } 156 | } 157 | , { // popstate events 158 | reg: /^popstate$/i 159 | , fix: function () { return stateProps } 160 | } 161 | , { // everything else 162 | reg: /.*/ 163 | , fix: function () { return commonProps } 164 | } 165 | ] 166 | , typeFixerMap = {} // used to map event types to fixer functions (above), a basic cache mechanism 167 | 168 | , Event = function (event, isNative) { 169 | this[originalEvent] = event 170 | this.isNative = isNative 171 | this.isBean = true 172 | 173 | if (!event) return 174 | 175 | var type = event.type 176 | , target = event[targetS] || event.srcElement 177 | , i, l, p, props, fixer 178 | 179 | this[targetS] = target && target.nodeType === 3 ? target.parentNode : target 180 | 181 | if (isNative) { // we only need basic augmentation on custom events, the rest expensive & pointless 182 | fixer = typeFixerMap[type] 183 | if (!fixer) { // haven't encountered this event type before, map a fixer function for it 184 | for (i = 0, l = typeFixers.length; i < l; i++) { 185 | if (typeFixers[i].reg.test(type)) { // guaranteed to match at least one, last is .* 186 | typeFixerMap[type] = fixer = typeFixers[i].fix 187 | break 188 | } 189 | } 190 | } 191 | 192 | props = fixer(event, this, type) 193 | for (i = props.length; i--;) { 194 | if (!((p = props[i]) in this) && p in event) this[p] = event[p] 195 | } 196 | } 197 | } 198 | 199 | , preventDefault = 'preventDefault' 200 | , stopPropagation = 'stopPropagation' 201 | , stopImmediatePropagation = 'stopImmediatePropagation' 202 | , originalEvent = 'originalEvent' 203 | 204 | Event.prototype[preventDefault] = function () { 205 | if (this[originalEvent][preventDefault]) this[originalEvent][preventDefault]() 206 | else this[originalEvent].returnValue = false 207 | } 208 | Event.prototype[stopPropagation] = function () { 209 | if (this[originalEvent][stopPropagation]) this[originalEvent][stopPropagation]() 210 | else this[originalEvent].cancelBubble = true 211 | } 212 | Event.prototype[stopImmediatePropagation] = function () { 213 | if (this[originalEvent][stopImmediatePropagation]) this[originalEvent][stopImmediatePropagation]() 214 | } 215 | Event.prototype.stop = function () { 216 | this[preventDefault]() 217 | this[stopPropagation]() 218 | this.stopped = true 219 | } 220 | 221 | return Event 222 | }()) 223 | 224 | // if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both 225 | , targetElement = function (element, isNative) { 226 | return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element 227 | } 228 | 229 | // we use one of these per listener, of any type 230 | , RegEntry = (function () { 231 | function entry(element, type, handler, original, namespaces, args) { 232 | var customType = customEvents[type] 233 | , isNative 234 | 235 | if (type == 'unload') { 236 | // self clean-up 237 | handler = once(removeListener, element, type, handler, original) 238 | } 239 | 240 | if (customType) { 241 | if (customType.condition) { 242 | handler = customHandler(element, handler, type, customType.condition, args, true) 243 | } 244 | type = customType.base || type 245 | } 246 | 247 | this.isNative = isNative = nativeEvents[type] && !!element[eventSupport] 248 | this.customType = !W3C_MODEL && !isNative && type 249 | this.element = element 250 | this.type = type 251 | this.original = original 252 | this.namespaces = namespaces 253 | this.eventType = W3C_MODEL || isNative ? type : 'propertychange' 254 | this[targetS] = targetElement(element, isNative) 255 | this[eventSupport] = !!this[targetS][eventSupport] 256 | 257 | this.handler = isNative 258 | ? nativeHandler(element, handler, args) 259 | : customHandler(element, handler, type, false, args, false) 260 | } 261 | 262 | entry.prototype = { 263 | // given a list of namespaces, is our entry in any of them? 264 | inNamespaces: function (checkNamespaces) { 265 | var i, j, c = 0 266 | if (!checkNamespaces) return true 267 | if (!this.namespaces) return false 268 | for (i = checkNamespaces.length; i--;) { 269 | for (j = this.namespaces.length; j--;) { 270 | if (checkNamespaces[i] == this.namespaces[j]) c++ 271 | } 272 | } 273 | return checkNamespaces.length === c 274 | } 275 | 276 | // match by element, original fn (opt), handler fn (opt) 277 | , matches: function (checkElement, checkOriginal, checkHandler) { 278 | return this.element === checkElement && 279 | (!checkOriginal || this.original === checkOriginal) && 280 | (!checkHandler || this.handler === checkHandler) 281 | } 282 | } 283 | 284 | return entry 285 | }()) 286 | 287 | , registry = (function () { 288 | // our map stores arrays by event type, just because it's better than storing 289 | // everything in a single array. uses '$' as a prefix for the keys for safety 290 | var map = {} 291 | 292 | // generic functional search of our registry for matching listeners, 293 | // `fn` returns false to break out of the loop 294 | , forAll = function (element, type, original, handler, fn) { 295 | if (!type || type == '*') { 296 | // search the whole registry 297 | for (var t in map) { 298 | if (t.charAt(0) == '$') { 299 | forAll(element, t.substr(1), original, handler, fn) 300 | } 301 | } 302 | } else { 303 | var i = 0, l, list = map['$' + type], all = element == '*' 304 | if (!list) return 305 | for (l = list.length; i < l; i++) { 306 | if ((all || list[i].matches(element, original, handler)) && !fn(list[i], list, i, type)) return 307 | } 308 | } 309 | } 310 | 311 | , has = function (element, type, original) { 312 | // we're not using forAll here simply because it's a bit slower and this 313 | // needs to be fast 314 | var i, list = map['$' + type] 315 | if (list) { 316 | for (i = list.length; i--;) { 317 | if (list[i].matches(element, original, null)) return true 318 | } 319 | } 320 | return false 321 | } 322 | 323 | , get = function (element, type, original) { 324 | var entries = [] 325 | forAll(element, type, original, null, function (entry) { 326 | return entries.push(entry) 327 | }) 328 | return entries 329 | } 330 | 331 | , put = function (entry) { 332 | (map['$' + entry.type] || (map['$' + entry.type] = [])).push(entry) 333 | return entry 334 | } 335 | 336 | , del = function (entry) { 337 | forAll(entry.element, entry.type, null, entry.handler, function (entry, list, i) { 338 | list.splice(i, 1) 339 | if (list.length === 0) delete map['$' + entry.type] 340 | return false 341 | }) 342 | } 343 | 344 | // dump all entries, used for onunload 345 | , entries = function () { 346 | var t, entries = [] 347 | for (t in map) { 348 | if (t.charAt(0) == '$') entries = entries.concat(map[t]) 349 | } 350 | return entries 351 | } 352 | 353 | return { has: has, get: get, put: put, del: del, entries: entries } 354 | }()) 355 | 356 | , selectorEngine 357 | , setSelectorEngine = function (e) { 358 | if (!arguments.length) { 359 | selectorEngine = doc[qSA] 360 | ? function (s, r) { 361 | return r[qSA](s) 362 | } 363 | : function () { 364 | throw new Error('Bean: No selector engine installed') // eeek 365 | } 366 | } else { 367 | selectorEngine = e 368 | } 369 | } 370 | 371 | // add and remove listeners to DOM elements 372 | , listener = W3C_MODEL ? function (element, type, fn, add) { 373 | element[add ? addEvent : removeEvent](type, fn, false) 374 | } : function (element, type, fn, add, custom) { 375 | if (custom && add && element['_on' + custom] == null) element['_on' + custom] = 0 376 | element[add ? attachEvent : detachEvent]('on' + type, fn) 377 | } 378 | 379 | , nativeHandler = function (element, fn, args) { 380 | var beanDel = fn.__beanDel 381 | , handler = function (event) { 382 | event = new Event(event || ((this[ownerDocument] || this.document || this).parentWindow || win).event, true) 383 | if (beanDel) event.currentTarget = beanDel.ft(event[targetS], element) // delegated event, fix the fix 384 | return fn.apply(element, [event].concat(args)) 385 | } 386 | handler.__beanDel = beanDel 387 | return handler 388 | } 389 | 390 | , customHandler = function (element, fn, type, condition, args, isNative) { 391 | var beanDel = fn.__beanDel 392 | , handler = function (event) { 393 | var target = beanDel ? beanDel.ft(event[targetS], element) : this // deleated event 394 | , handle = condition 395 | ? condition.apply(target, arguments) 396 | : W3C_MODEL ? true : event && event.propertyName == '_on' + type || !event 397 | if (handle) { 398 | if (event) { 399 | event = new Event(event || ((this[ownerDocument] || this.document || this).parentWindow || win).event, isNative) 400 | event.currentTarget = target 401 | } 402 | fn.apply(element, event && (!args || args.length === 0) ? arguments : slice.call(arguments, event ? 0 : 1).concat(args)) 403 | } 404 | } 405 | 406 | handler.__beanDel = beanDel 407 | return handler 408 | } 409 | 410 | , once = function (rm, element, type, fn, originalFn) { 411 | // wrap the handler in a handler that does a remove as well 412 | return function () { 413 | rm(element, type, originalFn) 414 | fn.apply(this, arguments) 415 | } 416 | } 417 | 418 | , removeListener = function (element, orgType, handler, namespaces) { 419 | var type = (orgType && orgType.replace(nameRegex, '')) 420 | , handlers = registry.get(element, type, handler) 421 | , i, l, entry 422 | 423 | for (i = 0, l = handlers.length; i < l; i++) { 424 | if (handlers[i].inNamespaces(namespaces)) { 425 | if ((entry = handlers[i])[eventSupport]) { 426 | listener(entry[targetS], entry.eventType, entry.handler, false, entry.type) 427 | } 428 | // TODO: this is problematic, we have a registry.get() and registry.del() that 429 | // both do registry searches so we waste cycles doing this. Needs to be rolled into 430 | // a single registry.forAll(fn) that removes while finding, but the catch is that 431 | // we'll be splicing the arrays that we're iterating over. Needs extra tests to 432 | // make sure we don't screw it up. @rvagg 433 | registry.del(entry) 434 | } 435 | } 436 | } 437 | 438 | , delegate = function (selector, fn) { 439 | //TODO: findTarget (therefore $) is called twice, once for match and once for 440 | // setting e.currentTarget, fix this so it's only needed once 441 | var findTarget = function (target, root) { 442 | var i, array = isString(selector) ? selectorEngine(selector, root) : selector 443 | for (; target && target !== root; target = target.parentNode) { 444 | for (i = array.length; i--;) { 445 | if (array[i] === target) return target 446 | } 447 | } 448 | } 449 | , handler = function (e) { 450 | var match = findTarget(e[targetS], this) 451 | if (match) fn.apply(match, arguments) 452 | } 453 | 454 | handler.__beanDel = { 455 | ft : findTarget // attach it here for customEvents to use too 456 | , selector : selector 457 | } 458 | return handler 459 | } 460 | 461 | , remove = function (element, typeSpec, fn) { 462 | var rm = removeListener 463 | , isTypeStr = isString(typeSpec) 464 | , k, type, namespaces, i 465 | 466 | if (isTypeStr && typeSpec.indexOf(' ') > 0) { 467 | // remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3') 468 | typeSpec = str2arr(typeSpec) 469 | for (i = typeSpec.length; i--;) 470 | remove(element, typeSpec[i], fn) 471 | return element 472 | } 473 | 474 | type = isTypeStr && typeSpec.replace(nameRegex, '') 475 | if (type && customEvents[type]) type = customEvents[type].type 476 | 477 | if (!typeSpec || isTypeStr) { 478 | // remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3) 479 | if (namespaces = isTypeStr && typeSpec.replace(namespaceRegex, '')) namespaces = str2arr(namespaces, '.') 480 | rm(element, type, fn, namespaces) 481 | } else if (isFunction(typeSpec)) { 482 | // remove(el, fn) 483 | rm(element, null, typeSpec) 484 | } else { 485 | // remove(el, { t1: fn1, t2, fn2 }) 486 | for (k in typeSpec) { 487 | if (typeSpec.hasOwnProperty(k)) remove(element, k, typeSpec[k]) 488 | } 489 | } 490 | 491 | return element 492 | } 493 | 494 | , on = function(element, events, selector, fn) { 495 | var originalFn, type, types, i, args, entry 496 | 497 | if (selector === undefined && typeof events == 'object') { 498 | //TODO: this can't handle delegated events 499 | for (type in events) { 500 | if (events.hasOwnProperty(type)) { 501 | on.call(this, element, type, events[type]) 502 | } 503 | } 504 | return 505 | } 506 | 507 | if (!isFunction(selector)) { 508 | // delegated event 509 | originalFn = fn 510 | args = slice.call(arguments, 4) 511 | fn = delegate(selector, originalFn, selectorEngine) 512 | } else { 513 | args = slice.call(arguments, 3) 514 | fn = originalFn = selector 515 | } 516 | 517 | types = str2arr(events) 518 | 519 | // special case for one() 520 | if (this === ONE) { 521 | fn = once(remove, element, events, fn, originalFn) 522 | } 523 | 524 | for (i = types.length; i--;) { 525 | entry = registry.put(new RegEntry( 526 | element 527 | , types[i].replace(nameRegex, '') 528 | , fn 529 | , originalFn 530 | , str2arr(types[i].replace(namespaceRegex, ''), '.') // namespaces 531 | , args 532 | )) 533 | if (entry[eventSupport]) { 534 | listener(entry[targetS], entry.eventType, entry.handler, true, entry.customType) 535 | } 536 | } 537 | 538 | return element 539 | } 540 | 541 | , add = function (element, events, fn, delfn) { 542 | return on.apply( 543 | this 544 | , !isString(fn) 545 | ? slice.call(arguments) 546 | : [ element, fn, events, delfn ].concat(arguments.length > 3 ? slice.call(arguments, 5) : []) 547 | ) 548 | } 549 | 550 | , one = function () { 551 | return add.apply(ONE, arguments) 552 | } 553 | 554 | , fireListener = W3C_MODEL ? function (isNative, type, element) { 555 | var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents') 556 | evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1) 557 | element.dispatchEvent(evt) 558 | } : function (isNative, type, element) { 559 | element = targetElement(element, isNative) 560 | // if not-native then we're using onpropertychange so we just increment a custom property 561 | isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++ 562 | } 563 | 564 | , fire = function (element, type, args) { 565 | var types = str2arr(type) 566 | , i, j, l, names, handlers 567 | 568 | for (i = types.length; i--;) { 569 | type = types[i].replace(nameRegex, '') 570 | if (names = types[i].replace(namespaceRegex, '')) names = str2arr(names, '.') 571 | if (!names && !args && element[eventSupport]) { 572 | fireListener(nativeEvents[type], type, element) 573 | } else { 574 | // non-native event, either because of a namespace, arguments or a non DOM element 575 | // iterate over all listeners and manually 'fire' 576 | handlers = registry.get(element, type) 577 | args = [false].concat(args) 578 | for (j = 0, l = handlers.length; j < l; j++) { 579 | if (handlers[j].inNamespaces(names)) handlers[j].handler.apply(element, args) 580 | } 581 | } 582 | } 583 | return element 584 | } 585 | 586 | , clone = function (element, from, type) { 587 | var handlers = registry.get(from, type) 588 | , i, l, args, beanDel 589 | 590 | for (i = 0, l = handlers.length;i < l; i++) { 591 | if (handlers[i].original) { 592 | beanDel = handlers[i].handler.__beanDel 593 | if (beanDel) { 594 | args = [ element, beanDel.selector, handlers[i].type, handlers[i].original ] 595 | } else 596 | args = [ element, handlers[i].type, handlers[i].original ] 597 | add.apply(null, args) 598 | } 599 | } 600 | return element 601 | } 602 | 603 | , bean = { 604 | add : add 605 | , on : on 606 | , one : one 607 | , remove : remove 608 | , clone : clone 609 | , fire : fire 610 | , setSelectorEngine : setSelectorEngine 611 | , noConflict : function () { 612 | context[name] = old 613 | return this 614 | } 615 | } 616 | 617 | if (win[attachEvent]) { 618 | // for IE, clean up on unload to avoid leaks 619 | var cleanup = function () { 620 | var i, entries = registry.entries() 621 | for (i in entries) { 622 | if (entries[i].type && entries[i].type !== 'unload') remove(entries[i].element, entries[i].type) 623 | } 624 | win[detachEvent]('onunload', cleanup) 625 | win.CollectGarbage && win.CollectGarbage() 626 | } 627 | win[attachEvent]('onunload', cleanup) 628 | } 629 | 630 | setSelectorEngine() 631 | 632 | return bean 633 | })); --------------------------------------------------------------------------------