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