SlideShare ist ein Scribd-Unternehmen logo
1 von 39
ng with
2.0




      Siarzh Miadzvedzeu @siarzh
is

“a web framework for a new era”

                   &

“designed by web developers
 for web developers”
                   Guillaume Bort, creator
0.x   how it was
1.0   how it was
1.2   how it was
2.0   how it was
2.0            is

•   full stack web framework for JVM
•   high-productive
•   asynch & reactive
•   stateless
•   HTTP-centric
•   typesafe
•   scalable
•   open source
•   part of Typesafe Stack 2.0
2.0             is fun and high-productive


•   fast turnaround: change you code and hit reload! :)
•   browser error reporting
•   db evolutions
•   modular and extensible via plugins
•   minimal bootstrap
•   integrated test framework
•   easy cloud deployment (e.g. Heroku)
2.0            is asynch & reactive


•   WebSockets
•   Comet
•   HTTP 1.1 chuncked responses
•   composable streams handling
•   based on event-driven, non-blocking Iteratee I/O
2.0            is HTTP-centric


•   based on HTTP and stateless
•   doesn't fight HTTP or browser
•   clean & easy URL design (via routes)
•   designed to work with HTML5

          “When a web framework starts an architecture fight
          with the web, the framework loses.”
2.0                 is typesafe where it matters




                                      }
•   templates
•   routes
•   configs
•   javascript (via Goggle Closure)
                                          all are compiled
•   LESS stylesheets
•   CoffeeScript

                + browser error reporting
2.0                 getting started

1. download Play 2.0 binary package


2. add play to your PATH:
                              export PATH=$PATH:/path/to/play20


3. create new project:
                              $ play new myFirstApp



4. run the project:
                              $ cd myFirstApp
                              $ play run
2.0                  config
                                             single conf/application.conf:
# This is the main configuration file for the application.

# The application languages
# ~~~~~
application.langs="en"

# Database configuration
# ~~~~~
# You can declare as many datasources as you want.
# By convention, the default datasource is named `default`
#
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
# db.default.user=sa
# db.default.password=

# Evolutions
# ~~~~~
# You can disable evolutions if needed
# evolutionplugin=disabled
...
2.0        IDE support
Eclipse:             $ eclipsify


IntelliJ IDEA:       $ idea



Netbeans:        add to plugins.sbt:
                 resolvers += {
                     "remeniuk repo" at "http://remeniuk.github.com/maven"
                 }


                 libraryDependencies += {
                   "org.netbeans" %% "sbt-netbeans-plugin" % "0.1.4"
                 }



                     $ play netbeans
2.0                  and SBT
              Build.scala                                     plugins.sbt
import sbt._                                  addSbtPlugin("play" % "sbt-plugin" % "2.0")
import Keys._
import PlayProject._

object ApplicationBuild extends Build {

val appName            = "Your application"
val appVersion         = "1.0"

val appDependencies = Seq(
 // Add your project dependencies here,
)

val main = PlayProject(
 appName, appVersion, appDependencies,
  mainLang = SCALA
).settings(
 // Add your own project settings here
)

}
2.0               routes
# Home page
GET /                     controllers.Application.homePage()
GET /home                 controllers.Application.show(page = "home")

# Display a client.
GET /clients/all          controllers.Clients.list()
GET /clients/:id          controllers.Clients.show(id: Long)

# Pagination links, like /clients?page=3
GET /clients              controllers.Clients.list(page: Int ?= 1)

# With regex
GET /orders/$id<[0-9]+>   controllers.Orders.show(id: Long)

# 'name' is all the rest part of the url including '/' symbols
GET /files/*name          controllers.Application.download(name)
2.0                reversed routing

# Hello action
GET /helloBob    controllers.Application.helloBob
GET /hello/:name controllers.Application.hello(name)




// Redirect to /hello/Bob
def helloBob = Action {
  Redirect( routes.Application.hello("Bob") )
}
2.0                   actions
action: (play.api.mvc.Request => play.api.mvc.Result)


Action(parse.text) { request =>
  Ok("<h1>Got: " + request.body + "</h1>").as(HTML).withSession(
                session + ("saidHello" -> "yes")
              ).withHeaders(
                CACHE_CONTROL -> "max-age=3600",
                ETAG -> "xx"
              ).withCookies(
                Cookie("theme", "blue")
              )
}

val   notFound = NotFound
val   pageNotFound = NotFound(<h1>Page not found</h1>)
val   badRequest = BadRequest(views.html.form(formWithErrors))
val   oops = InternalServerError("Oops")
val   anyStatus = Status(488)("Strange response type")
2.0                controllers

package controllers

import play.api.mvc._

object Application extends Controller {

    def index = Action {
      Ok("It works!")
    }

    def hello(name: String) = Action {
      Ok("Hello " + name)
    }

    def goodbye(name: String) = TODO

}
2.0              templates
                                                      …are just functions ;)
views/main.scala.html:     @(title: String)(content: Html)
                           <!DOCTYPE html>
                           <html>
                           <head>
                            <title>@title</title>
                           </head>
                           <body>
                            <section class="content">@content</section>
                           </body>
                           </html>

views/hello.scala.html:    @(name: String = “Guest”)

                           @main(title = "Home") {
                           <h1>Welcome @name! </h1>
                           }




then from Scala class:     val html = views.html.Application.hello(name)
2.0                database evolutions
conf/evolutions/${x}.sql:


        # Add Post

        # --- !Ups
        CREATE TABLE Post (
          id bigint(20) NOT NULL AUTO_INCREMENT,
          title varchar(255) NOT NULL,
          content text NOT NULL,
          postedAt date NOT NULL,
          author_id bigint(20) NOT NULL,
          FOREIGN KEY (author_id) REFERENCES User(id),
          PRIMARY KEY (id)
        );

        # --- !Downs
        DROP TABLE Post;
2.0   browser error reporting
2.0                   access SQL data via Anorm
import anorm._

DB.withConnection { implicit c =>

    val selectCountries = SQL("Select * from Country")

    // Transform the resulting Stream[Row] to a List[(String,String)]
    val countries = selectCountries().map(row =>
      row[String]("code") -> row[String]("name")
    ).toList
}


// using Parser API
import anorm.SqlParser._

val count: Long = SQL("select count(*) from Country").as(scalar[Long].single)

val result:List[String~Int] = {
  SQL("select * from Country")
  .as(get[String]("name")~get[Int]("population") map { case n~p => (n,p) } *)
}
2.0                     and Akka
 def index = Action {          // using actors, coverting Akka Future to Play Promise
  Async {
         (myActor ? "hello").mapTo[String].asPromise.map { response =>
           Ok(response)
         }
     }
 }


 def index = Action {          // execute some task asynchronously
  Async {
         Akka.future { longComputation() }.map { result =>
           Ok("Got " + result)
         }
     }
 }

// schedule sending message 'tick' to testActor every 30 minutes
Akka.system.scheduler.schedule(0 seconds, 30 minutes, testActor, "tick")

// schedule a single task
Akka.system.scheduler.scheduleOnce(10 seconds) {
  file.delete()
}
2.0                 streaming response
def index = Action {

    val file = new java.io.File("/tmp/fileToServe.pdf")
    val fileContent: Enumerator[Array[Byte]] = Enumerator.fromFile(file)

  SimpleResult(
    header = ResponseHeader(200, Map(CONTENT_LENGTH ->
file.length.toString)),
        body = fileContent
    )
}


// Play has a helper for the above:


def index = Action {
  Ok.sendFile(new java.io.File("/tmp/fileToServe.pdf"))
}
2.0                  chunked results
def index = Action {

    val data = getDataStream
    val dataContent: Enumerator[Array[Byte]] = Enumerator.fromStream(data)

    ChunkedResult(
     header = ResponseHeader(200),
        chunks = dataContent
    )
}


// Play has a helper for the ChunkedResult above:

Ok.stream(dataContent)
2.0                  Comet
lazy val clock: Enumerator[String] = {
  val dateFormat = new SimpleDateFormat("HH mm ss")

     Enumerator.fromCallback { () =>
       Promise.timeout(Some(dateFormat.format(new Date)), 100 milliseconds)
     }
 }

 def liveClock = Action {
   Ok.stream(clock &> Comet(callback = "parent.clockChanged"))
 }

and then in template:
<script type="text/javascript" charset="utf-8">
 var clockChanged = function(time) { // do something }
</script>

<iframe id="comet" src="@routes.Application.liveClock.unique"></iframe>
2.0             WebSockets

just another action in controller:

   def index = WebSocket.using[String] { request =>

       // Log events to the console
       val in = Iteratee.foreach[String](println).mapDone { _ =>
         println("Disconnected")
       }

       // Send a single 'Hello!' message
       val out = Enumerator("Hello!")

       (in, out)
   }
2.0   caching API
      by default uses EHCache, configurable via plugins


      Cache.set("item.key", connectedUser)


      val user: User = Cache.getOrElse[User]("item.key") {
        User.findById(connectedUser)
      }



      // cache HTTP response
      def index = Cached("homePage",600) {
        Action {
          Ok("Hello world")
        }
      }
2.0                 i18n

conf/application.conf:   application.langs="en,en-US,fr"


conf/messages.en:
                         home.title=File viewer
                         files.summary=The disk {1} contains {0} file(s).



 from Scala class:       val title   = Messages("home.title")
                         val titleFR = Messages("home.title")(Lang(“fr"))
                         val summary = Messages("files.summary", d.files.length, d.name)

  from template:         <h1>@Messages("home.title")</h1>
2.0                        testing
                                                   …using specs2 by default
"Computer model" should {


    "be retrieved by id" in {
     running(FakeApplication(additionalConfiguration = inMemoryDatabase())) {

            val Some(macintosh) = Computer.findById(21)


            macintosh.name must equalTo("Macintosh")
            macintosh.introduced must beSome.which(dateIs(_, "1984-01-24"))

        }
    }
}
2.0             testing templates


 "render index template" in {
   val html = views.html.index("Coco")

     contentType(html) must equalTo("text/html")
     contentAsString(html) must contain("Hello Coco")
 }
2.0               testing controllers


"respond to the index Action" in {
  val result = controllers.Application.index("Bob")(FakeRequest())

    status(result) must equalTo(OK)
    contentType(result) must beSome("text/html")
    charset(result) must beSome("utf-8")
    contentAsString(result) must contain("Hello Bob")
}
2.0              testing routes

"respond to the index Action" in {
  val Some(result) = routeAndCall(FakeRequest(GET, "/Bob"))

    status(result) must equalTo(OK)
    contentType(result) must beSome("text/html")
    charset(result) must beSome("utf-8")
    contentAsString(result) must contain("Hello Bob")
}
2.0               testing server


"run in a server" in {
  running(TestServer(3333)) {

        await( WS.url("http://localhost:3333").get ).status must equalTo(OK)

    }
}
2.0                 testing with browser
                                 …using Selenium WebDriver with FluentLenium

"run in a browser" in {
    running(TestServer(3333), HTMLUNIT) { browser =>

        browser.goTo("http://localhost:3333")
        browser.$("#title").getTexts().get(0) must equalTo("Hello Guest")

        browser.$("a").click()

        browser.url must equalTo("http://localhost:3333/Coco")
        browser.$("#title").getTexts().get(0) must equalTo("Hello Coco")


    }
}
2.0   Demo
2.0         Resources

http://www.playframework.org/
https://github.com/playframework/Play20/wiki
http://www.parleys.com/#st=5&id=3143&sl=4
http://www.parleys.com/#st=5&id=3144&sl=13
http://www.parleys.com/#id=3081&st=5
http://vimeo.com/41094673
2.0   Questions


         ?

Weitere ähnliche Inhalte

Was ist angesagt?

Was ist angesagt? (20)

Presentation
PresentationPresentation
Presentation
 
Python Code Camp for Professionals 1/4
Python Code Camp for Professionals 1/4Python Code Camp for Professionals 1/4
Python Code Camp for Professionals 1/4
 
WebGUI Developers Workshop
WebGUI Developers WorkshopWebGUI Developers Workshop
WebGUI Developers Workshop
 
Intro to-ant
Intro to-antIntro to-ant
Intro to-ant
 
Play vs Rails
Play vs RailsPlay vs Rails
Play vs Rails
 
Ant
AntAnt
Ant
 
OSCON Google App Engine Codelab - July 2010
OSCON Google App Engine Codelab - July 2010OSCON Google App Engine Codelab - July 2010
OSCON Google App Engine Codelab - July 2010
 
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
 
HTML5 JavaScript APIs
HTML5 JavaScript APIsHTML5 JavaScript APIs
HTML5 JavaScript APIs
 
A Little Backbone For Your App
A Little Backbone For Your AppA Little Backbone For Your App
A Little Backbone For Your App
 
Node.js in action
Node.js in actionNode.js in action
Node.js in action
 
Python Code Camp for Professionals 2/4
Python Code Camp for Professionals 2/4Python Code Camp for Professionals 2/4
Python Code Camp for Professionals 2/4
 
Burn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websitesBurn down the silos! Helping dev and ops gel on high availability websites
Burn down the silos! Helping dev and ops gel on high availability websites
 
Keeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and WebpackKeeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and Webpack
 
CodeIgniter 3.0
CodeIgniter 3.0CodeIgniter 3.0
CodeIgniter 3.0
 
REST with Eve and Python
REST with Eve and PythonREST with Eve and Python
REST with Eve and Python
 
Scala active record
Scala active recordScala active record
Scala active record
 
Phoenix + Reactで 社内システムを 密かに作ってる
Phoenix + Reactで 社内システムを 密かに作ってるPhoenix + Reactで 社内システムを 密かに作ってる
Phoenix + Reactで 社内システムを 密かに作ってる
 
Filling the flask
Filling the flaskFilling the flask
Filling the flask
 
A re introduction to webpack - reactfoo - mumbai
A re introduction to webpack - reactfoo - mumbaiA re introduction to webpack - reactfoo - mumbai
A re introduction to webpack - reactfoo - mumbai
 

Ähnlich wie Play!ng with scala

Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
Yehuda Katz
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on Android
Sven Haiges
 
Overview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web FrameworkOverview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web Framework
IndicThreads
 
Scala based Lift Framework
Scala based Lift FrameworkScala based Lift Framework
Scala based Lift Framework
vhazrati
 

Ähnlich wie Play!ng with scala (20)

Html5 For Jjugccc2009fall
Html5 For Jjugccc2009fallHtml5 For Jjugccc2009fall
Html5 For Jjugccc2009fall
 
Refresh Austin - Intro to Dexy
Refresh Austin - Intro to DexyRefresh Austin - Intro to Dexy
Refresh Austin - Intro to Dexy
 
Using and scaling Rack and Rack-based middleware
Using and scaling Rack and Rack-based middlewareUsing and scaling Rack and Rack-based middleware
Using and scaling Rack and Rack-based middleware
 
5.node js
5.node js5.node js
5.node js
 
Ruby MVC from scratch with Rack
Ruby MVC from scratch with RackRuby MVC from scratch with Rack
Ruby MVC from scratch with Rack
 
Paris js extensions
Paris js extensionsParis js extensions
Paris js extensions
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
 
Codegnitorppt
CodegnitorpptCodegnitorppt
Codegnitorppt
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on Android
 
Play 2.0
Play 2.0Play 2.0
Play 2.0
 
Web Components With Rails
Web Components With RailsWeb Components With Rails
Web Components With Rails
 
Overview Of Lift Framework
Overview Of Lift FrameworkOverview Of Lift Framework
Overview Of Lift Framework
 
Overview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web FrameworkOverview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web Framework
 
Scala based Lift Framework
Scala based Lift FrameworkScala based Lift Framework
Scala based Lift Framework
 
[Coscup 2012] JavascriptMVC
[Coscup 2012] JavascriptMVC[Coscup 2012] JavascriptMVC
[Coscup 2012] JavascriptMVC
 
Nodejs Explained with Examples
Nodejs Explained with ExamplesNodejs Explained with Examples
Nodejs Explained with Examples
 
Nodejsexplained 101116115055-phpapp02
Nodejsexplained 101116115055-phpapp02Nodejsexplained 101116115055-phpapp02
Nodejsexplained 101116115055-phpapp02
 
Xitrum Web Framework Live Coding Demos / Xitrum Web Framework ライブコーディング
Xitrum Web Framework Live Coding Demos / Xitrum Web Framework ライブコーディングXitrum Web Framework Live Coding Demos / Xitrum Web Framework ライブコーディング
Xitrum Web Framework Live Coding Demos / Xitrum Web Framework ライブコーディング
 
Xitrum @ Scala Matsuri Tokyo 2014
Xitrum @ Scala Matsuri Tokyo 2014Xitrum @ Scala Matsuri Tokyo 2014
Xitrum @ Scala Matsuri Tokyo 2014
 
Writing HTML5 Web Apps using Backbone.js and GAE
Writing HTML5 Web Apps using Backbone.js and GAEWriting HTML5 Web Apps using Backbone.js and GAE
Writing HTML5 Web Apps using Backbone.js and GAE
 

Kürzlich hochgeladen

Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
Joaquim Jorge
 

Kürzlich hochgeladen (20)

TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of Brazil
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 

Play!ng with scala

  • 1. ng with 2.0 Siarzh Miadzvedzeu @siarzh
  • 2. is “a web framework for a new era” & “designed by web developers for web developers” Guillaume Bort, creator
  • 3. 0.x how it was
  • 4. 1.0 how it was
  • 5. 1.2 how it was
  • 6. 2.0 how it was
  • 7. 2.0 is • full stack web framework for JVM • high-productive • asynch & reactive • stateless • HTTP-centric • typesafe • scalable • open source • part of Typesafe Stack 2.0
  • 8. 2.0 is fun and high-productive • fast turnaround: change you code and hit reload! :) • browser error reporting • db evolutions • modular and extensible via plugins • minimal bootstrap • integrated test framework • easy cloud deployment (e.g. Heroku)
  • 9. 2.0 is asynch & reactive • WebSockets • Comet • HTTP 1.1 chuncked responses • composable streams handling • based on event-driven, non-blocking Iteratee I/O
  • 10. 2.0 is HTTP-centric • based on HTTP and stateless • doesn't fight HTTP or browser • clean & easy URL design (via routes) • designed to work with HTML5 “When a web framework starts an architecture fight with the web, the framework loses.”
  • 11. 2.0 is typesafe where it matters } • templates • routes • configs • javascript (via Goggle Closure) all are compiled • LESS stylesheets • CoffeeScript + browser error reporting
  • 12. 2.0 getting started 1. download Play 2.0 binary package 2. add play to your PATH: export PATH=$PATH:/path/to/play20 3. create new project: $ play new myFirstApp 4. run the project: $ cd myFirstApp $ play run
  • 13. 2.0 config single conf/application.conf: # This is the main configuration file for the application. # The application languages # ~~~~~ application.langs="en" # Database configuration # ~~~~~ # You can declare as many datasources as you want. # By convention, the default datasource is named `default` # db.default.driver=org.h2.Driver db.default.url="jdbc:h2:mem:play" # db.default.user=sa # db.default.password= # Evolutions # ~~~~~ # You can disable evolutions if needed # evolutionplugin=disabled ...
  • 14. 2.0 IDE support Eclipse: $ eclipsify IntelliJ IDEA: $ idea Netbeans: add to plugins.sbt: resolvers += { "remeniuk repo" at "http://remeniuk.github.com/maven" } libraryDependencies += { "org.netbeans" %% "sbt-netbeans-plugin" % "0.1.4" } $ play netbeans
  • 15. 2.0 and SBT Build.scala plugins.sbt import sbt._ addSbtPlugin("play" % "sbt-plugin" % "2.0") import Keys._ import PlayProject._ object ApplicationBuild extends Build { val appName = "Your application" val appVersion = "1.0" val appDependencies = Seq( // Add your project dependencies here, ) val main = PlayProject( appName, appVersion, appDependencies, mainLang = SCALA ).settings( // Add your own project settings here ) }
  • 16. 2.0 routes # Home page GET / controllers.Application.homePage() GET /home controllers.Application.show(page = "home") # Display a client. GET /clients/all controllers.Clients.list() GET /clients/:id controllers.Clients.show(id: Long) # Pagination links, like /clients?page=3 GET /clients controllers.Clients.list(page: Int ?= 1) # With regex GET /orders/$id<[0-9]+> controllers.Orders.show(id: Long) # 'name' is all the rest part of the url including '/' symbols GET /files/*name controllers.Application.download(name)
  • 17. 2.0 reversed routing # Hello action GET /helloBob controllers.Application.helloBob GET /hello/:name controllers.Application.hello(name) // Redirect to /hello/Bob def helloBob = Action { Redirect( routes.Application.hello("Bob") ) }
  • 18. 2.0 actions action: (play.api.mvc.Request => play.api.mvc.Result) Action(parse.text) { request => Ok("<h1>Got: " + request.body + "</h1>").as(HTML).withSession( session + ("saidHello" -> "yes") ).withHeaders( CACHE_CONTROL -> "max-age=3600", ETAG -> "xx" ).withCookies( Cookie("theme", "blue") ) } val notFound = NotFound val pageNotFound = NotFound(<h1>Page not found</h1>) val badRequest = BadRequest(views.html.form(formWithErrors)) val oops = InternalServerError("Oops") val anyStatus = Status(488)("Strange response type")
  • 19. 2.0 controllers package controllers import play.api.mvc._ object Application extends Controller { def index = Action { Ok("It works!") } def hello(name: String) = Action { Ok("Hello " + name) } def goodbye(name: String) = TODO }
  • 20. 2.0 templates …are just functions ;) views/main.scala.html: @(title: String)(content: Html) <!DOCTYPE html> <html> <head> <title>@title</title> </head> <body> <section class="content">@content</section> </body> </html> views/hello.scala.html: @(name: String = “Guest”) @main(title = "Home") { <h1>Welcome @name! </h1> } then from Scala class: val html = views.html.Application.hello(name)
  • 21. 2.0 database evolutions conf/evolutions/${x}.sql: # Add Post # --- !Ups CREATE TABLE Post ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, content text NOT NULL, postedAt date NOT NULL, author_id bigint(20) NOT NULL, FOREIGN KEY (author_id) REFERENCES User(id), PRIMARY KEY (id) ); # --- !Downs DROP TABLE Post;
  • 22. 2.0 browser error reporting
  • 23. 2.0 access SQL data via Anorm import anorm._ DB.withConnection { implicit c => val selectCountries = SQL("Select * from Country") // Transform the resulting Stream[Row] to a List[(String,String)] val countries = selectCountries().map(row => row[String]("code") -> row[String]("name") ).toList } // using Parser API import anorm.SqlParser._ val count: Long = SQL("select count(*) from Country").as(scalar[Long].single) val result:List[String~Int] = { SQL("select * from Country") .as(get[String]("name")~get[Int]("population") map { case n~p => (n,p) } *) }
  • 24. 2.0 and Akka def index = Action { // using actors, coverting Akka Future to Play Promise Async { (myActor ? "hello").mapTo[String].asPromise.map { response => Ok(response) } } } def index = Action { // execute some task asynchronously Async { Akka.future { longComputation() }.map { result => Ok("Got " + result) } } } // schedule sending message 'tick' to testActor every 30 minutes Akka.system.scheduler.schedule(0 seconds, 30 minutes, testActor, "tick") // schedule a single task Akka.system.scheduler.scheduleOnce(10 seconds) { file.delete() }
  • 25. 2.0 streaming response def index = Action { val file = new java.io.File("/tmp/fileToServe.pdf") val fileContent: Enumerator[Array[Byte]] = Enumerator.fromFile(file) SimpleResult( header = ResponseHeader(200, Map(CONTENT_LENGTH -> file.length.toString)), body = fileContent ) } // Play has a helper for the above: def index = Action { Ok.sendFile(new java.io.File("/tmp/fileToServe.pdf")) }
  • 26. 2.0 chunked results def index = Action { val data = getDataStream val dataContent: Enumerator[Array[Byte]] = Enumerator.fromStream(data) ChunkedResult( header = ResponseHeader(200), chunks = dataContent ) } // Play has a helper for the ChunkedResult above: Ok.stream(dataContent)
  • 27. 2.0 Comet lazy val clock: Enumerator[String] = { val dateFormat = new SimpleDateFormat("HH mm ss") Enumerator.fromCallback { () => Promise.timeout(Some(dateFormat.format(new Date)), 100 milliseconds) } } def liveClock = Action { Ok.stream(clock &> Comet(callback = "parent.clockChanged")) } and then in template: <script type="text/javascript" charset="utf-8"> var clockChanged = function(time) { // do something } </script> <iframe id="comet" src="@routes.Application.liveClock.unique"></iframe>
  • 28. 2.0 WebSockets just another action in controller: def index = WebSocket.using[String] { request => // Log events to the console val in = Iteratee.foreach[String](println).mapDone { _ => println("Disconnected") } // Send a single 'Hello!' message val out = Enumerator("Hello!") (in, out) }
  • 29. 2.0 caching API by default uses EHCache, configurable via plugins Cache.set("item.key", connectedUser) val user: User = Cache.getOrElse[User]("item.key") { User.findById(connectedUser) } // cache HTTP response def index = Cached("homePage",600) { Action { Ok("Hello world") } }
  • 30. 2.0 i18n conf/application.conf: application.langs="en,en-US,fr" conf/messages.en: home.title=File viewer files.summary=The disk {1} contains {0} file(s). from Scala class: val title = Messages("home.title") val titleFR = Messages("home.title")(Lang(“fr")) val summary = Messages("files.summary", d.files.length, d.name) from template: <h1>@Messages("home.title")</h1>
  • 31. 2.0 testing …using specs2 by default "Computer model" should { "be retrieved by id" in { running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { val Some(macintosh) = Computer.findById(21) macintosh.name must equalTo("Macintosh") macintosh.introduced must beSome.which(dateIs(_, "1984-01-24")) } } }
  • 32. 2.0 testing templates "render index template" in { val html = views.html.index("Coco") contentType(html) must equalTo("text/html") contentAsString(html) must contain("Hello Coco") }
  • 33. 2.0 testing controllers "respond to the index Action" in { val result = controllers.Application.index("Bob")(FakeRequest()) status(result) must equalTo(OK) contentType(result) must beSome("text/html") charset(result) must beSome("utf-8") contentAsString(result) must contain("Hello Bob") }
  • 34. 2.0 testing routes "respond to the index Action" in { val Some(result) = routeAndCall(FakeRequest(GET, "/Bob")) status(result) must equalTo(OK) contentType(result) must beSome("text/html") charset(result) must beSome("utf-8") contentAsString(result) must contain("Hello Bob") }
  • 35. 2.0 testing server "run in a server" in { running(TestServer(3333)) { await( WS.url("http://localhost:3333").get ).status must equalTo(OK) } }
  • 36. 2.0 testing with browser …using Selenium WebDriver with FluentLenium "run in a browser" in { running(TestServer(3333), HTMLUNIT) { browser => browser.goTo("http://localhost:3333") browser.$("#title").getTexts().get(0) must equalTo("Hello Guest") browser.$("a").click() browser.url must equalTo("http://localhost:3333/Coco") browser.$("#title").getTexts().get(0) must equalTo("Hello Coco") } }
  • 37. 2.0 Demo
  • 38. 2.0 Resources http://www.playframework.org/ https://github.com/playframework/Play20/wiki http://www.parleys.com/#st=5&id=3143&sl=4 http://www.parleys.com/#st=5&id=3144&sl=13 http://www.parleys.com/#id=3081&st=5 http://vimeo.com/41094673
  • 39. 2.0 Questions ?