How do you add a simple javascript function in Sage 9?

Yes it works but only if you also call the function in common like above, otherwise nothing.

In Template

Screenshot (ignore file not found errors!

Let me know if you figure out a better way as I would definitely like to know. Being able to add custom javascript is a big plus and can save using resource hogging plugins.

Template code is not showing in Discourse, but its just script tags with the call in it.

Well thanks for looking, I don’t think this is just a ‘big plus’ for me it’s essential and I can’t believe that it’s this difficult to add a simple a javascript function.

Can anyone else shed any light on this issue? If I can’t find a solution I’m going to have to revert to Sage 8.5 because I’ve built lots of sites with this exact thing without any problems using that!

Kevin

This post might give you some ideas.

Hi, @the_lar, could you, for the sake of clarity, outline once more what you’re trying to do, and what you’ve tried, along with the errors you’re getting?

You can wrap code in three back-ticks (like this: ``` above and below the code) for better formatting on Discourse.

Hi,

Sure, it’s incredibly simple… all I want is to have a seperate javascript file with my ajax functions in and then call these functions from my blade templates.

Firstly my config.json file where I’ve added ajax.js:

{
  "entry": {
    "main": [
      "./scripts/main.js",
      "./styles/main.scss"
    ],
    "customizer": [
      "./scripts/customizer.js"
    ],
    "ajax" : [
      "./scripts/ajax.js"
    ]
  },
  "publicPath": "/app/themes/bbc",
  "devUrl": "http://bbc.localhost",
  "proxyUrl": "http://localhost:3000",
  "cacheBusting": "[name]_[hash:8]",
  "watch": [
    "app/**/*.php",
    "config/**/*.php",
    "resources/views/**/*.php"
  ]
}

Then enqueueing in my setup.php:

add_action('wp_enqueue_scripts', function () {
    $ajax_params = array(
      'ajax_url' => admin_url('admin-ajax.php'),
      'ajax_nonce' => wp_create_nonce('my_nonce'),
    );
    wp_enqueue_style('sage/main.css', asset_path('styles/main.css'), false, null);
    wp_enqueue_script('sage/main.js', asset_path('scripts/main.js'), ['jquery'], null, true);
    wp_enqueue_script('sage/ajax.js', asset_path('scripts/ajax.js'), ['jquery'], null, true);
    wp_localize_script( 'sage/ajax.js', 'the_ajax_script', $ajax_params );
}, 100);

The contents of ajax.js is just:

// eslint-disable-next-line no-unused-vars
function do_something(){
// eslint-disable-next-line no-undef
    console.log('some function');
}

And finally on the template, well the partial to be precise:

@section('scripts')
<script>
  jQuery(document).ready(function($){
    console.log('ready');
    do_something();
  });
</script>
@stop

@yield('scripts');

And I constantly get: ReferenceError: do_something is not defined

So as you can see it’s pretty simple! I’m new to ES6, WebPack and ESLint though, so it’s probably my lack of understanding. Thanks for the tip about the ``` too! That helps

Kevin

What’s the scope of these functions? Should they be available to all pages?

Yes, for now. Although it would be good to know a bit more about how to limit them to single pages etc.

Great. Okay, here’s how I would do this (and have done it successfully):

Remove your entries from config.json and setup.php and then…

Create ajax.js

  • Make a new directory: resources/assets/scripts/partials
  • Create a new file: resources/assets/scripts/partials/ajax.js
  • Put all your ajax calls in the newly created ajax.js

Bundle your partial with Webpack

  • Add the following to the top of resources/assets/scripts/routes/common.js:
import "../partials/ajax.js";
  • Run yarn build

And that should do it. Your ajax should be bundled and scoped for all endpoints on the site.

Give it a shot and let us know what happens.

3 Likes

I am interested in this and I just now tried both ways, the first way, the function never gets built, doesn’t appear anywhere in dist folder at all, error as above.

The second way, looks OK initially as the function is built and appears in dist/scripts/main.js, however, still exactly the same error, so getting closer but not solved!

Any fine tuning tips welcome! Thanks

@MWDelaney yeah done all that, no build errors but still getting: ReferenceError: do_something is not defined as @minimallinux confirms…

To be precise and hopefully help pin this down my exact code in my blade file is:

@php
  namespace Bbc;
  $nomination_id = get_nomination_id($type);
  edit_post($nomination_id);
@endphp

Nominate {{$type}}



@section('scripts')
<script>
  jQuery(document).ready(function($){
    console.log('ready');
    do_something();
  });
</script>
@stop

@yield('scripts');

This file is in views/partials

Thanks for helping
Kevin

If its any help, since using the second method, my hamburger and nav toggle from common.js has stopped working, although it is getting built.

To be more precise, it stopped working on the page I have called the function but works on other pages. A bit strange.

So I left the import in the project, hopefully, might find a way to actually use it.!

OK, a tiny bit of progress. So I took all my script out of my blade file and make my ajax.js file like this:

// eslint-disable-next-line no-unused-vars
function do_something(){
// eslint-disable-next-line no-undef
    console.log('doing something');
}

setInterval(function(){
  do_something();
}, 1000);

Now I’m seeing doing something in the console every second, as you’d expect. So I guess I could just scope my ajax.js to this template (if I knew how to do that?) but I still don’t understand why I can’t call the function from directly from the blade file??? Seems like a fairly basic thing that most dev’s would expect to be able to do quite easily?

Kevin

It’s still not great, there really should be a way to call it properly in the template, I tried with and without document ready just in case it was unavailable, and exactly the same error.
So I posted on stack overflow to see if someone knows anything.

@MWDelaney’s suggestion, which involves (ultimately) bundling all of your functions and calls into the theme’s main.js file is probably the most “Sage”-y way to handle the problem, but if having your ajax functions in a separate file and your scripts embedded in a <script> tag is necessary for your implementation, then it looks like your problem has to do with load order.

As I mentioned in your other topic, your wp_enque_script() calls are telling WordPress to print out the link to ajax.js in the footer of your page:

wp_enqueue_script(
  'sage/ajax.js', 
  asset_path('scripts/ajax.js'), 
  ['jquery'], 
  null, 
  true // Setting this to 'true' means "put this in the footer"
);

You aren’t telling WordPress where to enqueue jQuery, so it’s probably putting it in the head of your page (since that’s the default). Otherwise, you’d likely be getting an error in the console telling you the jQuery was undefined.

The <script> tag with your function call appears somewhere on your page below your head but above your footer. The browser will execute scripts as soon as it encounters them. Since it reads the page from top to bottom, it will encounter your <script> that tries to call do_function() before it encounters the link to ajax.js in the footer, where that function is actually defined.

You’ve wrapped your function call in jQuery(document).ready() with the thought (I’m assuming) that this command will prevent the call to do_function() from firing until the page is “ready,” which most people usually assume is “when the browser has all of the assets,” but this is not the case: As the documentation for ready() explains, ready() is fired “as soon as the page’s Document Object Model (DOM) becomes safe to manipulate.” The ready event does not mean all assets have been loaded, only that the browser has parsed the page’s HTML. Because ajax.js is a link to an asset (a JavaScript file), the browser does not execute its content (and become aware of do_function()) as soon as it encounters it: It must first download that file and then parse it, but it doesn’t wait to do that: It keeps loading the page, and then fires ready() when it’s finished—likely before it has finished downloading and parsing ajax.js.

Your call to do_something() is successful when you put it in ajax.js because then none of the above applies: The function is invoked in the same file where it is defined, so when the page is parsed and the file downloaded doesn’t matter.

Sage does not, directly, do anything that causes this: It’s just how browsers work. However, it looks like you copied your wp_enqueue_script() calls directly from the Sage instructions. Those calls specifically enqueue JS files in the footer for performance reasons, but this is not the WordPress default—WordPress defaults to enqueue files in the head. It’s possibly you didn’t run into this problem in the past because you loaded your scripts in the head and then the browser likely had them downloaded and parsed by the time it encountered your embedded <script>s. Webpack may have also contributed, since it does, as you observed, add some additional code to files it processes, which increases their size, which increases how long they take to download, which exacerbates the race condition that’s causing your problem.

This issue—the issue of load order and potential race conditions for JavaScript—is one of the reasons Sage prefers to compile all scripts into a single file. By doing so, it gives the developer complete control of when, where, and how those scripts are executed—it takes the unpredictability of network and browser performance out of the equation.

4 Likes

Hi,

@alwaysblank I really appreciate the thorough explanation of your solution, I did pressume that putting my code in a jQuery ready call would mean that it wouldn’t matter.

Unfortunately I have just tried setting main.js to load in the head where I’d moved the do_something function to and I’m still getting the reference error when I’m calling the function in the template. I also tried unbundling and going back to plan A with a seperate ajax.js file, again still getting the reference error. Looking at the source, in both tests the scripts are definitely loading in the head AND both DO have the do_something function in them!

So I’m still looking for a solution if anyone else has any thoughts, I’d appreciate it greatly.

Kevin

OK, bit more progress. With ajax.js in the head, unbundled I have changed ajax.js to the following:

// eslint-disable-next-line no-unused-vars
window.testvar = "some var";
// eslint-disable-next-line no-unused-vars
function do_something(){
// eslint-disable-next-line no-undef
    console.log('doing something');
}

Then on my template:

@section('scripts')
<script>
  jQuery(document).ready(function($){
    console.log('ready');
    console.log(testvar);
    //util.do_something();
  });
</script>
@stop

This is working in that my console shows the value of some var on load. So I think this shows that it is a scoping issue doesn’t it? By making the variable a property of window it’s in the global scope and thus available on the template. So what scope should I be using and how? Obviously window isn’t the ideal - I’d like to understand how I can create a scope for my functions and use that?

Kevin

At this point the problem is more of a Javascript issue, and a WordPress issue than a Sage one. Sage’s templating system doesn’t affect how the browser loads the javascript, or how the race condition that @alwaysblank described affects your page(s).

Whether you compile your scripts all together (the Sage-y way), or build separate scripts and include them separately, the browser and the nature of Javascript is the issue at this point. Those issues might be better addressed on Stack Overflow since they’re broader and more general.

One quick note, though, in your code you’re using

@section('scripts')
  ...
@stop

I believe the correct syntax for Blade (at least the example from Sage) is

@section('scripts') 
  ...
@endsection
1 Like

I just tried this again as I kept it in the project, using the bundled from partials folder, function appears in main.js, but nothing at all in the console, it’s not really working this way.
Id like to get it working though if anyone has any other ideas.

If the function appears in main.js then Sage is doing everything it’s designed to do. Anything beyond that is a Javascript issue, not a Sage one.