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