diff --git a/src/cellmanager.js b/src/cellmanager.js new file mode 100644 index 0000000..f25ea57 --- /dev/null +++ b/src/cellmanager.js @@ -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 = $(''); + + 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(); + } +} + diff --git a/src/datatable.js b/src/datatable.js index f9cceee..fc3020f 100644 --- a/src/datatable.js +++ b/src/datatable.js @@ -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(`
@@ -56,8 +69,9 @@ export default class DataTable {
-
-
+
+
+
`); @@ -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 = $(''); - - 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 = $('') .prependTo(this.wrapper);