/* DataTable */ +function($, window, document, Math) { "use strict"; var name = 'zui.datatable'; var DataTable = function(element, options) { this.name = name; this.$ = $(element); this.isTable = (this.$[0].tagName === 'TABLE'); if(this.isTable) { this.id = 'datatable-' + (this.$.attr('id') || $.uuid()); } else { this.$datatable = this.$.addClass('datatable'); if(this.$.attr('id')) { this.id = this.$.attr('id'); } else { this.id = 'datatable-' + $.uuid(); this.$.attr('id', this.id); } } this.getOptions(options); this.load(); this.render(); }; // default options DataTable.DEFAULTS = { // Check options checkable : false, // added check icon to the head of rows checkByClickRow: true, // change check status by click anywhere on a row checkedClass: 'active', // apply CSS class to an checked row // Sort options sortable : false, // enable sorter // fixed header of columns fixedHeader : true, // fixed header fixedHeaderOffset: 0, // set top offset of header when fixed fixedLeftWidth: '30%', // set left width after first render fixedRightWidth: '30%', // set right width after first render flexHeadDrag: true, // scroll flexarea by drag header // hover effection rowHover: true, // apply hover effection to row colHover: true, // apply hover effection to head // custom columns size customizable: false, // enable customizable minColWidth: 20, // min width of columns minFixedLeftWidth: 200, // min left width minFixedRightWidth: 200, // min right width minFlexAreaWidth: 200 // min flexarea width }; // Get options DataTable.prototype.getOptions = function (options) { var $e = this.$, options = $.extend({}, DataTable.DEFAULTS, this.$.data(), options); if(!options.tableClass) { options.tableClass = ' table-datatable'; if($e.hasClass('table-bordered')) { options.tableClass += ' table-bordered'; } if($e.hasClass('table-hover') || options.rowHover) { options.tableClass += ' table-hover'; } if($e.hasClass('table-striped')) { options.tableClass += ' table-striped'; } if($e.hasClass('table-condensed')) { options.tableClass += ' table-condensed'; } if($e.hasClass('table-fixed')) { options.tableClass += ' table-fixed'; } } else { options.tableClass = ' ' + options.tableClass; } this.options = options; }; // Load data form options or table dom DataTable.prototype.load = function() { var data = this.options.data, options = this.options, $t = this.$; if(!data) { if(this.isTable) { data = {cols: [], rows: []}; var cols = data.cols, rows = data.rows, $th, $tr, $td, row; $t.find('thead > tr:first').children('th').each(function() { $th = $(this); cols.push($.extend( { text: $th.html(), flex: false || $th.hasClass('flex-col'), width: 'auto', cssClass: $th.attr('class'), css: $th.attr('style'), type: 'string', sort: !$th.hasClass('sort-disabled') }, $th.data())); }); $t.find('tbody > tr').each(function() { $tr = $(this); row = $.extend( { data: [], checked: false, cssClass: $tr.attr('class'), css: $tr.attr('style'), id: $tr.attr('id') }, $tr.data()); $tr.children('td').each(function() { $td = $(this); row.data.push($.extend( { cssClass: $td.attr('class'), css: $td.attr('style'), text: $td.html() }, $td.data())); }); rows.push(row); }); var $tfoot = $t.find('tfoot'); if($tfoot.length) { data.footer = '' + $('
').append($tfoot).html() + '
'; } } else { throw new Error('No data avaliable!'); } } data.flexStart = -1; data.flexEnd = -1; var cols = data.cols; data.colsLength = cols.length; for(var i = 0; i < cols.length; ++i) { var col = cols[i]; if(col.flex) { if(data.flexStart < 0) { data.flexStart = i; } data.flexEnd = i; } } if(data.flexStart === 0 && data.flexEnd === cols.length) { data.flexStart = -1; data.flexEnd = -1; } data.flexArea = data.flexStart >= 0; data.fixedRight = data.flexEnd >= 0 && data.flexEnd < (cols.length-1); data.fixedLeft = data.flexStart > 0; if(data.flexStart < 0 && data.flexEnd < 0) { data.fixedLeft = true; data.flexStart = cols.length; data.flexEnd = cols.length; } this.data = data; this.callEvent('afterLoad', {data: data}); }; // Generage html DataTable.prototype.html = function() { var init = !$('#' + this.id).empty().length, html = '', options = this.options, data = this.data, cols = this.data.cols, leftHtml = '', rightHtml = '', flexHtml = '', rows = this.data.rows; var dataRowSpan = '
{1}
{2}
', dataHeadSpan = '
{1}
{2}
'; if(init) { html += '
'; } else { this.$datatable.toggleClass('sortable', options.sortable) .toggleClass('customizable', options.customizable); } // head html += '
'; var th, col, sortClass; for(var i = 0; i < cols.length; i++) { col = cols[i]; sortClass = ''; if(typeof col.customizable === 'undefined') { col.customizable = true; } if(typeof col.sort === 'undefined') { col.sort = true; } else if(col.sort === 'down') { sortClass = 'sort-down'; } else if(col.sort === 'up') { sortClass = 'sort-up'; } else if(col.sort === false) { sortClass = 'sort-disabled'; } th = '' + col.text + ((col.customizable && i != (data.flexStart - 1) && i != data.flexEnd && i < (cols.length - 1)) ? '
' : '') + ''; if(i == 0 && options.checkable) { th = '' + th; } if(i < data.flexStart) leftHtml += th; else if(i >= data.flexStart && i <= data.flexEnd) flexHtml += th; else if(i > data.flexEnd) rightHtml += th; } if(data.fixedLeft) { html += dataHeadSpan.format('fixed-left', leftHtml, '
'); } if(data.flexArea) { html += dataHeadSpan.format('flexarea', flexHtml, '
'); } if(data.fixedRight) { html += dataHeadSpan.format('fixed-right', rightHtml, '
'); } html += '
'; // cols html += '
'; var tr, row, i, td, cssClass, rowCol; leftHtml = ''; rightHtml = ''; flexHtml = ''; for(var r = 0; r < rows.length; ++r) { row = rows[r]; cssClass = row.cssClass || ''; if(row.checked) cssClass += ' ' + (options.checkedClass || ''); if(typeof row.id === 'undefined') { row.id = r; } row.index = r; tr = ''; leftHtml += tr; rightHtml += tr; flexHtml += tr; for(i = 0; i < row.data.length; ++i) { rowCol = row.data[i]; if(!$.isPlainObject(rowCol)) { rowCol = {text: rowCol, row: r, index: i}; row.data[i] = rowCol; } td = '' + rowCol.text + ''; if(i == 0 && options.checkable) { td = '' + td; } if(i < data.flexStart) leftHtml += td; else if(i >= data.flexStart && i <= data.flexEnd) flexHtml += td; else if(i > data.flexEnd) rightHtml += td; } leftHtml += ''; rightHtml += ''; flexHtml += ''; } if(data.fixedLeft) { html += dataRowSpan.format('fixed-left', leftHtml, ''); } if(data.flexArea) { html += dataRowSpan.format('flexarea', flexHtml, '
'); } if(data.fixedRight) { html += dataRowSpan.format('fixed-right', rightHtml, ''); } html += '
'; if(data.footer) { html += ''; } if(init) html += '
'; return html; }; // Render datatable DataTable.prototype.render = function() { if(this.isTable) { this.$.attr('data-datatable-id', this.id).hide(); this.$.after(this.html()); this.$datatable = $('#' + this.id); } else { this.$.html(this.html()); } this.$dataSpans = this.$datatable.children('.datatable-head, .datatable-rows').find('.datatable-span'); this.$rowsSpans = this.$datatable.children('.datatable-rows').children('.datatable-rows-span'); this.$headSpans = this.$datatable.children('.datatable-head').children('.datatable-head-span'); this.$cells = this.$dataSpans.find('td, th'); this.$dataCells = this.$cells.filter('td'); this.$headCells = this.$cells.filter('th'); this.$rows = this.$rowsSpans.find('.table > tbody > tr'); // bind events var options = this.options, self = this, data = this.data, $cells = this.$cells, $dataCells = this.$dataCells, $headCells = this.$headCells, $datatable = this.$datatable; // row hover if(options.rowHover) { var hoverClass = 'hover'; this.$rowsSpans.on('mouseenter', 'td', function() { $dataCells.filter('.' + hoverClass).removeClass(hoverClass); self.$rows.filter('.' + hoverClass).removeClass(hoverClass); self.$rows.filter('[data-index="' + $(this).addClass(hoverClass).closest('tr').data('index') + '"]').addClass(hoverClass); }).on('mouseleave', 'td', function() { $dataCells.filter('.' + hoverClass).removeClass(hoverClass); self.$rows.filter('.' + hoverClass).removeClass(hoverClass); }); } // col hover if(options.colHover) { var colHoverClass = 'col-hover'; this.$headSpans.on('mouseenter', 'th', function() { $cells.filter('.' + colHoverClass).removeClass(colHoverClass); $cells.filter('[data-index="' + $(this).data('index') + '"]').addClass(colHoverClass); }).on('mouseleave', 'th', function() { $cells.filter('.' + colHoverClass).removeClass(colHoverClass); }); } // scroll event if(this.data.flexArea) { var $scrollbar = $datatable.find('.scroll-slide'), $flexArea = $datatable.find('.datatable-span.flexarea .table'), $flexTable = $datatable.find('.datatable-rows-span.flexarea .table'); var $bar = $scrollbar.children('.bar'), flexWidth, scrollWidth, tableWidth, lastBarLeft, barLeft, scrollOffsetStoreName = self.id + '_' + 'scrollOffset', firtScroll, left; this.width = $datatable.width(); $datatable.resize(function(){self.width = $datatable.width();}); var srollTable = function(offset, silence) { barLeft = Math.max(0, Math.min(flexWidth - scrollWidth, offset)); if(!silence) { $datatable.addClass('scrolling'); } $bar.css('left', barLeft); left = 0 - Math.floor((tableWidth - flexWidth) * barLeft / (flexWidth - scrollWidth)); $flexArea.css('left', left); lastBarLeft = barLeft; $datatable.toggleClass('scrolled-in', barLeft > 2) .toggleClass('scrolled-out', barLeft < flexWidth - scrollWidth - 2); store.pageSet(scrollOffsetStoreName, barLeft); }; var resizeScrollbar = function() { flexWidth = $scrollbar.width(); tableWidth = $flexTable.width(); scrollWidth = Math.floor((flexWidth * flexWidth) / tableWidth); $bar.css('width', scrollWidth); $flexTable.css('min-width', flexWidth); $datatable.toggleClass('show-scroll-slide', tableWidth > flexWidth); if(!firtScroll && flexWidth !== scrollWidth) { firtScroll = true; srollTable(store.pageGet(scrollOffsetStoreName, 0), true); } if($datatable.hasClass('size-changing')) { srollTable(barLeft, true); } }; $scrollbar.resize(resizeScrollbar); $flexTable.resize(resizeScrollbar); resizeScrollbar(); var dragOptions = { move: false, stopPropagation: true, drag: function(e) { srollTable($bar.position().left + e.smallOffset.x * (e.element.hasClass('bar') ? 1 : -1)); }, finish: function() { $datatable.removeClass('scrolling'); } }; $bar.draggable(dragOptions); if(options.flexHeadDrag) { $datatable.find('.datatable-head-span.flexarea').draggable(dragOptions); } $scrollbar.mousedown(function(event) { var x = event.pageX - $scrollbar.offset().left; srollTable(x - (scrollWidth / 2)); }); } // row check if(options.checkable) { var checkedStatusStoreName = self.id + '_' + checkedStatus, checkedClass = options.checkedClass, rowId; var syncChecks = function() { var $rows = self.$rowsSpans.first().find('.table > tbody > tr'); var $checkedRows = $rows.filter('.' + checkedClass); var checkedStatus = { checkedAll: $rows.length === $checkedRows.length && $checkedRows.length > 0, checks: $checkedRows.map(function() { rowId = $(this).data('id'); return rowId; }).toArray() }; $.each(data.rows, function(index, value) { value.checked = ($.inArray(value.id, checkedStatus.checks) > -1); }); self.$headSpans.find('.check-all').toggleClass('checked', checkedStatus.checkedAll); store.pageSet(checkedStatusStoreName, checkedStatus); self.callEvent('checksChanged', {checks: checkedStatus}); }; this.$rowsSpans.on('click', options.checkByClickRow ? 'tr' : '.check-row', function() { self.$rows.filter('[data-index="' + $(this).closest('tr').data('index') + '"]').toggleClass(checkedClass); syncChecks(); }); this.$headSpans.find('.check-all').click(function() { self.$rows.toggleClass(checkedClass, $(this).toggleClass('checked').hasClass('checked')); syncChecks(); }); var checkedStatus = store.pageGet(checkedStatusStoreName); if(checkedStatus) { this.$headSpans.find('.check-all').toggleClass('checked', checkedStatus.checkedAll); if(checkedStatus.checkedAll) { self.$rows.addClass(checkedClass); } else { self.$rows.removeClass(checkedClass); $.each(checkedStatus.checks, function(index, ele) { self.$rows.filter('[data-id="' + ele + '"]').addClass(checkedClass); }); } } } // fix header if(options.fixedHeader) { var offsetTop, height, scrollTop, $dataTableHead = $datatable.children('.datatable-head'), navbarHeight = options.fixedHeaderOffset || $('.navbar.navbar-fixed-top').height() || 0; var handleScroll = function() { offsetTop = $datatable.offset().top; scrollTop = $(window).scrollTop(); height = $datatable.height(); $datatable.toggleClass('head-fixed', (scrollTop + navbarHeight) > offsetTop && (scrollTop + navbarHeight) < (offsetTop + height)); if($datatable.hasClass('head-fixed')) { $dataTableHead.css( { width: $datatable.width(), top: navbarHeight }); } else { $dataTableHead.attr('style', ''); } }; $(window).scroll(handleScroll); handleScroll(); } // make sortable if(options.sortable) { var $th, sortdown; this.$headSpans.on('click', 'th:not(.sort-disabled, .check-btn)', function() { if($datatable.hasClass('size-changing')) return; self.sortTable($(this)); }); } // custom column width if(options.customizable) { var oldWidth, $th, col, dragDirection, $span; var $sizeHandles = this.$headSpans.find('.size-handle'), refreshSize = function($handle, delta) { var eventParams = {}; if($handle.hasClass('size-handle-head')) { oldWidth = $handle.closest('.datatable-head-span').width(); if($handle.hasClass('size-handle-left')) { eventParams.change = 'fixedLeftWidth'; eventParams.oldWidth = self.fixedLeftWidth; self.fixedLeftWidth = Math.min(self.width - (self.fixedRightWidth || self.$headSpans.filter('.fixed-right').width()) - options.minFlexAreaWidth, Math.max(options.minFixedLeftWidth, oldWidth + delta)); eventParams.newWidth = self.fixedLeftWidth; } else { eventParams.change = 'fixedRightWidth'; eventParams.oldWidth = self.fixedRightWidth; self.fixedRightWidth = Math.min(self.width - (self.fixedLeftWidth || self.$headSpans.filter('.fixed-left').width()) - options.minFlexAreaWidth, Math.max(options.minFixedRightWidth, oldWidth - delta)); eventParams.newWidth = self.fixedRightWidth; } } else { $th = $handle.closest('th'); col = data.cols[$th.data('index')]; oldWidth = col.width; if(oldWidth === 'auto') { $handle = $th.closest('.datatable-head-span'); oldWidth = $handle.width(); if($handle.hasClass('fixed-left')) { eventParams.change = 'fixedLeftWidth'; eventParams.oldWidth = self.fixedLeftWidth; self.fixedLeftWidth = Math.min(self.width - (self.fixedRightWidth || self.$headSpans.filter('.fixed-right').width()) - options.minFlexAreaWidth, Math.max(options.minFixedLeftWidth, oldWidth + delta)); eventParams.newWidth = self.fixedLeftWidth; } else if($handle.hasClass('fixed-right')) { eventParams.change = 'fixedRightWidth'; eventParams.oldWidth = self.fixedRightWidth; self.fixedRightWidth = Math.min(self.width - (self.fixedLeftWidth || self.$headSpans.filter('.fixed-left').width()) - options.minFlexAreaWidth, Math.max(options.minFixedRightWidth, oldWidth - delta)); eventParams.newWidth = self.fixedRightWidth; } } else { eventParams.change = 'colWidth'; eventParams.oldWidth = col.width; eventParams.colIndex = col.index; col.width = Math.max(options.minColWidth, oldWidth + delta); eventParams.newWidth = col.width; } } self.refreshSize(); self.callEvent('sizeChanged', {changes: eventParams}); }; $sizeHandles.draggable( { move: false, stopPropagation: true, before: function() { dragDirection = null; $datatable.addClass('size-changing'); }, drag: function(e) { refreshSize(e.element, e.smallOffset.x); }, finish: function() { $datatable.removeClass('size-changing'); } }); } this.refresh(); this.callEvent('ready'); }; // Sort table DataTable.prototype.sortTable = function($th) { var sorterStoreName = self.id + '_' + 'datatableSorter'; var sorter = store.pageGet(sorterStoreName); if(!$th) { if(sorter) { $th = this.$headCells.filter('[data-index="' + sorter.index + '"]').addClass('sort-' + sorter.type); } else { $th = this.$headCells.filter('.sort-up, .sort-down').first(); } } if(!$th.length) { return; } var data = this.data; var cols = data.cols, rows = data.rows, $headCells = this.$headCells, sortUp, type, sortCol, index; sortUp = !$th.hasClass('sort-up'); $headCells.removeClass('sort-up sort-down'); $th.addClass(sortUp ? 'sort-up' : 'sort-down'); index = $th.data('index'); sortUp = $th.hasClass('sort-up'); $.each(cols, function(idx, col) { if(idx != index && (col.sort === 'up' || col.sort === 'down')) { col.sort = true; } else if(idx == index) { col.sort = sortUp ? 'up' : 'down'; type = col.type; } }); var valA, valB, result, $dataRows = this.$dataCells.filter('[data-index="' + index + '"]'); rows.sort(function(cellA, cellB) { cellA = cellA.data[index]; cellB = cellB.data[index]; valA = $dataRows.filter('[data-row="' + cellA.row + '"]').text(); valB = $dataRows.filter('[data-row="' + cellB.row + '"]').text(); if(type === 'number') { valA = parseFloat(valA); valB = parseFloat(valB); } else if(type === 'date') { valA = Date.parse(valA); valB = Date.parse(valB); } else { valA = valA.toLowerCase(); valB = valB.toLowerCase(); } result = valA > valB ? 1 : (valA < valB ? -1 : 0); if(sortUp) { result = result * (-1); } return result; }); var $rows = this.$rows, lastRows = [], $row, $lastRow, $r; $.each(rows, function(idx, row) { $row = $rows.filter('[data-index="' + row.index + '"]'); $row.each(function(rIdx) { $r = $(this); $lastRow = lastRows[rIdx]; if($lastRow) { $lastRow.after($r); } else { $r.parent().prepend($r); } lastRows[rIdx] = $r; }); }); var sorter = {index: index, type: sortUp ? 'up' : 'down'}; // save sort with local storage store.pageSet(sorterStoreName, sorter); this.callEvent('sort', {sorter: sorter}); }; DataTable.prototype.callEvent = function(name, params) { var result = this.$.callEvent(name + '.' + this.name, params, this).result; return !(result != undefined && (!result)); }; // Refresh size DataTable.prototype.refreshSize = function() { var $datatable = this.$datatable, options = this.options, rows = this.data.rows, cols = this.data.cols; $datatable.find('.datatable-span.fixed-left').css('width', this.fixedLeftWidth || options.fixedLeftWidth); $datatable.find('.datatable-span.fixed-right').css('width', this.fixedRightWidth || options.fixedRightWidth); var findMaxHeight = function($cells) { var mx = 0; $cells.css('height', 'auto'); $cells.each(function() { mx = Math.max(mx, $(this).height()); }); return mx; }, $dataCells = this.$dataCells, $cells = this.$cells, $headCells = this.$headCells; // set width of data cells for(var i = 0; i < cols.length; ++i) { $cells.filter('[data-index="' + i + '"]').css('width', cols[i].width); } // set height of head cells $headCells.height(findMaxHeight($headCells)); // set height of data cells var $rowCells; for(var i = 0; i < rows.length; ++i) { $rowCells = $dataCells.filter('[data-row="' + i + '"]'); $rowCells.height(findMaxHeight($rowCells)); } }; // Refresh changes of ui DataTable.prototype.refresh = function() { this.refreshSize(); this.sortTable(); }; $.fn.datatable = function(option) { return this.each(function() { var $this = $(this); var data = $this.data(name); var options = typeof option == 'object' && option; if (!data) $this.data(name, (data = new DataTable(this, options))); if (typeof option == 'string') data[option](); }); }; $.fn.datatable.Constructor = DataTable; }(jQuery,window,document,Math);