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 };