HTML5
CSS3
jQuery
JSON
Ajax
Y'sDay 15

フロントだけで文字予測機能を実装してみた

この記事は株式会社Y'SのAdvent Calendar15日目の記事です。

投稿自体初めてなので、間違いがあったらすいません。
こうしたほうがもっとよくなるよ!って方法がありましたらご教授くださいませ!

始めに話しておく

今回は、バックエンドの支援がなかったので、フロントのみで文字予測機能を作ったお話です。(言語は、HTML5/CSS3/jQuery+ajax/JSONで対応させました)
ですが、データ量が多ければ多いほど通信に時間がかかって仕様がない内容です。本件は130件程度だったのでなんとかなりました。
でも、もっとデータ量が多い場合はフロント側で担保しないに限ります!フロントでやる範囲と、バックエンドでやってくれる内容はちゃんと線引きしましょう…!

前提

  • APIなし
  • バックエンドからの支援なし

当初はこんなはずではなかったんですけどね。
ちゃんとAPIは使えると聞いていました。見せてもらったワイヤーは簡略化した内容でしたが、「まぁ大丈夫だろう」と判断したのが運の尽き。
途中から話がだいぶ変わり、結果フロントで文字予測することになりました。
また、既存で流用できるようなコードもない状態だったので、ロジックから考えなければならなくなりました。(最早積んでいる)

とりあえずロジック

話を聞く限り普通に検索したときにフォーム下に類似する検索項目を出せればいいらしい。ということで洗い出します。

  • データを用意する
  • Ajaxでデータを呼び出し
  • フォームに入力された文字を呼び出したデータと比較
  • 取得した情報を個数分数えて新しいタグにする
  • HTMLに挿入する
  • 挿入したタグがクリックされたあと、表示を消す/フォームテキストの変更
  • 一度検索したあと、別の内容を検索するときの処理
  • イベント発火のタイミング
  • コードのまとめ

大体こんなかんじならできるんじゃないかな?

データを用意する

まずは、必要なデータを全て用意します。
これはJSON形式で組みやすいように作成しました。

{
  "tokyo":[
      {
         "area":"アジアカフェ",
         "code":"AA",
      },
      {
          "area":"ネクストアジア",
          "code":"BB",
       }
  ],
   "hokkaido":[
       {
          "area":"カンフーカフェ",
          "code":"DD"
        },
        {
          "area":"アジアンX",
          "code":"EE"
        }
    ],
}

別の機能に併用できるようにtokyoやhokkaidoなど名前をつけておきます。
他にも地域別のデータを作りましたが、省略。

Ajaxでデータを呼び出し

データの呼び出しと、データ処理の下地作りをします。

$(function(){
 var data = '../common/js/map.json',
     defer = $.Deferred(),
     json , _json , area , code , label , _area = [] , _code = [];

 $.ajax({
   url      : data,
   type     : 'get',
   dataType : 'json',
   success  : defer.resolve,
   error    : defer.reject
 }).done(function(data){
   json  = JSON.stringify(data),
   _json = JSON.parse(json); //dataのままでは連想配列調べた時に[object object]で返ってくるので変換して保存します。

  return defer.promise();
 });
});

ここまでが下味です。
次に呼び出したデータを連想配列で処理し、且つフォームに入力した文字をvalueとして保管しておいて、連想配列のareaとvalueが一致したら_area,_codeにpushして吐き出すように設定します。

フォームに入力された文字を呼び出したデータと比較

$(function(){
 var data = '../common/js/map.json',
     defer = $.Deferred(),
     json , _json , area , code , val , label , _area = [] , _code = [];

 $.ajax({
   url      : data,
   type     : 'get',
   dataType : 'json',
   success  : defer.resolve,
   error    : defer.reject
 }).done(function(data){
   json  = JSON.stringify(data),
   _json = JSON.parse(json);

   for(var key in $json){
    for(var $key in $json[key]){
      area = $json[key][$key]['airPlane'];
      code = $json[key][$key]['code'];
      val  = $('.js_checker').val();
        if(area.indexOf(val)!= -1){
           len = t.length;
           _area.push(area);
           _code.push(code);
           console.log(_area);
           console.log(_code);
         }
       }
    }

  return defer.promise();
 });
});

一致したデータがちゃんと_area,_codeに入ってるか確認します。

console.png
ちゃんと返ってきてますね!

さらにこれを加工します。

HTMLに挿入する

$(function(){
 $ajax({
   //ajax内部省略

 //一致したデータをタグにして、HTMLに挿入する
 if(t != ''){
  for(var i = 0; i<len; i++){
   label = '<label class="md_minSearchLabel js_minSearchLabel" value="' +
 _code[i] + '">' + _area[i] + '</label>';
     $('.js_candidateBefore').before(label);
     };
   }else if(t == ''){return false;}

   return defer.promise();
  });
});

これで、調べた内容と一致するテキストがあればフォームでてきてくれます。
こんなかんじ。
form.png

よしよし、でました!

あとは、挿入したタグをクリックしたときにテキストをフォームに移すとか、文字を消したときの処理とかの処理を書くだけですね。
一つずつ組み立てましょう!

挿入したタグがクリックされたあと、表示を消す/フォームテキストの変更

ついでに「検索ボタン(虫眼鏡)」を押した後に別ウィンドウでcodeを保持したまま遷移する記述も書いておきます。

$(function(){
 $ajax({
   //ajax内部省略

 //一致したデータをタグにして、HTMLに挿入する
 if(t != ''){
  for(var i = 0; i<len; i++){
   label = '<label class="md_minSearchLabel js_minSearchLabel" value="' +
 _code[i] + '">' + _area[i] + '</label>';
     $('.js_candidateBefore').before(label);
     };
   }else if(t == ''){return false;}

   //挿入したタグがクリックされたときの処理
    $(document).on('click','.js_minSearchLabel',function(){
      var txt = $(this).text(),
          v = $(this).attr('value');
      $('.js_checker').val(txt);
      $('.js_minSearchLabel').fadeOut();

  //検索ボタン(虫眼鏡)」を押した後に別ウィンドウでcodeを保持したまま遷移
     var link = 'https:www.test.html'
         href = link + ' ? code=' + v;
      $('.js_minSearchLink').on('click',function(){
        window.location.href = href;
        });
     });
   return defer.promise();
  });
});

表示を消すだけなんでこれだけでOKです。
あとは、一度検索したあと、別のワードで検索をかけたい場合の処理とイベント発火のタイミングですね。

一度検索したあと、別の内容を検索するときの処理

$(function(){
 var data = '../common/js/map.json',
     defer = $.Deferred(),
     json , _json , area , code , val , label , _area = [] , _code = [];

 $.ajax({
   url      : data,
   type     : 'get',
   dataType : 'json',
   success  : defer.resolve,
   error    : defer.reject
 }).done(function(data){
   json  = JSON.stringify(data),
   _json = JSON.parse(json);

   for(var key in $json){
    for(var $key in $json[key]){
      area = $json[key][$key]['airPlane'];
      code = $json[key][$key]['code'];
      val  = $('.js_checker').val();

      if(_area = ''){    //新しいコード
        if(area.indexOf(val)!= -1){
           len = t.length;
           _area.push(area);
           _code.push(code);
           console.log(_area);
           console.log(_code);
         }
       }else{ //新しいコード
        if(val == ''){
          $('.js_minSearchLabel').remove();
          _area = [] , _code = [];
        }
         return false;
       }
     }
   }

  return defer.promise();
 });
});

一番最初に宣言していた_areaの中身が空だった場合に、フォームに入力したvalueと連想配列以降の処理をいれます。
もし、空でなければ、一度挿入した新しいタグをremoveし、_area , _code共にもう一度空にする処理をいれます。

イベント発火のタイミング

フォームの内容が変化するので、見守っていてほしいところ。
でも、常時監視では重たくなって仕方がない!ということで、フォームがfocusされているときに処理が走るように設定します。

$(function(){
 var data = '../common/js/map.json',
     defer = $.Deferred(),
     json , _json , area , code , val , label , _area = [] , _code = [];

 $.ajax({
   url      : data,
   type     : 'get',
   dataType : 'json',
   success  : defer.resolve,
   error    : defer.reject
 }).done(function(data){
   json  = JSON.stringify(data),
   _json = JSON.parse(json);
   var checker =setInterval(function(){
    for(var key in $json){
     for(var $key in $json[key]){
       area = $json[key][$key]['airPlane'];
       code = $json[key][$key]['code'];
       val  = $('.js_checker').val();

       if(_area = ''){    //新しいコード
         if(area.indexOf(val)!= -1){
            len = t.length;
            _area.push(area);
            _code.push(code);
          }
        }else{ //新しいコード
         if(val == ''){
           $('.js_minSearchLabel').remove();
           _area = [] , _code = [];
           }
           return false;
          }
        }
      }
    //一致したデータをタグにして、HTMLに挿入する(省略)
     //挿入したタグがクリックされたときの処理(省略)
    },1000);
  return defer.promise();
 });
});

これで処理は終わりです。
あとの機能については+α色々ありますが、長ーくなるので省略!
もし参考になるなら幸いです。
最後にまとめとして、コード全体を下記に残しておきます。

まとめ

$(function(){
 var data = '../common/js/map.json',
     defer = $.Deferred(),
     json , _json , area , code , val , label , _area = [] , _code = [];

 $.ajax({
   url      : data,
   type     : 'get',
   dataType : 'json',
   success  : defer.resolve,
   error    : defer.reject
 }).done(function(data){
   json  = JSON.stringify(data),
   _json = JSON.parse(json);
   var checker =setInterval(function(){
    for(var key in $json){
     for(var $key in $json[key]){
       area = $json[key][$key]['airPlane'];
       code = $json[key][$key]['code'];
       val  = $('.js_checker').val();

       if(_area = ''){
         if(area.indexOf(val)!= -1){ //フォームテキストとデータが一致しているか確認
            len = t.length;
            _area.push(area);
            _code.push(code);
          }
        }else{
         if(val == ''){  //再検索するときの処理
           $('.js_minSearchLabel').remove();
           _area = [] , _code = [];
           }
           return false;
          }
        }
      }
   

   //一致したデータをタグにして、HTMLに挿入する
 if(t != ''){
  for(var i = 0; i<len; i++){
   label = '<label class="md_minSearchLabel js_minSearchLabel" value="' +
 _code[i] + '">' + _area[i] + '</label>';
     $('.js_candidateBefore').before(label);
     };
   }else if(t == ''){return false;}

   //挿入したタグがクリックされたときの処理
    $(document).on('click','.js_minSearchLabel',function(){
      var txt = $(this).text(),
          v = $(this).attr('value');
      $('.js_checker').val(txt);
      $('.js_minSearchLabel').fadeOut();

  //検索ボタン(虫眼鏡)」を押した後に別ウィンドウでcodeを保持したまま遷移
     var link = 'https:www.test.html'
         href = link + ' ? code=' + v;
      $('.js_minSearchLink').on('click',function(){
        window.location.href = href;
        });
     });
    },1000);
  return defer.promise();
 });
});


そして見た目の図がこちら(再度掲載)。
form.png

とまぁ、うまくいきましたけど。
実装してみたかんじ、ロジックから考えて実装するまでめちゃくちゃ時間がかかりました。
バックエンドの方と協力できれば楽勝だったはずですけどね…。

皆さんもフロントでできそうだからって高をくくっちゃダメですよ!

以上。奮闘劇でした。
次回は @Bong さんの記事です。お楽しみに!!!