@cdmx/wappler_ag_grid 2.1.5 → 2.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dmx-ag-grid.js +15 -2
- package/package.json +6 -6
- package/tests/04-selection.html +62 -0
- package/tests/data/sample-data.js +12 -1
- package/tests/run-all-puppeteer.cjs +1 -1
- package/tests/run-all.cjs +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
**Major Update:** This release upgrades AG Grid to v35.1.0, bringing continued performance improvements and new features.
|
|
4
4
|
|
|
5
|
+
## 🔒 What's New in v2.1.7
|
|
6
|
+
|
|
7
|
+
**Content Security Policy compliance.** The status toggle column (rendered when a grid has a
|
|
8
|
+
`status` field and `row_status_event` is enabled) previously emitted an inline
|
|
9
|
+
`onclick="handleStatusToggle(...)"` attribute per row. Inline event handlers are blocked by any
|
|
10
|
+
CSP without `'unsafe-inline'` in `script-src`, and they cannot be allowed by hash because the
|
|
11
|
+
attribute interpolates per-row data — so under a locked-down policy the toggle rendered normally
|
|
12
|
+
but silently stopped firing `row_status_enabled` / `row_status_disabled`.
|
|
13
|
+
|
|
14
|
+
The renderer now emits `data-status-col` / `data-status-value` / `data-status-id` attributes, and
|
|
15
|
+
a single delegated `click` listener on the grid container dispatches the events. Behaviour is
|
|
16
|
+
unchanged; no configuration or view changes are required.
|
|
17
|
+
|
|
18
|
+
- **No inline event handlers** are emitted anywhere in the grid, so `script-src` needs neither
|
|
19
|
+
`'unsafe-inline'` nor per-row hashes.
|
|
20
|
+
- The three runtime `<style>` blocks (cell centering, sticky horizontal scrollbar, pagination
|
|
21
|
+
layout) are static and unchanged, and can be allowed by hash in `style-src`:
|
|
22
|
+
```
|
|
23
|
+
'sha256-rW177u2hd7RHx4qSlJmD1nI6607/SZ01lrewgfHuj/M=' cell centering
|
|
24
|
+
'sha256-Et0Yl8993s0IRJOeeDT++DYDRSXLRDRvbgCj9zG2J9A=' sticky horizontal scrollbar
|
|
25
|
+
'sha256-6oQ3cIavZD3cp6igHrAN3HjrosWY41/7+bYLY+je9F0=' pagination panel
|
|
26
|
+
```
|
|
27
|
+
These must be recomputed if that CSS text is ever edited.
|
|
28
|
+
- Toggle values are now attribute-escaped rather than interpolated into a JavaScript string
|
|
29
|
+
literal, closing a quote-breakout in the previous renderer.
|
|
30
|
+
|
|
31
|
+
Regression coverage lives in `tests/04-selection.html` (`window.__statusCheck`), which asserts the
|
|
32
|
+
grid emits zero `[onclick]` attributes and that the toggle still dispatches its events.
|
|
33
|
+
|
|
5
34
|
## 🚀 What's New in v2.1.1
|
|
6
35
|
|
|
7
36
|
1. **🔥 AG Grid v35.1.0** - Latest version with all community features
|
package/dmx-ag-grid.js
CHANGED
|
@@ -648,6 +648,18 @@ dmx.Component('ag-grid', {
|
|
|
648
648
|
this.dispatchEvent('row_status_disabled')
|
|
649
649
|
}
|
|
650
650
|
};
|
|
651
|
+
// ponytail: delegated — checkboxCellRenderer emits no inline onclick (CSP script-src).
|
|
652
|
+
// Delegation rather than per-input binding because AG Grid virtualizes rows.
|
|
653
|
+
if (themeContainer) {
|
|
654
|
+
themeContainer.removeEventListener('click', this._statusToggleHandler);
|
|
655
|
+
this._statusToggleHandler = (event) => {
|
|
656
|
+
const input = event.target.closest('.switch-input[data-status-id]');
|
|
657
|
+
if (input) {
|
|
658
|
+
window.handleStatusToggle(event, input.dataset.statusCol, input.dataset.statusValue, input.dataset.statusId);
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
themeContainer.addEventListener('click', this._statusToggleHandler);
|
|
662
|
+
}
|
|
651
663
|
function clickCellRenderer(params) {
|
|
652
664
|
const idValue = params.data.id;
|
|
653
665
|
const columnName = params.colDef.field;
|
|
@@ -696,13 +708,14 @@ dmx.Component('ag-grid', {
|
|
|
696
708
|
}
|
|
697
709
|
// Assuming `value` is a boolean representing the status
|
|
698
710
|
const checked = value==true ? "checked" : "";
|
|
711
|
+
const attr = (v) => String(v).replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
|
699
712
|
return `
|
|
700
713
|
<div class="col pt-2 pb-1 ps-1 pe-1 d-flex justify-content-center">
|
|
701
714
|
<label class="switch switch-success">
|
|
702
|
-
<input
|
|
715
|
+
<input
|
|
703
716
|
type="checkbox" class="switch-input"
|
|
704
717
|
${checked}
|
|
705
|
-
|
|
718
|
+
data-status-col="${attr(columnName)}" data-status-value="${attr(value)}" data-status-id="${attr(idValue)}"
|
|
706
719
|
/>
|
|
707
720
|
<span class="switch-toggle-slider" role="status">
|
|
708
721
|
</span>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdmx/wappler_ag_grid",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "App Connect module for AG Grid v35 - Advanced data grid with enhanced editing, filtering, and tree data capabilities.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,16 +16,16 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@ag-grid-community/locale": "~35.1.0",
|
|
18
18
|
"ag-grid-community": "~35.1.0",
|
|
19
|
-
"papaparse": "~5.5.
|
|
20
|
-
"pdfmake": "~0.2.
|
|
21
|
-
"read-excel-file": "~5.8.
|
|
19
|
+
"papaparse": "~5.5.4",
|
|
20
|
+
"pdfmake": "~0.2.23",
|
|
21
|
+
"read-excel-file": "~5.8.8"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"exceljs": "github:cdmx-in/exceljs#v4.4.0-uuid11.1.1-r2"
|
|
24
|
+
"exceljs": "github:cdmx-in/exceljs#v4.4.0-uuid11.1.1-r2",
|
|
25
|
+
"puppeteer-core": "^24.43.1"
|
|
25
26
|
},
|
|
26
27
|
"scripts": {
|
|
27
28
|
"build": "rollup --config",
|
|
28
29
|
"publish-dry-run": "npm publish ./dist --access public --dry-run"
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
|
-
|
package/tests/04-selection.html
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
<main class="test-main">
|
|
30
30
|
<dmx-test-data id="rows" source="employees"></dmx-test-data>
|
|
31
|
+
<dmx-test-data id="statusData" source="statusRows"></dmx-test-data>
|
|
31
32
|
<dmx-event-log id="evLog"></dmx-event-log>
|
|
32
33
|
|
|
33
34
|
<section class="feature">
|
|
@@ -69,6 +70,29 @@
|
|
|
69
70
|
</div>
|
|
70
71
|
</section>
|
|
71
72
|
|
|
73
|
+
<section class="feature">
|
|
74
|
+
<h2>Status toggle column</h2>
|
|
75
|
+
<p class="desc">
|
|
76
|
+
Data has a <code>status</code> field, so <code>row_status_event=true</code> renders
|
|
77
|
+
<code>checkboxCellRenderer</code>. The toggle must dispatch its event via a delegated
|
|
78
|
+
listener and emit <strong>no inline <code>onclick</code></strong> — inline handlers are
|
|
79
|
+
blocked by a CSP without <code>'unsafe-inline'</code> in <code>script-src</code>.
|
|
80
|
+
The puppeteer runner reads <code>window.__statusCheck</code>.
|
|
81
|
+
</p>
|
|
82
|
+
<div class="grid-wrap">
|
|
83
|
+
<dmx-ag-grid
|
|
84
|
+
id="statusGrid"
|
|
85
|
+
dmx-bind:data="statusData.value"
|
|
86
|
+
grid_theme="ag-theme-alpine"
|
|
87
|
+
dmx-bind:row_status_event="true"
|
|
88
|
+
dmx-bind:pagination="false"
|
|
89
|
+
dmx-on:row_status_enabled="evLog.add('row_status_enabled')"
|
|
90
|
+
dmx-on:row_status_disabled="evLog.add('row_status_disabled')">
|
|
91
|
+
</dmx-ag-grid>
|
|
92
|
+
</div>
|
|
93
|
+
<pre class="log" id="status-check"></pre>
|
|
94
|
+
</section>
|
|
95
|
+
|
|
72
96
|
<section class="feature">
|
|
73
97
|
<h2>Single-row selection</h2>
|
|
74
98
|
<p class="desc"><code>row_selection="singleRow"</code> — clicking a row replaces selection.</p>
|
|
@@ -88,6 +112,44 @@
|
|
|
88
112
|
const c = TestKit.grid('selGrid');
|
|
89
113
|
console.log('selectedRows', c && c.get('selectedRows'));
|
|
90
114
|
}
|
|
115
|
+
// Status-toggle assertions — exposed as window.__statusCheck for the runners.
|
|
116
|
+
// Guards the CSP fix: checkboxCellRenderer must emit data-status-* attributes and no
|
|
117
|
+
// inline onclick, with a delegated listener still dispatching row_status_*.
|
|
118
|
+
function runStatusChecks(tries) {
|
|
119
|
+
tries = tries || 0;
|
|
120
|
+
if (document.body.dataset.gridsReady !== 'true') return setTimeout(() => runStatusChecks(tries), 200);
|
|
121
|
+
const root = document.querySelector('#statusGrid-grid');
|
|
122
|
+
const input = root && root.querySelector('.switch-input');
|
|
123
|
+
if (!input) {
|
|
124
|
+
if (tries > 25) return publish({ toggle_rendered: { expect: true, actual: false } });
|
|
125
|
+
return setTimeout(() => runStatusChecks(tries + 1), 200);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const checks = {
|
|
129
|
+
// First row is status:true, so it renders checked — clicking it disables.
|
|
130
|
+
toggle_rendered: { expect: true, actual: true },
|
|
131
|
+
no_inline_onclick: { expect: 0, actual: root.querySelectorAll('[onclick]').length },
|
|
132
|
+
toggle_has_dataset: { expect: true, actual: !!(input.dataset.statusId && input.dataset.statusCol) }
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const log = dmx.app.get('evLog');
|
|
136
|
+
input.click();
|
|
137
|
+
setTimeout(() => {
|
|
138
|
+
const last = log && log.last;
|
|
139
|
+
checks.toggle_dispatches = { expect: 'row_status_disabled', actual: (last && last.name) || null };
|
|
140
|
+
publish(checks);
|
|
141
|
+
}, 150);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function publish(checks) {
|
|
145
|
+
const failed = Object.entries(checks).filter(([_, v]) => v.actual !== v.expect);
|
|
146
|
+
window.__statusCheck = { checks, ok: failed.length === 0, failed: failed.map(([k]) => k) };
|
|
147
|
+
document.getElementById('status-check').textContent = Object.entries(checks)
|
|
148
|
+
.map(([k, v]) => (v.actual === v.expect ? '✓' : '✗') + ' ' + k + ' — expected ' + v.expect + ', got ' + v.actual)
|
|
149
|
+
.join('\n');
|
|
150
|
+
}
|
|
151
|
+
runStatusChecks();
|
|
152
|
+
|
|
91
153
|
(function pollLog() {
|
|
92
154
|
if (!window.dmx || !dmx.app) return setTimeout(pollLog, 100);
|
|
93
155
|
const log = dmx.app.get('evLog');
|
|
@@ -48,5 +48,16 @@
|
|
|
48
48
|
{ id: 112, customer: 'Wayne Ent.', region: 'NA', product: 'Gadget', qty: 11, unit_price: 99.99, total: 1099.89,order_date: '2025-05-04' }
|
|
49
49
|
];
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
// Rows carrying a `status` field — the only shape that makes dmx-ag-grid render
|
|
52
|
+
// checkboxCellRenderer (needs a `status` column plus row_status_event). Kept separate
|
|
53
|
+
// from `employees` so the other scenarios don't grow a stray column.
|
|
54
|
+
const statusRows = [
|
|
55
|
+
{ id: 1, name: 'Alpha', department: 'Engineering', status: true },
|
|
56
|
+
{ id: 2, name: 'Bravo', department: 'Sales', status: false },
|
|
57
|
+
{ id: 3, name: 'Charlie', department: 'Marketing', status: true },
|
|
58
|
+
{ id: 4, name: 'Delta', department: 'HR', status: false },
|
|
59
|
+
{ id: 5, name: 'Echo', department: 'Finance', status: true }
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
window.SampleData = { employees, orders, departments, statusRows };
|
|
52
63
|
})();
|
|
@@ -13,7 +13,7 @@ const PAGES = [
|
|
|
13
13
|
{ file: '01-basic-render.html', grids: ['basicGrid'] },
|
|
14
14
|
{ file: '02-themes-locale.html', grids: ['g_alpine','g_balham','g_material','g_quartz','g_custom','g_dark','g_he','g_ru','g_es'] },
|
|
15
15
|
{ file: '03-editing.html', grids: ['cellEdit','rowEdit','selectEdit'] },
|
|
16
|
-
{ file: '04-selection.html', grids: ['selGrid','singleSel'] },
|
|
16
|
+
{ file: '04-selection.html', grids: ['selGrid','singleSel','statusGrid'], pageAssertion: '__statusCheck' },
|
|
17
17
|
{ file: '05-action-buttons.html', grids: ['actGrid'] },
|
|
18
18
|
{ file: '06-export.html', grids: ['expGrid','noExp'] },
|
|
19
19
|
{ file: '07-import.html', grids: ['importGrid'], expectEmpty: true },
|
package/tests/run-all.cjs
CHANGED
|
@@ -16,7 +16,7 @@ const PAGES = [
|
|
|
16
16
|
{ file: '01-basic-render.html', grids: ['basicGrid'] },
|
|
17
17
|
{ file: '02-themes-locale.html', grids: ['g_alpine','g_balham','g_material','g_quartz','g_custom','g_dark','g_he','g_ru','g_es'] },
|
|
18
18
|
{ file: '03-editing.html', grids: ['cellEdit','rowEdit','selectEdit'] },
|
|
19
|
-
{ file: '04-selection.html', grids: ['selGrid','singleSel'] },
|
|
19
|
+
{ file: '04-selection.html', grids: ['selGrid','singleSel','statusGrid'] },
|
|
20
20
|
{ file: '05-action-buttons.html', grids: ['actGrid'] },
|
|
21
21
|
{ file: '06-export.html', grids: ['expGrid','noExp'] },
|
|
22
22
|
{ file: '07-import.html', grids: ['importGrid'], expectEmpty: true },
|