Reputation: 57
I am trying to create a sorting method that sorts the cards in the DOM from Z to A when I press a button. So far, I have created the logic to sort the array correctly but can't seem to render it out.
I have a Drink Class and a DrinkCard Class, and the DrinkCard does the actual card creation, and the Drink creates the Drink.
I feel like calling the Drink class would help render sorted array to the DOM, but not sure how I would do that. Drawing blanks.
This is what I have so far
UPDATE I updated with the suggestion below, but I don't have a rendered-content
id anywhere. So, I used the querySelector
on the class .card
and this is the current error.
Uncaught DOMException: Failed to execute 'appendChild' on 'Node': The new child element contains the parent.
at Drink.render (file:///Users/austinredmond/dev/caffeine_me/frontend/src/models/drink.js:28:17)
at file:///Users/austinredmond/dev/caffeine_me/frontend/src/index.js:43:38
at Array.forEach (<anonymous>)
at HTMLInputElement.<anonymous> (file:///Users/austinredmond/dev/caffeine_me/frontend/src/index.js:43:17)
render @ drink.js:28
(anonymous) @ index.js:43
(anonymous) @ index.js:43
sortDesc.addEventListener("click", () => {
const sortedArray = allDrinks.sort((a, b) => {
const nameA = a.name.toLowerCase(),
nameB = b.name.toLowerCase()
if (nameA < nameB) //sort string ascending
return 1
if (nameA > nameB)
return -1
return 0 //default return value (no sorting)
})
const node = document.querySelector('.card');
sortedArray.forEach(card => card.render(node));
})
Drink Class
class Drink {
constructor(data) {
// Assign Attributes //
this.id = data.id
this.name = data.name
this.caffeine = data.caffeine
this.comments = []
this.card = new DrinkCard(this, this.comments)
}
// Searches allDrinks Array and finds drink by id //
static findById(id) {
return allDrinks.find(drink => drink.id === id)
}
// Delete function to Delete from API //
delete = () => {
api.deleteDrink(this.id)
delete this
}
render(element) {
// this method will render each card; el is a reference to a DOM node
console.log(element)
element.appendChild(this.card.cardContent);
}
}
DrinkCard Class
class DrinkCard {
constructor(drink, comments) {
// Create Card //
const card = document.createElement('div')
card.setAttribute("class", "card w-50")
main.append(card)
card.className = 'card'
// Add Nameplate //
const drinkTag = document.createElement('h3')
drinkTag.innerText = drink.name
card.append(drinkTag)
// Add CaffeinePlate //
const caffeineTag = document.createElement('p')
caffeineTag.innerText = `Caffeine Amount - ${drink.caffeine}`
card.append(caffeineTag)
// Adds Create Comment Input Field //
const commentInput = document.createElement("input");
commentInput.setAttribute("type", "text");
commentInput.setAttribute("class", "input-group mb-3")
commentInput.setAttribute("id", `commentInput-${drink.id}`)
commentInput.setAttribute("placeholder", "Enter A Comment")
card.append(commentInput);
// Adds Create Comment Button //
const addCommentButton = document.createElement('button')
addCommentButton.innerText = "Add Comment"
addCommentButton.setAttribute("class", "btn btn-primary btn-sm")
card.append(addCommentButton)
addCommentButton.addEventListener("click", () => this.handleAddComment())
// Add Comment List //
this.commentList = document.createElement('ul')
card.append(this.commentList)
comments.forEach(comment => this.addCommentLi(comment))
// Create Delete Drink Button
const addDeleteButton = document.createElement('button')
addDeleteButton.setAttribute("class", "btn btn-danger btn-sm")
addDeleteButton.innerText = 'Delete Drink'
card.append(addDeleteButton)
addDeleteButton.addEventListener("click", () => this.handleDeleteDrink(drink, card))
// Connects to Drink //
this.drink = drink
this.cardContent = card;
}
// Helpers //
addCommentLi = comment => {
// Create Li //
const li = document.createElement('li')
this.commentList.append(li)
li.innerText = `${comment.summary}`
// Create Delete Button
const button = document.createElement('button')
button.setAttribute("class", "btn btn-link btn-sm")
button.innerText = 'Delete'
li.append(button)
button.addEventListener("click", () => this.handleDeleteComment(comment, li))
}
// Event Handlers //
// Handle Adding Comment to the DOM //
handleAddComment = () => {
const commentInput = document.getElementById(`commentInput-${this.drink.id}`)
api.addComment(this.drink.id, commentInput.value)
.then(comment => {
commentInput.value = ""
const newComment = new Comment(comment)
Drink.findById(newComment.drinkId).comments.push(newComment)
this.addCommentLi(newComment)
})
}
// Loads last comment created for drink object //
handleLoadComment = () => {
const newComment = this.drink.comments[this.drink.comments.length - 1]
this.addCommentLi(newComment)
}
// Deletes Comment from API and Removes li //
handleDeleteComment = (comment, li) => {
comment.delete()
li.remove()
}
// Deletes Drink from API and Removes drink card //
handleDeleteDrink = (drink, card) => {
drink.delete()
card.remove()
}
}
Upvotes: 3
Views: 1636
Reputation: 2702
There are a few ways you can do this:
For (1), try the following changes:
DrinkCard.js
class DrinkCard {
constructor(drink, comments) {
// your existing code
this.cardContent = card; // or card.innerHTML
}
}
Drink.js
class Drink {
// your existing code
render(el) {
// this method will render each card; el is a reference to a DOM node
el.appendChild(this.card.cardContent);
}
}
Finally, passing the DOM reference to the sorted entries:
const node = document.getElementById('rendered-content'); // make sure this exists
sortedArray.forEach(card => card.render(node));
Hopefully that will give you some pointers on how to render the cards for your purpose.
Updates
The error you are getting is because of the following reasons:
id
rendered-content
does not exist in your DOM.card
to append the rendered element results in a cyclical error because you are trying to append an element (.card
) to the same element.You can try the following:
<div id="rendered-content"></div>
in your HTML somewhere the sorted cards needs to be renderedconst rc = document.createElement('div');
rc.setAttribute('id', 'rendered-content');
document.body.appendChild(rc);
const node = document.getElementById('rendered-content');
sortedArray.forEach(card => card.render(node));
This should help get rid of the errors hopefully.
Further Explanation
I am going to give you a very brief description of browser rendering and how it's working in this case. I will also leave a link for a detailed article that goes into more depth.
In the rendering flow, the following happens:
Your code took care of almost everything in the original post. You created the card, added it's content and sorted the cards based on Drink type. The only step missing was adding it all to the DOM.
If you create elements dynamically like you did in the DrinkCard
class, you need to attach it to your existing DOM. Otherwise, there is no way for the browser to know your card is in the DOM. Once you modify the DOM, layout and repainting is triggered which then shows your content on the display.
The purpose of div
with id='rendered-content'
is to provide a container that exists in your DOM or is added before you use it. When you are adding nodes to your DOM, you need a reference element where you want to add your new node. This reference could easily be document.body
. In that case, the render
method will add the card at the bottom of your body
in your DOM. Providing a separate container in this case gives you more control on how you can display this container.
Here's an in depth discussion of rendering and how it works in the browser here. Hope the explanation answers your question.
Upvotes: 2