/***
* Based on original code from stackoverflow answer at http://stackoverflow.com/questions/673153/html-table-with-fixed-headers/7732309#7732309
* Requires jquery and iscroll 4.
*Example usage:
*
*/
/** Requires jquery-outer to perform setting outerWidth */
if (typeof (WBI) == 'undefined') { WBI = {} }
(function ($) {
// Browser capabilities
var isAndroid = (/android/gi).test(navigator.appVersion),
isIDevice = (/iphone|ipad/gi).test(navigator.appVersion),
isPlaybook = (/playbook/gi).test(navigator.appVersion),
isTouchPad = (/hp-tablet/gi).test(navigator.appVersion),
has3d = 'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix(),
hasTouch = 'ontouchstart' in window && !isTouchPad,
hasTransitionEnd = isIDevice || isPlaybook
$.fn.iscrollTable = function (config) {
var that = this;
var hitTest = null;
var selectedEl = null;
var oTbl;
var rowH;
var iscroller;
var visibleChanged = null;
var scrollStart = null;
var touchScroll = hasTouch;
var onColSelected = null;
var onRowSelected = null;
var headerH;
var scrollDiv;
var mainParent;
var resizeCols = false;
var rightPadding = 0;
var rowSelected = function (el) {
$(el).addClass('selected');
}
var gridDown = function (evt) {
hitTest = {};
var target;
if (evt.touches) {
evt = (evt.touches[0]);
target = (evt.targetTouches[0]);
alert(target);
} else {
target = $(evt.target);
}
hitTest.downEl = target;
hitTest.downX = evt.pageX;
hitTest.downY = evt.pageY;
hitTest.target = evt.target;
}
var gridUp = function (evt) {
if (hitTest) {
// check to see if we still have a hit test obj
/*
var vis = visibleElements();
for (var i = vis.length; i > 0; i--){
var el = vis[i -1];
if (hitContains(el)){
rowSelected(el);
}
}
*/
if (selectedEl) {
selectedEl.removeClass('selectedCol')
selectedEl.parent().removeClass('selectedRow');
}
if (hitTest.downEl[0].nodeName == "TD") {
selectedEl = $(hitTest.downEl);
selectedEl.addClass('selectedCol');
selectedEl.parent().addClass('selectedRow');
if (onRowSelected) {
onRowSelected(selectedEl.parent());
}
if (onColSelected) {
onColSelected(selectedEl);
}
}
}
hitTest = null;
}
var gridMove = function (evt) {
if (hitTest == null) return;
var delta = evt.pageY - hitTest.downY;
// if we've moved more than 30 pixels for a scroll, cancel our hit test
if (Math.abs(delta) > 15) { hitTest = null };
}
var updateVisible = function () {
if (visibleChanged) {
var items = visibleElements()
visibleChanged(items);
}
}
var binarySearchStart = function (rows,search) {
var min = 0
var max = rows.length - 1;
var mid = Math.floor((min + max) / 2);
var found = false
if (rows.length == 1) { return 0; }
var searchValue = (search !== null) ? search : Math.abs(iscroller.y) - rows[0].offsetTop - rows[1].offsetTop
while (!found) {
if (rows[mid].offsetTop > searchValue) {
// see if our prev index is not
if (mid == 0) return mid;
if (rows[mid - 1].offsetTop <= searchValue) {
return mid - 1;
}
// the mid is past the start point
max = mid;
mid = Math.floor((min + max) / 2);
} else if (mid == 0 || mid == rows.length - 1) {
return mid;
} else {
if (mid == rows.length - 1) return mid;
// see if our next index is not
if (rows[mid + 1].offsetTop >= searchValue) {
found = true;
return mid + 1;
}
// mid is before our start point
min = mid
mid = Math.floor((min + max) / 2);
if ((max - min) == 1) {
return mid;
}
}
}
}
var visibleElements = function () {
var parent = oTbl.parent();
var parentH = parent.height();
var rows = oTbl.find("tbody tr");
var startIndex = binarySearchStart(rows,parent.scrollTop());
var amount = parseInt(parentH / rowH) + 1;
rows = rows.splice(startIndex, amount);
return rows;
}
this.getVisible = function(){
return visibleElements();
}
var getParentHeight = function () {
var test = mainParent[0]
while (test.style.height == "100%") {
var p = $(test).parent();
if (p.length > 0) {
test = p[0];
}
}
return $(test).height();
}
this.layoutWidth = function (layoutW) {
scrollDiv.width(layoutW);
var hasScrollbar = scrollDiv[0].clientHeight < scrollDiv[0].scrollHeight
if (hasScrollbar){
oTbl.width(layoutW - rightPadding);
newTbl.width(layoutW - rightPadding);
} else {
oTbl.width(layoutW);
newTbl.width(layoutW);
}
if (headerH == 0) headerH = newTbl.height();
//scrollDiv.height(mainParent.height() - headerH);
scrollDiv.height(getParentHeight() - headerH);
scrollDiv.css("top", headerH);
newTbl.trigger("doLayout");
iscroller.refresh();
}
this.doLayout = function () {
var layoutW = doLayoutBind.width();
this.layoutWidth(layoutW);
}
oTbl = this;
var iScrollConfig = { hideScrollbar: true };
if (config) {
iScrollConfig = config.iscroll;
visibleChanged = config.visibleChanged;
scrollStart = config.scrollStart;
onColSelected = config.onColSelected;
onRowSelected = config.onRowSelected;
if (config.resizeCols){
resizeCols = config.resizeCols;
}
if (config.rightPadding){
rightPadding = config.rightPadding;
}
}
// save original width
var origW = oTbl.outerWidth();
oTbl.attr("data-item-original-width", origW);
oTbl.find('thead tr th').each(function (idx) {
var item = $(this);
item.attr("data-item-original-width", item.outerWidth());
item.attr("data-idx", idx);
item.css("-webkit-user-select", "none");
item.css("user-select", "none");
});
oTbl.find('tbody tr:eq(0) td').each(function (idx) {
var item = $(this);
rowH = parseInt(item.height() + .5);
item.attr("data-item-original-width", item.outerWidth());
item.attr("data-idx", idx);
});
// clone the original table
var newTbl = oTbl.clone();
// remove table header from original table
oTbl.find('thead tr').remove();
// remove table body from new table
newTbl.find('tbody tr').remove();
var newBody = newTbl.find('tbody');
newBody.attr("id", "header-" + newBody.attr("id"));
oTbl.parent().parent().prepend(newTbl);
newTbl.wrap("");
var headerID = "header-" + oTbl.attr("id");
newTbl.attr("id", headerID);
// replace ORIGINAL COLUMN width
newTbl.width(newTbl.attr('data-item-original-width'));
newTbl.find('thead tr th').each(function (idx) {
var th = $(this)
var w = th.attr("data-item-original-width")
th.outerWidth(w);
var pct = parseInt(((w / origW) * 100) + .5);
th.attr("data-item-original-width", pct);
//th.width(pct + "%");
});
oTbl.width(oTbl.attr('data-item-original-width'));
oTbl.find('tbody tr:eq(0) td').each(function () {
$(this).outerWidth($(this).attr("data-item-original-width"));
});
// oTbl.css('table-layout','fixed');
// newTbl.css('table-layout','fixed');
headerH = newTbl.height();
scrollDiv = oTbl.parent();
mainParent = scrollDiv.parent();
parentPos = mainParent.css('position')
if (parentPos != "absolute") {
mainParent.css('position', 'relative');
}
scrollDiv.height(mainParent.height() - headerH);
//console.log("Main Parent:" + mainParent);
var doLayoutBind = mainParent
if (config && config.doRefreshParent) {
doLayoutBind = $(config.doRefreshParent);
}
mainParent.bind('afterShow', function (e) {
that.doLayout();
})
doLayoutBind.bind('doLayout', function (e) {
if (e.target == doLayoutBind[0]) {
that.doLayout();
}
});
/*
$(window).bind("resize",function(){
scrollDiv.height(mainParent.height() - headerH);
})
*/
scrollDiv.css('position', 'absolute');
scrollDiv.css('top', headerH);
scrollDiv.css('left', '0');
// test for touch events
if (hasTouch) {
iscroller = new iScroll(scrollDiv[0], { hideScrollbar: iScrollConfig.hideScrollbar,
onScrollEnd: function () {
updateVisible();
},
onTouchEnd: function () {
if (iscroller.animating == false) {
updateVisible();
}
},
onScrollStart: function () {
if (scrollStart) {
scrollStart();
}
}
});
} else {
iscroller = {
refresh: function() {
}
}
var parentDiv =oTbl.parent()
parentDiv.css('overflow-x','hidden');
parentDiv.css('overflow-y','auto');
$(parentDiv).scroll(function(){
var items = that.getVisible()
visibleChanged(items);
});
oTbl.css('overflow:auto');
}
$("#" + headerID).iscrollTableGrip(resizeCols);
// test for the existance of the jquery mobile framework, bind to those mouse options:
if (typeof $.mobile == 'undefined'){
oTbl.bind("mousedown", gridDown);
oTbl.bind("mouseup", gridUp);
oTbl.bind("mousemove", gridMove);
oTbl.bind("touchstart", gridDown);
oTbl.bind("touchend", gridUp);
oTbl.bind("touchmove", gridMove);
} else {
oTbl.bind("vmousedown", gridDown);
oTbl.bind("vmouseup", gridUp);
oTbl.bind("vmousemove", gridMove);
}
this.iscroll = iscroller;
return this;
};
})(jQuery);
(function ($) {
//shortcuts
var I = parseInt;
var M = Math;
var ie = $.browser.msie;
$.fn.iscrollTableGrip = function (config) {
var drag
var element
var tableEl;
var totalCols;
var minWidth;
var cs
var brd
var headers;
var touchZoneW = 40;
var touchZoneLeftOffset = (touchZoneW / 2)
var grips = [];
var hCols = [];
var realCols = [];
var resizeTimer = null;
var getRealCols = function () {
realCols = [];
headers.each(function (idx) {
var selector = "tr:first td:nth-child(" + I(idx + 1) + ")"
var td = $(tableEl).find(selector);
realCols.push(td);
});
}
var doResize = function () {
// loop through all our columns and figure out their percentage
// var totalW = I(element.attr('data-item-original-width'));
// var newW = element.parent().outerWidth() - 1;
var newW = element.outerWidth();
//element.outerWidth(newW);
$(tableEl).outerWidth(newW);
element.attr('data-item-original-width', newW);
headers = element.find('thead tr th');
if (config){
headers.each(function (idx) {
var w = $(this).attr("data-item-original-width");
var w = Math.floor(newW * (w / 100));
$(this).outerWidth(w);
});
}
setTimeout(function () { syncColsWithHeader() }, 50);
setTimeout(function () { syncGrips() }, 50);
}
var syncColsWithHeader = function () {
if (config){
headers = element.find('thead tr th');
headers.each(function (idx) {
var w = $(this).outerWidth();
realCols[idx].outerWidth(w);
});
} else {
// update the columns in case they changed();
getRealCols();
headers = element.find('thead tr th');
headers.each(function (idx) {
var w = realCols[idx].outerWidth();
$(this).outerWidth(w);
});
}
}
var stripPx = function (item) {
if (item) {
return parseInt(item.substring(0, item.length - 2));
} else {
return 0
}
}
var syncGrips = function () {
if (config == false) return;
// set the new grip positions aftwer we assign all the columns
// do not do this in the same loop or the position property will not be correct
headers.each(function (idx) {
if (idx < headers.length - 1) {
// get our grip and match it back up
var pos = $(this).position()
var left = pos.left + $(this).outerWidth() - touchZoneLeftOffset;
left += totalPaddingW($(this));
left += cs + brd
grips[idx].css('left', left + "px");
}
})
}
var totalPaddingW = function (el) {
var pad = stripPx($(el).css('padding-left'));
// pad += stripPx($(el).css('padding-right'));
return pad;
}
var gripDown = function (evt) {
WBI.touchMoveHandled = true;
$(tableEl).addClass('columnLines');
var target;
if (evt.touches) {
evt = (evt.touches[0]);
target = (evt.targetTouches[0]);
} else {
target = $(evt.target);
}
drag = {};
drag.downX = evt.pageX;
drag.target = target;
drag.idx = parseInt(target.attr('data-grip-idx'));
drag.col1 = hCols[drag.idx];
drag.col2 = hCols[drag.idx + 1];
drag.col3 = realCols[drag.idx];
drag.col4 = realCols[drag.idx + 1];
drag.position = target.position();
drag.col1.w = drag.col1.outerWidth();
drag.col2.w = drag.col2.outerWidth();
drag.col3.w = drag.col3.outerWidth();
drag.col4.w = drag.col4.outerWidth();
element.bind("vmousemove", gripMove);
tableEl.bind("vmousemove", gripMove);
target.bind("vmousemove", gripMove);
target.bind("vmouseup", gripEnd);
element.bind("vmouseup", gripEnd);
tableEl.bind("vmouseup", gripEnd);
}
var gripEnd = function (evt) {
$(tableEl).removeClass('columnLines');
var target = $(evt.target);
if (drag == null) return;
// find the widest of the two columns, set them both to it
// fixes a bug when the header or table column will wrap beyond another
drag.col3.outerWidth(drag.col1.outerWidth());
drag.col4.outerWidth(drag.col2.outerWidth());
var totalW = drag.col1.outerWidth() + drag.col2.outerWidth();
// give the dom time to update
setTimeout(function () {
var w1 = drag.col1.outerWidth();
var w2 = drag.col3.outerWidth();
w1 = M.max(w1, w2);
drag.col1.outerWidth(w1);
drag.col3.outerWidth(w1);
totalW -= w1
//give the remainder of the width to the other column:
drag.col2.outerWidth(totalW);
drag.col4.outerWidth(totalW);
// sync the headers with their actual column widths
setTimeout(function () {
headers.each(function (idx) {
var col = realCols[idx];
var header = $(this);
//col.width(header.width());
header.outerWidth(col.outerWidth());
});
syncGrips();
}, 100)
drag = null;
element.unbind("vmousemove", gripMove);
tableEl.unbind("vmousemove", gripMove);
target.unbind("vmousemove", gripMove);
target.unbind("vmouseup", gripEnd);
element.unbind("vmouseup", gripEnd);
tableEl.unbind("vmouseup", gripEnd);
WBI.touchMoveHandled = false;
}, 100)
// find the widest of the two columns, set them both to it
// fixes a bug when the header or table column will wrap beyond another
/*
drag.col2.css("width",w1 + "px");
drag.col4.css("width",w1 + "px");
*/
// position the handle above the column
}
var gripMove = function (evt) {
if (!drag) return;
var target;
if (evt.touches) {
evt = (evt.touches[0]);
target = (evt.targetTouches[0]);
} else {
target = $(evt.target);
}
target = drag.target;
var delta = drag.downX - evt.pageX;
// our left position should be the detla minus the left offset
var w1 = drag.col1.w - delta;
var w2 = drag.col2.w + delta;
//w1 -= totalPaddingW(drag.col1);
//w2 -= totalPaddingW(drag.col2);
if (w1 > minWidth && w2 > minWidth) {
drag.col1.outerWidth(w1);
drag.col2.outerWidth(w2);
// drag.col1.css("width",w1 + "px");
// drag.col2.css("width",w2 + "px");
/*
drag.col3.css("width",w1 + "px");
drag.col4.css("width",w2 + "px");
// find the widest of the two columns, set them both to it
// fixes a bug when the header or table column will wrap beyond another
w1 = drag.col1.width();
w2 = drag.col3.width();
w1 = M.max(w1,w2);
drag.col1.css("width",w1 + "px");
drag.col3.css("width",w1 + "px");
// find the widest of the two columns, set them both to it
// fixes a bug when the header or table column will wrap beyond another
w1 = drag.col2.width();
w2 = drag.col4.width();
w1 = M.max(w1,w2);
drag.col2.css("width",w1 + "px");
drag.col4.css("width",w1 + "px");
*/
var pos = drag.col1.position();
var left = pos.left + drag.col1.outerWidth() - touchZoneLeftOffset;
//left += cs + brd;
drag.target.css('left', left + 'px');
}
};
element = this;
element.bind("doLayout", function (e) {
if (e.target == element[0]) {
doResize();
}
});
minWidth = 20;
var childTable = this.selector.substring(8);
tableEl = $("#" + childTable);
div = "";
headers = this.find('thead tr th');
var len = headers.length;
totalCols = len - 1;
// cache the cell spacing and border
try {
cs = I(ie ? tableEl.cellSpacing || tableEl.currentStyle.borderSpacing : tableEl.css('border-spacing')) || 2; //table cellspacing (not even jQuery is fully cross-browser)
brd = I(ie ? tableEl.border || tableEl.currentStyle.borderLeftWidth : tableEl.css('border-left-width')) || 1; //outer border width (again cross-browser isues)
} catch (ex){
cs = 2;
brd = 1;
}
var headerH = this.height() + 20;
headers.each(function (idx) {
var th = $(this)
// cache our header so we don't so this selection again;
hCols.push(th);
var selector = "tr:first td:nth-child(" + I(idx + 1) + ")"
$(tableEl).bind("wbi.contentupdated", function (e) {
if (e.target == $(tableEl)[0]) {
getRealCols();
syncColsWithHeader();
}
})
var td = $(tableEl).find(selector);
realCols.push(td);
if (idx < len - 1) {
var pos = th.position();
var top = pos.top;
var left = pos.left + th.width() - touchZoneLeftOffset;
left += cs + brd;
top += cs + brd - 5;
div = "";
var grip = $(div);
if (config){
grip.insertBefore(element);
grip.bind("vmousedown", gripDown);
}
grips.push(grip);
}
});
syncGrips();
};
})(jQuery);