Reputation: 438
I'm making Add to favorite function. If I click an icon, breed next to the icon will be added to likes array. I made renderLikes function to make the array appear on the screen. However only one breed shows up probably because elements.favorite.innerHTML = '' part. If I don't write that part, every time I click an icon the entire array is keep on added to DOM tree. Is there any way that I can make render each item in the array like
item
item2
... ?Another question, if I click on an icon sometimes it adds an empty element to the array. How do I prevent this problem?
<!DOCTYPE html>
<html lang="en">
<head>
<style>
ul {
list-style: none;
line-height: 1.6;
}
.icon {
display: inline-block;
width: 1em;
height: 1em;
stroke-width: 0;
stroke: currentColor;
fill: currentColor;
vertical-align: middle;
cursor: pointer;
transition: transform .3s;
transform: scale(0.8);
}
.icon:hover {
transform: scale(1.6) rotate(-15deg);
}
.icon-fill {
fill: red;
}
</style>
<title>Like Button Practice</title>
</head>
<body>
<ul class="list"></ul>
<h2>My Favorite Dogs</h2>
<div class="favorite">
</div>
<script>
const data = [
{
"breed": "Beagle",
"characteristics": "playful"
},
{
"breed": "Golden Retriever",
"characteristics": "calm"
},
{
"breed": "Corgi",
"characteristics": "bright"
},
{
"breed": "Goldendoodle",
"characteristics": "gentle"
},
{
"breed": "Labrador Retriever",
"characteristics": "loyal"
},
]
const elements = {
"icon": document.querySelectorAll('svg'),
"dom": document.querySelector('.list'),
"favorite": document.querySelector('.favorite')
}
const likes = [];
// Render method
function test() {
data.map(item => {
renderList(item.breed);
return item.breed;
})
};
test();
function renderList(dog) {
const markup = `
<li id="${dog}">
<svg class="icon icon-heart-outlined"><use xlink:href="icon/sprite.svg#icon-heart-outlined"></use></svg>
${dog}
</li>
`;
elements.dom.insertAdjacentHTML('beforeend', markup);
}
function renderLikes(item) {
const markup = `
<p>${item}</p>
`;
elements.favorite.innerHTML = '';
elements.favorite.insertAdjacentHTML('beforeend', markup);
}
elements.dom.addEventListener('click', e => {
const id = e.target.parentNode.id;
if (e.target.matches('.icon, .icon *')) {
// If (Beagle, Golden Retriever... does not exist in likes array, it returns -1)
const index = likes.indexOf(e.target.parentNode.id);
if (index === -1) {
likes.push(id);
console.log(likes);
likes.forEach(function( value ) {
console.log( value );
renderLikes(value);
});
} else {
likes.splice(index, 1);
}
}
});
</script>
</body>
</html>
Upvotes: 1
Views: 105
Reputation: 669
Yep, the innerHTML = '' is deleting the whole content.
Anyways you should work with your data and present it, try to not couple your data to the view that much.
I'll explain it...
You are doing this:
By doing that you have to manage two different logics at once, the render logic (to paint something next to something) and the data logic itself (to add or remove the item from the list). This leads to inconsistencies. You should manage the data and the render logic should present the data.
I'd do it like this.
By doing this you are always sure that your render is consistent with the model you are working with. After making it work you can go for performance tweaks like virtual dom, differential rendering, etc. But consistency comes first.
My code would look like this:
const $likelist = document.querySelector('.likelist');
const likeTemplate = i => `<p>Whatever ... ${i} blah blah </p>`;
const renderLikes = () => $likelist.innerHTML = likes.map(i => likeTemplate(i)).join();
const addToLikelist = (el) => {
// Validate the item is valid and is not in the list yet
likes.push(el);
renderLikes();
}
Upvotes: 1