This post is following on from our moves to separate out concerns when it comes to our portlet development.
Following on from my previous post on using JSON and ETag caching, where we focused on provide data for our portlet to render client-side, we then wanted to concentrate on making the UI render side more elegant. Typically to date we’d been using .JSP with Tags to render, and using jQuery to render the JSON to page. That ended up with fairly lengthy <script> and JSP files, which were frankly messy to look at.
Here’s a truncated example of the horror:
$( document ).ready(function() { var jsonurl = "/libraryonline/rest/summary"; $.ajax({ url: jsonurl, dataType: "text", success: function( data ){ var json = $.parseJSON(data); $("#${n}libraryForm").attr("action",json.voyagerUrl); $("#${n}loginId").attr("value",json.barcode); $("#${n}lastName").attr("value",json.surname); $("#${n}loanTotal").html(json.totalLoans); $("#${n}requestTotal").html(json.totalRequests); $("#${n}fineTotal").html(json.totalFines); if(json.loanItems != null && json.loanItems.length > 0){ /* populate loan items table*/ var $itemTable = $("#${n}loanItemsTable"); $.each(json.loanItems, function(key,entry){ /* This is just getting silly */ $itemTable.append("<tr><td>"+ entry.title +"</td><td>"+ entry.statusText +"</td><td>"+ entry.dueDateConverted +"</td></tr>"); }); }
I wanted to introduce some kind of templating for rendering the HTML, and declutter the JavaScript we’re adding into pages to do the render. There are many options here todo this, we settled on Angular.
First Steps
First, let me recommend Code School’s course Shaping up with Angular as a great way to get started, especially if you already know JavaScript. That should set you up nicely to start producing Angular scripts!
The app.js file
Portlets are generally simple enough that you can manage with a single angular app.js file. This Angular app file handles getting the JSON data (including handling any errors), and setting the appropriately scoped variables so that the view render can display them. In more complicated angular apps you can have many controllers/routers/handlers and where complex split them out into multiple files. Below is an example of our portlet app.js file:
(function(){ var app = angular.module("libraryDetailsApp",[]); app.controller("libraryDetailsController",function($scope,$http) { $scope.errorMessage=""; $http.get('/alma-portlet/rest/summary').success(function(data){ $scope.libraryData = data; }).error(function(data,status,headers,config){ console.error(data); console.error(status); if (data!=null&&data.message!=null) { $scope.errorMessage=data.message; } if ($scope.errorMessage=="") { $scope.errorMessage="There was a problem retrieving your library account details."; } }); }); })();
There are methods already in Angular to handle JSON data, handle errors, and introduce scoped variables, so you can see you end up with nice neat Javascript code to handle it.
The view.jsp
Now that we have the data held in an Angular scoped variable, there’s no need to do any complex Javascript in the view to deal with it. It’s a matter of using Angular attributes and variables to do the render work.
Firstly, we hook in Angular and register the app.js:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js" type="text/javascript"></script> <script src="<c:url value="/js/app.js"/>" type="text/javascript"></script> <div ng-app="libraryDetailsApp"> <div ng-controller="libraryDetailsController" id="${n}topDiv" class="page-style">
Then we can refer to any variables in that scope with double-curly brackets, for example handling error and displaying errorMessage:
<div ng-if="errorMessage" id="${n}error" ng-cloak> <!-- system or patron error occurred --> <div class="row"> <div class="col-md-12"> <div class="alert alert-warning"> <strong>Warning:</strong> {{errorMessage}} </div> </div> </div> </div>
NOTE: ng-cloak is a really useful attribute as it hides the content of the div when you initially render the page.
If we need to loop around data it’s easy, just use ng-repeat:
<tbody> <tr ng-repeat="loanItem in libraryData.loanItems"> <td>{{loanItem.title}}</td> <td>{{loanItem.statusText}}</td> <td>{{loanItem.dueDateConverted}}</td> </tr> </tbody>
Finally
We end up with a view which is very nice and clean, and importantly removed of complex logic for dealing with the data. That way our UI designers can focus on the design without having to worry about what our code is doing, and the developers can focus on what the code is doing without having to worry about impacting on the design.
You can see the whole source on Github: https://github.com/is-apps/AlmaPortlet. Bear in mind portlet frameworks have some specific considerations, like you initialising jQuery/Angular/Bootstrap js in a safe way. There’s a nice write-up of the strategy in a pull request for the uPortal project on github: https://github.com/Jasig/uPortal/pull/582.
Also Agnes Gajda here has written a really good post on testing Angular portlets with Maven and Jasmin, definitely worth reading!