Underscore.jsにおける絞り込み検索について

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 1,004

hanayona

score 7

 前提・実現したいこと

Underscore.jsとjsonを使った検索機能付きのサイトを作成中です。

下記のサイトを参考にさレクトボックスでの実装は出来たのですが、
これをマルチのセレクトボックスで複数選択し検索したいのですが、
何分未熟なものでどうしてもうまくいきません。

"color"のredとblueを含む値を取得して検索するなど。

どなたかご教授頂ける方がいらっしゃいましたらお伺できますでしょうか?
何卒よろしくお願いいたします。

https://www.tam-tam.co.jp/tipsnote/javascript/post10620.html

<form id="search" name="search">

<select name="color" id="color">
<option value="">指定しない</option>
<option value="red">赤</option>
<option value="blue">青</option>
<option value="green">緑</option>
</select>


<select name="type" id="type">
<option value="">指定しない</option>
<option value="square">四角</option>
<option value="circle">丸</option>  
</select>

<input type="submit" value="検索">
</form>

<div id="items"></div>

var items = [
{
"name"  : "赤い四角",
"color" : "red",
"type"  : "square"
},
{
"name"  : "赤い丸",
"color" : "red",
"type"  : "circle"
},
{
"name"  : "青い四角",
"color" : "blue",
"type"  : "square"
},
{
"name"  : "青い丸",
"color" : "blue",
"type"  : "circle"
},
{
"name"  : "緑の四角",
"color" : "green",
"type"  : "square"
},
{
"name"  : "緑の丸",
"color" : "green",
"type"  : "circle"
}
];

// 検索が押された時の処理
$('#search').on('submit' , function(event){
// デフォルトのイベントをキャンセル
event.preventDefault();

// 検索項目のオブジェクトを作成してセレクトボックスの値を格納
var query = {};
if($('#color').val() != ''){
query.color = $('#color').val();
}
if($('#type').val() != ''){
query.type = $('#type').val();
}

// データの中から一致するオブジェクトを検索
var results = _.where(items, query);

// 返ってきた配列で出力処理
outputResults(results);
});

// 検索結果の出力処理
function outputResults(results){
// 変数の初期化
var html = '';

// 受け取った配列をループで処理
// 出力するHTMLの整形
jQuery.each(results, function() {
html += '<div class="item ' + this.color + ' ' + this.type + '">';
html += this.name;
html += '</div>';
});

// HTMLに出力
$('#items').empty().append(html);
}

// ページ読み込み時はすべてのアイテムを出力する
$(window).on('load', function(){
outputResults(items);
});

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

こんにちは。

まず前提として、lodash の where は以下

https://github.com/lodash/lodash/wiki/Deprecations

に記載されているように、 v4 から削除されており、替わりに filter を使うことになっていますので、以下の回答もこれに従っています。

ご質問では

マルチのセレクトボックスで複数選択し検索したい

とのことですが、その場合は filter の第2引数に、条件にマッチするかどうかにより true または false を返す関数を与えれば実現できます。
具体的な修正案の一例を以下に挙げます。単一選択の場合はご質問にあるコードと同様に、

    // データの中から一致するオブジェクトを検索
    var results = _.filter(items, query);


とするところを、以下のように修正すればよいかと思います。

    // データの中から一致するオブジェクトを検索
    var results = _.filter(items, function(item) {      
      return (
        (query.colors.includes("") || query.colors.includes(item.color)) &&
        (query.types.includes("") || query.types.includes(item.type))
      );
    });
  • selectが複数選択可能な場合、.val() は選択された <option>の value を要素とする配列になります。

  • したがって、<select>の id と、 query のプロパティ名であるcolortypeは、colors および、types と複数形にしたほうがよいです。

  • query.colors と query.types に対して、includes を使って、全item の中で条件を満たすものについて true を返すような関数を、filter の第2引数に渡します。 

以下に上記のサンプルを作成しました。(使用している lodash のバージョンは、4.17.10です)

以上参考になれば幸いです。


追記

さはさりながら、_.filter の第2引数に関数を渡すのであれば、そもそも JavaScriptの標準のArray.prototype.filter() を使って以下のように書けます。この場合は lodashは不要になります。

    // データの中から一致するオブジェクトを検索
    var results = items.filter(function(item) {      
      return (
        (query.colors.includes("") || query.colors.includes(item.color)) &&
        (query.types.includes("") || query.types.includes(item.type))
      );
    });

追記2

複数選択可能な場合、colorsおよびtypesと、複数形にしたほうがよい点を回答に加筆しました。


追記3

コメントのほうで頂いた、

“指定しない”と他の選択肢を連動させることは可能でしょうか?

との件ですが、<select> の中にある、どの<option>が選ばれているかが変化したときの chageイベントハンドラに、ご要望の処理を設定することで可能です。

具体的には例えば以下を追加します。

  // ”指定しない" が選択されたら、他の選択肢は選ばれていない状態にする 
  $('select').on('change', function() {
    if ($(this).val().includes(""))
      $(this).val([""]);
  });

上記を追加すると、以下のような効果があります。

例えば、 とが選ばれている状態で、さらに 選択しないを選択状態にすることを想定します。すると上記で設定したchangeハンドラ function() { … } が呼ばれて、関数の中で$(this).val()とすると、選択された option の valueの配列が取れるので、今想定している状況では、["", "red", "grren"] と、3つの要素を持ちますが、これらの要素のうち、空文字列  "" が含まれているということは(= .includes("") が true ならば)、「指定しない」が選ばれたことを示しています。その場合は、「指定しない」だけが選ばれていることを表す配列 [""] を、$(this).val(設定値) に与えると、「指定しない」ではない他の選択肢は選ばれていない状態にすることができます。

※以下にサンプルを作成しました。

https://jsfiddle.net/jun68ykt/qw64naub/81/


追記4

コメントのほうから、

何度もすみません、もし可能でしたら
・・・

ということで頂戴した、追加要件を実現する方法です。

ご希望の挙動を満たすには、changeイベントが発生したときに選択状態にした<option>の value配列を保存しておいて、次に新たなchangeイベントが発生したときに選択されている<option>の value配列と、保存しておいた前回のchangeイベント発生時に選択状態にした値の配列とを比較して、次の望ましい状態を作ってから、再度、保存しておく処理が必要になると思います。どこに保存しておくかですが、 data-属性に保存するというのが一案です。

上記の比較によって判断するべき条件は、

  • 前回のchangeイベント発生時に、最終的に選択状態にした値の配列が、[""]であるか否か

です。以下は上記の実装例です。前回の選択値が[""]であるかどうかの判定のために、 lodash の isEqual を使用しています。

  // optionの選択状態が変更されたときの設定
  $('select')
    .data('prev-values', [""])
    .on('change', function() {
      if ($(this).val().includes("")) {
        if (_.isEqual($(this).data('prev-values'), [""])) {
          $(this).val($(this).val().filter(v => v!=""));
        } else {
          $(this).val([""]);
        }
      }
      $(this).data('prev-values', [...$(this).val()]);
   });

上記のサンプルを

https://jsfiddle.net/jun68ykt/qw64naub/95/

に作成しました。


追記5

「onChangeハンドラの中で、対象のchangeイベントが発生する前の value を取得するにはどうしたらいいか?」というお題は、以下にも投稿されています。

stackoverflow: Getting value of select (dropdown) before change

上記の回答の中にも、changeイベントが発生して選択された値を保存しておく場所として、 data-属性を使うというものが、いくつか見つかります。


追記6

追記4 に書いたコードの以下の部分

$(this).val($(this).val().filter(v => v!=""));


は、以下のように書くことで少し短くすることができます。

$(this).val($(this).val().filter(v => v));

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/04 19:33 編集

    URLのご連絡どうもです。
    手元の iPhone8 を使い、ブラウザは Safari にて、上記URLを開いて、
    以下の操作と確認を行い、(一部、望ましい挙動になっていないところはありますが)、検索結果は意図通りになります。

    (1) 初期表示では、色、形の Select ともに、「指定しない」が表示され、すべてのデータ6個とも表示される。

    (2) 色のセレクタをタップ。画面下部にドラムロールが開き、「指定しない」だけがチェックされている。

    (3) ドラムロールの選択肢で、「赤」と「緑」をタップし各々にチェック印が追加される。ただし、この時点では、「指定しない」のチェックは外れない。

    ※1:このときに「指定しない」のチェックが外れるようにすることは、無理ではないかと個人的には予想してます。ブラウザ内の select要素と、それを実際にiOS上で表示するUI部品とが、そこまで緊密には結びついてないと思われるので。

    (4) ドラムロール右上の「完了」をタップするとドラムロールが消えて、色のSelect に「2項目」と表示される。

    (5) 同様にして、形のほうで「丸」を選び、「完了」でドラムロールを閉じたあと「検索」をタップ

    (6) 意図どおり、罫線の下には「赤い丸」「緑の丸」の2件のみが表示される。

    (7) 色のSelectを開いて、「指定しない」をタップ。「指定しない」にチェックがつくが、上記の ※1 と同様に、「赤」と「緑」はチェックされたまま。

    ※2: ちなみにここで、ホームボタンを押し、一度ブラウザを隠してから、再度 Safari を開くと、「赤」と「緑」のチェックは外れており、「指定しない」にだけチェックが付いている状態になります。

    (8) 同様に「形」のほうでも「指定しない」を選択してドラムロールの「完了」をタップ

    (9) 上記で両方のSelect には「指定しない」が表示されている。この状態で「検索」をタップ

    (10) 意図通り、罫線の下には全てのデータ6件が表示される。

    確認ですが、hanayonaさんのお手元で発生している不具合としては、先にお知らせいただいたAndroid 端末で上記を行うと、 最後の (10)のところで該当0件になってしまう、ということでしょうか?

    キャンセル

  • 2018/09/05 11:13

    お忙しい中、ご連絡ありがとうございます!!

    自分の iPhone7のSafariでも上記のURLを試したのですが、
    (1)~(7)までは問題なく動作できるのですが、(8)の「形」のほうでも「指定しない」を選択してドラムロールの「完了」の時に既に選択している(7) 色の「指定しない」が0件選択となってしまいます。
    その後、色、形共に「指定しない」を選択後に検索ボタンに限らず、画面のどこかをタップするたびに指定しないが0件選択となってしまう状態が発生いたします(>_<) 。
    ちなみにPCでは何の問題もなく作動します。

    ほんとお手間をとらせてしまい申し訳ありあません。

    キャンセル

  • 2018/09/06 14:59

    iPhone8で確認したところ問題無く完璧に動作いたしました!!
    やはり機種により「指定しない」が0件選択となってしまう模様です(>_<)

    ほんと無理せず、お時間ある時で構いませんのでご確認頂けると幸いです。

    キャンセル

0

なんどもすみません、もし可能でしたら“指定しない”を選択した状態で、“赤”“青”などを選択すると指定しないの選択が外れるようにすることは出来ますでしょうか?

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.21%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる