-
-
Notifications
You must be signed in to change notification settings - Fork 120
Editors
- Inline Editors
- How to prevent Editor from going to the next bottom cell
- onClick Editor (icon click)
- Event through Slick Grid object
- AutoComplete Editor
- Date Editor
- Select Editors (single/multiple)
- Validators
- Disabling specific cell Edit
- Editors on Mobile Phone
Angular-Slickgrid
ships with a few default inline editors (checkbox, dateEditor, float, integer, text, longText, slider). You can see the full list here.
Note: For the Float Editor, you can provide decimal places with your column definition, defaults to 0 decimal places (e.g. editor: { params: { decimalPlaces: 2 }}
)
Editors won't work without these 2 flags enableCellNavigation: true
and editable: true
enabled in your Grid Options, so make sure to always to always defined them. Also note that you can toggle the grid to read only (not editable) via the editable
grid option flag.
Simply call the editor
in your column definition with the Editors
you want, as for example (editor: { model: Editors.text }
). Here is an example with a full column definition:
this.columnDefinitions = [
{ id: 'title', name: 'Title', field: 'title', type: FieldType.string, editor: { model: Editors.longText } },
{ id: 'duration', name: 'Duration (days)', field: 'duration', type: FieldType.number, editor: { model: Editors.text } },
{ id: 'complete', name: '% Complete', field: 'percentComplete', type: FieldType.number, editor: { model: Editors.integer } },
{ id: 'start', name: 'Start', field: 'start', type: FieldType.date, editor: { model: Editors.date } },
{
id: 'finish', name: 'Finish', field: 'finish', type: FieldType.date,
editor: {
model: Editors.date,
// you can also add an optional placeholder
placeholder: 'choose a date'
}
},
{
id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', formatter: Formatters.checkmark,
type: FieldType.number, editor: { model: Editors.checkbox }
}
];
this.gridOptions {
enableCellNavigation: true, // <<-- VERY IMPORTANT, it won't work without this flag enabled
editable: true,
};
This probably comes often, so here's all the setting you would need for displaying & editing a dollar currency value with 2 decimal places.
this.columnDefinitions = [
{
id: 'cost', name: 'Cost', field: 'cost',
type: FieldType.float,
formatter: Formatters.dollar, // the Dollar Formatter will default to 2 decimals unless you provide a minDecimal/maxDecimal
// params: { minDecimal: 2, maxDecimal: 4, }, // optionally provide different decimal places
// the float editor has its own settings, `decimal` that will be used only in the editor
// note: that it has nothing to do with the Dollar Formatter
editor: { model: Editors.float, decimal: 2 },
},
];
You could also define an outputType
and a saveOutputType
to an inline editor. There is only 1 built-in Editor with this functionality for now which is the dateEditor
. For example, on a date field, we can call this outputType: FieldType.dateIso
(by default it uses dateUtc
as the output):
this.columnDefinitions = [
{
id: 'start', name: 'Start', field: 'start',
type: FieldType.date,
editor: { model: Editors.date },
type: FieldType.date, // dataset cell input format
// outputType: FieldType.dateUs, // date picker format
saveOutputType: FieldType.dateUtc, // save output date format
}
];
So to make it more clear, the saveOutputType
is the format that will be sent to the onCellChange
event, then the outputType
is how the date will show up in the date picker (Flatpickr) and finally the type
is basically the input format (coming from your dataset). Note however that each property are cascading, if 1 property is missing it will go to the next one until 1 is found... for example, on the onCellChange
if you aren't defining saveOutputType
, it will try to use outputType
, if again none is provided it will try to use type
and finally if none is provided it will use FieldType.dateIso
as the default.
In order to use different locale, you will have to import whichever Flatpickr locale you need. The best place to do these imports is in your App Module so it's global and you do it only once.
import { AngularSlickgridModule } from 'angular-slickgrid';
// load necessary Flatpickr Locale(s), but make sure it's imported AFTER the SlickgridModule import
import 'flatpickr/dist/l10n/fr';
@NgModule({
declarations: [/*...*/],
imports: [
// ...
AngularSlickgridModule.forRoot({
// add any Global Grid Options/Config you might want
})
],
providers: [/*...*/],
bootstrap: [AppComponent]
})
export class AppModule { }
The AutoComplete Editor has the same configuration (except for the model: Editors.autoComplete
) as the AutoComplete Filter, so you can refer to the AutoComplete Filter Wiki for more info on how to use it.
The library ships with two select editors: singleSelectEditor and the multipleSelectEditor. Both support the multiple-select library, but fallback to the bootstrap form-control style if you decide to exclude this library from your build. These editors will work with a list of foreign key values (custom structure not supported) and can be displayed properly with the collectionFormatter. example 3 has all the details for you to get started with these editors.
Here's an example with a collection
, collectionFilterBy
and collectionSortBy
this.columnDefinitions = [
{
id: 'prerequisites', name: 'Prerequisites', field: 'prerequisites',
type: FieldType.string,
editor: {
model: Editors.multipleSelect,
collection: Array.from(Array(12).keys()).map(k => ({ value: `Task ${k}`, label: `Task ${k}` })),
collectionSortBy: {
property: 'label',
sortDesc: true
},
collectionFilterBy: {
property: 'label',
value: 'Task 2'
}
}
}
];
All the available options that can be provided as editorOptions
to your column definitions can be found under this multipleSelectOption interface and you should cast your editorOptions
to that interface to make sure that you use only valid options of the multiple-select.js
library.
editor: {
model: Editors.SingleSelect,
editorOptions: {
maxHeight: 400
} as MultipleSelectOption
}
You can also load the collection asynchronously, but for that you will have to use the collectionAsync
property, which expect an Observable (HttpClient
) to be passed.
this.columnDefinitions = [
{
id: 'prerequisites', name: 'Prerequisites', field: 'prerequisites',
filterable: true,
editor: {
collectionAsync: this.http.get<{ value: string; label: string; }[]>('api/data/pre-requisites'),
model: Editors.multipleSelect,
}
}
];
If you want to modify the collection
afterward (can be collection
or even collectionAsync
), you can simply push or modify the collection
directly. You just need to find in your Column Definition and Angular-Slickgrid will take care of the rest for you.
For example
addItem() {
const lastRowIndex = this.dataset.length;
const newRows = this.mockData(1, lastRowIndex);
// wrap into a timer to simulate a backend async call
setTimeout(() => {
const requisiteColumnDef = this.columnDefinitions.find((column: Column) => column.id === 'prerequisites');
if (requisiteColumnDef) {
const collection = requisiteColumnDef.editor.collection;
if (Array.isArray(collection )) {
// add the new row to the grid
this.angularGrid.gridService.addItemToDatagrid(newRows[0]);
// then refresh the Filter "collection", we have 2 ways of doing it
// 1- Push to the Filter "collection"
collection.push({ value: lastRowIndex, label: lastRowIndex, prefix: 'Task' });
// or 2- replace the entire "collection"
// requisiteColumnDef.editor.collection = [...collection, ...[{ value: lastRowIndex, label: lastRowIndex }]];
}
}
}, 250);
}
You can use labelPrefix
and/or labelSuffix
which will concatenate the multiple properties together (labelPrefix
+ label
+ labelSuffix
) which will used by each Select Filter option label. You can also use the property separatorBetweenTextLabels
to define a separator between prefix, label & suffix.
Note
If enableTranslateLabel
flag is set to True
, it will also try to translate the Prefix / Suffix / OptionLabel texts.
For example, say you have this collection
const currencies = [
{ symbol: '$', currency: 'USD', country: 'USA' },
{ symbol: '$', currency: 'CAD', country: 'Canada' }
];
You can display all of these properties inside your dropdown labels, say you want to show (symbol with abbreviation and country name). Now you can.
So you can create the multipleSelect
Filter with a customStructure
by using the symbol as prefix, and country as suffix. That would make up something like this:
- $ USD USA
- $ CAD Canada
with a customStructure
defined as
editor: {
collection: this.currencies,
customStructure: {
value: 'currency',
label: 'currency',
labelPrefix: 'symbol',
labelSuffix: 'country',
},
collectionOptions: {
separatorBetweenTextLabels: ' ' // add white space between each text
},
model: Editors.multipleSelect
}
By default HTML is not rendered and the label
will simply show HTML as text. But in some cases you might want to render it, you can do so by enabling the enableRenderHtml
flag.
NOTE: this is currently only used by the Editors that have a collection
which are the MultipleSelect
& SingleSelect
Editors.
this.columnDefinitions = [
{
id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven',
formatter: Formatters.checkmark,
type: FieldType.boolean,
editor: {
// display checkmark icon when True
enableRenderHtml: true,
collection: [{ value: '', label: '' }, { value: true, label: 'True', labelPrefix: `<i class="fa fa-check"></i> ` }, { value: false, label: 'False' }],
model: Editors.singleSelect
}
}
];
If you find that the HTML that you passed is being sanitized and you wish to change it, then you can change the default sanitizeHtmlOptions
property defined in the Global Grid Options, for more info on how to change these global options, see the Wiki - Global Grid Options. The current defaults are:
sanitizeHtmlOptions: {
allowedTags: [ 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'iframe', 'span' ],
allowedAttributes: { '*': ['*'] }
},
You can use any options from Multiple-Select.js and add them to your filterOptions
property. However please note that this is a customized version of the original (all original lib options are available so you can still consult the original site for all options).
Couple of small options were added to suit Angular-SlickGrid needs, which is why it points to angular-slickgrid/lib
folder (which is our customized version of the original). This lib is required if you plan to use multipleSelect
or singleSelect
Filters. What was customized to (compare to the original) is the following:
-
okButton
option was added to add an OK button for simpler closing of the dropdown after selecting multiple options.-
okButtonText
was also added for locale (i18n)
-
-
offsetLeft
option was added to make it possible to offset the dropdown. By default it is set to 0 and is aligned to the left of the select element. This option is particularly helpful when used as the last right column, not to fall off the screen. -
autoDropWidth
option was added to automatically resize the dropdown with the same width as the select filter element. -
autoAdjustDropHeight
(defaults to true), when set will automatically adjust the drop (up or down) height -
autoAdjustDropPosition
(defaults to true), when set will automatically calculate the area with the most available space and use best possible choise for the drop to show (up or down) -
autoAdjustDropWidthByTextSize
(defaults to true), when set will automatically adjust the drop (up or down) width by the text size (it will use largest text width) - to extend the previous 3 autoAdjustX flags, the following options can be helpful
-
minWidth
(defaults to null, to use whenautoAdjustDropWidthByTextSize
is enabled) -
maxWidth
(defaults to 500, to use whenautoAdjustDropWidthByTextSize
is enabled) -
adjustHeightPadding
(defaults to 10, to use whenautoAdjustDropHeight
is enabled), when usingautoAdjustDropHeight
we might want to add a bottom (or top) padding instead of taking the entire available space -
maxHeight
(defaults to 275, to use whenautoAdjustDropHeight
is enabled)
-
this.columnDefinitions = [
{
id: 'isActive', name: 'Is Active', field: 'isActive',
filterable: true,
editor: {
collection: [{ value: '', label: '' }, { value: true, label: 'true' }, { value: false, label: 'false' }],
model: Editors.singleSelect,
elementOptions: {
// add any multiple-select.js options (from original or custom version)
autoAdjustDropPosition: false, // by default set to True, but you can disable it
position: 'top'
}
}
}
];
What is ideal is to bind to a SlickGrid Event, for that you can take a look at this Wiki - On Events
You could also, perform an action after the item changed event with onCellChange
. However, this is not the recommended way, since it would require to add a onCellChange
on every every single column definition (while you can do with 1 onEvent).
To create a Custom Editor, you need to create a class
that will extend the Editors
interface and then use it in your grid with editor: { model: myCustomEditor }
and that should be it.
NOTE Custom Editor accepts only regular HTML and/or jQuery
3rd party lib. It will not work with any Angular lib or template. I have no time to invest in this and not much use either since all embedded Editors are enough for my usage. However, if you wish to create a PR to support Angular lib/template, I certainly welcome PR (Pull Request).
Once you are done with the class, just reference it's class name as the editor
, for example:
export class IntegerEditor implements Editor {
constructor(private args: any) {
this.init();
}
init(): void {}
destroy() {}
focus() {}
loadValue(item: any) {}
serializeValue() {}
applyValue(item: any, state: any) {}
isValueChanged() {}
validate() {}
}
For Custom Editor class example, take a look at custom-inputEditor.ts
this.columnDefinitions = [
{
id: 'title2', name: 'Title, Custom Editor', field: 'title',
type: FieldType.string,
editor: {
model: CustomInputEditor // reference your custom editor class
},
}
];
You can see them in Example 22 which have both Custom Editors & Filters which uses Angular Components. The 2nd column "Assignee" is the column that uses both (it uses ng-select
3rd party lib wrapped in an Angular Components here) and you need to create a Custom Editor like here and use that Custom Editor in your column definition like here.
Personally I don't find this very straightforward and I don't recommend using Angular Components for Editors/Filters as it adds a lot of boilerplate (compare to 1 step with a jQuery Custom Editor) but if you really wish to go that route, it's now possible following the steps shown below.
The steps to use an Angular Component as a Custom Editor are the following:
- Create a Custom Editor that will handle the creation or compilation of the Angular Component into a SlickGrid Editors. For that you can take a look at this Custom Editor
- Define your Angular Component, for example take a look at this simple ng-select Editor
- Use the Custom Editor inside your Column Definitions, for that you can see previous paragraph here
The default behavior or SlickGrid is to go to the next cell at the bottom of the current cell that you are editing. You can change and remove this behavior by enabling autoCommitEdit
which will save current editor and remain in the same cell
this.gridOptions = {
autoCommitEdit: true,
editable: true,
}
Instead of an inline editor, you might want to simply click on an edit icon that could call a modal window, or a redirect URL, or whatever you wish to do. For that you can use the inline onCellClick
event and define a callback function for the action (you could also create your Custom Formatter).
- The
Formatters.editIcon
will give you a pen icon, while aFormatters.deleteIcon
is an "x" icon
this.columnDefinitions = [
{
id: 'edit', field: 'id',
formatter: Formatters.editIcon,
maxWidth: 30,
onCellClick: (args: OnEventArgs) => {
console.log(args);
}
},
// ...
];
The args
returned to the onCellClick
callback is of type OnEventArgs
which is the following:
export interface OnEventArgs {
row: number;
cell: number;
columnDef: Column;
dataContext: any;
dataView: any;
grid: any;
gridDefinition: GridOption;
}
You can also use the Slick Grid events as shown below
<angular-slickgrid gridId="grid2"
(onAngularGridCreated)="angularGridReady($event.detail)"
(onCellChange)="onCellChanged($event.detail.eventData, $event.detail.args)"
(onClick)="onCellClicked($event.detail.eventData, $event.detail.args)"
[columnDefinitions]="columnDefinitions" [gridOptions]="gridOptions" [dataset]="dataset">
</angular-slickgrid>
onCellChanged(e, args) {
this.updatedObject = args.item;
this.angularGrid.resizerService.resizeGrid(10);
}
onCellClicked(e, args) {
const metadata = this.angularGrid.gridService.getColumnFromEventArguments(args);
if (metadata.columnDef.id === 'edit') {
this.alertWarning = `open a modal window to edit: ${metadata.dataContext.title}`;
// highlight the row, to customize the color, you can change the SASS variable $row-highlight-background-color
this.angularGrid.gridService.highlightRow(args.row, 1500);
// you could also select the row, when using "enableCellNavigation: true", it automatically selects the row
// this.angularGrid.gridService.setSelectedRow(args.row);
} else if (metadata.columnDef.id === 'delete') {
if (confirm('Are you sure?')) {
this.angularGrid.gridService.deleteDataGridItemById(metadata.dataContext.id);
}
}
}
Each Editor needs to implement the validate()
method which will be executed and validated before calling the save()
method. Most Editor will simply validate that the value passed is correctly formed. The Float Editor is one of the more complex one and will first check if the number is a valid float then also check if minValue
or maxValue
was passed and if so validate against them. If any errors is found it will return an object of type EditorValidatorOutput
(see the signature on top).
If you want more complex validation then you can implement your own Custom Validator as long as it implements the following signature.
export type EditorValidator = (value: any, args?: EditorArgs) => EditorValidatorOutput;
So the value
can be anything but the args
is interesting since it provides multiple properties that you can hook into, which are the following
export interface EditorArgs {
column: Column;
container: any;
grid: any;
gridPosition: ElementPosition;
item: any;
position: ElementPosition;
cancelChanges?: () => void;
commitChanges?: () => void;
}
And finally the Validator Output has the following signature
export interface EditorValidatorOutput {
valid: boolean;
msg?: string | null;
}
So if we take all of these informations and we want to create our own Custom Editor to validate a Title field, we could create something like this:
const myCustomTitleValidator: EditorValidator = (value: any, args: EditorArgs) => {
// you can get the Editor Args which can be helpful, e.g. we can get the Translate Service from it
const grid = args && args.grid;
const columnDef = args.column;
const dataContext = args.item;
const gridOptions = (grid && grid.getOptions) ? grid.getOptions() : {};
const i18n = gridOptions.i18n;
if (value == null || value === undefined || !value.length) {
return { valid: false, msg: 'This is a required field' };
} else if (!/^Task\s\d+$/.test(value)) {
return { valid: false, msg: 'Your title is invalid, it must start with "Task" followed by a number' };
// OR use the Translate Service with your custom message
// return { valid: false, msg: i18n.tr('YOUR_ERROR', { x: value }) };
} else {
return { valid: true, msg: '' };
}
};
and use it in our Columns Definition like this:
this.columnDefinition = [
{
id: 'title', name: 'Title', field: 'title',
editor: {
model: Editors.longText,
validator: myCustomTitleValidator, // use our custom validator
},
onCellChange: (e: Event, args: OnEventArgs) => {
// do something
console.log(args.dataContext.title);
}
}
];
This can be answered by searching on Stack Overflow Stack Overflow and this is the best answer found.
Based on that, the only difference in Angular-Slickgrid is that all SlickGrid event needs the sg
prefix to differentiate SlickGrid Events (sg
prefix) versus Angular-Slickgrid Events (asg
prefix). More info can be found in this Wiki - Grid & DataView Events.
With that in mind and the code from the SO answer, we end up with the following code.
<angular-slickgrid gridId="grid1"
[columnDefinitions]="columnDefinitions"
[gridOptions]="gridOptions"
(onBeforeEditCell)="verifyCellIsEditableBeforeEditing($event.detail.eventData, $event.detail.args)"
>
</angular-slickgrid>
verifyCellIsEditableBeforeEditing(e, args): boolean {
// you can call `event.preventDefault()` to make cell not editable
// args contains the dataContext and other Slickgrid arguments
}
If your grid uses the autoResize
and you use Editors in your grid on a mobile phone, Android for example, you might have undesired behaviors. It might call a grid resize (and lose input focus) since the touch keyboard appears. This in term, is a bad user experience to your user, but there is a way to avoid this, you could use the pauseResizer
<div id="grid1">
<angular-slickgrid gridId="grid1"
[columnDefinitions]="columnDefinitions"
[gridOptions]="gridOptions"
[dataset]="dataset"
(onBeforeEditCell)="onBeforeEditCell($event)"
(onBeforeCellEditorDestroy)="onAfterEditCell($event)"
(onAngularGridCreated)="angularGridReady($event.detail)">
</angular-slickgrid>
</div>
angularGridReady(angularGrid: AngularGridInstance) {
this.angularGrid = angularGrid;
}
onAfterEditCell($event) {
// resume autoResize feature, and after leaving cell editing mode
// force a resize to make sure the grid fits the current dimensions
this.angularGrid.resizerService.pauseResizer(false);
this.angularGrid.resizerService.resizeGrid();
}
onBeforeEditCell($event) {
this.angularGrid.resizerService.pauseResizer(true);
}
Contents
- Angular-Slickgrid Wiki
- Installation
- Styling
- Interfaces/Models
- Testing Patterns
- Column Functionalities
- Global Grid Options
- Localization
- Events
- Grid Functionalities
- Auto-Resize / Resizer Service
- Resize by Cell Content
- Composite Editor
- Context Menu
- Custom Tooltip
- Add/Delete/Update or Highlight item
- Dynamically Change Row CSS Classes
- Excel Copy Buffer
- Export to Excel
- Export to File (CSV/Txt)
- Grid State & Presets
- Grouping & Aggregators
- Row Detail
- SlickGrid Controls
- SlickGrid Plugins
- Pinning (frozen) of Columns/Rows
- Tree Data Grid
- SlickGrid & DataView objects
- Addons (controls/plugins)
- Backend Services