source: trunk/spip/esqueleto-redcta/plugins/widget_calendar/img_pack/event.js @ 229

Last change on this file since 229 was 30, checked in by sebas, 17 years ago

nueva importacion del codigo del esqueleto de redcta con los plugins

File size: 29.6 KB
Line 
1/*
2Copyright (c) 2006 Spip! 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 */
16SPIP.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 (SPIP.util["Event"]) {
38        SPIP.util.Event.regCE(this);
39    }
40};
41
42SPIP.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 SPIP.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 */
128SPIP.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 */
161SPIP.util.Subscriber.prototype.contains = function(fn, obj) {
162    return (this.fn == fn && this.obj == obj);
163};
164
165/* Copyright (c) 2006 Spip! 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 (!SPIP.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    SPIP.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, SPIP.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                                SPIP.util.Event.fireLegacyEvent(
383                                    SPIP.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 SPIP.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[SPIP.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("SPIP.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    SPIP.util.Event.on = SPIP.util.Event.addListener;
887
888    if (document && document.body) {
889        SPIP.util.Event._load();
890    } else {
891        SPIP.util.Event.on(window, "load", SPIP.util.Event._load,
892                SPIP.util.Event, true);
893    }
894
895    SPIP.util.Event.on(window, "unload", SPIP.util.Event._unload,
896                SPIP.util.Event, true);
897
898    SPIP.util.Event._tryPreloadAttach();
899
900}
901
Note: See TracBrowser for help on using the repository browser.