define([
|
'dojo/_base/declare',
|
'dojo/_base/lang',
|
'dojo/dom-class',
|
'dojo/dom-construct',
|
'dojo/on',
|
'dojo/aspect',
|
'dojo/query',
|
'dojo/has',
|
'./util/misc',
|
'dojo/_base/sniff'
|
], function (declare, lang, domClass, domConstruct, on, aspect, query, has, miscUtil) {
|
has.add('event-mousewheel', function (global, document, element) {
|
return 'onmousewheel' in element;
|
});
|
has.add('event-wheel', function (global, document, element) {
|
return 'onwheel' in element;
|
});
|
|
var colsetidAttr = 'data-dgrid-column-set-id';
|
|
function adjustScrollLeft(grid, root) {
|
// Adjusts the scroll position of each column set in each row under the given root.
|
// (root can be a row, or e.g. a tree parent row element's connected property to adjust children)
|
var scrollLefts = grid._columnSetScrollLefts;
|
query('.dgrid-column-set', root).forEach(function (element) {
|
element.scrollLeft = scrollLefts[element.getAttribute(colsetidAttr)];
|
});
|
}
|
|
function getColumnSetSubRows(subRows, columnSetId) {
|
// Builds a subRow collection that only contains columns that correspond to
|
// a given column set id.
|
if (!subRows || !subRows.length) {
|
return;
|
}
|
var subset = [];
|
var idPrefix = columnSetId + '-';
|
for (var i = 0, numRows = subRows.length; i < numRows; i++) {
|
var row = subRows[i];
|
var subsetRow = [];
|
subsetRow.className = row.className;
|
for (var k = 0, numCols = row.length; k < numCols; k++) {
|
var column = row[k];
|
// The column id begins with the column set id.
|
if (column.id != null && column.id.indexOf(idPrefix) === 0) {
|
subsetRow.push(column);
|
}
|
}
|
subset.push(subsetRow);
|
}
|
return subset;
|
}
|
|
function isRootNode(node, rootNode) {
|
// If we've reached the top-level node for the grid then there is no parent column set.
|
// This guard prevents an error when scroll is initated over some node in the grid that is not a descendant of
|
// a column set. This can happen in a grid that has empty space below its rows (grid is taller than the rows).
|
return (rootNode && node === rootNode) || domClass.contains(node, 'dgrid');
|
}
|
|
function findParentColumnSet(node, root) {
|
// WebKit will invoke mousewheel handlers with an event target of a text
|
// node; check target and if it's not an element node, start one node higher
|
// in the tree
|
if (node.nodeType !== 1) {
|
node = node.parentNode;
|
}
|
|
while (node && !query.matches(node, '.dgrid-column-set[' + colsetidAttr + ']', root)) {
|
if (isRootNode(node, root)) {
|
return null;
|
}
|
node = node.parentNode;
|
}
|
|
return node;
|
}
|
|
var pointerMap = {
|
start: 'down',
|
end: 'up'
|
};
|
|
function getTouchEventName(type) {
|
// Given 'start', 'move', or 'end', returns appropriate touch or pointer event name
|
// based on browser support. (Assumes browser supports touch or pointer.)
|
var hasPointer = has('pointer');
|
if (hasPointer) {
|
type = pointerMap[type] || type;
|
if (hasPointer.slice(0, 2) === 'MS') {
|
return 'MSPointer' + type.slice(0, 1).toUpperCase() + type.slice(1);
|
}
|
else {
|
return 'pointer' + type;
|
}
|
}
|
return 'touch' + type;
|
}
|
|
var horizTouchMove = has('touch') && function (grid) {
|
return function (target, listener) {
|
var listeners = [
|
on(target, getTouchEventName('start'), function (event) {
|
if (!grid._currentlyTouchedColumnSet) {
|
var node = findParentColumnSet(event.target, target);
|
// If handling pointer events, only react to touch;
|
// MSPointerDown (IE10) reports 2, 3, 4 for touch, pen, mouse
|
if (node && (!event.pointerType || event.pointerType === 'touch' || event.pointerType === 2)) {
|
grid._currentlyTouchedColumnSet = node;
|
grid._lastColumnSetTouchX = event.clientX;
|
grid._lastColumnSetTouchY = event.clientY;
|
}
|
}
|
}),
|
on(target, getTouchEventName('move'), function (event) {
|
if (grid._currentlyTouchedColumnSet === null) {
|
return;
|
}
|
var node = findParentColumnSet(event.target);
|
if (!node) {
|
return;
|
}
|
listener.call(null, grid, node, grid._lastColumnSetTouchX - event.clientX);
|
grid._lastColumnSetTouchX = event.clientX;
|
grid._lastColumnSetTouchY = event.clientY;
|
}),
|
on(target, getTouchEventName('end'), function () {
|
grid._currentlyTouchedColumnSet = null;
|
})
|
];
|
|
return {
|
remove: function () {
|
for (var i = listeners.length; i--;) {
|
listeners[i].remove();
|
}
|
}
|
};
|
};
|
};
|
|
var horizMouseWheel = has('event-mousewheel') || has('event-wheel') ? function (grid) {
|
return function (target, listener) {
|
return on(target, has('event-wheel') ? 'wheel' : 'mousewheel', function (event) {
|
var node = findParentColumnSet(event.target, target),
|
deltaX;
|
|
if (!node) {
|
return;
|
}
|
|
// Normalize reported delta value:
|
// wheelDeltaX (webkit, mousewheel) needs to be negated and divided by 3
|
// deltaX (FF17+, wheel) can be used exactly as-is
|
deltaX = event.deltaX || -event.wheelDeltaX / 3;
|
if (deltaX) {
|
// only respond to horizontal movement
|
listener.call(null, grid, node, deltaX);
|
}
|
});
|
};
|
} : function (grid) {
|
return function (target, listener) {
|
return on(target, '.dgrid-column-set[' + colsetidAttr + ']:MozMousePixelScroll', function (event) {
|
if (event.axis === 1) {
|
// only respond to horizontal movement
|
listener.call(null, grid, this, event.detail);
|
}
|
});
|
};
|
};
|
|
function horizMoveHandler(grid, colsetNode, amount) {
|
var id = colsetNode.getAttribute(colsetidAttr),
|
scroller = grid._columnSetScrollers[id],
|
scrollLeft = scroller.scrollLeft + amount;
|
|
scroller.scrollLeft = scrollLeft < 0 ? 0 : scrollLeft;
|
}
|
|
return declare(null, {
|
// summary:
|
// Provides column sets to isolate horizontal scroll of sets of
|
// columns from each other. This mainly serves the purpose of allowing for
|
// column locking.
|
|
postCreate: function () {
|
var self = this;
|
this.inherited(arguments);
|
|
this.on(horizMouseWheel(this), horizMoveHandler);
|
if (has('touch')) {
|
this.on(horizTouchMove(this), horizMoveHandler);
|
}
|
|
this.on('.dgrid-column-set:dgrid-cellfocusin', function (event) {
|
self._onColumnSetCellFocus(event, this);
|
});
|
|
if (typeof this.expand === 'function') {
|
aspect.after(this, 'expand', function (promise, args) {
|
promise.then(function () {
|
var row = self.row(args[0]);
|
if (self._expanded[row.id]) {
|
// scrollLeft changes can't take effect on collapsed child rows;
|
// ensure they are properly updated once re-expanded.
|
adjustScrollLeft(self, row.element.connected);
|
}
|
});
|
return promise;
|
});
|
}
|
},
|
|
columnSets: [],
|
|
createRowCells: function (tag, each, subRows, object, options) {
|
var row = domConstruct.create('table', { className: 'dgrid-row-table' });
|
var tbody = domConstruct.create('tbody', null, row);
|
var tr = domConstruct.create('tr', null, tbody);
|
for (var i = 0, l = this.columnSets.length; i < l; i++) {
|
// iterate through the columnSets
|
var cell = domConstruct.create(tag, {
|
className: 'dgrid-column-set-cell dgrid-column-set-' + i
|
}, tr);
|
cell = domConstruct.create('div', {
|
className: 'dgrid-column-set'
|
}, cell);
|
cell.setAttribute(colsetidAttr, i);
|
var subset = getColumnSetSubRows(subRows || this.subRows, i) || this.columnSets[i];
|
cell.appendChild(this.inherited(arguments, [tag, each, subset, object, options]));
|
}
|
return row;
|
},
|
|
renderArray: function () {
|
var rows = this.inherited(arguments);
|
|
for (var i = 0; i < rows.length; i++) {
|
adjustScrollLeft(this, rows[i]);
|
}
|
return rows;
|
},
|
|
insertRow: function () {
|
var row = this.inherited(arguments);
|
adjustScrollLeft(this, row);
|
return row;
|
},
|
|
renderHeader: function () {
|
// summary:
|
// Setup the headers for the grid
|
this.inherited(arguments);
|
|
var columnSets = this.columnSets,
|
scrollers = this._columnSetScrollers,
|
grid = this,
|
i, l;
|
|
function reposition() {
|
grid._positionScrollers();
|
}
|
|
this._columnSetScrollerContents = {};
|
this._columnSetScrollLefts = {};
|
|
if (scrollers) {
|
// this isn't the first time; destroy existing scroller nodes first
|
for (i in scrollers) {
|
domConstruct.destroy(scrollers[i]);
|
}
|
} else {
|
// first-time-only operations: hook up event/aspected handlers
|
aspect.after(this, 'resize', reposition, true);
|
aspect.after(this, 'styleColumn', reposition, true);
|
this._columnSetScrollerNode = domConstruct.create('div', {
|
className: 'dgrid-column-set-scroller-container'
|
}, this.footerNode, 'after');
|
}
|
|
// reset to new object to be populated in loop below
|
scrollers = this._columnSetScrollers = {};
|
|
for (i = 0, l = columnSets.length; i < l; i++) {
|
this._putScroller(columnSets[i], i);
|
}
|
|
this._positionScrollers();
|
},
|
|
styleColumnSet: function (colsetId, css) {
|
// summary:
|
// Dynamically creates a stylesheet rule to alter a columnset's style.
|
|
var rule = this.addCssRule('#' + miscUtil.escapeCssIdentifier(this.domNode.id) +
|
' .dgrid-column-set-' + miscUtil.escapeCssIdentifier(colsetId, '-'), css);
|
this._positionScrollers();
|
return rule;
|
},
|
|
configStructure: function () {
|
// Squash the column sets together so the grid and other dgrid extensions and mixins can
|
// configure the columns and create any needed subrows.
|
this.columns = {};
|
this.subRows = [];
|
for (var i = 0, l = this.columnSets.length; i < l; i++) {
|
var columnSet = this.columnSets[i];
|
for (var j = 0; j < columnSet.length; j++) {
|
columnSet[j] = this._configColumns(i + '-' + j + '-', columnSet[j]);
|
}
|
}
|
this.inherited(arguments);
|
},
|
|
_positionScrollers: function () {
|
var domNode = this.domNode,
|
scrollers = this._columnSetScrollers,
|
scrollerContents = this._columnSetScrollerContents,
|
columnSets = this.columnSets,
|
scrollerWidth = 0,
|
numScrollers = 0, // tracks number of visible scrollers (sets w/ overflow)
|
i, l, columnSetElement, contentWidth;
|
|
for (i = 0, l = columnSets.length; i < l; i++) {
|
// iterate through the columnSets
|
columnSetElement = query('.dgrid-column-set[' + colsetidAttr + '="' + i + '"]', domNode)[0];
|
scrollerWidth = columnSetElement.offsetWidth;
|
contentWidth = columnSetElement.firstChild.offsetWidth;
|
scrollerContents[i].style.width = contentWidth + 'px';
|
scrollers[i].style.width = scrollerWidth + 'px';
|
|
if (has('ie') < 9) {
|
// IE seems to need scroll to be set explicitly
|
scrollers[i].style.overflowX = contentWidth > scrollerWidth ? 'scroll' : 'auto';
|
}
|
|
// Keep track of how many scrollbars we're showing
|
if (contentWidth > scrollerWidth) {
|
numScrollers++;
|
}
|
}
|
|
this._columnSetScrollerNode.style.bottom = this.showFooter ? this.footerNode.offsetHeight + 'px' : '0';
|
|
// Align bottom of body node depending on whether there are scrollbars
|
this.bodyNode.style.bottom = numScrollers ?
|
(has('dom-scrollbar-height') + (has('ie') ? 1 : 0) + 'px') :
|
'0';
|
},
|
|
_putScroller: function (columnSet, i) {
|
// function called for each columnSet
|
var scroller = this._columnSetScrollers[i] = domConstruct.create('span', {
|
// IE8 needs dgrid-scrollbar-height class for scrollbar to be visible,
|
// but for some reason IE11's scrollbar arrows become unresponsive, so avoid applying it there
|
className: 'dgrid-column-set-scroller dgrid-column-set-scroller-' + i +
|
(has('ie') < 9 ? ' dgrid-scrollbar-height' : '')
|
}, this._columnSetScrollerNode);
|
scroller.setAttribute(colsetidAttr, i);
|
|
this._columnSetScrollerContents[i] = domConstruct.create('div', {
|
className: 'dgrid-column-set-scroller-content'
|
}, scroller);
|
on(scroller, 'scroll', lang.hitch(this, '_onColumnSetScroll'));
|
},
|
|
_onColumnSetScroll: function (evt) {
|
var scrollLeft = evt.target.scrollLeft,
|
colSetId = evt.target.getAttribute(colsetidAttr),
|
newScrollLeft;
|
|
if (this._columnSetScrollLefts[colSetId] !== scrollLeft) {
|
query('.dgrid-column-set[' + colsetidAttr + '="' + colSetId +
|
'"],.dgrid-column-set-scroller[' + colsetidAttr + '="' + colSetId + '"]', this.domNode
|
).forEach(function (element, i) {
|
element.scrollLeft = scrollLeft;
|
if (!i) {
|
// Compute newScrollLeft based on actual resulting
|
// value of scrollLeft, which may be different than
|
// what we assigned under certain circumstances
|
// (e.g. Chrome under 33% / 67% / 90% zoom).
|
// Only need to compute this once, as it will be the
|
// same for every row.
|
newScrollLeft = element.scrollLeft;
|
}
|
});
|
this._columnSetScrollLefts[colSetId] = newScrollLeft;
|
}
|
},
|
|
_setColumnSets: function (columnSets) {
|
this._destroyColumns();
|
this.columnSets = columnSets;
|
this._updateColumns();
|
},
|
|
_scrollColumnSet: function (nodeOrId, offsetLeft) {
|
var id = nodeOrId.tagName ? nodeOrId.getAttribute(colsetidAttr) : nodeOrId;
|
var scroller = this._columnSetScrollers[id];
|
scroller.scrollLeft = offsetLeft < 0 ? 0 : offsetLeft;
|
},
|
|
_onColumnSetCellFocus: function (event, columnSetNode) {
|
var focusedNode = event.target;
|
var columnSetId = columnSetNode.getAttribute(colsetidAttr);
|
// columnSetNode's offsetLeft is not always correct,
|
// so get the columnScroller to check offsetLeft against
|
var columnScroller = this._columnSetScrollers[columnSetId];
|
var elementEdge = focusedNode.offsetLeft - columnScroller.scrollLeft + focusedNode.offsetWidth;
|
|
if (elementEdge > columnSetNode.offsetWidth ||
|
columnScroller.scrollLeft > focusedNode.offsetLeft) {
|
this._scrollColumnSet(columnSetNode, focusedNode.offsetLeft);
|
}
|
}
|
});
|
});
|