Elegantly using JavaScript on widget admin forms

I’m working on a project involving a lot of widgets, and I’m thinking it’d be really great to have a “More options” section built using jQuery UI Accordion that expands when clicked.

So, I’ve enqueued ‘jquery-ui-accordion’ using the ‘admin_enqueue_scripts’ action:

Read More
function add_my_deps() {
    wp_enqueue_script('jquery-ui-accordion');
}    
add_action( 'admin_enqueue_scripts', 'add_my_deps' );

…And my JavaScript looks something like this:

(function($){
    $('.more-options-expansible').accordion();
})(jQuery)

Now what?

If I enqueue that via the “add_my_deps” action above, it only works if I reload the page (I.e., when placing the widget, the JavaScript is instantiated before the element is created; if the widget is saved, its JavaScript is enqueued as normal and it instantiates as normal.).

If I place that in a <script> tag at the end of my HTML output in my widget’s form() method, I get the following exception: Uncaught TypeError: Object [object Object] has no method 'accordion'. This is because it seems to be instantiating the accordion via the inline <script> before the jQuery UI Accordion library is loaded.

Is there something obvious I’m missing? I’m using a simple jQuery instantiation for this example, but it seems like I’d have even worse issues if I tried to do anything using AJAX (Namely, how would I select the appropriate element to place the response in? Is this something I need to start using Backbone.js to accomplish?).

Any help?

Related posts

3 comments

  1. One way around this I’ve discovered since posting the above question is to instantiate inline via WP_Widget::form(), with wp_enqueue_script() loading the necessary JavaScript (exactly as mentioned above) — except, and here’s the key part, load the JavaScript in the header instead of the footer (fifth argument of wp_enqueue_script()).

    Which makes a lot of sense now that I think about it.

    Even still, I hate using inline JavaScript — ever — and loading everything in the header is less than ideal.

    As such, am leaving this question open in case anyone has a better idea.

  2. I’ve edited the my answer with my findings from a previous question, this should now set you straight.

    It’s going to fail because when the JS runs it’s being instantiated against the hidden widget form in the left hand side. It turns out when you drag the widget to an active sidebar it actually clones the hidden form into the right hand side, as opposed to ajaxing in it’s contents. This means, the new widget you’ve added already has accordion instantiated against it, but due to the cloning things do not work so well initially. Though obviously after a page refresh it will work perfectly.

    To make it work as intended when we add a new widget, we need to target our form content that is in the right hand side of the page, we can also hook onto the ajaxstop event, instead of load. This is because after the cloned widget is dropped into the right hand side, WordPress sends an ajax request to save the widgets position.

    $(document).on('ajaxstop', '#widget-right .more-options-expansible', function(e){
        $(this).accordion();
    });    
    

    This leads to a nicer solution than adding the JS inline and allows you to keep the scripts in the footer.

  3. WordPress added a few JavaScript events, ‘widget-added’, ‘widget-updated’, ‘widget-synced’ so you no longer need to hack the ajax event.

    https://core.trac.wordpress.org/ticket/19675

    So, I think you should update the code to something like this in your external js file:

    $(document).on('widget-added', function(event, widget) {
       $(widget).find('.more-options-expansible').accordion();
    }
    

    You probably need to add similar for ‘widget-updated’ and ‘widget-synced’ as they all affect your dom.

Comments are closed.