Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit a7d01c8

Browse files
authored
Merge pull request #170 from ghiscoding/bugfix/copy-paste-cells
fix(copy): copy+paste cells was not working, closes #164
2 parents e0b16a4 + 405e575 commit a7d01c8

File tree

11 files changed

+147
-25
lines changed

11 files changed

+147
-25
lines changed

.vscode/launch.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"type": "chrome",
66
"request": "launch",
7-
"name": "Launch Chrome against localhost",
7+
"name": "Chrome Debugger",
88
"url": "http://localhost:4300",
99
"webRoot": "${workspaceFolder}"
1010
},
@@ -34,7 +34,7 @@
3434
{
3535
"type": "node",
3636
"request": "launch",
37-
"name": "Jest Current File",
37+
"name": "Jest Current Spec File",
3838
"program": "${workspaceFolder}/node_modules/.bin/jest",
3939
"args": [
4040
"--runInBand",

src/app/examples/grid-editor.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,11 @@ export class GridEditorComponent implements OnInit {
515515
const tempDataset = [];
516516
for (let i = startingIndex; i < (startingIndex + itemCount); i++) {
517517
const randomYear = 2000 + Math.floor(Math.random() * 10);
518+
const randomFinishYear = (new Date().getFullYear() - 3) + Math.floor(Math.random() * 10); // use only years not lower than 3 years ago
518519
const randomMonth = Math.floor(Math.random() * 11);
519520
const randomDay = Math.floor((Math.random() * 29));
520521
const randomPercent = Math.round(Math.random() * 100);
522+
const randomFinish = new Date(randomFinishYear, (randomMonth + 1), randomDay);
521523

522524
tempDataset.push({
523525
id: i,
@@ -526,7 +528,7 @@ export class GridEditorComponent implements OnInit {
526528
percentComplete: randomPercent,
527529
percentCompleteNumber: randomPercent,
528530
start: new Date(randomYear, randomMonth, randomDay),
529-
finish: new Date(randomYear, (randomMonth + 1), randomDay),
531+
finish: randomFinish < new Date() ? '' : randomFinish, // make sure the random date is earlier than today
530532
effortDriven: (i % 5 === 0),
531533
prerequisites: (i % 2 === 0) && i !== 0 && i < 12 ? [i, i - 1] : [],
532534
countryOfOrigin: (i % 2) ? { code: 'CA', name: 'Canada' } : { code: 'US', name: 'United States' },

src/app/modules/angular-slickgrid/editors/autoCompleteEditor.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
FieldType
1010
} from './../models/index';
1111
import { Constants } from './../constants';
12+
import { findOrDefault } from '../services/utilities';
1213

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

171172
applyValue(item: any, state: any) {
173+
let newValue = state;
172174
const fieldName = this.columnDef && this.columnDef.field;
175+
176+
// if we have a collection defined, we will try to find the string within the collection and return it
177+
if (Array.isArray(this.collection) && this.collection.length > 0) {
178+
newValue = findOrDefault(this.collection, (collectionItem: any) => {
179+
if (collectionItem && collectionItem.hasOwnProperty(this.labelName)) {
180+
return collectionItem[this.labelName].toString() === state;
181+
}
182+
return collectionItem.toString() === state;
183+
});
184+
}
185+
173186
// when it's a complex object, then pull the object name only, e.g.: "user.firstName" => "user"
174187
const fieldNameFromComplexObject = fieldName.indexOf('.') ? fieldName.substring(0, fieldName.indexOf('.')) : '';
175-
item[fieldNameFromComplexObject || fieldName] = state;
188+
item[fieldNameFromComplexObject || fieldName] = newValue;
176189
}
177190

178191
isValueChanged() {

src/app/modules/angular-slickgrid/editors/dateEditor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ export class DateEditor implements Editor {
7474
this.$input = $(`<input type="text" data-defaultDate="${this.defaultDate}" class="${inputCssClasses.replace(/\./g, ' ')}" placeholder="${placeholder}" title="${title}" />`);
7575
this.$input.appendTo(this.args.container);
7676
this.flatInstance = (this.$input[0] && typeof this.$input[0].flatpickr === 'function') ? this.$input[0].flatpickr(pickerMergedOptions) : null;
77-
this.show();
7877

7978
// when we're using an alternate input to display data, we'll consider this input as the one to do the focus later on
8079
// else just use the top one
@@ -106,6 +105,9 @@ export class DateEditor implements Editor {
106105
destroy() {
107106
this.hide();
108107
this.$input.remove();
108+
if (this._$inputWithData && typeof this._$inputWithData.remove === 'function') {
109+
this._$inputWithData.remove();
110+
}
109111
}
110112

111113
show() {

src/app/modules/angular-slickgrid/editors/selectEditor.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Editor,
99
EditorValidator,
1010
EditorValidatorOutput,
11+
FieldType,
1112
GridOption,
1213
MultipleSelectOption,
1314
SelectOption,
@@ -272,18 +273,34 @@ export class SelectEditor implements Editor {
272273

273274
applyValue(item: any, state: any): void {
274275
const fieldName = this.columnDef && this.columnDef.field;
276+
const fieldType = this.columnDef && this.columnDef.type;
277+
let value = state;
278+
279+
// when the provided user defined the column field type as a possible number then try parsing the state value as that
280+
if (fieldType === FieldType.number || fieldType === FieldType.integer || fieldType === FieldType.boolean) {
281+
value = parseFloat(state);
282+
}
283+
284+
// when set as a multiple selection, we can assume that the 3rd party lib multiple-select will return a CSV string
285+
// we need to re-split that into an array to be the same as the original column
286+
if (this.isMultipleSelect && typeof state === 'string' && state.indexOf(',') >= 0) {
287+
value = state.split(',');
288+
}
289+
275290
// when it's a complex object, then pull the object name only, e.g.: "user.firstName" => "user"
276291
const fieldNameFromComplexObject = fieldName.indexOf('.') ? fieldName.substring(0, fieldName.indexOf('.')) : '';
277-
item[fieldNameFromComplexObject || fieldName] = state;
292+
item[fieldNameFromComplexObject || fieldName] = value;
278293
}
279294

280295
destroy() {
281296
this._destroying = true;
282-
if (this.$editorElm && this.$editorElm.multipleSelect) {
283-
this.$editorElm.multipleSelect('close');
297+
if (this.$editorElm && typeof this.$editorElm.multipleSelect === 'function') {
298+
this.$editorElm.multipleSelect('destroy');
284299
this.$editorElm.remove();
285300
const elementClassName = this.elementName.toString().replace('.', '\\.'); // make sure to escape any dot "." from CSS class to avoid console error
286301
$(`[name=${elementClassName}].ms-drop`).remove();
302+
} else if (this.$editorElm && typeof this.$editorElm.remove === 'function') {
303+
this.$editorElm.remove();
287304
}
288305
this._subscriptions = unsubscribeAllObservables(this._subscriptions);
289306
}
@@ -309,20 +326,25 @@ export class SelectEditor implements Editor {
309326
loadMultipleValues(currentValues: any[]) {
310327
// convert to string because that is how the DOM will return these values
311328
if (Array.isArray(currentValues)) {
312-
this.defaultValue = currentValues.map((i: any) => i.toString());
329+
// keep the default values in memory for references
330+
this.defaultValue = currentValues.map((i: any) => i);
331+
332+
// compare all the array values but as string type since multiple-select always return string
333+
const currentStringValues = currentValues.map((i: any) => i.toString());
313334
this.$editorElm.find('option').each((i: number, $e: any) => {
314-
$e.selected = (this.defaultValue.indexOf($e.value) !== -1);
335+
$e.selected = (currentStringValues.indexOf($e.value) !== -1);
315336
});
316337
}
317338
}
318339

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

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

@@ -513,7 +535,11 @@ export class SelectEditor implements Editor {
513535
const elementOptions = (this.columnDef.internalColumnEditor) ? this.columnDef.internalColumnEditor.elementOptions : {};
514536
this.editorElmOptions = { ...this.defaultOptions, ...elementOptions };
515537
this.$editorElm = this.$editorElm.multipleSelect(this.editorElmOptions);
516-
setTimeout(() => this.$editorElm.multipleSelect('open'));
538+
setTimeout(() => {
539+
if (this.$editorElm && typeof this.$editorElm.multipleSelect === 'function') {
540+
this.$editorElm.multipleSelect('open');
541+
}
542+
});
517543
}
518544
}
519545

src/app/modules/angular-slickgrid/formatters/checkmarkFormatter.spec.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,24 @@ describe('the Checkmark Formatter', () => {
1414
expect(result).toBe('');
1515
});
1616

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

23-
it('should return the Font Awesome Checkmark icon when input is filled with any string', () => {
24-
const value = 'anything';
25+
it('should return the Font Awesome Checkmark icon when the string "True" (case insensitive) is provided', () => {
26+
const value = 'True';
27+
const result1 = checkmarkFormatter(0, 0, value.toLowerCase(), {} as Column, {});
28+
const result2 = checkmarkFormatter(0, 0, value.toUpperCase(), {} as Column, {});
29+
expect(result1).toBe('<i class="fa fa-check checkmark-icon" aria-hidden="true"></i>');
30+
expect(result2).toBe('<i class="fa fa-check checkmark-icon" aria-hidden="true"></i>');
31+
});
32+
33+
it('should return the Font Awesome Checkmark icon when input is True', () => {
34+
const value = true;
2535
const result = checkmarkFormatter(0, 0, value, {} as Column, {});
2636
expect(result).toBe('<i class="fa fa-check checkmark-icon" aria-hidden="true"></i>');
2737
});
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Column } from './../models/column.interface';
22
import { Formatter } from './../models/formatter.interface';
3+
import { parseBoolean } from '../services/utilities';
34

4-
export const checkmarkFormatter: Formatter = (row: number, cell: number, value: any, columnDef: Column, dataContext: any) =>
5-
value ? `<i class="fa fa-check checkmark-icon" aria-hidden="true"></i>` : '';
5+
export const checkmarkFormatter: Formatter = (row: number, cell: number, value: any, columnDef: Column, dataContext: any) => {
6+
return parseBoolean(value) ? `<i class="fa fa-check checkmark-icon" aria-hidden="true"></i>` : '';
7+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,73 @@
11
import { EditorValidatorOutput } from './editorValidatorOutput.interface';
22

3+
/***
4+
* SlickGrid Editor interface, more info can be found on the SlickGrid repo
5+
* https://github.com/6pac/SlickGrid/wiki/Writing-custom-cell-editors
6+
*/
37
export interface Editor {
8+
/** Initialize the Editor */
49
init: () => void;
10+
11+
/** Saves the Editor value */
512
save?: () => void;
13+
14+
/** Cancels the Editor */
615
cancel?: () => void;
16+
17+
/**
18+
* if implemented, this will be called if the cell being edited is scrolled out of the view
19+
* implement this is your UI is not appended to the cell itself or if you open any secondary
20+
* selector controls (like a calendar for a datepicker input)
21+
*/
722
hide?: () => void;
23+
24+
/** pretty much the opposite of hide */
825
show?: () => void;
26+
27+
/**
28+
* if implemented, this will be called by the grid if any of the cell containers are scrolled
29+
* and the absolute position of the edited cell is changed
30+
* if your UI is constructed as a child of document BODY, implement this to update the
31+
* position of the elements as the position of the cell changes
32+
*
33+
* the cellBox: { top, left, bottom, right, width, height, visible }
34+
*/
935
position?: (position: any) => void;
36+
37+
/** remove all data, events & dom elements created in the constructor */
1038
destroy: () => void;
39+
40+
/** set the focus on the main input control (if any) */
1141
focus: () => void;
42+
43+
/**
44+
* Deserialize the value(s) saved to "state" and apply them to the data item
45+
* this method may get called after the editor itself has been destroyed
46+
* treat it as an equivalent of a Java/C# "static" method - no instance variables should be accessed
47+
*/
1248
applyValue: (item: any, state: any) => void;
49+
50+
/**
51+
* Load the value(s) from the data item and update the UI
52+
* this method will be called immediately after the editor is initialized
53+
* it may also be called by the grid if if the row/cell being edited is updated via grid.updateRow/updateCell
54+
*/
1355
loadValue: (item: any) => void;
56+
57+
/**
58+
* Return the value(s) being edited by the user in a serialized form
59+
* can be an arbitrary object
60+
* the only restriction is that it must be a simple object that can be passed around even
61+
* when the editor itself has been destroyed
62+
*/
1463
serializeValue: () => any;
64+
65+
/** return true if the value(s) being edited by the user has/have been changed */
1566
isValueChanged: () => boolean;
67+
68+
/**
69+
* Validate user input and return the result along with the validation message, if any
70+
* if the input is valid, return {valid:true,msg:null}
71+
*/
1672
validate: () => EditorValidatorOutput;
1773
}

src/app/modules/angular-slickgrid/services/gridEvent.service.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ export class GridEventService {
4040
return;
4141
}
4242
const column = grid.getColumns()[args.cell];
43+
const gridOptions = grid.getOptions();
44+
45+
// only when using autoCommitEdit, we will make the cell active (in focus) when clicked
46+
// setting the cell as active as a side effect and if autoCommitEdit is set to false then the Editors won't save correctly
47+
if (gridOptions && gridOptions.enableCellNavigation && !gridOptions.editable || (gridOptions.editable && gridOptions.autoCommitEdit)) {
48+
grid.setActiveCell(args.row, args.cell);
49+
}
4350

4451
// if the column definition has a onCellClick property (a callback function), then run it
4552
if (typeof column.onCellClick === 'function') {

src/app/modules/angular-slickgrid/services/utilities.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,11 @@ export function mapOperatorByFieldType(fieldType: FieldType | string): OperatorT
403403
return map;
404404
}
405405

406+
/** Parse any input (bool, number, string) and return a boolean or False when not possible */
407+
export function parseBoolean(input: boolean | number | string) {
408+
return /(true|1)/i.test(input + '');
409+
}
410+
406411
/**
407412
* Parse a date passed as a string and return a Date object (if valid)
408413
* @param inputDateString

0 commit comments

Comments
 (0)