FileMakerでタグ付け機能を実装する

コンテンツ系のサイトなどで，タグ付け機能をよく見ますよね。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/301f70dc-56a1-9dc9-a60d-e7137c25da10.png">

↑こんなの。

* 思いついたキーワードをどんどん登録していける
* 入力補完（サジェスト）機能が使えるのである程度データの正規化が可能
* キーワードから選んで検索が可能

といった利点を持った便利なUIだと思います。このタグ付け機能，FileMakerにも欲しいぞ！ということでWebビューアを利用して実装しました。せっかくなので，「FileMakerは結構使いこなしてきているけど，WebビューアとかJavaScriptとかはまだ良くわからん」といった方を対象にStep by Stepで解説してみます。

# どうやってやろう？
まぁだいたいこういうのはJavaScriptのライブラリがありそうだよね，と考えて「JavaScript library tagging」で検索して一番上に出てきたのがこちら。

[Taggle.js](https://sean.is/poppin/tags)

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/0a844933-c491-ef8d-22d4-c43478c60734.png">

紙パックのジュースを2本同時飲みする愉快なエンジニアが作成してくれたようである。ソースも軽そうだし依存関係もほぼ無し，というわけでFileMakerへの組み込みにはちょうど良さそう。というわけでこちらを利用させて頂きましょう。ちなみにライセンスはMIT。

# Taggle.jsの基本的な使い方
詳しくは上記の公式サイト（英語）を参照すれば調べられますが，基本的なコーディングはとてもシンプル。

```html:HTML
<div id="example"></div>
```

```javascript:JavaScript
new Taggle('example', {
    tags: ['tag1', 'tag2', 'tag3']
});
```
これで「tag1」「tag2」「tag3」がデフォルトとして表示されたタグ入力フィールドが出来上がります。

コンストラクタ関数の第1引数はタグ付けフィールドにすべき`<div>`タグのid，第2引数に各種設定を記載したオブジェクトです。設定オブジェクトには色々な項目が設定できますが，最低限設定しておくべきは

<dl>
<dt>tags</dt>
<dd>デフォルトで表示するタグ（配列形式）</dd>
<dt>preserveCase</dt>
<dd>入力された英字の大文字/小文字を保持するか。<b>なぜかデフォルトだと false になっている</b>のだが，日本人的には大文字で入力された英字が勝手に小文字にされるのは不自然なので true にしておく方がお勧め。</dd>
<dt>placeholder</dt>
<dd>空欄の時に表示されるメッセージ。デフォルトは英語（Enter tags...）なので適宜日本語へ。</dd>
<dt>onTagAdd, onTagRemove
<dd>タグの追加や削除に応じて発火するコールバック関数。これを用いてFileMaker側とやり取りを行う（後述）。</dd>
</dl>

といったところ。重複を許可したり，ホワイトリスト／ブラックリストを設定したり，タグ数の上限を決めたり……といったことも可能なので必要であれば公式ドキュメントを参照して下さい。

# FileMakerでJavaScriptライブラリを使用する時の小ネタ
Taggle.jsに限らず，FileMakerのWebビューアでJavaScriptライブラリを利用する時に問題になるのが「.jsファイルや.cssファイルなど，必要なライブラリファイルをどこから読み込むのか」ということ。

もちろん普通にCDNとかから引っ張って来たって良いんだけど……
```html
<script src='https://cdnjs.cloudflare.com/ajax/libs/taggle/1.15.0/taggle.min.js'></script>
```

ただこれだとWebビューアの表示が行われる度にアクセスが行われることになるし，そもそもFileMakerの場合インターネットに接続されていない環境で動く場合も多いので，できればライブラリファイルはローカル（データベース内）に保存しておきたいわけです。

今まで色々な先達のやり方を見てきた結果，

* Webビューアの「Webアドレス」に（HTMLを含めて）ベタ打ちで実装
* ライブラリの中身を吐き出すカスタム関数を作成しておく
* ライブラリの中身を入力したオブジェクトを作成し，`getLayoutObjectAttribute`関数を用いて中身を取り出す

といったやり方が観察されたのだけど，どれも可読性・メンテナンス性に乏しいので最近私が落ち着いたのが「<b>ライブラリファイル専用のテーブルを1つ用意し，ファイルごとにグローバルフィールドを作成して格納する</b>」という方法。これならば可読性も高いし，ライブラリがアップデートされた時でもコピペするだけなので更新が容易でおすすめ。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/422ffb51-590a-2bae-da78-7e859936e111.png">

個人的にはテーブル名は`sources`にしているけど，これは何でもOK。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/86189186-f8ed-c898-2939-d5b65480635c.png">

登録しておきたいファイルごとにフィールドを作成します。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/f6b3ee02-8c19-c4fd-2121-dc341807e025.png">

オプションで「グローバル格納を使用する」にチェックしておくのを忘れずに。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/7cd39efd-8a53-9a47-5752-2b7fedf701a8.png">

データベースの設定が終了したら，レイアウトも作成しておきましょう。タブコントロールで切り替えられるようにしておくと，ライブラリファイルが増えても管理しやすいです。フィールドはちょっと「それっぽく」なるようにフォントを等幅にするなり，背景を黒にするなり，まぁこの辺はお好みで。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/524ef6c2-8f93-bb45-fdd8-a6aea1766fc3.png">

フィールドはスクロールバーの表示と，「視覚的なスペルチェックを適用しない」にチェックしておくとよいです。

レイアウトが終了したらブラウズモードに戻ってフィールドにファイル内容をコピペします。Taggle.jsであればダウンロードしたファイルの「src」フォルダにある「taggle.js」と「minimal.css」をそれぞれコピペ。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/5f318553-f3a5-0b2c-76b1-309c19436f7a.png">

この状態でライブラリを使いたい時はこのグローバルフィールドを参照するのでも良いんですが，グローバル変数に読み込んでおいた方がメモリに保持されるので動作が速くなるし，何かと使いやすいです。基本的にライブラリはデータベースの使用中に内容が変わるわけではないので，データベースの起動時にグローバル変数にロードするスクリプトを組んでおきましょう。

```:loadSources
# ファイルを開いた際にsourceｓテーブルの各ファイルをグローバル変数にロードする
エラー処理 [オン]
ユーザによる強制終了を許可 [オフ]

変数を設定 [$$taggle.js; 値:　sources::taggle.js]
変数を設定 [$$taggle.css; 値:　sources::taggle.css]
```

このスクリプトを「ファイルオプション...」の「スクリプトトリガ」から`onFirstWindowOpen`に設定しておけば，あとはライブラリファイルを使用したいところでグローバル変数を呼び出せば良いことになります。

# データ保持用のフィールドを作成する

さて，今回作成するタグ付け機能だけど，当然ながらあくまでWebビューアはUIとして利用するだけなので入力されたタグのデータを保持するフィールドを作成しておく必要があります。JavaScriptとのやり取りをすることを考えるとJSON形式でデータ保持しておいた方が何かと便利なので，Taggle.jsの形式にあわせて

```
{"tags": ["foo", "bar"]}
```
というJSON形式のテキストを保持するフィールドを作成します。

今回はファイル管理用のデータベースの想定で，メインのテーブルは「files」テーブルです。ここに`tags_json`という名前のテキストフィールドを作成し，フィールドオプションで「入力値の自動化」を「データ」にして`{"tags":[]}`という初期値を設定しておきましょう。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/5b6f300d-3e3a-220f-3153-0de7ede44649.png">

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/324deda8-2b22-8473-595a-020edc6d5a64.png">

# Webビューア側からのデータを受け取るスクリプトを作成する
タグのデータを保管するフィールドは作成しましたので，次にWebビューア内で入力されたタグのデータをこのフィールドへ保存するためのスクリプトを組んでいきましょう。

前述のようにTaggle.jsの設定オブジェクトには，タグが追加されたり削除されたりといったイベントに応じてフックされるコールバック関数を設定することが出来ます。
```javascript
{
  onTagAdd: function(event, tag) {
    // タグが追加された時の処理
  },
  onTagRemove: function(event, tag) {
    // タグが削除された時の処理
  }
}
```
コールバック関数の引数である`tag`には追加／削除されたタグの文字列が渡されます。

Webビューア内のJavaScriptからは`FileMaker.PerformScriptWithOption`関数を呼び出すことでFileMaker側のスクリプトにデータを渡して処理を引き継ぐことができますので，コールバック関数からこれを呼び出してタグのデータを渡してあげれば良さそうです。

`FileMaker.PerformScriptWithOption`関数の詳しい使い方はこちら↓

https://help.claris.com/ja/pro-help/content/scripting-javascript-in-web-viewers.html

さて，先ほど見たようにTaggle.jsのイベントフックされるコールバック関数は，引数として追加／削除されたタグの文字列が利用できます。もちろんこのデータをFileMaker側に渡し，スクリプトで`tags_json`フィールドの中身を書き換えるのでも良いのですが，追加と削除で2つスクリプトを用意しなきゃならないし，ちょっと面倒くさい。

Taggle.jsには`getTagValues()`という，設定されている全タグの文字列を配列形式で返してくれる便利なメソッドが用意されていますので，これを利用すれば追加でも削除でも「最新のデータ」をFileMaker側に渡すことができます。そうすれば，FileMaker側では受け取ったデータをその都度保存用の`tags_json`フィールドに設定するだけで処理が済むのでシンプルです。

というわけで，JavaScript側からタグ文字列の配列データが渡される想定でFileMaker側のスクリプトを組んでおきましょう。

```:updateTags
# Webビューアのタグが追加／削除された際，フックされたコールバック関数から渡されるタグのデータ（文字列の配列）をtags_jsonフィールドにJSON形式で保存する
エラー処理 [オン]
ユーザによる強制終了を許可 [オフ]

フィールド設定 [files::tags_json; JSONSetElement ( "{}" ; "tags" ; Get ( スクリプト引数 ) JSONArray ) ]
```

実質1行の単純なスクリプトです。`Get ( スクリプト引数 )`でJavaScript側から渡された文字列の配列データを取り出せるので，これを`JSONSetElement`関数でJSON形式にしてから`tags_json`フィールドに設定しているだけです。

# Webビューアに設定するHTMLを作成する
いよいよWebビューア用のHTML & JavaScriptをコーディングしていきます。

Webビューア用のHTMLでは，&lt;script&gt;タグ内のJavaScriptでTaggle.jsのコンストラクタ関数を呼び出す際，先ほど作成した`tags_json`フィールドの内容を初期値として設定しなければなりません。また，先ほどグローバル変数から利用できるようにしておいたライブラリファイルも入れ込む必要があります。そのため，<b>予めテンプレートのHTMLを用意しておき，ライブラリファイルやタグの初期値を置き換えてからWebビューアに表示する</b>という手法を取ることにします。

テンプレートのHTMLの完成形が以下の通りです。
```html:tagViewer.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <style type="text/css">
      body {
        margin: 0;
        padding: 8px;
        overflow: auto;
        font-family: sans-serif;
        font-size: 16px;
        line-height: 1.5;
      }
    </style>
    <style type="text/css">
      $$taggle.css
    </style>
  </head>
  <body>
    <div id="taggle"></div>
    <script>
      $$taggle.js
    </script>
    <script type="text/javascript">
      var taggle = new Taggle('taggle', {
        tags: ~tagsArray,
        preserveCase: true,
        placeholder: 'タグを入力...',
        onTagAdd: function(event, tag) {
          FileMaker.PerformScriptWithOption ( 'updateTags', JSON.stringify( taggle.getTagValues() ), '0' );
        },
        onTagRemove: function(event, tag) {
          FileMaker.PerformScriptWithOption ( 'updateTags', JSON.stringify( taggle.getTagValues() ), '0' );
        }
      });
    </script>
  </body>
</html>
```

&lt;meta&gt;タグの中の`charset="utf-8"`を忘れるとWindows版のFileMakerでは日本語が文字化けするので忘れないようにしましょう。

1つめの&lt;style&gt;タグの中にはWebビューア全体としてのスタイル設定をベタ打ちしています。`overflow: auto;`に設定することでタグが増えてきても自動的にスクロールバーが表示されるようになります。`font-family`は設定しておかないとMacの場合はセリフ体（明朝体っぽいやつ），Windowsの場合はサンセリフ体になります。`font-size`や`line-height`はお好みで。
```html
    <style type="text/css">
      body {
        margin: 0;
        padding: 8px;
        overflow: auto;
        font-family: sans-serif;
        font-size: 16px;
        line-height: 1.5;
      }
    </style>
```

2つめの&lt;style&gt;タグの中身は後でライブラリファイルの内容に置き換えますので，ここでは先ほど設定したグローバル変数名を入れているだけです。

```html
    <style type="text/css">
      $$taggle.css
    </style>
```

同様に，1つめの&lt;script&gt;タグの中身もグローバル変数名のみとしています。

```html
    <script>
      $$taggle.js
    </script>
```

2つめの&lt;script&gt;タグの中が実際の処理になります。

```javascript
    <script type="text/javascript">
      var taggle = new Taggle('taggle', {
        tags: ~tagsArray,
        preserveCase: true,
        placeholder: 'タグを入力...',
        onTagAdd: function(event, tag) {
          FileMaker.PerformScriptWithOption ( 'updateTags', JSON.stringify( taggle.getTagValues() ), '0' );
        },
        onTagRemove: function(event, tag) {
          FileMaker.PerformScriptWithOption ( 'updateTags', JSON.stringify( taggle.getTagValues() ), '0' );
        }
      });
    </script>
```

設定オブジェクトの`tags`には本来であればタグ文字列の配列データが入りますが，後でFileMaker側のフィールドデータから置き換えるためここでは`~tagsArray`という文字列のみ記述しておきます。

`onTagAdd`・`onTagRemove`のイベントフックの記述はどちらも共通です。`FileMaker.PerformScriptWithOption`で先ほど作成したFileMaker側のスクリプトを呼び出し，`taggle.getTagValues()`で取得したタグデータを`JSON.stringify`で文字列に変換して渡しています。

このテンプレートHTMLも，先ほどのライブラリファイルと同じようにsourcesテーブルにグローバルフィールドを作成し，その中に記述しておきましょう。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/ded8b204-6b7d-9942-bc7c-ef397ad6e16c.png">

データベース起動時の読込スクリプトにも追加しておくのを忘れずに。

```diff_html:loadSources
  # ファイルを開いた際にsourceｓテーブルの各ファイルをグローバル変数にロードする
  エラー処理 [オン]
  ユーザによる強制終了を許可 [オフ]
  
  変数を設定 [$$taggle.js; 値:　sources::taggle.js]
  変数を設定 [$$taggle.css; 値:　sources::taggle.css]
+ 変数を設定 [$$taggle.css; 値:　sources::taggle.css]
```

終わったら一度スクリプトを実行してグローバル変数にロードしておきましょう。

# Webビューアを設定する

さあ，準備は整いました！早速Webビューアの設定をしていきます。

メインのテーブルのレイアウトにWebビューアを設置します。
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/ff3f86a2-613b-d37a-8092-70cb3a28ae9a.png">

「Webビューアの設定」で「進行状況バーの表示」「ステータスメッセージの表示」のチェックを外し，「JavaScriptによるFileMakerスクリプトの実行を許可」にチェックを入れておきます。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/d422f58a-73e3-46d9-1a3e-286f5c5b92b7.png">

その上で「Webアドレス」には以下の様に記述します。

```
Substitute ( $$tagViewer.html ; 
  [ "$$taggle.js" ; $$taggle.js ] ;
  [ "$$taggle.css" ; $$taggle.css ] ;
  [ "~tagsArray" ; JSONGetElement ( files::tags_json ; "tags" ) ]
)
```

`Substitute`関数はテキストの置換を行う関数です。ここではグローバル変数`$$tagViewer.html`に読み込んだテンプレートHTMLを対象として，`$$taggle.js` `$$taggle.css`の部分をそれぞれグローバル変数に読み込んだライブラリファイルに置換し，さらに`~tagsArray`で記述しておいた既存タグの部分を`tags_json`フィールドに保存してあるJSON形式のタグデータから`JSONGetElement`関数で抽出した配列に置き換えています。

置き換え前の文字列はダブルクォーテーションで括るのを忘れずに！

# 動作確認＆微調整
ではブラウズモードに切り替えて動作を確認してみます。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/2b6a1f69-d26b-01d5-789a-2c46a6d1ec3f.png">

先ほどレイアウト画面で設置したWebビューアにHTMLが読み込まれ，設定した「タグを入力...」というプレースホルダテキストが表示されています。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/65fe6f18-5350-6414-5d19-47dbd2b9c806.png">

テキストを入力してreturnキーやenterキーで確定すると……

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/b9934d52-be87-3a45-4ea5-e104781c6a45.png">

タグになりました！

もちろんタグはいくつでも追加していくことができますし……

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/d382d052-c74b-8ee2-3d19-e0e90193c1e2.png">

タグにマウスポインタを合わせると削除ボタンが表示されるので，簡単に削除もできます。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/f21367dd-aa7e-8e01-17e4-06caa3d80965.png">
<br>
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/cfd6184e-37ef-62cb-f0ac-e46dbbb4c3d1.png">

良さげです！😆

# 検索用フィールドを作成する
さてこれでひとまずタグ入力はできるようになりましたが，このままでは検索ができません。データ保存用の`tags_json`フィールドを用いて検索をしても良いのですが，JSON形式にするための余計な文字列が入っているので，FileMakerのリスト形式（改行区切りの文字列）でタグデータを保持するフィールドを作成しておきましょう。

`tags_list`という名前で計算フィールドを作成し，内容を以下のように記述しておきます。

```
JSONListValues ( tags_json ; "tags" )
```

計算結果を「テキスト」にしておくのを忘れずに。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/f37ab1cb-69f1-3073-8f3b-6d896f44eb9b.png">

検索モードでWebビューアの代わりに表示するようにしたいので，レイアウト画面でWebビューアに重ねて`tags_list`フィールドを配置します。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/97a63151-6af6-fbd7-6f72-e8d87028da42.png">

検索モード以外で表示する必要はないので，インスペクタの「次の場合にオブジェクトを隠す」を`Get ( ウインドウモード ) ≠ 1`に設定し，「検索モードで適用」をチェックしておきます。同様にWebビューアは`Get ( ウインドウモード ) = 1`で隠すように設定しておきましょう。

これで検索モードにしてみると……

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/95d34a54-3fe2-91d2-7f89-81389bcfbdf8.png">

Webビューアが隠れ，代わりに`tags_list`フィールドが表示されるようになりました。実際に検索が可能になっていることを確かめておきましょう。

# サジェスチョン（オートコンプリート）っぽい機能を追加する
最初にも述べたように，タグ入力UIの大きな利点の一つが「既に他のレコードに入力されているタグを候補として出すことで，ある程度のデータ正規化が可能」という点があります。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/301f70dc-56a1-9dc9-a60d-e7137c25da10.png">

「Javas」しか打ってなくても「JavaScript」やら「JavaSilver」やらが出てきてるこれですね。（「JavaScirpt」やら「JavaScriptpt」やらもあって「ちゃんと正規化出来てないやんけ！」というのはさておき……）

Taggle.jsをはじめとしたタグ付け用のライブラリには，大抵jQueryなどと連携してサジェスチョン機能を実現するためのAPIが実装されています。しかしこれをFileMakerのWebビューアで利用しようとするとうまくいきません。

というのも，当然のことながらWebビューアはレイアウト画面で配置したサイズ内でしかコンテンツの描画が出来ないのですが，JavaScriptを用いてサジェスチョンの候補を表示する場合，普通は入力しているテキストの下に新たな`<div>`要素を描画する形になるので，Webビューアの描画可能領域をはみ出てしまうのです。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/1a823903-9af8-1720-3dd6-824ef768c703.png">

したがってFileMakerでサジェスチョン機能を実現しようとすると，Webビューアの中ではなく外に機能を実装するのが現実的な解になります。

具体的にはサジェスチョン候補用の値一覧を作成し，タグ追加用のフィールドを新たに設定した上でスクリプトからタグの追加を行います。

サジェスチョン候補用の値一覧の作成は簡単です。検索用にタグデータをリスト形式で保持する`tags_list`フィールドを既に作成しているので，このフィールドから値一覧を作成すればOK。今回は`existingTags`という名称にしました。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/761a6ac8-07a1-3487-bf4b-3f4c13d64166.png">

値一覧を作成し，「フィールドの値を使用」に設定します。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/88466d34-a00a-874d-580f-cf6eb938f653.png">

「フィールド設定」ダイアログで検索用に作成した`tags_list`フィールドを指定すればOKです。

次にこの値一覧からタグを選択したり，入力をオートコンプリートさせるためのフィールドを設定します。テキストフィールド（今回は`tagSuggestion`という名称にしました）を作成し，Webビューアの下に配置しておきましょう。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/63908f74-210f-aad6-189b-0c0b3d27dd88.png">

コントロールスタイルは「ドロップダウンリスト」に設定し，値一覧を先ほど作成した`existingTags`に設定して「一覧の表示切り替え用矢印を表示」「値一覧を使用してオートコンプリート」にチェックします。検索モードで表示する必要はないので，Webビューアと同じく検索モードで隠すように設定しておきましょう。また，タグに複数行のテキストを入力することはないので「次のオブジェクトへの移動に使用するキー」の「Return」「Enter」にもチェックを入れておきます。

スクリプトも単純です。`tagSuggestion`フィールドに設定されているタグを`tags_json`フィールドのタグ一覧に追加してあげればOK。ただし`tagSuggestion`フィールドが空白でないかのチェックと，<b>値一覧には既に登録されているタグも表示されてしまう</b>ので重複チェックもしておきましょう。

こんな感じ↓

```:addTag
# サジェスチョン用フィールドからタグを追加する
エラー処理 [オン]
ユーザによる強制終了を許可 [オフ]

# 空白＆重複チェック
If [ files::tagSuggestion = "" ]
    現在のスクリプト終了[ テキスト結果: ]
Else If [ PatternCount ( files::tags_list & "¶" ; files::tagSuggestion & "¶" ) > 0 ]
    カスタムダイアログを表示 ["そのタグは既に登録されています。"]
Else
    # 重複していなければ tags_json フィールドの内容を更新
    フィールド設定 [ files::tags_json ; Let ( ~n = ValueCount ( files::tags_list ) ; JSO...]
End If

# サジェスチョン用フィールドを消去して終了
フィールド設定 [ files::tagSuggestion ; "" ]
```

重複チェックでは`PatternCount`関数を用いて検索用の`tags_list`フィールドのテキストに対して`tagSuggestion`フィールドの内容で検索を行っています。双方に`& "¶"`を追加しているのは，タグ文字列の一部分にヒットしてしまわないようにするためです（これをしておかないと例えば「アイスクリーム」が登録されている場合「アイス」が重複と判断されてしまいます）。

重複していなかった際の`tags_json`フィールドの内容は以下の様に`Let`関数を用いた計算式で設定しています。

```
Let ( 
  ~n = ValueCount ( files::tags_list )
;
  JSONSetElement ( files::tags_json ; "tags[" & ~n & "]" ; files::tagSuggestion ; JSONString )
)
```

新規に追加するタグは既存のタグリストの最後に追加しますので，まず既に設定されているタグ数を取得します。これは検索用の`tags_list`フィールドに対して`ValueCount`関数を用いればOK。

その後，`JSONSetElement`関数で`tags_json`フィールドに入力されているタグデータの最後に`tagSuggestion`フィールドの内容を設定します。<b>JSONの配列索引は0からスタートする</b>ので，先ほど取得したタグ数をそのまま用いて`"tags[~n]"`という配列索引を指定すればリストの最後に追加できます。

最後にスクリプトを実行するためのボタンをレイアウトに追加し，「ボタン設定」を「スクリプト実行」にして先ほど作成したスクリプトを指定しましょう。検索モードで隠す設定にしておくのを忘れずに。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/07481d57-53ec-e8dc-71f3-a4a3416d4df0.png">

完成したらブラウズモードに切り替え，新しいレコードから既存のレコードのタグが選択できること，ボタンを押すとWebビューア側でタグが追加されることを確認しましょう。フィールドに直接入力すればオートコンプリートも効くはずです。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/4b379032-a8f2-b092-fbb0-f312a171110a.png">

# ちょっとしたUX向上の工夫
もちろんこのままでも十分使えますが，少し工夫することでユーザエクスペリエンスを向上させましょう。

まず今のままだと，`tagSuggestion`フィールドが確定された際，「追加」ボタンではなく「タイトル」フィールドへフォーカスが移動してしまいます。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/b5dc87ad-e722-f528-f717-19bd65ad3861.png">

そこでレイアウト画面の「レイアウト」→「タブ順設定」から，`tagSuggestion`フィールドを確定したら「追加」ボタンにフォーカスが移動するように設定します。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/a8fa10ea-c71f-2e29-5ca6-7eba85db2118.png">

「設定」ボタンのタブ順が`tagSaggestion`フィールドのタブ順の次になるように設定します。

せっかくですから，フォーカスしていることがユーザにわかりやすいよう，フォーカス時のボタンのアピアランスを変更しておきましょう。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/8133df0f-fc98-641d-2cbd-c81c0d5c3093.png">

インスペクタの「外観」タブから，フォーカス時のボタンの塗りつぶしを目立つ色に変更します。これで`tagSuggestion`フィールドを確定すると「追加」ボタンの外観が変化し，ユーザへボタンクリックを促すことが可能となりました。

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1734470/d482e61e-b2c9-005b-a228-f01932c6ca3f.png">

また，複数のタグを連続して入力するような場合，タグを追加した後は自動的に`tagSuggestion`フィールドへフォーカスが戻ってくれると便利ですよね。そこで`addTag`スクリプトの最後に次の一文を追加しておきましょう。

```:addTag（追加分）
フィールドへ移動 [files::tagSuggestion]
```
これでボタンからタグを追加すると，`tagSuggestion`フィールドにフォーカスが戻ってくるようになりました。キーボードからの入力でも，「return/enter」キーで確定→追加→次のタグを入力……とマウスに触れることなく作業を進めていくことが出来るので捗ります。

なお`tagSuggestion`フィールドを確定した状態でタグを追加<b>せずに</b>他の操作に移る場合，`tagSuggestion`フィールドはリセットしておいた方が親切かもしれません。その場合，レイアウトの`onRecordCommit`スクリプトトリガに以下のスクリプトを設定しておきましょう。

```:onRecordCommit
フィールド設定 [files::tagSuggestion ; "" ]
```

おしまい。

# サンプルファイル

下のリンクから実際のファイルをダウンロードできますのでご自由にどうぞ。

https://www.dropbox.com/s/6yz2i47uu4qgw0s/%E3%82%BF%E3%82%B0%E4%BB%98%E3%81%91%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB.fmp12?dl=0