Context
I'm trying to build a simple page that will let me display and filter bookmarks. Bookmarks can have zero to many tags and tags can be repeated between different bookmarks. The tags for each bookmark are stored in a data-attribute in the wrapper element for each bookmark (see below). My end goal is to be able to dynamically generate a menu of all the tags in use on the page and hide/show based on selections there. I'm hoping to avoid using any external libraries for this as I want this to be my homepage and to keep it really simple. So pure JS solutions preferred, unless you think I'm being needlessly picky.
Problem
Right now I'm stuck generating a useful array of the tags. I can filter for undefined elements in the array (when a bookmark doesn't have a tag) and I can filter for duplicate elements in the array (when tags appear on multiple bookmarks). But I'm majorly stuck when a bookmark has multiple tags. You can only use a data-attribute once per element, which means all tags for a particular bookmark have to be stored in a list in the data attribute.
What I'd like to happen is that for every bookmark that contains two or more tags, the script splits them and makes each one a new element of the array.
Here's an example, note the "data-tags"
attribute in <article>
:
<article class="note"
data-date="2021-08-04T03:40"
data-tags="Tag-One Tag-Two">
<section class="item">Bookmark Description</section>
<section class="tags">
<a href="#" class="tag">Tag-One</a>
<a href="#" class="tag">Tag-Two</a>
</section>
</article>
This is the part of the script that's working:
const notes = document.querySelectorAll('.note');
let tags = [];
// get tags
notes.forEach(note => tags.push(note.dataset.tags));
//Will look like this: tags[] = ("Tag-One Tag-Two")
//remove dupes
tags = tags.filter((value,index,array) => array.indexOf(value) == index);
//remove undefined values
tags = tags.filter(tag => tag !== undefined);
Below is the part I have trouble with. I've tried looping through the array multiple ways but each time splice()
doesn't work in the way I expect and often the array is trimmed before later tags can be split up.
for (let index = 0; index < tags.length; index ) {
var value = tags[index];
if (value.indexOf(' ') > 0 ){
var newTags = value.split(' ');
newTags.forEach(newTag => tags.push(newTag));
tags.splice(index,1);
}
}
Any help would be hugely appreciated. If the earlier code is bad too, let me know!
CodePudding user response:
You can extract all the tags and split on space and filter on truthy value and then using Set
get all unique tags.
const notes = document.querySelectorAll('.note');
const tags = [...new Set(
[...notes]
.flatMap(note => note.dataset.tags.split(/\s /g))
.filter(Boolean)
)];
console.log(tags);
<article class="note" data-date="2021-08-04T03:40" data-tags="Tag-One Tag-Two">
<section class="item">Bookmark Description</section>
<section class="tags">
<a href="#" class="tag">Tag-One</a>
<a href="#" class="tag">Tag-Two</a>
</section>
</article>
<article class="note" data-date="2021-08-04T03:40" data-tags="Tag-three Tag-Two">
<section class="item">Bookmark Description</section>
<section class="tags">
<a href="#" class="tag">Tag-One</a>
<a href="#" class="tag">Tag-Two</a>
</section>
</article>
CodePudding user response:
Get all the elements with data-tags
using the attribute select [data-tags]
. Convert to an array using Array.from()
and map it to an array of tags. Flatten the array of arrays by splitting the tags string, and filter empty tags. Use a Set
to dedupe, and convert back to an array using Array.from()
(or array spread).
const notes = document.querySelectorAll('[data-tags]');
const tags = Array.from(
new Set(
Array.from(notes, note => note.dataset.tags.split(/\s /g))
.flat()
.filter(Boolean)
)
)
console.log(tags)
<article class="note"
data-date="2021-08-04T03:40"
data-tags="Tag-One Tag-Two">
</article>
<article class="note"
data-date="2021-08-04T03:40"
data-tags="Tag-Two">
</article>
<article class="note"
data-date="2021-08-04T03:40"
data-tags="">
</article>
<article class="note"
data-date="2021-08-04T03:40">
</article>
To fix your code, only push if there are tags, and then split the tags, and use array spread to add multiple items to the array, instead of a sub-array:
const notes = document.querySelectorAll('.note');
let tags = [];
// get tags
notes.forEach(note =>
note.dataset.tags && tags.push(...note.dataset.tags.split(/\s/g))
);
//Will look like this: tags[] = ("Tag-One Tag-Two")
//remove dupes
tags = tags.filter((value,index,array) => array.indexOf(value) == index);
//no need to filter empties
console.log(tags);
<article class="note"
data-date="2021-08-04T03:40"
data-tags="Tag-One Tag-Two">
</article>
<article class="note"
data-date="2021-08-04T03:40"
data-tags="Tag-Two">
</article>
<article class="note"
data-date="2021-08-04T03:40"
data-tags="">
</article>
<article class="note"
data-date="2021-08-04T03:40">
</article>