1 /** 2 * @author Gillis Haasnoot <gillis.haasnoot@gmail.com> 3 * @package Banana 4 * @summary Usi Control component is the base class for all renderable controls. 5 */ 6 7 goog.provide('Banana.UiControl'); 8 9 goog.require('Banana.Control'); 10 11 /** @namespace Banana.UiControl */ 12 namespace('Banana').UiControl = Banana.Control.extend( 13 /** @lends Banana.UiControl.prototype */ 14 { 15 /** 16 * @constructs 17 * @extends Banana.Control 18 */ 19 init: function() 20 { 21 this._super(); 22 this.css = {}; 23 this.style = ''; 24 this.isRendered = false; 25 this.visible = true; //default is visible 26 this.enabled = true; 27 }, 28 29 /** 30 * Adds control to collection 31 * Normally you would add control prior to the render phase. If this is not the case 32 * and you need to instantly render the control. autoRender should be true 33 * 34 * @param {mixed} Banana.Control or plain text 35 * @param {boolean} true when render should occur instantly 36 */ 37 addControl : function(c,autoRender) 38 { 39 //when a control is added on a already initialized control, we recall the initialization 40 //otherwise the control wont have a proper client id/databind and references to various objects 41 //TODO is this a performance hit, cause sometimes it is not needed 42 if (this.isInitialized) 43 { 44 if (autoRender) 45 { 46 //the render function also contains initializeControls, but we dont know if our item got sibblings, if so the client id should be amount of sibblings+1 47 this.getPage().initializeControl(c,this,false,this.controls.length+1); 48 this.render(c); 49 } 50 else 51 { 52 this.getPage().initializeControl(c,this,false,this.controls.length+1); 53 } 54 } 55 56 return this._super(c); 57 } 58 }); 59 60 /* 61 * @return object jquery dom writer 62 */ 63 Banana.UiControl.prototype.getDomWriter = function() 64 { 65 return jQuery('#'+this.getClientId()); 66 }; 67 68 /** 69 * @return string tag name of control 70 * overwrite this function to use your own tags. ie <div> <a> <p> <input> <label> 71 */ 72 Banana.UiControl.prototype.getTagName = function(){return '';}; 73 74 /** 75 * Computes widht,height and left right offsets 76 * 77 * @return {Object} of dimensions 78 */ 79 Banana.UiControl.prototype.getDimensions = function() 80 { 81 var params = {}; 82 83 params.height = jQuery('#'+this.getClientId()).height(); 84 params.width = jQuery('#'+this.getClientId()).width(); 85 params.offset = jQuery('#'+this.getClientId()).offset(); 86 params.position = jQuery('#'+this.getClientId()).position(); 87 88 return params; 89 }; 90 91 /** 92 * sets client id used to reference dom node. This method is auto called by the page 93 * @param {String} cid 94 */ 95 Banana.UiControl.prototype.setClientId = function(cid) 96 { 97 this.clientId = cid; 98 }; 99 100 /** 101 * @return {String} reference to the dom node id 102 */ 103 Banana.UiControl.prototype.getClientId = function() 104 { 105 return this.clientId; 106 }; 107 108 /** 109 * Triggers an event which will notify all listeners 110 * Optionally data can be send along. 111 * 112 * @param {String} name of the event to be triggered 113 * @param {mixed} data 114 */ 115 Banana.UiControl.prototype.triggerEvent = function(name,data) 116 { 117 if (this.getDomEventTypes().indexOf(name) !== -1) 118 { 119 if (this.isRendered) 120 { 121 jQuery('#'+this.clientId).trigger(name,data); 122 } 123 } 124 else 125 { 126 jQuery(this).trigger(name,data); 127 } 128 }; 129 130 /** 131 * Saves key value in a cookie 132 * Note that saved states are always unique for a page. 133 * 134 * @param {String} name of the state 135 * @param {String} value 136 * 137 * @return {this} 138 */ 139 Banana.UiControl.prototype.setState = function(name,value) 140 { 141 // We want the state to be unique for every page and control id 142 var page = Banana.Util.UrlManager.getModule('section'); 143 // check if control id is set 144 var id = this.getClientId(); 145 if (this.customId) 146 { 147 id = this.getId(); 148 } 149 Banana.Util.StateManager.setState(page + '-' + id + '-' + name, value); 150 151 return this; 152 }; 153 154 /** 155 * Retreives a saved state 156 * 157 * @param {String} name of state 158 * @return {String} 159 */ 160 Banana.UiControl.prototype.getState = function(name) 161 { 162 var page = Banana.Util.UrlManager.getModule('section'); 163 // check if control id is set 164 var id = this.getClientId(); 165 if (this.customId) 166 { 167 id = this.getId(); 168 } 169 170 return Banana.Util.StateManager.getState(page + '-' + id + '-' + name); 171 }; 172 173 /** 174 * Removes a saved state 175 * 176 * @param {String} name of state 177 * @return {String} 178 */ 179 Banana.UiControl.prototype.removeState = function(name) 180 { 181 var page = Banana.Util.UrlManager.getModule('section'); 182 // check if control id is set 183 var id = this.getClientId(); 184 if (this.customId) 185 { 186 id = this.getId(); 187 } 188 189 return Banana.Util.StateManager.removeState(page + '-' + id + '-' + name); 190 }; 191 192 /** 193 * Composes attributes for usuage in html tags. 194 * @return {Object} of attributes 195 */ 196 Banana.UiControl.prototype.getAttributes = function() 197 { 198 var attributes = {}; 199 200 if (this.attributes) 201 { 202 attributes = this.attributes; 203 } 204 205 attributes['id'] = this.getClientId(); 206 attributes['style'] = this.getStyle(); 207 208 if (this.getCss()) 209 { 210 var css = []; 211 for (prop in this.getCss()) 212 { 213 cssPart = prop + ':' + this.getCss()[prop] + ';'; 214 css.push(cssPart); 215 } 216 217 attributes['style'] += css.join(''); 218 } 219 220 if (this.getCssClass().length > 0) 221 { 222 attributes['class'] = this.getCssClass().join(' '); 223 } 224 225 return attributes; 226 }; 227 228 /** 229 * sets attribute 230 * depending on the type of key we set prop or attr 231 * 232 * see http://blog.jquery.com/ for more information 233 * @param {String} key 234 * @param {String} value 235 * @return {this} 236 */ 237 Banana.UiControl.prototype.setAttribute = function(key,value) 238 { 239 if (this.isRendered) 240 { 241 switch(key) 242 { 243 case 'async': 244 case 'autofocus': 245 case 'checked': 246 case 'location': 247 case 'multiple': 248 case 'readOnly': 249 case 'selected': 250 case 'autoplay': 251 case 'controls': 252 case 'defer': 253 case 'disabled': 254 case 'hidden': 255 case 'loop': 256 case 'open': 257 case 'scoped': 258 259 jQuery('#'+this.getClientId()).prop(key,value); 260 261 break; 262 263 default: 264 jQuery('#'+this.getClientId()).attr(key,value); 265 } 266 } 267 var attr = this.getAttributes(); 268 attr[key] = value; 269 this.attributes = attr; 270 return this; 271 }; 272 273 /** 274 * @String key of the attribute which should be removed 275 */ 276 Banana.UiControl.prototype.removeAttribute = function(key) 277 { 278 if (this.isRendered) 279 { 280 switch(key) 281 { 282 case 'async': 283 case 'autofocus': 284 case 'checked': 285 case 'location': 286 case 'multiple': 287 case 'readOnly': 288 case 'selected': 289 case 'autoplay': 290 case 'controls': 291 case 'defer': 292 case 'disabled': 293 case 'hidden': 294 case 'loop': 295 case 'open': 296 case 'scoped': 297 298 jQuery('#'+this.getClientId()).removeProp(key); 299 300 break; 301 302 default: 303 jQuery('#'+this.getClientId()).attr(key,""); 304 } 305 } 306 307 if (this.attributes && this.attributes[key]) 308 { 309 delete this.attributes[key]; 310 } 311 312 return this; 313 }; 314 315 /** 316 * @param {String} key of the attribute 317 * @return {String} attribute by key 318 */ 319 Banana.UiControl.prototype.getAttribute = function(key) 320 { 321 if (!this.attributes) 322 { 323 return null; 324 } 325 return this.attributes[key]; 326 }; 327 328 /** 329 * NOTE: use setCss() instead to directly apply it to dom 330 * @param {String} style of this control. by css definitions 331 * @return {this} 332 */ 333 Banana.UiControl.prototype.setStyle = function(style) 334 { 335 this.style = style; 336 return this; 337 }; 338 339 /** 340 * @return {String} 341 */ 342 Banana.UiControl.prototype.getStyle = function() 343 { 344 return this.style; 345 }; 346 347 /** 348 * adds css style in object key value style. 349 * @param {Object} css example {width:'100px',left:0}; 350 */ 351 Banana.UiControl.prototype.addCss = function(css) 352 { 353 var nc = {}; 354 for (prop in this.getCss()) 355 { 356 nc[prop] = this.getCss()[prop]; 357 } 358 for (prop in css) 359 { 360 nc[prop] = css[prop]; 361 } 362 this.css = nc; 363 }; 364 365 /** 366 * adds css style and instantly apply it to dom if rendered 367 * 368 * @param {Object} css 369 */ 370 Banana.UiControl.prototype.setCss = function(css) 371 { 372 this.addCss(css); 373 374 if (this.isRendered) 375 { 376 jQuery('#'+this.getClientId()).css(this.css); 377 } 378 379 return this; 380 }; 381 382 /** 383 * @return {Object} of css properties in key value format 384 */ 385 Banana.UiControl.prototype.getCss = function() 386 { 387 return this.css; 388 }; 389 390 /** 391 * @return {String} value of css property 392 */ 393 Banana.UiControl.prototype.getStyleProperty = function(prop) 394 { 395 return this.css[prop] || jQuery('#'+this.getClientId()).css(prop); 396 }; 397 398 /** 399 * @return {Array} of currently added css classes 400 */ 401 Banana.UiControl.prototype.getCssClass = function() 402 { 403 if (!this.cssClass) 404 { 405 this.cssClass = []; 406 } 407 return this.cssClass; 408 }; 409 410 /** 411 * Adds css class. this will be instantly applied to dom if rendered 412 * 413 * @param {String} css name of css class 414 */ 415 Banana.UiControl.prototype.addCssClass = function(css) 416 { 417 if (!this.cssClass) 418 { 419 this.cssClass = []; 420 } 421 422 if (!this.hasCssClass(css)) { 423 this.cssClass.push(css); 424 425 if (this.isRendered) 426 { 427 jQuery('#'+this.getClientId()).addClass(css); 428 } 429 } 430 431 return this; 432 }; 433 434 /** 435 * removes css class. this will be instantly applied to dom 436 * 437 * @param {String} css name of css class 438 */ 439 Banana.UiControl.prototype.removeCssClass = function(css) 440 { 441 if (!this.cssClass) 442 { 443 return this; 444 } 445 446 var indexOf = this.cssClass.indexOf(css); 447 448 if (indexOf != -1) 449 { 450 this.cssClass.splice(indexOf,1); 451 452 if (this.isRendered) 453 { 454 jQuery('#'+this.getClientId()).removeClass(css); 455 } 456 } 457 458 return this; 459 }; 460 461 /** 462 * Switch the old Css class with a new one 463 * 464 * @param {String} oldClass Old CSS class to replace 465 * @param {String} newClass New CSS class to add 466 */ 467 Banana.UiControl.prototype.switchCssClass = function(oldClass, newClass) 468 { 469 this.removeCssClass(oldClass); 470 this.addCssClass(newClass); 471 return this; 472 }; 473 474 /** 475 * @return {boolean} 476 */ 477 Banana.UiControl.prototype.hasCssClass = function(search) 478 { 479 if (this.isRendered) { 480 return jQuery('#'+this.getClientId()).hasClass(search); 481 } 482 483 if (jQuery.inArray(search, this.getCssClass()) >= 0) 484 { 485 return true; 486 } 487 return false; 488 }; 489 490 /** 491 * return string representation of html attributes. used by renderer 492 * 493 * @return {String} 494 */ 495 Banana.UiControl.prototype.getHtmlAttributes = function() 496 { 497 var attributes =this.getAttributes(); 498 var data = ""; 499 var attr; 500 for (attr in attributes) 501 { 502 if (attributes[attr]) 503 { 504 if (attributes[attr] != 'undefined') 505 { 506 data += attr+'="'+attributes[attr]+'" '; 507 } 508 } 509 } 510 511 return data; 512 }; 513 514 /** 515 * makes control visible or invisible. 516 * 517 * @param {boolean} v true when visible 518 * @param {String} speed animation speed of hiding/ showing 519 * @param {String} type of animation 520 * @param {Function} callback when finished 521 */ 522 Banana.UiControl.prototype.setVisible = function(v,speed,type,callback) 523 { 524 this.triggerEvent('onSetVisible',v); //trigger this first to prevent flickering 525 this.visible = v; 526 var ignoreDirectAction = false; 527 if (v) 528 { 529 if (this.isRendered) 530 { 531 if (type === 'fadeIn') 532 { 533 this.getDomWriter().fadeIn(speed,this.getProxy(function(){ 534 this.setCss({'display':''}); 535 if(callback) 536 { 537 callback(); 538 } 539 })); 540 ignoreDirectAction = true; 541 } 542 else 543 { 544 this.getDomWriter().show(speed,this.getProxy(function(){ 545 this.setCss({'display':''}); 546 if(callback) 547 { 548 callback(); 549 } 550 })); 551 } 552 } 553 554 if (!ignoreDirectAction) 555 { 556 this.setCss({'display':''}); 557 } 558 } 559 else 560 { 561 if (this.isRendered) 562 { 563 if (type === 'fadeOut') 564 { 565 ignoreDirectAction = true; 566 this.getDomWriter().fadeOut(speed,this.getProxy(function(){ 567 this.setCss({'display':'none'}); 568 if(callback) 569 { 570 callback(); 571 } 572 })); 573 } 574 else 575 { 576 this.getDomWriter().hide(speed,this.getProxy(function(){ 577 this.setCss({'display':'none'}); 578 if(callback) 579 { 580 callback(); 581 } 582 })); 583 } 584 } 585 if (!ignoreDirectAction) 586 { 587 this.setCss({'display':'none'}); 588 } 589 590 } 591 592 return this; 593 }; 594 595 /* 596 * @return bool true when visible 597 */ 598 Banana.UiControl.prototype.getVisible = function() 599 { 600 if (this.isRendered) 601 { 602 return jQuery('#'+this.getClientId()).is(":visible"); 603 } 604 else 605 { 606 return this.visible; //note this only works on controls which are directly made invisble not for their childs 607 } 608 }; 609 610 /** 611 * register all binded events 612 */ 613 Banana.UiControl.prototype.registerEvents = function() 614 { 615 if (!this.binds && !this.binds.length) 616 { 617 return; 618 } 619 var i, len; 620 for (i = 0, len = this.binds.length; i < len; i++) 621 { 622 var name = this.binds[i].name; 623 var func = this.binds[i].func; 624 var data = this.binds[i].data; 625 var type = this.binds[i].type; 626 627 if (type === Banana.Controls.EventTypes.DOM_EVENT) 628 { 629 this.debugEvent(type, name, "Register event"); 630 631 if (data) 632 { 633 jQuery('#'+this.getClientId()).bind(name,data,func); 634 } 635 else 636 { 637 jQuery('#'+this.getClientId()).bind(name,func); 638 } 639 } 640 //there is a difference between dom and data events. dom events can be registered only 641 //when the dom elements are rendered. data events can be registered right when object are instantiated 642 //if we bind an custom event during construction (before dom render, no rerender has occured) we want it instant to be registered 643 //because this function is called after rendering and rerendering we only need to bind the custom events 644 //after a RE-rerender, cause rerendering always starts with unbinding ALL events. 645 else if (type === Banana.Controls.EventTypes.CUSTOM_EVENT && this.getPage().isRerendering) 646 { 647 /// console.log('register '+name + ' on '+this.getId()) 648 if (data) 649 { 650 jQuery(this).bind(name,data,func); 651 } 652 else 653 { 654 jQuery(this).bind(name,func); 655 } 656 } 657 else 658 { 659 //console.log('IGNORE register '+name + ' on '+this.getId()) 660 //no need to bind this event, 661 } 662 } 663 }; 664 665 /** 666 * unregister all binded events. 667 * note we dont touch the bind array, cause we might rebind it at a later moment. ie rerender 668 * used by framework 669 */ 670 Banana.UiControl.prototype.unregisterEvents = function() 671 { 672 if (!this.binds || !this.binds.length) 673 { 674 return; 675 } 676 677 this.debugEvent(3, name, "Unregister all event"); 678 679 jQuery('#'+this.getClientId()).unbind(); //unbind all dom events 680 jQuery(this).unbind(); //and all custom events 681 682 }; 683 684 /** 685 * sets control enabled /disabled 686 * we basicly add a css class. so, make sure your css file contains this css classes 687 * TODO this is not nice, can it be done otherwise? 688 * 689 * @param {boolean} e True when enabled 690 * @param {boolean} recursive Also enable/disable child controls 691 */ 692 Banana.UiControl.prototype.setEnabled = function(e, recursive) 693 { 694 this.triggerEvent('onSetEnabled',e); 695 696 if (e) 697 { 698 this.enabled = true; 699 700 if (this.isRendered) 701 { 702 jQuery('#'+this.getClientId()+' div').removeClass('disabledDivs'); 703 jQuery('#'+this.getClientId()+' :input').removeClass('disabledInputs'); 704 jQuery('#'+this.getClientId()+' :input').removeProp('disabled'); 705 jQuery('#'+this.getClientId()).removeProp('disabled'); 706 } 707 708 this.removeAttribute('disabled'); 709 this.removeCssClass('disabledDivs'); 710 711 } 712 else 713 { 714 this.enabled = false; 715 716 if (this.isRendered) 717 { 718 jQuery('#'+this.getClientId()+' div').addClass('disabledDivs'); 719 jQuery('#'+this.getClientId()+' :input').addClass('disabledInputs'); 720 jQuery('#'+this.getClientId()+' :input').prop('disabled',true); 721 this.getDomWriter().show(); 722 } 723 724 this.setAttribute('disabled',true); 725 this.addCssClass('disabledDivs'); 726 } 727 728 if (recursive) { 729 var controls = this.getControls(); 730 var x, len; 731 for (x = 0, len = controls.length; x < len; x++) 732 { 733 if (controls[x].setEnabled) 734 { 735 controls[x].setEnabled(e, recursive); 736 } 737 } 738 } 739 740 return this; 741 }; 742 743 Banana.UiControl.prototype.getEnabled = function() 744 { 745 //todo lets jquery figure this out?? 746 return this.enabled; 747 }; 748 749 /** 750 * returns html markup string of control + all child controls 751 * 752 * @return {String} 753 */ 754 Banana.UiControl.prototype.getHtml = function(markAsRendered) 755 { 756 var html = ""; 757 758 html += '<'+this.getTagName()+' '; 759 html += this.getHtmlAttributes(); 760 html += '>'; 761 762 var childs = this.getControls(); 763 var i, len; 764 for (i=0, len = childs.length; i < len; i++) 765 { 766 if (childs[i] instanceof Banana.Control) 767 { 768 html += childs[i].getHtml(markAsRendered); 769 } 770 771 else if (typeof(childs[i]) === 'string') 772 { 773 html +=childs[i]; 774 } 775 } 776 777 if (this instanceof Banana.UiControl) 778 { 779 html += '</'+this.getTagName()+'>'; 780 } 781 782 //In the update display all the controls + their children should be marked as rendered 783 //if we mark controls in the update display as rendered instead of here. we 784 //wont know if a child is rendered or not. 785 if (markAsRendered) 786 { 787 this.isRendered = true; 788 } 789 790 return html; 791 };