經過上一篇文章 kintone 外掛開發 ③ 工具介紹:webpack-plugin-kintone-plugin ,我們已經在專案中加入了 webpack-plugin-kintone-plugin
與 plugin-uploader
以及其他相關的開發工具,讓我們可以即時觀察修改程式碼後的效果。
整備好開發環境後,現在我們可以來動手製作外掛的設定畫面了!
2025/6/3 修訂:
由於計算欄位不支援觸發 kintone Change Event,外掛設定中的「欲加總數值欄位」可選欄位類型改為僅可選擇數值欄位。
在這個外掛中,我們希望使用者可以自行設定要被加總的表格欄位、條件判斷的來源欄位、條件值,以及輸出加總結果的欄位,因此需要提供以下設定項目:
同時,也希望允許使用者添加多組設定,因此將設定欄位設計成表格形式,讓使用者可以依照需求添加設定。
設定頁面的最下方放置「保存」與「取消」按鈕,分別用來確定保存設定內容,以及返回外掛程式一覽畫面。
在這個範例中,我們將運用 kintone 官方提供的另外兩個套件來協助開發。
kintone-rest-api-client
匯總了在 JavaScript 中呼叫 kintone REST API 時所需的處理,並且支援 Web 瀏覽器和 Node.js 環境。
在接下來的實作中,我們會利用這個套件來呼叫 kintone REST API ,取得應用程式中的欄位資訊。
kintone-ui-component
是官方提供的 UI 元件庫,其設計風格和 kintone 介面相近,可以輕鬆打造風格一致的設定畫面。
kintone-rest-api-client
與 kintone-ui-component
都有提供 CDN 連結,我們也可以在外掛中直接引入 CDN 連結使用。方法相當簡單,就是直接在 manifest.json
中的 js
設定內加入連結即可。
kintone-rest-api-client
的 CDN 連結可從 cybozu developer network 取得,kintone-ui-component
則可從 kintone UI Component 官方文件網站 取得。
由於我們是要在設定畫面中使用套件,所以將連結加到 config.js
的陣列中:
"config": {
"html": "html/config.html",
"js": [
"https://unpkg.com/kintone-ui-component/umd/kuc.min.js",
"https://js.cybozu.com/kintone-rest-api-client/5.7.1/KintoneRestAPIClient.min.js",
"js/config.js"
]
}
※註:範例中引入的套件版本為文章撰寫當下的最新版本。
這裡需要留意連結的順序,由於我們是要在主要運行的程式碼 js/config.js
中使用這些套件功能,必須在主程式執行前就先引入套件,因此 CDN 的連結必須放置在主程式碼之前。
首先,使用 kintone UI Component 先做出設定畫面的基本 UI。在此範例中,我們會用到以下元件:
各元件詳細使用說明請參考官方文件(點擊上列項目可連結到對應的文件)。
以下是範例程式碼:
((PLUGIN_ID) => {
'use strict'
// 預設空白列資料
const defaltRowData = {
table: '',
conditionField: '',
conditionValue: '',
inputField: '',
outputField: ''
}
let data = [defaltRowData]
// 於 kintone UI Component 表格內渲染「選擇表格」下拉選單
const renderTableSelect = (cellData, rowData, rowIndex) => {
const dropdown = new Kuc.Dropdown({
items: [
{ label: 'Table 1', value: 'Table_1' },
{ label: 'Table 2', value: 'Table_2' }
],
value: cellData
})
return dropdown
}
// 於 kintone UI Component 表格內渲染「條件欄位」下拉選單
const renderConditionFieldSelect = (cellData, rowData, rowIndex) => {
const dropdown = new Kuc.Dropdown({
items: [
{ label: 'Field 1', value: 'Field_1' },
{ label: 'Field 2', value: 'Field_2' }
],
value: cellData
})
return dropdown
}
// 於 kintone UI Component 表格內渲染「條件值」 Input 元件
const renderTextInput = (cellData) => {
return new Kuc.Text({
value: cellData
})
}
// 於 kintone UI Component 表格內渲染「欲加總數值欄位」下拉選單
const renderInputFieldSelect = (cellData, rowData, rowIndex) => {
const dropdown = new Kuc.Dropdown({
items: [
{ label: 'Field 1', value: 'Field_1' },
{ label: 'Field 2', value: 'Field_2' }
],
value: cellData
})
return dropdown
}
// 於 kintone UI Component 表格內渲染「輸出加總值的欄位」下拉選單
const renderOutputFieldSelect = (cellData, rowData, rowIndex) => {
const dropdown = new Kuc.Dropdown({
items: [
{ label: 'Field 1', value: 'Field_1' },
{ label: 'Field 2', value: 'Field_2' }
],
value: cellData
})
return dropdown
}
// 建立 kintone UI Component 表格元件
const table = new Kuc.Table({
label: '設定加總條件',
columns: [
{
title: '選擇表格',
field: 'table',
render: renderTableSelect
},
{
title: '條件欄位',
field: 'conditionField',
render: renderConditionFieldSelect
},
{
title: '條件值',
field: 'conditionValue',
render: renderTextInput
},
{
title: '欲加總數值欄位(表格內)',
field: 'inputField',
render: renderInputFieldSelect
},
{
title: '輸出加總值的欄位(表格外)',
field: 'outputField',
render: renderOutputFieldSelect
}
],
data
})
// 建立 kintone UI Component 按鈕元件
const saveButton = new Kuc.Button({
text: '保存',
type: 'submit'
})
const cancelButton = new Kuc.Button({
text: '取消',
type: 'normal'
})
// 建立按鈕區塊
const buttonGroup = document.createElement('div')
buttonGroup.style.display = 'flex'
buttonGroup.style.justifyContent = 'flex-end'
buttonGroup.style.gap = '8px'
buttonGroup.style.paddingTop = '16px'
buttonGroup.style.marginTop = '60px'
buttonGroup.style.borderTop = '1px solid #e4e4e4'
// 掛載元件
const container = document.querySelector('#plugin-setting-container')
buttonGroup.appendChild(cancelButton)
buttonGroup.appendChild(saveButton)
container.appendChild(table)
container.appendChild(buttonGroup)
})(kintone.$PLUGIN_ID)
new Kuc.[元件]()
來建構所需元件。columns
屬性為一陣列,在陣列中透過 column
物件來建構表格欄。若要在表格欄中渲染其他元件,需使用 render
屬性透過函式來進行渲染,因此在建構 Table 元件前,先建立以下五個函式,分別用以渲染對應欄位中所需要的元件:
renderTableSelect
:「選擇表格」的 Dropdown 元件renderConditionFieldSelect
:「條件欄位」的 Dropdown 元件renderTextInput
:「條件值」的 Text 元件renderInputFieldSelect
:「欲加總數值欄位」的 Dropdown 元件renderOutputFieldSelect
:「輸出加總值的欄位」的 Dropdown 元件items
屬性為一陣列,用來設定下拉選單的選項。在這一步驟中,暫時先設定固定的假資料。以 npm start
指令啟動專案監聽,待自動打包上傳完成後,應該可以在外掛設定頁面中看到剛才做好的畫面。
做好基本 UI 之後,下一步我們要融入應用程式的資料,希望達到的效果如下:
這裡需要透過 取得欄位(getFormFields) 這支 API 來取得應用程式中的欄位資訊資訊,再篩選成下拉選單中的選項。
先將最外層的立即函式改成非同步函式,並將上面撰寫好的邏輯用 try...catch
包裝起來,方便待會加入呼叫 API 後的資料以及錯誤處理。
(async (PLUGIN_ID) => {
'use strict'
try {
// 從預設空白列起一直到掛載元件的所有邏輯...
} catch (error) {
console.error(error)
window.alert(error)
}
})(kintone.$PLUGIN_ID)
接著,使用 KintoneRestAPIClient
建構呼叫 API 用的客戶端。以下是基本的建構方式:
const client = new KintoneRestAPIClient({
// kintone 網域,於 node.js 環境中必填,在瀏覽器環境中將使用 location.origin
baseUrl: 'https://example.cybozu.com',
// 使用帳號密碼驗證身份
auth: {
username: process.env.KINTONE_USERNAME,
password: process.env.KINTONE_PASSWORD,
},
// 使用 API token 驗證身份
// auth: { apiToken: process.env.KINTONE_API_TOKEN }
// 瀏覽器環境中,未填 auth 時將使用登入階段驗證(Session authentication)
// 訪客空間:如果要處理訪客空間或訪客空間內的應用程式,需帶入 guestSpaceId
// guestSpaceId: GUEST_SPACE_ID
})
由於外掛設定畫面在瀏覽器環境中,且擁有應用程式管理權限的使用者才能進入設定,如果不考慮訪客空間的話,最簡潔的寫法為:
const req = new KintoneRestAPIClient()
若要在一般環境以及訪客空間都能正常使用的話,可以透過網頁路徑判斷是否為訪客空間並且取得訪客空間 ID:
(async (PLUGIN_ID) => {
'use strict'
// 判別是否為訪客空間,若為訪客空間,需在 REST API client 中帶入訪客空間 ID
const path = window.location.pathname
const guestMatch = path.match(/^\/k\/guest\/(\d+)\//)
const guestSpaceId = guestMatch ? guestMatch[1] : undefined
const req = new KintoneRestAPIClient({ guestSpaceId })
try {
// 從預設空白列起一直到掛載元件的所有邏輯...
} catch (error) {
console.error(error)
window.alert(error)
}
})(kintone.$PLUGIN_ID)
建立客戶端後,即可透過其預設的方法來呼叫 API:
(async (PLUGIN_ID) => {
'use strict'
// 判別是否為訪客空間,若為訪客空間,需在 REST API client 中帶入訪客空間 ID
const path = window.location.pathname
const guestMatch = path.match(/^\/k\/guest\/(\d+)\//)
const guestSpaceId = guestMatch ? guestMatch[1] : undefined
const req = new KintoneRestAPIClient({ guestSpaceId })
try {
// 預設空白列資料
const defaltRowData = {
table: '',
conditionField: '',
conditionValue: '',
inputField: '',
outputField: ''
}
let data = [defaltRowData]
// 取得應用程式中的欄位資訊
const { properties } = await req.app.getFormFields({
app: kintone.app.getId()
})
// 其他邏輯...
} catch (error) {
console.error(error)
window.alert(error)
}
})(kintone.$PLUGIN_ID)
這裡使用了解構賦值的方式,從 KintoneRestAPIClient.app.getFormFields()
回傳的物件中直接取得 properties
。properties
是一個物件,其 key 為欄位代碼,value 則包含該欄位詳細資訊的物件。等一下會使用到以下欄位資訊:
type
:欄位類型label
:欄位名稱code
:欄位代碼fields
:表格中的欄位如果對 API 回傳的資料格式還不熟悉,建議可以先使用 console.log(properties)
來檢視內容。關於 KintoneRestAPIClient.app.getFormFields()
的詳細說明,請參考其 官方文件。
首先,我們來處理表格外部的欄位選項。在「選擇表格」的下拉選單中,需要列出應用程式內的所有表格;而「輸出加總值的欄位」則要列出數值欄位。
const tableOptions = Object.entries(properties)
.filter(([key, value]) => value.type === 'SUBTABLE')
.map(([key, value]) => ({
label: `${value.label}(${value.code})`,
value: value.code
}))
const outputFiedlOptions = Object.entries(properties)
.filter(([key, value]) => value.type === 'NUMBER')
.map(([key, value]) => ({
label: `${value.label}(${value.code})`,
value: value.code
}))
這裡利用欄位資訊中的 type
屬性來篩選所需欄位,並將結果整理成 Dropdown 元件所需的選項(items)格式。每個選項的顯示內容為 欄位名稱(欄位代碼)
,實際的值則是欄位代碼。
接著將 renderTableSelect
和 renderOutputFieldSelect
中的 items
屬性,分別替換為前面定義好的 tableOptions
與 outputFieldOptions
。以下以其中一個為例:
// 於 kintone UI Component 表格內渲染「選擇表格」下拉選單
const renderTableSelect = (cellData, rowData, rowIndex) => {
const dropdown = new Kuc.Dropdown({
items: tableOptions,
value: cellData
})
return dropdown
}
回到外掛設定畫面查看,此時「選擇表格」的下拉選單中,應該會出現明細表格的選項;而「輸出加總值的欄位」則會顯示住宿費小計、交通費小計、餐飲費小計這三個位於表格外的數值欄位選項。
完成表格外的欄位選項設定後,接下來要處理較為複雜的部分——根據所選擇的表格,在「條件欄位」與「欲加總數值欄位」的下拉選單中,動態列出該表格內的欄位選項。
由於這項功能必須根據使用者即時選取的表格更新欄位選單,因此我們需監聽 table
元件本身,以及表格中的「選擇表格」Dropdown 元件的變化事件。
table
元件的變化事件首先,監聽整個表格元件的變化事件,當使用者在表格中進行任何編輯時,將最新的資料同步至 data
陣列中。
try {
// 預設空白列資料
const defaltRowData = {
table: '',
conditionField: '',
conditionValue: '',
inputField: '',
outputField: ''
}
let data = [defaltRowData]
// 其他邏輯...
// 監聽表格變化
table.addEventListener('change', e => {
data = e.detail.data
})
} catch (error) {
console.error(error)
window.alert(error)
}
接著,改寫 renderTableSelect
函式,讓其在回傳下拉選單元件前,先註冊 change
事件監聽器。當使用者更改所選的表格時,根據目前的 rowIndex
更新 data
陣列中對應資料列的值,並透過重新指定 table.data
的方式,觸發表格重新渲染。
// 於 kintone UI Component 表格內渲染「選擇表格」下拉選單
const renderTableSelect = (cellData, rowData, rowIndex) => {
const dropdown = new Kuc.Dropdown({
items: tableOptions,
value: cellData
})
dropdown.addEventListener('change', e => {
const newValue = e.detail.value
data[rowIndex].table = newValue
data[rowIndex].conditionField = ''
data[rowIndex].inputField = ''
table.data = [...data] // 更新表格資料,觸發重新渲染
})
return dropdown
}
當使用者更換「選擇表格」的值時,對應的「條件欄位」與「欲加總數值欄位」選項也會隨之變動。因此,在變更表格選項時,我們會將這兩個欄位的值一併清空,以避免出現與目前選定表格不相符的欄位設定,確保資料正確性與元件行為一致性。
最後一步,是根據使用者在「選擇表格」中所選的表格代碼,從一開始透過 REST API 取得的 properties
中,擷取該表格的欄位資料,並轉換成符合下拉選單格式的選項陣列。
// 於 kintone UI Component 表格內渲染「條件欄位」下拉選單
const renderConditionFieldSelect = (cellData, rowData, rowIndex) => {
let items = []
const subtable = properties[rowData.table]
if (subtable) {
const conditionFieldTypes = ['SINGLE_LINE_TEXT', 'RADIO_BUTTON', 'DROP_DOWN']
items = Object.entries(subtable.fields)
.filter(([key, value]) => conditionFieldTypes.includes(value.type))
.map(([key, value]) => ({
label: `${value.label}(${value.code})`,
value: value.code
}))
}
return new Kuc.Dropdown({
items,
value: cellData
})
}
// 於 kintone UI Component 表格內渲染「欲加總數值欄位」下拉選單
const renderInputFieldSelect = (cellData, rowData, rowIndex) => {
let items = []
const subtable = properties[rowData.table]
if (subtable) {
items = Object.entries(subtable.fields)
.filter(([key, value]) => value.type === 'NUMBER')
.map(([key, value]) => ({
label: `${value.label}(${value.code})`,
value: value.code
}))
}
return new Kuc.Dropdown({
items,
value: cellData
})
}
這兩個函式的邏輯結構相同,差別僅在於所篩選的欄位類型。
現在,可以回到應用程式的設定畫面中,新增另一個表格來測試功能是否如預期運作。
目前為止,我們已經完成了整個外掛設定畫面的 UI 設計。以下是完整的實作程式碼,包含表格欄位的動態載入與欄位選單的互動邏輯:
(async (PLUGIN_ID) => {
'use strict'
// 判別是否為訪客空間,若為訪客空間,則在 REST API client 中帶入訪客空間 ID
const path = window.location.pathname
const guestMatch = path.match(/^\/k\/guest\/(\d+)\//)
const guestSpaceId = guestMatch ? guestMatch[1] : undefined
const req = new KintoneRestAPIClient({ guestSpaceId })
try {
// 預設空白列資料
const defaltRowData = {
table: '',
conditionField: '',
conditionValue: '',
inputField: '',
outputField: ''
}
let data = [defaltRowData]
// 取得應用程式中的欄位資訊
const { properties } = await req.app.getFormFields({
app: kintone.app.getId()
})
const tableOptions = Object.entries(properties)
.filter(([key, value]) => value.type === 'SUBTABLE')
.map(([key, value]) => ({
label: `${value.label}(${value.code})`,
value: value.code
}))
const outputFiedlOptions = Object.entries(properties)
.filter(([key, value]) => value.type === 'NUMBER')
.map(([key, value]) => ({
label: `${value.label}(${value.code})`,
value: value.code
}))
// 於 kintone UI Component 表格內渲染「選擇表格」下拉選單
const renderTableSelect = (cellData, rowData, rowIndex) => {
const dropdown = new Kuc.Dropdown({
items: tableOptions,
value: cellData
})
dropdown.addEventListener('change', e => {
const newValue = e.detail.value
data[rowIndex].table = newValue
data[rowIndex].conditionField = ''
data[rowIndex].inputField = ''
table.data = [...data] // 更新表格資料,觸發重新渲染
})
return dropdown
}
// 於 kintone UI Component 表格內渲染「條件欄位」下拉選單
const renderConditionFieldSelect = (cellData, rowData, rowIndex) => {
let items = []
const subtable = properties[rowData.table]
if (subtable) {
const conditionFieldTypes = ['SINGLE_LINE_TEXT', 'RADIO_BUTTON', 'DROP_DOWN']
items = Object.entries(subtable.fields)
.filter(([key, value]) => conditionFieldTypes.includes(value.type))
.map(([key, value]) => ({
label: `${value.label}(${value.code})`,
value: value.code
}))
}
return new Kuc.Dropdown({
items,
value: cellData
})
}
// 於 kintone UI Component 表格內渲染「條件值」 Input 元件
const renderTextInput = (cellData) => {
return new Kuc.Text({
value: cellData
})
}
// 於 kintone UI Component 表格內渲染「欲加總數值欄位」下拉選單
const renderInputFieldSelect = (cellData, rowData, rowIndex) => {
let items = []
const subtable = properties[rowData.table]
if (subtable) {
items = Object.entries(subtable.fields)
.filter(([key, value]) => value.type === 'NUMBER')
.map(([key, value]) => ({
label: `${value.label}(${value.code})`,
value: value.code
}))
}
return new Kuc.Dropdown({
items,
value: cellData
})
}
// 於 kintone UI Component 表格內渲染「輸出加總值的欄位」下拉選單
const renderOutputFieldSelect = (cellData, rowData, rowIndex) => {
const dropdown = new Kuc.Dropdown({
items: outputFiedlOptions,
value: cellData
})
return dropdown
}
// 建立 kintone UI Component 表格元件
const table = new Kuc.Table({
label: '設定加總條件',
columns: [
{
title: '選擇表格',
field: 'table',
render: renderTableSelect
},
{
title: '條件欄位',
field: 'conditionField',
render: renderConditionFieldSelect
},
{
title: '條件值',
field: 'conditionValue',
render: renderTextInput
},
{
title: '欲加總數值欄位(表格內)',
field: 'inputField',
render: renderInputFieldSelect
},
{
title: '輸出加總值的欄位(表格外)',
field: 'outputField',
render: renderOutputFieldSelect
}
],
data
})
// 建立 kintone UI Component 按鈕元件
const saveButton = new Kuc.Button({
text: '保存',
type: 'submit'
})
const cancelButton = new Kuc.Button({
text: '取消',
type: 'normal'
})
// 建立按鈕區塊
const buttonGroup = document.createElement('div')
buttonGroup.style.display = 'flex'
buttonGroup.style.justifyContent = 'flex-end'
buttonGroup.style.gap = '8px'
buttonGroup.style.paddingTop = '16px'
buttonGroup.style.marginTop = '60px'
buttonGroup.style.borderTop = '1px solid #e4e4e4'
// 掛載元件
const container = document.querySelector('#plugin-setting-container')
buttonGroup.appendChild(cancelButton)
buttonGroup.appendChild(saveButton)
container.appendChild(table)
container.appendChild(buttonGroup)
// 監聽表格變化
table.addEventListener('change', e => {
data = e.detail.data
})
} catch (error) {
console.error(error)
window.alert(error)
}
})(kintone.$PLUGIN_ID)
至此,使用者已經可以透過視覺化的設定介面,自由選取表格與欄位,並定義加總條件與結果輸出欄位。
下一篇文章將會介紹如何使用 kintone plugin API 將這些設定資料保存下來,並且反映至 kintone 應用程式中,讓外掛真正發揮作用。