1 /**
  2  * @author Gillis Haasnoot <gillis.haasnoot@gmail.com>
  3  * @package Banana.Controls
  4  * @summary DataGridDataTreeListRender  
  5  */
  6 
  7 goog.provide('Banana.Controls.DataGridTreeListRender');
  8 
  9 goog.require('Banana.Controls.DataGridTreeListHolder');
 10 goog.require('Banana.Controls.DataGridTreeListNodeToggle');
 11 goog.require('Banana.Controls.DataGridTreeItemRender');
 12 
 13 /** @namespace Banana.Controls.DataGridTreeListRender */
 14 namespace('Banana.Controls').DataGridTreeListRender = Banana.Controls.DataGridBaseListRender.extend(
 15 /** @lends Banana.Controls.DataGridTreeListRender.prototype */
 16 {	
 17 	/**
 18 	 * Create a datagrid tree list render. a tree is a consists out of nodes with optional child nodes
 19 	 * For each node the list creates a node info object with various node state parameters.
 20 	 * 
 21 	 * bindable events
 22 	 * 
 23 	 * - dataSourceChanged
 24 	 * - onRequestChilds
 25      * - onItemOpened
 26 	 * - onItemClosed
 27 	 * - onPreCreateItem
 28 	 * - onPostCreateIndex
 29 	 * - onItemMouseClick
 30 	 * - onItemMouseOut
 31 	 * - onItemMouseEnter
 32 	 * - onSelectIndex
 33 	 * - onDeselectIndex
 34 	 * - onItemSelect
 35 	 * 
 36 	 * example
 37 
 38 	 	//make some datasource
 39 	 	var datasource =[]; 
 40 		
 41 		var s1 = {'name':'s1','children':[]};
 42 		var s2 = {'name':'s2','children':[]};
 43 		var s3 = {'name':'s3','children':[]};
 44 		var s4 = {'name':'s4','children':[]};
 45 		var s5 = {'name':'s5','children':[]};
 46 		var s6 = {'name':'s6','children':[]};
 47 		
 48 		root.push(s1);
 49 		root.push(s2);
 50 		root.push(s3);
 51 		
 52 		s1.children.push(s4);
 53 		s1.children.push(s5);		
 54 		s4.children.push(s6);
 55 
 56 		var list = new Banana.Controls.DataGrid();
 57 
 58 		var listRender = new Banana.Controls.DataGridDataTreeListRender();
 59 		listRender.setChildProperty("children");
 60 		listRender.setDefaultOpen(true);
 61 		
 62 		listRender.bind('selectIndex',this.getProxy(function(e,index){
 63 			listRender.selectAllFromIndex(index)
 64 		}));
 65 		
 66 		listRender.setSortfunction(function(a,b)
 67 		{ 
 68 			return a.name.toString().toLowerCase().localeCompare(b.name.toString().toLowerCase())
 69 		});
 70 		
 71 		list.setListRender(listRender);		
 72 	
 73 		list.setDataSource(datasource);
 74 	
 75 		listRender.bind('dataSourceChanged',this.getProxy(function(){
 76 			
 77 			this.dataChanged = true;
 78 		}));
 79 		
 80 		listRender.bind('dataChanged',this.getProxy(function(){
 81 			
 82 		}));
 83 	
 84 		this.addControl(list);			
 85 	 
 86 	 * @constructs
 87 	 * @extends Banana.Controls.DataGridBaseListRender
 88 	 */
 89 	init : function()
 90 	{
 91 		this._super();
 92 		
 93 		this.addCssClass("BDataGridTree");
 94 		
 95 		this.indexKey = null;
 96 		this.nodeData = []; //mapping between index key and nodeinfo
 97 		//this.debug = true;
 98 		
 99 		this.columns = [];
100 		this.dataItemRenderMap = new Banana.Util.ArrayBiCollection(); //mapping of item renders to data
101 		this.keyDataIndex = []; //mapping between key and datasource
102 		this.dataKeyIndex = []; //mapping between data key and index
103 		this.indexHolderMap = [];
104 		this.indexOrder = [];
105 		
106 		//default sort function
107 		this.sortFunc = null;
108 		
109 		this.childRequestSizeLimit = 20;
110 		this.childKey = 'children';
111 		this.rootNodeVisible = true;
112 		this.defaultContentItemRender = Banana.Controls.DataGridTreeItemRender;		
113 	},
114 	
115 	/**
116 	 * @param {Banana.Controls.TreeItemRender} render
117 	 */
118 	setDefaultItemRender : function(render)
119 	{
120 		this.defaultContentItemRender = render;
121 	},
122 	
123 	/**
124 	 * sets item render on index
125 	 * @param {int} index
126 	 * @param {Banana.Controls.TreeItemRender} ir
127 	 * @return {this}
128 	 */
129 	setIndexItemRender : function(index,ir)
130 	{
131 		if (!(typeof(ir) === 'function'))
132 		{
133 			throw "Item render should be a factory";
134 		}
135 		this.indexItemRenderFactory[index] = ir;
136 		
137 		return this;
138 	},
139 	
140 	/**
141 	 * By default only the root nodes are opened.
142 	 * if you want to have all nodes opened by default, call this method
143 	 * 
144 	 * @Param {boolean} bool
145 	 * @return {this}
146 	 */
147 	setDefaultOpen : function(bool)
148 	{
149 		this.defaultOpen = bool;
150 		return this;
151 	},
152 		
153 	/**
154 	 * @param {String} messsage
155 	 */
156 	toLogger : function()
157 	{
158 		if (!this.debug)
159 		{
160 			return;
161 		}
162 		console.log(arguments);
163 	},
164 	
165 	/**
166 	 * sort function which is used to order nodes per node level
167 	 * @param {Object} sf
168 	 * @return {this}
169 	 */
170 	setSortfunction : function(sf)
171 	{
172 		this.sortFunc = sf;
173 		return this;
174 	},
175 	
176 	/**
177 	 * Use this to change item render on a specific index.
178 	 * By default the list render will rerender the new item render
179 	 * 
180 	 * @param {int} index
181 	 * @param {String} render
182 	 * @param {Boolean} dontCreate
183 	 * @param {Boolean} ignoreDataItemRenderMap
184 	 */
185 	setItemRenderByIndex : function(index,render,dontCreate,ignoreDataItemRenderMap)
186 	{
187 		this.indexItemRenderFactory[index] = render;
188 		
189 		//also save the item render data map relation
190 		//could be handy when we set an item render and later change the datasource
191 		//the item location could be changed then. itemrender - index relation is 
192 		//in that situation not enough
193 		var datasource = this.getDataSourceByIndex(index);
194 
195 		if (!ignoreDataItemRenderMap && datasource)
196 		{
197 			this.dataItemRenderMap.addItem(datasource[this.indexKey],render);
198 		}
199 		
200 		if (!dontCreate)
201 		{
202 			//first check if holder is rendered
203 			if (this.getHolder(index))
204 			{
205 				this.createItemRenderByIndex(index, true, true);
206 				this.triggerEvent('itemRenderChanged');
207 			}			
208 		}		
209 	},
210 	
211 	/**
212 	 * use this method to determine if a node should have auto width (fits the content inside the node)
213 	 * or maximum width (fits the grid container) 
214 	 * We compute the width of each node and thus a small performance loss. 
215 	 * @param {boolean} bool
216 	 * @return {this}
217 	 */
218 	setMaximizeNodeWidth : function(bool)
219 	{
220 		this.maximizeNodeWidth = bool;
221 		return this;
222 	},
223 	
224 	/**
225 	 * Specify which property contain the childs
226 	 * @param {String} ck
227 	 * @return {this}
228 	 */
229 	setChildProperty : function(ck)
230 	{
231 		this.childKey = ck;
232 		return this;
233 	},
234 	
235 	/**
236 	 * sets child size request limit
237 	 * use this when loading from external resources is needed
238 	 * 
239 	 * @param {int} limit
240 	 * @return {this}
241 	 */
242 	setChildRequestSizeLimit : function(limit)
243 	{
244 		this.childRequestSizeLimit = limit;
245 		return this;
246 	},
247 	
248 	/**
249 	 * gives true when item is found by given index key
250 	 * use this function to ensure uniquality
251 	 * @param key
252 	 * @return {Boolean}
253 	 */
254 	hasItemIndexKey : function(key)
255 	{
256 		return this.keyDataIndex[key] ? true : false;
257 	},
258 
259 	/**
260 	 * always returns a source, when invalid key is given we return the root one
261 	 * 
262 	 * @return {Object}
263 	 */
264 	getDataSourceByKey : function(key)
265 	{
266 		var source = this.keyDataIndex[key];
267 		
268 		if (!source) //we are adding to root
269 		{
270 			source = this.getDataSourceByIndex(0);
271 		}
272 		
273 		return source;
274 	},
275 
276 	/**
277 	 * @param {Object} ds
278 	 * @return {Data}
279 	 */
280 	getNodeDataByData : function(data)
281 	{
282 		return this.nodeData[data[this.indexKey]];
283 	},
284 	
285 	/**
286 	 * Ensures data. Needed if items are added to an empty datasource
287 	 * 
288 	 * @ignore
289 	 */
290 	ensureRootData : function()
291 	{
292 		if (!this.rootData)
293 		{
294 			this.setupIndexing();
295 			
296 			var root = {};
297 			root[this.indexKey] = this.getUid();
298 			root[this.childKey] = this.datasource;
299 			this.rootData = root;
300 			
301 			var nodeData = {};
302 			nodeData.addedChildren = [];
303 			nodeData.isRoot = true;
304 			nodeData.open = true;
305 			
306 			this.setNodeDataByData(nodeData,root);
307 		}
308 	},
309 	
310 	/**
311 	 * Sets node data
312 	 * @param {Object} nodeData
313 	 */
314 	setNodeData : function(nodeData)
315 	{	
316 		this.ensureRootData();
317 		
318 		this.setNodeDataByData(nodeData,this.rootData);
319 	},
320 	
321 	/**
322 	 * gets node data from root
323 	 * @return {Object}
324 	 */
325 	getNodeData : function()
326 	{
327 		return this.getNodeDataByData(this.rootData);
328 	},
329 
330 	/**
331 	 * @param {Object} data
332 	 * @param {Object} source
333 	 */
334 	setNodeDataByData : function(data,source)
335 	{
336 		if(!source)
337 		{
338 			key = '__root__';
339 		}
340 		else
341 		{
342 			this.applyUid(source);
343 			var key = source[this.indexKey];
344 		}
345 		
346 		this.nodeData[key] = data;
347 	},
348 
349 	/**
350 	 * return {Banana.Controls.UiControl)
351 	 */
352 	getRenderedItemRenderByData : function(data)
353 	{
354 		var nodeData = this.getNodeDataByData(data);
355 
356 		if (!nodeData)
357 		{
358 			return null;
359 		}
360 		return this.getRenderedItemRenderByIndex(nodeData.index);
361 	},
362 
363 	/**
364 	 * We assume we can reach the data. this assumtion is made already
365 	 * in the create controls method
366 	 * 
367 	 * TODO: we are using this method a lot and it is slow. make is quicker by caching
368 	 * but watch out. the index datasource mapping relation is changed a lot also
369 	 * 
370 	 * @param {int} i
371 	 * @return {Object} 
372 	 */
373 	getDataSourceByIndex : function(i)
374 	{
375 		if (!this.datasource)
376 		{
377 			return null;
378 		}
379 		
380 		i = i.toString();
381 		var split = i.split("-");
382 		var result = null;
383 		var datasource = this.datasource;
384 
385 		var x;
386 		for (x = 0; x< split.length; x++)
387 		{
388 			result = datasource[split[x]];
389 			var key = result[this.indexKey];
390 
391 			//if no node info available then break out. we dont have childs
392 			if (!result || !this.nodeData[key])
393 			{
394 				break;
395 			}
396 			
397 			datasource = result[this.childKey];
398 			
399 			if (!datasource)
400 			{
401 				break;
402 			}
403 		}
404 
405 		return result;
406 	},
407 	
408 	/**
409 	 * @param {Array} datasource
410 	 * @return {int} index
411 	 */
412 	getIndexByDataSource : function(datasource)
413 	{
414 		return this.dataKeyIndex[datasource[this.indexKey]];		
415 	},
416 	
417 	/**
418 	 * @param {int} i 
419 	 * @return {String} parent index
420 	 * 
421 	 * TODO: we are using this method a lot and it is slow. make it quicker by caching
422 	 */
423 	getParentIndex :function(i)
424 	{
425 		var split = i.toString().split("-");
426 		split.pop();
427 		
428 		if (split.length)
429 		{
430 			return split.join('-');
431 		}
432 		
433 		return null;
434 	},
435 	
436 	/**
437 	 * @param {String} i index
438 	 * 
439 	 * @return {Banana.Controls.DataGridTreeListHolder}
440 	 */
441 	getHolder : function(i)
442 	{
443 		return this.indexHolderMap[i];
444 	},
445 	
446 	/**
447 	 * @param {String} i index
448 	 * @return {Banana.Controls.DataGridTreeListHolder}
449 	 * 
450 	 * @ignore
451 	 */
452 	getParentHolder: function(i)
453 	{
454 		var parentIndex = this.getParentIndex(i);
455 		
456 		if (parentIndex)
457 		{
458 			return this.indexHolderMap[parentIndex];
459 		}
460 		
461 		return null;
462 	},
463 
464 	/**
465 	 * called by constructor. creation of controls is starting here
466 	 * @ignore
467 	 */
468 	createControls : function()
469 	{
470 		try
471 		{
472 			this.setupIndexing();
473 			
474 			this.ensureRootData();
475 			
476 			var nodeData = this.getNodeDataByData(this.nodeData);
477 			
478 			this.rootData[this.childKey] = this.datasource;
479 			
480 			this.datasource = [this.rootData];
481 			
482 			this.toLogger("create Controls",this.datasource);
483 
484 			this.toLogger("determined key index as",this.indexKey);
485 
486 			this.bind('mousedown',this.getProxy(function(e){
487 				// TODO: This is catching every event in the scope of the table
488 				// This should first check if user is clicking inside a input box
489 				// otherwise multiselects etc don't work
490 				if (e.originalEvent.shiftKey || e.originalEvent.ctrlKey)
491 				{
492 					e.preventDefault();
493 				}
494 				else
495 				{
496 					//this.clearSelectedIndices();
497 				}
498 			}));
499 
500 		this.createNodeInfo(this.datasource,null,this.getProxy(function(item){
501 
502 				if (this.defaultOpen)
503 				{
504 					this.nodeData[item[this.indexKey]].open = true;
505 				}
506 			}));
507 
508 			this.createNodes();
509 
510 			this.restoreSelectedIndices();
511 			this.addControl('<div style="clear:both"></div>');
512 		}
513 		catch(e)
514 		{
515 			console.log(e.message)
516 		}
517 	},
518 
519 	/**
520 	 * TODO selected indices should be saved in nodedata
521 	 * now its seperated
522 	 */
523 	restoreSelectedIndices : function()
524 	{
525 		var i=0;
526 		var len;
527 		var indices = this.getSelectedIndices(true);
528 
529 		for (i = 0,len = indices.length; i < len; i++)
530 		{
531 			var ir = this.getRenderedItemRenderByIndex(indices[i]);
532 
533 			if (!ir)
534 			{
535 				continue;
536 			}
537 
538 			ir.select();
539 		}
540 	},
541 
542 	/**
543 	 * inserts recursively info to datasource items.
544 	 * this method should always be called after node modification(s)
545 	 * 
546 	 * @param {object} datasource
547 	 * @param {int} parentIndex
548 	 * @param {Object} func function which get applied on every item
549 	 * 
550 	 */
551 	createNodeInfo : function(datasource,parentIndex,func)
552 	{
553 		//sort first if sort method is supplied
554 		if (this.sortFunc)
555 		{
556 			datasource.sort(this.sortFunc);
557 		}
558 				 
559 		var i, len;
560 		for (i = 0, len = datasource.length; i < len; i++)
561 		{
562 			var part = datasource[i];
563 						
564 			this.applyUid(part);
565 
566 			var key = part[this.indexKey];
567 
568 			this.toLogger("create nodeinfo for key ",key,this.indexKey);
569 			if (!this.nodeData[key])
570 			{
571 				this.nodeData[key] = [];
572 			}
573 			
574 			//if function is supplied
575 			if (typeof(func) === 'function')
576 			{
577 				func(part);
578 			}
579 			
580 			if (!this.nodeData[key].addedChildren)
581 			{
582 				this.nodeData[key].addedChildren = [];
583 			}
584 			
585 			if (parentIndex !== null && parentIndex !== undefined)
586 			{
587 				currentIndex = parentIndex+"-"+i;
588 				this.nodeData[key].hasParent = true;
589 
590 				if (parentIndex.toString().split('-').length === 1)
591 				{
592 					this.nodeData[key].parentIsRoot = true;
593 				}
594 			}
595 			else
596 			{
597 				currentIndex = i;
598 				this.nodeData[key].hasParent = false;
599 			}
600 								
601 			this.keyDataIndex[part[this.indexKey]] = part;
602 			
603 			//this temp var is set when we have deleted an item
604 			//we need to reindex the index holder mapping. thats why we saved the old
605 			//scenario to keep a reference 
606 			if (this.tempIndexHolderMap)
607 			{
608 				var oldHolderIndex = this.tempIndexHolderMap[this.nodeData[key].index];
609 
610 				if (oldHolderIndex)
611 				{
612 					this.indexHolderMap[currentIndex] = oldHolderIndex;
613 				}
614 			}
615 			
616 			this.indexOrder.push(currentIndex);
617 			this.indexDataMap[currentIndex] = part;
618 					
619 			this.dataKeyIndex[part[this.indexKey]] = currentIndex;
620 
621 			this.nodeData[key].index = currentIndex; 
622 			
623 			if (part[this.childKey] && part[this.childKey].length)
624 			{
625 				this.toLogger(key,"has childs on",this.childKey," default open = ",this.defaultOpen);
626 				this.nodeData[key].hasChilds = true;
627 				this.nodeData[key].childProperty = this.childKey;
628 				
629 				if (!this.nodeData[key].hasParent || (this.nodeData[key].open || (this.nodeData[key].open == undefined && this.defaultOpen)))
630 				{
631 					this.createNodeInfo(part[this.childKey],currentIndex,func);
632 				}
633 			}
634 			else
635 			{
636 				delete this.nodeData[key].childProperty;
637 				this.nodeData[key].hasChilds = false;
638 			}
639 		}
640 	},
641 	
642 	/**
643 	 * create nodes and child nodes if they exist.
644 	 * 
645 	 * @param {Array} datasource nodes
646 	 * @param {String} parentIndex
647 	 * 
648 	 * @ignore
649 	 */
650 	createNodes : function(datasource,parentIndex)
651 	{
652 		///we are starting at root
653 		if (!datasource)
654 		{	
655 			datasource = this.datasource;
656 			parentIndex = null;
657 		}
658 
659 		var i, len;
660 		for (i =0, len = datasource.length; i < len; i++)
661 		{
662 			var ds = datasource[i];
663 			var key = ds[this.indexKey];
664 			var nodeData = this.nodeData[key];
665 			
666 			this.toLogger("create node from ",key,ds,"with child property ",nodeData);
667 			if (typeof(ds) === 'function')
668 			{
669 				continue;
670 			}
671 			
672 			this.createItemRenderByIndex(nodeData.index);
673 
674 			if (nodeData && typeof(ds[nodeData.childProperty]) === 'object' && ds[nodeData.childProperty].length)
675 			{
676 				if (nodeData.open)
677 				{
678 					this.createNodes(ds[nodeData.childProperty],currentIndex);
679 				}	
680 			}
681 			this.determineLoadNextButton(nodeData.index);		
682 		}
683 
684 		this.toLogger("create node complete");
685 	},
686 
687 	/**
688 	 * @param {Object} item
689 	 */
690 	addItem : function(item)
691 	{
692 		//if no datasource exist we create a dummy where we insert the item
693 		if (!this.datasource || !this.datasource.length)
694 		{
695 			this.setupIndexing();
696 			this.toLogger("add item as dummy ",this.indexKey,item,this.datasource);
697 			var dummynode = {};
698 			dummynode[this.childKey] = [item];
699 			dummynode.nodeinfo = {};
700 			dummynode.nodeinfo.childProperty = this.childKey;
701 			dummynode.nodeinfo.open = true;
702 			dummynode.hide = false;
703 			dummynode[this.indexKey] = "root";
704 
705 			this.datagrid.setDataSource([dummynode]);
706 		}
707 		else 
708 		{
709 			source = this.datasource[this.datasource.length-1];
710 			this.addDataSource(source,item,true);
711 		}
712 	},
713 
714 	/**
715 	 * 
716 	 */
717 	removeSelectedItems : function()
718 	{
719 		this._super();
720 		if (!this.datasource
721 		|| !this.datasource.length
722 		|| !this.datasource[0][this.childKey]
723 		|| !this.datasource[0][this.childKey].length)
724 		{
725 			this.datagrid.setDataSource([]);
726 		}
727 	},
728 	
729 	/**
730 	 * 
731 	 * How are we adding new data?
732 	 * Every node in our tree got a nodeinfo object with params. The following params are
733 	 * important for this function
734 	 * 
735 	 * nodeinfo.addedChildren -> children added which are not in the regular child collection
736 	 * nodeinfo.childCount -> the initial child collection child. this is without the nodeinfo.addedChildren
737 	 * 
738 	 * If we add new data with the instant boolean to false we add the children 
739 	 * to the nodeinfo.addedChildren array. When we receive new data later we check if 
740 	 * children exists in the addedChildren array. If yes we remove it from addedChildren 
741 	 * and increment the nodeinfo.childcount. This is useful to determine paging/loadnext buttons
742 	 * If the instant boolean is true we just add the children to the regular children
743 	 * 
744 	 * Note: children are always compared to children in nodeinfo.addedChildren. 
745 	 * 
746 	 * use the instant boolean when you know for sure that the addedDatasource consists out of new items
747 	 * use false if there might be children which are already in the list
748 	 * 
749 	 * @param {mixed} source to add items on
750 	 * @param {Array} targetItems
751 	 * @param {boolean} instant when true we instantly render the new items
752 	 */
753 	addDataSource : function(source,targetItems,instant)
754 	{
755 		try
756 		{
757 		if (!source && (!this.datasource || !this.datasource.length))
758 		{
759 			this.createRootNode();
760 			return this.addDataSource(null,targetItems,instant);
761 		}
762 		else if (!source)
763 		{
764 			source = this.datasource[0];
765 			this.toLogger("addDatasource no source, pick",source)
766 		}
767 		
768 		if (!source[this.childKey])
769 		{
770 			source[this.childKey] = [];
771 		}
772 		
773 		if (!(targetItems instanceof Array))
774 		{
775 			targetItems = [targetItems];
776 		}
777 
778 		var nodeData = this.getNodeDataByData(source);
779 		var childsToAdd = 0;
780 		var offset= source[this.childKey].length-nodeData.addedChildren.length-1;
781 
782 		
783 		var i, len;
784 		for (i = 0, len = targetItems.length; i< len;i++)
785 		{
786 			var target = targetItems[i];
787 
788 			this.toLogger("addDataSource",target,this.indexKey);
789 			
790 			var index = this.keyDataIndex[target[this.indexKey]];
791 			
792 			this.toLogger("addDataSource",'use index',index); 
793 					
794 			if (index !== undefined)
795 			{
796 				this.toLogger('found existing remove from added, and add childcount');
797 				var childFoundIndex = nodeData.addedChildren.indexOf(index);
798 				
799 				if (childFoundIndex >=0)
800 				{
801 					nodeData.addedChildren.splice(nodeData.addedChildren.indexOf(childFoundIndex,1));
802 					childsToAdd++; 
803 					continue;
804 				}
805 			}
806 		
807 			if (!instant)
808 			{ 
809 				nodeData.addedChildren.push(target);
810 			}
811 			
812 			if (this.sortFunc && source[this.childKey][offset])
813 			{
814 				var sortResult = this.sortFunc(target,source[this.childKey][offset]);
815 				this.toLogger('sort result of '+target.name+' and '+source[this.childKey][offset].name+' ',sortResult);
816 				if (sortResult < 0)
817 				{
818 					this.toLogger('remove '+target.name+' from addedChildren, we are before the last one');
819 					nodeData.addedChildren.splice(nodeData.addedChildren.indexOf(target),1);
820 					childsToAdd++;
821 				}
822 			}
823 			
824 			source[this.childKey].push(target);
825 		}
826 		
827 		//nodeData.childCount+=childsToAdd;
828 		
829 		this.toLogger('current childcount prop',nodeData.childCount,"children",source[this.childKey].length);
830 		
831 		this.prepareSourceToRender(source);
832 		}
833 		catch(e)
834 		{
835 			console.log(e)
836 		}
837 	},
838 	
839 	/**
840 	 * Creates a root node
841 	 * @param {mixed} source
842 	 * @ignore
843 	 */
844 	createRootNode : function(source)
845 	{
846 		var root = {};
847 		root[this.childKey]= [];
848 		root.nodeinfo = [];
849 		root.open = true;
850 
851 		if (!this.datasource)
852 		{
853 			this.datasource = [];
854 		}
855 		
856 		this.datasource.push(root);
857 
858 		this.triggerEvent('dataSourceChanged');
859 		this.setDataSource(this.datasource);
860 	},
861 	
862 	/**
863 	 * @param {Object} source
864 	 * @ignore
865 	 */
866 	prepareSourceToRender : function(source)
867 	{
868 		this.toLogger("PrepareSourceToRender",source);
869 		this.indexOrder = [];
870 		this.keyDataIndex = [];
871 		var nodeData = this.getNodeDataByData(source);
872 
873 		//set new info on datasource, mainly to reindex the tree TODO not neccessarly needed from root
874 		this.createNodeInfo(this.datasource,null,this.getProxy(function(item){
875 
876 			var itemNodeData = this.getNodeDataByData(item);
877 			this.toLogger("PrepareSourceToRender callback",item);
878 			if (this.defaultOpen && itemNodeData.open === undefined)
879 			{
880 				itemNodeData.open = true;
881 			}	
882 		}));
883 			
884 		this.indexHolderMap[nodeData.index].childsHolder.clear();
885 		
886 		if (nodeData.index != 0)
887 		{
888 			this.createToggleControl(nodeData.index);
889 		}
890 		
891 		//create the new nodes for the children
892 		//note that this.createNodeInfo() automaticly index the new nodes
893 		this.createNodes(source[this.childKey],nodeData.index);
894 		
895 		//remove the loader if there is one
896 		this.hideLoaderInNode(nodeData.index);
897 		
898 		//remove holder button. we might add it later again
899 		this.indexHolderMap[nodeData.index].buttonHolder.clear();
900 		
901 		//add loader button
902 		this.determineLoadNextButton(nodeData.index);
903 
904 		//rerender source with new nodes	
905 		this.indexHolderMap[nodeData.index].invalidateDisplay();
906 	},
907 	
908 	/**
909 	 * removes all data from item and children recursivly
910 	 * note this function does not modify gui objects
911 	 * 
912 	 * @param {Array} item
913 	 */
914 	removeChildDataRecursivly : function(item)
915 	{
916 		if (!item)
917 		{
918 			return; //this could happen if root and sub node are both selected. root already deleted sub
919 		}
920 
921 		var nodeData = this.getNodeDataByData(item);
922 
923 		if (!nodeData.removedItems)
924 		{
925 			nodeData.removedItems = [];
926 		}
927 			
928 		if (item[this.childKey] && item[this.childKey].length)
929 		{
930 			var i, len;
931 			for (i=0, len = item[this.childKey].length; i< len; i++)
932 			{
933 				var child = item[this.childKey][i];
934 			
935 				nodeData.removedItems.push(child);
936 				
937 				this.removeChildDataRecursivly(child);
938 				item[this.childKey]= [];
939 			}
940 		}
941 		
942 		item[this.childKey]= [];
943 	},
944 	
945 	/**
946 	 * removes node by key
947 	 * key is the {object}[this.indexKey];
948 	 * 
949 	 * @param {String} key
950 	 */
951 	removeNodeByKey : function(key)
952 	{
953 		var item = this.keyDataIndex[key];
954 		
955 		if (item)
956 		{
957 			this.removeItem(item);
958 		}
959 	},
960 	
961 	/**
962 	 * 
963 	 * @param {Object} item
964 	 */
965 	removeItem : function(item)
966 	{
967 		this._super(item);
968 
969 		var nodeData= this.getNodeDataByData(item);
970 		//remove all children
971 		this.removeChildDataRecursivly(item);
972 		
973 		//remove node from parent
974 		var parentIndex = this.getParentIndex(nodeData.index);
975 		
976 		var parentData = this.getDataSourceByIndex(parentIndex);
977 		
978 		if (parentData && parentData[this.childKey])
979 		{
980 			var childIndex = parentData[this.childKey].indexOf(item) ;
981 			
982 			if (childIndex !== -1)
983 			{	
984 				parentData[this.childKey].splice(childIndex,1);
985 
986 				var parentNodeData= this.getNodeDataByData(parentData);
987 
988 				var addedChildIndex = parentNodeData.addedChildren.indexOf(item);
989 				if (addedChildIndex >= 0)
990 				{
991 					parentNodeData.addedChildren.splice(addedChildIndex,1);
992 				}
993 					
994 				parentNodeData.childCount--;
995 			}
996 		}
997 		
998 		//remove gui node
999 		this.indexHolderMap[nodeData.index].remove();
1000 		
1001 		//reset some mappings
1002 		this.indexOrder = [];
1003 		this.keyDataIndex = [];
1004 		
1005 		//our generate nodeinfo should reindex the indexholder map.
1006 		this.tempIndexHolderMap = Banana.Util.Clone(this.indexHolderMap);
1007 		this.createNodeInfo(this.datasource);
1008 		this.tempIndexHolderMap = null;
1009 		
1010 		if(parentData && !parentData[this.childKey].length)
1011 		{
1012 			this.removeToggleControl(parentNodeData.index,true);
1013 			this.indexHolderMap[parentNodeData.index].invalidateDisplay();
1014 		}	
1015 	},
1016 		
1017 	/**
1018 	 * shows a loader inside a node
1019 	 * 
1020 	 * @param {int} index 
1021 	 */
1022 	showLoaderInNode : function(index)
1023 	{
1024 		this.showingLoader = true;
1025 		var loader = new Banana.Controls.Panel();
1026 		loader.setId("loader-"+index);
1027 		loader.addCssClass("BDataGridTreeLoader");
1028 		this.indexHolderMap[index].addControl(loader);
1029 		this.indexHolderMap[index].invalidateDisplay();
1030 	},
1031 	
1032 	/**
1033 	 * hides the loader
1034 	 * also sets this.showingLoader to false;
1035 	 * 
1036 	 * @param {int} index
1037 	 */
1038 	hideLoaderInNode : function(index)
1039 	{
1040 		this.showingLoader = false;
1041 		var loaderControl = this.indexHolderMap[index].findControl('loader-'+index);
1042 		if (loaderControl)
1043 		{
1044 			loaderControl.remove();
1045 		}		
1046 	},
1047 	
1048 	/**
1049 	 * @param {int} index
1050 	 * @param {Object} datasource
1051 	 */
1052 	openNode : function(index,datasource)
1053 	{
1054 		var nodeData = this.nodeData[datasource[this.indexKey]];
1055 
1056 		//set new info on datasource, mainly to reindex the tree TODO not neccessarly needed from root
1057 		this.createNodeInfo(this.datasource);
1058 
1059 		if (this.showingLoader)
1060 		{
1061 			return;
1062 		}
1063 		
1064 		if (nodeData.needsChilds)
1065 		{
1066 			var params = {};
1067 			params.targetIndex = index;
1068 			params.datasource = datasource;
1069 			params.limit = this.childRequestSizeLimit;
1070 			params.offset = 0;
1071 						
1072 			this.triggerEvent("onRequestChilds",params);
1073 		}
1074 		else
1075 		{
1076 			this.createNodes(datasource[this.childKey],index);
1077 			this.determineLoadNextButton(index);
1078 			this.indexHolderMap[index].invalidateDisplay();
1079 		}				
1080 		
1081 		//clear the indexorder
1082 		this.indexOrder = [];
1083 		this.keyDataIndex = [];
1084 		
1085 		this.triggerEvent("onItemOpened");
1086 	},
1087 	
1088 	/**
1089 	 * @param {int} index
1090 	 * @param {Object} datasource
1091 	 */
1092 	closeNode : function(index,datasource)
1093 	{
1094 		this.indexHolderMap[index].childsHolder.clear();
1095 		this.indexHolderMap[index].buttonHolder.clear();
1096 		
1097 		//clear the indexorder
1098 		this.indexOrder = [];
1099 		this.keyDataIndex = [];
1100 		
1101 		//set new info on datasource, mainly to reindex the tree TODO not neccessarly needed from root
1102 		this.createNodeInfo(this.datasource);
1103 		
1104 		this.triggerEvent("onItemClosed");
1105 	},
1106 	
1107 	/**
1108 	 * invoked when node is clicked
1109 	 * we open the node or close it
1110 	 * if in nodeinfo the property needschilds is true we trigger an event to let
1111 	 * the user load another childs
1112 	 * 
1113 	 * @param {index} int
1114 	 * @param {boolean} true when node is opened
1115 	 */
1116 	nodeClicked : function(index,open)
1117 	{
1118 		if (this.showingLoader)
1119 		{
1120 			return;
1121 		}
1122 		
1123 		var datasource = this.getDataSourceByIndex(index);
1124 		var nodeData = this.nodeData[datasource[this.indexKey]];
1125 
1126 		nodeData.open = open ? true : false;
1127 		
1128 		if (nodeData.open)
1129 		{
1130 			this.openNode(index,datasource);
1131 		}
1132 		else
1133 		{
1134 			this.closeNode(index,datasource);
1135 		}
1136 	},
1137 	
1138 	/**
1139 	 * determines if a load next button is needed
1140 	 * we also keep in count of deleted items. 
1141 	 * 
1142 	 * @ignore
1143 	 */
1144 	determineLoadNextButton : function(index)
1145 	{
1146 		var source = this.getDataSourceByIndex(index);
1147 		var nodeData = this.getNodeDataByData(source);
1148 
1149 		this.toLogger("determine load next button ",this.indexKey);
1150 
1151 		if (!nodeData)
1152 		{
1153 			return;
1154 		}
1155 		
1156 		if (source[this.childKey] && nodeData.open)
1157 		{
1158 			var serverChildCount = nodeData.childCount;
1159 			var clientChildCount = source[this.childKey].length;
1160 			var addedChildCount = nodeData.addedChildren.length;
1161 			
1162 			this.toLogger('server',serverChildCount,'client',clientChildCount,'added',addedChildCount);
1163 			
1164 			var offset = clientChildCount - addedChildCount;
1165 		
1166 			if (offset < serverChildCount)
1167 			{
1168 				var limit = serverChildCount - offset;
1169 				
1170 				if (limit > this.childRequestSizeLimit)
1171 				{
1172 					limit = this.childRequestSizeLimit;
1173 				}
1174 		
1175 				nodeData.loadnext = true;
1176 				
1177 			//	var debug = "serverCount "+serverChildCount + " clientChilds"+clientChildCount+ " addedChildCount"+addedChildCount+ "===>"+offset+","+limit;
1178 								
1179 				return this.createLoadMoreButton(index,limit,nodeData);
1180 			}
1181 		}
1182 			
1183 		nodeData.loadnext = false;
1184 	},
1185 	
1186 	/**
1187 	 * creates item render by index
1188 	 * 
1189 	 * Item render will be either from 
1190 	 *  - data to item render map
1191 	 *  - index to item render map
1192 	 *  - or default one
1193 	 *  
1194 	 *  @ignore
1195 	 */
1196 	createItemRenderByIndex : function(index,instantRerender,useExistingHolder)
1197 	{
1198 		var datasource = this.getDataSourceByIndex(index);
1199 		var nodeData = this.nodeData[datasource[this.indexKey]];
1200 
1201 		this.toLogger("create item render by index ",index,datasource);
1202 
1203 		this.triggerEvent('onPreCreateItem',{index:index,data:datasource});
1204 				
1205 		//this situation is only possible when user quickly closes and opens a node while loading server data
1206 		if (!nodeData)
1207 		{
1208 			this.toLogger("no nodeinfo. dont render");
1209 			return;
1210 		}
1211 		
1212 		// Optionally can be accessed by e.g. invalidateDisplay to peek
1213 		// at which index it is created
1214 		this.currentIndexCreation = index;
1215 		
1216 		var itemRenderFactory = null;
1217 		
1218 		var key = datasource[this.indexKey];
1219 				
1220 		if (key  && this.dataItemRenderMap.getItem(key))
1221 		{
1222 			itemRenderFactory = this.dataItemRenderMap.getItem(key); 
1223 		}
1224 		else if (this.indexItemRenderFactory[index])
1225 		{
1226 			itemRenderFactory = this.indexItemRenderFactory[index];
1227 		}
1228 		else
1229 		{
1230 			itemRenderFactory = this.defaultContentItemRender;
1231 		}
1232 		
1233 		var itemRender = this.getObject(itemRenderFactory);
1234 		
1235 		itemRender.setData(datasource);
1236 
1237 		itemRender.bind('dataChanged',this.getProxy(function(e,f){
1238 			
1239 			this.getDataSource()[parseInt(e.data, 10)] = e.currentTarget.getData();
1240 			this.triggerEvent('dataSourceChanged');
1241 			
1242 		}),index.toString());
1243 		
1244 		//save mapping between itemRender and data
1245 		this.dataItemRenderMap.addItem(key,itemRenderFactory);
1246 
1247 		//save mapping between index and item render
1248 		this.indexRenderedItemRenderMap[index] = itemRender;
1249 		
1250 		itemRender.setListRender(this);
1251 
1252 		var parentHolder = this.getParentHolder(index);
1253 		var holder = null;
1254 		
1255 		//if we use existing holder we need to clear the item render inside
1256 		if (useExistingHolder)
1257 		{
1258 			holder = this.getHolder(index);
1259 			holder.getItemRenderHolder().clear();
1260 		}
1261 		else
1262 		{
1263 			if (!parentHolder)
1264 			{	
1265 				this.rootHolder = new Banana.Controls.DataGridTreeListRootHolder();
1266 				this.addControl(this.rootHolder);
1267 				holder = this.rootHolder;
1268 			}
1269 			else
1270 			{
1271 				holder = this.getHolder(index);
1272 				//create a holder to put our itemrender in
1273 				var holder = new Banana.Controls.DataGridTreeListHolder();
1274 			}
1275 		}
1276 		
1277 		//give a reference of this to the holder
1278 		holder.listRender = this;
1279 		holder.maximizeAutoWidth = this.maximizeNodeWidth;
1280 		//set an unique id for which we later check if index map is correct
1281 		holder.setId(key);
1282 		//save mapping between index and holder
1283 		this.indexHolderMap[index] = holder;
1284 		
1285 		//look for a parent holder. 
1286 		var parentHolder = this.getParentHolder(index);
1287  
1288 		if (parentHolder)
1289 		{
1290 			if (!nodeData.parentIsRoot)
1291 			{
1292 				holder.addCssClass("BDataGridTreeItemHolder");
1293 			}
1294 			else
1295 			{
1296 				holder.addCssClass("BDataGridTreeItemHolderRoot");
1297 			}
1298 			
1299 			//if we use existing holder we dont need to add it
1300 			if (!useExistingHolder)
1301 			{
1302 				parentHolder.childsHolder.addControl(holder);
1303 			}
1304 			
1305 			holder.itemRenderHolder.addControl(itemRender);
1306 			
1307 			if (nodeData.childProperty || nodeData.needsChilds)
1308 			{
1309 				this.createToggleControl(index);
1310 			}
1311 			else if (parentHolder)
1312 			{
1313 				this.removeToggleControl(index);
1314 			}	
1315 			
1316 			if (this.selectedIndices.getItem(index))
1317 			{
1318 				this.selectIndex(index);
1319 			}
1320 
1321 			this.bindMouseHandler(holder, datasource);
1322 			this.triggerEvent('onPostCreateIndex',{index:index,data:datasource,holder:holder});
1323 		}
1324 		
1325 		this.currentIndexCreation = undefined;
1326 		
1327 		if (instantRerender)
1328 		{
1329 			if (parentHolder)
1330 			{
1331 				holder.invalidateDisplay();
1332 			}
1333 			else
1334 			{
1335 				this.rootHolder.invalidateDisplay();
1336 			}
1337 		}
1338 	},	
1339 	
1340 	/**
1341 	 * binds a click handler overwrite this function to use your own logic for binding click
1342 	 * 
1343 	 * @param {Banana.Controls.DataGridTreeListHolder} holder
1344 	 * @param {Object} datasource item
1345 	 * 
1346 	 * @ignore
1347 	 */
1348 	bindMouseHandler : function(holder,datasource)
1349 	{
1350 		holder.itemRenderHolder.bind('click',this.getProxy(function(e){
1351 			
1352 			this.onRowMouseClick(e);
1353 			this.triggerEvent('onItemMouseClick',e);
1354 
1355 		}),datasource);
1356 		
1357 		holder.itemRenderHolder.bind('mouseenter',this.getProxy(function(e){
1358 			
1359 			this.triggerEvent('onItemMouseEnter',e);
1360 
1361 		}),datasource);	
1362 		
1363 		holder.itemRenderHolder.bind('mouseleave',this.getProxy(function(e){
1364 			
1365 			this.triggerEvent('onItemMouseOut',e);
1366 
1367 		}),datasource);	
1368 	},
1369 	
1370 	/**
1371 	 * @param {int} index
1372 	 * @param {Boolean} rerender
1373 	 * 
1374 	 * @ignore
1375 	 */
1376 	createToggleControl : function(index,rerender)
1377 	{		
1378 		var datasource = this.getDataSourceByIndex(index);
1379 		var nodeData = this.nodeData[datasource[this.indexKey]];
1380 		
1381 		//dont add when root and root should not be visible
1382 //		if (!this.rootNodeVisible && !nodeData.hasParent)
1383 //		{
1384 //		//	return;
1385 //		}
1386 		
1387 		var holder =this.indexHolderMap[index];
1388 		
1389 		holder.toggleHolder.clear();
1390 		
1391 		var toggle = new Banana.Controls.DataGridTreeListNodeToggle().addCssClass('BDataGridTreeToggle');
1392 		toggle.index = index;
1393 		
1394 		toggle.setOn(nodeData.open);
1395 	
1396 		toggle.bind('toggle',this.getProxy(function(e,d){
1397 			
1398 			var index = this.getIndexByDataSource(e.data);
1399 
1400 			this.nodeClicked(index,d);
1401 			
1402 		}),datasource);
1403 		holder.toggleHolder.addControl(toggle);		
1404 		
1405 		if (rerender)
1406 		{
1407 			holder.toggleHolder.invalidateDisplay();
1408 		}
1409 	},
1410 	
1411 	/**
1412 	 * @param {int} index
1413 	 * @param {Boolean} rerender
1414 	 * 
1415 	 */
1416 	removeToggleControl : function(index,rerender)
1417 	{
1418 		var holder =this.indexHolderMap[index];
1419 		var datasource = this.getDataSourceByIndex(index);
1420 		var nodeData = this.getNodeDataByData(datasource);
1421 		
1422 		holder.toggleHolder.clear();
1423 		
1424 		//dont add dummy when root and root should not be visible
1425 		if (!this.rootNodeVisible && !nodeData.hasParent)
1426 		{
1427 			return;
1428 		}
1429 		
1430 		var dummy = new Banana.Controls.Panel().addCssClass('BDataGridTreeEmptyNode');
1431 		holder.toggleHolder.addControl(dummy);	
1432 		
1433 		if (rerender)
1434 		{
1435 			holder.toggleHolder.invalidateDisplay();
1436 		}
1437 	},
1438 	
1439 	/**
1440 	 * creates a load more button
1441 	 * 
1442 	 * @param {String} index
1443 	 * @param {int} limit
1444 	 */
1445 	createLoadMoreButton : function(index,limit,nodeData)
1446 	{
1447 		var holder =this.indexHolderMap[index];
1448 		
1449 		var button = new Banana.Controls.Button();
1450 		
1451 		if (index != 0)
1452 		{
1453 			button.addCssClass("BDataGridTreeLoadNextButton");
1454 		}
1455 		else
1456 		{
1457 			button.addCssClass("BDataGridTreeLoadNextButtonRoot");
1458 		}
1459 		
1460 		var datasource = this.getDataSourceByIndex(index);
1461 		var nodeData = this.getNodeDataByData(datasource);
1462 		
1463 		var totalrendered = nodeData.childCount - datasource.children.length;
1464 			
1465 		button.setText('Load next '+limit+ '/'+totalrendered);
1466 		
1467 		button.bind('click',this.getProxy(function(){
1468 		
1469 			if (this.showingLoader)
1470 			{
1471 				return;
1472 			}
1473 			
1474 			var datasource = this.getDataSourceByIndex(index);
1475 			
1476 			var offset = 0;
1477 
1478 			var info = datasource[this.childKey].length - nodeData.addedChildren.length;
1479 
1480 			if (info)
1481 			{
1482 				offset =info;
1483 			}
1484 			
1485 			var params = {};
1486 			params.targetIndex = index;
1487 			params.datasource = datasource;
1488 			params.limit = limit;
1489 			params.offset = offset;
1490 	
1491 			this.triggerEvent("onRequestChilds",params);
1492 			
1493 		}));	
1494 		
1495 		holder.buttonHolder.addControl(button);
1496 		holder.buttonHolder.addControl('<div style="clear:both"></div>');
1497 	},
1498 	
1499 	/**
1500 	 * calls the select method in the item render
1501 	 * 
1502 	 * @param {int} index
1503 	 */
1504 	selectIndex : function(index,preventEvent)
1505 	{
1506 		var ir = this.getRenderedItemRenderByIndex(index);
1507 		
1508 		if (!ir)
1509 		{
1510 			return;
1511 		}
1512 		
1513 		ir.select();
1514 	
1515 		if (!preventEvent)
1516 		{
1517 			this.triggerEvent('onSelectIndex',index);
1518 		}
1519 	},
1520 
1521 	/**
1522 	 * calls the deselect method in the item render
1523 	 * 
1524 	 * @param {int} index
1525 	 */
1526 	deSelectIndex : function(index)
1527 	{
1528 		var ir = this.getRenderedItemRenderByIndex(index);
1529 		
1530 		if (!ir)
1531 		{
1532 			return;
1533 		}
1534 		
1535 		ir.deselect();
1536 		
1537 		this.triggerEvent('onDeselectIndex',index);
1538 	},
1539 	
1540 	/**
1541 	 * Selects all indices from given index.
1542 	 * @param {String} index
1543 	 * @param {mixed} datasource for internal use only
1544 	 */
1545 	selectAllFromIndex : function(index,datasource)
1546 	{
1547 		if (!datasource)
1548 		{
1549 			datasource = this.getDataSourceByIndex(index);
1550 		}
1551 		
1552 		if (!datasource)
1553 		{
1554 			return;
1555 		}
1556 
1557 		var nodeData = this.getNodeDataByData(datasource);
1558 		
1559 		if (!nodeData)
1560 		{
1561 			return;
1562 		}
1563 		var i, len;
1564 		for (i =0, len = datasource[this.childKey].length; i < len; i++)
1565 		{
1566 			var item = datasource[this.childKey][i];
1567 			index = nodeData.index;
1568 			
1569 			var ir = this.getRenderedItemRenderByIndex(i);
1570 			
1571 			if (ir && !ir.getIsSelectable())
1572 			{
1573 				
1574 			}
1575 			else
1576 			{
1577 				this.selectedIndices.addItem(index,true);
1578 				
1579 				this.selectIndex(index,true);
1580 				
1581 				if (item[this.childKey])
1582 				{
1583 					this.selectAllFromIndex(index,item);
1584 				}
1585 			}
1586 		}
1587 	},
1588 	
1589 	/**
1590 	 * @return {Boolean}
1591 	 * @ignore
1592 	 */
1593 	indexBeforeIndex : function(indexA,indexB)
1594 	{
1595 		var i;
1596 		for (i = 0; i < this.indexOrder.length;i++)
1597 		{
1598 			if (this.indexOrder[i] === indexA)
1599 			{
1600 				return true;
1601 			}
1602 			
1603 			if (this.indexOrder[i]===indexB)
1604 			{
1605 				return false;
1606 			}
1607 		}
1608 		
1609 		return false;
1610 	},
1611 	
1612 	/**
1613 	 * Gets the rendered item render instance by index
1614 	 * @return {Object}
1615 	 */
1616 	getRenderedItemRenderByIndex : function(index)
1617 	{
1618 		var holder = this.indexHolderMap[index];
1619 
1620 		if (!holder)
1621 		{
1622 			return null;
1623 		}
1624 		
1625 		return holder.getItemRender();
1626 	},
1627 	
1628 	
1629 	/**
1630 	 * triggered when use moves outside
1631 	 * if row is unselected we trigger event 'onItemSelect'
1632 	 *
1633 	 * @param {event} e
1634 	 * @ignore
1635 	 */
1636 	onRowMouseClick : function(e)
1637 	{
1638 		var index = this.getNodeDataByData(e.data).index;
1639 
1640 		var itemRender = this.getRenderedItemRenderByIndex(index);
1641 		
1642 		if (!itemRender.getIsSelectable())
1643 		{
1644 			return;
1645 		}
1646 
1647 		var ctrlKey = e.ctrlKey;
1648 		var shiftKey = e.shiftKey;
1649 
1650 		if (this.getIndexIsSelected(index))
1651 		{
1652 			if (!ctrlKey && !shiftKey)
1653 			{
1654 				this.previousShiftFirstItem = null;
1655 				this.clearSelectedIndices();
1656 			}
1657 			else
1658 			{
1659 				this.previousShiftFirstItem = null;
1660 				this.clearSelectedIndex(index);
1661 			}
1662 		}
1663 		else
1664 		{
1665 			if (this.selectionType === 'single')
1666 			{
1667 				this.clearSelectedIndices();
1668 				this.addSelectedIndex(index);
1669 			}
1670 			else
1671 			{
1672 				if (!ctrlKey && !shiftKey)
1673 				{
1674 					this.previousShiftFirstItem = null;
1675 					this.clearSelectedIndices();
1676 					this.addSelectedIndex(index);
1677 				}
1678 				if (ctrlKey)
1679 				{
1680 					this.previousShiftFirstItem = null;
1681 					this.addSelectedIndex(index);
1682 				}
1683 				if (shiftKey)
1684 				{
1685 					//use our previous shift item
1686 					if (!this.previousShiftFirstItem)
1687 					{
1688 						this.previousShiftFirstItem = this.selectedIndices.getKeyByIndex(0);
1689 					}
1690 						
1691 					var firstItem = this.previousShiftFirstItem;
1692 					
1693 					if (this.indexOrder.indexOf(firstItem) === -1)
1694 					{
1695 						firstItem = this.indexOrder[0];
1696 					}
1697 					
1698 					if (!firstItem)
1699 					{
1700 						return;
1701 					}
1702 					
1703 					//reverse it when selected is above first
1704 					if (this.indexBeforeIndex(index,firstItem))
1705 					{
1706 						var temp = index;
1707 						index = firstItem;
1708 						firstItem = temp;
1709 					}
1710 					
1711 					var go = false;
1712 					
1713 					this.clearSelectedIndices();
1714 					
1715 					var x, len;
1716 					for (x = 0, len = this.indexOrder.length; x < len; x++)
1717 					{
1718 						if (typeof(this.indexOrder[x]) === 'function')
1719 						{
1720 							continue;
1721 						}
1722 						
1723 						if (this.indexOrder[x] === firstItem)
1724 						{
1725 							go = true;
1726 						}
1727 							
1728 						if (!go)
1729 						{
1730 							continue;
1731 						}
1732 						
1733 						if (this.indexOrder[x]===index)
1734 						{
1735 							go = false;
1736 						}
1737 						
1738 						var ir = this.getRenderedItemRenderByIndex(this.indexOrder[x]);
1739 						
1740 						if (ir && !ir.getIsSelectable())
1741 						{
1742 							
1743 						}
1744 						else
1745 						{
1746 							this.addSelectedIndex(this.indexOrder[x]);
1747 						}
1748 					}
1749 				}
1750 
1751 			}
1752 		}		
1753 		this.triggerEvent('onItemSelect');
1754 	}
1755 });