在Sage Intacct中使用jQuery事件监听器时自动生成字段被清除的问题求助
Problem Description
I'm working on a Sage Intacct customization using jQuery to implement real-time table updates: when users enter quantity and price, the system calculates and updates the total amount field automatically. This works perfectly for single rows, but I'm hitting a bug with multi-row scenarios:
- When adding a new row
- First selecting a project pulled from an API
- Then selecting an item from the corresponding combobox
The auto-populated fields (like state, opportunity type, vertical) get cleared, while manually entered fields (quantity/price) remain intact. Strangely, this only happens when the task field is updated via the item selection logic, even though the task update code follows the same pattern as other auto-population logic.
Code Snippets
Quantity/Price/Total Change Listeners
jq('#' + qtyid).on('change', function() { var qty = jq('#' + qtyid)[0].value; var rate = jq('#' + rateid)[0].value; var oriamt = jq('#' + amtid)[0].value; var totamnt = jq('#' + totalid)[0].value; var newamt = 0; if(qty == '') qty = 0; if(rate == '') rate = 0; try{ qty = parseFloat(qty.replace(',','')); }catch(e){ qty = parseFloat(qty); } try{ rate = parseFloat(rate.replace(',','')); }catch(e){ rate = parseFloat(rate); } newamt = qty * rate; jq(row).find('#' + amtid).val(newamt.toFixed(2)); jq(row).find('#' + totalid).val(qty); window.editor.data.view.ENTRIES[0].AMOUNT_CONVERTED = qty; window.editor.data.view.ENTRIES[0].TOTAL_AMOUNT = newamt; window.editor.data.view.ENTRIES[0].UIPRICE = rate; }); jq('#' + rateid).on('change', function() { console.log('rate changed'); var qty = jq('#' + qtyid)[0].value; var rate = jq('#' + rateid)[0].value; var oriamt = jq('#' + amtid)[0].value; var totamnt = jq('#' + totalid)[0].value; var newamt = 0; if(qty == '') qty = 0; if(rate == '') rate = 0; qty = parseFloat(qty.replace(',','')); rate = parseFloat(rate.replace(',','')); newamt = qty * rate; jq(row).find('#' + amtid).val(newamt.toFixed(2)); jq(row).find('#' + price).val(rate); jq(row).find('#' + grandtotal).val(totamnt * rate); }); jq('#' + totalid).on('input', function() { var qty = jq('#' + qtyid)[0].value; var rate = jq('#' + rateid)[0].value; var oriamt = jq('#' + amtid)[0].value; var totamnt = jq('#' + totalid)[0].value; jq(row).find('#' + grandtotal).val(totamnt * rate); });
Project/Item/Task Change Listeners
jq(row).find('#' + projectid).on('change', function() { var project = ""; var temp = jq(row).find('#' + projectid).val(); if (temp.includes("--")) { project = temp.split("--")[0]; } else { project = temp; } var selproject = _projectlist.find(x => x.PROJECTID == project); var state = _statelist.find(x => x.id == selproject.RSTATE); var optype = _optypelist.find(x => x.id == selproject.ROPPORTUNITY_TYPE_TEST); var opvertical = _opverticallist.find(x => x.id == selproject.ROPPORTUNITY_VERTICAL_TEST); jq(row).find('#' + stateid).val(state.name); window.editor.data.view.ENTRIES[rowind].GLDIMSTATE_disp = state.name; window.editor.data.view.ENTRIES[rowind].GLDIMSTATE = state.id; jq(row).find('#' + optypeid).val(optype.name); window.editor.data.view.ENTRIES[rowind].GLDIMOPPORTUNITY_TYPE_TEST_disp = optype.name; window.editor.data.view.ENTRIES[rowind].GLDIMOPPORTUNITY_TYPE_TEST = optype.id; jq(row).find('#' + opverticalid).val(opvertical.name); window.editor.data.view.ENTRIES[rowind].GLDIMOPPORTUNITY_VERTICAL_TEST_disp = opvertical.name; window.editor.data.view.ENTRIES[rowind].GLDIMOPPORTUNITY_VERTICAL_TEST = opvertical.id; }); jq(row).find('#' + itemid).on('change', function() { var item = ""; var temp = jq(row).find('#' + itemid).val(); if (temp.includes("--")) { item = temp.split("--")[0]; } else { item = temp; } var selitem = _itemlist.find(x => x.ITEMID == item); if (selitem != undefined) { var task = selitem.TASK_MAPPING; jq(row).find('#' + taskid).val(task); window.editor.data.view.ENTRIES[rowind].TASKID = task; } });
Field ID Generation Logic
jq('#tbody__obj__ENTRIES').each(function(i, tb){ jq('#tbody__obj__ENTRIES > tr').each(function(rowind, row) { var qtyid = '_obj__ENTRIES_' + rowind + '_-_obj__AMOUNT_CONVERTED'; var rateid = '_obj__ENTRIES_' + rowind + '_-_obj__KERRPRICE'; var amtid = '_obj__ENTRIES_' + rowind + '_-_obj__TOTAL_AMOUNT'; var totalid = '_obj__ENTRIES_' + rowind + '_-_obj__TOTAL_CONVERTED'; var price = '_obj__ENTRIES_' + rowind + '_-_obj__UIPRICE'; var extprice = '_obj__ENTRIES_' + rowind + '_-_obj__UIVALUE'; var grandtotal = '_obj__ENTRIES_' + rowind + '_-_obj__GRAND_TOTAL'; var projectid = '_obj__ENTRIES_' + rowind + '_-_obj__PROJECTID'; var itemid = '_obj__ENTRIES_' + rowind + '_-_obj__ITEMID'; var stateid = '_obj__ENTRIES_' + rowind + '_-_obj__GLDIMSTATE'; var opverticalid = '_obj__ENTRIES_' + rowind + '_-_obj__GLDIMOPPORTUNITY_VERTICAL_TEST'; var optypeid = '_obj__ENTRIES_' + rowind + '_-_obj__GLDIMOPPORTUNITY_TYPE_TEST'; var taskid = '_obj__ENTRIES_' + rowind + '_-_obj__TASKID'; // Event bindings here }); });
Troubleshooting Context
- I didn't write the original code, I'm tasked with fixing this bug
- The code logic is messy, and I can't pinpoint exactly why only auto-populated fields get cleared on item selection
- Manual input fields remain intact, suggesting the issue is related to how auto-populated data is stored/updated
Solutions to Fix the Issue
1. Fix Closure Scope for Row Index (Most Likely Root Cause)
The original loop binds events using the rowind variable directly, which creates a closure issue: all events end up referencing the last row's index instead of the current row's. Use an IIFE (Immediately Invoked Function Expression) to capture the current row and index:
jq('#tbody__obj__ENTRIES > tr').each(function(rowind, row) { // Capture current row and index in a closure (function(currentRowInd, currentRow) { // Generate IDs as before var qtyid = '_obj__ENTRIES_' + currentRowInd + '_-_obj__AMOUNT_CONVERTED'; var rateid = '_obj__ENTRIES_' + currentRowInd + '_-_obj__KERRPRICE'; // ... rest of ID generation // Rewrite all event bindings to use currentRowInd and currentRow jq(currentRow).find('#' + projectid).on('change', function() { var project = ""; var temp = jq(this).val(); if (temp.includes("--")) { project = temp.split("--")[0]; } else { project = temp; } var selproject = _projectlist.find(x => x.PROJECTID == project); if (!selproject) return; var state = _statelist.find(x => x.id == selproject.RSTATE); var optype = _optypelist.find(x => x.id == selproject.ROPPORTUNITY_TYPE_TEST); var opvertical = _opverticallist.find(x => x.id == selproject.ROPPORTUNITY_VERTICAL_TEST); jq(currentRow).find('#' + stateid).val(state.name); window.editor.data.view.ENTRIES[currentRowInd].GLDIMSTATE_disp = state.name; window.editor.data.view.ENTRIES[currentRowInd].GLDIMSTATE = state.id; // ... rest of project auto-population logic }); // Rewrite item/task binding similarly jq(currentRow).find('#' + itemid).on('change', function() { var item = ""; var temp = jq(this).val(); if (temp.includes("--")) { item = temp.split("--")[0]; } else { item = temp; } var selitem = _itemlist.find(x => x.ITEMID == item); if (selitem != undefined) { var task = selitem.TASK_MAPPING; jq(currentRow).find('#' + taskid).val(task); window.editor.data.view.ENTRIES[currentRowInd].TASKID = task; } }); // Rewrite quantity/price/total bindings using currentRowInd and currentRow })(rowind, row); });
2. Use Event Delegation for Dynamic Rows
Since rows are added dynamically, event bindings on initial rows won't apply to new rows. Bind events to the parent <tbody> element instead:
// Delegate project change event jq('#tbody__obj__ENTRIES').on('change', '[id^="_obj__ENTRIES_"][id$="_-_obj__PROJECTID"]', function() { var currentRow = jq(this).closest('tr'); var rowind = currentRow.index(); // Parse IDs or generate them based on rowind var stateid = '_obj__ENTRIES_' + rowind + '_-_obj__GLDIMSTATE'; var optypeid = '_obj__ENTRIES_' + rowind + '_-_obj__GLDIMOPPORTUNITY_TYPE_TEST'; // ... rest of ID generation // Project auto-population logic here, using currentRow and rowind }); // Delegate item change event jq('#tbody__obj__ENTRIES').on('change', '[id^="_obj__ENTRIES_"][id$="_-_obj__ITEMID"]', function() { var currentRow = jq(this).closest('tr'); var rowind = currentRow.index(); var temp = jq(this).val(); var item = temp.includes("--") ? temp.split("--")[0] : temp; var selitem = _itemlist.find(x => x.ITEMID == item); if (selitem != undefined) { var task = selitem.TASK_MAPPING; var taskid = '_obj__ENTRIES_' + rowind + '_-_obj__TASKID'; jq(currentRow).find('#' + taskid).val(task); window.editor.data.view.ENTRIES[rowind].TASKID = task; } // Optional: Re-trigger project change to restore cleared fields var projectid = '_obj__ENTRIES_' + rowind + '_-_obj__PROJECTID'; jq(currentRow).find('#' + projectid).trigger('change'); });
3. Prevent Sage Intacct Native Field Reset
Sage Intacct may trigger native form events when selecting items that reset auto-populated fields. Add event prevention and re-trigger project auto-population:
jq(currentRow).find('#' + itemid).on('change', function(e) { e.preventDefault(); e.stopPropagation(); // Original item/task logic here // Re-run project auto-population to restore cleared fields jq(currentRow).find('#' + projectid).trigger('change'); });
Key Takeaways
The most probable issues are closure-related index errors or missing event bindings for dynamic rows. Start with fixing the closure scope and implementing event delegation, as these address the core problems with multi-row interactions in Sage Intacct.
内容的提问来源于stack exchange,提问作者mferraro

