• Hi everyone, I hope I can get some help with this. I am developing a new plugin and have created it inside a shortcode function. Inside this function, I am also using some javascript. When I use the shortcode on a page, it works no problem, but if I try run it twice, the second instance gives me an error that my let variable inside my javascript has already been declared.

    So I could move the javascript to an external file using enqueue so it only calls it once, but the javascript doesn’t work unless I am inside the shortcode callback function. I know it is because I have saved a shortcode attribute into a variable called $time, and this $time variable basically populates the entire javascript block.

    I don’t know how to solve this so that I can have multiple shortcodes on one page, and not have a javascript variable redeclaration problem.

    Here is my code:

    function devgirl_countdown_clock_function($atts = '', $content = null) { 
    	$value = shortcode_atts( array(
    		'time' => "5 December 2023 8:00",
    		'end-notice' => "Countdown Ended",
    		'clock-colour' => "#011c25",
    		'text-colour' => "DarkTurquoise",
    	), $atts, 'devgirl_countdown_clock_function' );
    	
    	$time = sanitize_text_field($value['time']);	
    	$end_notice = sanitize_text_field($value['end-notice']);	
    	$clock_colour = sanitize_text_field($value['clock-colour']);
    	$text_colour = sanitize_text_field($value['text-colour']);
    
    
    ?>
    
    
    	
    	
    
    	<h2 class="end-notice"></h2>
    
    
    
    	
    	<div class="devgirl-countdown-clock border-radius">
    		<?php 
    		if ( ! function_exists( 'countdownBlocks' ) ) {
    		function countdownBlocks($clock_colour, $text_colour, $date_value, $date_value_title) { ?>
    
    			<div class="countdown-block" style="background:<?php echo esc_html($clock_colour); ?>; color:<?php echo esc_html($text_colour); ?>">
    				<div class="clock <?php echo esc_html($date_value); ?>-clock-value"></div>
    				<div class="text"><?php echo esc_html($date_value_title); ?></div>
    			</div><!---countdown-block-->
    			<div class="separators">:</div>
    				
    				
    		<?php }
    
    
    		countdownBlocks($clock_colour, $text_colour, 'days', 'Days');
    		countdownBlocks($clock_colour, $text_colour, 'hours', 'Hours');
    		countdownBlocks($clock_colour, $text_colour, 'mins', 'Mins');
    		countdownBlocks($clock_colour, $text_colour, 'secs', 'Secs');
    
    		} else {
    			echo 'The function name already exists and cannot be run. Plese check all your plugins function names to find the conflict.';
    		}
    		?>
    		
    	</div><!---devgirl-countdown-clock-->
    	
    	
    	
    	
    
    
    <script type="text/javascript">
    // Set the date we're counting down to
    const countDownDate = new Date('<?php echo $time; ?>').getTime();
    
    // Update the count down every 1 second
    let x = setInterval(function() {
    
      // Get today's date and time
      let now = new Date().getTime();
        
      // Find the distance between now and the count down date
      let distance = countDownDate - now;
        
      // Time calculations for days, hours, minutes and seconds
      let days = Math.floor(distance / (1000 * 60 * 60 * 24));
      let hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
      let minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
      let seconds = Math.floor((distance % (1000 * 60)) / 1000);
        
      // Output the result in an element with id="demo"
    
    	document.querySelector('.devgirl-countdown-clock').style.display = 'flex';
    	document.querySelector('.days-clock-value').innerHTML = days;
    	document.querySelector('.hours-clock-value').innerHTML = hours;
    	document.querySelector('.mins-clock-value').innerHTML = minutes;
    	document.querySelector('.secs-clock-value').innerHTML = seconds;
        
      // If the count down is over, write some text 
      if (distance < 0) {
        clearInterval(x);
        document.querySelector("h2.end-notice").textContent = '<?php echo $end_notice; ?>';
    		document.querySelector('.devgirl-countdown-clock').style.display = 'none';
      }
    }, 1000);
    </script>	
    
    			
    			
    <?php } //END devgirl_countdown_clock_function
    
    add_shortcode('devgirl-countdown-clock', 'devgirl_countdown_clock_function');
    
    
    
    
    
    
    
    // front end stylesheet
    function devgirl_countdown_clock_frontend_style() {
        wp_enqueue_style( 'countdown-clock-frontend', plugins_url( '/style/countdown-clock-frontend.css' , __FILE__ ) );
    }
    add_action( 'wp_enqueue_scripts', 'devgirl_countdown_clock_frontend_style' );
    
    
    
    // backend admin stylesheet
    function devgirl_countdown_clock_backend_style() {
        wp_enqueue_style( 'countdown-clock-backend', plugins_url( '/style/countdown-clock-backend.css' , __FILE__ ) );
    }
    add_action( 'admin_enqueue_scripts', 'devgirl_countdown_clock_backend_style' );
    
    ?>

    And then the user can call the shortcode in WordPress using something as simple as this:

    [devgirl-countdown-clock time="3 Jul 2023 8:00"]

Viewing 8 replies - 1 through 8 (of 8 total)
  • Jay

    (@jaygumanid)

    The problem is that the countdownBlocks function and the let x variable are defined within the devgirl_countdown_clock_function function, but they are not marked as local to the function by using the local keyword. As a result, when the function is called multiple times, these variables are re-declared and you get an error because the let keyword does not allow re-declaration of the same variable.

    To fix this, you can wrap the entire block of code in the devgirl_countdown_clock_function function in a block like this:

    {
    // code here
    }

    And then mark the countdownBlocks function and the let x variable as local to the block by using the local keyword like this:

    local function countdownBlocks($clock_colour, $text_colour, $date_value, $date_value_title) {
      // code here
    }
    
    local x = setInterval(function() {
      // code here
    });
    

    This will ensure that the countdownBlocks function and the let x variable are only visible within the block, and are not re-declared when the devgirl_countdown_clock_function function is called multiple times.

    Thread Starter devgirl

    (@elfynity5)

    Jay, thank you so much for your reply. I am having a hard time following your directives and implementing it into my code, I’m not really sure where everything is supposed to go and just got critical errors all around. I tried to use “local” in front of my function and also got an error. I’m just not sure what local even is. Is this kind of what you meant – however, it gives me a critical error:

    function devgirl_countdown_clock_function($atts = '', $content = null) { 
    
    		$value = shortcode_atts( array(
    			'time' => "5 December 2023 8:00",
    			'end-notice' => "Countdown Ended",
    			'clock-colour' => "#011c25",
    			'text-colour' => "DarkTurquoise",
    		), $atts, 'devgirl_countdown_clock_function' );
    		
    		$time = sanitize_text_field($value['time']);	
    		$end_notice = sanitize_text_field($value['end-notice']);	
    		$clock_colour = sanitize_text_field($value['clock-colour']);
    		$text_colour = sanitize_text_field($value['text-colour']);
    
    
    		{
    	?>
    
    
    		
    		
    
    		<h2 class="end-notice"></h2>
    
    
    
    		
    		<div class="devgirl-countdown-clock">
    			<?php 
    			if ( ! function_exists( 'countdownBlocks' ) ) {
    			local function countdownBlocks($clock_colour, $text_colour, $date_value, $date_value_title) { ?>
    
    				<div class="countdown-block" style="background:<?php echo esc_html($clock_colour); ?>; color:<?php echo esc_html($text_colour); ?>">
    					<div class="clock <?php echo esc_html($date_value); ?>-clock-value"></div>
    					<div class="text"><?php echo esc_html($date_value_title); ?></div>
    				</div><!---countdown-block-->
    				<div class="separators">:</div>
    					
    					
    			<?php }
    
    
    			countdownBlocks($clock_colour, $text_colour, 'days', 'Days');
    			countdownBlocks($clock_colour, $text_colour, 'hours', 'Hours');
    			countdownBlocks($clock_colour, $text_colour, 'mins', 'Mins');
    			countdownBlocks($clock_colour, $text_colour, 'secs', 'Secs');
    
    			} else {
    				echo 'The function name already exists and cannot be run. Plese check all your plugins function names to find the conflict.';
    			}
    			?>
    			
    		</div><!---devgirl-countdown-clock-->
    		
    		
    		
    		
    
    
    	<script type="text/javascript">
    	// Set the date we're counting down to
    	const countDownDate = new Date('<?php echo esc_html($time); ?>').getTime();
    
    		// Update the count down every 1 second
    		local x = setInterval(function() {
    
    		// Get today's date and time
    		let now = new Date().getTime();
    			
    		// Find the distance between now and the count down date
    		let distance = countDownDate - now;
    			
    		// Time calculations for days, hours, minutes and seconds
    		let days = Math.floor(distance / (1000 * 60 * 60 * 24));
    		let hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
    		let minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
    		let seconds = Math.floor((distance % (1000 * 60)) / 1000);
    			
    		// Output the result in an element with id="demo"
    
    		document.querySelector('.devgirl-countdown-clock').style.display = 'flex';
    		document.querySelector('.days-clock-value').innerHTML = days;
    		document.querySelector('.hours-clock-value').innerHTML = hours;
    		document.querySelector('.mins-clock-value').innerHTML = minutes;
    		document.querySelector('.secs-clock-value').innerHTML = seconds;
    			
    		// If the count down is over, write some text 
    		if (distance < 0) {
    			clearInterval(x);
    			document.querySelector("h2.end-notice").textContent = '<?php echo esc_html($end_notice); ?>';
    			document.querySelector('.devgirl-countdown-clock').style.display = 'none';
    		}
    	}, 1000);
    	</script>	
    
    				
    			
    	<?php 
    		}
    
    } //END devgirl_countdown_clock_function
    
    add_shortcode('devgirl-countdown-clock', 'devgirl_countdown_clock_function');
    
    

    I’m sorry, I’m just really new to all this so my understanding is limited to basics.

    Moderator bcworkz

    (@bcworkz)

    I’m just not sure what local even is

    Then it’s time to learn ?? Local, or more broadly, “variable scope” is a very important concept to grasp. Once you have a good understanding, hopefully Jay’s suggestions will make more sense.

    You’e encountered a common problem with shortcodes and JS. You can include JS within shortcode output, but then it needs to be specially written so that multiple instances of the JS code can occur on the same page without error. Plus it’s wasteful to have the same code over and over on one page.

    As you surmised, you could enqueue your JS instead. But then, if the page has no shortcodes the JS is needed for, it’s wasteful to enqueue it anyway. Many plugins do this anyway. It’s also inefficient to have code that verifies a shortcode is in use prior to enqueuing the related JS. Blindly enqueuing, whether needed or not is the lesser of two evils?

    I’ve not done so (JS isn’t my forte), but I believe it’s feasible for your shortcode to include a brief JS snippet to check if the bulk of your JS has been loaded, and if not, dynamically load it at run time. Then it’ll be available for other shortcode instances without needing to declare the same code over and over. Nor would you need to load it when it’s not needed at all. Clearly a more advanced approach. You might not be in a good place to try something like this. But it’s possibly the best approach if you’re willing to dive in deep.

    Thread Starter devgirl

    (@elfynity5)

    Hi bcworkz, so I actually found an example script that I want to run past you two and see if I am on the right track here with regards to localization. I am beginning to understand the concept, but just hoping that I am looking into the right way to do it if I implement something based on the following example:

    <?php
    
    // Step 1: Enqueue the script in the shortcode function
    
    function my_shortcode_function($atts) {
      wp_enqueue_script('my-script');
    
      // Step 2: Localize the script to pass data from PHP to JavaScript
      wp_localize_script(
        'my-script',
        'my_shortcode_data',
        array(
          'items' => $items, // An array of items to be passed to the script
        )
      );
    
      
    // Step 3: Output the HTML for the shortcode
      ob_start();
      ?>
      <div class="my-shortcode">
        <ul>
          <?php foreach ($items as $item): ?>
            <li><?php echo esc_html($item); ?></li>
          <?php endforeach; ?>
        </ul>
      </div>
      <?php
      return ob_get_clean();
    }
    add_shortcode('my_shortcode', 'my_shortcode_function');
    
    Then, in your JavaScript file, you can access the my_shortcode_data object to access the items array. For example:
    
    // Step 4: Use the data in the script
    
    console.log(my_shortcode_data.items); // Outputs the array of items
    
    Thread Starter devgirl

    (@elfynity5)

    I have created the simplest form of my plugin that I could to help sort out this issue.

    function testing1_shortcode($atts = '', $content = null) { 
    
    	$value = shortcode_atts( array(
    		'seconds' => "2000"
    	), $atts, 'testing1_shortcode' );
    	
    	$seconds = $value['seconds'];
    
    ?>
    
    <div class="timer">
    </div><!---timer-->
    
    
    
    
    <?php
    } // END shortcode
    
    add_shortcode('testing-variable-scope', 'testing1_shortcode');
    ?>
    
    
    
    <script type="text/javascript">
    	let time = "<?php echo $seconds; ?>";
    	let timerBlock = document.querySelector(".timer");
    
    	function timer() {
    		timerBlock.innerText = time;
    
    		if (time < 0) {
    			timerBlock.innerText = "Time's up!";
    			clearInterval();
    		}
    		time--;
    	}
    
    	setInterval(timer, 1000);		
    
    </script>	

    I have moved the js to outside the shortcode block now, and even now, it is still not working. I just don’t know what a working example would look like to replicate variable scope. Please could someone paste a working code using my above example so I can get an idea of what works and learn from it. I have learnt variable scope in the last day, I have understood the concept of redeclaration and still I honestly don’t know how to fix this.

    Thread Starter devgirl

    (@elfynity5)

    I’m trying a new approach. This works in a plain PHP file:

    	<div class="timer1"></div> 	
    	<div class="timer2"></div> 
    	
    	
    
    	<script type="text/javascript">
    
    	let myTime;
    	let timerDiv;
    	
    	function timer(timerDiv, myTime) {
    	
    		//myTime = 5000;
    		timerDiv = document.querySelector(timerDiv);
    		
    
    		function timer() {
    			timerDiv.innerText = myTime;
    
    			if (myTime < 0) {
    				timerDiv.innerText = "Time's up!";
    				clearInterval();
    			}
    			myTime--;
    		}
    
    		setInterval(timer, 1000);		
    	
    	}
    
    		timer('.timer1',5051);
    		timer('.timer2',6000);
    
    		
    	</script>

    But when I implement it on my WordPress shortcode side, it keeps giving me the error that TimerDiv is NULL:

    	<?php
    	function testing1_shortcode($atts = '', $content = null) { 
    
    		$value = shortcode_atts( array(
    			'name' => "myTimer",
    			'seconds' => "2000"
    		), $atts, 'testing1_shortcode' );
    		
    		echo $name = $value['name'];
    		echo $seconds = $value['seconds'];
    
    	?>
    
    
    
    
    	<div class="<?php echo $name; ?> timer">
    	</div><!---<?php echo $name; ?>-->
    	
    	<div class="mynewtimer"></div>
    
    
    
    
    	<script>
    
    	
    	
    	function timer(timerDiv, myTime) {
    
    		timerDiv = document.querySelector(timerDiv);
    
    		function timer() {
    			timerDiv.innerText = myTime;
    
    			if (myTime < 0) {
    				timerDiv.innerText = "Time's up!";
    				clearInterval();
    			}
    			myTime--;
    		}
    
    		setInterval(timer, 1000);	
    	} // timer	
    	
    	
    
    	timer('<?php echo $name; ?>',<?php echo $seconds; ?>);
    
    	</script>
    
    	<?php
    
    
    
    	} // END shortcode
    
    	add_shortcode('testing-variable-scope', 'testing1_shortcode');

    In an external JS file:

    	let myTime;
    	let timerDiv;

    And using these 2 shortcodes to try display in a page, but the countdown itself is not showing:

    [testing-variable-scope name=”timer1″ seconds=”6000″]

    [testing-variable-scope name=”timer2″ seconds=”11000″]

    • This reply was modified 1 year, 10 months ago by devgirl.
    • This reply was modified 1 year, 10 months ago by devgirl.
    • This reply was modified 1 year, 10 months ago by devgirl.
    Thread Starter devgirl

    (@elfynity5)

    I had a mistake in my timerDiv, I fixed it, it works! Instead of

    timerDiv = document.querySelector(timerDiv);

    I needed to have:

    timerDiv = document.querySelector('.'+timerDiv);
    	function timer(timerDiv, myTime) {
    
    		timerDiv = document.querySelector('.'+timerDiv);
    
    		function timer() {
    			timerDiv.innerText = myTime;
    
    			if (myTime < 0) {
    				timerDiv.innerText = "Time's up!";
    				clearInterval();
    			}
    			myTime--;
    		}
    
    		setInterval(timer, 1000);	
    	} // timer	
    	
    	
    
    	timer('<?php echo $name; ?>',<?php echo $seconds; ?>);
    Moderator bcworkz

    (@bcworkz)

    FWIW, the “localize” in PHP’s wp_localize_script() is entirely unrelated to “local” and variable scope in JS. Localization (sometimes abbreviated “l10n” because there are 10 chars between the l and n) mainly has to do with how certain data is formatted on output, like date format, or whether a decimal point is a dot or a comma, the local currency sign, etc. We actually slightly misuse wp_localize_script() by passing arbitrary PHP values to JS that have nothing to do with actual localization. But it serves a need, so we use it.

    Keep in mind that JS isn’t my forte. I think the issue you’re running into is the inner declaration of timer(). Within its scope, you try to assign something to timerDiv.innerText without first establishing what that object is. I’m unclear what the inner timer() declaration is for, it doesn’t seem to be called anywhere in the outer timer().

    I don’t think it’s good practice to use the same function name in two different instances, even if scoping allows it to work. Maybe you’re clear on what’s going on, but I’m confused ??

    Since you’re already enqueuing, IMO you may as well enqueue the bulk of your JS. Enqueued functions should not be declared local. The only JS within your shortcode should be the call to your main JS function that’s doing the heavy lifting. Avoid vars in any shortcode script, try to work directly on the related DOM objects. If you do need vars within the shortcode, they all should be local to that script.

    I think enqueuing the bulk of your JS is your best chance of getting something working. You may or may not choose to make it all more efficient later, but for now it’s best to focus on getting something working by the simplest means possible.

Viewing 8 replies - 1 through 8 (of 8 total)
  • The topic ‘Using javascript in Shortcode Callback when creating new plugin’ is closed to new replies.