Ragic workflow 入門
Ragic 提供基於 Nashorn 的伺服器端 JavaScript workflow 引擎,支援 ECMAScript 5.1。此引擎可在表單之間實現複雜的商業邏輯自動化。
Nashorn 支援 ECMAScript 5.1,因此請避免使用 ECMAScript 6 語法。此外,由 Java 傳遞給 JavaScript 的資料(例如陣列)可能受到某些限制,無法直接使用 JavaScript 的 join、map 等方法。不過,如果你在 workflow 中直接定義 JavaScript 陣列並將資料存入,這些 JavaScript 方法就可以正常使用。同樣地,瀏覽器專用的 API 如 setTimeout、setInterval、alert、document 等,也無法在此使用。
var query = db.getAPIQuery(pathToForm);
var entry = query.getAPIResult();
var values = entry.getFieldValues(fieldId); // 回傳陣列
var str = values.join(","); // 無法使用,會導致錯誤
var ary = [];
for (var i = 0; i < values.length; i++) {
ary.push(values[i]);
}
var str = ary.join(","); // 可以使用
JavaScript workflow 能做什麼?
Ragic 的表單設計介面可以處理大部分的資料管理工作,例如建立、編輯和查詢記錄都不成問題。但另一方面,手動維護資料在一段時間後可能會變得耗時且單調。這時 Ragic 的使用者就會開始思考如何自動化這些流程。在 Ragic 內部,有一個強大的腳本引擎,你可以撰寫在伺服器端執行的 JavaScript,來取得表單上的資料、進行修改,甚至一鍵建立多筆記錄。常見的用途包括更新庫存、根據另一筆記錄建立新記錄(從報價單建立銷售訂單、從銷售線索建立聯絡人),或者根據資料庫資料進行記錄驗證。
有 5 種主要方式可以執行你的 JavaScript workflow:
另外還有 Global workflow,你可以在其中放置多個 workflow 腳本共用的 JavaScript 函數定義。
快速開始
建立第一個 workflow
- 開啟您的 Ragic 表單
- 點擊表單標籤選單(表單名稱旁的向下箭頭)
- 選擇 Javascript workflow
- 選擇 workflow 類型(例如:Post workflow)
- 撰寫您的 JavaScript 程式碼:
// Post workflow:當記錄儲存時記錄訊息
var entry = param.getUpdatedEntry();
var recordId = entry.getRootNodeId();
log.info("Record saved: " + recordId);
存取表單資料
// 取得目前表單的 query
var query = db.getAPIQuery("/your-path/1");
// 取得特定記錄
var entry = query.getAPIEntry(12345);
// 讀取欄位值
var customerName = entry.getFieldValue(1000001);
修改記錄
// 取得並修改記錄
var entry = query.getAPIEntry(12345);
entry.setFieldValue(1000002, "Updated Value");
entry.save();
下一步
- 核心概念 - 了解資料模型
- workflow 類型 - 選擇適合的 workflow
- API 參考 - 完整 API 文件
核心概念
名詞解釋
在 workflow 中,有幾個特定的術語經常出現。這些術語的定義如下。假設我們表單中的一筆資料的 URL 為 https://www.ragic.com/testAP/testForm/1/3:
| 術語 | 定義 |
|---|---|
| APName | testAP,使用者的資料庫名稱 |
| Path | /testForm,此表單所在的頁籤;前面的斜線是必需的 |
| SheetIndex | 1,此表單在頁籤中的索引 |
| pathToForm | /testForm/1,也就是 Path 和 SheetIndex 的組合,常用作參數 |
| rootNodeId, recordId | 3,這筆資料的 ID(也稱為記錄);也可以透過 getNewNodeId(keyFieldId) 或 getOldNodeId(keyFieldId) 取得 |
| Key field | 此表單的主鍵;keyFieldId 是主鍵欄位的 ID,可在資料庫欄位定義文件中找到,或使用 entry.getKeyFieldId() 取得 |
| Field | 欄位,fieldId 是欄位的 ID,fieldName 是欄位的名稱。fieldId 可在資料庫欄位定義文件中找到,或在設計模式中選取欄位後進入「欄位設定」=>「基本」;欄位名稱下方的七位數字即為 fieldId |
| Subtable | 子表格,可以想像成表單下方的另一個表單;它也有自己的 keyFieldId、fieldId 和 rootNodeId |
| subtableId | 子表格的 ID,可在資料庫欄位定義文件中找到。可以想像成子表格的 keyFieldId |
| subtableRowIndex | 子表格資料的索引,通常透過迴圈指定 |
| subtableFieldId | 子表格欄位的 ID,可在資料庫欄位定義文件中找到。可以想像成子表格的 fieldId |
| subtableRootNodeId | 子表格記錄的 ID,可使用 getSubtableRootNodeId(subtableId, subtableRowIndex) 取得。可以想像成子表格的 rootNodeId |
顯示訊息
您可以像這樣顯示一個彈出訊息。要特別注意的是動作按鈕不支援 setStatus('CONFIRM')。
response.setStatus("WARN");
response.setMessage(message);
另外因為 workflow 不支援 console 或 alert 等 JavaScript 指令,所以如果您想進行除錯,可以透過 log.setToConsole(true) 與 log.println(message) 來顯示。
var message = "hello ragic";
log.setToConsole(true); // 叫出 console 區塊
log.println(message); // 印出 hello ragic
資料模型
記錄與項目
記錄(也稱為項目)是 Ragic 中資料的基本單位。每筆記錄由唯一的 root node ID(也稱為記錄 ID)識別。
// 透過 ID 取得記錄
var entry = query.getAPIEntry(12345);
// 取得記錄的 ID
var recordId = entry.getRootNodeId(); // 回傳 12345
欄位與 fieldId
表單中的每個欄位都有唯一的欄位 ID(也稱為 fieldId)。欄位 ID 是像 1000001 這樣的數字。
// 透過 ID 讀取欄位值
var value = entry.getFieldValue(1000001);
// 透過欄位名稱讀取(替代方式)
var value = entry.getFieldValueByName("客戶名稱");
子表格
子表格是記錄中重複列的群組。每個子表格都有一個子表格根欄位 ID,每一列都有自己的 root node ID。
// 計算子表格中的列數
var rowCount = entry.getSubtableSize(1000100);
// 讀取特定列的值
var qty = entry.getSubtableFieldValue(1000100, 0, "1000101");
// 新增一列(使用負數 ID)
entry.setSubtableFieldValue(1000101, -100, "新值");
workflow 情境
可用物件
不同的 workflow 類型可存取不同的全域物件:
| 物件 | 動作按鈕 | Pre workflow | Post workflow | Daily workflow | Approval workflow |
|---|---|---|---|---|---|
db |
✓ | ✓ | ✓ | ✓ | ✓ |
response |
✓ | ✓ | ✓ | ✓ | ✓ |
user |
✓ | ✓ | ✓ | ✓* | ✓ |
param |
- | ✓ | ✓ | - | - |
approval |
✓ | - | ✓ | - | - |
approvalParam |
- | - | - | - | ✓ |
mailer |
✓ | ✓ | ✓ | ✓ | ✓ |
util |
✓ | ✓ | ✓ | ✓ | ✓ |
account |
✓ | ✓ | ✓ | ✓ | ✓ |
log |
✓ | ✓ | ✓ | ✓ | ✓ |
* Daily workflow 中,user 代表系統使用者 dailyJob@ragic.com,擁有管理員權限,不是真實使用者。
魔術變數
只有一個魔術變數可用:
__actionButtonExecuteNodeId- 記錄 ID(僅限動作按鈕 workflow)
對於其他情境,請使用提供的物件:
- 記錄 ID:
param.getRootNodeId()或entry.getRootNodeId() - 使用者電子郵件:
user.getEmail()(Daily workflow 中回傳dailyJob@ragic.com) - 帳號名稱:
account.getApname()
資源限制
workflow 具有內建限制以防止程序失控:
| 資源 | 限制 |
|---|---|
| 資料庫查詢 | 1,000,000 次 |
| 建立記錄 | 45,000 筆 |
| HTTP 請求 | 500 次 |
| 檔案下載 | 100 次 |
| 郵件發送 | 視訂閱方案而定 * |
| 每次查詢結果 | 5,000 筆 |
* 郵件發送配額根據您的訂閱方案決定。免費方案每日 100 封,付費方案為每位使用者每日 1,000 封起。詳細配額請參考您的方案說明。
workflow 類型
1. 動作按鈕 workflow
使用者點擊動作面板中已設定的按鈕時執行的腳本。
設定方式: 要撰寫動作按鈕腳本,在表單上按右鍵並選擇 Javascript workflow:

然後從頂部下拉選單中選擇 installed sheet scope:

接著你可以進入表單頁面設計,新增一個類型為 JS Workflow 的動作按鈕,並引用你所撰寫的 JavaScript 函數。

請注意,你可以在函數呼叫的參數中使用 {id} 來傳遞當前記錄的記錄 ID,例如:
setStatus({id});
請注意動作按鈕不支援 setStatus('CONFIRM')。
使用案例:
- 依需求產生報表
- 發送手動通知
- 對選定記錄進行批次操作
可用物件: db, response, user, approval, mailer, util, account, log
// 動作按鈕:將記錄匯出至外部系統
function setStatus(id) {
var recordId = id;
var query = db.getAPIQuery("/orders/1");
var entry = query.getAPIEntry(recordId);
var orderData = {
orderId: entry.getFieldValue(1000001),
customer: entry.getFieldValue(1000002),
total: entry.getFieldValue(1000003),
};
var result = util.postURL(
"https://api.external-system.com/orders",
JSON.stringify(orderData)
);
response.setStatus("SUCCESS");
response.setMessage("訂單匯出成功");
}
2. Post workflow
記錄儲存後立即執行。適用於公式無法處理的自動化變更。
設定方式: 要新增 Post workflow,在表單上按右鍵並選擇 Javascript workflow:

然後從頂部下拉選單中選擇 Post-workflow。典型的 Post workflow 看起來像這樣:
var recordId = param.getNewNodeId(keyFieldId);
var query = db.getAPIQuery(pathToForm);
var record = query.getAPIEntry(recordId);
// 在此處理取得的記錄
請注意,如果你在列表頁面修改資料,Post workflow 不會被執行。
使用案例:
- 更新相關記錄
- 庫存調整
- 儲存後發送通知
- 建立稽核日誌
可用物件: db, response, user, param, approval, mailer, util, account, log
// Post workflow:訂單後更新庫存
var entry = param.getUpdatedEntry();
var productId = entry.getFieldValue(1000010);
var quantity = parseInt(entry.getFieldValue(1000011));
// 取得庫存記錄
var invQuery = db.getAPIQuery("/inventory/1");
invQuery.addFilter(1000001, "=", productId);
var invEntry = invQuery.getAPIResult();
if (invEntry != null) {
var currentStock = parseInt(invEntry.getFieldValue(1000002));
invEntry.setFieldValue(1000002, (currentStock - quantity).toString());
invEntry.save(); // 默認不會觸發 workflow
}
3. Pre workflow
記錄儲存前執行,用於驗證。可透過狀態碼阻止儲存。一般而言,大部分的驗證可以透過前端的正規表示式檢查或自由文字欄位的唯一值勾選來完成。但對於更複雜的後端檢查,就需要使用 Pre workflow。
設定方式: 要新增 Pre workflow,在表單上按右鍵並選擇 Javascript workflow:

然後從頂部下拉選單中選擇 Pre-workflow。以下是一個簡單的範例:假設我們有一個列表,

而我們想確保要儲存的價格不是負數。
/**
* 主鍵欄位: 1000004
*
* 欄位名稱 欄位 ID
* ----------- --------
* ID : 1000001
* 價格 : 1000002
* 名稱 : 1000003
* 是否可用? : 1000005
*/
function showMsg(str) {
response.setStatus("INVALID");
response.setMessage(str);
}
function ifThePriceIsNegative() {
var newPrice = param.getNewValue(1000002);
if (parseInt(newPrice) < 0) {
return true;
}
}
if (ifThePriceIsNegative()) {
showMsg("價格為負數!!");
}
現在我們嘗試儲存一個負數價格,就會看到:

阻擋儲存: 若要阻止記錄被儲存,請呼叫 response.setStatus("INVALID")(或 "ERROR")。當狀態設為這兩個值之一時,儲存會被阻擋,並向使用者顯示 response.setMessage() 設定的訊息。如果 Pre workflow 執行完畢時未設定這些狀態,儲存會正常進行。
值得注意的是,在 Ragic 撰寫 Pre workflow 時無法使用 entry.getFieldValue(因為 Ragic 尚未儲存)。請改用 param 來取得舊值和新值。
使用案例:
- 欄位驗證
- 強制執行商業規則
- 儲存前自動填入欄位
- 防止重複項目
可用物件: db, response, user, param, mailer, util, account, log
重要: 無法使用 entry.getFieldValue() - 必須使用 param.getNewValue()
// Pre workflow:驗證訂單總額
var total = parseFloat(param.getNewValue(1000003));
var creditLimit = parseFloat(param.getNewValue(1000004));
if (total > creditLimit) {
response.setStatus("INVALID");
response.setMessage("訂單總額超過信用額度");
}
4. Daily workflow
每日自動執行。預設時間為帳號設定時區的 19:00,可透過公司設定變更。
設定方式: 要新增 Daily workflow,在頁籤上按右鍵並選擇 Global Javascript Workflow:

然後從頂部下拉選單中選擇 Daily Workflow。

預設情況下,Daily workflow 在帳號設定時區的 19:00 執行。你可以在公司設定中變更預設排程執行時間和公司所在時區。若要專門為 Daily workflow 設定不同的執行時間,請直接在排程作業中調整設定。
點擊 History 可以查看 workflow 修改紀錄。

使用案例:
- 產生每日報表
- 發送提醒郵件
- 資料清理和歸檔
- 週期性計算
可用物件: db, response, user, mailer, util, account, log
注意: user 物件代表系統使用者 dailyJob@ragic.com,擁有管理員權限,不是真實使用者。
// Daily workflow:發送逾期提醒
var query = db.getAPIQuery("/invoices/1");
var today = new Date();
var todayStr =
today.getFullYear() + "/" + (today.getMonth() + 1) + "/" + today.getDate();
query.addFilter(1000005, "<", todayStr); // 到期日
query.addFilter(1000006, "=", "未付款"); // 狀態
var results = query.getAPIResultsFull();
while (results.hasNext()) {
var entry = results.next();
var customerEmail = entry.getFieldValue(1000002);
var invoiceNum = entry.getFieldValue(1000001);
mailer.compose(
customerEmail,
null,
"billing@company.com",
"帳務部門",
"發票 " + invoiceNum + " 已逾期",
"請儘速處理您的未付款發票。"
);
mailer.send();
}
5. Approval workflow
當簽核流程建立、完成(全部核准)、拒絕或取消時觸發。
設定方式: 要新增 Approval workflow,在表單上按右鍵並選擇 Javascript workflow:

然後從頂部下拉選單中選擇 Approval Workflow。
可用物件: db, response, user, approvalParam, mailer, util, account, log
簽核動作:
CREATE- 簽核流程開始FINISH- 所有步驟完成(全部核准)REJECT- 簽核在任一步驟被拒絕CANCEL- 簽核被取消
// Approval workflow:完成時通知
var action = approvalParam.getApprovalAction();
var recordId = approvalParam.getEntryRootNodeId();
if (action === "FINISH") {
var query = db.getAPIQuery("/purchase-orders/1");
var entry = query.getAPIEntry(recordId);
var requester = entry.getFieldValue(1000001);
mailer.sendAppNotification(
requester,
"您的採購單已核准",
"/purchase-orders/1",
recordId
);
}
處理所有簽核動作
approvalParam 是 Approval workflow 範圍內的預定義變數。您可以取得記錄 ID 和簽核動作,然後根據需求進行自訂:
var action = approvalParam.getApprovalAction();
var recordId = approvalParam.getEntryRootNodeId();
var query = db.getAPIQuery(pathToForm);
var entry = query.getAPIEntry(recordId);
if (action === "CANCEL") {
entry.setFieldValue(STATUS_FIELD, "簽核已取消!");
} else if (action === "FINISH") {
entry.setFieldValue(STATUS_FIELD, "簽核已完成!");
} else if (action === "CREATE") {
entry.setFieldValue(STATUS_FIELD, "簽核已建立!");
} else if (action === "REJECT") {
entry.setFieldValue(STATUS_FIELD, "簽核已拒絕!");
}
// 注意:僅有 CREATE、FINISH、REJECT 和 CANCEL 是有效的動作
entry.save();
6. Global workflow
可在同一帳號內所有 workflow 中共用的函數庫。Global workflow 在腳本引擎初始化時會被執行,其中定義的函數和變數可在上述任何類型的 workflow 中使用。它非常適合放置那些原本需要在多個表單中重複撰寫的腳本。
設定方式: 要新增 Global workflow,在頁籤上按右鍵並選擇 Global Javascript Workflow:

點擊 History 可以查看 workflow 修改紀錄。

運作方式: Global workflow 腳本在腳本引擎初始化時最先被解析,在任何Installed sheet scope、Pre workflow 或 Post workflow 程式碼之前執行。在 Global workflow 中定義的函數和變數,對同一次執行中的所有後續 workflow 腳本都是可用的,因為它們共用相同的 JavaScript 引擎實例。
解析順序:
- Global workflow——每次引擎初始化時解析一次
- Installed sheet scope(每個表單的共用程式碼)——在特定 workflow 之前解析
- Pre workflow / Post workflow / 動作按鈕 / Approval workflow / Daily workflow——實際的 workflow 腳本
// Global workflow:定義共用函數
function formatCurrency(amount) {
return "$" + parseFloat(amount).toFixed(2);
}
function sendNotification(email, subject, message) {
mailer.compose(email, null, "system@company.com", "系統", subject, message);
mailer.send();
}
// 這些函數現在可在所有前置/後置/動作按鈕workflow中使用:
// var formatted = formatCurrency(total);
// sendNotification(email, "訂單確認", message);
注意: Global workflow 函數與所有 workflow 共用範圍。避免在頂層定義常見名稱的變數(如
result或data),因為它們可能與個別 workflow 中的變數衝突。
API 參考
db 物件
查詢和操作記錄的主要進入點。
db.getAPIQuery(pathToForm)
為指定表單建立 query。
| 參數 | 類型 | 說明 |
|---|---|---|
| pathToForm | string | 表單路徑(例如 "/sales/1") |
回傳: query
var query = db.getAPIQuery("/sales/1");
db.getAPIQuery()
為目前表單(workflow 所附加的表單)建立 query。
回傳: query
var query = db.getAPIQuery();
db.getAPIQuery(path, sheetIndex)
以分別指定路徑和表單索引的方式建立 query。適用於多層路徑。
| 參數 | 類型 | 說明 |
|---|---|---|
| path | string | 頁籤路徑(例如 "/sales/orders") |
| sheetIndex | number | 頁籤內的表單索引 |
回傳: query
var query = db.getAPIQuery("/sales/orders", 1);
db.getApname()
回傳目前的帳號(資料庫)名稱。
回傳: string
db.getPath()
回傳目前的表單路徑。
回傳: string
db.getSheetIndex()
回傳目前的表單索引。
回傳: number
db.deleteOldRecords(pathToForm, daysOld)
刪除超過指定天數的記錄(每次最多 500 筆)。以記錄的建立日期進行比對。例如,若今天是 2025/07/10 且 daysOld 為 1,將刪除 2025/07/09 00:00:00 之前建立的記錄。
| 參數 | 類型 | 說明 |
|---|---|---|
| pathToForm | string | 表單路徑 |
| daysOld | number | 刪除超過此天數的記錄 |
db.deleteOldRecords("/logs/1", 90); // 刪除超過 90 天的記錄
db.deleteOldRecords(pathToForm, daysOld, hasTime)
同上,但 hasTime 為 true 時以時間層級精度進行比對。若 hasTime 為 true,則以執行當下的時間為基準進行比對。例如,若執行時間為 2025/07/10 21:00:00 且 daysOld 為 1,將刪除 2025/07/09 21:00:00 之前建立的記錄。
| 參數 | 類型 | 說明 |
|---|---|---|
| pathToForm | string | 表單路徑 |
| daysOld | number | 天數門檻 |
| hasTime | boolean | 比對時是否包含時間 |
db.recalculateAll(pathToForm)
重新計算指定表單中所有記錄的公式欄位。
| 參數 | 類型 | 說明 |
|---|---|---|
| pathToForm | string | 表單路徑 |
db.recalculateAll("/sales/orders/1");
db.recalculateAll(pathToForm, fieldId...)
重新計算所有記錄中的特定公式欄位。
| 參數 | 類型 | 說明 |
|---|---|---|
| pathToForm | string | 表單路徑 |
| fieldId... | number | 一個或多個欄位 ID |
db.recalculateAll("/sales/orders/1", 1000005, 1000006);
db.loadLinkAndLoadAll(pathToForm)
同步指定表單中所有記錄的連結載入欄位。
| 參數 | 類型 | 說明 |
|---|---|---|
| pathToForm | string | 表單路徑 |
db.loadLinkAndLoadAll("/sales/orders/1");
db.setLogRecalcCostTime(enable)
設為 true 時,在「表單 workflow 公式重新計算執行時間日誌」中顯示執行時間,該日誌記錄使用 db.recalculateAll() 重新計算表單所花費的時間。
| 參數 | 類型 | 說明 |
|---|---|---|
| enable | boolean | 設為 true 啟用記錄 |
query 物件
query.getAPIResult()
取得第一筆符合條件的記錄。
回傳: Entry 物件或 null
query.addFilter(1000001, "=", "啟用");
var entry = query.getAPIResult();
query.getAPIEntry(recordId)
透過 ID 取得單筆記錄。
| 參數 | 類型 | 說明 |
|---|---|---|
| recordId | number | root node ID |
回傳: Entry 物件或 null
var entry = query.getAPIEntry(12345);
query.getAPIResultsFull()
取得符合篩選條件的多筆記錄。回傳迭代器。
回傳: 具有 hasNext() 和 next() 方法的迭代器
var results = query.getAPIResultsFull();
while (results.hasNext()) {
var entry = results.next();
// 處理 entry
}
query.getAPIResultList()
取得所有符合篩選條件的記錄,以陣列形式回傳。與 getAPIResultsFull() 回傳迭代器不同,此方法會一次將所有符合條件的記錄載入記憶體並以陣列回傳。
注意: 大多數情況下建議使用
getAPIResultsFull()——它透過迭代器逐筆處理記錄,記憶體使用較有效率。僅在需要隨機存取結果(例如以索引存取)或需要多次遍歷相同結果時,才使用getAPIResultList()。
回傳: Entry[]
var entries = query.getAPIResultList();
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
// 處理 entry
}
query.addFilter(fieldId, operator, value)
新增篩選條件。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
| operator | string | "=" 或 "eq"、">" 或 "gt"、">=" 或 "gte"、"<" 或 "lt"、"<=" 或 "lte"、"like"、"regex" |
| value | string | 要比較的值 |
query.addFilter(1000001, "=", "啟用");
query.addFilter(1000002, ">=", "2024/01/01");
query.setFullTextSearch(searchText)
對索引欄位進行全文搜尋。
| 參數 | 類型 | 說明 |
|---|---|---|
| searchText | string | 搜尋關鍵字 |
query.setFullTextSearch("重要關鍵字");
query.insertAPIEntry()
建立新記錄。
回傳: 新的 Entry 物件
var entry = query.insertAPIEntry();
entry.setFieldValue(1000001, "新記錄");
entry.save();
query.deleteEntry(nodeId)
永久刪除記錄。
| 參數 | 類型 | 說明 |
|---|---|---|
| nodeId | number | 要刪除記錄的 root node ID |
query.deleteEntry(12345);
query.deleteEntryToRecycleBin(nodeId)
將記錄移至資源回收筒(軟刪除)。
| 參數 | 類型 | 說明 |
|---|---|---|
| nodeId | number | 要移動記錄的 root node ID |
query.deleteEntryToRecycleBin(12345);
query.setUpdateMode()
針對更新情境最佳化查詢。此方法會:
- 忽略欄位遮罩——回傳真實值而非遮罩後的值(例如完整身份證號碼,而非
A1234*****) - 跳過全文索引字串的建立以提升效能
注意: 建議在讀取打算修改的記錄時呼叫
setUpdateMode(),尤其是表單中包含遮罩欄位時。但即使未呼叫此方法,setFieldValue()和save()仍可正常運作。
query.setUpdateMode();
var entry = query.getAPIEntry(12345);
entry.setFieldValue(1000001, "已更新");
entry.save();
query.setOrder(fieldId, orderDir)
設定查詢結果的排序方式。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 排序欄位 ID |
| orderDir | number | 1=升冪、2=降冪、3=次要升冪、4=次要降冪 |
query.setOrder(1000001, 1); // 依欄位 1000001 升冪排序
query.setLimitSize(size)
設定每次查詢回傳記錄的最大數量。預設情況下,查詢每次回傳 1000 筆記錄。不建議每次查詢回傳過多記錄,因為可能會消耗過多記憶體並影響效能。建議改用 setLimitFrom() 進行分頁。
| 參數 | 類型 | 說明 |
|---|---|---|
| size | number | 回傳記錄的最大數量 |
query.setLimitSize(100);
query.setLimitFrom(offset)
設定分頁偏移量。此方法用於分頁瀏覽查詢中的所有記錄。設定 limitFrom 後,查詢將從指定的偏移量開始回傳記錄。由於查詢預設回傳 1000 筆記錄,您應該在回傳的記錄數等於限制大小時檢查是否有下一頁。
| 參數 | 類型 | 說明 |
|---|---|---|
| offset | number | 要跳過的記錄數量 |
query.setLimitFrom(100); // 跳過前 100 筆
query.setIfIgnoreFixedFilter(ignore)
設為 true 時,忽略表單上設定的固定篩選條件。
| 參數 | 類型 | 說明 |
|---|---|---|
| ignore | boolean | 設為 true 略過固定篩選條件 |
警告: 固定篩選條件用於實施列層級存取控制。繞過它們可能會暴露當前使用者不應看到的記錄。僅在 workflow 需要存取所有記錄(而不受使用者端篩選條件限制)時使用,並確保結果不會直接回傳給使用者。
query.setIgnoreAllMasks(ignore)
略過所有欄位的遮罩設定。
| 參數 | 類型 | 說明 |
|---|---|---|
| ignore | boolean | 設為 true 略過所有欄位遮罩 |
query.addIgnoreMaskDomain(fieldId)
略過特定欄位的遮罩設定。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 要取消遮罩的欄位 ID |
query.addIgnoreMaskDomain(1000005);
query.getKeyFieldId()
回傳表單的主鍵欄位 ID。
回傳: number
query.getFieldIdByName(fieldName)
依欄位名稱查詢欄位 ID。如果有多個相同名稱的欄位,將回傳第一個獨立的欄位 ID。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldName | string | 欄位名稱 |
回傳: number
var fieldId = query.getFieldIdByName("客戶名稱");
query.setGetUserNameAsSelectUserValue(enable)
控制「選擇使用者」欄位是否回傳使用者的顯示名稱而非電子郵件。預設(true)時,查詢會回傳「選擇使用者」欄位的使用者顯示名稱。設為 false 時,改為取得使用者的電子郵件。
| 參數 | 類型 | 說明 |
|---|---|---|
| enable | boolean | true 回傳顯示名稱(預設),false 回傳電子郵件 |
query.setFieldValueModeAsNodeIdOnly(mode)
設定欄位取值模式。啟用(true)時,透過 API 取得的欄位值將回傳連結記錄的 nodeId,而非顯示值。停用(false)時,API 將回傳欄位的實際顯示值(如文字、數字或日期)。
| 參數 | 類型 | 說明 |
|---|---|---|
| mode | boolean | true 僅回傳 nodeId,false 回傳顯示值 |
query.setFieldValueModeAsNodeIdAndValue(mode)
設定載入預設值時的欄位取值方式。啟用(true)時,透過 API 取得的欄位值將同時包含相關記錄的 nodeId 和顯示值。在載入預設值時,系統可以根據 nodeId 的有無來判斷欄位是否應載入預設值,而不是依賴欄位的顯示值是否為空。停用(false)時,僅回傳欄位的實際值(如文字、數字或日期)。
| 參數 | 類型 | 說明 |
|---|---|---|
| mode | boolean | true 同時回傳 nodeId 和顯示值 |
如果您發現呼叫 loadAllDefaultValues 或 loadDefaultValue 時,欄位預設值的行為與表單頁面上不同,可以使用此方法來改變欄位值的解析方式。
query.isFieldValueModeIsNodeIdOnly()
檢查目前欄位取值模式是否為僅回傳 nodeId。
回傳: boolean
query.setIfIncludeInfo(include)
設為 true 時,查詢結果將包含完整的記錄中繼資料(建立日期、建立者、簽核狀態等)。
| 參數 | 類型 | 說明 |
|---|---|---|
| include | boolean | true 包含記錄中繼資料 |
query.setIfIncludeInfo(true);
var entry = query.getAPIEntry(recordId);
var createDate = entry.getFieldValue("_create_date");
entry 物件
entry.getRootNodeId()
取得記錄的 root node ID。
回傳: number(未儲存的新記錄回傳 -1)
entry.getFieldValue(fieldId)
取得欄位值。不包括子表格欄位,子表格資料請使用 getSubtableFieldValue()。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
回傳: string 或 null
var name = entry.getFieldValue(1000001);
entry.getFieldValueByName(fieldName)
依欄位名稱取得欄位值。不包括子表格欄位。如果有多個相同名稱的欄位,將回傳第一個欄位的值。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldName | string | 欄位名稱 |
回傳: string 或 null
var name = entry.getFieldValueByName("客戶名稱");
entry.getFieldValues(fieldId)
以陣列形式取得多選欄位的值。不包括子表格欄位。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
回傳: string[]
var tags = entry.getFieldValues(1000003);
entry.setFieldValue(fieldId, value)
設定欄位值。對於子表格欄位,請使用 setSubtableFieldValue()。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
| value | string | 要設定的值 |
entry.setFieldValue(1000001, "新值");
entry.setFieldValue(fieldId, value, appendValue)
設定欄位值。appendValue 為 true 時,附加至多選欄位而非取代。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
| value | string | 要設定的值 |
| appendValue | boolean | true 為附加、false 為取代 |
entry.setFieldValue(1000003, "新標籤", true); // 附加至多選欄位
entry.setFieldValueByName(fieldName, value)
依欄位名稱設定欄位值。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldName | string | 欄位名稱 |
| value | string | 要設定的值 |
entry.setFieldValueByName("狀態", "已完成");
entry.removeFieldValue(fieldId, value)
從多選欄位中移除特定值。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
| value | string | 要移除的值 |
entry.getFieldValueFormatted(fieldId)
取得欄位的格式化顯示值(例如套用數字格式)。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
回傳: string 或 null
entry.getFieldIdByName(fieldName)
依欄位名稱查詢欄位 ID。如果有多個相同名稱的欄位,將回傳第一個欄位 ID。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldName | string | 欄位名稱 |
回傳: number
entry.getOriginalCreateUser()
取得最初建立此記錄的使用者電子郵件。
注意: 此方法需要在取得 entry 之前呼叫
query.setUpdateMode()或query.setIfIncludeInfo(true)。否則建立者資訊不會被載入,此方法會回傳空字串。
回傳: string
entry.extractTextFromFileUploadField(fieldId)
從檔案上傳欄位中擷取文字內容(例如 PDF、DOCX)。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 檔案上傳欄位 ID |
回傳: string
entry.setSendNotification(send)
控制儲存此記錄時是否觸發電子郵件通知。
| 參數 | 類型 | 說明 |
|---|---|---|
| send | boolean | true 發送通知 |
entry.setFieldFile(fieldId, fileName, fileContent)
以您提供的 fileName 和 fileContent 建立檔案,並儲存到指定的檔案上傳欄位,讓使用者可以在介面上下載此檔案。此方法適用於檔案上傳欄位和圖形欄位。對於子表格欄位,請使用 setSubtableFieldFile()。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 檔案上傳欄位 ID |
| fileName | string | 檔案名稱 |
| fileContent | string | 檔案內容 |
entry.setFieldFile(fieldId, fileName, fileContent, appendValue)
建立或附加檔案上傳。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 檔案上傳欄位 ID |
| fileName | string | 檔案名稱 |
| fileContent | string | 檔案內容 |
| appendValue | boolean | true 為附加檔案 |
entry.save()
儲存所有變更。
控制 Workflow 執行
默認行為: entry 對象在儲存時不會觸發 workflow(默認值為 false)。
觸發 workflow: 如需觸發其他表單的 workflow,使用 setIfExecuteWorkflow(true):
// 儲存時觸發該表單的 post-workflow
entry.setIfExecuteWorkflow(true);
entry.save();
重要限制: 在 post-workflow 中,如果試圖對同一張表單(觸發此 workflow 的表單)設置為 true,系統會拋出異常防止無限迴圈。
// 在 post-workflow 中
var entry = param.getUpdatedEntry();
entry.setIfExecuteWorkflow(true); // ❌ 會拋出異常:Workflow execution is denied
entry.save();
entry.getKeyFieldId()
回傳表單的主鍵欄位 ID。
回傳: number
entry.recalculateAllFormulas()
在儲存前重新計算項目中的所有公式。
entry.recalculateFormula(fieldId)
重新計算特定的公式欄位。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 要重新計算的欄位 ID |
注意: 如果兩個或多個欄位共享相同的 fieldId,請改用
recalculateFormula(fieldId, cellName)。
entry.recalculateFormula(fieldId, cellName)
使用 cellName 參數確定欄位的儲存格位置(如 A1、C2、H21 等),重新計算指定欄位的公式。此方法適用於具有相同 fieldId 的多個欄位。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
| cellName | string | 儲存格位置(例如 "A1"、"C2"、"H21") |
entry.lock()
鎖定記錄,防止其他使用者編輯。
entry.unlock()
解鎖記錄。
entry.isLocked()
檢查記錄是否已鎖定。
回傳: boolean
entry.setCreateHistory(enable)
控制儲存時是否建立歷史記錄。
| 參數 | 類型 | 說明 |
|---|---|---|
| enable | boolean | true 建立歷史記錄 |
警告: 設為
false會停用該次儲存操作的稽核紀錄。僅在大量更新記錄且歷史記錄會產生過多雜訊時使用。切勿為了對使用者隱藏變更而停用歷史記錄。
entry.isCreateHistory()
檢查是否啟用歷史記錄建立。
回傳: boolean
entry.setIgnoreEmptyCheck(ignore)
儲存時跳過必填欄位驗證。
| 參數 | 類型 | 說明 |
|---|---|---|
| ignore | boolean | true 跳過必填欄位驗證 |
警告: 此方法會繞過必填欄位檢查,可能導致不完整的記錄。僅在以程式方式建立或更新記錄,且某些必填欄位是刻意留空時使用。
entry.setRecalParentFormula(recalculate)
如果此表單是由另一表單的子表格建立的,或者被另一表單引用(亦即此表單具有父表單),此方法用來設定儲存後是否需要重新計算父表單的公式。
| 參數 | 類型 | 說明 |
|---|---|---|
| recalculate | boolean | true 儲存後重新計算父表單公式 |
entry.setIfDoLnls(enable)
設為 true 時,儲存後同步來源表單的連結載入。
| 參數 | 類型 | 說明 |
|---|---|---|
| enable | boolean | true 儲存後同步連結載入 |
您應該一律把這個方法加在來源表上,才能正確觸發其他表單的同步,並且在該組連結與載入中勾選「隨時同步載入欄位值」(可參考此篇文件)。
重要: 此方法僅支援在動作按鈕中使用。若您需要在 post-workflow 中同步連結與載入欄位,則可以直接取得需要更新的資料,並呼叫
loadAllLinkAndLoad()來進行同步。
entry.loadAllLinkAndLoad()
載入所有連結載入欄位的值。
entry.loadLinkAndLoad(linkFieldId)
載入特定連結載入欄位。
| 參數 | 類型 | 說明 |
|---|---|---|
| linkFieldId | number | 連結欄位 ID |
entry.loadLinkAndLoadField(loadFieldId)
載入單一載入欄位的值。
| 參數 | 類型 | 說明 |
|---|---|---|
| loadFieldId | number | 載入欄位 ID |
entry.loadAllDefaultValues(user)
為指定使用者載入所有預設值。
| 參數 | 類型 | 說明 |
|---|---|---|
| user | user | User 物件 |
entry.loadDefaultValue(fieldId, user)
載入特定欄位的預設值。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
| user | user | User 物件 |
entry.getSubtableSize(subtableId)
取得子表格的列數。
| 參數 | 類型 | 說明 |
|---|---|---|
| subtableId | number | 子表格 ID |
回傳: number
entry.getSubtableFieldValue(subtableId, rowIndex, fieldId)
讀取子表格中指定列和欄位的值。
| 參數 | 類型 | 說明 |
|---|---|---|
| subtableId | number | 子表格 ID |
| rowIndex | number | 列索引(從 0 開始) |
| fieldId | string | 子表格欄位 ID(字串) |
回傳: string 或 null
var value = entry.getSubtableFieldValue(1000100, 0, "1000101");
entry.getSubtableFieldValues(subtableId, rowIndex, fieldId)
取得子表格中多選欄位的所有值。
| 參數 | 類型 | 說明 |
|---|---|---|
| subtableId | number | 子表格 ID |
| rowIndex | number | 列索引(從 0 開始) |
| fieldId | string | 子表格欄位 ID(字串) |
回傳: string[]
entry.getSubtableRootNodeId(subtableId, rowIndex)
取得子表格指定列的 node ID。
| 參數 | 類型 | 說明 |
|---|---|---|
| subtableId | number | 子表格 ID |
| rowIndex | number | 列索引(從 0 開始) |
回傳: number
entry.setSubtableFieldValue(fieldId, rowNodeId, value)
設定子表格中指定列和欄位的值。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 子表格欄位 ID |
| rowNodeId | number | 列的 node ID |
| value | string | 要設定的值 |
entry.setSubtableFieldValue(fieldId, rowNodeId, value, appendValue)
設定或附加子表格欄位的值。appendValue 為 true 時,附加至多選欄位而非取代。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 子表格欄位 ID |
| rowNodeId | number | 列的 node ID |
| value | string | 要設定的值 |
| appendValue | boolean | true 為附加、false 為取代 |
entry.setSubtableFieldFile(fieldId, rowNodeId, fileName, fileContent)
上傳檔案至子表格的檔案上傳欄位。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 子表格欄位 ID |
| rowNodeId | number | 列的 node ID |
| fileName | string | 檔案名稱 |
| fileContent | string | 檔案內容 |
entry.setSubtableFieldFile(fieldId, rowNodeId, fileName, fileContent, appendValue)
上傳或附加檔案至子表格的檔案上傳欄位。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 子表格欄位 ID |
| rowNodeId | number | 列的 node ID |
| fileName | string | 檔案名稱 |
| fileContent | string | 檔案內容 |
| appendValue | boolean | true 為附加、false 為取代 |
entry.deleteSubtableRow(subtableId, rowNodeId)
依 node ID 刪除子表格列。
| 參數 | 類型 | 說明 |
|---|---|---|
| subtableId | number | 子表格 ID |
| rowNodeId | number | 列的 node ID |
entry.deleteSubtableRowByRowNumber(subtableId, rowIndex)
依索引刪除子表格列。
| 參數 | 類型 | 說明 |
|---|---|---|
| subtableId | number | 子表格 ID |
| rowIndex | number | 列索引(從 0 開始) |
entry.deleteSubtableRowAll(subtableId)
刪除子表格中的所有列。
| 參數 | 類型 | 說明 |
|---|---|---|
| subtableId | number | 子表格 ID |
response 物件
response.setStatus(status)
設定 workflow 結果狀態。
| 參數 | 類型 | 說明 |
|---|---|---|
| status | string | 狀態碼(見下方值) |
| 狀態 | 說明 |
|---|---|
| SUCCESS | 成功完成 |
| DEBUG | 除錯模式 |
| WARN | 警告(允許繼續) |
| CONFIRM | 需要確認 |
| INVALID | 驗證失敗(阻止儲存) |
| ERROR | 錯誤(阻止儲存) |
response.getStatus()
取得目前的狀態。
回傳: string
response.setMessage(message)
設定要顯示的訊息。可多次呼叫以堆疊訊息。
| 參數 | 類型 | 說明 |
|---|---|---|
| message | string | 要顯示的訊息文字 |
注意: 訊息會在執行過程中按順序累積,並在 workflow 執行完畢後一次顯示。它不像瀏覽器中的
console.log那樣即時輸出——使用者會在 workflow 結束時一次看到所有訊息。
response.setStatus("WARN");
response.setMessage("部分欄位未完成。");
response.setMessage("請在送出前再次確認。");
// 兩則訊息會在 workflow 結束後一起顯示
response.numOfMessages()
回傳已堆疊的訊息數量。
回傳: number
response.setOpenURL(url)
workflow 完成後導向至指定 URL。
| 參數 | 類型 | 說明 |
|---|---|---|
| url | string | 要開啟的 URL |
response.setOpenURL("https://www.example.com/result");
response.addOpenURL(url)
新增額外的 URL 在完成後開啟。可多次呼叫以開啟多個 URL。
| 參數 | 類型 | 說明 |
|---|---|---|
| url | string | 要開啟的 URL |
response.setOpenURLInNewTab(openInNewTab)
控制 URL 是否在新分頁開啟(預設:true)。
| 參數 | 類型 | 說明 |
|---|---|---|
| openInNewTab | boolean | true 在新分頁開啟(預設:true) |
response.getRootNodeId()
取得操作後記錄的 root node ID。
回傳: number
user 物件
user.getEmail()
取得使用者的電子郵件地址。
user.getUserName()
取得使用者的顯示名稱。
user.getGroups()
取得使用者的群組清單。
回傳: 字串清單(使用 .size() 取得數量,.get(i) 或 [i] 取得元素)
user.isInGroup(groupName)
檢查群組成員資格。
| 參數 | 類型 | 說明 |
|---|---|---|
| groupName | string | 群組名稱 |
if (user.isInGroup("經理")) {
// 允許操作
}
user.isValid()
檢查使用者帳號是否未過期。
回傳: boolean
user.isIdentifiable()
檢查使用者是否非訪客或匿名使用者。
回傳: boolean
param 物件 (Pre/Post-Workflow)
param.getNewValue(fieldId)
取得提交的欄位值。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
param.getOldValue(fieldId)
取得先前的欄位值。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
param.getNewValues(fieldId)
取得多選欄位提交的值。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
回傳: 字串清單(使用 .size() 取得數量,[i] 取得元素)。可能回傳 null。
param.getOldValues(fieldId)
取得多選欄位先前的值。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
回傳: 字串清單(使用 .size() 取得數量,[i] 取得元素)。可能回傳 null。
param.getNewNodeId(fieldId)
取得新值的 node ID。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
回傳: number
param.getOldNodeId(fieldId)
取得舊值的 node ID。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
回傳: number
param.getNewNodeIds(fieldId)
取得新值的 node ID(多選欄位適用)。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
回傳: 數字清單(使用 .size() 取得數量,[i] 取得元素)。可能回傳 null。
param.getOldNodeIds(fieldId)
取得舊值的 node ID(多選欄位適用)。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
回傳: 數字清單(使用 .size() 取得數量,[i] 取得元素)。可能回傳 null。
param.getOperationType(fieldId)
取得欄位的操作類型。
| 參數 | 類型 | 說明 |
|---|---|---|
| fieldId | number | 欄位 ID |
回傳: "I"(新增)、"M"(修改)或 "L"(連結 — 列表頁面的多選欄位變更,僅 Pre workflow)
param.isCreateNew()
檢查是否為新建記錄。
param.getRootNodeId()
取得記錄的 root node ID(新記錄為 -1)。
回傳: number
param.getUpdatedEntry()
取得已儲存的項目(僅限 Post workflow)。
param.getEntry()
getUpdatedEntry() 的別名(僅限 Post workflow)。
param.getSubtableEntry(subtableId)
取得子表格列參數。
| 參數 | 類型 | 說明 |
|---|---|---|
| subtableId | number | 子表格 ID |
var rows = param.getSubtableEntry(1000100);
for (var i = 0; i < rows.size(); i++) {
var qty = rows[i].getNewValue(1000101);
}
mailer 物件
mailer.compose(to, cc, from, fromPersonal, subject, content)
撰寫電子郵件。to 和 cc 參數皆可接受以逗號分隔的多個電子郵件地址。
| 參數 | 類型 | 說明 |
|---|---|---|
| to | string | 收件人電子郵件地址,逗號分隔 |
| cc | string | 副本電子郵件地址,逗號分隔 |
| from | string | 寄件人電子郵件地址 |
| fromPersonal | string | 寄件人顯示名稱 |
| subject | string | 電子郵件主旨 |
| content | string | HTML 電子郵件內容 |
mailer.compose(
"recipient@example.com",
null,
"sender@example.com",
"寄件者名稱",
"主旨",
"<p>HTML 內容</p>"
);
mailer.attach(url)
附加檔案(必須為 https URL)。你可以使用記錄的 URL 以 PDF、Excel 或合併列印格式附加 Ragic 記錄。URL 格式詳情請參閱常見模式中的電子郵件附件格式章節。
| 參數 | 類型 | 說明 |
|---|---|---|
| url | string | 要附加的檔案 URL |
mailer.setBcc(bcc)
設定密件副本收件人(以逗號分隔)。
| 參數 | 類型 | 說明 |
|---|---|---|
| bcc | string | 密件副本電子郵件地址,逗號分隔 |
mailer.send()
發送已撰寫的電子郵件。此方法內部呼叫 sendAsync()——兩者行為完全相同(非同步)。
注意: 我們對可發送的電子郵件數量有一定的限制。若您對電子郵件發送額度有任何疑問,請寄信至 support@ragic.com。
mailer.sendAsync()
非同步發送已撰寫的郵件。功能與 send() 相同。
mailer.setSendRaw(sendRaw)
設為 true 時,發送時不包含 Ragic HTML 範本。如果您的內容已經是 HTML 格式,請將其設定為 true。
| 參數 | 類型 | 說明 |
|---|---|---|
| sendRaw | boolean | true 不使用 Ragic 範本發送 |
mailer.sendMail(to, subject, content)
一行指令快速發送電子郵件。
| 參數 | 類型 | 說明 |
|---|---|---|
| to | string | 收件人電子郵件地址 |
| subject | string | 電子郵件主旨 |
| content | string | HTML 電子郵件內容 |
mailer.sendMail("recipient@example.com", "主旨", "<p>內容</p>");
mailer.sendMail(to, subject, content, attachmentURL)
一行指令發送附帶附件的電子郵件。
| 參數 | 類型 | 說明 |
|---|---|---|
| to | string | 收件人電子郵件地址 |
| subject | string | 電子郵件主旨 |
| content | string | HTML 電子郵件內容 |
| attachmentURL | string | 附件檔案 URL |
mailer.sendAppNotification(email, message)
若使用者已安裝 Ragic iOS 或 Android 應用程式,則發送行動應用程式推播通知給該使用者。
| 參數 | 類型 | 說明 |
|---|---|---|
| string | 使用者電子郵件地址 | |
| message | string | 通知訊息內容 |
mailer.sendAppNotification(email, message, pathToForm, nodeId)
若使用者已安裝 Ragic iOS 或 Android 應用程式,則發送行動應用程式推播通知給該使用者。當使用者點擊通知時,將會導向至指定的記錄。pathToForm 應為 "/forms/1" 格式(頁籤資料夾和表單索引,不包含帳號名稱)。
| 參數 | 類型 | 說明 |
|---|---|---|
| string | 使用者電子郵件地址 | |
| message | string | 通知訊息內容 |
| pathToForm | string | 表單路徑(例如 "/forms/1") |
| nodeId | number | 記錄的 root node ID |
util 物件
util.getURL(url)
發送 HTTP GET 請求。回傳回應內容的字串。
| 參數 | 類型 | 說明 |
|---|---|---|
| url | string | 請求的 URL |
回傳: string — 回應內容
var result = util.getURL("https://api.example.com/data");
log.info("Response: " + result);
// 解析 JSON 回應
var data = JSON.parse(result);
log.info("Name: " + data.name);
util.postURL(url, body)
發送 HTTP POST 請求。回傳回應內容的字串。
| 參數 | 類型 | 說明 |
|---|---|---|
| url | string | 請求的 URL |
| body | string | 請求本文 |
回傳: string — 回應內容
util.setHeader("Content-Type", "application/json");
var payload = JSON.stringify({ name: "Order #123", status: "confirmed" });
var result = util.postURL("https://api.example.com/orders", payload);
log.info("Response: " + result);
util.putURL(url, body)
發送 HTTP PUT 請求。
| 參數 | 類型 | 說明 |
|---|---|---|
| url | string | 請求的 URL |
| body | string | 請求本文 |
回傳: string
util.deleteURL(url)
發送 HTTP DELETE 請求。
| 參數 | 類型 | 說明 |
|---|---|---|
| url | string | 請求的 URL |
回傳: string
util.setHeader(name, value)
為後續的 HTTP 請求設定自訂標頭。
| 參數 | 類型 | 說明 |
|---|---|---|
| name | string | 標頭名 |
| value | string | 標頭值 |
util.setHeader("Authorization", "Bearer token");
util.setHeader("Content-Type", "application/json");
var result = util.postURL(url, body);
util.removeHeader(name)
移除先前設定的自訂標頭。
| 參數 | 類型 | 說明 |
|---|---|---|
| name | string | 要移除的標頭 |
util.downloadFile(fileUrl)
將 fileUrl 回傳的檔案下載至資料庫中的上傳資料夾。
| 參數 | 類型 | 說明 |
|---|---|---|
| fileUrl | string | 要下載的檔案 URL |
回傳: string — 已下載檔案的檔名,儲存於帳號的上傳資料夾中。可直接傳入 entry.setFieldValue() 以附加至檔案上傳欄位。
util.downloadFile(fileUrl, postBody)
透過 POST 請求將檔案下載至資料庫中的上傳資料夾。
| 參數 | 類型 | 說明 |
|---|---|---|
| fileUrl | string | 要下載的檔案 URL |
| postBody | string | POST 請求本文 |
回傳: string — 已下載檔案的檔名,儲存於帳號的上傳資料夾中。可直接傳入 entry.setFieldValue() 以附加至檔案上傳欄位。
util.postFile(sourceFileUrl, destinationUrl)
將來源 URL 的檔案下載至帳號的上傳資料夾,然後上傳至目標 URL。
| 參數 | 類型 | 說明 |
|---|---|---|
| sourceFileUrl | string | 來源檔案 URL |
| destinationUrl | string | 要上傳至的目標 URL |
回傳: string — 帳號上傳資料夾中已下載檔案的檔名
util.getDayOfMonth()
取得當月的日期(1–31)。
回傳: number
util.getDayOfWeek()
取得星期幾(1=週日、2=週一、...、7=週六)。
回傳: number
util.getMonthOfYear()
取得當月份(0–11,0=一月)。
回傳: number
util.getYear()
取得當前年份。
回傳: number
util.ignoreSSL()
停用後續 HTTP 請求的 SSL 憑證驗證。
util.resetSSL()
重新啟用 SSL 憑證驗證。
util.logWorkflowError(text)
在 workflow 日誌中記錄日誌訊息,您可以在資料庫維護頁面中找到此日誌。
| 參數 | 類型 | 說明 |
|---|---|---|
| text | string | 日誌訊息文字 |
util.logWorkflowError("記錄 " + recordId + " 的訂單處理失敗");
approval 物件
approval.create(signersJson)
僅適用於 Post workflow 和動作按鈕。建立 Approval workflow。signersJson 是一個包含以下格式物件的 JSON 字串陣列:
| 欄位 | 說明 |
|---|---|
| stepIndex | 簽核的步驟,從 0 開始 |
| approver | 簽核人的電子郵件 |
| stepName | 簽核人的姓名或職稱 |
回傳: boolean — true 表示簽核建立成功
var signers = [
{ stepIndex: 0, approver: "manager@example.com", stepName: "經理" },
];
approval.create(JSON.stringify(signers));
approval.cancel()
僅適用於 Post workflow 和動作按鈕。取消目前的簽核。
回傳: boolean
特殊簽核者格式
你可以使用這些特殊格式,根據你的公司組織樹動態決定簽核者:
$DS- 簽核者將設定為該使用者的直屬主管$SS- 簽核者將設定為該使用者的主管的主管$DSL- 簽核者將設定為前一位簽核者的主管
signers.push({
stepIndex: "1",
approver: "$DSL",
stepName: "",
});
使用這些特殊格式時,不需要提供非空的 stepName。注意:如果特殊格式中的使用者不符合你在設計模式中設定的規則,該簽核者將不會被建立。
建立與取消簽核
你可以透過在表單中新增 Post workflow 腳本來啟動或取消記錄的簽核。首先,在設計模式中設定簽核步驟:

然後在 Post workflow 中設定簽核細節:
function autoStartApprover() {
var signers = [];
signers.push({
stepIndex: "0",
approver: "kingjo@ragic.com",
stepName: "START",
});
signers.push({
stepIndex: "1",
approver: "HR0000@hr.hr",
stepName: "HR",
});
approval.create(JSON.stringify(signers));
}
autoStartApprover();
結果:

如果參數格式有誤,或者你提供的簽核者不符合你在設計模式中設定的規則,簽核將不會被建立。例如,如果設計模式中第二步僅有「HR00@gmail.com」一個候選人,你提供了不同的電子郵件如「kingjo@ragic.com」作為該步驟的簽核者,簽核建立將會失敗。我們還提供了 3 種特殊格式來根據你的公司組織樹動態決定簽核者。詳情可參閱簽核流程設定。
記錄資訊欄位
你可以透過在 query 上執行以下指令來包含完整的記錄資訊:
query.setIfIncludeInfo(true);
然後你可以取得記錄的簽核資訊和其他中繼資料:
entry.getFieldValue("_approve_status"); // 取得目前簽核的狀態
entry.getFieldValue("_approve_next"); // 取得下一位應簽核此記錄的人
entry.getFieldValue("_create_date"); // 取得記錄的建立日期
entry.getFieldValue("_create_user"); // 取得記錄建立者的電子郵件
簽核狀態值為:F 表示已核准,REJ 表示已拒絕,P 表示處理中。
approvalParam 物件(Approval workflow)
approvalParam.getEntryRootNodeId()
取得正在簽核的記錄 ID。
回傳: number
approvalParam.getApprovalAction()
取得觸發 workflow 的簽核動作。
回傳: "CREATE"、"FINISH"、"REJECT" 或 "CANCEL"
account 物件
account.getApname()
取得目前帳號(資料庫)名稱。
回傳: string
account.getUserName(email)
依電子郵件取得使用者的顯示名稱。
| 參數 | 類型 | 說明 |
|---|---|---|
| string | 使用者電子郵件 |
回傳: string(找不到時回傳 email 本身)
account.getUserEmail(userName)
依顯示名稱取得使用者的電子郵件。
| 參數 | 類型 | 說明 |
|---|---|---|
| userName | string | 使用者顯示名稱 |
回傳: string 或 null
account.getSheetPathByName(sheetName)
依表單名稱查詢完整路徑。
| 參數 | 類型 | 說明 |
|---|---|---|
| sheetName | string | 表單名稱 |
回傳: string 或 null
account.ifFree()
檢查帳號是否為免費方案。
回傳: boolean
account.getTimeZoneOffset()
取得帳號的時區偏移量(毫秒)。
回傳: number
account.getTimeZoneOffsetInHours()
取得帳號的時區偏移量(小時)。
回傳: number
account.reset()
程式執行完畢後,清除所有與帳號相關的快取並重新載入頁面。
警告: 此方法會使所有帳號層級的快取失效。頻繁呼叫可能導致帳號下所有使用者的效能嚴重下降。請謹慎使用,僅在確實需要清除快取時使用(例如:以程式方式修改表單設計後)。
log 物件
log 物件用於在工作流程執行期間記錄訊息。您可以在工作流程執行紀錄中查看這些日誌,從左上角漢堡選單的資料庫維護中進入。

log.debug(message)
記錄除錯層級訊息。
| 參數 | 類型 | 說明 |
|---|---|---|
| message | string | 日誌訊息 |
log.info(message)
記錄資訊層級訊息。
| 參數 | 類型 | 說明 |
|---|---|---|
| message | string | 日誌訊息 |
log.warn(message)
記錄警告層級訊息。
| 參數 | 類型 | 說明 |
|---|---|---|
| message | string | 日誌訊息 |
log.error(message)
記錄錯誤層級訊息。
| 參數 | 類型 | 說明 |
|---|---|---|
| message | string | 日誌訊息 |
log.print(message)
輸出訊息至主控台。
| 參數 | 類型 | 說明 |
|---|---|---|
| message | string | 輸出訊息 |
log.println(message)
輸出訊息至主控台(含換行)。
| 參數 | 類型 | 說明 |
|---|---|---|
| message | string | 輸出訊息 |
log.setToConsole(enable)
設為 true 時,在 UI 中顯示主控台區塊,方便即時檢視日誌輸出。
| 參數 | 類型 | 說明 |
|---|---|---|
| enable | boolean | true 在 UI 中顯示主控台區塊 |
log.setToConsole(true);
log.info("此訊息會顯示在主控台區塊中");
workflow 速查表
資料操作
取得記錄
// 單筆記錄
var query = db.getAPIQuery("/path/1");
var entry = query.getAPIEntry(recordId);
// 第一筆符合條件的記錄
query.addFilter(1000001, "=", "value");
var entry = query.getAPIResult();
// 多筆記錄(迭代器)
var results = query.getAPIResultsFull();
while (results.hasNext()) {
var entry = results.next();
}
// 以陣列形式(正在逐步淘汰中——建議使用 getAPIResultsFull)
var entries = query.getAPIResultList(); // entries[0], entries[1], entries.length
// 目前表單(無參數)
var query = db.getAPIQuery();
// 多層路徑(路徑 + 表單索引)
var query = db.getAPIQuery("/sales/orders", 1);
更新記錄
var entry = query.getAPIEntry(recordId);
entry.setFieldValue(1000001, "new value");
entry.save(); // 默認不會觸發 workflow
// 若要觸發該 entry 的 workflow:
// entry.setIfExecuteWorkflow(true);
// entry.save();
建立記錄
var query = db.getAPIQuery("/path/1");
var entry = query.insertAPIEntry();
entry.setFieldValue(1000001, "value");
entry.save();
var newId = entry.getRootNodeId();
刪除記錄
query.deleteEntry(nodeId); // 永久刪除
query.deleteEntryToRecycleBin(nodeId); // 移至資源回收筒
db.deleteOldRecords("/logs/1", 90); // 依日期批次刪除
篩選與排序
| 運算子 | 說明 | 範例 |
|---|---|---|
| = 或 eq | 完全符合 | addFilter(1000001, "=", "啟用") |
| like | 包含 | addFilter(1000001, "like", "緊急") |
| > 或 gt | 大於 | addFilter(1000002, ">", "100") |
| >= 或 gte | 大於等於 | addFilter(1000002, ">=", "2024/01/01") |
| < 或 lt | 小於 | addFilter(1000002, "<", "2024/12/31") |
| <= 或 lte | 小於等於 | addFilter(1000003, "<=", "1000") |
| regex | 正規表示式 | addFilter(1000001, "regex", "^A.*") |
// 排序:1=升冪、2=降冪、3=次要升冪、4=次要降冪
query.setOrder(1000001, 1);
// 全文搜尋
query.setFullTextSearch("關鍵字");
// 分頁
query.setLimitSize(100);
query.setLimitFrom(200);
// 依名稱查詢欄位 ID
var fieldId = query.getFieldIdByName("客戶名稱");
欄位與子表格
依名稱存取欄位
var value = entry.getFieldValueByName("客戶名稱");
entry.setFieldValueByName("狀態", "已完成");
多選欄位
// 讀取所有值
var tags = entry.getFieldValues(1000003);
// 附加至多選欄位(第 3 個參數 = true)
entry.setFieldValue(1000003, "新標籤", true);
// 在 param 中讀取
var newValues = param.getNewValues(1000003);
var oldValues = param.getOldValues(1000003);
檔案上傳欄位
entry.setFieldFile(1000005, "report.pdf", fileContent);
entry.setFieldFile(1000005, "extra.pdf", fileContent, true); // 附加
子表格
// 讀取
var count = entry.getSubtableSize(1000100);
var value = entry.getSubtableFieldValue(1000100, rowIndex, "1000101");
// 寫入現有列
var rowId = entry.getSubtableRootNodeId(1000100, rowIndex);
entry.setSubtableFieldValue(1000101, rowId, "new value");
// 附加至子表格多選欄位
entry.setSubtableFieldValue(1000101, rowId, "value", true);
// 子表格檔案上傳
entry.setSubtableFieldFile(1000101, rowId, "file.txt", content);
// 新增列(負數 ID)
entry.setSubtableFieldValue(1000101, -100, "value1");
entry.setSubtableFieldValue(1000102, -100, "value2");
// 刪除
entry.deleteSubtableRow(1000100, rowId); // 依 node ID
entry.deleteSubtableRowByRowNumber(1000100, 0); // 依索引
entry.deleteSubtableRowAll(1000100); // 所有列
記錄鎖定
entry.lock();
entry.unlock();
var locked = entry.isLocked();
連結載入
entry.loadAllLinkAndLoad(); // 所有欄位
entry.loadLinkAndLoad(1000010); // 特定連結欄位
entry.loadLinkAndLoadField(1000011); // 特定載入欄位
db.loadLinkAndLoadAll("/path/1"); // 表單中所有記錄
重新計算公式
entry.recalculateAllFormulas(); // 所有公式
entry.recalculateFormula(1000005); // 特定欄位
db.recalculateAll("/path/1"); // 所有記錄
db.recalculateAll("/path/1", 1000005, 1000006); // 特定欄位
工作流程
Pre workflow (param)
var newValue = param.getNewValue(1000001);
var oldValue = param.getOldValue(1000001);
var isNew = param.isCreateNew();
var recordId = param.getRootNodeId();
var opType = param.getOperationType(1000001); // "I" 或 "M"
// Node ID
var newNodeId = param.getNewNodeId(1000001);
var oldNodeId = param.getOldNodeId(1000001);
// 子表格變更
var rows = param.getSubtableEntry(1000100);
Post workflow
var entry = param.getUpdatedEntry(); // 或 param.getEntry()
var recordId = entry.getRootNodeId();
回應狀態
response.setStatus("SUCCESS"); // 綠色勾號
response.setStatus("WARN"); // 黃色警告
response.setStatus("INVALID"); // 阻止儲存
response.setStatus("ERROR"); // 阻止儲存
response.setMessage("您的訊息");
// 完成後導向
response.setOpenURL("https://example.com/result");
response.setOpenURLInNewTab(false); // 同一分頁
通訊
電子郵件
mailer.compose(to, cc, from, fromName, subject, htmlContent);
mailer.setBcc("bcc@example.com");
mailer.attach(url);
mailer.send();
mailer.sendAsync(); // 非同步發送
// 一行指令快速發送
mailer.sendMail(to, subject, content);
mailer.sendMail(to, subject, content, attachmentURL);
// 應用程式通知
mailer.sendAppNotification(email, message);
mailer.sendAppNotification(email, message, "/path/1", nodeId);
HTTP 請求
var result = util.getURL(url);
var result = util.postURL(url, body);
var result = util.putURL(url, body);
var result = util.deleteURL(url);
// 使用自訂標頭
util.setHeader("Authorization", "Bearer token");
util.setHeader("Content-Type", "application/json");
var result = util.postURL(url, body);
util.removeHeader("Authorization");
// 下載檔案
var ragicUrl = util.downloadFile(externalUrl);
var ragicUrl = util.downloadFile(externalUrl, postBody);
系統與工具
簽核
var signers = [
{ stepIndex: 0, approver: "email@example.com", stepName: "步驟名稱" },
];
approval.create(JSON.stringify(signers));
approval.cancel();
// 在Approval workflow中
var action = approvalParam.getApprovalAction(); // CREATE, FINISH, REJECT, CANCEL
var recordId = approvalParam.getEntryRootNodeId();
帳號資訊
var apname = account.getApname();
var displayName = account.getUserName("user@example.com");
var email = account.getUserEmail("顯示名稱");
var sheetPath = account.getSheetPathByName("表單名稱");
var isFree = account.ifFree();
使用者資訊
var email = user.getEmail();
var name = user.getUserName();
var groups = user.getGroups();
if (user.isInGroup("經理")) {
}
if (user.isValid()) {
} // 未過期
if (user.isIdentifiable()) {
} // 非訪客/匿名
日誌
log.debug("除錯資訊");
log.info("資訊");
log.warn("警告");
log.error("錯誤");
log.setToConsole(true); // 在 UI 中顯示主控台區塊
日期格式
永遠使用:yyyy/MM/dd 或 yyyy/MM/dd HH:mm:ss
entry.setFieldValue(1000001, "2024/03/15");
entry.setFieldValue(1000001, "2024/03/15 14:30:00");
魔術變數
| 變數 | 說明 | workflow 類型 |
|---|---|---|
__actionButtonExecuteNodeId |
記錄 ID | 僅動作按鈕 |
注意: 對於其他情境,請使用可用的物件:
- 記錄 ID:
param.getRootNodeId()(前置/後置)或entry.getRootNodeId() - 使用者電子郵件:
user.getEmail()(Daily workflow 中回傳dailyJob@ragic.com) - 帳號名稱:
db.getApname()或account.getApname()
常見模式
取得一筆資料
有兩種不同的方式來取得使用者正在查看的當前記錄(適用於動作按鈕)、正在提交的值(適用於 Pre workflow)或剛儲存的記錄(適用於 Post workflow)。
動作按鈕
表單頁面動作按鈕透過配置動作按鈕來呼叫您在 sheet scope 中定義的函數。當您定義動作按鈕呼叫的函數時,您可以傳遞參數 {id},即點擊動作按鈕所在記錄的 ID。您可以在動作按鈕 workflow中看到範例。
Pre-workflow
您可以透過以下方法取得記錄 ID:
var recordId = param.getNewNodeId(keyFieldId);
param 是一個預定義變數,您可以在 Pre-workflow 和 Post-workflow 中使用它。在取得記錄 ID 之後,這是取得記錄(entry)的方式:
var query = db.getAPIQuery(pathToForm);
var entry = query.getAPIEntry(recordId);
Post-workflow
對於 Post-workflow,您可以使用與 Pre-workflow 相同的方法。但是,還有一種方便的方法來檢索剛剛儲存的紀錄:
var entry = param.getUpdatedEntry();
處理文字遮罩欄位
如果表單頁面上有文字遮罩欄位,預設情況下您將獲得遮罩值。您可以透過以下方式取得未遮罩的值:
var query = db.getAPIQuery(pathToForm);
query.setUpdateMode();
query.setIgnoreAllMasks(true); // 取消所有遮罩欄位的遮罩
var entry = query.getAPIEntry(recordId);
您可以使用 query.addIgnoreMaskDomain(fieldId) 而不是 query.setIgnoreAllMasks(true) 來僅取消特定欄位的遮罩。
查詢資料
如果您想透過過濾條件取得多筆紀錄,您可以使用 addFilter(fieldId, operator, value) 來加上過濾條件,並使用 getAPIResultsFull() 來取得記錄列表。以下是您可以使用的操作符列表:
| 操作符名稱 | 操作符值 |
|---|---|
| 等於 | = |
| 正則表達式 | regex |
| 大於或等於 | >= |
| 小於或等於 | <= |
| 大於 | > |
| 小於 | < |
| 包含 | like |
單一條件
使用一次 addFilter 篩出符合條件的紀錄:
var colA = "1000002"; // 欄位 A
var query = db.getAPIQuery("/workflow-demo/1");
query.addFilter(colA, "=", "Green");
var results = query.getAPIResultsFull(); // results 為所有欄位 A 為 Green 的資料
var entry = results.next();
while (entry) {
// do something
entry = results.next();
}
複合條件 (AND)
對不同欄位使用多次 addFilter 來篩出符合所有條件的紀錄:
var colA = "1000002"; // 欄位 A
var colB = "1000004"; // 欄位 B
var query = db.getAPIQuery("/workflow-demo/1");
query.addFilter(colA, "=", "Green");
query.addFilter(colB, "=", "Yes");
var results = query.getAPIResultsFull(); // results 為所有欄位 A 為 Green 且欄位 B 為 Yes 的資料
var entry = results.next();
while (entry) {
// do something
entry = results.next();
}
複合條件 (OR)
對同一個欄位使用多次 addFilter 來篩出符合任一條件的紀錄:
var colA = "1000002"; // 欄位 A
var query = db.getAPIQuery("/workflow-demo/1");
query.addFilter(colA, "=", "Green");
query.addFilter(colA, "=", "Red");
var results = query.getAPIResultsFull(); // results 為所有欄位 A 為 Green 或欄位 A 為 Red 的資料
var entry = results.next();
while (entry) {
// do something
entry = results.next();
}
範圍查詢
var colA = "1000006"; // 欄位 A
var query = db.getAPIQuery("/workflow-demo/1");
query.addFilter(colA, ">", "20");
query.addFilter(colA, "<", "40");
var results = query.getAPIResultsFull(); // results 為所有欄位 A 大於 20 且小於 40 的資料
var entry = results.next();
while (entry) {
// do something
entry = results.next();
}
請注意,當您透過日期或日期時間進行過濾時,它們需要以下列格式表示:yyyy/MM/dd 或 yyyy/MM/dd HH:mm:ss。
您還可以透過呼叫 setFullTextSearch(queryTerm) 來使用全文搜尋作為查詢過濾條件,而不是 addFilter()。
更新資料
讓我們從一個簡單的範例開始,檢索當前記錄作為物件,使用按鈕更新其值,然後將其儲存回資料庫。以下是範例表單的外觀:

我們希望設計一些按鈕,通過點擊按鈕執行簡單的伺服器端 JavaScript workflow 來更改狀態欄位值。以下是按鈕背後的程式碼:
function setStatus(recordId, status) {
var STATUS_FIELD = 1000012; // 狀態欄位的欄位 ID
var query = db.getAPIQuery("/workflow-demo/2");
var entry = query.getAPIEntry(recordId);
if (status) {
entry.setFieldValue(STATUS_FIELD, status);
} else {
// 切換
var newStatus = entry.getFieldValue(STATUS_FIELD) == "On" ? "Off" : "On";
entry.setFieldValue(STATUS_FIELD, newStatus);
}
entry.save();
}
變數 db 是預定義的。通常,我們會呼叫 getAPIQuery(pathToForm) 來取得表單的 query。然後透過 getAPIEntry(recordId) 檢索記錄,並使用 setFieldValue(fieldId, value) 設定值或 getFieldValue(fieldId) 檢索值。
日期格式要求: 當加入日期值時,必須使用以下格式之一:
yyyy/MM/ddyyyy/MM/dd HH:mm:ssHH:mm:ss
多選欄位
如果您正在從多選欄位檢索值,請使用 getFieldValues(fieldId) 以陣列形式檢索所有值。您可以呼叫 setFieldValue(fieldId, value, true) 並在末尾加上 true 參數來指定「加入」選項到當前的值列表,而不是覆蓋現有值。
要複製一個多選欄位值到另一個多選欄位:
var multipleSelectionFieldValue = entry.getFieldValues(1013251);
var targetMultipleSelectionField = "";
for (var i = 0; i < multipleSelectionFieldValue.length; i++) {
if (i == 0) {
targetMultipleSelectionField = multipleSelectionFieldValue[i];
} else {
targetMultipleSelectionField =
targetMultipleSelectionField + "|" + multipleSelectionFieldValue[i];
}
}
entry.setFieldValue(1013252, targetMultipleSelectionField, false);
您需要使用垂直條字元(|)格式化選項,就像從 Excel 或 CSV 檔案匯入資料時一樣。
檔案上傳欄位
如果您要將值設定為文件上傳欄位,您可以使用 setFieldFile(fieldId, fileName, fileContent) 來建立文件並儲存到指定欄位。
建立記錄
建立記錄與更新資料非常相似。不同的是,您需要呼叫 query.insertAPIEntry() 而不是 query.getAPIEntry()。它也會回傳一個紀錄物件,您可以使用 setFieldValue 來加上欄位值。呼叫 entry.save() 之後,記錄將被建立:
function createRecord() {
var query = db.getAPIQuery("/workflow-demo/2");
var entry = query.insertAPIEntry(); // 建立新資料物件
entry.setFieldValue(1000011, "NEW-001");
entry.setFieldValue(1000012, "Active");
entry.save();
}
子表格教學
如果您在表單中有子表格,您也可以使用 API 來檢索資料或進行編輯。以下是一個包含子表格的表單:

這個 workflow 將遍歷子表格中的每一行,找到每年的總金額(根據日期欄位),並確定哪一年具有最高總額。這是一個 Post-workflow,因此唯讀欄位將在記錄儲存後由 workflow 填寫:
var KEY_FIELD = 1000006;
var AMOUNT_SUBTABLE_ID = 1000007;
var DATE_FIELD = 1000003;
var AMOUNT_FIELD = 1000004;
var MAX_YEAR_FIELD = 1000008;
var MAX_TOTAL_FIELD = 1000009;
var THIS_YEAR_TOTAL_FIELD = 1000010;
var query = db.getAPIQuery("/workflow-demo/1");
var entry = query.getAPIEntry(param.getNewNodeId(KEY_FIELD));
var subtableSize = entry.getSubtableSize(AMOUNT_SUBTABLE_ID);
var yearTotal = {};
for (var i = 0; i < subtableSize; i++) {
var year = parseInt(
entry.getSubtableFieldValue(AMOUNT_SUBTABLE_ID, i, DATE_FIELD).substr(0, 4),
);
var amount = parseInt(
entry.getSubtableFieldValue(AMOUNT_SUBTABLE_ID, i, AMOUNT_FIELD),
);
if (year in yearTotal) {
yearTotal[year] += amount;
} else {
yearTotal[year] = amount;
}
}
var maxYear;
for (var year in yearTotal) {
if (!maxYear || yearTotal[maxYear] < yearTotal[year]) {
maxYear = year;
}
}
entry.setFieldValue(MAX_YEAR_FIELD, maxYear);
entry.setFieldValue(MAX_TOTAL_FIELD, yearTotal[maxYear]);
entry.setFieldValue(THIS_YEAR_TOTAL_FIELD, yearTotal[new Date().getFullYear()]);
entry.save();
基本想法是使用 getSubtableSize(subtableId) 取得行數,並使用 getSubtableFieldValue(subtableId, subtableRowIndex, subtableFieldId) 檢索值。您還可以使用 setSubtableFieldValue(subtableFieldId, subtableRootNodeId, value) 設定值。subtableRootNodeId 用於指定您所指的行。使用 getSubtableRootNodeId(subtableId, subtableRowIndex) 來取得它。要加上新行,使用負數 subtableRootNodeId(如 -100),所有設定到相同負數 ID 的值將應用到同一新行。
Pre-workflow 和 Post-workflow 中的子表格存取
要在 Pre-workflow 和 Post-workflow 中取得子表格值,請使用 param.getSubtableEntry():
var list = param.getSubtableEntry(AMOUNT_SUBTABLE_ID);
var arr = list.toArray();
response.setStatus("WARN");
for (var i = 0; i < arr.length; i++) {
response.setMessage(
"日期: " +
arr[i].getNewValue(DATE_FIELD) +
", 金額: " +
arr[i].getNewValue(AMOUNT_FIELD) +
"\r\n",
);
}
訪問所有紀錄
使用 getAPIResultsFull() 遍歷查詢中的紀錄。它透過迭代器逐筆取得資料,記憶體用量低,適合任何大小的資料集。
var query = db.getAPIQuery("/workflow-demo/1");
var results = query.getAPIResultsFull();
while (results.hasNext()) {
var entry = results.next();
// do something
}
檢查用戶權限
您可以根據執行程式的用戶所在的群組進行條件處理。一個用戶可以在多個用戶群組中,所以您可以透過 user.isInGroup(groupName) 來判斷。user 物件和 isInGroup 呼叫可以在所有類型的 workflow 中使用,除了 Daily Workflow,它是由系統觸發的。
if (!user.isInGroup("SYSAdmin")) {
response.setStatus("INVALID");
response.setMessage("您沒有權限進行此操作。");
}
發送電子郵件通知
有時候您可能希望根據一組條件發送電子郵件通知,或者希望自訂通知訊息內容。以下是發送電子郵件的程式碼:
var name = entry.getFieldValue(1001426);
var email = entry.getFieldValue(1001428);
var title = entry.getFieldValue(1001386);
mailer.compose(
email, // 收件人
null, // 副本
"support@example.com", // 回覆地址
"Acme, Inc.", // 顯示的發件人
title,
"嗨 " +
name +
", 我們已收到您的銷售訂單," +
"並將很快處理您的訂單。\n\n" +
"您可以在 https://www.ragic.com/example/1 查看您的訂單詳情。\n\n" +
"謝謝。\n\n\n" +
"最好的祝福,Sophia, 銷售經理 Acme, Inc.",
);
// mailer.attach(myURL); // 您可以使用 .attach 來附加來自 URL 的內容
mailer.send();
請注意,如果您希望將電子郵件發送給多個收件人,只需用逗號分隔每個電子郵件地址。
我們對您可以發送的電子郵件數量有限制。如果您對電子郵件發送配額有任何疑問,請發送電子郵件至 support@ragic.com。
發送手機應用通知
除了電子郵件通知,您還可以發送手機應用通知。如果用戶在其行動裝置上安裝了 iOS 應用或 Android 應用,則會向其發送通知:
mailer.sendAppNotification("mike@example.com", "測試手機通知");
如果您希望用戶在點擊此通知時被重定向到指定記錄,請提供要重定向到的表單路徑和記錄 ID:
mailer.sendAppNotification("mike@example.com", "測試手機通知", "/forms/1", 12);
發送 HTTP 請求
您可以向 URL 發送 HTTP GET/POST/DELETE/PUT 請求並取得回傳結果:
util.getURL(urlstring);
util.postURL(urlstring, postBody);
util.deleteURL(urlstring);
util.putURL(urlstring, putBody);
變數 util 是預定義的。如果您需要忽略當前 HTTP 請求的 SSL 憑證檢查,可以在發送請求前加上以下程式碼:
util.ignoreSSL();
此外,您可以呼叫 util.setHeader(name, value) 設定 HTTP 標頭,以及 util.removeHeader(name) 用於移除標頭。
下載和上傳檔案
您可以透過 URL 下載檔案並取得檔案名稱:
var fileName = util.downloadFile(fileUrl);
// fileUrl:檔案的來源 URL
// 回傳值:下載下來的檔名
當您將檔案下載至資料庫後,可以使用 setFieldValue 將檔名填回檔案欄位,即可在表單中預覽或將檔案下載至本機:
var fileFieldId = 1000087;
var fileName = util.downloadFile(fileUrl);
entry.setFieldValue(1000087, fileName);
entry.save();
您也可以上傳檔案至目標網址並取得檔案名稱:
util.postFile(sourceFileUrl, destinationUrl);
// sourceFileUrl:檔案的來源 URL
// destinationUrl:檔案要上傳的目標 URL
// 回傳值:上傳到資料庫的檔名
關於取得完整檔案、圖片連結的方法可以參閱這篇教學。
庫存管理
訂單時扣減庫存
// 訂單表單的Post workflow
var entry = param.getUpdatedEntry();
var subtableId = "1000100"; // 訂單項目子表格
var itemCount = entry.getSubtableSize(subtableId);
for (var i = 0; i < itemCount; i++) {
var productId = entry.getSubtableFieldValue(subtableId, i, "1000101");
var qty = parseInt(entry.getSubtableFieldValue(subtableId, i, "1000102"));
// 每次迭代建立新的查詢——addFilter 會累積且結果會被快取
var invQuery = db.getAPIQuery("/inventory/1");
invQuery.setUpdateMode();
invQuery.addFilter(1000001, "=", productId);
var invEntry = invQuery.getAPIResult();
if (invEntry != null) {
var stock = parseInt(invEntry.getFieldValue(1000002));
invEntry.setFieldValue(1000002, (stock - qty).toString());
invEntry.save();
}
}
連鎖更新
更新所有相關記錄
// 當客戶資訊變更時,更新他們的所有訂單
var entry = param.getUpdatedEntry();
var customerId = entry.getRootNodeId();
var newAddress = entry.getFieldValue(1000003);
var orderQuery = db.getAPIQuery("/orders/1");
orderQuery.setUpdateMode();
orderQuery.addFilter(1000010, "=", customerId.toString());
var orders = orderQuery.getAPIResultsFull();
while (orders.hasNext()) {
var order = orders.next();
order.setFieldValue(1000011, newAddress);
order.save();
}
驗證模式
防止重複
// Pre workflow:檢查重複的電子郵件
var email = param.getNewValue(1000001);
var currentId = param.getRootNodeId();
var query = db.getAPIQuery("/contacts/1");
query.addFilter(1000001, "=", email);
var results = query.getAPIResultsFull();
while (results.hasNext()) {
var entry = results.next();
if (entry.getRootNodeId() !== currentId) {
response.setStatus("INVALID");
response.setMessage("電子郵件已存在:" + email);
break;
}
}
依狀態的必填欄位
// Pre workflow:核准時需要核准日期
var status = param.getNewValue(1000001);
var approvalDate = param.getNewValue(1000002);
if (status === "已核准" && !approvalDate) {
response.setStatus("INVALID");
response.setMessage("當狀態為已核准時,需要填寫核准日期");
}
自動產生
產生流水號
// Post workflow:新記錄自動產生訂單編號
var entry = param.getUpdatedEntry();
if (param.isCreateNew()) {
var query = db.getAPIQuery("/orders/1");
query.setOrder(1000001, 2); // 依訂單編號降冪排序(2 = 降冪,1 = 升冪)
query.setLimitSize(1);
var lastEntry = query.getAPIResult();
var nextNumber = 1;
if (lastEntry != null) {
var lastNum = lastEntry.getFieldValue(1000001);
nextNumber = parseInt(lastNum.replace("ORD-", "")) + 1;
}
entry.setFieldValue(1000001, "ORD-" + nextNumber);
entry.save();
}
通知
條件式電子郵件通知
// Post workflow:高價值訂單通知經理
var entry = param.getUpdatedEntry();
var total = parseFloat(entry.getFieldValue(1000003));
var customerName = entry.getFieldValue(1000001);
if (total > 10000) {
mailer.compose(
"manager@company.com",
null,
"orders@company.com",
"訂單系統",
"來自 " + customerName + " 的高價值訂單",
"<h2>新的高價值訂單</h2>" +
"<p>客戶:" +
customerName +
"</p>" +
"<p>總額:$" +
total.toFixed(2) +
"</p>" +
"<p><a href='https://www.ragic.com/" +
account.getApname() +
"/orders/1/" +
entry.getRootNodeId() +
"'>檢視訂單</a></p>"
);
mailer.send();
}
外部 API 整合
POST 到 Webhook
// Post workflow:發送資料到外部系統
var entry = param.getUpdatedEntry();
var payload = {
event: "order_created",
orderId: entry.getRootNodeId(),
customer: entry.getFieldValue(1000001),
total: entry.getFieldValue(1000003),
timestamp: new Date().toISOString(),
};
// 發送請求前設定標頭
util.setHeader("Content-Type", "application/json");
util.setHeader("Authorization", "Bearer your-api-key");
try {
var result = util.postURL(
"https://webhook.site/your-endpoint",
JSON.stringify(payload),
);
log.info("Webhook 回應:" + result);
} catch (e) {
log.error("Webhook 失敗:" + e);
}
Approval workflow
根據金額自動啟動簽核
// Post workflow:超過 $1000 的採購啟動簽核
var entry = param.getUpdatedEntry();
var amount = parseFloat(entry.getFieldValue(1000003));
var status = entry.getFieldValue(1000005);
if (amount > 1000 && status === "待處理") {
var signers = [];
if (amount <= 5000) {
signers.push({
stepIndex: 0,
approver: "manager@company.com",
stepName: "經理核准",
});
} else {
signers.push({
stepIndex: 0,
approver: "manager@company.com",
stepName: "經理核准",
});
signers.push({
stepIndex: 1,
approver: "director@company.com",
stepName: "總監核准",
});
}
approval.create(JSON.stringify(signers));
}
日期處理
計算到期日
// Post workflow:設定訂單日期後 30 天為到期日
var entry = param.getUpdatedEntry();
var orderDate = entry.getFieldValue(1000002);
if (orderDate) {
var parts = orderDate.split("/");
var date = new Date(parts[0], parts[1] - 1, parts[2]);
date.setDate(date.getDate() + 30);
var dueDate =
date.getFullYear() + "/" + (date.getMonth() + 1) + "/" + date.getDate();
entry.setFieldValue(1000003, dueDate);
entry.save();
}
子表格處理
計算子表格總計
// Post workflow:加總子表格值
var entry = param.getUpdatedEntry();
var subtableId = "1000100";
var rowCount = entry.getSubtableSize(subtableId);
var total = 0;
for (var i = 0; i < rowCount; i++) {
var qty =
parseFloat(entry.getSubtableFieldValue(subtableId, i, "1000101")) || 0;
var price =
parseFloat(entry.getSubtableFieldValue(subtableId, i, "1000102")) || 0;
total += qty * price;
}
entry.setFieldValue(1000010, total.toString());
entry.save();
在新記錄中新增子表格列
使用 insertAPIEntry() 建立記錄時,可以在呼叫 save() 前使用負數 ID 新增子表格列。同一列的所有欄位使用相同的負數 ID,不同的負數 ID 建立不同的列。
var query = db.getAPIQuery("/orders/1");
var entry = query.insertAPIEntry();
entry.setFieldValue(1000001, "ORD-001");
// 新增第一筆子表格列(此列所有欄位使用 -100)
entry.setSubtableFieldValue(1000101, -100, "產品 A");
entry.setSubtableFieldValue(1000102, -100, "5");
entry.setSubtableFieldValue(1000103, -100, "100");
// 新增第二筆子表格列(使用 -101,不同的負數 ID)
entry.setSubtableFieldValue(1000101, -101, "產品 B");
entry.setSubtableFieldValue(1000102, -101, "3");
entry.setSubtableFieldValue(1000103, -101, "250");
entry.save();
注意: 負數 ID 是僅在插入時使用的臨時識別碼。系統在儲存記錄時會將其對應到實際的 node ID。具體的負數值不重要,只要屬於同一列的欄位共用相同的負數 ID 即可。
複製記錄 (entryCopier)
複製記錄是最常見的 workflow 程式之一。Ragic 提供了一個簡單的 entryCopier 函數來簡化這類操作。假設我們想看到這個表單上的一筆記錄:

按一下按鈕,在這個表單上產生一筆記錄:

以下是這個動作按鈕的程式碼:
function copyEntry(nodeId) {
db.entryCopier(
JSON.stringify({
THIS_PATH: "/workflow-demo/3",
THIS_NODEID: nodeId,
NEW_PATH: "/workflow-demo/4",
COPY: {
1000030: 1000014, // A
1000031: 1000015, // C
1000032: 1000018, // S1
1000033: 1000020, // S3
},
}),
response,
);
}
entryCopier 接受一個 JSON 字串作為參數。只需填入來源表單、目標表單、要複製的記錄,以及最重要的——哪個欄位應該對應到哪個欄位(目標欄位 ID: 來源欄位 ID)。完成對應設定後,你就可以非常輕鬆地建立動作按鈕來將記錄從一個表單複製到另一個表單。
複製並進行額外修改
這裡我們想要將一筆記錄從「CopyFrom」複製到「CopyTo」,並將狀態設為「New」,同時記錄建立日期:

function copyEntry(nodeId) {
db.entryCopier(
JSON.stringify({
THIS_PATH: "/entrycopier/1",
THIS_NODEID: nodeId,
NEW_PATH: "/entrycopier/2",
COPY: {
1000004: 1000001, // To-ID : From-ID
1000005: 1000002, // To-Name : From-Name
},
}),
response,
);
// 取得複製後記錄的 rootNodeId
var newEntryRootNodeId = response.getRootNodeId();
var toApiQuery = db.getAPIQuery("/entrycopier/2");
var newEntry = toApiQuery.getAPIEntry(newEntryRootNodeId);
newEntry.setFieldValue(1000007, "New");
var current = new Date();
newEntry.setFieldValue(
1000008,
current.getFullYear() +
"/" +
(current.getMonth() + 1) +
"/" +
current.getDate(),
);
newEntry.save();
}
設定動作按鈕並儲存:

然後點擊動作按鈕顯示結果:

分頁取得記錄
在本節中,我們將展示如何使用 setLimitSize 和 setLimitFrom 來手動分頁取得記錄。在這個範例中,我們總共有 20 筆資料,每次取 6 筆並顯示訊息。
/**
* 欄位名稱 欄位 ID
* ----------- --------
* ID : 1000009
* 金額 : 1000010
*/
function limitFromExample() {
var apiQuery = db.getAPIQuery('/entrycopier/3');
// 記住偏移量
var fromNumber = 0;
apiQuery.setLimitFrom(fromNumber);
// 每次取 6 筆
apiQuery.setLimitSize(6);
var resultList = apiQuery.getAPIResultList();
while (resultList.length > 0) {
var msg = '';
for (var i = 0; i < resultList.length; i++) {
var entry = resultList[i];
msg += entry.getFieldValue(1000009);
if (i != resultList.length - 1) msg += ',';
}
response.setMessage(msg);
// 更新偏移量
fromNumber += resultList.length;
// 建立新的 query 並設定更新後的偏移量
apiQuery = db.getAPIQuery('/entrycopier/3');
apiQuery.setLimitSize(6);
apiQuery.setLimitFrom(fromNumber);
resultList = apiQuery.getAPIResultList();
}
response.setStatus('WARN');
}
資料:

結果:

刪除記錄
你可以使用 query.deleteEntry(nodeId) 來刪除記錄。如果需要刪除多筆記錄,使用 getAPIResultList() 取得所有符合條件的記錄陣列,再逐筆刪除。
注意: 刪除記錄時請勿使用
getAPIResultsFull()。getAPIResultsFull()透過迭代器逐筆取得記錄——在迭代過程中刪除記錄會導致不可預期的結果。請改用getAPIResultList(),它會先將所有記錄載入記憶體,刪除操作不會影響遍歷過程。
var query = db.getAPIQuery(pathToForm);
query.addFilter(1000001, "=", "To Be Deleted");
var entries = query.getAPIResultList();
for (var i = 0; i < entries.length; i++) {
query.deleteEntry(entries[i].getRootNodeId());
}
電子郵件附件格式
當將 Ragic 記錄作為電子郵件附件時,你可以使用特殊的 URL 格式。例如,如果記錄的 URL 為 https://www.ragic.com/wfdemo/workflow-demo/2/9(請忽略井號後面的 URL),你可以使用以下格式:
PDF 版本:
https://www.ragic.com/wfdemo/workflow-demo/2/9.pdf
Excel 版本:
https://www.ragic.com/wfdemo/workflow-demo/2/9.xlsx
合併列印版本(cid 是合併列印範本 ID,可從下載合併列印文件時的 URL 中取得):
https://www.ragic.com/wfdemo/workflow-demo/2/9.custom?rn=9&cid=1
呼叫其他表單的 workflow
使用 workflow 修改其他表單的資料時,該表單的 workflow 預設不會被觸發。如果你需要觸發該表單的 Post workflow 或 Pre workflow,請使用以下程式碼:
var query = db.getAPIQuery(pathToForm);
var entry = query.getAPIEntry(recordId);
// 進行操作...
entry.setIfExecuteWorkflow(true);
entry.save();
ES5 相容性指南
目前引擎:Nashorn(ECMAScript 5.1)
Ragic workflow 在 Nashorn JavaScript 引擎上執行,僅支援 ECMAScript 5.1。請避免使用 ES6+ 功能。
注意: 我們正在準備升級到 GraalVM,但尚未完成。升級完成後將可使用現代 JavaScript 功能(如 let、const、箭頭函數等)。在升級完成前,請繼續使用 ES5 語法。
功能相容性
變數
// 正確 - ES5
var name = "value";
var CONSTANT = 100;
// 錯誤 - ES6
let name = "value"; // SyntaxError
const CONSTANT = 100; // SyntaxError
函數
// 正確 - ES5
function processRecord(entry) {
return entry.getFieldValue(1000001);
}
var handler = function (data) {
return data.value;
};
// 錯誤 - ES6
const processRecord = (entry) => entry.getFieldValue(1000001); // SyntaxError
字串操作
// 正確 - ES5
var message = "Hello " + name + "!";
var multiline = "第一行\n" + "第二行\n" + "第三行";
// 錯誤 - ES6
var message = `Hello ${name}!`; // SyntaxError
var multiline = `第一行
第二行
第三行`; // SyntaxError
物件屬性
// 正確 - ES5
var obj = {
name: name,
value: value,
process: function (data) {
return data;
},
};
// 錯誤 - ES6
var obj = {
name, // 簡寫 - SyntaxError
value,
process(data) {
return data;
}, // 方法簡寫 - SyntaxError
};
解構
// 正確 - ES5
var data = getResponse();
var name = data.name;
var value = data.value;
var arr = [1, 2, 3];
var first = arr[0];
var second = arr[1];
// 錯誤 - ES6
var { name, value } = getResponse(); // SyntaxError
var [first, second] = [1, 2, 3]; // SyntaxError
陣列方法
// 正確 - ES5 陣列方法(可用)
var filtered = [];
for (var i = 0; i < items.length; i++) {
if (items[i].active) {
filtered.push(items[i]);
}
}
// 或使用 forEach(ES5)
items.forEach(function (item) {
if (item.active) filtered.push(item);
});
// 錯誤 - ES6 箭頭函數
var filtered = items.filter((item) => item.active); // SyntaxError
var names = items.map((item) => item.name); // SyntaxError
預設參數
// 正確 - ES5
function greet(name) {
name = name || "訪客";
return "您好 " + name;
}
// 錯誤 - ES6
function greet(name = "訪客") {
// SyntaxError
return "您好 " + name;
}
展開運算子
// 正確 - ES5
var combined = arr1.concat(arr2);
var copy = arr.slice();
// 錯誤 - ES6
var combined = [...arr1, ...arr2]; // SyntaxError
var copy = [...arr]; // SyntaxError
類別
// 正確 - ES5 建構函數模式
function Order(id, customer) {
this.id = id;
this.customer = customer;
}
Order.prototype.getTotal = function () {
return this.calculateTotal();
};
// 錯誤 - ES6 類別
class Order {
// SyntaxError
constructor(id, customer) {
this.id = id;
this.customer = customer;
}
}
Promise / Async-Await
// Promise 和 async/await 不可用
// 只能使用同步程式碼
// 正確 - 同步
var result = util.getURL("https://api.example.com/data");
var data = JSON.parse(result);
processData(data);
// 錯誤 - 非同步(不支援)
fetch(url).then((response) => response.json()); // 不可用
async function getData() {} // 不可用
可用的 ES5 功能
這些功能可正常運作:
var宣告function宣告for、while、do-while迴圈for-in迴圈if、else、switch陳述式try、catch、finally- 陣列方法:
forEach、map、filter、reduce、some、every JSON.parse()和JSON.stringify()- 正規表示式
Object.keys()、Object.create()Array.isArray()
GraalVM 升級計畫
我們正在準備從 Nashorn 升級到 GraalVM JavaScript 引擎,但升級工作尚未完成。在升級完成前,請繼續使用 ES5 語法撰寫 workflow。
升級完成後,您將可以使用以下現代 JavaScript 功能:
let和const- 箭頭函數
- 模板字面值
- 解構
- 展開運算子
- 類別
- 以及更多 ES6+ 功能
使用 ES5 撰寫的程式碼在遷移後仍可繼續運作。
安全模型
概述
Ragic workflow 在沙箱環境中執行,對 Java 類別和系統資源的存取受到限制。這確保 workflow 無法危害伺服器安全或存取未授權的資料。
被封鎖的 Java 類別
以下 Java 類別前綴被封鎖且無法存取:
| 封鎖前綴 | 原因 |
|---|---|
java.io |
檔案系統存取 |
java.nio |
NIO 檔案/網路存取 |
java.net |
網路 socket 存取 |
java.lang.Runtime |
程序執行 |
java.lang.System |
系統屬性 |
java.lang.Process |
程序控制 |
java.lang.reflect |
反射存取 |
javax.script |
腳本引擎存取 |
java.security |
安全類別 |
java.util.concurrent |
執行緒管理 |
com.sun |
內部 JDK 類別 |
jdk.nashorn |
引擎內部 |
嘗試使用被封鎖的類別將導致安全例外。
安全操作
HTTP 請求
使用 util 物件進行所有 HTTP 操作:
// 安全 - 使用 util 物件
var response = util.getURL("https://api.example.com/data");
var result = util.postURL("https://api.example.com/create", payload);
// 被封鎖 - 直接 Java 網路存取
var URL = Java.type("java.net.URL"); // SecurityException
檔案操作
使用 util 物件進行檔案下載/上傳:
// 安全 - 使用 util 物件
var fileUrl = util.downloadFile("https://example.com/file.pdf");
// 被封鎖 - 直接檔案系統存取
var File = Java.type("java.io.File"); // SecurityException
最佳實務
1. 驗證外部輸入
// 發送請求前永遠驗證 URL
var url = entry.getFieldValue(1000001);
if (url && url.indexOf("https://") === 0) {
var response = util.getURL(url);
}
2. 優雅地處理錯誤
try {
var result = util.postURL(webhookUrl, payload);
log.info("Webhook 成功");
} catch (e) {
log.error("Webhook 失敗:" + e);
// 不要向使用者暴露錯誤詳情
response.setMessage("外部服務暫時無法使用");
}
3. 限制資料暴露
// 不要在日誌中包含敏感資料
log.info("處理訂單:" + orderId); // 良好
log.info("客戶身分證:" + ssn); // 不好 - 敏感資料
4. 使用適當的存取層級
workflow 以觸發使用者的權限執行。對於系統層級操作,請考慮:
- 使用具有適當欄位存取權限的 Post workflow
- 對敏感操作實作 Approval workflow
- 將動作按鈕限制給特定使用者群組
帳號隔離
workflow 只能存取目前帳號內的資料。沒有跨帳號查詢 API——每個 workflow 嚴格在其定義所在的帳號範圍內執行。
稽核軌跡
workflow 執行會記錄以下資訊:
- 執行時間戳記
- 觸發使用者
- workflow 類型
- 狀態(成功/失敗)
- 錯誤訊息(如果有)
透過表單的 workflow 設定存取日誌。
疑難排解指南
常見錯誤
Pre workflow 中 "entry.getFieldValue is not a function"
問題: 在 Pre workflow 中使用 entry.getFieldValue()
解決方案: 改用 param.getNewValue()
// 錯誤(Pre workflow)
var value = entry.getFieldValue(1000001);
// 正確(Pre workflow)
var value = param.getNewValue(1000001);
"Post workflow 試圖觸發自己"
問題: 在 post-workflow 中試圖對同一張表單設置 setIfExecuteWorkflow(true)
錯誤訊息: "Workflow execution is denied because the trigger originates from the same form."
原因: 系統已有保護機制防止無限迴圈。在 post-workflow 中,如果試圖對觸發此 workflow 的同一張表單設置為 true,會直接拋出異常。
解決方案:
- 預設情況下,
entry.save()不會觸發 workflow,無需額外設置 - 如需觸發其他表單的 workflow,使用
setIfExecuteWorkflow(true) - 絕不要在 post-workflow 中對同一張表單設置為
true
// 在訂單表單的 post-workflow 中
var order = param.getUpdatedEntry();
// 錯誤:試圖觸發自己的 post-workflow
order.setIfExecuteWorkflow(true);
order.save(); // 會拋出異常
// 正確:默認不會觸發 workflow
order.setFieldValue(1000001, "processed");
order.save(); // 不會觸發 post-workflow
// 正確:觸發其他表單的 workflow
var invQuery = db.getAPIQuery("/inventory/1");
var invEntry = invQuery.getAPIEntry(123);
invEntry.setIfExecuteWorkflow(true); // 可以觸發庫存表單的 workflow
invEntry.save();
"Cannot call methods on null"
問題: 查詢沒有回傳結果
解決方案: 永遠檢查 null/空結果
var entry = query.getAPIEntry(recordId);
if (entry != null) {
var value = entry.getFieldValue(1000001);
}
var result = query.getAPIResult();
if (result != null) {
// 使用 result
}
日期解析問題
問題: 日期無法正確儲存
解決方案: 使用 Ragic 的日期格式 yyyy/MM/dd
// 錯誤
entry.setFieldValue(1000001, "03-15-2024");
entry.setFieldValue(1000001, "2024-03-15");
// 正確
entry.setFieldValue(1000001, "2024/03/15");
entry.setFieldValue(1000001, "2024/03/15 14:30:00");
多選無法運作
問題: 無法設定多個值
解決方案: 使用管線符號分隔值或附加模式
// 方法 1:管線符號分隔字串
entry.setFieldValue(1000001, "選項1|選項2|選項3");
// 方法 2:逐一附加值(第 3 個參數 = true)
entry.setFieldValue(1000001, "選項1");
entry.setFieldValue(1000001, "選項2", true);
entry.setFieldValue(1000001, "選項3", true);
查詢找不到記錄
問題: 篩選回傳空結果
可能原因:
- 欄位 ID 錯誤
- 值格式不符
- 權限問題
// 除錯方法
log.info("搜尋欄位 1000001 = " + searchValue);
var results = query.getAPIResultsFull();
var count = 0;
while (results.hasNext()) {
results.next();
count++;
}
log.info("找到 " + count + " 筆記錄");
"ReferenceError: X is not defined"
問題: 使用 ES6 功能或未定義的變數
解決方案: 只使用 ES5 語法
// 錯誤(ES6)- 會造成 SyntaxError
let value = "test"; // 錯誤 - 使用 var
const LIMIT = 100; // 錯誤 - 使用 var
var items = results.map((r) => r.getFieldValue(1000001)); // 錯誤 - 沒有箭頭函數
// 正確(ES5)
var value = "test";
var LIMIT = 100;
var items = [];
for (var i = 0; i < results.length; i++) {
items.push(results[i].getFieldValue(1000001));
}
意外覆蓋內建物件
問題: 變數名稱遮蔽了 workflow 全域物件
解決方案: 避免使用 response、db、param、user、mailer、util、account、log 或 approval 作為區域變數名稱。這些是內建的 workflow 物件,覆蓋它們會導致非預期的錯誤。
// 錯誤 - 覆蓋了workflow的 response 物件
var response = JSON.parse(util.getURL(apiUrl));
response.setStatus("SUCCESS"); // TypeError: response.setStatus is not a function
// 正確 - 使用不同的變數名稱
var apiResponse = JSON.parse(util.getURL(apiUrl));
response.setStatus("SUCCESS"); // 正常運作
效能問題
查詢時間過長
解決方案:
- 新增特定篩選條件
- 限制結果數量
- 使用 getAPIResultsFull() 進行迭代
// 新增篩選條件並使用迭代器
query.addFilter(1000005, "=", "啟用");
var results = query.getAPIResultsFull();
while (results.hasNext()) {
var entry = results.next();
// 處理記錄
}
除錯技巧
使用日誌陳述式
log.info("workflow開始");
log.debug("記錄 ID:" + recordId);
log.warn("潛在問題:" + warning);
log.error("發生錯誤:" + error);
檢查 workflow 執行日誌
透過表單的 workflow 設定存取 workflow 執行日誌,檢視最近的執行和任何錯誤訊息。
先在開發環境測試
// 新增安全檢查
if (user.getEmail() === "developer@company.com") {
// 測試程式碼
log.info("測試模式 - 將會更新 " + results.length + " 筆記錄");
} else {
// 正式環境程式碼
}