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