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