控件

frappe.ui.form.make_control

frappe.ui.form.make_control({ parent, df })

根据 df 属性创建一个 frappe 控件,并将其追加到 parent
容器中。

frappe.ui.form.make_control({
 parent: $wrapper.find('.my-control'),
 df: {
 label: 'Due Date',
 fieldname: 'due_date',
 fieldtype: 'Date'
 },
 render_input: true
})

以下是大多数 frappe 控件类型的 df 属性。

// Attach
{
 label: 'Attachment',
 fieldname: 'attachment',
 fieldtype: 'Attach'
}

// Attach Image
{
 label: 'User Image',
 fieldname: 'user_image',
 fieldtype: 'Attach Image'
}

// Autocomplete
{
 label: 'Select User',
 label: 'user',
 fieldtype: 'Autocomplete',
 options: [
 '[email protected]',
 '[email protected]'
 ]
}

// Barcode
{
 label: 'Item Barcode',
 fieldname: 'item_barcode',
 fieldtype: 'Barcode'
}

// Check
{
 label: 'Enable feature',
 fieldname: 'enable_feature',
 fieldtype: 'Check'
}

// Code
{
 label: 'JS Script',
 fieldname: 'script',
 fieldtype: 'Code',
 // for syntax highlighting
 options: 'Javascript' // JS, HTML, CSS, Markdown, SCSS, JSON,
 // wrap code
 wrap: true,
 // changing `max_lines` will set the max-height of the editor
 max_lines: 10,
 // changing `min_lines` will set the min-height of the editor
 min_lines: 5
}

// Color
{
 label: 'Your favorite color',
 fieldname: 'user_color',
 fieldtype: 'Color'
}

// Currency
{
 label: 'Amount',
 fieldname: 'amount',
 fieldtype: 'Currency',
 options: 'INR' // or name of field which holds currency
}

// Data
{
 label: 'First Name',
 fieldname: 'first_name',
 fieldtype: 'Data',
 options: 'Email' // 'Name', 'Phone', 'URL', 'Barcode'
}

// Date Range
{
 label: 'Select Date Range',
 fieldname: 'date_range',
 fieldtype: 'Date Range'
}

// Date
{
 label: 'Birth Date',
 fieldname: 'birth_date',
 fieldtype: 'Date'
}

// Datetime
{
 label: 'Submission Date and Time',
 fieldname: 'submission',
 fieldtype: 'Datetime'
}

// Dynamic Link
{
 label: 'Party',
 fieldname: 'party',
 fieldtype: 'Dynamic Link',
 options: 'party_type' // fieldname which holds the Link type
}

// Float
{
 label: 'Threshold',
 fieldname: 'threshold',
 fieldtype: 'Float'
}

// Geolocation
{
 label: 'Meeting Place',
 fieldname: 'meeting_place',
 fieldtype: 'Geolocation'
}

// HTML Editor
{
 label: 'Custom HTML',
 fieldname: 'custom_html',
 fieldtype: 'HTML Editor'
}

// Int
{
 label: 'No of days',
 fieldname: 'no_of_days',
 fieldtype: 'Int'
}

// Link
{
 label: 'Select User',
 fieldname: 'user',
 fieldtype: 'Link',
 options: 'User' // name of doctype
}

// Markdown Editor
{
 label: 'Blog Content',
 fieldname: 'content',
 fieldtype: 'Markdown Editor'
}

// MultiCheck
{
 label: 'Blog Content',
 fieldname: 'content',
 fieldtype: 'MultiCheck',
 options: [
 'Option 1',
 'Option 2',
 'Option 3',
 'Option 4',
 ],
 columns: 2 // break into 2 columns
}

// MultiSelect
{
 label: 'Select Users',
 fieldname: 'users',
 fieldtype: 'MultiSelect',
 options: [
 '[email protected]',
 '[email protected]',
 '[email protected]'
 ]
}

// Password
{
 label: 'New Password',
 fieldname: 'password',
 fieldtype: 'Password'
}

// Rating
{
 label: 'Rate your experience',
 fieldname: 'rating',
 fieldtype: 'Rating'
}

// Select
{
 label: 'Status',
 fieldname: 'status',
 fieldtype: 'Select',
 options: [
 'Open',
 'Closed',
 'Cancelled'
 ]
}

// Signature
{
 label: 'Status',
 fieldname: 'status',
 fieldtype: 'Signature'
}

// Text Editor
{
 label: 'Description',
 fieldname: 'description',
 fieldtype: 'Text Editor'
}

// Time
{
 label: 'In Time',
 fieldname: 'in_time',
 fieldtype: 'Time'
}

// Button
{
 label: 'Fetch',
 fieldname: 'fetch',
 fieldtype: 'Button',
 btn_size: 'xs' // xs, sm, lg
}

//Icon
{
 label: 'Page Icon',
 fieldname: 'page_icon',
 fieldtype: 'Icon'
}

添加自定义格式化器

您可以通过将自定义格式化器添加到 frappe.meta.docfield_map 中的 docfield 对象,为文本类型对象(如 Data、Select、Text 等)添加自定义格式化器。

示例:

frappe.meta.docfield_map['DocField'].fieldtype.formatter = (value) => {
 if (value==='Section Break') return '🔵 Section Break';
 else return value;
}

表单脚本

表单脚本(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 起)

表单 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_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');
}

介绍文本示例

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,通常是在 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”文档。

开发者API

Python

Frappe 旨在为其用户实现最低的认知负担。因此,你可以在
frappe 命名空间本身中找到最常用的方法和工具。在 Python 文件中,
这(大多数情况下)是你唯一需要的导入。

  1. 文档
  2. 数据库
  3. Jinja 模板
  4. 常用工具
  5. 路由器
  6. 响应
  7. 语言解析
  8. 搜索
  9. 钩子
  10. REST API
  11. 全文搜索
  12. 对话框 API
  13. 查询构建器

Javascript

Frappe 将自身挂载到 window 对象下的 frappe 命名空间中。你
可以在 frappe 对象下找到大部分客户端 API。所有这些方法
仅在 Desk 环境中可用。探索这些 API 的一个好方法是从
浏览器控制台入手。

  1. 表单
  2. 控件
  3. 页面
  4. 树形视图
  5. 服务器调用 (AJAX)
  6. 常用工具
  7. 对话框 API
  8. 图表 API
  9. 扫描器 API

其他

  1. REST API
  2. Jinja API