1

Fairly basic question here. I've run into a situation where I can't seem to access Javascript functions from within my HTML file, even though I've linked the JS file as a script src. It seems like a pretty simple issue but I can't figure out what the problem is.

I'm trying to add a function called startLogin to an HTML button. I added it as an onclick, but then when I try to click the button, the console says the function is undefined. However the function is clearly defined in the JS file and as far as I can tell the syntax I'm using for the onclick and the script src link is correct.

In addition I've confirmed that the JS file is linked to the HTML file. If I try to manipulate the DOM from the JS file just to do something simple, like set the background to red, that works fine. The problem is when I try to call a function defined in the JS file. Also I've made sure the function I'm trying to call does actually work. If I stick it right in the HTML file inside script tags, it works fine.

I've already tried moving the script tags inside the body at the end of the HTML, as I know that's often the issue, but in this case it didn't work. Can anyone help me identify why I'm unable to access the "startLogin" function from the HTML button?

FYI, this is a javascript project and I'm using Vite.js for bundling. All the other HTML/JS files in my project are playing nicely together, I'm only having an issue with the Login page.

file structure:

|-pages
  |-login.html
  |-login.js

login.html

 <!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial- 
    scale=1.0" />
    <title>Document</title>

    <!-- LINK JS FILE -->
    <!-- MODULE TYPE IS RELATED TO VITE.JS -->
    <script type="module" src="./login.js"></script>

  </head>

  <body>
    <!-- email login form -->
    <form name="emailLogin" id="emailLogin" style="display: none">
      <div class="row" style="width: 600px">
        <div class="col">
          <div class="form-row" style="padding-bottom: 10px">
            <input
              type="email"
              class="form-control"
              id="emailAddress"
              placeholder="email associated with your login"
              style="width: 576px"
            />
          </div>
        </div>
        <div class="form-row">
          <br />
          <button type="button" class="btn btn-primary" onclick="startLogin('email')">
            Send Email Login
          </button>
        </div>
      </div>
    </form>
  </body>
</html>

login.js

// start the login process by generating a code sent either SMS or EMAIL
      function startLogin(login_type) {

        // local variables
        var ajaxResult;
        var subs;
        var tempString;

        // get the login values and set up the call
        if (login_type == "phone") {
          // get the values
          use_country_code = $("#country").val();
          use_phone = $("#phoneNumber").val();
          use_phone = use_phone.replace(/\D/g, "");

          // do the validation
          if (use_phone.length < 10) {
            $("#errorText").html(
              "Phone number doesn't have enough digits, please try again."
            );
            $("#errorModal").modal("show");
            return;
          }

          // build the url
          post_url =
            "https://us-central1-dev-api-327415.cloudfunctions.net/user-login?cc=" +
            use_country_code +
            "&phone=" +
            use_phone;
        } else {
          // get the values
          use_email = $("#emailAddress").val();

          // do the validation
          if (!validateEmail(use_email)) {
            $("#errorText").html(
              "Email address does not appear to be valid, please check the format and try again."
            );
            $("#errorModal").modal("show");
            return;
          }

          // build the url
          post_url =
            "https://us-central1-dev-api-327415.cloudfunctions.net/user-login?email=" +
            use_email;
        }

        // send the request to the server and process the results
        $.LoadingOverlay("show");
        $.ajax({
          type: "POST",
          url: post_url,

          // process the returned result of the Ajax call
          success: function (ajaxResult) {
            // see if we have a session token and handle the response
            session_token = ajaxResult["session_token"];
            if (session_token == "None") {
              // hide the login and show the text message area if phone, otherwise hide email and show email message
              if (login_type == "phone") {
                $("#loginMethod").hide();
                $("#phoneLogin").hide();
                $("#codeLogin").show();
                $("#loginMessage").hide();
                $("#textMessage").show();
              } else {
                $("#loginMethod").hide();
                $("#emailLogin").hide();
                $("#loginMessage").hide();
                $("#codeLogin").show();
                $("#emailMessage").show();
              }
            } else {
              // hide everything since already logged in and show the right message
              $("#phoneLogin").hide();
              $("#emailLogin").hide();
              $("#loginMethod").hide();
              $("#loginMessage").hide();
              $("#codeLogin").hide();
              $("#continueLoginAlready").show();
            }
          },

          // process after the Ajax call has been fully completed
          complete: function () {
            $.LoadingOverlay("hide");
          },

          // handle total failure
          error: function (jqXHR, exception) {
            console.log(jqXHR);
            console.log(exception);
            json_error = jqXHR["responseJSON"];
            $("#errorText").html(json_error.error_message);
            $("#errorModal").modal("show");
          },
        });
      }
1
  • 3
    You have script type set to module, so functions defined won't leak into the global scope Commented Nov 19, 2021 at 20:54

2 Answers 2

10

Javascript modules work a bit differently. There, variables and functions are not exposed to the global scope.

If you want to use your function from other parts of the code, you have to set it explicitly on the window object:

function startLogin(...) {
...
}
window.startLogin = startLogin;
Sign up to request clarification or add additional context in comments.

8 Comments

I will mark this as the accepted answer as soon as I'm able, apparently I have to wait at least 10 minutes after posting.
Is there any way to make all the functions in the JS file available globally with one statement, or do I need to add that window statement for every function I want to access?
You’re welcome! And truth to be said, I’ve got 15 years of JS experience and 1 year of Vite experience, and I still had to test, if what I wanted to write in the answer actually works. This is conceptionally very different from the “traditional” way (not better or worse, though). The Vite docs assume (not fully unreasonably but still irksome), that one already obtained the knowledge from somewhere else, e.g. MDN.
The idea of JS modules is, that every bit of JS code is part of a module. So, there is no standard way of exporting loads of stuff to window apart from getting rid of type="module", which has its own side effects.
One possibility is to use a wrapper object. E.g., var MyNamespace = {}; window.MyNamespace = MyNamespace; and then putting every function onto this object. You still have to do it for every function, but the global namespace is less cluttered and you can make things like import * as MyNamespace from 'everything_else.js'; as shortcut if your project structure allows it.
|
-2

an other solution is to set the js at end of the html, than you don't need to use the window object (memory lag)

<html lang="en">
  <head>...</head>
  <body>
    <button type="button" id="myButton">Title</button>
  </body>
  <script>

    function myFunction(){
      console.log('running myFunction');
    }

    const button = document.querySelector('#myButton');
    
    button.addEventListener('click', function clickListener(
    {
      myFunction();
    }
  </script>
</html>

the browser is simply stupid, it loads the page from top to bottom and if you load your js after the body all your html is present and you can do it this way.

2 Comments

You are completely missing the point.
I agree this answer does not really address the issue. The whole point here is to separate the JS out into its own file. Thanks anyways for the suggestion.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.