Generate different code in wp_footer for each shortcode instance

I created a WordPress plug which provides a custom post type (Slides) and these can be grouped by a taxonomy (Slideshows). Using a shortcode [slides], you can display these Slides and filter them by Slideshow. Here’s how I coded the shortcode:

function slds_shortcode( $atts ) {
  extract( shortcode_atts( array(
    'slideshow' =>   'all',
    'timeout'   =>  '5000',
  ), $atts ) );
  $slideshow = $atts[slideshow];
  global $post;
  $tmp_post = $post;
  $args = array( 'slideshows' => $slideshow, 'timeout' => $timeout, );
  $myposts = get_posts( $args );
  ob_start(); // Helps output the HTML where the shortcode is (otherwise it outputs before the content).
      ?>
  <div id="<?php echo $slideshow; ?>" class="slds-slideshow">
    <div class="timeout-<?php echo $timeout; ?>">
    <?php foreach( $myposts as $post ) : setup_postdata($post); ?>
      <div class="slide"> 
      <?php the_content(); ?>
      </div>
    <?php endforeach; ?>
    </div>
  </div>
  <?php
  $post = $tmp_post;
    return ob_get_clean();
}    
add_shortcode('slides', 'slds_shortcode');

The scripts and stylesheet are included in the footer, only if a shortcode is found in the post. The script (responsiveslides.js) requires an init, which currently outputs in the footer whether a shortcode is found in the post or not. Here’s how I generate that:

Read More
function slds_init() {
  echo '
  <script>
    jQuery(function($) {
      $(document).ready(function() {
        $(".slds-slideshow .timeout-5000").responsiveSlides({
          pause: true,
          nav: true,
          pager: true,
          timeout: 5000,
          speed: 1800
        });
        $(".slds-slideshow .timeout-10000").responsiveSlides({
          pause: true,
          nav: true,
          pager: true,
          timeout: 10000,
          speed: 1800
        });
      });
    });
  </script>';
  }
add_action('wp_footer', 'slds_init');

This works great, so long as the defaults I coded in there are suitable for the project. Instead, I’d like to be able to set these parameters in the shortcode like: [slides slideshow=tour timeout=10000 speed=1200]. As you can see above, the init has a few major limitations. It selects ALL instances of .slds-slideshow instead of selecting each shortcode output individually by a unique id or class, meaning these default parameters have to be used for all shortcode outputs. In fact, the reason I have two jQuery selectors is so I can add an alternate .timeout class to the output, which just changes which jQuery selector is affecting the shortcode output (or slideshow HTML if you will). This is a shoddy solution. Obviously if I relied on classes for all the parameters this way, with the virtually infinite number of parameter combinations, the init would become extremely heavy and clunky.

How can I generate that middle section of the init (the jQuery selectors and their parameters) based on the shortcode attributes in such a way that they still correlate to the HTML output of the shortcodes in the post?


Edit

Thanks again @birgire for your ideas! I think they got me much closer to where I need to be.

I tried out a lot of options. I think the main thing I’m still running into is getting shortcode instances to generate anything in my javascript initialization. I put wp_localize_script() in my shortcode function as @birgire suggested:

wp_localize_script('slds_script', 'slds_vars', array(
  'instance' => $instance,
  'timeout'  => $timeout,
  'speed'    => $speed
));

So with two shortcodes on the page with timeout and speed set differently, I now get:

/* <![CDATA[ */
var avrgslds_vars = {"instance":"53442837d55c2","timeout":"10000","speed":"500"};
var avrgslds_vars = {"instance":"5344283814479","timeout":"5000","speed":"200"};
/* ]]> */

(I set a new variable for the instance: $instance = uniqid();)

That seems like a significant improvement! So in my javascript initialization, I tried echoing a variable set in my shortcode, which would have all the shortcode “atts” (parameters):

$jQselectors = "";
$jQselectors .= " // Hoping this will keep the array keys from overwriting eachother.
  $(slds_vars.instance).responsiveSlides({
    pause: true,
    nav: true,
    pager: true,
    timeout: $timeout,
    speed: $speed
  });
";

Of course, I realized after I saw this result…

<script>
  jQuery(function($) {
    $(document).ready(function() {
    });
  });
</script>

I can’t select the variable from outside my shortcode function like I was attempt to do:

function slds_init() { ?>
  <script>
    jQuery(function($) {
      $(document).ready(function() {
<?php echo $jQselectors; ?>
      });
    });
  </script>
<?php 
}    
add_action('wp_footer', 'slds_init');  }

I’m having trouble wrapping my head around how to do this. I may just have a misunderstanding on how arrays work. Any ideas? Any/all help is greatly appreciated.

Related posts

Leave a Reply

2 comments

  1. Score! Figured it out. It’s mostly just a variable scope matter.

    First though, I wound up not using wp_localize_script() at all as this requires more JavaScript know-how, where as I have more PHP know-how. Seems like a great solution for a more JavaScript savvy person though and thanks again to @birgire for the advise! I used the uniqid() function he recommended though, to assign a unique class to each shortcode instance (within my shortcode function):

    $instance = 'slds-'.uniqid();
    
    <div class="<?php echo $instance; ?>">
    

    So then I set a variable before my shortcode function starts (before, so it can be used within and after my shortcode function):

    $jQselectors = "";
    

    I pulled it into my shortcode function:

    function slds_shortcode( $atts ) { // <-- Beginning of my shortcode function
    
      global $jQselectors;
    

    That way I could add a jQuery selector to it with all the relevant parameters from the shortcode instance being processed each time the shortcode function is run:

    $jQselectors .= "
      $('.".$instance." .slides').responsiveSlides({
        auto: ".$auto.",
        speed: ".$speed.",
        timeout: ".$timeout."
      });";
    

    So in my init function (which runs after my shortcode function) I pulled that variable in again to write the middle section of my JavaScript initialization:

    <?php
    function slds_init() { ?>
      <script>
        jQuery(function($) {
          $(document).ready(function() {<?php
            global $jQselectors;
            echo $jQselectors; ?>
          });
        });
      </script>
    <?php 
    }    
    add_action('wp_footer', 'slds_init');  } ?>
    

    As far as I could tell from trial and error, this sequence is important. It basically goes:

    1. Set variable to store params for later use in the initialization
    2. Pull the variable into your shortcode function
    3. Store the shortcode attributes as parameters for the script (so they will be output in such a way that the JavaScript engine can parse it)
    4. After your shortcode function, add the variable to your JavaScript initialization (this will have to be in your footer – I don’t know how to have it output in the head)

    Hope this helps someone. :j

  2. You could try to include wp_enqueue_script() and wp_localise_script directly into your shortcode callback.

    But if you got more than one shortcode call on your page, you might get into an overwriting problem from multiple wp_localise_script calls:

    var my_setting = {"foo":"bar1"};
    var my_setting = {"foo":"bar2"};
    

    where only the last one is picked up by the enqueued script.

    Here’s similar problem described on WPSE

    Fortunately, there are some workarounds available, like this one on WPSE, trying to seperate the instances with:

    var my_setting1 = {"foo":"bar1"};
    var my_setting2 = {"foo":"bar2"};
    

    Instead of [myshortcode instance="1"] kind of workaround, you could try to automatically generate a unique ID for each shortcode call. There’s exists a handy PHP function for that: uniqid().

    ps: It’s not so good practice to use extract in your PHP code.

    Hope this helps.