表单脚本(Form Scripts)允许您为表单添加客户端逻辑。您可以编写表单脚本来自动获取值、添加验证或为表单添加上下文操作。
当您创建新的 DocType 时,系统会生成一个 {doctype}.js,您可以在其中编写表单脚本。
语法:
frappe.ui.form.on(doctype, {
event1() {
// handle event 1
},
event2() {
// handle event 2
}
})
例如,位于 frappe/desk/doctype/todo/todo.js 的 todo.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 起)
以下是 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_load、onload、refresh、timeline_refresh 和 onload_post_render。
frm.refresh();
frm.save
触发表单保存。将触发 validate、before_save、after_save、timeline_refresh 和 refresh。
它可以用来触发其他保存操作,如提交、取消和更新。在这种情况下,相关事件将被触发。
// 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');
}
介绍文本示例
在页面的内部工具栏中添加自定义按钮。是 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');
通过标签(和组)更改特定自定义按钮的类型。
// 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');
通过标签(和组)移除特定的自定义按钮。
// 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.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,通常是在 setup 或 onload 中。
// 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”文档。