2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

kintoneAdvent Calendar 2023

Day 14

Kintone Customization Debugging Tool - Field Code Table

Last updated at Posted at 2023-12-14

Ever get frustrated debugging a Kintone customization because you cannot remember the field codes for the app?
I made a bookmarklet to generate a markdown table of field codes for a Kintone App.

This article covers the installation, usage, and how the bookmarklet works.

TL;DR

kintone-tool-app-field-code-table.js is a bookmarklet that outputs a markdown table with the field name, field code, and field type for all fields in a Kintone App.

Here is a quick demo of the bookmarklet in action:

kintone-tool-app-field-code-table-demo.gif

Table of Contents

Initial Setup - Add the Bookmarklet

  1. Copy the below kintone-tool-app-field-code-table.js code block
  2. Enter @bookmarks in Chrome address bar
  3. Click on the at the top-right-corner
  4. Click Add new bookmark & paste the code in the URL field

Usage - Generate a Field Code Table

  1. Navigate to the Kintone App
  2. (Optional) Open the Chrome DevTools console
  3. Click the bookmarklet
  4. The markdown table will be copied to the clipboard and displayed in the Chrome DevTools console

If the bookmarklet is not working, check the console for error messages.

  • Mac: Command+Option+C
  • Windows, Linux, Chrome OS: Control+Shift+C

Code - kintone-tool-app-field-code-table.js

javascript: (() => {
  const markdownTableFormatter = () => {
    let cells = [];
    let columnWidths = [];
    let formattedMarkdownTable = "";

    const addMissingCellColumns = () => {
      cells.forEach((row, row_i) => {
        columnWidths.forEach((_, col_i) => {
          if (!cells[row_i][col_i]) {
            cells[row_i][col_i] = '';
          }
        });
      });
    };

    const getColumnWidths = () => {
      columnWidths = [];
      cells.forEach((row) => {
        row.forEach((cell, col_i) => {
          const cellLength = cell.length;
          columnWidths[col_i] = Math.max(columnWidths[col_i] || 0, cellLength);
        });
      });
    };

    const importTable = (table) => {
      const tableRows = table.split("\n").filter(row => row.includes('|'));
      cells = tableRows.map((row, row_i) => {
        let rowColumns = row.split(/(?<!\\)\|/g).map(cell => cell.trim());
        if (row_i === 1) {
          rowColumns = rowColumns.map(cell => cell.replace(/-+/g, "-"));
        }
        return rowColumns;
      });
      getColumnWidths();
      trimCells();
    };

    const padCellsForOutput = () => {
      cells.forEach((row, row_i) => {
        row.forEach((cell, col_i) => {
          const paddingChar = row_i === 1 ? '-' : ' ';
          cells[row_i][col_i] = cell.padEnd(columnWidths[col_i], paddingChar);
        });
      });
    };

    const trimCells = () => {
      if (columnWidths[0] === 0) {
        cells.forEach(row => row.shift());
      }
      getColumnWidths();
      if (columnWidths[columnWidths.length - 1] === 0) {
        cells.forEach(row => {
          if (row.length === columnWidths.length) {
            row.pop();
          }
        });
      }
      getColumnWidths();
    };

    return {
      formatTable: (table) => {
        importTable(table);
        addMissingCellColumns();
        padCellsForOutput();
        formattedMarkdownTable = "| " + cells[0].join(" | ") + " |\n";
        formattedMarkdownTable += "|-" + cells[1].join("-|-") + "-|\n";
        for (let row_i = 2, row_l = cells.length; row_i < row_l; row_i++) {
          formattedMarkdownTable += "| " + cells[row_i].join(" | ") + " |\n";
        }
        return formattedMarkdownTable;
      }
    };
  };

  const subTablesTable = (rawSubTable, subTableRows) => {
    const subTableLabel = rawSubTable.label;
    const subFieldsObj = rawSubTable.fields;
    for (const field in subFieldsObj) {
      const subFieldObj = subFieldsObj[field];
      const subType = subFieldObj.type;
      const subLabel = subFieldObj.label;
      const subCode = subFieldObj.code;
      subTableRows.push(`| ${subTableLabel}'s ${subLabel} | ${subCode} | ${subType}| SubTable |`);
    }
    subTableRows.sort();
    return subTableRows;
  };

  const mainFieldTable = (rawProperties) => {
    return Object.entries(rawProperties).flatMap(([field, mainFieldObj]) => {
      const { type, label, code } = mainFieldObj;

      if (mainFieldObj.enabled === false) {
        return [];
      } else if (mainFieldObj.hasOwnProperty('lookup')) {
        return [`| ${label} | ${code} | ${type} | Lookup |`];
      } else if (type === `SUBTABLE`) {
        const rows = [`| ${label} | ${code} | ${type} | SubTable |`];
        subTablesTable(mainFieldObj, rows);
        return rows;
      } else {
        return [`| ${label} | ${code} | ${type} |`];
      }
    });
  };

  const extractSpacerElementIds = (inputArray) => {
    const blankSpaceElementIds = [];
    const rawFieldsArray = inputArray.reduce((acc, obj) => {
      if (obj.fields && Array.isArray(obj.fields)) {
        return acc.concat(obj.fields);
      }
      return acc;
    }, []);
    rawFieldsArray.forEach(field => {
      if (field.type === "SPACER" && field.elementId) {
        blankSpaceElementIds.push(`| Blank Space | ${field.elementId} | SPACER | elementId |`);
      }
    });
    return blankSpaceElementIds;
  };

  const main = () => {
    const appID = { 'app': kintone.app.getId() };
    const mdHeader = `| Field Name | Code | Type | Note | \n| - | - | - | - |`;
    kintone.api(kintone.api.url('/k/v1/app/form/fields', true), 'GET', appID, resp => {
      const rawFields = resp.properties;
      const rawMainFieldTableRows = mainFieldTable(rawFields);
      kintone.api(kintone.api.url('/k/v1/app/form/layout', true), 'GET', appID, resp => {
        const getBlankSpaceRows = extractSpacerElementIds(resp.layout);
        const rows = rawMainFieldTableRows.concat(getBlankSpaceRows).sort();
        const rawTable = `${mdHeader}\n${rows.join('\n')}`;
        const formatter = markdownTableFormatter();
        const formattedTable = formatter.formatTable(rawTable);
        console.log(formattedTable);
        navigator.clipboard.writeText(formattedTable);
      }, error => console.error(error));
    }, error => console.error(error));
  };
  main();
})();

Code Breakdown

Quick Note - What is a Bookmarklet?

A bookmarklet is a small piece of JavaScript code that can be stored as a bookmark in a web browser. When you click on it, the code runs on the current web page, making extending the browser's functionality easy.

It is a great way to build simple tools for Kintone without creating a full-blown Kintone customization. No need to upload it to the Kintone App, just save it to your browser as a bookmark. There are limitations to what you can do with a bookmarklet, such as no comments or using external libraries, but it is a great way to get started with Kintone customization.

Overall Structure of the Bookmarklet

Since this is a bookmarklet, the code is wrapped in a self-invoking anonymous function, encapsulated in javascript: (() => {...})();. This pattern ensures that the code runs immediately when the bookmark is clicked and its scope does not interfere with the current page's environment.

The core of the bookmarklet revolves around the main function, which handles the API calls to Kintone. The main function is called at the end of the bookmarklet, ensuring that it runs when the bookmarklet is clicked.

Then we have three functions that handle the data processing: mainFieldTable, subTablesTable, and extractSpacerElementIds. These functions are called by the main function to process the data returned by the Kintone API calls.

Lastly, we have the markdownTableFormatter helper function, which handles the formatting of the raw markdown table generated by the main function. You can copy the function for your own markdown table formatting needs.

main Function

The main function acts as the entry point of the bookmarklet. It retrieves the Kintone App's ID and then makes two API calls using kintone.api. First, it uses Get Form Fields API to get the list of fields and field settings of the Kintone App. Then it uses Get Form Layout API to get the field layout info of a form in the Kintone App. The responses from these calls are processed to generate the Markdown table, which is then output to the console and copied to the clipboard.

Why call both APIs?

The Get Form Fields API response is nearly all the information we need. It gets everything except for Space fields! Since Blank Space fields are not technically a data-input field, they are not included in the Get Form Fields API response. The Get Form Layout API, on the other hand, does not include the field's labels (field name). It provides the field types and field codes. However, missing the field's label makes it difficult to identify the field. Thus, we need to call both APIs to get all the information we need to generate the Markdown table. The Get Form Fields API response is used to get the field's label, field type, and field code, while the Get Form Layout API response is used to get the Blank Space fields' element ID.

mainFieldTable Function & subTablesTable Helper Function

The mainFieldTable function uses the Get Form Fields API response to the fields' labels, types, and codes. First, it filters out disabled fields. For Lookup fields, Lookup is added in the notes column since the field type is set to the field it is looking up from. For Table fields, the function calls the subTablesTable helper function to iterate through all the subtable's fields. SubTable is added in the notes column to identify which fields are in a sub-table, and the sub-table's label is added to the field name column. This way, the table is sorted so fields in the same sub-table are grouped together. Finally, all the rows are sorted alphabetically by the field name and returned as an array of strings.

extractSpacerElementIds Function

The extractSpacerElementIds function extracts the element IDs of SPACER type objects from the the Get Form Layout API response. Initially, the function defines an empty array, blankSpaceElementIds, which is intended to store the extracted IDs. Then, a reduce operation is employed on the inputArray. This operation iteratively processes each object in the array. During each iteration, if an object contains a fields property that is also an array, the contents of this fields array are concatenated to an accumulator acc. This process effectively flattens any nested arrays of fields into a single array, rawFieldsArray.

The next step involves iterating over each field in rawFieldsArray using the forEach method. Within this loop, the function checks each field to determine if its type is "SPACER" and if it possesses a non-null elementId. When both conditions are met, a table row with the elementId is pushed into the blankSpaceElementIds array. The table row will include "Blank Space" as the field name, elementId as the field code, "SPACER" as the field type, and lastly, "elementId" is noted. After processing all fields, the function returns the blankSpaceElementIds array.

markdownTableFormatter Helper Function

The markdownTableFormatter helper function handles the formatting of the raw markdown table generated by the main function. It is primarily based on alanwsmith's markdown_table_formatter project. I mainly refactored it so it can be used as a bookmarklet. The function takes in a raw table string, splits it into rows and cells, and formats it into a Markdown table. Having the tables equally spaced and aligned makes it easier to glance at them and find the field code you want.

The addMissingCellColumns, getColumnWidths, importTable, padCellsForOutput, and trimCells helper functions are all inner functions of markdownTableFormatter. They are used to format the raw table data into Markdown syntax. The addMissingCellColumns function adds empty cells to rows that are missing columns. The getColumnWidths function calculates the width of each column. The importTable function splits the raw table string into rows and cells. The padCellsForOutput function adds padding to each cell for output. The trimCells function removes empty columns and rows from the table.

Conclusion

It was surprisingly complicated to gather all the information needed for a helpful reference table. It was pretty straightforward until I realized the Get Form Fields API response did not include the Blank Space field's element ID. However, it was smooth sailing again once I figured out how to extract the element ID from the Get Form Layout API response. I am so glad I found alanwsmith's markdown_table_formatter project. Since this is a bookmarklet, I could not use any external libraries, so coding out the markdown table formatter would have been a pain. Thankfully, I found his project, and it was exactly what I needed. Thank you, alanwsmith!

I hope you found this article useful. If you have any questions or comments, please feel free to comment below or post on the Kintone Developer Forum and tag me (@genji)!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?