Learn how to leverage REST data providers and sources to build a custom view in Kendo UI Builder.
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:
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.
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.
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.
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:
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:
const dataProviderFileName = `./meta/dataProviders/${metaModel.dataProvider}.json`;
The data provider name is available in the input metadata model variable (metaModel)
// 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.
There are two main items we want to render in our table:
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.
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.
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>
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!
If you jumped into this series in the middle and want to start from the beginning, you can find the previous blog posts here:
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.
Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.
Learn MoreSubscribe to get all the news, info and tutorials you need to build better business apps and sites