1 /**
  2  * @author Gillis Haasnoot <gillis.haasnoot@gmail.com>
  3  * @package Banana.Controls
  4  * @summary Datagrid  
  5  */
  6 
  7 goog.provide('Banana.Controls.DataGridBaseListRender');
  8 
  9 /** @namespace Banana.Controls.DataGridBaseListRender */
 10 namespace('Banana.Controls').DataGridBaseListRender = Banana.Controls.CustomListControl.extend(
 11 /** @lends Banana.Controls.DataGridBaseListRender.prototype */
 12 {
 13 	
 14 	/**
 15 	 * Creates Bases list render.
 16 	 * 
 17 	 * base class for list renders. a list render is responsible for rendering item renders.
 18 	 * a listrender instance should be inserted into a datagrid.
 19 	 * 
 20 	 * @constructs
 21 	 * @extends Banana.Controls.CustomListControl
 22 	 */
 23 	init : function()
 24 	{
 25 		this._super();
 26 		
 27 		this.addCssClass("BDataGridListRender");
 28 		
 29 		this.indexItemRenderFactory = []; //this array consists out of factory's or strings
 30 		
 31 		this.indexKey = 'id';
 32 		this.useAutoIndexing = false;
 33 		this.autoIndexKey = '__uid__';
 34 		this.selectedIndices = new Banana.Util.ArrayBiCollection();
 35 		this.indexDataMap = [];
 36 		this.indexRenderedItemRenderMap = []; //these are the real ui item renders
 37 	},
 38 	
 39 	/**
 40 	 * @param {int} index
 41 	 * @return {Banana.Controls.ItemRender}
 42 	 */
 43 	getRenderedItemRenderByIndex : function(index)
 44 	{
 45 		return this.indexRenderedItemRenderMap[index];
 46 	},
 47 
 48 	/**
 49 	* @return {Array} 
 50 	*/
 51 	getRenderedItemRenders : function()
 52 	{
 53 		return this.indexRenderedItemRenderMap;
 54 	},
 55 	
 56 	/**
 57 	 * @ignore
 58 	 */
 59 	createComponents : function()
 60 	{
 61 		this.setupIndexing();
 62 		this._super();
 63 	},
 64 	
 65 	/**
 66 	 * Items in a list needs to be indentified. You can specify a indexkey. 
 67 	 * or you can let the listrender generate one. Calling this method
 68 	 * will change the indexKey to autoIndexKey value
 69 	 * 
 70 	 * @return {boolean} true when index 
 71 	 * @ignore
 72 	 */
 73 	setupIndexing : function()
 74 	{
 75 		if (!this.indexKey)
 76 		{
 77 			this.useAutoIndexing = true;
 78 			this.indexKey = this.autoIndexKey;
 79 			return true;
 80 		}
 81 		
 82 		return false;
 83 	},
 84 	
 85 	/**
 86 	 * @return {String} almost unique
 87 	 * @ignore
 88 	 */
 89 	getUid : function()
 90 	{
 91 		return (((1+Math.random())*0x1000000000)|0).toString(16).substring(1);
 92 	},
 93 	
 94 	/**
 95 	 * applies a unique identifier to an object
 96 	 * not that we wont overwrite an existing identifier. this would result into inconsistency  problems.
 97 	 * 
 98 	 * @param {Object} data
 99 	 */
100 	applyUid : function(data)
101 	{
102 		if (this.useAutoIndexing && !data[this.indexKey])
103 		{
104 			data[this.indexKey] = this.getUid();
105 		}
106 	},
107 	
108 	/**
109 	 * Rerenders the list render.
110 	 */
111 	rerender : function()
112 	{
113 		this.clear();
114 		this.createControls();
115 		this.invalidateDisplay();
116 	},
117 	
118 	/**
119 	 * called by framework when control is removed
120 	 * @ignore
121 	 */
122 	unload : function()
123 	{
124 		this._super();
125 		
126 		this.selectedIndices.clear();
127 		this.selectedIndices = undefined;
128 		this.indexDataMap = undefined;
129 	},
130 	
131 	/**
132 	 * @param {String} key
133 	 */
134 	setItemIndexKey : function(key)
135 	{
136 		this.indexKey = key;
137 		return this;
138 	},
139 	
140 	/**
141 	 * @param {int} i
142 	 */
143 	addSelectedIndex : function(i)
144 	{
145 		this.selectIndex(i);
146 
147 		this.selectedIndices.addItem(i,true);
148 	},
149 	
150 	/**
151 	 * clears selected indices
152 	 */
153 	clearSelectedIndices : function()
154 	{		
155 		this.selectedIndices.each(this.getProxy(function(index,d)
156 		{
157 			var keyIndex = this.selectedIndices.getKeyByIndex(index);
158 			this.deSelectIndex(keyIndex);
159 		}));
160 
161 		this.selectedIndices.clear();
162 	},
163 	
164 	/**
165 	 * @param {int} index
166 	 */
167 	clearSelectedIndex : function(index)
168 	{
169 		this.deSelectIndex(index);
170 
171 		this.selectedIndices.remove(index);
172 	},
173 	
174 	/** 
175 	 * Retreives the selected indices. 
176 	 * @param {boolean} flat true returns array of selected indices
177 	 * @return {mixed} 
178 	 */
179 	getSelectedIndices : function(flat)
180 	{
181 		if (flat)
182 		{
183 			var result = [];
184 			this.selectedIndices.each(this.getProxy(function(index,d)
185 			{
186 				var keyIndex = this.selectedIndices.getKeyByIndex(index);
187 				result.push(keyIndex);
188 			}));
189 			
190 			return result;
191 		}
192 		
193 		return this.selectedIndices;
194 	},
195 	
196 	/**
197 	 * @param {int} index
198 	 * @return {Boolean} true when selected
199 	 */
200 	getIndexIsSelected : function(index)
201 	{
202 		if (this.selectedIndices.getItemByKey(index))
203 		{
204 			return true;
205 		}
206 
207 		return false;
208 	},
209 	
210 	/**
211 	 * Sets selected indices by items. 
212 	 * @param {Array} items
213 	 */
214 	setSelectedItems : function(items)
215 	{
216 		for (i = 0, len = items.length; i < len; i++)
217 		{
218 			var rowIndex = 0;
219 			var dataIndex;
220 			for (dataIndex in this.datasource)
221 			{
222 				if (typeof(this.datasource[dataIndex]) === 'function' ) {continue;}
223 
224 				if (items[i] && (this.datasource[dataIndex][this.indexKey] === items[i][this.indexKey]))
225 				{
226 					 this.addSelectedIndex(rowIndex);
227 				}
228 
229 				rowIndex++;
230 			}
231 		}
232 	},
233 	
234 	/**
235 	 * moves selected items up
236 	 * 
237 	 * @return {this}
238 	 */
239 	moveSelectedItemsUp : function()
240 	{
241 		var si = [];
242 		
243 		this.selectedIndices.each(this.getProxy(function(index,d)
244 		{
245 			si.push(this.selectedIndices.getKeyByIndex(index));
246 		}));
247 		
248 		//no items selected
249 		if (!si.length)
250 		{
251 			return;
252 		}
253 		
254 		//sort for extraction
255 		si.sort();
256 		
257 		var amountToExtract = si.length;
258 		
259 		//determine index for extraction
260 		var indexToExtract = si[0]-1;
261 		
262 		//no items above selected
263 		if (indexToExtract < 0)
264 		{
265 			return;
266 		}
267 		
268 		//save item we are going to remove
269 		var itemToExtract = this.datasource[indexToExtract];
270 		
271 		//remove item from index
272 		this.datasource.splice(indexToExtract,1);
273 		
274 		//reinsert item
275 		this.datasource.splice(indexToExtract+amountToExtract,0,itemToExtract);
276 		
277 		//force rerender
278 		this.setDataSource(this.datasource);
279 		
280 		return this;
281 	},
282 	
283 	/**
284 	 * moves selected items down
285 	 * 
286 	 * @return {this}
287 	 */
288 	moveSelectedItemsDown : function()
289 	{
290 		var si = [];
291 		
292 		this.selectedIndices.each(this.getProxy(function(index,d)
293 		{
294 			si.push(this.selectedIndices.getKeyByIndex(index));
295 		}));
296 		
297 		//no items selected
298 		if (!si.length)
299 		{
300 			return;
301 		}
302 		
303 		//sort for extraction
304 		si.sort();
305 		
306 		var amountToExtract = si.length;
307 		
308 		//determine index for extraction
309 		var indexToExtract = si[si.length-1]+1;
310 			
311 		//no items above selected
312 		if (indexToExtract+1 > this.datasource.length)
313 		{
314 			return;
315 		}
316 		
317 		//save item we are going to remove
318 		var itemToExtract = this.datasource[indexToExtract];
319 		
320 		//remove item from index
321 		this.datasource.splice(indexToExtract,1);
322 		
323 		//reinsert item
324 		this.datasource.splice(si[0],0,itemToExtract);
325 		
326 		//force rerender
327 		this.setDataSource(this.datasource);
328 		
329 		return this;
330 	},
331 
332 	/**
333 	 * Selects previous from list
334 	 * @return {this}
335 	 */
336 	selectPreviousFromList : function()
337 	{
338 		var selected = this.listRender.getSelectedIndices(true);
339 
340 		this.listRender.clearSelectedIndices();
341 
342 		if (selected.length)
343 		{
344 			var toSelect = Math.max(0,selected[0]-1);
345 			this.listRender.addSelectedIndex(toSelect);
346 		}
347 		else
348 		{
349 			this.listRender.addSelectedIndex(selected.length-1);
350 		}
351 		return this;
352 	},
353 
354 	/**
355 	 * Selects next item from list
356 	 * @return {this}
357 	 */
358 	selectNextFromList : function()
359 	{
360 		var selected = this.listRender.getSelectedIndices(true);
361 
362 		this.listRender.clearSelectedIndices();
363 
364 		if (selected.length)
365 		{
366 			var toSelect = Math.min(this.listRender.datasource.length-1,selected[0]+1);
367 			this.listRender.addSelectedIndex(toSelect);
368 		}
369 		else
370 		{
371 			this.listRender.addSelectedIndex(0);
372 		}
373 		return this;
374 	},
375 	
376 	/**
377 	 * @return {Array} of selected keys 
378 	 */
379 	getSelectedKeys : function()
380 	{
381 		var keys = [];
382 
383 		this.selectedIndices.each(this.getProxy(function(index,d)
384 		{
385 			var keyIndex = this.selectedIndices.getKeyByIndex(index);
386 			keys.push(this.indexDataMap[keyIndex][this.indexKey]);
387 		}));
388 	
389 		return keys;		
390 	},
391 	
392 	/**
393 	 * removes selected items
394 	 */
395 	removeSelectedItems : function()
396 	{
397 		var si = this.getSelectedItems();
398 		
399 		this.clearSelectedIndices();
400 		
401 		var i,len;
402 		for (i = 0, len = si.length; i < len; i++ )
403 		{
404 			var index = this.indexDataMap.indexOf(si[i]);
405 			this.indexDataMap.splice(index,1);
406 			this.removeItem(si[i],index);
407 		}
408 	},	
409 	
410 	/**
411 	 * @return {Array} of selected items
412 	 */
413 	getSelectedItems : function()
414 	{
415 		var items = [];
416 
417 		this.selectedIndices.each(this.getProxy(function(index,d)
418 		{
419 			var keyIndex = this.selectedIndices.getKeyByIndex(index);
420 			items.push(this.indexDataMap[keyIndex]);
421 		}));
422 	
423 		return items;
424 	},
425 	
426 	/**
427 	 * adds an item to the datasource.
428 	 * if there is no datasource, we will set an empty datasource on the grid
429 	 * to make sure our items will get rendered
430 	 * @param {mixed} data
431 	 * @return {int} position of added item
432 	 */
433 	addItem : function(data)
434 	{
435 		var addedAt = 0;
436 			
437 		var gridRerender = false;
438 		
439 		if (!this.datasource)
440 		{
441 			//force the datagrid to rerender
442 			this.datagrid.setDataSource([]);		
443 		}
444 		else
445 		{
446 			addedAt = this.datasource.length;
447 		}
448 		
449 		this.datasource[addedAt] = data;
450 		this.indexDataMap[addedAt] = data;
451 		this.triggerEvent('dataSourceChanged');
452 		
453 		return addedAt;
454 	},
455 	
456 	/**
457 	 * @param {Object} data
458 	 */
459 	removeItem : function(data)
460 	{
461 		var match = null;
462 		
463 		var k,len;
464 		for (k =0, len = this.datasource.length; k < len; k++)
465 		{
466 			if(data[this.indexKey] === this.datasource[k][this.indexKey])
467 			{
468 				this.selectedIndices.remove(k);
469 				match = k;
470 			}
471 		}
472 		
473 		if (match !== null)
474 		{
475 			this.datasource.splice(match,1);
476 				
477 			this.triggerEvent('dataSourceChanged');
478 		}
479 		
480 		return match;
481 	},
482 	
483 	/**
484 	 * Removes all items from the list and clears selected indices
485 	 */
486 	removeAllItems : function()
487 	{
488 		this.datasource = [];
489 		this.selectedIndices.clear();
490 	},
491 	
492 	/**
493 	 * @abstract
494 	 * @ignore
495 	 */
496 	selectIndex : function(i){},
497 
498 	/**
499 	 * @abstract
500 	 * @ignore
501 	 */
502 	deSelectIndex : function(i){},
503 	
504 	/**
505 	 * invoked after setting datasource
506 	 * @ignore
507 	 */
508 	createControls : function()
509 	{
510 		this.setupIndexing();
511 		
512 		var i,len;
513 		for (i =0, len = this.datasource.length; i < len; i++)
514 		{
515 			this.indexDataMap[i] = this.datasource[i];
516 		}
517 	},
518 	
519 	/**
520 	 * ensures item render from factory
521 	 * @return Banana.Controls.DatagridItemRender
522 	 */
523 	getObject : function(itemRender)
524 	{
525 		if (typeof(itemRender) === 'function')
526 		{
527 			itemRender = new itemRender();
528 		}
529 
530 		return itemRender;
531 	}
532 });
533