Executing Javascript When a Widget is Added in the Backend

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.

Read More

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?

Related posts

2 comments

  1. 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>
    
  2. As @aaron-holbrook commented a cleaner approach will be to do:

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

    In many cases you will also want to run the JS when the page loads as well as when the widgets are updated. You may do so like this.

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

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

Comments are closed.