Add CellManager
This commit is contained in:
parent
48815f1774
commit
0d211350dc
253
src/cellmanager.js
Normal file
253
src/cellmanager.js
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
import { getColumnHTML } from './utils';
|
||||||
|
import keyboard from 'keyboard';
|
||||||
|
|
||||||
|
export default class CellManager {
|
||||||
|
constructor(instance) {
|
||||||
|
this.instance = instance;
|
||||||
|
this.options = this.instance.options;
|
||||||
|
this.bodyScrollable = this.instance.bodyScrollable;
|
||||||
|
|
||||||
|
this.prepare();
|
||||||
|
this.bindEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare() {
|
||||||
|
this.$borderOutline = this.instance.$borders.find('.border-outline');
|
||||||
|
this.$borderBg = this.instance.$borders.find('.border-background');
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
this.bindFocusCell();
|
||||||
|
this.bindEditCell();
|
||||||
|
this.bindKeyboardNav();
|
||||||
|
}
|
||||||
|
|
||||||
|
bindFocusCell() {
|
||||||
|
const bodyScrollable = this.instance.bodyScrollable;
|
||||||
|
|
||||||
|
this.$focusedCell = null;
|
||||||
|
bodyScrollable.on('click', '.data-table-col', (e) => {
|
||||||
|
this.focusCell($(e.currentTarget));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
focusCell($cell) {
|
||||||
|
if (!$cell.length) return;
|
||||||
|
this.deactivateEditing();
|
||||||
|
|
||||||
|
const { colIndex } = this.getCellAttr($cell);
|
||||||
|
|
||||||
|
if (this.options.addCheckboxColumn && colIndex === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$focusedCell = $cell;
|
||||||
|
this.bodyScrollable.find('.data-table-col').removeClass('selected');
|
||||||
|
$cell.addClass('selected');
|
||||||
|
|
||||||
|
this.highlightRowColumnHeader($cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
highlightRowColumnHeader($cell) {
|
||||||
|
const { colIndex, rowIndex } = this.getCellAttr($cell);
|
||||||
|
const _colIndex = this.instance.getSerialColumnIndex();
|
||||||
|
const colHeaderSelector = `.data-table-header .data-table-col[data-col-index="${colIndex}"]`;
|
||||||
|
const rowHeaderSelector = `.data-table-col[data-row-index="${rowIndex}"][data-col-index="${_colIndex}"]`;
|
||||||
|
|
||||||
|
if (this.lastSelectors) {
|
||||||
|
this.instance.removeStyle(this.lastSelectors.colHeaderSelector);
|
||||||
|
this.instance.removeStyle(this.lastSelectors.rowHeaderSelector);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.instance.setStyle(colHeaderSelector, {
|
||||||
|
'background-color': 'var(--light-bg)'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.instance.setStyle(rowHeaderSelector, {
|
||||||
|
'background-color': 'var(--light-bg)'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.lastSelectors = {
|
||||||
|
colHeaderSelector,
|
||||||
|
rowHeaderSelector
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEditCell() {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
this.$editingCell = null;
|
||||||
|
this.bodyScrollable.on('dblclick', '.data-table-col', function () {
|
||||||
|
self.activateEditing($(this));
|
||||||
|
});
|
||||||
|
|
||||||
|
keyboard.on('enter', (e) => {
|
||||||
|
if (this.$focusedCell && !this.$editingCell) {
|
||||||
|
// enter keypress on focused cell
|
||||||
|
this.activateEditing(this.$focusedCell);
|
||||||
|
} else if (this.$editingCell) {
|
||||||
|
// enter keypress on editing cell
|
||||||
|
this.submitEditing(this.$editingCell);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document.body).on('click', e => {
|
||||||
|
if ($(e.target).is('.edit-cell, .edit-cell *')) return;
|
||||||
|
this.deactivateEditing();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bindKeyboardNav() {
|
||||||
|
keyboard.on('left', () => {
|
||||||
|
if (!this.$focusedCell) return;
|
||||||
|
|
||||||
|
this.focusCell(this.$focusedCell.prev());
|
||||||
|
});
|
||||||
|
|
||||||
|
keyboard.on('right', () => {
|
||||||
|
if (!this.$focusedCell) return;
|
||||||
|
|
||||||
|
this.focusCell(this.$focusedCell.next());
|
||||||
|
});
|
||||||
|
|
||||||
|
keyboard.on('up', () => {
|
||||||
|
if (!this.$focusedCell) return;
|
||||||
|
|
||||||
|
const { colIndex } = this.getCellAttr(this.$focusedCell);
|
||||||
|
const $upRow = this.$focusedCell.parent().prev();
|
||||||
|
const $upCell = $upRow.find(`[data-col-index="${colIndex}"]`);
|
||||||
|
|
||||||
|
this.focusCell($upCell);
|
||||||
|
});
|
||||||
|
|
||||||
|
keyboard.on('down', () => {
|
||||||
|
if (!this.$focusedCell) return;
|
||||||
|
|
||||||
|
const { colIndex } = this.getCellAttr(this.$focusedCell);
|
||||||
|
const $downRow = this.$focusedCell.parent().next();
|
||||||
|
const $downCell = $downRow.find(`[data-col-index="${colIndex}"]`);
|
||||||
|
|
||||||
|
this.focusCell($downCell);
|
||||||
|
});
|
||||||
|
|
||||||
|
keyboard.on('shift+left', () => {
|
||||||
|
if (!this.$focusedCell) return;
|
||||||
|
|
||||||
|
// this.focusCell($downCell);
|
||||||
|
});
|
||||||
|
|
||||||
|
keyboard.on('esc', () => {
|
||||||
|
this.deactivateEditing();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
activateEditing($cell) {
|
||||||
|
const { rowIndex, colIndex } = this.getCellAttr($cell);
|
||||||
|
const col = this.instance.getColumn(colIndex);
|
||||||
|
|
||||||
|
if (col && col.editable === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$editingCell) {
|
||||||
|
const { _rowIndex, _colIndex } = this.getCellAttr(this.$editingCell);
|
||||||
|
|
||||||
|
if (rowIndex === _rowIndex && colIndex === _colIndex) {
|
||||||
|
// editing the same cell
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$editingCell = $cell;
|
||||||
|
$cell.addClass('editing');
|
||||||
|
|
||||||
|
const $editCell = $cell.find('.edit-cell').empty();
|
||||||
|
const cell = this.instance.getCell(rowIndex, colIndex);
|
||||||
|
const editing = this.getEditingObject(colIndex, rowIndex, cell.content, $editCell);
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
this.currentCellEditing = editing;
|
||||||
|
// initialize editing input with cell value
|
||||||
|
editing.initValue(cell.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivateEditing() {
|
||||||
|
if (!this.$editingCell) return;
|
||||||
|
this.$editingCell.removeClass('editing');
|
||||||
|
this.$editingCell = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEditingObject(colIndex, rowIndex, value, parent) {
|
||||||
|
if (this.options.editing) {
|
||||||
|
return this.options.editing(colIndex, rowIndex, value, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// editing fallback
|
||||||
|
const $input = $('<input type="text" />');
|
||||||
|
|
||||||
|
parent.append($input);
|
||||||
|
|
||||||
|
return {
|
||||||
|
initValue(value) {
|
||||||
|
$input.focus();
|
||||||
|
return $input.val(value);
|
||||||
|
},
|
||||||
|
getValue() {
|
||||||
|
return $input.val();
|
||||||
|
},
|
||||||
|
setValue(value) {
|
||||||
|
return $input.val(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
submitEditing($cell) {
|
||||||
|
const { rowIndex, colIndex } = this.getCellAttr($cell);
|
||||||
|
|
||||||
|
if ($cell) {
|
||||||
|
const editing = this.currentCellEditing;
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
const value = editing.getValue();
|
||||||
|
const done = editing.setValue(value);
|
||||||
|
|
||||||
|
if (done && done.then) {
|
||||||
|
// wait for promise then update internal state
|
||||||
|
done.then(
|
||||||
|
() => this.updateCell(rowIndex, colIndex, value)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.updateCell(rowIndex, colIndex, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentCellEditing = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCell(rowIndex, colIndex, value) {
|
||||||
|
const cell = this.getCell(rowIndex, colIndex);
|
||||||
|
|
||||||
|
cell.content = value;
|
||||||
|
this.refreshCell(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshCell(cell) {
|
||||||
|
const selector = `.data-table-col[data-row-index="${cell.rowIndex}"][data-col-index="${cell.colIndex}"]`;
|
||||||
|
const bodyScrollable = this.instance.bodyScrollable;
|
||||||
|
const $cell = bodyScrollable.find(selector);
|
||||||
|
const $newCell = $(getColumnHTML(cell));
|
||||||
|
|
||||||
|
$cell.replaceWith($newCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCell(rowIndex, colIndex) {
|
||||||
|
return this.instance.datamanager.getCell(rowIndex, colIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCellAttr($cell) {
|
||||||
|
return $cell.data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
181
src/datatable.js
181
src/datatable.js
@ -3,12 +3,13 @@ import {
|
|||||||
getHeaderHTML,
|
getHeaderHTML,
|
||||||
getBodyHTML,
|
getBodyHTML,
|
||||||
getRowHTML,
|
getRowHTML,
|
||||||
getColumnHTML,
|
|
||||||
buildCSSRule,
|
buildCSSRule,
|
||||||
|
removeCSSRule,
|
||||||
getDefault
|
getDefault
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
import DataManager from './datamanager';
|
import DataManager from './datamanager';
|
||||||
|
import CellManager from './cellmanager';
|
||||||
|
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
@ -40,13 +41,25 @@ export default class DataTable {
|
|||||||
// map of checked rows
|
// map of checked rows
|
||||||
this.checkMap = [];
|
this.checkMap = [];
|
||||||
|
|
||||||
|
// make dom, make style, bind events
|
||||||
|
this.make();
|
||||||
|
|
||||||
this.datamanager = new DataManager(this.options);
|
this.datamanager = new DataManager(this.options);
|
||||||
|
this.cellmanager = new CellManager(this);
|
||||||
|
|
||||||
if (this.options.data) {
|
if (this.options.data) {
|
||||||
this.refresh(this.options.data);
|
this.refresh(this.options.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
make() {
|
||||||
|
if (this.wrapper.find('.data-table').length === 0) {
|
||||||
|
this.makeDom();
|
||||||
|
this.makeStyle();
|
||||||
|
this.bindEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
makeDom() {
|
makeDom() {
|
||||||
this.wrapper.html(`
|
this.wrapper.html(`
|
||||||
<div class="data-table">
|
<div class="data-table">
|
||||||
@ -56,8 +69,9 @@ export default class DataTable {
|
|||||||
</div>
|
</div>
|
||||||
<div class="data-table-footer">
|
<div class="data-table-footer">
|
||||||
</div>
|
</div>
|
||||||
<div class="data-table-popup">
|
<div class="data-table-borders">
|
||||||
<div class="edit-popup"></div>
|
<div class="border-outline"></div>
|
||||||
|
<div class="border-background"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
@ -66,6 +80,7 @@ export default class DataTable {
|
|||||||
this.bodyScrollable = this.wrapper.find('.body-scrollable');
|
this.bodyScrollable = this.wrapper.find('.body-scrollable');
|
||||||
// this.body = this.wrapper.find('.data-table-body');
|
// this.body = this.wrapper.find('.data-table-body');
|
||||||
this.footer = this.wrapper.find('.data-table-footer');
|
this.footer = this.wrapper.find('.data-table-footer');
|
||||||
|
this.$borders = this.wrapper.find('.data-table-borders');
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh(data) {
|
refresh(data) {
|
||||||
@ -79,12 +94,6 @@ export default class DataTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.wrapper.find('.data-table').length === 0) {
|
|
||||||
this.makeDom();
|
|
||||||
this.makeStyle();
|
|
||||||
this.bindEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.renderHeader();
|
this.renderHeader();
|
||||||
this.renderBody();
|
this.renderBody();
|
||||||
this.setDimensions();
|
this.setDimensions();
|
||||||
@ -188,29 +197,12 @@ export default class DataTable {
|
|||||||
return rows.map((row) => getRowHTML(row, { rowIndex: row[0].rowIndex }));
|
return rows.map((row) => getRowHTML(row, { rowIndex: row[0].rowIndex }));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCell(rowIndex, colIndex, value) {
|
|
||||||
const cell = this.getCell(rowIndex, colIndex);
|
|
||||||
|
|
||||||
cell.content = value;
|
|
||||||
this.refreshCell(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshRows() {
|
refreshRows() {
|
||||||
this.renderBody();
|
this.renderBody();
|
||||||
this.setDimensions();
|
this.setDimensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshCell(cell) {
|
|
||||||
const selector = `.data-table-col[data-row-index="${cell.rowIndex}"][data-col-index="${cell.colIndex}"]`;
|
|
||||||
const $cell = this.bodyScrollable.find(selector);
|
|
||||||
const $newCell = $(getColumnHTML(cell));
|
|
||||||
|
|
||||||
$cell.replaceWith($newCell);
|
|
||||||
}
|
|
||||||
|
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
this.bindFocusCell();
|
|
||||||
this.bindEditCell();
|
|
||||||
this.bindResizeColumn();
|
this.bindResizeColumn();
|
||||||
this.bindSortColumn();
|
this.bindSortColumn();
|
||||||
this.bindCheckbox();
|
this.bindCheckbox();
|
||||||
@ -277,136 +269,6 @@ export default class DataTable {
|
|||||||
this.bodyScrollable.find('.table').css('margin', 0);
|
this.bodyScrollable.find('.table').css('margin', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bindFocusCell() {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
this.$focusedCell = null;
|
|
||||||
this.bodyScrollable.on('click', '.data-table-col', function () {
|
|
||||||
const $cell = $(this);
|
|
||||||
const { colIndex } = self.getCellAttr($cell);
|
|
||||||
|
|
||||||
if (self.options.addCheckboxColumn && colIndex === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.$focusedCell = $cell;
|
|
||||||
self.bodyScrollable.find('.data-table-col').removeClass('selected');
|
|
||||||
$cell.addClass('selected');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bindEditCell() {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
this.$editingCell = null;
|
|
||||||
this.bodyScrollable.on('dblclick', '.data-table-col', function () {
|
|
||||||
self.activateEditing($(this));
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document.body).on('keypress', (e) => {
|
|
||||||
// enter keypress on focused cell
|
|
||||||
if (e.which === 13 && this.$focusedCell && !this.$editingCell) {
|
|
||||||
this.activateEditing(this.$focusedCell);
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document.body).on('keypress', (e) => {
|
|
||||||
// enter keypress on editing cell
|
|
||||||
if (e.which === 13 && this.$editingCell) {
|
|
||||||
this.log('submitCell');
|
|
||||||
this.submitEditing(this.$editingCell);
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document.body).on('click', e => {
|
|
||||||
if ($(e.target).is('.edit-cell, .edit-cell *')) return;
|
|
||||||
if (!this.$editingCell) return;
|
|
||||||
|
|
||||||
this.$editingCell.removeClass('editing');
|
|
||||||
this.$editingCell = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
activateEditing($cell) {
|
|
||||||
const { rowIndex, colIndex } = this.getCellAttr($cell);
|
|
||||||
const col = this.getColumn(colIndex);
|
|
||||||
|
|
||||||
if (col && col.editable === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.$editingCell) {
|
|
||||||
const { _rowIndex, _colIndex } = this.getCellAttr(this.$editingCell);
|
|
||||||
|
|
||||||
if (rowIndex === _rowIndex && colIndex === _colIndex) {
|
|
||||||
// editing the same cell
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$editingCell = $cell;
|
|
||||||
$cell.addClass('editing');
|
|
||||||
|
|
||||||
const $editCell = $cell.find('.edit-cell').empty();
|
|
||||||
const cell = this.getCell(rowIndex, colIndex);
|
|
||||||
const editing = this.getEditingObject(colIndex, rowIndex, cell.content, $editCell);
|
|
||||||
|
|
||||||
if (editing) {
|
|
||||||
this.currentCellEditing = editing;
|
|
||||||
// initialize editing input with cell value
|
|
||||||
editing.initValue(cell.content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getEditingObject(colIndex, rowIndex, value, parent) {
|
|
||||||
if (this.options.editing) {
|
|
||||||
return this.options.editing(colIndex, rowIndex, value, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// editing fallback
|
|
||||||
const $input = $('<input type="text" />');
|
|
||||||
|
|
||||||
parent.append($input);
|
|
||||||
|
|
||||||
return {
|
|
||||||
initValue(value) {
|
|
||||||
return $input.val(value);
|
|
||||||
},
|
|
||||||
getValue() {
|
|
||||||
return $input.val();
|
|
||||||
},
|
|
||||||
setValue(value) {
|
|
||||||
return $input.val(value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
submitEditing($cell) {
|
|
||||||
const { rowIndex, colIndex } = this.getCellAttr($cell);
|
|
||||||
|
|
||||||
if ($cell) {
|
|
||||||
const editing = this.currentCellEditing;
|
|
||||||
|
|
||||||
if (editing) {
|
|
||||||
const value = editing.getValue();
|
|
||||||
const done = editing.setValue(value);
|
|
||||||
|
|
||||||
if (done && done.then) {
|
|
||||||
// wait for promise then update internal state
|
|
||||||
done.then(
|
|
||||||
() => this.updateCell(rowIndex, colIndex, value)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.updateCell(rowIndex, colIndex, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentCellEditing = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
bindResizeColumn() {
|
bindResizeColumn() {
|
||||||
const self = this;
|
const self = this;
|
||||||
let isDragging = false;
|
let isDragging = false;
|
||||||
@ -651,6 +513,13 @@ export default class DataTable {
|
|||||||
this.$style.html(styles);
|
this.$style.html(styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeStyle(rule) {
|
||||||
|
let styles = this.$style.text();
|
||||||
|
|
||||||
|
styles = removeCSSRule(rule, styles);
|
||||||
|
this.$style.html(styles);
|
||||||
|
}
|
||||||
|
|
||||||
makeStyle() {
|
makeStyle() {
|
||||||
this.$style = $('<style data-id="datatable"></style>')
|
this.$style = $('<style data-id="datatable"></style>')
|
||||||
.prependTo(this.wrapper);
|
.prependTo(this.wrapper);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user