/**
* @param options.$el The jQuery element to render to. Must be in the DOM.
* @param options.items An array of items to display in the table
* @param options.search Whether to create a search widget
* @param options.rowHeader Renderer to call for each row in the table
* @param options.rowHeight Table row height
* @param height: Height in pixels of table. '564px',
* @param options.collapseBreakpoint: 500
* @param options.showHeader: true
* @param options.select: true
* @param options.responsive: true
* @param options.fixedWidth: Fixed table with when responsive is false. '320px'
* @param options.columns An array of column descriptors. Each column can have the properties:
* visible, name, field, renderer
*/
morpheus.Table = function (options) {
options = morpheus.Table.createOptions(options);
this.options = options;
if (!options.width) {
options.width = options.$el.attr('class');
}
var _this = this;
var height = options.height;
var $gridDiv = $('
');
this.$gridDiv = $gridDiv;
$gridDiv.appendTo(options.$el);
// all columns (including those that are currently not visible */
var columns = options.columns;
this.columns = columns;
var visibleColumns = columns.filter(function (c) {
return c.visible;
});
var grid = new morpheus.Grid({
gridOptions: {
select: options.select,
rowHeight: options.rowHeight,
autoEdit: false,
editable: false,
autoHeight: options.height === 'auto',
enableTextSelectionOnCells: true,
},
$el: $gridDiv,
items: options.items,
columns: visibleColumns
});
this.grid = grid;
this.searchFunction = null;
var searchFilter = {
isEmpty: function () {
return _this.searchFunction == null;
},
init: function () {
},
accept: function (item) {
return _this.searchFunction(item);
}
};
// add empty search filter
this.grid
.getFilter().add(searchFilter);
var $header = $('
');
this.$header = $header;
var $right = $header.find('.pull-right');
if (options.search) {
var tableSearch = new morpheus.TableSearchUI({
$el: $header.find('[name=top]'),
$right: $right
});
tableSearch.setTable(this);
this.tableSearch = tableSearch;
}
if (options.columnPicker && visibleColumns.length !== this.columns.length) {
var select = [];
select
.push('');
var $select = $(select.join(''));
$select.appendTo($right);
$select.selectpicker({
iconBase: 'fa',
tickIcon: 'fa-check',
style: 'btn-default btn-xs'
});
$select.on('change', function () {
var oldColumns = grid.getColumns().map(function (c) {
return c.id;
});
var selectedColumnIndices = $select.val();
visibleColumns = [];
for (var i = 0; i < selectedColumnIndices.length; i++) {
visibleColumns.push(sortedColumns[parseInt(selectedColumnIndices[i])]);
}
var newColumns = visibleColumns.map(function (c) {
return c.id;
});
grid.setColumns(visibleColumns);
if (newColumns.length > oldColumns.length) {
var set = new morpheus.Set();
for (var i = 0; i < newColumns.length; i++) {
set.add(newColumns[i]);
}
for (var i = 0; i < oldColumns.length; i++) {
set.remove(oldColumns[i]);
}
var added = set.values();
grid.setSortColumns([{
columnId: added[0],
sortAsc: true
}]);
}
// if column added, sort by added column
_this.resize();
_this.redraw();
});
}
$header.prependTo(options.$el);
var collapsed = false;
var lastWidth = -1;
var resize = function () {
if (!_this.options.responsive) {
return;
}
var gridWidth = options.$el.width();
if (gridWidth === lastWidth) {
return;
}
lastWidth = gridWidth;
$gridDiv.css('width', gridWidth + 'px');
// if (options.responsiveHeight) {
// var verticalPosition = _this.$gridDiv[0].getBoundingClientRect().top
// + window.pageYOffset;
// $gridDiv.css('height',
// (document.body.clientHeight - verticalPosition) + 'px');
// }
if (!collapsed && gridWidth < options.collapseBreakpoint
&& visibleColumns.length > 1) {
collapsed = true;
$gridDiv.addClass('slick-stacked');
_this.grid.grid.getOptions().rowHeight = (options.collapsedRowHeight ? options.collapsedRowHeight : options.rowHeight)
* visibleColumns.length;
// collapse
_this.grid.grid
.setColumns([{
id: 0,
tooltip: function (item, value) {
var html = [];
for (var i = 0; i < visibleColumns.length; i++) {
var text = visibleColumns[i].tooltip(item, visibleColumns[i]
.getter(item));
if (text != null && text !== '') {
html.push(text);
}
}
return html.join(' ');
},
collapsed: true,
getter: function (item) {
return item;
},
formatter: function (row, cell, value, columnDef,
dataContext) {
var html = [];
html
.push('
');
if (options.rowHeader) { // e.g. render checkbox
html.push(options.rowHeader(dataContext));
html.push('');
}
for (var i = 0; i < visibleColumns.length; i++) {
if (i > 0) {
html.push('');
}
var c = visibleColumns[i];
html.push(c.name);
html.push(':');
var s = c.renderer(dataContext, c
.getter(dataContext));
html.push(s);
}
html.push('
');
return html.join('');
},
sortable: false,
name: ''
}]);
$gridDiv.find('.slick-header').hide();
_this.grid.grid.resizeCanvas();
_this.grid.grid.invalidate();
} else if (collapsed && gridWidth >= options.collapseBreakpoint) {
$gridDiv.removeClass('slick-stacked');
collapsed = false;
if (options.showHeader) {
$gridDiv.find('.slick-header').show();
}
_this.grid.grid.getOptions().rowHeight = options.rowHeight;
_this.grid.grid.setColumns(visibleColumns);
_this.grid.grid.resizeCanvas();
if (options.select) {
_this.grid.grid.setSelectedRows(_this.grid.grid
.getSelectedRows());
}
_this.grid.grid.invalidate();
} else {
_this.grid.grid.resizeCanvas();
_this.grid.grid.invalidate();
}
_this.grid.maybeAutoResizeColumns();
};
if (!options.showHeader) {
$gridDiv.find('.slick-header').hide();
}
if (options.responsive) {
$(window).on('resize orientationchange', resize);
$gridDiv.on('remove', function () {
$(window).off('resize', resize);
});
resize();
}
this.resize = resize;
if (visibleColumns.length > 1 && options.items != null
&& options.items.length > 0) {
this.setItems(options.items);
}
if (!$gridDiv.is(':visible')) {
// find 1st parent that is not visible
var $parent = $gridDiv;
var observer = new MutationObserver(function (mutations) {
if (window.getComputedStyle($parent[0]).display !== 'none') {
observer.disconnect();
resize();
}
});
while ($parent.length > 0) {
if (window.getComputedStyle($parent[0]).display === 'none') {
break;
}
$parent = $parent.parent();
}
if ($parent.length > 0) {
observer.observe($parent[0], {
attributes: true,
childList: false,
characterData: false
});
}
}
}
;
morpheus.Table.defaultRenderer = function (item, value) {
if (value == null) {
return '';
} else if (_.isNumber(value)) {
return morpheus.Util.nf(value);
} else if (morpheus.Util.isArray(value)) {
var s = [];
for (var i = 0, length = value.length; i < length; i++) {
if (i > 0) {
s.push(', ');
}
var val = value[i];
s.push(value[i]);
}
return s.join('');
} else {
return '' + value;
}
};
morpheus.Table.prototype = {
toText: function () {
var text = [];
var items = this.getItems();
var columns = this.columns.filter(function (c) {
return c.visible;
});
for (var j = 0; j < columns.length; j++) {
if (j > 0) {
text.push('\t');
}
text.push(columns[j].name);
}
text.push('\n');
for (var i = 0; i < items.length; i++) {
var item = items[i];
for (var j = 0; j < columns.length; j++) {
if (j > 0) {
text.push('\t');
}
var value = columns[j].getter(item);
text.push(morpheus.Util.toString(value));
}
text.push('\n');
}
return text.join('');
},
setHeight: function (height) {
this.options.height = height;
if (height === 'auto') {
this.$gridDiv.css('height', '');
this.grid.grid.getOptions().autoHeight = true;
this.grid.grid.setOptions(this.grid.grid.getOptions());
} else {
this.$gridDiv.css('height', height);
}
this.grid.grid.resizeCanvas();
if (height === 'auto') {
var height = this.getItems().length * this.options.rowHeight
+ this.options.rowHeight;
this.$gridDiv.find('.slick-viewport').css('height', height + 'px');
}
this.grid.grid.invalidate();
},
setSearchVisible: function (visible) {
this.$header.find('[name=search]').css('display', visible ? '' : 'none');
},
autocomplete: function (tokens, response) {
var matches = [];
var token = tokens != null && tokens.length > 0 ? tokens[tokens.selectionStartIndex]
: '';
token = $.trim(token);
var columns = this.columns.filter(function (c) {
return (c.searchable && c.visible) || c.alwaysSearch;
});
var ncolumns = columns.length;
var showField = ncolumns > 1;
if (token === '') {
if (ncolumns <= 1) {
return response(matches);
}
for (var i = 0; i < ncolumns; i++) {
var field = columns[i].name;
matches.push({
value: field + ':',
label: '' + field
+ ':',
show: true
});
// show column names
}
matches
.sort(function (a, b) {
return (a.value === b.value ? 0
: (a.value < b.value ? -1 : 1));
});
return response(matches);
}
var field = null;
var semi = token.indexOf(':');
var regex = new RegExp('^' + morpheus.Util.escapeRegex(token), 'i');
if (semi > 0) { // field search?
if (token.charCodeAt(semi - 1) !== 92) { // \:
var possibleField = $.trim(token.substring(0, semi));
if (possibleField.length > 0 && possibleField[0] === '"'
&& possibleField[token.length - 1] === '"') {
possibleField = possibleField.substring(1,
possibleField.length - 1);
}
var columnNameToColumn = new morpheus.Map();
var columnNames = columns.map(function (c) {
return c.name;
});
for (var i = 0; i < columnNames.length; i++) {
columnNameToColumn.set(columnNames[i], columns[i]);
}
var c = columnNameToColumn.get(possibleField);
if (c !== undefined) {
token = $.trim(token.substring(semi + 1));
columns = [c];
ncolumns = 1;
}
}
} else if (ncolumns > 1) {
var regex = new RegExp('^' + morpheus.Util.escapeRegex(token), 'i');
for (var j = 0; j < ncolumns; j++) {
var field = columns[j].name;
if (regex.test(field)) {
matches.push({
value: field + ':',
label: '' + field
+ ':',
show: true
});
}
}
}
var set = new morpheus.Set();
var regex = new RegExp('^' + morpheus.Util.escapeRegex(token), 'i');
var items = this.getItems();
var dataTypes = [];
// filter numeric columns
var filteredColumns = [];
columns.forEach(function (c) {
var dataType = null;
for (var i = 0, nitems = items.length; i < nitems; i++) {
var value = c.getter(items[i]);
if (value != null) {
dataType = morpheus.Util.getDataType(value);
break;
}
}
if (dataType === 'string' || dataType === '[string]') {
dataTypes.push(dataType);
filteredColumns.push(c);
}
});
columns = filteredColumns;
ncolumns = columns.length;
var maxSize = matches.length + 10;
for (var i = 0, nitems = items.length; i < nitems; i++) {
var item = items[i];
for (var j = 0; j < ncolumns; j++) {
var field = columns[j].name;
var value = columns[j].getter(item);
var dataType = dataTypes[j];
if (dataType === '[string]') {
var nvalues = value == null ? 0 : value.length;
for (var k = 0; k < nvalues; k++) {
var val = value[k];
if (regex.test(val) && !set.has(val)) {
set.add(val);
matches
.push({
value: showField ? (field + ':' + val)
: val,
label: showField ? (''
+ field
+ ':'
+ ''
+ val + '')
: (''
+ val + '')
});
}
if (matches.length === maxSize) {
return response(matches);
}
}
} else {
if (regex.test(value) && !set.has(value)) {
set.add(value);
matches
.push({
value: showField ? (field + ':' + value)
: value,
label: showField ? (''
+ field
+ ':'
+ ''
+ value + '')
: (''
+ value + '')
});
if (matches.length === maxSize) {
return response(matches);
}
}
}
}
}
return response(matches);
},
searchWithPredicates: function (predicates) {
if (predicates == null || predicates.length === 0) {
this.searchFunction = null;
this.grid
.setFilter(this.grid
.getFilter());
return;
}
var columns = this.columns.filter(function (c) {
return (c.searchable && c.visible) || c.alwaysSearch;
});
var columnNameToColumn = new morpheus.Map();
var columnNames = columns.map(function (c) {
return c.name;
});
for (var i = 0; i < columnNames.length; i++) {
columnNameToColumn.set(columnNames[i], columns[i]);
}
var filteredPredicates = [];
var npredicates = predicates.length;
for (var i = 0; i < npredicates; i++) {
var predicate = predicates[i];
var filterColumnName = predicate.getField();
if (filterColumnName != null) {
var column = columnNameToColumn.get(filterColumnName);
if (column) {
predicate.column = column;
filteredPredicates.push(predicate);
}
} else {
filteredPredicates.push(predicate);
}
}
predicates = filteredPredicates;
npredicates = predicates.length;
var f = function (item) {
for (var p = 0; p < npredicates; p++) {
var predicate = predicates[p];
var searchColumns;
if (predicate.column) {
searchColumns = [predicate.column];
} else {
searchColumns = columns;
}
for (var j = 0, ncolumns = searchColumns.length; j < ncolumns; j++) {
var value = searchColumns[j].getter(item);
if (morpheus.Util.isArray(value)) {
var nvalues = value.length;
for (var i = 0; i < nvalues; i++) {
if (predicate.accept(value[i])) {
return true;
}
}
} else {
var predicate = predicates[p];
if (predicate.accept(value)) {
return true;
}
}
}
}
return false;
};
this.searchFunction = f;
this.grid
.setFilter(this.grid
.getFilter());
},
search: function (text) {
if (text === '') {
this.searchFunction = null;
this.grid
.setFilter(this.grid
.getFilter());
} else {
var tokens = morpheus.Util.getAutocompleteTokens(text);
var columns = this.columns.filter(function (c) {
return (c.searchable && c.visible) || c.alwaysSearch;
});
var columnNames = columns.map(function (c) {
return c.name;
});
var predicates = morpheus.Util.createSearchPredicates({
tokens: tokens,
fields: columnNames
});
this.searchWithPredicates(predicates);
}
},
getSelectedRows: function () {
return this.grid.getSelectedRows();
},
getSelectedItems: function () {
return this.grid.getSelectedItems();
},
getSelectedItem: function () {
return this.grid.getSelectedItem();
},
setSelectedRows: function (rows) {
this.grid.setSelectedRows(rows);
},
getItems: function (items) {
return this.grid.getItems();
},
getAllItemCount: function () {
return this.grid.getAllItemCount();
},
getFilteredItemCount: function () {
return this.grid.getFilteredItemCount();
},
setFilter: function (f) {
this.grid.setFilter(f);
},
getFilter: function () {
return this.grid.getFilter();
},
setItems: function (items) {
this.grid.setItems(items);
this.grid.redraw();
// TODO update height?
},
redraw: function () {
this.grid.redraw();
},
/**
* @param evtStr
* selectionChanged
*/
on: function (evtStr, handler) {
this.grid.on(evtStr, handler);
return this;
},
off: function (evtStr, handler) {
this.grid.off(evtStr, handler);
return this;
},
trigger: function (evtStr) {
this.grid.trigger(evtStr);
}
};
morpheus.Table.createOptions = function (options) {
options = $.extend(true, {}, {
items: [],
height: '564px',
collapseBreakpoint: 400,
showHeader: true,
select: true,
rowHeader: null,
responsive: true,
fixedWidth: '320px',
columnPicker: true
}, options);
if (!options.columns) {
options.columns = [{
name: ''
}];
}
var columns = [];
options.columns.forEach(function (c, i) {
var column = $.extend(true, {}, {
id: i,
tooltip: function (dataContext, value) {
return morpheus.Table.defaultRenderer(dataContext, value);
},
formatter: function (row, cell, value, columnDef, dataContext) {
var html = [];
html.push('