Should 3rd Parties Use $wp_scripts/$wp_styles->add_data?

Within the WP_Dependencies class exists a method named add_data. This function adds data to scripts/styles that have been enqueued during the WordPress load. A commonly cited use for this function is to add a conditional when adding stylesheets that are targeted at different versions of IE. For example, to target IE8 and lower:

function test_wp_print_styles() {
    global $wp_styles;

    wp_enqueue_style( 'test-style', get_template_directory_uri() . '/css/test.css', array(), 1, 'all' );
    $wp_styles->add_data( 'test-style', 'conditional', 'lte ie8' );
}
add_action( 'wp_print_styles', 'test_wp_print_styles' );

This will render as:

Read More
<!--[if lte ie8]>
<link rel='stylesheet' id='test-style-css'  href='http://trunkosaurus.dev/wp-content/themes/twentyeleven/css/test.css?ver=1' type='text/css' media='all' />
<![endif]--> 

When I look through Core, I see a handful of places where this method is used:

  • WP_Styles->add_inline_style() : adds inline style after the
    referenced stylesheet (done via WP_Styles->print_inline_style())

  • WP_Scripts->localize() : adds a json encoded object (wrapped by the
    more “public” wp_localize_script() function)

  • wp_plupload_default_settings() : adds json encoded object (created from
    a multidimensional array) for the ‘wp-plupload’ script (note that
    this is forthcoming in 3.4)

  • When registering/enqueueing scripts and
    styles Adding data for default scripts
    (wp-includes/script-loader.php)

From reading through the uses of the method, it does not appear to have a specific use case. In wp_plupload_default_settings, it seems to allow for arbitrary data injection. In wp_register_script, it seems to be used to differentiate between header and footer scripts. In add_inline_style, it is used to denote inline style that should be added after a specified stylesheet is enqueued.

An excellent use for this function would be something like the following code where you are enqueuing an external script but need to send it some config vars, some of which come from the DB:

function zdt_enqueue_add_this() {
    global $wp_scripts;

    wp_enqueue_script( 'zdt-add-this', 'http://s7.addthis.com/js/250/addthis_widget.js#pubid=myidhere' );

    // Contrived example of database call to get a twitter handle stored in the db
    $author_twitter_handle = zdt_get_twitter_handle();

    $js = "var addthis_share = { templates : { twitter: '{{title}} {{url}} (by @" . sanitize_key( $author_twitter_handle ) . "' } };n";
    $js .= 'var addthis_config = { ui_header_color: "#FFFFFF", ui_header_background: "#FA9628", ui_cobrand: "My Site" };';

    $wp_scripts->add_data( 'zdt-add-this', 'data', $js );
}
add_action( 'wp_enqueue_scripts', 'zdt_enqueue_add_this' );

This will result in:

<script type='text/javascript'>
/* <![CDATA[ */
var addthis_share = { templates : { twitter: '{{title}} {{url}} (by @tollmanz' } };
var addthis_config = { ui_header_color: "#FFFFFF", ui_header_background: "#FA9628", ui_cobrand: "My Site" };
/* ]]> */
</script>
<script type='text/javascript' src='http://s7.addthis.com/js/250/addthis_widget.js?ver=3.4-beta4-20731#pubid=myidhere'></script>

Note that this cannot be accomplished with wp_localize_script because the addthis_share object has properties within properties (I did write about a somewhat hacky way around this before).

EDIT: I was wrong in stating this. wp_localize_script handles multidimensional arrays just fine.

This method seems to work really well for the following reasons:

  1. It allows you to attach the data to the script handle so it is always properly enqueued with the script. Further, it will be intelligent about deenqueueing the script, script order, and script placement.
  2. It allows you to use PHP to send vars to JS.
  3. It seems more organized than using wp_print_styles to print out some arbitrary script that is acted on later by an enqueued script.

There are some things that do not work as expected that worries me about this method. One such issue is that if you use wp_localize_script along with $wp_scripts->add_data, you can get unexpected results. For instance:

// Contrived example of database call to get a twitter handle stored in the db
$author_twitter_handle = zdt_get_twitter_handle();

$js = "var addthis_share = { templates : { twitter: '{{title}} {{url}} (by @" . sanitize_key( $author_twitter_handle ) . "' } };n";
$js .= 'var addthis_config = { ui_header_color: "#FFFFFF", ui_header_background: "#FA9628", ui_cobrand: "My Site" };';

$wp_scripts->add_data( 'zdt-add-this', 'data', $js );
wp_localize_script( 'zdt-add-this', 'addthis_share', array( 'var' => 'val' ) );

Produces:

<script type='text/javascript'>
/* <![CDATA[ */
var addthis_share = { templates : { twitter: '{{title}} {{url}} (by @tollmanz' } };
var addthis_config = { ui_header_color: "#FFFFFF", ui_header_background: "#FA9628", ui_cobrand: "My Site" };
var addthis_share = {"var":"val"};
/* ]]> */
</script>
<script type='text/javascript' src='http://s7.addthis.com/js/250/addthis_widget.js?ver=3.4-beta4-20731#pubid=myidhere'></script>

Whereas this script:

// Contrived example of database call to get a twitter handle stored in the db
$author_twitter_handle = zdt_get_twitter_handle();

$js = "var addthis_share = { templates : { twitter: '{{title}} {{url}} (by @" . sanitize_key( $author_twitter_handle ) . "' } };n";
$js .= 'var addthis_config = { ui_header_color: "#FFFFFF", ui_header_background: "#FA9628", ui_cobrand: "My Site" };';

wp_localize_script( 'zdt-add-this', 'addthis_share', array( 'var' => 'val' ) );
$wp_scripts->add_data( 'zdt-add-this', 'data', $js );

Produces:

<script type='text/javascript'>
/* <![CDATA[ */
var addthis_share = { templates : { twitter: '{{title}} {{url}} (by @tollmanz' } };
var addthis_config = { ui_header_color: "#FFFFFF", ui_header_background: "#FA9628", ui_cobrand: "My Site" };
/* ]]> */
</script>
<script type='text/javascript' src='http://s7.addthis.com/js/250/addthis_widget.js?ver=3.4-beta4-20731#pubid=myidhere'></script>

The data key that is set by wp_localize_script is ultimately overwritten by the call to $wp_scripts->add_data, whereas if you call wp_localize_script twice for the same script, the string will be properly concatenated.

While all of this is a really handy way of printing arbitrary script for use with an enqueued script, it makes me think that it should not be widely used because of potential of conflicts. I certainly can see an argument for using this in personal projects where the code will not be used in community plugins/themes.

I also looked at Core Trac to see if there was any clues as to the purpose of the function. I found one ticket (http://core.trac.wordpress.org/ticket/11520) (an epic one at that) that explored other ways of adding arbitrary JS. So it seems that there is interest in creating a better way to add arbitrary JS, but not sure exactly if add_data should be part of the process.

My main question is: should developers be using this function? In some cases (e.g., wp_register_script), it seems like a “private” function that 3rd parties should not use; however, in other cases (e.g., wp_plupload_default_settings), it seems like a perfectly reasonable way to inject arbitrary JS prior to an enqueued script.

I don’t imagine there is a “correct” answer to this, but would love to hear what other devs think. I also imagine that there are pieces to this puzzle that I’ve completely neglected and would love to hear what others have to say about it.

Related posts

Leave a Reply

2 comments

  1. This function adds data to scripts/styles that have been enqueued during the WordPress load.

    Not really. It adds data to scripts/styles that’ve been registered.

    The data key that is set by wp_localize_script is ultimately overwritten by the call to $wp_scripts->add_data, whereas if you call wp_localize_script twice for the same script, the string will be properly concatenated.

    Right. They both call the underlying (non-accessible, internal) API, so it gets overwritten (as you stated). This happens when it calls $this->get_data( $handle, 'data' );.

    Question

    My main question is: should developers be using this function?

    Answer

    Simply said: Yes, when you got no other chance to do what you need.

    Another example: Check if a script was registered (for e.g. json2/jquery) and move it to the footer (check extra['group']).

    // Move scripts to the footer - in case it isn't already there
    if ( ! $wp_scripts->get_data( 'json2', 'group' ) )
        $wp_scripts->add_data( 'json2', 'group', 1 );
    
    if ( ! $wp_scripts->get_data( 'jquery', 'group' ) )
        $wp_scripts->add_data( 'jquery', 'group', 1 );
    

    Note: This ↑ only works for data filed under extra!

    Additional notes

    Counter-Question: Have you ever tried to add dependencies to scripts registered by core? For e.g.: Try to add JSON2 as needed deps to jQuery. This isn’t possible without intercepting the global $wp_scripts:

    global $wp_scripts;
    
    $scripts = array( 
         'jquery'      => array( 'json2' )
        ,'jquery-form' => array( 'json2' ) 
    );
    
    foreach ( $scripts as $handle => $deps )
    {
        // Ugly hack: Intercept the global to force the "natural"/needed order: JSON2 » jQuery
        $deps_default =& $wp_scripts->registered[ $handle ]->deps;
        $wp_scripts->registered[ $handle ]->deps = array_merge( $deps_default, $deps );
    }
    

    There’s a whole load of things the class can’t do. So using something like ->add_data() is imo fully valid. Just use what you got, as it’s still better then living the lacks of core classes.

  2. There was a big debate in WP 3.3 about how to handle script data:

    http://core.trac.wordpress.org/ticket/11520

    Note that you can pass nested arrays to wp_localize_data() now:

    wp_localize_script( 'jquery', 'jQueryL10n', array(
        'foo' => array(
            'bar' => array( 'apple', 'orange' )
        ),
    ) );
    

    So, I would use add_data() if there was no higher-level API for what I needed to do, with the understanding that it’s behavior might change in some edge cases, such as when concatenation is involved.