Skip to content

Commit

Permalink
fix(editor): input editor should call save on focusout or blur of inp…
Browse files Browse the repository at this point in the history
…ut (#1497)
  • Loading branch information
ghiscoding authored May 1, 2024
1 parent f9d8026 commit ccd344e
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 136 deletions.
2 changes: 1 addition & 1 deletion packages/common/src/editors/__tests__/floatEditor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe('FloatEditor', () => {
editor = new FloatEditor(editorArguments);
const editorElm = divContainer.querySelector('input.editor-text.editor-price') as HTMLInputElement;

expect(editorElm.ariaLabel).toBe('Price Number Editor');
expect(editorElm.ariaLabel).toBe('Price Input Editor');
});

it('should initialize the editor and focus on the element after a small delay', () => {
Expand Down
18 changes: 18 additions & 0 deletions packages/common/src/editors/__tests__/inputEditor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,24 @@ describe('InputEditor (TextEditor)', () => {
expect(spyCommit).toHaveBeenCalled();
expect(spySave).toHaveBeenCalled();
});

it('should call "getEditorLock" and "save" methods when "hasAutoCommitEdit" is enabled and the event "blur" is triggered', () => {
mockItemData = { id: 1, title: 'task', isActive: true };
gridOptionMock.autoCommitEdit = true;
const spyCommit = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit');

editor = new InputEditor(editorArguments, 'text');
editor.loadValue(mockItemData);
editor.setValue('task 21');
const spySave = jest.spyOn(editor, 'save');
const editorElm = editor.editorDomElement;

editorElm.dispatchEvent(new (window.window as any).Event('blur'));
jest.runAllTimers(); // fast-forward timer

expect(spyCommit).toHaveBeenCalled();
expect(spySave).toHaveBeenCalled();
});
});

describe('validate method', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe('IntegerEditor', () => {
editor = new IntegerEditor(editorArguments);
const editorElm = divContainer.querySelector('input.editor-text.editor-price') as HTMLInputElement;

expect(editorElm.ariaLabel).toBe('Price Slider Editor');
expect(editorElm.ariaLabel).toBe('Price Input Editor');
});

it('should initialize the editor and focus on the element after a small delay', () => {
Expand Down
83 changes: 0 additions & 83 deletions packages/common/src/editors/floatEditor.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,13 @@
import { createDomElement, toSentenceCase } from '@slickgrid-universal/utils';

import type { EditorArguments, EditorValidationResult } from '../interfaces/index';
import { floatValidator } from '../editorValidators/floatValidator';
import { InputEditor } from './inputEditor';
import { getDescendantProperty } from '../services/utilities';

const DEFAULT_DECIMAL_PLACES = 0;

export class FloatEditor extends InputEditor {
constructor(protected readonly args: EditorArguments) {
super(args, 'number');
}

/** Initialize the Editor */
init() {
if (this.columnDef && this.columnEditor && this.args) {
const columnId = this.columnDef?.id ?? '';
const compositeEditorOptions = this.args.compositeEditorOptions;

this._input = createDomElement('input', {
type: 'number', autocomplete: 'off', ariaAutoComplete: 'none',
ariaLabel: this.columnEditor?.ariaLabel ?? `${toSentenceCase(columnId + '')} Number Editor`,
className: `editor-text editor-${columnId}`,
placeholder: this.columnEditor?.placeholder ?? '',
title: this.columnEditor?.title ?? '',
step: `${(this.columnEditor.valueStep !== undefined) ? this.columnEditor.valueStep : this.getInputDecimalSteps()}`,
});
const cellContainer = this.args.container;
if (cellContainer && typeof cellContainer.appendChild === 'function') {
cellContainer.appendChild(this._input);
}

this._bindEventService.bind(this._input, 'focus', () => this._input?.select());
this._bindEventService.bind(this._input, 'keydown', ((event: KeyboardEvent) => {
this._lastInputKeyEvent = event;
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
event.stopImmediatePropagation();
}
}) as EventListener);

// the lib does not get the focus out event for some reason
// so register it here
if (this.hasAutoCommitEdit && !compositeEditorOptions) {
this._bindEventService.bind(this._input, 'focusout', () => {
this._isValueTouched = true;
this.save();
});
}

if (compositeEditorOptions) {
this._bindEventService.bind(this._input, ['input', 'paste'], this.handleOnInputChange.bind(this) as EventListener);
this._bindEventService.bind(this._input, 'wheel', this.handleOnMouseWheel.bind(this) as EventListener, { passive: true });
}
}
}

getDecimalPlaces(): number {
// returns the number of fixed decimal places or null
let rtn = this.columnEditor?.decimal ?? this.columnEditor?.params?.decimalPlaces ?? undefined;

if (rtn === undefined) {
rtn = DEFAULT_DECIMAL_PLACES;
}
return (!rtn && rtn !== 0 ? null : rtn);
}

getInputDecimalSteps(): string {
const decimals = this.getDecimalPlaces();
let zeroString = '';
for (let i = 1; i < decimals; i++) {
zeroString += '0';
}

if (decimals > 0) {
return `0.${zeroString}1`;
}
return '1';
}

loadValue(item: any) {
const fieldName = this.columnDef && this.columnDef.field;

Expand Down Expand Up @@ -137,17 +67,4 @@ export class FloatEditor extends InputEditor {
validator: this.validator,
});
}

// --
// protected functions
// ------------------

/** When the input value changes (this will cover the input spinner arrows on the right) */
protected handleOnMouseWheel(event: KeyboardEvent) {
this._isValueTouched = true;
const compositeEditorOptions = this.args.compositeEditorOptions;
if (compositeEditorOptions) {
this.handleChangeOnCompositeEditor(event, compositeEditorOptions);
}
}
}
56 changes: 50 additions & 6 deletions packages/common/src/editors/inputEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { getDescendantProperty } from '../services/utilities';
import { textValidator } from '../editorValidators/textValidator';
import { SlickEventData, type SlickGrid } from '../core/index';

const DEFAULT_DECIMAL_PLACES = 0;

/*
* An example of a 'detached' editor.
* KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter.
Expand Down Expand Up @@ -92,13 +94,18 @@ export class InputEditor implements Editor {
const compositeEditorOptions = this.args.compositeEditorOptions;

this._input = createDomElement('input', {
type: this._inputType || 'text',
autocomplete: 'off', ariaAutoComplete: 'none',
type: this._inputType || 'text', autocomplete: 'off', ariaAutoComplete: 'none',
ariaLabel: this.columnEditor?.ariaLabel ?? `${toSentenceCase(columnId + '')} Input Editor`,
className: `editor-text editor-${columnId}`,
placeholder: this.columnEditor?.placeholder ?? '',
title: this.columnEditor?.title ?? '',
className: `editor-text editor-${columnId}`,
});

// add "step" attribute when editor type is integer/float
if (this.inputType === 'number') {
this._input.step = `${(this.columnEditor.valueStep !== undefined) ? this.columnEditor.valueStep : this.getInputDecimalSteps()}`;
}

const cellContainer = this.args.container;
if (cellContainer && typeof cellContainer.appendChild === 'function') {
cellContainer.appendChild(this._input);
Expand All @@ -113,17 +120,21 @@ export class InputEditor implements Editor {
}
}) as EventListener);

// the lib does not get the focus out event for some reason
// so register it here
// listen to focusout or blur to automatically call a save
if (this.hasAutoCommitEdit && !compositeEditorOptions) {
this._bindEventService.bind(this._input, 'focusout', () => {
this._bindEventService.bind(this._input, ['focusout', 'blur'], () => {
this._isValueTouched = true;
this.save();
});
}

if (compositeEditorOptions) {
this._bindEventService.bind(this._input, ['input', 'paste'], this.handleOnInputChange.bind(this) as EventListener);

// add an extra mousewheel listener when editor type is integer/float
if (this.inputType === 'number') {
this._bindEventService.bind(this._input, 'wheel', this.handleOnMouseWheel.bind(this) as EventListener, { passive: true });
}
}
}

Expand Down Expand Up @@ -157,6 +168,30 @@ export class InputEditor implements Editor {
this._input?.focus();
}

getDecimalPlaces(): number {
// returns the number of fixed decimal places or null
let rtn = this.columnEditor?.decimal ?? this.columnEditor?.params?.decimalPlaces ?? undefined;

if (rtn === undefined) {
rtn = DEFAULT_DECIMAL_PLACES;
}
return (!rtn && rtn !== 0 ? null : rtn);
}

/** when editor is a float input editor, we'll want to know how many decimals to show */
getInputDecimalSteps(): string {
const decimals = this.getDecimalPlaces();
let zeroString = '';
for (let i = 1; i < decimals; i++) {
zeroString += '0';
}

if (decimals > 0) {
return `0.${zeroString}1`;
}
return '1';
}

show() {
const isCompositeEditor = !!this.args?.compositeEditorOptions;
if (isCompositeEditor) {
Expand Down Expand Up @@ -338,4 +373,13 @@ export class InputEditor implements Editor {
this._timer = setTimeout(() => this.handleChangeOnCompositeEditor(event, compositeEditorOptions), typingDelay);
}
}

/** When the input value changes (this will cover the input spinner arrows on the right) */
protected handleOnMouseWheel(event: KeyboardEvent) {
this._isValueTouched = true;
const compositeEditorOptions = this.args.compositeEditorOptions;
if (compositeEditorOptions) {
this.handleChangeOnCompositeEditor(event, compositeEditorOptions);
}
}
}
45 changes: 0 additions & 45 deletions packages/common/src/editors/integerEditor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { createDomElement, toSentenceCase } from '@slickgrid-universal/utils';

import type { EditorArguments, EditorValidationResult } from '../interfaces/index';
import { integerValidator } from '../editorValidators/integerValidator';
import { InputEditor } from './inputEditor';
Expand All @@ -10,49 +8,6 @@ export class IntegerEditor extends InputEditor {
super(args, 'number');
}

/** Initialize the Editor */
init() {
if (this.columnDef && this.columnEditor && this.args) {
const columnId = this.columnDef?.id ?? '';
const compositeEditorOptions = this.args.compositeEditorOptions;

this._input = createDomElement('input', {
type: 'number', autocomplete: 'off', ariaAutoComplete: 'none',
ariaLabel: this.columnEditor?.ariaLabel ?? `${toSentenceCase(columnId + '')} Slider Editor`,
placeholder: this.columnEditor?.placeholder ?? '',
title: this.columnEditor?.title ?? '',
step: `${(this.columnEditor.valueStep !== undefined) ? this.columnEditor.valueStep : '1'}`,
className: `editor-text editor-${columnId}`,
});
const cellContainer = this.args.container;
if (cellContainer && typeof cellContainer.appendChild === 'function') {
cellContainer.appendChild(this._input);
}

this._bindEventService.bind(this._input, 'focus', () => this._input?.select());
this._bindEventService.bind(this._input, 'keydown', ((event: KeyboardEvent) => {
this._lastInputKeyEvent = event;
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
event.stopImmediatePropagation();
}
}) as EventListener);

// the lib does not get the focus out event for some reason
// so register it here
if (this.hasAutoCommitEdit && !compositeEditorOptions) {
this._bindEventService.bind(this._input, 'focusout', () => {
this._isValueTouched = true;
this.save();
});
}

if (compositeEditorOptions) {
this._bindEventService.bind(this._input, ['input', 'paste'], this.handleOnInputChange.bind(this) as EventListener);
this._bindEventService.bind(this._input, 'wheel', this.handleOnMouseWheel.bind(this) as EventListener, { passive: true });
}
}
}

loadValue(item: any) {
const fieldName = this.columnDef && this.columnDef.field;

Expand Down

0 comments on commit ccd344e

Please sign in to comment.