ERPNext Customizationjavascript

How to Populate a Child Table in ERPNext Using a Custom Button and frappe.call?

Update Table on Button Click

Learn 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.

1frappe.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 more

frappe.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 more

frm.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 more

frm.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 more

frm.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 more

Step-by-Step Tutorial

Follow along to understand how this code works

1

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.

text
// 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_purchases
Next Step
2

Set 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.

javascript
frappe.ui.form.on("Purchase Order", "get_purchases", function(frm) {
    // Your logic will go here
});
Next Step
3

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.

javascript
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
    }
});
Next Step
4

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.

javascript
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 ...
    });
}
Next Step
5

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.

javascript
// ... 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