source: main/trunk/greenstone3/web/interfaces/basic-client-xslt/js/event.js@ 32127

Last change on this file since 32127 was 12011, checked in by shaoqun, 18 years ago

javascript package for berry basket

  • Property svn:keywords set to Author Date Id Revision
File size: 29.6 KB
Line 
1/*
2Copyright (c) 2006 Yahoo! Inc. All rights reserved.
3version 0.9.0
4*/
5
6/**
7 * @class The CustomEvent class lets you define events for your application
8 * that can be subscribed to by one or more independent component.
9 * @param {String} type The type of event, which is passed to the callback
10 * when the event fires
11 * @param {Object} oScope The context the event will fire from. "this" will
12 * refer to this object in the callback. Default value:
13 * the window object. The listener can override this.
14 * @constructor
15 */
16YAHOO.util.CustomEvent = function(type, oScope) {
17 /**
18 * The type of event, returned to subscribers when the event fires
19 * @type string
20 */
21 this.type = type;
22
23 /**
24 * The scope the the event will fire from. Defaults to the window obj
25 * @type object
26 */
27 this.scope = oScope || window;
28
29 /**
30 * The subscribers to this event
31 * @type array
32 */
33 this.subscribers = [];
34
35 // Register with the event utility for automatic cleanup. Made optional
36 // so that CustomEvent can be used independently of pe.event
37 if (YAHOO.util["Event"]) {
38 YAHOO.util.Event.regCE(this);
39 }
40};
41
42YAHOO.util.CustomEvent.prototype = {
43 /**
44 * Subscribes the caller to this event
45 * @param {Function} fn The function to execute
46 * @param {Object} obj An object to be passed along when the event fires
47 * @param {boolean} bOverride If true, the obj passed in becomes the execution
48 * scope of the listener
49 */
50 subscribe: function(fn, obj, bOverride) {
51 this.subscribers.push( new YAHOO.util.Subscriber(fn, obj, bOverride) );
52 },
53
54 /**
55 * Unsubscribes the caller from this event
56 * @param {Function} fn The function to execute
57 * @param {Object} obj An object to be passed along when the event fires
58 * @return {boolean} True if the subscriber was found and detached.
59 */
60 unsubscribe: function(fn, obj) {
61 var found = false;
62 for (var i=0; i<this.subscribers.length; ++i) {
63 var s = this.subscribers[i];
64 if (s && s.contains(fn, obj)) {
65 this._delete(i);
66 found = true;
67 }
68 }
69
70 return found;
71 },
72
73 /**
74 * Notifies the subscribers. The callback functions will be executed
75 * from the scope specified when the event was created, and with the following
76 * parameters:
77 * <pre>
78 * - The type of event
79 * - All of the arguments fire() was executed with as an array
80 * - The custom object (if any) that was passed into the subscribe() method
81 * </pre>
82 *
83 * @param {Array} an arbitrary set of parameters to pass to the handler
84 */
85 fire: function() {
86 for (var i=0; i<this.subscribers.length; ++i) {
87 var s = this.subscribers[i];
88 if (s) {
89 var scope = (s.override) ? s.obj : this.scope;
90 s.fn.call(scope, this.type, arguments, s.obj);
91 }
92 }
93 },
94
95 /**
96 * Removes all listeners
97 */
98 unsubscribeAll: function() {
99 for (var i=0; i<this.subscribers.length; ++i) {
100 this._delete(i);
101 }
102 },
103
104 /**
105 * @private
106 */
107 _delete: function(index) {
108 var s = this.subscribers[index];
109 if (s) {
110 delete s.fn;
111 delete s.obj;
112 }
113
114 delete this.subscribers[index];
115 }
116};
117
118/////////////////////////////////////////////////////////////////////
119
120/**
121 * @class
122 * @param {Function} fn The function to execute
123 * @param {Object} obj An object to be passed along when the event fires
124 * @param {boolean} bOverride If true, the obj passed in becomes the execution
125 * scope of the listener
126 * @constructor
127 */
128YAHOO.util.Subscriber = function(fn, obj, bOverride) {
129 /**
130 * The callback that will be execute when the event fires
131 * @type function
132 */
133 this.fn = fn;
134
135 /**
136 * An optional custom object that will passed to the callback when
137 * the event fires
138 * @type object
139 */
140 this.obj = obj || null;
141
142 /**
143 * The default execution scope for the event listener is defined when the
144 * event is created (usually the object which contains the event).
145 * By setting override to true, the execution scope becomes the custom
146 * object passed in by the subscriber
147 * @type boolean
148 */
149 this.override = (bOverride);
150};
151
152/**
153 * Returns true if the fn and obj match this objects properties.
154 * Used by the unsubscribe method to match the right subscriber.
155 *
156 * @param {Function} fn the function to execute
157 * @param {Object} obj an object to be passed along when the event fires
158 * @return {boolean} true if the supplied arguments match this
159 * subscriber's signature.
160 */
161YAHOO.util.Subscriber.prototype.contains = function(fn, obj) {
162 return (this.fn == fn && this.obj == obj);
163};
164
165/* Copyright (c) 2006 Yahoo! Inc. All rights reserved. */
166
167// Only load this library once. If it is loaded a second time, existing
168// events cannot be detached.
169if (!YAHOO.util.Event) {
170
171/**
172 * The event utility provides functions to add and remove event listeners,
173 * event cleansing. It also tries to automatically remove listeners it
174 * registers during the unload event.
175 * @class
176 * @constructor
177 */
178 YAHOO.util.Event = function() {
179
180 /**
181 * True after the onload event has fired
182 * @type boolean
183 * @private
184 */
185 var loadComplete = false;
186
187 /**
188 * Cache of wrapped listeners
189 * @type array
190 * @private
191 */
192 var listeners = [];
193
194 /**
195 * Listeners that will be attached during the onload event
196 * @type array
197 * @private
198 */
199 var delayedListeners = [];
200
201 /**
202 * User-defined unload function that will be fired before all events
203 * are detached
204 * @type array
205 * @private
206 */
207 var unloadListeners = [];
208
209 /**
210 * Cache of the custom events that have been defined. Used for
211 * automatic cleanup
212 * @type array
213 * @private
214 */
215 var customEvents = [];
216
217 /**
218 * Cache of DOM0 event handlers to work around issues with DOM2 events
219 * in Safari
220 * @private
221 */
222 var legacyEvents = [];
223
224 /**
225 * Listener stack for DOM0 events
226 * @private
227 */
228 var legacyHandlers = [];
229
230 return { // PREPROCESS
231
232 /**
233 * Element to bind, int constant
234 * @type int
235 */
236 EL: 0,
237
238 /**
239 * Type of event, int constant
240 * @type int
241 */
242 TYPE: 1,
243
244 /**
245 * Function to execute, int constant
246 * @type int
247 */
248 FN: 2,
249
250 /**
251 * Function wrapped for scope correction and cleanup, int constant
252 * @type int
253 */
254 WFN: 3,
255
256 /**
257 * Object passed in by the user that will be returned as a
258 * parameter to the callback, int constant
259 * @type int
260 */
261 SCOPE: 3,
262
263 /**
264 * Adjusted scope, either the element we are registering the event
265 * on or the custom object passed in by the listener, int constant
266 * @type int
267 */
268 ADJ_SCOPE: 4,
269
270 /**
271 * Safari detection is necessary to work around the preventDefault
272 * bug that makes it so you can't cancel a href click from the
273 * handler. There is not a capabilities check we can use here.
274 * @private
275 */
276 isSafari: (navigator.userAgent.match(/safari/gi)),
277
278 /**
279 * @private
280 * IE detection needed to properly calculate pageX and pageY.
281 * capabilities checking didn't seem to work because another
282 * browser that does not provide the properties have the values
283 * calculated in a different manner than IE.
284 */
285 isIE: (!this.isSafari && navigator.userAgent.match(/msie/gi)),
286
287 /**
288 * Appends an event handler
289 *
290 * @param {Object} el The html element to assign the
291 * event to
292 * @param {String} sType The type of event to append
293 * @param {Function} fn The method the event invokes
294 * @param {Object} oScope An arbitrary object that will be
295 * passed as a parameter to the handler
296 * @param {boolean} bOverride If true, the obj passed in becomes
297 * the execution scope of the listener
298 * @return {boolean} True if the action was successful or defered,
299 * false if one or more of the elements
300 * could not have the event bound to it.
301 */
302 addListener: function(el, sType, fn, oScope, bOverride) {
303
304 // The el argument can be an array of elements or element ids.
305 if ( this._isValidCollection(el)) {
306 var ok = true;
307 for (var i=0; i< el.length; ++i) {
308 ok = ( this.on(el[i],
309 sType,
310 fn,
311 oScope,
312 bOverride) && ok );
313 }
314 return ok;
315
316 } else if (typeof el == "string") {
317 // If the el argument is a string, we assume it is
318 // actually the id of the element. If the page is loaded
319 // we convert el to the actual element, otherwise we
320 // defer attaching the event until onload event fires
321
322 // check to see if we need to delay hooking up the event
323 // until after the page loads.
324 if (loadComplete) {
325 el = this.getEl(el);
326 } else {
327 // defer adding the event until onload fires
328 delayedListeners[delayedListeners.length] =
329 [el, sType, fn, oScope, bOverride];
330
331 return true;
332 }
333 }
334
335 // Element should be an html element or an array if we get
336 // here.
337 if (!el) {
338 return false;
339 }
340
341 // we need to make sure we fire registered unload events
342 // prior to automatically unhooking them. So we hang on to
343 // these instead of attaching them to the window and fire the
344 // handles explicitly during our one unload event.
345 if ("unload" == sType && oScope !== this) {
346 unloadListeners[unloadListeners.length] =
347 [el, sType, fn, oScope, bOverride];
348 return true;
349 }
350
351
352 // if the user chooses to override the scope, we use the custom
353 // object passed in, otherwise the executing scope will be the
354 // HTML element that the event is registered on
355 var scope = (bOverride) ? oScope : el;
356
357 // wrap the function so we can return the oScope object when
358 // the event fires;
359 var wrappedFn = function(e) {
360 return fn.call(scope, YAHOO.util.Event.getEvent(e),
361 oScope);
362 };
363
364 var li = [el, sType, fn, wrappedFn, scope];
365 var index = listeners.length;
366 // cache the listener so we can try to automatically unload
367 listeners[index] = li;
368
369 if (this.useLegacyEvent(el, sType)) {
370 var legacyIndex = this.getLegacyIndex(el, sType);
371 if (legacyIndex == -1) {
372
373 legacyIndex = legacyEvents.length;
374 // cache the signature for the DOM0 event, and
375 // include the existing handler for the event, if any
376 legacyEvents[legacyIndex] =
377 [el, sType, el["on" + sType]];
378 legacyHandlers[legacyIndex] = [];
379
380 el["on" + sType] =
381 function(e) {
382 YAHOO.util.Event.fireLegacyEvent(
383 YAHOO.util.Event.getEvent(e), legacyIndex);
384 };
385 }
386
387 // add a reference to the wrapped listener to our custom
388 // stack of events
389 legacyHandlers[legacyIndex].push(index);
390
391 // DOM2 Event model
392 } else if (el.addEventListener) {
393 el.addEventListener(sType, wrappedFn, false);
394 // Internet Explorer abstraction
395 } else if (el.attachEvent) {
396 el.attachEvent("on" + sType, wrappedFn);
397 }
398
399 return true;
400
401 },
402
403 /**
404 * Shorthand for YAHOO.util.Event.addListener
405 * @type function
406 */
407 // on: this.addListener,
408
409 /**
410 * When using legacy events, the handler is routed to this object
411 * so we can fire our custom listener stack.
412 * @private
413 */
414 fireLegacyEvent: function(e, legacyIndex) {
415 // alert("fireLegacyEvent " + legacyIndex);
416 var ok = true;
417
418 // var el = legacyEvents[YAHOO.util.Event.EL];
419
420 /* this is not working because the property may get populated
421 // fire the event we replaced, if it exists
422 var origHandler = legacyEvents[2];
423 alert(origHandler);
424 if (origHandler && origHandler.call) {
425 var ret = origHandler.call(el, e);
426 ok = (ret);
427 }
428 */
429
430 var le = legacyHandlers[legacyIndex];
431 for (i=0; i < le.length; ++i) {
432 var index = le[i];
433 // alert(index);
434 if (index) {
435 var li = listeners[index];
436 var scope = li[this.ADJ_SCOPE];
437 var ret = li[this.WFN].call(scope, e);
438 ok = (ok && ret);
439 // alert(ok);
440 }
441 }
442
443 return ok;
444 },
445
446 /**
447 * Returns the legacy event index that matches the supplied
448 * signature
449 * @private
450 */
451 getLegacyIndex: function(el, sType) {
452 for (var i=0; i < legacyEvents.length; ++i) {
453 var le = legacyEvents[i];
454 if (le && le[0] == el && le[1] == sType) {
455 return i;
456 }
457 }
458
459 return -1;
460 },
461
462 /**
463 * Logic that determines when we should automatically use legacy
464 * events instead of DOM2 events.
465 * @private
466 */
467 useLegacyEvent: function(el, sType) {
468
469 return ( (!el.addEventListener && !el.attachEvent) ||
470 (sType == "click" && this.isSafari) );
471 },
472
473 /**
474 * Removes an event handler
475 *
476 * @param {Object} el the html element or the id of the element to
477 * assign the event to.
478 * @param {String} sType the type of event to remove
479 * @param {Function} fn the method the event invokes
480 * @return {boolean} true if the unbind was successful, false
481 * otherwise
482 */
483 removeListener: function(el, sType, fn) {
484
485 // The el argument can be a string
486 if (typeof el == "string") {
487 el = this.getEl(el);
488 // The el argument can be an array of elements or element ids.
489 } else if ( this._isValidCollection(el)) {
490 var ok = true;
491 for (var i=0; i< el.length; ++i) {
492 ok = ( this.removeListener(el[i], sType, fn) && ok );
493 }
494 return ok;
495 }
496
497 var cacheItem = null;
498 var index = this._getCacheIndex(el, sType, fn);
499
500 if (index >= 0) {
501 cacheItem = listeners[index];
502 }
503
504 if (!el || !cacheItem) {
505 return false;
506 }
507
508
509 if (el.removeEventListener) {
510 el.removeEventListener(sType, cacheItem[this.WFN], false);
511 // alert("adsf");
512 } else if (el.detachEvent) {
513 el.detachEvent("on" + sType, cacheItem[this.WFN]);
514 }
515
516 // removed the wrapped handler
517 delete listeners[index][this.WFN];
518 delete listeners[index][this.FN];
519 delete listeners[index];
520
521 return true;
522
523 },
524
525 /**
526 * Returns the event's target element
527 * @param {Event} ev the event
528 * @param {boolean} resolveTextNode when set to true the target's
529 * parent will be returned if the target is a
530 * text node
531 * @return {HTMLElement} the event's target
532 */
533 getTarget: function(ev, resolveTextNode) {
534 var t = ev.target || ev.srcElement;
535
536 if (resolveTextNode && t && "#text" == t.nodeName) {
537 return t.parentNode;
538 } else {
539 return t;
540 }
541 },
542
543 /**
544 * Returns the event's pageX
545 * @param {Event} ev the event
546 * @return {int} the event's pageX
547 */
548 getPageX: function(ev) {
549 var x = ev.pageX;
550 if (!x && 0 !== x) {
551 x = ev.clientX || 0;
552
553 if ( this.isIE ) {
554 x += this._getScrollLeft();
555 }
556 }
557
558 return x;
559 },
560
561 /**
562 * Returns the event's pageY
563 * @param {Event} ev the event
564 * @return {int} the event's pageY
565 */
566 getPageY: function(ev) {
567 var y = ev.pageY;
568 if (!y && 0 !== y) {
569 y = ev.clientY || 0;
570
571 if ( this.isIE ) {
572 y += this._getScrollTop();
573 }
574 }
575
576 return y;
577 },
578
579 /**
580 * Returns the event's related target
581 * @param {Event} ev the event
582 * @return {HTMLElement} the event's relatedTarget
583 */
584 getRelatedTarget: function(ev) {
585 var t = ev.relatedTarget;
586 if (!t) {
587 if (ev.type == "mouseout") {
588 t = ev.toElement;
589 } else if (ev.type == "mouseover") {
590 t = ev.fromElement;
591 }
592 }
593
594 return t;
595 },
596
597 /**
598 * Returns the time of the event. If the time is not included, the
599 * event is modified using the current time.
600 * @param {Event} ev the event
601 * @return {Date} the time of the event
602 */
603 getTime: function(ev) {
604 if (!ev.time) {
605 var t = new Date().getTime();
606 try {
607 ev.time = t;
608 } catch(e) {
609 // can't set the time property
610 return t;
611 }
612 }
613
614 return ev.time;
615 },
616
617 /**
618 * Convenience method for stopPropagation + preventDefault
619 * @param {Event} ev the event
620 */
621 stopEvent: function(ev) {
622 this.stopPropagation(ev);
623 this.preventDefault(ev);
624 },
625
626 /**
627 * Stops event propagation
628 * @param {Event} ev the event
629 */
630 stopPropagation: function(ev) {
631 if (ev.stopPropagation) {
632 ev.stopPropagation();
633 } else {
634 ev.cancelBubble = true;
635 }
636 },
637
638 /**
639 * Prevents the default behavior of the event
640 * @param {Event} ev the event
641 */
642 preventDefault: function(ev) {
643 if (ev.preventDefault) {
644 ev.preventDefault();
645 } else {
646 ev.returnValue = false;
647 }
648 },
649
650 /**
651 * Returns the event, should not be necessary for user to call
652 * @param {Event} the event parameter from the handler
653 * @return {Event} the event
654 */
655 getEvent: function(e) {
656 var ev = e || window.event;
657
658 if (!ev) {
659 var c = this.getEvent.caller;
660 while (c) {
661 ev = c.arguments[0];
662 if (ev && Event == ev.constructor) {
663 break;
664 }
665 c = c.caller;
666 }
667 }
668
669 return ev;
670 },
671
672 /**
673 * Returns the charcode for an event
674 * @param {Event} ev the event
675 * @return {int} the event's charCode
676 */
677 getCharCode: function(ev) {
678 return ev.charCode || (ev.type == "keypress") ? ev.keyCode : 0;
679 },
680
681 /**
682 * @private
683 * Locating the saved event handler data by function ref
684 */
685 _getCacheIndex: function(el, sType, fn) {
686 for (var i=0; i< listeners.length; ++i) {
687 var li = listeners[i];
688 if ( li &&
689 li[this.FN] == fn &&
690 li[this.EL] == el &&
691 li[this.TYPE] == sType ) {
692 return i;
693 }
694 }
695
696 return -1;
697 },
698
699 /**
700 * We want to be able to use getElementsByTagName as a collection
701 * to attach a group of events to. Unfortunately, different
702 * browsers return different types of collections. This function
703 * tests to determine if the object is array-like. It will also
704 * fail if the object is an array, but is empty.
705 * @param o the object to test
706 * @return {boolean} true if the object is array-like and populated
707 */
708 _isValidCollection: function(o) {
709 // alert(o.constructor.toString())
710 // alert(typeof o)
711
712 return ( o && // o is something
713 o.length && // o is indexed
714 typeof o != "string" && // o is not a string
715 !o.tagName && // o is not an HTML element
716 !o.alert && // o is not a window
717 typeof o[0] != "undefined" );
718
719 },
720
721 /**
722 * @private
723 * DOM element cache
724 */
725 elCache: {},
726
727 /**
728 * We cache elements bound by id because when the unload event
729 * fires, we can no longer use document.getElementById
730 * @private
731 */
732 getEl: function(id) {
733 /*
734 // this is a problem when replaced via document.getElementById
735 if (! this.elCache[id]) {
736 try {
737 var el = document.getElementById(id);
738 if (el) {
739 this.elCache[id] = el;
740 }
741 } catch (er) {
742 }
743 }
744 return this.elCache[id];
745 */
746
747 return document.getElementById(id);
748 },
749
750 /**
751 * Clears the element cache
752 */
753 clearCache: function() {
754 for (i in this.elCache) {
755 delete this.elCache[i];
756 }
757 },
758
759 /**
760 * Called by CustomEvent instances to provide a handle to the
761 * event * that can be removed later on. Should be package
762 * protected.
763 * @private
764 */
765 regCE: function(ce) {
766 customEvents.push(ce);
767 },
768
769 /**
770 * @private
771 * hook up any deferred listeners
772 */
773 _load: function(e) {
774 loadComplete = true;
775 },
776
777 /**
778 * Polling function that runs before the onload event fires,
779 * attempting * to attach to DOM Nodes as soon as they are
780 * available
781 * @private
782 */
783 _tryPreloadAttach: function() {
784
785 // keep trying until after the page is loaded. We need to
786 // check the page load state prior to trying to bind the
787 // elements so that we can be certain all elements have been
788 // tested appropriately
789 var tryAgain = !loadComplete;
790
791 for (var i=0; i < delayedListeners.length; ++i) {
792 var d = delayedListeners[i];
793 // There may be a race condition here, so we need to
794 // verify the array element is usable.
795 if (d) {
796
797 // el will be null if document.getElementById did not
798 // work
799 var el = this.getEl(d[this.EL]);
800
801 if (el) {
802 this.on(el, d[this.TYPE], d[this.FN],
803 d[this.SCOPE], d[this.ADJ_SCOPE]);
804 delete delayedListeners[i];
805 }
806 }
807 }
808
809 if (tryAgain) {
810 setTimeout("YAHOO.util.Event._tryPreloadAttach()", 50);
811 }
812 },
813
814 /**
815 * Removes all listeners registered by pe.event. Called
816 * automatically during the unload event.
817 */
818 _unload: function(e, me) {
819 for (var i=0; i < unloadListeners.length; ++i) {
820 var l = unloadListeners[i];
821 if (l) {
822 var scope = (l[this.ADJ_SCOPE]) ? l[this.SCOPE]: window;
823 l[this.FN].call(scope, this.getEvent(e), l[this.SCOPE] );
824 }
825 }
826
827 if (listeners && listeners.length > 0) {
828 for (i = 0; i < listeners.length; ++i) {
829 l = listeners[i];
830 if (l) {
831 this.removeListener(l[this.EL], l[this.TYPE],
832 l[this.FN]);
833 }
834 }
835
836 this.clearCache();
837 }
838
839 for (i = 0; i < customEvents.length; ++i) {
840 customEvents[i].unsubscribeAll();
841 delete customEvents[i];
842 }
843
844 for (i = 0; i < legacyEvents.length; ++i) {
845 // dereference the element
846 delete legacyEvents[i][0];
847 // delete the array item
848 delete legacyEvents[i];
849 }
850 },
851
852 /**
853 * Returns scrollLeft
854 * @private
855 */
856 _getScrollLeft: function() {
857 return this._getScroll()[1];
858 },
859
860 /**
861 * Returns scrollTop
862 * @private
863 */
864 _getScrollTop: function() {
865 return this._getScroll()[0];
866 },
867
868 /**
869 * Returns the scrollTop and scrollLeft. Used to calculate the
870 * pageX and pageY in Internet Explorer
871 * @private
872 */
873 _getScroll: function() {
874 var dd = document.documentElement; db = document.body;
875 if (dd && dd.scrollTop) {
876 return [dd.scrollTop, dd.scrollLeft];
877 } else if (db) {
878 return [db.scrollTop, db.scrollLeft];
879 } else {
880 return [0, 0];
881 }
882 }
883 };
884 } ();
885
886 YAHOO.util.Event.on = YAHOO.util.Event.addListener;
887
888 if (document && document.body) {
889 YAHOO.util.Event._load();
890 } else {
891 YAHOO.util.Event.on(window, "load", YAHOO.util.Event._load,
892 YAHOO.util.Event, true);
893 }
894
895 YAHOO.util.Event.on(window, "unload", YAHOO.util.Event._unload,
896 YAHOO.util.Event, true);
897
898 YAHOO.util.Event._tryPreloadAttach();
899
900}
901
Note: See TracBrowser for help on using the repository browser.