client-scripts

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Frappe Client Scripts Reference

Frappe客户端脚本参考文档

Complete reference for client-side JavaScript development in Frappe Framework.
Frappe框架下客户端JavaScript开发的完整参考指南。

When to Use This Skill

适用场景

  • Writing form scripts (refresh, validate, field events)
  • Manipulating form fields (show/hide, require, read-only)
  • Creating dialogs and prompts
  • Making API calls from client
  • Customizing list views
  • Adding custom buttons
  • Handling child table events
  • 编写表单脚本(刷新、验证、字段事件)
  • 操作表单字段(显示/隐藏、设置必填、只读)
  • 创建对话框与提示框
  • 从客户端发起API调用
  • 定制列表视图
  • 添加自定义按钮
  • 处理子表格事件

Form Script Location

表单脚本位置

my_app/
└── my_module/
    └── doctype/
        └── my_doctype/
            └── my_doctype.js    # Client script
my_app/
└── my_module/
    └── doctype/
        └── my_doctype/
            └── my_doctype.js    # Client script

Form Events

表单事件

Complete Event Reference

完整事件参考

javascript
frappe.ui.form.on('My DocType', {
    // === LOAD EVENTS ===

    setup: function(frm) {
        // Called once when form is created (before data loads)
        // Use for: setting queries, initializing variables
        frm.set_query('customer', () => ({ filters: { status: 'Active' } }));
    },

    onload: function(frm) {
        // Called when form data is loaded (before refresh)
        // Use for: setting defaults for new docs
        if (frm.is_new()) {
            frm.set_value('posting_date', frappe.datetime.nowdate());
        }
    },

    onload_post_render: function(frm) {
        // Called after form is rendered
        // Use for: DOM manipulation, focus setting
        frm.get_field('customer').focus();
    },

    refresh: function(frm) {
        // Called every time form refreshes
        // Use for: custom buttons, field toggles, indicators
        if (!frm.is_new()) {
            frm.add_custom_button(__('Action'), () => do_action(frm));
        }
        frm.toggle_display('section_name', frm.doc.show_section);
    },

    // === SAVE EVENTS ===

    validate: function(frm) {
        // Called before save - return false to prevent
        if (frm.doc.end_date < frm.doc.start_date) {
            frappe.msgprint(__('End Date cannot be before Start Date'));
            return false;
        }
    },

    before_save: function(frm) {
        // Called after validate, before server request
        frm.doc.last_updated_by = frappe.session.user;
    },

    after_save: function(frm) {
        // Called after successful save
        frappe.show_alert({
            message: __('Saved successfully'),
            indicator: 'green'
        });
    },

    // === WORKFLOW EVENTS ===

    before_submit: function(frm) {
        // Called before document submission
    },

    on_submit: function(frm) {
        // Called after successful submission
    },

    before_cancel: function(frm) {
        // Called before cancellation
    },

    after_cancel: function(frm) {
        // Called after cancellation
    },

    // === FIELD EVENTS ===

    customer: function(frm) {
        // Called when 'customer' field changes
        if (frm.doc.customer) {
            fetch_customer_details(frm);
        }
    },

    posting_date: function(frm) {
        // Called when 'posting_date' field changes
        calculate_due_date(frm);
    }
});
javascript
frappe.ui.form.on('My DocType', {
    // === 加载事件 ===

    setup: function(frm) {
        // 表单创建时调用一次(数据加载前)
        // 用途:设置查询、初始化变量
        frm.set_query('customer', () => ({ filters: { status: 'Active' } }));
    },

    onload: function(frm) {
        // 表单数据加载完成后调用(刷新前)
        // 用途:为新文档设置默认值
        if (frm.is_new()) {
            frm.set_value('posting_date', frappe.datetime.nowdate());
        }
    },

    onload_post_render: function(frm) {
        // 表单渲染完成后调用
        // 用途:DOM操作、设置焦点
        frm.get_field('customer').focus();
    },

    refresh: function(frm) {
        // 每次表单刷新时调用
        // 用途:添加自定义按钮、切换字段显示、设置指示器
        if (!frm.is_new()) {
            frm.add_custom_button(__('Action'), () => do_action(frm));
        }
        frm.toggle_display('section_name', frm.doc.show_section);
    },

    // === 保存事件 ===

    validate: function(frm) {
        // 保存前调用 - 返回false可阻止保存
        if (frm.doc.end_date < frm.doc.start_date) {
            frappe.msgprint(__('End Date cannot be before Start Date'));
            return false;
        }
    },

    before_save: function(frm) {
        // 验证通过后、发起服务器请求前调用
        frm.doc.last_updated_by = frappe.session.user;
    },

    after_save: function(frm) {
        // 保存成功后调用
        frappe.show_alert({
            message: __('Saved successfully'),
            indicator: 'green'
        });
    },

    // === 工作流事件 ===

    before_submit: function(frm) {
        // 文档提交前调用
    },

    on_submit: function(frm) {
        // 提交成功后调用
    },

    before_cancel: function(frm) {
        // 取消前调用
    },

    after_cancel: function(frm) {
        // 取消成功后调用
    },

    // === 字段事件 ===

    customer: function(frm) {
        // 当'customer'字段变更时调用
        if (frm.doc.customer) {
            fetch_customer_details(frm);
        }
    },

    posting_date: function(frm) {
        // 当'posting_date'字段变更时调用
        calculate_due_date(frm);
    }
});

Field Manipulation

字段操作

Display Properties

显示属性

javascript
// Show/hide field
frm.toggle_display('fieldname', true);  // Show
frm.toggle_display('fieldname', false); // Hide
frm.toggle_display(['field1', 'field2'], condition);

// Set read-only
frm.set_df_property('fieldname', 'read_only', 1);
frm.toggle_enable('fieldname', false);  // Disable

// Set required
frm.set_df_property('fieldname', 'reqd', 1);
frm.toggle_reqd('fieldname', true);
frm.toggle_reqd(['field1', 'field2'], condition);

// Set hidden
frm.set_df_property('fieldname', 'hidden', 1);

// Change label
frm.set_df_property('fieldname', 'label', 'New Label');

// Change description
frm.set_df_property('fieldname', 'description', 'Help text');

// Change options (for Select)
frm.set_df_property('fieldname', 'options', 'Option1\nOption2\nOption3');

// Refresh after changes
frm.refresh_field('fieldname');
frm.refresh_fields();
javascript
// 显示/隐藏字段
frm.toggle_display('fieldname', true);  // 显示
frm.toggle_display('fieldname', false); // 隐藏
frm.toggle_display(['field1', 'field2'], condition);

// 设置只读
frm.set_df_property('fieldname', 'read_only', 1);
frm.toggle_enable('fieldname', false);  // 禁用

// 设置必填
frm.set_df_property('fieldname', 'reqd', 1);
frm.toggle_reqd('fieldname', true);
frm.toggle_reqd(['field1', 'field2'], condition);

// 设置隐藏
frm.set_df_property('fieldname', 'hidden', 1);

// 修改标签
frm.set_df_property('fieldname', 'label', 'New Label');

// 修改描述
frm.set_df_property('fieldname', 'description', 'Help text');

// 修改选项(下拉框)
frm.set_df_property('fieldname', 'options', 'Option1\nOption2\nOption3');

// 修改后刷新
frm.refresh_field('fieldname');
frm.refresh_fields();

Set Values

设置值

javascript
// Set single value
frm.set_value('fieldname', value);

// Set multiple values
frm.set_value({
    'field1': 'value1',
    'field2': 'value2',
    'field3': 'value3'
});

// Set with callback
frm.set_value('fieldname', value).then(() => {
    // After value is set
});

// Clear field
frm.set_value('fieldname', null);
frm.set_value('fieldname', '');

// Set default value
frm.set_df_property('fieldname', 'default', 'default_value');
javascript
// 设置单个值
frm.set_value('fieldname', value);

// 设置多个值
frm.set_value({
    'field1': 'value1',
    'field2': 'value2',
    'field3': 'value3'
});

// 设置值并添加回调
frm.set_value('fieldname', value).then(() => {
    // 值设置完成后执行
});

// 清空字段
frm.set_value('fieldname', null);
frm.set_value('fieldname', '');

// 设置默认值
frm.set_df_property('fieldname', 'default', 'default_value');

Link Field Queries

链接字段查询

javascript
// Basic filter
frm.set_query('customer', function() {
    return {
        filters: {
            status: 'Active',
            customer_type: 'Company'
        }
    };
});

// Dynamic filter based on form values
frm.set_query('item_code', function() {
    return {
        filters: {
            item_group: frm.doc.item_group,
            is_stock_item: 1
        }
    };
});

// Filter in child table
frm.set_query('item_code', 'items', function(doc, cdt, cdn) {
    let row = locals[cdt][cdn];
    return {
        filters: {
            warehouse: row.warehouse || doc.default_warehouse
        }
    };
});

// Custom query (server method)
frm.set_query('supplier', function() {
    return {
        query: 'my_app.api.get_suppliers',
        filters: {
            region: frm.doc.region
        }
    };
});

// Clear query
frm.set_query('fieldname', null);
javascript
// 基础过滤
frm.set_query('customer', function() {
    return {
        filters: {
            status: 'Active',
            customer_type: 'Company'
        }
    };
});

// 基于表单值的动态过滤
frm.set_query('item_code', function() {
    return {
        filters: {
            item_group: frm.doc.item_group,
            is_stock_item: 1
        }
    };
});

// 子表格中的过滤
frm.set_query('item_code', 'items', function(doc, cdt, cdn) {
    let row = locals[cdt][cdn];
    return {
        filters: {
            warehouse: row.warehouse || doc.default_warehouse
        }
    };
});

// 自定义查询(服务器方法)
frm.set_query('supplier', function() {
    return {
        query: 'my_app.api.get_suppliers',
        filters: {
            region: frm.doc.region
        }
    };
});

// 清除查询
frm.set_query('fieldname', null);

Custom Buttons

自定义按钮

javascript
refresh: function(frm) {
    // Simple button
    frm.add_custom_button(__('Do Something'), function() {
        do_something(frm);
    });

    // Button in group/dropdown
    frm.add_custom_button(__('Action 1'), function() {
        action_1(frm);
    }, __('Actions'));

    frm.add_custom_button(__('Action 2'), function() {
        action_2(frm);
    }, __('Actions'));

    // Primary button (highlighted)
    frm.add_custom_button(__('Submit'), function() {
        submit_doc(frm);
    }).addClass('btn-primary');

    // Button with icon
    let btn = frm.add_custom_button(__('Print'), function() {
        print_doc(frm);
    });
    btn.prepend('<i class="fa fa-print"></i> ');

    // Conditional buttons
    if (frm.doc.status === 'Draft') {
        frm.add_custom_button(__('Submit for Review'), function() {
            submit_for_review(frm);
        });
    }

    // Remove button
    frm.remove_custom_button(__('Do Something'));
    frm.remove_custom_button(__('Action 1'), __('Actions'));

    // Clear all buttons
    frm.clear_custom_buttons();

    // Page actions
    frm.page.set_primary_action(__('Save'), function() {
        frm.save();
    });

    frm.page.set_secondary_action(__('Cancel'), function() {
        frappe.set_route('List', 'My DocType');
    });
}
javascript
refresh: function(frm) {
    // 简单按钮
    frm.add_custom_button(__('Do Something'), function() {
        do_something(frm);
    });

    // 分组/下拉菜单中的按钮
    frm.add_custom_button(__('Action 1'), function() {
        action_1(frm);
    }, __('Actions'));

    frm.add_custom_button(__('Action 2'), function() {
        action_2(frm);
    }, __('Actions'));

    // 主按钮(高亮显示)
    frm.add_custom_button(__('Submit'), function() {
        submit_doc(frm);
    }).addClass('btn-primary');

    // 带图标的按钮
    let btn = frm.add_custom_button(__('Print'), function() {
        print_doc(frm);
    });
    btn.prepend('<i class="fa fa-print"></i> ');

    // 条件按钮
    if (frm.doc.status === 'Draft') {
        frm.add_custom_button(__('Submit for Review'), function() {
            submit_for_review(frm);
        });
    }

    // 移除按钮
    frm.remove_custom_button(__('Do Something'));
    frm.remove_custom_button(__('Action 1'), __('Actions'));

    // 清除所有按钮
    frm.clear_custom_buttons();

    // 页面操作
    frm.page.set_primary_action(__('Save'), function() {
        frm.save();
    });

    frm.page.set_secondary_action(__('Cancel'), function() {
        frappe.set_route('List', 'My DocType');
    });
}

Child Table Operations

子表格操作

Events

事件

javascript
frappe.ui.form.on('My DocType Item', {
    // Row added
    items_add: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        row.warehouse = frm.doc.default_warehouse;
        frm.refresh_field('items');
    },

    // Before row removed (can prevent)
    before_items_remove: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        if (row.is_mandatory) {
            frappe.throw(__('Cannot remove mandatory item'));
        }
    },

    // Row removed
    items_remove: function(frm, cdt, cdn) {
        calculate_total(frm);
    },

    // Field in row changes
    qty: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        row.amount = flt(row.qty) * flt(row.rate);
        frm.refresh_field('items');
        calculate_total(frm);
    },

    rate: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        row.amount = flt(row.qty) * flt(row.rate);
        frm.refresh_field('items');
        calculate_total(frm);
    },

    item_code: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        if (row.item_code) {
            frappe.call({
                method: 'my_app.api.get_item_details',
                args: { item_code: row.item_code },
                callback: function(r) {
                    if (r.message) {
                        frappe.model.set_value(cdt, cdn, {
                            'rate': r.message.rate,
                            'uom': r.message.uom,
                            'description': r.message.description
                        });
                    }
                }
            });
        }
    }
});

function calculate_total(frm) {
    let total = 0;
    frm.doc.items.forEach(item => {
        total += flt(item.amount);
    });
    frm.set_value('total', total);
}
javascript
frappe.ui.form.on('My DocType Item', {
    // 行添加完成
    items_add: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        row.warehouse = frm.doc.default_warehouse;
        frm.refresh_field('items');
    },

    // 行移除前调用(可阻止移除)
    before_items_remove: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        if (row.is_mandatory) {
            frappe.throw(__('Cannot remove mandatory item'));
        }
    },

    // 行移除完成
    items_remove: function(frm, cdt, cdn) {
        calculate_total(frm);
    },

    // 行内字段变更
    qty: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        row.amount = flt(row.qty) * flt(row.rate);
        frm.refresh_field('items');
        calculate_total(frm);
    },

    rate: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        row.amount = flt(row.qty) * flt(row.rate);
        frm.refresh_field('items');
        calculate_total(frm);
    },

    item_code: function(frm, cdt, cdn) {
        let row = locals[cdt][cdn];
        if (row.item_code) {
            frappe.call({
                method: 'my_app.api.get_item_details',
                args: { item_code: row.item_code },
                callback: function(r) {
                    if (r.message) {
                        frappe.model.set_value(cdt, cdn, {
                            'rate': r.message.rate,
                            'uom': r.message.uom,
                            'description': r.message.description
                        });
                    }
                }
            });
        }
    }
});

function calculate_total(frm) {
    let total = 0;
    frm.doc.items.forEach(item => {
        total += flt(item.amount);
    });
    frm.set_value('total', total);
}

Manipulating Rows

行操作

javascript
// Add row
let row = frm.add_child('items', {
    item_code: 'ITEM-001',
    qty: 10,
    rate: 100
});
frm.refresh_field('items');

// Get row by index
let first_row = frm.doc.items[0];

// Get row by name
let row = locals['My DocType Item'][cdn];

// Update row
frappe.model.set_value(cdt, cdn, 'fieldname', value);
frappe.model.set_value(cdt, cdn, {
    'field1': 'value1',
    'field2': 'value2'
});

// Remove row
frm.get_field('items').grid.grid_rows[0].remove();
frm.refresh_field('items');

// Remove all rows
frm.clear_table('items');
frm.refresh_field('items');

// Iterate rows
frm.doc.items.forEach((item, idx) => {
    console.log(idx, item.item_code);
});
javascript
// 添加行
let row = frm.add_child('items', {
    item_code: 'ITEM-001',
    qty: 10,
    rate: 100
});
frm.refresh_field('items');

// 通过索引获取行
let first_row = frm.doc.items[0];

// 通过名称获取行
let row = locals['My DocType Item'][cdn];

// 更新行
frappe.model.set_value(cdt, cdn, 'fieldname', value);
frappe.model.set_value(cdt, cdn, {
    'field1': 'value1',
    'field2': 'value2'
});

// 移除行
frm.get_field('items').grid.grid_rows[0].remove();
frm.refresh_field('items');

// 移除所有行
frm.clear_table('items');
frm.refresh_field('items');

// 遍历行
frm.doc.items.forEach((item, idx) => {
    console.log(idx, item.item_code);
});

Dialogs

对话框

Simple Prompt

简单提示框

javascript
// Single field
frappe.prompt(
    {
        fieldname: 'reason',
        fieldtype: 'Small Text',
        label: 'Reason',
        reqd: 1
    },
    function(values) {
        console.log(values.reason);
    },
    __('Enter Reason'),
    __('Submit')
);
javascript
// 单个字段
frappe.prompt(
    {
        fieldname: 'reason',
        fieldtype: 'Small Text',
        label: 'Reason',
        reqd: 1
    },
    function(values) {
        console.log(values.reason);
    },
    __('Enter Reason'),
    __('Submit')
);

Multi-field Prompt

多字段提示框

javascript
frappe.prompt([
    {
        fieldname: 'customer',
        fieldtype: 'Link',
        options: 'Customer',
        label: 'Customer',
        reqd: 1
    },
    {
        fieldname: 'date',
        fieldtype: 'Date',
        label: 'Date',
        default: frappe.datetime.nowdate()
    },
    {
        fieldname: 'priority',
        fieldtype: 'Select',
        label: 'Priority',
        options: 'Low\nMedium\nHigh',
        default: 'Medium'
    }
], function(values) {
    process_data(values);
}, __('Enter Details'), __('Process'));
javascript
frappe.prompt([
    {
        fieldname: 'customer',
        fieldtype: 'Link',
        options: 'Customer',
        label: 'Customer',
        reqd: 1
    },
    {
        fieldname: 'date',
        fieldtype: 'Date',
        label: 'Date',
        default: frappe.datetime.nowdate()
    },
    {
        fieldname: 'priority',
        fieldtype: 'Select',
        label: 'Priority',
        options: 'Low\nMedium\nHigh',
        default: 'Medium'
    }
], function(values) {
    process_data(values);
}, __('Enter Details'), __('Process'));

Custom Dialog

自定义对话框

javascript
let dialog = new frappe.ui.Dialog({
    title: __('Custom Dialog'),
    fields: [
        {
            fieldname: 'customer',
            fieldtype: 'Link',
            options: 'Customer',
            label: __('Customer'),
            reqd: 1,
            get_query: function() {
                return { filters: { status: 'Active' } };
            },
            change: function() {
                // Field change handler
                let value = dialog.get_value('customer');
                if (value) {
                    dialog.set_value('customer_name', 'Loading...');
                }
            }
        },
        { fieldtype: 'Column Break' },
        {
            fieldname: 'customer_name',
            fieldtype: 'Data',
            label: __('Customer Name'),
            read_only: 1
        },
        { fieldtype: 'Section Break', label: 'Items' },
        {
            fieldname: 'items',
            fieldtype: 'Table',
            label: __('Items'),
            cannot_add_rows: false,
            in_place_edit: true,
            fields: [
                {
                    fieldname: 'item',
                    fieldtype: 'Link',
                    options: 'Item',
                    in_list_view: 1,
                    label: __('Item')
                },
                {
                    fieldname: 'qty',
                    fieldtype: 'Float',
                    in_list_view: 1,
                    label: __('Qty')
                }
            ]
        }
    ],
    size: 'large', // small, large, extra-large
    primary_action_label: __('Submit'),
    primary_action: function(values) {
        console.log(values);
        dialog.hide();
        process_dialog(values);
    },
    secondary_action_label: __('Cancel')
});

dialog.show();

// Set values
dialog.set_value('customer', 'CUST-001');
dialog.set_values({
    'customer': 'CUST-001',
    'date': frappe.datetime.nowdate()
});

// Get values
let values = dialog.get_values();
let customer = dialog.get_value('customer');

// Access fields
let field = dialog.get_field('customer');
field.set_description('Select active customer');
javascript
let dialog = new frappe.ui.Dialog({
    title: __('Custom Dialog'),
    fields: [
        {
            fieldname: 'customer',
            fieldtype: 'Link',
            options: 'Customer',
            label: __('Customer'),
            reqd: 1,
            get_query: function() {
                return { filters: { status: 'Active' } };
            },
            change: function() {
                // 字段变更处理器
                let value = dialog.get_value('customer');
                if (value) {
                    dialog.set_value('customer_name', 'Loading...');
                }
            }
        },
        { fieldtype: 'Column Break' },
        {
            fieldname: 'customer_name',
            fieldtype: 'Data',
            label: __('Customer Name'),
            read_only: 1
        },
        { fieldtype: 'Section Break', label: 'Items' },
        {
            fieldname: 'items',
            fieldtype: 'Table',
            label: __('Items'),
            cannot_add_rows: false,
            in_place_edit: true,
            fields: [
                {
                    fieldname: 'item',
                    fieldtype: 'Link',
                    options: 'Item',
                    in_list_view: 1,
                    label: __('Item')
                },
                {
                    fieldname: 'qty',
                    fieldtype: 'Float',
                    in_list_view: 1,
                    label: __('Qty')
                }
            ]
        }
    ],
    size: 'large', // small, large, extra-large
    primary_action_label: __('Submit'),
    primary_action: function(values) {
        console.log(values);
        dialog.hide();
        process_dialog(values);
    },
    secondary_action_label: __('Cancel')
});

dialog.show();

// 设置值
dialog.set_value('customer', 'CUST-001');
dialog.set_values({
    'customer': 'CUST-001',
    'date': frappe.datetime.nowdate()
});

// 获取值
let values = dialog.get_values();
let customer = dialog.get_value('customer');

// 访问字段
let field = dialog.get_field('customer');
field.set_description('Select active customer');

Confirmation Dialog

确认对话框

javascript
frappe.confirm(
    __('Are you sure you want to delete this?'),
    function() {
        // On Yes
        delete_record();
    },
    function() {
        // On No (optional)
    }
);
javascript
frappe.confirm(
    __('Are you sure you want to delete this?'),
    function() {
        // 确认后执行
        delete_record();
    },
    function() {
        // 取消后执行(可选)
    }
);

API Calls

API调用

frappe.call

frappe.call

javascript
// Basic call
frappe.call({
    method: 'my_app.api.get_data',
    args: {
        customer: frm.doc.customer
    },
    callback: function(r) {
        if (r.message) {
            frm.set_value('data', r.message);
        }
    }
});

// With loading indicator
frappe.call({
    method: 'my_app.api.process',
    args: { data: frm.doc },
    freeze: true,
    freeze_message: __('Processing...'),
    callback: function(r) {
        frappe.msgprint(__('Done!'));
    },
    error: function(r) {
        frappe.msgprint(__('Error occurred'));
    }
});

// Async/await
async function getData() {
    const r = await frappe.call({
        method: 'my_app.api.get_data',
        args: { id: 123 }
    });
    return r.message;
}

// Promise chain
frappe.call({
    method: 'my_app.api.get_data'
}).then(r => {
    return frappe.call({
        method: 'my_app.api.process',
        args: { data: r.message }
    });
}).then(r => {
    console.log('Done', r.message);
});
javascript
// 基础调用
frappe.call({
    method: 'my_app.api.get_data',
    args: {
        customer: frm.doc.customer
    },
    callback: function(r) {
        if (r.message) {
            frm.set_value('data', r.message);
        }
    }
});

// 带加载指示器的调用
frappe.call({
    method: 'my_app.api.process',
    args: { data: frm.doc },
    freeze: true,
    freeze_message: __('Processing...'),
    callback: function(r) {
        frappe.msgprint(__('Done!'));
    },
    error: function(r) {
        frappe.msgprint(__('Error occurred'));
    }
});

// Async/await 方式
async function getData() {
    const r = await frappe.call({
        method: 'my_app.api.get_data',
        args: { id: 123 }
    });
    return r.message;
}

// Promise链式调用
frappe.call({
    method: 'my_app.api.get_data'
}).then(r => {
    return frappe.call({
        method: 'my_app.api.process',
        args: { data: r.message }
    });
}).then(r => {
    console.log('Done', r.message);
});

Messages & Alerts

消息与提示

javascript
// Toast alert
frappe.show_alert({
    message: __('Success!'),
    indicator: 'green'  // green, blue, orange, red
}, 5);  // seconds

// Message dialog
frappe.msgprint({
    title: __('Information'),
    message: __('This is important'),
    indicator: 'blue'
});

// Error (stops execution)
frappe.throw(__('Cannot proceed'));

// Confirmation required
frappe.validated = false;  // In validate event
javascript
// 提示弹窗
frappe.show_alert({
    message: __('Success!'),
    indicator: 'green'  // green, blue, orange, red
}, 5);  // 显示时长(秒)

// 消息对话框
frappe.msgprint({
    title: __('Information'),
    message: __('This is important'),
    indicator: 'blue'
});

// 错误提示(终止执行)
frappe.throw(__('Cannot proceed'));

// 标记验证不通过
frappe.validated = false;  // 在validate事件中使用

Utilities

工具函数

javascript
// Date/Time
frappe.datetime.nowdate();           // "2024-01-15"
frappe.datetime.now_datetime();      // "2024-01-15 10:30:00"
frappe.datetime.add_days("2024-01-15", 7);
frappe.datetime.add_months("2024-01-15", 1);

// Formatting
frappe.format(1234.56, {fieldtype: 'Currency'});
format_currency(1234.56, 'USD');
flt(value);  // Float
cint(value); // Integer

// Navigation
frappe.set_route('Form', 'Customer', 'CUST-001');
frappe.set_route('List', 'Customer');
frappe.new_doc('Customer');

// Translation
__('Translate this');
__('Hello {0}', [name]);
javascript
// 日期/时间
frappe.datetime.nowdate();           // "2024-01-15"
frappe.datetime.now_datetime();      // "2024-01-15 10:30:00"
frappe.datetime.add_days("2024-01-15", 7);
frappe.datetime.add_months("2024-01-15", 1);

// 格式化
frappe.format(1234.56, {fieldtype: 'Currency'});
format_currency(1234.56, 'USD');
flt(value);  // 转换为浮点数
cint(value); // 转换为整数

// 页面导航
frappe.set_route('Form', 'Customer', 'CUST-001');
frappe.set_route('List', 'Customer');
frappe.new_doc('Customer');

// 翻译
__('Translate this');
__('Hello {0}', [name]);