Skip to content
Skip to the article
In Frappe: 20 articles
Frappe

Client-Side Recipes

Small desk JavaScript patterns, such as filling a dialog's Select field from a query and pushing messages from Python to the browser.

Updated
Applies to
  • Frappe v15
  • Frappe v16
Tags
  • frappe
  • javascript
  • dialog
  • realtime
  • client-script
Reading time
3 min

These are short, reusable snippets for desk-side JavaScript: form scripts, page scripts, and bundles loaded through app_include_js. Each one is self-contained; copy the one you need and swap in your own doctypes and method paths.

Fill a Dialog's Select Field from a Query

Use this when a dialog needs a dropdown built from live data, for example letting a user map an imported line to an existing Item or create a new one.

Build the dialog with Frappe's field definitions rather than injecting raw HTML. Then load the options and set them with set_df_property:

function map_item_dialog(callback) {
    const dialog = new frappe.ui.Dialog({
        title: __("Map Item"),
        fields: [
            {
                fieldtype: "HTML",
                fieldname: "help",
                options: `<div class="alert alert-info">${__(
                    "Pick an existing item, or leave it blank to create a new one."
                )}</div>`,
            },
            { fieldtype: "Select", fieldname: "item", label: __("Existing Item") },
            { fieldtype: "Section Break", label: __("New Item") },
            { fieldtype: "Data", fieldname: "description", label: __("Description") },
            { fieldtype: "Currency", fieldname: "standard_rate", label: __("Standard Rate") },
        ],
        primary_action_label: __("Save"),
        async primary_action(values) {
            if (values.item) {
                callback(values.item);
            } else {
                const item = await frappe.xcall("<APP_NAME>.api.create_item", {
                    description: values.description,
                    standard_rate: values.standard_rate,
                });
                callback(item);
            }
            dialog.hide();
        },
    });

    frappe.db
        .get_list("Item", { fields: ["name", "item_name"], filters: { disabled: 0 }, limit: 500 })
        .then((items) => {
            dialog.set_df_property("item", "options", [
                "",
                ...items.map((item) => ({ value: item.name, label: item.item_name })),
            ]);
        });

    dialog.show();
    dialog.get_field("item").$input.trigger("focus");
}
  • Select options can be plain strings or { value, label } objects. The leading empty string gives the user a blank choice.

  • frappe.db.get_list returns 20 rows unless you pass limit. Set a ceiling that fits the data.

  • <APP_NAME>.api.create_item stands for a @frappe.whitelist() method in your app that creates the record and returns its name.

Tip

For more than a few hundred records, use a Link field (fieldtype: "Link", options: "Item") instead of a Select. It searches on the server as the user types, respects permissions, and you can narrow it with get_query.

Push Messages from Python to the Browser

Frappe's realtime server lets server code send events to the browser of a specific user, a document's viewers, or everyone. Use it to report progress from background jobs or to debug server code that runs where you can't see print output.

On the server, publish an event:

frappe.publish_realtime(
    "console_log",
    {"message": f"Adding punches for {employee}"},
    user=frappe.session.user,
)

In the browser (a form script, or a bundle loaded on every page), subscribe to it:

frappe.realtime.on("console_log", (data) => {
    console.log(data.message);
});

Call frappe.realtime.off("console_log") when the listener is no longer needed, such as in a page's on_page_hide.

Useful options and built-in events:

  • user= sends to one user's sessions; doctype= and docname= send to everyone viewing that document; with none of them, the event goes to every desk user on the site.

  • after_commit=True holds the event until the transaction commits, so the browser never hears about a change that was rolled back.

  • Publishing to the built-in msgprint event shows a message dialog without any client code: frappe.publish_realtime("msgprint", "Import finished", user=user). Without user=, it goes to the current session user.

  • frappe.publish_progress(percent, title="Importing") drives the built-in progress bar.

Note

Older notes mention frappe.emit_js or an eval_js realtime event for running arbitrary JavaScript from Python. Neither exists in current Frappe. Publish a named event and handle it explicitly, as above.

Sources

This article is in the public domain (CC0 1.0), code samples included. Use it however helps you.