I have widgets that have javascript controls attached to them.

If the widget is present when the widget admin page loads, the controls work correctly.

When I add a new widget, they do not work correctly, I get the markup, but no javascript events take effect.

If I save the new widget, when the form reloads, the controls are created correctly and now work.

Refreshing the page also fixes the issue, but only for existing widgets. New widgets have this issue still.

Specifically I'm running selectize on select inputs at these points:

  • document ready
  • Ajaxcomplete

I've verified that my code is run on each event as desired, but the results are not as expected.

Here is a test plugin that demonstrates the issue:

https://gist.github.com/Tarendai/8466299

You'll see I have a counter that increases with every select element it finds that it can convert.

Notes:

  • When the widget page loads, I can see the counter increasing in the JS console as expected
  • When I add a new widget, the code is run, however, it fails to find any select elements it can run on, as demonstrated by found.length being 0. This should not be the case, as every new widget of that type should have a select element
  • select elements have a class to identify them, this class is removed once the selectize library is applied to prevent duplication and re-processing on existing widgets.
  • Prior to using selectize, I used Select2, which had the same issue
  • Commenting out the AJAX code, I would expect new widgets to have a standard select input. They do not. I don't know why this is the case

So how do I make the selectize control work without telling the user to refresh/click save prior to making changes?

  • It's an event delegation issue. You will need to look into using jQuerys .on() method. Which requires you to bind an event to the document. However what event will be appropriate I'm not sure right now. Ultimately it's because your original bindings represent the initial version of the DOM when the page was loaded and for the most part are blind to the new elements ( widget content ) that are being added via ajax. Hopefully that'll point you in the right direction, if not I can answer better when I'm at work. – MichaelJames Jan 17 '14 at 7:38
  • This is what I expected, and why I added the jQuery( document ).ajaxStop( function() { part so that I could initialise the controls in the newly loaded widgets. However if I removed that line I would expect new widgets to have standard HTML select inputs, but they do not =s – Tom J Nowell Jan 17 '14 at 11:56
  • As @aaron-holbrook commented, its approach is the cleanest one by using the widget-updated and widget-added events. Also I want to emphasize, in @michaeljames code, the use of the element id #widgets-right. Be sure to use it in order to target active widgets elements inside your JS code otherwise you might get duplicate elements as your code may also select hidden inactive widget elements on the left side of the screen (and those get cloned when widget is added). – Julien Jun 20 '16 at 13:32
up vote 11 down vote accepted

Great News,

I've fixed it.

Apologies for missing your gist in my initial comment and giving you an answer that you had already implemented. That'll teach me to answer on my phone while on a train!

Your use of ajaxstop is correct. Hell the JS for the most part is working correctly, you could see the css styles being initialized and the changes in the DOM.

The issue lies in fact with your selector.

This is because widget content isn't loaded in via ajax, in fact it clones it from the left hand column where it's hidden and then fires an ajax call to save the widget's position in the right hand column. This explains why ajaxstop works a treat, even thought content is cloned. However, because there is a hidden version of the widget in the left hand column, your JS is instantiating on that. As such when you drag it to the right hand column, you are getting a mangled clone of the hidden widget.

Thus you need to select the one in the left side. Below is the corrected code:

<script>
jQuery( document ).ready( function( $ ) {
    function runSelect() {
        var found = $( '#widgets-right select.testselectize' );
        found.each( function( index, value ) {

            $( value ).selectize();
                .removeClass( 'testselectize' )
                .addClass( 'run-' + window.counter );

            console.log( $val );
            window.counter++;
        } );
    }

    window.counter = 1;

    runSelect();

    $( document ).ajaxStop( function() {
        runSelect();
    } );
} );
</script>
  • Holy moly, I need to go test this <3 – Tom J Nowell Jan 17 '14 at 14:18
  • Works perfectly =D Thanks!! – Tom J Nowell Jan 17 '14 at 14:41
  • Very elegant solution to a major headache! – bosco Jan 17 '14 at 20:13
  • 3
    I would advise against running on every single 'ajaxStop' event and instead suggest using on 'widget-added' as well as 'widget-updated' events. – Aaron Holbrook Aug 11 '14 at 19:45

As @aaron-holbrook commented a cleaner approach will be to do:

jQuery(document).on('widget-updated widget-added', function(){
    // your code to run
});

Just pasting here for reference as answers are much easier to find that comments

  • 1
    probably should use jQuery instead of $ – Mark Kaplun Jan 11 '16 at 10:26

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.