1 /* 2 * Copyright (C) 2009 WaveMaker Software, Inc. 3 * 4 * This file is part of the WaveMaker Client Runtime. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 dojo.provide("wm.base.widget.Container"); 19 20 /** 21 Base class for widget containers. 22 @name wm.Container 23 @class 24 @extends wm.Control 25 */ 26 wm.define("wm.Container", wm.Control, { 27 /** @lends wm.Container.prototype */ 28 published: { 29 invalid: { ignore: 1, bindSource: 1, readonly: 1, type: "Boolean" }, 30 lock: { order: 0 }, 31 freeze: { order: 5 }, 32 box: { ignore: 1 }, 33 boxPosition: { ignore: 1}, 34 disabled: { ignore: 1 } 35 }, 36 imageList: "", 37 border: 0, 38 container: true, 39 lock: false, 40 freeze: false, 41 classNames: "wmcontainer", 42 constructor: function() { 43 this.c$ = []; 44 }, 45 init: function() { 46 this.inherited(arguments); 47 // NOTE: we require certain side-effects of property setters at init time, but not others. 48 // E.g., we don't want to 'reflow' for every property setter. 49 // Strategy is to use flags to change setter behavior. 50 // FIXME (naming): Control is setting _cupdating flag during init. 51 this.setLayoutKind(this.layoutKind); 52 this.domNode.box = this.box = ""; 53 }, 54 postInit: function() { 55 //loaded: function() { 56 if (this.isDesignLoaded()) 57 this.setLock(this.lock); 58 this.inherited(arguments); 59 }, 60 // backward-compatibility fixups 61 afterPaletteDrop: function() { 62 if (this.verticalAlign == "justified") 63 this.verticalAlign = "top"; 64 if (this.horizontalAlign == "justified") 65 this.horizontalAlign = "left"; 66 }, 67 bc: function() { 68 this.inherited(arguments); 69 /* 70 if (this.verticalAlign == "justified") { 71 this.verticalAlign = "top"; 72 } 73 if (this.horizontalAlign == "justified") { 74 this.horizontalAlign = "left"; 75 } 76 */ 77 delete this.layoutJustify; 78 if (this.layoutAlign) { 79 this.contentAlign = this.layoutAlign; 80 delete this.layoutAlign; 81 } 82 if (this.layoutFit) { 83 this.fitToContent = this.layoutFit; 84 delete this.layoutFit; 85 } 86 if (this.box == "h") { 87 this.layoutKind = "left-to-right"; 88 //this.layout = wm.Container.vBox; 89 } 90 if (this.boxPosition) { 91 var 92 boxPositions = ['topLeft', 'center', 'bottomRight'], 93 vAligns = ["top", "middle", "bottom"], 94 hAligns = ["left", "center", "right"], 95 h = this.layoutKind == "left-to-right", 96 i = dojo.indexOf(boxPositions, this.boxPosition); 97 if (i != -1) { 98 if (h) 99 this.horizontalAlign = hAligns[i]; 100 else 101 this.verticalAlign = vAligns[i]; 102 } 103 } 104 }, 105 // 106 // Child Controls 107 // 108 addWidget: function(inWidget){ 109 this.inherited(arguments); 110 if (!inWidget.autoSize) { 111 if (this.box == 'h' && !inWidget.width) 112 inWidget.setProp("width", "64px"); 113 else if (this.box == 'v' && !inWidget.height) 114 inWidget.setProp("height", "64px"); 115 //inWidget.setSize(inWidget.size); 116 } 117 }, 118 getOrderedWidgets: function() { 119 return this.c$; 120 }, 121 addControl: function(inControl) { 122 this.c$.push(inControl); 123 //this.dom.append(inControl.dom); 124 }, 125 removeControl: function(inControl) { 126 //this.dom.remove(inControl.dom); 127 for (var i=0, c; c=this.c$[i]; i++){ 128 if (c == inControl) { 129 this.c$.splice(i, 1); 130 return i; 131 } 132 } 133 }, 134 // Added by michael k 5/15/09 to support the PopupHelp dialog 135 removeAllControls: function() { 136 while (this.c$.length) { 137 var c = this.c$[0]; 138 this.removeControl(c); 139 c.destroy(); 140 } 141 /* 142 while (this.c$.length) this.removeControl(this.c$[0]); 143 for (var n in this.widgets) 144 this.removeWidget(this.widgets[n]); 145 while (this.domNode.firstChild) this.domNode.removeChild(this.domNode.firstChild); 146 */ 147 this.reflow(); 148 }, 149 insertControl: function(inControl, inIndex) { 150 this.c$.splice(inIndex, 0, inControl); 151 //this.dom.append(inControl.dom); 152 }, 153 moveControl: function(inControl, inIndex) { 154 var i0 = this.removeControl(inControl); 155 if (i0 < inIndex) 156 inIndex--; 157 this.c$.splice(inIndex, 0, inControl); 158 }, 159 indexOfControl: function(inControl) { 160 for (var i=0, c; c=this.c$[i]; i++){ 161 if (c == inControl) { 162 return i; 163 } 164 } 165 return -1; 166 }, 167 nextSibling: function(inControl) { 168 for (var i=0, c; c=this.c$[i]; i++){ 169 if (c == inControl) { 170 return this.c$[i+1]; 171 } 172 } 173 }, 174 prevSibling: function(inControl) { 175 for (var i=0, c; c=this.c$[i]; i++){ 176 if (c == inControl) { 177 return this.c$[i-1]; 178 } 179 } 180 }, 181 // 182 // Flow 183 // 184 reflow: function() { 185 this._boundsDirty = true; 186 if (this._cupdating) 187 return; 188 if (this.isDesignLoaded() && this.parent && (this.fitToContent || this.parent.fitToContent)) { 189 this.parent.reflow(); 190 if (this.fitToContent) { 191 this.calcFitToContent(); 192 } 193 } else { 194 this.flow(); 195 } 196 }, 197 flow: function() { 198 if (this._boundsDirty && !this._cupdating) { 199 this.layout.flow(this); 200 } 201 //else (!this._boundsDirty) 202 // console.log(this.name, ": not flowing (clean bounds)"); 203 }, 204 renderControls: function() { 205 for (var i=0, c; c=this.c$[i]; i++) { 206 c.render(); 207 } 208 }, 209 // bc 210 nodeBoundsChange: function() { 211 // should be caused by box layout flow 212 /* 213 this.setBounds(dojo.marginBox(this.domNode)); 214 this.flow(); 215 */ 216 }, 217 // 218 // Image list 219 // 220 imageListChanged: function() { 221 for (var i=0, c; c=this.c$[i]; i++) { 222 wm.fire(c, "imageListChanged"); 223 } 224 }, 225 setImageList: function(inImageList) { 226 this.imageList = inImageList; 227 this.imageListChanged(); 228 }, 229 // 230 // validation 231 // 232 validate: function() { 233 this.setValue("invalid", this.getInvalid()); 234 wm.fire(this.parent, "validate"); 235 }, 236 getInvalid: function() { 237 for (var i in this.widgets) { 238 var w = this.widgets[i]; 239 if (w.getInvalid && w.getInvalid()) 240 return true; 241 } 242 return false; 243 }, 244 // 245 // Lock/freeze 246 // 247 getLock: function() { 248 return this.lock || (this.parent && wm.fire(this.parent, "getLock")); 249 }, 250 setLock: function(inLock) { 251 this.lock = inLock; 252 studio.refreshComponentOnTree(this); 253 }, 254 getFreeze: function() { 255 return this.freeze || this.getLock(); 256 }, 257 // FIXME: design only? vestigal? 258 /* 259 findContainer: function(inType) { 260 if (!this.lock) { 261 if (this.freeze || !this.isWidgetTypeAllowed(inType)) { 262 for (var i in this.widgets) { 263 var w = this.widgets[i]; 264 if (w.container) { 265 var r = w.findContainer(inType); 266 if (r) 267 return r; 268 } 269 } 270 } else { 271 return this; 272 } 273 } 274 }, 275 */ 276 // used by paste 277 isWidgetTypeAllowed: function(inType) { 278 // subclasses should override this to enforce only certain widget types 279 // are allowed to be added to the container. 280 return true; 281 }, 282 /* 283 setBox: function(inBox) { 284 if (this.box != inBox) { 285 this.box = (this.containerNode || this.domNode).box = inBox; 286 // FIXME: wtf? 287 //if (this.isSizeable() || !this.isMoveable()) 288 this._reorientChildren(this.box); 289 this.reflow(); 290 } 291 }, 292 */ 293 /* 294 _reorientChildren: function(inBox) { 295 var b = inBox, bp = wm.Box.prototype, bw = bp.width, bh = bp.height; 296 var parentNode = this.containerNode || this.domNode; 297 wm.forEachProperty(this.widgets, function(w) { 298 if (w.domNode.parentNode != parentNode) 299 return; 300 var s = w.domNode.style, f = (b == 'flow' || b == ''); 301 if (f) { 302 s.position = 'static'; 303 w.left = w.top = ''; 304 w.updateBounds(); 305 } else 306 s.position = 'absolute'; 307 w.moveable = !f; 308 if (b == 'h' || b == 'v') { 309 w.width = bw; 310 w.height = bh; 311 w.updateBounds(); 312 } 313 }); 314 },*/ 315 _reorientChildren: function(inBox) { 316 var parentNode = this.containerNode || this.domNode; 317 wm.forEachProperty(this.widgets, function(w) { 318 if (w.domNode.parentNode != parentNode) 319 return; 320 var ww = w.width; 321 w.width = w.height; 322 w.height = ww; 323 w.updateBounds(); 324 }); 325 }, 326 clearData: function() { 327 var clear = function(w) { 328 if (w instanceof wm.Editor) 329 w.clear(); 330 } 331 this.forEachWidget(clear); 332 } 333 }); 334 335 // Design 336 337 wm.Container.extend({ 338 listProperties: function() { 339 var p = this.inherited(arguments); 340 p.freeze.ignore = this.schema.freeze.ignore || this.getLock(); 341 return p; 342 }, 343 writeChildren: function(inNode, inIndent, inOptions) { 344 var s = []; 345 wm.forEach(this.getOrderedWidgets(), function(c) { 346 if (wm.isDesignable(c) && !c.flags.notStreamable) 347 s.push(c.write(inIndent, inOptions)); 348 }); 349 return s; 350 }, 351 suggestDropRect: function(inControl, ioInfo) { 352 this.layout.suggest(this, inControl, ioInfo); 353 }, 354 suggestSize: function(inControl, ioInfo) { 355 this.layout.suggestSize(this, inControl, ioInfo); 356 }, 357 designMoveControl: function(inControl, inDropInfo) { 358 info = {l:inDropInfo.l, t:inDropInfo.t, i: inDropInfo.i}; 359 if (inControl.parent == this) { 360 // inDropInfo.index 'i' may be counting inControl 361 this.moveControl(inControl, info.i || 0); 362 } else { 363 var p = inControl.parent; 364 inControl.setParent(this); 365 inControl.designWrapper.controlParentChanged(); 366 // inDropInfo.index 'i' is never counting inControl 367 this.removeControl(inControl); 368 this.insertControl(inControl, info.i || 0); 369 if (p) 370 p.reflow(); 371 } 372 if (this.layout.insert) { 373 this.layout.insert(this, inControl, inDropInfo); 374 //return; 375 } 376 this.reflow(); 377 }, 378 resizeUpdate: function(inBounds) { 379 // update the boundary rectangle highlight only 380 this.designWrapper._setBounds(inBounds); 381 } 382 }); 383 384 // bc 385 wm.Container.prototype.clearEditors = wm.Container.prototype.clearData; 386 387 // this stuff is layout specific 388 389 wm.Container.extend({ 390 layoutKind: "top-to-bottom", 391 //layoutFit: false, 392 //contentAlign: "leftTop", 393 fitToContent: false, 394 horizontalAlign: "justified", 395 verticalAlign: "justified", 396 //horizontalAlign: "left", 397 //verticalAlign: "top", 398 makePropEdit: function(inName, inValue, inDefault) { 399 switch (inName) { 400 case "layoutKind": 401 return new wm.propEdit.Select({component: this, value: inValue, name: inName, options: wm.layout.listLayouts()}); 402 case "horizontalAlign": 403 return new wm.propEdit.Select({component: this, value: inValue, name: inName, options: ["left", "center", "right"/*, "justified"*/]}); 404 case "verticalAlign": 405 return new wm.propEdit.Select({component: this, value: inValue, name: inName, options: ["top", "middle", "bottom"/*, "justified"*/]}); 406 } 407 return this.inherited(arguments); 408 }, 409 setLayoutKind: function(inLayoutKind) { 410 if (this.layoutKind != inLayoutKind || !this.layout) { 411 var ctor = wm.layout.registry[inLayoutKind]; 412 if (!ctor) { 413 return; 414 } 415 this.layoutKind = inLayoutKind; 416 this.layout = new ctor(); 417 } 418 this.reflow(); 419 }, 420 setHorizontalAlign: function(inHorizAlign) { 421 this.horizontalAlign = inHorizAlign; 422 this.reflow(); 423 }, 424 setVerticalAlign: function(inVertAlign) { 425 this.verticalAlign = inVertAlign; 426 this.reflow(); 427 }, 428 setFitToContent: function(inFitToContent) { 429 this.fitToContent = inFitToContent; 430 this.updateBounds(); 431 this.reflowParent(); 432 this.calcFitToContent(); 433 this.reflowParent(); 434 }, 435 calcFitToContent: function() { 436 if (this.layoutKind == "top-to-bottom") 437 this.height = this.bounds.h + "px"; 438 else 439 this.width = this.bounds.w + "px"; 440 } 441 }); 442 443 wm.Object.extendSchema(wm.Container, { 444 layoutKind: {group: "layout", order: 100}, 445 horizontalAlign: {group: "layout", order: 110}, 446 verticalAlign: {group: "layout", order: 120}, 447 fitToContent: {group: "layout", order: 90} 448 }); 449