1 /* 2 * Copyright (C) 2008-2009 WaveMaker Software, Inc. 3 * 4 * This file is part of the WaveMaker Client Runtime. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 dojo.provide("wm.base.widget.DataGrid"); 19 dojo.require("wm.base.widget.dijit.Grid"); 20 dojo.require("wm.base.widget.Formatters"); 21 dojo.require('wm.base.lib.data'); 22 23 /** 24 @name dojox.grid.cell 25 @class 26 An object that describes a cell in the Grid. 27 @todo document cell object 28 @noindex 29 */ 30 31 /** 32 @name wm.DataGrid.Event 33 @class 34 A browser Event object, with some additional properties. 35 @property {wm.dijit.Grid} grid The Dijit Grid that owns the click 36 @property {dojox.GridView} sourceView The View (scrolling region) that owns the click 37 @property {Number} cellIndex The index of the cell that owns the click (or -1) 38 @property {Number} rowIndex The index of the row that owns the click (or -1) 39 @noindex 40 */ 41 42 /** 43 @name wm.MavericksModel 44 @class 45 */ 46 dojo.declare("wm.MavericksModel", dojox.grid.data.Model, { 47 maxObjectDepth: 5, 48 allChange: function(){ 49 this.notify("AllChange", arguments); 50 this.notify("Change", arguments); 51 }, 52 setData: function(inData) { 53 this.variable = inData; 54 this.schemaToFields(); 55 }, 56 _schemaToFields: function(inSchema, inName, inRelated, inDepth) { 57 if (!inSchema || inDepth > this.maxObjectDepth) 58 return; 59 var p = inName ? inName + '.' : '', inDepth = inDepth || 0; 60 inDepth++; 61 for (var i in inSchema) { 62 var ti = inSchema[i], n = p + i; 63 var field = { name: n.replace(/\./g, "_"), key: n, field: n, compare: wm.data.compare }; 64 if (ti.isList) { 65 this.fields.set(this.fields.values.length, field); 66 } else if (wm.typeManager.isStructuredType(ti.type)) { 67 if (!inRelated || inRelated && dojo.indexOf(inRelated, n) != -1) { 68 // only need depth protection if we do not have related objects 69 var d = inRelated ? 0 : inDepth; 70 this._schemaToFields(wm.typeManager.getTypeSchema(ti.type), n, inRelated, d); 71 } 72 } else { 73 this.fields.set(this.fields.values.length, field); 74 } 75 } 76 }, 77 schemaToFields: function() { 78 this.fields.clear(); 79 if (this.variable) 80 this._schemaToFields(this.variable._dataSchema, '', this.variable.related); 81 }, 82 getRowCount: function(){ 83 return this.variable && this.variable.isList && this.variable.getCount(); 84 }, 85 measure: function(){ 86 this.count = this.getRowCount(); 87 this.allChange(); 88 }, 89 getRow: function(inRowIndex){ 90 //console.debug(this, "getRow called"); 91 return this.variable.getItem(inRowIndex); 92 }, 93 getDatum: function(inRowIndex, inField){ 94 var 95 i = this.getRow(inRowIndex), 96 f = i && (inField >= 0) && this.fields.values[inField]; 97 return f && (i.data[f.key] || i.getValue(f.key)); 98 }, 99 setDatum: function(inDatum, inRowIndex, inField){ 100 if (inDatum !== undefined) { 101 var i = this.variable.getItem(inRowIndex); 102 if (i) { 103 i.beginUpdate(); 104 i.setValue(this.fields.values[inField].key, inDatum); 105 i.endUpdate(); 106 this.notify("DatumChange", arguments); 107 } 108 } 109 }, 110 beginModifyRow: function(inRowIndex){ 111 }, 112 endModifyRow: function(inRowIndex) { 113 }, 114 cancelModifyRow: function(inRowIndex) { 115 this.allChange(); 116 }, 117 // sort 118 sort: function(/* (+|-)column_index+1, ... */){ 119 this.variable.sort(this.makeComparator(arguments)); 120 }, 121 // FIXME: data will be sorted on field key values; 122 // any data formatting performed by a grid column expression will be ignored 123 generateComparator: function(inCompare, inField, inTrueForAscend, inSubCompare){ 124 // we assume that a and b are wm.Variables 125 return function(a, b){ 126 a = a.getValue(inField); 127 b = b.getValue(inField); 128 var ineq = inCompare(a, b); 129 return ineq ? (inTrueForAscend ? ineq : -ineq) : inSubCompare && inSubCompare(a, b); 130 } 131 } 132 }); 133 134 /** 135 Encapsulates a column in a DataGrid. 136 @name wm.DataGridColumn 137 @class 138 @extends wm.Component 139 */ 140 dojo.declare("wm.DataGridColumn", wm.Component, { 141 /** @lends wm.DataGridColumn.prototype */ 142 /** 143 This column will be as wide as needed to fill available space. 144 If there is no space avaiable, the column will be invisible. 145 If multiple columns are <i>autoSize</i>, the available space 146 is divided evenly. 147 */ 148 autoSize: false, 149 /** 150 This column will be as wide as needed to fill available space. 151 If there is no space avaiable, the column will be invisible. 152 If multiple columns are <i>autoSize</i>, the available space 153 is divided evenly. 154 */ 155 dataExpression: "", 156 field: "", 157 caption: "", 158 columnWidth: "120px", 159 display: "", 160 format: "(details)", 161 index: 0, 162 showing: true, 163 addColumn: "(click to add column)", 164 removeColumn: "(click to remove)", 165 init: function() { 166 // BC 167 delete this.format; 168 this.caption = this.label || this.caption; 169 delete this.label; 170 // 171 this.inherited(arguments); 172 this.setDisplay(this.display); 173 }, 174 // FIXME: in progress attempt to allow deleting of columns natively 175 /*destroy: function() { 176 if (this._destroying) 177 return; 178 var o = this.owner; 179 this.inherited(arguments); 180 if (o) { 181 this._destroying = true; 182 o.doRemoveColumn(this); 183 } 184 },*/ 185 setField: function(inField) { 186 this.field = inField; 187 this.caption = this.caption || this.field; 188 }, 189 setIndex: function(inIndex) { 190 this.owner.setColumnIndex(this, inIndex); 191 }, 192 setColumnWidth: function(inColumnWidth) { 193 this.columnWidth = inColumnWidth; 194 this.autoSize = false; 195 }, 196 formatChanged: function() { 197 this.owner.columnsChanged(); 198 }, 199 valueChanged: function(inProp, inValue) { 200 this.inherited(arguments); 201 this.owner.columnsChanged(); 202 }, 203 getCellProps: function() { 204 var cell = { 205 name: this.caption, 206 field: this.field, 207 dataExpression: this.dataExpression, 208 hide: !this.showing 209 }; 210 if (this.autoSize) 211 cell.width = "auto"; 212 else if (this.columnWidth) 213 cell.width = this.columnWidth; 214 if (this.components.format) 215 dojo.mixin(cell, this.components.format.getColProps()); 216 if (this.editor) { 217 cell.editor = this.editor; 218 } 219 if (this.selectOptions) { 220 cell.options = this.selectOptions; 221 } 222 if (this.dataExpression) 223 cell.get = this.getExpressionDatum; 224 return cell; 225 }, 226 // cell getter for a column that contains an expression. 227 // called in context of grid cell 228 getExpressionDatum: function(inRowIndex) { 229 return wm.expression.getValue(this.dataExpression, this.grid.model.getRow(inRowIndex)); 230 }, 231 setDisplay: function(inDisplay) { 232 var c = this.display = inDisplay; 233 if (c.slice(0, 5) != "wm") 234 c = "wm." + c + "Formatter"; 235 var ctor = dojo.getObject(c); 236 if (!ctor) { 237 this.display = "" 238 ctor = wm.DataFormatter; 239 } 240 wm.fire(this.components.format, "destroy"); 241 new ctor({name: "format", owner: this}); 242 }, 243 /** 244 Fires when the user clicks the mouse in this cell. 245 <br/><br/> 246 @param {Number} inRowIndex The clicked row. 247 @param {dojox.grid.cell} inCell Various fields describing the clicked cell. 248 @param {wm.DataGrid.Event} inEvent The browser event object, decorated. 249 */ 250 onClick: function(inRowIndex, inCell, inEvent) { 251 } 252 }); 253 254 wm.DataGridColumn.extend({ 255 listProperties: function() { 256 var p = this.inherited(arguments); 257 p.columnWidth.ignore = this.autoSize; 258 return p; 259 }, 260 set_index: function(inIndex) { 261 var reselect = (studio.selected == this); 262 this.setIndex(inIndex); 263 if (reselect) { 264 wm.onidle(this, function() { 265 studio.select(null); 266 studio.select(this); 267 }); 268 } 269 }, 270 isParentLocked: function() { 271 return this.owner && this.owner.isParentLocked(); 272 }, 273 isParentFrozen: function() { 274 return this.owner && this.owner.isParentFrozen(); 275 }, 276 makePropEdit: function(inName, inValue, inDefault) { 277 switch (inName) { 278 case "addColumn": 279 case "removeColumn": 280 return makeReadonlyButtonEdit(inName, inValue, inDefault); 281 case "field": 282 return makeSelectPropEdit(inName, inValue, this.owner._listFields(), inDefault); 283 case "display": 284 return makeSelectPropEdit(inName, inValue, [""].concat(wm.formatters), inDefault); 285 case "columnWidth": 286 return new wm.propEdit.UnitValue({component: this, name: inName, value: inValue, options: this.owner._sizeUnits}); 287 } 288 return this.inherited(arguments); 289 }, 290 editProp: function(inName, inValue, inInspector) { 291 switch (inName) { 292 case "removeColumn": 293 return this.owner.doRemoveColumn(this); 294 case "addColumn": 295 return this.owner.doAddColumn(); 296 } 297 return this.inherited(arguments); 298 } 299 }); 300 301 /** 302 Widget for displaying and editing tabulated data. 303 @name wm.DataGrid 304 @class 305 @extends wm.dijit.Grid 306 */ 307 dojo.declare("wm.DataGrid", wm.dijit.Grid, { 308 /** @lends wm.DataGrid.prototype */ 309 addColumn: "(click to add column)", 310 autoColumns: "(auto add columns)", 311 clearColumns: "(clear all columns)", 312 updateNow: "(update now)", 313 collection: "Columns", 314 init: function() { 315 this.inherited(arguments); 316 this.dijit.canEdit = dojo.hitch(this, "canEdit"); 317 this._columns = []; 318 this.selectedItem = new wm.Variable({name: "selectedItem", owner: this}); 319 if (this.isDesignLoaded()) { 320 this.connect(this.dijit, "onHeaderCellClick", this, "headerCellDesignClick"); 321 this.connect(this.dijit, "setCellWidth", this, "setDesignCellWidth"); 322 } 323 this.connect(this.dijit, "sort", this, "sort"); 324 }, 325 // BC 326 doSetSizeBc: function() { 327 // Previously the default units for DataGrid were "flex" so "sizeUnits" was not streamed. 328 // Fixup here is to default DataGrids with explicit "size" to "flex" units. 329 if (this.size && !this.sizeUnits) 330 this.sizeUnits = "flex"; 331 this.inherited(arguments); 332 }, 333 headerCellDesignClick: function(e){ 334 var c = this._columns[e.cell.index]; 335 if (c) 336 studio.select(c); 337 //this.dijit.constructor.prototype.onHeaderCellClick.call(this.dijit, e); 338 dojo.stopEvent(e); 339 }, 340 setDesignCellWidth: function(inIndex, inUnitWidth) { 341 this._columns[inIndex].columnWidth = inUnitWidth; 342 }, 343 postInit: function() { 344 this.inherited(arguments); 345 this._clearColumns(); 346 for (var i in this.components) { 347 var c = this.components[i]; 348 if (c instanceof wm.DataGridColumn) 349 this._columns.push(c); 350 } 351 this.renderGrid(); 352 }, 353 // columns and structure 354 getCollection: function(inName) { 355 var cn = []; 356 for (var i in this.components) { 357 var c = this.components[i]; 358 if (c instanceof wm.DataGridColumn) 359 cn.push(c); 360 } 361 cn.sort(function(a, b) { 362 return a.index - b.index; 363 }); 364 return cn; 365 }, 366 columnsToStructure: function() { 367 // a subrow is an array of cells 368 // a view is a structure, with an array of subrows named 'rows' 369 // a grid layout is an array of views 370 var subrow = [], rows = [], view = {rows: rows}, s = [view]; 371 // currently we have exactly one subrow 372 this._columns.sort(this._columnsSorter); 373 for (var i=0, c; (c=this._columns[i]); i++) { 374 var cell = c.getCellProps(); 375 this.onSetColumns(cell, i); 376 subrow.push(cell); 377 } 378 this.adjustRowCellProps(subrow); 379 rows.push(subrow); 380 // ========================================= 381 // r&d: let's make another subrow for detail 382 /* cell = { 383 name: ' ', get: dojo.hitch(this, "getDetail"), colSpan: 4, styles: 'padding: 0; margin: 0;' 384 }; 385 rows.push([cell]);*/ 386 return s; 387 }, 388 /*getDetail: function(inRowIndex) { 389 var item = this.dataSet.getItem(inRowIndex); 390 return new Date(item.getValue("lastUpdate")); 391 // 392 var fas = item.getValue("filmActors"); 393 var n = []; 394 for (var i=0, l=fas.getCount(), f; i<l; i++) { 395 f = fas.getItem(i).getValue("film") 396 n.push(f.getValue("title")); 397 } 398 return n.join('<br>'); 399 },*/ 400 // ========================================= 401 // ensure cell meets requirements 402 adjustRowCellProps: function(inRow) { 403 var flex = 0; 404 // support translating width in flex to % 405 // get total flex 406 dojo.forEach(inRow, function(c) { 407 var u = wm.splitUnits(c.width); 408 if (u.units == "flex") 409 flex += u.value; 410 }); 411 // convert flex value to % 412 dojo.forEach(inRow, function(c) { 413 var u = wm.splitUnits(c.width); 414 if (flex && u.units == "flex" && u.value) 415 c.width = Math.round(u.value * 100/ flex) + "%"; 416 }); 417 }, 418 setStructure: function(inStructure) { 419 this.onSetStructure(inStructure); 420 this.dijit.setStructure(inStructure && inStructure.length ? inStructure : null); 421 }, 422 columnsChanged: function() { 423 if (!this._loading && !this._updating) { 424 this.setStructure(this.columnsToStructure()); 425 if (this.isDesignLoaded()) 426 studio.refreshComponentOnTree(this); 427 } 428 }, 429 setColumnIndex: function(inColumn, inIndex) { 430 inColumn.index = inIndex - 0.5; 431 this._columns.sort(this._columnsSorter); 432 for (var i=0, c; (c=this._columns[i]); i++) 433 c.index = i; 434 this.columnsChanged(); 435 }, 436 _columnsSorter: function(inA, inB) { 437 return inA.index - inB.index; 438 }, 439 _addFields: function(inList, inSchema, inName, inRelated, inDepth) { 440 if (!inSchema || inDepth > this.dijit.model.maxObjectDepth) 441 return; 442 var p = inName ? inName + '.' : '', inDepth = inDepth || 0; 443 inDepth++; 444 for (var i in inSchema) { 445 var ti = inSchema[i], n = p + i; 446 // list column not exposed 447 if (ti.isList) { 448 } else if (wm.typeManager.isStructuredType(ti.type)) { 449 // only show field info for available structured objects 450 if (!inRelated || inRelated && dojo.indexOf(inRelated, n) != -1) { 451 var d = inRelated ? 0 : inDepth; 452 this._addFields(inList, wm.typeManager.getTypeSchema(ti.type), n, inRelated, d); 453 } 454 } else { 455 inList.push(n); 456 } 457 } 458 }, 459 _listFields: function(inList, inSchema, inName) { 460 var list = [ "" ]; 461 if (this.dataSet) 462 this._addFields(list, this.dataSet._dataSchema, '', (this.dataSet ||0).related); 463 return list; 464 }, 465 _clearColumns: function() { 466 for (var i=0, c; (c=this._columns[i]); i++) 467 c.destroy(); 468 this._columns = []; 469 }, 470 _typifyColumn: function(ioColumn, inType) { 471 var t = wm.typeManager.getPrimitiveType(inType) || inType; 472 ioColumn.display = dojo.indexOf(wm.formatters, t) != -1 ? t : ""; 473 }, 474 _hasColumnForField: function(inField) { 475 for (var i=0, columns=this._columns, c; (c=columns[i]); i++) 476 if (c.field == inField) 477 return true; 478 }, 479 _viewToColumns: function(inView, inName) { 480 var p = inName ? inName + '.' : '', col; 481 for (var i=0, f, field; f=inView[i]; i++) { 482 // ignore view field if it's not to be included in lists 483 field = f.dataIndex; 484 if (!f.includeLists || this._hasColumnForField(field)) 485 continue; 486 this._index++; 487 col = { 488 name: f.dataIndex.replace(/\.(\S)/g, function(w) {return w.slice(1).toUpperCase();} ), 489 label: f.caption, 490 field: field, 491 owner: this, 492 index: f.order === undefined ? this._index : f.order, 493 autoSize: f.autoSize 494 }; 495 if (!col.autoSize && f.width && f.widthUnits) 496 col.columnWidth = f.width + f.widthUnits; 497 this._typifyColumn(col, f.displayType); 498 this._adjustColumnProps(col); 499 this._addColumn(col); 500 } 501 }, 502 _schemaToColumns: function(inSchema, inName) { 503 if (!inSchema) 504 return; 505 var p = inName ? inName + '.' : ''; 506 for (var i in inSchema) { 507 var ti = inSchema[i], n = p + i; 508 if (this._hasColumnForField(n)) 509 continue; 510 if (ti.isList) { 511 } else if (wm.typeManager.isStructuredType(ti.type)) { 512 } else { 513 this._index++; 514 var name = n.replace(/\./g, "_"); 515 var col = { name: name, label: n, field: n, owner: this, index: this._index }; 516 this._typifyColumn(col, ti.type); 517 this._adjustColumnProps(col); 518 this._addColumn(col); 519 } 520 } 521 }, 522 _adjustColumnProps: function(inColProps) { 523 // make sure we have a valid name 524 var name = inColProps.name || "column"; 525 // ensure name ends with a number (to avoid reserved word, e.g. state, id) 526 name = name.match(/[0-9]$/) ? name : name + 1; 527 name = this.getUniqueName(name); 528 inColProps.name = name; 529 }, 530 _addColumn: function(inColProps) { 531 this._columns.push(new wm.DataGridColumn(inColProps)); 532 }, 533 _getStartColIndex: function() { 534 var m = 0; 535 dojo.forEach(this._columns, function(c) { 536 m = Math.max(m, c.index); 537 }); 538 return m == 0 ? m : m++; 539 }, 540 dataSetToColumns: function() { 541 this._updating = true; 542 if (this.dataSet) { 543 this._index = this._getStartColIndex(); 544 if (this.dataSet.liveView && this.dataSet.liveView.service) 545 this._viewToColumns(this.dataSet.getViewListFields(), '') 546 else 547 this._schemaToColumns(this.dataSet._dataSchema, ''); 548 } 549 this._updating = false; 550 if (this.isDesignLoaded()) 551 studio.refreshComponentOnTree(this); 552 }, 553 createDefaultColumns: function() { 554 var col = {name: "column1", autoSize: true, owner: this}; 555 this._adjustColumnProps(col); 556 this._addColumn(col); 557 }, 558 // 559 _hasDefaultColumns: function() { 560 var c = this._columns[0]; 561 return (!c || (!c.field && !c.dataExpression) && this._columns.length == 1); 562 }, 563 // virtual binding target 564 setDataSet: function(inDataSet) { 565 var d = this.dataSet = inDataSet; 566 /*if (d && !d.isList) 567 d = this.dataSet = null;*/ 568 // disable lazy loading for dataSets assigned to grid. 569 // note: do allow this so that sub-grids can lazy load. 570 /* 571 if (d) 572 d._allowLazyLoad = false; 573 */ 574 // always have a model so that grid does not cause rendering error if model is removed (should be fixed in grid). 575 this.dijit.setModel(new wm.MavericksModel(null, d)); 576 if (d && this._hasDefaultColumns()) { 577 this._clearColumns(); 578 this.dataSetToColumns(); 579 } 580 this.renderGrid(); 581 }, 582 preRender: function() { 583 this.dataSetToSelectedItem(); 584 if (this._columns.length == 0) 585 this.createDefaultColumns(); 586 // save previous sort / selection info 587 this._lastSort = this.dijit.sortInfo; 588 this._lastSelectedIndex = this.getSelectedIndex(); 589 // clear sort indicator 590 this.dijit.sortInfo = 0; 591 // handle selection 592 this.clearSelection(); 593 }, 594 renderGrid: function() { 595 // make sure to set selectedItem type so that it's not out of sync. 596 if (!this._loading) { 597 this.preRender(); 598 this.onBeforeRender(); 599 this.setStructure(this.columnsToStructure()); 600 this.onAfterRender(); 601 } 602 603 }, 604 // selection 605 /** 606 Select a row by index. 607 <br/> 608 Previous selection is cleared. 609 @param {Number} inIndex Integer index of row to select. 610 */ 611 select: function(inIndex) { 612 this.dijit.selection.select(inIndex); 613 }, 614 /** 615 Clear the selection such that no rows are selected. 616 */ 617 clearSelection: function() { 618 this.dijit.selection.clear(); 619 this.updateSelected(); 620 }, 621 /** 622 Returns true if there any selected rows. 623 @returns @Boolean True if any rows are selected. 624 */ 625 hasSelection: function() { 626 return Boolean(this.dijit.selection.getFirstSelected() != -1); 627 }, 628 /** 629 Returns the index of the selected row. 630 @returns @Number Integer index of selected row. 631 */ 632 getSelectedIndex: function() { 633 return this.dijit.selection.selectedIndex; 634 }, 635 /** 636 Returns true if there are no selected rows. 637 <br/><br/> 638 This method exists to support the bindable emptySelection 639 virtual property (i.e. it implements <code>getValue("emptySelection")</code>.). 640 <br /> 641 @returns @Boolean True if no rows are selected. 642 */ 643 getEmptySelection: function() { 644 return !this.hasSelection(); 645 }, 646 dataSetToSelectedItem: function() { 647 this.selectedItem.setLiveView((this.dataSet|| 0).liveView); 648 this.selectedItem.setType(this.dataSet ? this.dataSet.type : "any"); 649 }, 650 // FIXME: not clear why we have this.selected and this.selectedItem which is a copy 651 updateSelected: function() { 652 var s = this.dijit.selection.selectedIndex; 653 this.selected = this.dataSet && s >= 0 ? this.dataSet.getItem(s) : null; 654 if (this.selected) 655 this.selectedItem.setData(this.selected); 656 else 657 this.selectedItem.clearData(); 658 // update "emptySelection" property (to trigger bindings) 659 this.setValue("emptySelection", !this.hasSelection()); 660 }, 661 // events 662 /** 663 Fires when the user clicks the mouse in a cell. 664 <br/><br/> 665 <i>inEvent</i> object is decorated with information 666 about the clicked cell. 667 <br/><br/> 668 @param {wm.DataGrid.Event} inEvent The browser event object, decorated. 669 */ 670 onCellClick: function(inEvent) { 671 if (inEvent.cell) { 672 var i = inEvent.cell.index; 673 var c = this._columns[i]; 674 wm.fire(c, "onClick", [inEvent.rowIndex, inEvent.cell, inEvent]); 675 } 676 }, 677 /** 678 Fires when the user clicks the mouse in the grid header. 679 <br/><br/> 680 <i>inEvent</i> object is decorated with information 681 about the clicked header. 682 <br/><br/> 683 @param {wm.DataGrid.Event} inEvent The browser event object, decorated. 684 */ 685 onHeaderCellClick: function(inEvent) { 686 }, 687 _onSelected: function(inIndex) { 688 // buffer selection 689 wm.job(this.getRuntimeId(), 250, dojo.hitch(this, "onSelected", inIndex)); 690 }, 691 _onSelectionChanged: function() { 692 wm.job(this.getRuntimeId() + "-selectionChanged", 250, dojo.hitch(this, "onSelectionChanged")); 693 }, 694 /** 695 Event that occurs when the selection is changed. 696 <br/><br/> 697 Selections are buffered, so only the last in a series of rapid selections will 698 trigger this event. 699 <br /> 700 */ 701 onSelectionChanged: function() { 702 }, 703 /** 704 Event that occurs when a selection is made. 705 <br/><br/> 706 Selections are buffered, so only the last in a series of rapid selections will 707 trigger this event. 708 <br /> 709 @param {Number} inIndex The index of the selected row. 710 */ 711 onSelected: function(inIndex) { 712 this.updateSelected(); 713 }, 714 _onDeselected: function(inIndex) { 715 // buffer deselection 716 wm.job(this.getRuntimeId(), 250, dojo.hitch(this, "onDeselected", inIndex)); 717 }, 718 /** 719 Event that occurs when a selection is removed. 720 <br/><br/> 721 Deselections are buffered, so only the last in a series of rapid deselections will 722 trigger this event. 723 <br /> 724 @param {Number} inIndex The index of the de-selected row. 725 */ 726 onDeselected: function(inIndex) { 727 this.updateSelected(); 728 }, 729 /** 730 Allows user code to customize column objects. 731 <br/><br/> 732 This event is fired for each column in the Grid. Custom code 733 can modify the column object to alter behavior or appearance 734 of the Grid. 735 <br/><br/> 736 @param {wm.DataGridColumn} inColumn Object whose properties describe the grid column. 737 @param {Number} inIndex Numeric index of the column 738 */ 739 onSetColumns: function(inColumn, inIndex) { 740 }, 741 /** 742 Allows user code to customize grid structure. 743 <br/><br/> 744 This event is fired whenever the Grid is initialized. Custom code 745 can modify the structure object to alter behavior or appearance 746 of the Grid. 747 <br/><br/> 748 ToDo: document inStructure properties. 749 <br/><br/> 750 @param {Object} inStructure Object whose properties describe the grid structure. 751 */ 752 onSetStructure: function(inStructure) { 753 }, 754 /** 755 Event that fires before the Grid is populated. 756 <br/><br/> 757 This event is fired before the Grid is populated, allowing custom code 758 to cache information or do tasks. 759 */ 760 onBeforeRender: function() { 761 }, 762 /** 763 Event that fires after the Grid is populated. 764 <br/><br/> 765 This event is fired after the Grid is populated, allowing custom code 766 to cache information or do tasks. 767 */ 768 onAfterRender: function() { 769 }, 770 // server update 771 /** 772 Force an update of the data model bound to this grid. 773 */ 774 update: function() { 775 var ds = this.getValueById((this.components.binding.wires["dataSet"] || 0).source); 776 wm.fire(ds, "update"); 777 }, 778 canEdit: function(inCell, inRowIndex) { 779 ioEdit = {canEdit: Boolean(inCell.editor)}; 780 this.onCanEdit(ioEdit, inCell, inRowIndex); 781 return ioEdit.canEdit; 782 }, 783 onCanEdit: function(ioEdit, inCell, inRowIndex) { 784 }, 785 sort: function() { 786 this.onSort(this.dijit.getSortField()); 787 }, 788 onSort: function(inSortInfo) { 789 } 790 }); 791 792 wm.DataGrid.extend({ 793 set_dataSet: function(inDataSet) { 794 // support setting dataSet via id from designer 795 if (inDataSet && !(inDataSet instanceof wm.Variable)) { 796 var ds = this.getValueById(inDataSet); 797 if (ds) 798 this.components.binding.addWire("", "dataSet", ds.getId()); 799 } else 800 this.setDataSet(inDataSet); 801 }, 802 doAddColumn: function(inColumn) { 803 var col = {name: "column1", index: this._columns.length, label: "", owner: this }; 804 if (!this._columns.length) 805 col.columnWidth = "auto"; 806 this._adjustColumnProps(col); 807 this._addColumn(col); 808 this.columnsChanged(); 809 if (this.isDesignLoaded()){ 810 var i = this._columns.length - 1; 811 var c = (i>=0 ? this._columns[i] : this); 812 studio.select(c); 813 } 814 }, 815 doRemoveColumn: function(inColumn) { 816 inColumn.destroy(); 817 // FIXME: hack 818 this._columns = []; 819 this.loaded(); 820 // end hack 821 this.columnsChanged(); 822 if (this.isDesignLoaded()){ 823 var i = inColumn.index - 1; 824 var c = this._columns[i < 0 ? 0 : i] || this; 825 studio.select(c); 826 } 827 }, 828 doClearColumns: function() { 829 this._clearColumns(); 830 this.renderGrid(); 831 }, 832 doAutoColumns: function() { 833 //if (this._hasDefaultColumns()) 834 this._clearColumns(); 835 this.dataSetToColumns(); 836 this.renderGrid(); 837 }, 838 makePropEdit: function(inName, inValue, inDefault) { 839 switch (inName) { 840 case "addColumn": 841 case "autoColumns": 842 case "clearColumns": 843 case "updateNow": 844 return makeReadonlyButtonEdit(inName, inValue, inDefault); 845 case "dataSet": 846 return new wm.propEdit.DataSetSelect({component: this, name: inName, value: this.dataSet ? this.dataSet.getId() : "", allowAllTypes: true, listMatch: true}); 847 } 848 return this.inherited(arguments); 849 }, 850 editProp: function(inName, inValue, inInspector) { 851 switch (inName) { 852 case "addColumn": 853 return this.doAddColumn(); 854 case "autoColumns": 855 return this.doAutoColumns(); 856 case "clearColumns": 857 return this.doClearColumns(); 858 case "updateNow": 859 return this.update(); 860 } 861 return this.inherited(arguments); 862 } 863 }); 864 865 // design-time only 866 wm.Object.extendSchema(wm.DataGridColumn, { 867 caption: { group: "common", order: 100, focus: 1 }, 868 addColumn: { group: "operation", order: 10}, 869 removeColumn: { group: "operation", order: 20}, 870 autoSize: { group: "layout", order: 10 }, 871 columnWidth: { group: "layout", order: 20 }, 872 index: { group: "layout", order: 30 }, 873 field: { group: "data", order: 10 }, 874 dataExpression: { group: "data", order: 15 }, 875 display: { group: "data", order: 20 }, 876 format: { group: "data", order: 30, categoryParent: "Properties", categoryProps: {component: "format"}}, 877 showing: {ignore: 1} 878 }); 879 880 wm.Object.extendSchema(wm.DataGrid, { 881 selectedItem: { ignore: true, isObject: true, bindSource: true, simpleBindProp: true }, 882 emptySelection: { ignore: true, bindSource: 1, type: "Boolean" }, 883 dataSet: { readonly: true, group: "data", order: 0, type: "wm.Variable", isList: true, bindTarget: true}, 884 addColumn: { group: "operation", order: 1}, 885 autoColumns: { group: "operation", order: 5}, 886 clearColumns: { group: "operation", order: 10}, 887 updateNow: { group: "operation", order: 15}, 888 collection: { ignore: true }, 889 autoSize: { ignore: true }, 890 disabled: { ignore: true } 891 }); 892