1 include(bbq.lang.Delegator); 2 include(bbq.web.DOMUtil); 3 4 bbq.gui.GUIWidget = new Class.create(bbq.lang.Delegator, /** @lends bbq.gui.GUIWidget.prototype */ { 5 /** 6 * This property holds the point at which this widget is attached to the DOM. 7 * 8 * @type {Node} 9 */ 10 _rootNode: null, 11 12 /** 13 * <p>This class is used as a base for all objects that have GUI representations.</p> 14 15 * <p>The constructor method of this class should always be called explicitly by child classes.</p> 16 * 17 * <p>The rootNode property is a DOM node that is attached to the DOM tree after the object is 18 * created via the appendTo method.</p> 19 * 20 * <pre><code class="language-javascript"> 21 * com.myapp.MyWidget = new Class.create(bbq.gui.GUIWidget, { 22 * _greeting: null, 23 * 24 * initialize: function($super, options) { 25 * $super(options); 26 * 27 * this._greeting = DOMUtil.createElement("p", "Hello world!"); 28 * }, 29 * 30 * render: function() { 31 * this.empty(); 32 * 33 * this.appendChild(this._greeting); 34 * } 35 * }); 36 * 37 * ... 38 * 39 * var widget = new com.myapp.MyWidget({ 40 * attributes: { 41 * className: "Foo", 42 * style: { 43 * backgroundColour: "red" 44 * } 45 * } 46 * }); 47 * widget.appendTo(document.body); 48 * </code></pre> 49 * 50 * @memberOf bbq.gui 51 * @constructs 52 * @param {Object} options 53 * @param {Object} [options.attributes] A key/value object of attributes to be applied to the root node 54 * @extends bbq.lang.Delegator 55 */ 56 initialize: function($super, options) { 57 $super(options); 58 59 this.setRootNode("div"); 60 }, 61 62 /** 63 * <p>The idea of the method is to create a DOM node tree representation of the widget. The root node of the tree should be 64 * this._rootNode. The presence of any other nodes in the DOM tree which are not descendants of this._rootNode should not 65 * be relied upon.</p> 66 * 67 * <p>This method will be called before the node tree is added to the main document tree (in a similar way to off-screen buffering 68 * in graphics programming) and may be called at seeming arbitrary times. Consequently it should always create a 69 * representation of the widget/object in it's current state but at the same time, not rely on any other portion of the DOM tree 70 * existing.</p> 71 */ 72 render: function() { 73 74 }, 75 76 /** 77 * <p>Sets the root node and registers this object for retrieval from the root node.</p> 78 * 79 * <p>This object can then be retrieved by calling owner() on the dom node passed as the argument to this method.</p> 80 * 81 * @param {Node or String} rootNode Either a DOM Node (from document.createElement) or the String name of a node - e.g. "div" 82 * @example 83 * <pre><code class="language-javascript"> 84 * bbq.gui.form.TextField = new Class.create(bbq.gui.GUIWidget, { 85 * initialize: function($super, options) { 86 * $super(options); 87 * 88 * this.setRootNode(document.createElement("input")); 89 * this.addClass("TextField"); 90 * this.setAttribute("value", this.options.value); 91 * } 92 * }); 93 * 94 * ... 95 * 96 * var foo = new bbq.gui.form.TextField({value: "a value"}); 97 * foo.appendTo(document.body); 98 * 99 * ... 100 * 101 * var bar = document.getElementById("exampleObject"); 102 * var foo = bar.owner(); 103 * </code></pre> 104 */ 105 setRootNode: function(rootNode) { 106 if(this._rootNode) { 107 var oldNode = this._rootNode; 108 } 109 110 if(Object.isString(rootNode)) { 111 this._rootNode = document.createElement(rootNode); 112 } else { 113 this._rootNode = rootNode; 114 } 115 116 if(oldNode && oldNode.className) { 117 this.addClass(oldNode.className.split(" ")); 118 } 119 120 if(this.options && this.options.attributes) { 121 var attr = this.options.attributes; 122 for(var key in attr) { 123 this.setAttribute(key, attr[key]); 124 } 125 } 126 127 this._rootNode = $(this._rootNode); 128 this.registerObject(); 129 }, 130 131 getRootNode: function() { 132 return this._rootNode; 133 }, 134 135 /** 136 * Sets a reference to this object on the rootNode DOM node. This allows us to take a DOM node from the document tree and 137 * get the GUIWidget object of which it is the root node. 138 * 139 * @see bbq.gui.GUIWidget#setRootNode 140 */ 141 registerObject: function() { 142 if(this._rootNode) { 143 this._rootNode.owner = function() { 144 return this; 145 }.bind(this); 146 } 147 }, 148 149 /** 150 * Removes all nodes attached to this GUIWidgets rootNode 151 */ 152 empty: function() { 153 if(this._rootNode) { 154 DOMUtil.emptyNode(this._rootNode); 155 } 156 }, 157 158 hasClass: function(className) { 159 return DOMUtil.hasClass(this.getRootNode(), className); 160 }, 161 162 /** 163 * Adds a class to the root node 164 * 165 * @param {String} className The name of the class to add 166 */ 167 addClass: function(className) { 168 DOMUtil.addClass(this.getRootNode(), className); 169 }, 170 171 /** 172 * Removes a class from the root node 173 * 174 * @param {String} className The name of the class to remove 175 */ 176 removeClass: function(className, recursively) { 177 DOMUtil.removeClass(this.getRootNode(), className, recursively); 178 }, 179 180 /** 181 * Attaches the root node to the passed node 182 * 183 * @param {Node} pageNode The node to attach the root node to 184 */ 185 appendTo: function(pageNode) { 186 try { 187 if(pageNode) { 188 pageNode = this._getNode(pageNode); 189 pageNode.appendChild(this.getRootNode()); 190 } 191 192 this.render(); 193 } catch(e) { 194 Log.error("Error while appending to node " + pageNode, e); 195 } 196 }, 197 198 /** 199 * Adds this GUIWidget to the DOM in front of the passed node 200 * 201 * @param Node node 202 */ 203 appendBefore: function(node) { 204 try { 205 node = this._getNode(node); 206 var parentNode = node.parentNode; 207 parentNode.insertBefore(this.getRootNode(), node); 208 209 this.render(); 210 } catch(e) { 211 Log.error("Error while appending before " + node, e); 212 } 213 }, 214 215 appendAfter: function(node) { 216 try { 217 node = this._getNode(node); 218 219 // find the parent node 220 var parentNode = node.parentNode; 221 222 // find the index of the node 223 var nodeArray = $A(parentNode.childNodes); 224 var index = nodeArray.indexOf(node); 225 var nextNode = nodeArray[index + 1]; 226 227 parentNode.insertBefore(this.getRootNode(), nextNode); 228 229 this.render(); 230 } catch(e) { 231 Log.error("Error while appending after " + node, e); 232 } 233 }, 234 235 /** 236 * Adds a node or GUIWidget to the root node of this element 237 * 238 * @param {Mixed} childNode A Node, GUIWidget or array of Node and/or GUIWidget objects 239 */ 240 appendChild: function(childNode) { 241 if(!childNode) { 242 return; 243 } 244 245 if(childNode instanceof Array) { 246 childNode.each(function(node) { 247 this.appendChild(node); 248 }.bind(this)); 249 } else { 250 if(childNode && this._rootNode) { 251 if(childNode.appendTo instanceof Function) { 252 childNode.appendTo(this._rootNode); 253 } else if(childNode.toUpperCase instanceof Function) { 254 this._rootNode.appendChild(document.createTextNode(childNode)); 255 } else { 256 this._rootNode.appendChild(childNode); 257 } 258 } 259 } 260 261 return childNode; 262 }, 263 264 /** 265 * <p>Attempts to remove the passed node if it is a child of this._rootNode</p> 266 * 267 * <p>The passed argument can be either a DOM Node object, or a GUIWidget object.</p> 268 * 269 * @param {Mixed} A child node 270 */ 271 removeChild: function(childNode) { 272 if(childNode && this.getRootNode()) { 273 try { 274 if(childNode.getRootNode instanceof Function) { 275 this.getRootNode().removeChild(childNode.getRootNode()); 276 } else { 277 this.getRootNode().removeChild(childNode); 278 } 279 } catch(e) { 280 Log.error("Error removing child " + childNode, e); 281 } 282 } 283 }, 284 285 /** 286 * Replaces a DOM node or GUIWidget that is a child of the root node of this object 287 * 288 * @param {Node || GUIWidget} oldNode The outgoing child 289 * @param {Node || GUIWidget} newNode The incoming child 290 * @return {Node || GUIWidget} The incoming child 291 */ 292 replaceChild: function(oldNode, newNode) { 293 var output = newNode; 294 295 if(oldNode && newNode && this.getRootNode()) { 296 try { 297 if(oldNode.getRootNode instanceof Function) { 298 oldNode = oldNode.getRootNode(); 299 } 300 301 if(newNode.getRootNode instanceof Function) { 302 newNode.render(); 303 newNode = newNode.getRootNode(); 304 } 305 306 // check that we actually contain the old node before attempting to replace it 307 if(Element.descendantOf(oldNode, this.getRootNode())) { 308 this.getRootNode().replaceChild(newNode, oldNode); 309 } 310 } catch(e) { 311 Log.error("Error replacing child " + oldNode + " for " + newNode, e); 312 } 313 } 314 315 return output; 316 }, 317 318 /** 319 * Sets the id attribute on the root node 320 * 321 * @param {string} id 322 */ 323 setID: function(id) { 324 this.setAttribute("id", id); 325 }, 326 327 /** 328 * Returns the id of the root node 329 * 330 * @return {string} 331 */ 332 getID: function() { 333 this.getAttribute("id"); 334 }, 335 336 /** 337 * Sets a CSS style property to the passed value on the root node 338 * 339 * @param {string} styleName e.g. "border" 340 * @param {string} styleValue e.g. "1px solid #CCC" 341 */ 342 setStyle: function(styleName, styleValue) { 343 DOMUtil.setStyle(this.getRootNode(), styleName, styleValue); 344 }, 345 346 /** 347 * Returns the requested CSS style property 348 * 349 * @param {string} styleName e.g. "font" 350 * @return {string} 351 */ 352 getStyle: function(styleName) { 353 return DOMUtil.getStyle(this.getRootNode(), styleName); 354 }, 355 356 /** 357 * Sets an attribute on the root node 358 * 359 * @param {String} attributeName e.g. "id" 360 * @param {String} attributeValue e.g. "anElement" 361 */ 362 setAttribute: function(attributeName, attributeValue) { 363 if(this._rootNode) { 364 try { 365 if(attributeName == "style" && attributeValue instanceof Object) { 366 for(var key in attributeValue) { 367 this.setStyle(key, attributeValue[key]); 368 } 369 } else if(this._rootNode[attributeName] != attributeValue) { 370 this._rootNode[attributeName] = attributeValue; 371 } 372 } catch(e) { 373 Log.error("exeption thrown setting " + attributeName + " to " + attributeValue, e); 374 } 375 } 376 }, 377 378 /** 379 * Returns the requested attribute from the root node 380 * 381 * @param {String} attributeName e.g. "id" 382 * @return {String} 383 */ 384 getAttribute: function(attributeName) { 385 if(this._rootNode) { 386 return this._rootNode[attributeName]; 387 } 388 }, 389 390 /** 391 * Returns whether or not the root node of this object is of the passed CSS class 392 * 393 * @return boolean 394 */ 395 isClass: function(className) { 396 return DOMUtil.hasClass(this.getRootNode(), className); 397 }, 398 399 /** 400 * @return void 401 */ 402 resize: function() { 403 this.notifyListeners("onResize"); 404 }, 405 406 _sizeScrollable: function(scrollable, dim, maxSize) { 407 if(scrollable && scrollable.parentNode) { 408 scrollable.owner().triggerEvent("onWillResize"); 409 var nodeList = $A(scrollable.parentNode.childNodes); 410 411 nodeList.each(function(node){ 412 if(node && node != scrollable) { 413 var dims = Element.getDimensions(node); 414 maxSize = maxSize - dims[dim]; 415 } 416 }); 417 418 scrollable.style[dim] = maxSize + "px"; 419 scrollable.owner().triggerEvent("onResize"); 420 } 421 }, 422 423 /** 424 * Makes the passed node the first child of this object. 425 * 426 * @param {Object} A GUIWidget or DOM Node 427 */ 428 insertAtTop: function(newNode) { 429 this.getRootNode().insertBefore(this._getNode(newNode), this.getRootNode().firstChild); 430 }, 431 432 /** 433 * Inserts the first passed node before the second. 434 * 435 * @param {Object} A GUIWidget or DOM Node 436 * @param {Object} A GUIWidget or DOM Node - should be a child of this element 437 */ 438 insertBefore: function(newNode, referenceNode) { 439 this.getRootNode().insertBefore(this._getNode(newNode), this._getNode(referenceNode)); 440 }, 441 442 /** 443 * Gets the DOM node from a GUIWidget 444 * 445 * @param {Object} A GUIWidget or DOM Node 446 * @return {Node} 447 */ 448 _getNode: function(fromNode) { 449 if(fromNode) { 450 if(fromNode.getRootNode instanceof Function) { 451 return fromNode.getRootNode(); 452 } else if(fromNode.appendChild) { 453 return fromNode; 454 } 455 } 456 457 Log.error("Invalid node!"); 458 }, 459 460 /** 461 * Causes this GUIWidget to gain focus 462 * 463 * @see bbq.gui.GUIWidget#blur 464 */ 465 focus: function() { 466 this.getRootNode().focus(); 467 }, 468 469 /** 470 * Causes this GUIWidget to lose focus 471 * 472 * @see bbq.gui.GUIWidget#focus 473 */ 474 blur: function() { 475 this.getRootNode().blur(); 476 }, 477 478 /** 479 * Shows this GUIWidget if hidden 480 * 481 * @see bbq.gui.GUIWidget#hide 482 */ 483 show: function() { 484 this.getRootNode().show(); 485 }, 486 487 /** 488 * Hides this GUIWidget 489 * 490 * @see bbq.gui.GUIWidget#show 491 */ 492 hide: function() { 493 this.getRootNode().hide(); 494 } 495 }); 496