CSS based Javascript controls
18 Mar

CSS based Javascript controls

  Using Javascript to handle visibility can sometimes be cumbersome. For small applications it is easy to throw a few hide()'s in a script tag and not worry about maintainability. As the application grows however, it becomes harder to track the state of the DOM and you end up with longs strings of hide() and show() just to handle the simplest of state transitions. To prevent this, I have learned to avoid hide() and show() and whenever possible use css to control state transition.

  CSS is great at controlling visibility. It has the the benefit of inheritance that makes labelling DOM states extremely simple, and at very little performance cost. The only real downsides are:

  • it requires some forethought to implement
  • it does not support transitions (fade in, fade out) without CSS3

Strategy

  The first order of business when setting up a page with multiple states is to identify the states and the regions of the page that they affect. In this case I want to make a Javascript control that has 3 states:

  1. initial: the main form
  2. loading: a small screen that hides the load of data
  3. complete: the completion screen

Each of these states has a specific place in the work flow, a work flow that will be controlled entirely by CSS.

HTML

<div class="container">
    <div class="initial">
        <input type="text" /><br />
        <input type="button" />
    </div>
    <div class="loading">
        <!-- a little Font Awesome here -->
        <i class="fa fa-refresh fa-spin"></i>
        <h3>loading...</h3>
    </div>
    <div class="complete">
        <h3>Thank you for your input. :)</h3>
    </div>
</div>

http://jsfiddle.net/a89nt/

Workflow

The work flow for the widget is very simple: initial -> loading -> complete for simplicity all of the states should be hidden by default:

CSS

.initial,
.loading,
.complete {
    display:none;
}

http://jsfiddle.net/a89nt/1/

The current state of the control will be set by an attribute (or class) on the containing div and the visibility of each child will be controlled by a correlating state. For example: setting state="initial" on the containing div will display the div with the "initial" class.

CSS

div.container[state="initial"] .initial,
div.container[state="loading"] .loading,
div.container[state="complete"] .complete {
    display:block;
}

http://jsfiddle.net/a89nt/2/

Wiring it up

Now that all of the states are set, we can easily set them with a quick Javascript or JQuery call:

Javascript

$(function(){
    $('.initial input[type="button"]').click(function(){
        $('.container').attr('state','loading');
        setTimeout(function() {
            $('.container').attr('state','complete');
        }, 1000);
    });
});

http://jsfiddle.net/a89nt/3/

  In a real control we would be setting the state to "loading" then making some kind of ajax call, then setting the state to "complete" or maybe using a "failure" state in the callback, depending on the result.

Transitions

  The above method displays a clean, immediate transition from one state to another that is backward compatible with all browsers since IE 6. However if you want something smoother, you should consider adding CSS3 transitions to the mix. I generally prefer the fade-out/fade-in transition:

CSS

.initial,
.loading,
.complete {
    -webkit-transition: opacity 1s ease;
    -moz-transition: opacity 1s ease;
    -ms-transition: opacity 1s ease;
    -o-transition: opacity 1s ease;
    transition: opacity 1s ease;
    opacity: 0; 
    height: 0;
    overflow: hidden;
}
div.container[state="initial"] .initial,
div.container[state="loading"] .loading,
div.container[state="complete"] .complete {
    opacity: 1;
    height: auto; 
}

http://jsfiddle.net/a89nt/4/

  Take note of the fact that CSS3 doesn't have a transition for the display property. To compensate we are using height: 0 and height: auto. Keep this in mind because if you want to set a height for one of the states it would have to be broken out into its own rule.

  As you can see there is not a hide() or show() to be found, CSS is minimal and the implementation is easy to read, understand and test. Since it relies so little on the Javascript it works well with Angular, JQuery, or no framework at all. On top of all of these advantages it is extensible and easy to add new steps (or states) to the control. If you ask me this is the superior way to build javascript controls.