├── images └── bullet.png ├── table-scroll.jquery.json ├── README.md ├── demo.css ├── demo.js ├── table-scroll.min.js ├── LICENSE ├── demo.html └── table-scroll.js /images/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/volodymyr-bobko/table-scroll/HEAD/images/bullet.png -------------------------------------------------------------------------------- /table-scroll.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "table-scroll", 3 | "title": "jQuery Table Scroll", 4 | "description": "jQuery.table_scroll plugin adds vertical and horizontal scrollbars to HTML table element.", 5 | "keywords": [ 6 | "scrolling", 7 | "fixed-columns", 8 | "scroll", 9 | "table", 10 | "touch-screen" 11 | ], 12 | "version": "2.0.4", 13 | "author": { 14 | "name": "Volodymyr Bobko", 15 | "url": "http://icocentre.com" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "GPL-2.0", 20 | "url": "http://opensource.org/licenses/GPL-2.0" 21 | } 22 | ], 23 | "bugs": "https://github.com/vol-bob/table-scroll/issues", 24 | "homepage": "https://github.com/vol-bob/table-scroll", 25 | "demo" : "http://table-scroll.site90.com/", 26 | "docs": "https://github.com/vol-bob/table-scroll/wiki", 27 | "download": "https://codeload.github.com/vol-bob/table-scroll/zip/master", 28 | "dependencies": { 29 | "jquery": ">=2.0", 30 | "jquery-ui": ">=1.10" 31 | } 32 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Table Scroll 2 | 3 | Try it here - http://volodymyr-bobko.github.io/table-scroll/ 4 | 5 | jQuery.table_scroll plugin adds vertical and horizontal scrollbars to HTML table element. 6 | 7 | Features 8 | Vertical scrolling. 9 | Horizontal scrolling with possibility to specify left and right fixed columns. 10 | Touch screen support. 11 | Auto detect vertical scrollable area and excluds thead and tfoot. 12 | Doesn't clone table elements - so your events stay bound. 13 | Doesn't divide your table into separate parts for scrolling, it means that width of column header is always in sync with cells width. 14 | 15 | API 16 | 17 | Options: 18 | rowsInHeader - Default: 1. Number of rows in table header. If table has thead element defined, this option should not be specified. 19 | rowsInFooter - Default: 0. Number of rows in table footer. If table has tfoot element defined, this option should not be specified. 20 | rowsInScrollableArea - Default: 10. Number of rows that remains visible in scrollable area. 21 | overflowY - Default: 'auto'. Possible values 'scroll', 'auto'. 22 | 'auto' - Scroll appears only if overflowing rows exists. 23 | 'scroll' - Scroll is always visible, and will be disabled if there are no overflowing rows. 24 | fixedColumnsLeft - Default: 0. Number of columns at the left side of scrollable area that will not be scrolled. 25 | fixedColumnsRight - Default: 0. Number of columns at the right side of scrollable area that will not be scrolled. 26 | columnsInScrollableArea - Default: 5. 27 | overflowX - Default: 'auto'. Possible values 'scroll', 'auto'. 28 | 'auto' - Scroll appears only if overflowing columns exists. 29 | 'scroll' - Scroll is always visible, and will be disabled if there are no overflowing columns. 30 | -------------------------------------------------------------------------------- /demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: normal .80em 'trebuchet ms', arial, sans-serif; 3 | background: #F5F5EE; 4 | color: #555; 5 | margin: 20px; 6 | } 7 | 8 | code { 9 | font-size: 17px; 10 | } 11 | 12 | h1>code { 13 | font-size: inherit; 14 | } 15 | 16 | h1, h2, h3, h4, h5, h6 { 17 | color: #000; 18 | font-weight: normal; 19 | } 20 | 21 | ul>li { 22 | list-style-type: none; 23 | background: url(images/bullet.png) no-repeat; 24 | margin: 0 0 0 0; 25 | padding: 0 0 4px 25px; 26 | line-height: 1.5em; 27 | } 28 | 29 | .semple>thead>tr>td { 30 | align-content: center; 31 | text-align: center; 32 | } 33 | 34 | .semple>thead>tr:first-child { 35 | color: #1a727c; 36 | } 37 | 38 | .semple tbody td { 39 | vertical-align: top; 40 | } 41 | 42 | .section { 43 | margin-top: 40px; 44 | margin-left: 30px; 45 | } 46 | 47 | .semple-section { 48 | -webkit-border-radius: 23px; 49 | -moz-border-radius: 23px; 50 | border-radius: 23px; 51 | background: white; 52 | border: 1px solid gray; 53 | min-width: 1400px 54 | } 55 | 56 | .semple>tbody>tr>td { 57 | padding: 10px; 58 | border-left: 3px solid #555; 59 | } 60 | 61 | .semple>tbody>tr>td:first-child { 62 | border-left: none; 63 | } 64 | 65 | .semple-section>h2 { 66 | text-align: center; 67 | margin-top: 7px; 68 | margin-bottom: 10px; 69 | } 70 | 71 | .semple-section pre { 72 | border: none; 73 | } 74 | 75 | .inner-table { 76 | border-collapse:collapse; 77 | } 78 | 79 | .inner-table>thead>tr>td { 80 | border: 1px solid black; 81 | padding:5px; 82 | vertical-align: middle; 83 | font-weight:bold; 84 | } 85 | 86 | .inner-table>thead>tr>td[colspan] { 87 | text-align:center; 88 | } 89 | 90 | .inner-table>tbody>tr>td { 91 | color: #777; 92 | border-bottom: 1px solid #ccc; 93 | padding: 5px; 94 | } 95 | 96 | .inner-table>tbody>tr:hover>td { 97 | 98 | } 99 | 100 | .inner-table>tfoot>tr>td { 101 | border: 1px solid black; 102 | padding:5px; 103 | vertical-align: middle; 104 | font-weight:bold; 105 | } 106 | 107 | .inner-table>tbody>tr { 108 | border-right: 1px solid black; 109 | border-left: 1px solid black; 110 | } -------------------------------------------------------------------------------- /demo.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | 3 | var rows = []; 4 | for (var i = 0; i < 100; i++) { 5 | rows.push({ number: i + 1 }); 6 | } 7 | 8 | var semple1Html = Mustache.to_html($('#tamplate-semple-1').html(), getFixedColumnsData()); 9 | $(semple1Html).appendTo($('#holder-semple-1')).table_scroll({ 10 | fixedColumnsLeft: 3, 11 | fixedColumnsRight: 1, 12 | columnsInScrollableArea: 3, 13 | scrollX: 5, 14 | scrollY: 10 15 | }); 16 | 17 | var semple2Html = Mustache.to_html($('#tamplate-semple-2').html(), { rows: rows }); 18 | $(semple2Html).appendTo($('#holder-semple-2')).table_scroll(); 19 | 20 | var semple3Html = Mustache.to_html($('#tamplate-semple-3').html(), { rows: rows }); 21 | $(semple3Html).appendTo($('#holder-semple-3')).table_scroll({ 22 | rowsInFooter: 1, 23 | }); 24 | 25 | var semple4Html = Mustache.to_html($('#tamplate-semple-4').html(), { rows: rows }); 26 | $(semple4Html).appendTo($('#holder-semple-4')).table_scroll(); 27 | 28 | }); 29 | 30 | function getFixedColumnsData() { 31 | var fixedColumnsData = []; 32 | var columnsCaptions = []; 33 | var footers = []; 34 | 35 | for (var j = 2013; j < 2014; j++) { 36 | for (var k = 0; k < months.length; k++) { 37 | columnsCaptions.push(months[k] + ' ' + j); 38 | } 39 | } 40 | 41 | for (var i = 0; i < authors.length; i++) { 42 | 43 | var data = []; 44 | 45 | for (var j = 2013; j < 2014; j++) { 46 | for (var k = 0; k < months.length; k++) { 47 | data.push(Math.floor((Math.random() * 500) + 10)); 48 | } 49 | } 50 | 51 | var row = { 52 | Index: i, 53 | FirstName: authors[i].FirstName, 54 | LastName: authors[i].LastName, 55 | data: data, 56 | Price: authors[i].Price, 57 | }; 58 | fixedColumnsData.push(row); 59 | } 60 | 61 | for (var j = 2013; j < 2014; j++) { 62 | for (var k = 0; k < months.length; k++) { 63 | 64 | var total = 0; 65 | for (var i = 0; i < authors.length; i++) { 66 | total += fixedColumnsData[i].data[(j - 2012) * k]; 67 | } 68 | 69 | footers.push(total); 70 | } 71 | } 72 | 73 | return { 74 | columnsCount: columnsCaptions.length, 75 | data: fixedColumnsData, 76 | columns: columnsCaptions, 77 | footers: footers 78 | }; 79 | } 80 | 81 | // data 82 | var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] 83 | 84 | var authors = [ 85 | { "FirstName": "David", "LastName": "Flanagan", "Price": 31.49 }, 86 | { "FirstName": "Douglas", "LastName": "Crockford", "Price": 19.79 }, 87 | { "FirstName": "John", "LastName": "Pollock", "Price": 26.39 }, 88 | { "FirstName": "David", "LastName": "Sawyer", "Price": 26.39 }, 89 | { "FirstName": "Nicholas", "LastName": "Zakas", "Price": 31.49 }, 90 | { "FirstName": "Michael", "LastName": "Morrison", "Price": 26.39 }, 91 | { "FirstName": "Ray", "LastName": "Harris", "Price": 34.34 }, 92 | { "FirstName": "Stoyan", "LastName": "Stefanov", "Price": 31.57 }, 93 | { "FirstName": "Steve", "LastName": "Suehring", "Price": 26.39 }, 94 | { "FirstName": "Shelley", "LastName": "Powers", "Price": 23.09 }, 95 | { "FirstName": "James", "LastName": "Keogh", "Price": 14.93 }, 96 | { "FirstName": "John", "LastName": "Resig", "Price": 32.03 }, 97 | { "FirstName": "Danny", "LastName": "Goodman", "Price": 29.69 }, 98 | { "FirstName": "Tom", "LastName": "Negrino", "Price": 23.09 }, 99 | { "FirstName": "John", "LastName": "Pollock", "Price": 18.96 }, 100 | { "FirstName": "Nicholas", "LastName": "Zakas", "Price": 23.09 }, 101 | { "FirstName": "Ross", "LastName": "Harmes", "Price": 29.69 }, 102 | { "FirstName": "John", "LastName": "Resig", "Price": 26.39 }, 103 | { "FirstName": "Danny", "LastName": "Goodman", "Price": 31.49 }, 104 | { "FirstName": "Jeremy", "LastName": "Keith", "Price": 26 }, 105 | { "FirstName": "David", "LastName": "Flanagan", "Price": 9.95 }, 106 | { "FirstName": "Paul", "LastName": "Wilton", "Price": 26.39 }, 107 | { "FirstName": "Kevin", "LastName": "Yank", "Price": 26.37 }, 108 | { "FirstName": "Andrew", "LastName": "Stellman", "Price": 49.99 }, 109 | { "FirstName": "Peter", "LastName": "Bako", "Price": 24.95 }, 110 | { "FirstName": "Peter", "LastName": "Bako", "Price": 9.99 }, 111 | { "FirstName": "Andrew", "LastName": "Troelsen", "Price": 59.99 }, 112 | { "FirstName": "Daniel", "LastName": "Solis", "Price": 49.99 }, 113 | { "FirstName": "John", "LastName": "Sharp", "Price": 22.79 }, 114 | { "FirstName": "Bill", "LastName": "Sempf", "Price": 44.99 }, 115 | { "FirstName": "Paul", "LastName": "Deitel", "Price": 39.99 }, 116 | { "FirstName": "Karli", "LastName": "Watson", "Price": 36.64 }, 117 | { "FirstName": "Joseph", "LastName": "Albahari", "Price": 26.09 }, 118 | { "FirstName": "Ian", "LastName": "Griffiths", "Price": 49.99 }, 119 | { "FirstName": "Robert", "LastName": "Martin", "Price": 38.75 }, 120 | { "FirstName": "Robert", "LastName": "Daigneau", "Price": 38.37 }, 121 | { "FirstName": "Dino", "LastName": "Esposito", "Price": 39.78 }, 122 | { "FirstName": "Jon", "LastName": "Arking", "Price": 31.67 }, 123 | { "FirstName": "Mark", "LastName": "Seemann", "Price": 31.85 }, 124 | { "FirstName": "Roy", "LastName": "Osherove", "Price": 25.04 }, 125 | { "FirstName": "James", "LastName": "Bender", "Price": 28.89 }, 126 | { "FirstName": "Erich", "LastName": "Gamma", "Price": 43.58 }, 127 | { "FirstName": "Martin", "LastName": "Fowler", "Price": 44.31 }, 128 | { "FirstName": "Scott", "LastName": "Millett", "Price": 30.85 }, 129 | { "FirstName": "Martin", "LastName": "Fowler", "Price": 49.92 }, 130 | { "FirstName": "Krzysztof", "LastName": "Cwalina", "Price": 43.48 }, 131 | { "FirstName": "Andrew", "LastName": "Troelsen", "Price": 33.48 }, 132 | { "FirstName": "Julia", "LastName": "Lerman", "Price": 35.55 }, 133 | { "FirstName": "Juval", "LastName": "Lowy", "Price": 33.08 }, 134 | { "FirstName": "Adam", "LastName": "Freeman", "Price": 29 }, 135 | { "FirstName": "Stephen", "LastName": "Ritchie", "Price": 42.91 }, 136 | { "FirstName": "Richard", "LastName": "Kiessig", "Price": 28.72 }, 137 | { "FirstName": "Eric", "LastName": "Evans", "Price": 47.85 }, 138 | { "FirstName": "Tony", "LastName": "Northrup", "Price": 39.36 }, 139 | { "FirstName": "Jeffrey", "LastName": "Richter", "Price": 39.36 }, 140 | { "FirstName": "Jon", "LastName": "Skeet", "Price": 29.33 }, 141 | { "FirstName": "Jeffrey", "LastName": "Richter", "Price": 36.21 }, 142 | { "FirstName": "Robert", "LastName": "Martin", "Price": 51.74 }, 143 | { "FirstName": "Steven", "LastName": "Sanderson", "Price": 34.28 }, 144 | { "FirstName": "Joseph", "LastName": "Albahari", "Price": 31.32 }, 145 | { "FirstName": "Jon", "LastName": "Galloway", "Price": 24.58 }, 146 | { "FirstName": "Tony", "LastName": "Northrup", "Price": 23.77 }, 147 | { "FirstName": "Steve", "LastName": "McConnell", "Price": 27.89 }, 148 | { "FirstName": "Dino", "LastName": "Esposito", "Price": 25.42 }, 149 | { "FirstName": "Bill", "LastName": "Wagner", "Price": 21.32 }, 150 | { "FirstName": "Julia", "LastName": "Lerman", "Price": 22.49 }, 151 | { "FirstName": "Elisabeth", "LastName": "Freeman", "Price": 27.89 }, 152 | { "FirstName": "Michele", "LastName": "Bustamante", "Price": 28.61 }, 153 | { "FirstName": "Dino", "LastName": "Esposito", "Price": 28.88 }, 154 | { "FirstName": "Glenn", "LastName": "Johnson", "Price": 43.97 }, 155 | { "FirstName": "Dan", "LastName": "Clark", "Price": 28.72 }, 156 | { "FirstName": "John", "LastName": "Sharp", "Price": 36.63 }, 157 | { "FirstName": "Paolo", "LastName": "Pialorsi", "Price": 33.84 }, 158 | { "FirstName": "Adam", "LastName": "Nathan", "Price": 33.74 } 159 | ]; -------------------------------------------------------------------------------- /table-scroll.min.js: -------------------------------------------------------------------------------- 1 | !function (t) { var o = "_sg_index_", s = "_sg_adj_"; t.widget("ui.table_scroll", { version: "1.0.0", options: { rowsInHeader: null, rowsInFooter: null, fixedColumnsLeft: 0, fixedColumnsRight: 0, scrollX: 0, scrollY: 0, rowsInScrollableArea: 10, columnsInScrollableArea: 5, overflowY: "auto", overflowX: "auto" }, _create: function () { this._columnsCount = -1, this._currentTouch = null, this._ensureSettings(), this.startFrom = 0, this._setActualCellIndexes(), this._yInitScroll(), this._yUpdateRowsVisibility(), this._xInitScroll(), this._xUpdateColumnsVisibility(), this._yUpdateScrollHeights(), this.widget().on("mousewheel", t.proxy(this._tableMouseWheel, this)), this.widget().on("DOMMouseScroll", t.proxy(this._tableMouseWheel, this)), this.widget().on("touchstart", t.proxy(this._touchStart, this)), this.widget().on("touchmove", t.proxy(this._touchMove, this)), this.widget().on("touchend", t.proxy(this._touchEnd, this)), this._xMoveScroll(this.options.scrollX), this._yMoveScroll(this.options.scrollY), this._yUpdateRowsVisibility(), this._xUpdateColumnsVisibility() }, _ensureSettings: function () { null == this.options.rowsInHeader && (this.options.rowsInHeader = this._table().tHead ? this._table().tHead.rows.length : 1), null == this.options.rowsInFooter && (this.options.rowsInFooter = this._table().tFoot ? this._table().tFoot.rows.length : 0) }, _xGetNumberOfColumns: function () { return -1 != this._columnsCount ? this._columnsCount : (this._columnsCount = Math.max.apply(null, t(this._table().rows).map(function () { return this.cells.length }).get()), t(".sg-v-scroll-cell", this.widget()).length > 0 && (this._columnsCount -= 1), this._columnsCount) }, _xNumberOfScrollableColumns: function () { var t = this._xGetNumberOfColumns() - this.options.fixedColumnsLeft - this.options.fixedColumnsRight; return 1 > t ? 1 : t }, _xScrollWidth: function () { var t = this._xGetNumberOfColumns() - this.options.fixedColumnsLeft - this.options.fixedColumnsRight; return t > this.options.columnsInScrollableArea ? this.options.columnsInScrollableArea : 1 > t ? 1 : t }, _xScrollNeeded: function () { var t = this._xGetNumberOfColumns() - this.options.fixedColumnsLeft - this.options.fixedColumnsRight; return t > this.options.columnsInScrollableArea }, _xInitScroll: function () { if (!(this._xGetNumberOfColumns() < this.options.fixedColumnsLeft + this.options.fixedColumnsRight || !this._xScrollNeeded() && "scroll" != this.options.overflowX)) { var o = this._table().insertRow(this._table().rows.length); if (this.options.fixedColumnsLeft > 0) { var s = t(o.insertCell(0)); s.attr("colspan", this.options.fixedColumnsLeft) } var i = t(o.insertCell(1)); i.attr("colspan", this._xScrollWidth()), i.addClass("sg-x-scroll-cell"); var l = t('
'); l.css("overflow-x", "scroll"), l.css("margin-right", "-20000px"), l.width(i.width()); var e = t('
'); if (e.width(this._xNumberOfScrollableColumns() / this._xScrollWidth() * i.width()), e.appendTo(l), l.appendTo(i), l.scroll(t.proxy(this._xUpdateColumnsVisibility, this)), this.options.fixedColumnsRight > 0) { var s = t(o.insertCell(2)); s.attr("colspan", this.options.fixedColumnsRight + (t(".sg-v-scroll-cell", this.widget()).length > 0 ? 1 : 0)) } } }, _xCurrentRelativeScrollLeft: function () { var o = t(".sg-h-scroll-container", this.widget()); return o.scrollLeft() / o.width() }, _xScrollDelta: function () { var o = t(".sg-h-scroll-container", this.widget()); return t("div", o).width() - o.width() }, _xScrollableColumnsCount: function () { return this._xNumberOfScrollableColumns() - this._xScrollWidth() }, _xColumnScrollStep: function () { return 0 == this._xScrollableColumnsCount() ? 0 : this._xScrollDelta() / this._xScrollableColumnsCount() }, _xMoveScroll: function (o) { o = Math.min(this._xScrollableColumnsCount(), o), o = Math.max(o, 0), o = this._xColumnScrollStep() * o; var s = t(".sg-h-scroll-container", this.widget()); s.scrollLeft() != o && s.scrollLeft(o) }, _setColumnVisibility: function (t, s, i, l) { for (var e = this._table().rows, r = i; l > r; r++) for (var n = e[r], h = 0; h < n.cells.length; h++) { var a = n.cells[h], c = a[o]; c == t && (a.colSpan && 1 != a.colSpan || (s && "none" == a.style.display && (a.style.display = ""), s || "none" == a.style.display || (a.style.display = "none"))) } }, _xFirstVisibleColumnWidth: function () { for (var o = this.options.rowsInHeader; o < this._table().rows.length - this.options.rowsInFooter - t(".sg-h-scroll-container", this.widget()).length; o++) if ("none" != t(this._table().rows[o]).css("display")) for (var s = this.options.fixedColumnsLeft; s < this._xGetNumberOfColumns() - this.options.fixedColumnsRight; s++) if ("none" != t(this._table().rows[o].cells[s]).css("display")) return t(this._table().rows[o].cells[s]).width(); return 0 }, _xLastVisibleColumnWidth: function () { for (var o = this.options.rowsInHeader; o < this._table().rows.length - this.options.rowsInFooter - t(".sg-h-scroll-container", this.widget()).length; o++) if ("none" != t(this._table().rows[o]).css("display")) for (var s = this._xGetNumberOfColumns() - this.options.fixedColumnsRight - 1; s >= this.options.fixedColumnsLeft; s--) if ("none" != t(this._table().rows[o].cells[s]).css("display")) return t(this._table().rows[o].cells[s]).width(); return 0 }, _xUpdateColumnsVisibility: function () { if (this._xScrollNeeded()) { for (var o = t(".sg-h-scroll-container", this.widget()), s = Math.floor(o.scrollLeft() / this._xColumnScrollStep()), i = (this._xCurrentRelativeScrollLeft(), this.options.fixedColumnsLeft) ; i < this._xGetNumberOfColumns() - this.options.fixedColumnsRight; i++) { var l = !1; i >= this.options.fixedColumnsLeft + s && i < this.options.fixedColumnsLeft + s + this.options.columnsInScrollableArea && (l = !0), this._setColumnVisibility(i, l, 0, this._table().rows.length - 1) } this._xUpdateScrollWidths() } }, _xUpdateScrollWidths: function () { var o = t(".sg-h-scroll-container", this.widget()), s = o.closest("td"); o.width(s.width()); var i = t("div", o); i.width(this._xNumberOfScrollableColumns() / this._xScrollWidth() * s.width()) }, _yScrollHeight: function () { var o = this._table().rows.length - this.options.rowsInHeader - this.options.rowsInFooter; return t(".sg-h-scroll-container", this.widget()).length > 0 && o--, o > this.options.rowsInScrollableArea ? this.options.rowsInScrollableArea : 1 > o ? 1 : o }, _yNumberOfScrollableRows: function () { var o = this._table().rows.length - this.options.rowsInHeader - this.options.rowsInFooter; return t(".sg-h-scroll-container", this.widget()).length > 0 && o--, 1 > o ? 1 : o }, _yScrollNeeded: function () { var o = this._table().rows.length - this.options.rowsInHeader - this.options.rowsInFooter; return t(".sg-h-scroll-container", this.widget()).length > 0 && o--, o > this.options.rowsInScrollableArea }, _yInitScroll: function () { if (!(this._table().rows.length < this.options.rowsInHeader + this.options.rowsInFooter || !this._yScrollNeeded() && "scroll" != this.options.overflowY)) { var o = t(this._table().rows[0].insertCell(this._table().rows[0].cells.length)); o.attr("rowspan", this.options.rowsInHeader); var s = t(this._table().rows[this.options.rowsInHeader + this.startFrom].insertCell(this._table().rows[this.options.rowsInHeader + this.startFrom].cells.length)); s.attr("rowspan", this._yScrollHeight()), s.attr("width", "1px"), s.addClass("sg-v-scroll-cell"); var i = t('
'); i.css("overflow-y", "scroll"), i.height(s.height()); var l = t('
'); if (l.height(this._yNumberOfScrollableRows() / this._yScrollHeight() * s.height()), l.appendTo(i), i.appendTo(s), this._attachToEndScrolling(i, t.proxy(this._yUpdateRowsVisibility, this)), 0 != this.options.rowsInFooter) { var e = this._table().rows[this._yNumberOfScrollableRows() + this.options.rowsInHeader], r = t(e.insertCell(e.cells.length)); r.attr("rowspan", this.options.rowsInFooter) } } }, _yCurrentRelativeScrollTop: function () { var o = t(".sg-v-scroll-container", this.widget()); return o.scrollTop() / o.height() }, _yMoveScrollToRightRow: function (o) { var s = t(".sg-v-scroll-cell", this.widget()).closest("tr").get(0), i = this._table().rows[this.options.rowsInHeader + this.startFrom], l = t(".sg-v-scroll-container", this.widget()), e = t("div", l); if (s != i) { var r = t(i.insertCell(i.cells.length)); r.attr("rowspan", this._yScrollHeight()), r.addClass("sg-v-scroll-cell"), r.attr("width", "1px"); var n = t(".sg-v-scroll-container", t(s)); n.height(0), n.appendTo(r), s.deleteCell(s.cells.length - 1), l.height(r.height()), e.height(this._yNumberOfScrollableRows() / this._yScrollHeight() * r.height()), l.scrollTop(o * l.height()), l.get(0) } }, _yScrollDelta: function () { var o = t(".sg-v-scroll-container", this.widget()); return t("div", o).height() - o.height() }, _yScrollableRowsCount: function () { return this._yNumberOfScrollableRows() - this._yScrollHeight() }, _yRowScrollStep: function () { return 0 == this._yScrollableRowsCount() ? 0 : this._yScrollDelta() / this._yScrollableRowsCount() }, _yMoveScroll: function (o) { o = Math.min(this._yScrollableRowsCount(), o), o = Math.max(o, 0); var s = this._yRowScrollStep(); o = s * o; var i = t(".sg-v-scroll-container", this.widget()); i.scrollTop() != o && i.scrollTop(o + s / 2) }, _yUpdateScrollHeights: function () { var o = t(".sg-v-scroll-container", this.widget()), s = o.closest("td"); o.hide(), o.height(s.height()); var i = t("div", o); i.height(this._yNumberOfScrollableRows() / this._yScrollHeight() * s.height()), o.show() }, _yFirstVisibleRowHeight: function () { for (var o = this.options.rowsInHeader; o < this._table().rows.length - this.options.rowsInFooter - t(".sg-h-scroll-container", this.widget()).length; o++) if ("none" != t(this._table().rows[o]).css("display")) return t(this._table().rows[o]).height(); return 0 }, _yLastVisibleRowHeight: function () { for (var o = this._table().rows.length - this.options.rowsInFooter - t(".sg-h-scroll-container", this.widget()).length - 1; o >= this.options.rowsInHeader; o--) if ("none" != t(this._table().rows[o]).css("display")) return t(this._table().rows[o]).height(); return 0 }, _yUpdateRowsVisibility: function () { if (this._yScrollNeeded()) { for (var o = t(".sg-v-scroll-container", this.widget()), s = Math.floor(o.scrollTop() / this._yRowScrollStep()), i = this._yCurrentRelativeScrollTop(), l = this.options.rowsInHeader; l < this._table().rows.length - this.options.rowsInFooter - t(".sg-h-scroll-container", this.widget()).length; l++) { var e = !1; l >= this.options.rowsInHeader + s && l < this.options.rowsInHeader + s + this.options.rowsInScrollableArea && (e = !0), e ? t(this._table().rows[l]).show() : t(this._table().rows[l]).hide() } this.startFrom != s && (this.startFrom = s, this._yMoveScrollToRightRow(i)) } }, _attachToEndScrolling: function (o, s) { o.scroll(function () { clearTimeout(o.data("scrollTimer")), t.data(this, "scrollTimer", setTimeout(function () { s.apply(this) }, 300)) }) }, _tableMouseWheel: function (o) { var s = !1, i = !1, l = o.originalEvent; l.wheelDelta && (l.wheelDelta >= 120 ? s = !0 : l.wheelDelta <= -120 && (i = !0)), l.detail && (-3 == l.detail ? s = !0 : 3 == l.detail && (i = !0)); var e = t(".sg-v-scroll-container", this.widget()), r = 0; s && (r = this._yRowScrollStep() + 1), i && (r = -this._yRowScrollStep() - 1), 0 != r && e.scrollTop(e.scrollTop() - r), o.preventDefault() }, _touchStart: function (t) { if (t.originalEvent.touches && 1 == t.originalEvent.touches.length) { var o = t.originalEvent.touches[0] || t.originalEvent.changedTouches[0]; this._currentTouch = { X: o.pageX, Y: o.pageY }, t.preventDefault(), t.stopPropagation() } }, _touchMove: function (o) { if (o.originalEvent.touches && 1 == o.originalEvent.touches.length && null != this._currentTouch) { var s = o.originalEvent.touches[0] || o.originalEvent.changedTouches[0], i = { X: s.pageX, Y: s.pageY }, l = this._currentTouch.X - i.X, e = this._currentTouch.Y - i.Y, r = t(".sg-v-scroll-container", this.widget()); if (e > 0) { var n = this._yFirstVisibleRowHeight(); 0 != n && e > n && (r.scrollTop(r.scrollTop() + (this._yRowScrollStep() + 1)), this._currentTouch.Y -= n, this._yUpdateRowsVisibility()) } else { var n = this._yLastVisibleRowHeight(); 0 != n && -1 * n > e && (r.scrollTop(r.scrollTop() - (this._yRowScrollStep() + 1)), this._currentTouch.Y += n, this._yUpdateRowsVisibility()) } var h = t(".sg-h-scroll-container", this.widget()); if (l > 0) { var a = this._xFirstVisibleColumnWidth(); 0 != a && l > a && (h.scrollLeft(h.scrollLeft() + (this._xColumnScrollStep() + 1)), this._currentTouch.X -= n) } else { var a = this._xLastVisibleColumnWidth(); 0 != a && -1 * a > l && (h.scrollLeft(h.scrollLeft() - (this._xColumnScrollStep() + 1)), this._currentTouch.X += a) } o.preventDefault(), o.stopPropagation() } }, _touchEnd: function () { this._currentTouch = null }, _table: function () { return this.widget().get(0) }, _setActualCellIndexes: function () { for (var i = this._table().rows, l = 0; l < i.length; l++) { var e = i[l], r = t(e).get(0)[s]; r || (r = []); for (var n = 0; n < e.cells.length; n++) { var h = n - 1; if (n > 0) { var a = t(e.cells[n - 1]); h = a.get(0)[o], a.attr("colspan") && (h += this._getColSpan(a) - 1) } for (var c = t(e.cells[n]), u = h + 1, d = 0; d < r.length; d++) r[d].index <= u && (u += r[d].adjustment, r[d].adjustment = 0); if (c.get(0)[o] = u, c.attr("rowspan") > 1) for (var g = c.attr("rowspan"), _ = l + 1; l + g > _ && _ < i.length; _++) { var p = t(i[_]), w = p.get(0)[s]; w || (w = []), w.push({ index: u, adjustment: this._getColSpan(c) }), p.get(0)[s] = w } } } }, _getColSpan: function (t) { return t.data("scroll-span") ? t.data("scroll-span") : t.attr("colspan") ? 1 * t.attr("colspan") : 1 } }) }(jQuery); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Table Scroll jQuery plugin 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |

Fork on GitHub

25 |
26 | 27 |
28 |

Table Scroll

29 | jQuery.table_scroll plugin adds vertical and horizontal scrollbars to HTML table element. 30 |
31 | 32 |
33 |

Features

34 | 42 |
43 | 44 |
45 |

API

46 | 47 |

Options:

48 | 67 |
68 | 69 |
70 |

Example 1 - floating thead, tfoot, and fixed columns.

71 |
72 | 73 | 74 | 75 | 78 | 81 | 84 | 85 | 86 | 87 | 88 | 132 | 143 | 185 | 186 | 187 |
76 | Demo 77 | 79 | JabvaScript 80 | 82 | HTML 83 |
89 | 131 | 133 |
134 | $('table').table_scroll({
135 |     fixedColumnsLeft: 3,
136 |     fixedColumnsRight: 1,
137 |     columnsInScrollableArea: 3,
138 |     scrollX: 5,
139 |     scrollY: 10
140 | });
141 | 
142 |
144 |
145 | <table>
146 |     <thead>
147 |         <tr>
148 |             <td rowspan="2"></td>
149 |             <td colspan="2">Author</td>
150 |             <td colspan="3" data-scroll-span="12">Periods</td>
151 |             <td rowspan="2">Price</td>
152 |         </tr>
153 |         <tr>
154 |             <td rowspan="2">First Name</td>
155 |             <td rowspan="2">Last Name</td>
156 |             
157 |             <td>Jan 2013</td>
158 |             ...
159 |             <td>Dec 2013</td>
160 |         </tr>
161 |     </thead>
162 |     <tbody>
163 |         <tr>
164 |             <td>First Name</td>
165 |             <td>Last Name</td>
166 |             <td>0000</td>
167 |             ...
168 |             <td>0000</td>
169 |             <td>$00.00</td>
170 |         </tr>
171 |         ...
172 |     </tbody>
173 |     <tfoot>
174 |         <tr>
175 |             <td></td>
176 |             <td colspan="2">Total</td>
177 |             <td>0000</td>
178 |             ...
179 |             <td>0000</td>
180 |             <td></td>
181 |         </tr>
182 |     </tfoot>
183 | </table>
184 |
188 |
189 | 190 |
191 |

Example 2 - floating thead and tfoot.

192 |
193 | 194 | 195 | 196 | 199 | 202 | 205 | 206 | 207 | 208 | 209 | 241 | 246 | 282 | 283 | 284 |
197 | Demo 198 | 200 | JabvaScript 201 | 203 | HTML 204 |
210 | 240 | 242 |
243 | $('table').table_scroll();
244 | 
245 |
247 |
248 | <table>
249 |     <thead>
250 |         <tr>
251 |             <td>Column 1</td>
252 |             <td>Column 2</td>
253 |             <td>Column 3</td>
254 |             <td>Column 4</td>
255 |         </tr>
256 |     </thead>
257 |     <tbody>
258 |         <tr>
259 |             <td>Cell 1 1</td>
260 |             <td>Cell 1 2</td>
261 |             <td>Cell 1 3</td>
262 |             <td>Cell 1 4</td>
263 |         </tr>
264 |         <tr>
265 |             <td>Cell 2 1</td>
266 |             <td>Cell 2 2</td>
267 |             <td>Cell 2 3</td>
268 |             <td>Cell 2 4</td>
269 |         </tr>
270 |         ...    
271 |     </tbody>
272 |     <tfoot>
273 |         <tr>
274 |             <td>Footer 1</td>
275 |             <td>Footer 2</td>
276 |             <td>Footer 3</td>
277 |             <td>Footer 4</td>
278 |         </tr>
279 |     </tfoot>
280 | </table>
281 |
285 |
286 | 287 |
288 |

Example 3 - floating header and footer for table that has tbody only.

289 |
290 | 291 | 292 | 293 | 296 | 299 | 302 | 303 | 304 | 305 | 306 | 332 | 338 | 368 | 369 | 370 |
294 | Demo 295 | 297 | JabvaScript 298 | 300 | HTML 301 |
307 | 331 | 333 |
$('table').table_scroll({
334 |     rowsInFooter: 1
335 | });
336 | 
337 |
339 |
340 | <table>
341 |     <tr>
342 |         <td>Column 1</td>
343 |         <td>Column 2</td>
344 |         <td>Column 3</td>
345 |         <td>Column 4</td>
346 |     </tr>
347 |     <tr>
348 |         <td>Cell 1 1</td>
349 |         <td>Cell 1 2</td>
350 |         <td>Cell 1 3</td>
351 |         <td>Cell 1 4</td>
352 |     </tr>
353 |     <tr>
354 |         <td>Cell 2 1</td>
355 |         <td>Cell 2 2</td>
356 |         <td>Cell 2 3</td>
357 |         <td>Cell 2 4</td>
358 |     </tr>
359 |     ...
360 |     <tr>
361 |         <td>Footer 1</td>
362 |         <td>Footer 2</td>
363 |         <td>Footer 3</td>
364 |         <td>Footer 4</td>
365 |     </tr>
366 | </table>
367 |
371 |
372 | 373 |
374 |

Example 4 - multi row thead and tfoot.

375 |
376 | 377 | 378 | 379 | 382 | 385 | 388 | 389 | 390 | 391 | 392 | 438 | 443 | 493 | 494 | 495 |
380 | Demo 381 | 383 | JabvaScript 384 | 386 | HTML 387 |
393 | 437 | 439 |
440 | $('table').table_scroll();
441 | 
442 |
444 |
445 | <table>
446 |     <thead>
447 |         <tr>
448 |             <td colspan="2">Column 1 - 2</td>
449 |             <td>Column 3</td>
450 |             <td rowspan="3">Column 4</td>
451 |         </tr>
452 |         <tr>
453 |             <td>Column 1</td>
454 |             <td colspan="2">Column 2 - 3</td>
455 |         </tr>
456 |         <tr>
457 |             <td colspan="2" >Column 1 - 2</td>
458 |             <td>Column 3</td>
459 |         </tr>
460 |     </thead>
461 |     <tbody>
462 |         <tr>
463 |             <td>Cell 1 1</td>
464 |             <td>Cell 1 2</td>
465 |             <td>Cell 1 3</td>
466 |             <td>Cell 1 4</td>
467 |         </tr>
468 |         <tr>
469 |             <td>Cell 2 1</td>
470 |             <td>Cell 2 2</td>
471 |             <td>Cell 2 3</td>
472 |             <td>Cell 2 4</td>
473 |         </tr>
474 |         ...    
475 |     </tbody>
476 |     <tfoot>
477 |         <tr>
478 |             <td rowspan="3">Footer 1</td>
479 |             <td colspan="2">Footer 2 - 3</td>
480 |             <td rowspan="2">Footer 4</td>
481 |         </tr>
482 |         <tr>
483 |             <td>Footer 2</td>
484 |             <td>Footer 3</td>
485 |         </tr>
486 |         <tr>
487 |             <td>Footer 2</td>
488 |             <td colspan="2">Footer 3 - 4</td>
489 |         </tr>
490 |     </tfoot>
491 | </table>
492 |
496 |
497 | 498 | 499 | 500 | -------------------------------------------------------------------------------- /table-scroll.js: -------------------------------------------------------------------------------- 1 | (function ($, undefined) { 2 | 3 | var CELL_INDEX_DATA = '_sg_index_'; 4 | var CELL_SPAN_ADJUSTMENTS = '_sg_adj_'; 5 | 6 | $.widget("ui.table_scroll", { 7 | version: "1.0.0", 8 | options: 9 | { 10 | rowsInHeader: null, 11 | rowsInFooter: null, 12 | 13 | fixedColumnsLeft: 0, 14 | fixedColumnsRight: 0, 15 | 16 | scrollX: 0, 17 | scrollY: 0, 18 | 19 | rowsInScrollableArea: 10, 20 | columnsInScrollableArea: 5, 21 | 22 | overflowY: 'auto', /*scroll, auto*/ 23 | overflowX: 'auto', /*scroll, auto*/ 24 | }, 25 | 26 | _create: function () { 27 | this._columnsCount = -1; 28 | this._currentTouch = null; 29 | 30 | this._ensureSettings(); 31 | 32 | this.startFrom = 0; 33 | 34 | this._setActualCellIndexes(); 35 | 36 | this._yInitScroll(); 37 | this._yUpdateRowsVisibility(); 38 | 39 | this._xInitScroll(); 40 | this._xUpdateColumnsVisibility(); 41 | 42 | this._yUpdateScrollHeights(); 43 | 44 | this.widget().on("mousewheel", $.proxy(this._tableMouseWheel, this)); 45 | this.widget().on("DOMMouseScroll", $.proxy(this._tableMouseWheel, this)); // Firefox 46 | this.widget().on('touchstart', $.proxy(this._touchStart, this)); 47 | this.widget().on('touchmove', $.proxy(this._touchMove, this)); 48 | this.widget().on('touchend', $.proxy(this._touchEnd, this)); 49 | 50 | this._xMoveScroll(this.options.scrollX); 51 | this._yMoveScroll(this.options.scrollY); 52 | this._yUpdateRowsVisibility(); 53 | this._xUpdateColumnsVisibility(); 54 | }, 55 | 56 | _ensureSettings: function() { 57 | if (this.options.rowsInHeader == null) { 58 | 59 | if (this._table().tHead) 60 | this.options.rowsInHeader = this._table().tHead.rows.length; 61 | else 62 | this.options.rowsInHeader = 1; 63 | } 64 | 65 | if (this.options.rowsInFooter == null) { 66 | if (this._table().tFoot) 67 | this.options.rowsInFooter = this._table().tFoot.rows.length; 68 | else 69 | this.options.rowsInFooter = 0; 70 | } 71 | }, 72 | 73 | // horisontal scrolling methods 74 | _xGetNumberOfColumns: function () { 75 | if (this._columnsCount != -1) 76 | return this._columnsCount; 77 | 78 | this._columnsCount = Math.max.apply(null, $(this._table().rows).map(function () { return this.cells.length; }).get()); 79 | 80 | if ($('.sg-v-scroll-cell', this.widget()).length > 0) 81 | this._columnsCount -= 1; 82 | 83 | return this._columnsCount; 84 | }, 85 | 86 | _xNumberOfScrollableColumns: function() { 87 | var width = this._xGetNumberOfColumns() - this.options.fixedColumnsLeft - this.options.fixedColumnsRight; 88 | if(width < 1) 89 | return 1; 90 | return width; 91 | }, 92 | 93 | _xScrollWidth: function() { 94 | var width = this._xGetNumberOfColumns() - this.options.fixedColumnsLeft - this.options.fixedColumnsRight; 95 | if (width > this.options.columnsInScrollableArea) 96 | return this.options.columnsInScrollableArea; 97 | if (width < 1) 98 | return 1; 99 | return width; 100 | }, 101 | 102 | _xScrollNeeded : function() { 103 | var width = this._xGetNumberOfColumns() - this.options.fixedColumnsLeft - this.options.fixedColumnsRight; 104 | return width > this.options.columnsInScrollableArea; 105 | }, 106 | 107 | _xInitScroll: function() { 108 | if (this._xGetNumberOfColumns() < (this.options.fixedColumnsLeft + this.options.fixedColumnsRight)) 109 | return; 110 | 111 | if (this._xScrollNeeded() || this.options.overflowX == 'scroll') { 112 | 113 | var row = this._table().insertRow(this._table().rows.length); 114 | 115 | if (this.options.fixedColumnsLeft > 0) { 116 | var $cell = $(row.insertCell(0)); 117 | $cell.attr('colspan', this.options.fixedColumnsLeft); 118 | } 119 | 120 | var $container = $(row.insertCell(1)); 121 | $container.attr('colspan', this._xScrollWidth()); 122 | $container.addClass('sg-x-scroll-cell'); 123 | 124 | var $widthDivContainer = $('
'); 125 | $widthDivContainer.css('overflow-x', 'scroll'); 126 | $widthDivContainer.css('margin-right', '-20000px'); 127 | $widthDivContainer.width($container.width()); 128 | 129 | var $widthDiv = $('
'); 130 | $widthDiv.width((this._xNumberOfScrollableColumns() / this._xScrollWidth()) * $container.width()); 131 | $widthDiv.appendTo($widthDivContainer); 132 | 133 | $widthDivContainer.appendTo($container); 134 | $widthDivContainer.scroll($.proxy(this._xUpdateColumnsVisibility, this)); 135 | 136 | if (this.options.fixedColumnsRight > 0) { 137 | var $cell = $(row.insertCell(2)); 138 | $cell.attr('colspan', this.options.fixedColumnsRight + 139 | ($('.sg-v-scroll-cell', this.widget()).length > 0 ? 1 : 0)); 140 | } 141 | } 142 | }, 143 | 144 | _xCurrentRelativeScrollLeft: function () { 145 | var $widthDivContainer = $('.sg-h-scroll-container', this.widget()); 146 | return $widthDivContainer.scrollLeft() / $widthDivContainer.width(); 147 | }, 148 | 149 | _xScrollDelta: function () { 150 | var widthContainer = $('.sg-h-scroll-container', this.widget()); 151 | return $('div', widthContainer).width() - widthContainer.width(); 152 | }, 153 | 154 | _xScrollableColumnsCount: function () { 155 | return this._xNumberOfScrollableColumns() - this._xScrollWidth(); 156 | }, 157 | 158 | _xColumnScrollStep: function () { 159 | if (this._xScrollableColumnsCount() == 0) 160 | return 0; 161 | return this._xScrollDelta() / this._xScrollableColumnsCount(); 162 | }, 163 | 164 | _xMoveScroll: function(position) { 165 | position = Math.min(this._xScrollableColumnsCount(), position); 166 | position = Math.max(position, 0); 167 | 168 | position = this._xColumnScrollStep() * position; 169 | var $widthDivContainer = $('.sg-h-scroll-container', this.widget()); 170 | if ($widthDivContainer.scrollLeft() != position) 171 | $widthDivContainer.scrollLeft(position); 172 | }, 173 | 174 | _setColumnVisibility: function(index, visible, start, end) { 175 | var rows = this._table().rows; 176 | 177 | for (var rowIndex = start; rowIndex < end; rowIndex++) { 178 | var row = rows[rowIndex]; 179 | 180 | for (var cellIndex = 0; cellIndex < row.cells.length; cellIndex++) { 181 | 182 | //in this cycle body we can't use jQuery because this code is critical for performance 183 | 184 | var cell = row.cells[cellIndex]; 185 | var cIndex = cell[CELL_INDEX_DATA]; 186 | if (cIndex == index) { 187 | 188 | if (!cell.colSpan || cell.colSpan == 1) // apply visibility only for cells with colspan = 1 189 | { 190 | if (visible && cell.style.display == 'none') 191 | cell.style.display = ''; 192 | 193 | if (!visible && cell.style.display != 'none') 194 | cell.style.display = 'none'; 195 | } 196 | } 197 | } 198 | } 199 | }, 200 | 201 | _xFirstVisibleColumnWidth: function () { 202 | for (var i = this.options.rowsInHeader; i < this._table().rows.length - this.options.rowsInFooter - $('.sg-h-scroll-container', this.widget()).length; i++) { 203 | if ($(this._table().rows[i]).css('display') != 'none') { 204 | for (var j = this.options.fixedColumnsLeft; j < this._xGetNumberOfColumns() - this.options.fixedColumnsRight; j++) { 205 | if ($(this._table().rows[i].cells[j]).css('display') != 'none') 206 | return $(this._table().rows[i].cells[j]).width(); 207 | } 208 | } 209 | } 210 | return 0; 211 | }, 212 | 213 | _xLastVisibleColumnWidth: function () { 214 | for (var i = this.options.rowsInHeader; i < this._table().rows.length - this.options.rowsInFooter - $('.sg-h-scroll-container', this.widget()).length; i++) { 215 | if ($(this._table().rows[i]).css('display') != 'none') { 216 | for (var j = this._xGetNumberOfColumns() - this.options.fixedColumnsRight - 1; j >= this.options.fixedColumnsLeft ; j--) { 217 | if ($(this._table().rows[i].cells[j]).css('display') != 'none') 218 | return $(this._table().rows[i].cells[j]).width(); 219 | } 220 | } 221 | } 222 | return 0; 223 | }, 224 | 225 | _xUpdateColumnsVisibility: function() { 226 | if (!this._xScrollNeeded()) 227 | return; 228 | 229 | var leftContainer = $('.sg-h-scroll-container', this.widget()); 230 | 231 | var startFromX = Math.floor(leftContainer.scrollLeft() / this._xColumnScrollStep()); 232 | var relativeLeft = this._xCurrentRelativeScrollLeft(); 233 | for (var i = this.options.fixedColumnsLeft; i < this._xGetNumberOfColumns() - this.options.fixedColumnsRight; i++) { 234 | var visible = false; 235 | 236 | if (i >= this.options.fixedColumnsLeft + startFromX 237 | && 238 | i < this.options.fixedColumnsLeft + startFromX + this.options.columnsInScrollableArea 239 | ) { 240 | visible = true; 241 | } 242 | 243 | this._setColumnVisibility(i, visible, 0, this._table().rows.length - 1 /* ignore scrolling row */); 244 | } 245 | this._xUpdateScrollWidths(); 246 | }, 247 | 248 | _xUpdateScrollWidths: function () { 249 | 250 | var leftContainer = $('.sg-h-scroll-container', this.widget()); 251 | var $container = leftContainer.closest('td'); 252 | leftContainer.width($container.width()); 253 | var $widthDiv = $('div', leftContainer); 254 | $widthDiv.width((this._xNumberOfScrollableColumns() / this._xScrollWidth()) * $container.width()); 255 | }, 256 | 257 | // vertical scrolling methods 258 | _yScrollHeight:function() { 259 | var height = this._table().rows.length - this.options.rowsInHeader - this.options.rowsInFooter; 260 | if ($('.sg-h-scroll-container', this.widget()).length > 0) 261 | height--; 262 | 263 | if (height > this.options.rowsInScrollableArea) 264 | return this.options.rowsInScrollableArea; 265 | if (height < 1) 266 | return 1; 267 | return height; 268 | }, 269 | 270 | _yNumberOfScrollableRows: function () { 271 | var height = this._table().rows.length - this.options.rowsInHeader - this.options.rowsInFooter; 272 | if ($('.sg-h-scroll-container', this.widget()).length > 0) 273 | height--; 274 | 275 | if (height < 1) 276 | return 1; 277 | return height; 278 | }, 279 | 280 | _yScrollNeeded: function() { 281 | var height = this._table().rows.length - this.options.rowsInHeader - this.options.rowsInFooter; 282 | if ($('.sg-h-scroll-container', this.widget()).length > 0) 283 | height--; 284 | return height > this.options.rowsInScrollableArea; 285 | }, 286 | 287 | _yInitScroll: function () { 288 | 289 | if (this._table().rows.length < (this.options.rowsInHeader + this.options.rowsInFooter)) 290 | return; 291 | 292 | if (this._yScrollNeeded() || this.options.overflowY == 'scroll') { 293 | var $cell = $(this._table().rows[0].insertCell(this._table().rows[0].cells.length)); 294 | $cell.attr('rowspan', this.options.rowsInHeader); 295 | 296 | var $container = $(this._table().rows[this.options.rowsInHeader + this.startFrom].insertCell(this._table().rows[this.options.rowsInHeader + this.startFrom].cells.length)); 297 | $container.attr('rowspan', this._yScrollHeight()); 298 | $container.attr('width', "1px"); 299 | $container.addClass('sg-v-scroll-cell'); 300 | 301 | var $heightDivContainer = $('
'); 302 | $heightDivContainer.css('overflow-y', 'scroll'); 303 | $heightDivContainer.height($container.height()); 304 | 305 | var $heightDiv = $('
'); 306 | $heightDiv.height((this._yNumberOfScrollableRows() / this._yScrollHeight()) * $container.height()); 307 | $heightDiv.appendTo($heightDivContainer); 308 | 309 | $heightDivContainer.appendTo($container); 310 | this._attachToEndScrolling($heightDivContainer, $.proxy(this._yUpdateRowsVisibility, this)); 311 | 312 | if (this.options.rowsInFooter != 0) { 313 | var firstBotomRow = this._table().rows[this._yNumberOfScrollableRows() + this.options.rowsInHeader]; 314 | var $bottomCell = $(firstBotomRow.insertCell(firstBotomRow.cells.length)); 315 | $bottomCell.attr('rowspan', this.options.rowsInFooter); 316 | } 317 | } 318 | }, 319 | 320 | _yCurrentRelativeScrollTop: function() { 321 | var $heightDivContainer = $('.sg-v-scroll-container', this.widget()); 322 | return $heightDivContainer.scrollTop() / $heightDivContainer.height(); 323 | }, 324 | 325 | _yMoveScrollToRightRow: function(oldRelativeTop) { 326 | var trCurrentContainer = $('.sg-v-scroll-cell', this.widget()).closest('tr').get(0); 327 | var trTargetContainer = this._table().rows[this.options.rowsInHeader + this.startFrom]; 328 | 329 | var $heightDivContainer = $('.sg-v-scroll-container', this.widget()); 330 | var $heightDiv = $('div', $heightDivContainer); 331 | 332 | if (trCurrentContainer != trTargetContainer) { 333 | var $newCell = $(trTargetContainer.insertCell(trTargetContainer.cells.length)); 334 | $newCell.attr('rowspan', this._yScrollHeight()); 335 | $newCell.addClass('sg-v-scroll-cell'); 336 | $newCell.attr('width', "1px"); 337 | 338 | var scrollDiv = $('.sg-v-scroll-container', $(trCurrentContainer)); 339 | scrollDiv.height(0); 340 | scrollDiv.appendTo($newCell); 341 | trCurrentContainer.deleteCell(trCurrentContainer.cells.length - 1); 342 | 343 | $heightDivContainer.height($newCell.height()); 344 | $heightDiv.height((this._yNumberOfScrollableRows() / this._yScrollHeight()) * $newCell.height()); 345 | 346 | $heightDivContainer.scrollTop(oldRelativeTop * $heightDivContainer.height()); 347 | $heightDivContainer.get(0); 348 | } 349 | }, 350 | 351 | _yScrollDelta: function () { 352 | var topContainer = $('.sg-v-scroll-container', this.widget()); 353 | return $('div', topContainer).height() - topContainer.height(); 354 | }, 355 | 356 | _yScrollableRowsCount: function() { 357 | return this._yNumberOfScrollableRows() - this._yScrollHeight(); 358 | }, 359 | 360 | _yRowScrollStep: function () { 361 | if (this._yScrollableRowsCount() == 0) 362 | return 0; 363 | return this._yScrollDelta() / this._yScrollableRowsCount(); 364 | }, 365 | 366 | _yMoveScroll: function(position) { 367 | position = Math.min(this._yScrollableRowsCount(), position); 368 | position = Math.max(position, 0); 369 | 370 | var step = this._yRowScrollStep(); 371 | position = step * position; 372 | var $heightDivContainer = $('.sg-v-scroll-container', this.widget()); 373 | if ($heightDivContainer.scrollTop() != position) 374 | $heightDivContainer.scrollTop(position + step / 2); 375 | }, 376 | 377 | _yUpdateScrollHeights: function () { 378 | 379 | var topContainer = $('.sg-v-scroll-container', this.widget()); 380 | var $container = topContainer.closest('td'); 381 | topContainer.hide(); 382 | topContainer.height($container.height()); 383 | var $heightDiv = $('div', topContainer); 384 | $heightDiv.height((this._yNumberOfScrollableRows() / this._yScrollHeight()) * $container.height()); 385 | topContainer.show(); 386 | }, 387 | 388 | _yFirstVisibleRowHeight: function(){ 389 | for (var i = this.options.rowsInHeader; i < this._table().rows.length - this.options.rowsInFooter - $('.sg-h-scroll-container', this.widget()).length; i++) { 390 | if ($(this._table().rows[i]).css('display') != 'none') { 391 | return $(this._table().rows[i]).height(); 392 | } 393 | } 394 | return 0; 395 | }, 396 | 397 | _yLastVisibleRowHeight: function () { 398 | for (var i = this._table().rows.length - this.options.rowsInFooter - $('.sg-h-scroll-container', this.widget()).length - 1; i >= this.options.rowsInHeader; i--) { 399 | if ($(this._table().rows[i]).css('display') != 'none') { 400 | return $(this._table().rows[i]).height(); 401 | } 402 | } 403 | return 0; 404 | }, 405 | 406 | _yUpdateRowsVisibility: function () { 407 | 408 | if (!this._yScrollNeeded()) 409 | return; 410 | 411 | var topContainer = $('.sg-v-scroll-container', this.widget()); 412 | 413 | var startFrom = Math.floor(topContainer.scrollTop() / this._yRowScrollStep()); 414 | var relativeTop = this._yCurrentRelativeScrollTop(); 415 | 416 | for (var i = this.options.rowsInHeader; i < this._table().rows.length - this.options.rowsInFooter - $('.sg-h-scroll-container', this.widget()).length; i++) { 417 | var visible = false; 418 | 419 | if (i >= this.options.rowsInHeader + startFrom 420 | && 421 | i < this.options.rowsInHeader + startFrom + this.options.rowsInScrollableArea 422 | ) { 423 | visible = true; 424 | } 425 | 426 | if (visible) { 427 | $(this._table().rows[i]).show(); 428 | } else { 429 | $(this._table().rows[i]).hide(); 430 | } 431 | } 432 | 433 | if (this.startFrom != startFrom) { 434 | this.startFrom = startFrom; 435 | this._yMoveScrollToRightRow(relativeTop); 436 | } 437 | }, 438 | 439 | _attachToEndScrolling: function (element, handler) { 440 | element.scroll(function() { 441 | clearTimeout(element.data('scrollTimer')); 442 | 443 | $.data(this, 'scrollTimer', setTimeout(function () { 444 | handler.apply(this); 445 | }, 300)); 446 | }); 447 | }, 448 | 449 | _tableMouseWheel: function (event) { 450 | 451 | var up = false; 452 | var down = false; 453 | var original = event.originalEvent; 454 | if (original.wheelDelta) { 455 | if (original.wheelDelta >= 120) { 456 | up = true; 457 | } 458 | else { 459 | if (original.wheelDelta <= -120) { 460 | down = true; 461 | } 462 | } 463 | } 464 | 465 | if (original.detail) { 466 | if (original.detail == -3) 467 | up = true; 468 | else 469 | if (original.detail == 3) 470 | down = true; 471 | } 472 | 473 | var $heightDivContainer = $('.sg-v-scroll-container', this.widget()); 474 | var delta = 0; 475 | 476 | if (up) 477 | delta = this._yRowScrollStep() + 1; 478 | if(down) 479 | delta = - this._yRowScrollStep() - 1; 480 | 481 | if (delta != 0) { 482 | $heightDivContainer.scrollTop($heightDivContainer.scrollTop() - delta); 483 | } 484 | event.preventDefault(); 485 | }, 486 | 487 | _touchStart: function (event) { 488 | if (event.originalEvent.touches && event.originalEvent.touches.length == 1) { 489 | var touch = event.originalEvent.touches[0] || event.originalEvent.changedTouches[0]; 490 | this._currentTouch = { X: touch.pageX, Y: touch.pageY }; 491 | event.preventDefault(); 492 | event.stopPropagation(); 493 | } 494 | }, 495 | 496 | _touchMove: function (event) { 497 | if (event.originalEvent.touches && event.originalEvent.touches.length == 1 && this._currentTouch != null) { 498 | var touch = event.originalEvent.touches[0] || event.originalEvent.changedTouches[0]; 499 | 500 | var newTouch = { X: touch.pageX, Y: touch.pageY }; 501 | var deltaX = this._currentTouch.X - newTouch.X; 502 | var deltaY = this._currentTouch.Y - newTouch.Y; 503 | 504 | var $heightDivContainer = $('.sg-v-scroll-container', this.widget()); 505 | if (deltaY > 0) { 506 | var rowToHideHeight = this._yFirstVisibleRowHeight(); 507 | if (rowToHideHeight != 0 && deltaY > rowToHideHeight) { 508 | $heightDivContainer.scrollTop($heightDivContainer.scrollTop() + (this._yRowScrollStep() + 1)) 509 | this._currentTouch.Y -= rowToHideHeight; 510 | this._yUpdateRowsVisibility(); 511 | } 512 | } 513 | else { 514 | var rowToHideHeight = this._yLastVisibleRowHeight(); 515 | if (rowToHideHeight != 0 && deltaY < -1 * rowToHideHeight) { 516 | $heightDivContainer.scrollTop($heightDivContainer.scrollTop() - (this._yRowScrollStep() + 1)) 517 | this._currentTouch.Y += rowToHideHeight; 518 | this._yUpdateRowsVisibility(); 519 | } 520 | } 521 | 522 | var $widthDivContainer = $('.sg-h-scroll-container', this.widget()); 523 | if (deltaX > 0) { 524 | var columnToHideWidth = this._xFirstVisibleColumnWidth(); 525 | if (columnToHideWidth != 0 && deltaX > columnToHideWidth) { 526 | $widthDivContainer.scrollLeft($widthDivContainer.scrollLeft() + (this._xColumnScrollStep() + 1)) 527 | this._currentTouch.X -= rowToHideHeight; 528 | } 529 | } 530 | else { 531 | var columnToHideWidth = this._xLastVisibleColumnWidth(); 532 | if (columnToHideWidth != 0 && deltaX < -1 * columnToHideWidth) { 533 | $widthDivContainer.scrollLeft($widthDivContainer.scrollLeft() - (this._xColumnScrollStep() + 1)) 534 | this._currentTouch.X += columnToHideWidth; 535 | } 536 | } 537 | event.preventDefault(); 538 | event.stopPropagation(); 539 | } 540 | }, 541 | 542 | _touchEnd: function (event) { 543 | this._currentTouch = null 544 | }, 545 | 546 | 547 | _table: function() { 548 | return this.widget().get(0); 549 | }, 550 | 551 | _setActualCellIndexes: function() { 552 | var rows = this._table().rows; 553 | 554 | for (var rowIndex = 0; rowIndex < rows.length; rowIndex++) { 555 | var row = rows[rowIndex]; 556 | var indAdjustments = $(row).get(0)[CELL_SPAN_ADJUSTMENTS]; 557 | if (!indAdjustments) 558 | indAdjustments = []; 559 | 560 | for (var cellIndex = 0; cellIndex < row.cells.length; cellIndex++) { 561 | 562 | var prevCellEndsAt = cellIndex - 1; 563 | 564 | if (cellIndex > 0) { 565 | var $prevCell = $(row.cells[cellIndex - 1]); 566 | prevCellEndsAt = $prevCell.get(0)[CELL_INDEX_DATA]; 567 | if ($prevCell.attr('colspan')) { 568 | prevCellEndsAt += this._getColSpan($prevCell) - 1; 569 | } 570 | } 571 | 572 | var $cell = $(row.cells[cellIndex]); 573 | var indexToSet = prevCellEndsAt + 1; 574 | 575 | for (var i = 0; i < indAdjustments.length; i++) { 576 | if (indAdjustments[i].index <= indexToSet) { 577 | indexToSet += indAdjustments[i].adjustment; 578 | indAdjustments[i].adjustment = 0; 579 | } 580 | } 581 | 582 | $cell.get(0)[CELL_INDEX_DATA] = indexToSet; 583 | 584 | if ($cell.attr('rowspan') > 1 ) { 585 | var span = $cell.attr('rowspan'); 586 | 587 | for (var rowShift = rowIndex + 1; rowShift < rowIndex + span && rowShift < rows.length; rowShift++) { 588 | var $shiftedRow = $(rows[rowShift]); 589 | var adjustments = $shiftedRow.get(0)[CELL_SPAN_ADJUSTMENTS]; 590 | if (!adjustments) 591 | adjustments = []; 592 | adjustments.push({ index: indexToSet, adjustment: this._getColSpan($cell) }); 593 | $shiftedRow.get(0)[CELL_SPAN_ADJUSTMENTS] = adjustments; 594 | } 595 | } 596 | } 597 | } 598 | }, 599 | 600 | _getColSpan: function($cell) { 601 | if ($cell.data('scroll-span')) 602 | return $cell.data('scroll-span'); 603 | 604 | if ($cell.attr('colspan')) 605 | return $cell.attr('colspan') * 1; 606 | 607 | return 1; 608 | } 609 | }); 610 | 611 | })(jQuery); --------------------------------------------------------------------------------