doap file added
[gnome.introspection-doc-generator] / Roo / 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 Roo = imports['Roo.js'].Roo;
12 /**
13  * @class Roo.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.Observable);
26 </code></pre>
27  * @param {Object} config properties to use (incuding events / listeners)
28  */
29  
30  
31 Observable = function(cfg){
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 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.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     on : 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.on(e, o[e], o.scope,  o);
148                 }else{
149                     // individual options
150                     this.on(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.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     un : 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                 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]){ this.events[ename] = true; };
198             o.on(ename, createHandler(ename), this);
199         }
200     },
201
202     /**
203      * Used to define events on this Observable
204      * @param {Object} object The object with the events defined
205      */
206     addEvents : function(o){
207         if(!this.events){
208             this.events = {};
209         }
210         Roo.applyIf(this.events, o);
211     },
212
213     /**
214      * Checks to see if this object has any listeners for a specified event
215      * @param {String} eventName The name of the event to check for
216      * @return {Boolean} True if the event is being listened for, else false
217      */
218     hasListener : function(eventName){
219         var e = this.events[eventName];
220         return typeof e == "object" && e.listeners.length > 0;
221     }
222 };
223  
224 /**
225  * Starts capture on the specified Observable. All events will be passed
226  * to the supplied function with the event name + standard signature of the event
227  * <b>before</b> the event is fired. If the supplied function returns false,
228  * the event will not fire.
229  * @param {Observable} o The Observable to capture
230  * @param {Function} fn The function to call
231  * @param {Object} scope (optional) The scope (this object) for the fn
232  * @static
233  */
234 Observable.capture = function(o, fn, scope){
235     o.fireEvent = o.fireEvent.createInterceptor(fn, scope);
236 };
237
238 /**
239  * Removes <b>all</b> added captures from the Observable.
240  * @param {Observable} o The Observable to release
241  * @static
242  */
243 Observable.releaseCapture = function(o){
244     o.fireEvent = Observable.prototype.fireEvent;
245 };
246
247 (function(){
248
249     var createBuffered = function(h, o, scope){
250         var task = new Roo.DelayedTask();
251         return function(){
252             task.delay(o.buffer, h, scope, Array.prototype.slice.call(arguments, 0));
253         };
254     };
255
256     var createSingle = function(h, e, fn, scope){
257         return function(){
258             e.removeListener(fn, scope);
259             return h.apply(scope, arguments);
260         };
261     };
262
263     var createDelayed = function(h, o, scope){
264         return function(){
265             var args = Array.prototype.slice.call(arguments, 0);
266             setTimeout(function(){
267                 h.apply(scope, args);
268             }, o.delay || 10);
269         };
270     };
271
272     Roo.Event = function(obj, name){
273         this.name = name;
274         this.obj = obj;
275         this.listeners = [];
276     };
277
278     Roo.Event.prototype = {
279         addListener : function(fn, scope, options){
280             var o = options || {};
281             scope = scope || this.obj;
282             if(!this.isListening(fn, scope)){
283                 var l = {fn: fn, scope: scope, options: o};
284                 var h = fn;
285                 if(o.delay){
286                     h = createDelayed(h, o, scope);
287                 }
288                 if(o.single){
289                     h = createSingle(h, this, fn, scope);
290                 }
291                 if(o.buffer){
292                     h = createBuffered(h, o, scope);
293                 }
294                 l.fireFn = h;
295                 if(!this.firing){ // if we are currently firing this event, don't disturb the listener loop
296                     this.listeners.push(l);
297                 }else{
298                     this.listeners = this.listeners.slice(0);
299                     this.listeners.push(l);
300                 }
301             }
302         },
303
304         findListener : function(fn, scope){
305             scope = scope || this.obj;
306             var ls = this.listeners;
307             for(var i = 0, len = ls.length; i < len; i++){
308                 var l = ls[i];
309                 if(l.fn == fn && l.scope == scope){
310                     return i;
311                 }
312             }
313             return -1;
314         },
315
316         isListening : function(fn, scope){
317             return this.findListener(fn, scope) != -1;
318         },
319
320         removeListener : function(fn, scope){
321             var index;
322             if((index = this.findListener(fn, scope)) != -1){
323                 if(!this.firing){
324                     this.listeners.splice(index, 1);
325                 }else{
326                     this.listeners = this.listeners.slice(0);
327                     this.listeners.splice(index, 1);
328                 }
329                 return true;
330             }
331             return false;
332         },
333
334         clearListeners : function(){
335             this.listeners = [];
336         },
337
338         fire : function(){
339             var ls = this.listeners, scope, len = ls.length;
340             if(len > 0){
341                 this.firing = true;
342                 var args = Array.prototype.slice.call(arguments, 0);
343                 for(var i = 0; i < len; i++){
344                     var l = ls[i];
345                     if(l.fireFn.apply(l.scope||this.obj||window, arguments) === false){
346                         this.firing = false;
347                         return false;
348                     }
349                 }
350                 this.firing = false;
351             }
352             return true;
353         }
354     };
355 })();