1 /**
  2  * @author Gillis Haasnoot <gillis.haasnoot@gmail.com>
  3  * @package Banana.Controls
  4  * @summary DataGridDataTreeListRender  
  5  */
  6 
  7 goog.require('Banana.Controls.DataGridTreeListRender');
  8 goog.require('Banana.Controls.DataGridTreeDataItemRender');
  9 
 10 goog.provide('Banana.Controls.DataGridDataTreeListRender');
 11 
 12 /** @namespace Banana.Controls.DataGridDataTreeListRender */
 13 namespace('Banana.Controls').DataGridDataTreeListRender = Banana.Controls.DataGridTreeListRender.extend(
 14 /** @lends Banana.Controls.DataGridDataTreeListRender.prototype */
 15 {
 16 		
 17 		/**
 18 		 * Data tree is extends from a Banana.Controls.DataGridTreeListRender with added functionality in the nodes
 19 		 * containing checkboxes. Selecting a checkbox will also selects checkboxes inside the child nodes
 20 		 * 
 21 		 * bind on "dataChanged" event to receive changes when checkboxes are checked.
 22 		 * Call getData() to receive selected keys
 23 		 * 
 24 		 * example: 
 25 		 *     
 26 	        var list = new Banana.Controls.DataGrid()
 27 	       
 28 	        //create list render
 29 	  		var listRender = new Application.Controls.DataGridDataTreeListRender();
 30 			listRender.setChildProperty("children");
 31 			listRender.setItemIndexKey('id'); 
 32 			listRender.setDefaultOpen(true);
 33 			
 34 			listRender.bind('selectIndex',this.getProxy(function(e,index){		
 35 				listRender.selectAllFromIndex(index);		
 36 			}));
 37 			
 38 		    list.setDataSource(datasource); 
 39 			listRender.setData(data);
 40 			
 41 			listRender.bind('dataSourceChanged',this.getProxy(function(){
 42 				
 43 			}));
 44 			
 45 			listRender.bind('dataChanged',this.getProxy(function(){
 46 				var selectedKeys = listRender.getData();
 47 			}));
 48 		 * 
 49 		 * @constructs
 50 		 * @extends Banana.Controls.DataGridTreeListRender
 51 		 */
 52 		init : function()
 53 		{
 54 			this._super();
 55 			this.data = [];
 56 			this.defaultContentItemRender = Banana.Controls.DataGridTreeDataItemRender;
 57 			
 58 			this.bind('onPostCreateIndex',this.getProxy(function(e,data){
 59 			
 60 	
 61 				this.bindClickHandler(data.holder,data.data);
 62 
 63 			}))
 64 		},
 65 		
 66 		/**
 67 		 * @ignore
 68 		 */
 69 		onRowMouseClick : function(){},
 70 		
 71 		/**
 72 		 * overwrite this function to create our own logic for the bind click handler
 73 		 * when user checks or unchecks a checkbox, we also want their children being affected
 74 		 * 
 75 		 * @param {Object} holder
 76 		 * @param {Object} datasource
 77 		 * 
 78 		 * @ignore
 79 		 */
 80 		bindClickHandler : function(holder,datasource)
 81 		{
 82 			var itemRender = holder.getItemRender();
 83 			
 84 			itemRender.bind('userChecked',this.getProxy(function(e){
 85 				
 86 				this.alterData(e.currentTarget.data[this.indexKey]);
 87 				
 88 				var nodeData = this.getNodeDataByData(e.currentTarget.data);
 89 				nodeData.checked = true;
 90 				
 91 				var data = e.currentTarget.getData();
 92 				
 93 				this.toggleCheckStateFrom(nodeData.index,null,true);
 94 				
 95 				this.determineCheckboxState();
 96 			}));
 97 			
 98 			itemRender.bind('userUnchecked',this.getProxy(function(e){
 99 				
100 				this.alterData(e.currentTarget.data[this.indexKey]);
101 				
102 				var nodeData = this.getNodeDataByData(e.currentTarget.data);
103 				
104 				nodeData.checked = false;
105 				
106 				var data = e.currentTarget.getData();
107 			
108 				this.toggleCheckStateFrom(nodeData.index,null,false);
109 				
110 				this.determineCheckboxState();
111 			}));
112 		},
113 		
114 		/**
115 		 * Alters data by given key and state
116 		 * @param {mixed} key
117 		 * @param {state} Boolean when true we add key to data, false removes it
118 		 */
119 		alterData : function(key,state)
120 		{
121 			if (!this.data)
122 			{
123 				this.data = [];
124 			}
125 			
126 			var index = this.data.indexOf(key);
127 			
128 			if (state)
129 			{
130 				if (index >-1)
131 				{
132 					return; 
133 				}
134 				this.data.push(key);
135 				
136 			}
137 			else
138 			{
139 				if (index<0)
140 				{
141 					return;
142 				}
143 				this.data.splice(index,1);
144 			}
145 			
146 			this.triggerEvent('dataChanged');
147 		},
148 		
149 		/**
150 		 * Checks or unchecks all nodes from a specific node point
151 		 *
152 		 * @param {String} index to start
153 		 * @param {Object} datasource
154 		 * @param {Boolean} checkbox state true for checked, false for unchecked
155 		 */
156 		toggleCheckStateFrom : function(index,datasource,state)
157 		{
158 			if (!datasource)
159 			{
160 				datasource = this.getDataSourceByIndex(index);
161 			}
162 			
163 			if (!datasource)
164 			{
165 				return;
166 			}
167 			var nodeData = this.getNodeDataByData(datasource);
168 			nodeData.checked = state;
169 			
170 			this.alterData(datasource[this.indexKey],state);
171 			
172 			var holder = this.getHolder(nodeData.index);
173 			
174 			//if we have the visual control (we dont have it when node is closed)
175 			if (holder)
176 			{	
177 				var ir = holder.getItemRender();
178 			
179 				if (ir)
180 				{
181 					ir.setChecked(state);
182 				}
183 			}
184 						
185 			if (!datasource.children || !datasource.children.length)
186 			{
187 				return;
188 			}
189 			
190 			var i, len;
191 			for (i = 0, len = datasource.children.length; i < len; i++)
192 			{
193 				var item = datasource.children[i];
194 				
195 				this.toggleCheckStateFrom(null,item,state);
196 			}			
197 		},
198 		
199 		/**
200 		 * @overwrite
201 		 * 
202 		 * @param {String} index
203 		 * @param {Object} datasource
204 		 */
205 		openNode : function(index,datasource)
206 		{
207 			this._super(index,datasource);
208 			
209 			this.determineCheckboxData();
210 			this.determineCheckboxState();
211 		},
212 		
213 		/**
214 		 * @overwrite
215 		 * 
216 		 * @param {String} index
217 		 * @param {Object} datasource
218 		 */
219 		closeNode : function(index,datasource)
220 		{
221 			this._super(index,datasource);
222 			
223 			this.determineCheckboxData();
224 			this.determineCheckboxState();
225 		},
226 		
227 		/**
228 		 * @param {Array} data
229 		 * @return {this}
230 		 */
231 		setData : function(data)
232 		{
233 			this.data = data;
234 			this.determineCheckboxData();
235 			this.determineCheckboxState();
236 			return this;
237 		},
238 		
239 		/**
240 		 * @ignore
241 		 */
242 		updateDisplay : function()
243 		{
244 			this._super();
245 		
246 			//TODO: we need to do this in case the data is set before the tree is
247 			//rendered. its a bit of overhead. a better implementation should be created
248 			//for now it works with some performance decrease. 
249 			this.determineCheckboxData();
250 			this.determineCheckboxState();
251 		},
252 		
253 		/**
254 		 * whats are we doing here?
255 		 * We are walking recursively though all nodes and determine if a checkbox
256 		 * should be checked or not. 
257 		 * 
258 		 * @ignore
259 		 */
260 		determineCheckboxData : function(datasource)
261 		{
262 			if (!this.isRendered || !this.data || !this.data.length)
263 			{
264 				return;
265 			}
266 			
267 			if (!datasource)
268 			{
269 				datasource = this.datasource;
270 			}
271 			
272 			var i, len;
273 			for (i =0, len = datasource.length; i < len; i++)	
274 			{
275 				var item = datasource[i];
276 				var nodeData = this.getNodeDataByData(item);
277 
278 				if (!item.children || !item.children.length)
279 				{
280 					if (this.data.indexOf(item[this.indexKey]) !== -1)
281 					{	
282 						var holder = this.getHolder(nodeData.index);
283 						
284 						if (!holder){continue;}	
285 						
286 						var ir = holder.getItemRender();
287 						
288 						if (!ir){continue;}	
289 
290 						ir.setChecked(true);
291 						nodeData.checked = true;
292 					}
293 				}
294 				else
295 				{	
296 					var c, clen;
297 					for (c =0, clen = item.children.length; c < clen; c++)	
298 					{
299 						var child = item.children[c];
300 					
301 						this.determineCheckboxData([child]);
302 					}
303 				}
304 			}			
305 		},
306 	
307 		/**
308 		 * we are walking recursively though all nodes and 
309 		 * do the following things
310 		 * 
311 		 *  if all children of a node are checked, we check the node and enable
312 		 *  if none of the children are checked, we uncheck the node en enable the node
313 		 *  if some of the children are checked we check the node and disable the node
314 		 *  
315 		 *  we do this in a depth first style. 
316 		 *  
317 		 *  @param {Object} datasource
318 		 *  @ignore
319 		 */
320 		determineCheckboxState: function(datasource)
321 		{			
322 			if (!this.isRendered || !this.data || !this.data.length)
323 			{
324 				return;
325 			}
326 			
327 			if (!datasource)
328 			{
329 				datasource = this.datasource;
330 			}
331 			
332 			var allChecked = true;
333 			var noneChecked = true;
334 			
335 			var i, len;
336 			for (i = 0, len = datasource.length; i < len; i++)	
337 			{
338 				var item = datasource[i];
339 					
340 				allChecked = true;
341 				noneChecked = true;
342 				var indeterminate = false;
343 				
344 				if (!item.children || !item.children.length)
345 				{
346 					continue;
347 				}
348 				
349 				var c, clen;
350 				for (c = 0, clen = item.children.length; c < clen; c++)	
351 				{
352 					var child = item.children[c];
353 			
354 					var nodeData = this.getNodeDataByData(child);
355 					this.determineCheckboxState([child]);
356 					
357 					if (!nodeData.checked)
358 					{
359 						allChecked = false;
360 					}
361 					else
362 					{
363 						noneChecked = false;
364 					}
365 					
366 					if (nodeData.indeterminate)
367 					{
368 						noneChecked = allChecked = false;
369 					}
370 				}
371 				
372 				itemNodeData = this.getNodeDataByData(item);				
373 
374 				var holder = this.getHolder(itemNodeData.index);
375 				var ir = null;
376 				
377 				if (holder)
378 				{
379 					ir = holder.getItemRender();
380 				}	
381 				
382 				if (allChecked)
383 				{
384 					if (ir)
385 					{
386 						ir.setChecked(true);
387 						ir.setEnabled(true);
388 					}
389 					
390 					itemNodeData.checked = true;
391 					itemNodeData.indeterminate = false;
392 				}
393 				else if (noneChecked)
394 				{
395 					if (ir)
396 					{
397 						ir.setChecked(false);
398 					}
399 					 
400 					itemNodeData.checked = false;
401 					itemNodeData.indeterminate = false;
402 				}
403 				else
404 				{
405 					if (ir)
406 					{
407 						ir.setChecked(true);
408 						ir.setEnabled(false);
409 					}
410 					
411 					itemNodeData.checked = true;
412 					itemNodeData.indeterminate = true;
413 				}
414 			}
415 				
416 		}
417 });