Jump to content

Recommended Posts

Hopefully this will get stickied.

 

Being a member of this board for a couple years, I've noticed a trend with those seeking JavaScript help.  Most people, despite their good intentions, are unaware of the best way to structure their scripts.  This leads to convoluted code, and even errors, which can stymie the best script writer.  It's not their fault - the internet is filled with horrible tutorials based on outdated methodolgy and free scripts that are, literally, worthless.  Attempting to learn from these sources tends to cause confusion rather than reduce it.

 

Properly structured JavaScript is easy to write, easy to maintain, and easy to debug.  It allows the developer to gracefully add features to their site while ensuring that the core content is available to all.  So, with that said, let us begin.

 

A website is typically developed and arranged in tiers:

 

There's the server-side processing that handles requests and processes forms.  It stores and retrieves values from the database.  It even serves up the proper page to the user itself.

 

Next, there's the actual HTML document.  Regardless of where it came from, and what server-side black magic was used to create the document, this is the one constant for all sites.

 

Finally, there's the client-side processing - JavaScript.  Used correctly, JavaScript is bolted on top of the HTML, facilitating greater user interaction and feedback.  It adds dynamism to what would otherwise be a static presentation.

 

When looked through the lens of designing websites in tiers, it becomes apparent that these tiers, these layers should have clearly defined boundaries.  They should lay on top of one another, and talk with each other through specific lines of communication.  They should not be mixed haphazardly.

 

HTML writers are already familiar with this thought process.  After all, this kind of separation of duties is what resulted in CSS being developed.  One is for structure, the other for formatting, and both are at their most useful when separated from each other.  The same applies to JavaScript.

 

How is this separation achieved?  By using what is commonly termed unobtrusive JavaScript.  It's a unique term that means a simple premise - there should be NO JavaScript code embedded in HTML code, with the exception of the <script> tags.  Even inline event handlers are not allowed.

 

Why?  Because embedding JavaScript within HTML creates coupling.  The two become so tied together that an edit in one requires an edit in the other.  Not only that, but it muddies which responsibility lies with which component.  Both of these, in turn, create code that is difficult to debug and maintain.  Even inline event handlers create unnecessary ties to the HTML document.  There's no reason to go down that road if a better alternative can be found.

 

Thankfully, JavaScript itself gives developers a couple different ways to grab a hold of HTML elements: getElementById(), and getElementsByTagName().  With these two tools, the developer can obtain any element within their HTML document.

 

So, with all that said, how is a proper JavaScript script structued?  The first step is to ensure that the script won't be executed until the HTML is fully loaded.  This is necessary, as JavaScript tends to be processed by the browser before the HTML is fully rendered, which makes it impossible for the script to obtain all of the HTML element references it needs in order to work.  This is one of the primary causes for things being 'undefined' in a script.  These elements are undefined because they don't actually exist when the JavaScript code attempts to access them.  Fixing this is, thankfully, a simple matter:

 

<script type="text/javascript">
   window.onload = function()
   {
      /* script goes here */
   }
</script>

 

The code won't fire until the document is fully loaded.  What's next?

 

Now, it's time to get a hold of the elements that need to be accessed by using one of the built-in JavaScript selectors:

 

<script type="text/javascript">
   window.onload = function()
   {
      var myButton = document.getElementById('myButton');
      var mySpans = document.getElementsByTagName('span');

      /* more script goes here */
   }
</script>

 

Elements are stored within variables for two main reasons:

 

1. It gives the developer an easy way to access the element.  Accessing myButton is far quicker than using getElementById() every time one wants to access that element.  It saves typing, and improves readablity.

 

2. It's more efficient code.  Rather than forcing JavaScript to traverse through multiple elements to find the right id every time one wants to access the element, a direct reference to the element is stored after the initial successful search.

 

Now, let's do something with these elements:

 

<script type="text/javascript">
   window.onload = function()
   {
      var myButton = document.getElementById('myButton');
      var mySpans = document.getElementsByTagName('span');

      myButton.onclick = function()
      {
         for(var i = 0; i < mySpans.length; i++)
         {
            mySpans[i].style.color = 'red';
         }
      }
   }
</script>

 

As you can see, adding an event handler is trivial.  One could also use a named function:

 

<script type="text/javascript">
   window.onload = function()
   {
      var myButton = document.getElementById('myButton');
      var mySpans = document.getElementsByTagName('span');

      myButton.onclick = turnToRed;

      function turnToRed()
      {
         for(var i = 0; i < mySpans.length; i++)
         {
            mySpans[i].style.color = 'red';
         }
      }
   }
</script>

 

The finalized code would look something like:

<html>
   <head>
      <title>Unobtrusive JavaScript Example</title>
      <script type="text/javascript">
         window.onload = function()
         {
            var myButton = document.getElementById('myButton');
            var mySpans = document.getElementsByTagName('span');

            myButton.onclick = function()
            {
               for(var i = 0; i < mySpans.length; i++)
               {
                  mySpans[i].style.color = 'red';
               }
            }
         }
      </script>
   </head>

   <body>
      <span>This sentence will turn red.</span><span>  As will this one.</span>
      <br /><br />
      <button id="myButton">Click</button>
   </body>

</html>

 

Admittedly, it's not the most interesting HTML document.  But it serves its purpose as a simple example that unobtrusive JavaScript can work.

 

Isn't that a lot of code to do something so simple?  Yes, it is.  Unobtrusive JavaScript tends to suffer when it comes to writing trivial code.  There's a lot of work to do up-front in order to create that wedge between the script and the markup.  In larger projects, however, unobtrusive techniques shine, especially when it comes to adding the same event handler to multiple elements.

 

Beyond that, though, unobtrusive techniques just work.  Given the amount of time people tend to spend beating their heads against the wall while trying to fix their meshed code, it seems that the extra few minutes writing in an unobtrusive manner more than makes up for the frustration.

 

In the end, the entire point comes down to doing things right the first time.  Writing scripts with structure in mind may not automatically ensure success, but it definitely helps.

 

I hope this helps some people.  If anyone is more curious about this, read through my post history.  The code there is most likely far more illuminating than my canned example above.

Not bad. But, this would probably be better as a tutorial instead of a sticky.

 

Also, you should cover methods of using parameters within functions. For example, if you have a single process which is performed by mutiple buttons (but with different parameters) you can't specify a parameter when assigning an event handler. See the following:

<script type="text/javascript">
   window.onload = function()
   {
      myButton1 = document.getElementById('myButton1');
      myButton2 = document.getElementById('myButton2');
      //myButton1.onclick = commonFunc('bar');  // NOT CORRECT
      //myButton2.onclick = commonFunc('other');  // NOT CORRECT
      myButton1.onclick = commonFunc;  // CORRECT - but no parameter
      myButton2.onclick = commonFunc;  // CORRECT - but no parameter
   }

   function commonFunc(foo)
   {
      //Do something with parameter foo
   }

</script>

 

So, instead of putting an event handler in-line with the HTML such as

<button id="myButton1" onclick="commonFunc('bar')">Label</button>

 

You would have to include some method to allow the specification of parameters when assinging event handlers. There are several ways to do this.

 

1. Add "custom" parameters to the HTML and reference in the common function

<button id="myButton1" foo="bar">Label</button>

 

2. Add the parameters directly to the object and reference in the common function

myButton1 = document.getElementById('myButton1');
myButton1.foo = 'bar';

 

3. Create a custom function for each button

myButton1 = document.getElementById('myButton1');
myButton2 = document.getElementById('myButton1');
myButton1.onclick = function() { commonFunc('bar'); }
myButton2.onclick = function() { commonFunc('other'); }

 

however, in my opinion, separating the event handlers from the HTML elements makes it more difficult for management and debugging. If there's a problem I can see the function call on the element that generated the error. If not, I have to dig through the onload JavaScript to see how the even handler may have been assigned.

I actually like the combination of using a custom attribute followed by a delegate.  It makes things semantically clear while keeping the script separate from the markup.

 

<script type="text/javascript">
   window.onload = function()
   {
      var myButtons = document.getElementsByTagNames('button'); //assuming they're the only buttons we want..used here because I'm a lazy typist

      for(var i = 0; i < myButtons.length; i++)
      {
         myButtons[i].onclick = function()
         {
            executeCommand(this);
         }
      }

      function executeCommand(button)
      {
         switch(button.cmd)
         {
            case "something":
                /* do something */
                break;

            case "another thing":
               /* do another thing */
               break;

            /* etc */
         }
      }
   }
</script>

<!-- generic HTML here -->

<button cmd="something">Do something</button>
<button cmd="another thing">Do another thing</button>

 

The only fishy 'smell' I'm getting is wondering whether or not that would validate.  That said, this is pretty much how ASP.NET solves this problem.  Its Button server control has an attribute aptly named CommandName that does the same thing as shown above.  I know that mentioning .NET is taboo on a PHP board, but if it works, why not?

That won't work in FireFox. If you create "custom" tag parameter - such as 'cmd' above, you cannot reference them via object.parameter in FireFox. You have to use:

object.getAttribute('cmd')

But, personally, I would go with the 3rd option I posted earlier.

 

However, using a switch will be problematic in many situations. When dealing with a dynamic page, the JavaScript will need to be built dynamically as well. It is not impossible to do so, but it is more difficult to create/maintain the javascript programatically. For example, if you are displaying a list of records from a database and based upon certain fields in the record you will have custom JavaScript actions (e.g. a list of images with a different mouseover event based upon whether the image is in a specific group). That would require that you build the JavaScript and the HTML separately witin the same process.

 

Again, that is perfectly doable, and sometimes absolutely necessary. But, it does make it more difficult, IMHO.

 

In any case, I think what you presented above has merit. I'd just suggest you build it out a little more using some real-world examples.

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.