├── .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 |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 = "";
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 | }));
--------------------------------------------------------------------------------