Add CellManager

This commit is contained in:
Faris Ansari 2017-11-01 22:32:30 +05:30
parent 48815f1774
commit 0d211350dc
2 changed files with 278 additions and 156 deletions

253
src/cellmanager.js Normal file
View 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();
}
}

View File

@ -3,12 +3,13 @@ import {
getHeaderHTML,
getBodyHTML,
getRowHTML,
getColumnHTML,
buildCSSRule,
removeCSSRule,
getDefault
} from './utils';
import DataManager from './datamanager';
import CellManager from './cellmanager';
import './style.scss';
@ -40,13 +41,25 @@ export default class DataTable {
// map of checked rows
this.checkMap = [];
// make dom, make style, bind events
this.make();
this.datamanager = new DataManager(this.options);
this.cellmanager = new CellManager(this);
if (this.options.data) {
this.refresh(this.options.data);
}
}
make() {
if (this.wrapper.find('.data-table').length === 0) {
this.makeDom();
this.makeStyle();
this.bindEvents();
}
}
makeDom() {
this.wrapper.html(`
<div class="data-table">
@ -56,8 +69,9 @@ export default class DataTable {
</div>
<div class="data-table-footer">
</div>
<div class="data-table-popup">
<div class="edit-popup"></div>
<div class="data-table-borders">
<div class="border-outline"></div>
<div class="border-background"></div>
</div>
</div>
`);
@ -66,6 +80,7 @@ export default class DataTable {
this.bodyScrollable = this.wrapper.find('.body-scrollable');
// this.body = this.wrapper.find('.data-table-body');
this.footer = this.wrapper.find('.data-table-footer');
this.$borders = this.wrapper.find('.data-table-borders');
}
refresh(data) {
@ -79,12 +94,6 @@ export default class DataTable {
}
render() {
if (this.wrapper.find('.data-table').length === 0) {
this.makeDom();
this.makeStyle();
this.bindEvents();
}
this.renderHeader();
this.renderBody();
this.setDimensions();
@ -188,29 +197,12 @@ export default class DataTable {
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() {
this.renderBody();
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() {
this.bindFocusCell();
this.bindEditCell();
this.bindResizeColumn();
this.bindSortColumn();
this.bindCheckbox();
@ -277,136 +269,6 @@ export default class DataTable {
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() {
const self = this;
let isDragging = false;
@ -651,6 +513,13 @@ export default class DataTable {
this.$style.html(styles);
}
removeStyle(rule) {
let styles = this.$style.text();
styles = removeCSSRule(rule, styles);
this.$style.html(styles);
}
makeStyle() {
this.$style = $('<style data-id="datatable"></style>')
.prependTo(this.wrapper);