equation-admin-template
Version:
Booststrap 4 admin template made by equation
510 lines (409 loc) • 19.3 kB
JavaScript
/**
* Abstract cell editor
* @constructor
* @class Base class for all cell editors
*/
function CellEditor(config) { this.init(config); }
CellEditor.prototype.init = function(config)
{
// override default properties with the ones given
if (config) for (var p in config) this[p] = config[p];
};
CellEditor.prototype.edit = function(rowIndex, columnIndex, element, value)
{
// tag element and remember all the things we need to apply/cancel edition
element.isEditing = true;
element.rowIndex = rowIndex;
element.columnIndex = columnIndex;
// call the specialized getEditor method
var editorInput = this.getEditor(element, value);
if (!editorInput) return false;
// give access to the cell editor and element from the editor widget
editorInput.element = element;
editorInput.celleditor = this;
// listen to pressed keys
// - tab does not work with onkeyup (it's too late)
// - on Safari escape does not work with onkeypress
// - with onkeydown everything is fine (but don't forget to return false)
editorInput.onkeydown = function(event) {
event = event || window.event;
// ENTER or TAB: apply value
if (event.keyCode == 13 || event.keyCode == 9) {
// backup onblur then remove it: it will be restored if editing could not be applied
this.onblur_backup = this.onblur;
this.onblur = null;
if (this.celleditor.applyEditing(this.element, this.celleditor.getEditorValue(this)) === false) this.onblur = this.onblur_backup;
// TAB: move to next cell
if (event.keyCode == 9) {
if (this.element.rowIndex >= 0 && this.celleditor.editablegrid.getColumnCount() > 0 && this.celleditor.editablegrid.getRowCount() > 0) {
var candidateRowIndex = this.element.rowIndex;
var candidateColumnIndex = this.element.columnIndex;
while (true) {
// find next cell in grid
if (candidateColumnIndex < this.celleditor.editablegrid.getColumnCount() - 1) candidateColumnIndex++;
else { candidateRowIndex++; candidateColumnIndex = 0; }
if (!this.celleditor.editablegrid.getRow(candidateRowIndex)) candidateRowIndex = 0;
// candidate cell is editable: edit it and break
var column = this.celleditor.editablegrid.getColumn(candidateColumnIndex);
if (column.editable && column.datatype != 'boolean' && this.celleditor.editablegrid.isEditable(candidateRowIndex, candidateColumnIndex)) {
this.celleditor.editablegrid.editCell(candidateRowIndex, candidateColumnIndex);
break;
}
// if we ever come back to the original cell, break
if (candidateRowIndex == this.element.rowIndex && candidateColumnIndex == this.element.columnIndex) break;
}
}
}
return false;
}
// ESC: cancel editing
if (event.keyCode == 27) {
this.onblur = null;
this.celleditor.cancelEditing(this.element);
return false;
}
};
// if simultaneous edition is not allowed, we cancel edition when focus is lost
if (!this.editablegrid.allowSimultaneousEdition) editorInput.onblur = this.editablegrid.saveOnBlur ? function(event) {
// backup onblur then remove it: it will be restored if editing could not be applied
this.onblur_backup = this.onblur;
this.onblur = null;
if (this.celleditor.applyEditing(this.element, this.celleditor.getEditorValue(this)) === false) this.onblur = this.onblur_backup;
}
: function(event) {
this.onblur = null;
this.celleditor.cancelEditing(this.element);
};
// display the resulting editor widget
this.displayEditor(element, editorInput);
// give focus to the created editor
this.autoFocus(editorInput);
};
CellEditor.prototype.autoFocus = function(editorInput) {
editorInput.focus();
};
CellEditor.prototype.getEditor = function(element, value) {
return null;
};
CellEditor.prototype.getEditorValue = function(editorInput) {
return editorInput.value;
};
CellEditor.prototype.formatValue = function(value) {
return value;
};
CellEditor.prototype.displayEditor = function(element, editorInput, adjustX, adjustY)
{
// use same font in input as in cell content
editorInput.style.fontFamily = this.editablegrid.getStyle(element, "fontFamily", "font-family");
editorInput.style.fontSize = this.editablegrid.getStyle(element, "fontSize", "font-size");
// static mode: add input field in the table cell
if (this.editablegrid.editmode == "static") {
while (element.hasChildNodes()) element.removeChild(element.firstChild);
element.appendChild(editorInput);
}
// absolute mode: add input field in absolute position over table cell, leaving current content
if (this.editablegrid.editmode == "absolute") {
element.appendChild(editorInput);
editorInput.style.position = "absolute";
// position editor input on the cell with the same padding as the actual cell content (and center vertically if vertical-align is set to "middle")
var paddingLeft = this.editablegrid.paddingLeft(element);
var paddingTop = this.editablegrid.paddingTop(element);
// find scroll offset
var offsetScrollX = this.editablegrid.getScrollXOffset(element);
var offsetScrollY = this.editablegrid.getScrollYOffset(element);
// position input
var vCenter = this.editablegrid.verticalAlign(element) == "middle" ? (element.offsetHeight - editorInput.offsetHeight) / 2 - paddingTop : 0;
editorInput.style.left = (this.editablegrid.getCellX(element) - offsetScrollX + paddingLeft + (adjustX ? adjustX : 0)) + "px";
editorInput.style.top = (this.editablegrid.getCellY(element) - offsetScrollY + paddingTop + vCenter + (adjustY ? adjustY : 0)) + "px";
// if number type: align field and its content as the containing cell
if (this.column.datatype == 'integer' || this.column.datatype == 'double') {
var rightPadding = this.editablegrid.getCellX(element) - offsetScrollX + element.offsetWidth - (parseInt(editorInput.style.left) + editorInput.offsetWidth);
editorInput.style.left = (parseInt(editorInput.style.left) + rightPadding) + "px";
editorInput.style.textAlign = EditableGrid.prototype.getStyle(element, 'textAlign', 'text-align');
}
}
// fixed mode: don't show input field in the cell
if (this.editablegrid.editmode == "fixed") {
var editorzone = _$(this.editablegrid.editorzoneid);
while (editorzone.hasChildNodes()) editorzone.removeChild(editorzone.firstChild);
editorzone.appendChild(editorInput);
}
if (element && element.isEditing && this.editablegrid.openedCellEditor) {
this.editablegrid.openedCellEditor(element.rowIndex, element.columnIndex);
}
};
CellEditor.prototype._clearEditor = function(element)
{
// untag element
element.isEditing = false;
// clear fixed editor zone if any
if (this.editablegrid.editmode == "fixed") {
var editorzone = _$(this.editablegrid.editorzoneid);
while (editorzone.hasChildNodes()) editorzone.removeChild(editorzone.firstChild);
}
};
CellEditor.prototype.cancelEditing = function(element)
{
with (this) {
// check that the element is still being edited (otherwise onblur will be called on textfields that have been closed when we go to another tab in Firefox)
if (element && element.isEditing) {
// render value before editon
var renderer = this == column.headerEditor ? column.headerRenderer : column.cellRenderer;
renderer._render(element.rowIndex, element.columnIndex, element, editablegrid.getValueAt(element.rowIndex, element.columnIndex));
_clearEditor(element);
}
}
};
CellEditor.prototype.applyEditing = function(element, newValue)
{
with (this) {
// check that the element is still being edited (otherwise onblur will be called on textfields that have been closed when we go to another tab in Firefox)
if (element && element.isEditing) {
// do nothing if the value is rejected by at least one validator
if (!column.isValid(newValue)) return false;
// format the value before applying
var formattedValue = formatValue(newValue);
// update model and render cell (keeping previous value)
var previousValue = editablegrid.setValueAt(element.rowIndex, element.columnIndex, formattedValue);
// if the new value is different than the previous one, let the user handle the model change
var newValue = editablegrid.getValueAt(element.rowIndex, element.columnIndex);
if (!this.editablegrid.isSame(newValue, previousValue)) {
editablegrid.modelChanged(element.rowIndex, element.columnIndex, previousValue, newValue, editablegrid.getRow(element.rowIndex));
}
_clearEditor(element);
return true;
}
return false;
}
};
/**
* Text cell editor
* @constructor
* @class Class to edit a cell with an HTML text input
*/
function TextCellEditor(size, maxlen, config) {
if (size) this.fieldSize = size;
if (maxlen) this.maxLength = maxlen;
if (config) this.init(config);
};
TextCellEditor.prototype = new CellEditor();
TextCellEditor.prototype.fieldSize = -1;
TextCellEditor.prototype.maxLength = -1;
TextCellEditor.prototype.autoHeight = true;
TextCellEditor.prototype.editorValue = function(value) {
return value;
};
TextCellEditor.prototype.updateStyle = function(htmlInput)
{
// change style for invalid values
if (this.column.isValid(this.getEditorValue(htmlInput))) this.editablegrid.removeClassName(htmlInput, this.editablegrid.invalidClassName);
else this.editablegrid.addClassName(htmlInput, this.editablegrid.invalidClassName);
};
TextCellEditor.prototype.getEditor = function(element, value)
{
// create and initialize text field
var htmlInput = document.createElement("input");
htmlInput.setAttribute("type", "text");
if (this.maxLength > 0) htmlInput.setAttribute("maxlength", this.maxLength);
if (this.fieldSize > 0) htmlInput.setAttribute("size", this.fieldSize);
else htmlInput.style.width = this.editablegrid.autoWidth(element) + 'px'; // auto-adapt width to cell, if no length specified
var autoHeight = this.editablegrid.autoHeight(element);
if (this.autoHeight) htmlInput.style.height = autoHeight + 'px'; // auto-adapt height to cell
htmlInput.value = this.editorValue(value);
// listen to keyup to check validity and update style of input field
htmlInput.onkeyup = function(event) { this.celleditor.updateStyle(this); };
return htmlInput;
};
TextCellEditor.prototype.displayEditor = function(element, htmlInput)
{
// call base method
CellEditor.prototype.displayEditor.call(this, element, htmlInput, -1 * this.editablegrid.borderLeft(htmlInput), -1 * (this.editablegrid.borderTop(htmlInput) + 1));
// update style of input field
this.updateStyle(htmlInput);
// select text
htmlInput.select();
};
/**
* Number cell editor
* @constructor
* @class Class to edit a numeric cell with an HTML text input
*/
function NumberCellEditor(type, config) {
this.type = type;
this.init(config);
}
NumberCellEditor.prototype = new TextCellEditor(-1, 32);
//editorValue is called in getEditor to initialize field
NumberCellEditor.prototype.editorValue = function(value) {
return (value === null || isNaN(value)) ? "" : (value + '').replace('.', this.column.decimal_point);
};
//getEditorValue is called before passing to isValid and applyEditing
NumberCellEditor.prototype.getEditorValue = function(editorInput) {
return editorInput.value.replace(',', '.');
};
//formatValue is called in applyEditing
NumberCellEditor.prototype.formatValue = function(value)
{
return this.type == 'integer' ? parseInt(value) : parseFloat(value);
};
/**
* Select cell editor
* @constructor
* @class Class to edit a cell with an HTML select input
*/
function SelectCellEditor(config) {
this.minWidth = 75;
this.minHeight = 22;
this.adaptHeight = true;
this.adaptWidth = true;
this.init(config);
}
SelectCellEditor.prototype = new CellEditor();
SelectCellEditor.prototype.isValueSelected = function(htmlInput, optionValue, value) { return (!optionValue && !value) || (optionValue == value); };
SelectCellEditor.prototype.getEditor = function(element, value)
{
var self = this;
// create select list
var htmlInput = document.createElement("select");
// auto adapt dimensions to cell, with a min width
if (this.adaptWidth && typeof jQuery.fn.select2 == 'undefined') htmlInput.style.width = Math.max(this.minWidth, this.editablegrid.autoWidth(element)) + 'px';
if (this.adaptHeight && typeof jQuery.fn.select2 == 'undefined') htmlInput.style.height = Math.max(this.minHeight, this.editablegrid.autoHeight(element)) + 'px';
// get column option values for this row
var optionValues = this.column.getOptionValuesForEdit(element.rowIndex);
// add these options, selecting the current one
var index = 0, valueFound = false;
for (var optionIndex = 0; optionIndex < optionValues.length; optionIndex++) {
var optionValue = optionValues[optionIndex];
// if values are grouped
if (typeof optionValue.values == 'object') {
var optgroup = document.createElement('optgroup');
optgroup.label = optionValue.label;
htmlInput.appendChild(optgroup);
for (var groupOptionIndex = 0; groupOptionIndex < optionValue.values.length; groupOptionIndex++) {
var groupOptionValue = optionValue.values[groupOptionIndex];
var option = document.createElement('option');
option.text = groupOptionValue.label;
option.value = groupOptionValue.value ? groupOptionValue.value : ""; // this otherwise changes a null into a "null" !
optgroup.appendChild(option);
if (this.isValueSelected(htmlInput, groupOptionValue.value, value)) { option.selected = true; valueFound = true; } else option.selected = false;
index++;
}
}
else {
var option = document.createElement('option');
option.text = optionValue.label;
option.value = optionValue.value ? optionValue.value : ""; // this otherwise changes a null into a "null" !
// add does not work as expected in IE7 (cf. second arg)
try { htmlInput.add(option, null); } catch (e) { htmlInput.add(option); }
if (this.isValueSelected(htmlInput, optionValue.value, value)) { option.selected = true; valueFound = true; } else option.selected = false;
index++;
}
}
// if the current value is not in the list add it to the front, using display value as label
if (!valueFound) {
var option = document.createElement('option');
var value_label = this.editablegrid.getDisplayValueAt(element.rowIndex, element.columnIndex);
option.text = value_label ? value_label : "";
option.value = value ? value : "";
// add does not work as expected in IE7 (cf. second arg)
try { htmlInput.add(option, htmlInput.options[0]); } catch (e) { htmlInput.add(option); }
htmlInput.selectedIndex = 0;
}
// when a new value is selected we apply it
htmlInput.onchange = function(event) { this.onblur = null; this.celleditor.applyEditing(this.element, self.getEditorValue(this)); };
return htmlInput;
};
//redefine displayEditor to setup select2
SelectCellEditor.prototype.displayEditor = function(element, htmlInput)
{
// call base method
CellEditor.prototype.displayEditor.call(this, element, htmlInput);
// use select2 if loaded
if (typeof jQuery.fn.select2 != 'undefined') {
// select2 v4 calls onblur before onchange, when the value is not changed yet
htmlInput.onblur = null;
htmlInput.onchange = null;
// setup and open
this.select2(element, htmlInput);
// select2 v4 does not position right in X: do it then open so that drodown is also positioned correctly
jQuery(htmlInput).siblings('span.select2-container').css('position', 'absolute').css('left', htmlInput.style.left);
jQuery(htmlInput).select2('open');
// catches select2-blur and select2-close to apply (or cancel) editing
jQuery(htmlInput)
.on('select2:close', function() { this.celleditor.applyEditing(this.element, this.celleditor.getEditorValue(this)); }) // v4
.on('select2-blur', function() { this.celleditor.applyEditing(this.element, this.celleditor.getEditorValue(this)); }) // v3
.on('select2-close', function() { this.celleditor.applyEditing(this.element, this.celleditor.getEditorValue(this)); }); // v3
}
};
SelectCellEditor.prototype.select2 = function(element, htmlInput)
{
jQuery(htmlInput).select2({
dropdownAutoWidth: true,
minimumResultsForSearch: 10 // since Select2 v4, escape and arrow keys will not work correctly if no search box present... cf. TODO in autoFocus below
});
};
SelectCellEditor.prototype.autoFocus = function(editorInput)
{
// no autofocus on original select otherwise this select appears when hitting arrow
if (typeof jQuery.fn.select2 != 'undefined') {
// TODO: select2('open') does not give focus as when the user clicks... side effects = escape does not work and arrows scroll the whole body... unless a search box is present!
return true;
}
return CellEditor.prototype.autoFocus.call(this, editorInput);
};
SelectCellEditor.prototype.getEditorValue = function(editorInput)
{
// use select2 if loaded
if (typeof jQuery.fn.select2 != 'undefined') return jQuery(editorInput).val();
return CellEditor.prototype.getEditorValue.call(this, editorInput);
};
SelectCellEditor.prototype.cancelEditing = function(element)
{
// destroy select2 if loaded
if (typeof jQuery.fn.select2 != 'undefined') jQuery(element).find('select').select2('destroy');
// call base method
CellEditor.prototype.cancelEditing.call(this, element);
};
/**
* Datepicker cell editor
*
* Text field editor with date picker capabilities.
* Uses the jQuery UI's datepicker.
* This editor is used automatically for date columns if we detect that the jQuery UI's datepicker is present.
*
* @constructor Accepts an option object containing the following properties:
* - fieldSize: integer (default=auto-adapt)
* - maxLength: integer (default=255)
*
* @class Class to edit a cell with a datepicker linked to the HTML text input
*/
function DateCellEditor(config)
{
// erase defaults with given options
this.init(config);
};
//inherits TextCellEditor functionalities
DateCellEditor.prototype = new TextCellEditor();
//redefine displayEditor to setup datepicker
DateCellEditor.prototype.displayEditor = function(element, htmlInput)
{
// call base method
TextCellEditor.prototype.displayEditor.call(this, element, htmlInput);
jQuery(htmlInput).datepicker({
dateFormat: (this.editablegrid.dateFormat == "EU" ? "dd/mm/yy" : "mm/dd/yy"),
changeMonth: true,
changeYear: true,
yearRange: "c-100:c+10",
beforeShow: function() {
// the field cannot be blurred until the datepicker has gone away
// otherwise we get the "missing instance data" exception
this.onblur_backup = this.onblur;
this.onblur = null;
},
onClose: function(dateText) {
// apply date if any, otherwise call original onblur event
if (dateText != '') this.celleditor.applyEditing(htmlInput.element, dateText);
else if (this.onblur_backup != null) this.onblur_backup();
}
}).datepicker('show');
};