Blog-Archiv

Sonntag, 6. März 2016

JS Table Layout Adjustment: DIV Tables

This is the last of my series about adjusting columns of nested tables. It answers the question


Tables can be built not only by HTML TABLE elements, but also by DIV elements when using specific CSS display styles. Here is an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <style type="text/css">
    .table {
      display: table;
    }
    .thead {
      display: table-header-group;
    }
    .tbody {
      display: table-row-group;
    }
    .tr {
      display: table-row;
    }
    .td, .th {
      display: table-cell;
    }
  </style>
  
</head>

<body>

  <div class="table">

    <div class="thead">

      <div class="tr">
        <div class="th">Column 1</div>
        <div class="th">Column 2</div>
      </div>

    </div>

    <div class="tbody">

      <div class="tr">
        <div class="td">Content One</div>
        <div class="td">Content Two</div>
      </div>

      <div class="tr">
        <div class="td">Content Three</div>
        <div class="td">Content Four</div>
      </div>

    </div>

  </div>

</body>
</html>

This displays like a normal HTML TABLE. The only difference is that DIV tables do not support certain features like e.g. the colspan attribute, and do not have browser-defaults for padding, margin etc.


How can I reuse the JS code for TABLE elements to do the same for DIV tables?

By implementing new categorizer and adjuster modules, both extending their already provided abstractions.
As you may remember, I implemented modules with different responsibilities:

  • identifying column cells as abstractCategorizer
    • concrete implementation as tableColumnCategorizer
  • measuring and sizing columns as abstractLayoutAdjuster
    • concrete implementation as nestedTablesColumnAdjuster

They were finally built together to create the adjuster fitting to the requirements.

Now here is the new categorizer module implementation for DIV tables:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    "use strict";

    /**
     * Concrete categorizer for HTML div table columns.
     */
    var divTableColumnCategorizer = function(CATEGORY_ATTRIBUTE_NAME)
    {
      var that = abstractCategorizer(CATEGORY_ATTRIBUTE_NAME);

      /** @return always 1 because DIVs do not support the colspan attribute. */
      that.getSpan = function(element) {
        return 1;
      };

      /**
       * @return true when given element should be categorized.
       *    This implementation returns true when element is
       *    DIV and its "display" CSS property is "table-cell".
       */
      that.isElementToCategorize = function(element) {
        if ( ! element.tagName === "DIV" )
          return false;

        var style = window.getComputedStyle(element);
        return style["display"] === "table-cell"; 
      };

      return that;

    };

This module extends abstractCategorizer. It then implements getSpan() to return 1 (does not support colspan), and isElementToCategorize() to identify table-cells of DIV tables.


Now I need to write an adjuster that can find nested DIV tables.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    "use strict";

    /**
     * Concrete implementation of a layout-adjuster for HTML DIV-tables.
     * @param abstractLayoutAdjuster the layouter to extend.
     */
    var nestedDivTablesColumnAdjuster = function(categorizer, abstractLayoutAdjuster)
    {
      var findNestedDivTables = function(element, nestedDivTables) {
        nestedDivTables = (nestedDivTables !== undefined) ? nestedDivTables : [];

        var children = element.children;
        for (var i = 0; i < children.length; i++) {
          var child = children[i];
          if (child.tagName === "DIV") {
            var style = window.getComputedStyle(child);
            if (style["display"] === "table")
              nestedDivTables.push(child);
          }
          findNestedDivTables(child, nestedDivTables);
        }
        return nestedDivTables;
      };

      var that = nestedTablesColumnAdjuster(categorizer, abstractLayoutAdjuster);

      /** @return all nested DIV table elements below given one. */
      that.getNestedContainers = function(elementToLayout) {
        return findNestedDivTables(elementToLayout);
      };

      return that;

    };

This module extends its predecessor module nestedTablesColumnAdjuster. It overwrites the getNestedContainers() function to call the private findNestedDivTables() implementation. Unfortunately the JS built-in querySelector() function can not be used here, because there is no CSS selector that can target CSS styles like display: table. So we need to loop through children and read their styles.


Here comes the code to integrate the two new modules:

      var categorizer = divTableColumnCategorizer();
      var abstractAdjuster = abstractLayoutAdjuster(categorizer);
      var columnAdjuster = nestedDivTablesColumnAdjuster(categorizer, abstractAdjuster);
      columnAdjuster.init(tables);

Remember that I provided an abstract module that provides pre-defined columns widths. To be able to integrate that module into the build-chain, I needed to specify the abstractAdjuster parameter separately.

Of course also both the elastic-column and predefined-column-widths modules can be reused for DIV tables. Just the build chain changes:

      var categorizer = divTableColumnCategorizer();
      
      var abstractAdjuster = predefinedSizes ?
          abstractPredefinedSizeAdjuster(categorizer, predefinedSizes) :
          abstractLayoutAdjuster(categorizer);
  
      var adjuster = nestedDivTablesColumnAdjuster(categorizer, abstractAdjuster);
      
      var columnAdjuster = elasticColumn ?
          elasticColumnAdjuster(categorizer, adjuster, elasticColumn) :
          adjuster;
  
      columnAdjuster.init(tables);

That's all! You can go to my homepage to see this working.


The beauty of code reuse by inheritance is that you are mostly done by overwriting only a few things. All object-oriented languages provide inheritance. Although JS is not object-oriented, similar can be provided by using functional inheritance.

Generally JS provides several kinds of inheritance (would we have needed so much?). There are prototypal, classical, .... I wrote about this in one of my past Blogs.
I recommend functional inheritance, because it is easy to use and understand, and avoids the risks of the prototype chain and the this pointer, and does not require understanding the new operator. And private instance variables work fine.

Keep it simple, and you will win!




Keine Kommentare: