/*** * 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: *
Header 1Header 2
row 1, cell 1row 1, cell 2
row 2, cell 1row 2, cell 2
row 3, cell 1row 3, cell 2
row 4, cell 1row 4, cell 2
row 5, cell 1row 5, cell 2
row 6, cell 1row 6, cell 2
row 7, cell 1row 7, cell 2
row 8, cell 1row 8, cell 2
Lastrow 8, cell 2
*/ /** 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);