Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Pippo and AngularJS

DZone's Guide to

Pippo and AngularJS

· Java Zone ·
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

In a previous article I made a short introduction about what is Pippo . In a sentence, Pippo is an open source (Apache License) micro (around 100K) web framework in Java, with minimal dependencies (only slf4j-api for logging) and a quick learning curve.  

I think that Pippo has many great things (modularity, easy to use, support out of the box for many embedded web servers - jetty, undertow, tjws -, multiple template engines - freemarker, jade, trimou, pebble, groovy -, multiple content types - plain text, json, xml, yaml -, spring and guice integration, internationalization, and many more) but in my opinion Pippo shines in mixing server generated pages with RESTful APIs. To prove this I want to show you in this article how to create a minimalistic CRUD ( Create, Retrieve, Update, Delete) using Pippo and AngularJS. 

First thing is to create a new application (CrudNgApplication) and to add some routes:

public class CrudNgApplication extends ControllerApplication {

   private ContactService contactService;

   public ContactService getContactService() {
       return contactService;
   }

   @Override
   protected void onInit() {
       /*
        * initiate the contact service
        */
       contactService = new InMemoryContactService();

       /*
        * add handlers for static resources
        */
       GET(new WebjarsResourceHandler());
       GET(new PublicResourceHandler());

       /*
        * redirect “/” to “/contacts”
        */
       GET("/", new RedirectHandler("/contacts"));

       /*
        * Server-generated HTML pages
        */
       GET("/contacts", new TemplateHandler("contacts"));
       GET("/contact/.*", new TemplateHandler("contact"));

       /*
        * RESTful API
        */
       GET("/api/contacts", CrudNgApiController.class, "getContacts");
       GET("/api/contact/{id}", CrudNgApiController.class, "getContact");
       DELETE("/api/contact/{id}", CrudNgApiController.class, "deleteContact");
       POST("/api/contact", CrudNgApiController.class, "saveContact");
   }

}
We need contacts and contact templates for “ /contacts” and “ /contact/.*” requests. We will use freemarker as template engine for this demo but you can use any template engine supported out of the box.
So, we must put these templates in “ resources/templates”. In this folder we have 3 (three) template files:

  • base.ftl

<#macro page title>
<!DOCTYPE html>
<html ng-app="crudNgApp">
   <head>
       <meta charset="utf-8">
       <meta content="IE=edge" http-equiv="X-UA-Compatible">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">

       <title>${title}</title>

       <link href="${webjarsAt('bootstrap/3.3.1/css/bootstrap.min.css')}" rel="stylesheet">
       <link href="${webjarsAt('font-awesome/4.2.0/css/font-awesome.min.css')}" rel="stylesheet">
       <link href="${publicAt('css/style.css')}" rel="stylesheet">
       <base href="${appPath}/">
   </head>
   <body>
       <div class="container">
           <#nested/>

           <script src="${webjarsAt('jquery/1.11.1/jquery.min.js')}"></script>
           <script src="${webjarsAt('bootstrap/3.3.1/js/bootstrap.min.js')}"></script>
           <script src="${webjarsAt('angularjs/1.3.6/angular.min.js')}"></script>
           <script src="${publicAt('js/crudNgApp.js')}"></script>
       </div>
   </body>
</html>
</#macro>

  • contacts.ftl

<#import "base.ftl" as base/>
<@base.page title="Contacts">
   <div class="page-header">
       <h2>Contacts <small>RESTful JSON APIs + AngularJS</small></h2>
   </div>

   <div ng-controller="ContactsCtrl">
       <div class="buttons pull-right">
           <a id="addContactButton" type="button" class="btn btn-primary" href="contact/0"/"><i class="fa fa-plus"></i> Add Contact</a>
       </div>

       <table class="table table-striped table-bordered table-hover">
           <thead>
           <tr>
               <th>#</th>
               <th>Name</th>
               <th>Phone</th>
               <th colspan='2'>Address</th>
           </tr>
           </thead>
           <tbody>
               <tr ng-repeat="contact in contacts">
                   <td>{{contact.id}}</td>
                   <td>{{contact.name}}</td>
                   <td>{{contact.phone}}</td>
                   <td>{{contact.address}}</td>
                   <td style="text-align: right;">
                       <div class="btn-group btn-group-xs">
                           <a class="btn btn-default" href="contact/{{contact.id}}"><i class="fa fa-pencil"></i> Edit</a>
                           <button class="btn btn-default" ng-click="deleteContact(contact.id)"><i class="fa fa-trash"></i> Delete</button>
                       </div>
                   </td>
               </tr>
           </tbody>
       </table>
   </div>
</@base.page>

  • contact.ftl

<#import "base.ftl" as base/>
<@base.page title="Contact">
   <div class="page-header">
       <h2>Contact <small>RESTful JSON APIs + AngularJS</small></h2>
   </div>

   <div ng-controller="ContactCtrl">
     <form novalidate class="form-horizontal" role="form" name="contactForm" ng-submit="postContact(contactForm.$valid)">
         <div class="form-group" ng-class="{ 'has-error' : contactForm.name.$invalid && !contactForm.name.$pristine }">
             <label for="name" class="col-sm-2 control-label">Name</label>
             <div class="col-sm-9">
                 <input type="text" class="form-control" id="name" name="name" ng-model="name" required ng-minlength="3">
                 <p ng-show="contactForm.name.$error.minlength" class="help-block">The entered name is too short.</p>
             </div>
         </div>
         <div class="form-group" ng-class="{ 'has-error' : contactForm.phone.$invalid && !contactForm.phone.$pristine }">
             <label for="phone"  class="col-sm-2 control-label">Phone</label>
             <div class="col-sm-9">
                 <input type="text" class="form-control" id="phone" name="phone" ng-model="phone" required ng-minlength="7">
                 <p ng-show="contactForm.phone.$error.minlength" class="help-block">The entered phone number is too short.</p>
             </div>
         </div>
         <div class="form-group" ng-class="{ 'has-error' : contactForm.address.$invalid && !contactForm.address.$pristine }">
             <label for="address" class="col-sm-2 control-label">Address</label>
             <div class="col-sm-9">
                 <input type="text" class="form-control" id="address" name="address" ng-model="address" required >
                 <p ng-show="contactForm.address.$invalid && !contactForm.address.$pristine" class="help-block">The address is required.</p>
             </div>
         </div>
         <div class="form-group">
             <div class="col-sm-offset-2 col-sm-9">
                 <button type="submit" class="btn btn-default btn-primary" ng-disabled="contactForm.$invalid">Submit</button>
                 <a type="submit" class="btn" href="contacts">Cancel</a>
             </div>
         </div>
     </form>
   </div>
</@base.page>

We must write the logic for our angularjs application so I created the crudNgApp.js file in “ resources/public/js” folder:

var crudNgApp = angular.module("crudNgApp", []);
var baseUrl = angular.element("base").attr("href");

//
// Contacts Controller is responsible for listing and deleting a contact
// see crudng/contacts.ftl
//
crudNgApp.controller("ContactsCtrl", [ '$scope', '$http', '$location',
     '$window', function($scope, $http, $location, $window) {

        //
        // Retrieve the contacts from Pippo
        //
        $http({
           method : 'GET',
           url : baseUrl + 'api/contacts'
        }).success(function(data, status, headers, config) {
           // update the ng-models on success
           $scope.contacts = data;
        }).error(function(data, status, headers, config) {
           // alert on failure
           alert("Whoops!\n\n" + JSON.stringify({
              message : data.statusMessage,
              code : data.statusCode,
              method : data.requestMethod,
              uri : data.requestUri
           }, null, 2));
        });

        //
        // Delete the specified contact
        $scope.deleteContact = function(id) {
           $http({
              method : 'DELETE',
              url : baseUrl + 'api/contact/' + id
           }).success(function(data, status, headers, config) {
              // redirect on success
              $window.location.href = baseUrl;
           }).error(function(data, status, headers, config) {
              // alert on failure
              alert("Whoops!\n\n" + JSON.stringify({
                 message : data.statusMessage,
                 code : data.statusCode,
                 method : data.requestMethod,
                 uri : data.requestUri
              }, null, 2));
           });
        };
     } ]);

//
// Contact Controller is responsible for editing a contact
// see crudng/contact.ftl
//
crudNgApp.controller("ContactCtrl", [ '$scope', '$http', '$location',
     '$window', function($scope, $http, $location, $window) {

        // Extract contact id from the url
        var contactUrl = $location.absUrl();
        var id = contactUrl.substring(contactUrl.lastIndexOf('/') + 1);

        //
        // Retrieve the specified contact from Pippo
        //
        $http({
           method : 'GET',
           url : baseUrl + 'api/contact/' + id
        }).success(function(data, status, headers, config) {
           // update the ng-models on success
           $scope.id = data.id;
           $scope.name = data.name;
           $scope.phone = data.phone;
           $scope.address = data.address;
        }).error(function(data, status, headers, config) {
           // alert on failure
           alert("Whoops!\n\n" + JSON.stringify({
              message : data.statusMessage,
              code : data.statusCode,
              method : data.requestMethod,
              uri : data.requestUri
           }, null, 2));
        });

        //
        // Post the new/updated contact back to Pippo
        //
        $scope.postContact = function(isValid) {

           if (isValid) {
              // prepare the contact from the ng-models
              var dataObj = {
                 id : $scope.id,
                 name : $scope.name,
                 phone : $scope.phone,
                 address : $scope.address
              };

              $http({
                 method : 'POST',
                 url : baseUrl + 'api/contact',
                 data : dataObj
              }).success(function(data, status, headers, config) {
                 // redirect on success
                 $window.location.href = baseUrl;
              }).error(function(data, status, headers, config) {
                 // alert on failure
                 alert("Whoops!\n\n" + JSON.stringify({
                    message : data.statusMessage,
                    code : data.statusCode,
                    method : data.requestMethod,
                    uri : data.requestUri
                 }, null, 2));
              });
           }
        };
     } ]);

Of course we must create a controller ( CrudNgApiController) that encapsulates our business:

public class CrudNgApiController extends Controller {

   private static final Logger log = LoggerFactory.getLogger(CrudNgApiController.class);

   ContactService getContactService() {
       return ((CrudNgApplication) getApplication()).getContactService();
   }

   public void getContacts() {
       getResponse().xml().contentType(getRequest()).send(getContactService().getContacts());
       log.info("Retrieved all contacts");
   }

   public void getContact(@Param("id") int id) {
       Contact contact = (id > 0) ? getContactService().getContact(id) : new Contact();
       getResponse().xml().contentType(getRequest()).send(contact);
       log.info("Retrieved contact #{} '{}'", contact.getId(), contact.getName());
   }

   public void deleteContact(@Param("id") int id) {
       if (id <= 0) {
           getResponse().badRequest();
       } else {
           Contact contact = getContactService().getContact(id);
           if (contact == null) {
               getResponse().badRequest();
           } else {
               getContactService().delete(id);
               log.info("Deleted contact #{} '{}'", contact.getId(), contact.getName());
               getResponse().ok();
           }
       }
   }

   public void saveContact(@Body Contact contact) {
       getContactService().save(contact);
       getResponse().ok();
       log.info("Saved contact #{} '{}'", contact.getId(), contact.getName());
   }

}

In above class it’s very clear the CRUD business methods: getContacts, getContact, deleteContact and saveContact. In these methods we read the request, make some operations in our storage via contact service and send the response back to user.
The code is simple so I would like only to mention that we used @Param and @Body annotation to inject some request details (request parameters for example) as method parameters (contact id, contact).
Now we have the application that declares routes, we have the controller that do the stuff so we need the launcher. To start our application we must use an external servlet container or an embedded servlet container. I will use  the second approach in this demo to see how easy it is.

public class CrudNgDemo {

   public static void main(String[] args) {
       Pippo pippo = new Pippo(new CrudNgApplication());
       pippo.start();
   }

}

So, our CrudNgDemo is a simple java application with a main method. You can run this application from command line or from any Java IDE (Eclipse, Idea IntelliJ).
When you build the project don’t forget to specify the dependencies in your Maven pom.xml:
<dependencies>
   <!-- Pippo -->
   <dependency>
       <groupId>ro.pippo</groupId>
       <artifactId>pippo-core</artifactId>
       <version>${project.version}</version>
   </dependency>

   <dependency>
       <groupId>ro.pippo</groupId>
       <artifactId>pippo-controller</artifactId>
       <version>${project.version}</version>
   </dependency>

   <dependency>
       <groupId>ro.pippo</groupId>
       <artifactId>pippo-freemarker</artifactId>
       <version>${project.version}</version>
   </dependency>

   <dependency>
       <groupId>ro.pippo</groupId>
       <artifactId>pippo-fastjson</artifactId>
       <version>${project.version}</version>
   </dependency>

   <!-- Webjars -->
   <dependency>
       <groupId>org.webjars</groupId>
       <artifactId>bootstrap</artifactId>
       <version>3.3.1</version>
   </dependency>

   <dependency>
       <groupId>org.webjars</groupId>
       <artifactId>font-awesome</artifactId>
       <version>4.2.0</version>
   </dependency>

   <dependency>
       <groupId>org.webjars</groupId>
       <artifactId>angularjs</artifactId>
       <version>1.3.6</version>
   </dependency>
</dependencies>
From above snippet you can see that we added as dependencies only pippo modules we need (core, controller, freemarker as template engine and fastjson as json library). Also we added bootstrap, font-awesome and angularjs as webjars.

I added 2 (two) screenshots from our application:



All code for this article is available on https://github.com/decebals/pippo/tree/master/pippo-demo/pippo-demo-crudng.

Useful links

https://github.com/decebals/pippo
http://www.pippo.ro

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}