Kviewerルックアップの2段階絞込

FormBridgeにおいて、Kintoneのルックアップのような入力後にさらにフィルターをかける方法を

Kviewerルックアップでも行いたいたのですが、

どのように外部公開APIをいじればよいかかが分かりません。

 

baseUrlは

https://viewer.kintoneapp.com/public/api/records/{ビューコード}/{ページ番号}

になると思うのですが、

どのフィールドの値で検索するのかを指定する箇所が

どの変数になるのでしょうか?

フィールドの入力値は動的に設定したく、

最初に入力した後に、第二条件のフィールドに絞込条件を入れておけば、

検索結果が絞り込んで表示されるイメージです。

 

どなたかご存知の方がいらっしゃったらご教示いただきたいです。

よろしくお願いいたします。

 

const generateUrl = (baseUrl, params = {}) => {
    let queryParams = []
    if (params !== [] && params !== {}) {
      const add = (key, value) => {
        // if value is a function then call it and assign it's return value as value
        value = (typeof value === 'function') ? value() : value

        // change null to empty string
        value = (value === null) ? '' : value

        queryParams.push(encodeURIComponent(key) + '=' + encodeURIComponent(value))
      }
      const buildQueryParams = (prefix, params, add) => {
        const rbracket = new RegExp(/\[\]$/)
        if (params instanceof Array) {
          params.forEach((val, i) => {
            if (rbracket.test(prefix)) {
              add(prefix, val)
            } else {
              buildQueryParams(prefix + '[' + (typeof val === 'object' ? i : '') + ']', val, add)
            }
          })
        } else if (typeof params === 'object') {
          for (let name in params) {
            buildQueryParams(prefix + '[' + name + ']', params[name], add)
          }
        } else {
          add(prefix, params)
        }
      }

      for (let prefix in params) {
        buildQueryParams(prefix, params[prefix], add)
      }
      return baseUrl + '?' + queryParams.join('&').replace(/%20/g, '+')
    } else {
      return baseUrl
    }
  }

 

少し勉強して、上記のコードには手を加えないことは理解しました。

そこで検索値を可変ではなくひとまず固定値にして、

var url1 =‘https://viewer.kintoneapp.com/public/api/records/8{ビューコード}/{ページ番号}’;
var additionalFilters = [{with: ‘and’,field: ‘フィールド1’,sign: ‘=’,value: ‘値1’},{with: ‘and’,field: ‘フィールド2’,sign: ‘=’,value: ‘値2’}];
const url = generateUrl(url1, {additionalFilters: additionalFilters});
url;

としたのですが、

kviewerルックアップの検索ボタンを押しても、

入力値(値1)はしぼりこまれるものの、値2は全件表示されてしまいます。

開発者ツールのコンソール画面で最初のコメントのスクリプトと

上記スクリプトでたたいた結果のURLを

直接ブラウザで開いたところtotal count=1となっており、

希望通りの絞込結果になっていました。

しかし、kviewerルックアップの検索ボタンを押しても、

カスタマイズの結果が反映されず、

通常の絞込条件にしかなりませんでした。

 

これらのjavascriptをkviewerルックアップの検索ボタンに

反映するのにはどのようにしたらよろしいでしょうか?

kyoden様

お世話になっております。
トヨクモの江田です。

https://developer.cybozu.io/hc/ja/community/posts/360048112252
こちら、返信が遅くなり、申し訳ございませんでした。

additionalFiltersをkViewerルックアップに反映することはできません。
JavaScriptカスタマイズで、外部公開APIを実行してデータの取得、データの表示、データのフォームへの反映といった機能を自作する必要があります。

前回のスレッドでデータの取得方法について、サンプルコードを提示いたしました。

取得したデータを表示する際に、DOM操作などの知識も必要になるので、実装の難易度は高めかもしれません。

江田様

↑でご回答の内容は下記内容に対するものでしょうか?

 

>こちらのスクリプト作成いただき誠にありがとうございます。

>これらをFormBridge側に読み込ませましたが、検索ボタンをクリックすることができませんでした。

>「検索」という文字自体は表示されています。

>また、FormBridgeのフィールド形式:フィールドコードを下記のように設定しました。

>文字列一行:銀行名

>文字列一行:支店名

>ラベル:label

>apiUrlには今回使いたい外部公開APIのURLを入れています。

>銀行名か支店名をkviewerルックアップフィールドにしなければならないのでしょうか?

 

>また、こちらのスクリプトですと、

>検索した結果、銀行コードや支店コードをコピーする記述が無さそうですが、

>そちらは追加しなければならないでしょうか?

 

kyoden様

お世話になっております。

>これらをFormBridge側に読み込ませましたが、検索ボタンをクリックすることができませんでした。
検索ボタンをクリックした際に、コンソール画面に、外部公開APIの実行結果を出力しております。
下記などを参考に、コンソール画面を確認してください。
https://developer.cybozu.io/hc/ja/articles/207613916

>また、FormBridgeのフィールド形式:フィールドコードを下記のように設定しました。…
FormBridgeの設定はそちらで問題ありません。
kintoneにも、「銀行名」と「支店名」というフィールドコードのフィールドが必要です。
kintoneの設定を変更すると問題があるようでしたら、sample.jsの10行目を変更すれば動作します。

>銀行名か支店名をkviewerルックアップフィールドにしなければならないのでしょうか?
kViewerルックアップは使用しません。
JavaScriptカスタマイズで同様の機能を自作すると良いと思います。

>また、こちらのスクリプトですと、
>検索した結果、銀行コードや支店コードをコピーする記述が無さそうですが、
>そちらは追加しなければならないでしょうか?
はい、その必要があります。
また、「銀行名」のみ入力されている場合などは、取得結果が複数あるので、kViewerルックアップのポップのようなUIも作成する必要があるかと思います。

「銀行名」と「支店名」の両方が入力された状態で検索するという仕様でしたら、フォームに値をコピーするだけなので、比較的容易に実装できるかと思います。
https://form.kintoneapp.com/help/customize

江田様

 

ご教示いただき誠にありがとうございます。

コンソール画面にて検索件数を取得できていることが確認できました。

 

ただし、銀行名と支店名を入力して検索しても部分一致で複数ヒットすると思うのですが、

そのような場合は単にコピーするだけでは難しいでしょうか?

難しいのであれば、ポップのUIを作成するにあたって何か参考になるコードが載っている記事があればご教示いただきたいです。

 

重ね重ね恐れ入りますが、ご検討のほどよろしくお願いいたします。

kyoden様

お世話になっております.

返信遅くなりました.

下記JavaScriptとCSSを順に読み込ませて実装できます.

・generateUrl.js

const generateUrl = (baseUrl, params = {}) => {
  let queryParams = []
  if (params !== [] && params !== {}) {
    const add = (key, value) => {
      // if value is a function then call it and assign it's return value as value
      value = (typeof value === 'function') ? value() : value

      // change null to empty string
      value = (value === null) ? '' : value

      queryParams.push(encodeURIComponent(key) + '=' + encodeURIComponent(value))
    }
    const buildQueryParams = (prefix, params, add) => {
      const rbracket = new RegExp(/\[\]$/)
      if (params instanceof Array) {
        params.forEach((val, i) => {
          if (rbracket.test(prefix)) {
            add(prefix, val)
          } else {
            buildQueryParams(prefix + '[' + (typeof val === 'object' ? i : '') + ']', val, add)
          }
        })
      } else if (typeof params === 'object') {
        for (let name in params) {
          buildQueryParams(prefix + '[' + name + ']', params[name], add)
        }
      } else {
        add(prefix, params)
      }
    }

    for (let prefix in params) {
      buildQueryParams(prefix, params[prefix], add)
    }
    return baseUrl + '?' + queryParams.join('&').replace(/%20/g, '+')
  } else {
    return baseUrl
  }
}

・MyLookUp.js

MyLookUp = (function(fieldSettings, state){
  function MyLookUp(fieldSettings, state) {
    this.fieldSettings = fieldSettings;
    this.state = state;
  }
  MyLookUp.prototype = {
    createModal: function(){
      var _this = this;
      this.modal = document.createElement('div');
      this.modalTable = document.createElement('table');
      this.modalTbody = document.createElement('tbody');
      this.modal.classList.add('lookUpModal');
      this.modalTable.classList.add('lookUpModalTable');
      this.modalTable.innerHTML = (
        '<thead><tr>' +
        this.fieldSettings.viewFields.reduce(function(columns, viewField){
          return columns + '<th>' + viewField + '</th>';
        }, '') +
        '<th>取得</th>' +
        '</tr></thead>'
      );
      this.modal.addEventListener('click', function(e){
        if(e.target === _this.modal){
          _this.removeModal();
        }
      });
      this.modalTbody.addEventListener('click', function(e){
        if(e.target.classList.contains('modalGetButton')){
          _this.copyDatas(_this.records[e.target.getAttribute('data-index')]);
        }
      });
      this.modalTable.appendChild(this.modalTbody);
      this.modal.appendChild(this.modalTable);
      document.body.appendChild(this.modal);
      return this;
    },
    showModal: function(){
      var _this = this;
      this.modalTbody.innerHTML =(
        _this.records.reduce(function(rows, record, index){
          return (
            rows +
            '<tr>' +
            _this.fieldSettings.viewFields.reduce(function(columns, viewField){
              return columns + '<td>' + record[viewField].value + '</td>';
            }, '') +
            '<td><a class="modalGetButton" data-index="' + index + '">取得</a></td>' +
            '</tr>'
          )
        }, '')
      );
      this.modal.classList.add('on');
    },
    removeModal: function(){
      this.modal.classList.remove('on');
    },
    createGetButton: function(){
      var _this = this;
      this.getButton = document.createElement('a');
      this.getButton.innerHTML = '取得';
      this.getButton.classList.add('lookUpButton');
      this.getButton.addEventListener('click', function(){
        var xhr = new XMLHttpRequest();
        xhr.open('GET', generateUrl(_this.fieldSettings.apiUrl, {
          additionalFilters: _this.fieldSettings.filterFields.filter(function(filterField){
            return _this.state.record[filterField.to].value;
          }).map(function(filterField){
            return {
              with: 'and',
              field: filterField.from,
              sign: filterField.sign,
              value: _this.state.record[filterField.to].value
            };
          })
        }));
        xhr.addEventListener('load', function(){
          var records = JSON.parse(xhr.response).records
          if(!records.length){
            alert('データがありません。');
          }else if(records.length === 1){
            _this.copyDatas(records[0]);
          }else{
            _this.records = records;
            _this.showModal();
          }
        });
        xhr.send();
      });
      fb.getElementByCode(this.fieldSettings.buttonSpace).appendChild(this.getButton);
      return this;
    },
    copyDatas: function(record){
      var _this = this;
      this.fieldSettings.filterFields.concat(this.fieldSettings.copyFields).forEach(function(field){
        _this.state.record[field.to].value = record[field.from].value;
      });
      this.removeModal();
    }
  }
  return MyLookUp;
})();

・sample.js

(function() {
  "use strict"
  fb.events.form.mounted = [function(state){
    var lookUpParams = {
      apiUrl: 'https://viewer.kintoneapp.com/public/api/records/ **** /1', //外部公開APIのURL
      buttonSpace: 'label', //ボタン設置用のスペースィールド
      filterFields: [ //検索条件のフィールド
        {to: '銀行名', from: '銀行名', sign: 'like'},
        {to: '支店名', from: '支店名', sign: 'like'},
      ],
      copyFields: [ //コピーするフィールド
        {to: '銀行コード', from: '銀行コード'},
        {to: '支店コード', from: '支店コード'},
      ],
      viewFields: ['銀行コード', '支店コード', '銀行名', '支店名'] //ポップアップに表示するフィールド
    };
    new MyLookUp(lookUpParams, state).createGetButton().createModal();
  }];
})()

・MyLookUp.css

.lookUpModal{
  display: none;
  position: fixed;
  background: rgba(0,0,0,.3);
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
}
.lookUpModal.on{
  display: block;
}
.lookUpModalTable{
  position: fixed;
  background: #fff;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 10px;
  border-radius: 2px;
  box-shadow: 0 1px 3px rgba(0,0,0,.3);
  border-collapse: collapse;
}
.lookUpModalTable th,
.lookUpModalTable td{
  border: 1px solid #000;
  padding: 10px;
}
.lookUpModalTable a,
.lookUpButton{
  cursor: pointer;
}

江田様

 

お世話になっております。

大変ご多忙の中、このように大変なコードを作成いただきまして、

誠にありがとうございました。

 

実装したい機能はこれで実現できました!

ただし、1点だけ改善したいところがございます。

例えば銀行名、支店名を三菱UFJ銀行、大阪と検索した場合検索候補数が多く、

スマホでもPCでも画面から見切れてしまいます。

そこで、モーダルウィンドウ内をスクロールさせる必要があると思うので、

MyLookUp.cssの

 

.lookUpModalTable{
position: fixed;
background: #fff;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 10px;
border-radius: 2px;
box-shadow: 0 1px 3px rgba(0,0,0,.3);
border-collapse: collapse;
}

 

.lookUpModalTable{
position: fixed;
background: #fff;
top: 50%;
left: 50%;
max-height: 100vh;
transform: translate(-50%, -50%);
padding: 10px;
border-radius: 2px;
box-shadow: 0 1px 3px rgba(0,0,0,.3);
border-collapse: collapse;
overflow-y: auto;
}

のように

max-height: 100vh;

overflow-y: auto;

を加えてみたのですが、スクロールできませんでした。

重ね重ね申し訳ございませんが、ご教示いただけると幸いです。

大変お手数をおかけいたしますが、よろしくお願いいたします。

kyoden様

お世話になっております。

「MyLookUp.js」と「MyLookUp.css」を下記に変更してください。

・MyLookUp.js

MyLookUp = (function(fieldSettings, state){
  function MyLookUp(fieldSettings, state) {
    this.fieldSettings = fieldSettings;
    this.state = state;
  }
  MyLookUp.prototype = {
    createModal: function(){
      var _this = this;
      this.modal = document.createElement('div');
      this.modalTableWrapper = document.createElement('div');
      this.modalTable = document.createElement('table');
      this.modalTbody = document.createElement('tbody');
      this.modal.classList.add('lookUpModal');
      this.modalTableWrapper.classList.add('lookUpModalTableWrapper');
      this.modalTable.classList.add('lookUpModalTable');
      this.modalTable.innerHTML = (
        '<thead><tr>' +
        this.fieldSettings.viewFields.reduce(function(columns, viewField){
          return columns + '<th>' + viewField + '</th>';
        }, '') +
        '<th>取得</th>' +
        '</tr></thead>'
      );
      this.modal.addEventListener('click', function(e){
        if(e.target === _this.modal){
          _this.removeModal();
        }
      });
      this.modalTbody.addEventListener('click', function(e){
        if(e.target.classList.contains('modalGetButton')){
          _this.copyDatas(_this.records[e.target.getAttribute('data-index')]);
        }
      });
      this.modalTable.appendChild(this.modalTbody);
      this.modalTableWrapper.appendChild(this.modalTable);
      this.modal.appendChild(this.modalTableWrapper);
      document.body.appendChild(this.modal);
      return this;
    },
    showModal: function(){
      var _this = this;
      this.modalTbody.innerHTML =(
        _this.records.reduce(function(rows, record, index){
          return (
            rows +
            '<tr>' +
            _this.fieldSettings.viewFields.reduce(function(columns, viewField){
              return columns + '<td>' + record[viewField].value + '</td>';
            }, '') +
            '<td><a class="modalGetButton" data-index="' + index + '">取得</a></td>' +
            '</tr>'
          )
        }, '')
      );
      this.modal.classList.add('on');
    },
    removeModal: function(){
      this.modal.classList.remove('on');
    },
    createGetButton: function(){
      var _this = this;
      this.getButton = document.createElement('a');
      this.getButton.innerHTML = '取得';
      this.getButton.classList.add('lookUpButton');
      this.getButton.addEventListener('click', function(){
        var xhr = new XMLHttpRequest();
        xhr.open('GET', generateUrl(_this.fieldSettings.apiUrl, {
          additionalFilters: _this.fieldSettings.filterFields.filter(function(filterField){
            return _this.state.record[filterField.to].value;
          }).map(function(filterField){
            return {
              with: 'and',
              field: filterField.from,
              sign: filterField.sign,
              value: _this.state.record[filterField.to].value
            };
          })
        }));
        xhr.addEventListener('load', function(){
          var records = JSON.parse(xhr.response).records
          if(!records.length){
            alert('データがありません。');
          }else if(records.length === 1){
            _this.copyDatas(records[0]);
          }else{
            _this.records = records;
            _this.showModal();
          }
        });
        xhr.send();
      });
      fb.getElementByCode(this.fieldSettings.buttonSpace).appendChild(this.getButton);
      return this;
    },
    copyDatas: function(record){
      var _this = this;
      this.fieldSettings.filterFields.concat(this.fieldSettings.copyFields).forEach(function(field){
        _this.state.record[field.to].value = record[field.from].value;
      });
      this.removeModal();
    }
  }
  return MyLookUp;
})();

・MyLookUp.css

.lookUpModal{
  display: none;
  position: fixed;
  background: rgba(0,0,0,.3);
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
}
.lookUpModal.on{
  display: block;
}
.lookUpModalTableWrapper{
  position: fixed;
  background: #fff;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 10px;
  border-radius: 2px;
  box-shadow: 0 1px 3px rgba(0,0,0,.3);
  max-height: 100vh;
  overflow-y: scroll;
}
.lookUpModalTable{
  border-collapse: collapse;
}
.lookUpModalTable th,
.lookUpModalTable td{
  border: 1px solid #000;
  padding: 10px;
}
.lookUpModalTable a,
.lookUpButton{
  cursor: pointer;
}

江田様

お世話になっております。

再度ご教示いただき、誠にありがとうございます。

 

これで実現したい動きは全て実現することができました!

長期にわたってご教示いただきまして、

誠にありがとうございました!

このトピックはベストアンサーに選ばれた返信から 3 日が経過したので自動的にクローズされました。新たに返信することはできません。