iT邦幫忙

0

kintone 外掛開發 ④ 簡單實作範例 part 2 - 製作外掛設定畫面

  • 分享至 

  • xImage
  •  

經過上一篇文章 kintone 外掛開發 ③ 工具介紹:webpack-plugin-kintone-plugin ,我們已經在專案中加入了 webpack-plugin-kintone-pluginplugin-uploader 以及其他相關的開發工具,讓我們可以即時觀察修改程式碼後的效果。

整備好開發環境後,現在我們可以來動手製作外掛的設定畫面了!

2025/6/3 修訂:
由於計算欄位不支援觸發 kintone Change Event,外掛設定中的「欲加總數值欄位」可選欄位類型改為僅可選擇數值欄位。

預期的效果

在這個外掛中,我們希望使用者可以自行設定要被加總的表格欄位、條件判斷的來源欄位、條件值,以及輸出加總結果的欄位,因此需要提供以下設定項目:

  • 選擇表格:選擇要被計算的表格。可選擇的欄位類型為【表格】。
  • 條件欄位:選擇該表格中,作為條件判斷來源的欄位。可選擇的欄位類型有【單行文字方塊、選項按鈕、下拉選單】。
  • 條件值:輸入條件值。當條件欄位的值等於條件值時,進行加總。
  • 欲加總數值欄位:選擇該表格中,符合條件時要被加總的欄位。可選擇的欄位類型為【數值】。
  • 輸出加總值的欄位:選擇在表格之外用來輸出加總結果的欄位,可選擇的欄位類型為【數值】。

同時,也希望允許使用者添加多組設定,因此將設定欄位設計成表格形式,讓使用者可以依照需求添加設定。

設定頁面的最下方放置「保存」與「取消」按鈕,分別用來確定保存設定內容,以及返回外掛程式一覽畫面。

使用的套件

在這個範例中,我們將運用 kintone 官方提供的另外兩個套件來協助開發。

  1. kintone-rest-api-client
  2. kintone-ui-component

kintone-rest-api-client

kintone-rest-api-client 匯總了在 JavaScript 中呼叫 kintone REST API 時所需的處理,並且支援 Web 瀏覽器和 Node.js 環境。

在接下來的實作中,我們會利用這個套件來呼叫 kintone REST API ,取得應用程式中的欄位資訊。

kintone-ui-component

kintone-ui-component 是官方提供的 UI 元件庫,其設計風格和 kintone 介面相近,可以輕鬆打造風格一致的設定畫面。

透過 CDN URL 引入套件

kintone-rest-api-clientkintone-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 刻出畫面

首先,使用 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)

程式碼說明

  • 以 kintone UI Component 提供的方法 new Kuc.[元件]() 來建構所需元件。
  • kintone UI Component 的 Table 元件中,columns 屬性為一陣列,在陣列中透過 column 物件來建構表格欄。若要在表格欄中渲染其他元件,需使用 render 屬性透過函式來進行渲染,因此在建構 Table 元件前,先建立以下五個函式,分別用以渲染對應欄位中所需要的元件:
    • renderTableSelect:「選擇表格」的 Dropdown 元件
    • renderConditionFieldSelect:「條件欄位」的 Dropdown 元件
    • renderTextInput:「條件值」的 Text 元件
    • renderInputFieldSelect:「欲加總數值欄位」的 Dropdown 元件
    • renderOutputFieldSelect :「輸出加總值的欄位」的 Dropdown 元件
  • Dropdown 元件中,items 屬性為一陣列,用來設定下拉選單的選項。在這一步驟中,暫時先設定固定的假資料。

npm start 指令啟動專案監聽,待自動打包上傳完成後,應該可以在外掛設定頁面中看到剛才做好的畫面。

使用 kintone REST API Client 取得應用程式欄位資料

做好基本 UI 之後,下一步我們要融入應用程式的資料,希望達到的效果如下:

  • 使用者點擊「選擇表格」下拉選單,選項會列出應用程式中所有的表格。
  • 選擇一個表格後,「條件欄位」下拉選單的選項會列出該表格中欄位類型符合「單行文字方塊」、「選項按鈕」、或是「下拉式選單」的欄位。
  • 選擇一個表格後,與「欲加總數值欄位」下拉選單的選項會列出該表格中欄位類型符合「數值」的欄位。
  • 「輸出加總值的欄位」下拉選單的選項,列出應用程式中不在表格內的「數值」欄位。

這裡需要透過 取得欄位(getFormFields) 這支 API 來取得應用程式中的欄位資訊資訊,再篩選成下拉選單中的選項。

透過 kintone REST API Client 呼叫 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() 回傳的物件中直接取得 propertiesproperties 是一個物件,其 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)格式。每個選項的顯示內容為 欄位名稱(欄位代碼),實際的值則是欄位代碼。

接著將 renderTableSelectrenderOutputFieldSelect 中的 items 屬性,分別替換為前面定義好的 tableOptionsoutputFieldOptions。以下以其中一個為例:

// 於 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)
}

② 監聽「選擇表格」Dropdown 元件的變化事件

接著,改寫 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 應用程式中,讓外掛真正發揮作用。


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言