| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651 |
- /**
- * File: datatables.responsive.js
- * Version: 0.2.0
- * Author: Seen Sai Yang
- * Info: https://github.com/Comanche/datatables-responsive
- *
- * Copyright 2013 Seen Sai Yang, all rights reserved.
- *
- * This source file is free software, under either the GPL v2 license or a
- * BSD style license.
- *
- * This source file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
- *
- * You should have received a copy of the GNU General Public License and the
- * BSD license along with this program. These licenses are also available at:
- * https://raw.github.com/Comanche/datatables-responsive/master/license-gpl2.txt
- * https://raw.github.com/Comanche/datatables-responsive/master/license-bsd.txt
- */
- 'use strict';
- /**
- * Constructor for responsive datables helper.
- *
- * This helper class makes datatables responsive to the window size.
- *
- * The parameter, breakpoints, is an object for each breakpoint key/value pair
- * with the following format: { breakpoint_name: pixel_width_at_breakpoint }.
- *
- * An example is as follows:
- *
- * {
- * tablet: 1024,
- * phone: 480
- * }
- *
- * These breakpoint name may be used as possible values for the data-hide
- * attribute. The data-hide attribute is optional and may be defined for each
- * th element in the table header.
- *
- * The parameter, options, is an object of options supported by the responsive
- * helper. The following options are supported:
- *
- * {
- * hideEmptyColumnsInRowDetail - Boolean, default: false.
- * clickOn - icon|cell|row, default: icon
- * }
- *
- * @param {Object|string} tableSelector jQuery wrapped set or selector for
- * datatables container element.
- * @param {Object} breakpoints Object defining the responsive
- * breakpoint for datatables.
- * @param {Object} options Object of options.
- */
- function ResponsiveDatatablesHelper(tableSelector, breakpoints, options) {
- if (typeof tableSelector === 'string') {
- this.tableElement = $(tableSelector);
- } else {
- this.tableElement = tableSelector;
- }
- // Get data table API.
- this.api = this.tableElement.dataTable().api();
- // State of column indexes and which are shown or hidden.
- this.columnIndexes = [];
- this.columnsShownIndexes = [];
- this.columnsHiddenIndexes = [];
- this.currentBreakpoint = '';
- this.lastBreakpoint = '';
- this.lastColumnsHiddenIndexes = [];
- // Save state
- var fileName = window.location.pathname.split("/").pop();
- var context = this.api.settings().context[0];
- this.tableId = context.sTableId;
- this.saveState = context.oInit.bStateSave;
- this.cookieName = 'DataTablesResponsiveHelper_' + this.tableId + (fileName ? '_' + fileName : '');
- this.lastStateExists = false;
- // Index of the th in the header tr that stores where the attribute
- // data-class="expand"
- // is defined.
- this.expandColumn = undefined;
- // Stores original breakpoint defitions
- this.origBreakpointsDefs = undefined;
- // Stores the break points defined in the table header.
- // Each th in the header tr may contain an optional attribute like
- // data-hide="phone,tablet"
- // These attributes and the breakpoints object will be used to create this
- // object.
- this.breakpoints = {
- /**
- * We will be generating data in the following format:
- * phone : {
- * lowerLimit : undefined,
- * upperLimit : 320,
- * columnsToHide: []
- * },
- * tablet: {
- * lowerLimit : 320,
- * upperLimit : 724,
- * columnsToHide: []
- * }
- */
- };
- // Store default options
- this.options = {
- hideEmptyColumnsInRowDetail: false,
- clickOn: 'icon'
- };
- // Expand icon template
- this.expandIconTemplate = '<span class="responsiveExpander"></span>';
- // Row template
- this.rowTemplate = '<tr class="row-detail"><td><ul><!--column item--></ul></td></tr>';
- this.rowLiTemplate = '<li><span class="columnTitle"><!--column title--></span>: <span class="columnValue"><!--column value--></span></li>';
- // Responsive behavior on/off flag
- this.disabled = true;
- // Skip next windows width change flag
- this.skipNextWindowsWidthChange = false;
- // Initialize settings
- this.init(breakpoints, options);
- }
- /**
- * Responsive datatables helper init function.
- * Builds breakpoint limits for columns and begins to listen to window resize
- * event.
- *
- * See constructor for the breakpoints parameter.
- *
- * @param {Object} breakpoints
- * @param {Object} options
- */
- ResponsiveDatatablesHelper.prototype.init = function (breakpoints, options) {
- this.origBreakpointsDefs = breakpoints;
- this.initBreakpoints();
- // Enable responsive behavior.
- this.disable(false);
- // Extend options
- $.extend(this.options, options);
- };
- ResponsiveDatatablesHelper.prototype.initBreakpoints = function () {
- // Get last state if it exists
- if (this.saveState) {
- this.getState();
- }
- if (!this.lastStateExists) {
- /** Generate breakpoints in the format we need. ***********************/
- // First, we need to create a sorted array of the breakpoints given.
- var breakpointsSorted = [];
- for (var prop in this.origBreakpointsDefs) {
- breakpointsSorted.push({
- name: prop,
- upperLimit: this.origBreakpointsDefs[prop],
- columnsToHide: []
- });
- }
- breakpointsSorted.sort(function (a, b) {
- return a.upperLimit - b.upperLimit;
- });
- // Set lower and upper limits for each breakpoint.
- var lowerLimit = 0;
- for (var i = 0; i < breakpointsSorted.length; i++) {
- breakpointsSorted[i].lowerLimit = lowerLimit;
- lowerLimit = breakpointsSorted[i].upperLimit;
- }
- // Add the default breakpoint which shows all (has no upper limit).
- breakpointsSorted.push({
- name : 'always',
- lowerLimit : lowerLimit,
- upperLimit : Infinity,
- columnsToHide: []
- });
- // Copy the sorted breakpoint array into the breakpoints object using the
- // name as the key.
- this.breakpoints = {};
- var i, l;
- for (i = 0, l = breakpointsSorted.length; i < l; i++) {
- this.breakpoints[breakpointsSorted[i].name] = breakpointsSorted[i];
- }
- /** Create range of visible columns and their indexes *****************/
- // We need the range of all visible column indexes to calculate the
- // columns to show:
- // Columns to show = all visible columns - columns to hide
- var columns = this.api.columns().header();
- var visibleColumnsHeadersTds = [];
- for (i = 0, l = columns.length; i < l; i++) {
- if (this.api.columns(i).visible()) {
- this.columnIndexes.push(i);
- visibleColumnsHeadersTds.push(columns[i]);
- }
- }
- /** Sort columns into breakpoints respectively ************************/
- // Read column headers' attributes and get needed info
- for (var index = 0; index < visibleColumnsHeadersTds.length; index++) {
- // Get the column with the attribute data-class="expand" so we know
- // where to display the expand icon.
- var col = visibleColumnsHeadersTds[index];
- if ($(col).attr('data-class') === 'expand') {
- this.expandColumn = this.columnIndexes[index];
- }
- // The data-hide attribute has the breakpoints that this column
- // is associated with.
- // If it's defined, get the data-hide attribute and sort this
- // column into the appropriate breakpoint's columnsToHide array.
- var dataHide = $(col).attr('data-hide');
- if (dataHide !== undefined) {
- var splitBreakingPoints = dataHide.split(/,\s*/);
- for (var i = 0; i < splitBreakingPoints.length; i++) {
- var bp = splitBreakingPoints[i];
- if (bp === 'always') {
- // A column with an 'always' breakpoint is always hidden.
- // Loop through all breakpoints and add it to each except the
- // default breakpoint.
- for (var prop in this.breakpoints) {
- if (this.breakpoints[prop].name !== 'default') {
- this.breakpoints[prop].columnsToHide.push(this.columnIndexes[index]);
- }
- }
- } else if (this.breakpoints[bp] !== undefined) {
- // Translate visible column index to internal column index.
- this.breakpoints[bp].columnsToHide.push(this.columnIndexes[index]);
- }
- }
- }
- }
- }
- };
- /**
- * Sets or removes window resize handler.
- *
- * @param {Boolean} bindFlag
- */
- ResponsiveDatatablesHelper.prototype.setWindowsResizeHandler = function(bindFlag) {
- if (bindFlag === undefined) {
- bindFlag = true;
- }
- if (bindFlag) {
- var that = this;
- $(window).bind("resize", function () {
- that.respond();
- });
- } else {
- $(window).unbind("resize");
- }
- };
- /**
- * Respond window size change. This helps make datatables responsive.
- */
- ResponsiveDatatablesHelper.prototype.respond = function () {
- if (this.disabled) {
- return;
- }
- var that = this;
- // Get new windows width
- var newWindowWidth = $(window).width();
- // Loop through breakpoints to see which columns need to be shown/hidden.
- var newColumnsToHide = [];
- for (var prop in this.breakpoints) {
- var element = this.breakpoints[prop];
- if ((!element.lowerLimit || newWindowWidth > element.lowerLimit) && (!element.upperLimit || newWindowWidth <= element.upperLimit)) {
- this.currentBreakpoint = element.name;
- newColumnsToHide = element.columnsToHide;
- }
- }
- // Find out if a column show/hide should happen.
- // Skip column show/hide if this window width change follows immediately
- // after a previous column show/hide. This will help prevent a loop.
- var columnShowHide = false;
- if (!this.skipNextWindowsWidthChange) {
- // Check difference in length
- if (this.lastBreakpoint.length === 0 && newColumnsToHide.length) {
- // No previous breakpoint and new breakpoint
- columnShowHide = true;
- } else if (this.lastBreakpoint != this.currentBreakpoint) {
- // Different breakpoints
- columnShowHide = true;
- } else if (this.columnsHiddenIndexes.length !== newColumnsToHide.length) {
- // Difference in number of hidden columns
- columnShowHide = true;
- } else {
- // Possible same number of columns but check for difference in columns
- var d1 = this.difference(this.columnsHiddenIndexes, newColumnsToHide).length;
- var d2 = this.difference(newColumnsToHide, this.columnsHiddenIndexes).length;
- columnShowHide = d1 + d2 > 0;
- }
- }
- if (columnShowHide) {
- // Showing/hiding a column at breakpoint may cause a windows width
- // change. Let's flag to skip the column show/hide that may be
- // caused by the next windows width change.
- this.skipNextWindowsWidthChange = true;
- this.columnsHiddenIndexes = newColumnsToHide;
- this.columnsShownIndexes = this.difference(this.columnIndexes, this.columnsHiddenIndexes);
- this.showHideColumns();
- this.lastBreakpoint = this.currentBreakpoint;
- this.setState();
- this.skipNextWindowsWidthChange = false;
- }
- // We don't skip this part.
- // If one or more columns have been hidden, add the has-columns-hidden class to table.
- // This class will show what state the table is in.
- if (this.columnsHiddenIndexes.length) {
- this.tableElement.addClass('has-columns-hidden');
- // Show details for each row that is tagged with the class .detail-show.
- $('tr.detail-show', this.tableElement).each(function (index, element) {
- var tr = $(element);
- if (tr.next('.row-detail').length === 0) {
- ResponsiveDatatablesHelper.prototype.showRowDetail(that, tr);
- }
- });
- } else {
- this.tableElement.removeClass('has-columns-hidden');
- $('tr.row-detail').each(function (event) {
- ResponsiveDatatablesHelper.prototype.hideRowDetail(that, $(this).prev());
- });
- }
- };
- /**
- * Show/hide datatables columns.
- */
- ResponsiveDatatablesHelper.prototype.showHideColumns = function () {
- // Calculate the columns to show
- // Show columns that may have been previously hidden.
- for (var i = 0, l = this.columnsShownIndexes.length; i < l; i++) {
- this.api.column(this.columnsShownIndexes[i]).visible(true);
- }
- // Hide columns that may have been previously shown.
- for (var i = 0, l = this.columnsHiddenIndexes.length; i < l; i++) {
- this.api.column(this.columnsHiddenIndexes[i]).visible(false);
- }
- // Rebuild details to reflect shown/hidden column changes.
- var that = this;
- $('tr.row-detail').each(function () {
- ResponsiveDatatablesHelper.prototype.hideRowDetail(that, $(this).prev());
- });
- if (this.tableElement.hasClass('has-columns-hidden')) {
- $('tr.detail-show', this.tableElement).each(function (index, element) {
- ResponsiveDatatablesHelper.prototype.showRowDetail(that, $(element));
- });
- }
- };
- /**
- * Create the expand icon on the column with the data-class="expand" attribute
- * defined for it's header.
- *
- * @param {Object} tr table row object
- */
- ResponsiveDatatablesHelper.prototype.createExpandIcon = function (tr) {
- if (this.disabled) {
- return;
- }
- // Get the td for tr with the same index as the th in the header tr
- // that has the data-class="expand" attribute defined.
- var tds = $('td', tr);
- // Loop through tds and create an expand icon on the td that has a column
- // index equal to the expand column given.
- for (var i = 0, l = tds.length; i < l; i++) {
- var td = tds[i];
- var tdIndex = this.api.cell(td).index().column;
- td = $(td);
- if (tdIndex === this.expandColumn) {
- // Create expand icon if there isn't one already.
- if ($('span.responsiveExpander', td).length == 0) {
- td.prepend(this.expandIconTemplate);
- // Respond to click event on expander icon.
- switch (this.options.clickOn) {
- case 'cell':
- td.on('click', {responsiveDatatablesHelperInstance: this}, this.showRowDetailEventHandler);
- break;
- case 'row':
- $(tr).on('click', {responsiveDatatablesHelperInstance: this}, this.showRowDetailEventHandler);
- break;
- default:
- td.on('click', 'span.responsiveExpander', {responsiveDatatablesHelperInstance: this}, this.showRowDetailEventHandler);
- break;
- }
- }
- break;
- }
- }
- };
- /**
- * Show row detail event handler.
- *
- * This handler is used to handle the click event of the expand icon defined in
- * the table row data element.
- *
- * @param {Object} event jQuery event object
- */
- ResponsiveDatatablesHelper.prototype.showRowDetailEventHandler = function (event) {
- var responsiveDatatablesHelperInstance = event.data.responsiveDatatablesHelperInstance;
- if (responsiveDatatablesHelperInstance.disabled) {
- return;
- }
- var td = $(this);
- // Nothing to do if there are no columns hidden.
- if (!td.closest('table').hasClass('has-columns-hidden')) {
- return;
- }
- // Get the parent tr of which this td belongs to.
- var tr = td.closest('tr');
- // Show/hide row details
- if (tr.hasClass('detail-show')) {
- ResponsiveDatatablesHelper.prototype.hideRowDetail(responsiveDatatablesHelperInstance, tr);
- } else {
- ResponsiveDatatablesHelper.prototype.showRowDetail(responsiveDatatablesHelperInstance, tr);
- }
- tr.toggleClass('detail-show');
- // Prevent click event from bubbling up to higher-level DOM elements.
- event.stopPropagation();
- };
- /**
- * Show row details.
- *
- * @param {ResponsiveDatatablesHelper} responsiveDatatablesHelperInstance instance of ResponsiveDatatablesHelper
- * @param {Object} tr jQuery wrapped set
- */
- ResponsiveDatatablesHelper.prototype.showRowDetail = function (responsiveDatatablesHelperInstance, tr) {
- // Get column because we need their titles.
- var api = responsiveDatatablesHelperInstance.api;
- var columns = api.columns().header();
- // Create the new tr.
- var newTr = $(responsiveDatatablesHelperInstance.rowTemplate);
- // Get the ul that we'll insert li's into.
- var ul = $('ul', newTr);
- // Loop through hidden columns and create an li for each of them.
- for (var i = 0; i < responsiveDatatablesHelperInstance.columnsHiddenIndexes.length; i++) {
- var index = responsiveDatatablesHelperInstance.columnsHiddenIndexes[i];
- // Get row td
- var rowIndex = api.row(tr).index();
- var td = api.cell(rowIndex, index).node();
- // Don't create li if contents are empty (depends on hideEmptyColumnsInRowDetail option).
- if (!responsiveDatatablesHelperInstance.options.hideEmptyColumnsInRowDetail || td.innerHTML.trim().length) {
- var li = $(responsiveDatatablesHelperInstance.rowLiTemplate);
- $('.columnTitle', li).html(columns[index].innerHTML);
- var contents = $(td).contents();
- var clonedContents = contents.clone();
- // Select elements' selectedIndex are not cloned. Do it manually.
- for (var n = 0, m = contents.length; n < m; n++) {
- var node = contents[n];
- if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'SELECT') {
- clonedContents[n].selectedIndex = node.selectedIndex
- }
- }
- // Set the column contents and save the original td source.
- $('.columnValue', li).append(clonedContents).data('originalTdSource', td);
- // Copy index to data attribute, so we'll know where to put the value when the tr.row-detail is removed.
- li.attr('data-column', index);
- // Copy td class to new li.
- var tdClass = $(td).attr('class');
- if (tdClass !== 'undefined' && tdClass !== false && tdClass !== '') {
- li.addClass(tdClass)
- }
- ul.append(li);
- }
- }
- // Create tr colspan attribute.
- var colspan = responsiveDatatablesHelperInstance.columnIndexes.length - responsiveDatatablesHelperInstance.columnsHiddenIndexes.length;
- newTr.find('> td').attr('colspan', colspan);
- // Append the new tr after the current tr.
- tr.after(newTr);
- };
- /**
- * Hide row details.
- *
- * @param {ResponsiveDatatablesHelper} responsiveDatatablesHelperInstance instance of ResponsiveDatatablesHelper
- * @param {Object} tr jQuery wrapped set
- */
- ResponsiveDatatablesHelper.prototype.hideRowDetail = function (responsiveDatatablesHelperInstance, tr) {
- // If the value of an input has changed while in row detail, we need to copy its state back
- // to the DataTables object so that value will persist when the tr.row-detail is removed.
- tr.next('.row-detail').find('li').each(function () {
- var columnValueContainer = $(this).find('span.columnValue');
- var tdContents = columnValueContainer.contents();
- var td = columnValueContainer.data('originalTdSource');
- $(td).empty().append(tdContents);
- });
- tr.next('.row-detail').remove();
- };
- /**
- * Enable/disable responsive behavior and restores changes made.
- *
- * @param {Boolean} disable, default is true
- */
- ResponsiveDatatablesHelper.prototype.disable = function (disable) {
- this.disabled = (disable === undefined) || disable;
- if (this.disabled) {
- // Remove windows resize handler.
- this.setWindowsResizeHandler(false);
- // Remove all trs that have row details.
- $('tbody tr.row-detail', this.tableElement).remove();
- // Remove all trs that are marked to have row details shown.
- $('tbody tr', this.tableElement).removeClass('detail-show');
- // Remove all expander icons.
- $('tbody tr span.responsiveExpander', this.tableElement).remove();
- this.columnsHiddenIndexes = [];
- this.columnsShownIndexes = this.columnIndexes;
- this.showHideColumns();
- this.tableElement.removeClass('has-columns-hidden');
- this.tableElement.off('click', 'span.responsiveExpander', this.showRowDetailEventHandler);
- } else {
- // Add windows resize handler.
- this.setWindowsResizeHandler();
- }
- };
- /**
- * Get state from cookie.
- */
- ResponsiveDatatablesHelper.prototype.getState = function () {
- try {
- var value = JSON.parse(decodeURIComponent(this.getCookie(this.cookieName)));
- if (value) {
- this.columnIndexes = value.columnIndexes;
- this.breakpoints = value.breakpoints;
- this.expandColumn = value.expandColumn;
- this.lastBreakpoint = value.lastBreakpoint;
- this.lastStateExists = true;
- }
- } catch (e) {
- }
- };
- /**
- * Saves state to cookie.
- */
- ResponsiveDatatablesHelper.prototype.setState = function () {
- var d1 = this.difference(this.lastColumnsHiddenIndexes, this.columnsHiddenIndexes).length;
- var d2 = this.difference(this.columnsHiddenIndexes, this.lastColumnsHiddenIndexes).length;
- if (d1 + d2 > 0) {
- var value = encodeURIComponent(JSON.stringify({
- columnIndexes: this.columnIndexes,
- columnsHiddenIndexes: this.columnsHiddenIndexes,
- breakpoints: this.breakpoints,
- expandColumn: this.expandColumn,
- lastBreakpoint: this.lastBreakpoint
- }));
- this.setCookie(this.cookieName, value, 2 * 60 * 60 * 1000);
- this.lastColumnsHiddenIndexes = this.columnsHiddenIndexes.slice(0);
- }
- };
- /**
- * Get cookie.
- */
- ResponsiveDatatablesHelper.prototype.getCookie = function (cname) {
- var name = cname + "=";
- var ca = document.cookie.split(';');
- for (var i = 0; i < ca.length; i++) {
- var c = ca[i].trim();
- if (c.indexOf(name) == 0) return c.substring(name.length, c.length);
- }
- return "";
- };
- /**
- * Set cookie.
- */
- ResponsiveDatatablesHelper.prototype.setCookie = function (cname, cvalue, cexp) {
- var d = new Date();
- d.setTime(d.getTime() + cexp);
- var expires = "expires=" + d.toGMTString();
- document.cookie = cname + "=" + cvalue + "; " + expires;
- };
- /**
- * Get Difference.
- */
- ResponsiveDatatablesHelper.prototype.difference = function (a, b) {
- var arr = [], i, hash = {};
- for (i = b.length - 1; i >= 0; i--) {
- hash[b[i]] = true;
- }
- for (i = a.length - 1; i >= 0; i--) {
- if (hash[a[i]] !== true) {
- arr.push(a[i]);
- }
- }
- return arr;
- };
|