(function ($) { 'use strict'; // RESPONSIVE TABLE CLASS DEFINITION // ========================== var ResponsiveTable = function(element, options) { var that = this; this.options = options; this.$tableWrapper = null; //defined later in wrapTable this.$tableScrollWrapper = $(element); //defined later in wrapTable this.$table = $(element).find('table'); if(this.$table.length !== 1) { throw new Error('Exactly one table is expected in a .table-responsive div.'); } //apply pattern option as data-attribute, in case it was set via js this.$tableScrollWrapper.attr('data-pattern', this.options.pattern); //if the table doesn't have a unique id, give it one. //The id will be a random hexadecimal value, prefixed with id. //Used for triggers with displayAll button. this.id = this.$table.prop('id') || this.$tableScrollWrapper.prop('id') || 'id' + Math.random().toString(16).slice(2); this.$tableClone = null; //defined farther down this.$stickyTableHeader = null; //defined farther down //good to have - for easy access this.$thead = this.$table.find('thead'); this.$tbody = this.$table.find('tbody'); this.$hdrCells = this.$thead.find('th'); this.$bodyRows = this.$tbody.find('tr'); //toolbar and buttons this.$btnToolbar = null; //defined farther down this.$dropdownGroup = null; //defined farther down this.$dropdownBtn = null; //defined farther down this.$dropdownContainer = null; //defined farther down this.$displayAllBtn = null; //defined farther down this.$focusGroup = null; //defined farther down this.$focusBtn = null; //defined farther down //misc this.displayAllTrigger = 'display-all-' + this.id + '.responsive-table'; this.idPrefix = this.id + '-col-'; // Check if iOS // property to save performance this.iOS = isIOS(); // Setup table // ------------------------- //wrap table this.wrapTable(); //create toolbar with buttons this.createButtonToolbar(); // Setup cells // ------------------------- //setup header cells this.setupHdrCells(); //setup standard cells this.setupStandardCells(); //create sticky table head if(this.options.stickyTableHeader){ this.createStickyTableHeader(); } // hide toggle button if the list is empty if(this.$dropdownContainer.is(':empty')){ this.$dropdownGroup.hide(); } // Event binding // ------------------------- // on orientchange, resize and displayAllBtn-click $(window).bind('orientationchange resize ' + this.displayAllTrigger, function(){ //update the inputs' checked status that.$dropdownContainer.find('input').trigger('updateCheck'); //update colspan and visibility of spanning cells $.proxy(that.updateSpanningCells(), that); }); }; ResponsiveTable.DEFAULTS = { pattern: 'priority-columns', stickyTableHeader: true, fixedNavbar: '.navbar-fixed-top', // Is there a fixed navbar? The stickyTableHeader needs to know about it! addDisplayAllBtn: true, // should it have a display-all button? addFocusBtn: true, // should it have a focus button? focusBtnIcon: 'glyphicon glyphicon-screenshot' }; // Wrap table ResponsiveTable.prototype.wrapTable = function() { this.$tableScrollWrapper.wrap('
'); this.$tableWrapper = this.$tableScrollWrapper.parent(); }; // Create toolbar with buttons ResponsiveTable.prototype.createButtonToolbar = function() { var that = this; this.$btnToolbar = $(''); this.$dropdownGroup = $(''); this.$dropdownBtn = $(''); this.$dropdownContainer = $(''); // Focus btn if(this.options.addFocusBtn) { // Create focus btn group this.$focusGroup = $(''); // Create focus btn this.$focusBtn = $(''); if(this.options.focusBtnIcon) { this.$focusBtn.prepend(' '); } // Add btn to group this.$focusGroup.append(this.$focusBtn); // Add focus btn to toolbar this.$btnToolbar.append(this.$focusGroup); // bind click on focus btn this.$focusBtn.click(function(){ $.proxy(that.activateFocus(), that); }); // bind click on rows this.$bodyRows.click(function(){ $.proxy(that.focusOnRow($(this)), that); }); } // Display-all btn if(this.options.addDisplayAllBtn) { // Create display-all btn this.$displayAllBtn = $(''); // Add display-all btn to dropdown-btn-group this.$dropdownGroup.append(this.$displayAllBtn); if (this.$table.hasClass('display-all')) { // add 'btn-primary' class to btn to indicate that display all is activated this.$displayAllBtn.addClass('btn-primary'); } // bind click on display-all btn this.$displayAllBtn.click(function(){ $.proxy(that.displayAll(null, true), that); }); } //add dropdown btn and menu to dropdown-btn-group this.$dropdownGroup.append(this.$dropdownBtn).append(this.$dropdownContainer); //add dropdown group to toolbar this.$btnToolbar.append(this.$dropdownGroup); // add toolbar above table this.$tableScrollWrapper.before(this.$btnToolbar); }; ResponsiveTable.prototype.clearAllFocus = function() { this.$bodyRows.removeClass('unfocused'); this.$bodyRows.removeClass('focused'); }; ResponsiveTable.prototype.activateFocus = function() { // clear all this.clearAllFocus(); if(this.$focusBtn){ this.$focusBtn.toggleClass('btn-primary'); } this.$table.toggleClass('focus-on'); }; ResponsiveTable.prototype.focusOnRow = function(row) { // only if activated (.i.e the table has the class focus-on) if(this.$table.hasClass('focus-on')) { var alreadyFocused = $(row).hasClass('focused'); // clear all this.clearAllFocus(); if(!alreadyFocused) { this.$bodyRows.addClass('unfocused'); $(row).addClass('focused'); } } }; /** * @param activate Forces the displayAll to be active or not. If anything else than bool, it will not force the state so it will toggle as normal. * @param trigger Bool to indicate if the displayAllTrigger should be triggered. */ ResponsiveTable.prototype.displayAll = function(activate, trigger) { if(this.$displayAllBtn){ // add 'btn-primary' class to btn to indicate that display all is activated this.$displayAllBtn.toggleClass('btn-primary', activate); } this.$table.toggleClass('display-all', activate); if(this.$tableClone){ this.$tableClone.toggleClass('display-all', activate); } if(trigger) { $(window).trigger(this.displayAllTrigger); } }; ResponsiveTable.prototype.preserveDisplayAll = function() { var displayProp = 'table-cell'; if($('html').hasClass('lt-ie9')){ displayProp = 'inline'; } $(this.$table).find('th, td').css('display', displayProp); if(this.$tableClone){ $(this.$tableClone).find('th, td').css('display', displayProp); } }; ResponsiveTable.prototype.createStickyTableHeader = function() { var that = this; //clone table head that.$tableClone = that.$table.clone(); //replace ids that.$tableClone.prop('id', this.id + '-clone'); that.$tableClone.find('[id]').each(function() { $(this).prop('id', $(this).prop('id') + '-clone'); }); // wrap table clone (this is our "sticky table header" now) that.$tableClone.wrap(''); that.$stickyTableHeader = that.$tableClone.parent(); // give the sticky table header same height as original that.$stickyTableHeader.css('height', that.$thead.height() + 2); //insert sticky table header if($('html').hasClass('lt-ie10')){ that.$tableWrapper.prepend(that.$stickyTableHeader); } else { that.$table.before(that.$stickyTableHeader); } // var bodyRowsClone = $(tableClone).find('tbody').find('tr'); // bind scroll and resize with updateStickyTableHeader $(window).bind('scroll resize', function(){ $.proxy(that.updateStickyTableHeader(), that); }); $(that.$tableScrollWrapper).bind('scroll', function(){ $.proxy(that.updateStickyTableHeader(), that); }); }; // Help function for sticky table header ResponsiveTable.prototype.updateStickyTableHeader = function() { var that = this, top = 0, offsetTop = that.$table.offset().top, scrollTop = $(window).scrollTop() -1, //-1 to accomodate for top border maxTop = that.$table.height() - that.$stickyTableHeader.height(), rubberBandOffset = (scrollTop + $(window).height()) - $(document).height(), // useFixedSolution = that.$table.parent().prop('scrollWidth') === that.$table.parent().width(); useFixedSolution = !that.iOS, navbarHeight = 0; //Is there a fixed navbar? if($(that.options.fixedNavbar).length) { var $navbar = $(that.options.fixedNavbar).first(); navbarHeight = $navbar.height(); scrollTop = scrollTop + navbarHeight; } var shouldBeVisible = (scrollTop > offsetTop) && (scrollTop < offsetTop + that.$table.height()); if(useFixedSolution) { that.$stickyTableHeader.scrollLeft(that.$tableScrollWrapper.scrollLeft()); //add fixedSolution class that.$stickyTableHeader.addClass('fixed-solution'); // Calculate top property value (-1 to accomodate for top border) top = navbarHeight - 1; // When the about to scroll past the table, move sticky table head up if(((scrollTop - offsetTop) > maxTop)){ top -= ((scrollTop - offsetTop) - maxTop); that.$stickyTableHeader.addClass('border-radius-fix'); } else { that.$stickyTableHeader.removeClass('border-radius-fix'); } if (shouldBeVisible) { //show sticky table header and update top and width. that.$stickyTableHeader.css({ 'visibility': 'visible', 'top': top + 'px', 'width': that.$tableScrollWrapper.innerWidth() + 'px'}); //no more stuff to do - return! return; } else { //hide sticky table header and reset width that.$stickyTableHeader.css({'visibility': 'hidden', 'width': 'auto' }); } } else { // alternate method //remove fixedSolution class that.$stickyTableHeader.removeClass('fixed-solution'); //animation duration var animationDuration = 400; // Calculate top property value (-1 to accomodate for top border) top = scrollTop - offsetTop - 1; // Make sure the sticky table header doesn't slide up/down too far. if(top < 0) { top = 0; } else if (top > maxTop) { top = maxTop; } // Accomandate for rubber band effect if(rubberBandOffset > 0) { top = top - rubberBandOffset; } if (shouldBeVisible) { //show sticky table header (animate repositioning) that.$stickyTableHeader.css({ 'visibility': 'visible' }); that.$stickyTableHeader.animate({ 'top': top + 'px' }, animationDuration); // hide original table head that.$thead.css({ 'visibility': 'hidden' }); } else { that.$stickyTableHeader.animate({ 'top': '0' }, animationDuration, function(){ // show original table head that.$thead.css({ 'visibility': 'visible' }); // hide sticky table head that.$stickyTableHeader.css({ 'visibility': 'hidden' }); }); } } }; // Setup header cells ResponsiveTable.prototype.setupHdrCells = function() { var that = this; // for each header column that.$hdrCells.each(function(i){ var $th = $(this), id = $th.prop('id'), thText = $th.text(); // assign an id to each header, if none is in the markup if (!id) { id = that.idPrefix + i; $th.prop('id', id); } if(thText === ''){ thText = $th.attr('data-col-name'); } // create the hide/show toggle for the current column if ( $th.is('[data-priority]') ) { var $toggle = $('