Stefan
Stefan

Reputation: 259

jQuery plugin and append

I have written a 'plugin' for add & edit items to a list. Adding and editing is working fine for existing items, but it doesn't work for a new item which is added via .before(). I make use of the following script, which isn't optimal I think?

<div id="test">
  <div class="item">Test</div>
  <div class="add">Add item</div>
</div>

<script>
(function ($) {

    $.fn.pim = function() {
        $(this).click(function() {

            if ($(this).hasClass('add')) {
                $($(this)).before('<div class="item">Test</div>');
            } else {
                alert('Edit succes');
            }
        });

    }

}(jQuery));
$("#test div").pim();
</script>

Upvotes: 0

Views: 1786

Answers (3)

Chofoteddy
Chofoteddy

Reputation: 747

What happens is that .on() function allows you to delegate the event. While .click() it only takes elements created.

Solution

Delegate the event to all child nodes, created and creating. Without loading the plugin for every element created.

jQuery .on( events [, selector ] [, data ], handler ): A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element.

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="test">
  <div class="item">Test</div>
  <div class="add">Add item</div>
</div>

<script>
(function($){
    $.fn.pim = function() {
        $(this).on('click', '>', function(event) {
            var $target = $(event.target);
    
            if ($target.hasClass('add')) {
                $target.before('<div class="item">Test</div>');
            } else {
                alert('Edit succes');
            }
        });

    }    
})(jQuery);
$("#test").pim();
</script>

Said that; You can modify the > to determine which elements want to delegate the event. In this example, it takes all direct children of "#test".

Upvotes: 1

War10ck
War10ck

Reputation: 12508

I think this achieves the desired effect:

(function ($) {

    $.fn.pim = function() {
        this.click(function() {
            if ($(this).hasClass('add')) {
                $(this).before($('<div />', { "class": "item", "text": "Test" }).pim());
            } else {
                alert('Edit succes');
            }
        });
        return this;
    }

}(jQuery));
$("#test div").pim();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="test">
  <div class="item">Test</div>
  <div class="add">Add item</div>
</div>

Explanation:

I've simplified a few things here. First the inital $(this).click can be simplified to this.click. Inside the $.fn.* namespace, this takes the context of the current jQuery instance so there is no need to create a new instance of it. However, inside the callback you will need to leave it as $(this) since it will be executing outside the $.fn.* scope.

In addition, I've changed the .before() to flow with the correct method implementation. When using .before() the html or jQuery object passed in is inserted before matched set of elements. In this case, the <div> that we're generating is added before $(this).

Next, you're event is not setup to be delegated. Therefore, when you create a new .item element, you'll need to call .pim() on the new element to ensure an event handler is created and attached to the new element. This will allow it to function properly when inserted in the DOM. Note that you don't want to call it on the full set (i.e. $('#test div') again because you'll end up attaching multiple event handlers to the already existing elements.

Finally, to allow your plugin to flow in the standard jQuery fashion, I've added return this; at the end of your function to allow your plugin to be changed with existing functions. The return this; statement will return the jQuery object of matched elements allowing for further chain methods to be executed. To show the effect of method chaining, see these two examples:

  1. JSFiddle - in this example, adding .css('background', 'red') results in a console error. Select F12 to open the browser's console and see this error. The method chaining ability of jQuery is broken because the jQuery object is not returned after the operation is complete. The error in the console will be:

    Uncaught TypeError: Cannot read property 'css' of undefined

  2. JSFiddle - in this example, adding the return this; as the last statement and then calling the additional .css('background', 'red') function results in the new item added with a red background. This demonstrates how the proper plugin setup can allowed for continued manipulation after it's function contents have been executed and completed.

I know this wasn't part of your original question. However, this is the optimal way that you want your plugins to perform so I figured it was worth mentioning.

Upvotes: 0

DZanella
DZanella

Reputation: 343

Try:

(function ($) {

    $.fn.pim = function() {
        $(this).click(function() {

            if ($(this).hasClass('add')) {
                $($(this)).before('<div class="item">Test</div>');
                $("#test div").pim();
            } else {
                alert('Edit succes');
            }
        });

    }

}(jQuery));
$("#test div").pim();

Upvotes: 0

Related Questions