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