简体   繁体   中英

How to apply classes to input item using jQuery

I'm building a shopping app using jQuery. Entering text into the text input and hitting enter adds a new item. Hovering over the item and clicking delete will delete the item. And clicking on an item will cross it off.

After I add new list items in the text input, the correct classes are applied, but the jquery functions to delete and cross off items don't work. For instance, the "delete" span should be hidden on page load and appear when you hover over the list items, but for new list items, the delete span always shows. Why are my functions not applying to new items that I add through the input?

HTML:

<div class="wrapper">
    <form  onsubmit="return false"> <!--return false prevents enter from refreshing page -->
        <input type="text" placeholder="I need to buy..." name="shopping_item">
    </form>
    <ul class="instructions">
        <li>Click on tile to mark complete</li>
        <li class="divider">|</li>
        <li>Hover and click “Delete” to delete</li>
    </ul>  
    <section>
        <h1 class="outline">Shopping List Items</h1>
        <ul class="shopping_item_list">
            <li class="tile">Flowers<span class="delete">Delete</span></li>
            <li class="tile middle">A gift card for mom's birthday<span class="delete">Delete</span></li>
            <li class="tile">A birthday card<span class="delete">Delete</span></li>
            <!-- Do I need to have divs to clear? It looks like it works without them. -->
            <li class="tile">Yogurt<span class="delete">Delete</span></li>
            <li class="tile middle">Applesauce<span class="delete">Delete</span></li>
            <li class="tile">Iced tea<span class="delete">Delete</span></li>

            <li class="tile">Ice cream<span class="delete">Delete</span></li>
            <li class="tile middle">Laundry detergent<span class="delete">Delete</span></li>
            <li class="tile">Sandwich bags<span class="delete">Delete</span></li>

        </ul>
    </section>
</div><!--end wrapper-->

CSS:

.shopping_item_list .delete {
display: block;
position: absolute;
bottom: 0;
left: 0;
right:0;
background-color: #000;
padding-top:10px;
padding-bottom: 10px;
font-family: Verdana, sans-serif;
font-size:18px;
text-align: center;
    }

.deleteAction {
background-color:#b7b7b7 !important; 
text-decoration:line-through;
color:#e1e1e1;
    }

JQuery:

$(document).ready(function() {

//add list item
$("input[name='shopping_item']").change(function() {
    var item = $(this).val();   
    $("<li class='tile'>" + item + "<span class='delete'>Delete</span>" + "</li>").prependTo(".shopping_item_list");
    $(".tile").removeClass("middle");   
    $(".shopping_item_list li:nth-child(3n+2)").addClass("middle");
}); 


// hide delete button
$(".delete").hide();


// delete square
$(".tile").hover(function() {
    $(this).find(".delete").toggle();
});

$(".tile").on("click", ".delete", function() {
    $(this).closest("li.tile").remove(); 
    $(".tile").removeClass("middle");   
    $(".shopping_item_list li:nth-child(3n+2)").addClass("middle");
});


// cross off list 
$(".tile").click(function() {
    $(this).toggleClass("deleteAction");
});


}); // end of ready function

When you run code like this:

$(".tile").hover(function() {
   $(this).find(".delete").toggle();
});

It finds all the items on the page with the class tile and installs hover behavior on them.

If you then add a new element to the page with the class tile , it is too late. The code to install hover behavior has already run.

The simple fix to this problem is to use this construct instead:

$(document).on("mouseenter mouseleave", ".tile", function() {
   $(this).find(".delete").toggle();
});

This says to watch for hovering on items in the document with the class tile generally. So it will apply for existing items and items added in the future.

Note that you can do this at any level up the hierarch, so if all your tile elements are always inside the .shopping_item_list , then you can do this instead:

$(".shopping_item_list").on("mouseenter mouseleave", ".tile", function() {
   ...
})

This is more efficient.

Because, for instance, the element you added's related .delete item doesn't exist yet when you run this function that attaches events. So while $(".delete").hide(); works wonderfully on items in the DOM on page load (or $(document).ready() , actually), it can't tell .delete items that don't exist yet what to do. Imagine $(document).ready() as in initial set of instructions in a classroom. If you come to class late, you miss the instructions.

You basically have two options.

1) Utilize Event Bubbling . When an event happens to an item in the DOM it bubbles the event up to other parent items in the DOM. So, when you click a button the 'click' event bubbles up through the DOM and all containing elements, including the <body> are aware that it happened. This turns out to be handy because items that you add to the DOM later still bubble their events up. That means you can attach listeners to an outer element (like <body> ) check which element was the source of the event and then take some action. jQuery, thoughtful library that it has become, includes a simple way to do this via the on method . You could rewrite your \\\\cross off list; method like this:

$('body').on('click', '.tile', function() {
    $(this).toggleClass("deleteAction");
});

2) Wrap everything you currently have within $(document).ready() in a separate named function like function cartInit() and call that function anytime $("input[name='shopping_item']").change() happens. Only, don't do that. Use event bubbling. :)

The issue is that you are attaching events to each individual elements directly and you do this only when the document is ready. That means all subsequent elements that are added later will not have any handlers attached.

To solve this problem you should use event delegation which basically means that you attach handlers to the parent node instead of every individual elements. Events from the children will bubble up to the parent node where you can intercept them. jQuery let's you specify a delegate selector so that you can specify the children of interest.

For example, the following would catch every clicks on .tile elements within the .shopping_item_list container.

$('.shopping_item_list').on('click', '.tile', function () {
    //do something
});

Now, regarding showing/hiding elements on hover I wouldn't use JS for that. CSS is powerful enough to express that behaviour. For example, if you want to show the .delete element only when a .tile element is being hovered you could do.

.tile .delete {
    display: none;
}

.tile:hover .delete {
    display: block;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM