-
-
Notifications
You must be signed in to change notification settings - Fork 29
Migration to 4.x
WIP (see Roadmap 4.0) 🚧
The aim of this release is to be standalone, improve best practices & move towards CSP compliance while also making the project leaner.
This new major release is dropping SlickGrid dependency entirely and is rather moving the SlickGrid core library directly into Slickgrid-Universal project to avoid being dependent on one another. The reason to move the core lib into the project is for simplicity but it is also allowing us to drop code that we never used in Slickgrid-Universal (i.e. SlickGrid had its own autosize
which is different than ours in ResizerService
, migrating SlickGrid to native JS also required a few DOM utils (when we dropped jQuery) and many of these methods were duplicated but now we can drop all these duplicate methods, so we end up eliminating a bunch of duplicated lines). The reason that we can now finally migrate the SlickGrid core files into the project is that I recently migrated the core SlickGrid to TypeScript and by doing so is allowing me to simply copy that code and merge it in here without too much work. It's also been tested in the wild for couple months already so I know it's stable. This new release also improves CSP (Content Security Policy) compliance.
- minimum requirements bump
- Node 18 is now required
- TypeScript builds are now targeting ES2021 (bumped from ES2018)
- more CSP (Content Security Policy) safe
- you can create custom Formatter with native HTML element (to be more CSP safe), HTML strings are still accepted
NOTE: if you come from an earlier version, please make sure to follow each migration in their respected order (see Wiki index)
The dist/commonjs
folder was renamed as dist/cjs
to make it shorter and also to follow the new convention used in the JS world.
- this should be transparent to most users
By migrating the SlickGrid core files into the project, we are now taking full advantage of TypeScript classes and we dropped the Slick
namespace and so you will not find it anymore on the window
object. The main usage of this for most users, is when you have an editable grid with "undo" functionality, you will need changes on the Slick.GlobalEditorLock
as shown below.
+ { SlickGlobalEditorLock } from '@slickgrid-universal/common';
- declare const Slick: SlickNamespace;
// ..
undo() {
const command = this.editCommandQueue.pop();
- if (command && Slick.GlobalEditorLock.cancelCurrentEdit()) {
+ if (command && SlickGlobalEditorLock.cancelCurrentEdit()) {
command.undo();
}
}
-
exportColumnWidth
was removed, just useexcelExportOptions
instead
-
sanitizeHtmlOptions
was renamed tosanitizerOptions
(can be useful to provide extra sanitizer options like DOMPurify) -
registerExternalResources
was renamed toexternalResources
this.gridOptions = {
- registerExternalResources: [new ExcelExportService()]
+ externalResources: [new ExcelExportService()]
}
-
isWithCursor
was renamed touseCursor
-
items
list was renamed ascommandItems
to align with all other Menu extensions
Any Editor/Filter options (internal or 3rd party lib like Flatpickr, Autocompleter, ...) were migrated from params
to filterOptions
or editorOptions
. You can still use params
for other usage though.
this.columnDefinitions = {
{
id: 'duration', name: 'Duration', field: 'duration',
filterable: true,
filter: {
model: Filters.slider,
- params: { hideSliderNumber: true } as SliderOption
+ filterOptions: { hideSliderNumber: true } as SliderOption
},
editor: {
model: Editors.slider,
- params: { hideSliderNumber: true } as SliderOption
+ editorOptions: { hideSliderNumber: true } as SliderOption
},
}
With this release, we merged the SlickEvent
with our own PubSub Service (which uses a CustomEvent
), this makes it easier to maintain and requires less monkey patch code. The reason we use our own PubSub is because it makes it easier to communicate with Component based frameworks (used in Angular-Slickgrid, Slickgrid-React, ...). Note that this only applies to SlickGrid/DataView events since all other extensions (plugins/controls) were already using the PubSub through grid options or extension configurations. Note that you can still use SlickGrid .subscribe
on SlickEvent but please remember to .unsubscribe
each of them if you use that approach (which is not needed with a PubSub since you only have to do that once at a global level).
Please also note that the native event is also included under the property nativeEvent
(it's also included under eventData
, which was the old prop name, but nativeEvent
is preferred and more representative). Also note again that our PubSub Service is a JS CustomEvent
based, so all the data is only available through the .eventDetail
property of a CustomEvent.
const containerElm = document.QuerySelector('#myGrid') as HTMLDivElement;
this.sgb = new SlickVanillaGridBundle(containerElm, columns, gridOptions, data);
containerElm.addEventListener('onBeforeEditCell', (e) => {
// e is a CustomEvent the data can be found under the `detail` property
- const nativeEvent = e.detail?.eventData; // deprecated but not yet removed, prefer `nativeEvent` when possible
+ const nativeEvent = e.detail?.nativeEvent; // preferred property to use
const args = e?.detail?.args;
const { column, item, grid } = args;
});
What was the monkey patch anyway? The previous patch required to loop through all SlickGrid/DataView events (SlickEvent
), we were then subscribing to each of them and whenever they were dispatching an event (through SlickEvent notify), we were then using our own PubSub Service to publish the event (similar to a middleware would do). As you can see, that was quite a monkey patch, the new approach simply adds an optional PubSub Service reference to the SlickEvent, allowing us to publish right away without the monkey patch or middleware.
This should be transparent and irrelevant to most users...
6pac/SlickGrid (external core lib) had implemented its own and different column AutoSize which was implemented after Slickgrid-Universal (here) with its own implementation and so we never actually used the core implementation, or at least it was hidden and not known for most users. Since we had 2 similar implementations, I just decided to remove core implementation and keep Slickgrid-Universal own implementation which exists in the Resizer Service, for more info and live demo, see Slickgrid-Universal Example 14.
This should be transparent and irrelevant to most users...
-
BindingEventService
moved from@slickgrid-universal/common
to@slickgrid-universal/binding
, you will also need to check your dependencies and make you have it installed.
// package.json
{
"dependencies": {
+ "@slickgrid-universal/binding": "4.0.0",
"@slickgrid-universal/common": "4.0.0",
}
}
// ...
// Component
- import { BindingEventService } from '@slickgrid-universal/common';
+ import { BindingEventService } from '@slickgrid-universal/binding';
This should be transparent and irrelevant to most users...
- remove leftover jQuery slide/fade animations in all toggle methods, use CSS animation instead. We are removing the last
animate
boolean argument on all methods-
setTopPanelVisibility
,setHeaderRowVisibility
,setColumnHeaderVisibility
,setFooterRowVisibility
,setPreHeaderPanelVisibility
-
This should be transparent and irrelevant to most users...
Some of the DomUtils Service function were renamed, if you use any of them then rename them (the first 2 are used in multiple places)
-
getHtmlElementOffset
renamed togetOffset
-
sanitizeHtmlToText
reimplemented asstripTags
-
destroyObjectDomElementProps
renamed todestroyAllElementProps
-
getElementOffsetRelativeToParent
renamed togetOffsetRelativeToParent
-
findFirstElementAttribute
renamed tofindFirstAttribute
-
htmlEncodedStringWithPadding
renamed tohtmlEncodeWithPadding
The previous Formatters implementation were all returning HTML strings, however that is not CSP safe and we have to do some transformations in order to be CSP safe. The transformation is to take the HTML string, sanitize it (we use DOMPurify internally, unless you provided a custom sanitizer
) and with DOMPurify that will return TrustedHTML
which is now CSP and we can then take this TrustedHTML
and apply it the cell via innerHTML
but that is a lot of steps. However, if on the other hand our we were to return native HTML from our Formatter then there would be no transformation to do and we could just use .appendChild()
to the cell div and that is what were are doing now, the bonus is that it's much more efficient (no transformation) and is also 100% CSP safe since it's already native.. hence why I decided to rewrite all Formatters to native HTML and for the most part that shouldn't affect you unless you chain Formatters (e.g. Formatters.multiple
) or if you were concatenating string to a buil-in Formatter result and that won't work anymore since it returns native HTML.
Note prior to this release, Slickgrid-Universal only supported returning HTML string (or via an object of type
FormatterResultObject
). With this release, we still support returning HTML string but we also support returning native HTML (or a newFormatterResultWithHtml
)
Since all Formatters were rewritten as HTML, you might get unexpected behavior in your UI, you will have to inspect your UI and make changes accordingly. For example, I had to adjust Example 12 customEditableInputFormatter
because it was expecting all Formatters to return an HTML string and I was concatenating them to an HTML string but that was now outputting [object HTMLElement]
, so I had to detect if Formatter output is a native then do something or else do something else... Below is the adjustment I had to do to fix my own demo (your use case may vary)
Note some Formatters return
HTMLElement
orDocumentFragment
, you can add a condition check withinstanceof HTMLElement
orinstanceof DocumentFragment
, however please also note that aDocumentFragment
does not haveinnerHTML
/outerHTML
(you can write a function to get it though, see this SO)
const customEditableInputFormatter: Formatter = (_row, _cell, value, columnDef, dataContext, grid) => {
const isEditableLine = checkItemIsEditable(dataContext, columnDef, grid);
value = (value === null || value === undefined) ? '' : value;
- return isEditableLine ? `<div class="editing-field">${value}</div>` : value;
+ const divElm = document.createElement('div');
+ divElm.className = 'editing-field';
+ if (value instanceof HTMLElement) {
+ divElm.appendChild(value);
+ } else {
+ divElm.textContent = value;
+ }
};
init() {
// ...
this.gridOptions = {
editable: true,
autoAddCustomEditorFormatter: customEditableInputFormatter,
}
}
here's also another use case from Example 18
const priceFormatter: Formatter = (_cell, _row, value, _col, dataContext) => {
const direction = dataContext.priceChange >= 0 ? 'up' : 'down';
- return `<span class="mdi mdi-arrow-${direction} color-${direction === 'up' ? 'success' : 'danger'}"></span> ${value}`;
+ const fragment = document.createDocumentFragment();
+ const spanElm = document.createElement('span');
+ spanElm.className = `mdi mdi-arrow-${direction} color-${direction === 'up' ? 'success' : 'danger'}`;
+ fragment.appendChild(spanElm);
+ if (value instanceof HTMLElement) {
+ fragment.appendChild(value);
+ }
+ return fragment;
};
init() {
this.columnDefinitions = [
{
id: 'priceChange', name: 'Change', field: 'priceChange', filterable: true, sortable: true, minWidth: 80, width: 80,
filter: { model: Filters.compoundInputNumber }, type: FieldType.number,
formatter: Formatters.multiple,
params: {
formatters: [Formatters.dollarColored, priceFormatter],
maxDecimal: 2,
}
},
},
];
Note you might be wondering, why do I use a
DocumentFragment
and not justHTMLElement
? The reason is simply because in some cases you just want to return multiple elements without having to wrap them in a div container and you guessed it, aDocumentFragment
allows you to do just that.
I decided to remove a bunch of Formatters (like Formatters.bold
, Formatters.uppercase
, ...) because they could and should be using the column cssClass
option. Basically, I did not use the best practice when creating soo many Formatters and did not realized that we could simply use cssClass
in a much more efficient way, so I'm correcting this inadvertence in this new release. With that in mind, I decided to do a big cleanup in the list of Formatters to make the project a little more lightweight with less code to support and replace some of them with some more generic alternatives.
The benefits of using cssClass
are non negligible since it will slightly decrease the project size but most important it is a lot more performant, because applying CSS is a lot quicker in comparison to parse and apply a formatter for each cells. See below for the list of deleted (or replaced) Formatters and their equivalent Column cssClass
property to use.
Note the CSS class name to use might be different depending on which framework you use in your project, i.e. Bootstrap/Bulma/Material
this.columnDefinitions = [
{
id: 'firstName', name: 'First Name', field: 'firstName',
- formatter: Formatters.bold
+ cssClass: 'text-bold'
},
{
id: 'lastName', name: 'Last Name', field: 'lastName',
- formatter: Formatters.multiple, params: { formatters: [Formatters.uppercase, Formatters.bold] },
+ cssClass: 'text-uppercase text-bold'
},
{
id: 'deleteIcon', name: '', field: '',
- formatter: Formatters.deleteIcon,
// NOTE: we previously accepted "icon" and "formatterIcon" in the past but these names are now removed
+ formatter: Formatters.icon, params: { iconCssClass: 'fa fa-trash pointer' }
},
];
Formatter removed |
cssClass equivalent |
alternative |
---|---|---|
Formatters.bold |
cssClass: 'text-bold' |
|
Formatters.center |
cssClass: 'text-center' |
|
Formatters.italic |
cssClass: 'text-italic' |
|
Formatters.alignRight |
cssClass: 'text-right' |
|
Formatters.lowercase |
cssClass: 'text-lowercase' |
|
Formatters.uppercase |
cssClass: 'text-uppercase' |
|
Formatters.fakeHyperlink |
cssClass: 'text-underline cursor' |
cssClass: 'fake-hyperlink' |
Formatters.checkbox |
n/a ... removed | use the Formatters.iconBoolean
|
Formatters.deleteIcon |
n/a ... removed | use the Formatters.icon (see above) |
Formatters.editIcon |
n/a ... removed | use the Formatters.icon (see above) |
Formatters.infoIcon |
n/a ... removed | use the Formatters.icon (see above) |
Formatters.yesNo |
n/a ... rarely used, so it was removed | create a custom Formatter |
- Slickgrid-Universal Wikis
- Installation
- Styling
- Interfaces/Models
- Column Functionalities
- Events
- Grid Functionalities
- Auto-Resize / Resizer Service
- Resize by Cell Content
- Column Picker
- Composite Editor Modal
- Custom Tooltip
- Context Menu
- Custom Footer
- Export to Excel
- Export to File (csv/txt)
- Grid Menu
- Grid State & Presets
- Grouping & Aggregators
- Header Menu & Header Buttons
- Pinning (frozen) of Columns/Rows
- Row Selection
- Tree Data Grid
- SlickGrid & DataView objects
- Backend Services