You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
971 lines
27 KiB
971 lines
27 KiB
define([ |
|
"dojo/_base/kernel", |
|
"../main", |
|
"dojo/_base/declare", |
|
"dojo/_base/array", |
|
"dojo/_base/lang", |
|
"dojo/_base/event", |
|
"dojo/dom-attr", |
|
"dojo/dom-class", |
|
"dojo/query", |
|
"dojo/keys", |
|
"dijit/tree/ForestStoreModel", |
|
"./DataGrid", |
|
"./_Layout", |
|
"./_FocusManager", |
|
"./_RowManager", |
|
"./_EditManager", |
|
"./TreeSelection", |
|
"./cells/tree", |
|
"./_TreeView" |
|
], function(dojo, dojox, declare, array, lang, event, domAttr, domClass, query, keys, ForestStoreModel, |
|
DataGrid, _Layout, _FocusManager, _RowManager, _EditManager, TreeSelection, TreeCell){ |
|
|
|
dojo.experimental("dojox.grid.TreeGrid"); |
|
|
|
var _TreeAggregator = declare("dojox.grid._TreeAggregator", null, { |
|
cells: [], |
|
grid: null, |
|
childFields: [], |
|
|
|
constructor: function(kwArgs){ |
|
this.cells = kwArgs.cells || []; |
|
this.childFields = kwArgs.childFields || []; |
|
this.grid = kwArgs.grid; |
|
this.store = this.grid.store; |
|
}, |
|
_cacheValue: function(cache, id, value){ |
|
cache[id] = value; |
|
return value; |
|
}, |
|
clearSubtotalCache: function(){ |
|
// summary: |
|
// Clears the subtotal cache so that we are forced to recalc it |
|
// (or reread it) again. This is needed, for example, when |
|
// column order is changed. |
|
if(this.store){ |
|
delete this.store._cachedAggregates; |
|
} |
|
}, |
|
|
|
cnt: function(cell, level, item){ |
|
// summary: |
|
// calculates the count of the children of item at the given level |
|
var total = 0; |
|
var store = this.store; |
|
var childFields = this.childFields; |
|
if(childFields[level]){ |
|
var children = store.getValues(item, childFields[level]); |
|
if (cell.index <= level + 1){ |
|
total = children.length; |
|
}else{ |
|
array.forEach(children, function(c){ |
|
total += this.getForCell(cell, level + 1, c, "cnt"); |
|
}, this); |
|
} |
|
}else{ |
|
total = 1; |
|
} |
|
return total; |
|
}, |
|
sum: function(cell, level, item){ |
|
// summary: |
|
// calculates the sum of the children of item at the given level |
|
var total = 0; |
|
var store = this.store; |
|
var childFields = this.childFields; |
|
if(childFields[level]){ |
|
array.forEach(store.getValues(item, childFields[level]), function(c){ |
|
total += this.getForCell(cell, level + 1, c, "sum"); |
|
}, this); |
|
}else{ |
|
total += store.getValue(item, cell.field); |
|
} |
|
return total; |
|
}, |
|
value: function(cell, level, item){ |
|
// summary: |
|
// Empty function so that we can set "aggregate='value'" to |
|
// force loading from the data - and bypass calculating |
|
}, |
|
getForCell: function(cell, level, item, type){ |
|
// summary: |
|
// Gets the value of the given cell at the given level and type. |
|
// type can be one of "sum", "cnt", or "value". If itemAggregates |
|
// is set and can be used, it is used instead. Values are also |
|
// cached to prevent calculating them too often. |
|
var store = this.store; |
|
if(!store || !item || !store.isItem(item)){ return ""; } |
|
var storeCache = store._cachedAggregates = store._cachedAggregates || {}; |
|
var id = store.getIdentity(item); |
|
var itemCache = storeCache[id] = storeCache[id] || []; |
|
if(!cell.getOpenState){ |
|
cell = this.grid.getCell(cell.layoutIndex + level + 1); |
|
} |
|
var idx = cell.index; |
|
var idxCache = itemCache[idx] = itemCache[idx] || {}; |
|
type = (type || (cell.parentCell ? cell.parentCell.aggregate : "sum"))||"sum"; |
|
var attr = cell.field; |
|
if(attr == store.getLabelAttributes()[0]){ |
|
// If our attribute is one of the label attributes, we should |
|
// use cnt instead (since it makes no sense to do a sum of labels) |
|
type = "cnt"; |
|
} |
|
var typeCache = idxCache[type] = idxCache[type] || []; |
|
|
|
// See if we have it in our cache immediately for easy returning |
|
if(typeCache[level] != undefined){ |
|
return typeCache[level]; |
|
} |
|
|
|
// See if they have specified a valid field |
|
var field = ((cell.parentCell && cell.parentCell.itemAggregates) ? |
|
cell.parentCell.itemAggregates[cell.idxInParent] : "")||""; |
|
if(field && store.hasAttribute(item, field)){ |
|
return this._cacheValue(typeCache, level, store.getValue(item, field)); |
|
}else if(field){ |
|
return this._cacheValue(typeCache, level, 0); |
|
} |
|
|
|
// Calculate it |
|
return this._cacheValue(typeCache, level, this[type](cell, level, item)); |
|
} |
|
}); |
|
|
|
var _TreeLayout = declare("dojox.grid._TreeLayout", _Layout, { |
|
// Whether or not we are collapsable - this is calculated when we |
|
// set our structure. |
|
_isCollapsable: false, |
|
|
|
_getInternalStructure: function(inStructure){ |
|
// Create a "Tree View" with 1 row containing references for |
|
// each column (recursively) |
|
var g = this.grid; |
|
|
|
var s = inStructure; |
|
var cells = s[0].cells[0]; |
|
var tree = { |
|
type: "dojox.grid._TreeView", |
|
cells: [[]] |
|
}; |
|
var cFields = []; |
|
var maxLevels = 0; |
|
var getTreeCells = function(parentCell, level){ |
|
var children = parentCell.children; |
|
var cloneTreeCell = function(originalCell, idx){ |
|
var k, n = {}; |
|
for(k in originalCell){ |
|
n[k] = originalCell[k]; |
|
} |
|
n = lang.mixin(n, { |
|
level: level, |
|
idxInParent: level > 0 ? idx : -1, |
|
parentCell: level > 0 ? parentCell : null |
|
}); |
|
return n; |
|
}; |
|
var ret = []; |
|
array.forEach(children, function(c, idx){ |
|
if("children" in c){ |
|
cFields.push(c.field); |
|
var last = ret[ret.length - 1]; |
|
last.isCollapsable = true; |
|
c.level = level; |
|
ret = ret.concat(getTreeCells(c, level + 1)); |
|
}else{ |
|
ret.push(cloneTreeCell(c, idx)); |
|
} |
|
}); |
|
maxLevels = Math.max(maxLevels, level); |
|
return ret; |
|
}; |
|
var tCell = {children: cells, itemAggregates: []}; |
|
tree.cells[0] = getTreeCells(tCell, 0); |
|
g.aggregator = new _TreeAggregator({cells: tree.cells[0], |
|
grid: g, |
|
childFields: cFields}); |
|
if(g.scroller && g.defaultOpen){ |
|
g.scroller.defaultRowHeight = g.scroller._origDefaultRowHeight * (2 * maxLevels + 1); |
|
} |
|
return [ tree ]; |
|
}, |
|
|
|
setStructure: function(inStructure){ |
|
// Mangle the structure a bit and make it work as desired |
|
var s = inStructure; |
|
var g = this.grid; |
|
// Only supporting single-view, single row or else we |
|
// are not collapsable |
|
if(g && g.treeModel && !array.every(s, function(i){ |
|
return ("cells" in i); |
|
})){ |
|
s = arguments[0] = [{cells:[s]}]; |
|
} |
|
if(s.length == 1 && s[0].cells.length == 1){ |
|
if(g && g.treeModel){ |
|
s[0].type = "dojox.grid._TreeView"; |
|
this._isCollapsable = true; |
|
s[0].cells[0][(this.grid.treeModel?this.grid.expandoCell:0)].isCollapsable = true; |
|
}else{ |
|
var childCells = array.filter(s[0].cells[0], function(c){ |
|
return ("children" in c); |
|
}); |
|
if(childCells.length === 1){ |
|
this._isCollapsable = true; |
|
} |
|
} |
|
} |
|
if(this._isCollapsable && (!g || !g.treeModel)){ |
|
arguments[0] = this._getInternalStructure(s); |
|
} |
|
this.inherited(arguments); |
|
}, |
|
|
|
addCellDef: function(inRowIndex, inCellIndex, inDef){ |
|
var obj = this.inherited(arguments); |
|
return lang.mixin(obj, TreeCell); |
|
} |
|
}); |
|
|
|
var TreePath = declare("dojox.grid.TreePath", null, { |
|
level: 0, |
|
_str: "", |
|
_arr: null, |
|
grid: null, |
|
store: null, |
|
cell: null, |
|
|
|
constructor: function(/*String|Integer[]|Integer|dojox.grid.TreePath*/ path, /*dojox.grid.TreeGrid*/ grid){ |
|
if(lang.isString(path)){ |
|
this._str = path; |
|
this._arr = array.map(path.split('/'), function(item){ return parseInt(item, 10); }); |
|
}else if(lang.isArray(path)){ |
|
this._str = path.join('/'); |
|
this._arr = path.slice(0); |
|
}else if(typeof path == "number"){ |
|
this._str = String(path); |
|
this._arr = [path]; |
|
}else{ |
|
this._str = path._str; |
|
this._arr = path._arr.slice(0); |
|
} |
|
this.level = this._arr.length-1; |
|
this.grid = grid; |
|
this.store = this.grid.store; |
|
if(grid.treeModel){ |
|
this.cell = grid.layout.cells[grid.expandoCell]; |
|
}else{ |
|
this.cell = grid.layout.cells[this.level]; |
|
} |
|
}, |
|
item: function(){ |
|
// summary: |
|
// gets the dojo.data item associated with this path |
|
if(!this._item){ |
|
this._item = this.grid.getItem(this._arr); |
|
} |
|
return this._item; |
|
}, |
|
compare: function(path /*dojox.grid.TreePath|String|Array*/){ |
|
// summary: |
|
// compares two paths |
|
if(lang.isString(path) || lang.isArray(path)){ |
|
if(this._str == path){ return 0; } |
|
if(path.join && this._str == path.join('/')){ return 0; } |
|
path = new TreePath(path, this.grid); |
|
}else if(path instanceof TreePath){ |
|
if(this._str == path._str){ return 0; } |
|
} |
|
for(var i=0, l=(this._arr.length < path._arr.length ? this._arr.length : path._arr.length); i<l; i++){ |
|
if(this._arr[i]<path._arr[i]){ return -1; } |
|
if(this._arr[i]>path._arr[i]){ return 1; } |
|
} |
|
if(this._arr.length<path._arr.length){ return -1; } |
|
if(this._arr.length>path._arr.length){ return 1; } |
|
return 0; |
|
}, |
|
isOpen: function(){ |
|
// summary: |
|
// Returns the open state of this cell. |
|
return this.cell.openStates && this.cell.getOpenState(this.item()); |
|
}, |
|
previous: function(){ |
|
// summary: |
|
// Returns the path that is before this path in the |
|
// grid. If no path is found, returns null. |
|
var new_path = this._arr.slice(0); |
|
|
|
if(this._str == "0"){ |
|
return null; |
|
} |
|
|
|
var last = new_path.length-1; |
|
|
|
if(new_path[last] === 0){ |
|
new_path.pop(); |
|
return new TreePath(new_path, this.grid); |
|
} |
|
|
|
new_path[last]--; |
|
var path = new TreePath(new_path, this.grid); |
|
return path.lastChild(true); |
|
}, |
|
next: function(){ |
|
// summary: |
|
// Returns the next path in the grid. If no path |
|
// is found, returns null. |
|
var new_path = this._arr.slice(0); |
|
|
|
if(this.isOpen()){ |
|
new_path.push(0); |
|
}else{ |
|
new_path[new_path.length-1]++; |
|
for(var i=this.level; i>=0; i--){ |
|
var item = this.grid.getItem(new_path.slice(0, i+1)); |
|
if(i>0){ |
|
if(!item){ |
|
new_path.pop(); |
|
new_path[i-1]++; |
|
} |
|
}else{ |
|
if(!item){ |
|
return null; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return new TreePath(new_path, this.grid); |
|
}, |
|
children: function(alwaysReturn){ |
|
// summary: |
|
// Returns the child data items of this row. If this |
|
// row isn't open and alwaysReturn is falsey, returns null. |
|
if(!this.isOpen()&&!alwaysReturn){ |
|
return null; |
|
} |
|
var items = []; |
|
var model = this.grid.treeModel; |
|
if(model){ |
|
var item = this.item(); |
|
var store = model.store; |
|
if(!model.mayHaveChildren(item)){ |
|
return null; |
|
} |
|
array.forEach(model.childrenAttrs, function(attr){ |
|
items = items.concat(store.getValues(item, attr)); |
|
}); |
|
}else{ |
|
items = this.store.getValues(this.item(), this.grid.layout.cells[this.cell.level+1].parentCell.field); |
|
if(items.length>1&&this.grid.sortChildItems){ |
|
var sortProps = this.grid.getSortProps(); |
|
if(sortProps&&sortProps.length){ |
|
var attr = sortProps[0].attribute, |
|
grid = this.grid; |
|
if(attr&&items[0][attr]){ |
|
var desc = !!sortProps[0].descending; |
|
items = items.slice(0); // don't touch the array in the store, make a copy |
|
items.sort(function(a, b){ |
|
return grid._childItemSorter(a, b, attr, desc); |
|
}); |
|
} |
|
} |
|
} |
|
} |
|
return items; |
|
}, |
|
childPaths: function(){ |
|
var childItems = this.children(); |
|
if(!childItems){ |
|
return []; |
|
} |
|
return array.map(childItems, function(item, index){ |
|
return new TreePath(this._str + '/' + index, this.grid); |
|
}, this); |
|
}, |
|
parent: function(){ |
|
// summary: |
|
// Returns the parent path of this path. If this is a |
|
// top-level row, returns null. |
|
if(this.level === 0){ |
|
return null; |
|
} |
|
return new TreePath(this._arr.slice(0, this.level), this.grid); |
|
}, |
|
lastChild: function(/*Boolean?*/ traverse){ |
|
// summary: |
|
// Returns the last child row below this path. If traverse |
|
// is true, will traverse down to find the last child row |
|
// of this branch. If there are no children, returns itself. |
|
var children = this.children(); |
|
if(!children || !children.length){ |
|
return this; |
|
} |
|
var path = new TreePath(this._str + "/" + String(children.length-1), this.grid); |
|
if(!traverse){ |
|
return path; |
|
} |
|
return path.lastChild(true); |
|
}, |
|
toString: function(){ |
|
return this._str; |
|
} |
|
}); |
|
|
|
var _TreeFocusManager = declare("dojox.grid._TreeFocusManager", _FocusManager, { |
|
setFocusCell: function(inCell, inRowIndex){ |
|
if(inCell && inCell.getNode(inRowIndex)){ |
|
this.inherited(arguments); |
|
} |
|
}, |
|
isLastFocusCell: function(){ |
|
if(this.cell && this.cell.index == this.grid.layout.cellCount-1){ |
|
var path = new TreePath(this.grid.rowCount-1, this.grid); |
|
path = path.lastChild(true); |
|
return this.rowIndex == path._str; |
|
} |
|
return false; |
|
}, |
|
next: function(){ |
|
// summary: |
|
// focus next grid cell |
|
if(this.cell){ |
|
var row=this.rowIndex, col=this.cell.index+1, cc=this.grid.layout.cellCount-1; |
|
var path = new TreePath(this.rowIndex, this.grid); |
|
if(col > cc){ |
|
var new_path = path.next(); |
|
if(!new_path){ |
|
col--; |
|
}else{ |
|
col = 0; |
|
path = new_path; |
|
} |
|
} |
|
if(this.grid.edit.isEditing()){ //when editing, only navigate to editable cells |
|
var nextCell = this.grid.getCell(col); |
|
if (!this.isLastFocusCell() && !nextCell.editable){ |
|
this._focusifyCellNode(false); |
|
this.cell=nextCell; |
|
this.rowIndex=path._str; |
|
this.next(); |
|
return; |
|
} |
|
} |
|
this.setFocusIndex(path._str, col); |
|
} |
|
}, |
|
previous: function(){ |
|
// summary: |
|
// focus previous grid cell |
|
if(this.cell){ |
|
var row=(this.rowIndex || 0), col=(this.cell.index || 0) - 1; |
|
var path = new TreePath(row, this.grid); |
|
if(col < 0){ |
|
var new_path = path.previous(); |
|
if(!new_path){ |
|
col = 0; |
|
}else{ |
|
col = this.grid.layout.cellCount-1; |
|
path = new_path; |
|
} |
|
} |
|
if(this.grid.edit.isEditing()){ //when editing, only navigate to editable cells |
|
var prevCell = this.grid.getCell(col); |
|
if (!this.isFirstFocusCell() && !prevCell.editable){ |
|
this._focusifyCellNode(false); |
|
this.cell=prevCell; |
|
this.rowIndex=path._str; |
|
this.previous(); |
|
return; |
|
} |
|
} |
|
this.setFocusIndex(path._str, col); |
|
} |
|
}, |
|
move: function(inRowDelta, inColDelta){ |
|
if(this.isNavHeader()){ |
|
this.inherited(arguments); |
|
return; |
|
} |
|
if(!this.cell){ return; } |
|
// Handle grid proper. |
|
var sc = this.grid.scroller, |
|
r = this.rowIndex, |
|
rc = this.grid.rowCount-1, |
|
path = new TreePath(this.rowIndex, this.grid); |
|
if(inRowDelta){ |
|
var row; |
|
if(inRowDelta>0){ |
|
path = path.next(); |
|
row = path._arr[0]; |
|
if(row > sc.getLastPageRow(sc.page)){ |
|
//need to load additional data, let scroller do that |
|
this.grid.setScrollTop(this.grid.scrollTop+sc.findScrollTop(row)-sc.findScrollTop(r)); |
|
} |
|
}else if(inRowDelta<0){ |
|
path = path.previous(); |
|
row = path._arr[0]; |
|
if(row <= sc.getPageRow(sc.page)){ |
|
//need to load additional data, let scroller do that |
|
this.grid.setScrollTop(this.grid.scrollTop-sc.findScrollTop(r)-sc.findScrollTop(row)); |
|
} |
|
} |
|
} |
|
var cc = this.grid.layout.cellCount-1, |
|
i = this.cell.index, |
|
col = Math.min(cc, Math.max(0, i+inColDelta)); |
|
var cell = this.grid.getCell(col); |
|
var colDir = inColDelta < 0 ? -1 : 1; |
|
while(col>=0 && col < cc && cell && cell.hidden === true){ |
|
// skip hidden cells |
|
col += colDir; |
|
cell = this.grid.getCell(col); |
|
} |
|
if (!cell || cell.hidden === true){ |
|
// don't change col if would move to hidden |
|
col = i; |
|
} |
|
if(inRowDelta){ |
|
this.grid.updateRow(r); |
|
} |
|
this.setFocusIndex(path._str, col); |
|
} |
|
}); |
|
|
|
var TreeGrid = declare("dojox.grid.TreeGrid", DataGrid, { |
|
// summary: |
|
// A grid that supports nesting rows - it provides an expando function |
|
// similar to dijit.Tree. It also provides mechanisms for aggregating |
|
// the values of subrows |
|
// description: |
|
// TreeGrid currently only works on "simple" structures. That is, |
|
// single-view structures with a single row in them. |
|
// |
|
// The TreeGrid works using the concept of "levels" - level 0 are the |
|
// top-level items. |
|
|
|
// defaultOpen: Boolean |
|
// Whether or not we default to open (all levels). This defaults to |
|
// false for grids with a treeModel. |
|
defaultOpen: true, |
|
|
|
// sortChildItems: Boolean |
|
// If true, child items will be returned sorted according to the sorting |
|
// properties of the grid. |
|
sortChildItems: false, |
|
|
|
// openAtLevels: Array |
|
// Which levels we are open at (overrides defaultOpen for the values |
|
// that exist here). Its values can be a boolean (true/false) or an |
|
// integer (for the # of children to be closed if there are more than |
|
// that) |
|
openAtLevels: [], |
|
|
|
// treeModel: dijit.tree.ForestStoreModel |
|
// A dijit.Tree model that will be used instead of using aggregates. |
|
// Setting this value will make the TreeGrid behave like a columnar |
|
// tree. When setting this value, defaultOpen will default to false, |
|
// and openAtLevels will be ignored. |
|
treeModel: null, |
|
|
|
// expandoCell: Integer |
|
// When used in conjunction with a treeModel (see above), this is a 0-based |
|
// index of the cell in which to place the actual expando |
|
expandoCell: 0, |
|
|
|
// aggregator: Object |
|
// The aggregator class - it will be populated automatically if we |
|
// are a collapsible grid |
|
aggregator: null, |
|
|
|
|
|
// Override this to get our "magic" layout |
|
_layoutClass: _TreeLayout, |
|
|
|
createSelection: function(){ |
|
this.selection = new TreeSelection(this); |
|
}, |
|
|
|
_childItemSorter: function(a, b, attribute, descending){ |
|
var av = this.store.getValue(a, attribute); |
|
var bv = this.store.getValue(b, attribute); |
|
if(av != bv){ |
|
return av < bv == descending ? 1 : -1; |
|
} |
|
return 0; |
|
}, |
|
|
|
_onNew: function(item, parentInfo){ |
|
if(!parentInfo || !parentInfo.item){ |
|
this.inherited(arguments); |
|
}else{ |
|
var idx = this.getItemIndex(parentInfo.item); |
|
if(typeof idx == "string"){ |
|
this.updateRow(idx.split('/')[0]); |
|
}else if(idx > -1){ |
|
this.updateRow(idx); |
|
} |
|
} |
|
}, |
|
|
|
_onSet: function(item, attribute, oldValue, newValue){ |
|
this._checkUpdateStatus(); |
|
if(this.aggregator){ |
|
this.aggregator.clearSubtotalCache(); |
|
} |
|
var idx = this.getItemIndex(item); |
|
if(typeof idx == "string"){ |
|
this.updateRow(idx.split('/')[0]); |
|
}else if(idx > -1){ |
|
this.updateRow(idx); |
|
} |
|
}, |
|
|
|
_onDelete: function(item){ |
|
this._cleanupExpandoCache(this._getItemIndex(item, true), this.store.getIdentity(item), item); |
|
this.inherited(arguments); |
|
}, |
|
|
|
_clearData: function() { |
|
this.inherited(arguments); |
|
this._by_idty_paths = {}; |
|
}, |
|
|
|
_cleanupExpandoCache: function(index, identity, item){}, |
|
|
|
_addItem: function(item, index, noUpdate, dontUpdateRoot){ |
|
// add our root items to the root of the model's children |
|
// list since we don't query the model |
|
if(!dontUpdateRoot && this.model && array.indexOf(this.model.root.children, item) == -1){ |
|
this.model.root.children[index] = item; |
|
} |
|
this.inherited(arguments); |
|
}, |
|
|
|
getItem: function(/*integer|Array|String*/ idx){ |
|
// summary: |
|
// overridden so that you can pass in a '/' delimited string of indexes to get the |
|
// item based off its path...that is, passing in "1/3/2" will get the |
|
// 3rd (0-based) child from the 4th child of the 2nd top-level item. |
|
var isArray = lang.isArray(idx); |
|
if(lang.isString(idx) && idx.indexOf('/')){ |
|
idx = idx.split('/'); |
|
isArray = true; |
|
} |
|
if(isArray && idx.length == 1){ |
|
idx = idx[0]; |
|
isArray = false; |
|
} |
|
if(!isArray){ |
|
return DataGrid.prototype.getItem.call(this, idx); |
|
} |
|
var s = this.store; |
|
var itm = DataGrid.prototype.getItem.call(this, idx[0]); |
|
var cf, i, j; |
|
if(this.aggregator){ |
|
cf = this.aggregator.childFields||[]; |
|
if(cf){ |
|
for(i = 0; i < idx.length - 1 && itm; i++){ |
|
if(cf[i]){ |
|
itm = (s.getValues(itm, cf[i])||[])[idx[i + 1]]; |
|
}else{ |
|
itm = null; |
|
} |
|
} |
|
} |
|
}else if(this.treeModel){ |
|
cf = this.treeModel.childrenAttrs||[]; |
|
if(cf&&itm){ |
|
for(i=1, il=idx.length; (i<il) && itm; i++) { |
|
for(j=0, jl=cf.length; j<jl; j++) { |
|
if(cf[j]){ |
|
itm = (s.getValues(itm, cf[j])||[])[idx[i]]; |
|
}else{ |
|
itm = null; |
|
} |
|
if(itm){ break; } |
|
} |
|
} |
|
} |
|
} |
|
return itm || null; |
|
}, |
|
|
|
_getItemIndex: function(item, isDeleted){ |
|
if(!isDeleted && !this.store.isItem(item)){ |
|
return -1; |
|
} |
|
var idx = this.inherited(arguments); |
|
if(idx == -1){ |
|
var idty = this.store.getIdentity(item); |
|
return this._by_idty_paths[idty] || -1; |
|
} |
|
return idx; |
|
}, |
|
|
|
postMixInProperties: function(){ |
|
if(this.treeModel && !("defaultOpen" in this.params)){ |
|
// Default open to false for tree models, true for other tree |
|
// grids. |
|
this.defaultOpen = false; |
|
} |
|
var def = this.defaultOpen; |
|
this.openAtLevels = array.map(this.openAtLevels, function(l){ |
|
if(typeof l == "string"){ |
|
switch(l.toLowerCase()){ |
|
case "true": |
|
return true; |
|
break; |
|
case "false": |
|
return false; |
|
break; |
|
default: |
|
var r = parseInt(l, 10); |
|
if(isNaN(r)){ |
|
return def; |
|
} |
|
return r; |
|
break; |
|
} |
|
} |
|
return l; |
|
}); |
|
this._by_idty_paths = {}; |
|
this.inherited(arguments); |
|
}, |
|
|
|
postCreate: function(){ |
|
this.inherited(arguments); |
|
if(this.treeModel){ |
|
this._setModel(this.treeModel); |
|
} |
|
}, |
|
|
|
setModel: function(treeModel){ |
|
this._setModel(treeModel); |
|
this._refresh(true); |
|
}, |
|
|
|
_setModel: function(treeModel){ |
|
if(treeModel && (!ForestStoreModel || !(treeModel instanceof ForestStoreModel))){ |
|
throw new Error("dojox.grid.TreeGrid: treeModel must be an instance of dijit.tree.ForestStoreModel"); |
|
} |
|
this.treeModel = treeModel; |
|
domClass.toggle(this.domNode, "dojoxGridTreeModel", this.treeModel ? true : false); |
|
this._setQuery(treeModel ? treeModel.query : null); |
|
this._setStore(treeModel ? treeModel.store : null); |
|
}, |
|
|
|
createScroller: function(){ |
|
this.inherited(arguments); |
|
this.scroller._origDefaultRowHeight = this.scroller.defaultRowHeight; |
|
}, |
|
|
|
createManagers: function(){ |
|
// summary: |
|
// create grid managers for various tasks including rows, focus, selection, editing |
|
|
|
// row manager |
|
this.rows = new _RowManager(this); |
|
// focus manager |
|
this.focus = new _TreeFocusManager(this); |
|
// edit manager |
|
this.edit = new _EditManager(this); |
|
}, |
|
|
|
_setStore: function(store){ |
|
this.inherited(arguments); |
|
if(this.treeModel&&!this.treeModel.root.children){ |
|
this.treeModel.root.children = []; |
|
} |
|
if(this.aggregator){ |
|
this.aggregator.store = store; |
|
} |
|
}, |
|
|
|
getDefaultOpenState: function(cellDef, item){ |
|
// summary: |
|
// Returns the default open state for the given definition and item |
|
// It reads from the openAtLevels and defaultOpen values of the |
|
// grid to calculate if the given item should default to open or |
|
// not. |
|
var cf; |
|
var store = this.store; |
|
if(this.treeModel){ return this.defaultOpen; } |
|
if(!cellDef || !store || !store.isItem(item) || |
|
!(cf = this.aggregator.childFields[cellDef.level])){ |
|
return this.defaultOpen; |
|
} |
|
if(this.openAtLevels.length > cellDef.level){ |
|
var dVal = this.openAtLevels[cellDef.level]; |
|
if(typeof dVal == "boolean"){ |
|
return dVal; |
|
}else if(typeof dVal == "number"){ |
|
return (store.getValues(item, cf).length <= dVal); |
|
} |
|
} |
|
return this.defaultOpen; |
|
}, |
|
onStyleRow: function(row){ |
|
if(!this.layout._isCollapsable){ |
|
this.inherited(arguments); |
|
return; |
|
} |
|
var base = domAttr.get(row.node, 'dojoxTreeGridBaseClasses'); |
|
if(base){ |
|
row.customClasses = base; |
|
} |
|
var i = row; |
|
var tagName = i.node.tagName.toLowerCase(); |
|
i.customClasses += (i.odd?" dojoxGridRowOdd":"") + |
|
(i.selected&&tagName=='tr'?" dojoxGridRowSelected":"") + |
|
(i.over&&tagName=='tr'?" dojoxGridRowOver":""); |
|
this.focus.styleRow(i); |
|
this.edit.styleRow(i); |
|
}, |
|
styleRowNode: function(inRowIndex, inRowNode){ |
|
if(inRowNode){ |
|
if(inRowNode.tagName.toLowerCase() == 'div' && this.aggregator){ |
|
query("tr[dojoxTreeGridPath]", inRowNode).forEach(function(rowNode){ |
|
this.rows.styleRowNode(domAttr.get(rowNode, 'dojoxTreeGridPath'), rowNode); |
|
},this); |
|
} |
|
this.rows.styleRowNode(inRowIndex, inRowNode); |
|
} |
|
}, |
|
onCanSelect: function(inRowIndex){ |
|
var nodes = query("tr[dojoxTreeGridPath='" + inRowIndex + "']", this.domNode); |
|
if(nodes.length){ |
|
if(domClass.contains(nodes[0], 'dojoxGridSummaryRow')){ |
|
return false; |
|
} |
|
} |
|
return this.inherited(arguments); |
|
}, |
|
onKeyDown: function(e){ |
|
if(e.altKey || e.metaKey){ |
|
return; |
|
} |
|
switch(e.keyCode){ |
|
case keys.UP_ARROW: |
|
if(!this.edit.isEditing() && this.focus.rowIndex != "0"){ |
|
event.stop(e); |
|
this.focus.move(-1, 0); |
|
} |
|
break; |
|
case keys.DOWN_ARROW: |
|
var currPath = new TreePath(this.focus.rowIndex, this); |
|
var lastPath = new TreePath(this.rowCount-1, this); |
|
lastPath = lastPath.lastChild(true); |
|
if(!this.edit.isEditing() && currPath.toString() != lastPath.toString()){ |
|
event.stop(e); |
|
this.focus.move(1, 0); |
|
} |
|
break; |
|
default: |
|
this.inherited(arguments); |
|
break; |
|
} |
|
}, |
|
canEdit: function(inCell, inRowIndex){ |
|
var node = inCell.getNode(inRowIndex); |
|
return node && this._canEdit; |
|
}, |
|
doApplyCellEdit: function(inValue, inRowIndex, inAttrName){ |
|
var item = this.getItem(inRowIndex); |
|
var oldValue = this.store.getValue(item, inAttrName); |
|
if(typeof oldValue == 'number'){ |
|
inValue = isNaN(inValue) ? inValue : parseFloat(inValue); |
|
}else if(typeof oldValue == 'boolean'){ |
|
inValue = inValue == 'true' ? true : inValue == 'false' ? false : inValue; |
|
}else if(oldValue instanceof Date){ |
|
var asDate = new Date(inValue); |
|
inValue = isNaN(asDate.getTime()) ? inValue : asDate; |
|
} |
|
this.store.setValue(item, inAttrName, inValue); |
|
this.onApplyCellEdit(inValue, inRowIndex, inAttrName); |
|
} |
|
}); |
|
TreeGrid.markupFactory = function(props, node, ctor, cellFunc){ |
|
var widthFromAttr = function(n){ |
|
var w = domAttr.get(n, "width")||"auto"; |
|
if((w != "auto")&&(w.slice(-2) != "em")&&(w.slice(-1) != "%")){ |
|
w = parseInt(w, 10)+"px"; |
|
} |
|
return w; |
|
}; |
|
|
|
var cellsFromMarkup = function(table){ |
|
var rows; |
|
// Don't support colgroup on our grid - single view, single row only |
|
if(table.nodeName.toLowerCase() == "table" && |
|
query("> colgroup", table).length === 0 && |
|
(rows = query("> thead > tr", table)).length == 1){ |
|
var tr = rows[0]; |
|
return query("> th", rows[0]).map(function(th){ |
|
// Grab type and field (the only ones that are shared |
|
var cell = { |
|
type: lang.trim(domAttr.get(th, "cellType")||""), |
|
field: lang.trim(domAttr.get(th, "field")||"") |
|
}; |
|
if(cell.type){ |
|
cell.type = lang.getObject(cell.type); |
|
} |
|
|
|
var subTable = query("> table", th)[0]; |
|
if(subTable){ |
|
// If we have a subtable, we are an aggregate and a summary cell |
|
cell.name = ""; |
|
cell.children = cellsFromMarkup(subTable); |
|
if(domAttr.has(th, "itemAggregates")){ |
|
cell.itemAggregates = array.map(domAttr.get(th, "itemAggregates").split(","), function(v){ |
|
return lang.trim(v); |
|
}); |
|
}else{ |
|
cell.itemAggregates = []; |
|
} |
|
if(domAttr.has(th, "aggregate")){ |
|
cell.aggregate = domAttr.get(th, "aggregate"); |
|
} |
|
cell.type = cell.type || dojox.grid.cells.SubtableCell; |
|
}else{ |
|
// Grab our other stuff we need (mostly what's in the normal |
|
// Grid) |
|
cell.name = lang.trim(domAttr.get(th, "name")||th.innerHTML); |
|
if(domAttr.has(th, "width")){ |
|
cell.width = widthFromAttr(th); |
|
} |
|
if(domAttr.has(th, "relWidth")){ |
|
cell.relWidth = window.parseInt(domAttr.get(th, "relWidth"), 10); |
|
} |
|
if(domAttr.has(th, "hidden")){ |
|
cell.hidden = domAttr.get(th, "hidden") == "true"; |
|
} |
|
cell.field = cell.field||cell.name; |
|
DataGrid.cell_markupFactory(cellFunc, th, cell); |
|
cell.type = cell.type || dojox.grid.cells.Cell; |
|
} |
|
if(cell.type && cell.type.markupFactory){ |
|
cell.type.markupFactory(th, cell); |
|
} |
|
return cell; |
|
}); |
|
} |
|
return []; |
|
}; |
|
|
|
var rows; |
|
if( !props.structure ){ |
|
var row = cellsFromMarkup(node); |
|
if(row.length){ |
|
// Set our structure here - so that we don't try and set it in the |
|
// markup factory |
|
props.structure = [{__span: Infinity, cells:[row]}]; |
|
} |
|
} |
|
return DataGrid.markupFactory(props, node, ctor, cellFunc); |
|
}; |
|
|
|
return TreeGrid; |
|
|
|
}); |