JavaScript Toggle Problem

I’ve got these Bootstrap 5 cards I’m tying to use as selector objects in a user tags viewer. I need them to toggle when clicked. My click events are firing successively even though there are two different classes. I’m not sure why. I know how it’s doing it in a sense, but I’m not sure how to stop it.

(document).addEventListener("click", function(e) {


    //
    //	Handle toggle clicks for tags in tag viewer
    //

    if (e.target.matches(".tag-list-item") || e.target.closest(".tag-list-item")) {

        // add the token to tagsObj

        var token = e.target.parentElement.getAttribute("data-token");
        var tag = e.target.parentElement.getAttribute("data-tag");
        var operation = e.target.parentElement.getAttribute("data-operation");

        if (token !== null && tag !== null) {

            if (!tagsObj.contains(token) && operation == "tag-list-item") {

                tagsObj.add(token);

                e.target.parentElement.classList.remove("tag-list-item");
                e.target.parentElement.classList.add("tag-list-item-selected");
                e.target.parentElement.setAttribute("data-operation", 'tag-list-item-selected');

                while (e.target.parentElement.firstChild.nextSibling.firstChild) {

                    e.target.parentElement.firstChild.nextSibling.firstChild.remove();

                }


                // create selected content

                e.target.classList.add("card-body");
                e.target.classList.add("d-flex");
                e.target.classList.add("align-items-center");

                var checkDiv = document.createElement("div");
                checkDiv.classList.add("tag-list-item-checkbox");

                var checkI = document.createElement("i");
                checkI.classList.add("icofont-checked")

                checkDiv.append(checkI);

                var tagDiv = document.createElement("div");
                tagDiv.append(tag);

                e.target.append(checkDiv);
                e.target.append(tagDiv);


            }

        }

    }

    if (e.target.matches(".tag-list-item-selected") || e.target.closest(".tag-list-item-selected")) {

        var token = e.target.parentElement.getAttribute("data-token");
        var tag = e.target.parentElement.getAttribute("data-tag");
        var operation = e.target.parentElement.getAttribute("data-operation");

        if (token !== null && tag !== null) {

            if (tagsObj.contains(token) && operation == 'tag-list-item-selected') {


                // the parent element (card wrapper)

                e.target.parentElement.classList.remove("tag-list-item-selected");
                e.target.parentElement.classList.add("tag-list-item");
                e.target.parentElement.setAttribute("data-operation", 'tag-list-item');

                e.target.classList.remove("d-flex");
                e.target.classList.remove("align-items-center");

                // Remove elements

                while (e.target.parentElement.firstChild.nextSibling.firstChild) {

                    e.target.parentElement.firstChild.nextSibling.firstChild.remove();

                }


                e.target.append(tag);

                // remove the item from tagsObj

                tagsObj.remove(token);

            }


        }

    }


});

Best guess without seeing the markup is that you’ve got BOTH .tag-list-item and .tag-list-item-selected on the same element, so it’s hitting both.

i.e.

<li class="tag-list-item tag-list-item-selected">test</li>

I’ll show you some code. I don’t think that’s the issue. The JavaScript is updated too. I fixed the problem on page load. But when I do a tag search in the modal it doesn’t work on results.

Here is the html before and after a search:

// unselected in modal before search (inserted with php)
<div class="card tag-list-item" data-token="17db77789e81cb9f5ae0cdfe286d66f8" data-tag="Lo-Fi (Hip-Hop)">
   <div class="card-body tag-text-output">Lo-Fi (Hip-Hop)</div>
</div>
// selected in modal before search (inserted with php)
<div class="card tag-list-item-selected" data-token="c5fdc52d79e436f5374a48506c492865" data-tag="Boom Bap (Hip-Hop)">
   <div class="card-body d-flex align-items-center">
      <div class="tag-list-item-checkbox"><i class="icofont-checked"></i></div>
      <div class="tag-text-output">Boom Bap (Hip-Hop)</div>
   </div>
</div>
// unselected after search (inserted with JavaScript)
<div class="card tag-list-item" data-token="17db77789e81cb9f5ae0cdfe286d66f8" data-tag="Lo-Fi (Hip-Hop)">
   <div class="card-body">Lo-Fi (Hip-Hop)</div>
</div>
// selected after search (inserted with JavaScript)
<div class="card tag-list-item-selected" data-token="c5fdc52d79e436f5374a48506c492865" data-tag="Boom Bap (Hip-Hop)">
   <div class="card-body d-flex align-items-center">
      <div class="tag-list-item-checkbox"><i class="icofont-checked"></i></div>
      <div>Boom Bap (Hip-Hop)</div>
   </div>
</div>

This is how the items are being put in with php:

if(!empty($hasTag))
						{
							
							echo '<div class="card tag-list-item-selected" data-token="'.$val['token'][0]->__toString().'" data-tag="'.$bbSanitize->unsanitizeXML($val['tag'][0]->__toString()).'" >
								  <div class="card-body d-flex align-items-center">
										<div class="tag-list-item-checkbox"><i class="icofont-checked"></i></div>
										<div class="tag-text-output">'.$bbSanitize->unsanitizeXML($val['tag'][0]->__toString()).'</div>
								  </div>
								</div>';
							
						}else{
							
							echo '<div class="card tag-list-item" data-token="'.$val['token'][0]->__toString().'" data-tag="'.$val[0]['tag']->__toString().'">
									  <div class="card-body tag-text-output">
										' .$bbSanitize->unsanitizeXML($val['tag'][0]->__toString()).'
									  </div>
								  </div>';
							
						}

All the JavaScript

//
//	Handle toggle clicks for tags in tag viewer
//
var skipTagListItemSelected = false;

if (e.target.matches(".tag-list-item") || e.target.closest(".tag-list-item")) {

    skipTagListItemSelected = true;
    // add the token to tagsObj

    var token = e.target.parentElement.getAttribute("data-token");
    var tag = e.target.parentElement.getAttribute("data-tag");

    if (token !== null && tag !== null) {

        if (!tagsObj.contains(token)) {
            console.log("fired: tag-list-item");
            tagsObj.add(token);

            e.target.parentElement.classList.remove("tag-list-item");
            e.target.parentElement.classList.add("tag-list-item-selected");

            while (e.target.parentElement.firstChild.nextSibling.firstChild) {

                e.target.parentElement.firstChild.nextSibling.firstChild.remove();

            }


            // create selected content

            e.target.classList.add("card-body");
            e.target.classList.add("d-flex");
            e.target.classList.add("align-items-center");

            var checkDiv = document.createElement("div");
            checkDiv.classList.add("tag-list-item-checkbox");

            var checkI = document.createElement("i");
            checkI.classList.add("icofont-checked")

            checkDiv.append(checkI);

            var tagDiv = document.createElement("div");
            tagDiv.append(tag);

            e.target.append(checkDiv);
            e.target.append(tagDiv);


        }

    }

}

if (e.target.matches(".tag-list-item-selected") || e.target.closest(".tag-list-item-selected")) {

    var token = e.target.parentElement.getAttribute("data-token");
    var tag = e.target.parentElement.getAttribute("data-tag");

    if (token !== null && tag !== null) {

        if (tagsObj.contains(token) && !skipTagListItemSelected) {

            console.log("fired: tag-list-item-selected");
            // the parent element (card wrapper)

            e.target.parentElement.classList.remove("tag-list-item-selected");
            e.target.parentElement.classList.add("tag-list-item");

            e.target.classList.remove("d-flex");
            e.target.classList.remove("align-items-center");

            // Remove elements

            while (e.target.parentElement.firstChild.nextSibling.firstChild) {

                e.target.parentElement.firstChild.nextSibling.firstChild.remove();

            }


            e.target.append(tag);

            // remove the item from tagsObj

            tagsObj.remove(token);

        }


        skipTagListItemSelected = false;

    }

}

if (e.target.matches(".tag-list-item-checkbox") || e.target.closest(".tag-list-item-checkbox")) {

    e.target.parentElement.parentElement.click()

}

if (e.target.matches(".tag-text-output") || e.target.closest(".tag-text-output")) {

    e.target.parentElement.click();

}

// update tags via Tags Viewer modal
if (e.target.matches("#updateTagsModalButton") || e.target.closest("#updateTagsModalButton")) {
    skipTagListItemSelected = false;
    var tagsData = tagsObj.tags;
    var userToken = document.querySelector("#userTokenElement").getAttribute("data-user-token");

    // send tags to server for update
    $.ajax({
        url: '../php/scripts/update_user_tags.php',
        dataType: 'json',
        method: 'POST',
        contentType: 'application/json; charset=utf-8',
        data: {
            tags: tagsData,
            userToken: userToken
        },
        success: function(data, textStatus, jQxhr) {

            if (data.hasOwnProperty("message")) {

                switch (data.message) {

                    case "success":
                        // tags have been updated
                        break;

                }

            }

        },
        error: function(jqXhr, textStatus, errorThrown) {
            console.log(errorThrown);
        }
    });


}

if (e.target.matches("#tagSearchSubmit") || e.target.closest("#tagSearchSubmit")) {

    skipTagListItemSelected = false;
    var searchField = document.querySelector("#tagsSearchInput").value;

    $.ajax({
        url: '../php/scripts/tags_viewer_search.php',
        dataType: 'json',
        method: 'POST',
        contentType: 'application/json; charset=utf-8',
        data: {
            search: searchField
        },
        success: function(data, textStatus, jQxhr) {
            if (data.hasOwnProperty("message")) {

                switch (data.message) {

                    case "success":
                        // clear tags viewer
                        while (document.querySelector("#tagsViewerWrapper").firstChild) {

                            document.querySelector("#tagsViewerWrapper").lastChild.remove();

                        }

                        ///
                        // Music Genres
                        ///

                        // create header

                        var headerDiv = document.createElement("h4");
                        headerDiv.classList.add("tag-viewer-header");
                        headerDiv.innerHTML = "Music Genres";

                        document.querySelector("#tagsViewerWrapper").append(headerDiv);

                        console.log(data.results);

                        for (var x = 0; x <= data.results['music genre'].length; x++) {

                            if (data.results['music genre'][x]) {

                                // create the elements for showing music genre tags
                                // has the tag

                                if (data.results['music genre'][x].has == 'yes') {

                                    var card = document.createElement("div");
                                    card.classList.add("card");
                                    card.classList.add("tag-list-item-selected");
                                    card.setAttribute("data-token", data.results['music genre'][x].token);
                                    card.setAttribute("data-tag", data.results['music genre'][x].tag);

                                    var cardBody = document.createElement("div");
                                    cardBody.classList.add("card-body");
                                    cardBody.classList.add("d-flex");
                                    cardBody.classList.add("align-items-center");

                                    var checkDiv = document.createElement("div");
                                    checkDiv.classList.add("tag-list-item-checkbox");

                                    var i = document.createElement("i");
                                    i.classList.add("icofont-checked");

                                    var tagText = document.createElement("div");
                                    tagText.innerHTML = data.results['music genre'][x].tag;

                                    checkDiv.append(i);
                                    cardBody.append(checkDiv);
                                    cardBody.append(tagText);
                                    card.append(cardBody);

                                    document.querySelector("#tagsViewerWrapper").append(card);

                                } else {

                                    // create the elements for showing music genre tags
                                    // does not have the tag

                                    var card = document.createElement("div");
                                    card.classList.add("card");
                                    card.classList.add("tag-list-item");
                                    card.setAttribute("data-token", data.results['music genre'][x].token);
                                    card.setAttribute("data-tag", data.results['music genre'][x].tag);

                                    var cardBody = document.createElement("div");
                                    cardBody.classList.add("card-body");
                                    cardBody.innerHTML = data.results['music genre'][x].tag;

                                    card.append(cardBody);
                                    document.querySelector("#tagsViewerWrapper").append(card);

                                }
                            }

                        }

                        // populate with categories

                        // create tags based on search results
                        break;

                }

            }
        },
        error: function(jqXhr, textStatus, errorThrown) {
            console.log(errorThrown);
        }
    });


}

When not doing a search console.log(e.target); in tag-list-item shows the html for a selected tag:

<div class="card-body tag-text-output d-flex align-items-center">
	<div class="tag-list-item-checkbox"><i class="icofont-checked"></i></div>
	<div>Industrial Techno</div>
</div>

But, If I test e.target classList for d-flex it doesn’t output, so it might still be selecting the right element.

I’m getting error, cannot read properties of null .firstChild. In that while loop condition. So for some reason that method of clearing the .card div won’t work after doing a tag search. A work around would be to add an extra class to the content coming from JS and check for it, clear the DIV from a different nextSibling location… but why?

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.