Using WebCenter Sites with Dropwizard and AngularJS, Part 2
This is the second part of our tutorial introducing ways to expose WebCenter Sites content as an external API.
You can catch up with the first part here.
In this second part we’re going to add a simple GUI to our API service. To do that we’re going to use a JavaScript framework called AngularJS.
Serving Assets
In order to serve static HTML and JavaScript from our Dropwizard service we need to make a few small changes. Dropwizard supports delivering static files with a plugin (bundle) called AssetsBundle
We can configure the AssetsBundle to:
- look in a particular part of our Maven project for its files
- deliver those files from a particular path
- determine what the default index file name is
What we can’t do however is serve our API resources and our static resources from the same path so the first thing we need to do is to change where we’re serving our API resources from.
To do that we only need to update our YAML configuration file by adding the following
# HTTP-specific options.
http:
rootPath: /service/*
This means that our API will now be served from the /service path
We also need to update the initialize method in our ArticleService to look like the following:
@Override
public void initialize(Bootstrap<ArticleConfiguration> bootstrap) {
bootstrap.setName("article");
bootstrap.addBundle(new AssetsBundle("/assets/", "/", "index.html"));
}
This tells Dropwizard that we want to take resources stored under /src/main/resources/assets and serve them from the root of our application (/) and that we want index.html to be the default index file name.
AngularJS
The following image shows what we’re heading towards. Our GUI will display a pageable list of articles from WCS and clicking the View button will display fuller details of that article.
In order to simplify the building of our UI we’re using resources from another couple of projects.
For the look and feel we’re using Twitter’s bootstrap framework.
We’re also going to use some pre-built directives (these are chunks of Angular functionality that are bundled up in a modular format) from this GitHub project.
Apps, Controllers and Layouts
Of the four files we need to create lets start with the main HTML file.
<!doctype html>
<html lang="en" ng-app="AngularWcsApp">
<head>
<meta charset="utf-8">
<title>Articles App</title>
<link data-require="bootstrap-css@2.3.2" data-semver="2.3.2" rel="stylesheet" href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" />
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<div class="nav-collapse collapse">
<a class="brand" href="#">Dropwizard, WCS and AngularJs</a>
<ul class="nav">
<li class="active">
<a href="/#/articles">Articles</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="container">
<div ng-view></div>
</div>
<script data-require="angular.js@1.1.5" data-semver="1.1.5" src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
<script data-require="angular-ui-bootstrap@0.3.0" data-semver="0.3.0" src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.3.0.min.js"></script>
<script src="/js/app.js"></script>
<script src="/js/controllers/ArticleController.js"></script>
</body>
</html>
First starting from the top we can see a binding to our application – AngularWcsApp (this is defined in the include /js/app.js ) which is described below
'use strict';
var AngularWcsApp = {};
var App = angular.module('AngularWcsApp', ['ui.bootstrap']);
App.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/articles', {
templateUrl: 'articles/layout.html',
controller: ArticleController
});
$routeProvider.otherwise({redirectTo: '/articles'});
}]);
Here we’re registering the primary URL route for our application – /articles and making sure that if we don’t call it directly we redirect to it.
We’re also supplying a template – articles/layout.html and a controller – ArticleController
Let’s start by looking at the layout template next
<div class="alert alert-error" ng-show="error">
{{errorMessage}}
</div>
<h3>Articles List</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="text-align: center; width: 25px;">#</th>
<th>Article Name</th>
<th> </th>
</tr>
</thead>
<tbody>
<tr ng-repeat="article in filteredArticles">
<td>{{article.id}}</td>
<td>{{article.name}}</td>
<td style="width:100px;text-align: center;">
<button class="btn btn-primary" ng-click="viewArticle(article)">
<span class="icon-eye-open icon-white"></span> View
</button>
</td>
</tr>
</tbody>
</table>
<div data-pagination="" data-num-pages="numPages()" data-current-page="currentPage" data-max-size="maxSize" data-boundary-links="true"></div>
<div class="well" ng-show="viewMode">
<h2 ng-bind="article.headlineField"></h2>
<h3 ng-bind="article.subheadlineField"></h3>
<blockquote ng-bind="article.abstractField"></blockquote>
<p ng-bind="article.authorField"></p>
<p ng-bind-html-unsafe="article.bodyField"></p>
</div>
There are three main parts to this template: the table that renders our list of articles; the pagination for the table supplied by the bootstrap-ui directive; and then the rendering of individual articles at the bottom.
It’ll help to make a bit more sense of the template by looking at the controller next.
'use strict';
/**
* ArticleController
* @constructor
*/
var ArticleController = function($scope, $http) {
$scope.filteredArticles = [];
$scope.articles = [];
$scope.currentPage = 1;
$scope.numPerPage = 5;
$scope.maxSize = 5;
$scope.viewMode = false;
$scope.fetchArticlesList = function() {
$http.get('service/articles').success(function(articlesList){
$scope.articles = articlesList;
$scope.filteredArticles = $scope.articles.slice(0, $scope.numPerPage);
});
}
$scope.viewArticle = function(selectedArticle) {
$scope.resetError();
$scope.viewMode = true;
$http.get('service/articles/' + selectedArticle.id).success(function(article){
$scope.article = article;
});
}
$scope.numPages = function () {
return Math.ceil($scope.articles.length / $scope.numPerPage);
};
$scope.$watch('currentPage + numPerPage', function() {
var begin = (($scope.currentPage - 1) * $scope.numPerPage)
, end = begin + $scope.numPerPage;
$scope.filteredArticles = $scope.articles.slice(begin, end);
});
$scope.resetError = function() {
$scope.error = false;
$scope.errorMessage = '';
}
$scope.setError = function(message) {
$scope.error = true;
$scope.errorMessage = message;
}
$scope.fetchArticlesList();
$scope.predicate = 'id';
}
Looking at some of the methods here in more detail…
$scope.fetchArticlesList – makes an http get request to service/articles and returns all the articles – it then sets the variable filteredArticles to the first page of the original list of articles. It’s this variable that’s bound to the data table in our template above
$scope.viewArticle – takes a selected article and makes an HTTP get request to service/articles/{articleId} to return data for a particular article. It saves this data to the article variable. Its this variable that’s bound to the bottom part of our template where we render details of the article
This function watches for changes to the current page from the pagination directive and updates the results stored in $scope.filteredArticles accordingly.
$scope.$watch('currentPage + numPerPage', function() {
var begin = (($scope.currentPage - 1) * $scope.numPerPage)
, end = begin + $scope.numPerPage;
$scope.filteredArticles = $scope.articles.slice(begin, end);
});
Putting these files in src/main/resources/assets the layout looks like this:
- assets/
- articles/
- layout.html
- js/
- controllers/
- ArticleController.js
- app.js
- controllers/
- index.html
- articles/
Finally we can build our application again with `mvn clean package’ and run it as before from the main project directory with
java -jar target/dropwizard-wcs-0.0.1-SNAPSHOT.jar server articles.config
Leave a reply
-
How does this affect the caching model, are we losing all the benefits of ecache or will the JSON responses be cached until a change to the content?