Programming Standards - Actions
Highlight selected row
Apply a highlight style to the selected row in the datatable to improve user feedback during interaction. Why: Visually indicating the selected row helps users track their current context, especially in large datasets. It reduces the chance of taking action on the wrong row and improves overall usability.
❌ Incorrect way
// No visual feedback when a row is selected// or using hardcoded CSS without structuredocument.querySelector('.dt-cell').style.backgroundColor = '#CFCFCF';
✅ Correct Way
STYLE.innerHTML = `.dt-row--highlight .dt-cell { background-color: var(--dt-selection-highlight-color); // :white_check_mark: Theme-aware background-color: #FFFCE7; // :white_check_mark: Visual Feedback}`;
Avoid Direct DOM Element Access
Why:
Directly querying DOM elements (document.querySelector
) tightly couples logic with specific HTML structure. It breaks when class names or structure change. Prefer abstracted methods or built-in framework features (e.g., filters, column definitions).
❌ Incorrect way
const ELEMENT = document.querySelector(`.dt-cell__content--header-${i}`);if (ELEMENT) { const TEXT = ELEMENT.innerText; laTextArray.push(TEXT);}
✅ Correct Way
def get_user_column_preferences(user): doc = frappe.get_doc("User Session Defaults", user) return doc.report_columns
Avoid Manual DOM-Based Feedback Rendering
Why:
Using manual DOM manipulation to show messages can lead to inconsistent UX and make maintenance difficult. It bypasses Frappe’s built-in UI mechanisms, which handle context, translation, and styling automatically. Prefer frappe.show_alert
or frappe.msgprint
for consistent and localized feedback.
❌ Incorrect way
// Avoid manual DOM updates or custom alert HTMLif (response.message) { const alertBox = document.createElement("div"); alertBox.className = "custom-alert success"; alertBox.innerText = "Column Preference Saved"; document.body.appendChild(alertBox); setTimeout(() => alertBox.remove(), 5000);}
✅ Correct Way
"callback": (response) => { if (response.message) { frappe.show_alert({ message: __('Column Preference Saved'), indicator: 'green' }, 5); }},"error": (response) => { if (response.message) { frappe.show_alert({ message: __('Failed to Save'), indicator: 'Red' }, 5); }}
Avoid Native confirm()
for User Prompts
Why:
Using native confirm()
dialogs interrupts user flow, lacks styling, and doesn’t support translations or callbacks well in Frappe. Prefer frappe.confirm
to ensure consistent UI/UX, localization, and better callback control.
❌ Incorrect way
if (confirm("This will Recalculate the Price for the Design again. Do you want to proceed?")) { recalculate_price();}
✅ Correct Way
frappe.confirm( __('This will Recalculate the Price for the Design again. Do you want to proceed?'), () => { recalculate_price(); });
Avoid Hardcoding Route Names
Why:
Hardcoding route names like "item"
reduces flexibility and can lead to issues if module structure or naming conventions change. Prefer frappe.set_route
with doctype
and dynamic values to ensure maintainability and correctness.
❌ Incorrect way
function fnViewItem(frm) { frappe.set_route("item", frm.doc.item);}
✅ Correct Way
function fnViewItem(frm) { frappe.set_route("Form", "Item", frm.doc.item);}
Avoid Deeply Nested Conditionals for Field Logic
Why: Deep nesting reduces code readability and makes future changes harder. Prefer flattening logic using guard clauses and helper functions to clarify intent and improve maintainability.
❌ Incorrect way
item(frm){ if(!frm.doc.item && frm.doc.status === "Item Created"){ if(frm.doc.is_design === 1){ frm.set_value('status', 'Calculation Received'); frm.save().then(function() { fnUpdateButtonGroup(frm); }); }else{ frm.set_value('status', 'Draft'); frm.save().then(function() { fnUpdateButtonGroup(frm); }); } }}
✅ Correct Way
item(frm) { if (frm.doc.item || frm.doc.status !== "Item Created") return; const new_status = frm.doc.is_design ? 'Calculation Received' : 'Draft'; frm.set_value('status', new_status); frm.save().then(() => fnUpdateButtonGroup(frm));}
Avoid Setting Field Options Without Validating Field Existence
Why:
Directly setting field properties without checking if the field exists may cause runtime errors, especially in dynamic or conditional forms. Always ensure the field is present before applying set_df_property
.
❌ Incorrect way
frm.set_df_property(iAttributeName, 'options', ldAttribute.options);
✅ Correct Way
if (frm.fields_dict[iAttributeName]) { frm.set_df_property(iAttributeName, 'options', ldAttribute.options);}
Avoid Using Unscoped Variables in Conditions
Why:
Using undeclared or out-of-scope variables (like row
in a condition outside its declaration context) can lead to reference errors or unexpected behavior. Always define and scope variables clearly to ensure logical correctness and code clarity.
❌ Incorrect way
const totalRow = frappe.query_report.data.find(row => row.power === 'Weekly Capacity');if (row[col] > totalRow[col]) { // logic}
✅ Correct Way
const totalRow = frappe.query_report.data.find(row => row.power === 'Weekly Capacity');frappe.query_report.data.forEach(row => { if (row[col] > totalRow[col]) { // logic }});
Avoid Using DOM to Capture Column State After User Customization
Why:
Accessing column headers via DOM (document.querySelector
) to capture user-modified report state is fragile. Instead, use frappe.query_report.get_visible_columns()
which returns the visible and ordered columns as per user interaction.
❌ Incorrect way
report.page.add_inner_button(__("Save Columns"), function() { for (let i = 0; i <= 69; i++) { const CLASSNAME = `dt-cell__content--header-${i}`; const ELEMENT = document.querySelector(`.${CLASSNAME}`); if (ELEMENT) { const TEXT = ELEMENT.innerText; laTextArray.push(TEXT); } } // Save laTextArray...});
✅ Correct Way
report.page.add_inner_button(__("Save Columns"), function () { const visibleCols = frappe.query_report.get_visible_columns(); const laTextArray = visibleCols.map(col => col.label); // Save laTextArray to DB or User Defaults...});