1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279 | 3
3
22
22
3
63
3
168
15
168
29
168
168
168
168
41
8
33
12
127
127
127
168
131
168
41
168
160
63
63
22
22
22
65
65
287
287
123
22
22
22
22
22
22
22
127
4
4
1
1
1
137
137
137
73
73
73
137
145
145
145
141
145
362
362
160
160
768
149
213
| define([
'dojo/_base/lang',
'dojo/_base/declare',
'dojo/sniff',
'dojo/query',
'../util/misc'
], function (lang, declare, has, query, miscUtil) {
return declare(null, {
// summary:
// Extension allowing for specification of columns with additional
// header rows spanning multiple columns for strictly display purposes.
// Only works on `columns` arrays, not `columns` objects or `subRows`
// (nor ColumnSets).
// description:
// CompoundColumns allows nested header cell configurations, wherein the
// higher-level headers may span multiple columns and are for
// display purposes only.
// These nested header cells are configured using a special recursive
// `children` property in the column definition, where only the deepest
// children are ultimately rendered in the grid as actual columns.
// In addition, the deepest child columns may be rendered without
// individual headers by specifying `showChildHeaders: false` on the parent.
configStructure: function () {
// create a set of sub rows for the header row so we can do compound columns
// the first row is a special spacer row
var columns = (this.subRows && this.subRows[0]) || this.columns,
headerRows = [[]],
topHeaderRow = headerRows[0],
contentColumns = [];
// This first row is spacer row that will be made invisible (zero height)
// with CSS, but it must be rendered as the first row since that is what
// the table layout is driven by.
headerRows[0].className = 'dgrid-spacer-row';
function processColumns(columns, level, hasLabel, parent) {
var numColumns = 0,
noop = function () {},
children,
hasChildLabels;
function processColumn(column, i) {
// Handle the column config when it is an object rather
// than an array.
if (typeof column === 'string') {
column = {label: column};
}
if (!(columns instanceof Array) && !column.field) {
column.field = i;
}
children = column.children;
hasChildLabels = children && (column.showChildHeaders !== false);
// Set a reference to the parent column so later the children's ids can
// be updated to indicate the parent-child relationship.
column.parentColumn = parent;
if (children) {
// it has children
// make sure the column has an id
if (column.id == null) {
column.id = ((parent && parent.id) || level - 1) + '-' + topHeaderRow.length;
}
else if (parent && parent.id) {
// Make sure nested compound columns have ids that are prefixed with
// their parent's ids.
column.id = parent.id + '-' + column.id;
}
}
else {
// it has no children, it is a normal header, add it to the content columns
contentColumns.push(column);
// add each one to the first spacer header row for proper layout of the header cells
topHeaderRow.push(lang.delegate(column, {renderHeaderCell: noop}));
numColumns++;
}
if (!hasChildLabels) {
// create a header version of the column where we can define a specific rowSpan
// we define the rowSpan as a negative, the number of levels less than the
// total number of rows, which we don't know yet
column = lang.delegate(column, {rowSpan: -level});
}
if (children) {
// Recursively process the children; this is specifically
// performed *after* any potential lang.delegate calls
// so the parent reference will receive additional info
numColumns += (column.colSpan =
processColumns(children, level + 1, hasChildLabels, column));
}
// add the column to the header rows at the appropriate level
if (hasLabel) {
(headerRows[level] || (headerRows[level] = [])).push(column);
}
}
miscUtil.each(columns, processColumn, this);
return numColumns;
}
processColumns(columns, 1, true);
var numHeaderRows = headerRows.length,
i, j, headerRow, headerColumn;
// Now go back through and increase the rowSpans of the headers to be
// total rows minus the number of levels they are at.
for (i = 0; i < numHeaderRows; i++) {
headerRow = headerRows[i];
for (j = 0; j < headerRow.length; j++) {
headerColumn = headerRow[j];
if (headerColumn.rowSpan < 1) {
headerColumn.rowSpan += numHeaderRows;
}
}
}
// we need to set this to be used for subRows, so we make it a single row
contentColumns = [contentColumns];
// set our header rows so that the grid will use the alternate header row
// configuration for rendering the headers
contentColumns.headerRows = headerRows;
this.subRows = contentColumns;
this.inherited(arguments);
},
renderHeader: function () {
var i,
columns = this.subRows[0],
headerColumns = this.subRows.headerRows[0];
this.inherited(arguments);
// The object delegation performed in configStructure unfortunately
// "protects" the original column definition objects (referenced by
// columns and subRows) from obtaining headerNode information, so
// copy them back in.
for (i = columns.length; i--;) {
columns[i].headerNode = headerColumns[i].headerNode;
}
},
_findSortArrowParent: function () {
var parent = this.inherited(arguments),
spacerRow = query('.dgrid-spacer-row', this.headerNode)[0],
columnId,
nodes;
if (parent && spacerRow.contains(parent)) {
columnId = parent.columnId;
nodes = query('.dgrid-column-' + columnId, this.headerNode);
return nodes[nodes.length - 1];
}
},
_configColumn: function (column, rowColumns, prefix) {
// Updates the id on a column definition that is a child to include
// the parent's id.
var parent = column.parentColumn;
var columnId = column.id;
if (parent) {
// Adjust the id to incorporate the parent's id.
// Remove the prefix if it was used to create the id
var id = columnId.indexOf(prefix) === 0 ? columnId.substring(prefix.length) : columnId;
prefix = parent.id + '-';
columnId = column.id = prefix + id;
}
this.inherited(arguments, [column, rowColumns, prefix]);
},
cell: function (target, columnId) {
// summary:
// Get the cell object by node, event, or id, plus a columnId.
// This extension prefixes children's column ids with the parents' column ids,
// so cell takes that into account when looking for a column id.
Eif (typeof columnId !== 'object') {
// Find the columnId that corresponds with the provided id.
// The provided id may be a suffix of the actual id.
var column = this.column(columnId);
if (column) {
columnId = column.id;
}
}
return this.inherited(arguments, [target, columnId]);
},
column: function (target) {
// summary:
// Get the column object by node, event, or column id. Take into account parent column id
// prefixes that may be added by this extension.
var results = this.inherited(arguments);
if (results == null && typeof target !== 'object') {
// Find a column id that ends with the provided column id. This will locate a child column
// by an id that was provided in the original column configuration. For example, if a compound column
// was given the id "compound" and a child column was given the id "child", this will find the column
// using only "child". If "compound-child" was being searched for, the inherited call
// above would have found the cell.
var suffix = '-' + target,
suffixLength = suffix.length;
for (var completeId in this.columns) {
if (completeId.indexOf(suffix, completeId.length - suffixLength) !== -1) {
return this.columns[completeId];
}
}
}
return results;
},
_updateCompoundHiddenStates: function (id, hidden) {
// summary:
// Called from _hideColumn and _showColumn (for ColumnHider)
// to adjust parent header cells
var column = this.columns[id],
colSpan;
if (column && column.hidden === hidden) {
// Avoid redundant processing (since it would cause colSpan skew)
return;
}
// column will be undefined when this is called for parents
while (column && column.parentColumn) {
// Update colSpans / hidden state of parents
column = column.parentColumn;
colSpan = column.colSpan = column.colSpan + (hidden ? -1 : 1);
if (colSpan) {
column.headerNode.colSpan = colSpan;
}
if (colSpan === 1 && !hidden) {
this._showColumn(column.id);
}
else if (!colSpan && hidden) {
this._hideColumn(column.id);
}
}
},
_hideColumn: function (id) {
var self = this;
this._updateCompoundHiddenStates(id, true);
this.inherited(arguments);
if (has('ff')) {
// Firefox causes display quirks in certain situations;
// avoid them by forcing reflow of the header
this.headerNode.style.display = 'none';
setTimeout(function () {
self.headerNode.style.display = '';
self.resize();
}, 0);
}
},
_showColumn: function (id) {
this._updateCompoundHiddenStates(id, false);
this.inherited(arguments);
},
_getResizedColumnWidths: function () {
// Overrides ColumnResizer method to report the total width and
// last column correctly for CompoundColumns structures
var total = 0,
columns = this.columns,
id;
for (id in columns) {
total += columns[id].headerNode.offsetWidth;
}
return {
totalWidth: total,
lastColId: this.subRows[0][this.subRows[0].length - 1].id
};
}
});
});
|