Chapter 42. Avoiding Global Scope

In Lesson 39, I mentioned that it's good practice to avoid the global scope. This is crucial when you incorporate someone else's code in your page, as it helps you avoid naming conflicts between the variable and function names given in yours and the other party's code. What exactly is a naming conflict? A naming conflict occurs when you use an identifier that is used in another script on the same level of scope.

Let's look at an example. The following code is a truncated version of the eventUtility object used throughout this book:

// your code
var eventUtility = {
    addEvent : (function() {
        if (typeof addEventListener !== "undefined") {
            return function(obj, evt, fn) {
                obj.addEventListener(evt, fn, false);
            };
        } else {
            return function(obj, evt, fn) {
                obj.attachEvent("on" + evt, fn);
            };
        }
    }())
};

This code creates the eventUtility object with the addEvent() method. This code is defined in the global scope, and your event-driven code heavily relies on this particular object and method. But you wanted to add some extra functionality to your page, and you incorporated code written by someone else. It too uses a global object called eventUtility, but it's different from yours. Its code is as follows:

// third party code
var eventUtility = {
    addEventHandler : function(obj, evt, fn) {
obj.addEventListener(evt, fn, false);
    }
};

The browser loads this code after loading yours. Since they both share the same identifier and they exist on the same level of scope (global), this second eventUtility object overwrites your eventUtility object. That's a huge problem because the rest of your code in the page makes extensive use of the eventUtility.addEvent() method, and it no longer exists because eventUtility was overwritten by someone else's code. So your page breaks, and you could spend a lot of time trying to figure out why. Obviously, you want to avoid naming conflicts. Let's see how you can do just that.

USING FUNCTIONS

In Lesson 6 you learned about scope and that functions have their own levels of scope; the variables, objects, and functions defined inside another function cannot be accessed from outside that function. In Lesson 20, while writing a cross-browser event utility, you learned about self-executing functions — anonymous functions that were executed immediately after they were defined. These two ideas, functional scope and self-executing functions, can be combined in order to hide your code from the global scope.

Let's look at an example. The following is some code using a self-executing function to isolate code from the global scope:

var someVariable = "Hello, Global!"; ///

(function() {

var someVariable = "Hello, Privacy!";

alert(someVariable); // Hello, Privacy!

}());

alert(someVariable); // Hello, Global!

The first line of this code creates a global variable called someVariable. It can be accessed anywhere in the page, and as a result it can potentially be overwritten by some other data.

Next a self-executing function is defined, and its body consists of a local variable called someVariable (the same name as the global variable) and a call to alert() to display the value of someVariable. Remember, Lesson 6 discussed how a variable or function in a local scope can override (not the same as overwrite) a global variable or function with the same name. The global variable or function is still intact with its value, but in the local scope the variable or function defined locally is used in place of the global variable or function. So when this self-executing function executes, the value of the local someVariable variable is displayed in an alert window.

After the function finishes, the final line of this code executes and alerts the text "Hello, Global!" because the global someVariable variable was not altered.

Let's look at another example. This one uses the eventUtility example from earlier in the lesson:

(function() {

var eventUtility = {
    addEvent : (function() {
        if (typeof addEventListener !== "undefined") {
            return function(obj, evt, fn) {
                obj.addEventListener(evt, fn, false);
            };
        } else {
            return function(obj, evt, fn) {
                obj.attachEvent("on" + evt, fn);
            };
        }
    }())
};

function documentLoad(event) {
    alert("Document loaded!");
}

// use local version of eventUtility and documentLoad()
eventUtility.addEvent(document, "load", documentLoad);

}());

function documentLoad(event) {
    alert("Event handled with third party");
}

// use third party eventUtility and global documentLoad()
eventUtility.addEventHandler(document, "load", documentLoad);

This code first executes a self-executing function. Inside this function your eventUtility object is defined and used to assign an event handler to handle the document object's load event. When the event fires, an alert window displays the text "Document loaded!"

After the function executes, another event handler is assigned to handle the document's load event using the third-party eventUtility object. When a page loads with this JavaScript you'll see two alert boxes: The first tells you the document was loaded, and the second says, "Event handled with third party" in browsers supporting addEventListener().

So by putting your code into a self-executing function, you can effectively protect it from the rest of the JavaScript code loaded in the page. But there is a problem here. The eventUtility object is something you want to use throughout your application — possibly in different JavaScript files. If it's hidden inside a function, how can other pieces of code use it? Shouldn't it be globally accessible so that you can use it wherever you need to? Yes, it should, and you can make it so by emulating namespaces.

EMULATING NAMESPACES

Many programming languages have built-in constructs called namespaces to help avoid naming collisions. A namespace is a container that provides context for the objects it contains while protecting the names of those objects from colliding with the names of other objects outside the namespace. Namespaces aren't a foolproof means of preventing naming collisions, but they do a good enough job.

Unfortunately, JavaScript does not support a formal namespace feature, but you can emulate namespaces by using object literals.

Note

This book refers to emulated namespaces as pseudo-namespaces, since they are not technically namespaces but serve the same purpose.

A namespace should be something meaningful, and yet somewhat unique to you, such as your name or your site's domain name. Your author's website is www.wdonline.com, so you could create a pseudo-namespace for him by typing the following:

var wdonline = {};

Then you can start to tack on the code you need to globally access, as in the following code:

wdonline.eventUtility = {
    addEvent : (function() {
        if (typeof addEventListener !== "undefined") {
            return function(obj, evt, fn) {
                obj.addEventListener(evt, fn, false);
            };
        } else {
            return function(obj, evt, fn) {
                obj.attachEvent("on" + evt, fn);
            };
        }
    }())
};

In the case of this code, the wdonline object is global, but the likelihood of wdonline being overwritten by a third party's code is slim. The eventUtility object is now a property of wdonline, giving it protection from being overwritten. If the chances of wdonline's being overwritten are slim, they're even smaller for wdonline.eventUtility.

The reality is that you cannot get away from the global scope if you want certain pieces of your code to be accessible anywhere in the page, but you can do your best to avoid naming collisions by using self-executing functions and pseudo-namespaces.

TRY IT

In this lesson, you learn how to minimize the impact your code has on the global scope in order to reduce the likelihood of naming collisions.

Lesson Requirements

To practice the techniques outlined in this lesson, you will refactor the eventUtility and ajaxUtility objects to include the use of self-executing anonymous functions and pseudo-namespaces. To do this, you will change the example in Lesson 35, the car dealership search using JSON.

You need a text editor. For Microsoft Windows users, Notepad is available by default on your system or you can download Microsoft's Visual Web Developer Express (www.microsoft.com/express/web/) or Web Matrix (www.asp.net/webmatrix/), both of which are free. Mac OS X users can use TextMate, which comes as part of OS X, or download a trial version of Coda (www.panic.com/coda/). Linux users can use the built-in VIM.

You also need a modern web browser. Choose any of the following:

  • Internet Explorer 8+

  • Google Chrome

  • Firefox 3.5+

  • Apple Safari 4+

  • Opera 10+

You also need a webserver installed with PHP support. Refer to Lesson 31 on the DVD for installation instructions.

Create a subfolder called Lesson42 in your webserver's root directory. Copy the contents of the Lesson35 directory into Lesson42, and rename the lesson35_example01.htm file to lesson42_example01.htm.

Step-by-Step

  1. Open lesson42_example01.htm in your text editor, and change the <title/> element to contain the text Lesson 42: Example 01.

  2. Create a new file called lesson42_example01.js, and move all the in-line JavaScript to that new file. Also remove the onsubmit event handler from the <form/> element. The altered HTML should look like this:

    <html>
    <head>
        <title>Lesson 42: Example 01</title>
    </head>
    <body>
    <div id="divResults">
        <div id="divSearchMessage"></div>
        <div id="divSearchResults"></div>
    </div>
    <form name="theForm" method="post" action="car_dealership.php">
        <p>
            Make: <input type="text" name="txtMake" value="" />
        </p>
        <p>
            Model: <input type="text" name="txtModel" value="" />
        </p>
        <p>
            Year: <input type="text" name="txtYear" value="" />
        </p>
        <p>
            <input type="submit" name="btnSubmit" value="Submit" />
        </p>
    </form>
    <script type="text/javascript" src="eventutility.js"></script>
    <script type="text/javascript" src="ajaxUtility.js"></script>
    <script type="text/javascript" src="json2.js"></script>
    <script type="text/javascript" src="lesson42_example01.js"></script>
    </body>
    </html>
  3. Open the eventUtility.js file in your text editor. You will create a pseudo-namespace called js24Hour and add the eventUtility object to the pseudo-namespace. Add the following code at the beginning of the file:

    if (typeof js24Hour === "undefined") {
        var js24Hour = {};
    }

    This code adds flexibility to your pseudo-namespace. Remember that the eventUtility and ajaxUtility objects exist in two separate files. If you create a js24Hour object in both files you will overwrite one of them. By using this code you check to see if js24Hour does not exist, and create it if necessary. This way both your eventUtility and ajaxUtility objects exist within the same js24Hour pseudo-namespace.

  4. Make changes to the file as shown in bold in the following code:

    if (!js24Hour) {
        var js24Hour = {};
    }
    
    js24Hour.eventUtility = {
        addEvent : (function() {
            if (typeof addEventListener !== "undefined") {
                return function(obj, evt, fn) {
                    obj.addEventListener(evt, fn, false);
                };
            } else {
                return function(obj, evt, fn) {
                    obj.attachEvent("on" + evt, fn);
    };
            }
        }()),
        removeEvent : (function() {
            if (typeof removeEventListener !== "undefined") {
                return function(obj, evt, fn) {
                    obj.removeEventListener(evt, fn, false);
                };
            } else {
                return function(obj, evt, fn) {
                    obj.detachEvent("on" + evt, fn);
                };
            }
        }()),
        getTarget : (function() {
            if (typeof addEventListener !== "undefined") {
                return function(event) {
                    return event.target;
                }
            } else {
                return function(event) {
                    return event.srcElement;
                }
            }
        }()),
        preventDefault : (function() {
            if (typeof addEventListener !== "undefined") {
                return function(event) {
                    event.preventDefault();
                }
            } else {
                return function(event) {
                    event.returnValue = false;
                }
            }
        }())
    };

    This code adds the eventUtility object to the js24Hour pseudo-namespace. There are no other changes. Save the file.

  5. Now open ajaxUtility.js and make the changes as marked in bold in the following code:

    if (typeof js24Hour === "undefined") {
        var js24Hour = {};
    }
    
    js24Hour.ajaxUtility = {
        createXHR : function() {
            if (typeof XMLHttpRequest !== "undefined") {
                return new XMLHttpRequest();
            } else {
                var versions = [ "MSXML2.XmlHttp.6.0",
                    "MSXML2.XmlHttp.3.0" ];
    
                for (var i = 0; i < versions.length; i++) {
    try {
                        var xhr = new ActiveXObject(versions[i]);
                        return xhr;
                    } catch (error) {
                        // do nothing
                    }
                }
            }
    
            alert("Your browser does not support XmlHttp");
    
            return null;
        },
    
        makeGetRequest : function(url, callback) {
            var xhr = this.createXHR();
    
            xhr.open("GET", url);
    
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    var status = xhr.status;
                    if ((status >= 200 && status < 300) || status === 304) {
                        callback(xhr.responseText);
                    } else {
                        alert("An error occurred");
                    }
                }
            };
    
            xhr.send(null);
        },
    
        getRequestBody : function(form) {
            var pieces = [];
            var elements = form.elements;
    
            for (var i = 0; i < elements.length; i++) {
                var element = elements[i];
                var name = encodeURIComponent(element.name);
                var value = encodeURIComponent(element.value);
    
                pieces.push(name + "=" + value);
            }
    
            return pieces.join("&");
        },
    
        makePostRequest : function(url, data, callback) {
            var xhr = this.createXHR();
    
            xhr.open("POST", url);
    
            xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
                    var status = xhr.status;
                    if ((status >= 200 && status < 300) || status === 304) {
                        callback(xhr.responseText);
                    } else {
                        alert("An error occurred");
                    }
                }
            };
    
            xhr.send(data);
        },
    
        postFromForm : function(url, form, callback) {
            var xhr = this.createXHR();
    
            xhr.open("POST", url);
            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    
            xhr.onreadystatechange = function() {
                if (xhr.readyState === 4) {
                    var status = xhr.status;
                    if ((status >= 200 && status < 300) || status === 304) {
                        callback(xhr.responseText);
                    } else {
                        alert("An error occurred");
                    }
                }
            };
    
            xhr.send(this.getRequestBody(form));
        }
    };

    Once again the first lines ensure that you do not overwrite any existing js24Hour object. Save the file.

  6. Now you need to open lesson42_example01.js and make changes to this file. First, surround the code in this file with a self-executing anonymous function. Then, because you removed the onsubmit event handler from the <form/> element's HTML, you need to wire up the submitForm() function to handle the form's submit event. Finally, you need to modify the submitForm() function to use the js24Hour.ajaxUtility and js24Hour.eventUtility objects. The changes to this file are bold in the following code:

    (function() {
    
    var ajaxUtility = js24Hour.ajaxUtility;
    var eventUtility = js24Hour.eventUtility;
    
    function submitForm(event) {
        var data = ajaxUtility.getRequestBody(document.theForm);
    
        ajaxUtility.makeGetRequest("car_dealership.php?" + data, processResponse);
    
        eventUtility.preventDefault(event);
    }
    
    function getCarString(carObj) {
        return [carObj.year, carObj.make, carObj.model].join(" ");
    }
    
    function processResponse(data) {
        var result = JSON.parse(data);
        var messageDiv = document.getElementById("divSearchMessage");
        var resultsDiv = document.getElementById("divSearchResults");
        var message = "";
        var results = [];
        var length = result.results.length;
        var carStr = getCarString(result.searchedCar);
    
        if (result.isFound) {
            message = "We found " + length + " matches for " + carStr;
        } else {
            message = "We could not find " + carStr + ". You might like: ";
    
            for (var i = 0; i < length; i++) {
                results.push(getCarString(result.results[i]));
            }
    
            resultsDiv.innerHTML = results.join("<br/>");
        }
    
        messageDiv.innerHTML = message;
    }
    
    eventUtility.addEvent(document.forms[0], "submit", submitForm);
    
    }());

    Using a self-executing anonymous function in this file protects this code from any accidental damage from other JavaScript that may be loaded in the page. The first two statements in the self-executing function cache the js24Hour.ajaxUtility and js24Hour.eventUtility objects in the ajaxUtility and eventUtility variables, respectively. Caching these objects enables you to use them inside the self-executing function exactly as you have in previous lessons.

    This code also decouples your JavaScript code from your HTML by using your event utility to assign a function to handle the form's submit event.

  7. Save lesson42_example01.js and point your browser to http://localhost/Lesson42/lesson42_example01.htm. Fill out the form and submit it, and you'll see that it works just as it did in Lesson 35.

You can also download the sample code for all lessons from the book's website at www.wrox.com.

Note

Please select Lesson 42 on the DVD to view the video that accompanies this lesson.

Step-by-Step
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.22.160