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