CodeinWP CodeinWP

Making Unobtrusive JavaScript Practical Again

Making Unobtrusive JavaScript Easier to Track DownMaybe it’s just me. Maybe I’m a JavaScript debugging n00b, or maybe it’s my limited knowledge of Firebug.

While I fully support the use of unobtrusive JavaScript in all projects, I do find it has one weakness: Connecting the HTML element with the code that acts upon it.

If you’ve been coding JavaScript since the dark ages of the web, you’ll know that JavaScript used to be commonly implemented in an obtrusive fashion that mixed markup with behaviour, which made your code messy, and added extra code to every individual page rather than putting it in an external reusable file.

Here is a simple example:

<a href="#" onclick="doSomething(); return false;">click this link</a>

If you did this once, it wasn’t such a big deal. But when multiple events were added to the DOM, this slowly became a nightmare for maintenance and page bloat, and so this method has mostly been done away with.

But, in my opinion, the maintenance issue is still complicated somewhat due to the fact that there is (apparently) no easy way to find out what behaviours occur on what elements.

Let me show you a simple example. Here’s the HTML:

<a href="#" id="myLink">click this link</a>

When you click the above link, any number of things could happen. Maybe it opens a modular window, maybe it causes something to slide up or slide down, maybe some content appears, or maybe an Ajax-based event is called. You’ll only know what happens if you click the link. But where does the event come from? Since we have no inline function call, how can we find out where the code is located?

How Do You Track Down the Code?

We could do a search in one of our external JavaScript files for the id “myLink”, assuming that the event is based on that selector. But what if there are multiple files to look through? Well, then I suppose if we have a good text editor, we can do a project-wide search for that particular id. But this would still take a little bit of time.

In my opinion, although the obtrusive method is not desirable, it’s much easier to deal with. Especially since we don’t always know just by viewing the HTML if an element is being targeted by JavaScript. In the above example where the link has an id, we suspect that there might be some JavaScript behaviour acting on the link, but we don’t really know for sure just by viewing the HTML.

And this process becomes even more complicated if the element is targeted through the id of a parent element, or through an element type selector.

Does Firebug Help Us?

I have tried to use Firebug to do this, but the best I can see that it does is tell you what scripts to look in, which doesn’t really help much, since I could just do that on my own. I ran the problem by a JavaScript contact of mine, and he agreed that it was not easy to do this with Firebug, because when a developer uses Firebug, they’re normally working from the source files, so will already know which events act on which elements.

This process would especially be complicated if there were multiple behaviours on the same page, multiple scripts being loaded, and if jQuery or another library was thrown into the mix.

Someone on Twitter suggested using the “Break on…” features in Firebug. Those options are available when you right-click an element inside the HTML inspector. I couldn’t figure out how to use those features to identify the event, file name, or line number. I’d be glad to hear if someone is able to use that feature to track down the code that fires an event.

Additionally, some solutions have been proposed on Stack Overflow, but many of them are somewhat complicated and seem to be more time consuming than should be necessary.

A Nice Bookmarklet That Solves Part of the Problem

A couple of the posts on Stack Overflow included mention of a bookmarklet called Visual Event, so I checked it out. This is a must-have bookmarklet for JavaScript developers. After you drag the bookmarklet to your bookmarks bar, just visit any page and click the bookmarklet. The page will be overlaid with a visual representation of all JavaScript events being fired, and where they’re located in the DOM:

Visual Event Bookmarklet

As you can see from the above screenshot, when the bookmarklet is triggered, the page is overlaid with some semi-transparent elements to indicate where the events take place. If you hover over any of the blue visuals, the code that fires the selected event appears in an another overlay. This is great, and can help us more quickly to figure out where to find the associated event.

But it still has one small flaw (unless I’m mistaken): It doesn’t tell us what specific JavaScript file to look in. So, we still have to rummage through our external files, which is the same problem mentioned earlier when using an element selector (although the code revealed by the bookmarklet does give us a definite way to find the event, since the exact code is shown. It just takes a little more time than I would like).

A Possible (But Tedious) Solution

I have a very simple solution that allows the developer to be able to see immediately from viewing the HTML (whether in a DOM viewer or just by viewing the source) where to find the behaviour for that element:

<a href="#" id="myLink">click this link</a>
<!-- JS: scripts/link.js line 34 -->

The HTML comment below the link tells the developer 3 things: (1) An unobtrusive JavaScript event is acting on the link; (2) the path to the file that contains the code; (3) the line of code inside the file where the behaviour occurs. But admittedly this might be overkill.

In many cases, it would not be very practical to include all those details. There might be new code added to the file that changes the line number, or the file itself might be merged with another file. So, you obviously don’t want to have to change all your JS “comment hints” whenever you make modifications to your code. So maybe this solution without the line number would work better:

<a href="#" id="myLink">click this link</a>
<!-- JS: scripts/link.js selector: myLink -->

Now the code tells you that there’s definitely a JS event occurring here, and where to find it. I’ve also included a selector name that indicates how the element is targeted in the JS file. So while the bookmarklet shown above tells you an event is present, and gives you the code, this solution tells you what file to look in. I think the two methods combined improve the ability to find events over what may or may not be possible with Firebug.

What About Dynamic Comment Injection?

An interesting solution was proposed by my JavaScript contact, mentioned earlier. He suggested including a “helper function” that could run when each event is called, and this would add the comments to the code on the fly. This solution, according to him, would be used in conjunction with Firebug, from which you would get the info you need (file name, line number, etc.)

Thus, the HTML would be clean and the comments would still be viewable through the generated source. This could work quite well, and would prevent any changes in the code from altering the comment, because the comment info will be injected based on where the event is actually located.

How Do You Find Events in Foreign Code?

As I mentioned at the outset, it could just be my limited abilities with JavaScript, Firebug, or related tools that makes it difficult for me to track down code in foreign or inherited projects. And while the simple solution I’ve offered isn’t the most practical, in conjunction with the bookmarklet, it could work temporarily in a team environment where events need to be tracked down more rapidly.

Does anyone have a better and easy-to-use solution? I’d love to hear your thoughts on this.

20 Responses

  1. Matt says:

    Firequery works for me.

    • I don’t know anything about FireQuery, but it seems to only work for jQuery-based code. Does it do what I discuss in this article?

    • Greg Rice says:

      I’m a lowly, would-be javascript code-paster, but I found a comment from a bright, young guy who is blogging his progress in code development – Corey Hart of CodeNothing.com

      “…all event’s binded (sic) through jQuery, are actually stored in the
      ‘events’ portion of that elements data object.”

      1. maybe this precludes a need for “firequery”?

      2. I know jQuery is just one library of javascript functions – but it sure
      is prevalent and in interesting, useful projects. So maybe this could help
      clarify a narrow tree-branch on tracking of bound events?

      3. Or am I off the mark – does this info not indicate the ultimate HTML
      affected by the javascript?

      thanks for your great info!

  2. jex says:

    if i use javascript links, i dont put them in the html…
    i generate them with javascript and append them.

    If you put those js links in html non-javascript user will be anoyed cuz they click a link that isnt working :D

    • Dan says:

      Jex, not always. Take this example:

      Click here to view the data
      Data will be loaded here

      
      $(document).ready(function(){
        $('#button').click(function(){
            var data = $(this).attr('href');
            $('#data').load(data);
            return false;
         });
      });
      

      You need the link in your HTML and still provide an elegant solution for those without JavaScript enabled.

      • Dan says:

        Darn, it didn’t display my HTML

        Let’s try this:

        (a href=”data.html” id=”button”)click here to view the data(/a)
        (div id=”data”)data will be loaded here(/div)

  3. nick says:

    Could you change the name of the element and see which functions break?

    Also, if you need a powerful, free text editor that can search multiple documents and/or directories of documents, check out Notepad++ (well, if you’re using Windows anyway). If you’re using Linux, just grep! Probably also possible on a Mac but most Mac users don’t get anywhere near the part of the system that allows such magic.

    • Yeah, in some cases you can cause Firebug to break the event, and it will show you what line number and what file. But it seems a little tedious in my opinion. Maybe I’m just not used to the way Firebug works; I don’t use it a whole lot for JS debugging.

      It just seems easier to be able to right-click an element in the DOM viewer and say “view event”, and that should immediately take you to the file and line number you need.

  4. Martin Chaov says:

    @Louis.

    In the company I am working for at the moment, we’ve had a similar problem. Because of the size of the project sometimes we use the method from the “dark ages”, because it does not involve parsing the whole document on load on the content heavy pages. It sacrifices compliance for performance, but as we know – users are a bit nervous when their software works slowly.

    The other thing we do is, specify one file that calls all global functions, and attaches all mouse and keyboard events to the DOM objects (by different selectors – ids or classes for example). It is a bit more work than usual, but when using much JS, and we do, it has proven to be very useful.

  5. Nick says:

    I’m using chrome 6.0.472.11 dev.

    ctrl+shift+j to open dev tools. Select an element; all its attached event listeners and js line numbers are listed on the right. (you may have to collapse the Styles panel to see them)

  6. I’ve never had a problem finding the code relating to different aspects on pages. I start with viewing the source to find the id of whatever it is acting on, then look at the different javascript files that the page links to at the top. Most often I find the filename is descriptive enough that you can guess which one has the code you’re looking for. If there’s just one javascript file, I just use Ctrl+F to look for my selector, or start scrolling through until I see something that looks like it should do what the code on the page does.

    I’ve never used Firebug or any other related tool. But I’ll probably look into Visual Event, that looked pretty cool.

  7. All of my sites use jQuery, and I generally follow the practice of only attaching events inside of the main $(function () { … }) block, i.e. the on-document-ready block. Thus I know I can just look in any script file relevant to the page, in that particular section, in order to figure out the handler location. And knowing the right script file is usually pretty easy, too: if it’s a global element (appearing on all pages), it’s probably in my /Scripts/Global.js; if it’s specific to the page, it’s probably in PageName.js.

    The only exception is dynamically-added event handlers (i.e. ones that aren’t always on), since it’s not appropriate to wire those up inside $(function () { … }). But in those cases, the elements are often themselves added dynamically, or if they’re not, then the event-adding process is so crucial to the page that it should be obvious enough to look for a function like ProcessCalendarActions() or the like.

  8. Matt Burgess says:

    I actually don’t think this is the issue you think it is. I rarely have trouble finding the action occurring, even on a complex website. You can almost always find it by something bound on the selector, or occasionally on the class, etc. That is trivially easy to find.

    But your “solution” here isn’t a good one.

    <a href="#" id="myLink">click this link</a>
    <!-- JS: scripts/link.js selector: myLink -->

    The point of the way things are done now is to get the javascript references OUT of the HTML, to separate them and make them not dependent on each other. This improves manageability of BOTH the JS and the HTML. Either can be upgraded or revised without them being intertwined.

    What you’re doing there is the worst of both worlds. You make your html require extra maintenence (say you move the javascript) and you make it obvious that “this HTML has javascript on it”. That should be the opposite of the point. You shouldn’t ever need to know that. It should be transparent, irrelevant, a separate and self-contained layer, the functional layer.

    • I agree, in most cases, it’s easy to find the event based on class or id. But that is not always the case, so at times events are somewhat hidden.

      I know that the solution I proposed has maintenance problems, and I pointed that out in the article. Which is why I proposed a simpler comment to be added that wouldn’t require maintenance.

      But I think you’re wrong when you say:

      You shouldn’t ever need to know that. It should be transparent, irrelevant, a separate and self-contained layer, the functional layer.”

      Separation of behaviour from markup is supposed to be for the purpose of maintainability. But if that separation causes maintenance problems, then it is for no purpose. I think we need to get away from the “trend” of separating our layers for separation’s sake, and start creating code that is maintainable.

      But don’t get me wrong: I agree with most of what you’re saying; I just think there are some circumstances where complex JS code can be a little tricky to track down, and a solution like this could help at least a little bit.

  9. qertoip says:

    You made a great point. This is the real issue that bothers me too.

    Moreover, firebug magic should never be necessary just to read and understand the code!

    We need to solve the connection problem in the code. See also Jamis Buck considerations about this: http://weblog.jamisbuck.org/2010/3/2/unobtrusive-yet-explicit

    Ideally, Ctrl+B on the element in your favorit IDE should take you to where the relevant events are defined.

Leave a Reply

Comment Rules: Please use a real name or alias. Keywords are not allowed in the "name" field and deep URLs are not allowed in the "Website" field. If you use keywords or deep URLs, your comment or URL will be removed. No foul language, please. Thank you for cooperating.

Markdown in use! Use `backticks` for inline code snippets and triple backticks at start and end for code blocks. You can also indent a code block four spaces. And no need to escape HTML, just type it correctly but make sure it's inside code delimeters (backticks or triple backticks).