Prerequisites
Part 1: Jooshe - JavaScript Object-Oriented Subclassing of HTML Elements
Introduction
This article expands the Jooshe JavaScript library with a set of table-related classes. These classes enable you to easily create and manipulate DOM-based tables.
How Easily?
As an example of Jooshe Tables, a 100 x 100 table can be created with:
var i, j, theTable = new table;
for(i=0;i<100;i++) for(j=0;j<100;j++) theTable.$.cell(i,j,null,{ innerHTML: i + "," + j });
Using Jooshe Tables
Load the Jooshe JavaScript library and the Jooshe Tables JavaScript library:
<script src="jooshe.js"></script>
<script src="jooshe.tables.js"></script>
or
<script src="jooshe.min.js"></script>
<script src="jooshe.tables.min.js"></script>
The src
attribute of the <script>
element must point to a copy of Jooshe and Jooshe Tables.
A copy of Jooshe and Jooshe Tables is provided in the download along with the demo code.
Compatibility
All major browsers, IE 9+.
Demo Link
The demo we'll be building in this article can be viewed here.
Background
Tables have always been a contentious issue in HTML. CSS purists avoid tables like the plague. The main problems are its cellpadding and cellspacing attributes. These two attributes probably should have been incorporated into the style for the table. Alternatively, they should at least both have a specified default value of 0, so the table would be purely structural. Unfortunately, the defaults for cellpadding and cellspacing are 1px and 2px, respectively.
Tables have their place and don't deserve all the FUD.
Use Case
The use case for tables is simply any collection of containers where the desired relative horizontal or vertical positioning is constant.
For example,
Google uses a table on their home page search box. I couldn't include the html to replicate it here, but you can do an Inspect element
on their site to see the structure for yourself. The image above was taken on Chrome - the microphone icon (Search by voice) doesn't seem to appear in any other browser. Google's used a table here with one row and two cells - one for the text input and one for the microphone icon. In this case, they want to ensure the microphone always appears to the right of the text input (constant relative horizontal positioning of the two elements).
Jooshe
Jooshe starts off by reducing the table to a purely structural object. In the 'CSS-Reset' section of Jooshe (jooshe.js), we have the following line of code:
if(tag == "table") { me.cellPadding = 0; me.cellSpacing = 0; }
If you need a table with non-zero cellpadding or cellspacing, creating one is as simple as:
var myTable = element("table",null,{cellpadding:1,cellspacing:2});
Jooshe Tables
Jooshe Tables introduces the following 6 classes, each with a set of methods which make dynamic table creation and manipulation a snap!
- table
- tbody
- thead
- tfoot
- tr
- td
jooshe.tables.js
createClass("table",function(o,p,q,r){ return element("table",o,[this].concat(p),q,r); },{$:{
expansionElement:"tbody",
appendCell:function(o,p,q,r){ return this.body(0).$.appendCell(o,p,q,r); },
appendRow:function(o,p,q,r){ return this.body(0).$.appendRow(o,p,q,r); },
body:function(i,o,p,q,r){
var b = /^\[object HTML/.test(Object.prototype.toString.call(o)), x,
u = this.el,
v = window[this.expansionElement],
w = J$.wrap,
s = v == tbody ? "tBodies" : "childNodes";
i = i || 0;
if(i >= 0 && u[s].length > i) {
x = u[s][i];
if(b) u.replaceChild(x,o);
}
else {
x = b ? o : new v(o,p,q);
if(i < 0 && u[s].length) u.insertBefore(x,u.firstChild);
else {
while(u[s].length < i) u.appendChild(new v);
u.appendChild(x);
}
}
w(x.style,b?p:o);
w(x,b?q:p);
w(x.$,b?r:q);
return x;
},
cell:function(i,j,o,p,q,r){ return this.body(0).$.cell(i,j,o,p,q,r); },
firstCell:function(o,p,q,r){ return this.body(0).$.firstCell(o,p,q,r); },
firstRow:function(o,p,q,r){ return this.body(0).$.firstRow(o,p,q,r); },
foot:function(o,p,q,r){ return this.el.tFoot || this.el.appendChild(new tfoot(o,p,q,r)); },
head:function(o,p,q,r){ return this.el.tHead || this.el.appendChild(new thead(o,p,q,r)); },
lastCell:function(o,p,q,r){ return this.body(0).$.lastCell(o,p,q,r); },
lastRow:function(o,p,q,r){ return this.body(0).$.lastRow(o,p,q); },
prependCell:function(o,p,q,r){ return this.body(0).$.prependCell(o,p,q,r); },
prependRow:function(o,p,q,r){ return this.body(0).$.prependRow(o,p,q,r); },
row:function(i,o,p,q,r){ return this.body(0).$.row(i,o,p,q,r); },
setBackground:function(s){ var o = this.el; o.style.backgroundImage = s; return o; }
}});
createClass("tbody",function(o,p,q,r,s){ return element(s || "tbody",o,[this].concat(p),q,r); },{$:{
expansionElement:"tr",expansionMethod:"row",
appendCell:function(o,p,q,r){ return this.lastRow().$.appendCell(o,p,q,r); },
appendRow:function(o,p,q,r){
return this[this.expansionMethod].call(this,this.el.childNodes.length,o,p,q,r);
},
cell:function(i,j,o,p,q,r){ return this.row(i).$.cell(j,o,p,q,r); },
firstCell:function(o,p,q,r){ return this.cell(0,0,o,p,q,r); },
firstRow:function(o,p,q,r){ return this.row(0,o,p,q,r); },
lastCell:function(o,p,q,r){ return this.lastRow().$.lastCell(o,p,q,r); },
lastRow:function(o,p,q,r){ var i = this.el.childNodes.length; return this.row(i?i-1:0,o,p,q,r); },
prependCell:function(o,p,q,r){ return this.lastRow().$.prependCell(o,p,q,r); },
prependRow:function(o,p,q,r){ return this[this.expansionMethod].call(this,-1,o,p,q,r); },
row:table.prototype.$.body
}});
createClass("thead",function(o,p,q,r){ return new tbody(o,p,q,r,"thead"); });
createClass("tfoot",function(o,p,q,r){ return new tbody(o,p,q,r,"tfoot"); });
createClass("tr",function(o,p,q,r){ return element("tr",o,[this].concat(p),q,r); },{$:{
expansionElement:"td",expansionMethod:"cell",
appendCell:tbody.prototype.$.appendRow,
cell:table.prototype.$.body,
firstCell:function(o,p,q,r){ return this.cell(0,o,p,q,r); },
lastCell:function(o,p,q,r){ var i = this.el.childNodes.length; return this.cell(i?i-1:0,o,p,q,r); },
prependCell:tbody.prototype.$.prependRow
}});
createClass("td",function(o,p,q,r,s){
var me = element("td",o,[this].concat(p),q,r);
if(s)me.$.setImage(s);
return me;
},{$:{
clear:function(){ var o=this.el; while(o.firstChild) o.removeChild(o.firstChild); },
setBackground:table.prototype.$.setBackground,
setImage:function(s,o,p){
this.clear();
var q=this.el;
q.style.textAlign="center";
return q.appendChild(element("img",o,p,null,{src:s}));
},
setSprite:function(s,i,j,k,l){
this.clear();
return this.el.appendChild(
element("div",
{background:"url("+s+") no-repeat "+i+"px "+j+"px",margin:"auto",width:k+"px",height:l+"px"}
)
);
}
}});
OK, that's a lot of code. The good news is the usage is the same for most of the functions and a lot of the functionality is replicated over the separate structural components of a table element.
Usage
All the createClass
functions accept the same set of parameters:
o
- style-level object or array of objectsp
- this
-level object or array of objectsq
- $-level object or array of objectsr
- element-level object or array of objects
All the functions which accept parameters o,p,q,r
have two forms of usage:
o
- style-level object or array of objectsp
- element-level object or array of objectsq
- $-level object or array of objects
or
o
- Jooshe elementp
- style-level object or array of objectsq
- element-level object or array of objectsr
- $-level object or array of objects
If the second version is used, the passed-in Jooshe element will be used to either expand the table or to replace an existing element of the table. The prepend / append functions expand the table - the rest of the functions which accept parameters o,p,q,r
access existing elements.
A note on the access-type functions: if you specify a row / column that doesn't exist, the table will be automatically expanded to the specified dimension(s).
Functionality
My goal was to use self-explanatory function names. If any of the functionality is unclear, let me know in the comments and I'll elaborate.
The best way to illustrate the use of all this functionality is to dig into the demo.
Demo
Let's start with a simple 10 x 10 table:
var i, j, o, theTable = new table({borderBottom:"1px solid black"});
for(i=0;i<10;i++)
for(j=0;j<10;j++)
theTable.$.cell(i,j,{padding: "5px 10px"},{innerHTML: i + "," + j});
That will generate the following table:
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 | 0,8 | 0,9 |
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | 1,6 | 1,7 | 1,8 | 1,9 |
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 | 2,6 | 2,7 | 2,8 | 2,9 |
3,0 | 3,1 | 3,2 | 3,3 | 3,4 | 3,5 | 3,6 | 3,7 | 3,8 | 3,9 |
4,0 | 4,1 | 4,2 | 4,3 | 4,4 | 4,5 | 4,6 | 4,7 | 4,8 | 4,9 |
5,0 | 5,1 | 5,2 | 5,3 | 5,4 | 5,5 | 5,6 | 5,7 | 5,8 | 5,9 |
6,0 | 6,1 | 6,2 | 6,3 | 6,4 | 6,5 | 6,6 | 6,7 | 6,8 | 6,9 |
7,0 | 7,1 | 7,2 | 7,3 | 7,4 | 7,5 | 7,6 | 7,7 | 7,8 | 7,9 |
8,0 | 8,1 | 8,2 | 8,3 | 8,4 | 8,5 | 8,6 | 8,7 | 8,8 | 8,9 |
9,0 | 9,1 | 9,2 | 9,3 | 9,4 | 9,5 | 9,6 | 9,7 | 9,8 | 9,9 |
Now, let's prepend a row
theTable.$.prependRow();
for(i=0;i<10;i++) theTable.$.cell(0,i,{padding:"10px 0",textAlign:"center"},{innerHTML:i});
Our table now looks like this:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 | 0,8 | 0,9 |
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | 1,6 | 1,7 | 1,8 | 1,9 |
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 | 2,6 | 2,7 | 2,8 | 2,9 |
3,0 | 3,1 | 3,2 | 3,3 | 3,4 | 3,5 | 3,6 | 3,7 | 3,8 | 3,9 |
4,0 | 4,1 | 4,2 | 4,3 | 4,4 | 4,5 | 4,6 | 4,7 | 4,8 | 4,9 |
5,0 | 5,1 | 5,2 | 5,3 | 5,4 | 5,5 | 5,6 | 5,7 | 5,8 | 5,9 |
6,0 | 6,1 | 6,2 | 6,3 | 6,4 | 6,5 | 6,6 | 6,7 | 6,8 | 6,9 |
7,0 | 7,1 | 7,2 | 7,3 | 7,4 | 7,5 | 7,6 | 7,7 | 7,8 | 7,9 |
8,0 | 8,1 | 8,2 | 8,3 | 8,4 | 8,5 | 8,6 | 8,7 | 8,8 | 8,9 |
9,0 | 9,1 | 9,2 | 9,3 | 9,4 | 9,5 | 9,6 | 9,7 | 9,8 | 9,9 |
Now, we'll set the font-weight of all the bottom row cells to bold:
o = theTable.$.lastRow();
for(i=0;i<10;i++) o.$.cell(i,{fontWeight:"bold"});
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 | 0,8 | 0,9 |
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | 1,6 | 1,7 | 1,8 | 1,9 |
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 | 2,6 | 2,7 | 2,8 | 2,9 |
3,0 | 3,1 | 3,2 | 3,3 | 3,4 | 3,5 | 3,6 | 3,7 | 3,8 | 3,9 |
4,0 | 4,1 | 4,2 | 4,3 | 4,4 | 4,5 | 4,6 | 4,7 | 4,8 | 4,9 |
5,0 | 5,1 | 5,2 | 5,3 | 5,4 | 5,5 | 5,6 | 5,7 | 5,8 | 5,9 |
6,0 | 6,1 | 6,2 | 6,3 | 6,4 | 6,5 | 6,6 | 6,7 | 6,8 | 6,9 |
7,0 | 7,1 | 7,2 | 7,3 | 7,4 | 7,5 | 7,6 | 7,7 | 7,8 | 7,9 |
8,0 | 8,1 | 8,2 | 8,3 | 8,4 | 8,5 | 8,6 | 8,7 | 8,8 | 8,9 |
9,0 | 9,1 | 9,2 | 9,3 | 9,4 | 9,5 | 9,6 | 9,7 | 9,8 | 9,9 |
Now, we'll add a header to the table:
o = theTable.$.head();
for(i=0;i<10;i++)
o.$.cell(0,i,
{background:"#f63",color:"#fff",fontWeight:"bold",padding:"5px 0",textAlign:"center"},
{innerHTML:"ABCDEFGHIJ"[i]}
);
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 | 0,8 | 0,9 |
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | 1,6 | 1,7 | 1,8 | 1,9 |
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 | 2,6 | 2,7 | 2,8 | 2,9 |
3,0 | 3,1 | 3,2 | 3,3 | 3,4 | 3,5 | 3,6 | 3,7 | 3,8 | 3,9 |
4,0 | 4,1 | 4,2 | 4,3 | 4,4 | 4,5 | 4,6 | 4,7 | 4,8 | 4,9 |
5,0 | 5,1 | 5,2 | 5,3 | 5,4 | 5,5 | 5,6 | 5,7 | 5,8 | 5,9 |
6,0 | 6,1 | 6,2 | 6,3 | 6,4 | 6,5 | 6,6 | 6,7 | 6,8 | 6,9 |
7,0 | 7,1 | 7,2 | 7,3 | 7,4 | 7,5 | 7,6 | 7,7 | 7,8 | 7,9 |
8,0 | 8,1 | 8,2 | 8,3 | 8,4 | 8,5 | 8,6 | 8,7 | 8,8 | 8,9 |
9,0 | 9,1 | 9,2 | 9,3 | 9,4 | 9,5 | 9,6 | 9,7 | 9,8 | 9,9 |
A | B | C | D | E | F | G | H | I | J |
Now, we'll add a row of images to the table:
for(i=0;i<10;i++) theTable.$.cell(11,i,{padding:"5px 0 10px"}).$.setImage("ball"+(i%2)+".png");
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 | 0,8 | 0,9 |
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | 1,6 | 1,7 | 1,8 | 1,9 |
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 | 2,6 | 2,7 | 2,8 | 2,9 |
3,0 | 3,1 | 3,2 | 3,3 | 3,4 | 3,5 | 3,6 | 3,7 | 3,8 | 3,9 |
4,0 | 4,1 | 4,2 | 4,3 | 4,4 | 4,5 | 4,6 | 4,7 | 4,8 | 4,9 |
5,0 | 5,1 | 5,2 | 5,3 | 5,4 | 5,5 | 5,6 | 5,7 | 5,8 | 5,9 |
6,0 | 6,1 | 6,2 | 6,3 | 6,4 | 6,5 | 6,6 | 6,7 | 6,8 | 6,9 |
7,0 | 7,1 | 7,2 | 7,3 | 7,4 | 7,5 | 7,6 | 7,7 | 7,8 | 7,9 |
8,0 | 8,1 | 8,2 | 8,3 | 8,4 | 8,5 | 8,6 | 8,7 | 8,8 | 8,9 |
9,0 | 9,1 | 9,2 | 9,3 | 9,4 | 9,5 | 9,6 | 9,7 | 9,8 | 9,9 |
| | | | | | | | | |
A | B | C | D | E | F | G | H | I | J |
Now, we'll use a sprite sheet to add another row of images:
for(i=0;i<10;i++){
var a=[[5,5],[31,5],[57,5],[5,31],[31,31],[57,31],[5,57],[31,57],[57,57],[5,31]][i];
theTable.$.cell(12,i,{padding:"5px 0 10px"}).$.setSprite("spritesheet.png",-a[0],-a[1],16,16);
}
I can't set the background of an element on a Code Project article. Everything else in the demo is beyond what I can show in a Code Project article. The full demo is here.
Btw, using Sprites instead of images is a great way to speed up your application. Stitches is an awesome tool for creating sprite sheets.
Finally, we'll add some code to highlight random cells:
var currentCell = null;
setInterval(function(){
if(currentCell)currentCell.style.background="";
var i = Math.floor(Math.random() * 11), j = Math.floor(Math.random() * 10);
currentCell = theTable.$.cell(i,j,{background:"#ffff76"});
},
1000
);
We can refactor the "flashing cells" code and encapsulate it within a custom Jooshe class:
var i, j, theTable = new table({marginTop:"40px"});
createClass("flashyCell",
function(i,j){return new td({padding:"5px 10px"},this,null,{innerHTML: i + "," + j});},
{$:{
flashNext:function(){
this.el.style.background = "";
var i = Math.floor(Math.random() * 10),
j = Math.floor(Math.random() * 10),
nextCell = theTable.$.cell(i,j,{background:"#ffff76"});
setTimeout(function(){nextCell.$.flashNext();},1000);}
}
}
);
for(i=0;i<10;i++) for(j=0;j<10;j++) theTable.$.cell(i,j,new flashyCell(i,j));
document.body.appendChild(theTable);
theTable.$.cell(0,0).$.flashNext();
Inspired?
If you're inspired by the possibilities of Jooshe Tables, consider writing your own Code Project article showing how you've used it and include a link to this article in your article.
| | |
Send your article link to | and I'll list it here. |
If you'd like your demo to be hosted on jooshe.org, include your demo files in the email and I'll send you back a permalink on jooshe.org.
Don't forget to vote, tweet, plus, and like this article (buttons up top) :)
Points of Interest
- I developed Jooshe several years ago to handle the dynamic requirements of dbiScript.
- dbiScript currently consists of 350 Jooshe classes including 230 class instances of the Jooshe table.
- If you're curious as to how well a major Jooshe application performs, download dbiScript and see for yourself.
Jooshe on the Web