...
Table of Contents minLevel 3 outline true style none
Background
Greenhouse Monitoring System will be taken as an example to illustrate creating User Interface applications using TQL Queries.
TQL queries and subscriptions consists of dynamic (user defined payloads) payload sent over HTTP or WEBSOCKET transports. Consuming TQL Queries in UIs (HTML/JS, Android, IOS and others) in this regards are similar to consuming any RESTful services.
In the tutorial we answer some of the following questions:
- What is the role TQL queries play in an UI app
- What are the 3-5 technical things I should know about using TQL Queries in my UI app?
- Where and how in my UI app should I use TQL? What is the relationship of TQL queries with my UI app components.
- How TQL queries fit into the development lifecycle of my UI app development?
- Can I write more interesting apps with TQL queries (which I cannot do using conventional APIs or using conventional SQL queries?)
- What are the design considerations of using TQL queries to make my app more <dynamic, scalable, extensible, future proof,..>
- What are the common errors/exceptions and how should I diagnose?
Let's address some of these questions when building UI Apps using TQL Queries using a template HTML/JS App. You can download this app here
Role of TQL Queries in UI App
TQL Queries Vs REST Services
REST is an architecture style for designing networked applications and in virtually all cases, the HTTP protocol is used. In many ways, the World Wide Web itself, based on HTTP, can be viewed as a REST-based architecture. By this definition of REST coupled with the fact that TQL Queries are executed over an HTTP endpoint that is automatically generated when models are deployed to TQLEngine, make TQL Queries Restful in nature.
...
TQL Queries | Restful Services |
---|---|
XML Oriented (Request & Response) (Note that TQLEngine supports both JSON & XML; the recommended guideline is XML for TQL Queries readability) | Restful Services are JSON oriented |
TQL Queries are sent as payload, therefore the HTTP Method to be used always POST. | Method can be: GET/PUT/POST/DELETE |
UI Technologies
Most commonly used UI technologies are:
- HTML,CSS and JavaScript
- JavaScript Frameworks (Angular JS, Backbone JS, etc.)
- HTML5, CSS/CSS3 and JavaScript/jQuery
Design Patterns
Model-View-Controller (MVC) is popular design pattern to develop HTML/JS app that consumes TQL Queries.
Component Name | Technology | In this tutorial |
---|---|---|
View | HTML, CSS | index.html and style.css |
Model | JS - Services & Factories independent of underlying framework (OR) Angular JS provided Model | Angular Model Service (services/queryService.js) |
Controller | JS - Can be independent of underlying framework (Write your own using JS) or Angular provided model | Angular Controller and bind with HTML using Angular directive ng-controller. (controllers/mainController.js) |
Configuration | Static Configuration File | config/url_config.js |
Implementation Steps
All the implementation steps can be tried out using any of the HTML/JS IDE of your choice.
Let's start out by creating a folder in y
Creating View
a) Main View - Single Page HTML file
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<!DOCTYPE html>
<html ng-app="GreenhouseUIApp">
<head>
<title>Smart Greenhouse</title>
<script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
<link
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"
rel="stylesheet">
<link href="style.css" rel="stylesheet">
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/x2js/1.2.0/xml2json.min.js"></script>
<script type="text/javascript" src="config/url_config.js"></script>
<script type="text/javascript" src="controllers/mainController.js"></script>
<script type="text/javascript" src="services/queryService.js"></script>
</head>
<body>
<div ng-controller="mainController">
<div class="row text-center header">
<h2>SMART GREENHOUSE</h2>
</div>
<div class="row text-center greenhouseDetails">
<h4>{{selectedGreenhouse.GreenhouseName}} |
Zones: {{selectedGreenhouse.ZoneCount}} |
Location: ({{selectedGreenhouse.Location.latitude}},
{{selectedGreenhouse.Location.longitude}})</h4>
</div>
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-3 statusBlock">
<div>
<h3>Internal Status</h3>
</div>
<div>Temperature: {{selectedGreenhouse.InternalEnv.Temperature}}
°C</div>
<div>Humidity: {{selectedGreenhouse.InternalEnv.Humidity}} %</div>
<div>Amb. Light: {{selectedGreenhouse.InternalEnv.Light}} lux</div>
<div>Soil Moisture:
{{selectedGreenhouse.InternalEnv.SoilMoisture}} cb</div>
</div>
<div class="col-md-1"></div>
<div class="col-md-4 statusBlock">
<div>
<h3>External Conditions</h3>
</div>
<div>Temperature: {{selectedGreenhouse.ExternalEnv.Temperature}}
°C</div>
<div>Humidity: {{selectedGreenhouse.ExternalEnv.Humidity}} %</div>
<div>Sunlight: {{selectedGreenhouse.ExternalEnv.Light}} lux</div>
<div>Soil Moisture:
{{selectedGreenhouse.ExternalEnv.SoilMoisture}} cb</div>
<div>Wind: {{selectedGreenhouse.ExternalEnv.Wind}} mph</div>
</div>
<div class="col-md-3"></div>
</div>
</div>
</body>
</html> |
b) Creating Style Sheet
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
body{
background: url(images/greenhouse.jpg);
background-size: cover;
padding: 0; margin: 0;
}
.header{
background: #000; color: #fff; margin-bottom:
20px;
}
.greenhouseDetails{
background: #39404a;
color: #fff;
}
.statusBlock{
background: #39404a;
color: #fff;
padding: 30px; margin: 30px 0;
} |
Creating Model
...
- Discover App URL
- Find Greenhouse
- Find Lane
- Find Zone
...
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
angular.module('GreenhouseUIApp').service('QueryService', function ($http){
return{
discoverGreenhouseEndpoint : discoverGreenhouseEndpoint,
getGreenhouses : getGreenhouses,
getLanesForGreenhouse : getLanesForGreenhouse,
getZonesForLane : getZonesForLane
}
function discoverGreenhouseEndpoint(){
// Find the project handle by the name of the project
var findProjectQuery = "<query><find><project><projName eq='Greenhouse'/></project></find></query>";
return $http.post(TQLEngineManagerURL, findProjectQuery).then(function(response) {
var x2js = new X2JS();
var jsonObj = x2js.xml_str2json(response.data);
// Find project end points - http and ws
var findProjectEndpointsQuery = "<GetProjectEndPoints><ProjectSysId>"+jsonObj.Find.Result.Project.SysId+"</ProjectSysId></GetProjectEndPoints>";
return $http.post(TQLEngineManagerURL, findProjectEndpointsQuery).then(function(response) {
var x2js = new X2JS();
var endpointsJson = x2js.xml_str2json(response.data);
return endpointsJson;
});
});
}
// Given endpoint URL, find the project end points for communication
function getGreenhouses(endpointURL){
var query = "<Query><Find><Greenhouse><GreenhouseID ne=''/></Greenhouse></Find></Query>";
return $http.post(endpointURL, query).then(function(response) {
var x2js = new X2JS();
var jsonObj = x2js.xml_str2json(response.data);
return jsonObj;
});
}
// Given greenhouse ID, get the array of lanes
function getLanesForGreenhouse(GreenhouseID){
var query = "<Query><Find><Lane><GreenhouseID eq="+ GreenhouseID +"/></Lane></Find></Query>";
return $http.post(url, query).then(function(response) {
var x2js = new X2JS();
var jsonObj = x2js.xml_str2json(response.data);
return jsonObj;
});
}
// Given the Lane ID, get the array of zones
function getZonesForLane(LaneID){
var query = "<Query><Find><Zone><LaneID eq="+LaneID+" /></Zone></Find></Query>";
return $http.post(url, query).then(function(response) {
var x2js = new X2JS();
var jsonObj = x2js.xml_str2json(response.data);
return jsonObj;
});
}
}); |
Identify Queries
Before we start wrapping TQL Queries into a Service model, we need to identify the list of queries to be executed.
...
Discovering TQL EndPoints URL:
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<Query>
<Find>
<project>
<projName eq='Greenhouse'/>
</project>
</Find>
</Query>
<GetProjectEndPoints>
<ProjectSysId>
Find.Result.Project.SysId
</ProjectSysId>
</GetProjectEndPoints> |
Things you should be aware of?
As with building any UI app that interacts with backend serivces, you would always need:
- Endpoint (HTTP or WS) to interact with backend
- Request and Response Format
- Ability to try out sample requests before integrating them into UI application
Design Consideration
...
Never hardcode the TQL Query endpoint urls as they are subject to change.
Instead the endpoints must be auto discovered given the project name.
The model developer (owner) will project provide you appropriate project name to discover your query endpoints.
- You can test the queries and make sure they are working via Query Editor (TQLStudio or Local TQLEngine)
Gather App related queries: In this example we will wrap only one single query to get Greenhouse information.
Code Block language xml theme Midnight title App Queries - Greenhouse Monitoring App linenumbers true <Query> <Find> <Greenhouse> <GreenhouseID ne=''/> </Greenhouse> </Find> </Query>
Creating Controller
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
angular.module('GreenhouseUIApp', [])
.controller('mainController', function($scope, QueryService) {
$scope.greenhouses = [];
$scope.httpEndpoint = "";
$scope.wsEndpoint = "";
$scope.getGreenhouses = function(){
QueryService.getGreenhouses($scope.httpEndpoint).then(function(response){
if(angular.isArray(response.Find.Result.Greenhouse)){
$scope.greenhouses = response.Find.Result.Greenhouse;
} else if(angular.isObject(response.Find.Result.Greenhouse)){
$scope.greenhouses.push(response.Find.Result.Greenhouse);
}
$scope.selectedGreenhouse = $scope.greenhouses[0];
console.log($scope.greenhouses);
});
};
$scope.replaceIPWithHostName = function(){
$scope.wsEndpoint = $scope.wsEndpoint.replace( /:.*?\:/, '://'+greenhouseHostName+':' );
$scope.httpEndpoint = $scope.httpEndpoint.replace( /:.*?\:/, '://'+greenhouseHostName+':' );
};
QueryService.discoverGreenhouseEndpoint().then(function(data){
angular.forEach(data.Find.projectEndpointMap.DataMap, function(obj){
if(obj.Value.indexOf("ws:") > -1){
$scope.wsEndpoint = obj.Value;
} else if(obj.Value.indexOf("http:") > -1 || obj.Value.indexOf("https:") > -1){
$scope.httpEndpoint = obj.Value;
}
});
$scope.replaceIPWithHostName();
$scope.getGreenhouses();
});
} |
Deploy & Test
It is recommended to serve your applications (html,css,js) files directly from the running TQLEngine.
Download & Install TQLEngine
Please refer to download section to download and install TQLEngine.
Attach applications to the project
Now you can point your browser to URL and load the UI
- Notification frequency and WEBSOCKET reconnect logic.
- Externalize the Queries as much as possible to the App. (Via some config file like: querys.cfg.xml)
- You can serve your UI app (in case of say, HTML/JS) via TQLEngine itself using TQLEngine's ThingSpace / Attach Application feature