在JavaScript中使用HTML模板

在构建 JavaScript 界面时,通常需要将 DOM 渲染为 HTML 模板。Frappe 框架使用 John Resig 的 Microtemplate 脚本在 Desk 应用程序中渲染 HTML 模板。

> 注意 1:在 Frappe 中,我们使用类似 Jinja 的 {% raw %}{%{% endraw %} 标签来嵌入代码,而不是标准的 <%

> 注意 2:切勿在 HTML 模板中使用单引号 '。

要渲染模板,请执行以下操作:

  1. 在您的应用中创建一个模板 html 文件。例如:address_list.html
  2. 将其添加到您应用的 build.json 中(您可以将其包含在 frappe.min.js 或您自己的 JavaScript 文件中)。
  3. 要在您的应用中渲染它,请使用 frappe.render(frappe.templates.address_list, {[context]})

示例模板:

来自 erpnext/public/js/templates/address_list.js

<p>
    <button class="btn btn-sm btn-default btn-address">New Address</button>
</p>

{% for(var i=0, l=addrlist.length; i<l; i++) { %}
    <hr>
    {%= _("Edit") %}
    {%= addr_list[i].address_type %}

    {% if(addr_list[i].is_primary_address) { %}
        {%= __("Primary") %}
    {% } %}

    {% if(addr_list[i].is_shipping_address) { %}
        {%= __("Shipping") %}
    {% } %}

    {%= addr_list[i].display %}
{% } %}

{% if(!addrlist.length) { %}
    {%= _("No address added yet.") %}
{% } %}

如何在应用安装期间创建自定义字段

您的自定义应用在安装到新站点时,可以自动向应用之外的 DocTypes 添加自定义字段。

要实现此操作,请使用 Frappe Web 应用程序添加您的应用所需的新自定义字段。

在您的 hooks.py 文件中,添加 "Custom Fields"

fixtures = [“Custom Field”]

在提交应用之前,请使用以下命令导出 fixtures:

$ bench –site mysite export-fixtures

这将在您的应用文件夹中创建一个名为 fixtures 的新文件夹,并生成一个 .csv 或 .json 文件,其中包含您添加的自定义字段的定义。

当应用安装到新站点或通过 bench update 更新时,此文件将被自动导入。

注意:您也可以将单个 DocTypes(如“网站设置”)添加为 fixtures。

通过API插入文档

您可以通过脚本使用 frappe.get_doc 方法插入文档

示例:

1. 插入一个待办事项

todo = frappe.get_doc({“doctype”:”ToDo”, “description”: “test”})
todo.insert()


2. 不检查用户权限直接插入:

todo = frappe.get_doc({“doctype”:”ToDo”, “description”: “test”})
todo.insert(ignore_permissions = True)


3. 插入后提交

todo = frappe.get_doc({“doctype”:”ToDo”, “description”: “test”})
todo.insert(ignore_permissions=True)
todo.submit()


4. 在保存另一个文档时插入文档

class MyType(Document):
def on_update(self):
todo = frappe.get_doc({“doctype”:”ToDo”, “description”: “test”})
todo.insert()


5. 插入带有子表的文档:

sales_order = frappe.get_doc({
“doctype”: “Sales Order”,
“company”: “_Test Company”,
“customer”: “_Test Customer”,
“delivery_date”: “2013-02-23”,
“sales_order_details”: [
{
“item_code”: “_Test Item Home Desktop 100”,
“qty”: 10.0,
“rate”: 100.0,
“warehouse”: “_Test Warehouse – _TC”
}
]
})
sales_order.insert()

自定义模块图标

如果你想为你的模块创建一个自定义图标,你需要为你的模块创建一个 SVG 文件,并在你的应用的 desktop/config.py 中设置该文件的路径。

该图标首次通过 AJAX 加载,之后会被渲染显示。

示例:

from frappe import _

 def get_data():
    return {
     "Frappe Apps": {
     "color": "orange",
     "icon": "assets/frappe/images/frappe.svg",
     "label": _("Frappe.io Portal"),
     "type": "module"
    }
  }

附注:一个购买低价 SVG 图标的好地方是优秀的 Noun Project 网站。

创建新的连接应用

您可以使用已连接应用,让您的 frappe 实例代表您访问其他网络服务。

此示例演示如何连接 Google Mail,但您可以使用任何支持 OAuth 2.0 的服务。

首先,创建一个新的已连接应用并为其命名。大多数服务提供商都会提供一个类似 /.well-known/openid-configuration 的 URL,用于发布所有 OAuth 端点。输入此 URL 并点击“获取 OpenID 配置”。

如果您不知道此 URL,请前往“端点”部分,手动输入授权 URI 和令牌 URI。授权 URI 是您将被重定向以授予同意的地方。令牌 URI 是系统可以用授权码换取访问令牌或刷新令牌的端点。请查阅特定集成或第三方服务的文档以获取正确的值。

现在,配置应该如下所示:

点击“保存”。现在您应该会看到一个名为重定向 URI 的新只读字段。请注意,其最后一部分特定于您的已连接应用。您需要向您的服务提供商(在此示例中为 Google)注册此重定向 URI。

复制重定向 URI,前往第三方服务并创建新的 OAuth 2.0 客户端凭据。您需要指定授权的 JavaScript 来源以及您刚刚复制的重定向 URI。将您的 frappe 实例的域名输入为授权的 JavaScript 来源。在生产服务器上,这可能是 https://erp.mycompany.com。对于本地开发,您可以使用类似 http://localhost:8000 的内容。

授权的 JavaScript 来源和授权的重定向 URI 必须正确,这一点至关重要。否则,您将无法连接。

将第三方服务的客户端 ID 和密钥复制到您的已连接应用并保存。

接下来,您需要定义您的系统需要访问的范围(scopes)。在此示例中,我们要连接到一个电子邮件收件箱,因此我们需要范围 https://mail.google.com/。对于其他用例,请查阅您的服务提供商文档中可用的范围。

最后,我们可以定义额外的参数。Google 文档说明我们需要参数 access_type=offline,以便我们的服务器可以在后台访问电子邮件收件箱,并在无需用户干预的情况下持续刷新访问令牌。如果我们不传递此参数,临时访问权限将很快过期,我们将需要反复登录。

现在,您和您的用户可以点击右上角的“连接到…”。这将打开一个新的浏览器选项卡,显示第三方服务的同意屏幕。之后,您将被重定向回您的 frappe 实例。

在后端,您的服务器将把重定向期间获得的代码兑换为临时访问令牌,并可选地兑换为刷新令牌。然后,这些令牌可用于代表您访问第三方服务。

用自定义脚本覆盖链接查询

您可以通过工作台的客户端脚本 DocType 使用 set_query 来覆盖标准链接查询。

set_query 方法支持两种格式:set_query(field_name, options_function()) 用于常规字段,或 set_query(field_name, child_table_name, options_function()) 用于子表中的字段。

1. 添加筛选条件

您可以为查询添加筛选条件:

frappe.ui.form.on("Bank Reconciliation", "onload", function(frm) {
 frm.set_query("bank_account", function() {
 return {
 "filters": {
 "account_type": "Bank",
 "group_or_ledger": "Ledger"
 }
 };
 });
});

更复杂的查询示例:

frappe.ui.form.on("Bank Reconciliation", "onload", function(frm){
 frm.set_query("bank_account", function(){
 return {
 "filters": [
 ["Bank Account", "account_type", "=", "Bank"],
 ["Bank Account", "group_or_ledger", "!=", "Group"]
 ]
 }
 });
});

2. 调用其他方法生成结果

您还可以设置在查询时调用的服务器端方法:

frm.set_query("item_code", "items", function() {
 return {
 query: "erpnext.controllers.queries.item_query",
 filters: frm.doc.enquiry_type === "Maintenance" ?
 {"is_service_item": "Yes"} : {"is_sales_item": "Yes"}
 };
});

自定义方法

自定义方法应返回一个用于自动选择的项目列表。如果您想发送额外的数据,可以在列表中发送多个列。

自定义方法的参数如下:

def custom_query(doctype, txt, searchfield, start, page_len, filters)

示例:

# searches for leads which are not converted
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def lead_query(doctype, txt, searchfield, start, page_len, filters):
 return frappe.db.sql("""
 SELECT name, lead_name, company_name
 FROM `tabLead`
 WHERE docstatus < 2
 AND ifnull(status, '') != 'Converted'
 AND ({key} LIKE %(txt)s
 OR lead_name LIKE %(txt)s
 OR company_name LIKE %(txt)s)
 {mcond}
 ORDER BY
 IF(LOCATE(%(_txt)s, name), LOCATE(%(_txt)s, name), 99999),
 IF(LOCATE(%(_txt)s, lead_name), LOCATE(%(_txt)s, lead_name), 99999),
 IF(LOCATE(%(_txt)s, company_name), LOCATE(%(_txt)s, company_name), 99999),
 name, lead_name
 LIMIT %(start)s, %(page_len)s
 """.format(**{
 'key': searchfield,
 'mcond':get_match_cond(doctype)
 }), {
 'txt': "%{}%".format(txt),
 '_txt': txt.replace("%", ""),
 'start': start,
 'page_len': page_len
 })

注意: @frappe.whitelist() 用于向客户端暴露 lead_query
并且 @frappe.validate_and_sanitize_search_inputs 装饰器用于验证和清理通过 API 或客户端请求发送的用户输入,以避免潜在的 SQL 注入。

更多示例请参见:

https://github.com/frappe/erpnext/blob/develop/erpnext/controllers/queries.py

对话框类型

Frappe 提供了一组标准对话框,在编码时非常有用。

警告对话框

警告对话框用于显示非阻塞性的消息。

它有两个参数:

  • txt: 要在 Alert Dialog 中显示的消息
  • seconds: 消息显示的持续时间。默认值为 3 seconds。

示例

show_alert(‘你好,你有新消息’, 5);


提示对话框

提示对话框用于从用户那里收集数据。

它有 4 个参数:

  • fields: 包含字段对象的列表
  • callback: 用于处理对话框中数据的函数
  • title: 对话框的标题
  • primary_label: 主按钮的标签

示例

frappe.prompt([
{‘fieldname’: ‘birth’, ‘fieldtype’: ‘Date’, ‘label’: ‘出生日期’, ‘reqd’: 1}
],
function(values){
show_alert(values, 5);
},
‘年龄验证’,
‘订阅我’
)


确认对话框

确认对话框用于在执行操作前获取用户的确认。

它有 3 个参数:

  • mesage: 要在对话框中显示的消息
  • onyes: 肯定确认时的回调函数
  • oncancel: 否定确认时的回调函数

示例

frappe.confirm(
‘确定要离开此页面吗?’,
function(){
window.close();
},
function(){
show_alert(‘感谢您继续在此操作!’)
}
)


消息打印

消息打印用于向用户显示信息。

它有 2 个参数:

  • message: 要显示的消息。可以是 HTML 字符串
  • title: 对话框的标题

示例

msgprint(“服务器状态“

  • “

“

  • “”
  • “* 28% 内存
    “
  • “* 12% 处理器
    “
  • “* 0.3% 磁盘
    “
  • “
    “, ‘服务器信息’)

自定义对话框

您可以使用 frappe.ui.Dialog 扩展并构建自己的自定义对话框

示例

var d = new frappe.ui.Dialog({
‘fields’: [
{‘fieldname’: ‘ht’, ‘fieldtype’: ‘HTML’},
{‘fieldname’: ‘today’, ‘fieldtype’: ‘Date’, ‘default’: frappe.datetime.nowdate()}
],
primary_action: function(){
d.hide();
show_alert(d.get_values());
}
});
d.fields_dict.ht.$wrapper.html(‘你好,世界’);
d.show();

删除网格行时触发事件

当子表中的某一行被删除时(即用户点击 delete 按钮),若要触发相应事件,您需要在子表中为 fieldname_remove 事件添加一个处理程序,其中 fieldname 是子表在父表声明中的字段名。

例如:

假设您的父 DocType 名为 Item,它有一个表字段链接到 Item Color DocType,声明名称为 color。

要“捕获”删除事件,请执行以下操作:

frappe.ui.form.on('Item Color', {
 color_remove: function(frm) {
 // You code here
 // If you console.log(frm.doc.color) you will get the remaining color list
 }
);

同样的方法也用于触发添加事件(当用户点击 add row 按钮时):

frappe.ui.form.on('Item Color', {
 color_remove: function(frm) {
 // You code here
 // If you console.log(frm.doc.color) you will get the remaining color list
 },
 color_add: function(frm) {}
});

请注意,处理程序是在子 DocType 表 form.ui.on 上进行的,而不是在父 DocType 上,因此一个最小化的完整示例如下:

frappe.ui.form.on('Item',{
 // Your client side handling for Item
});

frappe.ui.form.on('Item Color', {
 color_remove: function(frm) {
 // Deleting is triggered here
 }
);

处理程序包括:

  1. fieldname_add(添加)
  2. fieldname_move(移动)
  3. fieldname_before_remove(移除前)
  4. fieldname_remove(移除)

为表单添加自定义按钮

要在表单上创建自定义按钮,您需要编辑与您的文档类型关联的 JavaScript 文件。例如,如果您想向用户表单添加自定义按钮,则必须编辑 user.js。

在此文件中,您需要编写一个新方法 add_custom_button,该方法应向您的表单添加一个按钮。

add_custom_button(...) 的函数签名

frm.add_custom_button(__(buttonName), function() {
// perform desired action such as routing to new form or fetching etc.
}, __(groupName));

示例 1:向用户表单添加按钮

我们应该编辑 frappe/core/doctype/user/user.js

frappe.ui.form.on('User', {
   refresh: function(frm) {
       frm.add_custom_button(__('Get User Email Address'), function() {
           frappe.msgprint(frm.doc.email);
       }, __("Utilities"));
   }
});

您应该会在用户表单上看到一个按钮,如下所示:

运行后台作业

有时,您可能不希望用户请求立即执行,而是将其添加到队列中,由后台工作进程执行。这样做的好处是,您的 Web 工作进程可以保持空闲以执行其他请求,并且较长的任务不会耗尽您的所有资源。

Frappe 使用 Python RQ 来运行后台任务。以下示例说明了如何将任务加入队列:

import frappe

def long_job(arg1, arg2):
 frappe.publish_realtime('msgprint', 'Starting long job...')
 # this job takes a long time to process
 frappe.publish_realtime('msgprint', 'Ending long job...')

def enqueue_long_job(arg1, args2):
 frappe.enqueue('myapp.mymodule.long_job', arg1=arg1, arg2=arg2)

这会将 long_job 加入名为 default 的队列。其他可用的队列包括 long 和 short。您应根据预估的任务执行时长来选择适当的队列。

从版本 14 开始,您还可以通过在入队调用中将 at_front 作为 True 传递,将任务添加到队列的前端。

在文档对象上调用延迟操作

您也可以在文档对象上调用延迟操作,例如在库存盘点中,如果项目超过 100 个,则会作为后台任务执行。

示例:您可以调用 doc.queue_action('submit')

注意:这仅适用于 save、submit、cancel

如果您预计某些操作的执行量非常大,也可以将其推送到后台执行。

例如:

def submit(self):
 if len(self.items) > 100:
  self.queue_action('submit')
 else:
  self._submit()

调试

如果您正在使用 bench start

您将在终端中看到日志。

注意:默认的工作进程不会自动重启,因此您需要在修改后终止 bench 并重新启动。