Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"name": "Chrome Debugger",
"url": "http://localhost:4300",
"webRoot": "${workspaceFolder}"
},
Expand Down Expand Up @@ -34,7 +34,7 @@
{
"type": "node",
"request": "launch",
"name": "Jest Current File",
"name": "Jest Current Spec File",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": [
"--runInBand",
Expand Down
4 changes: 3 additions & 1 deletion src/app/examples/grid-editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,9 +515,11 @@ export class GridEditorComponent implements OnInit {
const tempDataset = [];
for (let i = startingIndex; i < (startingIndex + itemCount); i++) {
const randomYear = 2000 + Math.floor(Math.random() * 10);
const randomFinishYear = (new Date().getFullYear() - 3) + Math.floor(Math.random() * 10); // use only years not lower than 3 years ago
const randomMonth = Math.floor(Math.random() * 11);
const randomDay = Math.floor((Math.random() * 29));
const randomPercent = Math.round(Math.random() * 100);
const randomFinish = new Date(randomFinishYear, (randomMonth + 1), randomDay);

tempDataset.push({
id: i,
Expand All @@ -526,7 +528,7 @@ export class GridEditorComponent implements OnInit {
percentComplete: randomPercent,
percentCompleteNumber: randomPercent,
start: new Date(randomYear, randomMonth, randomDay),
finish: new Date(randomYear, (randomMonth + 1), randomDay),
finish: randomFinish < new Date() ? '' : randomFinish, // make sure the random date is earlier than today
effortDriven: (i % 5 === 0),
prerequisites: (i % 2 === 0) && i !== 0 && i < 12 ? [i, i - 1] : [],
countryOfOrigin: (i % 2) ? { code: 'CA', name: 'Canada' } : { code: 'US', name: 'United States' },
Expand Down
15 changes: 14 additions & 1 deletion src/app/modules/angular-slickgrid/editors/autoCompleteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
FieldType
} from './../models/index';
import { Constants } from './../constants';
import { findOrDefault } from '../services/utilities';

// using external non-typed js libraries
declare var $: any;
Expand Down Expand Up @@ -169,10 +170,22 @@ export class AutoCompleteEditor implements Editor {
}

applyValue(item: any, state: any) {
let newValue = state;
const fieldName = this.columnDef && this.columnDef.field;

// if we have a collection defined, we will try to find the string within the collection and return it
if (Array.isArray(this.collection) && this.collection.length > 0) {
newValue = findOrDefault(this.collection, (collectionItem: any) => {
if (collectionItem && collectionItem.hasOwnProperty(this.labelName)) {
return collectionItem[this.labelName].toString() === state;
}
return collectionItem.toString() === state;
});
}

// when it's a complex object, then pull the object name only, e.g.: "user.firstName" => "user"
const fieldNameFromComplexObject = fieldName.indexOf('.') ? fieldName.substring(0, fieldName.indexOf('.')) : '';
item[fieldNameFromComplexObject || fieldName] = state;
item[fieldNameFromComplexObject || fieldName] = newValue;
}

isValueChanged() {
Expand Down
4 changes: 3 additions & 1 deletion src/app/modules/angular-slickgrid/editors/dateEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ export class DateEditor implements Editor {
this.$input = $(`<input type="text" data-defaultDate="${this.defaultDate}" class="${inputCssClasses.replace(/\./g, ' ')}" placeholder="${placeholder}" title="${title}" />`);
this.$input.appendTo(this.args.container);
this.flatInstance = (this.$input[0] && typeof this.$input[0].flatpickr === 'function') ? this.$input[0].flatpickr(pickerMergedOptions) : null;
this.show();

// 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
Expand Down Expand Up @@ -106,6 +105,9 @@ export class DateEditor implements Editor {
destroy() {
this.hide();
this.$input.remove();
if (this._$inputWithData && typeof this._$inputWithData.remove === 'function') {
this._$inputWithData.remove();
}
}

show() {
Expand Down
46 changes: 36 additions & 10 deletions src/app/modules/angular-slickgrid/editors/selectEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Editor,
EditorValidator,
EditorValidatorOutput,
FieldType,
GridOption,
MultipleSelectOption,
SelectOption,
Expand Down Expand Up @@ -272,18 +273,34 @@ export class SelectEditor implements Editor {

applyValue(item: any, state: any): void {
const fieldName = this.columnDef && this.columnDef.field;
const fieldType = this.columnDef && this.columnDef.type;
let value = state;

// when the provided user defined the column field type as a possible number then try parsing the state value as that
if (fieldType === FieldType.number || fieldType === FieldType.integer || fieldType === FieldType.boolean) {
value = parseFloat(state);
}

// when set as a multiple selection, we can assume that the 3rd party lib multiple-select will return a CSV string
// we need to re-split that into an array to be the same as the original column
if (this.isMultipleSelect && typeof state === 'string' && state.indexOf(',') >= 0) {
value = state.split(',');
}

// when it's a complex object, then pull the object name only, e.g.: "user.firstName" => "user"
const fieldNameFromComplexObject = fieldName.indexOf('.') ? fieldName.substring(0, fieldName.indexOf('.')) : '';
item[fieldNameFromComplexObject || fieldName] = state;
item[fieldNameFromComplexObject || fieldName] = value;
}

destroy() {
this._destroying = true;
if (this.$editorElm && this.$editorElm.multipleSelect) {
this.$editorElm.multipleSelect('close');
if (this.$editorElm && typeof this.$editorElm.multipleSelect === 'function') {
this.$editorElm.multipleSelect('destroy');
this.$editorElm.remove();
const elementClassName = this.elementName.toString().replace('.', '\\.'); // make sure to escape any dot "." from CSS class to avoid console error
$(`[name=${elementClassName}].ms-drop`).remove();
} else if (this.$editorElm && typeof this.$editorElm.remove === 'function') {
this.$editorElm.remove();
}
this._subscriptions = unsubscribeAllObservables(this._subscriptions);
}
Expand All @@ -309,20 +326,25 @@ export class SelectEditor implements Editor {
loadMultipleValues(currentValues: any[]) {
// convert to string because that is how the DOM will return these values
if (Array.isArray(currentValues)) {
this.defaultValue = currentValues.map((i: any) => i.toString());
// keep the default values in memory for references
this.defaultValue = currentValues.map((i: any) => i);

// compare all the array values but as string type since multiple-select always return string
const currentStringValues = currentValues.map((i: any) => i.toString());
this.$editorElm.find('option').each((i: number, $e: any) => {
$e.selected = (this.defaultValue.indexOf($e.value) !== -1);
$e.selected = (currentStringValues.indexOf($e.value) !== -1);
});
}
}

loadSingleValue(currentValue: any) {
// convert to string because that is how the DOM will return these values
// make sure the prop exists first
this.defaultValue = currentValue && currentValue.toString();
// keep the default value in memory for references
this.defaultValue = currentValue;

// make sure the prop exists first
this.$editorElm.find('option').each((i: number, $e: any) => {
$e.selected = (this.defaultValue === $e.value);
// check equality after converting defaultValue to string since the DOM value will always be of type string
$e.selected = (currentValue.toString() === $e.value);
});
}

Expand Down Expand Up @@ -513,7 +535,11 @@ export class SelectEditor implements Editor {
const elementOptions = (this.columnDef.internalColumnEditor) ? this.columnDef.internalColumnEditor.elementOptions : {};
this.editorElmOptions = { ...this.defaultOptions, ...elementOptions };
this.$editorElm = this.$editorElm.multipleSelect(this.editorElmOptions);
setTimeout(() => this.$editorElm.multipleSelect('open'));
setTimeout(() => {
if (this.$editorElm && typeof this.$editorElm.multipleSelect === 'function') {
this.$editorElm.multipleSelect('open');
}
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,24 @@ describe('the Checkmark Formatter', () => {
expect(result).toBe('');
});

it('should return the Font Awesome Checkmark icon when input is True', () => {
const value = true;
const result = checkmarkFormatter(0, 0, value, {} as Column, {});
expect(result).toBe('<i class="fa fa-check checkmark-icon" aria-hidden="true"></i>');
it('should return an empty string when the string "FALSE" (case insensitive) is provided', () => {
const value = 'FALSE';
const result1 = checkmarkFormatter(0, 0, value.toLowerCase(), {} as Column, {});
const result2 = checkmarkFormatter(0, 0, value.toUpperCase(), {} as Column, {});
expect(result1).toBe('');
expect(result2).toBe('');
});

it('should return the Font Awesome Checkmark icon when input is filled with any string', () => {
const value = 'anything';
it('should return the Font Awesome Checkmark icon when the string "True" (case insensitive) is provided', () => {
const value = 'True';
const result1 = checkmarkFormatter(0, 0, value.toLowerCase(), {} as Column, {});
const result2 = checkmarkFormatter(0, 0, value.toUpperCase(), {} as Column, {});
expect(result1).toBe('<i class="fa fa-check checkmark-icon" aria-hidden="true"></i>');
expect(result2).toBe('<i class="fa fa-check checkmark-icon" aria-hidden="true"></i>');
});

it('should return the Font Awesome Checkmark icon when input is True', () => {
const value = true;
const result = checkmarkFormatter(0, 0, value, {} as Column, {});
expect(result).toBe('<i class="fa fa-check checkmark-icon" aria-hidden="true"></i>');
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Column } from './../models/column.interface';
import { Formatter } from './../models/formatter.interface';
import { parseBoolean } from '../services/utilities';

export const checkmarkFormatter: Formatter = (row: number, cell: number, value: any, columnDef: Column, dataContext: any) =>
value ? `<i class="fa fa-check checkmark-icon" aria-hidden="true"></i>` : '';
export const checkmarkFormatter: Formatter = (row: number, cell: number, value: any, columnDef: Column, dataContext: any) => {
return parseBoolean(value) ? `<i class="fa fa-check checkmark-icon" aria-hidden="true"></i>` : '';
}
56 changes: 56 additions & 0 deletions src/app/modules/angular-slickgrid/models/editor.interface.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,73 @@
import { EditorValidatorOutput } from './editorValidatorOutput.interface';

/***
* SlickGrid Editor interface, more info can be found on the SlickGrid repo
* https://github.com/6pac/SlickGrid/wiki/Writing-custom-cell-editors
*/
export interface Editor {
/** Initialize the Editor */
init: () => void;

/** Saves the Editor value */
save?: () => void;

/** Cancels the Editor */
cancel?: () => void;

/**
* if implemented, this will be called if the cell being edited is scrolled out of the view
* implement this is your UI is not appended to the cell itself or if you open any secondary
* selector controls (like a calendar for a datepicker input)
*/
hide?: () => void;

/** pretty much the opposite of hide */
show?: () => void;

/**
* if implemented, this will be called by the grid if any of the cell containers are scrolled
* and the absolute position of the edited cell is changed
* if your UI is constructed as a child of document BODY, implement this to update the
* position of the elements as the position of the cell changes
*
* the cellBox: { top, left, bottom, right, width, height, visible }
*/
position?: (position: any) => void;

/** remove all data, events & dom elements created in the constructor */
destroy: () => void;

/** set the focus on the main input control (if any) */
focus: () => void;

/**
* Deserialize the value(s) saved to "state" and apply them to the data item
* this method may get called after the editor itself has been destroyed
* treat it as an equivalent of a Java/C# "static" method - no instance variables should be accessed
*/
applyValue: (item: any, state: any) => void;

/**
* Load the value(s) from the data item and update the UI
* this method will be called immediately after the editor is initialized
* it may also be called by the grid if if the row/cell being edited is updated via grid.updateRow/updateCell
*/
loadValue: (item: any) => void;

/**
* Return the value(s) being edited by the user in a serialized form
* can be an arbitrary object
* the only restriction is that it must be a simple object that can be passed around even
* when the editor itself has been destroyed
*/
serializeValue: () => any;

/** return true if the value(s) being edited by the user has/have been changed */
isValueChanged: () => boolean;

/**
* Validate user input and return the result along with the validation message, if any
* if the input is valid, return {valid:true,msg:null}
*/
validate: () => EditorValidatorOutput;
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ export class GridEventService {
return;
}
const column = grid.getColumns()[args.cell];
const gridOptions = grid.getOptions();

// only when using autoCommitEdit, we will make the cell active (in focus) when clicked
// setting the cell as active as a side effect and if autoCommitEdit is set to false then the Editors won't save correctly
if (gridOptions && gridOptions.enableCellNavigation && !gridOptions.editable || (gridOptions.editable && gridOptions.autoCommitEdit)) {
grid.setActiveCell(args.row, args.cell);
}

// if the column definition has a onCellClick property (a callback function), then run it
if (typeof column.onCellClick === 'function') {
Expand Down
5 changes: 5 additions & 0 deletions src/app/modules/angular-slickgrid/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,11 @@ export function mapOperatorByFieldType(fieldType: FieldType | string): OperatorT
return map;
}

/** Parse any input (bool, number, string) and return a boolean or False when not possible */
export function parseBoolean(input: boolean | number | string) {
return /(true|1)/i.test(input + '');
}

/**
* Parse a date passed as a string and return a Date object (if valid)
* @param inputDateString
Expand Down
3 changes: 1 addition & 2 deletions src/assets/lib/multiple-select/multiple-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -897,9 +897,8 @@
},

destroy: function () {
this.$el.show();
this.$el.before(this.$parent).show();
this.$parent.remove();
delete $.fn.multipleSelect;
},

checkAll: function () {
Expand Down