@cdmx/wappler_ag_grid 2.1.8 → 2.1.10

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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Change Log
2
2
 
3
+ ## Version 2.1.9 (Filter on JS-rendered columns)
4
+
5
+ ### Summary
6
+ Column filters, the floating filter row, the quick filter, `applyFilters` and preset `cfilters` now match the
7
+ text a `js_data_changes` function renders (HTML removed) instead of the raw field value. Columns whose raw
8
+ value is a JSON array/object are matched against their JSON text. Fixes "contains" filters on result-list
9
+ columns (e.g. Monitoring Results / Screening Results) returning no rows.
10
+
11
+ ### Changes Made
12
+ 1. **`dmx-ag-grid.js`** - text-type `js_data_changes` columns get a `filterValueGetter` that runs the render
13
+ function and strips tags via `removeHtmlTags` (now a hoisted function declaration); number/date columns keep
14
+ the raw value for their comparison filters. The shared filter value getter stringifies object/array values.
15
+ 2. **`tests/13-flags-styles.html`**, **`tests/data/sample-data.js`** - `jsFilterGrid` scenario with a
16
+ `transactions` fixture and page assertions for column + quick filters.
17
+
18
+ ---
19
+
3
20
  ## Version 2.1.4 (Row Color Formatting Fix)
4
21
 
5
22
  ### Summary
package/README.md CHANGED
@@ -408,6 +408,8 @@ ex: jsChanges.
408
408
  }
409
409
  </script>
410
410
  ```
411
+ Column filters, the floating filter row and the quick filter match the text this function renders (HTML tags removed, `<br>` treated as a line break), so searching for a word that is visible in the cell finds the row. This applies to text columns; number and date columns keep filtering on their raw value. Columns whose raw value is a JSON array/object are matched against their JSON text.
412
+
411
413
  ---
412
414
  # Custom Headers
413
415
 
package/dmx-ag-grid.js CHANGED
@@ -1353,8 +1353,10 @@ dmx.Component('ag-grid', {
1353
1353
  return parseFloat(value);
1354
1354
  }
1355
1355
  }
1356
- // Return the original value if no matching changes were found
1357
- return value;
1356
+ // Return the original value if no matching changes were found.
1357
+ // ponytail: arrays/objects (JSON result lists) stringify so text filters can match nested words;
1358
+ // keys match too - flatten string leaves if that ever bites.
1359
+ return (value !== null && typeof value === 'object') ? JSON.stringify(value) : value;
1358
1360
  };
1359
1361
  };
1360
1362
  if (Array.isArray(this.props.column_defs) && this.props.column_defs.length > 0) {
@@ -1538,6 +1540,20 @@ dmx.Component('ag-grid', {
1538
1540
  return cellValue;
1539
1541
  }
1540
1542
  }
1543
+ // ponytail: filter on what the user sees - renderer output with tags stripped.
1544
+ // Covers column/floating filters, quick filter, applyFilters and cfilters (all read filterValueGetter).
1545
+ // Text columns only: number/date filters compare the raw value, so they keep the base getter.
1546
+ if (dataType === 'text') {
1547
+ const baseFilterValueGetter = filterValueGetter;
1548
+ filterValueGetter = (params) => {
1549
+ const fn = window[matchingJsChange.function];
1550
+ const rendered = (typeof fn === 'function' && params.data) ? fn(params.data) : undefined;
1551
+ if (typeof rendered !== 'string') {
1552
+ return baseFilterValueGetter ? baseFilterValueGetter(params) : undefined;
1553
+ }
1554
+ return removeHtmlTags(rendered);
1555
+ };
1556
+ }
1541
1557
  }
1542
1558
  else {
1543
1559
  cellRenderer = undefined;
@@ -2288,7 +2304,8 @@ dmx.Component('ag-grid', {
2288
2304
 
2289
2305
  //CSV Export Function
2290
2306
  // Helper function to remove HTML tags from string
2291
- const removeHtmlTags = (htmlString) => {
2307
+ // Function declaration (hoisted): column-def filter getters built earlier in refreshGrid use it.
2308
+ function removeHtmlTags(htmlString) {
2292
2309
  if (typeof htmlString !== 'string') return htmlString;
2293
2310
  // Remove all HTML tags and decode common HTML entities
2294
2311
  return htmlString
@@ -2300,7 +2317,7 @@ dmx.Component('ag-grid', {
2300
2317
  .replace(/&amp;/g, '&')
2301
2318
  .replace(/&quot;/g, '"')
2302
2319
  .replace(/&#39;/g, "'");
2303
- };
2320
+ }
2304
2321
 
2305
2322
  exportGridData = (currentGridInstance, currentGridConfig) => {
2306
2323
  const exportConfig = currentGridConfig.context.exportConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdmx/wappler_ag_grid",
3
- "version": "2.1.8",
3
+ "version": "2.1.10",
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",
@@ -29,3 +29,4 @@
29
29
  "publish-dry-run": "npm publish ./dist --access public --dry-run"
30
30
  }
31
31
  }
32
+
@@ -36,6 +36,7 @@
36
36
 
37
37
  <main class="test-main">
38
38
  <dmx-test-data id="rows" source="employees"></dmx-test-data>
39
+ <dmx-test-data id="txns" source="transactions"></dmx-test-data>
39
40
 
40
41
  <section class="feature">
41
42
  <h2>Suppression flags + ci_sort + hide_filters/sort</h2>
@@ -238,6 +239,28 @@
238
239
  </div>
239
240
  </section>
240
241
 
242
+ <section class="feature">
243
+ <h2>Filters on JS-rendered columns (js_data_changes)</h2>
244
+ <p class="desc">
245
+ <code>monitoring_results</code> / <code>screening_results</code> are JSON lists rendered by
246
+ <code>js_data_changes</code> functions (one entry per line via <code>&lt;br&gt;</code>). Since 2.1.9 the
247
+ column filters, <code>applyFilters</code> and the quick filter match the rendered text, not the raw array
248
+ (text columns only: <code>amount</code> also has a JS renderer and must keep its number filter).
249
+ Try typing <code>PAYOUT</code> in a floating filter or the quick filter box.
250
+ </p>
251
+ <input type="search" id="jsqf" placeholder="Quick filter…" oninput="document.getElementById('jsFilterGrid').dmxComponent.get('gridInstance').setGridOption('quickFilterText', this.value)">
252
+ <div class="grid-wrap">
253
+ <dmx-ag-grid
254
+ id="jsFilterGrid"
255
+ dmx-bind:data="txns.value"
256
+ grid_theme="ag-theme-alpine"
257
+ quick_filter_field="jsqf"
258
+ dmx-bind:js_data_changes="[{field:'monitoring_results', function:'renderMonitoring'},{field:'screening_results', function:'renderScreening'},{field:'amount', function:'renderAmount'}]"
259
+ dmx-bind:pagination="false">
260
+ </dmx-ag-grid>
261
+ </div>
262
+ </section>
263
+
241
264
  <section class="feature">
242
265
  <h2>Live rstyles assertions</h2>
243
266
  <p class="desc">
@@ -250,6 +273,12 @@
250
273
  </main>
251
274
 
252
275
  <script>
276
+ // js_data_changes renderers for jsFilterGrid: JSON lists -> one entry per line.
277
+ window.renderMonitoring = data => (data.monitoring_results || []).map(r => '<b>' + r.rule + '</b> (' + r.score + ')').join('<br>');
278
+ window.renderScreening = data => (data.screening_results || []).map(r => r.list + ': ' + r.match).join('<br>');
279
+ // Number column with a JS renderer: its number filter must keep comparing the raw value.
280
+ window.renderAmount = data => '<span class="badge">' + Number(data.amount).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '</span>';
281
+
253
282
  // Helpers: count rows in a grid's DOM with a given background colour. Multiple matches
254
283
  // are accepted (puppeteer normalises CSS to rgb()).
255
284
  function bgCount(gridId, predicate) {
@@ -364,13 +393,49 @@
364
393
  checks.cs_textHigh = { expect: highSalary, actual: csTextHigh };
365
394
  checks.cs_cellLow = { expect: lowSalary, actual: csCellLow };
366
395
 
367
- const failed = Object.entries(checks).filter(([_, v]) => v.actual !== v.expect);
368
- window.__rsCheck = { checks, ok: failed.length === 0, failed: failed.map(([k]) => k) };
396
+ // js_data_changes filter checks (2.1.9): filters must match the rendered text, not the raw JSON array.
397
+ // The component's filter methods defer to dmx.nextTick, so this part is async and __rsCheck is
398
+ // published once it has run.
399
+ (async () => {
400
+ const txns = (window.SampleData && window.SampleData.transactions) || [];
401
+ const hasWord = (list, key, word) => (list || []).some(r => String(r[key]).includes(word));
402
+ const monPayout = txns.filter(r => hasWord(r.monitoring_results, 'rule', 'PAYOUT')).length;
403
+ const scrPayout = txns.filter(r => hasWord(r.screening_results, 'match', 'PAYOUT')).length;
404
+ const anyPayout = txns.filter(r => hasWord(r.monitoring_results, 'rule', 'PAYOUT') || hasWord(r.screening_results, 'match', 'PAYOUT')).length;
405
+ const el = document.getElementById('jsFilterGrid');
406
+ const comp = el && el.dmxComponent;
407
+ const gi = comp && comp.get('gridInstance');
408
+ const tick = () => new Promise(res => dmx.nextTick(res));
409
+ const call = async (name, arg) => { comp.methods[name].call(comp, arg); await tick(); await tick(); };
410
+ const visible = () => gi.getDisplayedRowCount();
411
+ if (gi) {
412
+ await call('applyFilters', { monitoring_results: { type: 'contains', filter: 'PAYOUT' } });
413
+ checks.jsFilter_monitoring = { expect: monPayout, actual: visible() };
414
+ await call('applyFilters', { screening_results: { type: 'contains', filter: 'PAYOUT' } });
415
+ checks.jsFilter_screening = { expect: scrPayout, actual: visible() };
416
+ await call('applyFilters', { monitoring_results: { type: 'contains', filter: 'NO-SUCH-RULE' } });
417
+ checks.jsFilter_noMatch = { expect: 0, actual: visible() };
418
+ await call('applyFilters', { id: { type: 'equals', filter: 9003 } });
419
+ checks.jsFilter_plainColumn = { expect: 1, actual: visible() };
420
+ await call('applyFilters', { amount: { type: 'greaterThan', filter: 1000 } });
421
+ checks.jsFilter_numberWithRenderer = { expect: txns.filter(r => r.amount > 1000).length, actual: visible() };
422
+ await call('clearFilters');
423
+ gi.setGridOption('quickFilterText', 'payout');
424
+ checks.jsFilter_quick = { expect: anyPayout, actual: visible() };
425
+ gi.setGridOption('quickFilterText', '');
426
+ checks.jsFilter_cleared = { expect: txns.length, actual: visible() };
427
+ } else {
428
+ checks.jsFilter_gridReady = { expect: true, actual: false };
429
+ }
430
+
431
+ const failed = Object.entries(checks).filter(([_, v]) => v.actual !== v.expect);
432
+ window.__rsCheck = { checks, ok: failed.length === 0, failed: failed.map(([k]) => k) };
369
433
 
370
- const box = document.getElementById('rs-check');
371
- box.textContent = Object.entries(checks)
372
- .map(([k, v]) => (v.actual === v.expect ? '✓' : '✗') + ' ' + k + ' — expected ' + v.expect + ', got ' + v.actual)
373
- .join('\n');
434
+ const box = document.getElementById('rs-check');
435
+ box.textContent = Object.entries(checks)
436
+ .map(([k, v]) => (v.actual === v.expect ? '✓' : '✗') + ' ' + k + ' — expected ' + v.expect + ', got ' + v.actual)
437
+ .join('\n');
438
+ })();
374
439
  }
375
440
  runChecks();
376
441
  </script>
@@ -59,5 +59,16 @@
59
59
  { id: 5, name: 'Echo', department: 'Finance', status: true }
60
60
  ];
61
61
 
62
- window.SampleData = { employees, orders, departments, statusRows };
62
+ // Transactions whose result columns are JSON lists rendered via js_data_changes.
63
+ // Used by 13-flags-styles.html to prove column + quick filters match the rendered text.
64
+ const transactions = [
65
+ { id: 9001, reference: 'TXN9001', amount: 1250.00, monitoring_results: [{ rule: 'PAYOUT', score: 80 }, { rule: 'VELOCITY', score: 40 }], screening_results: [{ list: 'PEP', match: 'PAYOUT AGENT' }] },
66
+ { id: 9002, reference: 'TXN9002', amount: 300.00, monitoring_results: [{ rule: 'VELOCITY', score: 55 }], screening_results: [{ list: 'SANCTIONS', match: 'NONE' }] },
67
+ { id: 9003, reference: 'TXN9003', amount: 9800.00, monitoring_results: [{ rule: 'PAYOUT', score: 95 }], screening_results: [] },
68
+ { id: 9004, reference: 'TXN9004', amount: 75.00, monitoring_results: [], screening_results: [{ list: 'PEP', match: 'PAYOUT' }] },
69
+ { id: 9005, reference: 'TXN9005', amount: 410.00, monitoring_results: null, screening_results: null },
70
+ { id: 9006, reference: 'TXN9006', amount: 2200.00, monitoring_results: [{ rule: 'STRUCTURING', score: 70 }, { rule: 'PAYOUT', score: 60 }], screening_results: [{ list: 'ADVERSE MEDIA', match: 'NONE' }] }
71
+ ];
72
+
73
+ window.SampleData = { employees, orders, departments, statusRows, transactions };
63
74
  })();
@@ -1,5 +1,5 @@
1
1
  {
2
- "startedAt": "2026-09-23T12:01:47.474Z",
2
+ "startedAt": "2026-10-01T12:45:25.153Z",
3
3
  "base": "http://localhost:8765/tests",
4
4
  "pages": [
5
5
  {
@@ -594,7 +594,8 @@
594
594
  "rsLegacy",
595
595
  "csCell",
596
596
  "transformGrid",
597
- "tipGrid"
597
+ "tipGrid",
598
+ "jsFilterGrid"
598
599
  ],
599
600
  "missing": [],
600
601
  "result": {
@@ -711,6 +712,17 @@
711
712
  "filterStateKeys": [],
712
713
  "columnStateLen": 0,
713
714
  "domRows": 25
715
+ },
716
+ "jsFilterGrid": {
717
+ "count": 6,
718
+ "state": {
719
+ "firstDataRendered": true
720
+ },
721
+ "dataLen": null,
722
+ "selectedRowsLen": 0,
723
+ "filterStateKeys": [],
724
+ "columnStateLen": 0,
725
+ "domRows": 6
714
726
  }
715
727
  }
716
728
  },
@@ -760,6 +772,34 @@
760
772
  "cs_cellLow": {
761
773
  "expect": 5,
762
774
  "actual": 5
775
+ },
776
+ "jsFilter_monitoring": {
777
+ "expect": 3,
778
+ "actual": 3
779
+ },
780
+ "jsFilter_screening": {
781
+ "expect": 2,
782
+ "actual": 2
783
+ },
784
+ "jsFilter_noMatch": {
785
+ "expect": 0,
786
+ "actual": 0
787
+ },
788
+ "jsFilter_plainColumn": {
789
+ "expect": 1,
790
+ "actual": 1
791
+ },
792
+ "jsFilter_numberWithRenderer": {
793
+ "expect": 3,
794
+ "actual": 3
795
+ },
796
+ "jsFilter_quick": {
797
+ "expect": 4,
798
+ "actual": 4
799
+ },
800
+ "jsFilter_cleared": {
801
+ "expect": 6,
802
+ "actual": 6
763
803
  }
764
804
  },
765
805
  "ok": true,
@@ -888,5 +928,5 @@
888
928
  }
889
929
  }
890
930
  ],
891
- "finishedAt": "2026-09-23T12:02:10.813Z"
931
+ "finishedAt": "2026-10-01T12:45:49.727Z"
892
932
  }
@@ -131,7 +131,9 @@
131
131
  console.error('[TestKit] no method', method, 'on', id);
132
132
  return null;
133
133
  }
134
- return g[key].apply(g, args);
134
+ // AppConnect's __method wrapper drops its first argument (Wappler passes the event there),
135
+ // so prepend a placeholder or methods like applyFilters(model) receive undefined.
136
+ return g[key].apply(g, [null].concat(args));
135
137
  },
136
138
  list: function () {
137
139
  // Find dmx-ag-grid elements in the DOM and return their dmx ids — more reliable
@@ -22,7 +22,7 @@ const PAGES = [
22
22
  { file: '10-state-persistence.html',grids: ['stateGrid'] },
23
23
  { file: '11-layout.html', grids: ['autoH','fixedH','scrollH'] },
24
24
  { file: '12-methods.html', grids: ['m'] },
25
- { file: '13-flags-styles.html', grids: ['flagsGrid','rsFn','rsOp','rsCompound','rsShort','rsFirst','rsLegacy','csCell','transformGrid','tipGrid'], pageAssertion: '__rsCheck' },
25
+ { file: '13-flags-styles.html', grids: ['flagsGrid','rsFn','rsOp','rsCompound','rsShort','rsFirst','rsLegacy','csCell','transformGrid','tipGrid','jsFilterGrid'], pageAssertion: '__rsCheck' },
26
26
  { file: '14-compact-view.html', grids: ['compactGrid','extButtons','cellEvtGrid'] },
27
27
  { file: '15-csp.html', grids: ['cspGrid','statusGrid'], pageAssertion: '__cspCheck' }
28
28
  ];
@@ -132,8 +132,11 @@ function findChrome() {
132
132
  let assertion = null;
133
133
  if (spec.pageAssertion) {
134
134
  try {
135
+ // Page checks poll until their grids settle; wait for them to publish instead of
136
+ // reading too early (a missing assertion must fail, not pass silently).
137
+ await page.waitForFunction((key) => !!window[key], { timeout: 10000 }, spec.pageAssertion);
135
138
  assertion = await page.evaluate((key) => window[key] || null, spec.pageAssertion);
136
- } catch (e) { assertion = { error: e.message }; }
139
+ } catch (e) { assertion = { ok: false, error: e.message }; }
137
140
  }
138
141
  const assertionFailed = assertion && assertion.ok === false;
139
142
 
@@ -160,6 +163,7 @@ function findChrome() {
160
163
  if (assertion) {
161
164
  if (assertionFailed) {
162
165
  console.log(' assertion FAILED — checks that did not match:');
166
+ if (assertion.error) console.log(' error: ' + assertion.error);
163
167
  (assertion.failed || []).forEach(k => {
164
168
  const c = assertion.checks && assertion.checks[k];
165
169
  console.log(' ' + k + ' expected ' + (c && c.expect) + ' got ' + (c && c.actual));
package/tests/run-all.cjs CHANGED
@@ -25,7 +25,7 @@ const PAGES = [
25
25
  { file: '10-state-persistence.html',grids: ['stateGrid'] },
26
26
  { file: '11-layout.html', grids: ['autoH','fixedH','scrollH'] },
27
27
  { file: '12-methods.html', grids: ['m'] },
28
- { file: '13-flags-styles.html', grids: ['flagsGrid','stylesGrid','transformGrid','tipGrid'] },
28
+ { file: '13-flags-styles.html', grids: ['flagsGrid','rsFn','rsOp','rsCompound','rsShort','rsFirst','rsLegacy','csCell','transformGrid','tipGrid','jsFilterGrid'] },
29
29
  { file: '14-compact-view.html', grids: ['compactGrid','extButtons','cellEvtGrid'] },
30
30
  { file: '15-csp.html', grids: ['cspGrid','statusGrid'] }
31
31
  ];