x-data-spreadsheet
Version:
a javascript xpreadsheet
282 lines (262 loc) • 7.77 kB
JavaScript
//* global window */
import { h } from './element';
import Suggest from './suggest';
import Datepicker from './datepicker';
import { cssPrefix } from '../config';
// import { mouseMoveUp } from '../event';
function resetTextareaSize() {
const { inputText } = this;
if (!/^\s*$/.test(inputText)) {
const {
textlineEl, textEl, areaOffset,
} = this;
const txts = inputText.split('\n');
const maxTxtSize = Math.max(...txts.map(it => it.length));
const tlOffset = textlineEl.offset();
const fontWidth = tlOffset.width / inputText.length;
const tlineWidth = (maxTxtSize + 1) * fontWidth + 5;
const maxWidth = this.viewFn().width - areaOffset.left - fontWidth;
let h1 = txts.length;
if (tlineWidth > areaOffset.width) {
let twidth = tlineWidth;
if (tlineWidth > maxWidth) {
twidth = maxWidth;
h1 += parseInt(tlineWidth / maxWidth, 10);
h1 += (tlineWidth % maxWidth) > 0 ? 1 : 0;
}
textEl.css('width', `${twidth}px`);
}
h1 *= this.rowHeight;
if (h1 > areaOffset.height) {
textEl.css('height', `${h1}px`);
}
}
}
function insertText({ target }, itxt) {
const { value, selectionEnd } = target;
const ntxt = `${value.slice(0, selectionEnd)}${itxt}${value.slice(selectionEnd)}`;
target.value = ntxt;
target.setSelectionRange(selectionEnd + 1, selectionEnd + 1);
this.inputText = ntxt;
this.textlineEl.html(ntxt);
resetTextareaSize.call(this);
}
function keydownEventHandler(evt) {
const { keyCode, altKey } = evt;
if (keyCode !== 13 && keyCode !== 9) evt.stopPropagation();
if (keyCode === 13 && altKey) {
insertText.call(this, evt, '\n');
evt.stopPropagation();
}
if (keyCode === 13 && !altKey) evt.preventDefault();
}
function inputEventHandler(evt) {
const v = evt.target.value;
// console.log(evt, 'v:', v);
const { suggest, textlineEl, validator } = this;
const { cell } = this;
if (cell !== null) {
if (('editable' in cell && cell.editable === true) || (cell.editable === undefined)) {
this.inputText = v;
if (validator) {
if (validator.type === 'list') {
suggest.search(v);
} else {
suggest.hide();
}
} else {
const start = v.lastIndexOf('=');
if (start !== -1) {
suggest.search(v.substring(start + 1));
} else {
suggest.hide();
}
}
textlineEl.html(v);
resetTextareaSize.call(this);
this.change('input', v);
} else {
evt.target.value = cell.text;
}
} else {
this.inputText = v;
if (validator) {
if (validator.type === 'list') {
suggest.search(v);
} else {
suggest.hide();
}
} else {
const start = v.lastIndexOf('=');
if (start !== -1) {
suggest.search(v.substring(start + 1));
} else {
suggest.hide();
}
}
textlineEl.html(v);
resetTextareaSize.call(this);
this.change('input', v);
}
}
function setTextareaRange(position) {
const { el } = this.textEl;
setTimeout(() => {
el.focus();
el.setSelectionRange(position, position);
}, 0);
}
function setText(text, position) {
const { textEl, textlineEl } = this;
// firefox bug
textEl.el.blur();
textEl.val(text);
textlineEl.html(text);
setTextareaRange.call(this, position);
}
function suggestItemClick(it) {
const { inputText, validator } = this;
let position = 0;
if (validator && validator.type === 'list') {
this.inputText = it;
position = this.inputText.length;
} else {
const start = inputText.lastIndexOf('=');
const sit = inputText.substring(0, start + 1);
let eit = inputText.substring(start + 1);
if (eit.indexOf(')') !== -1) {
eit = eit.substring(eit.indexOf(')'));
} else {
eit = '';
}
this.inputText = `${sit + it.key}(`;
// console.log('inputText:', this.inputText);
position = this.inputText.length;
this.inputText += `)${eit}`;
}
setText.call(this, this.inputText, position);
}
function resetSuggestItems() {
this.suggest.setItems(this.formulas);
}
function dateFormat(d) {
let month = d.getMonth() + 1;
let date = d.getDate();
if (month < 10) month = `0${month}`;
if (date < 10) date = `0${date}`;
return `${d.getFullYear()}-${month}-${date}`;
}
export default class Editor {
constructor(formulas, viewFn, rowHeight) {
this.viewFn = viewFn;
this.rowHeight = rowHeight;
this.formulas = formulas;
this.suggest = new Suggest(formulas, (it) => {
suggestItemClick.call(this, it);
});
this.datepicker = new Datepicker();
this.datepicker.change((d) => {
// console.log('d:', d);
this.setText(dateFormat(d));
this.clear();
});
this.areaEl = h('div', `${cssPrefix}-editor-area`)
.children(
this.textEl = h('textarea', '')
.on('input', evt => inputEventHandler.call(this, evt))
.on('paste.stop', () => {})
.on('keydown', evt => keydownEventHandler.call(this, evt)),
this.textlineEl = h('div', 'textline'),
this.suggest.el,
this.datepicker.el,
)
.on('mousemove.stop', () => {})
.on('mousedown.stop', () => {});
this.el = h('div', `${cssPrefix}-editor`)
.child(this.areaEl).hide();
this.suggest.bindInputEvents(this.textEl);
this.areaOffset = null;
this.freeze = { w: 0, h: 0 };
this.cell = null;
this.inputText = '';
this.change = () => {};
}
setFreezeLengths(width, height) {
this.freeze.w = width;
this.freeze.h = height;
}
clear() {
// const { cell } = this;
// const cellText = (cell && cell.text) || '';
if (this.inputText !== '') {
this.change('finished', this.inputText);
}
this.cell = null;
this.areaOffset = null;
this.inputText = '';
this.el.hide();
this.textEl.val('');
this.textlineEl.html('');
resetSuggestItems.call(this);
this.datepicker.hide();
}
setOffset(offset, suggestPosition = 'top') {
const {
textEl, areaEl, suggest, freeze, el,
} = this;
if (offset) {
this.areaOffset = offset;
const {
left, top, width, height, l, t,
} = offset;
// console.log('left:', left, ',top:', top, ', freeze:', freeze);
const elOffset = { left: 0, top: 0 };
// top left
if (freeze.w > l && freeze.h > t) {
//
} else if (freeze.w < l && freeze.h < t) {
elOffset.left = freeze.w;
elOffset.top = freeze.h;
} else if (freeze.w > l) {
elOffset.top = freeze.h;
} else if (freeze.h > t) {
elOffset.left = freeze.w;
}
el.offset(elOffset);
areaEl.offset({ left: left - elOffset.left - 0.8, top: top - elOffset.top - 0.8 });
textEl.offset({ width: width - 9 + 0.8, height: height - 3 + 0.8 });
const sOffset = { left: 0 };
sOffset[suggestPosition] = height;
suggest.setOffset(sOffset);
suggest.hide();
}
}
setCell(cell, validator) {
// console.log('::', validator);
const { el, datepicker, suggest } = this;
el.show();
this.cell = cell;
const text = (cell && cell.text) || '';
this.setText(text);
this.validator = validator;
if (validator) {
const { type } = validator;
if (type === 'date') {
datepicker.show();
if (!/^\s*$/.test(text)) {
datepicker.setValue(text);
}
}
if (type === 'list') {
suggest.setItems(validator.values());
suggest.search('');
}
}
}
setText(text) {
this.inputText = text;
// console.log('text>>:', text);
setText.call(this, text, text.length);
resetTextareaSize.call(this);
}
}