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