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