]> git.wh0rd.org - tt-rss.git/blob - lib/dojo/on.js.uncompressed.js
modify dojo rebuild script to remove uncompressed files
[tt-rss.git] / lib / dojo / on.js.uncompressed.js
1 define("dojo/on", ["./has!dom-addeventlistener?:./aspect", "./_base/kernel", "./has"], function(aspect, dojo, has){
2
3 "use strict";
4 if( 1 ){ // check to make sure we are in a browser, this module should work anywhere
5 var major = window.ScriptEngineMajorVersion;
6 has.add("jscript", major && (major() + ScriptEngineMinorVersion() / 10));
7 has.add("event-orientationchange", has("touch") && !has("android")); // TODO: how do we detect this?
8 has.add("event-stopimmediatepropagation", window.Event && !!window.Event.prototype && !!window.Event.prototype.stopImmediatePropagation);
9 }
10 var on = function(target, type, listener, dontFix){
11 // summary:
12 // A function that provides core event listening functionality. With this function
13 // you can provide a target, event type, and listener to be notified of
14 // future matching events that are fired.
15 // target: Element|Object
16 // This is the target object or DOM element that to receive events from
17 // type: String|Function
18 // This is the name of the event to listen for or an extension event type.
19 // listener: Function
20 // This is the function that should be called when the event fires.
21 // returns: Object
22 // An object with a remove() method that can be used to stop listening for this
23 // event.
24 // description:
25 // To listen for "click" events on a button node, we can do:
26 // | define(["dojo/on"], function(listen){
27 // | on(button, "click", clickHandler);
28 // | ...
29 // Evented JavaScript objects can also have their own events.
30 // | var obj = new Evented;
31 // | on(obj, "foo", fooHandler);
32 // And then we could publish a "foo" event:
33 // | on.emit(obj, "foo", {key: "value"});
34 // We can use extension events as well. For example, you could listen for a tap gesture:
35 // | define(["dojo/on", "dojo/gesture/tap", function(listen, tap){
36 // | on(button, tap, tapHandler);
37 // | ...
38 // which would trigger fooHandler. Note that for a simple object this is equivalent to calling:
39 // | obj.onfoo({key:"value"});
40 // If you use on.emit on a DOM node, it will use native event dispatching when possible.
41
42 if(typeof target.on == "function" && typeof type != "function"){
43 // delegate to the target's on() method, so it can handle it's own listening if it wants
44 return target.on(type, listener);
45 }
46 // delegate to main listener code
47 return on.parse(target, type, listener, addListener, dontFix, this);
48 };
49 on.pausable = function(target, type, listener, dontFix){
50 // summary:
51 // This function acts the same as on(), but with pausable functionality. The
52 // returned signal object has pause() and resume() functions. Calling the
53 // pause() method will cause the listener to not be called for future events. Calling the
54 // resume() method will cause the listener to again be called for future events.
55 var paused;
56 var signal = on(target, type, function(){
57 if(!paused){
58 return listener.apply(this, arguments);
59 }
60 }, dontFix);
61 signal.pause = function(){
62 paused = true;
63 };
64 signal.resume = function(){
65 paused = false;
66 };
67 return signal;
68 };
69 on.once = function(target, type, listener, dontFix){
70 // summary:
71 // This function acts the same as on(), but will only call the listener once. The
72 // listener will be called for the first
73 // event that takes place and then listener will automatically be removed.
74 var signal = on(target, type, function(){
75 // remove this listener
76 signal.remove();
77 // proceed to call the listener
78 return listener.apply(this, arguments);
79 });
80 return signal;
81 };
82 on.parse = function(target, type, listener, addListener, dontFix, matchesTarget){
83 if(type.call){
84 // event handler function
85 // on(node, touch.press, touchListener);
86 return type.call(matchesTarget, target, listener);
87 }
88
89 if(type.indexOf(",") > -1){
90 // we allow comma delimited event names, so you can register for multiple events at once
91 var events = type.split(/\s*,\s*/);
92 var handles = [];
93 var i = 0;
94 var eventName;
95 while(eventName = events[i++]){
96 handles.push(addListener(target, eventName, listener, dontFix, matchesTarget));
97 }
98 handles.remove = function(){
99 for(var i = 0; i < handles.length; i++){
100 handles[i].remove();
101 }
102 };
103 return handles;
104 }
105 return addListener(target, type, listener, dontFix, matchesTarget);
106 };
107 var touchEvents = /^touch/;
108 function addListener(target, type, listener, dontFix, matchesTarget){
109 // event delegation:
110 var selector = type.match(/(.*):(.*)/);
111 // if we have a selector:event, the last one is interpreted as an event, and we use event delegation
112 if(selector){
113 type = selector[2];
114 selector = selector[1];
115 // create the extension event for selectors and directly call it
116 return on.selector(selector, type).call(matchesTarget, target, listener);
117 }
118 // test to see if it a touch event right now, so we don't have to do it every time it fires
119 if(has("touch")){
120 if(touchEvents.test(type)){
121 // touch event, fix it
122 listener = fixTouchListener(listener);
123 }
124 if(!has("event-orientationchange") && (type == "orientationchange")){
125 //"orientationchange" not supported <= Android 2.1,
126 //but works through "resize" on window
127 type = "resize";
128 target = window;
129 listener = fixTouchListener(listener);
130 }
131 }
132 if(addStopImmediate){
133 // add stopImmediatePropagation if it doesn't exist
134 listener = addStopImmediate(listener);
135 }
136 // normal path, the target is |this|
137 if(target.addEventListener){
138 // the target has addEventListener, which should be used if available (might or might not be a node, non-nodes can implement this method as well)
139 // check for capture conversions
140 var capture = type in captures,
141 adjustedType = capture ? captures[type] : type;
142 target.addEventListener(adjustedType, listener, capture);
143 // create and return the signal
144 return {
145 remove: function(){
146 target.removeEventListener(adjustedType, listener, capture);
147 }
148 };
149 }
150 type = "on" + type;
151 if(fixAttach && target.attachEvent){
152 return fixAttach(target, type, listener);
153 }
154 throw new Error("Target must be an event emitter");
155 }
156
157 on.selector = function(selector, eventType, children){
158 // summary:
159 // Creates a new extension event with event delegation. This is based on
160 // the provided event type (can be extension event) that
161 // only calls the listener when the CSS selector matches the target of the event.
162 //
163 // The application must require() an appropriate level of dojo/query to handle the selector.
164 // selector:
165 // The CSS selector to use for filter events and determine the |this| of the event listener.
166 // eventType:
167 // The event to listen for
168 // children:
169 // Indicates if children elements of the selector should be allowed. This defaults to
170 // true
171 // example:
172 // | require(["dojo/on", "dojo/mouse", "dojo/query!css2"], function(listen, mouse){
173 // | on(node, on.selector(".my-class", mouse.enter), handlerForMyHover);
174 return function(target, listener){
175 // if the selector is function, use it to select the node, otherwise use the matches method
176 var matchesTarget = typeof selector == "function" ? {matches: selector} : this,
177 bubble = eventType.bubble;
178 function select(eventTarget){
179 // see if we have a valid matchesTarget or default to dojo.query
180 matchesTarget = matchesTarget && matchesTarget.matches ? matchesTarget : dojo.query;
181 // there is a selector, so make sure it matches
182 while(!matchesTarget.matches(eventTarget, selector, target)){
183 if(eventTarget == target || children === false || !(eventTarget = eventTarget.parentNode) || eventTarget.nodeType != 1){ // intentional assignment
184 return;
185 }
186 }
187 return eventTarget;
188 }
189 if(bubble){
190 // the event type doesn't naturally bubble, but has a bubbling form, use that, and give it the selector so it can perform the select itself
191 return on(target, bubble(select), listener);
192 }
193 // standard event delegation
194 return on(target, eventType, function(event){
195 // call select to see if we match
196 var eventTarget = select(event.target);
197 // if it matches we call the listener
198 return eventTarget && listener.call(eventTarget, event);
199 });
200 };
201 };
202
203 function syntheticPreventDefault(){
204 this.cancelable = false;
205 }
206 function syntheticStopPropagation(){
207 this.bubbles = false;
208 }
209 var slice = [].slice,
210 syntheticDispatch = on.emit = function(target, type, event){
211 // summary:
212 // Fires an event on the target object.
213 // target:
214 // The target object to fire the event on. This can be a DOM element or a plain
215 // JS object. If the target is a DOM element, native event emiting mechanisms
216 // are used when possible.
217 // type:
218 // The event type name. You can emulate standard native events like "click" and
219 // "mouseover" or create custom events like "open" or "finish".
220 // event:
221 // An object that provides the properties for the event. See https://developer.mozilla.org/en/DOM/event.initEvent
222 // for some of the properties. These properties are copied to the event object.
223 // Of particular importance are the cancelable and bubbles properties. The
224 // cancelable property indicates whether or not the event has a default action
225 // that can be cancelled. The event is cancelled by calling preventDefault() on
226 // the event object. The bubbles property indicates whether or not the
227 // event will bubble up the DOM tree. If bubbles is true, the event will be called
228 // on the target and then each parent successively until the top of the tree
229 // is reached or stopPropagation() is called. Both bubbles and cancelable
230 // default to false.
231 // returns:
232 // If the event is cancelable and the event is not cancelled,
233 // emit will return true. If the event is cancelable and the event is cancelled,
234 // emit will return false.
235 // details:
236 // Note that this is designed to emit events for listeners registered through
237 // dojo/on. It should actually work with any event listener except those
238 // added through IE's attachEvent (IE8 and below's non-W3C event emiting
239 // doesn't support custom event types). It should work with all events registered
240 // through dojo/on. Also note that the emit method does do any default
241 // action, it only returns a value to indicate if the default action should take
242 // place. For example, emiting a keypress event would not cause a character
243 // to appear in a textbox.
244 // example:
245 // To fire our own click event
246 // | on.emit(dojo.byId("button"), "click", {
247 // | cancelable: true,
248 // | bubbles: true,
249 // | screenX: 33,
250 // | screenY: 44
251 // | });
252 // We can also fire our own custom events:
253 // | on.emit(dojo.byId("slider"), "slide", {
254 // | cancelable: true,
255 // | bubbles: true,
256 // | direction: "left-to-right"
257 // | });
258 var args = slice.call(arguments, 2);
259 var method = "on" + type;
260 if("parentNode" in target){
261 // node (or node-like), create event controller methods
262 var newEvent = args[0] = {};
263 for(var i in event){
264 newEvent[i] = event[i];
265 }
266 newEvent.preventDefault = syntheticPreventDefault;
267 newEvent.stopPropagation = syntheticStopPropagation;
268 newEvent.target = target;
269 newEvent.type = type;
270 event = newEvent;
271 }
272 do{
273 // call any node which has a handler (note that ideally we would try/catch to simulate normal event propagation but that causes too much pain for debugging)
274 target[method] && target[method].apply(target, args);
275 // and then continue up the parent node chain if it is still bubbling (if started as bubbles and stopPropagation hasn't been called)
276 }while(event && event.bubbles && (target = target.parentNode));
277 return event && event.cancelable && event; // if it is still true (was cancelable and was cancelled), return the event to indicate default action should happen
278 };
279 var captures = {};
280 if(!has("event-stopimmediatepropagation")){
281 var stopImmediatePropagation =function(){
282 this.immediatelyStopped = true;
283 this.modified = true; // mark it as modified so the event will be cached in IE
284 };
285 var addStopImmediate = function(listener){
286 return function(event){
287 if(!event.immediatelyStopped){// check to make sure it hasn't been stopped immediately
288 event.stopImmediatePropagation = stopImmediatePropagation;
289 return listener.apply(this, arguments);
290 }
291 };
292 }
293 }
294 if(has("dom-addeventlistener")){
295 // normalize focusin and focusout
296 captures = {
297 focusin: "focus",
298 focusout: "blur"
299 };
300
301 // emiter that works with native event handling
302 on.emit = function(target, type, event){
303 if(target.dispatchEvent && document.createEvent){
304 // use the native event emiting mechanism if it is available on the target object
305 // create a generic event
306 // we could create branch into the different types of event constructors, but
307 // that would be a lot of extra code, with little benefit that I can see, seems
308 // best to use the generic constructor and copy properties over, making it
309 // easy to have events look like the ones created with specific initializers
310 var nativeEvent = target.ownerDocument.createEvent("HTMLEvents");
311 nativeEvent.initEvent(type, !!event.bubbles, !!event.cancelable);
312 // and copy all our properties over
313 for(var i in event){
314 var value = event[i];
315 if(!(i in nativeEvent)){
316 nativeEvent[i] = event[i];
317 }
318 }
319 return target.dispatchEvent(nativeEvent) && nativeEvent;
320 }
321 return syntheticDispatch.apply(on, arguments); // emit for a non-node
322 };
323 }else{
324 // no addEventListener, basically old IE event normalization
325 on._fixEvent = function(evt, sender){
326 // summary:
327 // normalizes properties on the event object including event
328 // bubbling methods, keystroke normalization, and x/y positions
329 // evt:
330 // native event object
331 // sender:
332 // node to treat as "currentTarget"
333 if(!evt){
334 var w = sender && (sender.ownerDocument || sender.document || sender).parentWindow || window;
335 evt = w.event;
336 }
337 if(!evt){return evt;}
338 if(lastEvent && evt.type == lastEvent.type){
339 // should be same event, reuse event object (so it can be augmented)
340 evt = lastEvent;
341 }
342 if(!evt.target){ // check to see if it has been fixed yet
343 evt.target = evt.srcElement;
344 evt.currentTarget = (sender || evt.srcElement);
345 if(evt.type == "mouseover"){
346 evt.relatedTarget = evt.fromElement;
347 }
348 if(evt.type == "mouseout"){
349 evt.relatedTarget = evt.toElement;
350 }
351 if(!evt.stopPropagation){
352 evt.stopPropagation = stopPropagation;
353 evt.preventDefault = preventDefault;
354 }
355 switch(evt.type){
356 case "keypress":
357 var c = ("charCode" in evt ? evt.charCode : evt.keyCode);
358 if (c==10){
359 // CTRL-ENTER is CTRL-ASCII(10) on IE, but CTRL-ENTER on Mozilla
360 c=0;
361 evt.keyCode = 13;
362 }else if(c==13||c==27){
363 c=0; // Mozilla considers ENTER and ESC non-printable
364 }else if(c==3){
365 c=99; // Mozilla maps CTRL-BREAK to CTRL-c
366 }
367 // Mozilla sets keyCode to 0 when there is a charCode
368 // but that stops the event on IE.
369 evt.charCode = c;
370 _setKeyChar(evt);
371 break;
372 }
373 }
374 return evt;
375 };
376 var lastEvent, IESignal = function(handle){
377 this.handle = handle;
378 };
379 IESignal.prototype.remove = function(){
380 delete _dojoIEListeners_[this.handle];
381 };
382 var fixListener = function(listener){
383 // this is a minimal function for closing on the previous listener with as few as variables as possible
384 return function(evt){
385 evt = on._fixEvent(evt, this);
386 var result = listener.call(this, evt);
387 if(evt.modified){
388 // cache the last event and reuse it if we can
389 if(!lastEvent){
390 setTimeout(function(){
391 lastEvent = null;
392 });
393 }
394 lastEvent = evt;
395 }
396 return result;
397 };
398 };
399 var fixAttach = function(target, type, listener){
400 listener = fixListener(listener);
401 if(((target.ownerDocument ? target.ownerDocument.parentWindow : target.parentWindow || target.window || window) != top ||
402 has("jscript") < 5.8) &&
403 !has("config-_allow_leaks")){
404 // IE will leak memory on certain handlers in frames (IE8 and earlier) and in unattached DOM nodes for JScript 5.7 and below.
405 // Here we use global redirection to solve the memory leaks
406 if(typeof _dojoIEListeners_ == "undefined"){
407 _dojoIEListeners_ = [];
408 }
409 var emiter = target[type];
410 if(!emiter || !emiter.listeners){
411 var oldListener = emiter;
412 emiter = Function('event', 'var callee = arguments.callee; for(var i = 0; i<callee.listeners.length; i++){var listener = _dojoIEListeners_[callee.listeners[i]]; if(listener){listener.call(this,event);}}');
413 emiter.listeners = [];
414 target[type] = emiter;
415 emiter.global = this;
416 if(oldListener){
417 emiter.listeners.push(_dojoIEListeners_.push(oldListener) - 1);
418 }
419 }
420 var handle;
421 emiter.listeners.push(handle = (emiter.global._dojoIEListeners_.push(listener) - 1));
422 return new IESignal(handle);
423 }
424 return aspect.after(target, type, listener, true);
425 };
426
427 var _setKeyChar = function(evt){
428 evt.keyChar = evt.charCode ? String.fromCharCode(evt.charCode) : '';
429 evt.charOrCode = evt.keyChar || evt.keyCode;
430 };
431 // Called in Event scope
432 var stopPropagation = function(){
433 this.cancelBubble = true;
434 };
435 var preventDefault = on._preventDefault = function(){
436 // Setting keyCode to 0 is the only way to prevent certain keypresses (namely
437 // ctrl-combinations that correspond to menu accelerator keys).
438 // Otoh, it prevents upstream listeners from getting this information
439 // Try to split the difference here by clobbering keyCode only for ctrl
440 // combinations. If you still need to access the key upstream, bubbledKeyCode is
441 // provided as a workaround.
442 this.bubbledKeyCode = this.keyCode;
443 if(this.ctrlKey){
444 try{
445 // squelch errors when keyCode is read-only
446 // (e.g. if keyCode is ctrl or shift)
447 this.keyCode = 0;
448 }catch(e){
449 }
450 }
451 this.defaultPrevented = true;
452 this.returnValue = false;
453 };
454 }
455 if(has("touch")){
456 var Event = function(){};
457 var windowOrientation = window.orientation;
458 var fixTouchListener = function(listener){
459 return function(originalEvent){
460 //Event normalization(for ontouchxxx and resize):
461 //1.incorrect e.pageX|pageY in iOS
462 //2.there are no "e.rotation", "e.scale" and "onorientationchange" in Andriod
463 //3.More TBD e.g. force | screenX | screenX | clientX | clientY | radiusX | radiusY
464
465 // see if it has already been corrected
466 var event = originalEvent.corrected;
467 if(!event){
468 var type = originalEvent.type;
469 try{
470 delete originalEvent.type; // on some JS engines (android), deleting properties make them mutable
471 }catch(e){}
472 if(originalEvent.type){
473 // deleting properties doesn't work (older iOS), have to use delegation
474 Event.prototype = originalEvent;
475 var event = new Event;
476 // have to delegate methods to make them work
477 event.preventDefault = function(){
478 originalEvent.preventDefault();
479 };
480 event.stopPropagation = function(){
481 originalEvent.stopPropagation();
482 };
483 }else{
484 // deletion worked, use property as is
485 event = originalEvent;
486 event.type = type;
487 }
488 originalEvent.corrected = event;
489 if(type == 'resize'){
490 if(windowOrientation == window.orientation){
491 return null;//double tap causes an unexpected 'resize' in Andriod
492 }
493 windowOrientation = window.orientation;
494 event.type = "orientationchange";
495 return listener.call(this, event);
496 }
497 // We use the original event and augment, rather than doing an expensive mixin operation
498 if(!("rotation" in event)){ // test to see if it has rotation
499 event.rotation = 0;
500 event.scale = 1;
501 }
502 //use event.changedTouches[0].pageX|pageY|screenX|screenY|clientX|clientY|target
503 var firstChangeTouch = event.changedTouches[0];
504 for(var i in firstChangeTouch){ // use for-in, we don't need to have dependency on dojo/_base/lang here
505 delete event[i]; // delete it first to make it mutable
506 event[i] = firstChangeTouch[i];
507 }
508 }
509 return listener.call(this, event);
510 };
511 };
512 }
513 return on;
514 });