datatable-scroll-debug.js revision 488020361dcc98fbcf160519fd817ec79a954673
YUI.add('datatable-scroll', function(Y) {
/**
* Extends DataTable base to enable x,y, and xy scrolling.
* @module datatable
* @submodule datatable-scroll
*/
var YNode = Y.Node,
YLang = Y.Lang,
YUA = Y.UA,
YgetClassName = Y.ClassNameManager.getClassName,
DATATABLE = "datatable",
CLASS_HEADER = YgetClassName(DATATABLE, "hd"),
CLASS_BODY = YgetClassName(DATATABLE, "bd"),
CLASS_DATA = YgetClassName(DATATABLE, "data"),
CLASS_LINER = YgetClassName(DATATABLE, "liner"),
CLASS_SCROLLABLE = YgetClassName(DATATABLE, "scrollable"),
CONTAINER_HEADER = '<div class="'+CLASS_HEADER+'"></div>',
CONTAINER_BODY = '<div class="'+CLASS_BODY+'"></div>',
TEMPLATE_TABLE = '<table></table>',
scrollbarWidth = Y.cached(function () {
var testNode = Y.one('body').appendChild('<div style="position:absolute;visibility:hidden;overflow:scroll;width:20px;"><p style="height:1px"/></div>'),
width = testNode.get('offsetWidth') - testNode.get('clientWidth');
testNode.remove(true);
return width;
});
/**
* Adds scrolling to DataTable.
* @class DataTableScroll
* @extends Plugin.Base
*/
function DataTableScroll() {
DataTableScroll.superclass.constructor.apply(this, arguments);
}
Y.mix(DataTableScroll, {
NS: "scroll",
NAME: "dataTableScroll",
ATTRS: {
/**
* The width for the table. Set to a string (ex: "200px", "20em") if you want the table to scroll in the x direction.
*
* @attribute width
* @public
* @type string
*/
width: {
value: undefined,
writeOnce: "initOnly"
},
/**
* The height for the table. Set to a string (ex: "200px", "20em") if you want the table to scroll in the y-direction.
*
* @attribute height
* @public
* @type string
*/
height: {
value: undefined,
writeOnce: "initOnly"
},
/**
* The scrolling direction for the table.
*
* @attribute scroll
* @private
* @type string
*/
_scroll: {
//value: 'y',
valueFn: function() {
var w = this.get('width'),
h = this.get('height');
if (w && h) {
return 'xy';
}
else if (w) {
return 'x';
}
else if (h) {
return 'y';
}
else {
return null;
}
}
},
/**
* The hexadecimal colour value to set on the top-right of the table if a scrollbar exists.
*
* @attribute COLOR_COLUMNFILLER
* @public
* @type string
*/
COLOR_COLUMNFILLER: {
value: '#f2f2f2',
validator: YLang.isString,
setter: function(param) {
if (this._headerContainerNode) {
this._headerContainerNode.setStyle('backgroundColor', param);
}
}
}
}
});
Y.extend(DataTableScroll, Y.Plugin.Base, {
/**
* The table node created in datatable-base
*
* @property _parentTableNode
* @private
* @type Y.Node
*/
_parentTableNode: null,
/**
* The THEAD node which resides within the table node created in datatable-base
*
* @property _parentTheadNode
* @private
* @type Y.Node
*/
_parentTheadNode: null,
/**
* The TBODY node which resides within the table node created in datatable-base
*
* @property _parentTbodyNode
* @private
* @type Y.Node
*/
_parentTbodyNode: null,
/**
* The TBODY Message node which resides within the table node created in datatable-base
*
* @property _parentMsgNode
* @private
* @type Y.Node
*/
_parentMsgNode: null,
/**
* The contentBox specified for the datatable in datatable-base
*
* @property _parentContainer
* @private
* @type Y.Node
*/
_parentContainer: null,
/**
* The DIV node that contains all the scrollable elements (a table with a tbody on it)
*
* @property _bodyContainerNode
* @private
* @type Y.Node
*/
_bodyContainerNode: null,
/**
* The DIV node that contains a table with a THEAD in it (which syncs its horizontal scroll with the _bodyContainerNode above)
*
* @property _headerContainerNode
* @private
* @type Y.Node
*/
_headerContainerNode: null,
//--------------------------------------
// Methods
//--------------------------------------
initializer: function(config) {
var dt = this.get("host");
this._parentContainer = dt.get('contentBox');
this._parentContainer.addClass(CLASS_SCROLLABLE);
this._setUpNodes();
},
/////////////////////////////////////////////////////////////////////////////
//
// Set up Table Nodes
//
/////////////////////////////////////////////////////////////////////////////
/**
* Set up methods to fire after host methods execute
*
* @method _setUpNodes
* @private
*/
_setUpNodes: function() {
this.afterHostMethod("_addTableNode", this._setUpParentTableNode);
this.afterHostMethod("_addTheadNode", this._setUpParentTheadNode);
this.afterHostMethod("_addTbodyNode", this._setUpParentTbodyNode);
this.afterHostMethod("_addMessageNode", this._setUpParentMessageNode);
//this.beforeHostMethod('renderUI', this._removeCaptionNode);
this.afterHostMethod("renderUI", this.renderUI);
this.afterHostMethod("bindUI", this.bindUI);
this.afterHostMethod("syncUI", this.syncUI);
if (this.get('_scroll') !== 'x') {
this.afterHostMethod('_attachTheadThNode', this._attachTheadThNode);
this.afterHostMethod('_attachTbodyTdNode', this._attachTbodyTdNode);
}
},
/**
* Stores the main &lt;table&gt; node provided by the host as a private property
*
* @method _setUpParentTableNode
* @private
*/
_setUpParentTableNode: function() {
this._parentTableNode = this.get('host')._tableNode;
},
/**
* Stores the main &lt;thead&gt; node provided by the host as a private property
*
* @method _setUpParentTheadNode
* @private
*/
_setUpParentTheadNode: function() {
this._parentTheadNode = this.get('host')._theadNode;
},
/**
* Stores the main &lt;tbody&gt; node provided by the host as a private property
*
* @method _setUpParentTbodyNode
* @private
*/
_setUpParentTbodyNode: function() {
this._parentTbodyNode = this.get('host')._tbodyNode;
},
/**
* Stores the main &lt;tbody&gt; message node provided by the host as a private property
*
* @method _setUpParentMessageNode
* @private
*/
_setUpParentMessageNode: function() {
this._parentMsgNode = this.get('host')._msgNode;
},
/////////////////////////////////////////////////////////////////////////////
//
// Renderer
//
/////////////////////////////////////////////////////////////////////////////
/**
* Primary rendering method that takes the datatable rendered in
* the host, and splits it up into two separate &lt;divs&gt; each containing two
* separate tables (one containing the head and one containing the body).
* This method fires after renderUI is called on datatable-base.
*
* @method renderUI
*/
renderUI: function() {
//Y.Profiler.start('render');
this._createBodyContainer();
this._createHeaderContainer();
this._setContentBoxDimensions();
//Y.Profiler.stop('render');
//console.log(Y.Profiler.getReport("render"));
},
/**
Binds event subscriptions to keep the state and UI in sync
@method bindUI
**/
bindUI: function () {
// FIXME: I don't know why the string bind, but I don't want to break
// stuff until I have time to rebuild it properly
this._bodyContainerNode.on('scroll', Y.bind("_onScroll", this));
this.afterHostEvent("recordsetChange", this.syncUI);
this.afterHostEvent("recordset:recordsChange", this.syncUI);
},
/**
* Post rendering method that is responsible for creating a column
* filler, and performing width and scroll synchronization between the &lt;th&gt;
* elements and the &lt;td&gt; elements.
* This method fires after syncUI is called on datatable-base
*
* @method syncUI
* @public
*/
syncUI: function() {
//Y.Profiler.start('sync');
this._removeCaptionNode();
this._syncWidths();
this._syncScroll();
//Y.Profiler.stop('sync');
//console.log(Y.Profiler.getReport("sync"));
},
/**
* Remove the caption created in base. Scrolling datatables dont support captions.
*
* @method _removeCaptionNode
* @private
*/
_removeCaptionNode: function() {
this.get('host')._captionNode.remove();
//Y.DataTable.Base.prototype.createCaption = function(v) {/*do nothing*/};
//Y.DataTable.Base.prototype._uiSetCaption = function(v) {/*do nothing*/};
},
/**
* Adjusts the width of the TH and the TDs to make sure that the two are in sync
*
* Implementation Details:
* Compares the width of the TH liner div to the the width of the TD node.
* The TD liner width is not actually used because the TD often stretches
* past the liner if the parent DIV is very large. Measuring the TD width
* is more accurate.
*
* Instead of measuring via .get('width'), 'clientWidth' is used, as it
* returns a number, whereas 'width' returns a string, In IE6,
* 'clientWidth' is not supported, so 'offsetWidth' is used. 'offsetWidth'
* is not as accurate on Chrome,FF as 'clientWidth' - thus the need for
* the fork.
*
* @method _syncWidths
* @private
*/
_syncWidths: function() {
var headerTable = this._parentContainer.one('.' + CLASS_HEADER),
bodyTable = this._parentContainer.one('.' + CLASS_BODY),
// nodelist of all the THs
headers = headerTable.all('thead .' + CLASS_LINER),
// nodelist of the TDs in the first row
firstRow = bodyTable.one('.' + CLASS_DATA + ' tr'),
cells = firstRow && firstRow.all('.' + CLASS_LINER),
// FIXME: Code smell
widthProperty = (YUA.ie) ? 'offsetWidth' : 'clientWidth';
//stylesheet = new YStyleSheet('columnsSheet'),
//className;
// If there are data rows, iterate each header and the cells of the
// first row comparing cell widths. Assign the larger width to the
// narrower node (header or cell).
if (cells && cells.size()) {
headers.each(function (header, i) {
var cell = cells.item(i),
headerWidth = header.get(widthProperty),
cellWidth = cell.get(widthProperty),
width = Math.max(headerWidth, cellWidth);
width -= (parseInt(header.getComputedStyle('paddingLeft'),10)|0) +
(parseInt(header.getComputedStyle('paddingRight'),10)|0);
header.setStyle('width', width + 'px');
cell.setStyle('width', width + 'px');
});
}
/*
// If browser is not IE - get the clientWidth of the Liner
// div and the TD.
// Note: We are not getting the width of the TDLiner, we
// are getting the width of the actual cell. Why? Because
// when the table is set to auto width, the cell will grow
// to try to fit the table in its container. The liner
// could potentially be much smaller than the cell width.
// TODO: Explore if there is a better way using only LINERS
// widths - I don't think this should be a problem, given
// that the liner is a div, a block element and will
// expand to width.
if (!ie) {
// TODO: this should actually be done with
// getComputedStyle('width') but this messes up
// columns. Explore this option.
thWidth = thLiner.get('clientWidth');
tdWidth = td.item(i).get('clientWidth');
} else {
// IE wasn't recognizing clientWidths, so we are using
// offsetWidths.
// TODO: should use getComputedStyle('width') because
// offsetWidth will screw up when padding is changed.
// TODO: for some reason, using
// tdLiner.get('clientWidth') doesn't work - why not?
thWidth = thLiner.get('offsetWidth');
tdWidth = td.item(i).get('offsetWidth');
//thWidth = parseFloat(thLiner.getComputedStyle('width').split('px')[0]);
//tdWidth = parseFloat(td.item(i).getComputedStyle('width').split('px')[0]);
}
// expand the TH or the TD to match the wider
if (thWidth > tdWidth) {
tdLiner.setStyle('width', (thWidth - 20 + 'px'));
//thLiner.setStyle('width', (tdWidth - 20 + 'px'));
//stylesheet.set(className,{'width': (thWidth - 20 + 'px')});
} else if (tdWidth > thWidth) {
// if you don't set an explicit width here, when the width
// is set in line 368, it will auto-shrink the widths of
// the other cells (because they dont have an explicit
// width)
thLiner.setStyle('width', (tdWidth - 20 + 'px'));
tdLiner.setStyle('width', (tdWidth - 20 + 'px'));
//stylesheet.set(className,{'width': (tdWidth - 20 + 'px')});
}
//}
}
*/
//stylesheet.enable();
},
/**
* Adds the approriate width to the liner divs of the TH nodes before they are appended to DOM
*
* @method _attachTheadThNode
* @private
*/
_attachTheadThNode: function(o) {
var width = o.column.get('width');
if (width) {
o.th.one('.' + CLASS_LINER)
.setStyles({
width: width,
overflow:'hidden'
});
}
},
/**
* Adds the appropriate width to the liner divs of the TD nodes before they are appended to DOM
*
* @method _attachTbodyTdNode
* @private
*/
_attachTbodyTdNode: function(o) {
var width = o.column.get('width');
if (width) {
o.td.one('.' + CLASS_LINER)
.setStyles({
width: width,
overflow: 'hidden'
});
}
},
/**
* Creates the body DIV that contains all the data.
*
* @method _createBodyContainer
* @private
*/
_createBodyContainer: function() {
var bd = YNode.create(CONTAINER_BODY);
this._bodyContainerNode = bd;
this._setStylesForTbody();
bd.appendChild(this._parentTableNode);
this._parentContainer.appendChild(bd);
},
/**
* Creates the DIV that contains a &lt;table&gt; with all the headers.
*
* @method _createHeaderContainer
* @private
*/
_createHeaderContainer: function() {
var hd = YNode.create(CONTAINER_HEADER),
tbl = YNode.create(TEMPLATE_TABLE);
this._headerContainerNode = hd;
//hd.setStyle('backgroundColor',this.get("COLOR_COLUMNFILLER"));
this._setStylesForThead();
tbl.appendChild(this._parentTheadNode);
hd.appendChild(tbl);
this._parentContainer.prepend(hd);
},
/**
* Creates styles for the TBODY based on what type of table it is.
*
* @method _setStylesForTbody
* @private
*/
_setStylesForTbody: function() {
var dir = this.get('_scroll'),
w = this.get('width') || "",
h = this.get('height') || "",
el = this._bodyContainerNode,
styles = {width:"", height:h};
if (dir === 'x') {
//X-Scrolling tables should not have a Y-Scrollbar so overflow-y is hidden. THe width on x-scrolling tables must be set by user.
styles.overflowY = 'hidden';
styles.width = w;
} else if (dir === 'y') {
//Y-Scrolling tables should not have a X-Scrollbar so overflow-x is hidden. The width isn't neccessary because it can be auto.
styles.overflowX = 'hidden';
} else if (dir === 'xy') {
styles.width = w;
} else {
//scrolling is set to 'null' - ie: width and height are not set. Don't have any type of scrolling.
styles.overflowX = 'hidden';
styles.overflowY = 'hidden';
styles.width = w;
}
el.setStyles(styles);
return el;
},
/**
* Creates styles for the THEAD based on what type of datatable it is.
*
* @method _setStylesForThead
* @private
*/
_setStylesForThead: function() {
var w = this.get('width') || "",
el = this._headerContainerNode;
//if (dir !== 'y') {
el.setStyles({'width': w, 'overflow': 'hidden'});
// }
},
/**
* Sets an auto width on the content box if it doesn't exist or if its a y-datatable.
*
* @method _setContentBoxDimensions
* @private
*/
_setContentBoxDimensions: function() {
if (this.get('_scroll') === 'y' || (!this.get('width'))) {
this._parentContainer.setStyle('width', 'auto');
}
},
/////////////////////////////////////////////////////////////////////////////
//
// Scroll Syncing
//
/////////////////////////////////////////////////////////////////////////////
/**
* Ensures that scrolling is synced across the two tables
*
* @method _onScroll
* @private
*/
_onScroll: function() {
this._headerContainerNode.set('scrollLeft', this._bodyContainerNode.get('scrollLeft'));
},
/**
* Syncs padding around scrollable tables, including Column header right-padding
* and container width and height.
*
* @method _syncScroll
* @private
*/
_syncScroll : function() {
this._syncScrollX();
this._syncScrollY();
this._syncScrollOverhang();
if (YUA.opera) {
// Bug 1925874
this._headerContainerNode.set('scrollLeft', this._bodyContainerNode.get('scrollLeft'));
if(!this.get("width")) {
// Bug 1926125
document.body.style += '';
}
}
},
/**
* Snaps container width for y-scrolling tables.
*
* @method _syncScrollY
* @private
*/
_syncScrollY : function() {
var tBody = this._parentTbodyNode,
tBodyContainer = this._bodyContainerNode,
w;
// X-scrolling not enabled
if(!this.get("width")) {
// Snap outer container width to content
w = (tBodyContainer.get('scrollHeight') > tBodyContainer.get('clientHeight')) ?
// but account for y-scrollbar since it is visible
(tBody.get('parentNode').get('clientWidth') + scrollbarWidth()) + "px" :
// no y-scrollbar, just borders
(tBody.get('parentNode').get('clientWidth') + 2) + "px";
this._parentContainer.setStyle('width', w);
}
},
/**
* Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
* Taken from YUI2 ScrollingDataTable.js
*
* @method _syncScrollX
* @private
*/
_syncScrollX: function() {
var tBody = this._parentTbodyNode,
tBodyContainer = this._bodyContainerNode,
w;
this._headerContainerNode.set('scrollLeft',
this._bodyContainerNode.get('scrollLeft'));
if (!this.get('height') && (YUA.ie)) {
w = (tBodyContainer.get('scrollWidth') > tBodyContainer.get('offsetWidth')) ?
(tBody.get('parentNode').get('offsetHeight') + scrollbarWidth()) + "px" :
tBody.get('parentNode').get('offsetHeight') + "px";
tBodyContainer.setStyle('height', w);
}
if (tBody.get('rows').size()) {
this._parentMsgNode.get('parentNode').setStyle('width', "");
} else {
this._parentMsgNode.get('parentNode').setStyle('width', this._parentTheadNode.get('parentNode').get('offsetWidth')+'px');
}
},
/**
* Adds/removes Column header overhang as necesary.
* Taken from YUI2 ScrollingDataTable.js
*
* @method _syncScrollOverhang
* @private
*/
_syncScrollOverhang: function() {
var tBodyContainer = this._bodyContainerNode,
padding = 1;
//when its both x and y scrolling
if ((tBodyContainer.get('scrollHeight') > tBodyContainer.get('clientHeight')) || (tBodyContainer.get('scrollWidth') > tBodyContainer.get('clientWidth'))) {
padding = 18;
}
this._setOverhangValue(padding);
// After the widths have synced, there is a wrapping issue in the
// headerContainer in IE6. The header does not span the full length of
// the table (does not cover all of the y-scrollbar). By adding this
// line in when there is a y-scroll, the header will span correctly.
// TODO: this should not really occur on this.get('_scroll') === y - it
// should occur when scrollHeight > clientHeight, but clientHeight is
// not getting recognized in IE6?
if (YUA.ie !== 0 && this.get('_scroll') === 'y' && this._bodyContainerNode.get('scrollHeight') > this._bodyContainerNode.get('offsetHeight'))
{
this._headerContainerNode.setStyle('width', this._parentContainer.get('width'));
}
},
/**
* Sets Column header overhang to given width.
* Taken from YUI2 ScrollingDataTable.js with minor modifications
*
* @method _setOverhangValue
* @param nBorderWidth {Number} Value of new border for overhang.
* @private
*/
_setOverhangValue: function(borderWidth) {
var host = this.get('host'),
cols = host.get('columnset').get('definitions'),
//lastHeaders = cols[cols.length-1] || [],
len = cols.length,
value = borderWidth + "px solid " + this.get("COLOR_COLUMNFILLER"),
children = YNode.all('#'+this._parentContainer.get('id')+ ' .' + CLASS_HEADER + ' table thead th');
children.item(len-1).setStyle('borderRight', value);
}
});
Y.namespace("Plugin").DataTableScroll = DataTableScroll;
}, '@VERSION@' ,{requires:['datatable-base','plugin']});