1 /** 2 * @author Gillis Haasnoot <gillis.haasnoot@gmail.com> 3 * @package Banana 4 * @summary Page 5 */ 6 7 goog.provide('Banana.Page'); 8 9 goog.require('Banana.Controls.Panel'); 10 11 /** @namespace Banana.Page */ 12 namespace('Banana').Page = Banana.Controls.Panel.extend( 13 /** @lends Banana.Page.prototype */ 14 { 15 /** 16 * Creates a page component. 17 * 18 * Page is responsible for 19 * 20 * rendering its control collection, 21 * register events on controls, 22 * unregister events on controls, 23 * apply databinding on controls, 24 * removing controls from data/dom, 25 * 26 * page should not be manually instantiated. Application is responsible for this. 27 * 28 * example of a page: 29 30 var mypage = Banana.Page.extend({}); 31 * 32 * point your url to §ion=mypage 33 * @constructs 34 * @extends Banana.Controls.Panel 35 */ 36 init : function() 37 { 38 this.triggerEvent('onInit'); 39 40 this.addCssClass('BPage'); 41 42 this._super(); 43 44 this.uniqueId = 0; 45 this.validators = []; 46 this.dataSets = []; 47 48 if (!this.resizefunc) 49 { 50 this.resizefunc = this.getProxy(function() 51 { 52 this.onWindowResize(this); 53 }); 54 } 55 jQuery(window).bind('resize',this.resizefunc); 56 } 57 58 }); 59 60 /** 61 * reference to application Default = Banana.Application 62 * @param {Banana.Application} app 63 */ 64 Banana.Page.prototype.setApplication = function(app) 65 { 66 this.application = app; 67 }; 68 69 /** 70 * @return {Banana.Application} 71 */ 72 Banana.Page.prototype.getApplication = function() 73 { 74 return this.application; 75 }; 76 77 78 /** 79 * Starting point to initialize new page render. 80 * The application is responsible for calling this method 81 * 82 * @param {mixed} target could be a string or object. in case of string we assume we have a dom id. 83 */ 84 Banana.Page.prototype.run = function(target) 85 { 86 this.validationControls = {}; 87 88 this.initRender(this, target || this.getApplication().settings.renderTarget); 89 }; 90 91 92 /** 93 * initialize rendering process 94 * 95 * @param {Banana.Control} control 96 * @param {Banana.UiControl} target 97 * @param {Banana.UiControl} place optional replace control 98 * @param {boolean} wasRendering true if this control is already in a rerender phase 99 * @param {boolean} parentRerendering true when parent control is currently rerendering 100 */ 101 Banana.Page.prototype.initRender = function(control,target,place,wasRerendering,parentRendering) 102 { 103 this.rendering = true; 104 105 if (wasRerendering) 106 { 107 //if the page was already rerendering we dont need to render this action again 108 //we still need to rerender and register events on the control. 109 this.renderControl(control,target,place); 110 this.rendering = false; 111 this.recursiveRegisterEvents(control); 112 return; 113 } 114 115 this.isRendered = false; 116 117 this.initializeControl(control,target); 118 119 this.renderControl(control,target,place); 120 121 this.isRendered = true; 122 this.setVisible(true); 123 124 //some controls are not Banana.Controls but plain text 125 if (control && control.triggerEvent) 126 { 127 control.triggerEvent('renderComplete',this); 128 } 129 130 131 //if parent is already rendering we dont have to register events 132 //parent control will do that for us 133 if (!parentRendering) 134 { 135 this.recursiveRegisterEvents(control); 136 137 //only set this boolean to false when parent is not rerendering. then the parent will eventualy make sure that 138 //this bool will be false 139 this.rendering = false; 140 141 this.parentFirstTimeRendering = false; 142 } 143 144 this.recursiveUpdateDisplay(control); 145 }; 146 147 /** 148 * rerenders control 149 * 150 * @param {Banana.Control} control 151 */ 152 Banana.Page.prototype.rerender = function(control) 153 { 154 ///this bool is needed in the following situation: 155 // call method updatedisplay on child controls from current method updatedisplay 156 // creates a situation that the the isRerendering bool is set to false in the first updatedisplay 157 // updatedisplay A -> isRerendering = true 158 // updateDisplay B -> isRerendering = true 159 // updateDisplay B is finished we set isRerendering to false 160 // updateDisplay A is not finished yet and still needs to register events. but isRerendering is false 161 // to prevent this situation we added this bool 162 var parentRerendering = this.isRerendering; 163 var parentRendering = this.rendering; 164 165 //exception case 166 if (parentRendering && !parentRerendering) 167 { 168 this.parentFirstTimeRendering = true; 169 } 170 171 this.isRerendering = true; 172 173 if (!parentRendering) 174 { 175 this.recursiveUnRegisterEvents(control); 176 } 177 178 var orig = control.getFirstUiControl(); 179 var parentControl = orig.getParent(); 180 181 this.initRender(control, parentControl, orig,parentRerendering,parentRendering); 182 183 // Restore this.isRerendering 184 this.isRerendering = parentRerendering; 185 }; 186 187 /** 188 * sets content placeholder page 189 * 190 * @param {Banana.PageTemplate} ph 191 */ 192 Banana.Page.prototype.setContentPlaceHolder = function(ph) 193 { 194 this.contentPlaceHolder = ph; 195 }; 196 197 /** 198 * gets content placeholder 199 * 200 * @return {Banana.PageTemplate} 201 */ 202 Banana.Page.prototype.getContentPlaceHolder = function() 203 { 204 return this.contentPlaceHolder; 205 }; 206 207 /** 208 * sets content placeholder page 209 * 210 * @param {Banana.PageTemplate} ph 211 */ 212 Banana.Page.prototype.setPageTemplate = function(ph) 213 { 214 this.contentPlaceHolder = ph; 215 }; 216 217 /** 218 * gets content placeholder 219 * 220 * @return {Banana.PageTemplate} 221 */ 222 Banana.Page.prototype.getPageTemplate = function() 223 { 224 return this.contentPlaceHolder; 225 }; 226 227 228 /** 229 * Hides loader 230 */ 231 Banana.Page.prototype.hideLoader = function() 232 { 233 if (!this.loader) {return;} 234 this.loader.hide(); 235 }; 236 237 /** 238 * Datasets are to centralize data storage of your controls 239 * You can either use stand alone datasets and manually assign them to your controls or 240 * add datasets to the page and let the page automatically handle the data. 241 * 242 * @param {String} id for the dataset 243 * @param {Banana.Data.DataSet} the dataset 244 */ 245 Banana.Page.prototype.addDataSet = function(id,d) 246 { 247 if (this.dataSets[id]) 248 { 249 log.warning('Dataset id "'+id+'" already exists'); 250 return; 251 } 252 d.id = id; 253 254 this.dataSets[id] = d; 255 }; 256 257 /** 258 * @return Banana.Data.DataSet 259 * 260 * @param string id of the dataset 261 */ 262 Banana.Page.prototype.getDataSet = function(id) 263 { 264 return this.dataSets[id]; 265 }; 266 267 /** 268 * removes all datasets from page 269 */ 270 Banana.Page.prototype.removeDataSets = function() 271 { 272 var index; 273 for (index in this.dataSets) 274 { 275 if (typeof(this.dataSets[index]) === 'function') { continue;} 276 277 this.dataSets[index].clear(); 278 } 279 280 this.dataSets = []; 281 }; 282 283 /** 284 * validates all controls 285 * 286 */ 287 Banana.Page.prototype.isValid = function() 288 { 289 return Banana.Util.ValidationManager.validateAll(); 290 }; 291 292 /** 293 * initialize controls 294 * this method walks through all controls and sets the following things 295 * 296 * clientId -> for all Banana.Controls 297 * parent -> parent control holding this control 298 * page -> reference to the page 299 * 300 * Page also automatically binds controls to their datasets 301 * 302 * @param {Banana.Control} control 303 * @param {Banana.Control} target control used to determine parent 304 */ 305 Banana.Page.prototype.initializeControl = function(control,target) 306 { 307 //only handle banana controls 308 if (!(control instanceof Banana.Control)) 309 { 310 return; 311 } 312 313 //if the target is not a ui control we fetch the first anchester which is a ui control 314 if (target instanceof Banana.Control) 315 { 316 if (!control.clientId) 317 { 318 control.setClientId(target.getClientId()+'-'+this.uniqueId++); 319 } 320 } 321 //if the target is just a string then we assume it the first element 322 else 323 { 324 var sn = this.getApplication().settings.applicationName; 325 326 if (sn) 327 { 328 control.setClientId(sn+this.uniqueId++); 329 } 330 else 331 { 332 control.setClientId(this.uniqueId++); 333 } 334 } 335 336 control.setParent(target); 337 control.setPage(this); 338 339 if (!control.isInitialized) 340 { 341 /* 342 @createComponents Method is applied on all controls during creation of the page. 343 In this state thi 344 */ 345 control.createComponents(); 346 control.isInitialized = true; 347 348 //bs/bd[0] = dataset name 349 //bs/bd[1] = dataset property name 350 var bs = control.bindedDataSource; 351 if (bs) 352 { 353 if (this.getDataSet(bs[0])) 354 { 355 this.getDataSet(bs[0]).bindControlToDataSource(control); 356 } 357 } 358 359 var bd = control.bindedData; 360 if (bd) 361 { 362 if (this.getDataSet(bd[0])) 363 { 364 this.getDataSet(bd[0]).bindControlToData(control); 365 } 366 } 367 } 368 369 // Because in the create components other controls can be added, we 370 // call this function after the createComponents 371 var childs = control.getControls(); 372 373 var i, len; 374 for (i = 0, len = childs.length; i < len; i++) 375 { 376 this.initializeControl(childs[i],control); 377 } 378 }; 379 380 /** 381 * We render the control by fetching all html data from the control. 382 * 383 * @param {Banana.Control|String} control Control which should be rendered. 384 * @param {Banana.Control} target The target where the control should be rendered in. 385 * @param {Banana.Control} place (optional) If given we replace the old control html data with new data 386 */ 387 Banana.Page.prototype.renderControl = function(control,target,place) 388 { 389 if (control instanceof Banana.Control) 390 { 391 var data = control.getHtml(true); 392 } 393 else 394 { 395 data = control; 396 } 397 398 if (place) 399 { 400 Banana.Util.DomHelper.replace(data,place); 401 } 402 else 403 { 404 Banana.Util.DomHelper.render(data,target); 405 } 406 }; 407 408 409 /** 410 * Call updateDisplay on all controls in the hierarchy 411 * 412 * Controls can perform post-render actions in this function. These 413 * will also be called on rerender. 414 * 415 * As a side-effect, the isRendered-property of all controls will be set. 416 * This should happen in the {@link #renderControl}-function, but we don't 417 * have a recursive pass there. 418 * 419 * Events are not registered yet in updateDisplay! 420 * 421 * @param {Banana.Control|string} control Control which is rendered 422 */ 423 Banana.Page.prototype.recursiveUpdateDisplay = function(control) 424 { 425 if (control instanceof Banana.Control) 426 { 427 // TODO: a parent control asking a child control to be rendered returns false. 428 // but in reality it is rendered. This flag should be moved to earlier phase 429 control.isRendered = true; 430 431 control.updateDisplay(); 432 433 var childs = control.getControls(); 434 435 var i, len; 436 437 for (i = 0, len = childs.length; i < len; i++) 438 { 439 this.recursiveUpdateDisplay(childs[i]); 440 } 441 } 442 }; 443 444 445 /** 446 * Register all binded events to control 447 * 448 * @param {Banana.Control|string} control 449 */ 450 Banana.Page.prototype.recursiveRegisterEvents = function(control) 451 { 452 if (control instanceof Banana.Control) 453 { 454 control.registerEvents(); 455 var childs = control.getControls(); 456 457 var i, len; 458 for (i = 0, len = childs.length; i < len; i++) 459 { 460 this.recursiveRegisterEvents(childs[i]); 461 } 462 } 463 }; 464 465 /** 466 * unregister events from control 467 * 468 * @param {Banana.Control} control 469 */ 470 Banana.Page.prototype.recursiveUnRegisterEvents = function(control) 471 { 472 if (control instanceof Banana.Control) 473 { 474 control.onPreInvalidateContents(); 475 control.unregisterEvents(); 476 477 if (control instanceof Banana.Control) 478 { 479 var childs = control.getControls(); 480 481 var i, len; 482 for (i = 0, len = childs.length; i < len; i++) 483 { 484 this.recursiveUnRegisterEvents(childs[i]); 485 } 486 487 } 488 } 489 }; 490 491 /** 492 * completely removes a control from dom and data model 493 * 494 * @param Banana.Control 495 * @param bool dont remove dom when true. this is an optimalisation. 496 * from the controls which are getting removed, the root items (the one user want to remove) should 497 * be treated differently. 498 * 499 * <Root item> - <ChildItems> 500 * parent control collection altered parent control collection stays 501 * call dom remove dont call dom remove (done in root) 502 * 503 */ 504 Banana.Page.prototype.removeControl = function(control,dontRemoveDom) 505 { 506 if (!(control instanceof Banana.Control)) 507 { 508 return; 509 } 510 511 var childs = control.controls.slice(); //slice to clone 512 513 var i; 514 for (i = childs.length-1; i >= 0; i--) 515 { 516 this.removeControl(childs[i],true); 517 childs.pop(); 518 } 519 520 control.unregisterEvents(); 521 522 control.unload(); 523 524 if (!dontRemoveDom) 525 { 526 Banana.Util.DomHelper.remove(control); 527 } 528 529 //if our control got a parent, then we also need to remove it from the parent array controls 530 //we only do this is we directly call remove on this control, otherwise we remove the controls array anywayy 531 var parent = control.parent; 532 533 if (parent) 534 { 535 var indexInParent = parent.controls.indexOf(control); 536 537 parent.controls.splice(indexInParent,1); 538 } 539 540 //delete all props to prevent memory leaks ---> Does it really helps???? 541 // var prop; 542 // for (prop in control) 543 // { 544 // if (prop !== 'parent') 545 // { 546 // delete control[prop]; 547 // } 548 // } 549 550 control = undefined; 551 }; 552 553 554 /** 555 * clears a control by removing all children 556 * 557 * @param {Banana.Control} control 558 */ 559 Banana.Page.prototype.clearControl = function(control) 560 { 561 var childs = control.getControls().slice(); 562 563 var totalCleared = 0; 564 565 var i, len; 566 for (i = 0, len = childs.length; i < len; i++) 567 { 568 totalCleared++; 569 this.removeControl(childs[i],true); 570 } 571 572 control.controls = null; 573 574 childs = null; 575 576 //TODO added this to also remove non object controls ie strings. 577 //move this to domhelper.js 578 jQuery('#'+control.getClientId()).empty(); 579 }; 580 581 /** 582 * removes complete page plus all its controls/events/binds and dom data 583 * We traverse through child controls in a async operation. This is needed 584 * to prevent slow script running detects by various browsers 585 * 586 * @param {boolean} keepDom when true we dont remove the dom 587 */ 588 Banana.Page.prototype.remove = function(keepPageDom,cb) 589 { 590 //get flatten depth first collection of control hierargie 591 var flat = Banana.Util.flattenTreeDepthFirst(this,'controls'); 592 593 flat.pop(); //remove the last page which is {this} we handle {this} is the complete event 594 595 Banana.Util.arrayInteratorAsync(flat,this.getProxy(function(control,i){ 596 597 if (!(control instanceof Banana.Control)) 598 { 599 return; 600 } 601 602 control.unregisterEvents(); 603 control.unload(); 604 605 var parent = control.parent; 606 607 if (parent && parent.controls) 608 { 609 parent.controls.splice(parent.controls.indexOf(control),1); 610 } 611 control = undefined; 612 }), 613 0, //timeout for each chuck 614 this.getProxy(function(){ //callback called after completion 615 616 this.unload(); 617 618 //remove all validators 619 Banana.Util.ValidationManager.removeValidators(); 620 621 this.clearIds(); 622 623 //if we clear the page we also need to unregister the resize event. otherwise the callback will always 624 //be called after screensize changes. 625 jQuery(window).unbind(); 626 627 this.unbind(); 628 this.removeDataSets(); 629 630 clearTimeout(this.timer); 631 632 if (!keepPageDom) 633 { 634 Banana.Util.DomHelper.remove(this); 635 } 636 637 jQuery('#debug').remove(); 638 clearTimeout(this.debugTimer); 639 640 if (cb){ 641 cb(); 642 } 643 }) 644 ,this.getProxy(function(){ 645 646 //not implemented here 647 })); 648 }; 649 650 /** 651 * used internally -> should move to another location 652 * @ignore 653 */ 654 Banana.Page.prototype.removeDom = function() 655 { 656 jQuery('#cleared').remove(); 657 }; 658 659 /** 660 * @ignore 661 */ 662 Banana.Page.prototype.clearProps = function() 663 { 664 // var prop; 665 // for (prop in this) 666 // { 667 // //this[prop] = undefined; 668 // } 669 }; 670 671 /** 672 * 673 * @param Banana.UiControl 674 */ 675 Banana.Page.prototype.onWindowResize = function(control) 676 { 677 if (control instanceof Banana.UiControl) 678 { 679 control.onWindowResize(); 680 681 var childs = control.getControls(); 682 683 var i, len; 684 for (i = 0, len = childs.length; i < len; i++) 685 { 686 this.onWindowResize(childs[i]); 687 } 688 } 689 }; 690 691 /** 692 * Clears all ids in the page. 693 * Used by application during page transition. 694 */ 695 Banana.Page.prototype.clearIds = function(control) 696 { 697 if (!control) 698 { 699 control = this; 700 } 701 Banana.Util.DomHelper.clearIdsFrom(control.getClientId()); 702 };