editable-grid
Version:
Bootstrap grid with CRUD functionality.
311 lines (260 loc) • 10.4 kB
JavaScript
var $ = require('jquery'),
_ = require('underscore'),
sorter = require('stand-in-order');
var SORT_MAPPINGS = {
text: 'string',
cost: 'float',
percent: 'float',
select: 'string'
};
var setValue = function (path, obj, value) {
var split = path.split('.');
if (split.length === 1) {
obj[split[0]] = value;
return;
}
var name = split[0];
if (!_.has(obj, name)) {
obj[name] = {};
}
split.shift();
setValue(split.join('.'), obj[name], value);
};
var delpthFirstSearch = function (rowId, collection, options) {
for (var i = 0; i < collection.length; i++) {
var obj = collection[i];
if (obj[options.id] === rowId) {
return obj;
} else if ('children' in obj && _.isArray(obj.children) && obj.children.length) {
var foundObj = delpthFirstSearch(rowId, obj.children, options);
if (foundObj != null) {
return foundObj;
}
}
}
};
module.exports = function (options) {
var grid = this;
var utils = {
_valueChanged: function (rowId, colId, value) {
var column = _.findWhere(options.columns, {id: colId});
var id = {};
id[options.id] = isNaN(rowId) ? rowId : Number(rowId);
var obj = _.findWhere(options.data, id);
var parsedValue = column.parser(colId, value);
setValue(colId, obj, parsedValue);
grid.ears.trigger('editable-value-updated', {
rowId: rowId,
colId: colId,
value: parsedValue
});
this._updateInput(rowId, colId, parsedValue);
},
_updateInput: function (rowId, colId, value) {
var column = _.findWhere(options.columns, {id: colId});
var input = options.el.find('.editable-body-table tr[data-row-id="' + rowId + '"] ' +
'td[data-col-id="' + colId + '"] input');
input.val(column.formatter(colId, value));
},
_newRowChanged: function (colId) {
var newObj = this._getNewRowData();
grid.ears.trigger('editable-new-row-value-changed', newObj, colId);
},
_getNewRowData: function () {
var tr = options.el.find('.editable-footer-table tr.new-row');
var newObj = {};
_.each(options.columns, function (column) {
var el;
if (column.type === 'select') {
el = tr.find('td[data-col-id="' + column.id + '"] select');
} else {
el = tr.find('td[data-col-id="' + column.id + '"] input');
}
var parsedValue = null;
if (el.is('input') && el.attr('type') === 'checkbox') {
parsedValue = el.prop('checked');
} else {
parsedValue = column.parser(column.id, el.val());
}
newObj[column.id] = parsedValue;
}, this);
return newObj;
},
_newRowClicked: function () {
var newObj = {};
newObj[options.id] = _.uniqueId('-');
_.extend(newObj, this._getNewRowData());
grid.dataOrder.push(newObj);
options.data.push(newObj);
grid.ears.trigger('editable-new-row', newObj);
grid.render();
// focus to the first input or select
var controls = options.el.find('.editable-footer-table tr.new-row select,input');
controls.eq(0).focus();
},
_rowClicked: function (rowId, colId) {
var id = {};
id[options.id] = rowId;
var obj = delpthFirstSearch(rowId, options.data, options);
grid.ears.trigger('editable-row-clicked', {obj: obj, rowId: rowId, colId: colId});
},
_isColumnSorted: function (id) {
var sortConfig = _.findWhere(options.sortConfig, {id: id});
if (sortConfig == null) {
return null;
}
return sortConfig.asc ? 'asc' : 'desc';
},
_columnClicked: function (id) {
var column = _.findWhere(options.columns, {id: id});
if (!column.sortable) {
return null;
}
var sort = utils._isColumnSorted(id);
if (sort == null) {
utils._sort(id, 'asc');
} else if (sort === 'asc') {
utils._sort(id, 'desc');
} else if (sort === 'desc') {
utils._sort(id, null);
}
},
_getSortType: function (type) {
if (_.has(SORT_MAPPINGS, type)) {
return SORT_MAPPINGS[type];
}
return type;
},
_sort: function (id, order) {
grid.ears.trigger('editable-before-sort', {id: id, order: order});
var sortConfig = _.findWhere(options.sortConfig, {id: id});
if (order == null) {
options.sortConfig.splice(_.indexOf(options.sortConfig, sortConfig), 1);
}
if (sortConfig == null) {
options.sortConfig = [];
options.sortConfig.push({
id: id,
asc: true
});
} else {
sortConfig.asc = order === 'asc' ? true : false;
}
var sorterConfig = [];
_.each(options.sortConfig, function (config) {
var column = _.findWhere(options.columns, {id: config.id});
sorterConfig.push({
name: config.id,
type: utils._getSortType(column.sortType || column.type),
ascending: config.asc,
compare: column.sortCompare
});
});
if (sorterConfig.length) {
sorter(options.data, sorterConfig);
} else {
// reset order
options.data = _.clone(grid.dataOrder);
}
grid.render();
grid.ears.trigger('editable-after-sort', {id: id, order: order});
},
_validateRow: function (row/*tr*/) {
var valid = true;
var inputs = row.find('input'),
rowId = row.attr('data-row-id');
_.each(inputs, function (input) {
var $input = $(input),
colId = $input.closest('td').attr('data-col-id');
if (!this._validate(rowId, colId, $input)) {
valid = false;
}
}, this);
return valid;
},
_validate: function (rowId, colId, input) {
// no validation required for check boxes
if (input.attr('type') === 'checkbox') {
return true;
}
var column = _.findWhere(options.columns, {id: colId});
var cell = input.closest('td');
cell.removeClass('has-error');
cell.find('.validation-error').remove();
var nullable = column.nullable,
value = input.val();
nullable = nullable != null ? JSON.parse(nullable) : false;
var message = column.validate(column.id, value);
if (nullable && value.length === 0) {
message = '';
}
if (message.length) {
// not valid
cell.addClass('has-error');
if (nullable) {
cell.append('<span class="validation-error help-block small">' +
message + '</div>');
} else {
cell.append('<span class="validation-error help-block small">Required. ' +
message + '</div>');
}
}
return !message.length;
},
_createDeleteRows: function () {
var tds = options.el.find('td[data-col-id="' + options.columns[0].id + '"]');
tds.addClass('delete-column');
tds.prepend(
'<div class="row-delete">' +
'<button type="button" class="close" aria-hidden="true">×</button>' +
'</div>');
},
_removeDeleteRows: function () {
var tds = options.el.find('td[data-col-id="' + options.columns[0].id + '"]');
tds.removeClass('delete-column');
tds.find('.row-delete').remove();
},
_deleteRow: function (rowId) {
grid.ears.trigger('editable-can-delete', rowId).done(function () {
// can delete
for (var i = 0; i < options.data.length; i++) {
if (options.data[i].id === rowId) {
options.data.splice(i, 1);
break;
}
}
grid.bodyTable.find('tr[data-row-id="' + rowId + '"]').remove();
});
},
_treeNodeExpand: function (rowId) {
grid.ears.trigger('editable-before-tree-node-expand', rowId);
// grab the row object
var obj = delpthFirstSearch(rowId, options.data, options);
// if no children property exist, then go get them
var dfd = $.Deferred();
if (!('children' in obj)) {
dfd = options.childData(rowId, obj).done(function (children) {
children = _.isArray(children) ? children : [];
obj.children = children;
});
} else {
dfd.resolve();
}
dfd.done(function () {
if (!('_treeState' in grid)) {
grid._treeState = {};
}
grid._treeState[rowId] = 'expand';
grid.render();
grid.ears.trigger('editable-after-tree-node-expand', rowId);
});
},
_treeNodeCollapse: function (rowId) {
grid.ears.trigger('editable-before-tree-node-collapse', rowId);
grid._treeState[rowId] = 'collapse';
grid.render();
grid.ears.trigger('editable-after-tree-node-collapse', rowId);
}
};
return utils;
};