Blog-Archiv

Montag, 29. Februar 2016

JS Table Layout Adjustment: Predefined Widths

Here comes the 4th part of my series about adjusting the layout of nested HTML tables. As this seems to be an endless story, there will be just one more follower, about how to apply the JS modules to a DIV structure with CSS display: table; layout.

Introducing Predefined Sizes

The layout of nested tables has been done by calculating the maximum width of all cells in a column, and then applying that width to all cells. Now I want to provide the opportunity to pre-define certain column widths.

The main problem is to associate columns to dotted numbers (which I call "categories"). You have a column that you know as "lastName", and you would like to assign a width of 40 pixels to it. You need to somehow map that semantic column name to a category like "1.1.3". Then you could associate the pixel width and pass such a map to the JS script.

I think that map will have to look similar to this:

            var predefinedSizes = {};
            predefinedSizes["1.1.3"] = 40; // "lastName" pixels
            predefinedSizes["1.1.4"] = 30; // "firstName" pixels
            ....

Take care that you don't define a column as both elastic and predefined-sized !

Having this map, we can weave the pre-defined widths into the existing modules. The abstractCategorizer module won't be affected. I started implementing in abstractLayoutAdjuster module, and when it worked, I took the functions to an overriding module that uses a facility I had to create in the underlying module. Here is how the that override facility looks.

    "use strict";

    var abstractLayoutAdjuster = function(categorizer)
    {
      ....

      /**
       * To be overridden for pre-processing directly before sizes are
       * calculated and set to the table cells. This implementation does nothing.
       * @param levelArraysMap a map with key = level, and value = array of cells on that level.
       * @param maximumLevel the maximum 0-n level being in levelArraysMap.
       */
      that.beforeSizing = function(levelArraysMap, maximumLevel) {
      };
      
      that.init = function(elementsToLayout, predefinedSizes) {
        ....

        for (var i = 0; i < elementsToLayout.length; i++) {
          ....
          
          that.beforeSizing(levelArraysMap, maximumLevel);

          for (var level = maximumLevel; level >= 0; level--)
            calculateAndSize(levelArraysMap[level]);

          ....
        }
        ....
      };

      return that;

    }; // module end

A new function beforeSizing(levelArraysMap, maximumLevel) has been introduced, and it is doing nothing. Overriders can use it to set pre-defined sizes before the calculation of the maximum is done by calculateAndSize().

Why I did not leave the code where I implemented and tested it:

  • I consider JS modules to be smallest building blocks that encapsulate some piece of complexity. My aim is to tame complexity, so I divide it until it gets simple, and then I encapsulate the parts. That way I got the "ghost in a bottle". Don't let the ghost become overwhelming complex again by weaving in all new aspects.
    In short, I try to keep my JS modules simple and understandable. New features can be done in an OO way by overrides.

Implementing Predefined Sizes

Here is the new module. Mind the additional module parameter predefinedSizes, being the column widths map.

 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
    "use strict";

    /**
     * Abstract implementation of a layout-adjuster that also sets predefined sizes.
     * @param categorizer see abstractLayoutAdjuster.
     * @param predefinedSizes map of key = category and value = size integer.
     */
    var abstractPredefinedSizeAdjuster = function(categorizer, predefinedSizes)
    {
      var predefineSizes = function(categorizedElements, predefinedSizes) {
        for (var i = 0; i < categorizedElements.length; i++) {
          var element = categorizedElements[i];

          if (that.isElementToSize(element)) {
            var category = categorizer.getCategory(element);
            var size = predefinedSizes[category];
            if (size !== undefined)
              that.setSize(element, size);
          }
        }
      };

      var that = abstractLayoutAdjuster(categorizer);
      
      /**
       * Overridden to set predefined sizes before sizes are
       * calculated and set to the table cells.
       * @param levelArraysMap a map with key = level, and value = array of cells on that level.
       * @param maximumLevel the maximum 0-n level being in levelArraysMap.
       */
      that.beforeSizing = function(levelArraysMap, maximumLevel) {
          /* bottom-up loop to calculate and size deepest level first */
          if (predefinedSizes)
            for (var level = maximumLevel; level >= 0; level--)
              predefineSizes(levelArraysMap[level], predefinedSizes);
      };
      
      return that;
    };

This new module contains the implementation for setting predefined widths to categorized elements in the private predefineSizes() function. Mind that it calls setSize(), so the module also could be reused for adjusting row heights instead of column widths, just the implementation of setSize() decides what sizing means!

Extending the abstractLayoutAdjuster module is done by

      var that = abstractLayoutAdjuster(categorizer);

The module then overrides the public beforeSizing() function of its super-module to call predefineSizes() with all categorized elements of all levels, from bottom to top.

Using the New Module

To use the new module, an new initialization sequence is necessary. Here is an example, also including an elastic column.

      var predefinedSizes = {
        "1.1.3": 40,
        "1.1.4": 30
      };

      var categorizer = tableColumnCategorizer();
      var predefinedSizesAdjuster = abstractPredefinedSizeAdjuster(categorizer, predefinedSizes);
      var columnAdjuster = nestedTablesColumnAdjuster(categorizer, predefinedSizesAdjuster);
      columnAdjuster.init(tables);

The attentive reader might have noticed that a module parameter is missing here. To be able to use the new module, I changed the signature of the nestedTablesColumnAdjuster module to introduce a new parameter abstractLayoutAdjuster.

    "use strict";

    var nestedTablesColumnAdjuster = function(categorizer, abstractLayoutAdjuster)
    {
      var that = abstractLayoutAdjuster;

      .....

      return that;
    };

I call this "inheritance at runtime". Such would not be possible in a strictly typed language like Java. I pass the module to extend as construction parameter to nestedTablesColumnAdjuster. An impressive power demonstration of the rubber-language JavaScript :-)

As it can be seen here, code re-usage in JS has its price. There are many different ways how modules, functions and their parameters could be put together. In this case, a construction chain elaborates. You could also call it columnAdjusterBuilder.


All JS described here refers to already implemented modules from previous Blogs. When you want to see complete source code, or try out predefined column widths, go to my homepage. Press Ctrl-U or use your browser menu item "View Page Source" to see its HTML and JS.




Keine Kommentare: