Is invalid code created through JavaScript ok?
When developing web sites with heavy interactivity, your scripting skills are really put to the test. And, sooner or later, you will be put in a situation where it’s a fine line between following web standards and what’s best from a performance and structure perspective. One question that follows that is: is it ok to apply invalid attributes via script to elements?
Generally, I’ve had the stance that code should be valid, no matter if it’s in the initial code when the web page is loaded, or if it’s dynamically altered through JavaScript. By doing some DOM manipulation, you can create great interactivity for web sites which really helps the end user and improves the experience. I’m glad that most scripting is of that kind (or is moving in that direction, at least), as opposed to doing things just because they’re technically possible.
When invalid feels good
So, let me give you a very basic example of invalid code created through JavaScript that I find more structured and efficient than the valid equivalent. Let’s say that you have one or several links in your web page that will show/hide certain elements. First, let’s find all those links and apply the event (example code is written using DOMAssistant):
// ---
$(window).addEvent("load", findExpandCollapseLinks);
// ---
function findExpandCollapseLinks(){
var links = document.getElementsByClassName("expand-collapse", "a");
for(var i=0; i<links.length; i++){
/*
Note: always apply events to elements, where it
would work fine without JavaScript enabled
*/
links[i].addEvent("click", expandCollapse);
}
}
// ---
Now we have applied the events to all links. Let’s examine different ways to achieve the showing/hiding of them:
Option 1: Look up related elements each time
Let’s say that the function executed each time the link is clicked first gets some elements through a getElementsByClassname function (try The Ultimate getElementsByClassName or DOMAssistant’s core file for that), and loops through them.
// ---
function expandCollapse(evt){
var parent = this.parentNode();
var elementsToExpand = parent.getElementsByClassName("expanded", "div");
var elementsToCollapse = parent.getElementsByClassName("collapsed", "div");
// Loop through the elements, etc...
}
// ---
- Upside
- Little code overall, and easy to understand.
- Downside
- Unnecessary performance hit finding all the elements each time.
Option 2: Global variables
Another option is for each link to save its references to its elements into global variables once the click events are applied to them.
// ---
function findExpandCollapseLinks(){
var links = document.getElementsByClassName("expand-collapse", "a");
var linkElm;
var elmId;
var parent;
for(var i=0; i<links.length; i++){
/*
Note: always apply events to elements, where it
would work fine without JavaScript enabled
*/
linkElm = links[i];
elmId = linkElm.getAttribute("id");
parent = linkElm.parentNode();
window[elmId + "-" + elementsToExpand] = parent.getElementsByClassName("expanded", "div");
window[elmId + "-" + elementsToCollapse] = parent.getElementsByClassName("collapsed", "div");
linkElm.addEvent("click", expandCollapse);
}
}
// ---
function expandCollapse(evt){
var elmId = this.getAttribute("id");
var elementsToExpand = window[elmId + "-" + elementsToExpand];
var elementsToCollapse = window[elmId + "-" + elementsToCollapse];
// Loop through the elements, etc...
}
// ---
- Upside
- Little code in the
expandCollapsefunction. - Downside
- Global variables are seldom good coding practice, because the risk of having them being overwritten. No direct reference between the element and the actual related elements exist either. Also, personally, I don’t think it’s that suitable for scalability.
Option 3: Object property
One way to do it is to save all the elements in an object’s property as an array, as soon as the click events are applied to them (an index class is used for each link, to avoid looping through the property array and comparing objects each time).
// ---
var linkObj = {
links : []
}
// ---
function findExpandCollapseLinks(){
var links = document.getElementsByClassName("expand-collapse", "a");
var linkElm;
var parent;
var elementsToExpand;
var elementsToCollapse;
for(var i=0; i<links.length; i++){
/*
Note: always apply events to elements, where it
would work fine without JavaScript enabled
*/
linkElm = links[i];
linkElm.addClass(("link-" + i));
parent = linkElm.parentNode();
elementsToExpand = parent.getElementsByClassName("expanded", "div");
elementsToCollapse = parent.getElementsByClassName("collapsed", "div");
linkObj.links.push([linkElm, elementsToExpand, elementsToCollapse]);
linkElm.addEvent("click", expandCollapse);
}
}
// ---
function expandCollapse(evt){
var linkIndex = this.className.replace(/.*link-(\d+).*/i, "$1");
var arrayPosition = linkObj[linkIndex];
if(arrayPosition){
var elementsToExpand = arrayPosition[arrayPosition][1];
var elementsToCollapse = arrayPosition[arrayPosition][2];
}
// Loop through the elements, etc...
}
// ---
- Upside
- Code hidden away in an object dedicated for the task.
- Downside
- Indexing links, and getting no direct reference between the element and the actual related elements, except through an external object.
Option 4: Invalid code
Let’s instead create two invalid attributes when applying the click event to the links that keep a direct and cached reference from the link direct to the elements.
// ---
$(window).addEvent("load", findExpandCollapseLinks);
// ---
function findExpandCollapseLinks(){
var links = document.getElementsByClassName("expand-collapse", "a");
for(var i=0; i<links.length; i++){
/*
Note: always apply events to elements, where it
would work fine without JavaScript enabled
*/
linkElm = links[i];
parent = linkElm.parentNode();
linkElm.elementsToExpand = parent.getElementsByClassName("expanded", "div");
linkElm.elementsToCollapse = parent.getElementsByClassName("collapsed", "div");
linkElm.addEvent("click", expandCollapse);
}
}
// ---
function expandCollapse(evt){
var elementsToExpand = this.elementsToExpand;
var elementsToCollapse = this.elementsToCollapse;
// Loop through the elements, etc...
}
// ---
- Upside
- Little code in the
expandCollapsefunction. Good overview and direct reference between the link and the elements it will interact with. - Downside
- Invalid code.
Summary
In my opinion, the code in Option 4 seems to be the best for performance reasons, while at the same time establishing a direct relation between the link itself and its associated elements that will be affected. It is invalid, but only in the DOM and not from a general SEO or accessibility perspective.
Which option would you choose? And generally, is invalid code in the DOM ok if it’s justified from a performance or structure angle?


