Issue with Defining and Using Functions from Custom Script in Sage 10

Hello everyone,

I’m currently working on a project using Sage 10 and I have encountered an issue with importing and using a custom JavaScript function. Here are the details:
What I’ve Done

Created a custom JavaScript file:
    File: resources/scripts/custom.js
    Content:
    export const custom = async (err) => {
      if (err) {
        console.error(err);
      }
      console.log('custom js loaded');
    };

    function changeImage(src) {
      document.getElementById('mainImage').src = src;
    }

Imported the custom script in app.js:

File: resources/scripts/app.js
Content:

        import domReady from '@roots/sage/client/dom-ready';
        import { custom } from '@scripts/custom.js';

        /**
         * Application entrypoint
         */
        domReady(async () => {
          // application code
          custom();
        });

The Issue

The console log console.log('custom js loaded'); in custom.js works well, indicating that the custom script is being loaded.
However, the changeImage function defined in custom.js is not working and returns an error changeImage is not defined.

What I’ve Tried

If I include the changeImage function directly in the Blade file, it works without any issues.
I want to keep the code clean and maintain the separation of concerns by keeping the JavaScript in the custom.js file.

Question

How can I properly define and use the changeImage function (and other similar functions) from the custom.js file in my Blade templates or other JavaScript files in Sage 10? Any guidance on how to solve this issue while keeping the code clean would be greatly appreciated.

Thank you in advance for your help!
Additional Information

Environment: Sage 10
JavaScript Framework: Included with Sage 10 setup
Any other relevant details: The project structure follows the default Sage 10 setup.

If I understand you correctly, you want to import the changeImage method in app.js. In your current code the changeImage method is not exported, only custom method is.

You want to make the changeImage method globally available?

I mean, you could just assign window.changeImage = changeImage and it should be globally available. But you have to export it correctly first.

Can you give more details if you have an idea about the topic? Thanks

When I understand you correctly, you want to make the changeImage method available to some other, external script? Or do you want to use it in your own script?

the function changeImage to be used in the templates files , when to use in html file it will be working add it to onclick for example

I see, so are you using onclick or addEventListener on those elements in the template files (HTML)?

    export const custom = async (err) => {
      if (err) {
        console.error(err);
      }
      console.log('custom js loaded');
    };

    export const changeImage = (src) => {
      document.getElementById('mainImage').src = src;
    };
        import domReady from '@roots/sage/client/dom-ready';
        import { custom, changeImage } from '@scripts/custom.js';

        /**
         * Application entrypoint
         */
        domReady(async () => {
          // application code
          custom();

         const element = document.querySelector('.my-selector');
         element.addEventListener('click', changeImage.bind(this, 'some-src'));
         element.addEventListener('touchstart', changeImage.bind(this, 'some-src'));
        });

I am not sure what the exact use case is. There are many solutions, but few are of best practice. If each element should have its own src, you may want to use a data-attribute and the changeImage handler to use that instead.
Or use addEventListener on the containing element and filter by those elements.

2 Likes

The problem that the js script works only when I add it to single custom post type in the bottom of single-product.blade.php
but when I enqueue it (I use bundle , entrypoints correctly ) It does not work and show as undefined function.

<script>
    function changeImage(src) {
      document.getElementById('mainImage').src = src;
      document.querySelectorAll('.product-thumbnails img').forEach(img => img.classList.remove('active'));
      document.querySelector(`.product-thumbnails img[src="${src.replace('600x600', '150x150')}"]`).classList.add('active');
    }
</script>

But where does it show as undefined? Are you using event listener attributes directly (as onclick)?

I am using on click as function in html attribute; onclick=functionname() after that I am defining the function in js file
function functionname(){…

When you import that function in app.js, but not using it there, webpack would optimize it away. You should be able to make it globally available and prevent webpack from optimizing it away by attaching it to the window object:

import { functionname } from ...

window.functionname = functionname;
1 Like