To cope with something means to live together with something unknown and seemingly uncontrollable. We don't need to control everything, but we also don't want to suffer from consequences of incalculable risks.
One thing we can do when we have a problem is giving it a name. Practically that name should make it more comprehensible. When we can understand it, we can cope with it.
This is not a self-help-group-instruction. This is about a web-page layout-problem solved by giving names. Although these are different things, I would consider the naming-activity being more important in this than the layout-solution. Remember:
We can cope with things that got a name.
And it will be fine when it is a meaningful name.
This Blog is about HTML tables nested into other tables. The nested tables are not related to each other, thus their column widths will be different. Such layout does not look good. The JavaScript to be introduced in the following tries to fix that. (Mind that you won't have this problem when you never embed tables into other tables.)
Here is an example of what we have to cope with:
Date |
| |||||||||
---|---|---|---|---|---|---|---|---|---|---|
2016-02-05 |
| |||||||||
2016-02-06 |
| |||||||||
2016-02-07 |
|
In this example table, all tables have a green border, header cells a blue, data cells a magenta. Tables of nesting-level 1 are yellow. Table cells that have a colspan
attribute are rendered orange.
Sure, all data are there and readable. But we can hardly associate the columns in the different nested tables to each other. For example, try to sum up all prices.
The fix won't be just for the eye, it'll be for avoiding human mistakes. When we talk about shape and content, we should be aware that we need them both and together. As is HTML.
The introduction of the JavaScript solving this layout problem will be divided into several Blogs. This one is the first, and it is about naming cells being logically (but sometimes not visually) below each other.
The second one will be about adjusting these cells to have same widths. The column-width will be the initial width of the widest cell in it.
The third Blog will be about script extensions to achieve an elastic column in a 100% stretched table, and how to do the same for a DIV table.
All JS scripts will be modules, and I always use functional inheritance to reuse JS code in a simple and safe manner. No jQuery is used here, nevertheless the code works even in IE-9.
For a more compound test page and full JS source code you can visit my homepage.
When I want to size all cells of one logical column to the same width, I have to cope with table-cell elements being somewhere in a tree of HTML elements, most likely quite far away from each other. To be able to size them, I give names to them. My naming scheme is the well-known chapter numbering system: 1 for first chapter, 1.1 for first sub-chapter of first chapter, 1.1.2 for second sub-sub-chapter of first sub-chapter, and so on. I will call these names categories, because they won't be unique identifiers for cells, but more a category a cell falls into.
Transferring this numbering system to the cells of a table containing nested tables, I would give 1 to all cells in first column, 2 to all cells in second column, and so on. To all cells in first column of a table nested into a cell 2, I would give 2.1. Would there be another table in that cell, its first cell would have the category 2.1.1.
1 |
| |||||||||||||||
1 |
|
As you see, all cells that must have same widths are named by the same category (dotted number). The count of dots in the category reflects the nesting level, and the numbers give the column order index.
You could immediately write some CSS now to set fixed widths to the columns categorized in that way. But, in my opinion, JS solutions are more sophisticated and reusable, so I will do it with JS.
As the table nesting is not restricted to any depth, the JS implementation should work recursive. Here is a JS module that categorizes a given table. It is called "abstract" because it does not know yet the nature of the HTML tags it should look for. It just traverses the HTML table and names elements. A concrete extension of that module will add functions that decide which HTML elements should be looked for.
1 | /** |
Mind that two functions are not yet implemented:
- that.isElementToCategorize(element)
- that.getSpan(predecessor)
The isElementToCategorize()
function will restrict the module to HTML TABLE elements with TH and TD cells. But I want to apply that module also on tables built from DIV elements with CSS display: table
. So I will implement concrete sub-classes of this, one for TABLE elements, one for DIV-table elements.
The colspan
attribute is specific to TABLE, a DIV-table doesn't support that. The getSpan()
implementation for TABLE will return the number in the colspan
attribute, or 1 when not found, and the DIV-table implementation always will return 1.
The
CATEGORY_ATTRIBUTE_NAME
parameter lets set a name for the element attribute where the name (category) will be written into.The
buildDottedNumber()
function receives the index of the element to name, and it ascends until it finds a parent-category. When it does not find one, the index alone will be the category, else it is appended to the parent-category.The
categorizeElement()
function recognizes thecolspan
of a predecessor element, and corrects the index when one exists. It sets the built category into the element attribute. Then it calculates the nesting level from the number of dots in the category, and adds the element to an array in a level-map which is to return.The
categorize()
function is the recursive traversal. First it categorizes the element received as parameter, then it calls itself recursively in a loop over all children (whereby the child-index will be part of the the generated category). Keeping that call-order, the children will always find parent-categories when callingbuildDottedNumber()
.The
getCategory()
function reads the category from an element. This is here to encapsulateCATEGORY_ATTRIBUTE_NAME
. Mind that this is the only public function here, besidesinit()
. No other function will need to be called or overridden from modules extending this one.The
init()
function at last returns a map of levels. In each level (0-n, used as map-key) an array of elements on that level is stored. This return-map can be used to layout the table.
Now I can extend this module to implement a concrete table-column categorizer.
1 | /** |
This adds the not-yet-implemented functions, and thus makes the abstract module concrete. The extension of the abstract module is done by the line
var that = abstractCategorizer(CATEGORY_ATTRIBUTE_NAME);
This is like extending a class in Java. Just that in JS no classes exist, everything is an object. As a consequence you can extend modules even at runtime, which is quite useful in some situations.
The
isElementToCategorize()
function determines that TD and TH elements will be named (categorized).The
getSpan()
function delivers the number of colums the given element spans. As I did not want to restrict this tocolspan
I named itgetSpan()
, because we also haverowspan
, and basically the script is also able to categorize cells for adjusting row heights.
That's it! Functional inheritance helps to keep JS modules short and encapsulated. When you start the concrete module over a TABLE, all cells will be categorized.
In my next Blog I will show how this can be used to adjust table columns. Again there will be an abstractColumnAdjuster
with concrete sub-modules.
ɔ⃝ Fritz Ritzberger, 2016-02-06