1 /* 2 Copyright (c) <2011>, <Gillis Haasnoot, Maarten van Schaik> 3 All rights reserved. 4 5 Redistribution and use in source and binary forms, with or without 6 modification, are permitted provided that the following conditions are met: 7 * Redistributions of source code must retain the above copyright 8 notice, this list of conditions and the following disclaimer. 9 * Redistributions in binary form must reproduce the above copyright 10 notice, this list of conditions and the following disclaimer in the 11 documentation and/or other materials provided with the distribution. 12 * Neither the name of the <organization> nor the 13 names of its contributors may be used to endorse or promote products 14 derived from this software without specific prior written permission. 15 16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY 20 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 /** 29 * @author Gillis Haasnoot <gillis.haasnoot@gmail.com> 30 * @package Banana 31 * @summary Main Application object 32 */ 33 34 /** 35 * this is needed for internet explorer which doesnt have a native indexOf method 36 */ 37 if (!Array.prototype.indexOf) 38 { 39 /** 40 * @ignore 41 */ 42 Array.prototype.indexOf = function(obj, start) 43 { 44 var i,j; 45 for (i = (start || 0), j = this.length; i < j; i++) { 46 if (this[i] === obj) { return i; } 47 } 48 return -1; 49 }; 50 } 51 52 53 goog.provide('Banana'); 54 55 goog.require('Banana.Util.NameSpace'); 56 goog.require('Banana.Util.Base'); 57 goog.require('Banana.Control'); 58 59 goog.require('Banana.Util.ArrayBiCollection'); 60 goog.require('Banana.Util.Utils'); 61 goog.require('Banana.Util.ValidationManager'); 62 goog.require('Banana.Util.UrlManager'); 63 goog.require('Banana.Util.StateManager'); 64 goog.require('Banana.Util.DomHelper'); 65 goog.require('Banana.Util.DateTimecode'); 66 goog.require('Banana.Util.Color'); 67 goog.require('Banana.Util.LogManager'); 68 69 70 goog.require('Banana.Data.DataSet'); 71 72 goog.require('Banana.Page'); 73 goog.require('Banana.PageTemplate'); 74 75 goog.require('Banana.Controls.StatusBar'); 76 goog.require('Banana.Controls.DataControls.TextBox'); 77 goog.require('Banana.Controls.DataControls.Password'); 78 goog.require('Banana.Controls.DataControls.TextArea'); 79 goog.require('Banana.Controls.DataControls.CheckBox'); 80 goog.require('Banana.Controls.DataControls.Table'); 81 goog.require('Banana.Controls.DataControls.Label'); 82 goog.require('Banana.Controls.DataControls.LimitCharLabel'); 83 goog.require('Banana.Controls.DataControls.DatePicker'); 84 goog.require('Banana.Controls.DataControls.DateTimePicker'); 85 goog.require('Banana.Controls.DataControls.DateSpan'); 86 goog.require('Banana.Controls.DataControls.Image'); 87 goog.require('Banana.Controls.DataControls.MaskedTextBox'); 88 goog.require('Banana.Controls.DataControls.RadioButton'); 89 goog.require('Banana.Controls.DataControls.ProgressBar'); 90 goog.require('Banana.Controls.DataControls.Timecode'); 91 goog.require('Banana.Controls.DataControls.ListControls.RadioButtonList'); 92 goog.require('Banana.Controls.DataControls.ListControls.CheckBoxList'); 93 goog.require('Banana.Controls.DataControls.ListControls.DropDown'); 94 goog.require('Banana.Controls.DataControls.ListControls.MultiSelect'); 95 goog.require('Banana.Controls.DataControls.Slider'); 96 goog.require('Banana.Controls.DataControls.UnorderedList'); 97 98 goog.require('Banana.Controls.DataGrid'); 99 100 goog.require('Banana.Controls.Window'); 101 goog.require('Banana.Controls.Button'); 102 goog.require('Banana.Controls.Link'); 103 goog.require('Banana.Controls.LinkButton'); 104 goog.require('Banana.Controls.Canvas'); 105 goog.require('Banana.Controls.Modal'); 106 goog.require('Banana.Controls.ConfirmModal'); 107 goog.require('Banana.Controls.Fieldset'); 108 goog.require('Banana.Controls.Legend'); 109 goog.require('Banana.Controls.Title'); 110 goog.require('Banana.Controls.Loader'); 111 goog.require('Banana.Controls.Form'); 112 goog.require('Banana.Controls.FileInput'); 113 goog.require('Banana.Controls.TabSlider'); 114 115 goog.require('Banana.Controls.Decorators.Decorator'); 116 goog.require('Banana.Controls.Decorators.TimecodeFieldValidator'); 117 goog.require('Banana.Controls.Decorators.RequiredFieldValidator'); 118 goog.require('Banana.Controls.Decorators.RegExValidator'); 119 goog.require('Banana.Controls.Decorators.CharLimitFieldValidator'); 120 goog.require('Banana.Controls.Decorators.LabelDecorator'); 121 goog.require('Banana.Controls.Decorators.LabelDecoratorRight'); 122 goog.require('Banana.Controls.Decorators.InfoDecorator'); 123 goog.require('Banana.Controls.Decorators.EqualValidator'); 124 125 goog.require('Banana.Controls.HBox'); 126 goog.require('Banana.Controls.VBox'); 127 128 129 /** 130 * Eventtypes used in Banana 131 */ 132 namespace('Banana.Controls').EventTypes = { 133 'DOM_EVENT':1, 134 'CUSTOM_EVENT':2 135 } 136 137 138 /** 139 @namespace Banana 140 */ 141 namespace('Banana').Application = Banana.Control.extend( 142 /** @lends Banana.Application.prototype */ 143 { 144 /** 145 * Main Banana Application components. 146 * This object is responsible for loading and running pages. 147 * By default the application is accessible through Banana.Application. 148 * if you need multiple instances, an application name is required. 149 * 150 * typicaly an instance of banana is started with 151 152 <div id="target"></div> 153 154 <script> 155 156 var applicationParameters = {}; 157 applicationParameters.renderTarget = 'target'; 158 applicationParameters.imagedir ='images'; 159 applicationParameters.defaultSection="Home"; 160 applicationParameters.pageTemplate="PageTemplate"; 161 applicationParameters.paths = {} 162 applicationParameters.applicationName = "foo" //needed if you want to run multiple instances of banana 163 applicationParameters.paths.pages = "Application.Pages"; 164 applicationParameters.paths.pageTemplates = "Application.Pages.PageTemplates"; 165 var app = new Banana.Application(applicationParameters); 166 app.run(); 167 168 </script> 169 170 * 171 * It is also possible to launch multiple instances of banana. 172 * Make sure the applicationName is unique 173 * 174 var applicationParameters = {}; 175 applicationParameters.renderTarget = 'target'; //id of the target div 176 applicationParameters.imagedir ='images'; 177 applicationParameters.paths = {}; 178 applicationParameters.applicationName = "myUniqueApp"; 179 applicationParameters.paths.pages = "Application.Pages"; 180 applicationParameters.paths.pageTemplates = "Application.Pages.PageTemplates"; 181 182 183 var pageClass = Banana.Page.extend({ 184 185 createComponents : function() 186 { 187 //do something here 188 } 189 }); 190 191 var page = new pageClass(); 192 193 var app = new Banana.Application(applicationParameters); 194 app.run(page); 195 196 * 197 * 198 * Following parameters are available for creating a new application 199 * 200 * - renderTarget Id of the dom element where the application is rendered in. 201 * - imagedir relative path of various images Banana uses 202 * - paths.pages namespace of Banana Pages (by default "Application.Pages") 203 * - paths.pageTemplates namespace of Banana Template Pages (by default "Application.Pages.PageTemplates") 204 * 205 * A new page can be loaded by calling loadPage(String page). The current page will be removed 206 * and replaced by the new page. Url history params are restored in case an already visited page is loaded. 207 * 208 * 209 * @constructs 210 * @param {Object} settings 211 */ 212 init : function(settings) 213 { 214 // Add the logger 215 log = new Banana.Util.LogManager(); 216 log.addLogger(new Banana.Util.Logger.Console()); 217 log.setLevel("error", true); 218 log.setLevel("warning", true); 219 220 try 221 { 222 jQuery(); 223 } 224 catch(e) 225 { 226 log.error("JQuery is not found. JQuery required."); 227 } 228 229 if (!settings) 230 { 231 settings = {}; 232 } 233 234 this.settings = settings; 235 236 //to remember the vertical scroll position of a page 237 this.sectionVerticalScrollPosition ={}; 238 this.id = 0; // Will increment on each page load 239 240 if (settings.applicationName) 241 { 242 namespace('Banana')[settings.applicationName] = this; 243 } 244 else 245 { 246 namespace('Banana').Application = this; 247 } 248 249 if (!settings.pageTemplate) 250 { 251 settings.pageTemplate = "PageTemplate"; 252 } 253 254 if (!settings.paths) 255 { 256 settings.paths = {}; 257 } 258 259 if (!settings.paths.pages) 260 { 261 settings.paths.pages = "Application.Pages"; 262 } 263 264 this.prepareUnload(); 265 }, 266 267 /** 268 * When a user closes/refresh the browser tab/window, 269 * the current page will be removed + all registered event 270 * @ignore 271 */ 272 prepareUnload : function() 273 { 274 jQuery(window).unload( this.getProxy(function() 275 { 276 jQuery('*').unbind(); 277 if (this.activePage) 278 { 279 this.activePage.remove(); 280 } 281 if (this.pageTemplate) 282 { 283 this.pageTemplate.remove(); 284 } 285 delete Banana; 286 }) ); 287 }, 288 289 /** 290 * Automatically called by Banana 291 * The page template is specified in application settings with the "pageTemplate" property. 292 * If no Page template is given, we use default Banana.PageTemplate 293 * @ignore 294 */ 295 loadPageTemplate : function() 296 { 297 if (!this.settings.paths.pageTemplates) 298 { 299 pageTemplateClass = Banana.PageTemplate; 300 } 301 else 302 { 303 var path = this.settings.paths.pageTemplates; 304 305 var objectname = path+'.'+this.settings.pageTemplate; 306 307 var pageTemplateClass = Banana.Util.NamespaceToFunction(objectname); 308 } 309 310 if (!pageTemplateClass) 311 { 312 log.error("Error: could not find Template: "+objectname); 313 314 return; 315 } 316 317 try 318 { 319 this.pageTemplate = new pageTemplateClass(); 320 } 321 catch(e) 322 { 323 log.error("Error: could not instantiate template: "+objectname); 324 } 325 326 }, 327 328 /** 329 * @returns {Array} of settings 330 */ 331 getSettings : function() 332 { 333 return this.settings; 334 }, 335 336 /** 337 * @param {String} section 338 */ 339 setSectionVerticalScrollPosition : function(section) 340 { 341 this.sectionVerticalScrollPosition[section] = window.scrollY; 342 }, 343 344 /** 345 * 346 * @param {String} section 347 * @returns {String} 348 */ 349 getSectionVerticalScrollPosition : function(section) 350 { 351 return this.sectionVerticalScrollPosition[section]; 352 }, 353 354 355 /** 356 * Loads a page. 357 * 358 * Loads a page by specifying the full namespace name of the page. 359 * Additional parameters can be send along. Parameters should be in key => value format 360 * stored in an Object. All parameters will be written in the hash section of the url. 361 * Loading a page also results in saving the url parameters of the current page. This means 362 * that we when we have a url like http://mysite?page=foo#section=Home&id=12 363 * we save the id parameter only. Unless specified we restore the original parameters when the 364 * page is reloaded. 365 * 366 * @param {string} page to be loaded. This is the full namespace name 367 * @param {object} key value format 368 * @param {bool} ignoreHistoryParams if true we ignore previous url params. 369 */ 370 loadPage : function(page,urlParams,ignoreHistoryParams) 371 { 372 //we save the current url params to use them later on when we return to this page 373 Banana.Util.UrlManager.saveModuleHistory('section'); 374 375 //we also save the current vertical scroll position. so when we return we also return to scroll position 376 this.setSectionVerticalScrollPosition(this.section); 377 378 //clean the url 379 Banana.Util.UrlManager.clearUrl(); 380 381 //Here we assign the history property to a global class property. 382 //We need to do this because below code which is registering url parameters in the urlmanager one by one. 383 //If every Url Manager action results into a url change, we would get loads of history points 384 //in our browser. This is something we don't want. To prevent this situation we register them 385 //but without modifing the url. Later in the Run() method we modify the url at once. 386 this.loadingWithHistory = !ignoreHistoryParams; 387 388 if (this.loadingWithHistory) 389 { 390 var history = Banana.Util.UrlManager.getModuleHistory(page); 391 392 var his; 393 for (his in history) 394 { 395 if (history.hasOwnProperty(his)) 396 { 397 Banana.Util.UrlManager.registerModule(his); 398 Banana.Util.UrlManager.setModule(his,history[his], true); 399 } 400 } 401 } 402 403 if (urlParams) 404 { 405 var prop; 406 407 for (prop in urlParams) 408 { 409 if (urlParams.hasOwnProperty(prop)) 410 { 411 Banana.Util.UrlManager.registerModule(prop); 412 Banana.Util.UrlManager.setModule(prop,urlParams[prop], true); 413 } 414 } 415 } 416 417 this.run(page); 418 419 420 421 //restore vertical scroll position 422 if (!ignoreHistoryParams) 423 { 424 window.scrollTo(0,this.getSectionVerticalScrollPosition(page) || 0); 425 } 426 }, 427 428 /** 429 * This method is used when only one single page instance is attached to the application. 430 * 431 * @ignore 432 */ 433 instantiateSinglePage : function() 434 { 435 if (!this.pageTemplate) 436 { 437 this.loadPageTemplate(); 438 } 439 440 this.page.bind('renderComplete',this.getProxy(function(){ 441 442 if (this.activePage) 443 { 444 this.activePage.removeDom(); 445 this.activePage.clearProps(); 446 this.activePage = undefined; 447 } 448 449 this.activePage = this.page; 450 this.activePage.setVisible(true); 451 })); 452 453 //our new page will be not visible 454 this.page.setVisible(false); 455 456 //now we add it to the pagetemplate 457 this.pageTemplate.addPageControl(this.page); 458 459 this.pageTemplate.setApplication(this); 460 461 //load the whole page 462 this.pageTemplate.run(this); 463 }, 464 465 /** 466 * Run a page 467 * 468 * This function should only be initially called to bootstrap the 469 * framework. Consecutive pages should be loaded by using {@link #loadPage}. 470 * 471 * @param {string} page 472 */ 473 run : function(page) 474 { 475 // Save the parameter 476 if (page) 477 { 478 this.page = page; 479 } 480 481 if (page instanceof Banana.Page) 482 { 483 return this.instantiateSinglePage(); 484 } 485 486 this.triggerEvent('onPreLoadPage'); 487 488 // If this is the first page we run, we register all the url parameters 489 if (!this.activePage) 490 { 491 Banana.Util.UrlManager.autoRegisterModules(); 492 } 493 494 //if we have no pagetemplate loaded, usually the first time, we load it 495 if (!this.pageTemplate) 496 { 497 this.loadPageTemplate(); 498 } 499 500 // Determine page (section) to load 501 var section = this.page || Banana.Util.UrlManager.getModule('section') || this.settings.defaultSection; 502 this.section = section; 503 504 // Unset saved parameter for next calls 505 this.page = undefined; 506 507 //the register url section registers the section without modifing the url. 508 //we receive a change event 509 this.registerUrlSection(section); 510 //apply url change in the Url Manager 511 if (!this.loadingWithHistory) 512 { 513 Banana.Util.UrlManager.updateUrl(); 514 } 515 516 var load = this.getProxy(function(){ 517 518 var objectname = this.settings.paths.pages+'.'+section; 519 var pageClass = Banana.Util.NamespaceToFunction(objectname); 520 521 //if the pageclass cannot be find use the default one 522 if (!pageClass) 523 { 524 if (this.settings.defaultSection === section) 525 { 526 log.error("Error default page "+this.settings.defaultSection+" cannot be loaded"); 527 return; 528 } 529 else 530 { 531 log.error("Page "+section+" not found. Load default page "+this.settings.defaultSection); 532 } 533 return this.loadPage(this.settings.defaultSection); 534 } 535 536 //id of the application which thus increments every page load 537 this.id++; 538 539 var newPage = new pageClass(); 540 541 //when a page is rendered we remove the old page. 542 //We already removed its events and timers. now its time for the visible items as well 543 //We also remove its properties. This is too prevent memory leaks 544 newPage.bind('renderComplete',this.getProxy(function(){ 545 546 if (this.activePage) 547 { 548 this.activePage.removeDom(); 549 this.activePage.clearProps(); 550 this.activePage = undefined; 551 } 552 553 this.activePage = newPage; 554 this.activePage.setVisible(true); 555 556 })); 557 558 //our new page will be not visible yet 559 newPage.setVisible(false); 560 561 //now we add it to the pagetemplate 562 this.pageTemplate.addPageControl(newPage); 563 564 this.pageTemplate.setApplication(this); 565 566 //run the page. if everything is ok we will receive a renderComplete event 567 this.pageTemplate.run(); 568 569 //update the url to its final state 570 Banana.Util.UrlManager.updateUrl(); 571 }); 572 573 //if we have an active page we want it to be removed 574 //but only its internal data. like (events, timers and more) 575 //the dom needs to stay to prevent an ugly flickering between page transitions 576 if (this.activePage) 577 { 578 //this is extremely important. Our remove function below is asynchronous 579 // it might happen that the section change which is also asnchronous (statement above) results into 580 //a url change during removing of the controls. I have to admit that 581 //Oh yeah is a hacky solution. and very difficult to debug or to even understand. 582 Banana.Util.UrlManager.stopChecking(); 583 584 this.activePage.remove(true,this.getProxy(function(){ 585 Banana.Util.UrlManager.startChecking(); 586 load(); 587 Banana.Util.UrlManager.listenModule('section',this.getProxy(function() 588 { 589 this.loadPage(Banana.Util.UrlManager.getModule('section')); 590 })); 591 592 })); 593 } 594 else 595 { 596 load(); 597 Banana.Util.UrlManager.listenModule('section',this.getProxy(function() 598 { 599 this.loadPage(Banana.Util.UrlManager.getModule('section')); 600 })); 601 602 } 603 }, 604 605 /** 606 * Registers section url and binds a change event to it 607 * which determines which page should be loaded 608 * @param {String} section 609 * @ignore 610 */ 611 registerUrlSection : function(section) 612 { 613 Banana.Util.UrlManager.registerModule('section',section); 614 615 Banana.Util.UrlManager.setModule('section',section,true); 616 } 617 }); 618