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