How to Populate a Child Table in ERPNext Using a Custom Button and frappe.call?
Update Table on Button ClickLearn to create a custom button in an ERPNext DocType that triggers a Client Script to fetch data via frappe.call and dynamically populate a child table.
In ERPNext, business logic often requires fetching and displaying related data on demand without storing it permanently on the document. This Frappe Framework Client Script demonstrates a powerful pattern: using a custom button to trigger an API call, retrieve data from the server, and then dynamically populate a child table.
This approach is highly efficient for displaying contextual information, like previous purchase history for an item, directly on the Purchase Order form. It enhances user experience by providing necessary data precisely when it's needed, triggered by a simple button click.
1 frappe.ui.form.on("Purchase Order", "get_purchases", function(frm) { 2 var item_cd = ""; 3 if(frm.doc.items != 0){ 4 item_cd = frm.doc.items[0].item_code || ""; 5 } 6
7 frappe.call({ 8 method: "shivshakti.api.get_purchase", 9 args: { 10 item: item_cd || "", 11 broker_name: frm.doc.broker || "", 12 supplier_name:frm.doc.supplier || "", 13 }, 14 callback: function(r) { 15 if(r.message) { 16 frm.clear_table("purchases"); 17 var qty = 0; 18 var final_amt = 0; 19 var item; 20 $.each(r.message, function(i, d) { 21 var c = frm.add_child("purchases"); 22 c.name1 = d.name; 23 c.date = d.date; 24 c.lorry_no = d.lorry_no; 25 c.bags = d.bags; 26 c.rate = d.rate; 27 c.gross_weight = d.gross_wt; 28 c.net_weight = d.net_wt; 29 c.moisture = d.moisture; 30 c.amount = d.amount; 31 c.tax_amount = d.tax_amount; 32 c.after_tax_amount = d.after_tax_amount; 33 c.final_amount = d.after_commission_amount; 34 qty += d.gross_wt; 35 final_amt += d.after_commission_amount; 36 item = d.items; 37 }); 38 } 39 40 var rate = final_amt / qty; 41 frm.doc.items[0].qty = qty; 42 frm.doc.items[0].rate = rate; 43 frm.refresh_field("items"); 44 frm.refresh_field("purchases"); 45 } 46 }); 47 });
Understanding This Code
What It Does
This Client Script fetches purchase data from a custom API endpoint when a user clicks the 'get_purchases' button on the Purchase Order form. It then clears and populates the 'purchases' child table with the fetched data and updates summary values in the 'items' child table.
When To Use
Use this pattern when you need to load related data into a child table on demand. It's ideal for situations where you want to provide users with contextual information from external or aggregated sources without storing it directly on the document.
Prerequisites
- •A Custom Script created for the 'Purchase Order' DocType.
- •A custom Button field named 'get_purchases' on the Purchase Order form.
- •A server-side whitelisted Python method (e.g., `shivshakti.api.get_purchase`).
- •Two child tables on the Purchase Order DocType: 'items' and 'purchases'.
Key Concepts
Important ideas to understand in this code
frappe.ui.form.on
A client-side event handler in Frappe. It's used to bind a function to a specific DocType form event, such as a field change or, in this case, a button click. The syntax `on('DocType', 'fieldname', function(frm){...})` targets the specific event.
Learn morefrappe.call
The primary method for making asynchronous server calls from the client-side. It securely calls a whitelisted Python method, passes arguments, and handles the response in a callback function.
Learn morefrm.clear_table
A Form API function that removes all rows from a specified child table. It's essential for ensuring the table is clean before populating it with new data from an API call.
Learn morefrm.add_child
This function adds a new row to a specified child table. It returns a reference to the new row object, allowing you to set values for its fields programmatically.
Learn morefrm.refresh_field
Refreshes the UI of a specific field or table on the form. After programmatically adding rows to a child table, this command must be called to make the changes visible to the user.
Learn moreStep-by-Step Tutorial
Follow along to understand how this code works
Create the Custom Button
First, navigate to the 'Purchase Order' DocType and click 'Customize'. Add a new field. Set the 'Type' to 'Button' and the 'Fieldname' to `get_purchases`. This fieldname is what you will use in the Client Script to trigger the action.
// No code needed for this step. This is done via the ERPNext UI.
// DocType: Purchase Order
// Customize Form > Add Field:
// Label: Get Purchases
// Type: Button
// Fieldname: get_purchasesSet Up the Client Script Event Handler
Create a new Client Script for the 'Purchase Order' DocType. The `frappe.ui.form.on` function links your code to the button you just created. Any code inside this function will execute when the button is clicked.
frappe.ui.form.on("Purchase Order", "get_purchases", function(frm) {
// Your logic will go here
});Implement the Server Call with frappe.call
Inside the event handler, use `frappe.call` to communicate with the server. Specify the Python method to call in `method` and pass any required data from the form in the `args` object. The server's response will be handled in the `callback` function.
frappe.call({
method: "shivshakti.api.get_purchase", // Your whitelisted python method
args: {
item: frm.doc.items[0].item_code || "",
broker_name: frm.doc.broker || "",
supplier_name: frm.doc.supplier || "",
},
callback: function(r) {
// Handle the response 'r' here
}
});Process the Response and Populate the Child Table
In the callback, first check if `r.message` contains data. Before adding new rows, clear the target child table using `frm.clear_table('purchases')`. Then, loop through the response data (e.g., using `$.each`) and use `frm.add_child('purchases')` for each item to create a new row. Map the data from the response to the fields of the new child row.
if(r.message) {
frm.clear_table("purchases");
$.each(r.message, function(i, d) {
var c = frm.add_child("purchases");
c.name1 = d.name;
c.date = d.date;
c.lorry_no = d.lorry_no;
// ... map all other fields ...
});
}Update Form and Refresh UI
After populating the table, you might need to perform calculations and update other fields on the form, as shown with the 'items' table. Finally, and most importantly, call `frm.refresh_field('your_table_fieldname')` for each child table you've modified. This redraws the tables on the screen to show the new data.
// ... after the loop ...
var rate = final_amt / qty;
frm.doc.items[0].qty = qty;
frm.doc.items[0].rate = rate;
frm.refresh_field("items");
frm.refresh_field("purchases");Common Issues & Solutions
Troubleshoot problems you might encounter