Skip to content

Commit

Permalink
Merge pull request #332 from ghiscoding/feat/jquery-to-native-elements
Browse files Browse the repository at this point in the history
chore: replace jQuery with native elements
  • Loading branch information
ghiscoding authored May 8, 2021
2 parents ec2ad6c + d39d363 commit d6e8f4e
Show file tree
Hide file tree
Showing 11 changed files with 351 additions and 247 deletions.
2 changes: 1 addition & 1 deletion packages/common/src/editors/__tests__/dateEditor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ describe('DateEditor', () => {
const editorElm = editor.editorDomElement;

expect(editor.getValue()).toBe('2001-01-02T11:02:02.000Z');
expect(editorElm[0].defaultValue).toBe('2001-01-02T11:02:02.000Z');
expect(editorElm.defaultValue).toBe('2001-01-02T11:02:02.000Z');
});

it('should hide the DOM element when the "hide" method is called', () => {
Expand Down
41 changes: 24 additions & 17 deletions packages/common/src/editors/__tests__/longTextEditor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ describe('LongTextEditor', () => {

afterEach(() => {
editor.destroy();
jest.clearAllMocks();
});

beforeEach(() => {
Expand Down Expand Up @@ -206,14 +205,14 @@ describe('LongTextEditor', () => {
expect(currentTextLengthElm.textContent).toBe('6');
expect(maxTextLengthElm).toBeNull();
expect(editor.getValue()).toBe('task 1');
expect(editorElm[0].defaultValue).toBe('task 1');
expect(editorElm.defaultValue).toBe('task 1');
});

it('should hide the DOM element div wrapper when the "hide" method is called', () => {
editor = new LongTextEditor(editorArguments);
const wrapperElm = document.body.querySelector('.slick-large-editor-text.editor-title') as HTMLDivElement;
editor.show();
expect(wrapperElm.style.display).toBe('');
expect(wrapperElm.style.display).toBe('block');

editor.hide();
expect(wrapperElm.style.display).toBe('none');
Expand All @@ -227,7 +226,7 @@ describe('LongTextEditor', () => {
expect(wrapperElm.style.display).toBe('none');

editor.show();
expect(wrapperElm.style.display).toBe('');
expect(wrapperElm.style.display).toBe('block');
});

describe('isValueChanged method', () => {
Expand Down Expand Up @@ -445,7 +444,7 @@ describe('LongTextEditor', () => {
const spySave = jest.spyOn(editor, 'save');
const editorElm = editor.editorDomElement;

editorElm[0].dispatchEvent(new (window.window as any).KeyboardEvent('keydown', {
editorElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', {
keyCode: KeyCode.ENTER,
ctrlKey: true,
bubbles: true
Expand All @@ -462,7 +461,7 @@ describe('LongTextEditor', () => {
const spyCancel = jest.spyOn(editor, 'cancel');
const editorElm = editor.editorDomElement;

editorElm[0].dispatchEvent(new (window.window as any).KeyboardEvent('keydown', {
editorElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', {
keyCode: KeyCode.ESCAPE,
bubbles: true
}));
Expand All @@ -476,7 +475,7 @@ describe('LongTextEditor', () => {
const editorElm = editor.editorDomElement;
const spyNavigate = jest.spyOn(gridStub, 'navigatePrev');

editorElm[0].dispatchEvent(new (window.window as any).KeyboardEvent('keydown', {
editorElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', {
keyCode: KeyCode.TAB,
shiftKey: true,
bubbles: true
Expand All @@ -492,7 +491,7 @@ describe('LongTextEditor', () => {
const editorElm = editor.editorDomElement;
const spyNavigate = jest.spyOn(gridStub, 'navigateNext');

editorElm[0].dispatchEvent(new (window.window as any).KeyboardEvent('keydown', {
editorElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', {
keyCode: KeyCode.TAB,
shiftKey: false,
bubbles: true
Expand All @@ -504,12 +503,18 @@ describe('LongTextEditor', () => {
});

describe('on button clicked events', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should call "save" method when the save button is clicked', () => {
mockItemData = { id: 1, title: 'task', isActive: true };

editor = new LongTextEditor(editorArguments);
// const spySave = jest.spyOn(editor, 'save');
const spySave = jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit');

editor.loadValue(mockItemData);
const spySave = jest.spyOn(editor, 'save');
const editorFooterElm = document.body.querySelector('.slick-large-editor-text.editor-title .editor-footer') as HTMLDivElement;
const buttonSaveElm = editorFooterElm.querySelector('.btn-primary') as HTMLButtonElement;

Expand All @@ -523,7 +528,9 @@ describe('LongTextEditor', () => {

editor = new LongTextEditor(editorArguments);
editor.loadValue(mockItemData);
const spyCancel = jest.spyOn(editor, 'cancel');
// const spyCancel = jest.spyOn(editor, 'cancel');
const spyCancel = jest.spyOn(editorArguments, 'cancelChanges');

const editorFooterElm = document.body.querySelector('.slick-large-editor-text.editor-title .editor-footer') as HTMLDivElement;
const buttonCancelElm = editorFooterElm.querySelector('.btn-default') as HTMLButtonElement;

Expand Down Expand Up @@ -739,7 +746,7 @@ describe('LongTextEditor', () => {
const editorElm = document.body.querySelector('.slick-large-editor-text') as HTMLDivElement;

expect(editorElm.style.top).toBe('100px');
expect(editorElm.style.left).toBe('675px'); // cellLeftPos - (editorWidth - cellWidth + marginAdjust) => (900 - (310 - 100 + 15))
expect(editorElm.style.left).toBe('690px'); // cellLeftPos - (editorWidth - cellWidth + marginAdjust) => (900 - (310 - 100 + 0))
});

it('should assume editor to positioned on the top of the cell when there is NOT enough room on the bottom', () => {
Expand Down Expand Up @@ -812,8 +819,8 @@ describe('LongTextEditor', () => {
formValues: { title: '' }, editors: {}, triggeredBy: 'user',
}, expect.anything());
expect(disableSpy).toHaveBeenCalledWith(true);
expect(editor.editorDomElement.attr('disabled')).toEqual('disabled');
expect(editor.editorDomElement.val()).toEqual('');
expect(editor.editorDomElement.disabled).toEqual(true);
expect(editor.editorDomElement.value).toEqual('');
});

it('should call "show" and expect the DOM element to become disabled and empty when "onBeforeEditCell" returns false and also expect "onBeforeComposite" to not be called because the value is blank', () => {
Expand All @@ -834,8 +841,8 @@ describe('LongTextEditor', () => {
expect(onBeforeEditSpy).toHaveBeenCalledWith({ ...activeCellMock, column: mockColumn, item: mockItemData, grid: gridStub, target: 'composite', compositeEditorOptions: editorArguments.compositeEditorOptions });
expect(onCompositeEditorSpy).not.toHaveBeenCalled;
expect(disableSpy).toHaveBeenCalledWith(true);
expect(editor.editorDomElement.attr('disabled')).toEqual('disabled');
expect(editor.editorDomElement.val()).toEqual('');
expect(editor.editorDomElement.disabled).toEqual(true);
expect(editor.editorDomElement.value).toEqual('');
});

it('should call "disable" method and expect the DOM element to become disabled and have an empty formValues be passed in the onCompositeEditorChange event', () => {
Expand All @@ -856,8 +863,8 @@ describe('LongTextEditor', () => {
...activeCellMock, column: mockColumn, item: mockItemData, grid: gridStub,
formValues: {}, editors: {}, triggeredBy: 'user',
}, expect.anything());
expect(editor.editorDomElement.attr('disabled')).toEqual('disabled');
expect(editor.editorDomElement.val()).toEqual('');
expect(editor.editorDomElement.disabled).toEqual(true);
expect(editor.editorDomElement.value).toEqual('');
});

it('should expect "onCompositeEditorChange" to have been triggered with the new value showing up in its "formValues" object', () => {
Expand Down
1 change: 0 additions & 1 deletion packages/common/src/editors/autoCompleteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,6 @@ export class AutoCompleteEditor implements Editor {
// add an empty <span> in order to add loading spinner styling
$(`<span></span>`).appendTo(this._$editorInputGroupElm);

// this._$input = $(`<input type="text" data-input data-defaultDate="${this.defaultDate}" class="${inputCssClasses.replace(/\./g, ' ')}" placeholder="${placeholder}" title="${title}" />`);
// show clear date button (unless user specifically doesn't want it)
if (!this.columnEditor?.params?.hideClearButton) {
this._$closeButtonGroupElm.appendTo($closeButtonGroupElm);
Expand Down
108 changes: 61 additions & 47 deletions packages/common/src/editors/dateEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ import {
SlickGrid,
SlickNamespace,
} from './../interfaces/index';
import { destroyObjectDomElementProps, getDescendantProperty, mapFlatpickrDateFormatWithFieldType, mapMomentDateFormatWithFieldType, setDeepValue } from './../services/utilities';
import {
destroyObjectDomElementProps,
emptyElement,
getDescendantProperty,
mapFlatpickrDateFormatWithFieldType,
mapMomentDateFormatWithFieldType,
setDeepValue
} from './../services/utilities';
import { BindingEventService } from '../services/bindingEvent.service';
import { TranslaterService } from '../services/translater.service';

// using external non-typed js libraries
Expand All @@ -31,10 +39,11 @@ declare const Slick: SlickNamespace;
* https://chmln.github.io/flatpickr
*/
export class DateEditor implements Editor {
protected _$inputWithData: any;
protected _$input: any;
protected _$editorInputGroupElm: any;
protected _$closeButtonGroupElm: any;
protected _bindEventService: BindingEventService;
protected _closeButtonElm!: HTMLButtonElement;
protected _editorInputGroupElm!: HTMLDivElement;
protected _inputElm!: HTMLInputElement;
protected _inputWithDataElm!: HTMLInputElement | null;
protected _isValueTouched = false;
protected _lastTriggeredByClearDate = false;
protected _originalDate?: string;
Expand Down Expand Up @@ -64,6 +73,7 @@ export class DateEditor implements Editor {
if (this.gridOptions?.translater) {
this._translaterService = this.gridOptions.translater;
}
this._bindEventService = new BindingEventService();
this.init();
}

Expand All @@ -83,8 +93,8 @@ export class DateEditor implements Editor {
}

/** Getter for the Editor DOM Element */
get editorDomElement(): any {
return this._$input;
get editorDomElement(): HTMLInputElement {
return this._inputElm;
}

/** Get Flatpickr options passed to the editor by the user */
Expand Down Expand Up @@ -146,26 +156,39 @@ export class DateEditor implements Editor {
this._pickerMergedOptions.altInputClass = 'flatpickr-alt-input form-control';
}

this._$editorInputGroupElm = $(`<div class="flatpickr input-group"></div>`);
const $closeButtonGroupElm = $(`<span class="input-group-btn input-group-append" data-clear></span>`);
this._$closeButtonGroupElm = $(`<button class="btn btn-default icon-clear" type="button"></button>`);
this._editorInputGroupElm = document.createElement('div');
this._editorInputGroupElm.className = 'flatpickr input-group';

const closeButtonGroupElm = document.createElement('span');
closeButtonGroupElm.className = 'input-group-btn input-group-append';
closeButtonGroupElm.dataset.clear = '';

this._closeButtonElm = document.createElement('button');
this._closeButtonElm.type = 'button';
this._closeButtonElm.className = 'btn btn-default icon-clear';

this._inputElm = document.createElement('input');
this._inputElm.dataset.input = '';
this._inputElm.dataset.defaultdate = this.defaultDate;
this._inputElm.className = inputCssClasses.replace(/\./g, ' ');
this._inputElm.placeholder = placeholder;
this._inputElm.title = title;

this._$input = $(`<input type="text" data-input data-defaultDate="${this.defaultDate}" class="${inputCssClasses.replace(/\./g, ' ')}" placeholder="${placeholder}" title="${title}" />`);
this._$input.appendTo(this._$editorInputGroupElm);
this._editorInputGroupElm.appendChild(this._inputElm);

// show clear date button (unless user specifically doesn't want it)
if (!this.columnEditor?.params?.hideClearButton) {
this._$closeButtonGroupElm.appendTo($closeButtonGroupElm);
$closeButtonGroupElm.appendTo(this._$editorInputGroupElm);
this._$closeButtonGroupElm.on('click', () => this._lastTriggeredByClearDate = true);
closeButtonGroupElm.appendChild(this._closeButtonElm);
this._editorInputGroupElm.appendChild(closeButtonGroupElm);
this._bindEventService.bind(this._closeButtonElm, 'click', () => this._lastTriggeredByClearDate = true);
}

this._$editorInputGroupElm.appendTo(this.args.container);
this.flatInstance = (this._$editorInputGroupElm[0] && typeof this._$editorInputGroupElm[0].flatpickr === 'function') ? this._$editorInputGroupElm[0].flatpickr(this._pickerMergedOptions) : flatpickr(this._$editorInputGroupElm, this._pickerMergedOptions as unknown as Partial<FlatpickrBaseOptions>);
this.args.container.appendChild(this._editorInputGroupElm);
this.flatInstance = flatpickr(this._editorInputGroupElm, this._pickerMergedOptions as unknown as Partial<FlatpickrBaseOptions>);

// when we're using an alternate input to display data, we'll consider this input as the one to do the focus later on
// else just use the top one
this._$inputWithData = (this._pickerMergedOptions && this._pickerMergedOptions.altInput) ? $(`${inputCssClasses}.flatpickr-alt-input`) : this._$input;
this._inputWithDataElm = (this._pickerMergedOptions?.altInput) ? document.querySelector<HTMLInputElement>(`.flatpickr-alt-input`) : this._inputElm;

if (!compositeEditorOptions) {
setTimeout(() => {
Expand All @@ -178,21 +201,17 @@ export class DateEditor implements Editor {

destroy() {
this.hide();
if (this.flatInstance && typeof this.flatInstance.destroy === 'function') {
this._bindEventService.unbindAll();

if (this.flatInstance?.destroy) {
this.flatInstance.destroy();
if (this.flatInstance.element) {
if (this.flatInstance?.element) {
setTimeout(() => destroyObjectDomElementProps(this.flatInstance));
}
}
if (this._$editorInputGroupElm?.remove) {
this._$editorInputGroupElm.remove();
this._$editorInputGroupElm = null;
}
if (this._$inputWithData?.remove) {
this._$inputWithData.remove();
this._$inputWithData = null;
}
this._$input.remove();
emptyElement(this._editorInputGroupElm);
emptyElement(this._inputWithDataElm);
emptyElement(this._inputElm);
}

disable(isDisabled = true) {
Expand All @@ -202,7 +221,7 @@ export class DateEditor implements Editor {
if (this.flatInstance?._input) {
if (isDisabled) {
this.flatInstance._input.setAttribute('disabled', 'disabled');
this._$closeButtonGroupElm.prop('disabled', true);
this._closeButtonElm.disabled = true;

// clear picker when it's newly disabled and not empty
const currentValue = this.getValue();
Expand All @@ -211,7 +230,7 @@ export class DateEditor implements Editor {
}
} else {
this.flatInstance._input.removeAttribute('disabled');
this._$closeButtonGroupElm.prop('disabled', false);
this._closeButtonElm.disabled = false;
}
}
}
Expand All @@ -232,11 +251,12 @@ export class DateEditor implements Editor {
}

focus() {
if (this._$input) {
this._$input.focus();
if (this._inputElm?.focus) {
this._inputElm.focus();
}
if (this._$inputWithData && typeof this._$inputWithData.focus === 'function') {
this._$inputWithData.focus().select();
if (this._inputWithDataElm?.focus) {
this._inputWithDataElm.focus();
this._inputWithDataElm.select();
}
}

Expand All @@ -257,7 +277,7 @@ export class DateEditor implements Editor {
}

getValue(): string {
return this._$input.val();
return this._inputElm.value;
}

setValue(val: string, isApplyingValue = false, triggerOnCompositeEditorChange = true) {
Expand Down Expand Up @@ -298,7 +318,7 @@ export class DateEditor implements Editor {
}

isValueChanged(): boolean {
const elmValue = this._$input.val();
const elmValue = this._inputElm.value;
const inputFormat = mapMomentDateFormatWithFieldType(this.columnEditor.type || this.columnDef?.type || FieldType.dateIso);
const outputTypeFormat = mapMomentDateFormatWithFieldType((this.columnDef && (this.columnDef.outputType || this.columnEditor.type || this.columnDef.type)) || FieldType.dateUtc);
const elmDateStr = elmValue ? moment(elmValue, inputFormat, false).format(outputTypeFormat) : '';
Expand Down Expand Up @@ -364,7 +384,7 @@ export class DateEditor implements Editor {
}

serializeValue() {
const domValue: string = this._$input.val();
const domValue: string = this._inputElm.value;

if (!domValue) {
return '';
Expand All @@ -379,7 +399,7 @@ export class DateEditor implements Editor {

validate(_targetElm?: any, inputValue?: any): EditorValidationResult {
const isRequired = this.args?.compositeEditorOptions ? false : this.columnEditor.required;
const elmValue = (inputValue !== undefined) ? inputValue : this._$input && this._$input.val && this._$input.val();
const elmValue = (inputValue !== undefined) ? inputValue : this._inputElm?.value;
const errorMsg = this.columnEditor.errorMessage;

// when using Composite Editor, we also want to recheck if the field if disabled/enabled since it might change depending on other inputs on the composite form
Expand All @@ -398,16 +418,10 @@ export class DateEditor implements Editor {

// by default the editor is almost always valid (except when it's required but not provided)
if (isRequired && elmValue === '') {
return {
valid: false,
msg: errorMsg || Constants.VALIDATION_REQUIRED_FIELD
};
return { valid: false, msg: errorMsg || Constants.VALIDATION_REQUIRED_FIELD };
}

return {
valid: true,
msg: null
};
return { valid: true, msg: null };
}

//
Expand Down
Loading

0 comments on commit d6e8f4e

Please sign in to comment.