How to add stylesheets only to pages with specific shortcode?

I’m running a function on pages where the shortcode [make-me-a-map] is used. It enqueues javascript to the page and makes a map. That part works fine (woot!) but I’m having trouble with adding the stylesheets to only those pages in a similar fashion.

functions.php (Current)

Read More
add_shortcode("make-me-a-map", "create_new_map");
add_action("wp_head", "style_my_new_map");

function create_new_map () {
    global $new_map_called_for;
    $map_called_for = true;

    wp_register_script('make-a-new-map', get_template_directory_uri() . '/js/new-map.js', array('jquery'), '0.1', true);
    wp_enqueue_script('make-a-new-map');
    }

function style_my_new_map() {
    global $new_map_called_for;
    if ($new_map_called_for == true) {
        $tDir = get_template_directory_uri();

        // Echo stylesheets because wp_register_style & wp_enqueue_style don't work atm
        // And add_action('wp_enqueue_script', 'style_my_new_maps') with those functions would add style to all pages
        // Not just the ones with [make-me-a-map] shortcode
        echo "<link rel='stylesheet' href='", $tDir, "/css/new-map.css' />", PHP_EOL;

        // Echo Javascript straight to page (couldn't do it with wp_enqueue_script) to let js code know the template directory
        echo '<script type="text/javascript">var templateDir = "', get_template_directory_uri(), '";</script>', PHP_EOL;
        $map_called_for = false;
    }
}

This doesn’t seem to be working unfortunately. The styles are being added to all pages.

I didn’t use wp_register_style & wp_enqueue_style because this bug is still adding <Link /> tags to the footer from those functions.

I could have used add_action("wp_enqueue_scripts", "style_my_new_map") instead of add_action('wp-head', "style_my_new_map") but as far as I can see and have checked, there’s no difference? Still adds the stylesheets to all pages.

So my question is actually in two parts: 🙂

  1. Why is the current functions.php not working and adding styles to all pages? I’m not sure if it’s the global call or the if statement…
  2. What’s the best way for me to add the stylesheets to the header of only the pages that the above shortcode is used on, assuming I will not know the page ids, etc?

Thanks in advance!
P

Related posts

8 comments

  1. 2 questions so 2 answers 🙂

    Why is the current functions.php not working and adding styles to all
    pages? I’m not sure if it’s the global call or the if statement…

    It doesn’t work because of the order in which these functions are called. Your shortcode callback is called during rendering post content (most probably the_content function call).

    Both wp_enqueue_scripts and wp_head are called much earlier: wp_head somewhere in your themes header.php file and wp_enqueue_scripts during wp_head call.

    What’s the best way for me to add the stylesheets to the header of
    only the pages that the above shortcode is used on, assuming I will
    not know the page ids, etc?

    I don’t think there is such “best” way. It’s not a good idea at all. Browsers cache CSS files. So if you have the same CSS on all pages, browser will get it only once. If there are many CSS files for different pages, browser will have to get all of them. So I wouldn’t do it at all, and include these CSS styles in global css file.

    Although if you have to include it only on pages that use this shortcode, then (2 solutions, the choice which is better is yours; I think 2nd one is nicer)…

    1. You can add these styles as inline styles. Just be careful to not include them many times. So output them with your shortcode and assure they will be printed only once.
    2. Since posts are already selected, when wp_head/wp_enqueue_scripts is called, you can add some function to this hook, loop through selected posts, check if they’re containing your shortcode and enqueue your CSS if it’s needed. (Of course it won’t be very efficient).
  2. Loading Scripts and Styles Dynamically Using Shortcode

    Advantages

    • Does not search through all the posts every time the shortcode is called.
    • Able to add styles as well as scripts dynamically only when shortcode is on the page.
    • Does not use regexes since they tend to be slower than strstr() or strpos(). You could switch to regex if you need to accept args.
    • Reduces file calls

    Explanation of Code

    1. Finds the shortcodes on page using the save_post hook only when the post is not a revision and matches the specified post_type.

    2. Saves the found post ids as an array using add_option() with autoload set to yes unless the entry is already present. Then it will use update_option().

    3. Uses hook wp_enqueue_scripts to call our add_scripts_and_styles() function.

    4. That function then calls get_option() to retrieve our array of page ids. If the current $page_id is in the $option_id_array then it adds the scripts and styles.

    Please note: I converted the code from OOP Namespaced classes so I may have missed something. Let me know in the comments if I did.

    Code Example: Finding Shortcode Occurences

    function find_shortcode_occurences($shortcode, $post_type = 'page')
    {
        $found_ids = array();
        $args         = array(
            'post_type'   => $post_type,
            'post_status' => 'publish',
            'posts_per_page' => -1,
        );
        $query_result = new WP_Query($args);
        foreach ($query_result->posts as $post) {
            if (false !== strpos($post->post_content, $shortcode)) {
                $found_ids[] = $post->ID;
            }
        }
        return $found_ids;
    }
    
    function save_option_shortcode_post_id_array( $post_id ) {
        if ( wp_is_post_revision( $post_id ) OR 'page' != get_post_type( $post_id )) {
            return;
        }
        $option_name = 'yourprefix-yourshortcode';
        $id_array = find_shortcode_occurences($option_name);
        $autoload = 'yes';
        if (false == add_option($option_name, $id_array, '', 'yes')) update_option($option_name, $id_array);
    }
    
    add_action('save_post', 'save_option_shortcode_id_array' );
    

    Code Example: Shortcode Dynamically Include Scripts and Styles

    function yourshortcode_add_scripts_and_styles() {
        $page_id = get_the_ID();
        $option_id_array = get_option('yourprefix-yourshortcode');
        if (in_array($page_id, $option_id_array)) {
            wp_enqueue_script( $handle, $src, $deps, $ver, $footer = true );
            wp_enqueue_style( $handle, $src , $deps);
        }
    }
    
    add_action('wp_enqueue_scripts', 'yourshortcode_add_scripts_and_styles');
    
  3. This works for me:

    function register_my_css () {
        wp_register_style('my-style', plugins_url('styles.css', __FILE__));
    }
    
    function register_my_scripts () {
        wp_register_script('my-script', plugins_url('scripts.js', __FILE__));
    }
    
    // only enqueue the scripts / styles when the short code is called
    function do_my_shortcode () {
        wp_enqueue_style('my-style');
        wp_enqueue_script('my-script');
    
        // do short code stuff...
    }
    
    // register css
    add_action('wp_enqueue_scripts', 'register_my_css');
    
    // register javascript
    add_action('wp_enqueue_scripts', 'register_my_scripts');
    
    // register shortcode
    add_shortcode('my-shortcode', 'do_my_shortcode');
    

    More info here: http://mikejolley.com/2013/12/sensible-script-enqueuing-shortcodes/

  4. You can hook into an action after the main query has run and determine if you need to load your styles and scripts.

    add_action('template_include', 'custom_template_include');
    function custom_template_include($template) {
        if (is_single() || is_page()) {
              global $post;
              if (has_shortcode($post->post_content, 'your_short_code')) {
                  add_action('wp_enqueue_scripts, 'custom_enqueue_scripts');
              }
        } 
    }
    
    function custom_enqueue_scripts() {
        //Enqueue your stuff here. 
    }
    
  5. add_action( 'wp_enqueue_scripts', 'enqueue_shortcode_styles', 1 );
    function enqueue_shortcode_styles() {
    
        global $post;
    
        if ( isset($post->post_content) && is_singular(array( 'post','page' ) ) && ! has_shortcode( $post->post_content, '$tag' ) ) {
    
        wp_enqueue_style( 'shortcode-styles', get_stylesheet_directory_uri() . '/style.css' );
    
        }
    
    }
    

    I would use has_shortcode to check the $post content and load the scripts using wp_enqueue_scripts but it depends on where your shortcodes are embedded.

    Swap out $tag with your shortcode tag in the above code.

    Add to your child themes functions file and change the conditionals if needed.

  6. A much simpler solution that worked for me was to register the script outside of the shortcode function and then enqueue the script within the function. Then it only loads the script on pages using your shortcode.

    wp_register_script('make-a-new-map', get_template_directory_uri() . '/js/new-map.js', array('jquery'), '0.1', true);
    wp_register_style('make-a-new-map-style', get_template_directory_uri() . '/css/new-map.css');
    
    add_shortcode("make-me-a-map", "create_new_map");
    add_action("wp_head", "style_my_new_map");
    
    function create_new_map () {
       wp_enqueue_script('make-a-new-map');
       wp_enqueue_style('make-a-new-map-style');
    
       global $new_map_called_for;
       $map_called_for = true;
    }
    

    A description of that method can be found here: http://mikejolley.com/2013/12/sensible-script-enqueuing-shortcodes/

    Note that this does load the CSS in the footer, which may not be desirable or W3C valid. In my case that was not an issue.

  7. You can simply do it like this:

    // shortcode hook
    add_shortcode("make-me-a-map", "create_new_map");
    
    // shortcode callback
    function style_my_new_map() {
        global $new_map_called_for;
        if ($new_map_called_for == true) {
      wp_register_script('make-a-new-map', get_template_directory_uri() . '/js/new-map.js', array('jquery'), '0.1', true);
      wp_enqueue_script('make-a-new-map');
    
    // the rest of your codes
        }
    }
    

    It’s also works for stylesheets. To load your stylesheet at the last, then add numbers to your shortcode hook, like this:

    add_shortcode("make-me-a-map", "create_new_map", 9999);

    I’ve tested it and it works.

  8. Loading Scripts and Styles Dynamically Using Shortcode

    This is extended code for the solution provided by Commandz above.

    I have found his method to be best and more feasible as the database option is available at almost all hooks.

    It is changed to find the occurrences of shortcodes only for the current post content, instead of going through all posts – which may be slower when the number of posts is large.

    It is changed here in two lines after passing another $post_id variable from the save_post action to function find_shortcode_occurences()

    if( !empty($post_id) ) {
        $args['posts__in'] = $post_id; 
    }
    

    Changed code for both functions

    function find_shortcode_occurences($shortcode, $post_id='', $post_type = 'page') {
        $found_ids = array();
        $args         = array(
            'post_type'   => $post_type,
            'post_status' => 'publish',
            'posts_per_page' => -1,
        );
    
        if( !empty($post_id) ) {
            $args['posts__in'] = $post_id; 
        }
    
        $query_result = new WP_Query($args);
        foreach ($query_result->posts as $post) {
            if (false !== strpos($post->post_content, $shortcode)) {
                $found_ids[] = $post->ID;
            }
        }
        return $found_ids;
    }
    
    add_action('save_post', 'save_option_for_sppro_pages' );
    function save_option_for_sppro_pages( $post_id ) {
        if ( wp_is_post_revision( $post_id ) ) {
            return;
        }
        $option_name = 'database-option';
        $shortcode = 'shortcode-name';
        $id_array = find_shortcode_occurences($shortcode, $post_id);
        $autoload = 'yes';
        if (false == add_option($option_name, $id_array, '', 'yes')) {
            $current_value = get_option($option_name);
            $new_value = array_merge($current_value, $id_array);
            update_option($option_name, $id_array);
        }
    }
    

    Also, another variable has been added so you can have different names for the database option and the shortcode name.

Comments are closed.