1 jls.loader.provide('jls.gui.Element');
  2 
  3 jls.loader.require('jls.gui.Style');
  4 jls.loader.require('jls.gui.FlowLayout');
  5 
  6 jls.gui.Element = jls.lang.Class.create( /** @lends jls.gui.Element.prototype */
  7 {
  8     /**
  9      * Creates an element.
 10      * 
 11      * @param {Object} [parameters] The element parameter.
 12      * @param {Object} [parameters.attributes] The element attributes.
 13      * @param {Object} [parameters.style] The element style.
 14      * @param {Array} [parameters.children] The element children.
 15      * @param {jls.gui.Element} [parent] The parent.
 16      * @constructs
 17 	 * @class This class represents a graphical element.
 18      */
 19     initialize : function(parameters, parent) {
 20         parameters = parameters || {};
 21         this._children = [];
 22         this._parent = parent || null;
 23         this._eventsHandlers = {};
 24         // Default parameters
 25         this._title = undefined;
 26         this._id = undefined;
 27         // Create style
 28         this._style = new jls.gui.Style(parameters.style);
 29         // Register style handler
 30     	this.getStyle().setHandler(this.onStyleChange.bind(this));
 31         if (this._parent) {
 32         	this._style.setParent(this._parent.getStyle());
 33         }
 34         // Create default layout
 35     	this._layout = this.createDefaultLayout();
 36     	// Set bounds
 37         this._x = this._style.getPropertyValue('left');
 38         this._y = this._style.getPropertyValue('top');
 39         this._w = this._style.getPropertyValue('width');
 40         this._h = this._style.getPropertyValue('height');
 41         // Set the attributes
 42         // TODO is this the right place?
 43         if (parameters.attributes) {
 44             this.setAttributes(parameters.attributes);
 45         }
 46         // Add this element to its parent
 47         // TODO is this the right place?
 48         if (this._parent != null) {
 49             this._parent.addChild(this);
 50         }
 51         // Create the concrete element
 52         this.onCreate();
 53         // Activate the style properties
 54         this._style.activate();
 55         // Add the children
 56         if (Object.isArray(parameters.children)) {
 57             this.addChildren(parameters.children);
 58         }
 59         if (this._layout != null) {
 60         	this._layout.onCreate();
 61         }
 62     },
 63     onCreate : jls.lang.Class.emptyFunction,
 64     getParent : function() {
 65         return this._parent;
 66     },
 67     /*
 68      * Attributes
 69      */
 70     /**
 71      * Sets an attribute.
 72      *
 73      * @param {String} key The attribute key.
 74      * @param {String} value The attribute value.
 75      */
 76     setAttribute : function(key, value) {
 77         /* look for a setter method in this */
 78         var setter = key.camelize('set');
 79         jls.logger.trace('setter: "' + setter + '"');
 80         if (Object.isFunction(this[setter])) {
 81             this[setter](value);
 82             return this;
 83         }
 84         this._setAttribute(key, value);
 85         return this;
 86     },
 87     _setAttribute : jls.lang.Class.emptyFunction,
 88     /**
 89      * Returns an attribute.
 90      *
 91      * @param {String} key The attribute key.
 92      * @returns {String} The attribute value.
 93      */
 94     getAttribute : function(key) {
 95         jls.logger.trace('getAttribute(' + key + ')');
 96         /* look for a getter method in this */
 97         var getter = key.camelize('get');
 98         jls.logger.trace('getter: "' + getter + '"');
 99         if (Object.isFunction(this[getter])) {
100             return this[getter]();
101         }
102         return this._getAttribute(key);
103     },
104     _getAttribute : jls.lang.Class.emptyFunction,
105     /**
106      * Sets some attributes.
107      *
108      * @param {Object} attributes The attributes to set.
109      */
110     setAttributes : function(attributes) {
111         for ( var k in attributes) {
112             this.setAttribute(k, attributes[k]);
113         }
114         return this;
115     },
116     /**
117      * Returns the element style.
118      * 
119      * @returns {jls.gui.Style} The element style.
120      */
121     getStyle : function() {
122         return this._style;
123     },
124     // onStyleChange(key, oldValue, newValue)
125     onStyleChange : jls.lang.Class.emptyFunction,
126     /**
127      * Returns the layout associated with this element.
128      * 
129      * @returns {jls.gui.Layout} The element layout or null if there is no layout.
130      */
131     getLayout : function() {
132         return this._layout;
133     },
134     createDefaultLayout : function() {
135         return null;
136     },
137     /**
138      * Sets the layout for this element.
139      * 
140      * @param {jls.gui.Layout} layout The layout to associate to this element.
141      */
142     setLayout : function(layout) {
143     	if (layout != null) {
144     		if (typeof layout == 'string') {
145     			var layoutClass = jls.loader.require(layout);
146     			layout = new layoutClass(this);
147     		} else if (layout instanceof jls.gui.Layout) {
148     			layout.setElement(this);
149     		} else {
150     			throw new jls.lang.Exception('Invalid layout');
151     		}
152     	}
153         this._layout = layout;
154         return this;
155     },
156     /*
157      * Element hierarchy
158      */
159     /**
160      * Adds a child.
161      *
162      * @param {Object} child The child to add.
163      */
164     addChild : function(child) {
165         if (child instanceof jls.gui.Element) {
166             jls.logger.trace('addChild for jls.gui.Element');
167             // TODO if the child already have a parent remove it
168             child._parent = this;
169             this._children.push(child);
170         } else if ((typeof child == "object") && ('classname' in child)) {
171             jls.logger.trace('addChild for object');
172             child = jls.lang.Class.newInstance(child, this);
173         } else {
174             throw new jls.lang.Exception('don\'t know how to add "' + (typeof child) + '" child type');
175         }
176         if (this._layout != null) {
177         	this._layout.onAddChild(child);
178         }
179         return child;
180     },
181     /**
182      * Adds children.
183      *
184      * @param {Object} children The children to add.
185      */
186     addChildren : function(children) {
187         for ( var i = 0; i < children.length; i++) {
188             this.addChild(children[i]);
189         }
190         return this;
191     },
192     /**
193      * Removes a child.
194      *
195      * @param {Object} child The child to remove.
196      */
197     removeChild : function(child) {
198         var index = 0;
199         if (typeof child == 'number') {
200             index = child;
201         } else {
202             for (index = this._children.length - 1; (index >= 0) && (this._children[index] !== child); index--);
203             if (index < 0) {
204                 throw new jls.lang.Exception('Child not found');
205             }
206         }
207         var removed = this._children[index];
208         this._children.splice(index, 1);
209         //jls.logger.warn('removeChild(' + index + '))');
210         if (removed && (removed instanceof jls.gui.Element)) {
211             removed._parent = null;
212             removed.onDestroy();
213         }
214         if (this._layout != null) {
215         	this._layout.onRemoveChild(child);
216         }
217         return removed;
218     },
219     /**
220      * Removes all children.
221      *
222      */
223     removeChildren : function() {
224         for ( var i = this._children.length - 1; i >= 0; i--) {
225             this.removeChild(i);
226         }
227     },
228     /**
229      * Returns all children.
230      *
231      */
232     getChildren : function() {
233         return this._children;
234     },
235     getChildCount : function() {
236         return this._children.length;
237     },
238     /**
239      * Gets a child.
240      *
241      * @param {Number} index The child index.
242      * @returns {Object} The child.
243      */
244     getChild : function(index) {
245         return this._children[index];
246     },
247     /**
248      * Destroys this element.
249      *
250      */
251     destroy : function() {
252         if (this.getParent() != null) {
253             this.getParent().removeChild(this);
254         } else {
255             this.onDestroy();
256         }
257         return this;
258     },
259     onDestroy : jls.lang.Class.emptyFunction,
260     /*
261      * Event
262      */
263     dispatch : function(event) {
264         if (event.type in this._eventsHandlers) {
265             var handlers = this._eventsHandlers[event.type];
266             for (var i = handlers.length - 1; i >= 0; i--) {
267                 handlers[i](event);
268             }
269         }
270         return this;
271     },
272     onStartObserving: jls.lang.Class.emptyFunction,
273     onStopObserving: jls.lang.Class.emptyFunction,
274     /**
275      * Register an event handler.
276      *
277      * @param {String} type The event type to observe.
278      * @param {Function} handler The handler to register.
279      * @returns {Function} The handler.
280      */
281     observe: function(type, handler) {
282         if (Object.keys(this._eventsHandlers).length == 0) {
283             this.onStartObserving();
284         }
285         if (! (type in this._eventsHandlers)) {
286             this._eventsHandlers[type] = [];
287         }
288         this._eventsHandlers[type].push(handler);
289         return handler;
290     },
291     /**
292      * Unregister an event handler.
293      *
294      * @param {String} type The event type to unregister.
295      * @param {Function} handler The handler to unregister.
296      * @returns {Array} The removed handlers.
297      */
298     unobserve: function(type, handler) {
299         var removed = [];
300         if (type in this._eventsHandlers) {
301             var handlers = this._eventsHandlers[type];
302             for (var i = handlers.length - 1; i >= 0; i--) {
303                 if (handler == handlers[i]) {
304                     removed.push(handlers[i]);
305                     handlers.splice(i, 1);
306                 }
307             }
308             if (handlers.length == 0) {
309                 delete this._eventsHandlers[type];
310             }
311         }
312         if (Object.keys(this._eventsHandlers).length == 0) {
313             this.onStopObserving();
314         }
315         return removed;
316     },
317     /*
318      * Update
319      */
320     // TODO doLayout ?
321     update : function() {
322         if (this._layout != null) {
323         	this._layout.onUpdate();
324         }
325     },
326     /*
327      * Default attributes
328      */
329     getX : function() {
330         return this._x;
331     },
332     getY : function() {
333         return this._y;
334     },
335     getW : function() {
336         return this._w;
337     },
338     getH : function() {
339         return this._h;
340     },
341     getBounds : function() {
342         return [this._x, this._y, this._w, this._h];
343     },
344     setBounds : function(rect) {
345         this._x = rect[0];
346         this._y = rect[1];
347         this._w = rect[2];
348         this._h = rect[3];
349         return this;
350     },
351     getLocation : function() {
352         return [this._x, this._y];
353     },
354     setLocation : function(point) {
355         this._x = point[0];
356         this._y = point[1];
357         return this;
358     },
359     getSize : function() {
360         return [this._w, this._h];
361     },
362     setSize : function(dim) {
363         this._w = dim[0];
364         this._h = dim[1];
365         return this;
366     },
367     getClientOffset : function() {
368         return [0, 0];
369     },
370     /*getClientBounds : function() {
371         return this.getBounds();
372     },*/
373     getClientSize : function() {
374         return this.getSize();
375     },
376     getTitle : function() {
377         return this._title;
378     },
379     setTitle : function(title) {
380         this._title = title;
381         return this;
382     }
383 });
384 
385 Object.extend(jls.gui.Element, /** @lends jls.gui.Element */
386 {
387     /**
388      * Calls a function synchronously in the GUI message thread.
389      *
390      * @param {Function} fn The function to call.
391      * @returns {Object} The function result if any.
392      * @function
393      */
394     invokeAndWait : jls.win32.Window.invokeAndWait,
395     /**
396      * Calls a function asynchronously in the GUI message thread.
397      *
398      * @param {Function} fn The handler to unregister.
399      * @function
400      */
401     invokeLater : jls.win32.Window.invokeLater
402 });
403