UIZE JavaScript Framework

2014 NEWS 2014-06-14 - NEW MODULE: Uize.Templates.Text.Table

The new Uize.Templates.Text.Table JavaScript Template module lets you generate a text-based table layout of data that can be used when outputting to logs, consoles, terminals, etc.

The Uize.Templates.Text.ProgressBar module is a simple template module that can be useful in situations where you need to display a set of data in table format using purely text characters.

For instance, you may wish to display a summary of results from a build script in table form in the console, or output such a table to a log file. There are several build scripts in the UIZE JavaScript Framework, for example, that make use of this template module to generate text tables for output to the console.

To illustrate how this module is used, consider the following example of a text table that is being generated to display some price inflation data for the United Kingdom...

EXAMPLE

Uize.Templates.Text.Table.process ({
  title:'Price Inflation in the United Kingdom',
  columns:[
    {title:'Description'},
    {
      title:'Price in 1973',
      align:'right',
      formatter:'value < 1 ? (value * 100).toFixed (0) + "p" : "£" + value.toFixed (2)'
    },
    {
      title:'Price in 2013',
      align:'right',
      formatter:'value < 1 ? (value * 100).toFixed (0) + "p" : "£" + value.toFixed (2)'
    },
    {
      title:'Price Increase',
      align:'right',
      formatter:'value + "%"'
    }
  ],
  rows:[
    ['Pint of lager',.14,2.87,1948],
    ['Loaf of bread',.11,1.30,1082],
    ['Apples, per kg',.28,2.02,622],
    ['Pint of milk',.06,.46,667],
    ['Sausages, per kg',.58,4.84,735],
    ['Butter per 250g',.13,1.42,992],
    ['Carrots, per kg',.11,.91,723],
    ['Sugar, per kg',.11,.93,787],
    ['Coffee, per 100g',.28,2.67,853],
    ['Dozen eggs',.33,2.78,743],
    ['Flour, per 1.5kg',.15,1.19,724],
    ['Diesel, per litre',.08,1.41,1727],
    ['Average detached house',16980,305391,1699],
    ['Gold: troy ounce',34,1051,2952]
  ]
});

The above statement, when executed, will produce the following multi-line string as its output...

OUTPUT

+-------------------------------------------------------------------------+
|                  Price Inflation in the United Kingdom                  |
+-------------------------------------------------------------------------+
|      Description       | Price in 1973 | Price in 2013 | Price Increase |
|------------------------+---------------+---------------+----------------|
| Pint of lager          |           14p |         £2.87 |          1948% |
|------------------------+---------------+---------------+----------------|
| Loaf of bread          |           11p |         £1.30 |          1082% |
|------------------------+---------------+---------------+----------------|
| Apples, per kg         |           28p |         £2.02 |           622% |
|------------------------+---------------+---------------+----------------|
| Pint of milk           |            6p |           46p |           667% |
|------------------------+---------------+---------------+----------------|
| Sausages, per kg       |           58p |         £4.84 |           735% |
|------------------------+---------------+---------------+----------------|
| Butter per 250g        |           13p |         £1.42 |           992% |
|------------------------+---------------+---------------+----------------|
| Carrots, per kg        |           11p |           91p |           723% |
|------------------------+---------------+---------------+----------------|
| Sugar, per kg          |           11p |           93p |           787% |
|------------------------+---------------+---------------+----------------|
| Coffee, per 100g       |           28p |         £2.67 |           853% |
|------------------------+---------------+---------------+----------------|
| Dozen eggs             |           33p |         £2.78 |           743% |
|------------------------+---------------+---------------+----------------|
| Flour, per 1.5kg       |           15p |         £1.19 |           724% |
|------------------------+---------------+---------------+----------------|
| Diesel, per litre      |            8p |         £1.41 |          1727% |
|------------------------+---------------+---------------+----------------|
| Average detached house |     £16980.00 |    £305391.00 |          1699% |
|------------------------+---------------+---------------+----------------|
| Gold: troy ounce       |        £34.00 |      £1051.00 |          2952% |
+-------------------------------------------------------------------------+

1. Table Data

Table data is specified using the rows property of the input object passed to the Uize.Templates.Text.ProgressBar.process method.

The value specified for the rows property should be an array of column arrays. That is to say, each value in the rows array should be a column array that contains values for each of the columns in the table. Therefore, each row array should have the same number of column value elements.

EXAMPLE

Uize.Templates.Text.Table.process ({
  columns:[
    {title:'Column 1'},
    {title:'Column 2'}
  ],
  rows:[
    ['foo','bar'],  // values for column 1 and column 2 of the first row
    ['baz','qux']   // values for column 1 and column 2 of the second row
  ]
});

In the above example, we are generating a text table with two rows and two columns. The first row contains the values 'foo' and 'bar' for the two columns, while the second row contains the values 'baz' and 'qux' for the two columns.

OUTPUT

+---------------------+
| Column 1 | Column 2 |
|----------+----------|
| foo      | bar      |
|----------+----------|
| baz      | qux      |
+---------------------+

2. Table Title

The Uize.Templates.Text.ProgressBar module allows a title to be specified in the title property of the input object passed to the Uize.Templates.Text.ProgressBar.process method.

2.1. Title is Optional

Text tables generated by the Uize.Templates.Text.ProgressBar module do not need to contain a title.

A table without a table can be generated by specifying no title property in the input object, or by specifying the value '' (empty string), null, or undefined for the title property.

EXAMPLE

Uize.Templates.Text.Table.process ({
  columns:[
    {title:'Column 1'},
    {title:'Column 2'}
  ],
  rows:[
    ['foo','bar'],
    ['baz','qux']
  ]
});

OUTPUT

+---------------------+
| Column 1 | Column 2 |
|----------+----------|
| foo      | bar      |
|----------+----------|
| baz      | qux      |
+---------------------+

2.2. Title is Center-aligned

When a title is specified, it is always center-aligned in the generated text table.

EXAMPLE

Uize.Templates.Text.Table.process ({
  title:'Foo Table',
  columns:[
    {title:'Column 1'},
    {title:'Column 2'}
  ],
  rows:[
    ['foo','bar'],
    ['baz','qux']
  ]
});

OUTPUT

+---------------------+
|      Foo Table      |
+---------------------+
| Column 1 | Column 2 |
|----------+----------|
| foo      | bar      |
|----------+----------|
| baz      | qux      |
+---------------------+

3. Column Descriptions

Columns are described for a table using the columns property of the input object passed to the Uize.Templates.Text.ProgressBar.process method.

3.1. Column Description Object

The value specified for the columns property should be an array, where each element is a column description object of the form...

{
  title:titleSTR,                // the display title for the column (required)
  align:alignSTRorFLOAT,         // the alignment of values in the column (optional)
  formatter:formatterFUNCorSTR,  // a value transformer for formatting column values (optional)
  minValue:minValueNUM,          // the lower end of the column's value range (optional)
  maxValue:maxValueNUM           // the upper end of the column's value range (optional)
}

The various properties of the column description object are discussed in more detail in the following sections.

3.2. Column Titles

Titles are specified for columns using the title property of the column description object.

Column titles are required, unlike other properties of the column description object. Column titles are always displayed center-aligned when the calculated column width is greater than the column title width.

EXAMPLE

Uize.Templates.Text.Table.process ({
  title:'Foo Table',
  columns:[
    {title:'Column 1'},
    {title:'Column 2'}
  ],
  rows:[
    ['this is a wide value','another wide value'],
    ['yet another wide value','the final wide value']
  ]
});

OUTPUT

+-----------------------------------------------+
|                   Foo Table                   |
+-----------------------------------------------+
|        Column 1        |       Column 2       |
|------------------------+----------------------|
| this is a wide value   | another wide value   |
|------------------------+----------------------|
| yet another wide value | the final wide value |
+-----------------------------------------------+

3.3. Column Alignment

Alignment of values can be specified for columns using the align property of the column description object.

Column values can be left aligned by specifying the value 'left' or 0, center-aligned by specifying the value 'center' or .5, or right-aligned by specifying the value 'right' or 1.

EXAMPLE

Uize.Templates.Text.Table.process ({
  title:'Foo Table',
  columns:[
    {title:'Column 1',align:'left'},
    {title:'Column 2',align:'center'},
    {title:'Column 3',align:'right'}
  ],
  rows:[
    ['ABC','ABC','ABC'],
    ['ABCDEFG','ABCDEFG','ABCDEFG'],
    ['ABCDEFGHIJK','ABCDEFGHIJK','ABCDEFGHIJK'],
    ['ABCDEFGHIJKLMNO','ABCDEFGHIJKLMNO','ABCDEFGHIJKLMNO'],
    ['ABCDEFGHIJKLMNOPQRS','ABCDEFGHIJKLMNOPQRS','ABCDEFGHIJKLMNOPQRS']
  ]
});

OUTPUT

+-----------------------------------------------------------------+
|                            Foo Table                            |
+-----------------------------------------------------------------+
|      Column 1       |      Column 2       |      Column 3       |
|---------------------+---------------------+---------------------|
| ABC                 |         ABC         |                 ABC |
|---------------------+---------------------+---------------------|
| ABCDEFG             |       ABCDEFG       |             ABCDEFG |
|---------------------+---------------------+---------------------|
| ABCDEFGHIJK         |     ABCDEFGHIJK     |         ABCDEFGHIJK |
|---------------------+---------------------+---------------------|
| ABCDEFGHIJKLMNO     |   ABCDEFGHIJKLMNO   |     ABCDEFGHIJKLMNO |
|---------------------+---------------------+---------------------|
| ABCDEFGHIJKLMNOPQRS | ABCDEFGHIJKLMNOPQRS | ABCDEFGHIJKLMNOPQRS |
+-----------------------------------------------------------------+

3.3.1. Left-aligned, by Default

Column values are left-aligned, by default, if no value is specified explicitly for the align property of the column description object.

EXAMPLE

Uize.Templates.Text.Table.process ({
  title:'Foo Table',
  columns:[
    {title:'Column 1'},
    {title:'Column 2',align:'right'}
  ],
  rows:[
    ['ABC','ABC'],
    ['ABCDEFG','ABCDEFG'],
    ['ABCDEFGHIJK','ABCDEFGHIJK']
  ]
});

In the above example, no value is specified for the align property in the description for the first column. As a result, the alignment for this column is defaulted to left-alignment.

OUTPUT

+---------------------------+
|         Foo Table         |
+---------------------------+
|  Column 1   |  Column 2   |
|-------------+-------------|
| ABC         |         ABC |
|-------------+-------------|
| ABCDEFG     |     ABCDEFG |
|-------------+-------------|
| ABCDEFGHIJK | ABCDEFGHIJK |
+---------------------------+

3.3.2. Fractional Alignment

In addition to being able to specify column alignment using the string values 'left', 'center', and 'right', any arbitrary fractional alignment from left to right can be specified using floating point numbers from 0 to 1.

EXAMPLE

Uize.Templates.Text.Table.process ({
  title:'Foo Table',
  columns:[
    {title:'Column 1',align:0},
    {title:'Column 2',align:.25},
    {title:'Column 3',align:.5},
    {title:'Column 4',align:.75},
    {title:'Column 5',align:1}
  ],
  rows:[
    ['ABC','ABC','ABC','ABC','ABC'],
    ['ABCDEFG','ABCDEFG','ABCDEFG','ABCDEFG','ABCDEFG'],
    ['ABCDEFGHIJK','ABCDEFGHIJK','ABCDEFGHIJK','ABCDEFGHIJK','ABCDEFGHIJK'],
    ['ABCDEFGHIJKLMNO','ABCDEFGHIJKLMNO','ABCDEFGHIJKLMNO','ABCDEFGHIJKLMNO','ABCDEFGHIJKLMNO']
  ]
});

OUTPUT

+-----------------------------------------------------------------------------------------+
|                                        Foo Table                                        |
+-----------------------------------------------------------------------------------------+
|    Column 1     |    Column 2     |    Column 3     |    Column 4     |    Column 5     |
|-----------------+-----------------+-----------------+-----------------+-----------------|
| ABC             |    ABC          |       ABC       |          ABC    |             ABC |
|-----------------+-----------------+-----------------+-----------------+-----------------|
| ABCDEFG         |   ABCDEFG       |     ABCDEFG     |       ABCDEFG   |         ABCDEFG |
|-----------------+-----------------+-----------------+-----------------+-----------------|
| ABCDEFGHIJK     |  ABCDEFGHIJK    |   ABCDEFGHIJK   |    ABCDEFGHIJK  |     ABCDEFGHIJK |
|-----------------+-----------------+-----------------+-----------------+-----------------|
| ABCDEFGHIJKLMNO | ABCDEFGHIJKLMNO | ABCDEFGHIJKLMNO | ABCDEFGHIJKLMNO | ABCDEFGHIJKLMNO |
+-----------------------------------------------------------------------------------------+

3.4. Column Formatters

Values for a column can optionally be formatted by specifying a formatter function or expression for the formatter property of the column description object.

EXAMPLE

Uize.Templates.Text.Table.process ({
  title:'2013 Food Prices in the UK',
  columns:[
    {title:'Description'},
    {
      title:'Price',
      align:'right',
      formatter:function (value) {
        return value < 1 ? (value * 100).toFixed (0) + "p" : "£" + value.toFixed (2);
      }
    }
  ],
  rows:[
    ['Loaf of bread',1.30],
    ['Apples, per kg',2.02],
    ['Pint of milk',.46],
    ['Sausages, per kg',4.84],
    ['Carrots, per kg',.91],
    ['Sugar, per kg',.93],
    ['Dozen eggs',2.78]
  ]
});

In the above example, a column formatter function is being specified for the price column. The formatter function is passed a price in pounds and returns a formatted value that is expressed in pence if the value is below 1 pound, and pounds if the value is 1 pound or greater.

OUTPUT

+----------------------------+
| 2013 Food Prices in the UK |
+----------------------------+
|    Description    | Price  |
|-------------------+--------|
| Loaf of bread     |  £1.30 |
|-------------------+--------|
| Apples, per kg    |  £2.02 |
|-------------------+--------|
| Pint of milk      |    46p |
|-------------------+--------|
| Sausages, per kg  |  £4.84 |
|-------------------+--------|
| Carrots, per kg   |    91p |
|-------------------+--------|
| Sugar, per kg     |    93p |
|-------------------+--------|
| Dozen eggs        |  £2.78 |
+----------------------------+

3.4.1. Column Formatter String Expression

The Uize.Templates.Text.Table module resolves a value specified for the formatter option of the column description object to a value transformer function using the Uize.resolveTransformer method.

This means that the column formatter can also be specifeid in the form of a string expression, as shown in the example below...

EXAMPLE

Uize.Templates.Text.Table.process ({
  title:'2013 Food Prices in the UK',
  columns:[
    {title:'Description'},
    {
      title:'Price',
      align:'right',
      formatter:'value < 1 ? (value * 100).toFixed (0) + "p" : "£" + value.toFixed (2)'
    }
  ],
  rows:[
    ['Loaf of bread',1.30],
    ['Apples, per kg',2.02],
    ['Pint of milk',.46],
    ['Sausages, per kg',4.84],
    ['Carrots, per kg',.91],
    ['Sugar, per kg',.93],
    ['Dozen eggs',2.78]
  ]
});

OUTPUT

+----------------------------+
| 2013 Food Prices in the UK |
+----------------------------+
|    Description    | Price  |
|-------------------+--------|
| Loaf of bread     |  £1.30 |
|-------------------+--------|
| Apples, per kg    |  £2.02 |
|-------------------+--------|
| Pint of milk      |    46p |
|-------------------+--------|
| Sausages, per kg  |  £4.84 |
|-------------------+--------|
| Carrots, per kg   |    91p |
|-------------------+--------|
| Sugar, per kg     |    93p |
|-------------------+--------|
| Dozen eggs        |  £2.78 |
+----------------------------+

3.5. Column minValue and maxValue

The Uize.Templates.Text.Table module will compute min and max values for each column, unless values are specified explicitly for the minValue and maxValue properties in the column description object.

Specifically, if no minValue property is present in the column description object for a column, then a min value will be computed from all the values of the column, and this value will be set on the minValue property. Similarly, if no maxValue property is present in the column description object, then a max value will be computed from the column's values, and this value will be set on the maxValue property.

Because a column formatter is called as an instance method on the column description object when formatting a value for a column, the formatter can access the values of the minValue and maxValue properties and use these values to affect the formatted value. One way that this feature can be used is to add level indicator bars in columns to indicate where the current value falls in the range of values for the column across all rows of the table.

Consider the following example...

EXAMPLE

Uize.Templates.Text.Table.process ({
  title:'2013 Food Prices in the UK',
  columns:[
    {title:'Description'},
    {
      title:'Price',
      align:'right',
      formatter:function (value) {
        return (
          (value < 1 ? (value * 100).toFixed (0) + "p" : "£" + value.toFixed (2)) + ' ' +
          Uize.Templates.Text.ProgressBar.process ({
            progress:(value - this.minValue) / (this.maxValue - this.minValue),
            trackLength:20,
            endsChar:''
          })
        );
      }
    }
  ],
  rows:[
    ['Loaf of bread',1.30],
    ['Apples, per kg',2.02],
    ['Pint of milk',.46],
    ['Sausages, per kg',4.84],
    ['Carrots, per kg',.91],
    ['Sugar, per kg',.93],
    ['Dozen eggs',2.78]
  ]
});

In the above example, the Uize.Templates.Text.ProgressBar template module is being used to add a level indicator alongside the formatted price for the values in the price column. This level indicator provides a quick visual cue as to where a price falls in the range of prices for the foods listed in the table.

Now, the column description object for the price column does not specify values for the minValue and maxValue properties, so valuaes for these properties are computed from the values in the column. The formatter function for the column can then access the computed values using the this keyword, since the formatter is called as an instance method on the column description object.

From the above example, we get the following output...

OUTPUT

+-----------------------------------------------+
|          2013 Food Prices in the UK           |
+-----------------------------------------------+
|   Description    |           Price            |
|------------------+----------------------------|
| Loaf of bread    | £1.30 ▓▓▓▓█░░░░░░░░░░░░░░░ |
|------------------+----------------------------|
| Apples, per kg   | £2.02 ▓▓▓▓▓▓▓█░░░░░░░░░░░░ |
|------------------+----------------------------|
| Pint of milk     |   46p █░░░░░░░░░░░░░░░░░░░ |
|------------------+----------------------------|
| Sausages, per kg | £4.84 ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█ |
|------------------+----------------------------|
| Carrots, per kg  |   91p ▓▓█░░░░░░░░░░░░░░░░░ |
|------------------+----------------------------|
| Sugar, per kg    |   93p ▓▓█░░░░░░░░░░░░░░░░░ |
|------------------+----------------------------|
| Dozen eggs       | £2.78 ▓▓▓▓▓▓▓▓▓▓█░░░░░░░░░ |
+-----------------------------------------------+

4. Column Width

Column width for a column is calculated as the maximum of the column title width and the width of all the column values.

EXAMPLE

Uize.Templates.Text.Table.process ({
  title:'Foo Table',
  columns:[
    {title:'Column 1'},
    {title:'Column 2 (Wide Column Title)'}
  ],
  rows:[
    ['column value','foo'],
    ['wide column value','bar'],
    ['even wider column value','baz']
  ]
});

In the above example, the widest value in the first column is wider than the column's title, so the width of the widest value is used as the column width. In the second column, the column title is wider than the widest column value, so the column title width is used as the column width.

OUTPUT

+--------------------------------------------------------+
|                       Foo Table                        |
+--------------------------------------------------------+
|        Column 1         | Column 2 (Wide Column Title) |
|-------------------------+------------------------------|
| column value            | foo                          |
|-------------------------+------------------------------|
| wide column value       | bar                          |
|-------------------------+------------------------------|
| even wider column value | baz                          |
+--------------------------------------------------------+

4.1. Table Title Can Expand Column Widths

If the table title is wider than the sum of all the calculated column widths and column separators, then the difference will be distributed as padding evenly across the columns.

EXAMPLE

Uize.Templates.Text.Table.process ({
  title:'This is an extremely long title for this table',
  columns:[
    {title:'Col 1'},
    {title:'Col 2'},
    {title:'Col 3'}
  ],
  rows:[
    ['Foo 1','Foo 2','Foo 3'],
    ['Bar 1','Bar 2','Bar 3'],
    ['Baz 1','Baz 2','Baz 3'],
    ['Qux 1','Qux 2','Qux 3']
  ]
});

In the above example, the table's title is wider than the sum of the column widths (as calculated from the column values and column titles) and column separators. The number of characters by which the title is wider is distributed as padding spaces evenly across the three columns.

OUTPUT

+------------------------------------------------+
| This is an extremely long title for this table |
+------------------------------------------------+
|     Col 1     |     Col 2      |     Col 3     |
|---------------+----------------+---------------|
| Foo 1         | Foo 2          | Foo 3         |
|---------------+----------------+---------------|
| Bar 1         | Bar 2          | Bar 3         |
|---------------+----------------+---------------|
| Baz 1         | Baz 2          | Baz 3         |
|---------------+----------------+---------------|
| Qux 1         | Qux 2          | Qux 3         |
+------------------------------------------------+

5. Comprehensively Documented and Tested

The Uize.Templates.Text.Table module is comprehensively documented and has exhaustive unit tests in the Uize.Test.Uize.Templates.Text.Table test module.