Upgrade to ExtJS 3.2.1 - Released 04/27/2010
[extjs.git] / src / util / Observable-more.js
1 /*!
2  * Ext JS Library 3.2.1
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.util.Observable
9  */
10 Ext.apply(Ext.util.Observable.prototype, function(){
11     // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
12     // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
13     // private
14     function getMethodEvent(method){
15         var e = (this.methodEvents = this.methodEvents ||
16         {})[method], returnValue, v, cancel, obj = this;
17
18         if (!e) {
19             this.methodEvents[method] = e = {};
20             e.originalFn = this[method];
21             e.methodName = method;
22             e.before = [];
23             e.after = [];
24
25             var makeCall = function(fn, scope, args){
26                 if((v = fn.apply(scope || obj, args)) !== undefined){
27                     if (typeof v == 'object') {
28                         if(v.returnValue !== undefined){
29                             returnValue = v.returnValue;
30                         }else{
31                             returnValue = v;
32                         }
33                         cancel = !!v.cancel;
34                     }
35                     else
36                         if (v === false) {
37                             cancel = true;
38                         }
39                         else {
40                             returnValue = v;
41                         }
42                 }
43             };
44
45             this[method] = function(){
46                 var args = Array.prototype.slice.call(arguments, 0),
47                     b;
48                 returnValue = v = undefined;
49                 cancel = false;
50
51                 for(var i = 0, len = e.before.length; i < len; i++){
52                     b = e.before[i];
53                     makeCall(b.fn, b.scope, args);
54                     if (cancel) {
55                         return returnValue;
56                     }
57                 }
58
59                 if((v = e.originalFn.apply(obj, args)) !== undefined){
60                     returnValue = v;
61                 }
62
63                 for(var i = 0, len = e.after.length; i < len; i++){
64                     b = e.after[i];
65                     makeCall(b.fn, b.scope, args);
66                     if (cancel) {
67                         return returnValue;
68                     }
69                 }
70                 return returnValue;
71             };
72         }
73         return e;
74     }
75
76     return {
77         // these are considered experimental
78         // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
79         // adds an 'interceptor' called before the original method
80         beforeMethod : function(method, fn, scope){
81             getMethodEvent.call(this, method).before.push({
82                 fn: fn,
83                 scope: scope
84             });
85         },
86
87         // adds a 'sequence' called after the original method
88         afterMethod : function(method, fn, scope){
89             getMethodEvent.call(this, method).after.push({
90                 fn: fn,
91                 scope: scope
92             });
93         },
94
95         removeMethodListener: function(method, fn, scope){
96             var e = this.getMethodEvent(method);
97             for(var i = 0, len = e.before.length; i < len; i++){
98                 if(e.before[i].fn == fn && e.before[i].scope == scope){
99                     e.before.splice(i, 1);
100                     return;
101                 }
102             }
103             for(var i = 0, len = e.after.length; i < len; i++){
104                 if(e.after[i].fn == fn && e.after[i].scope == scope){
105                     e.after.splice(i, 1);
106                     return;
107                 }
108             }
109         },
110
111         /**
112          * Relays selected events from the specified Observable as if the events were fired by <tt><b>this</b></tt>.
113          * @param {Object} o The Observable whose events this object is to relay.
114          * @param {Array} events Array of event names to relay.
115          */
116         relayEvents : function(o, events){
117             var me = this;
118             function createHandler(ename){
119                 return function(){
120                     return me.fireEvent.apply(me, [ename].concat(Array.prototype.slice.call(arguments, 0)));
121                 };
122             }
123             for(var i = 0, len = events.length; i < len; i++){
124                 var ename = events[i];
125                 me.events[ename] = me.events[ename] || true;
126                 o.on(ename, createHandler(ename), me);
127             }
128         },
129
130         /**
131          * <p>Enables events fired by this Observable to bubble up an owner hierarchy by calling
132          * <code>this.getBubbleTarget()</code> if present. There is no implementation in the Observable base class.</p>
133          * <p>This is commonly used by Ext.Components to bubble events to owner Containers. See {@link Ext.Component.getBubbleTarget}. The default
134          * implementation in Ext.Component returns the Component's immediate owner. But if a known target is required, this can be overridden to
135          * access the required target more quickly.</p>
136          * <p>Example:</p><pre><code>
137 Ext.override(Ext.form.Field, {
138     //  Add functionality to Field&#39;s initComponent to enable the change event to bubble
139     initComponent : Ext.form.Field.prototype.initComponent.createSequence(function() {
140         this.enableBubble('change');
141     }),
142
143     //  We know that we want Field&#39;s events to bubble directly to the FormPanel.
144     getBubbleTarget : function() {
145         if (!this.formPanel) {
146             this.formPanel = this.findParentByType('form');
147         }
148         return this.formPanel;
149     }
150 });
151
152 var myForm = new Ext.formPanel({
153     title: 'User Details',
154     items: [{
155         ...
156     }],
157     listeners: {
158         change: function() {
159             // Title goes red if form has been modified.
160             myForm.header.setStyle('color', 'red');
161         }
162     }
163 });
164 </code></pre>
165          * @param {String/Array} events The event name to bubble, or an Array of event names.
166          */
167         enableBubble : function(events){
168             var me = this;
169             if(!Ext.isEmpty(events)){
170                 events = Ext.isArray(events) ? events : Array.prototype.slice.call(arguments, 0);
171                 for(var i = 0, len = events.length; i < len; i++){
172                     var ename = events[i];
173                     ename = ename.toLowerCase();
174                     var ce = me.events[ename] || true;
175                     if (typeof ce == 'boolean') {
176                         ce = new Ext.util.Event(me, ename);
177                         me.events[ename] = ce;
178                     }
179                     ce.bubble = true;
180                 }
181             }
182         }
183     };
184 }());
185
186
187 /**
188  * Starts capture on the specified Observable. All events will be passed
189  * to the supplied function with the event name + standard signature of the event
190  * <b>before</b> the event is fired. If the supplied function returns false,
191  * the event will not fire.
192  * @param {Observable} o The Observable to capture events from.
193  * @param {Function} fn The function to call when an event is fired.
194  * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Observable firing the event.
195  * @static
196  */
197 Ext.util.Observable.capture = function(o, fn, scope){
198     o.fireEvent = o.fireEvent.createInterceptor(fn, scope);
199 };
200
201
202 /**
203  * Sets observability on the passed class constructor.<p>
204  * <p>This makes any event fired on any instance of the passed class also fire a single event through
205  * the <i>class</i> allowing for central handling of events on many instances at once.</p>
206  * <p>Usage:</p><pre><code>
207 Ext.util.Observable.observeClass(Ext.data.Connection);
208 Ext.data.Connection.on('beforerequest', function(con, options) {
209     console.log('Ajax request made to ' + options.url);
210 });</code></pre>
211  * @param {Function} c The class constructor to make observable.
212  * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
213  * @static
214  */
215 Ext.util.Observable.observeClass = function(c, listeners){
216     if(c){
217       if(!c.fireEvent){
218           Ext.apply(c, new Ext.util.Observable());
219           Ext.util.Observable.capture(c.prototype, c.fireEvent, c);
220       }
221       if(typeof listeners == 'object'){
222           c.on(listeners);
223       }
224       return c;
225    }
226 };