Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / util / Observable.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * Base class that provides a common interface for publishing events. Subclasses are expected to to have a property
17  * "events" with all the events defined, and, optionally, a property "listeners" with configured listeners defined.
18  *
19  * For example:
20  *
21  *     Ext.define('Employee', {
22  *         extend: 'Ext.util.Observable',
23  *         constructor: function(config){
24  *             this.name = config.name;
25  *             this.addEvents({
26  *                 "fired" : true,
27  *                 "quit" : true
28  *             });
29  *
30  *             // Copy configured listeners into *this* object so that the base class's
31  *             // constructor will add them.
32  *             this.listeners = config.listeners;
33  *
34  *             // Call our superclass constructor to complete construction process.
35  *             this.callParent(arguments)
36  *         }
37  *     });
38  *
39  * This could then be used like this:
40  *
41  *     var newEmployee = new Employee({
42  *         name: employeeName,
43  *         listeners: {
44  *             quit: function() {
45  *                 // By default, "this" will be the object that fired the event.
46  *                 alert(this.name + " has quit!");
47  *             }
48  *         }
49  *     });
50  */
51 Ext.define('Ext.util.Observable', {
52
53     /* Begin Definitions */
54
55     requires: ['Ext.util.Event'],
56
57     statics: {
58         /**
59          * Removes **all** added captures from the Observable.
60          *
61          * @param {Ext.util.Observable} o The Observable to release
62          * @static
63          */
64         releaseCapture: function(o) {
65             o.fireEvent = this.prototype.fireEvent;
66         },
67
68         /**
69          * Starts capture on the specified Observable. All events will be passed to the supplied function with the event
70          * name + standard signature of the event **before** the event is fired. If the supplied function returns false,
71          * the event will not fire.
72          *
73          * @param {Ext.util.Observable} o The Observable to capture events from.
74          * @param {Function} fn The function to call when an event is fired.
75          * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to
76          * the Observable firing the event.
77          * @static
78          */
79         capture: function(o, fn, scope) {
80             o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
81         },
82
83         /**
84          * Sets observability on the passed class constructor.
85          *
86          * This makes any event fired on any instance of the passed class also fire a single event through
87          * the **class** allowing for central handling of events on many instances at once.
88          *
89          * Usage:
90          *
91          *     Ext.util.Observable.observe(Ext.data.Connection);
92          *     Ext.data.Connection.on('beforerequest', function(con, options) {
93          *         console.log('Ajax request made to ' + options.url);
94          *     });
95          *
96          * @param {Function} c The class constructor to make observable.
97          * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
98          * @static
99          */
100         observe: function(cls, listeners) {
101             if (cls) {
102                 if (!cls.isObservable) {
103                     Ext.applyIf(cls, new this());
104                     this.capture(cls.prototype, cls.fireEvent, cls);
105                 }
106                 if (Ext.isObject(listeners)) {
107                     cls.on(listeners);
108                 }
109                 return cls;
110             }
111         }
112     },
113
114     /* End Definitions */
115
116     /**
117      * @cfg {Object} listeners
118      *
119      * A config object containing one or more event handlers to be added to this object during initialization. This
120      * should be a valid listeners config object as specified in the {@link #addListener} example for attaching multiple
121      * handlers at once.
122      *
123      * **DOM events from Ext JS {@link Ext.Component Components}**
124      *
125      * While _some_ Ext JS Component classes export selected DOM events (e.g. "click", "mouseover" etc), this is usually
126      * only done when extra value can be added. For example the {@link Ext.view.View DataView}'s **`{@link
127      * Ext.view.View#itemclick itemclick}`** event passing the node clicked on. To access DOM events directly from a
128      * child element of a Component, we need to specify the `element` option to identify the Component property to add a
129      * DOM listener to:
130      *
131      *     new Ext.panel.Panel({
132      *         width: 400,
133      *         height: 200,
134      *         dockedItems: [{
135      *             xtype: 'toolbar'
136      *         }],
137      *         listeners: {
138      *             click: {
139      *                 element: 'el', //bind to the underlying el property on the panel
140      *                 fn: function(){ console.log('click el'); }
141      *             },
142      *             dblclick: {
143      *                 element: 'body', //bind to the underlying body property on the panel
144      *                 fn: function(){ console.log('dblclick body'); }
145      *             }
146      *         }
147      *     });
148      */
149     // @private
150     isObservable: true,
151
152     constructor: function(config) {
153         var me = this;
154
155         Ext.apply(me, config);
156         if (me.listeners) {
157             me.on(me.listeners);
158             delete me.listeners;
159         }
160         me.events = me.events || {};
161
162         if (me.bubbleEvents) {
163             me.enableBubble(me.bubbleEvents);
164         }
165     },
166
167     // @private
168     eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal|freezeEvent)$/,
169
170     /**
171      * Adds listeners to any Observable object (or Ext.Element) which are automatically removed when this Component is
172      * destroyed.
173      *
174      * @param {Ext.util.Observable/Ext.Element} item The item to which to add a listener/listeners.
175      * @param {Object/String} ename The event name, or an object containing event name properties.
176      * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
177      * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
178      * in which the handler function is executed.
179      * @param {Object} opt (optional) If the `ename` parameter was an event name, this is the
180      * {@link Ext.util.Observable#addListener addListener} options.
181      */
182     addManagedListener : function(item, ename, fn, scope, options) {
183         var me = this,
184             managedListeners = me.managedListeners = me.managedListeners || [],
185             config;
186
187         if (typeof ename !== 'string') {
188             options = ename;
189             for (ename in options) {
190                 if (options.hasOwnProperty(ename)) {
191                     config = options[ename];
192                     if (!me.eventOptionsRe.test(ename)) {
193                         me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
194                     }
195                 }
196             }
197         }
198         else {
199             managedListeners.push({
200                 item: item,
201                 ename: ename,
202                 fn: fn,
203                 scope: scope,
204                 options: options
205             });
206
207             item.on(ename, fn, scope, options);
208         }
209     },
210
211     /**
212      * Removes listeners that were added by the {@link #mon} method.
213      *
214      * @param {Ext.util.Observable/Ext.Element} item The item from which to remove a listener/listeners.
215      * @param {Object/String} ename The event name, or an object containing event name properties.
216      * @param {Function} fn (optional) If the `ename` parameter was an event name, this is the handler function.
217      * @param {Object} scope (optional) If the `ename` parameter was an event name, this is the scope (`this` reference)
218      * in which the handler function is executed.
219      */
220     removeManagedListener : function(item, ename, fn, scope) {
221         var me = this,
222             options,
223             config,
224             managedListeners,
225             length,
226             i;
227
228         if (typeof ename !== 'string') {
229             options = ename;
230             for (ename in options) {
231                 if (options.hasOwnProperty(ename)) {
232                     config = options[ename];
233                     if (!me.eventOptionsRe.test(ename)) {
234                         me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
235                     }
236                 }
237             }
238         }
239
240         managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
241
242         for (i = 0, length = managedListeners.length; i < length; i++) {
243             me.removeManagedListenerItem(false, managedListeners[i], item, ename, fn, scope);
244         }
245     },
246
247     /**
248      * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
249      * to {@link #addListener}).
250      *
251      * An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) by
252      * calling {@link #enableBubble}.
253      *
254      * @param {String} eventName The name of the event to fire.
255      * @param {Object...} args Variable number of parameters are passed to handlers.
256      * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
257      */
258     fireEvent: function(eventName) {
259         var name = eventName.toLowerCase(),
260             events = this.events,
261             event = events && events[name],
262             bubbles = event && event.bubble;
263
264         return this.continueFireEvent(name, Ext.Array.slice(arguments, 1), bubbles);
265     },
266
267     /**
268      * Continue to fire event.
269      * @private
270      *
271      * @param {String} eventName
272      * @param {Array} args
273      * @param {Boolean} bubbles
274      */
275     continueFireEvent: function(eventName, args, bubbles) {
276         var target = this,
277             queue, event,
278             ret = true;
279
280         do {
281             if (target.eventsSuspended === true) {
282                 if ((queue = target.eventQueue)) {
283                     queue.push([eventName, args, bubbles]);
284                 }
285                 return ret;
286             } else {
287                 event = target.events[eventName];
288                 // Continue bubbling if event exists and it is `true` or the handler didn't returns false and it
289                 // configure to bubble.
290                 if (event && event != true) {
291                     if ((ret = event.fire.apply(event, args)) === false) {
292                         break;
293                     }
294                 }
295             }
296         } while (bubbles && (target = target.getBubbleParent()));
297         return ret;
298     },
299
300     /**
301      * Gets the bubbling parent for an Observable
302      * @private
303      * @return {Ext.util.Observable} The bubble parent. null is returned if no bubble target exists
304      */
305     getBubbleParent: function(){
306         var me = this, parent = me.getBubbleTarget && me.getBubbleTarget();
307         if (parent && parent.isObservable) {
308             return parent;
309         }
310         return null;
311     },
312
313     /**
314      * Appends an event handler to this object.
315      *
316      * @param {String} eventName The name of the event to listen for. May also be an object who's property names are
317      * event names.
318      * @param {Function} fn The method the event invokes.  Will be called with arguments given to
319      * {@link #fireEvent} plus the `options` parameter described below.
320      * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed. **If
321      * omitted, defaults to the object which fired the event.**
322      * @param {Object} [options] An object containing handler configuration.
323      *
324      * **Note:** Unlike in ExtJS 3.x, the options object will also be passed as the last argument to every event handler.
325      *
326      * This object may contain any of the following properties:
327      *
328      * - **scope** : Object
329      *
330      *   The scope (`this` reference) in which the handler function is executed. **If omitted, defaults to the object
331      *   which fired the event.**
332      *
333      * - **delay** : Number
334      *
335      *   The number of milliseconds to delay the invocation of the handler after the event fires.
336      *
337      * - **single** : Boolean
338      *
339      *   True to add a handler to handle just the next firing of the event, and then remove itself.
340      *
341      * - **buffer** : Number
342      *
343      *   Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
344      *   milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
345      *   handler is scheduled in its place.
346      *
347      * - **target** : Observable
348      *
349      *   Only call the handler if the event was fired on the target Observable, _not_ if the event was bubbled up from a
350      *   child Observable.
351      *
352      * - **element** : String
353      *
354      *   **This option is only valid for listeners bound to {@link Ext.Component Components}.** The name of a Component
355      *   property which references an element to add a listener to.
356      *
357      *   This option is useful during Component construction to add DOM event listeners to elements of
358      *   {@link Ext.Component Components} which will exist only after the Component is rendered.
359      *   For example, to add a click listener to a Panel's body:
360      *
361      *       new Ext.panel.Panel({
362      *           title: 'The title',
363      *           listeners: {
364      *               click: this.handlePanelClick,
365      *               element: 'body'
366      *           }
367      *       });
368      *
369      * **Combining Options**
370      *
371      * Using the options argument, it is possible to combine different types of listeners:
372      *
373      * A delayed, one-time listener.
374      *
375      *     myPanel.on('hide', this.handleClick, this, {
376      *         single: true,
377      *         delay: 100
378      *     });
379      *
380      * **Attaching multiple handlers in 1 call**
381      *
382      * The method also allows for a single argument to be passed which is a config object containing properties which
383      * specify multiple events. For example:
384      *
385      *     myGridPanel.on({
386      *         cellClick: this.onCellClick,
387      *         mouseover: this.onMouseOver,
388      *         mouseout: this.onMouseOut,
389      *         scope: this // Important. Ensure "this" is correct during handler execution
390      *     });
391      *
392      * One can also specify options for each event handler separately:
393      *
394      *     myGridPanel.on({
395      *         cellClick: {fn: this.onCellClick, scope: this, single: true},
396      *         mouseover: {fn: panel.onMouseOver, scope: panel}
397      *     });
398      *
399      */
400     addListener: function(ename, fn, scope, options) {
401         var me = this,
402             config,
403             event;
404
405         if (typeof ename !== 'string') {
406             options = ename;
407             for (ename in options) {
408                 if (options.hasOwnProperty(ename)) {
409                     config = options[ename];
410                     if (!me.eventOptionsRe.test(ename)) {
411                         me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
412                     }
413                 }
414             }
415         }
416         else {
417             ename = ename.toLowerCase();
418             me.events[ename] = me.events[ename] || true;
419             event = me.events[ename] || true;
420             if (Ext.isBoolean(event)) {
421                 me.events[ename] = event = new Ext.util.Event(me, ename);
422             }
423             event.addListener(fn, scope, Ext.isObject(options) ? options : {});
424         }
425     },
426
427     /**
428      * Removes an event handler.
429      *
430      * @param {String} eventName The type of event the handler was associated with.
431      * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
432      * {@link #addListener} call.**
433      * @param {Object} scope (optional) The scope originally specified for the handler. It must be the same as the
434      * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
435      */
436     removeListener: function(ename, fn, scope) {
437         var me = this,
438             config,
439             event,
440             options;
441
442         if (typeof ename !== 'string') {
443             options = ename;
444             for (ename in options) {
445                 if (options.hasOwnProperty(ename)) {
446                     config = options[ename];
447                     if (!me.eventOptionsRe.test(ename)) {
448                         me.removeListener(ename, config.fn || config, config.scope || options.scope);
449                     }
450                 }
451             }
452         } else {
453             ename = ename.toLowerCase();
454             event = me.events[ename];
455             if (event && event.isEvent) {
456                 event.removeListener(fn, scope);
457             }
458         }
459     },
460
461     /**
462      * Removes all listeners for this object including the managed listeners
463      */
464     clearListeners: function() {
465         var events = this.events,
466             event,
467             key;
468
469         for (key in events) {
470             if (events.hasOwnProperty(key)) {
471                 event = events[key];
472                 if (event.isEvent) {
473                     event.clearListeners();
474                 }
475             }
476         }
477
478         this.clearManagedListeners();
479     },
480
481     //<debug>
482     purgeListeners : function() {
483         if (Ext.global.console) {
484             Ext.global.console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
485         }
486         return this.clearListeners.apply(this, arguments);
487     },
488     //</debug>
489
490     /**
491      * Removes all managed listeners for this object.
492      */
493     clearManagedListeners : function() {
494         var managedListeners = this.managedListeners || [],
495             i = 0,
496             len = managedListeners.length;
497
498         for (; i < len; i++) {
499             this.removeManagedListenerItem(true, managedListeners[i]);
500         }
501
502         this.managedListeners = [];
503     },
504
505     /**
506      * Remove a single managed listener item
507      * @private
508      * @param {Boolean} isClear True if this is being called during a clear
509      * @param {Object} managedListener The managed listener item
510      * See removeManagedListener for other args
511      */
512     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
513         if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
514             managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
515             if (!isClear) {
516                 Ext.Array.remove(this.managedListeners, managedListener);
517             }
518         }
519     },
520
521     //<debug>
522     purgeManagedListeners : function() {
523         if (Ext.global.console) {
524             Ext.global.console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
525         }
526         return this.clearManagedListeners.apply(this, arguments);
527     },
528     //</debug>
529
530     /**
531      * Adds the specified events to the list of events which this Observable may fire.
532      *
533      * @param {Object/String} o Either an object with event names as properties with a value of `true` or the first
534      * event name string if multiple event names are being passed as separate parameters. Usage:
535      *
536      *     this.addEvents({
537      *         storeloaded: true,
538      *         storecleared: true
539      *     });
540      *
541      * @param {String...} more (optional) Additional event names if multiple event names are being passed as separate
542      * parameters. Usage:
543      *
544      *     this.addEvents('storeloaded', 'storecleared');
545      *
546      */
547     addEvents: function(o) {
548         var me = this,
549             args,
550             len,
551             i;
552
553             me.events = me.events || {};
554         if (Ext.isString(o)) {
555             args = arguments;
556             i = args.length;
557
558             while (i--) {
559                 me.events[args[i]] = me.events[args[i]] || true;
560             }
561         } else {
562             Ext.applyIf(me.events, o);
563         }
564     },
565
566     /**
567      * Checks to see if this object has any listeners for a specified event
568      *
569      * @param {String} eventName The name of the event to check for
570      * @return {Boolean} True if the event is being listened for, else false
571      */
572     hasListener: function(ename) {
573         var event = this.events[ename.toLowerCase()];
574         return event && event.isEvent === true && event.listeners.length > 0;
575     },
576
577     /**
578      * Suspends the firing of all events. (see {@link #resumeEvents})
579      *
580      * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
581      * after the {@link #resumeEvents} call instead of discarding all suspended events.
582      */
583     suspendEvents: function(queueSuspended) {
584         this.eventsSuspended = true;
585         if (queueSuspended && !this.eventQueue) {
586             this.eventQueue = [];
587         }
588     },
589
590     /**
591      * Resumes firing events (see {@link #suspendEvents}).
592      *
593      * If events were suspended using the `queueSuspended` parameter, then all events fired
594      * during event suspension will be sent to any listeners now.
595      */
596     resumeEvents: function() {
597         var me = this,
598             queued = me.eventQueue;
599
600         me.eventsSuspended = false;
601         delete me.eventQueue;
602
603         if (queued) {
604             Ext.each(queued, function(e) {
605                 me.continueFireEvent.apply(me, e);
606             });
607         }
608     },
609
610     /**
611      * Relays selected events from the specified Observable as if the events were fired by `this`.
612      *
613      * @param {Object} origin The Observable whose events this object is to relay.
614      * @param {String[]} events Array of event names to relay.
615      * @param {String} prefix
616      */
617     relayEvents : function(origin, events, prefix) {
618         prefix = prefix || '';
619         var me = this,
620             len = events.length,
621             i = 0,
622             oldName,
623             newName;
624
625         for (; i < len; i++) {
626             oldName = events[i].substr(prefix.length);
627             newName = prefix + oldName;
628             me.events[newName] = me.events[newName] || true;
629             origin.on(oldName, me.createRelayer(newName));
630         }
631     },
632
633     /**
634      * @private
635      * Creates an event handling function which refires the event from this object as the passed event name.
636      * @param newName
637      * @returns {Function}
638      */
639     createRelayer: function(newName){
640         var me = this;
641         return function(){
642             return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.call(arguments, 0, -1)));
643         };
644     },
645
646     /**
647      * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
648      * present. There is no implementation in the Observable base class.
649      *
650      * This is commonly used by Ext.Components to bubble events to owner Containers.
651      * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component returns the
652      * Component's immediate owner. But if a known target is required, this can be overridden to access the
653      * required target more quickly.
654      *
655      * Example:
656      *
657      *     Ext.override(Ext.form.field.Base, {
658      *         //  Add functionality to Field's initComponent to enable the change event to bubble
659      *         initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
660      *             this.enableBubble('change');
661      *         }),
662      *
663      *         //  We know that we want Field's events to bubble directly to the FormPanel.
664      *         getBubbleTarget : function() {
665      *             if (!this.formPanel) {
666      *                 this.formPanel = this.findParentByType('form');
667      *             }
668      *             return this.formPanel;
669      *         }
670      *     });
671      *
672      *     var myForm = new Ext.formPanel({
673      *         title: 'User Details',
674      *         items: [{
675      *             ...
676      *         }],
677      *         listeners: {
678      *             change: function() {
679      *                 // Title goes red if form has been modified.
680      *                 myForm.header.setStyle('color', 'red');
681      *             }
682      *         }
683      *     });
684      *
685      * @param {String/String[]} events The event name to bubble, or an Array of event names.
686      */
687     enableBubble: function(events) {
688         var me = this;
689         if (!Ext.isEmpty(events)) {
690             events = Ext.isArray(events) ? events: Ext.Array.toArray(arguments);
691             Ext.each(events,
692             function(ename) {
693                 ename = ename.toLowerCase();
694                 var ce = me.events[ename] || true;
695                 if (Ext.isBoolean(ce)) {
696                     ce = new Ext.util.Event(me, ename);
697                     me.events[ename] = ce;
698                 }
699                 ce.bubble = true;
700             });
701         }
702     }
703 }, function() {
704
705     this.createAlias({
706         /**
707          * @method
708          * Shorthand for {@link #addListener}.
709          * @alias Ext.util.Observable#addListener
710          */
711         on: 'addListener',
712         /**
713          * @method
714          * Shorthand for {@link #removeListener}.
715          * @alias Ext.util.Observable#removeListener
716          */
717         un: 'removeListener',
718         /**
719          * @method
720          * Shorthand for {@link #addManagedListener}.
721          * @alias Ext.util.Observable#addManagedListener
722          */
723         mon: 'addManagedListener',
724         /**
725          * @method
726          * Shorthand for {@link #removeManagedListener}.
727          * @alias Ext.util.Observable#removeManagedListener
728          */
729         mun: 'removeManagedListener'
730     });
731
732     //deprecated, will be removed in 5.0
733     this.observeClass = this.observe;
734
735     Ext.apply(Ext.util.Observable.prototype, function(){
736         // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
737         // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
738         // private
739         function getMethodEvent(method){
740             var e = (this.methodEvents = this.methodEvents || {})[method],
741                 returnValue,
742                 v,
743                 cancel,
744                 obj = this;
745
746             if (!e) {
747                 this.methodEvents[method] = e = {};
748                 e.originalFn = this[method];
749                 e.methodName = method;
750                 e.before = [];
751                 e.after = [];
752
753                 var makeCall = function(fn, scope, args){
754                     if((v = fn.apply(scope || obj, args)) !== undefined){
755                         if (typeof v == 'object') {
756                             if(v.returnValue !== undefined){
757                                 returnValue = v.returnValue;
758                             }else{
759                                 returnValue = v;
760                             }
761                             cancel = !!v.cancel;
762                         }
763                         else
764                             if (v === false) {
765                                 cancel = true;
766                             }
767                             else {
768                                 returnValue = v;
769                             }
770                     }
771                 };
772
773                 this[method] = function(){
774                     var args = Array.prototype.slice.call(arguments, 0),
775                         b, i, len;
776                     returnValue = v = undefined;
777                     cancel = false;
778
779                     for(i = 0, len = e.before.length; i < len; i++){
780                         b = e.before[i];
781                         makeCall(b.fn, b.scope, args);
782                         if (cancel) {
783                             return returnValue;
784                         }
785                     }
786
787                     if((v = e.originalFn.apply(obj, args)) !== undefined){
788                         returnValue = v;
789                     }
790
791                     for(i = 0, len = e.after.length; i < len; i++){
792                         b = e.after[i];
793                         makeCall(b.fn, b.scope, args);
794                         if (cancel) {
795                             return returnValue;
796                         }
797                     }
798                     return returnValue;
799                 };
800             }
801             return e;
802         }
803
804         return {
805             // these are considered experimental
806             // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
807             // adds an 'interceptor' called before the original method
808             beforeMethod : function(method, fn, scope){
809                 getMethodEvent.call(this, method).before.push({
810                     fn: fn,
811                     scope: scope
812                 });
813             },
814
815             // adds a 'sequence' called after the original method
816             afterMethod : function(method, fn, scope){
817                 getMethodEvent.call(this, method).after.push({
818                     fn: fn,
819                     scope: scope
820                 });
821             },
822
823             removeMethodListener: function(method, fn, scope){
824                 var e = this.getMethodEvent(method),
825                     i, len;
826                 for(i = 0, len = e.before.length; i < len; i++){
827                     if(e.before[i].fn == fn && e.before[i].scope == scope){
828                         Ext.Array.erase(e.before, i, 1);
829                         return;
830                     }
831                 }
832                 for(i = 0, len = e.after.length; i < len; i++){
833                     if(e.after[i].fn == fn && e.after[i].scope == scope){
834                         Ext.Array.erase(e.after, i, 1);
835                         return;
836                     }
837                 }
838             },
839
840             toggleEventLogging: function(toggle) {
841                 Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
842                     if (Ext.isDefined(Ext.global.console)) {
843                         Ext.global.console.log(en, arguments);
844                     }
845                 });
846             }
847         };
848     }());
849 });
850