1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

XenForo JavaScript - how to re-add a class listener for dynamic content?

Discussion in 'XenForo Development Discussions' started by tenants, Aug 26, 2013.

  1. tenants

    tenants Well-Known Member

    If I create content dynamically, how can I re-add a class listener

    For instance, if my page has

    Code:
    <a class="SomeClass">click me</a>
    <div>more</div>
    
    And my external script uses
    Code:
    !function($, window, document, _undefined) {
        XenForo.SomeClass = function($lnk){
            $lnk.bind('click', function() {
                doSomething();
                return false;
            });  
        }
        XenForo.register('a.SomeClass', 'XenForo.SomeClass');
    }(jQuery, this, document);
    
    Then the click event is bound to all links on that page with the class "SomeClass" .... that works all well and good.

    But if I now dynamically add <a class="SomeClass">click me2</a> so that my page is now:

    Code:
    <a class="SomeClass">click me</a>
    <div>more</div>
    <a class="SomeClass">click me2</a>
    
    The binding will only occur on the first link (the link that is not dynamically created) and not the second. How can I re bind my class event listener on content that is created dynamically (or all content)?

    (I think this is a scope issue)
     
  2. Chris D

    Chris D XenForo Developer Staff Member

    I have had this issue before.

    You need to use the jQuery on function (previously was the jQuery live function but that is now deprecated).

    This should work:

    Code:
    !function($, window, document, _undefined)
    {
        XenForo.SomeClass = function($lnk)
        {
            $lnk.on('click', function(event)
            {
                event.preventDefault();
                doSomething();
            });
        }
        XenForo.register('a.SomeClass', 'XenForo.SomeClass');
    }
    (jQuery, this, document);
    
     
    tenants likes this.
  3. Chris D

    Chris D XenForo Developer Staff Member

    Actually, I think my example is flawed.

    If the HTML was:
    HTML:
    <div class="SomeContainer">    
        <a class="SomeClass">click me</a>
        <div>more</div>
    </div>
    I think this example is more accurate:
    Code:
    !function($, window, document, _undefined)
    {
        XenForo.SomeContainer = function($container)
        {
            $('.SomeContainer a.SomeClass').on('click', '.SomeClass', function(event)
            {
                event.preventDefault();
                doSomething();
            });
        }
        XenForo.register('.SomeContainer', 'XenForo.SomeContainer');
    }
    (jQuery, this, document);
     
  4. Chris D

    Chris D XenForo Developer Staff Member

    Hehe, actually that might be a bit wrong too... but basically the jQuery "on" function is what's needed.

    http://api.jquery.com/on/
     
  5. tenants

    tenants Well-Known Member

    It would be nice if I could use .on (or even .live), but alas

    .on() was not added to jquery until 1.7 (a default set up of xf still uses jquery-1.10.1)
    .live was added in 1.3 (and has performance issues)

    For developing add-ons (where you don't want to ask users to "update their jquery", user setup variations may mean they can't upgrade to a later jquery), so using either method is not ideal

    I don't want to bloat it with an addition jquery

    I have to use a call back and re-add the class listener, but the xf scope means I can't do this locally (I think).

    (btw, a.SomeClass is fine... and I know what you meant ;) )
     
  6. Chris D

    Chris D XenForo Developer Staff Member

    XenForo 1.2 now uses jQuery 1.10 (ten) which was released after jQuery 1.7 (seven).

    1.10.0: http://blog.jqueryui.com/2013/01/jquery-ui-1-10-0/
    1.7: http://blog.jquery.com/2011/11/03/jquery-1-7-released/

    The only way that using .on would be an issue is if you want to retain backwards compatibility with XenForo 1.1 which uses jQuery 1.5. (I do not recommend making special allowances for backwards compatibility any more).

    So you're perfectly fine to use .on because that was introduced in 2011 and our current jQuery version was released just in January 2013.
     
    tenants likes this.
  7. tenants

    tenants Well-Known Member

    ha.. okay, hmm (clearly I can't read version numbers and mistakenly thought 1.10 was 1.1).
    Then there is something else going on with my script...hmmm

    .on() is working for my content (but not my dynamically added content)

    also, how would you re-add the Avatar members card class listener (or does this already use .on() )
     
    Last edited: Aug 26, 2013
  8. xf_phantom

    xf_phantom Well-Known Member

    How do you add the new content?

    if you use the xf methods, xf will register everything for you again:)
     
  9. Chris D

    Chris D XenForo Developer Staff Member

    Let me look through some of my code... I'm sure I've had to deal with this before... And to my knowledge I didn't update the code for XenForo 1.2 so perhaps I didn't use .on after all... but it's in my bookmarks so I must have marked it as relevant for some reason...

    I will see what I have. I think I used it in my gallery...
     
    tenants likes this.
  10. tenants

    tenants Well-Known Member

    To append the new data, I make use of 2 scripts, cutting it down and simplifying this is what I do:

    my external js:

    Code:
    !function($, window, document, _undefined) {
        XenForo.SomeClass = function($lnk){
            $lnk.on('click', function() {
                doSomething();
                return false;
            });
        }
        XenForo.register('a.SomeClass', 'XenForo.SomeClass');
    }(jQuery, this, document);
    
    my local template js (makes use of masonry and infinite scroll)

    Code:
        $(document).ready(function(){
            var $container = $('#xzInvis_a').masonry();
            $('.box_a').css({"position":"absolute" }).css({"left":"0px" });
            $container.css({"visibility":"hidden"}).imagesLoaded(function(){          
                $('#imgloading_a').hide('slow', function(){
                    $container.css({"visibility":"visible"}).masonry({
                        itemSelector: '.box_a',columnWidth: 0, isFitWidth: true,
                        isAnimated: !Modernizr.csstransitions,
                        animationOptions: {duration: 750, easing: 'linear', queue: true}
                    });    }); });
            $container.infinitescroll({
                 navSelector  : '#page-nav', nextSelector : '#page-nav a', itemSelector : '.box_a',   
                },
            function( newElements ){
                var $newElems = $( newElements ).css({ opacity: 0 });
               $newElems.imagesLoaded(function(){
                    $newElems.animate({ opacity: 1 });
                    $container.masonry( 'appended', $newElems, true );
                });            
            });
        });
    
     
  11. Chris D

    Chris D XenForo Developer Staff Member

    Heh, turns out I've been using .delegate which was fine... seems like I do need to do a bit of updating after all as .delegate has been superseded by on.

    This is what some of my code looks like:

    Code:
    $('#VideoPreviewArea').on('click', '.titleContainer input.DeleteVideo', function(e)
    {
        // code
    });   
    So in my code, a video is added to a container #VideoPreviewArea. Each video contains an input[type=button] with a class of DeleteVideo.

    To ensure I could bind a click event to the button I use .on on the container and listen for a click on input.DeleteVideo.

    That works fine for anything dynamically added.
     
    tenants likes this.
  12. Chris D

    Chris D XenForo Developer Staff Member

    Try something like one of my examples last night. Use the .on function on some sort of arbitrary container that contains the elements which will be dynamically added.

    My example last night was broken, something like this should work:

    HTML:
    <div class="SomeContainer">
        <a class="SomeClass">click me</a>
        <div>more</div>
    </div>
    Code:
    !function($, window, document, _undefined)
    {
        XenForo.SomeContainer = function($container)
        {
            $container.on('click', 'a.SomeClass', function(event)
            {
                event.preventDefault();
                doSomething();
            });
        }
        XenForo.register('.SomeContainer', 'XenForo.SomeContainer');
    }
    (jQuery, this, document);
     
    tenants likes this.
  13. tenants

    tenants Well-Known Member


    Yes, it should work. But the on event only gets triggerd for the existing data (not the dynamically added data), so there must be something strange witht he way it's added... I just can't see it.

    I've even tried moving the local script above into the external !function($, window, document, _undefined)
    (since I thought it might be a scope issue) and I still get the same thing. The dynamical added data classes are not listened for (only the existing data is listened for)
     
    Last edited: Aug 26, 2013
  14. tenants

    tenants Well-Known Member

    hang on, It might be that I'm not adding the .on() to the pre-existing container (but adding it to classes)
    I take it in your example, the container can just be body... okay, that might be the issue

     
  15. Chris D

    Chris D XenForo Developer Staff Member

    Not just that. But your code is:

    Code:
    $lnk.on('click', function() { doSomething(); return false; });
    My example includes an additional parameter. The selectors for the binding event:

    Code:
    $container.on('click', 'a.SomeClass', function(event)
    My code binds the click event to a.SomeClass within the $container. That's any existing a.SomeClass and any a.SomeClass that is added in the future.
     
    batpool52! and tenants like this.
  16. tenants

    tenants Well-Known Member

    yeah, listening to the container (rather than the classes) was key when using on() :)

    Chris, you nailed it my friend :) Works a treat!


    upload_2013-8-26_14-33-28.png
     
    Chris D likes this.

Share This Page