在上一篇文章 kintone 外掛開發 ① kintone 外掛的基本架構 中,我們介紹了 kintone 外掛的基本結構與打包方式,並簡單說明了外掛的組成與開發時的注意事項。
這一篇文章,我們將透過一個簡單的實作範例,實際動手打包一個 kintone 外掛,帶大家從寫好一段基本的 JavaScript 客製化邏輯開始,逐步轉換成可安裝的外掛檔案。
在 kintone 中,我們可以透過「表格外」的計算欄位搭配公式 SUM(表格中的數值欄位代碼)
來加總表格中特定的數值欄位。例如下圖中應用程式的「總計」欄位,就是使用 SUM(金額)
公式,自動加總明細表格中的「金額」欄位:
然而,如果我們希望計算出「表格中分類為交通費的金額總和」,就無法單純透過自動計算欄位來達成。
常見的誤解是,嘗試在「表格外」的計算欄位中使用 IF(條件, 條件為真的值, 條件為假的值)
,像是:
IF(分類="交通費", SUM(金額), 0)
但目前 kintone 的自動計算公式不支援這種跨表格欄位條件判斷的寫法。詳情可參考官方說明文件:kintone 說明 - 計算公式的錯誤訊息
比較常見的替代方案,是在「表格內」增加一個新的計算欄位,來進行條件判斷。例如新增一個「交通費金額」欄位,公式如下:
IF(分類="交通費", 金額, 0)
接著再於「表格外」的計算欄位使用 SUM(交通費金額)
來加總這個欄位的值。
雖然這樣做可以達成功能,但也會讓表格變得較長、欄位數量變多,在視覺上較為混亂。
若希望畫面更簡潔、避免出現過多中間欄位,就可以透過 JavaScript 客製化,透過程式碼運算來實現條件加總。或者也採用 顯示/隱藏欄位的 API 將不必要的欄位隱藏,避免干擾使用者操作。
在本篇範例中,我們將採用 JavaScript 客製化的方式,來實作一個簡單的條件分類加總功能,並進一步將它打包成可重複使用的 kintone 外掛。
以下是一個簡單的範例應用程式,我們希望達到的效果是:
在明細表格中輸入分類(交通費、住宿費、餐飲費)與金額後,系統能夠自動計算出每個分類的小計,並顯示在表格外的「交通費小計」、「住宿費小計」、「餐飲費小計」三個欄位中。
欄位名稱 | 欄位代碼 | 欄位類型 | 備註 |
---|---|---|---|
申請日 | 申請日 | 日期 | |
標題 | 標題 | 單行文字方塊 | |
申請人 | 申請人 | 選擇使用者 | |
明細 | 明細 | 表格 | |
分類 | 分類 | 下拉式選單 | 表格「明細」中的欄位。選項內容:交通費、住宿費、餐飲費。 |
項目 | 項目 | 單行文字方塊 | 表格「明細」中的欄位。 |
金額 | 金額 | 數值 | |
總計 | 總計 | 計算 | 計算公式:SUM(金額) |
交通費小計 | 交通費小計 | 數值 | |
住宿費小計 | 住宿費小計 | 數值 | |
餐飲費小計 | 餐飲費小計 | 數值 |
以下是一段簡單的客製化程式碼,透過觸發表格「明細」以及表格中欄位「分類」、「金額」改變時的 kintone event,在輸入分類與金額時自動加總數值,並輸出到對應的小計欄位中:
(() => {
'use strict'
const tableField = '明細'
const inputFields = ['分類', '金額']
const outputFields = ['交通費小計', '住宿費小計', '餐飲費小計']
kintone.events.on([
'app.record.create.show', 'app.record.edit.show',
'mobile.app.record.create.show', 'mobile.app.record.edit.show',
createChangeEvents([...inputFields, tableField]).flat()
], event => {
const { record } = event
const table = record[tableField].value
// 禁止編輯小計欄位
outputFields.forEach(field => {
if (record[field]) record[field].disabled = true
})
// 根據條件分類加總計算表格中的金額
const subtotal = Object.fromEntries(outputFields.map(field => [field, 0]))
table.forEach(row => {
const category = row.value['分類'].value
const price = Number(row.value['金額'].value) || 0
if (category === '交通費') {
subtotal['交通費小計'] += price
} else if (category === '住宿費') {
subtotal['住宿費小計'] += price
} else if (category === '餐飲費') {
subtotal['餐飲費小計'] += price
}
})
// 將小計值輸出到指定的欄位
outputFields.forEach(field => {
record[field].value = subtotal[field]
})
return event
})
// 建立 change events 用
function createChangeEvents(fieldCodes) {
if (!Array.isArray(fieldCodes)) return
return fieldCodes.map(fieldCode => [
`app.record.create.change.${fieldCode}`,
`app.record.edit.change.${fieldCode}`,
`mobile.app.record.create.change.${fieldCode}`,
`mobile.app.record.edit.change.${fieldCode}`,
`app.record.index.edit.change.${fieldCode}`
]).flat()
}
})()
這段程式碼的邏輯並不複雜,主要是根據使用者輸入的分類與金額,分別加總出三個分類的總金額,並自動填入至表格外的對應欄位。同時也透過 disabled
屬性,讓使用者無法編輯這些欄位,使其更像是自動計算欄位的行為。
雖然這樣的程式碼很實用,但若未來要更改欄位代碼、分類項目,或是希望將功能套用到不同的應用程式,就必須每次都手動修改原始碼,稍嫌不便。
這時候,我們就可以考慮把這段程式碼封裝成一個可重複使用的 kintone 外掛,讓設定項目可以由非工程師透過圖形介面調整。
下一段,我們就會開始動手,嘗試把這段功能封裝成可安裝的外掛檔案。
首先,建立專案資料夾以及外掛所需的基本檔案:
demo-plugin/ // 專案根目錄
├── src/ // 將需打包的檔案集中在此資料夾下
│ ├── js/
│ │ ├── config.js // 外掛設定畫面的程式碼
│ │ └── customize.js // 反映於應用程式中的程式碼
│ ├── html/
│ │ └── config.html // 外掛設定畫面的 HTML 結構
│ ├── image/
│ │ └── icon.png // 外掛的圖示
│ └── manifest.json // 外掛設定的中樞,定義所有檔案與基本資訊
└── package.json
請先建立專案資料夾「demo-plugin」,並於其中執行 npm init -y
產生 package.json
。接著,依照上方架構建立相對應的資料夾與檔案。
這個檔案是外掛設定畫面的 HTML 結構,這裡我們先簡單放上一個作為 container 的元素:
<div id="plugin-setting-container"></div>
此檔案負責設定畫面的程式邏輯。為了演示效果,這裡先不處理實際設定功能,僅顯示目前的外掛 ID:
((PLUGIN_ID) => {
'use strict'
const container = document.querySelector('#plugin-setting-container')
const pluginIdText = document.createElement('p')
pluginIdText.textContent = `Plugin ID: ${PLUGIN_ID}`
container.appendChild(pluginIdText)
})(kintone.$PLUGIN_ID)
同上篇文章中「外掛ID的參照範例」的部分所述,當一個應用程式中使用了多個外掛時,kintone.$PLUGIN_ID 可能會被多次賦值,因此建議使用立即執行函式(IIFE)來補捉並使用當下的外掛 ID,並封裝邏輯。
這是實際作用於應用程式的客製化程式碼,可將先前撰寫的範例程式碼原封不動搬過來。
圖示為打包時必備的檔案,請選擇一張合適的圖片(建議為正方形 PNG 格式)。
這是整個外掛的設定檔,定義了基本資訊與載入的檔案:
{
"manifest_version": 1,
"version": 1,
"type": "APP",
"name": {
"ja": "サンプルプラグイン",
"en": "sample plugin",
"zh-TW": "範例外掛"
},
"description": {
"ja": "これはサンプルプラグインです。",
"en": "This is sample plugin.",
"zh-TW": "這是一個範例外掛"
},
"icon": "image/icon.png",
"desktop": {
"js": [
"js/customize.js"
]
},
"mobile": {
"js": [
"js/customize.js"
]
},
"config": {
"html": "html/config.html",
"js": [
"js/config.js"
]
}
}
plugin-packer
在專案根目錄下執行以下指令,安裝 kintone 官方提供的打包工具:
npm install -D @kintone/plugin-packer
為了簡化後續打包流程,我們可以將指令寫入 package.json
的 scripts
區段:
"scripts": {
"pack:new": "kintone-plugin-packer --out dist/plugin.zip src",
"pack": "kintone-plugin-packer --out dist/plugin.zip --ppk dist/private.ppk src"
}
初次打包時,執行:
npm run pack:new
上述指令會將打包後的外掛壓縮檔 plugin.zip
輸出至 dist
資料夾,並同時產生一個密鑰檔(預設為 PLUGIN_ID.ppk
)。請將該密鑰檔重新命名為 private.ppk
,日後即可使用下列指令重新打包:
npm run pack
至此,我們已成功將原本的客製化功能封裝為一個 kintone 外掛。接下來,你可以將 plugin.zip
上傳至 kintone 並安裝使用。安裝後,請記得將應用程式中原本的客製化程式碼移除,再啟用此外掛。
打開外掛設定畫面,應該可以看到顯示外掛 ID 的字樣:
更新應用程式後,回到記錄編輯畫面進行測試,應可如預期執行分類加總的功能。
目前外掛中的欄位代碼仍為寫死的狀態,因此若應用程式的欄位有所變動,仍需修改程式碼。為了讓外掛更具彈性與可調整性,下一篇文章將介紹如何透過外掛設定頁面及相關 API,讓使用者可以自行設定欄位代碼與分類項目。