Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Jooshe Tables - Part 2 of JavaScript Object-Oriented Subclassing of HTML Elements - Makes Dynamic Tables a Snap!

4.89/5 (5 votes)
23 Dec 2013MIT7 min read 21.6K   94  
Jooshe is juicy!

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:

JavaScript
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:

HTML
<script src="jooshe.js"></script>
<script src="jooshe.tables.js"></script>

or

HTML
<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:

JavaScript
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:

JavaScript
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

JavaScript
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";  // this function is used by other classes
  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 objects
  • p - this-level object or array of objects
  • q - $-level object or array of objects
  • r - 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 objects
  • p - element-level object or array of objects
  • q - $-level object or array of objects

or

  • o - Jooshe element
  • p - style-level object or array of objects
  • q - element-level object or array of objects
  • r - $-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:

JavaScript
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,00,10,20,30,40,50,60,70,80,9
1,01,11,21,31,41,51,61,71,81,9
2,02,12,22,32,42,52,62,72,82,9
3,03,13,23,33,43,53,63,73,83,9
4,04,14,24,34,44,54,64,74,84,9
5,05,15,25,35,45,55,65,75,85,9
6,06,16,26,36,46,56,66,76,86,9
7,07,17,27,37,47,57,67,77,87,9
8,08,18,28,38,48,58,68,78,88,9
9,09,19,29,39,49,59,69,79,89,9

Now, let's prepend a row

JavaScript
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:

0123456789
0,00,10,20,30,40,50,60,70,80,9
1,01,11,21,31,41,51,61,71,81,9
2,02,12,22,32,42,52,62,72,82,9
3,03,13,23,33,43,53,63,73,83,9
4,04,14,24,34,44,54,64,74,84,9
5,05,15,25,35,45,55,65,75,85,9
6,06,16,26,36,46,56,66,76,86,9
7,07,17,27,37,47,57,67,77,87,9
8,08,18,28,38,48,58,68,78,88,9
9,09,19,29,39,49,59,69,79,89,9

Now, we'll set the font-weight of all the bottom row cells to bold:

JavaScript
o = theTable.$.lastRow();
for(i=0;i<10;i++) o.$.cell(i,{fontWeight:"bold"});
0123456789
0,00,10,20,30,40,50,60,70,80,9
1,01,11,21,31,41,51,61,71,81,9
2,02,12,22,32,42,52,62,72,82,9
3,03,13,23,33,43,53,63,73,83,9
4,04,14,24,34,44,54,64,74,84,9
5,05,15,25,35,45,55,65,75,85,9
6,06,16,26,36,46,56,66,76,86,9
7,07,17,27,37,47,57,67,77,87,9
8,08,18,28,38,48,58,68,78,88,9
9,09,19,29,39,49,59,69,79,89,9

Now, we'll add a header to the table:

JavaScript
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]}
  );
0123456789
0,00,10,20,30,40,50,60,70,80,9
1,01,11,21,31,41,51,61,71,81,9
2,02,12,22,32,42,52,62,72,82,9
3,03,13,23,33,43,53,63,73,83,9
4,04,14,24,34,44,54,64,74,84,9
5,05,15,25,35,45,55,65,75,85,9
6,06,16,26,36,46,56,66,76,86,9
7,07,17,27,37,47,57,67,77,87,9
8,08,18,28,38,48,58,68,78,88,9
9,09,19,29,39,49,59,69,79,89,9
ABCDEFGHIJ

Now, we'll add a row of images to the table:

JavaScript
for(i=0;i<10;i++) theTable.$.cell(11,i,{padding:"5px 0 10px"}).$.setImage("ball"+(i%2)+".png");
0123456789
0,00,10,20,30,40,50,60,70,80,9
1,01,11,21,31,41,51,61,71,81,9
2,02,12,22,32,42,52,62,72,82,9
3,03,13,23,33,43,53,63,73,83,9
4,04,14,24,34,44,54,64,74,84,9
5,05,15,25,35,45,55,65,75,85,9
6,06,16,26,36,46,56,66,76,86,9
7,07,17,27,37,47,57,67,77,87,9
8,08,18,28,38,48,58,68,78,88,9
9,09,19,29,39,49,59,69,79,89,9
ABCDEFGHIJ

Now, we'll use a sprite sheet to add another row of images:

JavaScript
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:

JavaScript
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:

JavaScript
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

License

This article, along with any associated source code and files, is licensed under The MIT License