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