Upgrade to ExtJS 4.0.2 - Released 06/09/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  *             Employee.superclass.constructor.call(this, config)
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 {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 {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 ExtJS {@link Ext.Component Components}**
124      *
125      * While _some_ ExtJs 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)$/,
169
170     /**
171      * Adds listeners to any Observable object (or Element) which are automatically removed when this Component is
172      * destroyed.
173      *
174      * @param {Observable/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 {Observable|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() {
259         var me = this,
260             args = Ext.Array.toArray(arguments),
261             ename = args[0].toLowerCase(),
262             ret = true,
263             event = me.events[ename],
264             queue = me.eventQueue,
265             parent;
266
267         if (me.eventsSuspended === true) {
268             if (queue) {
269                 queue.push(args);
270             }
271         } else if (event && event !== true) {
272             if (event.bubble) {
273                 if (event.fire.apply(event, args.slice(1)) === false) {
274                     return false;
275                 }
276                 parent = me.getBubbleTarget && me.getBubbleTarget();
277                 if (parent && parent.isObservable) {
278                     if (!parent.events[ename] || parent.events[ename] === true || !parent.events[ename].bubble) {
279                         parent.enableBubble(ename);
280                     }
281                     return parent.fireEvent.apply(parent, args);
282                 }                
283             }
284             else {
285                 args.shift();
286                 ret = event.fire.apply(event, args);
287             }
288         }
289         return ret;
290     },
291
292     /**
293      * Appends an event handler to this object.
294      *
295      * @param {String} eventName The name of the event to listen for. May also be an object who's property names are
296      * event names.
297      * @param {Function} handler The method the event invokes.  Will be called with arguments given to
298      * {@link #fireEvent} plus the `options` parameter described below.
299      * @param {Object} scope (optional) The scope (`this` reference) in which the handler function is executed. **If
300      * omitted, defaults to the object which fired the event.**
301      * @param {Object} options (optional) An object containing handler configuration.
302      * 
303      * **Note:** Unlike in ExtJS 3.x, the options object will also be passed as the last argument to every event handler.
304      *
305      * This object may contain any of the following properties:
306      *
307      * - **scope** : Object
308      *
309      *   The scope (`this` reference) in which the handler function is executed. **If omitted, defaults to the object
310      *   which fired the event.**
311      *
312      * - **delay** : Number
313      *
314      *   The number of milliseconds to delay the invocation of the handler after the event fires.
315      *
316      * - **single** : Boolean
317      *
318      *   True to add a handler to handle just the next firing of the event, and then remove itself.
319      *
320      * - **buffer** : Number
321      *
322      *   Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed by the specified number of
323      *   milliseconds. If the event fires again within that time, the original handler is _not_ invoked, but the new
324      *   handler is scheduled in its place.
325      *
326      * - **target** : Observable
327      *
328      *   Only call the handler if the event was fired on the target Observable, _not_ if the event was bubbled up from a
329      *   child Observable.
330      *
331      * - **element** : String
332      *
333      *   **This option is only valid for listeners bound to {@link Ext.Component Components}.** The name of a Component
334      *   property which references an element to add a listener to.
335      *
336      *   This option is useful during Component construction to add DOM event listeners to elements of
337      *   {@link Ext.Component Components} which will exist only after the Component is rendered.
338      *   For example, to add a click listener to a Panel's body:
339      *
340      *       new Ext.panel.Panel({
341      *           title: 'The title',
342      *           listeners: {
343      *               click: this.handlePanelClick,
344      *               element: 'body'
345      *           }
346      *       });
347      *
348      * **Combining Options**
349      *
350      * Using the options argument, it is possible to combine different types of listeners:
351      *
352      * A delayed, one-time listener.
353      *
354      *     myPanel.on('hide', this.handleClick, this, {
355      *         single: true,
356      *         delay: 100
357      *     });
358      *
359      * **Attaching multiple handlers in 1 call**
360      *
361      * The method also allows for a single argument to be passed which is a config object containing properties which
362      * specify multiple events. For example:
363      *
364      *     myGridPanel.on({
365      *         cellClick: this.onCellClick,
366      *         mouseover: this.onMouseOver,
367      *         mouseout: this.onMouseOut,
368      *         scope: this // Important. Ensure "this" is correct during handler execution
369      *     });
370      *
371      * One can also specify options for each event handler separately:
372      *
373      *     myGridPanel.on({
374      *         cellClick: {fn: this.onCellClick, scope: this, single: true},
375      *         mouseover: {fn: panel.onMouseOver, scope: panel}
376      *     });
377      *
378      */
379     addListener: function(ename, fn, scope, options) {
380         var me = this,
381             config,
382             event;
383
384         if (typeof ename !== 'string') {
385             options = ename;
386             for (ename in options) {
387                 if (options.hasOwnProperty(ename)) {
388                     config = options[ename];
389                     if (!me.eventOptionsRe.test(ename)) {
390                         me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
391                     }
392                 }
393             }
394         }
395         else {
396             ename = ename.toLowerCase();
397             me.events[ename] = me.events[ename] || true;
398             event = me.events[ename] || true;
399             if (Ext.isBoolean(event)) {
400                 me.events[ename] = event = new Ext.util.Event(me, ename);
401             }
402             event.addListener(fn, scope, Ext.isObject(options) ? options : {});
403         }
404     },
405
406     /**
407      * Removes an event handler.
408      *
409      * @param {String} eventName The type of event the handler was associated with.
410      * @param {Function} handler The handler to remove. **This must be a reference to the function passed into the
411      * {@link #addListener} call.**
412      * @param {Object} scope (optional) The scope originally specified for the handler.
413      */
414     removeListener: function(ename, fn, scope) {
415         var me = this,
416             config,
417             event,
418             options;
419
420         if (typeof ename !== 'string') {
421             options = ename;
422             for (ename in options) {
423                 if (options.hasOwnProperty(ename)) {
424                     config = options[ename];
425                     if (!me.eventOptionsRe.test(ename)) {
426                         me.removeListener(ename, config.fn || config, config.scope || options.scope);
427                     }
428                 }
429             }
430         } else {
431             ename = ename.toLowerCase();
432             event = me.events[ename];
433             if (event && event.isEvent) {
434                 event.removeListener(fn, scope);
435             }
436         }
437     },
438
439     /**
440      * Removes all listeners for this object including the managed listeners
441      */
442     clearListeners: function() {
443         var events = this.events,
444             event,
445             key;
446
447         for (key in events) {
448             if (events.hasOwnProperty(key)) {
449                 event = events[key];
450                 if (event.isEvent) {
451                     event.clearListeners();
452                 }
453             }
454         }
455
456         this.clearManagedListeners();
457     },
458
459     //<debug>
460     purgeListeners : function() {
461         if (Ext.global.console) {
462             Ext.global.console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
463         }
464         return this.clearListeners.apply(this, arguments);
465     },
466     //</debug>
467
468     /**
469      * Removes all managed listeners for this object.
470      */
471     clearManagedListeners : function() {
472         var managedListeners = this.managedListeners || [],
473             i = 0,
474             len = managedListeners.length;
475
476         for (; i < len; i++) {
477             this.removeManagedListenerItem(true, managedListeners[i]);
478         }
479
480         this.managedListeners = [];
481     },
482     
483     /**
484      * Remove a single managed listener item
485      * @private
486      * @param {Boolean} isClear True if this is being called during a clear
487      * @param {Object} managedListener The managed listener item
488      * See removeManagedListener for other args
489      */
490     removeManagedListenerItem: function(isClear, managedListener, item, ename, fn, scope){
491         if (isClear || (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope))) {
492             managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
493             if (!isClear) {
494                 Ext.Array.remove(this.managedListeners, managedListener);
495             }    
496         }
497     },
498
499     //<debug>
500     purgeManagedListeners : function() {
501         if (Ext.global.console) {
502             Ext.global.console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
503         }
504         return this.clearManagedListeners.apply(this, arguments);
505     },
506     //</debug>
507
508     /**
509      * Adds the specified events to the list of events which this Observable may fire.
510      *
511      * @param {Object/String} o Either an object with event names as properties with a value of `true` or the first
512      * event name string if multiple event names are being passed as separate parameters. Usage:
513      *
514      *     this.addEvents({
515      *         storeloaded: true,
516      *         storecleared: true
517      *     });
518      *
519      * @param {String...} more Optional additional event names if multiple event names are being passed as separate
520      * parameters. Usage:
521      *
522      *     this.addEvents('storeloaded', 'storecleared');
523      *
524      */
525     addEvents: function(o) {
526         var me = this,
527             args,
528             len,
529             i;
530             
531             me.events = me.events || {};
532         if (Ext.isString(o)) {
533             args = arguments;
534             i = args.length;
535             
536             while (i--) {
537                 me.events[args[i]] = me.events[args[i]] || true;
538             }
539         } else {
540             Ext.applyIf(me.events, o);
541         }
542     },
543
544     /**
545      * Checks to see if this object has any listeners for a specified event
546      *
547      * @param {String} eventName The name of the event to check for
548      * @return {Boolean} True if the event is being listened for, else false
549      */
550     hasListener: function(ename) {
551         var event = this.events[ename.toLowerCase()];
552         return event && event.isEvent === true && event.listeners.length > 0;
553     },
554
555     /**
556      * Suspends the firing of all events. (see {@link #resumeEvents})
557      *
558      * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
559      * after the {@link #resumeEvents} call instead of discarding all suspended events.
560      */
561     suspendEvents: function(queueSuspended) {
562         this.eventsSuspended = true;
563         if (queueSuspended && !this.eventQueue) {
564             this.eventQueue = [];
565         }
566     },
567
568     /**
569      * Resumes firing events (see {@link #suspendEvents}).
570      * 
571      * If events were suspended using the `**queueSuspended**` parameter, then all events fired
572      * during event suspension will be sent to any listeners now.
573      */
574     resumeEvents: function() {
575         var me = this,
576             queued = me.eventQueue || [];
577
578         me.eventsSuspended = false;
579         delete me.eventQueue;
580
581         Ext.each(queued,
582         function(e) {
583             me.fireEvent.apply(me, e);
584         });
585     },
586
587     /**
588      * Relays selected events from the specified Observable as if the events were fired by `this`.
589      *
590      * @param {Object} origin The Observable whose events this object is to relay.
591      * @param {[String]} events Array of event names to relay.
592      * @param {Object} prefix
593      */
594     relayEvents : function(origin, events, prefix) {
595         prefix = prefix || '';
596         var me = this,
597             len = events.length,
598             i = 0,
599             oldName,
600             newName;
601
602         for (; i < len; i++) {
603             oldName = events[i].substr(prefix.length);
604             newName = prefix + oldName;
605             me.events[newName] = me.events[newName] || true;
606             origin.on(oldName, me.createRelayer(newName));
607         }
608     },
609
610     /**
611      * @private
612      * Creates an event handling function which refires the event from this object as the passed event name.
613      * @param newName
614      * @returns {Function}
615      */
616     createRelayer: function(newName){
617         var me = this;
618         return function(){
619             return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.call(arguments, 0, -1)));
620         };
621     },
622
623     /**
624      * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
625      * present. There is no implementation in the Observable base class.
626      *
627      * This is commonly used by Ext.Components to bubble events to owner Containers.
628      * See {@link Ext.Component#getBubbleTarget}. The default implementation in Ext.Component returns the
629      * Component's immediate owner. But if a known target is required, this can be overridden to access the
630      * required target more quickly.
631      *
632      * Example:
633      *
634      *     Ext.override(Ext.form.field.Base, {
635      *         //  Add functionality to Field's initComponent to enable the change event to bubble
636      *         initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
637      *             this.enableBubble('change');
638      *         }),
639      *
640      *         //  We know that we want Field's events to bubble directly to the FormPanel.
641      *         getBubbleTarget : function() {
642      *             if (!this.formPanel) {
643      *                 this.formPanel = this.findParentByType('form');
644      *             }
645      *             return this.formPanel;
646      *         }
647      *     });
648      *
649      *     var myForm = new Ext.formPanel({
650      *         title: 'User Details',
651      *         items: [{
652      *             ...
653      *         }],
654      *         listeners: {
655      *             change: function() {
656      *                 // Title goes red if form has been modified.
657      *                 myForm.header.setStyle('color', 'red');
658      *             }
659      *         }
660      *     });
661      *
662      * @param {String/[String]} events The event name to bubble, or an Array of event names.
663      */
664     enableBubble: function(events) {
665         var me = this;
666         if (!Ext.isEmpty(events)) {
667             events = Ext.isArray(events) ? events: Ext.Array.toArray(arguments);
668             Ext.each(events,
669             function(ename) {
670                 ename = ename.toLowerCase();
671                 var ce = me.events[ename] || true;
672                 if (Ext.isBoolean(ce)) {
673                     ce = new Ext.util.Event(me, ename);
674                     me.events[ename] = ce;
675                 }
676                 ce.bubble = true;
677             });
678         }
679     }
680 }, function() {
681
682     this.createAlias({
683         /**
684          * @method
685          * Shorthand for {@link #addListener}.
686          * @alias Ext.util.Observable#addListener
687          */
688         on: 'addListener',
689         /**
690          * @method
691          * Shorthand for {@link #removeListener}.
692          * @alias Ext.util.Observable#removeListener
693          */
694         un: 'removeListener',
695         /**
696          * @method
697          * Shorthand for {@link #addManagedListener}.
698          * @alias Ext.util.Observable#addManagedListener
699          */
700         mon: 'addManagedListener',
701         /**
702          * @method
703          * Shorthand for {@link #removeManagedListener}.
704          * @alias Ext.util.Observable#removeManagedListener
705          */
706         mun: 'removeManagedListener'
707     });
708
709     //deprecated, will be removed in 5.0
710     this.observeClass = this.observe;
711
712     Ext.apply(Ext.util.Observable.prototype, function(){
713         // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
714         // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
715         // private
716         function getMethodEvent(method){
717             var e = (this.methodEvents = this.methodEvents || {})[method],
718                 returnValue,
719                 v,
720                 cancel,
721                 obj = this;
722
723             if (!e) {
724                 this.methodEvents[method] = e = {};
725                 e.originalFn = this[method];
726                 e.methodName = method;
727                 e.before = [];
728                 e.after = [];
729
730                 var makeCall = function(fn, scope, args){
731                     if((v = fn.apply(scope || obj, args)) !== undefined){
732                         if (typeof v == 'object') {
733                             if(v.returnValue !== undefined){
734                                 returnValue = v.returnValue;
735                             }else{
736                                 returnValue = v;
737                             }
738                             cancel = !!v.cancel;
739                         }
740                         else
741                             if (v === false) {
742                                 cancel = true;
743                             }
744                             else {
745                                 returnValue = v;
746                             }
747                     }
748                 };
749
750                 this[method] = function(){
751                     var args = Array.prototype.slice.call(arguments, 0),
752                         b, i, len;
753                     returnValue = v = undefined;
754                     cancel = false;
755
756                     for(i = 0, len = e.before.length; i < len; i++){
757                         b = e.before[i];
758                         makeCall(b.fn, b.scope, args);
759                         if (cancel) {
760                             return returnValue;
761                         }
762                     }
763
764                     if((v = e.originalFn.apply(obj, args)) !== undefined){
765                         returnValue = v;
766                     }
767
768                     for(i = 0, len = e.after.length; i < len; i++){
769                         b = e.after[i];
770                         makeCall(b.fn, b.scope, args);
771                         if (cancel) {
772                             return returnValue;
773                         }
774                     }
775                     return returnValue;
776                 };
777             }
778             return e;
779         }
780
781         return {
782             // these are considered experimental
783             // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
784             // adds an 'interceptor' called before the original method
785             beforeMethod : function(method, fn, scope){
786                 getMethodEvent.call(this, method).before.push({
787                     fn: fn,
788                     scope: scope
789                 });
790             },
791
792             // adds a 'sequence' called after the original method
793             afterMethod : function(method, fn, scope){
794                 getMethodEvent.call(this, method).after.push({
795                     fn: fn,
796                     scope: scope
797                 });
798             },
799
800             removeMethodListener: function(method, fn, scope){
801                 var e = this.getMethodEvent(method),
802                     i, len;
803                 for(i = 0, len = e.before.length; i < len; i++){
804                     if(e.before[i].fn == fn && e.before[i].scope == scope){
805                         Ext.Array.erase(e.before, i, 1);
806                         return;
807                     }
808                 }
809                 for(i = 0, len = e.after.length; i < len; i++){
810                     if(e.after[i].fn == fn && e.after[i].scope == scope){
811                         Ext.Array.erase(e.after, i, 1);
812                         return;
813                     }
814                 }
815             },
816
817             toggleEventLogging: function(toggle) {
818                 Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
819                     if (Ext.isDefined(Ext.global.console)) {
820                         Ext.global.console.log(en, arguments);
821                     }
822                 });
823             }
824         };
825     }());
826 });
827