11e39c5c8783e1f03bd5da29ba6b33c43e00947d
[roojs1] / Roo / util / Observable.js
1 /*
2  * Based on:
3  * Ext JS Library 1.1.1
4  * Copyright(c) 2006-2007, Ext JS, LLC.
5  *
6  * Originally Released Under LGPL - original licence link has changed is not relivant.
7  *
8  * Fork - LGPL
9  * <script type="text/javascript">
10  */
11
12 /**
13  * @class Roo.util.Observable
14  * Base class that provides a common interface for publishing events. Subclasses are expected to
15  * to have a property "events" with all the events defined.<br>
16  * For example:
17  * <pre><code>
18  Employee = function(name){
19     this.name = name;
20     this.addEvents({
21         "fired" : true,
22         "quit" : true
23     });
24  }
25  Roo.extend(Employee, Roo.util.Observable);
26 </code></pre>
27  * @param {Object} config properties to use (incuding events / listeners)
28  */
29
30 Roo.util.Observable = function(cfg){
31     console.log("UTIL OBSERVABLE CONSTRUCTOR");
32     
33     cfg = cfg|| {};
34     this.addEvents(cfg.events || {});
35     if (cfg.events) {
36         delete cfg.events; // make sure
37     }
38      
39     Roo.apply(this, cfg);
40     
41     if(this.listeners){
42         this.on(this.listeners);
43         delete this.listeners;
44     }
45 };
46 Roo.util.Observable.prototype = {
47     /** 
48  * @cfg {Object} listeners  list of events and functions to call for this object, 
49  * For example :
50  * <pre><code>
51     listeners :  { 
52        'click' : function(e) {
53            ..... 
54         } ,
55         .... 
56     } 
57   </code></pre>
58  */
59     
60     
61     /**
62      * Fires the specified event with the passed parameters (minus the event name).
63      * @param {String} eventName
64      * @param {Object...} args Variable number of parameters are passed to handlers
65      * @return {Boolean} returns false if any of the handlers return false otherwise it returns true
66      */
67     fireEvent : function(){
68         var ce = this.events[arguments[0].toLowerCase()];
69         if(typeof ce == "object"){
70             return ce.fire.apply(ce, Array.prototype.slice.call(arguments, 1));
71         }else{
72             return true;
73         }
74     },
75
76     // private
77     filterOptRe : /^(?:scope|delay|buffer|single)$/,
78
79     /**
80      * Appends an event handler to this component
81      * @param {String}   eventName The type of event to listen for
82      * @param {Function} handler The method the event invokes
83      * @param {Object}   scope (optional) The scope in which to execute the handler
84      * function. The handler function's "this" context.
85      * @param {Object}   options (optional) An object containing handler configuration
86      * properties. This may contain any of the following properties:<ul>
87      * <li>scope {Object} The scope in which to execute the handler function. The handler function's "this" context.</li>
88      * <li>delay {Number} The number of milliseconds to delay the invocation of the handler after te event fires.</li>
89      * <li>single {Boolean} True to add a handler to handle just the next firing of the event, and then remove itself.</li>
90      * <li>buffer {Number} Causes the handler to be scheduled to run in an {@link Roo.util.DelayedTask} delayed
91      * by the specified number of milliseconds. If the event fires again within that time, the original
92      * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</li>
93      * </ul><br>
94      * <p>
95      * <b>Combining Options</b><br>
96      * Using the options argument, it is possible to combine different types of listeners:<br>
97      * <br>
98      * A normalized, delayed, one-time listener that auto stops the event and passes a custom argument (forumId)
99                 <pre><code>
100                 el.on('click', this.onClick, this, {
101                         single: true,
102                 delay: 100,
103                 forumId: 4
104                 });
105                 </code></pre>
106      * <p>
107      * <b>Attaching multiple handlers in 1 call</b><br>
108      * The method also allows for a single argument to be passed which is a config object containing properties
109      * which specify multiple handlers.
110      * <pre><code>
111                 el.on({
112                         'click': {
113                         fn: this.onClick,
114                         scope: this,
115                         delay: 100
116                 }, 
117                 'mouseover': {
118                         fn: this.onMouseOver,
119                         scope: this
120                 },
121                 'mouseout': {
122                         fn: this.onMouseOut,
123                         scope: this
124                 }
125                 });
126                 </code></pre>
127      * <p>
128      * Or a shorthand syntax which passes the same scope object to all handlers:
129         <pre><code>
130                 el.on({
131                         'click': this.onClick,
132                 'mouseover': this.onMouseOver,
133                 'mouseout': this.onMouseOut,
134                 scope: this
135                 });
136                 </code></pre>
137      */
138     addListener : function(eventName, fn, scope, o){
139         if(typeof eventName == "object"){
140             o = eventName;
141             for(var e in o){
142                 if(this.filterOptRe.test(e)){
143                     continue;
144                 }
145                 if(typeof o[e] == "function"){
146                     // shared options
147                     this.addListener(e, o[e], o.scope,  o);
148                 }else{
149                     // individual options
150                     this.addListener(e, o[e].fn, o[e].scope, o[e]);
151                 }
152             }
153             return;
154         }
155         o = (!o || typeof o == "boolean") ? {} : o;
156         eventName = eventName.toLowerCase();
157         var ce = this.events[eventName] || true;
158         if(typeof ce == "boolean"){
159             ce = new Roo.util.Event(this, eventName);
160             this.events[eventName] = ce;
161         }
162         ce.addListener(fn, scope, o);
163     },
164
165     /**
166      * Removes a listener
167      * @param {String}   eventName     The type of event to listen for
168      * @param {Function} handler        The handler to remove
169      * @param {Object}   scope  (optional) The scope (this object) for the handler
170      */
171     removeListener : function(eventName, fn, scope){
172         var ce = this.events[eventName.toLowerCase()];
173         if(typeof ce == "object"){
174             ce.removeListener(fn, scope);
175         }
176     },
177
178     /**
179      * Removes all listeners for this object
180      */
181     purgeListeners : function(){
182         for(var evt in this.events){
183             if(typeof this.events[evt] == "object"){
184                  this.events[evt].clearListeners();
185             }
186         }
187     },
188
189     relayEvents : function(o, events){
190         var createHandler = function(ename){
191             return function(){
192                  
193                 return this.fireEvent.apply(this, Roo.combine(ename, Array.prototype.slice.call(arguments, 0)));
194             };
195         };
196         for(var i = 0, len = events.length; i < len; i++){
197             var ename = events[i];
198             if(!this.events[ename]){
199                 this.events[ename] = true;
200             };
201             o.on(ename, createHandler(ename), this);
202         }
203     },
204
205     /**
206      * Used to define events on this Observable
207      * @param {Object} object The object with the events defined
208      */
209     addEvents : function(o){
210         if(!this.events){
211             this.events = {};
212         }
213         Roo.applyIf(this.events, o);
214     },
215
216     /**
217      * Checks to see if this object has any listeners for a specified event
218      * @param {String} eventName The name of the event to check for
219      * @return {Boolean} True if the event is being listened for, else false
220      */
221     hasListener : function(eventName){
222         var e = this.events[eventName];
223         return typeof e == "object" && e.listeners.length > 0;
224     }
225 };
226 /**
227  * Appends an event handler to this element (shorthand for addListener)
228  * @param {String}   eventName     The type of event to listen for
229  * @param {Function} handler        The method the event invokes
230  * @param {Object}   scope (optional) The scope in which to execute the handler
231  * function. The handler function's "this" context.
232  * @param {Object}   options  (optional)
233  * @method
234  */
235 Roo.util.Observable.prototype.on = Roo.util.Observable.prototype.addListener;
236 /**
237  * Removes a listener (shorthand for removeListener)
238  * @param {String}   eventName     The type of event to listen for
239  * @param {Function} handler        The handler to remove
240  * @param {Object}   scope  (optional) The scope (this object) for the handler
241  * @method
242  */
243 Roo.util.Observable.prototype.un = Roo.util.Observable.prototype.removeListener;
244
245 /**
246  * Starts capture on the specified Observable. All events will be passed
247  * to the supplied function with the event name + standard signature of the event
248  * <b>before</b> the event is fired. If the supplied function returns false,
249  * the event will not fire.
250  * @param {Observable} o The Observable to capture
251  * @param {Function} fn The function to call
252  * @param {Object} scope (optional) The scope (this object) for the fn
253  * @static
254  */
255 Roo.util.Observable.capture = function(o, fn, scope){
256     o.fireEvent = o.fireEvent.createInterceptor(fn, scope);
257 };
258
259 /**
260  * Removes <b>all</b> added captures from the Observable.
261  * @param {Observable} o The Observable to release
262  * @static
263  */
264 Roo.util.Observable.releaseCapture = function(o){
265     o.fireEvent = Roo.util.Observable.prototype.fireEvent;
266 };
267
268 (function(){
269
270     var createBuffered = function(h, o, scope){
271         var task = new Roo.util.DelayedTask();
272         return function(){
273             task.delay(o.buffer, h, scope, Array.prototype.slice.call(arguments, 0));
274         };
275     };
276
277     var createSingle = function(h, e, fn, scope){
278         return function(){
279             e.removeListener(fn, scope);
280             return h.apply(scope, arguments);
281         };
282     };
283
284     var createDelayed = function(h, o, scope){
285         return function(){
286             var args = Array.prototype.slice.call(arguments, 0);
287             setTimeout(function(){
288                 h.apply(scope, args);
289             }, o.delay || 10);
290         };
291     };
292
293     Roo.util.Event = function(obj, name){
294         this.name = name;
295         this.obj = obj;
296         this.listeners = [];
297     };
298
299     Roo.util.Event.prototype = {
300         addListener : function(fn, scope, options){
301             var o = options || {};
302             scope = scope || this.obj;
303             if(!this.isListening(fn, scope)){
304                 var l = {fn: fn, scope: scope, options: o};
305                 var h = fn;
306                 if(o.delay){
307                     h = createDelayed(h, o, scope);
308                 }
309                 if(o.single){
310                     h = createSingle(h, this, fn, scope);
311                 }
312                 if(o.buffer){
313                     h = createBuffered(h, o, scope);
314                 }
315                 l.fireFn = h;
316                 if(!this.firing){ // if we are currently firing this event, don't disturb the listener loop
317                     this.listeners.push(l);
318                 }else{
319                     this.listeners = this.listeners.slice(0);
320                     this.listeners.push(l);
321                 }
322             }
323         },
324
325         findListener : function(fn, scope){
326             scope = scope || this.obj;
327             var ls = this.listeners;
328             for(var i = 0, len = ls.length; i < len; i++){
329                 var l = ls[i];
330                 if(l.fn == fn && l.scope == scope){
331                     return i;
332                 }
333             }
334             return -1;
335         },
336
337         isListening : function(fn, scope){
338             return this.findListener(fn, scope) != -1;
339         },
340
341         removeListener : function(fn, scope){
342             var index;
343             if((index = this.findListener(fn, scope)) != -1){
344                 if(!this.firing){
345                     this.listeners.splice(index, 1);
346                 }else{
347                     this.listeners = this.listeners.slice(0);
348                     this.listeners.splice(index, 1);
349                 }
350                 return true;
351             }
352             return false;
353         },
354
355         clearListeners : function(){
356             this.listeners = [];
357         },
358
359         fire : function(){
360             var ls = this.listeners, scope, len = ls.length;
361             if(len > 0){
362                 this.firing = true;
363                 var args = Array.prototype.slice.call(arguments, 0);                
364                 for(var i = 0; i < len; i++){
365                     var l = ls[i];
366                     if(l.fireFn.apply(l.scope||this.obj||window, args) === false){
367                         this.firing = false;
368                         return false;
369                     }
370                 }
371                 this.firing = false;
372             }
373             return true;
374         }
375     };
376 })();