How to Build Custom Views from Your Data with Kendo UI Builder

July 27, 2018 Application Development, Data & AI

Learn how to leverage REST data providers and sources to build a custom view in Kendo UI Builder.

Please Note: Progress Kendo UI Builder is no longer available. To learn more about options for your OpenEdge application, read Application Evolution: Web UI Strategy for Modern Progress OpenEdge Applications.

When you're building an app, most views will need to display data from backend systems. Kendo UI Builder provides access to such data via Data Providers and their associated data sources. Currently, the product has built-in support for REST, OData and JSDO.

In this blog, we will learn how to create a custom view that renders data from a REST service. We will see how we can interface to data sources to get at the data as well as at the metadata (fields definition). 

To keep things simple, we will create a custom view that renders an HTML table with the list of fields as headers and the data in additional table rows as in the following example:

Download from GitHub and Follow Along

To follow along, download the template samples zip from github or clone the repository. Then open the folder "custom-table-from-data-source" and follow the same steps originally outlined in this post to install this template in your target application.

Setup

The custom view can render data from any configured REST data source, but for the sake of illustration in this blog we will render a list of metrics from IoT devices. The device metrics are provided by the same test REST server we setup in a blog post from earlier in this series.

If you have not set them up yet, please refer to the detailed instructions in the prerequisites section of the same post.

Custom View Definition

From the Kendo UI Builder view property panel, our custom view users should be able to select the data provider and the data source to use.

We add the following definitions to the definition file (See custom-table-from-data-source.json).

If you've been following along in our Kendo UI Builder series, most properties should be familiar to you by now, but if not you may want to head back and read the post where I first introduced custom views

The key thing here is the editorType for each property. 

"dataProvider": {
  "type": "string",
  "editorType": "dataProvider",
  "title": "Data Provider",
  "description": "The name of the data provider from which we get datasource definition",
  "default": "",
  "minLength": 1,
  "order": 2
},
"dataSource": {
  "type": "string",
  "editorType": "dataSource",
  "title": "Data Source",
  "description": "The name of the data source to from which we get data",
  "default": "",
  "minLength": 1,
  "order": 3
}

Now at design-time, we will be able to select where the data is coming from with a few clicks in the property panel.

Design-Time View

We want to give our users a feel for how the table will render. 

To achieve this, we need to take care of two main items:

  1. Output the table headers with the field names based on the selected data source. And if no data providers or data sources are selected, output fake headers.
  2. Output some data. We do not make a REST call at design-time, so we will simply generate some sample data.

As we learned in the earlier post on augmenting models, we need to implement the function augmentModel in index.js to provide these two items.

To access the list of fields: the fields are available in a json file in the app sub-directory: “/meta/dataProviders/”. The file name is based on the data provider name as defined by the user, in my sample, I named it IoT, thus the file relative file path is /meta/dataProviders/IoT.json.

So, the goal is to read the list of fields from the metadata directly from the file system. This entails creating the path to the file, reading the file, parsing it as JSON and accessing from the JSON data, the array of fields for the specific data source. 

There are two key things to keep in mind to achieve this:

  1. at line 51 of index.js:
    const dataProviderFileName = `./meta/dataProviders/${metaModel.dataProvider}.json`;

    The data provider name is available in the input metadata model variable (metaModel)

  2. at line 63 of index.js
    // and accessing from the JSON data,the array of fields for the specific data source
    const dataSource = dataProvider.children.find(dataSource => dataSource.name === metaModel.dataSource);

The data source name is also available in the metamodel input variable. For more details on it, in design/index.js, check the function getFieldsFromDataSource.

Finally, to generate some data, we simply create an array of 10 object literals. Object properties are populated by looping data source fields and adding some fake values. We generate different data for number fields versus all other fields. In design/index.js, you'll find the function createSampleDataForEachField

We also take care of rendering the custom view with dummy fields and data when no data provider or data source are selected. In design/index.js, see the function createSampleFieldsAndDataWhenNoDataSource for this.

All in all, implementing this was not so straightforward: it helped me to have some debug data. So, along the way, I stored various data points in metaModel variable and output them in the designer view.

Run-Time

There are two main items we want to render in our table:

  1. Getting the field names for table header
  2. Getting at the data to render in table rows by making a REST call

To get the field names, we use same strategy as in design time view. We read the names from the metadata JSON file and store them as an array in the metaModel. We've already learned this technique from the previous blog post on Augmenting Models (see the section "Run-Time Model").

This is done in the views/custom-table-from-data-source/angular/generator/index.js, augmentModel function like so:

const pathToDataProviderFile = path.join(__dirname,'..','..','..','..','..',`./meta/dataProviders/${metaModel.dataProvider}.json`);
const contents = fs.readFileSync(pathToDataProviderFile);
const dataProvider = JSON.parse(contents);
const dataSource = dataProvider.children.find(dataSource => dataSource.name === metaModel.dataSource);
metaModel.myDataSrcFields = dataSource['fields'];

We store the list of fields in myDataSrcFields. We will use this variable when we need to retrieve the field names when rendering.

Access to the Data

See views/custom-table-from-data-source/angular/__name__.view.base.component.ts.ejs

I have provided a set of utilities in the form of templated typescript code. Check out the folder views/custom-table-from-data-source/angular/data-services. I won’t go into the details of each file - treat them as a black box to help you when you need it.

However, it’s worth pointing out that in views/custom-table-from-data-source/angular/data-services/data-properties-initialization.ts.ejs, we have the definition for the $dataServices object literal. This object contains a data service for each data source. This will be used in ngOnInit (__name__.view.base.component.ts.ejs). Here's the relevant code extract:

const dataSourceName = '<%- view.dataSource %>';
const dataService = this.$dataServices[dataSourceName];
 
const results: Observable<any> = dataService.dataChanges();
    results.map(response => {
        debugger;
        return response ? response.data : [];
    }).subscribe(data => this.myRestList = data);

We get the view data source and access the view data service.

We call the dataChanges function on the data service. The dataChanges function returns an RxJs observable. Check out this overview to learn more if you aren't familiar with those.

When the RxJs stream has received its data, we store it in the member variable myRestList ready for rendering in the view component.

Rendering Data

Now this is comparatively the easier part 😊.

See views/custom-table-from-data-source/angular/__name__.view.component.html.ejs

We render the headers by iterating through the variable myDataSrcFields and outputing a table cell for each field like this: 

<tr style="background-color: #3fd5d5; font-weight: bold; ">
    <% view.myDataSrcFields.forEach(field => { %>
       <td style="padding-top: 0.5rem; padding-bottom: 0.5rem;">
            <!-- <%- field.name %> -->
            <span style="margin-left: 0.8rem; margin-right: 0.8rem;">
                    <%- field.label %>
             </span>
        </td>
  <% })%>
</tr>

To render the data: For all items received from the server, we output a table row.  Within the row, we simply output the value for each of the REST field. Notice that we loop through the data from REST server at run-time but we loop through at code generation time. Here is the code extract:

<tr *ngFor="let item of myRestList" style="background-color: #ddd;">
<% view.myDataSrcFields.forEach(field => { %>
    <td align="right">
      <span style="margin-right: 0.8rem;">
         {{item['<%- field.name %>']}}
      </span>
    </td>
<% })%>
</tr>

Conclusion

We have learned how to interface with data providers and data sources so that we can render a sample table at design-time, leverage the data services to access the data from a REST server and finally render that data at run-time.

We will use the techniques we learned here to build a card UI in a future post in this series - stay tuned!

Catch Up on Kendo UI Builder

If you jumped into this series in the middle and want to start from the beginning, you can find the previous blog posts here:

  1. Introduction to Kendo UI Builder Templates
  2. Behind the Scenes of Kendo UI Builder Templates
  3. Event Processing in Kendo UI Builder Templates
  4. Augmenting Models in Kendo UI Builder Templates
  5. Introduction to Custom Components for Kendo UI Builder
  6. Creating Kendo UI Builder Custom Components from Angular Components
  7. Creating a Custom Rating Component with Kendo UI Builder
  8. Displaying Metrics from IoT Devices with Kendo UI Builder
  9. Single Value vs. Multi-Value Kendo UI Builder Custom Components

Thierry Ciot

Thierry Ciot is a Software Architect on the Corticon Business Rule Management System. Ciot has gained broad experience in the development of products ranging from development tools to production monitoring systems. He is now focusing on bringing Business Rule Management to Javascript and in particular to the serverless world where Corticon will shine. He holds two patents in the memory management space.

Read next Progress DataDirect Now Connects to Denodo