Although the page itself is pretty bare bones, the script itself is even simpler. The bandanna guide is another piece I wrote to accompany the game Pirate Quest.
This article attempts to demonstrate how we can use dynamically generated DOM objects to update table data on the fly. In this case it’s fairly simple as only one value is changing and we’re only changing the cell text. There are some other ideas demonstrated here, such as using opacity and alternating row styles to create a colourful and easy to read table.
The following is the table we’ll be updating. It’s pretty simple – just the column headings. EDIT: added thead and tbody tags. IE would append rows to the table unless they were in the tbody tag.
<table id="tbl_guide" border="1">
<thead>
<tr>
<td rowspan="2">Level</td>
<td rowspan="2">Energy</td>
<td colspan="2">No Bandanna</td>
<td colspan="2">Bandanna of Vigor</td>
<td colspan="2">Bandanna of Vitality</td>
<td colspan="2">Bafunda de la Cabeza</td>
</tr>
<tr>
<td>min</td>
<td>max</td>
<td>min</td>
<td>max</td>
<td>min</td>
<td>max</td>
<td>min</td>
<td>max</td>
</tr>
</thead>
<tbody id="tbl_body"/>
</table>
You can use whatever you want to start the update process. In this case I used the onchange event on a drop down list
<select id="hideout" onchange="update(Number(this.options[this.selectedIndex].value));">
<option value="100">homeless (100)</option>
<option value="105">Wretched Alcove (105)</option>
<option value="110">Abandoned Outhouse (110)</option>
<option value="115">Festering Swamp (115)</option>
<option value="120">Swamp with a View (120)</option>
<option value="130">Desolate Beach (130)</option>
<option value="140">Rundown Shanty (140)</option>
<option value="150">Rusted Roof Shack (150)</option>
<option value="160">Shanty with a Fence (160)</option>
<option value="170">Deserted Manor (170)</option>
<option value="180">Ruined Castle (180)</option>
<option value="185">Rundown Castle (185)</option>
<option value="190">Stronghold (190)</option>
<option value="195">Fortified Stronghold (195)</option>
<option value="200">Shack on Skull Island (200)</option>
<option value="220">Cavern on Skull Island (220)</option>
<option value="230">Stronghold on Skull Island (230)</option>
</select>
Next, on to the script itself. First we define the number of rows and columns, and we create a 2D array for our cells.
//2D array of table cells
var cells = new Array();
var numcols = 10;
var numrows = 600;
I used the body onload event to set up the table rows. EDIT: Because IE doesn’t seem to recognize a class or style change made with setAttribute(), I had to fall back on an alternative
function onLoad() {
//get our table and our select box
var table = document.getElementById("tbl_guide");
var select = document.getElementById("hideout");
var row; //tr DOM object
var cell; //td DOM object
var celltext; //createTextNode DOM object
var row_arr; //array of cells for each row
//for the opacity overlay we use three divs. one on the outside, one for the
//background color and one for the text
var out_div, in_div1, in_div2;
//loop through the number of rows we want
for (var i = 0; i<numrows; i++) {
//create this row and set the style for even or odd.
row = document.createElement("tr");
row.setAttribute("class",(i%2 == 0)? "even":"odd");
row.className = (i%2 == 0)? "even":"odd"; //for IE
//get a new, clean array to work with
row_arr = new Array();
//loop through each cell for the number of columns we have
for (var j=0; j<numcols;j++) {
//create our table cell and div arrangement for our opacity trick
cell = document.createElement("td");
out_div = document.createElement("div");
in_div1 = document.createElement("div");
in_div2 = document.createElement("div");
//set the styles for the div objects
out_div.setAttribute("class","outer");
in_div2.setAttribute("class","text");
in_div1.setAttribute("class","bg");
//for IE (up to 7) since it appears to lack proper support for setAttribute
out_div.className = "outer";
in_div1.className = "bg";
in_div2.className = "text";
//depending on what cell we're in we should do different things. mostly this affects the styles
switch(j) {
case 0:
//level
celltext = document.createTextNode(String(i+1));
in_div1.setAttribute("style","background-color:yellow;");
in_div1.style.background = "yellow"; //for IE again
break;
case 1:
//Energy
celltext = document.createTextNode(String(i+10));
in_div1.setAttribute("style","background-color:yellow;");
in_div1.style.background = "yellow";
break;
case 2:
celltext = document.createTextNode("");
in_div1.setAttribute("style","background-color:red;");
in_div1.style.background = "red";
break;
case 3:
celltext = document.createTextNode("");
in_div1.setAttribute("style","background-color:red;");
in_div1.style.background = "red";
break;
case 4:
celltext = document.createTextNode("");
in_div1.setAttribute("style","background-color:blue;");
in_div1.style.background = "blue";
break;
case 5:
celltext = document.createTextNode("");
in_div1.setAttribute("style","background-color:blue;");
in_div1.style.background = "blue";
break;
case 6:
celltext = document.createTextNode("");
in_div1.setAttribute("style","background-color:green;");
in_div1.style.background = "green";
break;
case 7:
celltext = document.createTextNode("");
in_div1.setAttribute("style","background-color:green;");
in_div1.style.background = "green";
break;
case 8:
celltext = document.createTextNode("");
in_div1.setAttribute("style","background-color:purple;");
in_div1.style.background = "purple";
break;
case 9:
celltext = document.createTextNode("");
in_div1.setAttribute("style","background-color:purple;");
in_div1.style.background = "purple";
break;
}
//add our text to the second inner div
in_div2.appendChild(celltext);
//append the two inner divs to the outer div, then append the outer div to the cell
out_div.appendChild(in_div1);
out_div.appendChild(in_div2);
cell.appendChild(out_div);
//append the cell to the row
row.appendChild(cell);
/* finally add this celltext to the row array.
* NOTE: you could just as easily append one of the divs or the td tag. In this case
* I'm only changing the text. If you wanted to change the colors, for example, you'd
* need to push in_div1 onto the array. To change both the color and the text\, you'd
* need to push out_div onto the array and later access the children
*/
row_arr.push(celltext);
}
//add the row to the table, and push the row array onto our 2d cell array
table.appendChild(row);
cells.push(row_arr);
}
/* then update the values. The operation performed in update() could have been included
* in the switch-case above, but then if I changed it, I'd need to change it in two places.
* Casts the value from the select box to Number. for *, / , etc. the value is interpreted as
* a number anyways, but for +, it assumes it's a string value.... so we have to cast it.
*/
update(Number(select.options[select.selectedIndex].value));
}
The onLoad function only assigns values to level and energy. Hopefully the comments in the code are clear enough to see what I’ve done.
Next up, the update function
function update(value) {
//loop through each row
for (var i = 0; i<numrows; i++) {
//loop through each cell in a row but ignore the first two
//(values were set up in the onLoad and don't change now)
for (var j=2; j<numcols;j++) {
//again switch-case, this time for the different formulas in each cell
switch(j) {
case 2:
//no bandanna min
cells[i][j].nodeValue = tp((value/150) * ((i+10)/20));
break;
case 3:
//no bandanna max
cells[i][j].nodeValue = tp((value/75) * ((i+10)/20));
break;
case 4:
//vigor min
cells[i][j].nodeValue = tp((value/150) * ((i+20)/20));
break;
case 5:
//vigor max
cells[i][j].nodeValue = tp((value/75) * ((i+20)/20));
break;
case 6:
//vitality min
cells[i][j].nodeValue = tp(((value + 20)/150) * ((i+10)/20));
break;
case 7:
//vitality max
cells[i][j].nodeValue = tp(((value + 20)/75) * ((i+10)/20));
break;
case 8:
//bafunda min
cells[i][j].nodeValue = tp(((value + 25)/150) * ((i+25)/20));
break;
case 9:
//bafunda max
cells[i][j].nodeValue = tp(((value + 25)/75) * ((i+25)/20));
break;
}
}
}
}
The update function, as you can see, is very simple. we use .nodeValue to change the text content of our text nodes. As noted in the comments of the onLoad, you could also use the cell itself or one of the containing divs. Your cases would look more like cells[i][j].childNodes[1].childNodes[0].nodeValue if you used out_div in your cells array.
I suppose the last thing to look at is the style section.
table { border-collapse:collapse; border-width:3px; border-style:double; border-color:black; }
td { border-color:black; border-width:1px; }
thead tr td { padding-left:.5em; }
tbody tr td { padding: 0px 0px 0px 0px; height:1em; width:5em; }
div.outer { position:relative;}
div.bg { opacity: 0.3; height:1em; filter: alpha(opacity=30); padding: 5px 10px 5px 10px; }
div.text { position: absolute; top: 0; bottom: 0; color: black; padding: 5px 10px 5px 10px; }
tbody tr.even { background-color:#d0d0d0; }
tbody tr.odd { background-color:#ffffff; }
It’s pretty short. True, I could have done more to pretty up the page.. and I may yet. but for now, it works well enough and demonstrates the dynamic generation and update technique.
As you can see the code is far from complicated and this would have been far messier in the old pre-DOM days *shudder*.
EDIT: Also, please note that in IE the page is so slow to generate and update that I develop a whole new loathing for Ie every time I open it. In firefox, the generation takes a little time, but not too bad. The updating in Firefox is quite quick. For a small set of rows, IE is fine… 600 rows seemsto kill it though.
Tags: CSS, DOM, Dynamic, JavaScript, Opacity, Positioning, Table