Upgrade to ExtJS 3.2.1 - Released 04/27/2010
[extjs.git] / docs / source / Observable.html
1 <html>
2 <head>
3   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    
4   <title>The source code</title>
5     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
6     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
7 </head>
8 <body  onload="prettyPrint();">
9     <pre class="prettyprint lang-js">/*!
10  * Ext JS Library 3.2.1
11  * Copyright(c) 2006-2010 Ext JS, Inc.
12  * licensing@extjs.com
13  * http://www.extjs.com/license
14  */
15 (function(){
16
17 var EXTUTIL = Ext.util,
18     EACH = Ext.each,
19     TRUE = true,
20     FALSE = false;
21 <div id="cls-Ext.util.Observable"></div>/**
22  * @class Ext.util.Observable
23  * Base class that provides a common interface for publishing events. Subclasses are expected to
24  * to have a property "events" with all the events defined, and, optionally, a property "listeners"
25  * with configured listeners defined.<br>
26  * For example:
27  * <pre><code>
28 Employee = Ext.extend(Ext.util.Observable, {
29     constructor: function(config){
30         this.name = config.name;
31         this.addEvents({
32             "fired" : true,
33             "quit" : true
34         });
35
36         // Copy configured listeners into *this* object so that the base class&#39;s
37         // constructor will add them.
38         this.listeners = config.listeners;
39
40         // Call our superclass constructor to complete construction process.
41         Employee.superclass.constructor.call(this, config)
42     }
43 });
44 </code></pre>
45  * This could then be used like this:<pre><code>
46 var newEmployee = new Employee({
47     name: employeeName,
48     listeners: {
49         quit: function() {
50             // By default, "this" will be the object that fired the event.
51             alert(this.name + " has quit!");
52         }
53     }
54 });
55 </code></pre>
56  */
57 EXTUTIL.Observable = function(){
58     <div id="cfg-Ext.util.Observable-listeners"></div>/**
59      * @cfg {Object} listeners (optional) <p>A config object containing one or more event handlers to be added to this
60      * object during initialization.  This should be a valid listeners config object as specified in the
61      * {@link #addListener} example for attaching multiple handlers at once.</p>
62      * <br><p><b><u>DOM events from ExtJs {@link Ext.Component Components}</u></b></p>
63      * <br><p>While <i>some</i> ExtJs Component classes export selected DOM events (e.g. "click", "mouseover" etc), this
64      * is usually only done when extra value can be added. For example the {@link Ext.DataView DataView}'s
65      * <b><code>{@link Ext.DataView#click click}</code></b> event passing the node clicked on. To access DOM
66      * events directly from a Component's HTMLElement, listeners must be added to the <i>{@link Ext.Component#getEl Element}</i> after the Component
67      * has been rendered. A plugin can simplify this step:<pre><code>
68 // Plugin is configured with a listeners config object.
69 // The Component is appended to the argument list of all handler functions.
70 Ext.DomObserver = Ext.extend(Object, {
71     constructor: function(config) {
72         this.listeners = config.listeners ? config.listeners : config;
73     },
74
75     // Component passes itself into plugin&#39;s init method
76     init: function(c) {
77         var p, l = this.listeners;
78         for (p in l) {
79             if (Ext.isFunction(l[p])) {
80                 l[p] = this.createHandler(l[p], c);
81             } else {
82                 l[p].fn = this.createHandler(l[p].fn, c);
83             }
84         }
85
86         // Add the listeners to the Element immediately following the render call
87         c.render = c.render.{@link Function#createSequence createSequence}(function() {
88             var e = c.getEl();
89             if (e) {
90                 e.on(l);
91             }
92         });
93     },
94
95     createHandler: function(fn, c) {
96         return function(e) {
97             fn.call(this, e, c);
98         };
99     }
100 });
101
102 var combo = new Ext.form.ComboBox({
103
104     // Collapse combo when its element is clicked on
105     plugins: [ new Ext.DomObserver({
106         click: function(evt, comp) {
107             comp.collapse();
108         }
109     })],
110     store: myStore,
111     typeAhead: true,
112     mode: 'local',
113     triggerAction: 'all'
114 });
115      * </code></pre></p>
116      */
117     var me = this, e = me.events;
118     if(me.listeners){
119         me.on(me.listeners);
120         delete me.listeners;
121     }
122     me.events = e || {};
123 };
124
125 EXTUTIL.Observable.prototype = {
126     // private
127     filterOptRe : /^(?:scope|delay|buffer|single)$/,
128
129     <div id="method-Ext.util.Observable-fireEvent"></div>/**
130      * <p>Fires the specified event with the passed parameters (minus the event name).</p>
131      * <p>An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget})
132      * by calling {@link #enableBubble}.</p>
133      * @param {String} eventName The name of the event to fire.
134      * @param {Object...} args Variable number of parameters are passed to handlers.
135      * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
136      */
137     fireEvent : function(){
138         var a = Array.prototype.slice.call(arguments, 0),
139             ename = a[0].toLowerCase(),
140             me = this,
141             ret = TRUE,
142             ce = me.events[ename],
143             cc,
144             q,
145             c;
146         if (me.eventsSuspended === TRUE) {
147             if (q = me.eventQueue) {
148                 q.push(a);
149             }
150         }
151         else if(typeof ce == 'object') {
152             if (ce.bubble){
153                 if(ce.fire.apply(ce, a.slice(1)) === FALSE) {
154                     return FALSE;
155                 }
156                 c = me.getBubbleTarget && me.getBubbleTarget();
157                 if(c && c.enableBubble) {
158                     cc = c.events[ename];
159                     if(!cc || typeof cc != 'object' || !cc.bubble) {
160                         c.enableBubble(ename);
161                     }
162                     return c.fireEvent.apply(c, a);
163                 }
164             }
165             else {
166                 a.shift();
167                 ret = ce.fire.apply(ce, a);
168             }
169         }
170         return ret;
171     },
172
173     <div id="method-Ext.util.Observable-addListener"></div>/**
174      * Appends an event handler to this object.
175      * @param {String}   eventName The name of the event to listen for.
176      * @param {Function} handler The method the event invokes.
177      * @param {Object}   scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
178      * <b>If omitted, defaults to the object which fired the event.</b>
179      * @param {Object}   options (optional) An object containing handler configuration.
180      * properties. This may contain any of the following properties:<ul>
181      * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
182      * <b>If omitted, defaults to the object which fired the event.</b></div></li>
183      * <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>
184      * <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>
185      * <li><b>buffer</b> : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
186      * by the specified number of milliseconds. If the event fires again within that time, the original
187      * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
188      * <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>
189      * if the event was bubbled up from a child Observable.</div></li>
190      * </ul><br>
191      * <p>
192      * <b>Combining Options</b><br>
193      * Using the options argument, it is possible to combine different types of listeners:<br>
194      * <br>
195      * A delayed, one-time listener.
196      * <pre><code>
197 myDataView.on('click', this.onClick, this, {
198 single: true,
199 delay: 100
200 });</code></pre>
201      * <p>
202      * <b>Attaching multiple handlers in 1 call</b><br>
203      * The method also allows for a single argument to be passed which is a config object containing properties
204      * which specify multiple handlers.
205      * <p>
206      * <pre><code>
207 myGridPanel.on({
208 'click' : {
209     fn: this.onClick,
210     scope: this,
211     delay: 100
212 },
213 'mouseover' : {
214     fn: this.onMouseOver,
215     scope: this
216 },
217 'mouseout' : {
218     fn: this.onMouseOut,
219     scope: this
220 }
221 });</code></pre>
222  * <p>
223  * Or a shorthand syntax:<br>
224  * <pre><code>
225 myGridPanel.on({
226 'click' : this.onClick,
227 'mouseover' : this.onMouseOver,
228 'mouseout' : this.onMouseOut,
229  scope: this
230 });</code></pre>
231      */
232     addListener : function(eventName, fn, scope, o){
233         var me = this,
234             e,
235             oe,
236             isF,
237         ce;
238         if (typeof eventName == 'object') {
239             o = eventName;
240             for (e in o){
241                 oe = o[e];
242                 if (!me.filterOptRe.test(e)) {
243                     me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o);
244                 }
245             }
246         } else {
247             eventName = eventName.toLowerCase();
248             ce = me.events[eventName] || TRUE;
249             if (typeof ce == 'boolean') {
250                 me.events[eventName] = ce = new EXTUTIL.Event(me, eventName);
251             }
252             ce.addListener(fn, scope, typeof o == 'object' ? o : {});
253         }
254     },
255
256     <div id="method-Ext.util.Observable-removeListener"></div>/**
257      * Removes an event handler.
258      * @param {String}   eventName The type of event the handler was associated with.
259      * @param {Function} handler   The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
260      * @param {Object}   scope     (optional) The scope originally specified for the handler.
261      */
262     removeListener : function(eventName, fn, scope){
263         var ce = this.events[eventName.toLowerCase()];
264         if (typeof ce == 'object') {
265             ce.removeListener(fn, scope);
266         }
267     },
268
269     <div id="method-Ext.util.Observable-purgeListeners"></div>/**
270      * Removes all listeners for this object
271      */
272     purgeListeners : function(){
273         var events = this.events,
274             evt,
275             key;
276         for(key in events){
277             evt = events[key];
278             if(typeof evt == 'object'){
279                 evt.clearListeners();
280             }
281         }
282     },
283
284     <div id="method-Ext.util.Observable-addEvents"></div>/**
285      * Adds the specified events to the list of events which this Observable may fire.
286      * @param {Object|String} o Either an object with event names as properties with a value of <code>true</code>
287      * or the first event name string if multiple event names are being passed as separate parameters.
288      * @param {string} Optional. Event name if multiple event names are being passed as separate parameters.
289      * Usage:<pre><code>
290 this.addEvents('storeloaded', 'storecleared');
291 </code></pre>
292      */
293     addEvents : function(o){
294         var me = this;
295         me.events = me.events || {};
296         if (typeof o == 'string') {
297             var a = arguments,
298                 i = a.length;
299             while(i--) {
300                 me.events[a[i]] = me.events[a[i]] || TRUE;
301             }
302         } else {
303             Ext.applyIf(me.events, o);
304         }
305     },
306
307     <div id="method-Ext.util.Observable-hasListener"></div>/**
308      * Checks to see if this object has any listeners for a specified event
309      * @param {String} eventName The name of the event to check for
310      * @return {Boolean} True if the event is being listened for, else false
311      */
312     hasListener : function(eventName){
313         var e = this.events[eventName.toLowerCase()];
314         return typeof e == 'object' && e.listeners.length > 0;
315     },
316
317     <div id="method-Ext.util.Observable-suspendEvents"></div>/**
318      * Suspend the firing of all events. (see {@link #resumeEvents})
319      * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
320      * after the {@link #resumeEvents} call instead of discarding all suspended events;
321      */
322     suspendEvents : function(queueSuspended){
323         this.eventsSuspended = TRUE;
324         if(queueSuspended && !this.eventQueue){
325             this.eventQueue = [];
326         }
327     },
328
329     <div id="method-Ext.util.Observable-resumeEvents"></div>/**
330      * Resume firing events. (see {@link #suspendEvents})
331      * If events were suspended using the <tt><b>queueSuspended</b></tt> parameter, then all
332      * events fired during event suspension will be sent to any listeners now.
333      */
334     resumeEvents : function(){
335         var me = this,
336             queued = me.eventQueue || [];
337         me.eventsSuspended = FALSE;
338         delete me.eventQueue;
339         EACH(queued, function(e) {
340             me.fireEvent.apply(me, e);
341         });
342     }
343 };
344
345 var OBSERVABLE = EXTUTIL.Observable.prototype;
346 <div id="method-Ext.util.Observable-on"></div>/**
347  * Appends an event handler to this object (shorthand for {@link #addListener}.)
348  * @param {String}   eventName     The type of event to listen for
349  * @param {Function} handler       The method the event invokes
350  * @param {Object}   scope         (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
351  * <b>If omitted, defaults to the object which fired the event.</b>
352  * @param {Object}   options       (optional) An object containing handler configuration.
353  * @method
354  */
355 OBSERVABLE.on = OBSERVABLE.addListener;
356 <div id="method-Ext.util.Observable-un"></div>/**
357  * Removes an event handler (shorthand for {@link #removeListener}.)
358  * @param {String}   eventName     The type of event the handler was associated with.
359  * @param {Function} handler       The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
360  * @param {Object}   scope         (optional) The scope originally specified for the handler.
361  * @method
362  */
363 OBSERVABLE.un = OBSERVABLE.removeListener;
364
365 <div id="method-Ext.util.Observable-Observable.releaseCapture"></div>/**
366  * Removes <b>all</b> added captures from the Observable.
367  * @param {Observable} o The Observable to release
368  * @static
369  */
370 EXTUTIL.Observable.releaseCapture = function(o){
371     o.fireEvent = OBSERVABLE.fireEvent;
372 };
373
374 function createTargeted(h, o, scope){
375     return function(){
376         if(o.target == arguments[0]){
377             h.apply(scope, Array.prototype.slice.call(arguments, 0));
378         }
379     };
380 };
381
382 function createBuffered(h, o, l, scope){
383     l.task = new EXTUTIL.DelayedTask();
384     return function(){
385         l.task.delay(o.buffer, h, scope, Array.prototype.slice.call(arguments, 0));
386     };
387 };
388
389 function createSingle(h, e, fn, scope){
390     return function(){
391         e.removeListener(fn, scope);
392         return h.apply(scope, arguments);
393     };
394 };
395
396 function createDelayed(h, o, l, scope){
397     return function(){
398         var task = new EXTUTIL.DelayedTask();
399         if(!l.tasks) {
400             l.tasks = [];
401         }
402         l.tasks.push(task);
403         task.delay(o.delay || 10, h, scope, Array.prototype.slice.call(arguments, 0));
404     };
405 };
406
407 EXTUTIL.Event = function(obj, name){
408     this.name = name;
409     this.obj = obj;
410     this.listeners = [];
411 };
412
413 EXTUTIL.Event.prototype = {
414     addListener : function(fn, scope, options){
415         var me = this,
416             l;
417         scope = scope || me.obj;
418         if(!me.isListening(fn, scope)){
419             l = me.createListener(fn, scope, options);
420             if(me.firing){ // if we are currently firing this event, don't disturb the listener loop
421                 me.listeners = me.listeners.slice(0);
422             }
423             me.listeners.push(l);
424         }
425     },
426
427     createListener: function(fn, scope, o){
428         o = o || {}, scope = scope || this.obj;
429         var l = {
430             fn: fn,
431             scope: scope,
432             options: o
433         }, h = fn;
434         if(o.target){
435             h = createTargeted(h, o, scope);
436         }
437         if(o.delay){
438             h = createDelayed(h, o, l, scope);
439         }
440         if(o.single){
441             h = createSingle(h, this, fn, scope);
442         }
443         if(o.buffer){
444             h = createBuffered(h, o, l, scope);
445         }
446         l.fireFn = h;
447         return l;
448     },
449
450     findListener : function(fn, scope){
451         var list = this.listeners,
452             i = list.length,
453             l;
454
455         scope = scope || this.obj;
456         while(i--){
457             l = list[i];
458             if(l){
459                 if(l.fn == fn && l.scope == scope){
460                     return i;
461                 }
462             }
463         }
464         return -1;
465     },
466
467     isListening : function(fn, scope){
468         return this.findListener(fn, scope) != -1;
469     },
470
471     removeListener : function(fn, scope){
472         var index,
473             l,
474             k,
475             me = this,
476             ret = FALSE;
477         if((index = me.findListener(fn, scope)) != -1){
478             if (me.firing) {
479                 me.listeners = me.listeners.slice(0);
480             }
481             l = me.listeners[index];
482             if(l.task) {
483                 l.task.cancel();
484                 delete l.task;
485             }
486             k = l.tasks && l.tasks.length;
487             if(k) {
488                 while(k--) {
489                     l.tasks[k].cancel();
490                 }
491                 delete l.tasks;
492             }
493             me.listeners.splice(index, 1);
494             ret = TRUE;
495         }
496         return ret;
497     },
498
499     // Iterate to stop any buffered/delayed events
500     clearListeners : function(){
501         var me = this,
502             l = me.listeners,
503             i = l.length;
504         while(i--) {
505             me.removeListener(l[i].fn, l[i].scope);
506         }
507     },
508
509     fire : function(){
510         var me = this,
511             listeners = me.listeners,
512             len = listeners.length,
513             i = 0,
514             l;
515
516         if(len > 0){
517             me.firing = TRUE;
518             var args = Array.prototype.slice.call(arguments, 0);
519             for (; i < len; i++) {
520                 l = listeners[i];
521                 if(l && l.fireFn.apply(l.scope || me.obj || window, args) === FALSE) {
522                     return (me.firing = FALSE);
523                 }
524             }
525         }
526         me.firing = FALSE;
527         return TRUE;
528     }
529
530 };
531 })();
532 </pre>    
533 </body>
534 </html>