kintoneプラグイン雛形を「宣言的UI・多言語対応・jQuery無し」にする

# 現状

kintoneプラグイン雛形はサイボウズ社より下記で案内されています。
[create-pluginを使ってプラグインの雛形を作成しよう！](https://developer.cybozu.io/hc/ja/articles/360000877946-create-plugin%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%AE%E9%9B%9B%E5%BD%A2%E3%82%92%E4%BD%9C%E6%88%90%E3%81%97%E3%82%88%E3%81%86-)

create-kintone-pluginでできるファイルは下記です。
```config.html
<section class="settings">
  <h2 class="settings-heading">Settings for sample</h2>
  <p class="kintoneplugin-desc">This message is displayed on the app page after the app has been updated.</p>
  <form class="js-submit-settings">
    <p class="kintoneplugin-row">
      <label for="message">
        Message:
        <input type="text" class="js-text-message kintoneplugin-input-text">
      </label>
    </p>
    <p class="kintoneplugin-row">
        <button type="button" class="js-cancel-button kintoneplugin-button-dialog-cancel">Cancel</button>
        <button class="kintoneplugin-button-dialog-ok">Save</button>
    </p>
  </form>
</section>
```
```config.js
jQuery.noConflict();

(function($, PLUGIN_ID) {
  'use strict';

  var $form = $('.js-submit-settings');
  var $cancelButton = $('.js-cancel-button');
  var $message = $('.js-text-message');
  if (!($form.length > 0 && $cancelButton.length > 0 && $message.length > 0)) {
    throw new Error('Required elements do not exist.');
  }
  var config = kintone.plugin.app.getConfig(PLUGIN_ID);

  if (config.message) {
    $message.val(config.message);
  }
  $form.on('submit', function(e) {
    e.preventDefault();
    kintone.plugin.app.setConfig({message: $message.val()}, function() {
      alert('The plug-in settings have been saved. Please update the app!');
      window.location.href = '../../flow?app=' + kintone.app.getId();
    });
  });
  $cancelButton.on('click', function() {
    window.location.href = '../../' + kintone.app.getId() + '/plugin/';
  });
})(jQuery, kintone.$PLUGIN_ID);
```
```desktop.js
jQuery.noConflict();

(function($, PLUGIN_ID) {
  'use strict';

  kintone.events.on('app.record.index.show', function() {
    var config = kintone.plugin.app.getConfig(PLUGIN_ID);

    var spaceElement = kintone.app.getHeaderSpaceElement();
    if (spaceElement === null) {
      throw new Error('The header element is unavailable on this page');
    }
    var fragment = document.createDocumentFragment();
    var headingEl = document.createElement('h3');
    var messageEl = document.createElement('p');

    messageEl.classList.add('plugin-space-message');
    messageEl.textContent = config.message;
    headingEl.classList.add('plugin-space-heading');
    headingEl.textContent = 'Hello kintone plugin!';

    fragment.appendChild(headingEl);
    fragment.appendChild(messageEl);
    spaceElement.appendChild(fragment);
  });
})(jQuery, kintone.$PLUGIN_ID);
```
今回はこれを、宣言的UI・多言語対応・jQuery無し、で書き直してみます。
# 雛形案
```config.html
<section id="sample-plugin-settings">
</section>
```

Google翻訳と、
[innerHTML のシンタックスハイライト](https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html)
を利用しています。
（Qiitaだとハイライトされないので見づらいですが）

```config.js
((PLUGIN_ID) => {
	'use strict';

	//設定値。初期値を定義し、保存値で上書き
	const config = {
		message: '',
		...kintone.plugin.app.getConfig(PLUGIN_ID)
	};

	// ja: 日本語、en: 英語、zh: 中国語
	const lang = kintone.getLoginUser().language;

	//設定値と3言語分のラベルを埋め込んだ html
	document.getElementById('sample-plugin-settings').innerHTML = /* html */`
		<h2 class="settings-heading">
			${{
				ja: 'サンプルの設定',
				en: 'Settings for sample',
				zh: '样品设置'
			}[lang]}
		</h2>
		<p class="kintoneplugin-desc">
			${{
				ja: 'このメッセージは、アプリの更新後にアプリページに表示されます',
				en: 'This message is displayed on the app page after the app has been updated.',
				zh: '应用程序更新后，此消息会显示在应用程序页面上'
			}[lang]}
		</p>
		<form name="samplePluginSettings">
			<p class="kintoneplugin-row">
				<label for="message">
					${{
						ja: 'メッセージ',
						en: 'Message',
						zh: '信息'
					}[lang]}:
					<input
						type="text"
						name="message"
						class="kintoneplugin-input-text"
						value="${config.message}"
					/>
				</label>
			</p>
			<p class="kintoneplugin-row">
				<button
					type="button"
					name="cancel"
					class="kintoneplugin-button-dialog-cancel"
				>
					${{
						ja: 'キャンセル',
						en: 'Cancel',
						zh: '取消'
					}[lang]}
				</button>
				<button
					type="submit"
					class="kintoneplugin-button-dialog-ok"
				>
					${{
						ja: '保存',
						en: 'Save',
						zh: '节省'
					}[lang]}
				</button>
			</p>
		</form>
	`;

	const form = document.forms.samplePluginSettings;

	//設定保存
	form.onsubmit = (event) => {
		event.preventDefault();
		kintone.plugin.app.setConfig(
			{
				message: form.elements.message.value
			},
			() => {
				alert({
					ja: 'プラグインの設定が保存されました。アプリを更新してください',
					en: 'The plug-in settings have been saved. Please update the app!',
					zh: '插件设置已保存。请更新应用程序'
				}[lang]);
				window.location.href = `../../flow?app=${kintone.app.getId()}`;
			}
		);
	};

	//キャンセル
	form.elements.cancel.onclick = () => {
		window.location.href = `../../${kintone.app.getId()}/plugin/`;
	};
})(kintone.$PLUGIN_ID);
```
```desktop.js
(function(PLUGIN_ID) {
  'use strict';

  kintone.events.on('app.record.index.show', () => {
    const config = kintone.plugin.app.getConfig(PLUGIN_ID);
    const lang = kintone.getLoginUser().language;

    const spaceElement = kintone.app.getHeaderSpaceElement();
    if (!spaceElement) {
      throw new Error({
        ja: 'ヘッダー要素はこのページでは利用できません',
        en: 'The header element is unavailable on this page',
        zh: '标题元素在此页面上不可用'
      }[lang]);
    }
    spaceElement.innerHTML = /* html */`
      <h3 class="plugin-space-heading">
        ${{
          ja: 'こんにちは kintone プラグイン！',
          en: 'Hello kintone plugin!',
          zh: '你好kintone插件!'
        }[lang]}
      </h3>
      <p class="plugin-space-message">
        ${config.message}
      </p>
    `;
  });
})(kintone.$PLUGIN_ID);
```
# 感想
・jQuery無くなって少しうれしい
・言語ごとのラベルセットを用意して埋め込む作業が無くなるのうれしい
　どの位置にどのラベルが来るかを探さなくて良いのでうれしい
・命令的になりがちな kintone の js が若干分かりやすくなってうれしい
・イベントリスナの登録は命令的になった。
　html でイベントリスナを登録できるとより宣言的だが、
　グローバルスコープに変数を用意する必要が出てくるので躊躇...
```listener.js
            window.listeners = {
                handleCancel: () => /* ... */
            };

            elem.innerHTML = /* html */`
                <!-- ... -->
				<button
					type="button"
					name="cancel"
					class="kintoneplugin-button-dialog-cancel"
                    onclick="window.listeners.handleCancel()"
				>
					${{
						ja: 'キャンセル',
						en: 'Cancel',
						zh: '取消'
					}[lang]}
				</button>
                <!-- ... -->
            `;
```

# React＋Typescript
Github 側を見たところ、Experimental となってますが React＋Typescript 使えるそうです。
じゃあ、ねぇ、、

https://github.com/kintone/js-sdk/tree/master/packages/create-plugin