Javascript cards arranged in descending order

I'm in the process of developing a sorting feature that arranges cards in the DOM from Z to A with the click of a button. Although I've successfully implemented the logic for sorting the array, I'm facing difficulties in rendering it.

Within my project, I have a Drink Class and a DrinkCard Class where the DrinkCard handles card creation and the Drink manages drinks.

I believe calling the Drink class might assist in rendering the sorted array on the DOM, but I'm unsure of the approach to take. Feeling stuck at the moment.

This is the progress so far: UPDATE Following the suggestion below, I made updates, but rendered-content id isn't available anywhere. Instead, I used querySelector on the class .card, leading to the current error message displayed below.

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()
    }
}

Answer №1

If you're looking to display Drink cards on your website, here are a couple of methods you can consider:

  1. Pass a reference to a DOM node where the cards should be appended
  2. Extract the raw HTML from the card and insert it into an element when needed

To implement method (1), make the following changes:

Update DrinkCard.js as follows:

class DrinkCard {
    constructor(drink, comments) {
         // Your existing code
         this.cardContent = card; // or card.innerHTML
    }
}

Update Drink.js as follows:

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);

   }
}

Lastly, pass the DOM reference to the sorted entries:

const node = document.getElementById('rendered-content'); // Ensure this element exists

sortedArray.forEach(card => card.render(node));

This approach should assist you in rendering the cards according to your requirements.

Updates

The error message you received may have arisen due to the following reasons:

  1. Firstly, the element with id rendered-content might not exist in your DOM
  2. Using .card to append the rendered element could lead to a cyclical error by attempting to add an element (.card) to itself

To address this issue, you can try the following steps:

  1. Include
    <div id="rendered-content"></div>
    somewhere in your HTML where the sorted cards should be displayed
  2. If you prefer not to have it hardcoded in the HTML, create it dynamically before referencing it, like so:
const 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));

These adjustments should help mitigate the errors you encountered.

Further Explanation

A brief overview of browser rendering and its operation in this scenario will be provided, along with a link to a more comprehensive article for additional insights.

In the rendering process, the following stages occur:

  1. The browser retrieves and parses your HTML document
  2. A DOM tree is constructed based on the parsed document
  3. CSS styling is applied to the DOM elements
  4. The content of the DOM is painted onto the screen

Your initial code handled most aspects adequately. You created the cards, added their content, and sorted them based on the Drink type. The only missing step was integrating this into the DOM.

When dynamically generating elements as done in the DrinkCard class, it's crucial to attach them to the existing DOM for visibility. Triggering modifications to the DOM prompts layout adjustment and repainting, ultimately displaying your content on the screen.

The purpose of the div with id='rendered-content' is to act as a container within your DOM or be generated prior to usage. When inserting nodes into the DOM, a valid reference element is required. While document.body could serve as a default reference, using a dedicated container grants more flexibility in presentation options.

For a detailed exploration of browser rendering processes, refer to this informative discussion here. Trust this explanation clarifies any uncertainties you may have.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Building forms within an AngularJS directive

I recently developed an AngularJS directive that includes a form. This form consists of a required text field along with two additional child forms. Each child form also contains a required text field. The distinguishing factor between the two child forms ...

Tips for choosing and deselecting data using jQuery

Is there a way to toggle the selection of data in my code? Currently, when I click on the data it gets selected and a tick image appears. However, I want it so that when I click again on the same data, the tick will disappear. How can I achieve this func ...

Ensuring the script waits for the complete loading of iframe prior to

I'm faced with an issue on the website I'm currently working on. There's a Live Chat plugin integrated on an iframe, and I need to change an image if no agents are available. Interestingly, my code works perfectly fine when tested on the con ...

Iterate through an Array of Objects and exhibit a single object property in HTML one at a time by utilizing Javascript

I am working with an array of objects that contain two properties: "quote" and "author". I have a container div where I want the quotes to be displayed one by one at an interval of every 2 seconds. Currently, it is showing one letter at a time instead of d ...

storing information in localStorage using react-big-calendar

Incorporating react-big-calendar into my project, I encountered a problem where the events in the calendar would disappear upon page refresh despite saving them in localStorage. I had planned to store the events using localStorage and retrieve them later, ...

Just a quick inquiry regarding adding new line characters in JSON to be used in

After encountering an issue with a JSON file in my JavaScript application where it would not print new lines when viewed on the console, I am at a loss for a solution. The contents of my JSON file are as follows: [ { "id": "71046" ...

I'm curious if it's possible to utilize Raspberry Pi GPIO pins within a JavaScript frontend

Is it possible to utilize Raspberry Pi's GPIO pins in Javascript? Specifically, I am interested in reading the values of the Raspberry Pi PIR sensor without having separate Python and Javascript applications. Ideally, I would like a solution that inte ...

The returned state from setState(prev) seems to be in the opposite order when referencing another useState variable within a useEffect

As part of my interactive chat simulation project, I have implemented a feature where users can click on a button named obj4 to start their chat session. Initially, everything functions smoothly, displaying messages 1-4 in the correct order. However, when ...

What is the best way to create subpages within a survey?

If I want to create a survey page on the web with multiple questions, but I am facing a challenge. I do not want to have several different pages and use a "Next Button" that links to another page. I am struggling to come up with ideas on how to implement ...

One way to create a unique JavaScript Module Pattern that retrieves the default value of a

I'm struggling to retrieve the latest private property from a Module, as I keep getting the initial value instead of the most recent one. Upon submission of the form and triggering onSuccess, I assign partnerId = 10. Subsequently, upon clicking an e ...

Determine the index of items within an array of objects in Lodash where a boolean property is set to true

I am new to using lodash after transitioning from C# where I occasionally used LINQ. I have discovered that lodash can be utilized for querying in a LINQ-style manner, but I'm struggling to retrieve the indexes of items in an array of objects with a b ...

Placing a Div wrapper around the contents of two corresponding elements

I am currently attempting to wrap a div with the class name 'wrapped' around two other divs containing innerHTML of 'one' and 'two'. <div class='blk'>one</div> <div class='blk'>two</di ...

Removing the Login button from the layout page after the user has logged in can be achieved by

I am currently developing an MVC application in Visual Studio 2012. Within my layout page, there is a login button that I would like to hide after the user successfully logs in. Despite my attempts, the method I am using doesn't seem to be working. Ca ...

Utilizing conditional statements like if/else and for loops within a switch statement in JavaScript allows for

I am currently developing a request portal that involves dynamic checkboxes, labels, and textboxes that are dependent on an option list. As a beginner in javascript, I am facing challenges with creating conditional statements. I have managed to make progr ...

Passing URL parameters from App.js to routes/index.js

I have a URL that looks like this: http://localhost:3000/?url=test In my Express application's app.js file, I'm attempting to extract the "test" parameter from the URL and pass it to my routes/index.js. Currently, I can easily send a static var ...

Graphic selectors: a new take on radio buttons

I've been attempting to make this work, but it's not functioning correctly. Below is the CSS code: .input_hidden { position: absolute; left: -9999px; } .selected { background-color: #000000; } #carte label { display: inline-bl ...

The request to register at http://localhost:3000/api/auth/register returned a 404 error, indicating that the

I've successfully set up a backend for user registration authentication, which works fine in Postman as I am able to add users to the MongoDB. However, when trying to implement actual authentication on localhost:3000/frontend, I encounter an error dis ...

The collapsible section expands upon loading the page

Trying to implement Disqus as a plugin on my test webpage, I created a collapsible section with a "Show comments" button. The idea was to hide the plugin by default and reveal it only when users click on "Show comments". Despite using the w3schools example ...

Step-by-step guide on utilizing the .change method in order to deactivate a

Can someone help me with a quick solution for this issue? I need to know how to disable a select option once the value has been changed. The option should be available on initial load, but after the user selects it for the first time, it should be disable ...

Unusual behavior of .replace() function observed in Chrome browser

<div> <input type="text" class="allownumericwithdecimal"/>saadad </div> $(".allownumericwithdecimal").live("keypress keyup ", function (event) { $(this).val($(this).val().replace(/[^0-9\.]/g, '')); var text = ...