Lessons Learned: Cross-platform JavaScript

One area where I regularly hit a wall is when using JavaScript. Documentation on the various browsers' support of JavaScript and the Document Object Model (the DOM - a way of interacting with the various elements within a document in a logical fashion) is not always well documented. Sometimes the information is there, but it takes a lot of digging, and other times it's just time to try and learn by finding what works.

One thing that is very important is to never assume that what works in FireFox will work in Internet Explorer, or any other browser. I'm slightly ashamed to admit that I made this mistake again only last week and only realised the error when the client commented on the problem (thankfully, it was not a production site, but delivering even a staging site to a client like that is never a good idea).

Controlling CSS Attributes

I was writing some JavaScript functions to react to onclick events which changed the background and text colours of a number of elements. Because I wanted “unselected” elements to return to a normal state, I had used the following JavaScript:

var selected_element_id = null;
 
function select_element(element_id) {
 
  var element = document.getElementById(element_id);
 
 
  if (element_id == selected_element_id) {
 
    selected_element_id = null;
 
    element.style.color = "inherit";  // set the text colour to match the parent's setting
    element.style.backgroundColor = "inherit";  // set the background colour to match the parent's setting
 
  } else {
 
    if (selected_element_id != null) {
 
      var selected_element = document.getElementById(selected_element_id);
 
      selected_element.style.color = "inherit";  // set the text colour to match the parent's setting
      selected_element.style.backgroundColor = "inherit";  // set the background colour to match the parent's setting
 
    }
 
    selected_element_id = element_id;
 
    element.style.color = "#fff";
    element.style.backgroundColor = "#33f";
 
  }
 
}

Unfortunately, the current CSS standard only allows that inherit may be supported by browsers for selected elements. In a standard, words like may carry the meaning of an option to support a particular feature.

Not surprisingly, Microsoft decided not to support this particular value, but most of the time this is invisible. The CSS standard forces browsers to ignore “unknown” values. Ironically, the effect of ignoring a value of inherit is the same as obeying it – so you just don’t see that Internet Explorer is ignoring it.

However, the world comes crashing down (or at least your onclick highlighting does) when you try to implement a CSS style change in JavaScript. This is because JavaScript doesn’t have to obey the CSS standard – but if the browser doesn’t accept a particular property value, then the browser can throw as much of a fit about it as it likes.

Selects and Options

A longstanding popular effect in forms are multiple selects – your choice in one select element (for example, a country) will cause a second select element to display a different list of options (states or counties).

The update of the second element is done using JavaScript:

var states = [];
var states[0] = ['South Australia', 'New South Wales']; // Australia
var states[1] = ['Cumbria', 'Berkshire'];  // United Kingdom
 
function change_state_select()
{
  var nation_select = document.getElementById('nation_select');
  var state_select = document.getElementById('state_select');
 
  var selected_nation = nation_select.selectedIndex;
 
  while (state_select.options.length > 0) {
    // clears previous values from state selection list
 
    state_select.remove(0);
 
  }
 
  for (stateIdx in states[selected_nation]) {
 
    var new_opt = document.createElement('option');
    new_opt.text = states[selected_nation][stateIdx];
    new_opt.value = stateIdx;
 
    try {
 
      state_select.add(new_opt,null); // standards compliant - should work in most browsers but not IE
 
    } catch(e) {
 
      state_select.add(new_opt); // non-standard method - works in IE but not in most other browsers
 
    }
 
  }
 
}

This script makes use of the try..catch statement of JavaScript – just give the above script with the following changes a try in IE, and you’ll see what try…catch is doing:

// try {
 
      state_select.add(new_opt,null); // standards compliant - should work in most browsers but not IE
 
//     } catch(e) {
 
//       state_select.add(new_opt); // non-standard method - works in IE but not in most other browsers
 
//     }

IE will not display errors, and the script won’t function – try…catch is normally used to trap errors in sequences of code and display the error message in a “friendly” way (as an alert, or a rewrite of some of the page’s content), or just enact some other alternative. In this case, the statement is being used to overcome the differences between standards compliant and non-compliant browsers.

Working with Tables

FireFox lets you add rows to a table simply by using the innerHTML property of the table object:

function addRow()
{
 
  var table = document.getElementById('data_table');
 
  table.innerHTML += '<tr>';
  table.innerHTML += '<td>Cell 1</td>';
  table.innerHTML += '<td>Cell 2</td>';
  table.innerHTML += '</tr>';
 
}

<table id="data_table">
  <tr>
    <td>Cell 1</td>
    <td>Cell 2</td>
  </tr>
</table>
 
<p>
  <button type="button" onclick="addRow()">Add a Row</button>
</p>

Unfortunately, this is not allowed under a strict interpretation of the aforementioned DOM, which means that this shortcut is not necessarily supported by all browsers. In particular, Internet Explorer does not support innerHTML on tables or table rows.

Fixing this is pretty easy, and in fact using the ‘correct’ method of interacting with tables, cells and rows makes life a lot more exciting when manipulating tables in the long run:

function addRow()
{
 
  var table = document.getElementById('data_table');
 
  // get the number of rows already in the table
  var num_rows = table.rows.length;
 
  // insert a new row at the bottom of the table
  // and assign a variable to refer to it
  var new_row = table.insertRow(num_rows);
 
  // insert the first cell to the new row
  // and then put content in the cell
  var new_cell = new_row.insertCell(0);
  new_cell.innerHTML = "Cell 1";
 
  // insert the second cell to the new row
  // and then put content in the cell
  new_cell = new_row.insertCell(1);
  new_cell.innerHTML = "Cell 2";
 
}

The insertRow(index) method lets you insert a row at any point you want by passing the index variable (in the above example we use the number of rows to set the index to add a new row at the bottom). You could insert at the top of the table by passing an index of 0, or anywhere else in the table by passing the relevant index (upto the number of rows in the table).

There’s a lot more you can do working this way through the DOM that you’d miss with the innerHTML methods that FireFox uses – deleteRow and deleteCell, for one. Plus there are methods to create and delete the table caption, table footer, table header, and table bodies.

A complete reference can be found at W3Schools.