4

I want to include and use third party JavaScript file (Bootstrap Tooltip) in my plugin. Since Bootstrap is highly popular framework and tooltip() is rather generic name I want to make use of included noConflict() functionality to isolate tooltip's instance in my own variable.

If I understand timing at all right, this needs to be done right after script file loaded - so that nothing else has a chance to mess up instance (it had just set up) or get messed up by it either.

However there seems to be no convention in queue functionality to echo JS at such point. $wp_scripts->print_extra_script() is called before the script.

Is this at all possible to accomplish with current queue implementation?

5

You have to filter script_loader_src, check if you are on the correct script and add a one-time filter for clean_url. Then add your extra script, and make sure the output is syntactically valid.

Pretty simple, albeit rather hacky.

class Extra_Script
{
    private $extra = [];
    private $handle;
    private $url;
    private $versioned_url;

    public function __construct( $handle, $url )
    {
        $this->handle = $handle;
        $this->url    = $url;
        add_filter( 'script_loader_src', array( $this, 'filter_script_src' ), 10, 2 );
    }

    public function add_script( $script )
    {
        $this->extra[] = $script;
    }

    public function filter_script_src( $src, $handle )
    {
        if ( empty ( $this->extra ) )
            return $src;

        if ( $this->handle !== $handle )
            return $src;

        if ( 0 !== strpos( $src, $this->url ) )
            return $src;

        $this->versioned_url = $src;

        add_filter( 'clean_url', array( $this, 'filter_esc_url' ) );
        remove_filter( current_filter(), array( $this, __FUNCTION__ ) );

        return $src;
    }

    public function filter_esc_url( $url )
    {
        if ( $url !== $this->versioned_url )
            return $url;

        remove_filter( current_filter(), array( $this, __FUNCTION__ ) );

        $scripts = join( "\n", $this->extra );

        return "$url'></script><script>$scripts</script><script class='";
    }
}

Usage

add_action( 'wp_enqueue_scripts', function()
{
    $handle = 'foo';
    $url    = plugins_url( '/foo.js', __FILE__ );

    wp_enqueue_script( $handle, $url );

    $extra = new Extra_Script( $handle, $url );
    $extra->add_script( "alert('bar!');" );
});

This will add an inline script right after your enqueued script.

An alternative might be Patchwork. You can redefine WordPress’ code at runtime with that. But that wouldn’t be less hacky.

  • Points for perversion, but I wouldn't ship this in public plugin. :) Had bad experience with WP changing escaping and messing things up in the past, staying away from those parts. – Rarst Dec 2 '13 at 18:40
3

WordPress >= 4.1

WordPress 4.1 had added script_loader_tag filter for complete HTML of enqueued script. Perfect for the purpose of adding something right after it.


WordPress < 4.1

I ended up hooking into wp_print_footer_scripts at priority 11 (after queue has finished) and calling wp_print_scripts() explicitly on script's handle, then echoing what I needed to.

I would be hesitant to use this for something major, but for small contained script it seems acceptable.

2

Today I needed to do more or less same thing, ending up on an alternative solution, that I want to share in case it can be useful for someone.

I wrote a custom class extending WP_Script, something like this:

class GM_Scripts extends WP_Scripts {

    static function buildFromWp( WP_Scripts $scripts ) {
        $class = get_called_class();
        $obj = new $class;
        foreach ( get_object_vars( $scripts ) as $n => $v ) {
            $obj->$n = $v;
        }
        return $obj;
    }

    public function do_item( $handle, $group = false ) {
        $done = parent::do_item( $handle, $group );
        do_action( 'after_script_done', $handle, $done );
    }

}

This class inherit all methods from parent WP class, except one, do_item that is the one used to effectively print script on markup.

GM_Scripts::do_item() calls same method on WP_Script and immediately after fires the custom action 'after_script_done' passing as arguments the script handle and a boolean that is true if the script was effectively print on page.

After that, I hooked wp_print_scripts to overwrite the global $wp_scripts object with an instance of my class, built using the static method buildFromWp that inherits the status of a WP_Scripts passed as argument.

add_action( 'wp_print_scripts', function() {

  if ( is_admin() || did_action('login_head') || ! isset($GLOBALS['wp_scripts']) ) {
    return;
  }

  $GLOBALS[ 'wp_scripts' ] = GM_Scripts::buildFromWp( $GLOBALS[ 'wp_scripts' ] );

}, 0 );

Once my instance inherits status, type and and methods of original WP class that should not cause any issue with WordPress or third parties code.

Also, this happen only on fronted, to further reduce any chance of conflicts.

Finally I added the action that echo what I need:

add_action( 'after_script_done', function( $handle, $done ) {

  if ( $handle === 'myscript' && $done ) {
     // echo what needed
  }

}, 0, 2 );

and enqueued my script as usual:

add_action( 'wp_enqueue_script', function() {

  wp_enqueue_script( 'myscript', $script_url );

} );

What's the benefit of this approach against the accepted one?

First of all it is a more generic approach usable for every script, and more important, any dependency to my script is solved correctly.

As example, another piece of code can use

add_action( 'wp_enqueue_script', function() {

  wp_enqueue_script( 'another-script', $another_url, array( 'myscript' ) );

} );

Using the accepted approach, in this case

  • if my script is already registered before 'wp_head' it will be inserted in page without no conflict or whatever I want to print after it

  • if it isn't registereded the 'another-script' will not be inserted because WordPress will not be able to solve the dependency

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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