1
dojo.provide("dojox.grid._Builder");
3
dojo.require("dojox.grid.util");
4
dojo.require("dojo.dnd.Moveable");
9
var getTdIndex = function(td){
10
return td.cellIndex >=0 ? td.cellIndex : dojo.indexOf(td.parentNode.cells, td);
13
var getTrIndex = function(tr){
14
return tr.rowIndex >=0 ? tr.rowIndex : dojo.indexOf(tr.parentNode.childNodes, tr);
17
var getTr = function(rowOwner, index){
18
return rowOwner && ((rowOwner.rows||0)[index] || rowOwner.childNodes[index]);
21
var findTable = function(node){
22
for(var n=node; n && n.tagName!='TABLE'; n=n.parentNode);
26
var ascendDom = function(inNode, inWhile){
27
for(var n=inNode; n && inWhile(n); n=n.parentNode);
31
var makeNotTagName = function(inTagName){
32
var name = inTagName.toUpperCase();
33
return function(node){ return node.tagName != name; };
36
var rowIndexTag = dojox.grid.util.rowIndexTag;
37
var gridViewTag = dojox.grid.util.gridViewTag;
39
// base class for generating markup for the views
40
dg._Builder = dojo.extend(function(view){
43
this.grid = view.grid;
48
_table: '<table class="dojoxGridRowTable" border="0" cellspacing="0" cellpadding="0" role="'+(dojo.isFF<3 ? "wairole:" : "")+'presentation"',
50
// Returns the table variable as an array - and with the view width, if specified
51
getTableArray: function(){
52
var html = [this._table];
53
if(this.view.viewWidth){
54
html.push([' style="width:', this.view.viewWidth, ';"'].join(''));
60
// generate starting tags for a cell
61
generateCellMarkup: function(inCell, inMoreStyles, inMoreClasses, isHeader){
62
var result = [], html;
63
var waiPrefix = dojo.isFF<3 ? "wairole:" : "";
65
var sortInfo = inCell.index != inCell.grid.getSortIndex() ? "" : inCell.grid.sortInfo > 0 ? 'aria-sort="ascending"' : 'aria-sort="descending"';
66
html = ['<th tabIndex="-1" role="', waiPrefix, 'columnheader"', sortInfo];
68
html = ['<td tabIndex="-1" role="', waiPrefix, 'gridcell"'];
70
inCell.colSpan && html.push(' colspan="', inCell.colSpan, '"');
71
inCell.rowSpan && html.push(' rowspan="', inCell.rowSpan, '"');
72
html.push(' class="dojoxGridCell ');
73
inCell.classes && html.push(inCell.classes, ' ');
74
inMoreClasses && html.push(inMoreClasses, ' ');
75
// result[0] => td opener, style
76
result.push(html.join(''));
77
// SLOT: result[1] => td classes
79
html = ['" idx="', inCell.index, '" style="'];
80
if(inMoreStyles && inMoreStyles[inMoreStyles.length-1] != ';'){
83
html.push(inCell.styles, inMoreStyles||'', inCell.hidden?'display:none;':'');
84
inCell.unitWidth && html.push('width:', inCell.unitWidth, ';');
85
// result[2] => markup
86
result.push(html.join(''));
87
// SLOT: result[3] => td style
90
inCell.attrs && html.push(" ", inCell.attrs);
92
// result[4] => td postfix
93
result.push(html.join(''));
94
// SLOT: result[5] => content
96
// result[6] => td closes
97
result.push(isHeader?'</th>':'</td>');
98
return result; // Array
102
isCellNode: function(inNode){
103
return Boolean(inNode && inNode!=dojo.doc && dojo.attr(inNode, "idx"));
106
getCellNodeIndex: function(inCellNode){
107
return inCellNode ? Number(dojo.attr(inCellNode, "idx")) : -1;
110
getCellNode: function(inRowNode, inCellIndex){
111
for(var i=0, row; row=getTr(inRowNode.firstChild, i); i++){
112
for(var j=0, cell; cell=row.cells[j]; j++){
113
if(this.getCellNodeIndex(cell) == inCellIndex){
120
findCellTarget: function(inSourceNode, inTopNode){
121
var n = inSourceNode;
122
while(n && (!this.isCellNode(n) || (n.offsetParent && gridViewTag in n.offsetParent.parentNode && n.offsetParent.parentNode[gridViewTag] != this.view.id)) && (n!=inTopNode)){
125
return n!=inTopNode ? n : null
129
baseDecorateEvent: function(e){
130
e.dispatch = 'do' + e.type;
132
e.sourceView = this.view;
133
e.cellNode = this.findCellTarget(e.target, e.rowNode);
134
e.cellIndex = this.getCellNodeIndex(e.cellNode);
135
e.cell = (e.cellIndex >= 0 ? this.grid.getCell(e.cellIndex) : null);
139
findTarget: function(inSource, inTag){
141
while(n && (n!=this.domNode) && (!(inTag in n) || (gridViewTag in n && n[gridViewTag] != this.view.id))){
144
return (n != this.domNode) ? n : null;
147
findRowTarget: function(inSource){
148
return this.findTarget(inSource, rowIndexTag);
151
isIntraNodeEvent: function(e){
153
return (e.cellNode && e.relatedTarget && dojo.isDescendant(e.relatedTarget, e.cellNode));
155
// e.relatedTarget has permission problem in FF if it's an input: https://bugzilla.mozilla.org/show_bug.cgi?id=208427
160
isIntraRowEvent: function(e){
162
var row = e.relatedTarget && this.findRowTarget(e.relatedTarget);
163
return !row && (e.rowIndex==-1) || row && (e.rowIndex==row.gridRowIndex);
165
// e.relatedTarget on INPUT has permission problem in FF: https://bugzilla.mozilla.org/show_bug.cgi?id=208427
170
dispatchEvent: function(e){
171
if(e.dispatch in this){
172
return this[e.dispatch](e);
176
// dispatched event handlers
177
domouseover: function(e){
178
if(e.cellNode && (e.cellNode!=this.lastOverCellNode)){
179
this.lastOverCellNode = e.cellNode;
180
this.grid.onMouseOver(e);
182
this.grid.onMouseOverRow(e);
185
domouseout: function(e){
186
if(e.cellNode && (e.cellNode==this.lastOverCellNode) && !this.isIntraNodeEvent(e, this.lastOverCellNode)){
187
this.lastOverCellNode = null;
188
this.grid.onMouseOut(e);
189
if(!this.isIntraRowEvent(e)){
190
this.grid.onMouseOutRow(e);
195
domousedown: function(e){
197
this.grid.onMouseDown(e);
198
this.grid.onMouseDownRow(e)
202
// Produces html for grid data content. Owned by grid and used internally
203
// for rendering data. Override to implement custom rendering.
204
dg._ContentBuilder = dojo.extend(function(view){
205
dg._Builder.call(this, view);
206
},dg._Builder.prototype,{
211
// cache html for rendering data rows
212
prepareHtml: function(){
213
var defaultGet=this.grid.get, cells=this.view.structure.cells;
214
for(var j=0, row; (row=cells[j]); j++){
215
for(var i=0, cell; (cell=row[i]); i++){
216
cell.get = cell.get || (cell.value == undefined) && defaultGet;
217
cell.markup = this.generateCellMarkup(cell, cell.cellStyles, cell.cellClasses, false);
222
// time critical: generate html using cache and data source
223
generateHtml: function(inDataIndex, inRowIndex){
225
html = this.getTableArray(),
227
cells = v.structure.cells,
228
item = this.grid.getItem(inRowIndex);
230
dojox.grid.util.fire(this.view, "onBeforeRow", [inRowIndex, cells]);
231
for(var j=0, row; (row=cells[j]); j++){
232
if(row.hidden || row.header){
235
html.push(!row.invisible ? '<tr>' : '<tr class="dojoxGridInvisible">');
236
for(var i=0, cell, m, cc, cs; (cell=row[i]); i++){
237
m = cell.markup, cc = cell.customClasses = [], cs = cell.customStyles = [];
238
// content (format can fill in cc and cs as side-effects)
239
m[5] = cell.format(inRowIndex, item);
245
html.push.apply(html, m);
249
html.push('</table>');
250
return html.join(''); // String
253
decorateEvent: function(e){
254
e.rowNode = this.findRowTarget(e.target);
255
if(!e.rowNode){return false};
256
e.rowIndex = e.rowNode[rowIndexTag];
257
this.baseDecorateEvent(e);
258
e.cell = this.grid.getCell(e.cellIndex);
259
return true; // Boolean
263
// Produces html for grid header content. Owned by grid and used internally
264
// for rendering data. Override to implement custom rendering.
265
dg._HeaderBuilder = dojo.extend(function(view){
266
this.moveable = null;
267
dg._Builder.call(this, view);
268
},dg._Builder.prototype,{
269
_skipBogusClicks: false,
275
this.tableMap.mapRows(this.view.structure.cells);
277
this.tableMap = new dg._TableMap(this.view.structure.cells);
281
generateHtml: function(inGetValue, inValue){
282
var html = this.getTableArray(), cells = this.view.structure.cells;
284
dojox.grid.util.fire(this.view, "onBeforeRow", [-1, cells]);
285
for(var j=0, row; (row=cells[j]); j++){
289
html.push(!row.invisible ? '<tr>' : '<tr class="dojoxGridInvisible">');
290
for(var i=0, cell, markup; (cell=row[i]); i++){
291
cell.customClasses = [];
292
cell.customStyles = [];
293
if(this.view.simpleStructure){
294
if(cell.headerClasses){
295
if(cell.headerClasses.indexOf('dojoDndItem') == -1){
296
cell.headerClasses += ' dojoDndItem';
299
cell.headerClasses = 'dojoDndItem';
302
if(cell.attrs.indexOf("dndType='gridColumn_") == -1){
303
cell.attrs += " dndType='gridColumn_" + this.grid.id + "'";
306
cell.attrs = "dndType='gridColumn_" + this.grid.id + "'";
309
markup = this.generateCellMarkup(cell, cell.headerStyles, cell.headerClasses, true);
311
markup[5] = (inValue != undefined ? inValue : inGetValue(cell));
313
markup[3] = cell.customStyles.join(';');
315
markup[1] = cell.customClasses.join(' '); //(cell.customClasses ? ' ' + cell.customClasses : '');
316
html.push(markup.join(''));
320
html.push('</table>');
321
return html.join('');
325
getCellX: function(e){
328
var n = ascendDom(e.target, makeNotTagName("th"));
329
x -= (n && n.offsetLeft) || 0;
330
var t = e.sourceView.getScrollbarWidth();
331
if(!dojo._isBodyLtr() && e.sourceView.headerNode.scrollLeft < t)
333
//x -= getProp(ascendDom(e.target, mkNotTagName("td")), "offsetLeft") || 0;
335
var n = ascendDom(e.target, function(){
336
if(!n || n == e.cellNode){
339
// Mozilla 1.8 (FF 1.5) has a bug that makes offsetLeft = -parent border width
340
// when parent has border, overflow: hidden, and is positioned
341
// handle this problem here ... not a general solution!
342
x += (n.offsetLeft < 0 ? 0 : n.offsetLeft);
349
decorateEvent: function(e){
350
this.baseDecorateEvent(e);
352
e.cellX = this.getCellX(e);
358
prepareResize: function(e, mod){
360
var i = getTdIndex(e.cellNode);
361
e.cellNode = (i ? e.cellNode.parentNode.cells[i+mod] : null);
362
e.cellIndex = (e.cellNode ? this.getCellNodeIndex(e.cellNode) : -1);
363
}while(e.cellNode && e.cellNode.style.display == "none");
364
return Boolean(e.cellNode);
367
canResize: function(e){
368
if(!e.cellNode || e.cellNode.colSpan > 1){
371
var cell = this.grid.getCell(e.cellIndex);
372
return !cell.noresize && cell.canResize();
375
overLeftResizeArea: function(e){
376
//Bugfix for crazy IE problem (#8807). IE returns position information for the icon and text arrow divs
377
//as if they were still on the left instead of returning the position they were 'float: right' to.
378
//So, the resize check ends up checking the wrong adjacent cell. This checks to see if the hover was over
379
//the image or text nodes, then just ignored them/treat them not in scale range.
382
if(dojo.hasClass(tN, "dojoxGridArrowButtonNode") ||
383
dojo.hasClass(tN, "dojoxGridArrowButtonChar")){
388
if(dojo._isBodyLtr()){
389
return (e.cellIndex>0) && (e.cellX < this.overResizeWidth) && this.prepareResize(e, -1);
391
var t = e.cellNode && (e.cellX < this.overResizeWidth);
395
overRightResizeArea: function(e){
396
//Bugfix for crazy IE problem (#8807). IE returns position information for the icon and text arrow divs
397
//as if they were still on the left instead of returning the position they were 'float: right' to.
398
//So, the resize check ends up checking the wrong adjacent cell. This checks to see if the hover was over
399
//the image or text nodes, then just ignored them/treat them not in scale range.
402
if(dojo.hasClass(tN, "dojoxGridArrowButtonNode") ||
403
dojo.hasClass(tN, "dojoxGridArrowButtonChar")){
408
if(dojo._isBodyLtr()){
409
return e.cellNode && (e.cellX >= e.cellNode.offsetWidth - this.overResizeWidth);
411
return (e.cellIndex>0) && (e.cellX >= e.cellNode.offsetWidth - this.overResizeWidth) && this.prepareResize(e, -1);
414
domousemove: function(e){
415
//console.log(e.cellIndex, e.cellX, e.cellNode.offsetWidth);
417
var c = (this.overRightResizeArea(e) ? 'e-resize' : (this.overLeftResizeArea(e) ? 'w-resize' : ''));
418
if(c && !this.canResize(e)){
422
var t = e.sourceView.headerNode.scrollLeft;
423
e.sourceView.headerNode.style.cursor = c || ''; //'default';
424
e.sourceView.headerNode.scrollLeft = t;
426
e.sourceView.headerNode.style.cursor = c || ''; //'default';
434
domousedown: function(e){
436
if((this.overRightResizeArea(e) || this.overLeftResizeArea(e)) && this.canResize(e)){
437
this.beginColumnResize(e);
439
this.grid.onMouseDown(e);
440
this.grid.onMouseOverRow(e);
443
// this.beginMoveColumn(e);
448
doclick: function(e) {
449
if(this._skipBogusClicks){
456
beginColumnResize: function(e){
457
this.moverDiv = document.createElement("div");
458
dojo.style(this.moverDiv,{position: "absolute", left:0}); // to make DnD work with dir=rtl
459
dojo.body().appendChild(this.moverDiv);
460
var m = this.moveable = new dojo.dnd.Moveable(this.moverDiv);
462
var spanners = [], nodes = this.tableMap.findOverlappingNodes(e.cellNode);
463
for(var i=0, cell; (cell=nodes[i]); i++){
464
spanners.push({ node: cell, index: this.getCellNodeIndex(cell), width: cell.offsetWidth });
465
//console.log("spanner: " + this.getCellNodeIndex(cell));
468
var view = e.sourceView;
469
var adj = dojo._isBodyLtr() ? 1 : -1;
470
var views = e.grid.views.views;
472
for(var i=view.idx+adj, cView; (cView=views[i]); i=i+adj){
473
followers.push({ node: cView.headerNode, left: window.parseInt(cView.headerNode.style.left) });
475
var table = view.headerContentNode.firstChild;
477
scrollLeft: e.sourceView.headerNode.scrollLeft,
481
w: dojo.contentBox(e.cellNode).w,
482
vw: dojo.contentBox(view.headerNode).w,
484
tw: dojo.contentBox(table).w,
489
m.onMove = dojo.hitch(this, "doResizeColumn", drag);
491
dojo.connect(m, "onMoveStop", dojo.hitch(this, function(){
492
this.endResizeColumn(drag);
493
if(drag.node.releaseCapture){
494
drag.node.releaseCapture();
496
this.moveable.destroy();
497
delete this.moveable;
498
this.moveable = null;
501
view.convertColPctToFixed();
503
if(e.cellNode.setCapture){
504
e.cellNode.setCapture();
509
doResizeColumn: function(inDrag, mover, leftTop){
510
var isLtr = dojo._isBodyLtr();
511
var deltaX = isLtr ? leftTop.l : -leftTop.l;
512
var w = inDrag.w + deltaX;
513
var vw = inDrag.vw + deltaX;
514
var tw = inDrag.tw + deltaX;
515
if(w >= this.minColWidth){
516
for(var i=0, s, sw; (s=inDrag.spanners[i]); i++){
517
sw = s.width + deltaX;
518
s.node.style.width = sw + 'px';
519
inDrag.view.setColWidth(s.index, sw);
520
//console.log('setColWidth', '#' + s.index, sw + 'px');
522
for(var i=0, f, fl; (f=inDrag.followers[i]); i++){
523
fl = f.left + deltaX;
524
f.node.style.left = fl + 'px';
526
inDrag.node.style.width = w + 'px';
527
inDrag.view.setColWidth(inDrag.index, w);
528
inDrag.view.headerNode.style.width = vw + 'px';
529
inDrag.view.setColumnsWidth(tw);
531
inDrag.view.headerNode.scrollLeft = inDrag.scrollLeft + deltaX;
534
if(inDrag.view.flexCells && !inDrag.view.testFlexCells()){
535
var t = findTable(inDrag.node);
536
t && (t.style.width = '');
540
endResizeColumn: function(inDrag){
541
dojo.destroy(this.moverDiv);
542
delete this.moverDiv;
543
this._skipBogusClicks = true;
544
var conn = dojo.connect(inDrag.view, "update", this, function(){
545
dojo.disconnect(conn);
546
this._skipBogusClicks = false;
548
setTimeout(dojo.hitch(inDrag.view, "update"), 50);
552
// Maps an html table into a structure parsable for information about cell row and col spanning.
553
// Used by HeaderBuilder.
554
dg._TableMap = dojo.extend(function(rows){
559
mapRows: function(inRows){
560
// summary: Map table topography
562
//console.log('mapRows');
564
var rowCount = inRows.length;
568
// map which columns and rows fill which cells
570
for(var j=0, row; (row=inRows[j]); j++){
573
for(var j=0, row; (row=inRows[j]); j++){
574
for(var i=0, x=0, cell, colSpan, rowSpan; (cell=row[i]); i++){
575
while (this.map[j][x]){x++};
576
this.map[j][x] = { c: i, r: j };
577
rowSpan = cell.rowSpan || 1;
578
colSpan = cell.colSpan || 1;
579
for(var y=0; y<rowSpan; y++){
580
for(var s=0; s<colSpan; s++){
581
this.map[j+y][x+s] = this.map[j][x];
591
for(var j=0, row, h=''; (row=this.map[j]); j++,h=''){
592
for(var i=0, cell; (cell=row[i]); i++){
593
h += cell.r + ',' + cell.c + ' ';
599
getMapCoords: function(inRow, inCol){
600
// summary: Find node's map coords by it's structure coords
601
for(var j=0, row; (row=this.map[j]); j++){
602
for(var i=0, cell; (cell=row[i]); i++){
603
if(cell.c==inCol && cell.r == inRow){
604
return { j: j, i: i };
606
//else{console.log(inRow, inCol, ' : ', i, j, " : ", cell.r, cell.c); };
609
return { j: -1, i: -1 };
612
getNode: function(inTable, inRow, inCol){
613
// summary: Find a node in inNode's table with the given structure coords
614
var row = inTable && inTable.rows[inRow];
615
return row && row.cells[inCol];
618
_findOverlappingNodes: function(inTable, inRow, inCol){
620
var m = this.getMapCoords(inRow, inCol);
621
//console.log("node j: %d, i: %d", m.j, m.i);
622
var row = this.map[m.j];
623
for(var j=0, row; (row=this.map[j]); j++){
624
if(j == m.j){ continue; }
626
//console.log("overlaps: r: %d, c: %d", rw.r, rw.c);
627
var n = (rw?this.getNode(inTable, rw.r, rw.c):null);
628
if(n){ nodes.push(n); }
630
//console.log(nodes);
634
findOverlappingNodes: function(inNode){
635
return this._findOverlappingNodes(findTable(inNode), getTrIndex(inNode.parentNode), getTdIndex(inNode));