kintoneゼロ埋めプラグイン製作記（前編）

全９回にわたって書いた、プラグイン作家になる為記事「プラグイン開発メモその１〜その９」までのまとめです。
[*後編はこちらから](https://qiita.com/sy250f/items/060b51edef4ad931da2f)

# まとめ記事のプラグインについて
メモその１〜９で作成したプラグインの機能は、サブテーブルに対応していなかったので、機能としてはもう一つでした。
そのまま使える様に、今回のまとめではサブテーブル内のフィールドに対応しています。

# プラグイン作成の流れ
作成方法は色々だと思いますが、今回は次のような流れで作成しました。

 1. テストアプリ作成
 2. JavaScriptカスタマイズ
 3. ダミーの設定値データの作成
 4. ダミーの設定値を読み込んでJSカスタマイズ
 5. 設定画面の作成
 6. 設定画面生成用のJavaScriptカスタマイズ
 7. 実データを使ってのJavaScriptカスタマイズ

肝は3.のダミーの設定値データの作成です。ここで設定値を設定画面で扱いやすい様に設計できると楽だと思います。

# テストアプリ作成
<img width="1157" alt="スクリーンショット 2019-02-20 22.48.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/214523/1af80fdf-f8cd-1951-9458-c6ee147b0b37.png">
数値１、２は数値フィールド。計算は計算フィールドです。
サブテーブルはルックアップ、数値フィールド、計算フィールドを配置しています。

# JavaScriptカスタマイズ
プラグイン作成前に、通常のJavaScriptカスタマイズを作成します。
この時に、設定画面でセットした設定値を読み込んで、利用する様に作成します。

```customize.js
(function(pluginId) {
    "use strict";
    let config = kintone.plugin.app.getConfig(pluginId)
    config = {
        'ZeroFillItem': ["単価", "単価_1", "単価_0"]
    };

    // 詳細画面　編集、新規作成
    kintone.events.on(['app.record.edit.show', 'app.record.create.show'], function(event) {
        let myIndexButton = document.createElement('button');
        myIndexButton.id = 'my_index_button';
        myIndexButton.innerHTML = 'ゼロ埋めボタン';
        myIndexButton.onclick = function() {
            let record = kintone.app.record.get();
            config.ZeroFillItem.forEach(function(value){
                if (!record.record[value].value) {
                    record.record[value].value = 0;
                } else {
                    console.log(record.record[value].value);
                }
            });
            kintone.app.record.set(record);
        }
        kintone.app.record.getHeaderMenuSpaceElement().appendChild(myIndexButton);
        return event;
    });
})(kintone.$PLUGIN_ID);
```

# 設定画面の作成
設定画面のHTMLテンプレートを作成します。
設定画面のJavaScriptカスタマイズから、繰り返し部分をコピーして使える様にclass名等を付けます。

```config.html
<html>
    <body>
        <h1>ゼロ埋めプラグイン設定画面</h1>
        <div id="form">
            <div class="zero-fill-plugin_div">
                <input type="checkbox" name="code" value="code" class="checkbox">
                <label class="label" for="text">label</label>
            </div>
        </div>
        <div class="button">
            <button type="button" id="button_submit">保存</button>
            <button type="button" id="button_cancel">キャンセル</button>
        </div>
    </body>
</html>
```

# 設定画面生成用のJavaScriptカスタマイズ
設定画面用のJavaScriptです。
大きく次の３つの処理をしています。

 1. 前回の設定値を取得
 2. kintoneアプリのフォームフィールド情報を取得して設定画面を生成
 3. 保存ボタンの押下で設定値を保存

```config.js
jQuery.noConflict();
(function ($, pluginId) {
    "use strict";
    $(document).ready(function() {

        function escapeHtml(htmlstr) {
            return htmlstr
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/'/g, '&quot;')
                .replace(/'/g, '&#39;');
        }

        // 設定を取得する
        // confValue = ["code", "code", ... ]
        // type: SUBTABLE
        //   confValue = ["subtable-code": ["code", "code", ... ]]
        let conf = kintone.plugin.app.getConfig(pluginId);
        let confValue = [];
        if (Object.keys(conf).length !== 0) {
            confValue = [
                JSON.parse(conf.ZeroFillItem),
            ];
        }
        console.log("ZeroFillItem: " + confValue);
        console.log(confValue[0]);

        let param = {'app': kintone.app.getId()};
        let url = kintone.api.url('/k/v1/preview/app/form/fields', true);
        // アプリのフォームフィールド情報を取得
        kintone.api(url, 'GET', param, function(resp) {
            console.log(resp);
            console.log(resp.properties);
            let cloneElemCnt = 0;
            // １つ目のdiv要素のクローンを変数にセット
            let $cloneElem = $('.zero-fill-plugin_div').clone();
            console.log($cloneElem);
            // フィールド要素ごとループ
            for (let key in resp.properties) {
                console.log(key);
                // keyが存在しない場合は次へ
                if (!resp.properties.hasOwnProperty(key)) { continue; }
                // フィールド要素１つ取り出し
                let prop = resp.properties[key];
                // フィールドタイプごとに処理を分ける
                switch (prop.type) {
                    // サブテーブル対応
                    case 'SUBTABLE':
                    console.log(prop.code);
                    console.log(prop.fields);
                    for (let key in prop.fields) {
                        console.log(key);
                        // フィールド要素１つ取り出し
                        let field = prop.fields[key];
                        console.log(field);
                        switch (field.type) {
                            case 'NUMBER':
                            console.log(field.code);
                            if (cloneElemCnt !== 0) {
                                // ２つ目以降は、要素をクローンして親要素に追加する
                                $cloneElem.clone().appendTo('#form');
                            }
                            $('.label').eq(cloneElemCnt).text(escapeHtml(field.code));
                            $('.checkbox').eq(cloneElemCnt).attr('name', escapeHtml(field.code));
                            $('.checkbox').eq(cloneElemCnt).attr('id', 'check_' + cloneElemCnt);
                            $('.checkbox').eq(cloneElemCnt).attr('value', escapeHtml(field.code));
                            $('.checkbox').eq(cloneElemCnt).toggleClass(escapeHtml("subtable_" + prop.code));
                            // 既存の設定値を反映
                            let findFieldCode = confValue[0].find(function(value){
                                console.log(value[prop.code]);
                                if ( $.inArray(field.code, value[prop.code]) !== -1 ) {
                                    $('.checkbox').eq(cloneElemCnt).attr('checked', 'checked');
                                    return value;
                                }
                            });
                            console.log("find field.code: " + findFieldCode);
                            cloneElemCnt++;
                            break;
                        }
                    }

                    case 'STATUS':
                    case 'STATUS_ASSIGNEE':
                    case 'RECORD_NUMBER':
                    case 'CALC':
                        break;
                    
                    case 'NUMBER':
                        if (cloneElemCnt !== 0) {
                            // ２つ目以降は、要素をクローンして親要素に追加する
                            $cloneElem.clone().appendTo('#form');
                        }
                        $('.label').eq(cloneElemCnt).text(escapeHtml(prop.code));
                        $('.checkbox').eq(cloneElemCnt).attr('name', escapeHtml(prop.code));
                        $('.checkbox').eq(cloneElemCnt).attr('id', 'check_' + cloneElemCnt);
                        $('.checkbox').eq(cloneElemCnt).attr('value', escapeHtml(prop.code));
                        // 既存の設定値を反映
                        if ($.inArray(prop.code, confValue[0]) !== -1) {
                            $('.checkbox').eq(cloneElemCnt).attr('checked', 'checked');
                        }
                        cloneElemCnt++;
                        break;

                    default :
                        break;
                }
            }
        });

        function createConfig() {
            let arrayZeroFillItem = [];
            // チェックした要素をループ
            $('.checkbox:checked').each(function(){
                // checked value
                console.log("checked: " + $(this).val());
                // subtableの時
                if($(this).attr("class").match(/subtable_/)) {
                    let matchStr = $(this).attr("class").match(/(subtable)(_)(.*)/g)[0].split(/_/g);
                    // subtable code
                    console.log("subtable code: " + matchStr[1]);
                    let subtableCode = matchStr[1];
                    // keyが存在した時
                    console.log(arrayZeroFillItem);
                    let findIndex = arrayZeroFillItem.findIndex(function(elm){
                    console.log("element: " + elm);
                        if(elm[subtableCode]){
                            console.log(elm[subtableCode]);
                            return elm;
                        } else {
                            console.log("not element");
                        }
                    });
                    console.log("find index: " + findIndex);
                    if(findIndex !== -1){
                        arrayZeroFillItem[findIndex][subtableCode].push($(this).val());
                    } else {
                        // keyが存在しない時
                        let key = matchStr[1];
                        let obj = new Object();
                        console.log("new key: " + $(this).val());
                        obj[key] = new Array($(this).val());
                        arrayZeroFillItem.push(obj);
                    }
                } else { // subtableでは無い時
                    console.log($(this).val());
                    console.log("not subtable");
                    arrayZeroFillItem.push($(this).val());
                }
            });
            let config = {
                'ZeroFillItem': JSON.stringify(arrayZeroFillItem),
            };
            return config;
        }

        // 保存
        $('#button_submit').on('click', function() {
            let config = createConfig();
            console.log(config);
            // 設定を保存する
            kintone.plugin.app.setConfig(config);
            window.alert("保存しました");
        });
        // キャンセル
        $('#button_cancel').on('click', function() {
            window.history.back();
        });
    });

})(jQuery, kintone.$PLUGIN_ID);
```
# 設定画面イメージ

<img width="714" alt="スクリーンショット 2019-02-21 5.53.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/214523/1cd17b4b-aabf-290a-85aa-a244f3c40e67.png">


[後編へ続く。。。](https://qiita.com/sy250f/items/060b51edef4ad931da2f)