Make WordPress Core

Opened 5 years ago

Closed 5 years ago

#48098 closed defect (bug) (wontfix)

ReferenceError: jQuery is not defined even if it is enqueued ('wp_display_script_once' needed)

Reported by: kestutisit's profile KestutisIT Owned by:
Milestone: Priority: normal
Severity: normal Version: 5.2.3
Component: Script Loader Keywords:
Focuses: javascript Cc:

Description

So I have a template in a plugin with:

<?php
// Scripts
wp_enqueue_script('jquery');
?>
<div class="great-plugin-wrapper">
<!--- BUNCH OF HTML -->
</div>
<script type="text/javascript">
jQuery(document).ready(function() {
    jQuery('.responsive-deals-slider').slick({
    // Bunch of javascript
    });
});
</script>

And I'm getting 'ReferenceError: jQuery is not defined' error in console even if it is enqueued in HTML before using.
In source code it says jQuery is loaded late in footer:

<script type='text/javascript' src='http://localhost/GitHub/web1/sub1/wp-includes/js/jquery/jquery.js?ver=1.12.4-wp'></script>

. Why? Does something changed in WordPress in recent months regarding this, and why so? It was working at least 6 months ago.
And how to make sure jQuery is not loaded in the footer, or that
jQuery(document).ready( would work?

I'm using:

  1. Twenty Nineteen theme
  2. Multisite
  3. Guttenberg
  4. Newest WordPress
  5. Plugin's shortcode
  6. Just one plugin and one theme.

Change History (9)

#1 in reply to: ↑ description ; follow-up: @SergeyBiryukov
5 years ago

  • Keywords reporter-feedback added; needs-patch removed
  • Severity changed from major to normal

Replying to KestutisIT:

So I have a template in a plugin with:

<?php
// Scripts
wp_enqueue_script('jquery');
?>
<div class="great-plugin-wrapper">
<!--- BUNCH OF HTML -->
</div>
<script type="text/javascript">
jQuery(document).ready(function() {
    jQuery('.responsive-deals-slider').slick({
    // Bunch of javascript
    });
});
</script>

Thanks for the report! When exactly this code runs?

If it's not hooked to the wp_enqueue_scripts action as recommended in the documentation, then it's too late for jQuery to be printed in wp_head(), so it's only printed in wp_footer().

And I'm getting 'ReferenceError: jQuery is not defined' error in console even if it is enqueued in HTML before using.
In source code it says jQuery is loaded late in footer

Note that wp_enqueue_script( 'jquery' ) does not print the script right away, it only enqueues the script to be printed later by wp_print_head_scripts() or wp_print_footer_scripts().

Does something changed in WordPress in recent months regarding this, and why so? It was working at least 6 months ago.

As far as I know, nothing has changed in the script loader in the last few years in that regard.

#2 in reply to: ↑ 1 @KestutisIT
5 years ago

It is registered via plugin's registerScripts methods in AssetLoader, and it registers the script 'init' and enqueues after 'add_shortcode' call. The enqueuing on 'add_shortcode' is important, because the we save network resources, and plugin scripts are not loaded on the page where the is no plugin shortcode used:

<?php
class MainController
{
    public function run()
    {
        // <...>
        add_action('init', array($this, 'runOnInit'), 0);
        // <...>
    }

    public function runOnInit()
    {
        //<..>
        // Register front-end scripts
        $objAssetController->registerScripts();
    }
}

I also did another experiment:

  1. I tested with 'TwentySixteen' theme - it works as expected.
  2. I tested with 'TwentyNineteen' theme - it works unexpectecly.

Appears that TwentySixteen was using jQuery in theme for keyboard navigation or so, so it was not raising issues for the plugin, as jQuery got in the header.

We need to solve this somehow, and force and say by out plugin that it must be loaded in header.
And expected behavior is that if I run 'enqueue_script' either WordPress should give me a warning that the given script is NOT (yet) registered, or enqueue it. While now it does not gives me any warning but acts unexpectedly. This confuses me as a developer of the purpose of enqueue_script as it does not look reliable then with code.

The full source code of MainController is in GitHub, if you want to take a deeper look:
https://github.com/SolidMVC/Deals/blob/master/Controllers/MainController.php
(and on W.org SVN as well of the plugin as /deals/).

Replying to SergeyBiryukov:

Replying to KestutisIT:

So I have a template in a plugin with:

<?php
// Scripts
wp_enqueue_script('jquery');
?>
<div class="great-plugin-wrapper">
<!--- BUNCH OF HTML -->
</div>
<script type="text/javascript">
jQuery(document).ready(function() {
    jQuery('.responsive-deals-slider').slick({
    // Bunch of javascript
    });
});
</script>

Thanks for the report! When exactly this code runs?

If it's not hooked to the wp_enqueue_scripts action as recommended in the documentation, then it's too late for jQuery to be printed in wp_head(), so it's only printed in wp_footer().

And I'm getting 'ReferenceError: jQuery is not defined' error in console even if it is enqueued in HTML before using.
In source code it says jQuery is loaded late in footer

Note that wp_enqueue_script( 'jquery' ) does not print the script right away, it only enqueues the script to be printed later by wp_print_head_scripts() or wp_print_footer_scripts().

Does something changed in WordPress in recent months regarding this, and why so? It was working at least 6 months ago.

As far as I know, nothing has changed in the script loader in the last few years in that regard.

#3 follow-up: @peterwilsoncc
5 years ago

  • Keywords reporter-feedback removed
  • Milestone Awaiting Review deleted
  • Resolution set to wontfix
  • Status changed from new to closed

Thanks for providing additional information.

As noted above, the purpose of the wp_enqueue_scripts() function is to output a script tag in either the header or footer of an HTML document. There are other functions to print script tags immediately.

In your case, the best course of action would be to enqueue jQuery and add additional JavaScript to run within wp_footer

<?php
wp_enqueue_script('jquery');
add_action( 'wp_footer', function() {
  ?>
  <script>/*javascript here */</script>
  <?php
}, 11 );

I'm going to close this ticket as wontfix, due the existing functionality available and because changing the purpose of the enqueue functionality could adversely affect a number of sites.

#4 in reply to: ↑ 3 @KestutisIT
5 years ago

  • Keywords 2nd-opinion needs-patch added
  • Resolution wontfix deleted
  • Status changed from closed to reopened
  • Summary changed from ReferenceError: jQuery is not defined even if it is enqueued to ReferenceError: jQuery is not defined even if it is enqueued ('wp_display_script_once' needed)

Replying to peterwilsoncc:

Thanks for providing additional information.

As noted above, the purpose of the wp_enqueue_scripts() function is to output a script tag in either the header or footer of an HTML document. There are other functions to print script tags immediately.

What functions exactly?

As I'm lead architect in out team, our whole DEVELOPERS DEPARTMENT was NOT ABLE to figure out what the hell is happening here for a half day, I can confirm - that this is UNEXPECTED BEHAVIOR.
And if we want to have new developers from other systems comming to WordPress, especially those, that are familiar with MVC-like architecture with full template separation, they will never get the point what is happening here.
If template we MUST NEVER HAVE function() {...} like function, or we are breaking THE BASICS OF CLEAN CODE - we are messing Code and template. And the JavaScript should not ever be hidden somewhere in model or controller, because we have to allow designers fully change the UI and UX (interactions, spinners etc., that are handled by jQuery hooks).

So I'm reopening this ticket, and adding an additional proposal here:

  1. Please follow the PHP logic, that already has 'require_once' and 'include_once' functions,

so suggest to add 'wp_display_script_once()' new WordPress function via this tickets (please update it's content, as I cannot do that), if you cannot update existing functions.

Once again, believe me, I'm not the only one. We been this weekend on biggest hackathon of the Port of Klaipeda, with hundreds of developers there, and NOBODY WAS ABLE TO FIGURE OUT WHAT THE HELL IS HAPPENING HERE, so instead we BROKE WHAT WORDPRESS says and used <script> directly in the template, and WordPress says we should never do this (due to support for Amazon S3 script loading, or to avoid double-loading).

#5 follow-up: @peterwilsoncc
5 years ago

  • Resolution set to wontfix
  • Status changed from reopened to closed

@KestutisIT

The existing function is https://developer.wordpress.org/reference/functions/wp_print_scripts/

However, the recommended approach is to use code similar to the example provided above.

#6 in reply to: ↑ 5 @KestutisIT
5 years ago

  • Resolution wontfix deleted
  • Status changed from closed to reopened

Replying to peterwilsoncc:

@KestutisIT

The existing function is https://developer.wordpress.org/reference/functions/wp_print_scripts/

However, the recommended approach is to use code similar to the example provided above.

Thanks for the information.
So to be clear (as the documentation of that function is TRAGIC there - meaning no documentation at all) - does it will render that specific the script immediately. And if other plugin calls it once again - it will not render that script file for the second time.
Also - why it is called 'wp_print_scripts', as I want to print out only specific scripts related to the plugin.
And finally, if I have 'x.js' registed on AssetLoader.php with:

wp_register_script('x', array('jquery'));

When I call:

wp_print_scripts('x');

Will it render two scripts immediately:
jquery.min.js
and
x.js

I'm reopening this ticket as your answer is not clear as well as I do get the point why 'wp_print_scripts' are plural, but 'wp_enqueue_script' is singular, meaning:
Is the 'wp_print_scripts' is equal to described appoarch of 'wp_display_script_once' or not.

Or it prints all the scripts that I do not want to print as well (including the one's that are enqueued by other plugins and which has to be printed on those plugin's template scopes).

And the last thing - who decided that that this is not recomended? The 'Clean code' book, as well as all the books about MVC, including the legendary:

  1. Rozanski, E. Woods - Software Systems Architecture - Working With Stakeholders Using Viewpoints and Perspectives (2nd Edition)

Says differently what you said and they says that the mentioned path I wrote is the best practice. As well as the whole fact that WordPress is now based on 'BLOCKS' where it can be anything and it's part should stay in it's part only (expect the actually 'header' and 'meta' things required by some plugins).

Feel free to close this ticket if it is does what I described. Otherwise there is no solution what I described.

Last edited 5 years ago by KestutisIT (previous) (diff)

#7 @KestutisIT
5 years ago

Additionally in here it is written:

A possible incompatibility with WordPress 3.3 could arise if you are using the wp_print_styles hook to enqueue styles — your styles may end up in the admin.

https://make.wordpress.org/core/2011/12/12/use-wp_enqueue_scripts-not-wp_print_styles-to-enqueue-scripts-and-styles-for-the-frontend/

That is ANOTHER UNEXPECTED BEHAVIOR.

#8 @KestutisIT
5 years ago

A note for those who a Googling and ending up at this bug (unexpected behavior) page.
Even a @peterwilsoncc reply does not fix a bug for jQuery in TwentyNineteen theme. As TwentyNineteen does not use jQuery at all.
So you have to pay attention that scripts are printed with the '20' priority at the footer, meaning, that the workaround until the 'wp_display_script_once' function will be added to WP core, in all your plugin's HTML templates (with .php extension), you have to use this VERY UGLY WORKAROUND by also setting a loading priority to 100 to make it (jQuery and jQuery DATEPICKER) to work:

<?php
defined( 'ABSPATH' ) or die( 'No script kiddies, please!' );
// Scripts
wp_enqueue_script('jquery');
wp_enqueue_script('jquery-ui-datepicker'); // Plugin-specific
wp_enqueue_script('jquery-ui-datepicker-locale'); // Plugin-specific


// Styles
wp_enqueue_style('jquery-ui-theme'); // Plugin-specific for datepicker
?>
<?php
// Global variable are needed here, because only then we will be able to access them inside the 'add_action' hook
$GLOBALS['MIN_DATE'] = $minDate; // Variable is set in the current view's controller
$GLOBALS['CONF_DATEPICKER_DATE_FORMAT'] = $settings['conf_datepicker_date_format']; // Plugin-specific
?>
<?php add_action('wp_footer', function() { // A workaround until #48098 will be resolved ( https://core.trac.wordpress.org/ticket/48098 ). Scripts are printed with the '20' priority. ?>
<script type="text/javascript">
jQuery(document).ready(function(){
    jQuery('.special-plugin-wrapper .pickup-date').datepicker({
        minDate: "+<?=esc_js($GLOBALS['MIN_DATE']);?>D",
        maxDate: "+1035D",
        dateFormat: '<?=esc_js($GLOBALS['CONF_DATEPICKER_DATE_FORMAT']);?>',
        // <...> further code
    });
});
</script>
<?php }, 100); ?>

#9 @peterwilsoncc
5 years ago

  • Keywords 2nd-opinion needs-patch removed
  • Resolution set to wontfix
  • Status changed from reopened to closed

When I call wp_print_scripts('x') Will it render two scripts immediately

It will, provided dependencies have been set correctly. Using this function will also ensure scripts are only printed once.

As has been mentioned, the functionality you are after exists in WordPress core already so I am again closing this ticket wontfix.

Discussion can continue on a closed ticket, please don't reopen this ticket again. I recommend opening a support ticket on the developing with WordPress support forums for further assistance.

Note: See TracTickets for help on using tickets.