add observer to formula and table

This commit is contained in:
Sergey Krylov 2022-06-28 10:20:05 +05:00
parent 3870d7dfd0
commit c74c0490c1
9 changed files with 134 additions and 16 deletions

View File

@ -1,4 +1,6 @@
import { $, Dom } from '../../core/dom'; import { $, Dom } from '../../core/dom';
import { Emitter } from '../../core/Emitter';
import { ExcelComponent } from '../../core/ExcelComponent';
interface ExcelOptionsType { interface ExcelOptionsType {
components: any[] components: any[]
@ -7,10 +9,12 @@ interface ExcelOptionsType {
export class Excel { export class Excel {
$el: HTMLElement | Dom; $el: HTMLElement | Dom;
components: any[]; components: any[];
emitter: Emitter;
constructor(selector: string, options: ExcelOptionsType) { constructor(selector: string, options: ExcelOptionsType) {
this.$el = $(selector); this.$el = $(selector);
this.components = options.components; this.components = options.components;
this.emitter = new Emitter();
console.log(`Created new Excel class in ${selector} with options: ${options}`); console.log(`Created new Excel class in ${selector} with options: ${options}`);
} }
@ -18,9 +22,13 @@ export class Excel {
getRoot() { getRoot() {
const $root = $.create('div', 'excel'); const $root = $.create('div', 'excel');
const componentOptions = {
emitter: this.emitter,
};
this.components = this.components.map(Component => { this.components = this.components.map(Component => {
const $el = $.create('div', Component.className); const $el = $.create('div', Component.className);
const component: any = new Component($el); const component: ExcelComponent = new Component($el, componentOptions);
$el.html(component.toHTML()); $el.html(component.toHTML());
$root.append($el.$el); $root.append($el.$el);
@ -35,4 +43,8 @@ export class Excel {
this.$el.append(this.getRoot()); this.$el.append(this.getRoot());
this.components.forEach(component => component.init()); this.components.forEach(component => component.init());
} }
destroy() {
this.components.forEach(component => component.destroy());
}
} }

View File

@ -1,28 +1,51 @@
import { DomClass } from '../../core/dom'; import { Dom, DomClass } from '../../core/dom';
import { ExcelComponent } from '../../core/ExcelComponent'; import { ExcelComponent } from '../../core/ExcelComponent';
export class Formula extends ExcelComponent { export class Formula extends ExcelComponent {
static className = 'excel__formula'; static className = 'excel__formula';
constructor($root: DomClass) { private formulaInput: Dom;
constructor($root: DomClass, options: any) {
super($root, { super($root, {
listeners: ['input', 'click'], listeners: ['input', 'keydown'],
name: 'Formula', name: 'Formula',
...options,
}); });
} }
toHTML(): string { toHTML(): string {
return ` return `
<div class="info">fx</div> <div class="info">fx</div>
<div class="input" contenteditable spellcheck="false"></div> <div id="formula-input" class="input" contenteditable spellcheck="false"></div>
`; `;
} }
onInput(event: Event) { init() {
console.log('Formula on input listeners', event); super.init();
this.formulaInput = this.$root.find('#formula-input');
this.$on('table:input', text => {
this.formulaInput.text = text;
});
this.$on('table:select-cell', text => {
this.formulaInput.text = text;
});
} }
onClick() { onInput(event: Event) {
const text = (event.target as HTMLElement).textContent.trim();
this.$emit('formula:input', text);
}
onKeydown(event: KeyboardEvent) {
const preventedKeys = ['Enter', 'Tab'];
if (preventedKeys.includes(event.key)) event.preventDefault();
if (event.key === 'Enter') {
this.$emit('formula:enter-press');
}
} }
} }

View File

@ -1,8 +1,16 @@
import { DomClass } from '../../core/dom';
import { ExcelComponent } from '../../core/ExcelComponent'; import { ExcelComponent } from '../../core/ExcelComponent';
export class Header extends ExcelComponent { export class Header extends ExcelComponent {
static className = 'excel__header'; static className = 'excel__header';
constructor($root: DomClass, options: any) {
super($root, {
name: 'Header',
...options,
});
}
toHTML(): string { toHTML(): string {
return ` return `
<input type="text" class="input" value="Новая таблица"> <input type="text" class="input" value="Новая таблица">

View File

@ -1,3 +1,4 @@
import { DomClass } from '../../core/dom';
import { ExcelComponent } from '../../core/ExcelComponent'; import { ExcelComponent } from '../../core/ExcelComponent';
import { selectHandler } from './handlers/table.select.handler'; import { selectHandler } from './handlers/table.select.handler';
import { TableSelection } from './TableSelection'; import { TableSelection } from './TableSelection';
@ -9,10 +10,11 @@ export class Table extends ExcelComponent {
private selection: TableSelection; private selection: TableSelection;
constructor($root: any) { constructor($root: DomClass, options: any) {
super($root, { super($root, {
name: 'Table', name: 'Table',
listeners: ['mousedown', 'keydown'], listeners: ['mousedown', 'keydown', 'input'],
...options,
}); });
} }
@ -29,14 +31,33 @@ export class Table extends ExcelComponent {
const $cell = this.$root.find('[data-id="0:0"]'); const $cell = this.$root.find('[data-id="0:0"]');
this.selection.select($cell); this.selection.select($cell);
this.$emit('table:select-cell', $cell.text);
this.$on('formula:input', (data) => {
this.selection.current.text = data;
});
this.$on('formula:enter-press', () => {
this.selection.current.focus();
});
}
emitSelectCallback() {
this.$emit('table:select-cell', this.selection.current.text);
} }
onMousedown(event: MouseEvent) { onMousedown(event: MouseEvent) {
selectHandler(event, this.selection); selectHandler(event, this.selection, this.emitSelectCallback.bind(this));
resizeHandler(this.$root, event); resizeHandler(this.$root, event);
} }
onKeydown(event: KeyboardEvent) { onKeydown(event: KeyboardEvent) {
selectHandler(event, this.selection); selectHandler(event, this.selection, this.emitSelectCallback.bind(this));
}
onInput(event: InputEvent) {
const inputText = (event.target as HTMLElement).textContent.trim();
this.$emit('table:input', inputText);
} }
} }

View File

@ -2,7 +2,7 @@ import { $ } from '../../../core/dom';
import { getParamsFromCellId, isCell } from '../table.functions'; import { getParamsFromCellId, isCell } from '../table.functions';
import { TableSelection } from '../TableSelection'; import { TableSelection } from '../TableSelection';
export function selectHandler(event: MouseEvent | KeyboardEvent, selection: TableSelection) { export function selectHandler(event: MouseEvent | KeyboardEvent, selection: TableSelection, callback?: () => void) {
switch (event.type) { switch (event.type) {
case 'mousedown': { case 'mousedown': {
onMouseDownHandler(); onMouseDownHandler();
@ -15,6 +15,8 @@ export function selectHandler(event: MouseEvent | KeyboardEvent, selection: Tabl
default: break; default: break;
} }
callback();
function onMouseDownHandler() { function onMouseDownHandler() {
if (isCell(event)) { if (isCell(event)) {
if (event.shiftKey) selection.selectGroup($(event.target as HTMLElement)); if (event.shiftKey) selection.selectGroup($(event.target as HTMLElement));

View File

@ -1,13 +1,14 @@
import { Dom } from '../../core/dom'; import { DomClass } from '../../core/dom';
import { ExcelComponent } from '../../core/ExcelComponent'; import { ExcelComponent } from '../../core/ExcelComponent';
export class Toolbar extends ExcelComponent { export class Toolbar extends ExcelComponent {
static className = 'excel__toolbar'; static className = 'excel__toolbar';
constructor($root: Dom) { constructor($root: DomClass, options: any) {
super($root, { super($root, {
name: 'Toolbar', name: 'Toolbar',
listeners: ['click'], listeners: ['click'],
...options,
}); });
} }

28
src/core/Emitter.ts Normal file
View File

@ -0,0 +1,28 @@
export class Emitter {
private listeners: {
[k: string]: Array<(args?: any) => any>
};
constructor() {
this.listeners = {};
}
emit(eventName: string, ...args: any[]) {
if (!Array.isArray(this.listeners[eventName])) return false;
this.listeners[eventName].forEach(listener => {
listener(args);
});
return true;
}
subscribe(eventName: string, callback: (args?: any) => any) {
this.listeners[eventName] = this.listeners[eventName] || [];
this.listeners[eventName].push(callback);
return () => {
this.listeners[eventName] = this.listeners[eventName].filter(listener => listener !== callback);
};
}
}

View File

@ -1,4 +1,5 @@
import { DomListener } from './DomListener'; import { DomListener } from './DomListener';
import { Emitter } from './Emitter';
interface ExcelComponentClass { interface ExcelComponentClass {
toHTML: () => string; toHTML: () => string;
@ -8,15 +9,19 @@ interface ExcelComponentClass {
type OptionsType = { type OptionsType = {
listeners?: string[]; listeners?: string[];
name: string; name: string;
emitter?: Emitter;
}; };
export class ExcelComponent extends DomListener implements ExcelComponentClass { export class ExcelComponent extends DomListener implements ExcelComponentClass {
name: string; name: string;
emitter: Emitter;
private unsubscribers: ((args?: any) => any)[];
constructor($root: any, options: OptionsType) { constructor($root: any, options: OptionsType) {
super($root, options?.listeners); super($root, options?.listeners);
this.name = options?.name; this.name = options?.name;
this.emitter = options?.emitter;
this.unsubscribers = [];
this.prepare(); this.prepare();
} }
@ -28,11 +33,21 @@ export class ExcelComponent extends DomListener implements ExcelComponentClass {
return ''; return '';
} }
$emit(event: string, ...args: any): void {
this.emitter.emit(event, ...args);
}
$on(event: string, callback: (args: any) => any) {
const unsub = this.emitter.subscribe(event, callback);
this.unsubscribers.push(unsub);
}
init() { init() {
this.initDOMListeners(); this.initDOMListeners();
} }
destroy() { destroy() {
this.removeDOMListeners(); this.removeDOMListeners();
this.unsubscribers.forEach(unsub => unsub());
} }
} }

View File

@ -24,6 +24,14 @@ export class Dom implements DomClass {
return this.$el.outerHTML.trim(); return this.$el.outerHTML.trim();
} }
set text(text: string) {
this.$el.textContent = text;
}
get text() {
return this.$el.textContent;
}
clear() { clear() {
this.html(''); this.html('');