Upgrade to ExtJS 3.0.0 - Released 07/06/2009
[extjs.git] / docs / source / Observable.html
1 <html>\r
2 <head>\r
3   <title>The source code</title>\r
4     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />\r
5     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>\r
6 </head>\r
7 <body  onload="prettyPrint();">\r
8     <pre class="prettyprint lang-js">(function(){
9
10 var EXTUTIL = Ext.util,
11     TOARRAY = Ext.toArray,
12     EACH = Ext.each,
13     ISOBJECT = Ext.isObject,
14     TRUE = true,
15     FALSE = false;
16 <div id="cls-Ext.util.Observable"></div>/**
17  * @class Ext.util.Observable
18  * Base class that provides a common interface for publishing events. Subclasses are expected to
19  * to have a property "events" with all the events defined, and, optionally, a property "listeners"
20  * with configured listeners defined.<br>
21  * For example:
22  * <pre><code>
23 Employee = Ext.extend(Ext.util.Observable, {
24     constructor: function(config){
25         this.name = config.name;
26         this.addEvents({
27             "fired" : true,
28             "quit" : true
29         });
30
31         // Copy configured listeners into *this* object so that the base class&#39;s
32         // constructor will add them.
33         this.listeners = config.listeners;
34
35         // Call our superclass constructor to complete construction process.
36         Employee.superclass.constructor.call(config)
37     }
38 });
39 </code></pre>
40  * This could then be used like this:<pre><code>
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 </code></pre>
51  */
52 EXTUTIL.Observable = function(){
53     <div id="cfg-Ext.util.Observable-listeners"></div>/**
54      * @cfg {Object} listeners (optional) <p>A config object containing one or more event handlers to be added to this
55      * object during initialization.  This should be a valid listeners config object as specified in the
56      * {@link #addListener} example for attaching multiple handlers at once.</p>
57      * <br><p><b><u>DOM events from ExtJs {@link Ext.Component Components}</u></b></p>
58      * <br><p>While <i>some</i> ExtJs Component classes export selected DOM events (e.g. "click", "mouseover" etc), this
59      * is usually only done when extra value can be added. For example the {@link Ext.DataView DataView}'s
60      * <b><code>{@link Ext.DataView#click click}</code></b> event passing the node clicked on. To access DOM
61      * events directly from a Component's HTMLElement, listeners must be added to the <i>{@link Ext.Component#getEl Element}</i> after the Component
62      * has been rendered. A plugin can simplify this step:<pre><code>
63 // Plugin is configured with a listeners config object.
64 // The Component is appended to the argument list of all handler functions.
65 Ext.DomObserver = Ext.extend(Object, {
66     constructor: function(config) {
67         this.listeners = config.listeners ? config.listeners : config;
68     },
69
70     // Component passes itself into plugin&#39;s init method
71     init: function(c) {
72         var p, l = this.listeners;
73         for (p in l) {
74             if (Ext.isFunction(l[p])) {
75                 l[p] = this.createHandler(l[p], c);
76             } else {
77                 l[p].fn = this.createHandler(l[p].fn, c);
78             }
79         }
80
81         // Add the listeners to the Element immediately following the render call
82         c.render = c.render.{@link Function#createSequence createSequence}(function() {
83             var e = c.getEl();
84             if (e) {
85                 e.on(l);
86             }
87         });
88     },
89
90     createHandler: function(fn, c) {
91         return function(e) {
92             fn.call(this, e, c);
93         };
94     }
95 });
96
97 var combo = new Ext.form.ComboBox({
98
99     // Collapse combo when its element is clicked on
100     plugins: [ new Ext.DomObserver({
101         click: function(evt, comp) {
102             comp.collapse();
103         }
104     })],
105     store: myStore,
106     typeAhead: true,
107     mode: 'local',
108     triggerAction: 'all'
109 });
110      * </code></pre></p>
111      */
112     var me = this, e = me.events;
113     if(me.listeners){
114         me.on(me.listeners);
115         delete me.listeners;
116     }
117     me.events = e || {};
118 };
119
120 EXTUTIL.Observable.prototype = function(){
121     var filterOptRe = /^(?:scope|delay|buffer|single)$/, toLower = function(s){
122         return s.toLowerCase();
123     };
124
125     return {
126         <div id="method-Ext.util.Observable-fireEvent"></div>/**
127          * <p>Fires the specified event with the passed parameters (minus the event name).</p>
128          * <p>An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget})
129          * by calling {@link #enableBubble}.</p>
130          * @param {String} eventName The name of the event to fire.
131          * @param {Object...} args Variable number of parameters are passed to handlers.
132          * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
133          */
134
135         fireEvent : function(){
136             var a = TOARRAY(arguments),
137                 ename = toLower(a[0]),
138                 me = this,
139                 ret = TRUE,
140                 ce = me.events[ename],
141                 q,
142                 c;
143             if (me.eventsSuspended === TRUE) {
144                 if (q = me.suspendedEventsQueue) {
145                     q.push(a);
146                 }
147             }
148             else if(ISOBJECT(ce) && ce.bubble){
149                 if(ce.fire.apply(ce, a.slice(1)) === FALSE) {
150                     return FALSE;
151                 }
152                 c = me.getBubbleTarget && me.getBubbleTarget();
153                 if(c && c.enableBubble) {
154                     c.enableBubble(ename);
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 (!filterOptRe.test(e)) {
237                         me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o);
238                     }
239                 }
240             } else {
241                 eventName = toLower(eventName);
242                 ce = me.events[eventName] || TRUE;
243                 if (typeof ce == "boolean") {
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[toLower(eventName)];
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          * Used to define events on this Observable
280          * @param {Object} object The object with the events defined
281          */
282         addEvents : function(o){
283             var me = this;
284             me.events = me.events || {};
285             if (typeof o == 'string') {
286                 EACH(arguments, function(a) {
287                     me.events[a] = me.events[a] || TRUE;
288                 });
289             } else {
290                 Ext.applyIf(me.events, o);
291             }
292         },
293
294         <div id="method-Ext.util.Observable-hasListener"></div>/**
295          * Checks to see if this object has any listeners for a specified event
296          * @param {String} eventName The name of the event to check for
297          * @return {Boolean} True if the event is being listened for, else false
298          */
299         hasListener : function(eventName){
300             var e = this.events[eventName];
301             return ISOBJECT(e) && e.listeners.length > 0;
302         },
303
304         <div id="method-Ext.util.Observable-suspendEvents"></div>/**
305          * Suspend the firing of all events. (see {@link #resumeEvents})
306          * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
307          * after the {@link #resumeEvents} call instead of discarding all suspended events;
308          */
309         suspendEvents : function(queueSuspended){
310             this.eventsSuspended = TRUE;
311             if (queueSuspended){
312                 this.suspendedEventsQueue = [];
313             }
314         },
315
316         <div id="method-Ext.util.Observable-resumeEvents"></div>/**
317          * Resume firing events. (see {@link #suspendEvents})
318          * If events were suspended using the <tt><b>queueSuspended</b></tt> parameter, then all
319          * events fired during event suspension will be sent to any listeners now.
320          */
321         resumeEvents : function(){
322             var me = this;
323             me.eventsSuspended = !delete me.suspendedEventQueue;
324             EACH(me.suspendedEventsQueue, function(e) {
325                 me.fireEvent.apply(me, e);
326             });
327         }
328     }
329 }();
330
331 var OBSERVABLE = EXTUTIL.Observable.prototype;
332 <div id="method-Ext.util.Observable-on"></div>/**
333  * Appends an event handler to this object (shorthand for {@link #addListener}.)
334  * @param {String}   eventName     The type of event to listen for
335  * @param {Function} handler       The method the event invokes
336  * @param {Object}   scope         (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
337  * <b>If omitted, defaults to the object which fired the event.</b>
338  * @param {Object}   options       (optional) An object containing handler configuration.
339  * @method
340  */
341 OBSERVABLE.on = OBSERVABLE.addListener;
342 <div id="method-Ext.util.Observable-un"></div>/**
343  * Removes an event handler (shorthand for {@link #removeListener}.)
344  * @param {String}   eventName     The type of event the handler was associated with.
345  * @param {Function} handler       The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
346  * @param {Object}   scope         (optional) The scope originally specified for the handler.
347  * @method
348  */
349 OBSERVABLE.un = OBSERVABLE.removeListener;
350
351 <div id="method-Ext.util.Observable-Observable.releaseCapture"></div>/**
352  * Removes <b>all</b> added captures from the Observable.
353  * @param {Observable} o The Observable to release
354  * @static
355  */
356 EXTUTIL.Observable.releaseCapture = function(o){
357     o.fireEvent = OBSERVABLE.fireEvent;
358 };
359
360 function createTargeted(h, o, scope){
361     return function(){
362         if(o.target == arguments[0]){
363             h.apply(scope, TOARRAY(arguments));
364         }
365     };
366 };
367
368 function createBuffered(h, o, scope){
369     var task = new EXTUTIL.DelayedTask();
370     return function(){
371         task.delay(o.buffer, h, scope, TOARRAY(arguments));
372     };
373 }
374
375 function createSingle(h, e, fn, scope){
376     return function(){
377         e.removeListener(fn, scope);
378         return h.apply(scope, arguments);
379     };
380 }
381
382 function createDelayed(h, o, scope){
383     return function(){
384         var args = TOARRAY(arguments);
385         (function(){
386             h.apply(scope, args);
387         }).defer(o.delay || 10);
388     };
389 };
390
391 EXTUTIL.Event = function(obj, name){
392     this.name = name;
393     this.obj = obj;
394     this.listeners = [];
395 };
396
397 EXTUTIL.Event.prototype = {
398     addListener : function(fn, scope, options){
399         var me = this,
400             l;
401         scope = scope || me.obj;
402         if(!me.isListening(fn, scope)){
403             l = me.createListener(fn, scope, options);
404             if(me.firing){ // if we are currently firing this event, don't disturb the listener loop
405                 me.listeners = me.listeners.slice(0);
406             }
407             me.listeners.push(l);
408         }
409     },
410
411     createListener: function(fn, scope, o){
412         o = o || {}, scope = scope || this.obj;
413         var l = {
414             fn: fn,
415             scope: scope,
416             options: o
417         }, h = fn;
418         if(o.target){
419             h = createTargeted(h, o, scope);
420         }
421         if(o.delay){
422             h = createDelayed(h, o, scope);
423         }
424         if(o.single){
425             h = createSingle(h, this, fn, scope);
426         }
427         if(o.buffer){
428             h = createBuffered(h, o, scope);
429         }
430         l.fireFn = h;
431         return l;
432     },
433
434     findListener : function(fn, scope){
435         var s, ret = -1;
436         EACH(this.listeners, function(l, i) {
437             s = l.scope;
438             if(l.fn == fn && (s == scope || s == this.obj)){
439                 ret = i;
440                 return FALSE;
441             }
442         },
443         this);
444         return ret;
445     },
446
447     isListening : function(fn, scope){
448         return this.findListener(fn, scope) != -1;
449     },
450
451     removeListener : function(fn, scope){
452         var index,
453             me = this,
454             ret = FALSE;
455         if((index = me.findListener(fn, scope)) != -1){
456             if (me.firing) {
457                 me.listeners = me.listeners.slice(0);
458             }
459             me.listeners.splice(index, 1);
460             ret = TRUE;
461         }
462         return ret;
463     },
464
465     clearListeners : function(){
466         this.listeners = [];
467     },
468
469     fire : function(){
470         var me = this,
471             args = TOARRAY(arguments),
472             ret = TRUE;
473
474         EACH(me.listeners, function(l) {
475             me.firing = TRUE;
476             if (l.fireFn.apply(l.scope || me.obj || window, args) === FALSE) {
477                 return ret = me.firing = FALSE;
478             }
479         });
480         me.firing = FALSE;
481         return ret;
482     }
483 };
484 })();</pre>    \r
485 </body>\r
486 </html>