SitePoint Sponsor

User Tag List

Results 1 to 11 of 11
  1. #1
    SitePoint Addict
    Join Date
    Jun 2007
    Posts
    396
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)

    Unobtrusive Javascript

    Hi guys,

    I'm just after some information regarding unobtrusive Javascript, preferably something that isn't too heavy (I'm still very new to js) and will explain the concept in clear terms.

    At the moment I believe it to be something along the lines of setting up a load function, like follows:

    Code JAVASCRIPT:
    function addLoadEvent(func) {
      var oldonload = window.onload;
      if (typeof window.onload != 'function') {
        window.onload = func;
      } else {
        window.onload = function() {
          if (oldonload) {
            oldonload();
          }
          func();
        }
      }
    }

    Then using this function, call others you want to run when the page loads:
    Code JAVASCRIPT:
    addLoadEvent(test);

    Then, in the function being called, find out the element ID and apply a function to it:
    Code JAVASCRIPT:
    function test () {
    	document.getElementById('live_phone').onclick = function () {
    		alert('Function is working...');
    	}
    }

    Assuming this is indeed the correct way to do it, that's fine I guess, but there's a few added complications too: For example, if I have all of my js in functions.js, will the above code not be executed on each page? What if I only wanted to execute it on one of hundreds of pages?

    Also, I'm sure I'm not alone in having a header.php file that'll call in any relevant javascript onto each page; this itself is an added complication because you can't execute code using body onLoad either, or are you not supposed to do this even for something you only want executed on one of many pages?

    Hope this post has made some sort of sense.

  2. #2
    SitePoint Member
    Join Date
    Feb 2007
    Location
    Amsterdam, Netherlands
    Posts
    16
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    since you're using PHP it shouldn't be all that hard to write the logic (in php) which decides wether or not to load the appropriate javascript.

    I'm not sure what you mean by the secopnd part.. The browser doesn't care wether your page is compiled by php and send to it or if it accesses a static html page. The actual code it receives is exactly the same.

    lastly, yes, that is pretty much how it's best done..
    Patrick Kanne

  3. #3
    SitePoint Enthusiast
    Join Date
    Dec 2007
    Posts
    66
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Unobtrusive JavaScript simply means that you are separating your content from your behavior (JavaScript), in much the same way as you would separate your content from your presentation (CSS). JavaScript is an enhancement, and is not guaranteed to be available, so if you develop your page to the content first, you can then add Unobtrusive JavaScript while maintaining a nice separation of layers (ie - you don't muck up your content).

    Yes, JavaScript should be in a separate file from your content.

    Your example will work, but its assigning "event handlers" to your objects (each object can have only 1 event handler for a particular event). A better approach is to use "event listeners". These essentially listen for a particular event, and you can have as many event listeners as you'd like for a particular object event.

    There are many addEventListener methods available, but my favorite is from the Yahoo UI Library. Here's how it would work (note, my script would be in a separate file, but I'm just showing them inline for example purposes):

    Code:
    <script type="text/javascript" src="http://yui.yahooapis.com/2.4.1/build/yahoo-dom-event/yahoo-dom-event.js"></script>
    <script type="text/javascript">
    YAHOO.util.Event.on(window, 'load', test);
    YAHOO.util.Event.on(window, 'load', function() {
        alert('I see the window load event. Hooraaaay!');
    });
    </script>
    In the example above, both your test function and the anonymous function below it will run on the window load event. I could take this further:
    Code:
    <script type="text/javascript" src="http://yui.yahooapis.com/2.4.1/build/yahoo-dom-event/yahoo-dom-event.js"></script>
    <script type="text/javascript">
    YAHOO.util.Event.on(window, 'load', function() {
        YAHOO.util.Event.on('live_phone', 'click', function() {
            alert('Function is working...');
        });
    });
    </script>
    That code does the equivalent of what your example did, except I'm using event listeners, and I've moved the test function to an anonymous function.

    With Event Handlers, a handler that you assign to a particular object/event can be overwritten by a 3rd party script. You don't have to worry about that with Event Listeners.

    For example, if I have all of my js in functions.js, will the above code not be executed on each page? What if I only wanted to execute it on one of hundreds of pages?
    Typically, you'd only include the JavaScript on those pages where you want it to run. Your header.php file could check to see what the current page was and use that to determine whether or not to include that JavaScript. Alternatively, you could do something like this in your header.php file:

    Code:
    <?php
    // Print the page header info
    ?>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
     "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset:utf-8">
      <title><?php echo ${pageTitle}; ?></title>
      <?php echo ${pageScripts}; ?>
    </head>
    And then the file that includes header.php where you need the scripts would look something like this:
    Code:
    <?php
    $pageTitle = 'Home';
    $pageScripts = "<script type=\"text/javascript\" src=\"functions.js\"></script>";
    
    include 'header.php';
    ?>
    Note, however, that it's better to include script files just before your closing </body> tag instead of in the head of your document (so if you have a footer.php file, that would be a better place for the scripts).

  4. #4
    SitePoint Addict
    Join Date
    Jun 2007
    Posts
    396
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Many thanks for the reply - I'm using some jQuery at the moment, does this do a similar thing to the Yahoo library? Should I try to use this more than write my own code, and should I ditch it for an alternative (scriptalicious, Yahoo etc?). Thanks for the idea of having the page title and scripts defined as variables, the current set-up of the website doesn't really allow for that unfortunately but it's definitely something I'll try to implement into any future projects.

    The thing that still throws me, are these two scenarios:
    1. I've got a lot of functions being loaded for each page on functions.js - but one page, out of a hundred pages doesn't need one function to run, but does need the others.

    2. I've got twelve pages that require just a single function to be run, does this mean I'll need twelve different .js files!?

    Seems to me there's a massive flaw in the concept of many people comparing it to the way CSS works - in CSS, if you're applying a class to something that doesn't exist on the particular page it just ignores it, in Javascript it throws up an error and breaks your page.

  5. #5
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,729
    Mentioned
    104 Post(s)
    Tagged
    4 Thread(s)
    Quote Originally Posted by xkratosx View Post
    The thing that still throws me, are these two scenarios:
    1. I've got a lot of functions being loaded for each page on functions.js - but one page, out of a hundred pages doesn't need one function to run, but does need the others.
    If there are situations where the element may not be available, this is where you test to see if it's available before using it.

    Code Javascript:
    function test () {
        if (document.getElementById('live_phone')) {
            document.getElementById('live_phone').onclick = function () {
                alert('Function is working...');
            }
        }
    }

    Quote Originally Posted by xkratosx View Post
    2. I've got twelve pages that require just a single function to be run, does this mean I'll need twelve different .js files!?
    Yes it does. If you're using PHP though, you can append all off the script files together into one mega script file, which helps to improve performance.

    You were mentioning earlier about issues with using onload. Because it's better to load your scripts from the end of the body, this is the perfect place to activate any code that wants running too.

    Check out Best Practices For Speeding Up Your Web Site
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  6. #6
    SitePoint Addict
    Join Date
    Jun 2007
    Posts
    396
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Sorry for the late reply, only just came back to this particular project and remembered this thread! Many thanks for the advice, and that Yahoo link looks great - I'll take a good look now.

    I've got this function called via the addLoadEvent function, I'm just wondering if I've done it correctly or if there are better methods of achieving what I'm trying to do? Either less code, more compliant or whatever, I'm really not very familiar with Javascript, thanks.

    Code Javascript:
    // applies various events to the live search area
    function livesearchevents () {
    	var searchinput = document.getElementById("livesearch2");
    		// displays the live search results depending on the user's input
    		searchinput.onkeyup = function() {showResult(this.value);}
    		searchinput.onkeypress = function() {show('livesearch_phone');}
    		searchinput.onclick = function() {check_input(this.value);}
     
    	var search_name = document.getElementById("search_name");
    	search_name.onclick= function () {
    		hidelive(); // hides the search box
    		document.getElementById('searchtype').value = 'name'; // sets search type to be used by the showResult function->livesearch.php file
    		this.className = 'selected'; // makes this tab appear active
    		document.getElementById('search_number').className = 'deselected'; // makes the other tab(s) appear inactive
    	}
     
    	var search_number = document.getElementById("search_number");
    	search_number.onclick= function () {
    		hidelive(); // hides the search box
    		document.getElementById('searchtype').value = 'name'; // sets search type to be used by the showResult function->livesearch.php file
    		this.className = 'selected'; // makes this tab appear active
    		document.getElementById('search_name').className = 'deselected'; // makes the other tab(s) appear inactive
    	}
    }

  7. #7
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,729
    Mentioned
    104 Post(s)
    Tagged
    4 Thread(s)
    There are two things.

    The deselected class is superfluous as that should be the standard style for it.
    If you want to remove the selected class from the element, you should set the class value to an empty string.

    Code Javascript:
    document.getElementById('search_number').className = 'deselected';

    The other more important thing that comes to mind is that you're going to need a loop to select and deselect the form elements.

    Otherwise as more elements are added to the form, your scripting code is going to grow and grow in order to manage just the highlighting of the selected elements.

    I have used an array here, but a further improvement if warranted is to loop through the form elements array and use that instead.

    Code Javascript:
    var search_elements = ['search_number', 'search_name'];
    for (var i = 0; i < search_elements.length; i++) {
        document.getElementById(search_elements[i]).onclick = highlight;
    }
    function highlight() {
        hidelive(); // hides the search box
        document.getElementById('searchtype').value = 'name'; // sets search type to be used by the showResult function->livesearch.php file
        for (var i = 0; i < search_elements.length; i++) {
            if (search_elements[i] === this) {
                this.className = 'selected'; // makes this tab appear active
            } else {
                search_elements[i].className = ''; // makes the other tab(s) appear inactive
            }
        }
    }
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  8. #8
    SitePoint Addict
    Join Date
    Jun 2007
    Posts
    396
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Many thanks for the reply, and very good point about the superfluous deselected class.

    For some reason using the above code doesn't work unfortunately, although I agree that it'd be great for it to be a lot easier to add new tabbed items. I've tried changing it so that I pass the current element into the function (longshot, I know) but that didn't; work either:

    Code JAVASCRIPT:
    	var search_elements = ['search_number', 'search_name'];
    	for (var i = 0; i < search_elements.length; i++) {
    		document.getElementById(search_elements[i]).onclick = highlight(this);
    	}
    	function highlight(tab) {
    		hidelive(); // hides the search box
    		document.getElementById('searchtype').value = 'name'; // sets search type to be used by the showResult function->livesearch.php file
    		for (var i = 0; i < search_elements.length; i++) {
    			if (search_elements[i] === tab) {
    			tab.className = 'selected'; // makes this tab appear active
    		} else {
    	            search_elements[i].className = ''; // makes the other tab(s) appear inactive
    	        }
    	    }
    	}

    I also forgot to change the hidden field in my original code, oops! Especially worrying since that's the entire point of the tabs! The second line of the third chunk should read as follows, does this mean I'll need a 3D array?

    Code JAVASCRIPT:
    document.getElementById('searchtype').value = 'number';

    On a final note, I'd like to clear the search results, is the .innerHTML okay to use? I heard somewhere it was depreciated but I'm not sure.

    Code JAVASCRIPT:
    document.getElementById('livesearch_phone').innerHTML = ''; // clears the search results

    Thanks again
    Last edited by xkratosx; Feb 4, 2008 at 04:05.

  9. #9
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,729
    Mentioned
    104 Post(s)
    Tagged
    4 Thread(s)
    Quote Originally Posted by xkratosx View Post
    For some reason using the above code doesn't work unfortunately, although I agree that it'd be great for it to be a lot easier to add new tabbed items.

    I also forgot to change the hidden field in my original code, oops! Especially worrying since that's the entire point of the tabs! The second line of the third chunk should read as follows, does this mean I'll need a 3D array?
    The previous was quickly written with no testing involved.
    I've tested the following code to confirm that it works with no problems.

    With the hidden field value, that can be easily achieved by setting and retrieving properties from the element itself.

    Code Javascript:
    var search_elements = {'search_name': 'name', 'search_number': 'number'};
    for (name in search_elements) {
        document.getElementById(name).searchType = search_elements[name];
        document.getElementById(name).onclick = highlight;
    }
    function highlight() {
        hidelive(); // hides the search box
        document.getElementById('searchtype').value = this.searchType; // sets search type to be used by the showResult function->livesearch.php file
        for (name in search_elements) {
            var selected = ''; // makes this tab appear active
            if (name == [this.id]) {
                selected = 'selected';
            }
            document.getElementById(name).className = selected;
        }
    }

    Quote Originally Posted by xkratosx View Post
    On a final note, I'd like to clear the search results, is the .innerHTML okay to use? I heard somewhere it was depreciated but I'm not sure.
    It's okay, but the more standards way of doing that is as follows

    Code Javascript:
    var livesearchPhone = document.getElementById('livesearch_phone');
    while (livesearchPhone.firstChild) {
        livesearchPhone.removeChild(livesearchPhone.firstChild);
    }
    Last edited by paul_wilkins; Feb 6, 2008 at 01:36.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  10. #10
    SitePoint Evangelist
    Join Date
    Jan 2005
    Posts
    502
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Arrow

    I consider 'unobtrusive javascript' to also be in regards to other scripts that may be included on your page. Ideally you would not conflict with any other scripts that may be included. You can do this by using the module pattern, and by attaching event listeners rather than by setting the onclick attribute (etc) directly.

    eg (using YUI library)
    Code JavaScript:
    YAHOO.namespace('example.MyApp');
     
    YAHOO.example.MyApp = function() {
        // private shortforms 
        var YUD = YAHOO.util.Dom,
             YUE = YAHOO.util.Event;
     
        // private vars
        var searchInput, searchName;
     
        return {   
            // applies various events to the live search area
            livesearchevents: function() {
                // displays the live search results depending on the user's input
                YUE.on(searchInput, 'keyup', function() {showResult(this.value));
            },
     
            myOtherFunc: function() { 
                ...
            },
     
            init: function() {
                searchInput = YUD.get("livesearch2");
                this.livesearchevents();
                this.myOtherFun();
            }    
    }();
     
    YAHOO.util.Event.onDOMReady(YAHOO.example.MyApp.init, YAHOO.example.MyApp, true);

    that is a bad example, but you get the idea

  11. #11
    SitePoint Addict
    Join Date
    Jun 2007
    Posts
    396
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Bah! So sorry for the late reply, for some reason I looked at the code above in a rush and completely forgot to respond.

    The code is fantastic and works far better than my original, and I really appreciate the alternative to innerHTML too. Many thanks once again.

    Huge thanks Mr_Money for the Yahoo Library code too, it's certainly interesting to compare the two and I plan on checking out the Yahoo library as soon as I get a chance.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •