アプリ内検索に入力された検索ワードを伝えたい

# はじめに

- [kintone Advent Calendar 2022](https://qiita.com/advent-calendar/2022/kintone) 3日目の記事です
- 2年前に書いた[アプリ内検索に入力された検索ワードを調べたい](https://qiita.com/kentaro1sh11/items/eb8d03dabb82e4049c1e)という記事の2年越し続編です

# 自己紹介
[![刷毛.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105085/b733b425-cd4d-1d41-e755-786a293c2e54.jpeg)](https://www.maru-t.co.jp/)

- ｲｼｲｹﾝﾀﾛｳですハケをつくっている会社の兼業情シスです
- kintone触る係をしてます認定資格5つ持ち&公認Evangelistです

![資格ロゴ統合.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105085/01ce2ca4-d9f1-34a5-af76-fa3789461379.png)

- その他にITストラテジストやシステム監査技術者などの国家資格をいくつか取得しています
- kintoneのカスタマイズは社内向けのみで、プラグインは作ってません（作れません）

# やりたいこと

- アプリ内検索でどのような単語で検索されているのかを**いまから検索する人に伝えたい**
- 「へぇーみんなは最近こんな単語を検索してるのかーなるほどねー」と思ってくれるハズ

# 考え方

- 右上のkintone標準検索ボックスと同じ機能の検索ボックスをメニュー位置に作成する
- 作成した検索ボックスクリック時に[アプリ内検索に入力された検索ワードを調べたい](https://qiita.com/kentaro1sh11/items/eb8d03dabb82e4049c1e)で貯めた検索ワードの直近何件かを表示する
- アクセスログから取得するので検索成功した検索ワードしか出てこない（良いか悪いかわからないけど

# 完成！

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105085/fb9ddcdf-8c84-4139-9ef1-ff851a3e8c35.png)

- 見た目をkintoneライクにするため [ui component v1](https://developer.cybozu.io/hc/ja/articles/900005544623-kintone-UI-Component-v1) を使いたかったんですが、list属性を付与する方法がわかりませんでした（[v0](https://kintone-labs.github.io/kintone-ui-component/latest/) だとできるんだけど現在はメンテナンスモード）
- それならばと[51-modern-default.css](https://developer.cybozu.io/hc/ja/articles/203302214-kintone%E3%83%A9%E3%82%A4%E3%82%AF%E3%81%AA%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E3%82%B7%E3%83%BC%E3%83%88%E3%81%AE%E5%88%A9%E7%94%A8)を使おうとしたけど今度はdisplayにinline-blockを適用する方法がわからなかった（floatをleftにすることもできなかった）
- ということで旧来システムみが溢れるテキストボックスとボタンですご容赦ください🙇‍♂️



# 検索対象アプリ

- 2年前の使いまわしです
- 質問を投稿し、コメントで回答するアプリです
- アプリ内検索は添付ファイルの中身（の一部）やコメントも検索してくれます

|フィールド名 / フィールドコード|フィールドタイプ|
|:----------------------------|:--------------|
|質問内容                     |文字列（複数行）|
|添付ファイル                 |添付ファイル    |


# アクセスログアプリ

- こちらも使いまわしです
- 1アクセスごとに1レコードを新規登録します

|フィールド名 / フィールドコード|フィールドタイプ|
|:----------------------------|:--------------|
|keyword                      |文字列（1行）   |
|accessUrl                    |文字列（1行）   |



# 検索対象アプリに実装
アクセスログアプリのアプリIDだけ書き換えてください

``` javascript

(() => {
  'use strict';
/**
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
●              定数および無名即時関数スコープ変数の宣言
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
*/
// アクセスログアプリのアプリID
const accessLogAppId = XXX;

/**
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
●                        function                          
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
*/	
/**
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
▼             アクセスログアプリへ登録する関数                          
▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲
*/
const postLog = async () => {
  
  // keywordの抽出（「&s.keyword=」の次の文字から「&s.app」の手前の文字まで）
  let keyword = '';
  if (location.href.match(/s.keyword/)){
      const indexStart = location.href.indexOf('&s.keyword=') + '&s.keyword='.length;
      const indexEnd = location.href.indexOf('&s.app');
      keyword = decodeURI(decodeURI(location.href.substring(indexStart, indexEnd)));
  }

  // アクセスログアプリへの登録
  const paramPost = {
      'app': accessLogAppId,
      'record': {
          'accessUrl': {
              'value': location.href
          },
          'keyword': {
              'value': keyword
          }
      }
  };
  const respPost = await kintone.api(kintone.api.url('/k/v1/record', true), 'POST', paramPost);
}
/**
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
▼             検索ワードリストを作成する関数                          
▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲
*/
const makeSearchWords = async () => {
  // アクセスログアプリから検索ワードを取得する（API仕様に任せて最大500件）
  const paramGet = {
      'app': accessLogAppId,
      'fields': ['keyword'],
      'query': 'accessUrl like "https://' + location.hostname + '/k/' + kintone.app.getId() + '/" and accessUrl like "s.keyword"'
  };
  const respGet = await kintone.api(kintone.api.url('/k/v1/records', true), 'GET', paramGet);
  
  // 重複を排除して配列に変換する
  const respArray = [];
  respGet.records.forEach((record) => {
      respArray.push(record.keyword.value);
  });
  const wordList = [...new Set(respArray)];
    
  return wordList;  
};

/**
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
▼              検索ボックスを配置する関数                          
▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲
*/
const makeSearchBox = async (event) => {
  
  // 増殖バグ回避
  if(document.getElementById('divSearchBox')){
      return;
  }
  
  // ヘッダースペース（一覧画面と詳細画面で配置場所分岐制御）
  let spaceHeader;
  if(event.type === 'app.record.index.show'){
    spaceHeader = kintone.app.getHeaderMenuSpaceElement();
  }else{
    spaceHeader = kintone.app.record.getSpaceElement('searchBoxSpace');
  }

  // 検索窓div
  const divSearchBox = document.createElement('div');
  divSearchBox.id = 'divSearchBox';

  // 検索ワードdatalist
  const wordList = document.createElement('datalist');
  wordList.id = 'wordList';
  const words = await makeSearchWords();
  words.forEach((word) => {
    const option = document.createElement('option');
    option.value = word;
    wordList.appendChild(option);
  });
  divSearchBox.appendChild(wordList);
  
  // 検索窓text
  const searchBoxText = document.createElement('input');
  searchBoxText.setAttribute('id', 'searchBoxText');
  searchBoxText.setAttribute('type', 'text');
  searchBoxText.setAttribute('list','wordList');
  divSearchBox.appendChild(searchBoxText);

  // 検索button
  const searchBoxButton = document.createElement('button');
  searchBoxButton.setAttribute('class', 'kintoneplugin-button-dialog-ok');
  searchBoxButton.innerText = '検索';
  const getTextBoxValue = () => {
    const textBoxValue = document.getElementById('searchBoxText').value;
    location.href = 'https://' + location.hostname + '/k/search?keyword=' + textBoxValue + '&app=' + kintone.app.getId();
  };
  searchBoxButton.addEventListener('click', getTextBoxValue);
  divSearchBox.appendChild(searchBoxButton);

  // ヘッダーに検索divを配置
  spaceHeader.appendChild(divSearchBox);

};
/**
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
●                         event
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
*/
kintone.events.on(['app.record.index.show', 'app.record.detail.show'], (event) => {
  // アクセスログアプリへの登録
  postLog();   
  // 検索ボックスの配置
  makeSearchBox(event);
});

})();



```



# おわりに

- 月刊ｲｼｲｹﾝﾀﾛｳ12月号です
- ただでさえ遅筆なのにQiita記事久しぶりなのでものすごく時間かかりました
- 苦闘してたのは2ヶ月近く前でしたｗ

https://twitter.com/kentaro1sh11/status/1580471721672396800

- ui component v1 のアップデート期待してます
- 弊社来年4月に大阪で展示会やります！宣伝！
- 私も行くので4/1夜お会いできる方募集したいところですが宿がどこだかわからないｗ
![Fes43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105085/75bd6d0b-cb5a-df79-ee14-dbe2c830c9b9.png)