@wavemaker/app-ng-runtime 11.14.1-3.6306 → 11.14.1-32.647748

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.
Files changed (67) hide show
  1. package/components/base/bundles/index.umd.js +6 -22
  2. package/components/base/esm2022/pipes/custom-pipes.mjs +1 -1
  3. package/components/base/esm2022/widgets/common/base/base.component.mjs +8 -24
  4. package/components/base/fesm2022/index.mjs +7 -23
  5. package/components/base/fesm2022/index.mjs.map +1 -1
  6. package/components/chart/bundles/index.umd.js +2 -2
  7. package/components/chart/esm2022/chart.utils.mjs +3 -3
  8. package/components/chart/fesm2022/index.mjs +2 -2
  9. package/components/chart/fesm2022/index.mjs.map +1 -1
  10. package/components/containers/wizard/bundles/index.umd.js +21 -2
  11. package/components/containers/wizard/esm2022/wizard-step/wizard-step.component.mjs +3 -1
  12. package/components/containers/wizard/esm2022/wizard.component.mjs +20 -3
  13. package/components/containers/wizard/fesm2022/index.mjs +21 -2
  14. package/components/containers/wizard/fesm2022/index.mjs.map +1 -1
  15. package/components/containers/wizard/wizard-step/wizard-step.component.d.ts +2 -0
  16. package/components/data/pagination/bundles/index.umd.js +6 -1
  17. package/components/data/pagination/esm2022/pagination.component.mjs +7 -2
  18. package/components/data/pagination/fesm2022/index.mjs +6 -1
  19. package/components/data/pagination/fesm2022/index.mjs.map +1 -1
  20. package/components/data/table/bundles/index.umd.js +331 -23
  21. package/components/data/table/esm2022/table-action/table-action.directive.mjs +8 -1
  22. package/components/data/table/esm2022/table-column/table-column.directive.mjs +108 -4
  23. package/components/data/table/esm2022/table-column-group/table-column-group.directive.mjs +9 -1
  24. package/components/data/table/esm2022/table-cud.directive.mjs +22 -5
  25. package/components/data/table/esm2022/table-filter.directive.mjs +43 -4
  26. package/components/data/table/esm2022/table-row/table-row.directive.mjs +8 -1
  27. package/components/data/table/esm2022/table-row-action/table-row-action.directive.mjs +8 -1
  28. package/components/data/table/esm2022/table.component.mjs +134 -15
  29. package/components/data/table/fesm2022/index.mjs +333 -25
  30. package/components/data/table/fesm2022/index.mjs.map +1 -1
  31. package/components/data/table/table-action/table-action.directive.d.ts +3 -2
  32. package/components/data/table/table-column/table-column.directive.d.ts +3 -2
  33. package/components/data/table/table-column-group/table-column-group.directive.d.ts +3 -2
  34. package/components/data/table/table-cud.directive.d.ts +3 -1
  35. package/components/data/table/table-filter.directive.d.ts +3 -1
  36. package/components/data/table/table-row/table-row.directive.d.ts +3 -2
  37. package/components/data/table/table-row-action/table-row-action.directive.d.ts +3 -2
  38. package/components/data/table/table.component.d.ts +10 -0
  39. package/components/page/default/bundles/index.umd.js +1 -0
  40. package/components/page/default/esm2022/page.directive.mjs +2 -1
  41. package/components/page/default/fesm2022/index.mjs +1 -0
  42. package/components/page/default/fesm2022/index.mjs.map +1 -1
  43. package/core/bundles/index.umd.js +185 -330
  44. package/core/esm2022/public_api.mjs +2 -2
  45. package/core/esm2022/utils/watcher.mjs +189 -328
  46. package/core/fesm2022/index.mjs +192 -330
  47. package/core/fesm2022/index.mjs.map +1 -1
  48. package/core/public_api.d.ts +1 -1
  49. package/core/utils/watcher.d.ts +5 -26
  50. package/locales/moment-timezone/moment-timezone-with-data.js +179 -27
  51. package/npm-shrinkwrap.json +2 -2
  52. package/package-lock.json +2 -2
  53. package/package.json +1 -1
  54. package/runtime/base/bundles/index.umd.js +20 -46
  55. package/runtime/base/components/base-prefab.component.d.ts +6 -4
  56. package/runtime/base/esm2022/components/app-component/app.component.mjs +2 -7
  57. package/runtime/base/esm2022/components/base-page.component.mjs +2 -8
  58. package/runtime/base/esm2022/components/base-partial.component.mjs +3 -11
  59. package/runtime/base/esm2022/components/base-prefab.component.mjs +21 -21
  60. package/runtime/base/esm2022/components/base-spa-page.component.mjs +2 -9
  61. package/runtime/base/fesm2022/index.mjs +21 -47
  62. package/runtime/base/fesm2022/index.mjs.map +1 -1
  63. package/runtime/dynamic/bundles/index.umd.js +4 -0
  64. package/runtime/dynamic/esm2022/app/app.config.mjs +6 -2
  65. package/runtime/dynamic/fesm2022/index.mjs +5 -1
  66. package/runtime/dynamic/fesm2022/index.mjs.map +1 -1
  67. package/scripts/datatable/datatable.js +336 -33
@@ -583,6 +583,8 @@ $.widget('wm.datatable', {
583
583
  var self = this, preparedData,$tbody,pageStartIndex = self.getPageStartIndex(),
584
584
  startRowIndex = self.options.startRowIndex,
585
585
  isScrollorOnDemand = self.options.isNavTypeScrollOrOndemand();
586
+ var $activeRow = self.gridElement.find('tr.row-editing:not(.always-new-row)').first();
587
+ var index = $activeRow.index();
586
588
  if(isScrollorOnDemand) {
587
589
  $tbody = this.gridElement;
588
590
  if(this.renderTableOnViewLess) {
@@ -638,6 +640,16 @@ $.widget('wm.datatable', {
638
640
  this.options.setIsDataUpdatedByUser(false);
639
641
  this.options.clearActionRowIndex();
640
642
  }
643
+ if(this.options.editmode===this.CONSTANTS.QUICK_EDIT){
644
+ if (index>=0) {
645
+ const $newActiveRow = self.gridElement.find('tr').eq(index);
646
+ if ($newActiveRow && $newActiveRow.length) {
647
+ $newActiveRow.addClass('active');
648
+ $newActiveRow.trigger('click', [undefined, {action: 'edit', skipFocus:false}]);
649
+ self.setActiveRow($newActiveRow);
650
+ self.focusActiveRow();
651
+ }
652
+ }}
641
653
  return $tbody;
642
654
  },
643
655
 
@@ -724,6 +736,10 @@ $.widget('wm.datatable', {
724
736
  colExpression = colDef.customExpression,
725
737
  styles = "text-align: " + colDef.textAlignment + ";position: relative;"
726
738
 
739
+ if (!_.isUndefined(colDef.show) && !colDef.show) {
740
+ styles += "display: none;";
741
+ }
742
+
727
743
  $htm = $('<td class="' + classes + '" data-col-id="' + colId + '" role="cell" tabindex="0"></td>');
728
744
  this._setStyles($htm, styles);
729
745
 
@@ -1057,7 +1073,9 @@ $.widget('wm.datatable', {
1057
1073
  'value': '',
1058
1074
  'event': null
1059
1075
  },
1060
- compiledCellTemplates: {}
1076
+ compiledCellTemplates: {},
1077
+ // MEMORY LEAK FIX: Store bound function for proper event listener cleanup
1078
+ // boundRowClickHandlerOnCapture: null
1061
1079
  });
1062
1080
  // TODO: Variable loading status is getting updated before dataset update. This is resulting in loader going off before data is rendered.
1063
1081
  // Need to update code with suitable fix. For now 250ms is added as workaround
@@ -1117,6 +1135,144 @@ $.widget('wm.datatable', {
1117
1135
  this._setGridEditMode(false);
1118
1136
  this.toggleNewRowActions(false);
1119
1137
  },
1138
+ /*
1139
+ * Updates internal state (options.data, preparedData, custom expressions,
1140
+ * conditional class watchers, and summary row) without a full DOM re-render.
1141
+ * Used during quickedit inline save for model variables to avoid focus loss.
1142
+ */
1143
+ softRefreshData: function (newData) {
1144
+ // With pagination, navigator / selection can emit the same page twice in one
1145
+ // save cycle. Full refresh skipped duplicate data via isEqual on option set;
1146
+ // soft refresh must do the same or onDataRender (datarender) fires twice.
1147
+ if (this.options.data && newData && _.isEqual(this.options.data, newData)) {
1148
+ return;
1149
+ }
1150
+
1151
+ var self = this,
1152
+ isScrollOrOnDemand = this.options.isNavTypeScrollOrOndemand(),
1153
+ pageStartIndex = this.getPageStartIndex(),
1154
+ startRowIndex = this.options.startRowIndex || 0,
1155
+ selectedMap = {},
1156
+ preparedData;
1157
+
1158
+ // Preserve _selected state before _prepareData rebuilds preparedData
1159
+ this.preparedData.forEach(function (data, i) {
1160
+ if (data._selected) {
1161
+ selectedMap[i] = true;
1162
+ }
1163
+ });
1164
+
1165
+ this.options.data = newData;
1166
+ this._prepareData();
1167
+
1168
+ // Restore _selected state on new preparedData
1169
+ Object.keys(selectedMap).forEach(function (i) {
1170
+ if (self.preparedData[i]) {
1171
+ self.preparedData[i]._selected = true;
1172
+ }
1173
+ });
1174
+
1175
+ if (isScrollOrOnDemand) {
1176
+ preparedData = this.preparedData.slice(pageStartIndex);
1177
+ } else {
1178
+ preparedData = this.preparedData;
1179
+ }
1180
+
1181
+ // Update existing custom expression view contexts in-place
1182
+ // so Angular change detection picks up new data without DOM manipulation.
1183
+ if (_.isFunction(this.options.updateCustomExpressionContexts)) {
1184
+ this.options.updateCustomExpressionContexts(preparedData);
1185
+ }
1186
+
1187
+ // Update plain text cells with fresh data from preparedData.
1188
+ // hideRowEditMode runs before softRefreshData and may restore cells
1189
+ // before preparedData is updated, leaving them with stale values.
1190
+ self.gridElement.find('tr.' + self.options.cssClassNames.tableRow + ':not(.always-new-row)').each(function () {
1191
+ var $row = $(this),
1192
+ rowId = +$row.attr('data-row-id'),
1193
+ rowData = self.preparedData[rowId];
1194
+ if (!rowData) return;
1195
+
1196
+ $row.find('td.' + self.options.cssClassNames.tableCell + ':not(.cell-editing)').each(function () {
1197
+ var $cell = $(this),
1198
+ colId = +$cell.attr('data-col-id'),
1199
+ colDef = self.preparedHeaderData[colId],
1200
+ value;
1201
+
1202
+ if (colDef && !colDef.customExpression && colDef.type !== 'custom') {
1203
+ value = _.get(rowData, colDef.field);
1204
+ var newText;
1205
+ if (self.Utils.isDefined(value) && value !== null) {
1206
+ newText = '' + value;
1207
+ } else if (self.options.filterNullRecords && value === null) {
1208
+ newText = 'null';
1209
+ } else {
1210
+ newText = '';
1211
+ }
1212
+ if ($cell.text() !== newText) {
1213
+ $cell.text(newText);
1214
+ $cell.attr('title', newText);
1215
+ }
1216
+ }
1217
+ });
1218
+ });
1219
+
1220
+ // Re-register conditional class watchers with fresh row clones
1221
+ _.forEach(preparedData, function (row, index) {
1222
+ var _row = _.clone(row),
1223
+ rowIndex = isScrollOrOnDemand ? startRowIndex + index - 1 : index;
1224
+ _row.$index = rowIndex + 1;
1225
+
1226
+ self.options.registerRowNgClassWatcher(_row, rowIndex);
1227
+
1228
+ self.preparedHeaderData.forEach(function (colDef, colId) {
1229
+ self.options.registerColNgClassWatcher(_row, colDef, rowIndex, colId);
1230
+ });
1231
+ });
1232
+
1233
+ // Reset action state flags (mirrors _getGridTemplate cleanup)
1234
+ if (!this.options.isdynamictable) {
1235
+ this.options.setLastActionPerformed(this.options.ACTIONS.DEFAULT);
1236
+ this.options.setIsDataUpdatedByUser(false);
1237
+ this.options.clearActionRowIndex();
1238
+ }
1239
+
1240
+ // Refresh row action buttons and expansion buttons with updated row data.
1241
+ // Scope to data rows only — exclude always-new-row which has no backing data entry.
1242
+ var $dataRows = self.gridElement.find('tr.' + self.options.cssClassNames.tableRow + ':not(.always-new-row)');
1243
+ this.options.clearRowActions();
1244
+ this._appendRowExpansionButtons($dataRows);
1245
+ this._appendRowActions($dataRows);
1246
+ // Regenerated action / expansion DOM — re-bind handlers (attachEventHandlers only runs on full render).
1247
+ this._bindRowActionsAndExpansionClickHandlers($dataRows);
1248
+
1249
+ // Re-render summary row if present
1250
+ if (this.options.summaryRow) {
1251
+ var $summaryRowHtm = $(this._getSummaryRowTemplate());
1252
+ this.tableContainer.find('tfoot').remove();
1253
+ if (this.options.data.length) {
1254
+ this.tableContainer.append($summaryRowHtm);
1255
+ }
1256
+ this.gridFooter = this.tableContainer.find('tfoot');
1257
+ }
1258
+
1259
+ // Invalidate row detail (expansion) cache so collapse+re-expand
1260
+ // creates fresh views with updated data.
1261
+ if (_.isFunction(this.options.invalidateRowDetailCache)) {
1262
+ this.options.invalidateRowDetailCache(preparedData);
1263
+ }
1264
+
1265
+ // Refresh this.items with fresh data references from the updated dataset
1266
+ // so that selecteditem / currentItem reflect the new values.
1267
+ if (_.isFunction(this.options.refreshSelectedItems)) {
1268
+ this.options.refreshSelectedItems();
1269
+ }
1270
+
1271
+ // Fire datarender event callback
1272
+ if (_.isFunction(this.options.onDataRender)) {
1273
+ this.options.onDataRender();
1274
+ }
1275
+ },
1120
1276
  //Populate row data with default data
1121
1277
  setDefaultRowData: function (rowData) {
1122
1278
  _.forEach(this.preparedHeaderData, function (colDef) {
@@ -1336,6 +1492,12 @@ $.widget('wm.datatable', {
1336
1492
  }
1337
1493
  }
1338
1494
  },
1495
+ refreshColumnWidths: function () {
1496
+ if (this.gridContainer.width() > 0) {
1497
+ this.tableContainer.children('colgroup').children('col').css('width', '');
1498
+ }
1499
+ this.setColGroupWidths();
1500
+ },
1339
1501
  /*Set the default widths for the colgroup*/
1340
1502
  setColGroupWidths: function () {
1341
1503
  var self = this,
@@ -1669,8 +1831,8 @@ $.widget('wm.datatable', {
1669
1831
  },
1670
1832
 
1671
1833
  /* Selects a row. */
1672
- selectRow: function (row, value) {
1673
- var rowIndex = _.isNumber(row) ? row : this.Utils.getObjectIndex(this.options.data, row),
1834
+ selectRow: function (row, value,index) {
1835
+ var rowIndex =index?index:( _.isNumber(row) ? row : this.Utils.getObjectIndex(this.options.data, row)),
1674
1836
  selector,
1675
1837
  $row;
1676
1838
  if (rowIndex !== -1) {
@@ -1982,8 +2144,8 @@ $.widget('wm.datatable', {
1982
2144
  var $firstEl,
1983
2145
  $target = e && $(e.target),
1984
2146
  $focusEl;
1985
- //If focused directly on the cell, focus the input in the cell
1986
- if ($target && $target.hasClass('app-datagrid-cell')) {
2147
+ //If focused directly on the cell, focus the input in the cell (only when the element is still in the DOM)
2148
+ if ($target && $target.hasClass('app-datagrid-cell') && document.body.contains($target[0])) {
1987
2149
  $firstEl = $target.find('input');
1988
2150
  } else {
1989
2151
  if (!$el) {
@@ -1999,7 +2161,7 @@ $.widget('wm.datatable', {
1999
2161
  if (!$firstEl.length) {
2000
2162
  $firstEl = $($el).first().find('select');
2001
2163
  }
2002
- if(!($(e.target).find('td').first().hasClass('td.cell-editing'))){
2164
+ if(!($(e?.target).find('td').first().hasClass('td.cell-editing'))){
2003
2165
  $firstEl=$el.find('td');
2004
2166
  }
2005
2167
  }
@@ -2035,9 +2197,9 @@ $.widget('wm.datatable', {
2035
2197
  this.toggleNewRowActions(false);
2036
2198
  },
2037
2199
  //Method to save a row which is in editable state
2038
- saveRow: function (callBack) {
2200
+ saveRow: function (callBack, skipFocus) {
2039
2201
  this.gridElement.find('tr.app-datagrid-row.row-editing:not(.always-new-row)').each(function () {
2040
- $(this).trigger('click', [undefined, {action: 'save', skipSelect: true, noMsg: true, success: callBack}]);
2202
+ $(this).trigger('click', [undefined, {action: 'save', skipSelect: true, noMsg: true, skipFocus: skipFocus, success: callBack}]);
2041
2203
  });
2042
2204
  },
2043
2205
  //Method to make row editable with widgets
@@ -2095,10 +2257,11 @@ $.widget('wm.datatable', {
2095
2257
  });
2096
2258
 
2097
2259
  $editableElements = $row.find('td.cell-editing');
2098
- $editableElements.on('click', function (e) {
2260
+ // PERFORMANCE FIX: Use event delegation on row instead of individual cell listeners
2261
+ $row.on('click.cellEdit', 'td.cell-editing', function (e) {
2099
2262
  e.stopPropagation();
2100
2263
  });
2101
- $editableElements.on('keydown', function (e) {
2264
+ $row.on('keydown.cellEdit', 'td.cell-editing', function (e) {
2102
2265
  //To prevent up and down arrows, navigating to other rows in edit mode
2103
2266
  if ((e.which === 38 || e.which === 40) && (e.currentTarget && !e.currentTarget.closest('.always-new-row'))) {
2104
2267
  e.stopPropagation();
@@ -2140,6 +2303,10 @@ $.widget('wm.datatable', {
2140
2303
  var rowId = parseInt($row.attr('data-row-id'), 10);
2141
2304
  return rowId >= this.preparedData.length || $row.hasClass('always-new-row');
2142
2305
  },
2306
+ _getWmVariableRowLocator: function (rowId) {
2307
+ var liveRow = this.options.data[parseInt(rowId, 10)];
2308
+ return (liveRow && liveRow.$$wmSourceListIndex != null) ? liveRow.$$wmSourceListIndex : (liveRow || parseInt(rowId, 10));
2309
+ },
2143
2310
  /* Toggles the edit state of a row. */
2144
2311
  toggleEditRow: function (e, options) {
2145
2312
  if($(e.target).closest('.app-menu').length) {
@@ -2167,7 +2334,8 @@ $.widget('wm.datatable', {
2167
2334
  $requiredEls,
2168
2335
  alwaysNewRow = $row.hasClass('always-new-row'),
2169
2336
  advancedEdit = self.options.editmode === self.CONSTANTS.QUICK_EDIT,
2170
- editOptions = {};
2337
+ editOptions = {},
2338
+ rowindex=$row.attr('data-row-id');
2171
2339
 
2172
2340
  // when a row is edited set actionrow variables
2173
2341
  this.options.setActionRowIndex($row.attr('data-row-id'));
@@ -2178,7 +2346,8 @@ $.widget('wm.datatable', {
2178
2346
  if (_.isFunction(options.success)) {
2179
2347
  options.success(skipFocus, error);
2180
2348
  }
2181
- if (!advancedEdit || self.options.actionsEnabled.edit) {
2349
+ // [WMS-31104] Do not refocus this table when the save was initiated from outside (e.g. clicking another table).
2350
+ if (!skipFocus && !options.skipFocus && (!advancedEdit || self.options.actionsEnabled.edit)) {
2182
2351
  self.focusActiveRow();
2183
2352
  }
2184
2353
  }
@@ -2189,7 +2358,7 @@ $.widget('wm.datatable', {
2189
2358
  }
2190
2359
  //Select the current edited row
2191
2360
  if (options.selectRow) {
2192
- this.selectRow(rowData, true);
2361
+ this.selectRow(rowData, true,rowindex);
2193
2362
  }
2194
2363
  e = e || {};
2195
2364
  e.data = e.data || {};
@@ -2205,6 +2374,43 @@ $.widget('wm.datatable', {
2205
2374
  });
2206
2375
  return;
2207
2376
  }
2377
+ if (advancedEdit) {
2378
+ // [WMS-31104] Close/save row in another quick-edit table before editing this row.
2379
+ $(document).find('tr.app-datagrid-row.always-new-row.row-editing').filter(function () {
2380
+ return $(this).closest(self.element).length === 0;
2381
+ }).each(function () {
2382
+ var $newRow = $(this),
2383
+ $otherGrid = $newRow.closest('.app-datagrid');
2384
+ if ($otherGrid.length) {
2385
+ $otherGrid.datatable('resetNewRow', $newRow);
2386
+ }
2387
+ });
2388
+ // [WMS-31104] Save the editing row in another table before opening the clicked row in this table.
2389
+ var $otherEditingRow = $(document).find('tr.app-datagrid-row.row-editing:not(.always-new-row)').filter(function () {
2390
+ return $(this).closest(self.element).length === 0;
2391
+ }).first();
2392
+ if ($otherEditingRow.length) {
2393
+ var rowId = $row.attr('data-row-id');
2394
+ $otherEditingRow.trigger('click', [undefined, {
2395
+ action: 'save',
2396
+ $row: $otherEditingRow,
2397
+ skipSelect: true,
2398
+ noMsg: true,
2399
+ skipFocus: true,
2400
+ success: function () {
2401
+ var $liveRow = self.gridElement.find('tr.app-datagrid-row[data-row-id="' + rowId + '"]');
2402
+ var $targetRow = $liveRow.length ? $liveRow : $row;
2403
+ self.toggleEditRow(e, _.extend({}, options, {
2404
+ action: 'edit',
2405
+ $row: $targetRow,
2406
+ skipFocus: true,
2407
+ focusAfterCrossTableSave: true
2408
+ }));
2409
+ }
2410
+ }]);
2411
+ return;
2412
+ }
2413
+ }
2208
2414
  $row.addClass('row-editing');
2209
2415
  if (_.isFunction(this.options.beforeRowUpdate)) {
2210
2416
  this.options.beforeRowUpdate(rowData, e);
@@ -2245,6 +2451,12 @@ $.widget('wm.datatable', {
2245
2451
  if (_.isFunction(this.options.onFormRender)) {
2246
2452
  this.options.onFormRender($row, e, options.operation || action, $row.hasClass('always-new-row'));
2247
2453
  }
2454
+ // [WMS-31104] Focus target row after widgets render when edit opened from another table save.
2455
+ if (options.focusAfterCrossTableSave) {
2456
+ self.options.timeoutCall(function () {
2457
+ self.setFocusOnElement(undefined, $row.find('td.cell-editing'), true);
2458
+ });
2459
+ }
2248
2460
  } else {
2249
2461
  $editableElements = $row.find('td.cell-editing');
2250
2462
  isNewRow = self._isNewRow($row);
@@ -2257,6 +2469,10 @@ $.widget('wm.datatable', {
2257
2469
  }
2258
2470
 
2259
2471
  if (isDataChanged) {
2472
+ editOptions.skipFocus = options.skipFocus;
2473
+ if (options.skipFocus) {
2474
+ self.options.activeRow = undefined;
2475
+ }
2260
2476
  $editableElements.each(function () {
2261
2477
  var $el = $(this),
2262
2478
  colId = $el.attr('data-col-id'),
@@ -2344,6 +2560,10 @@ $.widget('wm.datatable', {
2344
2560
  return;
2345
2561
  }
2346
2562
  }
2563
+ var ds = this.options.getDataSource && this.options.getDataSource();
2564
+ if (ds && ds.category === 'wm.Variable') {
2565
+ editOptions.rowindex = self._getWmVariableRowLocator(rowindex);
2566
+ }
2347
2567
  this.options.afterRowUpdate(rowData, e, onSaveSuccess, editOptions);
2348
2568
  }
2349
2569
  } else {
@@ -2380,6 +2600,8 @@ $.widget('wm.datatable', {
2380
2600
 
2381
2601
  this.disableActions(false);
2382
2602
  $row.removeClass('row-editing');
2603
+ // PERFORMANCE FIX: Remove namespaced event listeners from row
2604
+ $row.off('.cellEdit');
2383
2605
  $editableElements.off('click');
2384
2606
  $editableElements.each(function () {
2385
2607
  var $el = $(this),
@@ -2428,6 +2650,8 @@ $.widget('wm.datatable', {
2428
2650
  $saveButton = $row.find('.save-edit-row-button'),
2429
2651
  self = this;
2430
2652
  $row.removeClass('row-editing');
2653
+ // PERFORMANCE FIX: Remove namespaced event listeners from row
2654
+ $row.off('.cellEdit');
2431
2655
  $editableElements.off('click');
2432
2656
  this.disableActions(false);
2433
2657
  this._setGridEditMode(false);
@@ -2491,6 +2715,10 @@ $.widget('wm.datatable', {
2491
2715
  return;
2492
2716
  }
2493
2717
  }
2718
+ var deleteDs = this.options.getDataSource && this.options.getDataSource();
2719
+ if (deleteDs && deleteDs.category === 'wm.Variable') {
2720
+ options.rowindex = this._getWmVariableRowLocator(rowId);
2721
+ }
2494
2722
  if (_.isFunction(this.options.onRowDelete)) {
2495
2723
  className = this.options.cssClassNames.deleteRow;
2496
2724
  isActiveRow = $row.attr('class').indexOf('active') !== -1;
@@ -2848,7 +3076,7 @@ $.widget('wm.datatable', {
2848
3076
  var $cell = $target.closest('td.app-datagrid-cell'),
2849
3077
  $editCells;
2850
3078
 
2851
- if ($cell.is(':last-child')) {
3079
+ if ($cell.is(':last-child') || $cell.nextAll('td.app-datagrid-cell:visible').length === 0) {
2852
3080
  return true;
2853
3081
  }
2854
3082
 
@@ -2869,6 +3097,28 @@ $.widget('wm.datatable', {
2869
3097
  this.sortHandler(event);
2870
3098
  }
2871
3099
  },
3100
+ /**
3101
+ * Binds jQuery handlers on save/cancel row-action buttons and row-expansion controls.
3102
+ * Must run after DOM for those controls is replaced (e.g. softRefreshData).
3103
+ */
3104
+ _bindRowActionsAndExpansionClickHandlers: function ($scope) {
3105
+ var self = this;
3106
+ if (this.options.rowActions.length) {
3107
+ $scope.find('.cancel-edit-row-button').off('click');
3108
+ $scope.find('.save-edit-row-button').off('click');
3109
+ $scope.find('.cancel-edit-row-button').on('click', {action: 'cancel'}, this.toggleEditRow.bind(this));
3110
+ $scope.find('.save-edit-row-button').on('click', {action: 'save'}, this.toggleEditRow.bind(this));
3111
+ }
3112
+ $scope.find('[data-identifier="rowExpansionButtons"]').off('click');
3113
+ $scope.find('[data-identifier="rowExpansionButtons"]').on('click', function (e) {
3114
+ var $row = $(e.target).closest('tr.app-datagrid-row');
3115
+ if ($(this).find('.app-button').attr('disabled')) {
3116
+ return;
3117
+ }
3118
+ self.toggleExpandRow(+$row.attr('data-row-id'), undefined, e);
3119
+ });
3120
+ },
3121
+
2872
3122
  /* Attaches all event handlers for the table. */
2873
3123
  attachEventHandlers: function ($htm) {
2874
3124
  var $header = this.gridHeaderElement,
@@ -2876,8 +3126,17 @@ $.widget('wm.datatable', {
2876
3126
 
2877
3127
  if (this.options.enableRowSelection) {
2878
3128
  $htm[0].removeEventListener('click', this.rowClickHandlerOnCapture.bind(this));
3129
+ // MEMORY LEAK FIX: Remove old listener if exists before adding new one
3130
+ // if (this.boundRowClickHandlerOnCapture && $htm[0]) {
3131
+ // $htm[0].removeEventListener('click', this.boundRowClickHandlerOnCapture, true);
3132
+ // }
2879
3133
  $htm.off();
2880
3134
  $htm[0].addEventListener('click', this.rowClickHandlerOnCapture.bind(this));
3135
+
3136
+ // MEMORY LEAK FIX: Store bound function reference for proper removal
3137
+ // this.boundRowClickHandlerOnCapture = this.rowClickHandlerOnCapture.bind(this);
3138
+ // $htm[0].addEventListener('click', this.boundRowClickHandlerOnCapture, true);
3139
+
2881
3140
  // add js click handler for capture phase in order to first listen on grid and
2882
3141
  // assign selectedItems so that any child actions can have access to the selectedItems.
2883
3142
  $htm.on('click', this.rowSelectionHandler.bind(this));
@@ -2913,16 +3172,14 @@ $.widget('wm.datatable', {
2913
3172
  }
2914
3173
  }
2915
3174
  }
2916
- if (this.options.rowActions.length) {
2917
- $htm.find('.cancel-edit-row-button').off('click');
2918
- $htm.find('.save-edit-row-button').off('click');
2919
- $htm.find('.cancel-edit-row-button').on('click', {action: 'cancel'}, this.toggleEditRow.bind(this));
2920
- $htm.find('.save-edit-row-button').on('click', {action: 'save'}, this.toggleEditRow.bind(this));
2921
- }
3175
+ this._bindRowActionsAndExpansionClickHandlers($htm);
2922
3176
  if (self.options.editmode === self.CONSTANTS.QUICK_EDIT) {
2923
3177
  $htm.on('focus', 'tr.app-datagrid-row', function (e) {
2924
3178
  var $row = $(e.currentTarget);
2925
3179
  if (!$row.hasClass('row-editing')) {
3180
+ if(e.target===e.currentTarget && (e.which===0||!e.which)) {
3181
+ return;
3182
+ }
2926
3183
  self.toggleEditRow(e, { $row: $row, action: 'edit'});
2927
3184
  }
2928
3185
  });
@@ -2938,6 +3195,25 @@ $.widget('wm.datatable', {
2938
3195
  isRelatedTargetGridAction = $relatedTarget.closest('div.app-datagrid-actions').length>0,
2939
3196
  invalidTargets = '.row-editing:not(".always-new-row"), .row-action-button, .app-datagrid-cell, .caption, button.btn-time, button.btn-date';
2940
3197
 
3198
+ // [WMS-31104] Do not insert the new row when focus moves to another table; that table's row-click handler will reset it.
3199
+ if ($row.hasClass('always-new-row')
3200
+ && $relatedTarget.length
3201
+ && !$relatedTarget.closest(self.element).length
3202
+ && $relatedTarget.closest('.app-datagrid').length) {
3203
+ return;
3204
+ }
3205
+ // Focus moved to a date/time picker — the row is still being edited, do not save.
3206
+ if ($relatedTarget.closest('bs-datepicker-container, .btn-date, .btn-time').length) {
3207
+ return;
3208
+ }
3209
+ // [WMS-31104] For regular rows, do not save on focusout when focus leaves this table; the outside-click handler handles it.
3210
+ // The new row (always-new-row) is excluded from this guard so Tab-key save still works when focus moves to pagination.
3211
+ if (!$row.hasClass('always-new-row')
3212
+ && $relatedTarget.length
3213
+ && !$relatedTarget.closest(self.element).length) {
3214
+ return;
3215
+ }
3216
+
2941
3217
  //Check if the focus out element is outside the grid or some special elements
2942
3218
  function isInvalidTarget() {
2943
3219
  if (!$relatedTarget.closest('.app-grid').length) {
@@ -2961,7 +3237,7 @@ $.widget('wm.datatable', {
2961
3237
  // triggered from the Row Actions
2962
3238
  if (!isTargetGridAction && !isTargetRowAction) {
2963
3239
  //Save the row on last column of the data table. Do not save the row if focus is out of input file.
2964
- if (!isLastColumn || (isLastColumn && e.relatedTarget === null) || $target.hasClass("file-upload")) {
3240
+ if (!isLastColumn || (isLastColumn && (e.relatedTarget === null || $(e.target).is('tr.app-datagrid-row'))) || $target.hasClass("file-upload")) {
2965
3241
  return;
2966
3242
  }
2967
3243
  }
@@ -2979,15 +3255,6 @@ $.widget('wm.datatable', {
2979
3255
  });
2980
3256
  }
2981
3257
 
2982
- // row selection
2983
- $htm.find('[data-identifier="rowExpansionButtons"]').off("click");
2984
- $htm.find('[data-identifier="rowExpansionButtons"]').on('click', function (e) {
2985
- var $row = $(e.target).closest('tr.app-datagrid-row');
2986
- if ($(this).find('.app-button').attr('disabled')) {
2987
- return;
2988
- }
2989
- self.toggleExpandRow(+$row.attr('data-row-id'), undefined, e);
2990
- });
2991
3258
  },
2992
3259
  expandRow: function(rowId) {
2993
3260
  this.toggleExpandRow(rowId, true)
@@ -3511,7 +3778,11 @@ $.widget('wm.datatable', {
3511
3778
  this.dataStatusContainer.hide();
3512
3779
  } else {
3513
3780
  // [WMS-23839] always show load more btn if show view less btn is true
3514
- if (this.options.isNavTypeScrollOrOndemand() && (state === 'nodata' || ((this.options.getCurrentPage() == this.options.getPageCount()) && !this.options.showviewlessbutton))) {
3781
+ if (this.options.isNavTypeScrollOrOndemand() &&
3782
+ (state === 'nodata' ||
3783
+ (this.options.getCurrentPage && typeof this.options.getCurrentPage === 'function' &&
3784
+ this.options.getPageCount && typeof this.options.getPageCount === 'function' &&
3785
+ (this.options.getCurrentPage() == this.options.getPageCount()) && !this.options.showviewlessbutton))) {
3515
3786
  this.element.find('.on-demand-datagrid a').hide();
3516
3787
  }
3517
3788
  this.dataStatusContainer.show();
@@ -3521,7 +3792,9 @@ $.widget('wm.datatable', {
3521
3792
  if (state === 'nodata') {
3522
3793
  this.dataStatusContainer.css('height', 'auto');
3523
3794
  this.dataStatus.contentHeight = 0;
3524
- } else if (this.options.isNavTypeScrollOrOndemand() && this.options.getCurrentPage() > 1){
3795
+ } else if (this.options.isNavTypeScrollOrOndemand() &&
3796
+ this.options.getCurrentPage && typeof this.options.getCurrentPage === 'function' &&
3797
+ this.options.getCurrentPage() > 1){
3525
3798
  // showing the loading icon only for the first page
3526
3799
  // from second page there is another loader which is being shown instead of LoadMore btn
3527
3800
  this.dataStatusContainer.hide();
@@ -3531,7 +3804,9 @@ $.widget('wm.datatable', {
3531
3804
  this.dataStatusContainer.css('height', this.dataStatus.height > this.dataStatus.contentHeight ? 'auto' : this.dataStatus.contentHeight);
3532
3805
  }
3533
3806
  }
3534
- if (!isScrollOrOndemand || (isScrollOrOndemand && this.options.getCurrentPage() === 1)) {
3807
+ if (!isScrollOrOndemand || (isScrollOrOndemand &&
3808
+ this.options.getCurrentPage && typeof this.options.getCurrentPage === 'function' &&
3809
+ this.options.getCurrentPage() === 1)) {
3535
3810
  this.gridContainer.addClass("show-msg");
3536
3811
  }
3537
3812
  } else {
@@ -3650,6 +3925,34 @@ $.widget('wm.datatable', {
3650
3925
  },
3651
3926
 
3652
3927
  _destroy: function () {
3928
+ // MEMORY LEAK FIX: Remove native event listener using stored bound function
3929
+ // if (this.gridElement && this.gridElement[0] && this.boundRowClickHandlerOnCapture) {
3930
+ // this.gridElement[0].removeEventListener('click', this.boundRowClickHandlerOnCapture, true);
3931
+ // this.boundRowClickHandlerOnCapture = null;
3932
+ // }
3933
+
3934
+ // MEMORY LEAK FIX: Remove all jQuery event handlers
3935
+ if (this.gridHeaderElement) {
3936
+ this.gridHeaderElement.off();
3937
+ }
3938
+ if (this.gridElement) {
3939
+ this.gridElement.off();
3940
+ }
3941
+ if (this.gridSearch) {
3942
+ this.gridSearch.off();
3943
+ }
3944
+ if (this.tableContainer) {
3945
+ this.tableContainer.off();
3946
+ }
3947
+ if (this.gridContainer) {
3948
+ this.gridContainer.off();
3949
+ }
3950
+
3951
+ // MEMORY LEAK FIX: Clear all internal structures
3952
+ this.preparedData = [];
3953
+ this.preparedHeaderData = [];
3954
+ this.compiledCellTemplates = {};
3955
+
3653
3956
  this.element.text('');
3654
3957
  window.clearTimeout(this.refreshGridTimeout);
3655
3958
  }