By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
462,185 Members | 673 Online
Bytes IT Community
Submit an Article
Got Smarts?
Share your bits of IT knowledge by writing an article on Bytes.

organizing Javascripts

Dormilich
Expert Mod 5K+
P: 8,639
Organize your Scripts

what you should have:
  • basic Javascript understanding
  • a standard compliant browser
additional goodies:
  • Firefox with the Firebug plugin installed
1. Javascript, the old way or the inline model

in the early days of Web Coding dynamic functionality was added to a web site using the onEvent attributes.

Expand|Select|Wrap|Line Numbers
  1. <span onclick="doSomething()">click me</span>
thus, for every event you had a separate function which was defined in a script tag (preferably located in the <head>)
Expand|Select|Wrap|Line Numbers
  1. <script type="text/javascript">
  2. function doSomething()
  3. {
  4.     // sample code
  5.     alert("you have successfully clicked me");
  6. }
  7. </script>

2. Play it again, Sam or the traditional model

As time went by, people started to put their code in an external javascript file to keep markup and behaviour separate, because you can maintain your code a lot better this way.

Still the functionality was maintained via the onEvent attribute.
Expand|Select|Wrap|Line Numbers
  1. <script type="text/javascript">
  2. function doSomething()
  3. {
  4.     // sample code
  5.     alert("page finished loading");
  6. }
  7.  
  8. // attach the function to an event attribute
  9. window.onload = doSomething;
  10. </script>
Although the script was better organized this way, you still have the functions hanging around and additionally you have to cope with a new problem. While the inline method directly applied the functionality, an external script has to find the elements that need changing first.

This is where the Document Object Model (DOM) came into play. This may seem tedious work at first, but when you advance within Javascript, you'll see the benefits it brings (no Javascript calls in the HTML code required, inclusion of the Javascript is enough).
Expand|Select|Wrap|Line Numbers
  1. // HTML
  2. <span id="submit">click me</span>
Expand|Select|Wrap|Line Numbers
  1. // Javascript
  2. function doSomething()
  3. {
  4.     // sample code
  5.     alert("you have successfully clicked me");
  6. }
  7. // this will only work, if the element is already loaded into the browser
  8. document.getElementById("submit").onclick = doSomething;
  9.  
  10. // to delay any function call after the page load, you have to use the onload event
  11. window.onload = function()
  12. {
  13.     document.getElementById("submit").onclick = doSomething;
  14. }
Note: the Firebug plugin allows you to view the current document tree (DOM tree).

All this code has a real drawback: you can only apply one function call per event (and element), i.e. if you include several scripts, only the last window.onload call will work (reason, you only have on onEvent attribute per element available).

3. Javascript, next generation or the DOM way of life

All this changed, when the W3C discharged the DOM Level 2. Now you can handle events through functions: addEventListener & removeEventListener which bind for a selected event a function to an element.
Expand|Select|Wrap|Line Numbers
  1. function doSomething()
  2. {
  3.     // sample code
  4.     alert("you have successfully clicked me");
  5. }
  6.  
  7. function doSomethingElse()
  8. {
  9.     // code comes here...
  10. }
  11.  
  12. function init()
  13. {
  14.     document.getElementById("submit").addEventListener("click", doSomething, false);
  15.     document.getElementById("submit").addEventListener("click", doSomethingElse, false);
  16. }
  17. window.addEventListener("load", init, false);
Now you can add as many events to an element (from whichever script) as you like.

Note: I only scratch the Event topic here, because it is hardly explained in such a short article, though event handling is at the heart of Javascript (and a good example too).

Further note: I can't cover IE bugs and workarounds here, because that would need an article for itself.

4. Cleaning up the code

there are already good articles out there, howto write good code:
Unobtrusive Javascript
From DHTML to DOM scripting - an example of how to replace outdated JavaScript techniques.
Vitamin Features The Importance of Maintainable JavaScript

So far we used functions to organize our script, which will be no problem as long as there's not too much code. Imagine you have 3 script files and each of them has its own init() function! this will surely result in problems. So what now? If you have 3rd party scripts, you can't really rewrite their code...

Since Javascript has OOP capabilities (admittedly it's no match to Java) we can use this to stop our functions interfering with each other.

To understand how this works, we have to learn about scope (for instance here: Digital Web Magazine - Scope in JavaScript). Every variable in Javascript has a scope (the context in which it is defined). The important ones are Global Scope (the variable is accessible everywhere in the script) and Local Scope (the variable is accessible only inside the function it was defined in). So any function we define (as we did it) will have Global Scope. In every script file.

So if you have two functions defined, the last defined will win because it will overwrite the former (no matter if they are defined in different files or not).
Expand|Select|Wrap|Line Numbers
  1. function init() 
  2.     alert(1); 
  3. }
  4.  
  5. // somewhere else but later
  6. function init() 
  7.     alert(2); 
  8. }
  9.  
  10. // alerts '2'
  11. init();

Note: While talking about scope, there is also a very useful technique called Closures, which you may find handy (JavaScript Closures for Dummies | Developing thoughts — Morris Johns)

Now that we know about scope, how will that benefit us? The two functions from above had the same name ('init') and the same scope (global). To make them different we need to change at least one of them. You can of course invent countless names including 'init', but we chose this name in the first place, because it described (and still does) the function best.

That's leaving us with the option of changing the scope, but how do we do that? That's where Object Oriented Programming (OOP) comes into play. OOP means dealing with Objects.

In short words, an object is a construct, that has (can have) values (called properties) and functions (called methods) that belong exclusively to that object.

Objects are used as follows:
Expand|Select|Wrap|Line Numbers
  1. // define a new value for the property
  2. object.property = x;
  3.  
  4. // assign the property value to a variable
  5. var y = object.property;
  6.  
  7. // call a method
  8. object.method();
  9.  
  10. // call a method with arguments
  11. object.method(arg1, arg2);
  12.  
  13. // doesn't work
  14. property = x;
  15. var y = property;
  16. method();
If you have your eyes open and your brain on, you'll probably see how this can solve our scope problem. For those left I'll tell you:

the object defines the scope for its methods (functions) and properties (values).

Thus: obj1.init() and obj2.init() are two different functions. Got it?

To solve our scope problem, we just define our functions as methods of different objects.

Note: this is not the original idea of OOP, but I'm not explaining OOP here (it would take too long and it's out of the scope of this article).

Let's get it on. How do we define those objects? There is a way of writing (called the Object Literal (Wait till I come! Blog Archive Show love to the object literal)) we can use here.
Expand|Select|Wrap|Line Numbers
  1. var object_name = {
  2.     property_1 : "value_1",
  3.     property_2 : "value_2",
  4.     method_1 : function()
  5.     {
  6.         alert("method 1 executing");
  7.     },
  8.     method_2 : function(x)
  9.     {
  10.         if (this.x)
  11.         {
  12.             alert(this.x);
  13.         }
  14.     }
  15. }
  16.  
  17. // alerts “method 1 executing”
  18. object_name.method_1();
  19.  
  20. // does nothing (value_1 is not the name of a valid property)
  21. object_name.method_2("value_1");
  22.  
  23. // alerts “value_2”
  24. object_name.method_2('property_2');
After knowing how to define objects, we can rewrite the 'init' example from above.
Expand|Select|Wrap|Line Numbers
  1. // since we define the objects (not its methods) in global scope, 
  2. // we can omit the var keyword
  3. NS1 = {
  4.     init : function()
  5.     {
  6.         alert(1);
  7.     }
  8. }
  9.  
  10. NS2 = {
  11.     init : function()
  12.     {
  13.         alert(2);
  14.     }
  15. }
  16.  
  17. // alerts “1”
  18. NS1.init();
  19.  
  20. // alerts “2”
  21. NS2.init();
We now have successfully applied a different scope for each init() function!

5. Structuring the script

When you write a script, then you have (in most cases) only a few number of tasks to do (e.g. event handling (some click events, maybe a mouseover event), dynamic content loading (AJAX, Submenu) or form validation). To get these started, one init() function per task is enough. All the other functions around only help those starter functions getting the work done. So you can easily group the helper functions around the starter functions. And every group can be assigned to its own scope.

Of course you can structure the scopes too … (but that should only be necessary for large libraries)

example:
Expand|Select|Wrap|Line Numbers
  1. myName.menu      // function group for menu creation
  2. myName.forms     // function group for form validation
  3. myName.commons   // functions used in many groups 
  4.                  // e.g. event handling, loop functions, …
If you have read about OOP, you may have heard that an object may possess private properties and methods (the keyword here is ‘(data) encapsulation’), that can only be called inside the object and are not accessible from outside (no matter what scope it is). Wouldn't it be useful to make all helper functions of a group private (if the function is used only in the group, why granting global access)?

We can do that too. With a little change in the code, we can make public and private properties and methods!

Expand|Select|Wrap|Line Numbers
  1. // our scope (let's call it namespace …)
  2. bytes = {
  3.     // subclasses
  4.     // technically not necessary, but providing an overview
  5.     // esp. if you have many subclasses
  6.     josah : new Object,
  7.     msquared : new Object
  8.  
  9.     // you can define metadata here (dublincore.org)
  10. }
  11.  
  12. // object with only public members
  13. bytes.msquared = {
  14.  
  15.     favouriteColour : "purple",
  16.  
  17.     hateColour : "gray",
  18.  
  19.     state : function()
  20.     {
  21.         alert("I like " + this.favouriteColour);
  22.     },
  23.  
  24.     stateNot : function()
  25.     {
  26.         alert("I hate " + this.hateColour);
  27.     }
  28. }
  29.  
  30. // alerts "I hate gray"
  31. bytes.msquared.stateNot();
  32.  
  33. // alerts "I like blue"
  34. bytes.msquared.favouriteColour = "blue";
  35. bytes.msquared.state();
  36.  
  37. // an object with private and public members
  38. // I didn't go for real functions here……… ;-)
  39. bytes.josah = function() {
  40.  
  41.     // this is a private property
  42.     var hateColour = "purple";
  43.  
  44.     // this is a private function
  45.     // the 'this' keyword does not necessarily
  46.     // point to bytes.josah!
  47.     function throw()
  48.     {
  49.         this.lift();
  50.         this.move();
  51.         this.drop();
  52.     }
  53.  
  54.     return {
  55.  
  56.         // this is a public function
  57.         defenestrate : function(num)
  58.         {
  59.             var OP = thread[num].firstChild;
  60.             OP.addEventListener("unlike", throw, true);
  61.  
  62.             // you can call members from different scopes
  63.             bytes.msquared.hateColour = hateColour;
  64.             bytes.msquared.stateNot();
  65.         }
  66.     };
  67. }();
  68.  
  69. // doesn't work
  70. bytes.josah.throw();
  71.  
  72. // works (but I wouldn't try it)
  73. // defenestrates #666 and alerts "I hate purple"
  74. bytes.josah.defenestrate(666);
now you can group all your functions in classes and you're perfectly organized.

And finally a real world example

Description: popup an image (or anything else you can do within window.open()) when you click on an registered element. there's no constraint, what the element can be, as long as its id attribute is registered for popup. you can even open more than one popup windows, though don't make it too many (the user might get annoyed then).
Expand|Select|Wrap|Line Numbers
  1. /*
  2. #########################################################
  3. #         Popup script by Dormilich, bytes.com          #
  4. # http://bytes.com, If you use this script on your site #
  5. # Please keep this comment at the top of the javascript #
  6. #########################################################
  7. */
  8. Dormilich.Popup = function() {
  9.  
  10.     /**
  11.      * [private] array to store the objects holding the necessary information for window.open()
  12.      */
  13.     var IMG = new Array;
  14.  
  15.     /**
  16.      * [private] calling window.open() on event
  17.      * since the image info object is not part of the element, the correct image object
  18.      * is identified by ID comparison
  19.      *
  20.      * @return (void)
  21.      */
  22.     function pop() 
  23.     {
  24.         var l = IMG.length;
  25.         for (var j=0; j<l; j++) 
  26.         {
  27.             // because pop() is used by addEvent() 'this' points to the object
  28.             // having the event attached to
  29.             if (IMG[j].id == this.id) 
  30.             {
  31.                 window.open(IMG[j].src, IMG[j].titel, IMG[j].props);
  32.             }
  33.         }
  34.     }
  35.  
  36.     return {
  37.  
  38.         /**
  39.          * [public] create an image info object that hold the parameters for window.open() and put
  40.          * it in the private storage array. src, titel & props are the parameters for window.open()
  41.          *
  42.          * @param (string) ID   id of the element to apply the onclick event to
  43.          * @param (string) LOC  the location of the image to popup
  44.          * @param (string) TTL  title of the popup window
  45.          * @param (int) HGT     image height
  46.          * @param (int) WDT     image width
  47.          * @return (void)
  48.          */
  49.         create : function(ID, LOC, TTL, HGT, WDT) 
  50.         {
  51.             var obj = {
  52.                 id    : ID,
  53.                 src   : LOC,
  54.                 titel : TTL,
  55.                 props : "height=" + HGT + ",width=" + WDT + ",menubar=no,resizable=yes,scrollbars=yes"
  56.             }
  57.             IMG.push(obj);
  58.         },
  59.  
  60.         /**
  61.          * [public] loop through all element in the private storage array and attach the private pop() method
  62.          * with the onclick event handler. (interestingly, even though the addEvent() method comes from a
  63.          * different class it's executed in the local scope)
  64.          *
  65.          * @return (void)
  66.          */
  67.         register : function()
  68.         {
  69.             var l = IMG.length;
  70.             for (var i=0; i<l; i++) 
  71.             {
  72.                 var z = document.getElementById(IMG[i].id);  
  73.                 if (z) 
  74.                 {
  75.                     z.style.cursor = "pointer";
  76.                     // currently Tino Zijdel's addEvent() function namespaced
  77.                     Events.addEvent(z, "click", pop);
  78.                 }
  79.             }       
  80.         }
  81.  
  82.     };  
  83. }();
  84.  
  85. Events = {
  86.     addEvent : function(obj, evType, fn, useCapture)
  87.     {
  88.         // full code see http://therealcrisp.xs4all.nl/upload/addEvent2
  89.         // of course you can use any other addEvent() function you like
  90.     }
  91. };
  92.  
sample usage:

Expand|Select|Wrap|Line Numbers
  1. /* Javascript */
  2. // add image
  3. // assigning the image "test.png" to the element with the id "test"
  4. // a new 500x700 popup (inner size) will appear onclick
  5. Dormilich.Popup.create('test', 'test.png', 'TST', 500, 700);
  6. // open a PDF in a new window (just used a random size)
  7. Dormilich.Popup.create('oath', 'slayers_try_prophecy.pdf', 'TRY', 387, 632);
  8. // register elements/images
  9. Events.addEvent(window, "load", Dormilich.Popup.register);
  10.  
  11. /* HTML */
  12. // show a full size image when clicking on a thumbnail
  13. <img src="test_thumb.jpg" width="50" height="70" alt="test image for popup" title="click for full image" id="test" class="thumbnails" />
  14. // show the full text of an essay
  15. <div id="oath" class="shortdesc" title="click for full text">There shall come a controller of a dark star who shall call forth the light. (…)</div>
  16.  
終 (the end)

Dormilich
Dec 21 '08 #1
Share this Article
Share on Google+