perl dancer with angularJS HighChart Pie
Article published
Services and AngularJS components.
As we are pushing into AngularJS MVC, we may need to think of services at the backend. Services can be distributed with vmWare cluster service nodes.
Following query was hoisted as a RESTful service to pull the data from backend.
select distinct(theater), count(*) from asap_company_data
where ANS_LOB<>'Service Provider'
and ( Theater in ('APAC', 'EMEA', 'Greater China') OR Theater like 'US _ Canada')
group by Theater order by Theater;
· AngularJS consumes this service to display a Pie ( Figure 1.)
Possible advantages of services.
· Stateless
· Provide business components(API and dashboard levels)for users
· Easy to deploy and pushes us to think of components.
o Possible load balancing and distribution of services across different nodes of vmWare cloud (reverse proxy and caching )
o Use Oracle’s message bus to publish database updates to the local cache (Perl cached or memcached nodes)
Figure 1. Theater Pie with Angular
Dances with Perl:
The cpan Perl component “Dance” was used to host a REST service.
- Faster to prototype and develop with a framework such as Dance.
- Helps us to reuse current code in components such as :
· DAO (data access Perl objects having sql query and cache objects)
· Business Logic (reuse current perl code as separate Perl classes to call DAO ), to be used inside REST services.
AngularJS Directives :
Apart from Angular’s power of Model View Controller architecture, it provides directives that helps:
- code reuse by delegating work to other interfaces/components.
- ‘isolation’ of components. They can help isolate (loose couple) implementation details facilitating easier componentization and code management.
For e.g, the directive used to display above pie chart can interface with Highchart framework without developer knowing what UI framework he/she is using. Only one file has to be added. The rest of the code will talk with directive interface to framework of our choice such as KendoUI or Highchart or Google chart.
The above is NOT a good example. Directives can provide simple interface hiding complex factory and controller logic.
They work as adapter/facade design pattern.
Listing 1. directive in yellow highlight
<body ng-controller="pieDataController">
<h1>pieData Request: </h1>
….
<div id="debug">
<h3>Actual data:</h3>
<ul>
<li ng-repeat="point in pieData">{{ point }} </li>
<ul>
</div>
<!-- directive -->
<draw-pie-chart pie-data="pieData" pie-title="{{pietitle}}" series-name="{{seriesname}}" />
</body>
Listing 2. Simple Factory for PieChart: (using webApi/Rest at backend )
.factory('dataFactory', ['$http', function($http){ ç http injected here
thisFactory.getMockupData = function(){
printToConsole(theaterData);
return theaterData;
};
thisFactory.getDataToPlot = function(callback){ ç controller gets data through callback
$http.jsonp(url)
.success(callback)
.error(function(data,status,headers,config){
dataToPlot = "Request Failed" + status ;
});
return dataToPlot;
}; return thisFactory;
Listing 3. Controller to drive the Factory above.
controller('pieDataController', function($scope, $http, dataFactory) {
$scope.pieData = {};
init();
var callback = function(data, status,headers,config ){
$scope.pieData = data;
};
function init() {
$scope.pieData = dataFactory.getMockupData();
};
$scope.getChartData = function(){
$scope.pieData = dataFactory.getDataToPlot(callback); ç callback passed to Factory
};
$scope.getTypicalData = function() {
$scope.pieData = dataFactory.getMockupData();
};
})
================ ACTUAL CODE segments ===================
1. write the perl stub to deliver json SOAP service
/dashboard/geopie
#!/usr/bin/perl
use CGI;
use DBI;
use DBD::Oracle qw(:ora_types);
use Data::Dumper;
use Dancer;
use Dancer::Response;
use lib "/var/www/cgi-bin/MY_LIB/";
use tk_database_conf;
# use JSON;
get '/dashboard/geopie' => sub {
content_type 'application/json' ; # no use as we create new response
my $response = Dancer::Response->new(
status => 200,
content => 'my content',
content_type => 'application/json', # no use
headers => ['Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Methods' => 'GET'],
);
$response->content_type('application/json');
my $callbackFn = param('callback');
my $itemsString = &getFromDatabase ;
if ($callbackFn) { # for JSONP we wrap all of this into a function
$response->content($callbackFn . '(' . $itemsString . ')' );
return $response;
}else{
return $itemsString;
};
};
sub getFromDatabase {
my $db = new database_conf;
my $EP_prd_conn = $db->db_eportal_prod;
my $sqlStmt = qq|select distinct(tk_theater), count(*) from asap_company_data where ANS_LOB<>'Service Provider' and ( Theater in ('APAC', 'EMEA', 'Greater China') OR Theater like 'US _ Canada') group by Theater order by Theater|;
my $sth = $EP_prd_conn->prepare($sqlStmt)|| $EP_prd_conn->errstr;
$sth->execute() or $sth->errstr ;
my $jsonStr = "[";
my $iter=0;
while(my @item = $sth->fetchrow_array()) {
if($iter>0){
$jsonStr .= ", ";
};
$jsonStr .= qq|{"$item[0]": $item[1]}| ;
$iter++;
};
$jsonStr .= "]";
return $jsonStr;
}
get '/appname' => sub {
return "This is " . config->{appname};
};
get '/github/repos/angular/angular.js/issues' => sub {
return "This is " . config->{appname};
};
start;
# dance;
2. Get the Angular going.
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title>Hi Pie Test </title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link href="simplepie.css" rel="stylesheet" />
<script data-require="angular.js@1.2.x" src="http://code.angularjs.org/1.2.7/angular.js" data-semver="1.2.7"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script src="http://code.highcharts.com/highcharts.js"></script>
<script src="tkModule.js"></script>
<script>
var myapp = angular.module('myApp',['tkDirectives'])
.controller('pieDataController', function($scope, $http, dataFactory) {
$scope.pietitle = "Theater";
$scope.pieData = {};
init();
var callback = function(data, status,headers,config ){
$scope.pieData = data;
};
function init() {
$scope.pieData = dataFactory.getMockupData();
};
$scope.getChartData = function(){
$scope.pieData = dataFactory.getDataToPlot(callback);
};
$scope.getTypicalData = function() {
$scope.pieData = dataFactory.getMockupData();
};
})
.factory('dataFactory', ['$http', function($http){
var url = 'http://eportal-dev.cisco.com:3000/getPieData?alt=json-in-script&callback=JSON_CALLBACK';
var thisFactory = {};
var dataToPlot = {};
var theaterData = [
['GC', 44],
['EMEAR', 111],
['APAC', 41],
['US/CANADA', 239],
['GSP', 282] ];
thisFactory.getMockupData = function(){
printToConsole(theaterData);
return theaterData;
};
thisFactory.getDataToPlot = function(callback){
$http.jsonp(url)
.success(callback)
.error(function(data,status,headers,config){
dataToPlot = "Request Failed" + status ;
});
printToConsole(dataToPlot);
return dataToPlot;
};
function printToConsole(obj) {
for(i=0; i<obj.length; i++) {
var item = obj[i];
console.log('item ' + i + ': ' +item);
}
};
return thisFactory;
}]);
</script>
</head>
</head>
<body ng-controller="pieDataController">
<h1>pieData Request: </h1>
<div id="ctrls">
<p class="button" ng-click="getTypicalData();">Get MOCK data</p>
<p class="button" ng-click="getChartData();">Get BACKEND data </p>
</div>
<div id="debug">
<h3>Actual data:</h3>
<ul>
<li ng-repeat="point in pieData">{{ point }} </li>
<ul>
</div>
<!-- directive -->
<draw-pie-chart pie-data="pieData" pie-title="{{pietitle}}" series-name="{{seriesname}}" />
</body>
</html>
3. tkmodule.js directives ?
'use strict';// 1. Declare app level module wangular.module('tkDirectives', [])// 2. declare constants.constant('MOUDLE_VERSION', '0.0.5')// 3. declare defaults if any.value('defaults', {'pieChartTitle': 'Title: '})// 4. directive.directive('drawPieChart', function () {// return the directive link function. (compile function not needed)return {scope: {pieTitle: '@',seriesName: '@',pieData:'=',},restrict: 'AE',replace: true,template: '<div id="_k_pie" ><div>',link: function (scope, element, attrs) {var container = element.attr("id");// watch the expression, and update the UI on change.scope.$watch('pieData', function () { //pieData is child scopedrawPlot();}, true);// Radialize the colorsHighcharts.getOptions().colors = Highcharts.map(Highcharts.getOptions().colors, function (color) {return {radialGradient: { cx: 0.5, cy: 0.3, r: 0.7 },stops: [[0, color],[1, Highcharts.Color(color).brighten(-0.3).get('rgb')] // darken]};});var drawPlot = function () {var chart;chart = new Highcharts.Chart({chart: {renderTo: container,plotBackgrroundColor:null,plotBorderWidth:null,plotShadow:false},credits: {enabled: false},title: {text: scope.pieTitle},tooltip: {formatter: function () {return this.point.name + '<br/> (' + this.point.y + '): <b>' + Highcharts.numberFormat(this.percentage, 2) + '%</b>';},//pointFormat: '{series.name}: <b> ({point.y}), {Highcharts.numberFormat(this.percentage, 2)} % </b>',//percentageDecimals: 2},plotOptions: {pie: {allowPointSelect: true,cursor: 'pointer',dataLabels: {enabled: true,format: '<b>{point.name}</b>: {point.percentage:.1f} %',style: {color: (Highcharts.theme && Highcharts.theme.contrastTextColor) || 'black'},//connectorColor: 'silver'distance: -55,color: 'white'}}},series: [{type: 'pie',name: scope.seriesName, //'Browser share',data: scope.pieData // local scope dude}]});}}}; // return});
'use strict';
// 1. Declare app level module w
angular.module('tkDirectives', []).constant('MOUDLE_VERSION', '0.0.5').value('defaults', {'pieChartTitle': 'Title: '}).directive('drawPieChart', function () {
// return the directive link function. (compile function not needed)
return { scope: { pieTitle: '@', seriesName: '@', pieData:'=', }, restrict: 'AE', replace: true, template: '<div id="_k_pie" ><div>', link: function (scope, element, attrs) {
var container = element.attr("id"); // watch the expression, and update the UI on change. scope.$watch('pieData', function () { //pieData is child scope drawPlot(); }, true); // Radialize the colors Highcharts.getOptions().colors = Highcharts.map(Highcharts.getOptions().colors, function (color) { return { radialGradient: { cx: 0.5, cy: 0.3, r: 0.7 }, stops: [ [0, color], [1, Highcharts.Color(color).brighten(-0.3).get('rgb')] // darken ] }; }); var drawPlot = function () { var chart; chart = new Highcharts.Chart({ chart: { renderTo: container, plotBackgrroundColor:null, plotBorderWidth:null, plotShadow:false }, credits: { enabled: false }, title: { text: scope.pieTitle }, tooltip: { formatter: function () { return this.point.name + '<br/> (' + this.point.y + '): <b>' + Highcharts.numberFormat(this.percentage, 2) + '%</b>'; }, //pointFormat: '{series.name}: <b> ({point.y}), {Highcharts.numberFormat(this.percentage, 2)} % </b>', //percentageDecimals: 2 },
plotOptions: { pie: { allowPointSelect: true, cursor: 'pointer', dataLabels: { enabled: true,
format: '<b>{point.name}</b>: {point.percentage:.1f} %',
style: { color: (Highcharts.theme && Highcharts.theme.contrastTextColor) || 'black' }, //connectorColor: 'silver' distance: -55, color: 'white' } } }, series: [{ type: 'pie', name: scope.seriesName, //'Browser share',
data: scope.pieData // local scope dude
}] }); } } }; // return });
