I am always striving to perfect workflow methods that incorporate separation, semantics, and other “proper” web design techniques. But with each page developed I am finding a few consistent rules that should be broken. Breaking these rules makes for easier development, faster script execution, and more legible code.

onclick=”return false;”

The most common of the bunch, links that do not go anywhere (and usually execute JavaScript functions) must have onclick="return false;" attached to them. If we were to follow the rules, this would be done with unobtrusive JavaScript. The problem with doing this unobtrusively is that scope is usually lost when attaching an event to an object, so returning false from a function doesn’t always do the trick. This leads to code such as the following:

Event.observe(link, 'click', doSomething, false);
link.onclick = function(){return false;}

Instead of littering our clean JavaScript with that mess, just keep it in the markup. Designers understand what it means, and rarely does a link change from triggering a function to acting as a normal link would.

Browser Detection

Modern browsers are great because they recognize the pseudo CSS class :hover. With such power at their hands, designers can change the appearance of elements as the mouse goes over them. No need for JavaScript. The performance benefits of this are huge when dealing with a large amount of elements. Unfortunately, IE6 doesn’t play nicely, so it is reasonable to only attach the onMouseOver event for IE6.

Not So Semantic Class Names

Some CSS class names, ‘hide’ and ‘show’ for example, may be frowned upon because they aren’t semantic. The word ‘hide’ is describing a behavior. But the beauty of these class names is that they make sense. They are written once and never changed, and then are primarily used in JavaScript. And then looking at the code:

Element.addClassName(el, 'hide');

The line can be understood immediately. The word ‘hide’ is also short and sweet. Say it with me: ‘hide’. Go on and give it a try — you’ll be using it everywhere before long.

Globals in JavaScript

I hate global variables. They are so easy to lose track of that they often hurt more than they help. Lately though, I am finding that they have their place in JavaScript. Let’s say we have 50 <li> elements that have a two states: no class name or a class name of ‘selected’. When clicked, the class name of ‘selected’ will be applied, and it will also be removed from then 49 other elements. Accomplishing this can be done in two ways:

  • onClick, loop through all 50 elements and remove the class name. Then apply the class name to the element clicked.

  • When an element is clicked, store its ID in a global variable. Then, when the next element is clicked, remove the class name from the element stored in the global, and apply the class to the new element.

By removing the loop in the second option, there are noticeable performance gains. The global variable also serves a specific purpose, so has less chance of getting passed around, changed, and causing mass confusion.

So join me and break some rules. At the same time, I’d like to be called out if better approaches exist.

HTML Form Builder
Ryan Campbell

Breaking The Rules by Ryan Campbell

This entry was posted 4 years ago and was filed under Notebooks.
Comments are currently closed.

· 17 Comments! ·

  1. Gary Haran · 4 years ago

    For the hrefs I do recommend that sites be built with JS turned off and that once it works without JS that you decorate with JS functions afterwards. In those instances the hrefs would have a url attached to them and the onclick=”actions(…); return false” would make sense. In any other cases it looks weird.

    As far as globals is concerned I totally understand your concern with that. Looping through numerous page elements is slow and when dealing with IE6 (yuck) and innerHTML or image src attributes it can be easier to have a global variable deal with the whole mess.

    Managing through objects    Event.observe(window, 'load', function(event){
            $$('ul li').each(
                function(li){
                    Event.observe(li, 'click', function(event){
                        var clickedLi = Event.element(event);
                        var parentUl  = Event.findElement(event, 'ul');
                        clickedLi.style.background = '#CCC';
                        if (parentUl.lastElement){
                            parentUl.lastElement.style.background = '#FFF';
                        }
                        parentUl.lastElement = clickedLi;
                    });
                }
            );
        });
    <ul>
        one</li>
        two</li>
        three</li>
    </ul>
    
  2. Gary Haran · 4 years ago

    I wished that there would be a preview button as well (or that the submit comment would be a preview button… as I excpected it to be).

    The example above wasn’t explained correctly.

    Javascript allows you to add properties to an HTML element that you can access again later. In this example all I do is tell the parent HTML element to keep the last clicked element in memory as a property. Whenever a user clicks on one of the child nodes it’ll automatically ask the parent to rollback the previously clicked sibbling to it’s original state. Of course this is just a proof of concept bit and can be extended in any way to suit specific needs but you’ll be glad to hear that it works in IE6, Firefox and Safari. I haven’t checked in IE7 though but I assume it would.

  3. Tobie Langel · 4 years ago

    Hi Ryan,

    I’m of course going to advocate not breaking them. Here’s how:

    For onclick="return false;", it’s easy:

    var doSomething = function(event) { 
      // do something
      Event.stop(event);
    }
    Event.observe(link, 'click', doSomething);

    For non, semantic class-names… well, because the CSS display property sucks, we’re kind of stuck. However, you really only need one class name, and hidden somehow sounds less behavioral than hide to me.

    Regarding your globals, you can achieve the same result while keeping your code namespaced.

    For example (using Prototype):

    var Selector = Class.create();
    Selector.selected = null;
    Selector.prototype = {
      initialize: function(list) {
        list = $(list);
        list.observe('click', this.onClick);
        Selector.selected = list.getElementsByClassName("selected").first();
      },
      
      select: function(event) {
        var element = $(Event.findElement(event, 'li'));
        if(!element.hasClassName("selected")) {
          if(Selector.selected)
            Selector.selected.removeClassName("selected");
          element.addClassName("selected");
          Selector.selected = element;
        }  
      }
    }
    

    and finally regarding the browser sniffing bit… why can’t you do that properly (i.e. using Event.observe) and if need be (i.e. without falling into the trap of premature optimization) some browser sniffing to speed up the process in more standard compliant browsers.

  4. Rich Waters · 4 years ago

    @Gary - Is storing the last element changed in its parent element any more efficient than storing it in a global variable? I would think they would be pretty similar.

  5. Tobie Langel · 4 years ago
    list.observe('click', this.onClick);

    above should have read:

    list.observe('click', this.select);
  6. Dave Kees · 4 years ago

    To handle the lack of :active, :hover, and :focus in versions if IE prior to 7, you can utilize IE’s behavior mechanism and attach the csshover.htc behavor (http://www.xs4all.nl/~peterned/csshover.html). I’ve always included this in a conditional comment so you can avoid linking it in for browsers that already handle things like :hover. Also, Gary’s example does work in IE7.

    Also browser detection has gotten worlds easier in Prototype 1.5.1 land. There’s a Prototype.Browser object which has properties including IE, Webkit, Gecko, etc. each of which are boolean flags to help you detect at javascript runtime what browser you’re in. I’ve used Prototype.Browser.IE numerous times to handle things for IE which are handled differently by the other browsers. Prototype 1.5.1 is only available right now as a release candidate (RC2) but it’s been completely functional and stable for me for about three weeks of rather intensive development and use.

    And, I, like Gary, would suggest continuing to avoid onclick=”return false;” in your HTML, but I’ve been indoctrinated with unobtrusive javascript styles so my bias is probably showing. If you use the Event.observe() functionality of Prototype, then you can always use Event.stop(e) where e is the parameter that is automatically passed to the function called by the observed element on an event. For example:

    $$("form input[type=submit]").each(function(element) { element.observe("click", function(e) { Event.stop(e); // stops the event and keeps the form from being submitted. /* ... do other stuff ... */

          Event.element(e).form.submit();   // after doing stuff, submit the form.
     });
    

    });

  7. Ryan Campbell · 4 years ago

    Good feedback. I give up on onclick=”return false”. Event.stop is the better approach.

    For borwser detection, I am unaware of a way to target browser supporting the hover property. If there is no way, then browser detection is still the only way to optimize (if optimization is truly necessary). Dave, your approach works also, but it is still browser detection — just moved to from the JavaScript to conditional comments.

    As for the global variables, Tobie, your method does work. I’m still undecided at what point I would use your approach though. I see a class member variable in a similar light to global variables. In the example we’re using, is the overhead/complexity of a class worth removing the global? Probably, but I’m not convinced. I’ll have to approach it your way for a few projects and see which I enjoy more :)

  8. Tobie Langel · 4 years ago

    Actually, let me rephrase that:

    var Selector = Class.create();
    Selector.prototype = {
      initialize: function(list) {
        list = $(list);
        this.onClick = this.select.bindAsEventListener(this);
        list.observe('click', this.onClick);
        this.selected = list.getElementsByClassName("selected").first() || null;
      },  select: function(event) {
        var element = $(Event.findElement(event, 'li'));
        if(!element.hasClassName("selected")) {
          if(this.selected)
            this.selected.removeClassName("selected");
          element.addClassName("selected");
          this.selected = element;
        }
      }
    }

    Better ?

  9. Paul Irish · 4 years ago

    I so appreciate it when others make use of the onclick attribute, it makes analyzing someone else’s code FAR easier. I don’t see a good justification for why event attaching needs to be done more unobtrusively.

  10. Ryan Campbell · 4 years ago

    Yeah, Tobie, the more I look at your approach the more I like it. On the surface it appears more complex, but it is reusable and self sustaining, which is nice. The reason I initially switched to globals is because of performance. If Event.findElement has no performance drawbacks, then I think I’ll start trying this approach.

  11. Tobie Langel · 4 years ago

    Actually, as you are setting one observer instead of 50, you should get pretty drastic performance improvements.

  12. Graham · 4 years ago

    Not to split hairs, but if you have 50 LIs and only the most recently clicked one gets the class “selected” then you should really us an ID instead of a class. This way, in order to remove the old “selected” and apply it to a new class, you can just do something like $(‘selected’).setAttribute(“id”,”“) when the new LI is clicked, then add that ID back to the newly clicked LI.

    function changeSelected(argumentElement) { $(‘selected’).setAttribute(“id”,”“); argumentElement.setAttribute(“id”,”selected”); } // fire this function when any LI is clicked and pass the clicked LI as the argument.

  13. Binny V A · 4 years ago

    As long as you know what you are doing, I guess breaking rules are OK.

  14. awflasher · 4 years ago

    Hey, I find this topic by accident, sounds great. BTW, I am a web developer from China.

  15. Dustin Diaz · 4 years ago

    I just want to say that I agree with everything that was said in this article. I know I haven’t been a while for ages, but it was a breath of fresh air seeing other js ninja’s breaking the rules as well. It’s just downright practical.

  16. Matt Keogh · 4 years ago

    I think the hover technique described by Dave Kees in the comments makes more sense.You can set the hover behaviour using conditional comments just for the IE 5 and 6. That way we keep the code clean and you only have to set it once. Obviously you have to remember to think about what happens when JS if off still.

  17. Leptos Estates · 3 years ago

    Property in Greece. Wide range of holiday and residential properties from the leading property developer. Excellent investment opportunities. Custom built and completed properties in Greece, Paros, Crete and Santorini.