@xuda.io/runtime-bundle 1.0.1412 → 1.0.1414

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.
@@ -2188,7 +2188,7 @@ func.runtime.bind.build_datasource_changes = function (dsSessionP, currentRecord
2188
2188
  },
2189
2189
  };
2190
2190
  };
2191
- func.runtime.bind.resolve_field = async function (SESSION_ID, prog_id, dsSessionP, field_id) {
2191
+ func.runtime.bind.resolve_field = async function (SESSION_ID, prog_id, dsSessionP, field_id, iterate_info) {
2192
2192
  let _prog_id = prog_id;
2193
2193
  let _dsP = dsSessionP;
2194
2194
  let is_dynamic_field = false;
@@ -2204,7 +2204,20 @@ func.runtime.bind.resolve_field = async function (SESSION_ID, prog_id, dsSession
2204
2204
 
2205
2205
  if (['_FOR_VAL', '_FOR_KEY'].includes(field_id)) {
2206
2206
  is_dynamic_field = true;
2207
- field_prop = SESSION_OBJ[SESSION_ID]?.DS_GLB?.[_dsP]?.dynamic_fields?.[field_id];
2207
+ if (iterate_info && (iterate_info.iterator_val === field_id || iterate_info.iterator_key === field_id)) {
2208
+ const iter_value = iterate_info.iterator_val === field_id ? iterate_info._val : iterate_info._key;
2209
+ const toType = function (obj) {
2210
+ return {}.toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
2211
+ };
2212
+ field_prop = {
2213
+ id: field_id,
2214
+ data: { type: 'virtual', field_id },
2215
+ props: { fieldType: typeof iter_value !== 'undefined' ? toType(iter_value) : 'string' },
2216
+ value: iter_value,
2217
+ };
2218
+ } else {
2219
+ field_prop = SESSION_OBJ[SESSION_ID]?.DS_GLB?.[_dsP]?.dynamic_fields?.[field_id];
2220
+ }
2208
2221
  } else {
2209
2222
  field_prop = await find_in_view(field_id, _prog_id);
2210
2223
  if (!field_prop) {
@@ -2288,7 +2301,7 @@ func.runtime.bind.update_reference_source_array = async function (options) {
2288
2301
  if (field_type === 'object' && options.val_is_reference_field) {
2289
2302
  let obj_item = new_arr[arr_idx];
2290
2303
  let e_exp = options.expression_value.replace(options.bind_field_id, 'obj_item');
2291
- eval(e_exp + (options.input_field_type === 'string' ? `="${options.value}"` : `=${options.value}`));
2304
+ eval(e_exp + `=${JSON.stringify(options.value)}`);
2292
2305
  new_arr[arr_idx] = obj_item;
2293
2306
  } else {
2294
2307
  new_arr[arr_idx] = options.value;
@@ -4282,6 +4295,23 @@ func.datasource.execute = async function (SESSION_ID, dataSourceSession, IS_DATA
4282
4295
  let prog_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
4283
4296
 
4284
4297
  const callback_datasource = async function () {
4298
+ const run_on_load_events = async function () {
4299
+ if (!(await func.datasource.get_view_events_count(SESSION_ID, dataSourceSession, 'on_load'))) {
4300
+ return false;
4301
+ }
4302
+ await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'on_load');
4303
+ return true;
4304
+ };
4305
+ const schedule_panel_on_load_events = function () {
4306
+ setTimeout(async function () {
4307
+ try {
4308
+ await run_on_load_events();
4309
+ } catch (error) {
4310
+ console.error(error);
4311
+ }
4312
+ }, 0);
4313
+ };
4314
+
4285
4315
  if (typeof IS_WORKER === 'undefined' && typeof IS_DOCKER === 'undefined' && typeof IS_PROCESS_SERVER === 'undefined' && _ds.viewSourceProp === 'globals') {
4286
4316
  if (!['main'].includes(_session.opt.app_computing_mode)) {
4287
4317
  await func.index.call_worker(SESSION_ID, {
@@ -4291,10 +4321,13 @@ func.datasource.execute = async function (SESSION_ID, dataSourceSession, IS_DATA
4291
4321
  }
4292
4322
  }
4293
4323
 
4294
- if (await func.datasource.get_view_events_count(SESSION_ID, dataSourceSession, 'on_load')) {
4295
- await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'on_load');
4324
+ if (args.is_panelP) {
4325
+ const callback_ret = await func.datasource.callback(SESSION_ID, dataSourceSession, args.rowIdP, args.jobNoP, _ds.prog_id);
4326
+ schedule_panel_on_load_events();
4327
+ return callback_ret;
4296
4328
  }
4297
- // }
4329
+
4330
+ await run_on_load_events();
4298
4331
  return await func.datasource.callback(SESSION_ID, dataSourceSession, args.rowIdP, args.jobNoP, _ds.prog_id);
4299
4332
  };
4300
4333
 
@@ -5832,6 +5865,7 @@ func.datasource.update = async function (SESSION_ID, datasource_changes, update_
5832
5865
  dsSession_changed: findMin(datasource_changed),
5833
5866
  avoid_xu_for_refresh,
5834
5867
  trigger,
5868
+ ignore_screen_blocker: true,
5835
5869
  });
5836
5870
  // await removed from the below function cause to dead lock Mar 3 25
5837
5871
  await func.runtime.ui.refresh_screen({
@@ -10131,7 +10165,7 @@ func.UI.utils.get_panels_wrapper_from_dom = async function (SESSION_ID, $xu_embe
10131
10165
  const panel_keys = Object.keys(refresh_state?.panel_wrappers_cache || {});
10132
10166
  for (let index = 0; index < panel_keys.length; index++) {
10133
10167
  const panel_entry = refresh_state.panel_wrappers_cache[panel_keys[index]];
10134
- if (!$panel_entry?.$panel_div?.length || !$panel_entry.$panel_div[0]?.isConnected) {
10168
+ if (!panel_entry?.$panel_div?.length || !panel_entry.$panel_div[0]?.isConnected) {
10135
10169
  requires_rebuild = true;
10136
10170
  break;
10137
10171
  }
@@ -10584,15 +10618,52 @@ func.UI.worker.init = async function (SESSION_ID) {
10584
10618
  return elementBottom > viewportTop && elementTop < viewportBottom;
10585
10619
  };
10586
10620
 
10587
- const job_iterator = async function (run_mode = 'data') {
10588
- const contains_job_element = function ($parent, $child) {
10589
- const parent_node = $parent?.[0];
10590
- const child_node = $child?.[0];
10591
- if (!parent_node || !child_node) {
10592
- return false;
10621
+ if (!UI_WORKER_OBJ.in_flight_job_elements) {
10622
+ UI_WORKER_OBJ.in_flight_job_elements = [];
10623
+ }
10624
+
10625
+ const contains_job_element = function ($parent, $child) {
10626
+ const parent_node = $parent?.[0];
10627
+ const child_node = $child?.[0];
10628
+ if (!parent_node || !child_node) {
10629
+ return false;
10630
+ }
10631
+ return parent_node.contains(child_node);
10632
+ };
10633
+
10634
+ // Cancel in-flight child jobs when a parent element is being removed/hidden.
10635
+ // This prevents orphaned jobs whose DOM elements no longer exist.
10636
+ func.UI.worker.cancel_child_in_flight_jobs = function ($parent_element) {
10637
+ if (!$parent_element?.length || !UI_WORKER_OBJ.in_flight_job_elements.length) return;
10638
+ const parent_node = $parent_element[0];
10639
+ if (!parent_node) return;
10640
+ for (let i = UI_WORKER_OBJ.in_flight_job_elements.length - 1; i >= 0; i--) {
10641
+ const child_el = UI_WORKER_OBJ.in_flight_job_elements[i];
10642
+ const child_node = child_el?.[0];
10643
+ if (child_node && parent_node.contains(child_node)) {
10644
+ UI_WORKER_OBJ.in_flight_job_elements.splice(i, 1);
10593
10645
  }
10594
- return parent_node.contains(child_node);
10595
- };
10646
+ }
10647
+ };
10648
+
10649
+ const overlaps_any_in_flight = function (job) {
10650
+ if (!job?.elementP) {
10651
+ return false;
10652
+ }
10653
+ for (let index = 0; index < UI_WORKER_OBJ.in_flight_job_elements.length; index++) {
10654
+ const active_el = UI_WORKER_OBJ.in_flight_job_elements[index];
10655
+ if (!active_el) continue;
10656
+ // Block only when the new job is a CHILD of an in-flight element.
10657
+ // If the new job is a PARENT of an in-flight element, allow it to run
10658
+ // (e.g. xu-render hiding a container while a child panel's on_load is still running).
10659
+ if (contains_job_element(active_el, job.elementP)) {
10660
+ return true;
10661
+ }
10662
+ }
10663
+ return false;
10664
+ };
10665
+
10666
+ const job_iterator = async function (run_mode = 'data') {
10596
10667
  const mark_pending_delete_on_descendants = function ($element) {
10597
10668
  if (!$element?.length) {
10598
10669
  return;
@@ -10611,6 +10682,7 @@ func.UI.worker.init = async function (SESSION_ID) {
10611
10682
  };
10612
10683
  const dom_job_budget = run_mode === 'dom' ? UI_WORKER_OBJ.dom_jobs_per_frame : Number.POSITIVE_INFINITY;
10613
10684
  let dom_jobs_processed = 0;
10685
+ const dispatched_jobs = [];
10614
10686
 
10615
10687
  if (UI_WORKER_OBJ.active_jobs_count) {
10616
10688
  func.UI.worker.idle = 0;
@@ -10624,9 +10696,15 @@ func.UI.worker.init = async function (SESSION_ID) {
10624
10696
 
10625
10697
  if (run_mode === 'data' && job_lane !== 'data') continue;
10626
10698
  if (job_lane === 'dom' && dom_jobs_processed >= dom_job_budget) continue;
10699
+ // skip jobs whose element overlaps with any currently in-flight job (parent/child)
10700
+ // sibling containers can run in parallel
10701
+ if (overlaps_any_in_flight(val)) continue;
10627
10702
 
10628
10703
  if (!val.elementP) {
10629
- await func.UI.worker.execute(val.SESSION_ID, val);
10704
+ const job_promise = func.UI.worker.execute(val.SESSION_ID, val).catch(function (err) {
10705
+ console.error(err);
10706
+ });
10707
+ dispatched_jobs.push(job_promise);
10630
10708
  if (job_lane === 'dom') {
10631
10709
  dom_jobs_processed++;
10632
10710
  }
@@ -10646,67 +10724,48 @@ func.UI.worker.init = async function (SESSION_ID) {
10646
10724
  continue;
10647
10725
  }
10648
10726
 
10649
- // abort - if active job element exist in the current job ui (parent element changed)
10727
+ // keep overlapping parent/child jobs serialized; siblings can run in-flight together
10650
10728
  if (contains_job_element(val.elementP, running_job_obj.elementP)) {
10651
- if (val.functionP === 'execute_xu_render_attributes') {
10652
- mark_pending_delete_on_descendants(val.elementP);
10653
- func.UI.worker.mark_pending_delete_session(SESSION_ID);
10654
- await func.UI.worker.delete_job(SESSION_ID, running_job_obj.job_num);
10655
- break;
10656
- }
10729
+ continue;
10657
10730
  }
10658
10731
  }
10659
10732
 
10660
- // execute - if active job element not exist the current job ui and not detected as parent element
10661
- await func.UI.worker.execute(val.SESSION_ID, val);
10733
+ // Track this element as in-flight so future schedule_run passes skip overlapping jobs
10734
+ const in_flight_entry = val.elementP;
10735
+ UI_WORKER_OBJ.in_flight_job_elements.push(in_flight_entry);
10736
+
10737
+ // execute siblings/non-overlapping jobs — fire and forget, re-trigger scheduler on completion
10738
+ const job_promise = func.UI.worker.execute(val.SESSION_ID, val).catch(function (err) {
10739
+ console.error(err);
10740
+ }).finally(function () {
10741
+ // remove this element from in-flight tracking
10742
+ const idx = UI_WORKER_OBJ.in_flight_job_elements.indexOf(in_flight_entry);
10743
+ if (idx !== -1) {
10744
+ UI_WORKER_OBJ.in_flight_job_elements.splice(idx, 1);
10745
+ }
10746
+ // re-trigger scheduler so blocked sibling jobs can now run
10747
+ func.UI.worker.schedule_run(0);
10748
+ });
10749
+ dispatched_jobs.push(job_promise);
10662
10750
  if (job_lane === 'dom') {
10663
10751
  dom_jobs_processed++;
10664
10752
  }
10665
10753
  continue;
10666
-
10667
- // active_job_children_elements = [];
10668
- // if (val.elementP) {
10669
- // // get active_job children elements only if job handle ui job (not update)
10670
- // $.each(val.elementP.find('*'), function (key, val) {
10671
- // const xu_ui_id = val.elementP.attr('xu-ui-id');
10672
- // if (!xu_ui_id) return true;
10673
- // active_job_children_elements.push(xu_ui_id);
10674
- // });
10675
- // func.UI.worker.execute(val.SESSION_ID, val);
10676
- // continue;
10677
- // } else {
10678
- // // non ui job
10679
- // await func.UI.worker.execute(val.SESSION_ID, val);
10680
- // break;
10681
- // }
10682
-
10683
- // if (UI_WORKER_OBJ.jobs.length > 1 && val.elementP) {
10684
- // const xu_ui_id = val.elementP.attr('xu-ui-id');
10685
- // if (!xu_ui_id) continue;
10686
- // // skip - if job element exist in the active job ui as child element
10687
- // if (active_job_children_elements.includes(xu_ui_id)) {
10688
- // continue;
10689
- // }
10690
- // // abort - if active job element exist in the current job ui (parent element changed)
10691
- // $.each(val.elementP.find('*'), function (key, val) {
10692
- // const xu_ui_id = val.elementP.attr('xu-ui-id');
10693
- // if (!xu_ui_id) return true;
10694
- // active_job_children_elements.push(xu_ui_id);
10695
- // });
10696
- // // execute - if active job element not exist the current job ui and not detected as parent element
10697
- // const active_job = UI_WORKER_OBJ.jobs[0];
10698
- // }
10699
-
10700
- // break;
10701
10754
  } catch (err) {
10702
10755
  console.error(err);
10703
10756
  }
10704
10757
  }
10758
+
10759
+ // Only await jobs that have no element (data jobs) — don't block on long-running UI jobs
10760
+ // UI jobs with elements will re-trigger schedule_run when they complete
10705
10761
  }
10706
10762
  };
10707
10763
  func.UI.worker.schedule_run = function (delay = 0, preferred_mode) {
10708
10764
  func.UI.worker.ensure_runtime_indexes();
10709
10765
  if (UI_WORKER_OBJ.run_in_progress) {
10766
+ // Allow re-entry: even though a scan is in progress, mark run_again
10767
+ // so the scheduler re-scans after the current pass. This lets sibling
10768
+ // jobs that were queued after the scan started get picked up quickly.
10710
10769
  UI_WORKER_OBJ.run_again = true;
10711
10770
  return;
10712
10771
  }
@@ -10952,8 +11011,69 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
10952
11011
  }
10953
11012
  return $root.find(`[xu-ui-id="${xu_ui_id}"]`);
10954
11013
  },
10955
- get_live_element_context: function (elem_key, fallback_$elm) {
10956
- const $elm = elem_key ? fx.get_element_by_ui_id(elem_key) : fallback_$elm;
11014
+ get_live_element_context: function (elem_key, fallback_$elm, context = {}) {
11015
+ let raw_$elm = elem_key ? fx.get_element_by_ui_id(elem_key) : fallback_$elm;
11016
+ let $elm = func.runtime?.ui?.get_preferred_live_element ? func.runtime.ui.get_preferred_live_element(raw_$elm) : raw_$elm;
11017
+
11018
+ if (context.node_id) {
11019
+ const $root = func.UI.worker.get_session_root(SESSION_ID);
11020
+ const runtime_nodes = func.runtime?.ui?.get_refresh_index_elements
11021
+ ? func.runtime.ui.get_refresh_index_elements(SESSION_ID, $root).toArray()
11022
+ : $root.find('[xu-ui-id]').toArray();
11023
+ let matching_nodes = [];
11024
+
11025
+ for (let index = 0; index < runtime_nodes.length; index++) {
11026
+ const node = runtime_nodes[index];
11027
+ const node_data = func.runtime.ui.get_data(node);
11028
+ const node_xu_data = node_data?.xuData;
11029
+ if (!node_xu_data) {
11030
+ continue;
11031
+ }
11032
+ if (node_xu_data.nodeid !== context.node_id) {
11033
+ continue;
11034
+ }
11035
+ if (context.recordid && node_xu_data.recordid !== context.recordid) {
11036
+ continue;
11037
+ }
11038
+ if (context.prog_id && node_xu_data.paramsP?.prog_id !== context.prog_id) {
11039
+ continue;
11040
+ }
11041
+ if (context.parent_element_ui_id && node_xu_data.parent_element_ui_id !== context.parent_element_ui_id) {
11042
+ continue;
11043
+ }
11044
+ if (node_xu_data.pending_to_delete) {
11045
+ continue;
11046
+ }
11047
+ matching_nodes.push(node);
11048
+ }
11049
+
11050
+ if (!matching_nodes.length) {
11051
+ for (let index = 0; index < runtime_nodes.length; index++) {
11052
+ const node = runtime_nodes[index];
11053
+ const node_data = func.runtime.ui.get_data(node);
11054
+ const node_xu_data = node_data?.xuData;
11055
+ if (!node_xu_data) {
11056
+ continue;
11057
+ }
11058
+ if (node_xu_data.nodeid !== context.node_id) {
11059
+ continue;
11060
+ }
11061
+ if (context.recordid && node_xu_data.recordid !== context.recordid) {
11062
+ continue;
11063
+ }
11064
+ if (node_xu_data.pending_to_delete) {
11065
+ continue;
11066
+ }
11067
+ matching_nodes.push(node);
11068
+ }
11069
+ }
11070
+
11071
+ if (matching_nodes.length) {
11072
+ raw_$elm = $(matching_nodes);
11073
+ $elm = func.runtime?.ui?.get_preferred_live_element ? func.runtime.ui.get_preferred_live_element(raw_$elm) : raw_$elm;
11074
+ }
11075
+ }
11076
+
10957
11077
  if (!$elm?.length) {
10958
11078
  return {
10959
11079
  $elm: $(),
@@ -11008,6 +11128,13 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11008
11128
  'xu-for': true,
11009
11129
  'xu-exp:xu-bind': true,
11010
11130
  };
11131
+ const raw_value_attributes = {
11132
+ 'xu-bind': true,
11133
+ 'xu-ref': true,
11134
+ 'xu-on': true,
11135
+ 'xu-for-key': true,
11136
+ 'xu-for-val': true,
11137
+ };
11011
11138
 
11012
11139
  const execution_plan = [];
11013
11140
  for (let index = 0; index < (refresh_attributes || []).length; index++) {
@@ -11023,7 +11150,7 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11023
11150
  attr_new,
11024
11151
  xu_func,
11025
11152
  is_regular_attribute: !!(attr_new && attr_new.substr(0, 2) !== 'xu'),
11026
- requires_expression: attr !== 'xu-class' && attr !== 'xu-ui-plugin',
11153
+ requires_expression: !raw_value_attributes[attr] && attr !== 'xu-class' && attr !== 'xu-ui-plugin',
11027
11154
  regular_attr_name: attr_new ? (attr_new !== 'viewBox' ? attr_new.toLowerCase() : attr_new) : null,
11028
11155
  });
11029
11156
  }
@@ -11099,23 +11226,25 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11099
11226
  },
11100
11227
  execute_xu_render_attributes: async function () {
11101
11228
  const perf_end = func.runtime?.perf?.start?.(SESSION_ID, 'execute_xu_render_attributes');
11102
- const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj.paramsP?.elem_val?.$elm);
11229
+ const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj.paramsP?.elem_val?.$elm, queue_obj.paramsP);
11103
11230
  const _data = live_context.data;
11104
11231
  try {
11105
11232
  if (_data?.xuData?.paramsP) {
11233
+ const live_xu_data = _data.xuData;
11106
11234
  await func.runtime.render.execute_xu_function({
11107
11235
  SESSION_ID,
11108
11236
  is_skeleton: null,
11109
- $root_container: _data.xuData.$root_container,
11110
- nodeP: _data.xuData.node,
11111
- $container: fx.get_element_by_ui_id(_data.xuData.parent_element_ui_id),
11112
- paramsP: _data.xuData.paramsP,
11113
- parent_infoP: {},
11237
+ $root_container: live_xu_data.$root_container,
11238
+ nodeP: live_xu_data.node,
11239
+ $container: fx.get_element_by_ui_id(live_xu_data.parent_element_ui_id) || live_xu_data.$container,
11240
+ paramsP: live_xu_data.paramsP,
11241
+ parent_infoP: live_xu_data.iterate_info ? { iterate_info: live_xu_data.iterate_info } : {},
11114
11242
  jobNoP: queue_obj.jobNoP,
11115
- keyP: _data.xuData.key,
11116
- parent_nodeP: _data.xuData.parent_node,
11243
+ keyP: live_xu_data.key,
11244
+ parent_nodeP: live_xu_data.parent_node,
11117
11245
  xu_func: 'xu-render',
11118
11246
  $elm: live_context.$elm,
11247
+ $live_elm: live_context.$elm,
11119
11248
  val: {
11120
11249
  key: 'xu-render',
11121
11250
  value: queue_obj.paramsP.attr_value,
@@ -11138,7 +11267,7 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11138
11267
  };
11139
11268
 
11140
11269
  try {
11141
- const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj.paramsP?.elem_val?.$elm);
11270
+ const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj.paramsP?.elem_val?.$elm, queue_obj.paramsP);
11142
11271
  const $elm = live_context.$elm;
11143
11272
  const live_data = live_context.data;
11144
11273
  const elm_node = $elm?.[0];
@@ -11151,18 +11280,20 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11151
11280
  const refresh_attributes = queue_obj.paramsP?.elem_val?.attributes || [];
11152
11281
  const execution_plan = fx.get_refresh_execution_plan(live_data, refresh_attributes);
11153
11282
  const expression_results_cache = {};
11283
+ const live_parent_container = fx.get_element_by_ui_id(live_xu_data.parent_element_ui_id);
11154
11284
  const xu_execution_context = {
11155
11285
  SESSION_ID,
11156
11286
  is_skeleton: null,
11157
11287
  $root_container: live_xu_data.$root_container,
11158
11288
  nodeP: live_xu_data.node,
11159
- $container: live_xu_data.$container,
11289
+ $container: live_parent_container?.length ? live_parent_container : live_xu_data.$container,
11160
11290
  paramsP: live_xu_data.paramsP,
11161
- parent_infoP: {},
11291
+ parent_infoP: live_xu_data.iterate_info ? { iterate_info: live_xu_data.iterate_info } : {},
11162
11292
  jobNoP: queue_obj.jobNoP,
11163
- keyP: null,
11164
- parent_nodeP: null,
11293
+ keyP: live_xu_data.key,
11294
+ parent_nodeP: live_xu_data.parent_node,
11165
11295
  $elm,
11296
+ $live_elm: $elm,
11166
11297
  get_params_obj_new: func.runtime.program.get_params_obj,
11167
11298
  };
11168
11299
  const handler_bundle = func.runtime.render.build_xu_handlers(xu_execution_context, SESSION_OBJ[SESSION_ID].DS_GLB[live_xu_data.paramsP.dsSessionP]);
@@ -11217,7 +11348,7 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11217
11348
  execute_xu_for: async function () {
11218
11349
  const perf_end = func.runtime?.perf?.start?.(SESSION_ID, 'execute_xu_for');
11219
11350
  try {
11220
- const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj?.paramsP?.elem_val?.$elm);
11351
+ const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj?.paramsP?.elem_val?.$elm, queue_obj.paramsP);
11221
11352
  var $elm = live_context.$elm; // $(SESSION_OBJ[SESSION_ID].root_element).find(`[xu-ui-id=${queue_obj.paramsP.elem_key}]`)
11222
11353
 
11223
11354
  if (!$elm?.length) {
@@ -11294,7 +11425,7 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11294
11425
  },
11295
11426
  execute_xu_widget: async function () {
11296
11427
  try {
11297
- const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj?.paramsP?.elem_val?.$elm);
11428
+ const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj?.paramsP?.elem_val?.$elm, queue_obj.paramsP);
11298
11429
  var $elm = live_context.$elm; // $(SESSION_OBJ[SESSION_ID].root_element).find(`[xu-ui-id=${queue_obj.paramsP.elem_key}]`)
11299
11430
 
11300
11431
  if (!$elm?.length) {
@@ -11323,7 +11454,16 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11323
11454
  },
11324
11455
  };
11325
11456
 
11326
- return await fx[queue_obj.functionP]();
11457
+ try {
11458
+ return await fx[queue_obj.functionP]();
11459
+ } catch (error) {
11460
+ console.error(error);
11461
+ const failed_job_index = func.UI.worker.find_job_index(SESSION_ID, queue_obj.job_num);
11462
+ if (failed_job_index !== null && typeof failed_job_index !== 'undefined' && UI_WORKER_OBJ.jobs[failed_job_index]) {
11463
+ await func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
11464
+ }
11465
+ return null;
11466
+ }
11327
11467
  };
11328
11468
  func.UI.worker.find_job_index = function (SESSION_ID, jobNoP) {
11329
11469
  var ret = null;
@@ -12027,7 +12167,7 @@ func.runtime.ui.init_screen = async function (options) {
12027
12167
  $rootFrame.css('display', 'contents');
12028
12168
  }
12029
12169
 
12030
- func.UI.utils.indicator.screen.busy();
12170
+ if (!is_panelP) func.UI.utils.indicator.screen.busy();
12031
12171
 
12032
12172
  const ret = await func.datasource.create(
12033
12173
  SESSION_ID,
@@ -12043,7 +12183,7 @@ func.runtime.ui.init_screen = async function (options) {
12043
12183
  null,
12044
12184
  null,
12045
12185
  null,
12046
- null,
12186
+ is_panelP,
12047
12187
  parameters_obj_inP,
12048
12188
  );
12049
12189
 
@@ -12070,7 +12210,7 @@ func.runtime.ui.init_screen = async function (options) {
12070
12210
  if (!node.length) return console.warn('ui node empty');
12071
12211
  const ret_render_$container = await func.runtime.render.render_ui_tree(SESSION_ID, $rootFrame, node[0], null, params, jobNoP, null, null, null, null, null, $rootFrame);
12072
12212
 
12073
- func.UI.utils.indicator.screen.normal();
12213
+ if (!is_panelP) func.UI.utils.indicator.screen.normal();
12074
12214
 
12075
12215
  return await func.runtime.ui.screen_loading_done({
12076
12216
  SESSION_ID,
@@ -12595,6 +12735,7 @@ func.runtime.render = func.runtime.render || {};
12595
12735
  // Browser-only panel rendering helpers live here so generic view rendering can stay focused.
12596
12736
 
12597
12737
  func.runtime.ui.render_panel_node = async function (options) {
12738
+
12598
12739
  const $wrapper = $('<div>');
12599
12740
  const $div = await func.runtime.ui.create_container({
12600
12741
  SESSION_ID: options.SESSION_ID,
@@ -13168,7 +13309,7 @@ func.runtime.ui.normalize_refresh_field_reference = function (field_ref) {
13168
13309
 
13169
13310
  return [...normalized].filter(Boolean);
13170
13311
  };
13171
- func.runtime.ui.extract_expression_refresh_fields = function (elm_data, expression_text, without_var) {
13312
+ func.runtime.ui.extract_expression_refresh_fields = function (elm_data, expression_text, without_var, _visited) {
13172
13313
  const fields = new Set();
13173
13314
  if (!expression_text) {
13174
13315
  return fields;
@@ -13199,8 +13340,12 @@ func.runtime.ui.extract_expression_refresh_fields = function (elm_data, expressi
13199
13340
 
13200
13341
  const parameters_raw_obj = elm_data?.xuData?.paramsP?.parameters_raw_obj || {};
13201
13342
  const parameter_keys = Object.keys(parameters_raw_obj);
13343
+ const visited = _visited || new Set();
13202
13344
  for (let index = 0; index < parameter_keys.length; index++) {
13203
13345
  const param_key = parameter_keys[index];
13346
+ if (visited.has(param_key)) {
13347
+ continue;
13348
+ }
13204
13349
  const param_val = parameters_raw_obj[param_key];
13205
13350
  if (!param_val?.includes?.('@')) {
13206
13351
  continue;
@@ -13209,7 +13354,8 @@ func.runtime.ui.extract_expression_refresh_fields = function (elm_data, expressi
13209
13354
  if (!text.includes(param_token)) {
13210
13355
  continue;
13211
13356
  }
13212
- const nested_fields = func.runtime.ui.extract_expression_refresh_fields(elm_data, param_val, false);
13357
+ visited.add(param_key);
13358
+ const nested_fields = func.runtime.ui.extract_expression_refresh_fields(elm_data, param_val, false, visited);
13213
13359
  nested_fields.forEach(function (field_id) {
13214
13360
  add_field(field_id);
13215
13361
  });
@@ -13509,6 +13655,41 @@ func.runtime.ui.find_refresh_elements_by_attr = function ($root, attr_name, attr
13509
13655
 
13510
13656
  return $(elements);
13511
13657
  };
13658
+ func.runtime.ui.get_preferred_live_element = function (target) {
13659
+ const elements = func.runtime.ui.as_jquery(target).toArray();
13660
+ if (!elements.length) {
13661
+ return $();
13662
+ }
13663
+
13664
+ let best_node = null;
13665
+ let best_score = -Infinity;
13666
+ for (let index = 0; index < elements.length; index++) {
13667
+ const node = elements[index];
13668
+ if (!node) {
13669
+ continue;
13670
+ }
13671
+ const node_data = func.runtime.ui.get_data(node);
13672
+ let score = 0;
13673
+ if (node.isConnected) {
13674
+ score += 100;
13675
+ }
13676
+ if (!node_data?.xuData?.pending_to_delete) {
13677
+ score += 50;
13678
+ }
13679
+ if (node.getClientRects?.().length) {
13680
+ score += 25;
13681
+ }
13682
+ if (!node.hidden) {
13683
+ score += 10;
13684
+ }
13685
+ if (score >= best_score) {
13686
+ best_score = score;
13687
+ best_node = node;
13688
+ }
13689
+ }
13690
+
13691
+ return best_node ? $(best_node) : $(elements[elements.length - 1]);
13692
+ };
13512
13693
  func.runtime.ui.get_refresh_indexed_element_by_ui_id = function (SESSION_ID, selector_id) {
13513
13694
  const state = func.runtime.ui.ensure_refresh_dependency_state(SESSION_ID);
13514
13695
  if (!state) {
@@ -13519,12 +13700,14 @@ func.runtime.ui.get_refresh_indexed_element_by_ui_id = function (SESSION_ID, sel
13519
13700
  }
13520
13701
 
13521
13702
  let $elm = state.elements_by_ui_id?.[selector_id];
13703
+ $elm = func.runtime.ui.get_preferred_live_element($elm);
13522
13704
  if ($elm?.length && $elm[0]?.isConnected) {
13705
+ state.elements_by_ui_id[selector_id] = $elm;
13523
13706
  return $elm;
13524
13707
  }
13525
13708
 
13526
13709
  const $root = func.runtime.ui.get_refresh_index_root(SESSION_ID);
13527
- $elm = func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-ui-id', selector_id, true);
13710
+ $elm = func.runtime.ui.get_preferred_live_element(func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-ui-id', selector_id));
13528
13711
  if ($elm?.length) {
13529
13712
  state.elements_by_ui_id[selector_id] = $elm;
13530
13713
  state.runtime_elements_cache = null;
@@ -13628,12 +13811,14 @@ func.runtime.ui.get_refresh_indexed_panel_wrapper_by_id = function (SESSION_ID,
13628
13811
  }
13629
13812
 
13630
13813
  let $elm = state.panel_wrappers_by_id?.[wrapper_id];
13814
+ $elm = func.runtime.ui.get_preferred_live_element($elm);
13631
13815
  if ($elm?.length && $elm[0]?.isConnected) {
13816
+ state.panel_wrappers_by_id[wrapper_id] = $elm;
13632
13817
  return $elm;
13633
13818
  }
13634
13819
 
13635
13820
  const $root = func.runtime.ui.get_refresh_index_root(SESSION_ID);
13636
- $elm = func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-panel-wrapper-id', wrapper_id, true);
13821
+ $elm = func.runtime.ui.get_preferred_live_element(func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-panel-wrapper-id', wrapper_id));
13637
13822
  if ($elm?.length) {
13638
13823
  state.panel_wrappers_by_id[wrapper_id] = $elm;
13639
13824
  state.panel_wrapper_elements_cache = null;
@@ -13865,6 +14050,10 @@ func.runtime.ui.build_refresh_job_obj = function (options, elem_key, elem_val, e
13865
14050
  SESSION_ID: options.SESSION_ID,
13866
14051
  fields_arr: options.fields_arr,
13867
14052
  elem_key,
14053
+ node_id: elm_data?.xuData?.nodeid,
14054
+ recordid: elm_data?.xuData?.recordid,
14055
+ parent_element_ui_id: elm_data?.xuData?.parent_element_ui_id,
14056
+ prog_id: elm_data?.xuData?.paramsP?.prog_id,
13868
14057
  elem_val: {
13869
14058
  attributes: [...(elem_val.attributes || [])],
13870
14059
  },
@@ -14189,7 +14378,7 @@ func.runtime.render = func.runtime.render || {};
14189
14378
  // Browser-only xu-attribute refresh helpers live here so screen refresh orchestration can stay focused.
14190
14379
 
14191
14380
  func.runtime.ui.refresh_xu_attributes = async function (options) {
14192
- if (options.trigger !== 'click' && !_.isEmpty(SCREEN_BLOCKER_OBJ)) {
14381
+ if (!options.ignore_screen_blocker && options.trigger !== 'click' && !_.isEmpty(SCREEN_BLOCKER_OBJ)) {
14193
14382
  setTimeout(() => {
14194
14383
  func.runtime.ui.refresh_xu_attributes(options);
14195
14384
  }, 100);
@@ -16710,11 +16899,19 @@ func.runtime.render.handle_xu_ref = async function (options) {
16710
16899
  func.runtime.render.handle_xu_bind = async function (options) {
16711
16900
  if (options.is_skeleton) return {};
16712
16901
 
16713
- const elm_data = func.runtime.ui.get_data(options.$elm);
16902
+ const $elm = func.runtime?.ui?.get_preferred_live_element ? func.runtime.ui.get_preferred_live_element(options.$elm) : func.runtime.ui.as_jquery(options.$elm);
16903
+ const elm_data = func.runtime.ui.get_data($elm);
16714
16904
  const xuData = elm_data?.xuData;
16905
+ const bind_expression =
16906
+ typeof options?.val?.value === 'string'
16907
+ ? options.val.value
16908
+ : elm_data?.xuAttributes?.['xu-bind'] || func.runtime.ui.get_attr($elm, 'xu-bind');
16715
16909
  if (!xuData?.paramsP) {
16716
16910
  return {};
16717
16911
  }
16912
+ if (!bind_expression) {
16913
+ return {};
16914
+ }
16718
16915
 
16719
16916
  let val_is_reference_field = false;
16720
16917
  let _prog_id = xuData.paramsP.prog_id;
@@ -16722,10 +16919,94 @@ func.runtime.render.handle_xu_bind = async function (options) {
16722
16919
  let is_dynamic_field = false;
16723
16920
  let field_prop;
16724
16921
  let bind_field_id;
16725
- const input_field_type = options.$elm?.[0]?.type || func.runtime.ui.get_attr(options.$elm, 'type');
16922
+ const input_field_type = $elm?.[0]?.type || func.runtime.ui.get_attr($elm, 'type');
16923
+ const bind_ui_id = xuData.ui_id || func.runtime.ui.get_attr($elm, 'xu-ui-id');
16924
+ const get_bind_targets = function () {
16925
+ const $root = func.runtime.ui.get_refresh_index_root?.(options.SESSION_ID) || func.runtime.ui.get_root_element?.(options.SESSION_ID) || $('body');
16926
+ const live_targets = [];
16927
+ const seen_nodes = new Set();
16928
+ const add_candidate = function (node) {
16929
+ if (!node || seen_nodes.has(node)) {
16930
+ return;
16931
+ }
16932
+ const target_data = func.runtime.ui.get_data(node);
16933
+ if (!node.isConnected || target_data?.xuData?.pending_to_delete) {
16934
+ return;
16935
+ }
16936
+ seen_nodes.add(node);
16937
+ live_targets.push(node);
16938
+ };
16939
+
16940
+ if (bind_ui_id && options.SESSION_ID && func.runtime?.ui?.find_refresh_elements_by_attr) {
16941
+ const ui_id_targets = func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-ui-id', bind_ui_id).toArray();
16942
+ for (let index = 0; index < ui_id_targets.length; index++) {
16943
+ add_candidate(ui_id_targets[index]);
16944
+ }
16945
+ }
16946
+
16947
+ const runtime_nodes = func.runtime?.ui?.get_refresh_index_elements
16948
+ ? func.runtime.ui.get_refresh_index_elements(options.SESSION_ID, $root).toArray()
16949
+ : $root.find('[xu-ui-id]').toArray();
16950
+ const target_parent_ui_id = xuData.parent_element_ui_id;
16951
+ const target_node_id = xuData.nodeid;
16952
+ const target_recordid = xuData.recordid;
16953
+ const target_prog_id = xuData.paramsP?.prog_id;
16954
+ for (let index = 0; index < runtime_nodes.length; index++) {
16955
+ const node = runtime_nodes[index];
16956
+ const node_data = func.runtime.ui.get_data(node);
16957
+ const node_xu_data = node_data?.xuData;
16958
+ if (!node_xu_data) {
16959
+ continue;
16960
+ }
16961
+ if (target_node_id && node_xu_data.nodeid !== target_node_id) {
16962
+ continue;
16963
+ }
16964
+ if (target_parent_ui_id && node_xu_data.parent_element_ui_id !== target_parent_ui_id) {
16965
+ continue;
16966
+ }
16967
+ if (target_prog_id && node_xu_data.paramsP?.prog_id !== target_prog_id) {
16968
+ continue;
16969
+ }
16970
+ if (typeof target_recordid !== 'undefined' && target_recordid !== null && node_xu_data.recordid !== target_recordid) {
16971
+ continue;
16972
+ }
16973
+ const node_bind_expression = node_data?.xuAttributes?.['xu-bind'] || func.runtime.ui.get_attr(node, 'xu-bind');
16974
+ if (node_bind_expression !== bind_expression) {
16975
+ continue;
16976
+ }
16977
+ add_candidate(node);
16978
+ }
16979
+
16980
+ if (!live_targets.length) {
16981
+ return $elm;
16982
+ }
16983
+
16984
+ for (let index = 0; index < live_targets.length; index++) {
16985
+ const node = live_targets[index];
16986
+ const target_data = func.runtime.ui.get_data(node);
16987
+ if (!node?.isConnected || target_data?.xuData?.pending_to_delete) {
16988
+ continue;
16989
+ }
16990
+ add_candidate(node);
16991
+ }
16992
+
16993
+ if (!live_targets.length) {
16994
+ return $elm;
16995
+ }
16996
+
16997
+ const target_nodes = live_targets;
16998
+ const visible_targets = [];
16999
+ for (let index = 0; index < target_nodes.length; index++) {
17000
+ const node = target_nodes[index];
17001
+ if (node.getClientRects?.().length && !node.hidden) {
17002
+ visible_targets.push(node);
17003
+ }
17004
+ }
17005
+ return $(visible_targets.length ? visible_targets : live_targets);
17006
+ };
16726
17007
 
16727
17008
  try {
16728
- const bind_field = await func.runtime.bind.resolve_field(options.SESSION_ID, _prog_id, _dsP, options.val.value.split('.')[0]);
17009
+ const bind_field = await func.runtime.bind.resolve_field(options.SESSION_ID, _prog_id, _dsP, bind_expression.split('.')[0], xuData.iterate_info);
16729
17010
  bind_field_id = bind_field.bind_field_id;
16730
17011
  field_prop = bind_field.field_prop;
16731
17012
  is_dynamic_field = bind_field.is_dynamic_field;
@@ -16774,38 +17055,85 @@ func.runtime.render.handle_xu_bind = async function (options) {
16774
17055
  field_prop,
16775
17056
  val_is_reference_field,
16776
17057
  input_field_type,
16777
- expression_value: options.val.value,
17058
+ expression_value: bind_expression,
16778
17059
  value,
16779
17060
  });
16780
17061
 
16781
17062
  await func.datasource.update_changes_for_out_parameter(options.SESSION_ID, _dsP, _ds.parentDataSourceNo);
16782
17063
  };
16783
17064
 
16784
- bind.listener(options.$elm[0], field_changed);
17065
+ const bind_targets = get_bind_targets();
17066
+ const target_nodes = bind_targets.toArray();
17067
+ for (let index = 0; index < target_nodes.length; index++) {
17068
+ const target = target_nodes[index];
17069
+ const target_data = func.runtime.ui.get_data(target);
17070
+ if (!target_data?.xuData || target_data.xuData.bind_listener_attached) {
17071
+ continue;
17072
+ }
17073
+ bind.listener(target, field_changed);
17074
+ target_data.xuData.bind_listener_attached = true;
17075
+ }
16785
17076
 
16786
- const set_value = function () {
16787
- const _ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[options.paramsP.dsSessionP];
16788
- if (!_ds.currentRecordId) return;
17077
+ const set_value = async function () {
17078
+ const _ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[_dsP];
17079
+ if (!_ds) return;
17080
+ const target_record_id = xuData.recordid || _ds.currentRecordId;
17081
+ if (!target_record_id) return;
16789
17082
  let value;
16790
17083
  try {
16791
17084
  if (val_is_reference_field) {
16792
- value = func.runtime.bind.get_source_value(_ds, bind_field_id, is_dynamic_field);
16793
- value = func.runtime.bind.format_display_value(options.$elm, field_prop, bind_field_id, options.val.value, value, input_field_type);
17085
+ const iter = xuData.iterate_info;
17086
+ if (iter && is_dynamic_field && (iter.iterator_val === bind_field_id || iter.iterator_key === bind_field_id)) {
17087
+ value = iter.iterator_val === bind_field_id ? iter._val : iter._key;
17088
+ } else {
17089
+ const resolved_value = await func.datasource.get_value(options.SESSION_ID, bind_field_id, _dsP, target_record_id);
17090
+ if (resolved_value?.found) {
17091
+ value = resolved_value.ret.value;
17092
+ } else {
17093
+ value = func.runtime.bind.get_source_value(_ds, bind_field_id, is_dynamic_field);
17094
+ }
17095
+ }
17096
+ value = func.runtime.bind.format_display_value($elm, field_prop, bind_field_id, bind_expression, value, input_field_type);
16794
17097
  } else {
16795
- value = options.val.value;
17098
+ value = bind_expression;
16796
17099
  }
16797
17100
  if (typeof value === 'undefined') return;
16798
- bind.setter(options.$elm[0], value);
17101
+
17102
+ const live_bind_targets = get_bind_targets().toArray();
17103
+ for (let index = 0; index < live_bind_targets.length; index++) {
17104
+ const elm = live_bind_targets[index];
17105
+ if (!elm) {
17106
+ continue;
17107
+ }
17108
+
17109
+ bind.setter(elm, value);
17110
+ const target_input_type = elm.type || input_field_type;
17111
+ switch (target_input_type) {
17112
+ case 'radio':
17113
+ elm.checked = elm.value === _.toString(value);
17114
+ break;
17115
+ case 'checkbox':
17116
+ elm.checked = !!value;
17117
+ break;
17118
+ default:
17119
+ if (typeof elm.value !== 'undefined') {
17120
+ elm.value = value === null ? '' : _.toString(value);
17121
+ }
17122
+ break;
17123
+ }
17124
+ }
16799
17125
  } catch (err) {
16800
17126
  console.error(err);
16801
17127
  }
16802
17128
  };
16803
17129
 
16804
- $('body').on('xu-bind-refresh.' + options.ds.dsSession.toString(), () => {
16805
- set_value();
17130
+ const bind_refresh_event = 'xu-bind-refresh.' + _dsP.toString();
17131
+ const bind_refresh_namespace = `.ui${bind_ui_id || bind_field_id || 'bind'}`;
17132
+ $('body').off(bind_refresh_namespace).on(bind_refresh_event + bind_refresh_namespace, async () => {
17133
+ await set_value();
16806
17134
  });
16807
17135
 
16808
- set_value();
17136
+ await set_value();
16809
17137
  return {};
16810
17138
  };
16811
17139
  func.runtime.render.apply_xu_class = async function (options) {
@@ -17117,7 +17445,11 @@ func.runtime.render.handle_modern_xu_render = async function (options) {
17117
17445
  };
17118
17446
 
17119
17447
  const post_render = async function () {
17120
- const nodeP = func.runtime.ui.get_data(options.$container).xuData.node.children[options.keyP];
17448
+ const container_data = func.runtime.ui.get_data(options.$container);
17449
+ if (!container_data?.xuData?.node?.children?.[options.keyP]) {
17450
+ return;
17451
+ }
17452
+ const nodeP = container_data.xuData.node.children[options.keyP];
17121
17453
  nodeP.xu_render_made = value;
17122
17454
  if (value) {
17123
17455
  try {
@@ -17163,12 +17495,23 @@ func.runtime.render.handle_modern_xu_render = async function (options) {
17163
17495
  }
17164
17496
 
17165
17497
  func.runtime.render.insert_ordered_child(options.$container, new_$div, options.keyP);
17498
+ // Remove the XURENDER placeholder now that the real content has been inserted.
17499
+ if (options.$elm?.[0]?.tagName === 'XURENDER') {
17500
+ func.runtime.ui.remove(options.$elm);
17501
+ }
17166
17502
  } catch (error) {
17167
17503
  func.events.delete_job(options.SESSION_ID, options.jobNoP);
17168
17504
  }
17169
17505
  return;
17170
17506
  }
17171
17507
 
17508
+ // Cancel any in-flight child jobs before removing the element.
17509
+ // This prevents orphaned jobs (e.g. a child panel's long-running on_load delay)
17510
+ // from blocking future scheduler passes after the parent element is gone.
17511
+ if (func.UI?.worker?.cancel_child_in_flight_jobs) {
17512
+ func.UI.worker.cancel_child_in_flight_jobs(options.$elm);
17513
+ }
17514
+
17172
17515
  const xu_ui_id = func.runtime.ui.get_attr(options.$elm, 'xu-ui-id');
17173
17516
  const exclude_fields = func.runtime.render.get_xu_render_exclude_fields(options.$elm);
17174
17517
  const cache_str = await func.runtime.render.get_xu_render_cache_str(