948 lines
35 KiB
JavaScript
948 lines
35 KiB
JavaScript
/*!
|
|
* ZUI: 数据表格 - v1.9.1 - 2019-05-10
|
|
* http://zui.sexy
|
|
* GitHub: https://github.com/easysoft/zui.git
|
|
* Copyright (c) 2019 cnezsoft.com; Licensed MIT
|
|
*/
|
|
|
|
/* ========================================================================
|
|
* ZUI: datatable.js
|
|
* http://zui.sexy
|
|
* ========================================================================
|
|
* Copyright (c) 2014-2016 cnezsoft.com; Licensed MIT
|
|
* ======================================================================== */
|
|
(function($) {
|
|
'use strict';
|
|
|
|
var name = 'zui.datatable';
|
|
var store = $.zui.store;
|
|
|
|
/**
|
|
* Datatable class
|
|
*
|
|
* @param object element DOM element or jquery element
|
|
* @param object options Datatable options
|
|
*/
|
|
var DataTable = function(element, options) {
|
|
this.name = name;
|
|
this.$ = $(element);
|
|
this.isTable = (this.$[0].tagName === 'TABLE');
|
|
this.firstShow = true;
|
|
if(this.isTable) {
|
|
this.$table = this.$;
|
|
this.id = 'datatable-' + (this.$.attr('id') || $.zui.uuid());
|
|
} else {
|
|
this.$datatable = this.$.addClass('datatable');
|
|
if(this.$.attr('id')) {
|
|
this.id = this.$.attr('id');
|
|
} else {
|
|
this.id = 'datatable-' + $.zui.uuid();
|
|
this.$.attr('id', this.id);
|
|
}
|
|
}
|
|
this.getOptions(options);
|
|
this.load();
|
|
|
|
this.callEvent('ready');
|
|
};
|
|
|
|
// 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
|
|
checkboxName: null,
|
|
selectable: true,
|
|
|
|
// Sort options
|
|
sortable: false, // enable sorter
|
|
|
|
// storage
|
|
storage: true, // enable storage
|
|
|
|
// fixed header of columns
|
|
fixedHeader: false, // 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
|
|
scrollPos: 'in', // scroll bar position: 'out' | 'in'
|
|
|
|
// hover effection
|
|
rowHover: true, // apply hover effection to row
|
|
colHover: true, // apply hover effection to head
|
|
hoverClass: 'hover',
|
|
colHoverClass: 'col-hover',
|
|
|
|
|
|
// Fix cell height
|
|
fixCellHeight: true,
|
|
|
|
// Merge rows
|
|
mergeRows: false, // Merge rows
|
|
|
|
// 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);
|
|
|
|
options.tableClass = options.tableClass || '';
|
|
options.tableClass = ' ' + options.tableClass + ' table-datatable';
|
|
|
|
$.each(['bordered', 'condensed', 'striped', 'condensed', 'fixed'], function(idx, cls) {
|
|
cls = 'table-' + cls;
|
|
if($e.hasClass(cls)) options.tableClass += ' ' + cls;
|
|
});
|
|
|
|
if($e.hasClass('table-hover') || options.rowHover) {
|
|
options.tableClass += ' table-hover';
|
|
}
|
|
|
|
if(!options.checkable || !$.fn.selectable) options.selectable = false;
|
|
|
|
this.options = options;
|
|
};
|
|
|
|
// Load data form options or table dom
|
|
DataTable.prototype.load = function(data) {
|
|
var options = this.options,
|
|
cols;
|
|
|
|
if($.isFunction(data)) {
|
|
data = data(this.data, this);
|
|
data.keepSort = true;
|
|
} else if($.isPlainObject(data)) {
|
|
this.data = data;
|
|
} else if(typeof data === 'string') {
|
|
var $table = $(data);
|
|
if($table.length) {
|
|
this.$table = $table.first();
|
|
this.$table.data(name, this);
|
|
this.isTable = true;
|
|
}
|
|
data = null;
|
|
} else {
|
|
data = options.data;
|
|
}
|
|
|
|
if(!data) {
|
|
if(this.isTable) {
|
|
data = {
|
|
cols: [],
|
|
rows: []
|
|
};
|
|
cols = data.cols;
|
|
var rows = data.rows,
|
|
i,
|
|
$th, $tr, $td, row, $t = this.$table,
|
|
colSpan;
|
|
|
|
$t.children('thead').children('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',
|
|
ignore: $th.hasClass('ignore'),
|
|
sort: !$th.hasClass('sort-disabled'),
|
|
mergeRows: $th.attr('merge-rows'),
|
|
title: $th.attr('title')
|
|
}, $th.data()));
|
|
});
|
|
|
|
$t.children('tbody').children('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);
|
|
colSpan = $td.attr('colspan') || 1;
|
|
row.data.push($.extend({
|
|
cssClass: $td.attr('class'),
|
|
css: $td.attr('style'),
|
|
text: $td.html(),
|
|
colSpan: colSpan,
|
|
title: $td.attr('title')
|
|
}, $td.data()));
|
|
|
|
if(colSpan > 1) {
|
|
for(i = 1; i < colSpan; i++) {
|
|
row.data.push({
|
|
empty: true
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
rows.push(row);
|
|
});
|
|
|
|
var $tfoot = $t.children('tfoot');
|
|
if($tfoot.length) {
|
|
data.footer = $('<table class="table' + options.tableClass + '"></table>').append($tfoot);
|
|
}
|
|
} else {
|
|
throw new Error('No data avaliable!');
|
|
}
|
|
}
|
|
|
|
data.flexStart = -1;
|
|
data.flexEnd = -1;
|
|
|
|
cols = data.cols;
|
|
data.colsLength = cols.length;
|
|
for(var i = 0; i < data.colsLength; ++i) {
|
|
var col = cols[i];
|
|
if(col.flex) {
|
|
if(data.flexStart < 0) {
|
|
data.flexStart = i;
|
|
}
|
|
|
|
data.flexEnd = i;
|
|
}
|
|
}
|
|
|
|
if(data.flexStart === 0 && data.flexEnd === data.colsLength) {
|
|
data.flexStart = -1;
|
|
data.flexEnd = -1;
|
|
}
|
|
|
|
data.flexArea = data.flexStart >= 0;
|
|
data.fixedRight = data.flexEnd >= 0 && data.flexEnd < (data.colsLength - 1);
|
|
data.fixedLeft = data.flexStart > 0;
|
|
if(data.flexStart < 0 && data.flexEnd < 0) {
|
|
data.fixedLeft = true;
|
|
data.flexStart = data.colsLength;
|
|
data.flexEnd = data.colsLength;
|
|
}
|
|
|
|
this.data = data;
|
|
|
|
this.callEvent('afterLoad', {
|
|
data: data
|
|
});
|
|
|
|
this.render();
|
|
};
|
|
|
|
// Render datatable
|
|
DataTable.prototype.render = function() {
|
|
var that = this;
|
|
var $datatable = that.$datatable || (that.isTable ? $('<div class="datatable" id="' + that.id + '"/>') : that.$datatable),
|
|
options = that.options,
|
|
data = that.data,
|
|
cols = that.data.cols,
|
|
rows = that.data.rows;
|
|
var checkable = options.checkable,
|
|
$left,
|
|
i,
|
|
$right,
|
|
$flex,
|
|
dataRowSpan = '<div class="datatable-rows-span datatable-span"><div class="datatable-wrapper"><table class="table"></table></div></div>',
|
|
dataHeadSpan = '<div class="datatable-head-span datatable-span"><div class="datatable-wrapper"><table class="table"><thead></thead></table></div></div>';
|
|
|
|
$datatable.children('.datatable-head, .datatable-rows, .scroll-wrapper').remove();
|
|
|
|
// Set css class to datatable by options
|
|
$datatable.toggleClass('sortable', options.sortable);
|
|
// $datatable.toggleClass('customizable', options.customizable);
|
|
|
|
// Head
|
|
var $head = $('<div class="datatable-head"/>'),
|
|
$tr,
|
|
$th,
|
|
col;
|
|
$left = $('<tr class="datatable-row datatable-row-left"/>');
|
|
$right = $('<tr class="datatable-row datatable-row-right"/>');
|
|
$flex = $('<tr class="datatable-row datatable-row-flex"/>');
|
|
for(i = 0; i < cols.length; i++) {
|
|
col = cols[i];
|
|
$tr = i < data.flexStart ? $left : ((i >= data.flexStart && i <= data.flexEnd) ? $flex : $right);
|
|
if(i === 0 && checkable) {
|
|
$tr.append('<th data-index="check" class="check-all check-btn"><i class="icon-check-empty"></i></th>');
|
|
}
|
|
if(col.ignore) continue;
|
|
|
|
$th = $('<th class="datatable-head-cell"/>');
|
|
|
|
// set sort class
|
|
$th.toggleClass('sort-down', col.sort === 'down')
|
|
.toggleClass('sort-up', col.sort === 'up')
|
|
.toggleClass('sort-disabled', col.sort === false);
|
|
|
|
$th.addClass(col.cssClass)
|
|
.addClass(col.colClass)
|
|
.html(col.text)
|
|
.attr({
|
|
'data-index': i,
|
|
'data-type': col.type,
|
|
style: col.css,
|
|
title: col.title,
|
|
}).css('width', col.width);
|
|
$tr.append($th);
|
|
}
|
|
|
|
var $headSpan;
|
|
if(data.fixedLeft) {
|
|
$headSpan = $(dataHeadSpan);
|
|
$headSpan.addClass('fixed-left')
|
|
// .find('.datatable-wrapper')
|
|
// .append('<div class="size-handle size-handle-head size-handle-left"></div>')
|
|
.find('table')
|
|
.addClass(options.tableClass)
|
|
.find('thead').append($left);
|
|
$head.append($headSpan);
|
|
}
|
|
if(data.flexArea) {
|
|
$headSpan = $(dataHeadSpan);
|
|
$headSpan.addClass('flexarea')
|
|
.find('.datatable-wrapper')
|
|
.append('<div class="scrolled-shadow scrolled-in-shadow"></div><div class="scrolled-shadow scrolled-out-shadow"></div>')
|
|
.find('table')
|
|
.addClass(options.tableClass)
|
|
.find('thead').append($flex);
|
|
$head.append($headSpan);
|
|
}
|
|
if(data.fixedRight) {
|
|
$headSpan = $(dataHeadSpan);
|
|
$headSpan.addClass('fixed-right')
|
|
// .find('.datatable-wrapper')
|
|
// .append('<div class="size-handle size-handle-head size-handle-right"></div>')
|
|
.find('table')
|
|
.addClass(options.tableClass)
|
|
.find('thead').append($right);
|
|
$head.append($headSpan);
|
|
}
|
|
$datatable.append($head);
|
|
|
|
// Rows
|
|
var $rows = $('<div class="datatable-rows">');
|
|
var $leftRow,
|
|
$flexRow,
|
|
$rightRow,
|
|
// $tr,
|
|
$td,
|
|
$cTd,
|
|
row,
|
|
rowLen = rows.length,
|
|
rowCol,
|
|
rowColLen;
|
|
$left = $('<tbody/>');
|
|
$right = $('<tbody/>');
|
|
$flex = $('<tbody/>');
|
|
|
|
for(var r = 0; r < rowLen; ++r) {
|
|
row = rows[r];
|
|
|
|
// format row
|
|
if(typeof row.id === 'undefined') {
|
|
row.id = r;
|
|
}
|
|
row.index = r;
|
|
|
|
$leftRow = $('<tr class="datatable-row"/>');
|
|
$leftRow.addClass(row.cssClass)
|
|
.toggleClass(options.checkedClass, !!row.checked)
|
|
.attr({
|
|
'data-index': r,
|
|
'data-id': row.id
|
|
});
|
|
$flexRow = $leftRow.clone().addClass('datatable-row-flex');
|
|
$rightRow = $leftRow.clone().addClass('datatable-row-right');
|
|
$leftRow.addClass('datatable-row-left');
|
|
|
|
rowColLen = row.data.length;
|
|
for(i = 0; i < rowColLen; ++i) {
|
|
rowCol = row.data[i];
|
|
if(i > 0 && rowCol.empty) {
|
|
continue;
|
|
}
|
|
|
|
$tr = i < data.flexStart ? $leftRow : ((i >= data.flexStart && i <= data.flexEnd) ? $flexRow : $rightRow);
|
|
if(i === 0 && checkable) {
|
|
$cTd = $('<td data-index="check" class="check-row check-btn"><i class="icon-check-empty"></i></td>');
|
|
if(options.checkboxName) {
|
|
$cTd.append('<input class="hide" type="checkbox" name="' + options.checkboxName + '" value="' + row.id + '">');
|
|
}
|
|
$tr.append($cTd);
|
|
}
|
|
|
|
if(cols[i].ignore) continue;
|
|
|
|
// format row column
|
|
if(!$.isPlainObject(rowCol)) {
|
|
rowCol = {
|
|
text: rowCol,
|
|
row: r,
|
|
index: i
|
|
};
|
|
} else {
|
|
rowCol.row = r;
|
|
rowCol.index = i;
|
|
}
|
|
row.data[i] = rowCol;
|
|
|
|
$td = $('<td class="datatable-cell"/>');
|
|
|
|
$td.html(rowCol.text)
|
|
.addClass(rowCol.cssClass)
|
|
.addClass(cols[i].colClass)
|
|
.attr('colspan', rowCol.colSpan)
|
|
.attr({
|
|
'data-row': r,
|
|
'data-index': i,
|
|
'data-flex': false,
|
|
'data-type': cols[i].type,
|
|
style: rowCol.css,
|
|
title: rowCol.title || ''
|
|
}).css('width', cols[i].width);
|
|
|
|
|
|
$tr.append($td);
|
|
}
|
|
|
|
$left.append($leftRow);
|
|
$flex.append($flexRow);
|
|
$right.append($rightRow);
|
|
}
|
|
|
|
|
|
var $rowSpan;
|
|
if(data.fixedLeft) {
|
|
$rowSpan = $(dataRowSpan);
|
|
$rowSpan.addClass('fixed-left')
|
|
.find('table')
|
|
.addClass(options.tableClass)
|
|
.append($left);
|
|
$rows.append($rowSpan);
|
|
}
|
|
if(data.flexArea) {
|
|
$rowSpan = $(dataRowSpan);
|
|
$rowSpan.addClass('flexarea')
|
|
.find('.datatable-wrapper')
|
|
.append('<div class="scrolled-shadow scrolled-in-shadow"></div><div class="scrolled-shadow scrolled-out-shadow"></div>')
|
|
.find('table')
|
|
.addClass(options.tableClass)
|
|
.append($flex);
|
|
$rows.append($rowSpan);
|
|
}
|
|
if(data.fixedRight) {
|
|
$rowSpan = $(dataRowSpan);
|
|
$rowSpan.addClass('fixed-right')
|
|
.find('table')
|
|
.addClass(options.tableClass)
|
|
.append($right);
|
|
$rows.append($rowSpan);
|
|
}
|
|
$datatable.append($rows);
|
|
|
|
if(data.flexArea) {
|
|
$datatable.append('<div class="scroll-wrapper"><div class="scroll-slide scroll-pos-' + options.scrollPos + '"><div class="bar"></div></div></div>');
|
|
}
|
|
|
|
var $oldFooter = $datatable.children('.datatable-footer').detach();
|
|
if(data.footer) {
|
|
$datatable.append($('<div class="datatable-footer"/>').append(data.footer));
|
|
data.footer = null;
|
|
} else if($oldFooter.length) {
|
|
$datatable.append($oldFooter);
|
|
}
|
|
|
|
that.$datatable = $datatable.data(name, that);
|
|
if(that.isTable && that.firstShow) {
|
|
that.$table.attr('data-datatable-id', this.id).hide().after($datatable);
|
|
that.firstShow = false;
|
|
}
|
|
|
|
that.bindEvents();
|
|
that.refreshSize();
|
|
that.callEvent('render');
|
|
};
|
|
|
|
// Bind global events
|
|
DataTable.prototype.bindEvents = function() {
|
|
var that = this,
|
|
data = this.data,
|
|
options = this.options,
|
|
$datatable = this.$datatable;
|
|
|
|
var $dataSpans = that.$dataSpans = $datatable.children('.datatable-head, .datatable-rows').find('.datatable-span');
|
|
var $rowsSpans = that.$rowsSpans = $datatable.children('.datatable-rows').children('.datatable-rows-span');
|
|
var $headSpans = that.$headSpans = $datatable.children('.datatable-head').children('.datatable-head-span');
|
|
var $cells = that.$cells = $dataSpans.find('.datatable-head-cell,.datatable-cell');
|
|
var $dataCells = that.$dataCells = $cells.filter('.datatable-cell');
|
|
that.$headCells = $cells.filter('.datatable-head-cell');
|
|
var $rows = that.$rows = that.$rowsSpans.find('.datatable-row');
|
|
|
|
// handle row hover events
|
|
if(options.rowHover) {
|
|
var hoverClass = options.hoverClass;
|
|
$rowsSpans.on('mouseenter', '.datatable-cell', function() {
|
|
$dataCells.filter('.' + hoverClass).removeClass(hoverClass);
|
|
$rows.filter('.' + hoverClass).removeClass(hoverClass);
|
|
|
|
$rows.filter('[data-index="' + $(this).addClass(hoverClass).data('row') + '"]').addClass(hoverClass);
|
|
}).on('mouseleave', '.datatable-cell', function() {
|
|
$dataCells.filter('.' + hoverClass).removeClass(hoverClass);
|
|
$rows.filter('.' + hoverClass).removeClass(hoverClass);
|
|
});
|
|
}
|
|
|
|
// handle col hover events
|
|
if(options.colHover) {
|
|
var colHoverClass = options.colHoverClass;
|
|
$headSpans.on('mouseenter', '.datatable-head-cell', function() {
|
|
$cells.filter('.' + colHoverClass).removeClass(colHoverClass);
|
|
$cells.filter('[data-index="' + $(this).data('index') + '"]').addClass(colHoverClass);
|
|
}).on('mouseleave', '.datatable-head-cell', function() {
|
|
$cells.filter('.' + colHoverClass).removeClass(colHoverClass);
|
|
});
|
|
}
|
|
|
|
// handle srcoll for flex area
|
|
if(data.flexArea) {
|
|
var $scrollbar = $datatable.find('.scroll-slide'),
|
|
$flexArea = $datatable.find('.datatable-span.flexarea'),
|
|
$fixedLeft = $datatable.find('.datatable-span.fixed-left'),
|
|
$flexTable = $datatable.find('.datatable-span.flexarea .table-datatable');
|
|
var $bar = $scrollbar.children('.bar'),
|
|
flexWidth,
|
|
scrollWidth,
|
|
tableWidth,
|
|
barLeft,
|
|
scrollOffsetStoreName = that.id + '_' + 'scrollOffset',
|
|
firtScroll,
|
|
left;
|
|
|
|
that.width = $datatable.width();
|
|
$datatable.resize(function() {
|
|
that.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));
|
|
$flexTable.css('left', left);
|
|
|
|
$datatable.toggleClass('scrolled-in', barLeft > 2)
|
|
.toggleClass('scrolled-out', barLeft < flexWidth - scrollWidth - 2);
|
|
|
|
if(options.storage) store.pageSet(scrollOffsetStoreName, barLeft);
|
|
};
|
|
var resizeScrollbar = function() {
|
|
flexWidth = $flexArea.width() - 2;
|
|
$scrollbar.width(flexWidth).css('left', $fixedLeft.width());
|
|
tableWidth = 0;
|
|
$flexTable.first().find('tr:first').children('td,th').each(function() {
|
|
tableWidth += $(this).outerWidth();
|
|
});
|
|
scrollWidth = Math.floor((flexWidth * flexWidth) / tableWidth);
|
|
$bar.css('width', scrollWidth);
|
|
$flexTable.css('min-width', Math.max(flexWidth, tableWidth));
|
|
$datatable.toggleClass('show-scroll-slide', tableWidth > flexWidth);
|
|
|
|
if(!firtScroll && flexWidth !== scrollWidth) {
|
|
firtScroll = true;
|
|
srollTable(store.pageGet(scrollOffsetStoreName, 0), true); // todo: unused?
|
|
}
|
|
|
|
if($datatable.hasClass('size-changing')) {
|
|
srollTable(barLeft, true);
|
|
}
|
|
};
|
|
$flexArea.resize(resizeScrollbar);
|
|
if(options.storage) 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');
|
|
}
|
|
};
|
|
|
|
if($.fn.draggable) {
|
|
$bar.draggable(dragOptions);
|
|
if(options.flexHeadDrag) {
|
|
$datatable.find('.datatable-head-span.flexarea').draggable(dragOptions);
|
|
}
|
|
} else {
|
|
console.error('DataTable requires draggable.js to improve UI.');
|
|
}
|
|
|
|
$scrollbar.mousedown(function(event) {
|
|
var x = event.pageX - $scrollbar.offset().left;
|
|
srollTable(x - (scrollWidth / 2));
|
|
});
|
|
}
|
|
|
|
// handle row check events
|
|
if(options.checkable) {
|
|
var checkedStatusStoreName = that.id + '_checkedStatus',
|
|
checkedClass = options.checkedClass,
|
|
rowId;
|
|
var syncChecks = function() {
|
|
var $checkRows = $rowsSpans.first().find('.datatable-row');
|
|
var $checkedRows = $checkRows.filter('.' + checkedClass);
|
|
if(options.checkboxName) $checkRows.find('.check-row input:checkbox').prop('checked', false);
|
|
var checkedStatus = {
|
|
checkedAll: $checkRows.length === $checkedRows.length && $checkedRows.length > 0,
|
|
checks: $checkedRows.map(function() {
|
|
rowId = $(this).data('id');
|
|
if(options.checkboxName) {
|
|
$(this).find('.check-row input:checkbox').prop('checked', true);
|
|
}
|
|
return rowId;
|
|
}).toArray()
|
|
};
|
|
that.checks = checkedStatus;
|
|
$.each(data.rows, function(index, value) {
|
|
value.checked = ($.inArray(value.id, checkedStatus.checks) > -1);
|
|
});
|
|
$headSpans.find('.check-all').toggleClass('checked', !!checkedStatus.checkedAll);
|
|
|
|
if(options.storage) store.pageSet(checkedStatusStoreName, checkedStatus);
|
|
|
|
that.callEvent('checksChanged', {
|
|
checks: checkedStatus
|
|
});
|
|
};
|
|
|
|
var toggleRowClass = function(ele, toggle) {
|
|
var $tr = $(ele).closest('tr');
|
|
if(toggle === undefined) toggle = !$tr.hasClass(checkedClass);
|
|
$rows.filter('[data-index="' + $tr.data('index') + '"]').toggleClass(checkedClass, !!toggle);
|
|
};
|
|
|
|
var checkEventPrefix = 'click.zui.datatable.check';
|
|
if(options.selectable) {
|
|
var selectableOptions = {
|
|
selector: '.datatable-rows .datatable-row',
|
|
trigger: '.datatable-rows',
|
|
start: function(e) {
|
|
var $checkRow = $(e.target).closest('.check-row, .check-btn');
|
|
if($checkRow.length) {
|
|
if($checkRow.is('.check-row')) {
|
|
toggleRowClass($checkRow);
|
|
syncChecks();
|
|
}
|
|
return false;
|
|
}
|
|
},
|
|
rangeFunc: function(range, targetRange) {
|
|
return Math.max(range.top, targetRange.top) < Math.min(range.top + range.height, targetRange.top + targetRange.height);
|
|
},
|
|
select: function(e) {
|
|
toggleRowClass(e.target, true);
|
|
},
|
|
unselect: function(e) {
|
|
toggleRowClass(e.target, false);
|
|
},
|
|
finish: function(e) {
|
|
syncChecks();
|
|
}
|
|
};
|
|
if($.isPlainObject(options.selectable)) {
|
|
$.extend(selectableOptions, options.selectable);
|
|
}
|
|
this.$datatable.selectable(selectableOptions);
|
|
} else {
|
|
this.$rowsSpans.off(checkEventPrefix).on(checkEventPrefix + 'row', options.checkByClickRow ? 'tr' : '.check-row', function() {
|
|
toggleRowClass(this);
|
|
syncChecks();
|
|
});
|
|
}
|
|
|
|
this.$datatable.off(checkEventPrefix).on('click.zui.datatable.check', '.check-all', function() {
|
|
$rows.toggleClass(checkedClass, $(this).toggleClass('checked').hasClass('checked'));
|
|
syncChecks();
|
|
}).on(checkEventPrefix + '.none', '.check-none', function() {
|
|
$rows.toggleClass(checkedClass, false);
|
|
syncChecks();
|
|
}).on(checkEventPrefix + '.inverse', '.check-inverse', function() {
|
|
$rows.toggleClass(checkedClass);
|
|
syncChecks();
|
|
});
|
|
|
|
if(options.storage) {
|
|
var checkedStatus = store.pageGet(checkedStatusStoreName);
|
|
if(checkedStatus) {
|
|
$headSpans.find('.check-all').toggleClass('checked', checkedStatus.checkedAll);
|
|
if(checkedStatus.checkedAll) {
|
|
$rows.addClass(checkedClass);
|
|
} else {
|
|
$rows.removeClass(checkedClass);
|
|
$.each(checkedStatus.checks, function(index, ele) {
|
|
$rows.filter('[data-id="' + ele + '"]').addClass(checkedClass);
|
|
});
|
|
}
|
|
if(checkedStatus.checks.length) {
|
|
syncChecks();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// fixed 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();
|
|
}
|
|
|
|
// handle sort
|
|
if(options.sortable) {
|
|
$headSpans.on('click', 'th:not(.sort-disabled, .check-btn)', function() {
|
|
if($datatable.hasClass('size-changing')) return;
|
|
that.sortTable($(this));
|
|
});
|
|
|
|
if(options.storage) that.sortTable();
|
|
} else if(options.mergeRows) {
|
|
this.mergeRows();
|
|
}
|
|
};
|
|
|
|
DataTable.prototype.mergeRows = function() {
|
|
var $cells = this.$rowsSpans.find('.datatable-cell');
|
|
var cols = this.data.cols;
|
|
for(var i = 0; i < cols.length; i++) {
|
|
var col = cols[i];
|
|
if(col.mergeRows) {
|
|
var $cs = $cells.filter('[data-index="' + i + '"]');
|
|
if($cs.length > 1) {
|
|
var $lastCell, rowspan;
|
|
$cs.each(function() {
|
|
var $cell = $(this);
|
|
if($lastCell) {
|
|
if($cell.html() === $lastCell.html()) {
|
|
rowspan = $lastCell.attr('rowspan') || 1;
|
|
if(typeof rowspan !== 'number') {
|
|
rowspan = parseInt(rowspan);
|
|
if(isNaN(rowspan)) rowspan = 1;
|
|
}
|
|
|
|
$lastCell.attr('rowspan', rowspan + 1).css('vertical-align', 'middle');
|
|
$cell.remove();
|
|
} else {
|
|
$lastCell = $cell;
|
|
}
|
|
} else {
|
|
$lastCell = $cell;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Sort table
|
|
DataTable.prototype.sortTable = function($th) {
|
|
var store = $.zui.store,
|
|
options = this.options;
|
|
var sorterStoreName = this.id + '_datatableSorter';
|
|
var sorter = options.storage ? store.pageGet(sorterStoreName) : null;
|
|
|
|
// sort-down: desc
|
|
// sort-up: asc
|
|
|
|
if(!$th) {
|
|
if(sorter) {
|
|
$th = this.$headCells.filter('[data-index="' + sorter.index + '"]').addClass('sort-' + (sorter.type === 'up' ? 'down' : 'up'));
|
|
} 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,
|
|
index;
|
|
|
|
sortUp = !$th.hasClass('sort-up');
|
|
if(data.keepSort) sortUp = !sortUp;
|
|
data.keepSort = null;
|
|
|
|
$headCells.removeClass('sort-up sort-down');
|
|
$th.addClass(sortUp ? 'sort-up' : 'sort-down');
|
|
|
|
index = $th.data('index');
|
|
|
|
$.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;
|
|
});
|
|
});
|
|
|
|
sorter = {
|
|
index: index,
|
|
type: sortUp ? 'up' : 'down'
|
|
};
|
|
|
|
// save sort with local storage
|
|
if(options.storage) store.pageSet(sorterStoreName, sorter);
|
|
|
|
this.callEvent('sort', {
|
|
sorter: sorter
|
|
});
|
|
};
|
|
|
|
// Refresh size
|
|
DataTable.prototype.refreshSize = function() {
|
|
var $datatable = this.$datatable,
|
|
options = this.options,
|
|
rows = this.data.rows,
|
|
cols = this.data.cols,
|
|
i;
|
|
|
|
$datatable.find('.datatable-span.fixed-left').css('width', options.fixedLeftWidth);
|
|
$datatable.find('.datatable-span.fixed-right').css('width', options.fixedRightWidth);
|
|
|
|
if(options.fixCellHeight) {
|
|
var findMaxHeight = function($cells) {
|
|
var mx = 0,
|
|
$cell, rowSpan;
|
|
$cells.css('height', 'auto');
|
|
$cells.each(function() {
|
|
$cell = $(this);
|
|
rowSpan = $cell.attr('rowspan');
|
|
if(!rowSpan || rowSpan == 1) mx = Math.max(mx, $cell.outerHeight());
|
|
});
|
|
return mx;
|
|
};
|
|
var $dataCells = this.$dataCells,
|
|
$cells = this.$cells,
|
|
$headCells = this.$headCells;
|
|
|
|
// set height of head cells
|
|
var headMaxHeight = findMaxHeight($headCells);
|
|
$headCells.css('min-height', headMaxHeight).css('height', headMaxHeight);
|
|
|
|
// set height of data cells
|
|
var $rowCells;
|
|
for(i = 0; i < rows.length; ++i) {
|
|
$rowCells = $dataCells.filter('[data-row="' + i + '"]');
|
|
var rowMaxHeight = findMaxHeight($rowCells);
|
|
$rowCells.css('min-height', rowMaxHeight).css('height', rowMaxHeight);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Call event
|
|
DataTable.prototype.callEvent = function(name, params) {
|
|
var result = this.$.callEvent(name + '.' + this.name, params, this).result;
|
|
return !(result !== undefined && (!result));
|
|
};
|
|
|
|
$.fn.datatable = function(option, newData) {
|
|
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') {
|
|
if(option === 'load' && $.isPlainObject(newData) && (newData.keepSort === undefined || newData.keepSort === null)) newData.keepSort = true;
|
|
data[option](newData);
|
|
}
|
|
});
|
|
};
|
|
|
|
$.fn.datatable.Constructor = DataTable;
|
|
}(jQuery));
|