Skip to content

Commit

Permalink
feat(editor): start working on a Compound Editor
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding-SE committed Apr 24, 2020
1 parent 5904c67 commit 49107c1
Show file tree
Hide file tree
Showing 17 changed files with 981 additions and 16 deletions.
214 changes: 214 additions & 0 deletions packages/common/src/editors/compoundInputEditor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import { Constants } from '../constants';
import { KeyCode } from '../enums/keyCode.enum';
import { Column, ColumnEditor, Editor, EditorArguments, EditorValidator, EditorValidatorOutput } from '../interfaces/index';
import { getDescendantProperty, setDeepValue } from '../services/utilities';

/*
* An example of a 'detached' editor.
* KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter.
*/
export class CompoundInputEditor implements Editor {
protected _inputType = 'text';
private _lastInputEvent: KeyboardEvent;
private _leftInput: HTMLInputElement;
private _rightInput: HTMLInputElement;
originalLeftValue: string;
originalRightValue: string;

/** SlickGrid Grid object */
grid: any;

constructor(private args: EditorArguments) {
if (!args) {
throw new Error('[Slickgrid-Universal] Something is wrong with this grid, an Editor must always have valid arguments.');
}
this.grid = args.grid;
this.init();
}

/** Get Column Definition object */
get columnDef(): Column | undefined {
return this.args && this.args.column;
}

/** Get Column Editor object */
get columnEditor(): ColumnEditor {
return this.columnDef && this.columnDef.internalColumnEditor || {};
}

/** Getter of input type (text, number, password) */
get inputType() {
return this._inputType;
}

/** Setter of input type (text, number, password) */
set inputType(type: string) {
this._inputType = type;
}

/** Get the Editor DOM Element */
get editorDomElement(): { leftInput: HTMLInputElement, rightInput: HTMLInputElement } {
return { leftInput: this._leftInput, rightInput: this._rightInput };
}

get hasAutoCommitEdit() {
return this.grid.getOptions().autoCommitEdit;
}

/** Get the Validator function, can be passed in Editor property or Column Definition */
get validator(): EditorValidator | undefined {
return (this.columnEditor && this.columnEditor.validator) || (this.columnDef && this.columnDef.validator);
}

init() {
const editorParams = this.columnEditor.params;
if (!editorParams || !editorParams.leftField || !editorParams.rightField) {
throw new Error(`[Slickgrid-Universal] Please make sure that your Compound Editor has params defined with "leftField" and "rightField" (example: { editor: { model: Editors.compound, params: { leftField: 'firstName', rightField: 'lastName' } }}`);
}
this._leftInput = this.createInput('left');
this._rightInput = this.createInput('right');

const cellContainer = this.args?.container;
if (cellContainer && typeof cellContainer.appendChild === 'function') {
cellContainer.appendChild(this._leftInput);
cellContainer.appendChild(this._rightInput);
}

this._leftInput.onkeydown = ((event: KeyboardEvent) => {
this._lastInputEvent = event;
if (event.keyCode === KeyCode.LEFT || event.keyCode === KeyCode.RIGHT || event.keyCode === KeyCode.TAB) {
event.stopImmediatePropagation();
}
});

// the lib does not get the focus out event for some reason
// so register it here
if (this.hasAutoCommitEdit) {
this._leftInput.addEventListener('focusout', () => this.save());
}

setTimeout(() => this.focus(), 50);
}

destroy() {
const columnId = this.columnDef && this.columnDef.id;
const elm = document.querySelector(`.compound-editor-text.editor-${columnId}`);
if (elm) {
elm.removeEventListener('focusout', () => { });
}
}

createInput(position: 'left' | 'right'): HTMLInputElement {
const columnId = this.columnDef && this.columnDef.id;
const placeholder = this.columnEditor && this.columnEditor.placeholder || '';
const title = this.columnEditor && this.columnEditor.title || '';
const input = document.createElement('input') as HTMLInputElement;
input.className = `compound-editor-text editor-${columnId} ${position}`;
input.title = title;
input.type = this._inputType || 'text';
input.setAttribute('role', 'presentation');
input.autocomplete = 'off';
input.placeholder = placeholder;
input.title = title;

return input;
}

focus(): void {
this._leftInput.focus();
}

getValue(): string {
return this._leftInput.value || '';
}

setValue(value: string) {
this._leftInput.value = value;
}

applyValue(item: any, state: any) {
const fieldName = this.columnDef && this.columnDef.field;
if (fieldName !== undefined) {
const isComplexObject = fieldName && fieldName.indexOf('.') > 0; // is the field a complex object, "address.streetNumber"

// validate the value before applying it (if not valid we'll set an empty string)
const validation = this.validate(state);
const newValue = (validation && validation.valid) ? state : '';

// set the new value to the item datacontext
if (isComplexObject) {
setDeepValue(item, fieldName, newValue);
} else if (fieldName) {
item[fieldName] = newValue;
}
}
}

isValueChanged(): boolean {
const elmValue = this._leftInput.value;
const lastEvent = this._lastInputEvent && this._lastInputEvent.keyCode;
if (this.columnEditor && this.columnEditor.alwaysSaveOnEnterKey && lastEvent === KeyCode.ENTER) {
return true;
}
return (!(elmValue === '' && this.originalLeftValue === null)) && (elmValue !== this.originalLeftValue);
}

loadValue(item: any) {
const leftFieldName = this.columnDef && this.columnDef.field;
const rightFieldName = this.columnEditor.params?.rightField;

// is the field a complex object, "address.streetNumber"
const isComplexObject = leftFieldName && leftFieldName.indexOf('.') > 0;

if (item && leftFieldName !== undefined && this.columnDef && (item.hasOwnProperty(leftFieldName) || isComplexObject)) {
const leftValue = (isComplexObject) ? getDescendantProperty(item, leftFieldName) : (item.hasOwnProperty(leftFieldName) && item[leftFieldName] || '');
this.originalLeftValue = leftValue;
this._leftInput.value = this.originalLeftValue;
this._leftInput.select();
}

if (item && rightFieldName !== undefined && this.columnDef && (item.hasOwnProperty(rightFieldName) || isComplexObject)) {
const rightValue = (isComplexObject) ? getDescendantProperty(item, rightFieldName) : (item.hasOwnProperty(rightFieldName) && item[rightFieldName] || '');
this.originalRightValue = rightValue;
this._rightInput.value = this.originalRightValue;
}
}

save() {
const validation = this.validate();
if (validation && validation.valid && this.isValueChanged()) {
if (this.hasAutoCommitEdit) {
this.grid.getEditorLock().commitCurrentEdit();
} else {
this.args.commitChanges();
}
}
}

serializeValue() {
return this._leftInput.value;
}

validate(inputValue?: any): EditorValidatorOutput {
const isRequired = this.columnEditor.required;
const elmValue = (inputValue !== undefined) ? inputValue : this._leftInput && this._leftInput.value;
const errorMsg = this.columnEditor.errorMessage;

if (this.validator) {
return this.validator(elmValue, this.args);
}

// 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: true,
msg: null
};
}
}
2 changes: 1 addition & 1 deletion packages/common/src/editors/dateEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class DateEditor implements Editor {

// 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 = (pickerMergedOptions && pickerMergedOptions.altInput) ? $(`${inputCssClasses}.flatpickr-alt-input`) : this._$input;
this._$inputWithData = (pickerMergedOptions && pickerMergedOptions.altInput) ? $(`.${pickerMergedOptions.altInputClass.replace(' ', '.')}`) : this._$input;
}
}

Expand Down
4 changes: 4 additions & 0 deletions packages/common/src/editors/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AutoCompleteEditor } from './autoCompleteEditor';
import { CheckboxEditor } from './checkboxEditor';
import { CompoundInputEditor } from './compoundInputEditor';
import { DateEditor } from './dateEditor';
import { FloatEditor } from './floatEditor';
import { IntegerEditor } from './integerEditor';
Expand All @@ -16,6 +17,9 @@ export const Editors = {
/** Checkbox Editor (uses native checkbox DOM element) */
checkbox: CheckboxEditor,

/** Checkbox Editor (uses native checkbox DOM element) */
compoundText: CompoundInputEditor,

/** Date Picker Editor (which uses 3rd party lib "flatpickr") */
date: DateEditor,

Expand Down
3 changes: 2 additions & 1 deletion packages/common/src/editors/sliderEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export class SliderEditor implements Editor {
});
}
}
this.focus();
}

cancel() {
Expand All @@ -111,7 +112,7 @@ export class SliderEditor implements Editor {
}

focus() {
this._$editorElm.focus();
this._$input.focus();
}

getValue(): string {
Expand Down
8 changes: 8 additions & 0 deletions packages/common/src/styles/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ $checkbox-selector-opacity: 0.15 !default;
$checkbox-selector-opacity-hover: 0.35 !default;

/* Editors */
$editor-focus-border-color: lighten($primary-color, 10%);
$editor-focus-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px rgba(lighten($primary-color, 3%), .6);
$date-editor-focus-border-color: $editor-focus-border-color;
$date-editor-focus-box-shadow: $editor-focus-box-shadow;
$large-editor-background-color: #ffffff !default;
$large-editor-border: 2px solid gray !default;
$large-editor-text-padding: 5px !default;
Expand All @@ -408,9 +412,13 @@ $text-editor-padding-bottom: 0 !default;
$text-editor-padding-left: 2px !default;
$text-editor-padding-right: 0 !default;
$text-editor-padding-top: 0 !default;
$text-editor-focus-border-color: $editor-focus-border-color;
$text-editor-focus-box-shadow: $editor-focus-box-shadow;
$slider-editor-height: 24px !default;
$slider-editor-runnable-track-padding: 0 6px !default;
$slider-editor-number-padding: 4px 6px !default;
$slider-editor-focus-border-color: $editor-focus-border-color;
$slider-editor-focus-box-shadow: $editor-focus-box-shadow;

/* Compound Filters */
$compound-filter-bgcolor: #e4eacf !default;
Expand Down
7 changes: 7 additions & 0 deletions packages/common/src/styles/slick-default-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@
outline: 0;
transform: translate(0, -2px);
}

input.compound-editor-text {
width: calc(50% - 5px);
height: 100%;
outline: 0;
transform: translate(0, -2px);
}
}
}

Expand Down
29 changes: 28 additions & 1 deletion packages/common/src/styles/slick-editors.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@import './variables';

.slick-cell.active {
input.compound-editor-text,
input.editor-text {
border: $text-editor-border;
border-radius: $text-editor-border-radius;
Expand All @@ -13,7 +14,33 @@
margin-bottom: $text-editor-margin-bottom;
margin-right: $text-editor-margin-right;
margin-top: $text-editor-margin-top;
}

&:focus {
outline: 0;
border-color: $text-editor-focus-border-color;
box-shadow: $text-editor-focus-box-shadow;
}

&.right {
margin-left: calc(#{$text-editor-margin-left + 9px});
}
}

.slider-editor-input {
&:focus {
outline: 0;
border-color: $slider-editor-focus-border-color;
box-shadow: $slider-editor-focus-box-shadow;
}
}

.flatpickr-alt-input.editor-text {
&:focus {
outline: 0;
border-color: $date-editor-focus-border-color;
box-shadow: $date-editor-focus-box-shadow;
}
}
}

/* Long Text Editor */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,11 @@
*, :after, :before {
box-sizing: border-box;
}

.form-control:focus{
// border-color: #66afe9;
border-color: lighten($primary-color, 10%);
outline: 0;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px rgba(lighten($primary-color, 3%), .6);
}
}
1 change: 1 addition & 0 deletions packages/common/src/styles/slickgrid-theme-salesforce.scss
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ $multiselect-icon-search: "\F0349";
$multiselect-unchecked-opacity: 0.8;
$row-move-plugin-cursor: grab;
$row-move-plugin-icon: "\F0278";
$editor-focus-box-shadow: 0 0 3px $primary-color;
$slider-editor-height: 26px;
$row-selected-color: darken($cell-odd-background-color, 7%);
$row-mouse-hover-color: rgba(128, 183, 231, 0.1);
Expand Down
1 change: 0 additions & 1 deletion packages/web-demo-vanilla-bundle/src/app-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export class AppRouting {
{ route: 'example06', name: 'example06', title: 'Example06', moduleId: './examples/example06' },
{ route: 'example07', name: 'example07', title: 'Example07', moduleId: './examples/example07' },
{ route: 'example50', name: 'example50', title: 'Example50', moduleId: './examples/example50' },
{ route: 'example51', name: 'example51', title: 'Example51', moduleId: './examples/example51' },
{ route: '', redirect: 'example01' },
{ route: '**', redirect: 'example01' }
];
Expand Down
11 changes: 5 additions & 6 deletions packages/web-demo-vanilla-bundle/src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
<span>
<h4 class="title is-4 has-text-white">Slickgrid-Universal</h4>
</span>
<span style="position: relative; top: 5px; margin-left: 5px;">
<!-- <span style="position: relative; top: 5px; margin-left: 5px;">
<iframe src="https://ghbtns.com/github-btn.html?user=ghiscoding&repo=slickgrid-universal&type=star&count=true"
allowtransparency="true" scrolling="no" frameborder="0" width="170px" height="20px"></iframe>
</span>
</span> -->
</a>

<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
data-target="navbarBasicExample">
data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
Expand Down Expand Up @@ -48,9 +48,8 @@ <h4 class="title is-4 has-text-white">Slickgrid-Universal</h4>
Example06 - Tree Data from Hierarchical View Dataset
</a>
<a class="navbar-item" onclick.delegate="loadRoute('example07')">Example07 - Row Move &amp; Row Selections</a>
<!-- <hr class="navbar-divider">
<a class="navbar-item" onclick.delegate="loadRoute('example50')">Example50 - SE Grouping &amp; Aggregators</a>
<a class="navbar-item" onclick.delegate="loadRoute('example51')">Example51 - SE Tree Data</a> -->
<hr class="navbar-divider">
<a class="navbar-item" onclick.delegate="loadRoute('example50')">Example50 - SE Tree Data</a>
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit 49107c1

Please sign in to comment.