XObject.js
[gitlive] / XObject.js
1 //<script type="text/javascript">
2
3 /**
4  * XObject
5  * Yet another attempt to create a usable object construction library for seed..
6  * 
7  * Why is this useful?
8  * A) It turns rather messy code into a tree structure, making it easy to find code relating to 
9  *    an interface element
10  * B) In theory it should be gjs/Seed compatible..
11  * C) It provides getElementById style lookups for elements.
12  *
13  * Extend this.. to use it's wonderful features..
14  * 
15  * normal usage:
16  * Xyz = new XObject({
17  *     xtype: Gtk.Window,
18  *     id : 'window',
19  *     items : [
20  *     
21  *     ]
22  *  });
23  *  Xyz.init(); // create and show.
24  * 
25  * 
26  * 
27  * @arg xtype {String|Function} constructor or string.
28  * @arg id {String}  (optional) id for registry
29  * @arg xns {String|Object}   (optional) namespace eg. Gtk or 'Gtk' - used with xtype.
30  * @arg items {Array}   (optional) list of child elements which will be constructed.. using XObject
31  * @arg listeners {Object}   (optional) map Gobject signals to functions
32  * @arg pack {Function|String|Array}   (optional) how this object gets added to it's parent
33  * @arg el {Object}   (optional) premade GObject
34  * 
35  *  --- needs a xdebug option!
36  * 
37  * 
38  * He's some questions.
39  * - should we generate ID's for all elements? (if so we probably need to garbage collect)
40  * - should we have a special property to use as the constructor / gobject.properties rather
41  *   than sending all basic types to this?
42  * 
43  * 
44  */
45
46 function XObject (cfg) {
47     // first apply cfg if set.
48     this.config = cfg;
49     if (cfg.init) {
50         this.init = cfg.init; // override!
51     }
52     
53     
54 }
55
56
57
58 XObject.prototype = {
59     /**
60      * @property el {GObject} the Gtk / etc. element.
61      */
62     el : false, 
63     /*
64      * @property items {Array} list of sub elements
65      */
66     /**
67      * @property parent {XObject} parent Element
68      */
69      
70      /**
71      * @property config {Object} the construction configuration.
72      */
73      /**
74       * @method init
75       * Initializes the Element (el) hooks up all the listeners
76       * and packs the children.
77       * you can override this, in child objects, then 
78       * do this to do thi initaliztion.
79       * 
80       * XObject.prototype.init.call(this); 
81       * 
82       */ 
83     init : function()
84     {
85         var cfg = this.config;
86     
87         print("new xobj?"  + XObject.keys(cfg).join(','));
88         //print(cfg);
89         o =  {};
90         
91         cfg.items = cfg.items || [];
92         
93         XObject.extend(o, cfg); // copy everything into o.
94         
95         o.pack = typeof(o.pack) == 'undefined' ? 'add' : o.pack;
96         
97         XObject.extend(this, o);
98
99         // remove items.
100         
101         this.listeners = this.listeners || {}; 
102         this.items = [];
103         
104         // remove objects/functions from o, so they can be sent to the contructor.
105         for (var i in o) {
106             if ((typeof(o[i]) == 'object') || 
107                 (typeof(o[i]) == 'function') || 
108                 i == 'pack' ||
109                 i == 'id' ||
110                 i == 'xtype' ||
111                 i == 'xdebug' ||
112                 i == 'xns'
113             ) {
114                 delete o[i];
115             }
116         }
117         
118         // do we need to call 'beforeInit here?'
119          
120         // handle include?
121         //if ((this.xtype == 'Include')) {
122         //    o = this.pre_registry[cls];
123         //}
124         var isSeed = typeof(Seed) != 'undefined';
125          
126         // xtype= Gtk.Menu ?? what about c_new stuff?
127         print(this.xtype);
128         if (typeof(this.xtype) == 'function') {
129             print("func?"  + XObject.keys(o).join(','));
130             this.el = this.el ||   this.xtype(o);
131         }
132         if (typeof(this.xtype) == 'object') {
133             print("obj?"  + XObject.keys(o).join(','));
134             this.el = this.el ||  new this.xtype(o);
135         }
136         //print(this.el);
137         if (!this.el && o.xns) {
138             
139             var NS = imports.gi[o.xns];
140             if (!NS) {
141                 Seed.print('Invalid xns: ' + o.xns);
142             }
143             constructor = NS[o.xtype];
144             if (!constructor) {
145                 Seed.print('Invalid xtype: ' + o.xns + '.' + o.xtype);
146             }
147             this.el  =   isSeed ? new constructor(o) : new constructor();
148             
149         }
150         // always overlay props..
151         for (var i in o) {
152             this.el[i] = o[i];
153         }
154         // register it!
155         //if (o.xnsid  && o.id) {
156          //   XObject.registry = XObject.registry || { };
157          //   XObject.registry[o.xnsid] = XObject.registry[o.xnsid] || {}; 
158          //   XObject.registry[o.xnsid][o.id] = this;
159         //}
160         
161         cfg.items.forEach(this.addItem, this);
162         
163         for (var i in this.listeners) {
164             this.addListener(i, this.listeners[i]);
165         }
166         // delete this.listeners ?
167         
168         
169         // do we need to call 'init here?'
170     },
171       
172      
173      /**
174       * @method addItem
175       * Adds an item to the object using a new XObject
176       * uses pack property to determine how to add it.
177       * @arg cfg {Object} same as XObject constructor.
178       */
179     addItem : function(o) {
180         
181          
182         var item = (o.constructor == XObject) ? o : new XObject(o);
183         item.init();
184         item.parent = this;
185         this.items.push(item);
186         
187         if (item.pack===false) {  // no 
188             return;
189         }
190         if (typeof(item.pack) == 'function') {
191             // parent, child
192             item.pack.apply(o, [ o , o.items[i] ]);
193             item.parent = this;
194             return;
195         }
196         var args = [];
197         var pack_m  = false;
198         if (typeof(item.pack) == 'string') {
199             pack_m = item.pack;
200         } else {
201             pack_m = item.pack.shift();
202             args = item.pack;
203         }
204         
205         // handle error.
206         if (pack_m && typeof(this.el[pack_m]) == 'undefined') {
207             Seed.print('pack method not available : ' + this.xtype + '.' +  pack_m);
208             return;
209         }
210         
211         
212         //Seed.print('Pack ' + this.el + '.'+ pack_m + '(' + item.el + ')');
213
214         args.unshift(item.el);
215         print('[' + args.join(',') +']');
216         //Seed.print('args: ' + args.length);
217         if (pack_m) {
218             this.el[pack_m].apply(this.el, args);
219         }
220         
221        
222         
223     },
224     /**
225       * @method addListener
226       * Connects a method to a signal. (gjs/Seed aware)
227       * 
228       * @arg sig  {String} name of signal
229       * @arg fn  {Function} handler.
230       */
231     addListener  : function(sig, fn) 
232     {
233  
234         Seed.print("Add signal " + sig);
235  
236         var _li = XObject.createDelegate(fn,this);
237         // private listeners that are not copied to GTk.
238         
239         if (typeof(Seed) != 'undefined') {
240           //   Seed.print(typeof(_li));
241             this.el.signal[sig].connect(_li);
242         } else {
243             this.el.connect( sig, _li);
244         }
245              
246         
247     },
248      /**
249       * @method get
250       * Finds an object in the child elements using xid of object.
251       * prefix with '.' to look up the tree.. multiple '..' to look further up..
252       * 
253       * @arg name  {String} name of signal
254       * @return   {XObject|false} the object if found.
255       */
256     get : function(xid)
257     {
258         var ret=  false;
259         if (xid[0] == '.') {
260             return this.parent.get(xid.substring(1));
261         }
262         
263         
264         this.items.forEach(function(ch) {
265             if (ch.id == xid) {
266                 ret = ch;
267                 return true;
268             }
269         })
270         if (ret) {
271             return ret;
272         }
273         // iterate children.
274         this.items.forEach(function(ch) {
275             ret = ch.get(xid);
276             if (ret) {
277                 return true;
278             }
279         })
280         return ret;
281     }
282       
283       
284
285          
286         
287 /**
288  * Copies all the properties of config to obj.
289  *
290  * Pretty much the same as JQuery/Prototype..
291  * @param {Object} obj The receiver of the properties
292  * @param {Object} config The source of the properties
293  * @param {Object} defaults A different object that will also be applied for default values
294  * @return {Object} returns obj
295  * @member XObject extend
296  */
297
298
299 XObject.extend = function(o, c, defaults){
300     if(defaults){
301         // no "this" reference for friendly out of scope calls
302         XObject.extend(o, defaults);
303     }
304     if(o && c && typeof c == 'object'){
305         for(var p in c){
306             o[p] = c[p];
307         }
308     }
309     return o;
310 };
311
312 XObject.extend(XObject,
313 {
314     /**
315      * Copies all the properties of config to obj, if the do not exist.
316      * @param {Object} obj The receiver of the properties
317      * @param {Object} config The source of the properties
318      * @return {Object} returns obj
319      * @member Object extendIf
320      */
321
322
323     extendIf : function(o, c){
324
325         if(!o || !c || typeof c != 'object'){
326             return o;
327         }
328         for(var p in c){
329             if (typeof(o[p]) != 'undefined') {
330                 continue;
331             }
332             o[p] = c[p];
333         }
334         return o;
335     },
336
337  
338
339     /**
340      * Extends one class with another class and optionally overrides members with the passed literal. This class
341      * also adds the function "override()" to the class that can be used to override
342      * members on an instance.
343      *
344      * usage:
345      * MyObject = Object.define(
346      *     function(...) {
347      *          ....
348      *     },
349      *     parentClass, // or Object
350      *     {
351      *        ... methods and properties.
352      *     }
353      * });
354      * @param {Function} constructor The class inheriting the functionality
355      * @param {Object} superclass The class being extended
356      * @param {Object} overrides (optional) A literal with members
357      * @return {Function} constructor (eg. class
358      * @method define
359      */
360     define : function(){
361         // inline overrides
362         var io = function(o){
363             for(var m in o){
364                 this[m] = o[m];
365             }
366         };
367         return function(sb, sp, overrides) {
368             if (typeof(sp) == 'undefined') {
369                 // error condition - try and dump..
370                 throw "Missing superclass: when applying: " + sb
371             }
372
373             var F = function(){}, sbp, spp = sp.prototype;
374             F.prototype = spp;
375             sbp = sb.prototype = new F();
376             sbp.constructor=sb;
377             sb.superclass=spp;
378
379             // extends Object.
380             if(spp.constructor == Object.prototype.constructor){
381                 spp.constructor=sp;
382             }
383             
384             sb.override = function(o){
385                 Object.extend(sb.prototype, o);
386             };
387             sbp.override = io;
388             Object.extend(sb.prototype, overrides);
389             return sb;
390         };
391     }(),
392
393          
394     /**
395      * returns a list of keys of the object.
396      * @param {Object} obj object to inspect
397      * @return {Array} returns list of kyes
398      * @member XObject keys
399      */
400     keys : function(o)
401     {
402         var ret = [];
403         for(var i in o) {
404             ret.push(i);
405         }
406         return ret;
407     },
408       
409     /**
410      * @member XObject createDelegate
411      * creates a delage metdhod
412      * @param {Function} method to wrap
413      * @param {Object} scope 
414      * @param {Array} args to add
415      * @param {Boolean|Number} append arguments or replace after N arguments.
416      * @return {Function} returns the delegate
417      */
418
419     createDelegate : function(method, obj, args, appendArgs){
420         
421         return function() {
422             var callArgs = args || arguments;
423             if(appendArgs === true){
424                 callArgs = Array.prototype.slice.call(arguments, 0);
425                 callArgs = callArgs.concat(args);
426             }else if(typeof appendArgs == "number"){
427                 callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
428                     var applyArgs = [appendArgs, 0].concat(args); // create method call params
429                     Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
430                 }
431                 return method.apply(obj || window, callArgs);
432             };
433     }
434     
435 });