XenForo.register and future elements

Rasmus Vind

Well-known member
I was under the impression that XenForo.register(selector, fn) would run fn every time an object matching selector would appear. I wrote a little sample of how I thought it would work:

Code:
XenForo.MyButton = function($ele) {
    return $ele.bind('click', function() {
      var $template;
      $template = $($ele[0].outerHTML);
      return $template.xfInsert('replaceAll', $ele, 'slideDown', XenForo.speed.fast);
   });
};

I have this in my template:

Code:
<div class="MyButton">The button</div>

So I was expecting "The button" to be clickable each time but apparently it only works the first time. I do get something like this in the console:
Code:
XenForo.activate([div.MyButton.__XenForoActivator, prevObject: e.fn.init[1], context: undefined, jquery: "1.11.0", constructor: function,selector: ""…]

This is just some sample code. I have some HTML that I replace each time the user does a certain action with it and I don't want to manually re-bind events. I thought that was the point of XenForo.register, that it works with elements created at a later point using XenForo.activate.
 
Last edited:
I believe I may have found the issue now. When the event XenForoActivateHtml occurs, all registered events will call this code:
Code:
$(e.element).find(selector).each(fn);
Which means that for each activated element the selector will be matched against its children and not itself. That is why my sample code does not work because I am expecting to match the new element itself and not children.

...

So here is my modified version of XenForo.register:
Code:
        register2: function(selector, fn, event) {
            if (typeof fn == 'string') {
                var className = fn;
                fn = function(i) {
                    XenForo.create(className, this);
                };
            }

            $(document).bind(event || 'XenForoActivateHtml', function(e) {
                if ($(e.element).is(selector)) {
                    fn($(e.element));
                } else {
                    $(e.element).find(selector).each(fn);
                }
            });
        },
 
Ralle, for the uninitiated (such as myself), would you mind sharing where and how you'd use the modified register function? I believe this might solve an issue I'm also having with new elements introduced to the DOM via jQuery that cease to "work".
 
First of all, I wouldn't recommend modifying original XenForo files.

The difference in my register function is that it will also run your function if the element with the given selector is activated, not only its children.

Say you have this:

Code:
XenForo.register '.myObject', 'XenForo.MyFunction'

And you replace the element with the class name .myObject, it will not activate because it only looks for elements inside the replaced element. My version checks if the replaced element itself matches the selector.
 
What you describe sounds like what I need. But getting back to my original (and apologies if it sounds stupid) question, "where and how" would you define 'register2' within this construct:

Code:
/** @param {jQuery} $ jQuery Object */
!function($, window, document, _undefined)
{
    XenForo.Foo =
    {
        Bar: function()
        {
            /* Stuff */
        }
    };
       
    XenForo.register('.myClass', 'XenForo.Foo.Bar'); /* Assume this would be changed to register2 instead */
}
(jQuery, this, document);

Is it at the same level as XenForo.Foo?
 
Thanks again, I'm currently away from my development environment but will definitely give this a try when I return next week and report back my findings.
 
Actually I think this version would be more correct:
Code:
XenForo.register2 = function(selector, fn, event) {
    if (typeof fn == 'string') {
        var className = fn;
        fn = function(i) {
            XenForo.create(className, this);
        };
    }

    $(document).bind(event || 'XenForoActivateHtml', function(e) {
        if ($(e.element).is(selector)) {
            fn($(e.element));
        }
        $(e.element).find(selector).each(fn);
    });
}
 
@Ralle, so I finally got around to playing with this again, and still having no luck.

Do you have a fully working example that you can share? If not, I may create a simple addon to illustrate my problem and post it here.
 
The attached addon (called 'Sandbox') is about as minimal I can get! :)

Upload, install, then navigate to {xf URL}/sandbox and you'll be presented with the following basic list of items:

sandbox.webp

Each of these items are draggable so that you can rearrange the order. This drag-to-sort functionality was introduced in xenForo 1.3, refer to js/sortable.

Now, the Edit link allows you to modify the title via a form overlay, and if saved, the element is updated inline via ajax (i.e. no reload required). And this is my problem - as soon as you edit an element, it can no longer be dragged or re-ordered among the other elements!

I'm also using the updated 'XenForo.register2' function in my javascript, as per your (@Ralle) suggestion, but as previously mentioned, the problem still persists.

So, any ideas as to what's wrong? :)
 

Attachments

I think the issue has to do with the fact that you aren't replacing the whole element ".Sortable" each time. The registered function will only run if a new element with the class ".Sortable" is added anew, not elements inside it. You will probably have to return the entire "sandbox_index" each time. At least I think that would work.
 
Just to emphasize this, it's not related to XF in any way; it's how the sortable library works. It applies the events to:
Code:
items = $(this).children(options.items)
(With some other caveats.)

So when you replace the element it's getting wiped out of that list, thus it doesn't have the events.
 
Thanks for the clarification, @Mike.

Is there a simple way to re-bind the events after an element has been replaced? I noticed the sortable library has a 'reload' method -- could that be used?
 
So I've found a (partial) solution by calling the 'reload' method from within my InlineUpdate function as follows:

Code:
    XenForo.Sandbox =
    {
        Sortable: function($container)
        {
            $container.sortable(
            // stuff
            );
        },
     
        InlineUpdate: function($form)
        {
            $form.on('AutoValidationComplete', function(e)
            {
                new XenForo.ExtLoader(e.ajaxData, function()
                {
                    $(e.ajaxData.templateHtml).xfInsert('replaceAll', $form.data('rowid'));
                });

                $('.Sortable').sortable("reload");
            });
        }
    };
 
    XenForo.register('.Sortable', 'XenForo.Sandbox.Sortable');
    XenForo.register('#EditForm', 'XenForo.Sandbox.InlineUpdate');

When an item is replaced, the list is "reloaded", and the replaced element can be dragged about.

However, I've hard-coded the class selector (.Sortable) within the reload call. Is there a more elegant way to specify the selector?
 
Top Bottom