(4) Configuration of thresholds

Overview

The fourth chapter is a follow-up of chapter three and describes further steps to configure thresholds for a custom widget.
This chapter is recommended for anyone planning to create a custom widget that uses thresholds.
The basis for chapter four is a custom widget that uses the data assignment as it was explained and created in chapter three.

Widget Preview

The custom widget “Demo widget 4” will display the data of columns and rows just like “Demo widget 3”, but enriched with threshold colors.

Resulting files

Demo widget 4 consists of the following directories and files.
As compared to the structure of "Demo widget 3" the following folders and files were added, removed or changed.

\---customWidgets
     \---demoWidget4 
          |     demoWidget4Config.js 
          |     demoWidget4Module.js 
          | 
          +---assets 
          |     +—css
          |     |        demoWidget3Styles.css
          |     |        widget_demoWidget4.less
          |     | 
          |     \---images 
          |              demoWidget4MenuIcon_32x32.png 
          | 
          +---js 
          |        demoWidget4AssignDataCtrl.js 
          |        demoWidget4Ctrl.js 
          |        demoWidget4DataService.js 
          | 
          \---partials 
               |   demoWidget4.html 
               | 
               \--- assignDataDialog
                     |   advancedProperties.html 

                     |   assignColumns.html 
                     |   assignData.html 
                     |   thresholdProperties.html
                     | 
                     \---  columnTypeConfigTemplates 
                               date.html 
                               numeric.html 
                               text.html 

The created files and source code accompanying this article can be downloaded here.

Recap and next steps

As described in chapter three and implemented in "Demo widget 3" the custom widget provides additional column configuration controls now.
In this chapter, the data assignment view will be extended even more to provide controls for the configuration of thresholds.
The image below shows the data assignment view with the activated threshold properties view.

Creation steps

Step 1: Create the threshold properties view

In order to configure thresholds a new view template needs to be created. 
Create the file thresholdProperties.html in folder assignDataDialog and add the following source code:

thresholdProperties.html
<div ng-show="showThresholdView" class="advanced-properties">
    Threshold Configuration
    <div>
        <div ff-thresholds-config
             thresholds="itemClone.config.advancedWidgetCfg.thresholds"
             item="itemClone"
                >
        </div>
    </div>
</div> 

Explanation: The directive "ff-thresholds-config" used in line #4 provides the controls necessary to configure thresholds.

Step 2: Extend the custom widget controller with threshold properties

Edit the file demoWidget4Ctrl.js and extend it with the following source code.

demoWidget4Ctrl.js
angular.module('demoWidget4Module')

/**
* Controller for the custom widget
* The controller provides the widget variables and manages the interactions between data-model and view
*/

.controller(‘demoWidget4Ctrl’,[‘$scope’,‘formatDateService’,‘formatNumberService’,‘thresholdConstants’,‘thresholdService’,
function($scope, formatDateService, formatNumberService, thresholdConstants, thresholdService){

var thresholds = ;

        <span style="color:#0000FF;">/**
         * The init function creates all necessary scope variables at widget start up time
         */</span>
       <span style="color:#B22222;"> var </span>initWidget = <span style="color:#B22222;">function</span>() {


//Define a property “advancedWidgetCfg” in the widget settings
//to manage the information about the advanced column(s) configuration in the controller

if (!$scope.config.advancedWidgetCfg) {
$scope.config.advancedWidgetCfg = {
thresholds: angular.copy(thresholdConstants.defaultThresholds)
};
}
};

/**
* This scope function returns the threshold color for a matching value
* @param thresholdIndex The row index pointing to the corresponding threshold in the threshold array
* @param row The row data object
* @return the threshold color or undefined if not found
*/

$scope.getRowColor = function(thresholdIndex, row){
if(thresholds[thresholdIndex]) {
var thresholdColumn = $scope.config.assignedColumns.numDataColumn;
if (thresholdColumn != undefined) {
var thresholdColumnIdx = -1;
for (var i = 0; i < $scope.columns.length; i++) {
if ($scope.columns[i].name == thresholdColumn.newName) {
thresholdColumnIdx = $scope.columns[i].idx;
break;
}
}
var thresholdColumnCellValue = row.values[thresholdColumnIdx];
for (var i = 0; i < thresholds[thresholdIndex].length; i++) {
if (thresholds[thresholdIndex][i].containsValue(thresholdColumnCellValue)) {
return thresholds[thresholdIndex][i].colour;
}
}
}
}
return undefined; //‘transparent’;
};

        <span style="color:#0000FF;">/**
         * This function collects the threshold colors for all rows
         * @param data The data for the assigned and server-requested data source columns (feed result)
         */</span>
       <span style="color:#B22222;"> var</span> createThresholds = function(data) {
            thresholds = [];
            <span style="color:#B22222;">for</span>(<span style="color:#B22222;">var </span>i = 0; i &lt; data.rows.length; i++){
               <span style="color:#B22222;"> var</span> row = data.rows[i];
                <span style="color:#B22222;">if</span>($scope.config.advancedWidgetCfg.thresholds &amp;&amp; data) {
                    <span style="color:#B22222;">var </span>thresholdAreas = thresholdService.getThresholdAreas($scope.config.advancedWidgetCfg.thresholds, data, row);
                    thresholds.push(thresholdAreas);
                }
            }
        };

      <span style="color:#0000FF;">  /**
         * This function is called from the custom widget framework when the requested data from the server returns
         * @param data The data for the assigned and server-requested data source columns (feed result)
         */</span>
        $scope.setData = <span style="color:#B22222;">function</span>(feedResult){


//thresholds will be calculated for all data updates
createThresholds(feedResult);
};


initWidget();
}
]);

Explanation: The AngularJS services "thresholdConstants" and "thresholdService" were added to the controller (see line #7). These services provide constants and functionalities to determine the threshold colors. In line #17 and following the property "advancedWidgetCfg" was defined to manage the threshold settings in the controller. The new function "createThresholds()" is called for all data updates, calculates the different threshold areas for the given values and stored them in the array "thresholds[]". The new function getRowColor() is a scope function that is used in the custom widget view template to determine the threshold color for a given row. 
Hint: Please also read the documentation comments in the code example.

TODO Filter Threshold columns

Step 3: Register threshold properties view in custom widget configuration

The created threshold properties view template must be registered in the custom widget configuration file.
Therefore please edit the file demoWidget4Config.js and add the following line of code.

demoWidget4Config.js
angular.module('demoWidget4Module')
<span style="color:#0000FF;">/**
 * Configuration for the custom widget
 * The config contains all configurable properties of the widget
 */</span>
.config([<span style="color:#008000;">'dashboardProviderProvider'</span>,<span style="color:#008000;">'columnTypeConfigUrlServiceProvider'</span>,
   <span style="color:#B22222;"> function</span> (dashboardProviderProvider, columnTypeConfigUrlServiceProvider) {
        dashboardProviderProvider.widget(<span style="color:#008000;">'demoWidget4'</span>, {
            title: <span style="color:#008000;">'Demo Widget 4'</span>,


thresholdProperties: ‘widgets/customWidgets/demoWidget4/partials/assignDataDialog/thresholdProperties.html’
});

}
]);

Explanation: Line #12 registers the threshold properties view template in the widget configuration.

Step 4: Extend assign data dialog view with the threshold properties view

After registering the threshold properties view template it can now be used in the assign data dialog.
Therefore please edit the file assignData.html and add the following line of code.

assignData.html
div class="assign-data">
    <div class="inline-edit">
        <div class="inline-edit" ng-controller="demoWidget4AssignDataCtrl">
...
            <div class="details-panel" ng-include="widget.thresholdProperties"></div>
        </div>
    </div>
</div>

Explanation: Line #5 adds the threshold properties view template to the assign data dialog.

Step 5: Extend assign data dialog controller to manage the visibility of the threshold properties view

The threshold properties view is to be visible for numeric columns only. Therefore it can be set to invisible for other column selections.
In order to manage the visibility of the threshold properties view the assign data dialog controller must be extended as follows.
Please edit the file demoWidget4AssignDataCtrl.js and adapt the source code as shown in the example below.

demoWidget4AssignDataCtrl.js
angular.module('demoWidget4Module')
<span style="color:#0000FF;">/**
 * Controller for the Assign data dialog
 */</span>
.controller(<span style="color:#008000;">'demoWidget4AssignDataCtrl'</span>,[<span style="color:#008000;">'$scope'</span>,<span style="color:#008000;">'aggregationConstants'</span>,<span style="color:#008000;">'typeconstants'</span>,<span style="color:#008000;">'numberFormatConstants'</span>,
   <span style="color:#B22222;"> function</span>($scope, aggregationConstants, typeconstants, numberFormatConstants){

       <span style="color:#0000FF;"> /**
         * This function changes the selection state of a column
         * A column selection triggers the loading of the matching configuration view template
         * Initially the threshold control is set to be hidden
         * @param column The column that is to be selected
         */</span>
        $scope.columnSelectionHandler = <span style="color:#B22222;">function</span>(column) {
            $scope.showThresholdView = <span style="color:#B22222;">false</span>;
            $scope.selectedColumn = column;
        };

       <span style="color:#0000FF;"> /**
         * This function is called to enable the threshold properties view
         */</span>
        $scope.thresholdSelectionHandler = <span style="color:#B22222;">function</span>() {
            $scope.showThresholdView = <span style="color:#B22222;">true</span>;
        };

       <span style="color:#0000FF;"> /**
         * Indicates if a column should be marked as selected
         * @param column The column to check
         * @returns {boolean} true if the threshold view is not visible and the given column matches the selected one, otherwise false
         */</span>
        $scope.selectColumn =<span style="color:#B22222;"> function</span>(column) {
            <span style="color:#0000FF;">//return $scope.selectedColumn === column;</span>
           <span style="color:#B22222;"> return</span> $scope.showThresholdView ? <span style="color:#B22222;">false</span> : $scope.selectedColumn === column;
        };

        <span style="color:#0000FF;">//There must be one selected column, initially select the first column found</span>
        $scope.selectFirstColumnFound();
    }
]);</code></pre>

Explanation: A new scope variable "showThresholdView" was added to the controller. This variable controls the visibility of the threshold properties view. Please also have a look at the documentation comments in the code example describing the new and changed functions of the controller.

Step 6: Adapt the advanced properties view to manage the visibility of the threshold properties view

Whenever the threshold properties view is to be shown, the advanced properties view must be hidden. Therefore the advances properties view must be adapted to react on the value of the scope variable "showThresholdView".
Please edit the file advancedProperties.html and change the first line like in the example below.

advancedProperties.html
<div ng-if="!showThresholdView && selectedColumn" class="advanced-properties">
   ...
</div>

Explanation: In line #1 the condition of the ng-if directive will be extended. As a result the advanced properties view will be hidden if the threshold properties view is set to be visible.

Step 7: Adapt custom widget data service to handle threshold columns

demoWidget4DataService.js
angular.module('demoWidget4Module')
    /**
     * Data service for the custom widget
     * The data service defines the data mapping - the structure of the data delivered by the server
     */
    .service('demoWidget4DataService',['widgetDataService','formatNumberService','typeconstants',
        function(widgetDataService,formatNumberService,typeconstants) {
            /**
             * Utility function widgetDataService.createDataMappingColumn(...) creates one data mapping column
             * @param name The column name in the source, use dataColumn.name
             * @param type The data type of the column ("TEXT", "DATE", "NUMERIC")
             * @param filter Not interesting for this use case, set to '' or undefined
             * @param copyFrom If the user wants to have a source column twice, e.g. to see different aggregations for a numeric column, here he has to define the original column name
             * @param newName If the user has changed the column name please provide dataColumn.newName
             * @param round For numeric columns the user can define if values should be rounded or not. Set to true or false
             * @param precision Specifies precision for numeric columns. There is a utility function in formatNumberService that is called getPrecisionFromFormat
             * that can be used to calculate the precision from a format pattern. Set to undefined for text or date columns.
             * @param keepInResult Not interesting for this use case, set to true
             * @param aggregation Aggregation, if not specified the aggregation in the datamapping column is undefined
             * @returns A datamapping column object
             */
            var createDataMappingColumn =
                function(dataColumn){
...
                };
       <span style="color:#B22222;"> var </span>createDataMappingColumnForThresholdColumnDrops = <span style="color:#B22222;">function</span>(thresholdValue){
           <span style="color:#B22222;"> if</span>(!thresholdValue || !thresholdValue.column){
                <span style="color:#B22222;">return</span> undefined;
            }
           <span style="color:#B22222;"> var </span>thAggregation = {type:thresholdValue.column.aggregation.type};
          <span style="color:#B22222;">  return</span> widgetDataService.
                createDataMappingColumn(
                    thresholdValue.column.newName,
                    thresholdValue.column.type,
                    undefined,
                    thresholdValue.column.name,
                    '',
                    <span style="color:#B22222;">false</span>,
                    -1,
                    <span style="color:#B22222;">true</span>,
                    thAggregation);
        };
       <span style="color:#B22222;"> return </span>{

           <span style="color:#0000FF;"> /**
              * This function is called by the custom widget framework.
              * It prepares the data handling of the custom widget (item) with the server
              * by creating the data mapping for each configured (assigned) data column
              */</span>
            getDataMapping : <span style="color:#B22222;">function</span>(item) {


//Add columns used for threshold configuration
if(item.config && item.config.advancedWidgetCfg && item.config.advancedWidgetCfg.thresholds){
for(var i = 0; i < item.config.advancedWidgetCfg.thresholds.length; i++){
var threshold = item.config.advancedWidgetCfg.thresholds[i];
var thColumn = createDataMappingColumnForThresholdColumnDrops(threshold.value);
if(thColumn){
dataMapping.columns.push(thColumn);
}
var thColumn2 = createDataMappingColumnForThresholdColumnDrops(threshold.value2);
if(thColumn2){
dataMapping.columns.push(thColumn2);
}
}
}
return dataMapping;
}
};
}
]);

Step 8: Adapt the CSS styling

Up until now a pure css file was used for the styling of the view templates, but as the view and the styling get more complex it makes sense to use a more advanced styling technique, like "Less". 
Less is a CSS pre-processor and extends the CSS language. The usage of Less has many advantages, a few of them will be used for the custom widget styling.
Create the file widget_demoWidget4.less in the folder CSS and add the source code below. The CSS folder should only contain this file.

widget_demoWidget4.less
.panel-dashboard-default[widget-type='demoWidget4'] {
.panel-body{
    <span style="color:#B22222;">background-color</span>: lighten(@brand-primary, <span style="color:#DAA520;">55%</span>);
   <span style="color:#B22222;"> color:</span> @default-text-color;

    .title {
        <span style="color:#B22222;">font-weight</span>: <span style="color:#DAA520;">bold</span>;
    }
}

&amp;.hideBorder .panel-body {
    <span style="color:#B22222;">background-color</span>: <span style="color:#DAA520;">transparent</span>;
}

}

.demoWidget4 {
width: 100%;
height: 100%;
overflow: auto;
padding: 10px;

h<span style="color:#DAA520;">3</span>{

text-decoration: underline;
text-align: center;
}

table {

  <span style="color:#B22222;">  width:</span> <span style="color:#DAA520;">100%</span>;

    th {
        <span style="color:#B22222;">border:</span><span style="color:#DAA520;"> 1px solid grey;</span>
      <span style="color:#B22222;">  padding:</span> <span style="color:#DAA520;">5px;</span>
    }

    td {
       <span style="color:#B22222;"> border:</span> <span style="color:#DAA520;">1px solid grey;</span>
        <span style="color:#B22222;">padding:</span> <span style="color:#DAA520;">5px;</span>
    }

    tr:nth-child(odd) {
       <span style="color:#B22222;"> background-color:</span> <span style="color:#DAA520;">#f1f1f1</span>;
    }

    tr:nth-child(even) {
        <span style="color:#B22222;">background-color</span>: <span style="color:#DAA520;">#ffffff</span>;
    }
}

}

Hints:
After a new less file was created the server needs to be restarted in order to compile LESS into CSS.

Summary and sources

As the final result of chapter four, the custom widget provides additional configuration controls for thresholds. The configured threshold colors will be used to enrich the custom widget view.

The source code for "Demo Widget 4" can be downloaded here.

Read in this series: