Reputation: 13
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
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
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>
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
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
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