表单脚本

表单脚本(Form Scripts)允许您为表单添加客户端逻辑。您可以编写表单脚本来自动获取值、添加验证或为表单添加上下文操作。

标准表单脚本

当您创建新的 DocType 时,系统会生成一个 {doctype}.js,您可以在其中编写表单脚本。

语法:

frappe.ui.form.on(doctype, {
    event1() {
        // handle event 1
    },
    event2() {
        // handle event 2
    }
})

例如,位于 frappe/desk/doctype/todo/todo.jstodo.js 可能如下所示:

// Script for ToDo Form
frappe.ui.form.on('ToDo', {
    // on refresh event
    refresh(frm) {
        // if reference_type and reference_name are set,
        // add a custom button to go to the reference form
        if (frm.doc.reference_type && frm.doc.reference_name) {
            frm.add_custom_button(__(frm.doc.reference_name), () => {
                frappe.set_route("Form", frm.doc.reference_type, frm.doc.reference_name);
            });
        }
    }
})

子表脚本

子表脚本应与其父表脚本写在同一个文件中。

frappe.ui.form.on('Quotation', {
    // ...
})

frappe.ui.form.on('Quotation Item', {
    // cdt is Child DocType name i.e Quotation Item
    // cdn is the row name for e.g bbfcb8da6a
    item_code(frm, cdt, cdn) {
        let row = frappe.get_doc(cdt, cdn);
    }
})

自定义表单脚本

您还可以通过在系统中创建客户端脚本(Client Script)来编写表单脚本。如果逻辑是针对您特定站点的,则应编写客户端脚本。如果您希望跨站点共享表单脚本,则必须通过应用(Apps)来包含它们。

要创建新的客户端脚本,请前往

首页 > 自定义 > 客户端脚本 > 新建

新建表单客户端脚本

表单事件

表单脚本依赖事件来触发。以下是表单触发的所有表单事件的列表。

这些事件的处理函数会将 frm 作为第一个参数接收。

frappe.ui.form.on('ToDo', {
    // frm passed as the first parameter
    setup(frm) {
        // write setup code
    }
})
事件名称 描述
setup 当表单首次创建时触发一次
before_load 在表单即将加载之前触发
onload 当表单已加载并即将渲染时触发
refresh 当表单已加载并渲染完成时触发。
onload_post_render 在表单加载并渲染完成后触发
validate 在 before_save 之前触发
before_save 在调用保存之前触发
after_save 在表单保存后触发
before_submit 在调用提交之前触发
on_submit 在表单提交后触发
before_cancel 在调用取消之前触发
after_cancel 在表单取消后触发
before_discard 在调用放弃之前触发
after_discard 在表单被放弃后触发
timeline_refresh 在表单时间线渲染后触发
{fieldname}_on_form_rendered 当表格字段中的某行作为表单打开时触发
{fieldname} 当字段名的值发生变化时触发
get_email_recipient_filters 由电子邮件对话框调用,用于获取电子邮件收件人的默认筛选条件。应接受两个参数 frm(当前表单)和 field(”recipients”、”cc” 或 “bcc”),并返回一个数组或字典形式的筛选条件(与联系人(Contact) DocType 相关)。
get_email_recipients 由电子邮件对话框调用,用于获取默认收件人。应接受两个参数 frm(当前表单)和 field(”recipients”、”cc” 或 “bcc”),并返回该字段的电子邮件地址列表。

子表事件

这些事件在子表的上下文中触发。因此,除了 frm 之外,它们的处理函数还会接收 cdt(子 DocType)和 cdn(子 Docname)参数。

假设我们的“待办事项(ToDo)”DocType 有一个名为“链接(links)”的字段,其中包含一个子表。该子表在名为“动态链接(Dynamic Link)”的 DocType 中定义。我们希望每当向该表添加一行时都运行我们的代码。

// this code is located inside `todo.js`

frappe.ui.form.on('Dynamic Link', { // The child table is defined in a DoctType called "Dynamic Link"
    links_add(frm, cdt, cdn) { // "links" is the name of the table field in ToDo, "_add" is the event
        // frm: current ToDo form
        // cdt: child DocType 'Dynamic Link'
        // cdn: child docname (something like 'a6dfk76')
        // cdt and cdn are useful for identifying which row triggered this event

        frappe.msgprint('A row has been added to the links table 🎉 ');
    }
});
事件名称 描述
before_{fieldname}_remove 当一行即将从表格字段中移除时触发
{fieldname}_add 当向表格字段添加一行时触发
{fieldname}_remove 当从表格字段中移除一行时触发
{fieldname}_move 当一行在表格字段中被重新排序到其他位置时触发
form_render 当表格字段中的某行作为表单打开时触发

注意:上表列出的前三个事件,即 before_{fieldname}_remove{fieldname}_add{fieldname}_remove,也会针对字段类型为表格多选(Table MultiSelect)的字段触发。(自版本 16 起)

表单 API

以下是 frm 对象上可用的一些常用方法列表。

frm.set_value

设置字段的值。这将触发表单中的字段更改事件。

// set a single value
frm.set_value('description', 'New description')

// set multiple values at once
frm.set_value({
    status: 'Open',
    description: 'New description'
})

// returns a promise
frm.set_value('description', 'New description')
    .then(() => {
        // do something after value is set
    })

frm.refresh

使用服务器上的最新值刷新表单。将触发 before_loadonloadrefreshtimeline_refreshonload_post_render

frm.refresh();

frm.save

触发表单保存。将触发 validatebefore_saveafter_savetimeline_refreshrefresh

它可以用来触发其他保存操作,如提交、取消和更新。在这种情况下,相关事件将被触发。

// save form
frm.save();

// submit form
frm.save('Submit');

// cancel form
frm.save('Cancel');

// update form (after submit)
frm.save('Update');

// all methods returns a promise

frm.enable_save / frm.disable_save

用于启用/禁用表单中“保存”按钮的方法。

if (frappe.user_roles.includes('Custom Role')) {
    frm.enable_save();
} else {
    frm.disable_save();
}

frm.email_doc

打开此表单的电子邮件对话框。

// open email dialog
frm.email_doc();

// open email dialog with some message
frm.email_doc(`Hello ${frm.doc.customer_name}`);

frm.reload_doc

使用服务器上的最新值重新加载文档,并调用 frm.refresh()

frm.reload_doc();

frm.refresh_field

刷新字段及其依赖项。

frm.refresh_field('description');

frm.is_dirty

检查表单值是否已更改且尚未保存。

if (frm.is_dirty()) {
    frappe.show_alert('Please save form before attaching a file')
}

frm.dirty

将表单设置为“脏”状态。这用于在文档值更改时将表单标记为脏状态。这会触发表单视图中的“未保存”指示器。

frm.doc.browser_data = navigator.appVersion;
frm.dirty();
frm.save();

在不将表单设置为脏状态的情况下调用保存,将触发“文档无更改”的提示消息。

frm.is_new

检查表单是否为新建且尚未保存。

// add custom button only if form is not new
if (!frm.is_new()) {
    frm.add_custom_button('Click me', () => console.log('Clicked custom button'))
}

frm.set_intro

在表单顶部设置介绍文本。该函数接受两个参数:message(字符串,必填)和 color(字符串,可选)。

颜色可以是“蓝色”、“红色”、“橙色”、“绿色”或“黄色”。默认是蓝色。

if (!frm.doc.description) {
    frm.set_intro('Please set the value of description', 'blue');
}

介绍文本示例

frm.add_custom_button

在页面的内部工具栏中添加自定义按钮。是 page.add_inner_button 的别名。

// Custom buttons
frm.add_custom_button('Open Reference form', () => {
    frappe.set_route('Form', frm.doc.reference_type, frm.doc.reference_name);
})

// Custom buttons in groups
frm.add_custom_button('Closed', () => {
    frm.doc.status = 'Closed'
}, 'Set Status');

frm.change_custom_button_type

通过标签(和组)更改特定自定义按钮的类型。

// change type of ungrouped button
frm.change_custom_button_type('Open Reference form', null, 'primary');

// change type of a button in a group
frm.change_custom_button_type('Closed', 'Set Status', 'danger');

frm.remove_custom_button

通过标签(和组)移除特定的自定义按钮。

// remove custom button
frm.remove_custom_button('Open Reference form');

// remove custom button in a group
frm.remove_custom_button('Closed', 'Set Status');

frm.clear_custom_buttons

从内部工具栏中移除所有自定义按钮。

frm.clear_custom_buttons();

frm.set_df_property

更改字段的文档字段属性并刷新该字段。

// change the fieldtype of description field to Text
frm.set_df_property('description', 'fieldtype', 'Text');

// set the options of the status field to only be [Open, Closed]
frm.set_df_property('status', 'options', ['Open', 'Closed'])

// set a field as mandatory
frm.set_df_property('title', 'reqd', 1)

// set a field as read only
frm.set_df_property('status', 'read_only', 1)

frm.toggle_enable

根据条件将字段或字段列表切换为 read_only 状态。

// set status and priority as read_only
// if user does not have System Manager role
let is_allowed = frappe.user_roles.includes('System Manager');
frm.toggle_enable(['status', 'priority'], is_allowed);

frm.toggle_reqd

根据条件将字段或字段列表切换为必填(reqd)状态。

// set priority as mandatory
// if status is Open
frm.toggle_reqd('priority', frm.doc.status === 'Open');

frm.toggle_display

根据条件显示/隐藏字段或字段列表。

// show priority and due_date field
// if status is Open
frm.toggle_display(['priority', 'due_date'], frm.doc.status === 'Open');

frm.set_query

对链接字段应用过滤器,以显示有限的记录供选择。您必须在表单生命周期的早期调用 frm.set_query,通常是在 setuponload 中。

// show only customers whose territory is set to India
frm.set_query('customer', () => {
    return {
        filters: {
            territory: 'India'
        }
    }
})

// show customers whose territory is any of India, Nepal, Japan
frm.set_query('customer', () => {
    return {
        filters: {
            territory: ['in', ['India', 'Nepal', 'Japan']]
        }
    }
})

// set filters for Link field item_code in
// items field which is a Child Table
frm.set_query('item_code', 'items', () => {
    return {
        filters: {
            item_group: 'Products'
        }
    }
})

您还可以覆盖过滤方法,并在服务器端提供自己的自定义方法。只需将查询设置为您的 Python 方法的模块路径即可。

// change the filter method by passing a custom method
frm.set_query('fieldname', () => {
    return {
        query: 'dotted.path.to.custom.custom_query',
        filters: {
            field1: 'value1'
        }
    }
})
# python method signature
def custom_query(doctype, txt, searchfield, start, page_len, filters):
    # your logic
    return filtered_list

frm.add_child

向表格字段添加一行带值的记录。

let row = frm.add_child('items', {
    item_code: 'Tennis Racket',
    qty: 2
});

frm.refresh_field('items');

frm.call

使用参数调用服务器端的控制器方法。

注意: 在使用 frm.call() 访问任何服务器端方法时,您需要使用 @frappe.whitelist 装饰器将该方法列入白名单。

对于以下控制器代码:

class ToDo(Document):
    @frappe.whitelist()
    def get_linked_doc(self, throw_if_missing=False):
        if not frappe.db.exists(self.reference_type, self.reference_name):
            if throw_if_missing:
                frappe.throw('Linked document not found')

        return frappe.get_doc(self.reference_type, self.reference_name)

您可以使用 frm.call 从客户端调用它。

frm.call('get_linked_doc', { throw_if_missing: true })
    .then(r => {
        if (r.message) {
            let linked_doc = r.message;
            // do something with linked_doc
        }
    })

frm.trigger

显式触发任何表单事件。

frappe.ui.form.on('ToDo', {
    refresh(frm) {
        frm.trigger('set_mandatory_fields');
    },

    set_mandatory_fields(frm) {
        frm.toggle_reqd('priority', frm.doc.status === 'Open');
    }
})

frm.get_selected

在子表中获取选中的行,返回一个对象,其中键是表格字段名,值是行名称。

let selected = frm.get_selected()
console.log(selected)
// {
// items: ["bbfcb8da6a", "b1f1a43233"]
// taxes: ["036ab9452a"]
// }

frm.ignore_doctypes_on_cancel_all

为避免在全部取消期间取消链接文档,您需要将 frm.ignored_doctypes_on_cancel_all 属性设置为链接文档的 DocTypes 数组。

frappe.ui.form.on("DocType 1", {
    onload: function(frm) {
        // Ignore cancellation for all linked documents of respective DocTypes.
        frm.ignore_doctypes_on_cancel_all = ["DocType 2", "DocType 3"];
    }
}

在上述示例中,系统将在取消期间避免取消所有与“DocType 1”文档链接的“DocType 2”和“DocType 3”文档。