52

What is the idea way to register/enqueue scripts and/or styles for use in plugins?

I recently made a plugin simple plugin to add the user avatar/gravatar with a shortcode. I have different style options for displaying the avatar (square, round, etc.) and decided to put the css directly in the shortcode itself.

However, I realize now this isn't a good approach since it will repeat the css every time the shortcode is used on a page. I've seen several other approaches on this site and the wp codex even has two examples of their own so it's hard to know what approach is most consistent and fastest.

Here are the methods I currently am aware of:

Method 1: Include directly in shortcode - This is what I'm currently doing in the plugin, but doesn't seem good since it repeats code.

class My_Shortcode {
function handle_shortcode( $atts, $content="" ) {
/* simply enqueue or print the scripts/styles in the shortcode itself */
?>
<style type="text/css">

</style>
<?php
    return "$content";
     }
}
add_shortcode( 'myshortcode', array( 'My_Shortcode', 'handle_shortcode' ) );

Method 2: Use class for enqueueing scripts or styles conditionally

class My_Shortcode {
    static $add_script;
    static function init() {
        add_shortcode('myshortcode', array(__CLASS__, 'handle_shortcode'));
        add_action('init', array(__CLASS__, 'register_script'));
        add_action('wp_footer', array(__CLASS__, 'print_script'));
    }
    static function handle_shortcode($atts) {
        self::$add_script = true;
        // shortcode handling here
    }
    static function register_script() {
        wp_register_script('my-script', plugins_url('my-script.js', __FILE__), array('jquery'), '1.0', true);
    }
    static function print_script() {
        if ( ! self::$add_script )
            return;
        wp_print_scripts('my-script');
    }
}
My_Shortcode::init();

Method 3: Using get_shortcode_regex();

function your_prefix_detect_shortcode() {

    global $wp_query;   
    $posts = $wp_query->posts;
    $pattern = get_shortcode_regex();

    foreach ($posts as $post){
        if (   preg_match_all( '/'. $pattern .'/s', $post->post_content, $matches )
            && array_key_exists( 2, $matches )
            && in_array( 'myshortcode', $matches[2] ) )
        {
            // css/js 
            break;  
        }    
    }
}
add_action( 'wp', 'your_prefix_detect_shortcode' );

Method 4: Using has_shortcode();

function custom_shortcode_scripts() {
    global $post;
    if( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'myshortcode') ) {
        wp_enqueue_script( 'my-script');
    }
}
add_action( 'wp_enqueue_scripts', 'custom_shortcode_scripts');
  • I think Method 4: Using has_shortcode(); is best because it will ensure that scripts and styles will load once if post content has shortcode regardless of multiple uses of shortcode. Although it might not work for shortcode uses in widgets or in sidebar, not sure though. If it's for a plugin then I would not recommend you to tie scripts with shortcode because some might call your function instead of shortcode to get desired output. – Robert hue Oct 18 '14 at 7:46
  • 1
    Possible duplicate of How to add stylesheets only to pages with specific shortcode? – kontur Mar 30 '18 at 13:35
43

I found an other way that works well for me:

Here is an example plugin using this method that lets you use get_avatar in as shortcode. The stylesheet is only enqueued when the shortcode is present.

Usage (id defaults to current user):

[get_avatar id="" size="32" default="mystery" alt="Profile Photo" class="round"]

function wpse_165754_avatar_shortcode_wp_enqueue_scripts() {
    wp_register_style( 'get-avatar-style', plugins_url( '/css/style.css', __FILE__ ), array(), '1.0.0', 'all' );
}

add_action( 'wp_enqueue_scripts', 'wpse_165754_avatar_shortcode_wp_enqueue_scripts' );
if ( function_exists( 'get_avatar' ) ) {
    function wpse_165754_user_avatar_shortcode( $attributes ) {

        global $current_user;
        get_currentuserinfo();

        extract( shortcode_atts(
                     array(
                         "id"      => $current_user->ID,
                         "size"    => 32,
                         "default" => 'mystery',
                         "alt"     => '',
                         "class"   => '',
                     ), $attributes, 'get_avatar' ) );

        $get_avatar = get_avatar( $id, $size, $default, $alt );

        wp_enqueue_style( 'get-avatar-style' );

        return '<span class="get_avatar ' . $class . '">' . $get_avatar . '</span>';
    }

    add_shortcode( 'get_avatar', wpse_165754_user_avatar_shortcode' );
}
  • Forgot I asked this question last year, but I had actually started doing it this way as well in a lot of cases. It's kind of like combining method 1 and 2. – Bryan Willis Jul 8 '15 at 17:36
  • 4
    This method loads the CSS in the footer, which surely has limitations? – Jacob Raccuia Apr 21 '17 at 13:26
  • This is absolutely the right way to do this. Use this method any time you need to conditionally load a script or stylesheet when the wp_enqueue_scripts hook has already occurred. (Shortcodes are parsed during the_content which is a part of the the_post hook, meaning enqueueing when it is parsed is too late unless you've already registered the script) – JRad the Bad Apr 20 at 21:56
25

Before starting answer I have to say that regarding this topic css and js are not the same.

The reason is simple: while adding js to the body of the page (in footer) is a common and valid way to go, css need to be placed in the <head> section of the page: even if majority of browsers can proper render css in page body, that's not valid HTML code.

When a shortcode is rendered, <head> section was already printed, it means that js can be added without any problem on footer, but css must be added before the shortcode is rendered.

Scripts

If your shortcode needs only js you are lucky and can just use wp_enqueue_script in the body of shortcode callback:

add_shortcode( 'myshortcode', 'my_handle_shortcode' );

function my_handle_shortcode() {
  wp_enqueue_script( 'myshortcodejs', '/path/to/js/file.js' );
  // rest of code here...
}

Doing so your script is added to the footer and only once, even if the shortcode is used more than once in the page.

Styles

If you code needs styles, then you need to act before shortcode is actually rendered.

There are different ways to do this:

  1. look at all the posts in current query and add the shortcode styles if needed. This is what you do in both method #3 and #4 in OP. In fact, both two methods do same thing, but has_shortcode was added in WP 3.6, while get_shortcode_regex is available since version 2.5, so use get_shortcode_regex only if you want make your plugin compatible with older versions.

  2. always add shortcode style, in all pages

Issues

Issue with #1 is performance. Regex are pretty slow operations and launch regex in a loop of all posts may slow down page consistently. Moreover, it's a pretty common task in themes to show only post excerpt in archives and show full content with shortcodes only in singular views. If that happen, when an archive is shown, your plugin will launch a regex matching in a loop with the aim to add a style that will never be used: an unnecessary double performance impact: slow down page generation + additional unnecessary HTTP request

Issue with #2 is performance, again. Adding style to all pages means add an additional HTTP request for all pages, even when not needed. Even if server side page generation time is not affected, total page rendering time will, and for all site pages.

So, what does a plugin developer should do?

I think that best thing to do is adding an option page to plugin, where users can choose if the shortcode should be handled in singular view only or even in archives. In both cases is better to provide another option to choose for which post types enable shortcode.

In that way is possible to hook "template_redirect" to check if current query satisfy requirements and in that case add the style.

If user choose to use shortcode only in singular post views, is a good idea check if post has shortcode or not: once only one regex is required it should not slow down page so much.

If user choose to use shortcode even in archives, then I would avoid to run regex for all posts if number of posts is high and just enqueue the style if query fit requirements.

What to consider "high" on this regard should be get using some performance tests or, as alternative, add another option and give choice to users.

5

For my plugin I found that sometimes users have a theme builder that has shortcode stored in post meta data. Here is what I am using to detect whether my plugin shortcode is present in current post or post meta data:

function abcd_load_my_shorcode_resources() {
       global $post, $wpdb;

       // determine whether this page contains "my_shortcode" shortcode
       $shortcode_found = false;
       if ( has_shortcode($post->post_content, 'my_shortcode') ) {
          $shortcode_found = true;
       } else if ( isset($post->ID) ) {
          $result = $wpdb->get_var( $wpdb->prepare(
            "SELECT count(*) FROM $wpdb->postmeta " .
            "WHERE post_id = %d and meta_value LIKE '%%my_shortcode%%'", $post->ID ) );
          $shortcode_found = ! empty( $result );
       }

       if ( $shortcode_found ) {
          wp_enqueue_script(...);
          wp_enqueue_style(...);
       }
}
add_action( 'wp_enqueue_scripts', 'abcd_load_my_shorcode_resources' );
2

I do so:

class My_Shortcode {

    function __construct() {
        do_action('my_start_shortcode'); // call
....

and catch hook in other functions (or other plugins):

function wpse_3232_load_script(){
    wp_enqueue_script('js-myjs');
}
add_action('my_start_shortcode','wpse_3232_load_script',10);
1

I did find out for my own plugin, if the shortcode is present in the text widget.

function check_shortcode($text) {

  $pattern = get_shortcode_regex();

   if (   preg_match_all( '/'. $pattern .'/s', $text, $matches )
        && array_key_exists( 2, $matches )
        && in_array( 'myshortcode', $matches[2] ) )
    {
        // myshortcode is being used

        // enque my css and js
        /****** Enqueu RFNB  ******/
        wp_enqueue_style('css-mycss');
        wp_enqueue_script('js-myjs');

        // OPTIONAL ALLOW SHORTCODE TO WORK IN TEXT WIDGET
        add_filter( 'widget_text', 'shortcode_unautop');
        add_filter( 'widget_text', 'do_shortcode'); 

    }

  return $text;

}
add_filter('widget_text', 'check_shortcode');
1

WordPress has a built-in function to do something based on a specific Shortcode presenting status. The function name is has_shortcode(). You can use the following code to enqueue your style and scripts.

Here I used the is_a( $post, 'WP_Post' ) to check if the $post object is of the WP_Post class and I used $post->post_content to check the post content.

if ( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'shortcode_tag') ) {
    wp_enqueue_style( 'handle', get_template_directory_uri() . '/your_file_filename.css' );
}
0

I made a combination of the example code from the Wordpress page for has_shortcode() and the answer zndencka gave. I did notice that the has_shortcode function is added in Wordpress 3.6, so that's why I first check if the function exists. Maybe that check is a bit obsolete though, since there aren't many users from below wordpress 3.6 anymore according to wordpress their own stats.

// This makes sure the styling is already enqueued in the header, so before the shortcode loads in the page/post
function enqueue_shortcode_header_script() {
    global $post;
    if ( function_exists( 'has_shortcode' ) ){  // is added in wordpress 3.6
        // Source: https://codex.wordpress.org/Function_Reference/has_shortcode
        if( is_a( $post, 'WP_Post' ) && has_shortcode( $post->post_content, 'my_shortcode') ) {
            wp_enqueue_style( 'shortcode-css' );
        }
    }
    else if ( isset($post->ID) ) { // Small percentage wordpress users are below 3.6 https://wordpress.org/about/stats/
        global $wpdb;
        $result = $wpdb->get_var(
          $wpdb->prepare(
              "SELECT count(*) FROM $wpdb->postmeta " .
              "WHERE post_id = %d and meta_value LIKE '%%my_shortcode%%'",
               $post->ID )
        );
        if (!empty( $result )) { wp_enqueue_style( 'shortcode-css' ); }
    }
}
add_action( 'wp_enqueue_scripts', 'enqueue_shortcode_header_script');

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.