Let's

all day

Anton Sarov

Warsjawa 2014

About me

Java and Eclipse RCP developer by day

Play Framework developer by night

        @duxanton

Workshop PREREQUIREMENTs

  • JDK 8
  • Play Framework 2.3.x
  • IDE (IntelliJ preferred)

Create a new Play project

$ cd /somewhere/where/you/have/write/permissions
$ activator new ghjm play-java

Anatomy

  • app
  • conf
  • project
  • public
  • test

Launch the Play Application

$ cd ghjm
$ activator run

go to http://localhost:9000

OUr goal for today

Create a really cool job portal web application

But with some limitations (due to time pressure, like in real life)

  • Job search functionality only
  • Simple UI

models, views, CONtrollers

+

Actions & Routes

  • Avaje Ebean
  • JPA
  • JDBC

Working with SQL databases

AVAJE ebean Orm

  • uses JPA annotations
  • but much simpler
  • session less

Creating a model

  1. extend play.db.ebean.Model
  2. @Entity class annotation
  3. create a Model.Finder
  4. add your business logic

Task: create a new model 'user'

hints :

use an id field of type long

Our User model

package models;

import play.db.ebean.Model;
import javax.persistence.*;

@Entity
public class User extends Model {

    public static Finder<Long, User> find = new Finder<>(Long.class, User.class);

    @Id
    private Long id;

    ...
}

The ROUTES FILE

# Homepage

GET    /              controllers.Application.index()

# Display a person

GET   /persons/:id    controllers.Persons.show(id: Long)

let's rest

task: create a REst api for our user model

hints:

define request patterns in the routes file

implement the logic in a new controller (and in the model, if needed)

views & templates

  • Scala-based template engine
  • plain HTML(5)
  • the magic '@' character

task: create an index page with a login form

hints:
action to render the page
a login action to handle the form submission
where should we send the login form data?

our index view

views/index.scala.html

<h1>Welcome to GHJM</h1>

<form action="@routes.Application.login()" method="post">
<input type="text" name="username"/>
<input type="submit" value="Login"/>
</form>

task: create a dashboard page for the logged in user

our dashboard view

@(user: User)

<h1>Welcome @user.getName!</h1>

views/dashboard.scala.html

// retrieve the request body in any controller action
request().body();

// retrieve the response in any controller action
response();

// redirect to some URL
return redirect(routes.Application.dashboard());

actions and controllers

conventions and shortcuts

task: implement logout and session-awareness

// store session data
session("key", "someValue");

// retrieve session data
String value = session("key");

// clear the session
session().clear();

calling webservices

libraryDependencies ++= Seq(
  javaWs
)

in your build.sbt file:

Promise<WSResponse> responsePromise = WS.url("http://warsjawa.pl/").get();

in your code:

using from your controller:

public static Promise<Result> index() {
    return WS.url(someUrl).get().map(response ->
            ok(response.asJson().findPath("name").asText())
    );
}

task: define a new 'get' request with query parameter and a corresponding action which returns a promise

a short excursion to fp & scala

map

Evaluates a function over each element in the list, returning a list with the same number of elements.

scala> val numbers = List(1, 2, 3, 4)

scala> numbers.map((i: Int) => i * 2)
res0: List[Int] = List(2, 4, 6, 8)

task: in the action make a ws request to fetch the jobs and print them on the screen/console

https://jobs.github.com/positions.json?description=java

task: for every job fetch also the coordinates of its location via the google geocode api

return the 'enhanced' job objects

http://maps.googleapis.com/maps/api/geocode/json?address=Warszawa&sensor=false

Webjars

libraryDependencies ++= Seq(
  ...
  javaWs,
  ...
  "org.webjars" % "angularjs" % "1.2.23"
  ...
)
<script src="@routes.Assets.versioned("lib/angularjs/angular.js")"></script>

in your views:

in your build.sbt file:

webjars

libraryDependencies ++= Seq(
  javaJdbc,
  javaEbean,
  cache,
  javaWs,
  "org.webjars" % "angularjs" % "1.2.23",
  "org.webjars" % "angular-leaflet-directive" % "0.7.7",
  "org.webjars" % "leaflet" % "0.7.3"
)

angularjs

  • data binding

  • directives

  • backend

angularjs

@(title: String)(content: Html)

<!DOCTYPE html>

<html ng-app="gitHubJobsMapApp">
    ...
    <body>

	<div>
            <form ng-controller="Search">
            <input type="search" ng-model="query" 
                placeholder="Enter a search term here"/>
            <input type="submit" ng-click="search()" value="Search"/>
            <hr>
            <h1>All search results for {{query}}!</h1>
            </form>
	</div>
    ...
    </body>
</html>

main.index.scala:

angularjs

@(user: User)

@main(user.getName) {

    <div ng-controller="Jobs">
        <leaflet width="100%" height="500px" markers="markers"></leaflet>
        <ul>
        <li ng-repeat="job in jobs">{{job.location}} {{job.coordinates}}</li>
        </ul>
    </div>
}

dashboard.scala.html:

let us be javascript ninjas!

angularjs

var app = angular.module('gitHubJobsMapApp', ["leaflet-directive"]);

app.factory('GHJM', function($http, $timeout) {

    var jobsService = {
        jobs: [],
        query: function (query) {
            $http({method: 'GET', url: '/jobs?q='+query}).
                success(function (data) {
                    jobsService.jobs = data;
                }).
		error(function(data, status, headers, config) {
        		// called asynchronously if an error occurs
			// or server returns response with an error status.
		});
        }
    };

    return jobsService;
});

application.js:

angularjs

app.controller('Search', function($scope, $http, $timeout, GHJM) {
    $scope.search = function() {
        GHJM.query($scope.query);
    };
});

app.controller('Jobs', function($scope, $http, $timeout, GHJM) {
    $scope.jobs = [];
    $scope.markers = [];

    $scope.$watch(
        // This function returns the value being watched.
        function() {
            return GHJM.jobs;
        },
        // This is the change listener, called when the value returned from the above function changes
        function(jobs) {
            $scope.jobs = jobs;
            $scope.markers = jobs.map(function(job) {
                return {
                    lng: job.coordinates.lng,
                    lat: job.coordinates.lat,
                    message: '<a href=\"'+job.url+'\">'+job.title+'</a>',
                    focus: true,
                    draggable: true
                }
            });
        }
    );
});

application.js:

live badass demo

Play Framework

next steps:

  • visit www.playframework.com

  • Work further on this demo project

  • Help translate the documentation: see github.com/antonsarov/translation-project

Thank you

Now ask all the questions :)

Let's Play all day at Warsjawa

By duxanton

Let's Play all day at Warsjawa

Presentation for the Play Framework workshop at the Warsjawa conference

  • 888