Wednesday, July 3, 2013

My Notes: Playframework with MongoDB Part 2 of 2

Configuring MongoDB Plugin for Play

I use https://github.com/vznet/play-mongo-jackson-mapper as my MongoDB plugin for Play.

Open project/target/Build.scala and add this line to appDependencies:

    "net.vz.mongodb.jackson" %% "play-mongo-jackson-mapper" % "1.1.0" 

My Build.scala looks like this:

import sbt._
import Keys._
import play.Project._

object ApplicationBuild extends Build {

  val appName         = "myweb"
  val appVersion      = "1.0-SNAPSHOT"

  val appDependencies = Seq(
    // Add your project dependencies here,
    javaCore,
    javaJdbc,
    javaEbean,
    "net.vz.mongodb.jackson" %% "play-mongo-jackson-mapper" % "1.1.0" 
  )

  val main = play.Project(appName, appVersion, appDependencies).settings(
    // Add your own project settings here      
   resolvers += "Scala-Tools Maven2 Snapshots Repository" at "http://scala-tools.org/repo-snapshots"
  )
}

Open conf/application.conf and add these lines (I put these below Database Configuration section).
Assuming your MongoDB is installed on the same server (localhost) and listening to the default port of 27017 and you have no password setup.

# MongoDB Jackon Mapper configuration
# ~~~~~
# Configure the database name
mongodb.database=mydb
# Configure credentials
#mongodb.credentials="user:pass"
#mongodb.credentials=
# Configure the servers
mongodb.servers="localhost:27017"
# Configure a custom ObjectMapper to use
#mongodb.objectMapperConfigurer=

Now from your Play console do clean and compile and eclipse (to re-load eclipse's project files with this mongodb plugin in the .classpath)
@mbp ~/dev/play/play-mongo$ play
[info] Loading project definition from /Users/elousf/dev/play/play-mongo/project
[info] Set current project to play-mongo (in build file:/Users/elousf/dev/play/play-mongo/)
       _            _
 _ __ | | __ _ _  _| |
| '_ \| |/ _' | || |_|
|  __/|_|\____|\__ (_)
|_|            |__/

play! 2.1.0 (using Java 1.7.0_09 and Scala 2.10.0), http://www.playframework.org

> Type "help play" or "license" for more information.
> Type "exit" or use Ctrl+D to leave this console.

[play-mongo] $ clean
[success] Total time: 0 s, completed Jul 1, 2013 9:30:17 PM
[play-mongo] $ compile
[info] Updating {file:/Users/elousf/dev/play/play-mongo/}play-mongo...
[info] Done updating.                                                                  
[info] Compiling 4 Scala sources and 2 Java sources to /Users/elousf/dev/play/play-mongo/target/scala-2.10/classes...
[success] Total time: 13 s, completed Jul 1, 2013 9:30:32 PM



[play-mongo] $ eclipse
[info] About to create Eclipse project files for your project(s).
[info] Compiling 5 Scala sources and 4 Java sources to /Users/elousf/dev/play/play-mongo/target/scala-2.10/classes...

[info] Successfully created Eclipse project files for project(s):

Create MVC-based Web Page

Let's create a web page that shows detail information about our fish. The format of the url is something like: 
http://localhost:9000/fish/<fish-name> 
<fish-name> is the object id (_id) of the fish stored in MongoDB fakeFish collection (see the previous blog)

3 steps to do this:
  1. create a route to the page in /conf/routes
  2. create a view in /app/views
  3. create a method in a controller, for this we'll create a new controller called FishController
And since we also want physically separating the presentation from the data, the page rendered above does not contain any data but after it loads it makes a web service call to get its data in JSON format and fills the page with the data. I know this may seem very complicated but having to avoid populating the web page with data on server side code (even in MVC model) to me is a very clean separation: let the client code (jquery) do the final rendering. So, for the web service we need:
  1. add an entity object to represent Fish and a helper class to retrieve data from MongoDB
  2. create a route to the ws call in /conf/routes
  3. create a controller to return the data
  4. add javascript (in jquery) on the /fish/<fish-id> page to do ws call and populate the page

Java Class for Fish and MongoDB Util Class

Fish.java


package entity;

import java.util.List;

import javax.persistence.Id;
import play.modules.mongodb.jackson.KeyTyped;


public class Fish implements KeyTyped {
    @Id
    private String fishId; 
    private String name;
    private String latin;
    private List sizeRangeMillimeter;
    private String image;
 
    public String getFishId() {
        return fishId;
    }
    public void setFishId(String fishId) {
        this.fishId = fishId;
    }
    .... getters and setters...
..

MongoUtil.java

package util;

import net.vz.mongodb.jackson.JacksonDBCollection;
import play.modules.mongodb.jackson.MongoDB;
import entity.Fish;


public class MongoUtil {
 public static Fish getFish(String fishId) {
    JacksonDBCollection fishes = MongoDB.getCollection("fakeFish", Fish.class);
    Fish dao = fishes.findOneById(fishId);
    return dao;
  }
}



Create an entry in /conf/routes for the page

Add this line to /conf/routes

GET     /fish/:fishId controllers.FishController.fish(fishId: String)

Create a view in /app/views

Create a new file called showFish.scala.html in /app/views, the content should be like this:

@(fishId: String)
<!DOCTYPE html>

<html lang="en">
<head>
<meta charset="utf-8">
<title>Fish</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">

<!-- Bootstrap -->
<link rel="stylesheet" href="@routes.Assets.at("stylesheets/bootstrap.css")">
<!-- Font awesome icon -->
</head>
<body>
   <div class="container">
    <div class="span4">
    <h1>Fish</h1>
    <h2 id="fishNameTitle">Guppy</h2>
<form class="form-horizontal">
  <div class="control-group">
    <label class="control-label" for="inputFishId">Fish Id</label>
    <div class="controls">
      <input type="text" id="inputFishId" placeholder="fish id">
    </div>
  </div>

  <div class="control-group">
    <label class="control-label" for="inputName">Name</label>
    <div class="controls">
      <input type="text" id="inputName" placeholder="name">
    </div>
  </div>

  <div class="control-group">
    <label class="control-label" for="inputLatin">Scientific Name</label>
    <div class="controls">
      <input type="text" id="inputLatin" placeholder="latin name">
    </div>
  </div>

  <div class="control-group">
    <label class="control-label" for="inputSizeFrom">Size (in mm)</label>
    <div class="controls">
      <input type="text" id="inputSizeFrom" placeholder="">
    </div>
  </div>

  <div class="control-group">
    <label class="control-label" for="inputSizeTo">To (in mm)</label>
    <div class="controls">
      <input type="text" id="inputSizeTo" placeholder="">
    </div>
  </div>

    </form>
    </div>
    </div>
    <script src="@routes.Assets.at("javascripts/jquery-1.9.0.min.js")"></script>
    <script>
     function populateForm(fish) {
         $('#inputFishId').val(fish.fishId);
         $('#inputName').val(fish.name);
         $('#inputLatin').val(fish.latin);
         $('#inputSizeFrom').val(fish.sizeRangeMillimeter[0]);
         $('#inputSizeTo').val(fish.sizeRangeMillimeter[1]);
     }
     
    $(document).ready(function() {
    var fishId = '@fishId';
    var url = "/ws/fish/" + fishId;
        $.ajax({
            headers: { 
                'Accept': 'application/json',
                'Content-Type': 'application/json' 
            },
            type: 'GET',
            url: url,
            data: null,
            dataType: 'json'
        }).done(function(data) {
            console.log("data from server", data);
            populateForm(data);
        }).fail(function() {
        }).always(function() {
        });
    });

    </script>
</body>
</html>


Note that first line @(fishId: String). This is how Play passes arguments (or Model) from a Controller to a View. Again, we want to minimize the html mixing with data as much as possible here. The data will be populated by another REST call and handled by ajax. See the javascript in this view.

Create a method to handle the url request in controller.

A new controller class is create here called FishController.java, and it looks like:

package controllers;

import java.util.LinkedHashMap;

import org.codehaus.jackson.map.ObjectMapper;
package controllers;

import java.util.LinkedHashMap;

import org.codehaus.jackson.map.ObjectMapper;

import play.mvc.Content;
import play.mvc.Controller;
import play.mvc.Result;
import util.MongoUtil;
import entity.Fish;


public class FishController extends Controller {
public static Result showFish(String fishId) {
Content html = views.html.showFish.render(fishId);
return ok(html);
}

public static Result getFish(String fishId) {
Fish fish = MongoUtil.getFish(fishId);
String jsonString = "";
LinkedHashMap<String, Object> result = new LinkedHashMap<String, Object>();
if (fish != null) {
result.put("fishId", fish.getFishId());
result.put("name", fish.getName());
result.put("latin", fish.getLatin());
result.put("sizeRangeMillimeter", fish.getSizeRangeMillimeter());
ObjectMapper om = new ObjectMapper();
try {
jsonString = om.writeValueAsString(result);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return ok(jsonString).as("application/json");
}
}

The first method, showFish(String fishId) is to return showFish.scala.html view we wrote earlier and the second method getFish(String fishId) is to return data about a fish as json.

Create a route for the web service (REST) call in /conf/routes



GET     /fish/:fishId controllers.FishController.showFish(fishId: String)

GET     /ws/fish/:fishId controllers.FishController.getFish(fishId: String)


Controller to handle the REST call

See getFish(String fishId) method above. The method implements the controller. Note that the routes says /ws/fish/:fishId is handled by controllers.FishController.getFish(fishId: String).

Add javascript (in jquery) on the /fish/<fish-id> page to do ws call and populate the page

See showFish.scala.html above that already contains the javascript required to make the ajax call to /ws/fish/:fishId and function to populate the form.


That's all! Happy Play-ing.

No comments: