      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