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