jfifth1976
jfifth1976

Reputation: 13

Filtering jquery objects by data attribute array

Is there an easier, better or cleaner way to filter a list of elements that utilize a data- attribute holding an array?

Currently we have a large list of items each containing 1 or more tags in an array stored in a "data-tags" attribute, as follows:

<div class="viewItem" data-tags='[{"id":1,"name":"Tag 01"},{"id":2,"name":"Tag 02"}]'></div>
<div class="viewItem" data-tags='[{"id":2,"name":"Tag 02"}]'></div>
<div class="viewItem" data-tags='[{"id":1,"name":"Tag 01"},{"id":3,"name":"Tag 03"}]'></div>

The object is to show only the divs that have a particular tag in the data-tags array. The following code works, but I feel it's very inefficient when dealing with a large number of items and would like to find a better answer, whether it's using jquery filter or grep or something else.

$(function () {
    //Instantiate variables
    var $viewItems = $('.viewItem');
    var filterId = 2;

    //Hide all items in object array.
    $viewItems.hide(); 

    //Loop thru EACH item and show only those with matching id in array
    $viewItems.each(function () {
        var thisItem = $(this);
        var array = thisItem.data("tags");

        $.each(array, function (i, obj) {
            if (obj.id == filterId) { thisItem.show(); return false; }
        });                    
    });
});

Upvotes: 1

Views: 4265

Answers (4)

Bryan
Bryan

Reputation: 17581

How about jQuery filter?

var $viewItems = $('.viewItem');
var filterId = 2;

//Hide all items in object array.
$viewItems.hide(); 

//Loop thru EACH item and show only those with matching id in array
$viewItems.filter(function (i, el) {
  var dataTags = $(el).data("tags");
  return dataTags.filter(tag => tag.id === filterId).length
}).show()

Upvotes: 0

trincot
trincot

Reputation: 350270

First of all, the data method is rather fast: jQuery only reads the value from the DOM on the first access, but then keeps the values (objects in this case) in memory, and will not read the DOM's data attribute again.

So, if these JSON values are not very large, the filtering of the items by id will not be the part that takes most of the time. The most time-consuming part will be where you call .hide() and .show(), since it involves interaction with the DOM and rendering.

But, if you really need to optimise it, you could do some preprocessing and key your elements by this JSON id value, for instance like this:

$(function () {
    // Pre-processing: key all viewItems by the id in their data-tags:
    var hash = $('.viewItem').get().reduce(function (hash, div) {
        return $(div).data("tags").reduce(function (hash, o) {
            hash[o.id] = (hash[o.id] || []).concat(div);
            return hash;
        }, hash);
    }, {});

    // Actual filtering
    $('#apply').click(function() {
        var filterId = $('#filter').val();
        $('.viewItem').hide();
        $(hash[filterId]).show();
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Filter ID: <input id="filter"><button id="apply">Apply</button><br>

<div class="viewItem" data-tags='[{"id":1,"name":"Tag 01"},{"id":2,"name":"Tag 02"}]'>1,2</div>
<div class="viewItem" data-tags='[{"id":2,"name":"Tag 02"}]'>2</div>
<div class="viewItem" data-tags='[{"id":1,"name":"Tag 01"},{"id":3,"name":"Tag 03"}]'>1,3</div>

Imperative alternative

As the functional approach to create the hash may look confusing, I provide here the imperative alternative. But the resulting hash object will be exactly the same:

$(function () {
    // Pre-processing: key all viewItems by the id in their data-tags:
    var hash = [];
    $('.viewItem').each(function (i, div) {
        $.each($(div).data("tags"), function (j, obj) {
            if (!(obj.id in hash)) hash[obj.id] = [];
            hash[obj.id].push(div);
        });
    });

    // Actual filtering
    $('#apply').click(function() {
        var filterId = $('#filter').val();
        $('.viewItem').hide();
        $(hash[filterId]).show();
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Filter ID: <input id="filter"><button id="apply">Apply</button><br>

<div class="viewItem" data-tags='[{"id":1,"name":"Tag 01"},{"id":2,"name":"Tag 02"}]'>1,2</div>
<div class="viewItem" data-tags='[{"id":2,"name":"Tag 02"}]'>2</div>
<div class="viewItem" data-tags='[{"id":1,"name":"Tag 01"},{"id":3,"name":"Tag 03"}]'>1,3</div>

Upvotes: 3

quirimmo
quirimmo

Reputation: 9988

Use jQuery grep function to filter your elements. Then inside the grep function, parse the data-tags attribute and find if there is an element with the given id:

var filterId = 1;
var dataTags;
var arr = $.grep($('.viewItem'), function( el ) {
  dataTags = JSON.parse($(el).attr('data-tags'));
  return dataTags.find(el => el.id === filterId);
});
console.log(arr);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="viewItem" data-tags='[{"id":1,"name":"Tag 01"},{"id":2,"name":"Tag 02"}]'></div>
<div class="viewItem" data-tags='[{"id":2,"name":"Tag 023"}]'></div>
<div class="viewItem" data-tags='[{"id":1,"name":"Tag 01"},{"id":3,"name":"Tag 03"}]'></div>

Upvotes: 2

Oleg Imanilov
Oleg Imanilov

Reputation: 2751

You can create css classes for every tag. Example: tag-1, tag-2, etc. Put tags inside class section like this: <div class="viewItem tag-1 tag2".../> So you can easily select/show/hide any combination of tags like this:

// Hide tag-1
$(".tag-1").css('display','none')
// Show tag-2
$(".tag-2").css('display','block');
// Select elements with tag-1 and tag-2
$(".tag-1 tag-2").css('background','pink');

Upvotes: 0

Related Questions