Lift round trips with FoBo, Angular and Slick

a article by Peter Petersson (karma4u101) - Published 3 Sep 2014

This article is about Lift 3's round trip concept. At the end of this article you will know how to set up a data grid with CRUD operations using Lift 3's round trip concept with Angular and Slick.

So what do we mean by Lift 3's round trip concept? Let's clarify with a quote from Lift Founder David Pollak.

With Lift 3, we introduce the idea of Round Trips: a client-side request that's sent to the server where the client receives a Streaming Promise, the server performs its computations and when the results are ready, they get pushed to the client… and if there are multiple results, the multiple results are pushed to the client as the results become available.

David Pollak @dpp in Angular JS, Lift 3, and Streaming Promises

Although this application is kick-started using the lift-advanced-bs3 Template Application with the Lift module FoBo's Bootstrap v3.x setting as it's base the following sections is mostly about Angular and Lift 3's Round Trip concept. To make the examples a bit more realistic/exciting we are also using Slick to handle the back-end storage. The choice of using Slick to handle the back-end storage has however no impact on the overall concept described in this article/tutorial.

Run this application locally on your computer!

If you wish, you can have this article/tutorial up and running in less than 5 minutes, the only prerequisites is that you have Java and Git installed on the target computer. One benefits (apart from easy acces to the code) would be that you will not only have acces to the client side consol logs but also the server side logs. To get started go to FoBo-Demo and read the README.md.

The examples are increasingly complex in what they accomplish but considering the "normal complexity" involved in coding the same thing using other techniques the code needed using Lift and Angular is elegant and fairly simple to comprehend.

To make the examples clear I have tried to keep the code as simple as possible, for that reason the Slick integration is close to the bare minimum and dose, for that reason, not reflect what you would expect in a production ready project. Also the examples lacks most error handling although some error handling is briefly mentioned and it should be fairly easy for you the reader, to imagine the extensions needed.

This demo application uses the following components:

Lift
Lift is the most powerful, most secure web framework available today.
Slick
The Scala Language-Integrated Connection Kit giving you seamless data access.
FoBo
A comprehensive Front-End toolkit module for Lift. For developing mobile scalable responsive web applications. All toolkits, Bootstrap, AngularJS, Font Awesome, google-code-prettify used in this article is provided by FoBo.
AngularJS
Including the Angular UI modules UI Grid and UI Bootstrap. This component is provided via the FoBo module.
Bootstrap v3.x
Sleek, intuitive, and powerful mobile first front-end framework that makes doing the right thing easy. This component is provided via the FoBo module.
Font Awesome
The iconic font designed for Bootstrap. This component is provided via the FoBo module.
google-code-prettify
Syntax highlighting of code snippets in a web page. This component is provided via the FoBo module.

You can read more about Angular JS, Lift 3 and Streaming Promises in DPP's Blog

Lift and ORM's

Lift comes with two (optional) ORM persistence layer modules Mapper and Record where, notably, the later includes specific support for MongoDB and Squeryl .

Although Lift has those excellent ORM modules Lift's WebKit is agnostic as to the use of ORM frameworks or database query and access library as can be seen in this article/tutorial where we are using the Slick 2.0 Functional Relational Mapping framework.

The FoBo Lift module

FoBo is all about providing toolkits for responsive design solutions. In this the AngularJS toolkit and its modules like NGTouch , UI Bootstrap , UI-Grid and more has a natural fit.

However nothing in the articles angular examples depends on the FoBo module, by that I mean, that there is not FoBo specific Angular API magic involved in the examples just plain Lift and Angular stuff. FoBo is however providing a nice uniform resource setup and some nice bootstrap-Lift snippets used herein.

The lift-ng module

The lift-ng module is a excellent Angular specific Lift module that lets you utilize Lift's reactive features such as asynchronous comet updates to the client and returning LAFuture[T] results for services.

Here is a youtube video 15-Minute Chat with lift-ng showcasing some of the lift-ng powers.


Angular Examples

This section contains Angular examples alongside with descriptions of the involved functions and code. We start with a trivial example, proceeds with a fairly simple round trip example with a thorough explanation of how it utilizes the round trip concept. We then proceed with a serie of example where we add more and more functionality to a data-grid that in the end implements a "excel like" data-grid with CRUD data functionality that interact with the server using Lift 3's round trip functions.

For overall setup and configuration of required resources see the FoBo setup section.

Resource setup

To avoid cluttering the examples with to much repeating details the overall resource setup has been omitted from the example section. A convenient way to setup Angular, bootstrap and other front end toolkit resources in Lift is to use the FoBo module, you can read all about how to setup the resources used in this demo using FoBo in the FoBo setup section.

Example 1:

If you are new to Angular this example is for you, taken from the introduction on AngularJS home , it will prepare you for the coming examples. If you are already familiar with Angular you can skip this example and jump to the next where we throw our self right in to the world Lift's server round trips.

New to AngularJS?

If you are new to AngularJS I strongly suggest that you, to get started with the basics, visit AngularJS excellent home. There are plenty of examples, tutorials and other information that you will find valuable in you exciting endeavors into AngularJS land.

This is a very simple Angular only example (no server interaction) doing a ajax updates of the Angular expression yourName.

Hello {{yourName}}!

The following shows the code involved in this example.

index.html
⋮	  
<div class="form-horizontal" role="form">
  <div class="form-group">
    <label for="name" class="col-sm-2 control-label">Name:</label> 
    <div class="col-sm-10">
      <input id="name" class="form-control" type="text" data-ng-model="yourName" placeholder="Enter a name here">
    </div>
  </div>
</div>
⋮

In this example we are creating a Angular model ngModel (data-ng-model="yourName") with the assignable Angular expression "yourName", we are then using this expression in a binding {{ expression }} on the heading as shown below.

index.html
<h4>Hello { { yourName } }!</h4>

As you type in a name the {{yourName}} value will get updated.


Example 2:

In this example we are doing a Lift 3 round trip to the server and back. Still we are keeping the function simple to get you familiar with the concept.

When clicking on the "Simple RT" button the text above the button will be replaced with a text fetched from the server. So what's happening is that the Angular model gets updated via the Angular controller who is invoking a lift round trip function that, as part of its execution, invokes a Scala function on the server, this function dose the calculations and returns it to the client.

The return value is put into Lift's RoundTripHandlerFunc who is then sending it back from the server to the client, in this case it will end up as a future in the Angular controller whereby the Angular module gets updated and the value will be displayed above the click button.

In our case we are not handling errors but Lift's RoundTripHandlerFunc is not only able to send back data but also signal end of data (for streams) or some error condition, you can read more about that in DPP's Blog

{{lrtResModel}}

Below I will outline the things involved in hooking up Lift roundtrip's with a AngularJS controller , this "hookup" pattern is used in most of the following example(s).

Modeling Lift Round Trips!

As there are many ways to model the Lift round trips functionality you may see other blog post, articles or tutorials doing thing's in a slightly different way, my hope is that the pattern used in this article is simple enough that you will understand and see the overall "wiring" yet flexible enough for most use cases.

Bootstraping Angular

Now lets look at the code. As this is the first example involving Lift roundtrip's and a bit more Angular stuff I will show you all the essential parts, we start by look at this from the view (Lift's designer friendly templates ) focusing on the Angular stuff.

index.html
<body data-lift-content-id="main">
  <div id="main" data-ng-app="foboDemoApp" data-lift="surround?with=default;at=content">
      ⋮
  <span data-lift='embed?what=_embededIndexAJSExamples'>Embedding demo examples section here</span>
     ⋮  
  </div>
</body>

The data-ng-app attribute represents an Angular directive named ngApp. This directive is used to flag the html element that Angular should consider to be the root element of the Angular application scope. In our case the root element will be the div id=main element.

Angular application scope

Although it is common to have only one ng-app directive in a application (typical in a single-page application) it is possible to have more than one data-ng-app directives in one and the same application (one per page).

app.js
var app = angular.module('foboDemoApp',['ui.grid','ui.bootstrap']);

Here we are loading the foboDemoApp module with it's configuration and run blocks. Although not necessary in this example we are also loading some module dependencies ['ui.grid','ui.bootstrap']. As the module dependencies is not used in this example we could have made it a empty list [], we are however going to use the dependancies in later examples.

The view bindings

index.html
 ⋮
<div data-ng-controller="SimpleRTCtrl">
  <pre>{ { lrtResModel } }</pre>
  <button type="button" class="btn btn-default" data-ng-click="doSimpleLiftRT()">Simple RT</button>
  <button type="button" class="btn btn-default" data-ng-click="doResetRT()">Reset</button>
</div>
 ⋮

Above shows the controller declaration in the Lift template. Bellow is the javascript implementation of the same Angular controller

The controller

simpleRTCtrl.js
app.controller('SimpleRTCtrl',['$scope', function($scope){	
	$scope.lrtResModel="Click and I will do a server roundtrip";
	
	$scope.doSimpleLiftRT = function() {
	    var promise = myRTFunctions.doSimpleRT(); // call to lift function
	    return promise.then(function(data) {
	      $scope.$apply(function() {
	        $scope.lrtResModel = data;
	      })
	      return data;
	    });			
	};
	$scope.doResetRT = function() {
		return $scope.lrtResModel="Click again and I will do another roundtrip";
	};
}]);

Now in the Angular controller things are starting to get a bit more interesting, although we are keeping things simple. Here we are calling, from the Angular javascript controller, the round trip function myRTFunctions.doSimpleRT(); that is, a function that is hooked to native Lift/Scala code on the server. This function will return a promise and when fulfilled it will contain a lift-json string originated from the server with the message "There and back again!".

The function that returns the future is a lift round trip call and hence something Angular knows nothing about (it's not handled by Angular) so when the future is resolved we need to tell Angular that things has happened/changed, in Angular we do that by calling $scope.$apply

Lift round trip bindings

I am going to show you the Lift server side code in a moment but let us first take a look at the things that provides the glue between the server and the client starting with the snippet declaration in the lift template .

default.html
<!DOCTYPE html>
<html>
<head>
⋮
</head>		  
<body ... >
   ⋮
 <span data-lift="RoundTripBindingInjector"> This snippet injects the roundtrip script's </span>
</body>
</html>

The lift html template above shows the markup with our RoundTripBindingInjector snippet invocation. When the page/template containing this snippet gets rendered Lift will resolve the RoundTripBindingInjector snippet and invoke it's render function and transform the nodes. Lift’s snippets are Scala functions: NodeSeq => NodeSeq.

Below is the actual snippet implementation that renders the hook up functions as javascript code that appends to lift's page script file. Our use case specific hook up functions is added by calling our getRoundTrips function. The essential Lift round trip building magic is condensed into the buildRoundtrip toJsCmd transformation. In this example our use case specific server side round trip functions is declared in the SimpleRT trait, in later examples our use case specific server side round trip functions will be in the PersonRT trait.

RoundTripBindingInjector.scala
/*This snippet is appending the roundtrip scripts into lift's page script file.*/
class RoundTripBindingInjector extends PersonRT with SimpleRT {
  def render() : NodeSeq = {
    val functions = ((for {
      session <- S.session
    } yield {
       S.appendGlobalJs(JsRaw(s"var myRTFunctions = ${session.buildRoundtrip(getRoundTrips).toJsCmd}").cmd)
       NodeSeq.Empty
     }
    ) openOr NodeSeq.Empty)
    functions
  }  
}

Below is our SimpleRT trait containing a List with a RoundTripInfo object that maps the js function doSimpleRT in the Angular controller using it's name (the string value) with our Scala function doSimpleRT(value :JValue, func :RoundTripHandlerFunc) :Unit.

As you can see we are for clarity using the same function name doSimpleRT in booth ends.

SimpleRT.scala
trait SimpleRT extends EmptyRoundTrip {
  
  protected def doSimpleRT(value :JValue, func :RoundTripHandlerFunc) :Unit = {
    //send json data to the client as a JString using the RoundTripHandlerFunc
    func.send(JString("There and back again!"))
  }
  
  private val roundtrips:List[RoundTripInfo] = List("doSimpleRT" -> doSimpleRT _)
  abstract override def getRoundTrips = super.getRoundTrips ++ roundtrips  
}   

The doSimpleRT function has two parameters, namely the input value named value of type JsonAST.JValue and the return value named func of type RoundTripHandlerFunc.

As this is a very simple example we are only using the RoundTripHandlerFunc send(/*some data*/) function. In addition we also have the functions done() and failure(/*Some failure message*/), that gives us explicit control over sending data, signaling end of data (done) for a stream of data, and sending a failure message on failure.

Decoupling our round trip functions

For completeness, the code below is showing how we declare getRoundTrips so that by extending EmptyRoundTrip we have a way of organizing our round trip functions into separate traits.

PageRoundTrips.scala
import net.liftweb.http.RoundTripInfo

abstract class PageRoundTrips {
  protected def getRoundTrips : List[RoundTripInfo]
}

class EmptyRoundTrip extends PageRoundTrips {
  protected def getRoundTrips : List[RoundTripInfo] = Nil
}

That's it!

When you get your head around it, it ain't that complicated and as has been said above this is one way of doing it. Compared with a simpler and more strait forward approach we are using the PageRoundTrips abstraction (originally used by M. Lucchetta) which gives us some additional flexibility as it in a decoupled way lets us organize your round trip functions into multiple snippet traits.

Styleguide

This application group files in a functionally way (all templates/views together, all css together, all js together etc...), this is, today, a common way of structuring web apps. New web technologies is however emerging with web components as one of the most exiting and impacting ones, this will change how we structure future web apps and as technology merges this will include how we structure AngularJS and Lift web apps too.

With this in mind Google has put together some AngularJS style guide documentation, so if you want to get up to date on how to structure future web apps the structurally way you can take a look at this Angular blog post An AngularJS Style Guide and Best Practice for App Structure it contains references to these documents.


Next in example 3 we are going to use the same design pattern as outlined above but this time we are going to, with Lift round trip's, up on request, send a array of lift-json data collected from the back-end store Slick and display it using the Angular UI-Grid UI module .

In example 4 to 6 we will expand up on the functionality in example 3 to in the grid have, not only, read operations but also create, update and delete operations.


Example 3:

Using the Angular UI-Grid UI module , provided via the FoBo module , this example is doing a round trip to the server to fetch and display grid data from the back-end store handled by Slick . We are going to build on this example in the following examples to extend its functionality. Here we start with the simplest task of just fetching and displaying the data.

The view bindings

Here the simpleGridOptions is the variable we are going to bind to to initiate our grid options, this will be done in the PersonGridCtrl.

index.html
 ⋮
<div data-ng-controller="PersonGridCtrl">
  <div class="gridStyle" ui-grid="simpleGridOptions"></div>
</div> 
 ⋮

The controller

In $scope.simpleGridOptions we are binding $scope.myData to the grid and when the promise returned by Lift's round trip function personsQuery() called via the $scope.doPopulate function is fulfilled the grid will get populated with data fetched from the back-end store.

personGridCtrl.js
app.controller('PersonGridCtrl',['$scope',function($scope) {
	
  $scope.myData = []
	
  $scope.doPopulate = function() {
    var promise = myRTFunctions.personsQuery();  //Round trip call to Lift server function
    return promise.then(function(data) {
      $scope.$apply(function() {
        $scope.myData = data; 
      })
      return data;
    });	 
  };
  $scope.doPopulate();
 	
  $scope.simpleGridOptions = { 
    data: 'myData',
    enableRowSelection: false,       
    columnDefs: [{field: 'id', visible: false},
                 {field: 'name', displayName: 'Name'}, 
                 {field:'age', displayName:'Age'}]
  };
    
}]);

Lift round trip bindings

Below is the PersonRT trait containing the Scala Lift server side part of the round trip function. For now we only have the personsQuery() round trip function that fetches and maps the back-end person data to a list of JsonAST.JObject's.

PersonRT.scala
trait PersonRT extends EmptyRoundTrip with PersonComponent {
  
  protected def personsQuery(value : JValue, func : RoundTripHandlerFunc) : Unit = {
     val jsondata = selectAllPersons().map { p => ( ("id" -> p.id) ~ ("name" -> p.name) ~ ("age" -> p.age)  ) }
     func.send( JArray(jsondata) )
  }    
  
  private val roundtrips : List[RoundTripInfo] = List("personsQuery" -> personsQuery _)
  abstract override def getRoundTrips = super.getRoundTrips ++ roundtrips  
}  

We will in the following examples add additional round trip functions, for handling create, update and delete operations.


Example 4:

Building on example 3 we are now adding the ability to create and add new Person data to the back-end store and display it in the grid by doing a roundtrip to the server to add the Person data and then update the grid with the new data.

The view bindings

Below is the html scoped by the person grid controller. It has grown a bit compared with example 3 as we now add some, bootstrap styled, input fields and a button to submit the field values. The interesting and new Angular stuff, compared to example 3, is the data-ng-model and the data-ng-click bindings on input and button.

index.html
 ⋮
<div data-ng-controller="PersonGridCtrl">
   <div class="gridStyle" data-ui-grid="simpleGridOptions"></div>
   <div class="form-inline" role="form">
      <div class="form-group">
         <input type="hidden" id="pid" data-ng-model="person.id">
         <label class="sr-only" for="pname">Name</label> 
         <input type="text" class="form-control" id="pname" placeholder="Enter name" data-ng-model="person.name">
      </div>
      <div class="form-group">
         <label class="sr-only" for="page">Age</label> 
         <input type="number" class="form-control" id="page" placeholder="Enter age" data-ng-model="person.age">
      </div>
         <button class="btn btn-default" data-ng-click='doAdd()'>Add</button>
      </div>
   </div> 
</div>
 ⋮

The controller

Added to the controller compared to example 3 is the $scope.doAdd function where in we now also is sending json data from the client to the server via the round trip function addPersonCmd. Then when data has been inserted into the back-end store we also make sure the grid is updated with the newly added data.

personGridCtrl.js
app.controller('PersonGridCtrl',function($scope) {
	
  $scope.myData = []
	
  $scope.doPopulate = function() {	
    var promise = myRTFunctions.personsQuery();  //Round trip call to Lift server function
    return promise.then(function(data) {
      $scope.$apply(function() {
        $scope.myData = data; 
      })
      return data;
    });	 
  };
  $scope.doPopulate();
    
  $scope.doAdd = function() {
    var json = angular.toJson($scope.person);
    var promise = myRTFunctions.addPersonCmd(json); //send the data to the server
    $scope.resetPersonFields();
	return promise.then(function(data) {
	   $scope.$apply(function() { 
          if(data.inserted) {	
             $scope.doPopulate();
          }
	   })
	   return data;
    });	
  };  
    
  $scope.resetPersonFields = function() {
    $scope.person = {id: 0, name: "", age: ""};        
  }
  $scope.resetPersonFields();        
  
  $scope.simpleGridOptions = { 
    data: 'myData',
    enableRowSelection: false,       
    columnDefs: [{field: 'id', visible: false},
                 {field: 'name', displayName: 'Name'}, 
                 {field:'age', displayName:'Age'}]
  };
    
}]);

Lift round trip bindings

Here we add the addPersonCmd function to the RoundTripInfo object list and the addPersonCmd function where we extract the input value before we put it into the Person case class and inserts it into the back-end store.

PersonRT.scala
 ⋮
trait PersonRT extends EmptyRoundTrip with PersonComponent {
  
  protected def personsQuery(value : JValue, func : RoundTripHandlerFunc) : Unit = {
     val jsondata = selectAllPersons().map { p => ( ("id" -> p.id) ~ ("name" -> p.name) ~ ("age" -> p.age)  ) } 
     func.send( JArray(jsondata) )
  }    
  
  protected def addPersonCmd(value : JValue, func : RoundTripHandlerFunc) : Unit = {
    implicit val formats = DefaultFormats   
    val pval = parse(value.extract[String])
    val person = pval.extract[Person]
    insertPerson(person)
    val retStaus = ("inserted" -> true)
    func.send(retStaus)
  }
  
  private val roundtrips : List[RoundTripInfo] = List("personsQuery" -> personsQuery _,                                                
                                                      "addPersonCmd" -> addPersonCmd _)
  abstract override def getRoundTrips = super.getRoundTrips ++ roundtrips  
} 

Handling errors

In the example code above we are not handling errors we are simply assuming that everything is okay and are sending back a property of "inserted" set to true as return status.


Example 5:

Building on example 3 and 4 we now also add excel like cell edit operations. We also add a discrete delete button column for entity delete operations and as before we have the add function.

CRUD operations

To edit grid data, double click on a name or age cell, "end of edit" will trigger a entity update. Add and delete operations should be self-explaining.

The view bindings

Below is the html for example 5 scoped by the person controller, the only difference from example 4 is a change in grid options from ui-grid="simpleGridOptions" to ui-grid="gridOptions"

index.html
 ⋮
<div data-ng-controller="PersonGridCtrl">
   <div class="gridStyle" data-ui-grid="gridOptions"></div>
   <div class="form-inline" role="form">
      <div class="form-group">
         <input type="hidden" id="pid" data-ng-model="person.id">
         <label class="sr-only" for="pname">Name</label> 
         <input type="text" class="form-control" id="pname" placeholder="Enter name" data-ng-model="person.name">
      </div>
      <div class="form-group">
         <label class="sr-only" for="page">Age</label> 
         <input type="number" class="form-control" id="page" placeholder="Enter age" data-ng-model="person.age">
      </div>
         <button class="btn btn-default" data-ng-click='doAdd()'>Add</button>
      </div>
   </div> 
</div>
 ⋮

The controller

Below is the controller handling the CRUD operations on the ui-grid.

personGridCtrl.js
app.controller('PersonGridCtrl',['$scope','$modal',function($scope,$modal) {
	
  $scope.myData = []
	
  $scope.doPopulate = function() {	
    var promise = myRTFunctions.personsQuery();  //Round trip call to Lift server function
    return promise.then(function(data) {
      $scope.$apply(function() {
        $scope.myData = data; 
      })
      return data;
    });	 
  };
  $scope.doPopulate();
    
  $scope.doAdd = function() {
    var json = angular.toJson($scope.person);
    var promise = myRTFunctions.addPersonCmd(json); //send the data to the server 
    $scope.resetPersonFields();
	return promise.then(function(data) {
	   $scope.$apply(function() { 
          if(data.inserted) {	
             $scope.doPopulate();
          }
	   })
	   return data;
    });	
  };  

  $scope.remove = function(entity) {    
    var modalInstance = $scope.openDeletePersonAnswerModal(entity);
    modalInstance.result.then(function (answer) {
        if(answer=='ok'){
          var json = angular.toJson(entity);
          var promise = myRTFunctions.deletePersonCmd(json);
          return promise.then(function(data) {
             $scope.$apply(function() {
        		if(data.deleted){
        		  $scope.doPopulate();	
        		}
        	 })
             return data;
          });         		
        }
    }, function (reason) {
      ;//modal dialog dismissed via cancel button, do noting
    });
  }    

  $scope.openDeletePersonAnswerModal = function(entity){
    return $modal.open({
    	     templateUrl: 'myModalTemplate.html',
    	     controller: 'PersonModalInstCtrl',
    	     size: 'sm',
    	     resolve: {
    	       entity: function () {
    	         return entity;
    	       }
    	     }    	    
           });    	
  }
    
  $scope.resetPersonFields = function() {
    $scope.person = {id: 0, name: "", age: ""};        
  }        
  $scope.resetPersonFields();
  
  $scope.updatePerson = function(entity) {
     var json = angular.toJson(entity);
     var promise = myRTFunctions.updatePersonCmd(json);
	 return promise.then(function(data) {
	   $scope.$apply(function() {
		   if(data.updated){
              $scope.doPopulate();		        	
		   }
		})
		return data;
	 });     	
  }
    
  //a ui-grid callback event that can be used to handle cell edit updates
  $scope.$on('uiGridEventEndCellEdit', function(evt){
     //Detect changes and send entity to server 
     $scope.updatePerson(evt.targetScope.row.entity)     
  });    
  
  
  $scope.gridOptions = { 
        data: 'myData',      
        enableRowSelection: false,
        multiSelect: false,        
        enableCellSelection: true,   
        columnDefs: [{
            field: 'id', 
            visible: false
        },{
            field: 'name', 
            displayName: 'Name', 
            enableCellEdit: true
        },{
            field:'age', 
            displayName:'Age', 
            enableCellEdit: true
        },{ 
            field: 'remove',
            displayName: 'Delete',
            cellTemplate: '<button type="button" data-ng-model="row.entity.remove"  
                           data-ng-click="remove(row.entity)" class="close"  
                           style="float:left;margin-left:5px;color: darkred;opacity: 0.3;">
                           <span aria-hidden="true">×</span>
                           <span class="sr-only">Remove</span>
                           </button>',
            width: '55px'
        }]
  };  
    
}]);

Lift round trip bindings

Below is the PersonRT trait handling the CRUD operations.

PersonRT.scala
 ⋮
trait PersonRT extends EmptyRoundTrip with PersonComponent {
  
  protected def personsQuery(value : JValue, func : RoundTripHandlerFunc) : Unit = {
     val jsondata = selectAllPersons().map { p => ( ("id" -> p.id) ~ ("name" -> p.name) ~ ("age" -> p.age)  ) } 
     func.send( JArray(jsondata) )
  }    
  
  protected def addPersonCmd(value : JValue, func : RoundTripHandlerFunc) : Unit = {
    implicit val formats = DefaultFormats   
    val pval = parse(value.extract[String])
    val person = pval.extract[Person]
    insertPerson(person)
    val retStaus = ("inserted" -> true)
    func.send(retStaus)
  }
  
  protected def updatePersonCmd(value : JValue, func : RoundTripHandlerFunc) : Unit = {
    implicit val formats = DefaultFormats   
    val pval = parse(value.extract[String])
    val person = pval.extract[Person]
    updatePerson(person)
    val retStaus = ("updated" -> true)
    func.send(retStaus)    
  } 
  
  protected def deletePersonCmd(value : JValue, func : RoundTripHandlerFunc) : Unit = {
    implicit val formats = DefaultFormats   
    val pval = parse(value.extract[String])
    val person = pval.extract[Person] 
    deletePerson(person)
    val retStaus = ("deleted" -> true)
    func.send(retStaus)    
  } 
    
  private val roundtrips : List[RoundTripInfo] = List("personsQuery" -> personsQuery _,                                                
                                                      "addPersonCmd" -> addPersonCmd _,
                                                      "updatePersonCmd" -> updatePersonCmd _,
                                                      "deletePersonCmd" -> deletePersonCmd _)
  abstract override def getRoundTrips = super.getRoundTrips ++ roundtrips  
} 

Example 6:

Building on example 3 - 5, while the view stays the same as in example 5 we have now done some refactoring and moved out the back-end calls (the round trip calls) from the PersonGridCtrl into a PersonFactory service. The PersonFactory is a angular factory service provider . This separation of concerns has also made it easy to add some simple client side cashing to avoid unnecessary server calls.

The controller

Here we have injected the PersonFactory service and replaced the round trip calls with calls to the PersonFactory factory service.

personGridCtrl.js
app.controller('PersonGridCtrl',['$scope','$modal','PersonFactory',
               function($scope,$modal,PersonFactory) {
	
  $scope.myData = []
	
  $scope.doPopulate = function() {	
    var promise = PersonFactory.personsQuery();//Using factory service to handle back-end calls.
    return promise.then(function(data) {
      $scope.$apply(function() {
        $scope.myData = data; 
      })
      return data;
    });	 
  };
  $scope.doPopulate();
    
  $scope.doAdd = function() {
    var json = angular.toJson($scope.person);
    var promise = PersonFactory.addPersonCmd(json);//Using factory service to handle back-end calls.
    $scope.resetPersonFields();
	return promise.then(function(data) {
	   $scope.$apply(function() { 
          if(data.inserted) {	
             $scope.doPopulate();
          }
	   })
	   return data;
    });	
  };  

  $scope.remove = function(entity) {    
    var modalInstance = $scope.openDeletePersonAnswerModal(entity);
    modalInstance.result.then(function (answer) {
        if(answer=='ok'){
          var json = angular.toJson(entity);
          var promise = PersonFactory.deletePersonCmd(json);//Using factory service to handle back-end calls.
          return promise.then(function(data) {
             $scope.$apply(function() {
        		if(data.deleted){
        		  $scope.doPopulate();	
        		}
        	 })
             return data;
          });         		
        }
    }, function (reason) {
      ;//modal dialog dismissed via cancel button, do noting
    });
  }    

  $scope.openDeletePersonAnswerModal = function(entity){
    return $modal.open({
    	     templateUrl: 'myModalTemplate.html',
    	     controller: 'PersonModalInstCtrl',
    	     size: 'sm',
    	     resolve: {
    	       entity: function () {
    	         return entity;
    	       }
    	     }    	    
           });    	
  }
    
  $scope.resetPersonFields = function() {
    $scope.person = {id: 0, name: "", age: ""};        
  }        
  $scope.resetPersonFields();
  
  $scope.updatePerson = function(entity) {
     var json = angular.toJson(entity);
     var promise = PersonFactory.updatePersonCmd(json);//Using factory service to handle back-end calls.
	 return promise.then(function(data) {
	   $scope.$apply(function() {
		   if(data.updated){
              $scope.doPopulate();		        	
		   }
		})
		return data;
	 });     	
  }
    
  //a ui-grid callback event that can be used to handle cell edit updates
  $scope.$on('uiGridEventEndCellEdit', function(evt){
     //Detect changes and send entity to server 
     $scope.updatePerson(evt.targetScope.row.entity)     
  });    
  
  
  $scope.gridOptions = { 
        data: 'myData',      
        enableRowSelection: false,
        multiSelect: false,        
        enableCellSelection: true,   
        columnDefs: [{
            field: 'id', 
            visible: false
        },{
            field: 'name', 
            displayName: 'Name', 
            enableCellEdit: true
        },{
            field:'age', 
            displayName:'Age', 
            enableCellEdit: true
        },{ 
            field: 'remove',
            displayName: 'Delete',
            cellTemplate: '<button type="button" data-ng-model="row.entity.remove"  
                           data-ng-click="remove(row.entity)" class="close"  
                           style="float:left;margin-left:5px;color: darkred;opacity: 0.3;">
                           <span aria-hidden="true">×</span>
                           <span class="sr-only">Remove</span>
                           </button>',
            width: '55px'
        }]
  };  
    
}]);

The factory service

Below is the PersonFactory factory service that now handles the person round trip server calls. To avoide some unnecessary server calls we also add cashing abilities to the service.

personFactory.js
app.factory('PersonFactory',function() {
	
	var factory = {}; 
	var cacheData=null;
	var invalidateCashe=false;
	
	factory.personsQuery = function() {
		if(cacheData!=null && isCashInvalidated()==false){
		  return cacheData;
		}else{
		  var promise = myRTFunctions.personsQuery(); 
		  setCasheInvalidated(false);
		  return cacheData = promise;
		}
	}
	
	factory.deletePersonCmd = function(json){
		var promise = myRTFunctions.deletePersonCmd(json);
		setCasheInvalidated(true);
		return promise;
	}
	
	factory.addPersonCmd = function(json){
		var promise = myRTFunctions.addPersonCmd(json);
		setCasheInvalidated(true);
		return promise;
	}
	
	factory.updatePersonCmd = function(json){
		var promise = myRTFunctions.updatePersonCmd(json);
		setCasheInvalidated(true);
		return promise;
	}	
	
	setCasheInvalidated = function(inv){
		invalidateCashe=inv;
	}
	
	isCashInvalidated = function(){
		return invalidateCashe;
	}	
	
    return factory;
});

This completes the Lift round trip example series as we now have CRUD operations on the ui-grid and some client side data cashing and a good separation of concerns. Now that you have seen a small part of how well Angular and Lift fits together you may like to learn more about Angular so here is 50 more Angular examples that start from the basics and beyond. If you are new to Lift visit the liftweb.net and join the community forum .

Improving/extending the examples

From here we could go on and some routing with Angular NgRoute using route controllers and separate things out into Angular templates and probably much more but I'm saving that for a rainy day.




You will find code that goes along with this screen cast above intro To Angular and you will find part 2 of the screen cast here


FoBo setup

The following section describes how to set up the Lift Front-End Toolkit module FoBo to provide AngularJS and it's UI modules, bootstrap and more the way it is set up in this demo.

Starting a new Lift project using FoBo!

If you plan to use FoBo in a new project I can recommend using the lift-advanced-bs3 Template Application as a starting point then most of the setup presented in this section will be done for you.

Dependency settings

Make sure to check for the latest version in the FoBo Github readme .

SBT
"net.liftmodules" %% "fobo_3.1 % "2.0"
Maven
<dependency>
  <groupId>net.liftmodules</groupId>
  <artifactId>fobo_3.1_2.12</artifactId>
  <version>2.0</version>
</dependency>

FoBo and Lift versions

FoBo currently have artifacts for Lift 2.5, 2.6, 3.0 and 3.1. For more information about dependency settings supported toolkits and more, see the FoBo readme on Github .

Lift Boot

One of the benefit you get from using the FoBo module is that you have a easy up/down-grade cycle with a "single point of change", i.e the FoBo init parameter in Lift Boot.
Quick and easy up/down -grade of toolkit resources is provided by FoBo via a "single point of change" as:
  • FoBo simultaneously provides several versions of the included toolkits.
  • FoBo automatically serves minified toolkit resources in production and more easily debuggable resources otherwise.
  • FoBo uses uniform settings to resources in html templates, no need for changes when doing a up-grade/down-grade or running in a dev/test environment vs deploying to a production environment.

FoBo takes care of and provides minified resources in production and more easily debuggable code in development, this is done without any need for code changes and at the same time, by using Fobo, you do not clutter your applications assets space with toolkit library code that should not be directly manipulated any way. By changing a available FoBo toolkit init parameters you can quickly change the version of the front end toolkit's you are using or add another FoBo supported one.

Boot.scala
package bootstrap.liftweb
⋮
import net.liftmodules.fobo
⋮		  
class Boot {
  def boot {	
       ⋮  	  
    //init the FoBo - Front-End Toolkit module,
    //see http://liftweb.net/lift_modules for more info
    fobo.Toolkit.init=fobo.Toolkit.JQuery224
    fobo.Toolkit.init=fobo.Toolkit.Bootstrap337
    fobo.Toolkit.init=fobo.Toolkit.FontAwesome463
    fobo.Toolkit.init=fobo.Toolkit.AngularJS148
    fobo.Toolkit.init=fobo.Toolkit.AJSUIGrid307
    fobo.Toolkit.init=fobo.Toolkit.AJSUIBootstrap0100
    fobo.Toolkit.init=fobo.Toolkit.PrettifyJun2011
    fobo.API.init=fobo.API.FoBo1
       ⋮ 
  } 
}    		  

Lift default template

The most common place to put global references to css and javascript resources in a Lift application is in the default.html template, this project and the example below is set up like that. If you need more flexibility you can put the css/js resource reference in the page body and add the data-lift="head" attribute to it, then when lift processes the page it will be moved into the page head, this could be useful if you have page specific resource needs.
default.html
<!DOCTYPE html>
<html>
<head>
   ⋮
  <!-- instead of manually adding the fobo provided css resources we inject them -->
  <link data-lift="FoBo.Resources.injectCSS?resources=bootstrap,bootstrap-theme,font-awesome,ui-grid,prettify"></link>
   ⋮
</head>		  
<body>
    ⋮
  <div id="content">The main content will get bound here</div>
    ⋮  
  <!-- instead of manually adding the fobo provided javascript resources we inject them -->
  <script data-lift="FoBo.Resources.injectJS?resources=prettify,lang-scala,jquery,bootstrap,angular,angular-animate,ui-bootstrap-tpls,ui-grid"></script>     

  <!-- Your application specific angularJS (controller and services) files -->
  <script src="/assets/js/app.js" type="text/javascript"></script> 
  <script src="/assets/js/controllers/simpleRTCtrl.js" type="text/javascript"></script>
  <script src="/assets/js/controllers/personGridCtrl.js" type="text/javascript"></script>
  <script src="/assets/js/controllers/personModalInstCtrl.js" type="text/javascript"></script>  
  <script src="/assets/js/apputil.js" type="text/javascript"></script> 
   
  ⋮
</body>
</html>
index.html
<body data-lift-content-id="main">
  <div id="main" data-ng-app="foboDemoApp" data-lift="surround?with=default;at=content">
      ⋮
  <span data-lift='embed?what=_embededIndexAJSExamples'>Embedding demo examples section here</span>
     ⋮  
  </div>
</body>

Specific to this demo we are setting the data-ng-app="foboDemoApp" (Angular bootstrap directive) in the same tag as the lift surround tag this means that we have set the root element of the Angular application so that it will surround/include most of the code and it will be available on all demo pages.

You are, in your application, of course free to set the data-ng-app name and directive where ever it fits your needs best.


This Article is Powered by Lift using the FoBo Module
with AngularJS, Bootstrap and Font Awesome
Designed and Developed by Peter Petersson

Lift is Copyright 2007-2015 WorldWide Conferencing, LLC. Distributed under an Apache 2.0 License.