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

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)
 
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);
 
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);
 
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 ;) )
 
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.
 
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:
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...
 
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 );
            });            
        });
    });
 
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.
 
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);

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

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


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:
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

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.

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);
 
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.
 
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.webp
 
Top Bottom