0

I'm having an issue understanding general javascript/jquery execution order, and where the proper place is to store your js. I'm trying to reorganize my code at the moment, and keep running into the same problems, that just get me more and more frustrated with all of javascript.

I'm building a Python+Flask webapp with Jinja2 templating, incorporating various jquery stuff all over. Initially I had some standard html code that looks a bit like this...

<html>
 <head>
    <script type="text/javascript" src="{{url_for('static',filename='js/jquery/jquery-1.11.2.min.js')}}"></script>
    <script type="text/javascript" src="{{url_for('static',filename='js/bootstrap/bootstrap.min.js')}}"></script>
    <script type="text/javascript" src="{{url_for('static',filename='js/utils.js')}}"></script>

    <script type='text/javascript'>
       # get hash from page
       function getHash() {
          var hash = window.location.hash;
          var newhash = hash.slice(hash.search('_')).replace('_','#');
          return newhash;
       }

      // Load ifu div element if hash present when page loads
      $(function(){
        var ifuhash = getHash();
        var ifu = ifuhash.slice(ifuhash.search('#')+1);
        $(String(ifuhash)).fadeIn();    
        if (hash.search('comment') >= 0) $(String(hash)).fadeIn();
      });           

      ... other js ....

    </script>
 </head>

 <body>
    {% include 'header.html' %}
 </body>
</html>

This runs just fine. But since a good practice seems to be to put all JS inside their own files, so as not to clutter up the html, I'm trying to reorganize my current javascript code to what I think should work, but I'm running into problems.

the function getHash is common to several pages so I pulled that out and stuck it in a separate piece of js,called plateinfo.js, such that my code was now

<html>
 <head>
    <script type="text/javascript" src="{{url_for('static',filename='js/jquery/jquery-1.11.2.min.js')}}"></script>
    <script type="text/javascript" src="{{url_for('static',filename='js/bootstrap/bootstrap.min.js')}}"></script>
    <script type="text/javascript" src="{{url_for('static',filename='js/utils.js')}}"></script>
    <script type="text/javascript" src="{{url_for('static',filename='js/plateinfo.js')}}"></script>

    <script type='text/javascript'>
      // Load ifu div element if hash present when page loads
      $(function(){
        var ifuhash = getHash();
        var ifu = ifuhash.slice(ifuhash.search('#')+1);
        $(String(ifuhash)).fadeIn();    
        if (hash.search('comment') >= 0) $(String(hash)).fadeIn();
      });           

      ... other js ....

    </script>
 </head>

 <body>
   {% include 'header.html' %}
 </body>
</html>

and plateinfo.js looks like

// Toggle add comment login function
$(function() {
    $('#addcommentbut').click(function() {
        var fxn = 'grabComments';
        $('#fxn').val(fxn);
    });
});

// Get IFU hash from page
function getHash() {
    var hash = window.location.hash;
    var newhash = hash.slice(hash.search('_')).replace('_','#');
    return newhash;
}

// Render Tags in div
function renderTags(hash) {
  ..does stuff
}

However, when I do this, I now get the error

ReferenceError: getHash is not defined

The function is outside the DOM scope and should be importable. And I have done this successfully before. The utils.js file contains standalone functions that work perfectly. I've cleared my cache, restarted my browser to refresh the session, restarted my computer, but nothing seems to work. So what's the deal? I thought the execution order of html/js code was direct top-to-bottom. So what's the best practice here? What's the right import/execution order?

Update to show execution order:
So I put in a bunch of console.logs like so

<html>
 <head>
    <script type="text/javascript" src="{{url_for('static',filename='js/jquery/jquery-1.11.2.min.js')}}"></script>
    <script type="text/javascript" src="{{url_for('static',filename='js/bootstrap/bootstrap.min.js')}}"></script>

 <script type='text/javascript'>$(function() {console.log('before utils.js');});</script>
    <script type="text/javascript" src="{{url_for('static',filename='js/utils.js')}}"></script>
<script type='text/javascript'>$(function() {console.log('after utils.js');});</script>

<script type='text/javascript'>$(function() {console.log('before plateinfo.js');});</script>
    <script type="text/javascript" src="{{url_for('static',filename='js/plateinfo.js')}}"></script>
<script type='text/javascript'>$(function() {console.log('after plateinfo.js, before last script');});</script>

    <script type='text/javascript'>
      console.log('start of last script');

      // Load ifu div element if hash present when page loads
      $(function(){
        console.log('calling gethash');
        var ifuhash = getHash();
        var ifu = ifuhash.slice(ifuhash.search('#')+1);
        $(String(ifuhash)).fadeIn();    
        if (hash.search('comment') >= 0) $(String(hash)).fadeIn();
      });           

      ... other js ....

    </script>
 </head>

 <body>
   {% include 'header.html' %}
 </body>
</html>

and here's the log output

before utils.js plateInfo.html:40:1
after utils.js plateInfo.html:44:1
start of last script plateInfo.html:204:5
before plateinfo.js plateInfo.html:185:3
after plateinfo.js, before last script plateInfo.html:189:15
calling gethash plateInfo.html:221:3
ReferenceError: getHash is not defined plateInfo.html:222:6

Why is this?

Aside: I'm also having separate but related issues with referencing my Jinja2 template variables inside javascript. This is happening with this header.html file I'm including. If I include all the js code inside the same html file, it works fine. But when I move the js to a separate header.js, the template variables no longer are referenced properly. Maybe this deserves a separate post.

havok2063
  • 517
  • 2
  • 9
  • 25
  • I would think that {{url_for('static',filename='js/plateinfo.js')}} is being evaluated after the DOM is ready, so that your inline script is firing before your scripts are all loaded. I would maybe try inserting console.logs in various places in your code so you can track when the components are being loaded. – Neil S Sep 17 '15 at 18:01
  • So yeah, I tried that, and it seems like it's calling everything way out of order. It's skipping over console.logs, then going back to them afterwards. I have no idea what's going on. If it is occurring in the right order, then why isn't the method getting defined. I've edited the text to update with the log order. – havok2063 Sep 17 '15 at 18:34
  • @Neil Jinja is a Python templating language; I'm almost certain it will get evaluated on the server and not in the browser. – Dan O Sep 17 '15 at 18:37
  • don't put your console.logs in jQuery onload methods. They are all being executed at once, one after the other. A better methodology would be to put the console.log calls directly in the files themselves (utils.js, plateinfo.js) – Neil S Sep 17 '15 at 20:28

1 Answers1

0

getHash() is not defined as a global function. It's private, belonging only to the function in plateinfo.js which you tell jQuery to run when the page loads. The way your code is currently set up, you can't get to it anywhere else. But in your inline JS, that's what you're trying to do:

var ifuhash = getHash();

You can make getHash() available globally:

function getHash() {
  // ...
}

window.getHash = getHash;

but this is generally considered poor coding practice. A better way to structure your code is to use namespaces to keep all of your custom functions and objects in one place and attach that to window:

<script type="text/javascript">
  window.myNamespace = window.myNamespace || {};
  window.myNamespace.getHash = function() {
    return 123;
  };
</script>
<script type="text/javascript">
  $(function() {
    var foo = window.myNamespace.getHash(); // foo now equals 123
  });
<script>
Community
  • 1
  • 1
Dan O
  • 6,022
  • 2
  • 32
  • 50
  • ah, I mis-parsed your `plateinfo.js` file. But I still bet your problem has to do with the scope you declare your function in and the scope you try to run it in. – Dan O Sep 17 '15 at 19:02
  • He's declaring getHash in the global namespace already, it's not a closure. Just because the function declaration is in a separate file, that doesn't mean it's not being declared in the global scope. – Neil S Sep 17 '15 at 20:26
  • I think this was sort of the answer. I ended up rewriting the entire plateinfo.js so it was an object, which I instantiate in my main code block. I converted getHash into a function that runs on initialization and stores the result in a variable in the object. This seemed to simplify and solve my problem. – havok2063 Sep 22 '15 at 15:45