The html times

Elegantly Powered by Google

Custom Attributes by Rick Waldron and Boaz Sender October 15th, 2008 Delicious



Relevant Downloads



Relevant Links

microformats.org
Prototype.

Custom Attributes

On a recent carpooling-to-girls-that-live-in-New York trip, Rick and I found ourselves discussing the potential for creating our own tag attributes and values to pass semantic information about the kind of information in a given element we might happen to be working with.

With the aid of JavaScript, that information can also be utilized to perform element-specific tasks. Whether we need to perform an interface or data related task, there are many procedural benefits.

The code bellow was originally dictated by Rick (as he drove) as I hammered away on my MacBook. Two hundred and thirteen miles later, we had a basic model representation of a style class change to nearly identical elements grouped only by sharing a common or differing custom attribute value. After a bit of house cleaning we came up with this example.

Notice in the demo that only the contents of the divs remains the same. Based solely on our custom state attribute, the JavaScript has gone through and swapped class names and in turn the styles (background-img, color, etc).

We are still thinking about the practical application of this technique, but the immediate benefits appear to be twofold. Since HTML, CSS and JavasScript allow for custom attributes we can:

Though we did not use any microformats standards for this, the opportunity to adopt that kind of standard using attributes besides class (ie: link, rel, rev, etc) is clear. Read this tutorial to see what we're talking about, and check out the finished example.

The HTML:

First, we set up a couple of divs to represent states that we drive through in order to visit our girlfriends: Connecticut and New York. To establish the grouping capabilities of custom attributes, we’ve defined four divs each with a unique id, identical class name and our custom attribute state (which will represent the geographic location associated with that element or group of elements).

<div id="div_1" state="ct" class="state_any">
  <span>This is the 1st Connecticut div</span>
</div>

<div id="div_2" state="ct" class="state_any">
  <span>This is the 2nd Connecticut div</span>
</div>

<div id="div_3" state="ny" class="state_any">
  <span>This is the 1st New York div</span>
</div>

<div id="div_4" state="" class="state_any state_null">
  <input type="button" id="btn_change_all" class="mapify_btn" value="Mapify!" />
  <input type="button" id="btn_change_xx" class="mapify_btn" value="Reset!" />
  <input type="button" id="btn_change_ct" class="mapify_btn" value="Mapify CT!" />
  <input type="button" id="btn_change_ny" class="mapify_btn" value="Mapify NY!" />

  <p>
    whatever you want to put here
  </p>
</div>

The JavaScript:

With some basic styles defined for the above divs, we will use JavaScript to cycle through and reassign class names to elements based on the state attribute’s value.

Now for the nitty gritty. In the head of our HTML document, we begin by including a script src for prototype.js. This will give us the power of prototype's Event and Enumerable classes.

Event.observe(window,'load',function(){
  
 $$('.mapify_btn').each( function (mapify_btn) {
 	
  mapify_btn.observe('click', function () {
    
    var btn_command = mapify_btn.id.replace('btn_change_', '');

    $$('.state_any').each( function (div_element) {
    
      var state_attr = div_element.getAttribute('state');
    	
      if ( btn_command == 'all' ) {

         if ( !state_attr ) {
          div_element.addClassName('state_null');                    
        }
        else {
          var state_class    = 'state_' + state_attr;
          div_element.addClassName(state_class);
        }            
        
      }
      else if ( btn_command == 'xx' ) {
    
        var class_arr	= div_element.readAttribute('class').split(' ');
      	if ( class_arr[1] !== 'state_null' ) div_element.removeClassName(class_arr[1]);
      
      }
      else {
      
      	var state_update = 'state_' + btn_command;
      	
      	if ( state_attr == btn_command ) 
          div_element.addClassName(state_update).addClassName('state_any');
      	else
          div_element.addClassName('state_any');
      
      }
    });
  }); // END mapify_btn.observe()
 }); // END $$('.mapify_btn').each()
}); // END Event.observe()

We start by encapsulating our JavaScript within an Event.observe() block in order to observe the window load. This ensures that all of our DOM elements are completely registered. Once inside our anonymous function, we create a $$ selector collection of the input buttons based on their shared class name "mapify_btn". A $$ selector collection allows us to create an array of elements based on a CSS selector/class name (read about it: Prototypejs Utilities).

With this $$ collection, we then iterate over each element in our array and assign an event observer with an anonymous callback function to listen for button clicks. When a button is clicked, we resolve our command by reading the button id and replacing the prefix "btn_change_" with "" (thats nothing :) ) and storing it's suffix in the var btn_command. The btn_command var stores information that will determine the
next process to execute.

The next block creates a new $$ collection of our div elements. We use Enumerable.each() to iterate over the elements in the array and
read/store the value of our custom attribute state into var state_attr.

The first possible process is command == 'all', which will simply assign a new CSS style class to the current node based on the value of state_attr.

The second possible process is command == 'xx', which reads the current class names assigned to the node's class attribute and turns them into an array using split() to break up the string value at each space (' ').
Since the first class name will ALWAYS be "state_any", array elements beyond index 0 are assumed to be our specially assigned class names and are removed with Element.removeClassName(). The effect appears to have "reset" our map elements.

The third and final process is the commandless process that allows us to target a specific element node from its relative button. Since btn_command can also store the suffix that matches the value of state_attr, we can conditionally update the target node by comparing the current btn_command with the value of state_attr. This process has the effect of creating each map element individually.