Статьи

Pippo и AngularJS

В предыдущей  статье я сделал краткое введение о том, что такое  Пиппо . В предложении Pippo — это микро-  фреймворк с открытым исходным кодом (Apache License) micro ( около 100K) на Java, с минимальными зависимостями (только slf4j-api для регистрации) и кривой быстрого обучения.    


Я думаю, что в Pippo есть много замечательных вещей (модульность, простота в использовании, встроенная поддержка для многих встроенных веб-серверов — jetty, undertow, tjws -, несколько шаблонизаторов — freemarker, jade, trimou, pebble, groovy -, многократный контент типы — обычный текст, json, xml, yaml -, интеграция с Spring и Guice, интернационализация и многое другое), но, на мой взгляд, Pippo хорошо сочетает создание страниц, созданных сервером, с API-интерфейсами RESTful.
Чтобы доказать это, я хочу показать вам в этой статье, как создать минималистичный
CRUD (
C reate,
R etrieve,
U pdate,
D elete) с использованием Pippo и AngularJS. 

Прежде всего, нужно создать новое приложение ( CrudNgApplication ) и добавить несколько маршрутов:

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");
   }

}

Нам нужны контакты и шаблоны контактов для
запросов «
/ contacts » и «
/contact/.* ». Мы будем использовать freemarker в качестве движка шаблонов для этой демонстрации, но вы можете использовать любой движок шаблонов, поддерживаемый из коробки.

Итак, мы должны поместить эти шаблоны в «
ресурсы / шаблоны ». В этой папке у нас есть 3 (три) файла шаблона:

  • 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>


Мы должны написать логику для нашего приложения angularjs, поэтому я создал
файл
crudNgApp.js в папке «
resources / public / js »:

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));
              });
           }
        };
     } ]);


Конечно, мы должны создать контроллер (
CrudNgApiController ), который инкапсулирует наш бизнес:

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());
   }

}


В вышеприведенном классе очень ясно представлены бизнес-методы CRUD:
getContacts ,
getContact ,
deleteContact и
saveContact . В этих методах мы читаем запрос, делаем некоторые операции в нашем хранилище через службу контактов и отправляем ответ обратно пользователю.

Код прост, поэтому я хотел бы только отметить, что мы использовали
аннотации
@Param и
@Body, чтобы ввести некоторые детали запроса (например, параметры запроса) в качестве параметров метода (идентификатор контакта, контакт).

Теперь у нас есть приложение, которое объявляет маршруты, у нас есть контроллер, который делает вещи, поэтому нам нужен модуль запуска.
Для запуска нашего приложения мы должны использовать внешний контейнер сервлета или встроенный контейнер сервлета. Я буду использовать второй подход в этой демонстрации, чтобы увидеть, насколько это просто.

public class CrudNgDemo {

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

}


Итак, наш
CrudNgDemo — это простое Java-приложение с
основным методом. Вы можете запустить это приложение из командной строки или из любой Java IDE (Eclipse, Idea IntelliJ).

При сборке проекта не забудьте указать зависимости в вашем 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>

Из приведенного выше фрагмента видно, что мы добавили в качестве зависимостей только те модули pippo, которые нам нужны (ядро, контроллер, freemarker в качестве движка шаблонов и fastjson в качестве библиотеки json).
Также мы добавили
самозагрузки ,
шрифт-удивительный и
angularjs как
webjars .

Я добавил 2 (два) скриншота из нашего приложения:

Весь код этой статьи доступен по адресу https://github.com/decebals/pippo/tree/master/pippo-demo/pippo-demo-crudng .

Полезные ссылки

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