Step 1 : Design your schema
Let us decide upon the user schema first . A Simple User schema would look like thisTablename => user id long , PRIMARY KEY , AUTOINCREMENT first_name string last_name string mobile long email string |
We will now make use of evolutions to create the table for the above schema
Create a new package evolutions.default under the conf directory. Now add a new file with name “1.sql” that contains the evolution script as below
Step 2 : Define a User Model for the above schema design
Create model package under app folder .
Now create a User.scala class in the model package (app>model>User) and define a case class like below
case class User(id : Long , firstName : String, lastName : String , mobile : Long , email : String)
Step 3 : Implement the data access layer(app.dao)
Add a new package dao inside the app folder.
Add UserDAO trait inside the dao package , with all the required User CRUDs
trait UserDAO { def add(user:User) : Future[String] def get(id : Long) : Future[Option[User]] def delete(id : Long) : Future[Int] def listAll : Future[Seq[User]] }
Implement the UserDAO trait using UserDAOImpl class .
Play uses Google Guice DI framework to handle Dependency Injection . Guice provides an out-of-box implementation that helps to automatically inject required objects at run time .
Since the database is configured using PlaySlick , we can obtain the database configuration inside a dao class using DatabaseConfigProvider trait provided by the PlaySlick module .
Now describe a database schema with Table row classes for the tables and implement the abstract CRUD methods
package dao import javax.inject.Inject import model.User import play.api.db.slick.DatabaseConfigProvider import slick.driver.JdbcProfile import scala.concurrent.Future @Singleton class UserDAOImpl @Inject()(dbConfigProvider: DatabaseConfigProvider) extends UserDAO { private val dbConfig = dbConfigProvider.get[JdbcProfile] import dbConfig._ import driver.api._ class UserTable(tag:Tag) extends Table[User](tag, "user") { def id = column[Long]("id", O.PrimaryKey,O.AutoInc) def firstName = column[String]("first_name") def lastName = column[String]("last_name") def mobile = column[Long]("mobile") def email = column[String]("email") override def * = (id, firstName,lastName,mobile,email) <> (User.tupled, User.unapply) } implicit val users = TableQuery[UserTable] override def add(user: User): Future[String] = { db.run(users += user).map(res => "User successfully added").recover { case ex : Exception => ex.getCause.getMessage } } override def delete(id: Long): Future[Int] = { db.run(users.filter(_.id === id).delete) } override def get(id: Long): Future[Option[User]] = { db.run(users.filter(_.id === id).result.headOption) } override def listAll: Future[Seq[User]] = { db.run(users.result) } }
Now bind the trait to its implementation using Guice . The simplest way to do this using guice is by adding @ImplementedBy annotation to the trait. So your final dao trait should look like
package dao import com.google.inject.ImplementedBy import model.User import scala.concurrent.Future @ImplementedBy(classOf[UserDAOImpl]) trait UserDAO { def add(user:User) : Future[String] def get(id : Long) : Future[Option[User]] def delete(id : Long) : Future[Int] def listAll : Future[Seq[User]] }
[error] E:\MyFirstPlay\app\dao\UserDAOImpl.scala:37: Cannot find an implicit ExecutionContext. You might pass [error] an (implicit ec: ExecutionContext) parameter to your method [error] or import scala.concurrent.ExecutionContext.Implicits.global. [error] db.run(users += user).map(res => "User successfully added").recover { [error] ^ [error] one error found [error] (compile:compileIncremental) Compilation failed
So , to provide an implicit execution , you can do it in two ways :
Import the implicit context using the import statementimport play.api.libs.concurrent.Execution.Implicits._ Or import scala.concurrent.ExecutionContext.Implicits.global
OR Pass an additional parameter to your class definition
class UserDAOImpl @Inject()(dbConfigProvider: DatabaseConfigProvider) (implicit ec: ExecutionContext) extends UserDAO { .... }
Step 4 : Implement Service Layer( app.service)
Add a new package service inside the app folder.
Add UserService trait inside the service package , with all the required User Service Methods
trait UserService { def addUser(user:User) : Future[String] def getUser(id : Long) : Future[Option[User]] def deleteUser(id : Long) : Future[Int] def listAllUsers : Future[Seq[User]] }
Implement the UserService trait using UserServiceImpl class .
package service import javax.inject.Inject import dao.UserDAO import model.User import scala.concurrent.Future @Singleton class UserServiceImpl @Inject()(userDAO: UserDAO) extends UserService { override def addUser(user: User): Future[String] = { userDAO.add(user) } override def deleteUser(id: Long): Future[Int] = { userDAO.delete(id) } override def getUser(id: Long): Future[Option[User]] = { userDAO.get(id) } override def listAllUsers: Future[Seq[User]] = { userDAO.listAll } }
Now again bind the trait to its implementation .
package service import com.google.inject.ImplementedBy import model.User import scala.concurrent.Future @ImplementedBy(classOf[UserServiceImpl]) trait UserService { def addUser(user:User) : Future[String] def getUser(id : Long) : Future[Option[User]] def deleteUser(id : Long) : Future[Int] def listAllUsers : Future[Seq[User]] }
Step 5 : Implement the View Layer(app.views)
In HTML, the data is usually submitted through html forms . So we need to define a form structure for our User case class to help play handle to the form data in an easy and well-defined manner, with the help of play-forms
Create a userForm for the corresponding User case class that we define above
case class UserFormData(firstName : String, lastName : String , mobile : Long , email : String ) object UserForm { val form = Form( mapping( "firstName" -> nonEmptyText, "lastName" -> nonEmptyText, "mobile" -> longNumber, "email" -> email )(UserFormData.apply)(UserFormData.unapply) ) }
Create a html file (user.scala.html) inside the views package. Pass the empty userform to be displayed on the html page . The implicit Request header and messages are provided to support Internationalization
@(userForm: Form[model.UserFormData],users : Seq[model.User])(implicit request: RequestHeader,messages : Messages) @main(Messages("app.name")) { @request.flash.get(messages("flash.success")).map { msg => <div class="col-md-6 col-md-offset-3 alert alert-danger alert-error"> <a href="#" class="close" data-dismiss="alert">×</a> <strong>@msg</strong> </div> } @request.flash.get(messages("flash.error")).map { msg => <div class="col-md-6 col-md-offset-3 alert alert-danger alert-error"> <a href="#" class="close" data-dismiss="alert">×</a> <strong>@msg</strong> </div> } <form id="user-data-form" role="form" action='@routes.UserController.addUser()' method="post" class="form-horizontal" align="center" autocomplete="off"> <fieldset class="user-fieldset"> <div class="user-form"> <label class="form-title" style="color: #F8741B; font-size: 22px;font-weight: bold; text-decoration:none">@messages("page.title")</label> </div> <br/> <table align="center" cellspacing="20"> <tr> <td align="left"> <div class="user-form" id="firstName_field_label"> <div class="controls col-xs-offset-3 col-xs-9"> <div class="input-group"> <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span> <strong>@messages("firstname")</strong> : </div> </div> </div> </td> <td align="center"> <div class="user-form" id="firstName_field_value"> <div class="controls col-xs-offset-3 col-xs-9"> <div class="input-group"> <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span> <input type="text" id="firstName" name="firstName" value="" placeholder="First Name" class="form-control input-lg" required> </div> </div> </div> </td> </tr> <tr> <td align="left"> <div class="user-form" id="lastName_field_label"> <div class="controls col-xs-offset-3 col-xs-9"> <div class="input-group"> <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span> <strong>@messages("lastname")</strong> : </div> </div> </div> </td> <td align="center"> <div class="user-form" id="lastName_field_value"> <div class="controls col-xs-offset-3 col-xs-9"> <div class="input-group"> <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span> <input type="text" id="lastName" name="lastName" value="" placeholder="Last Name" class="form-control input-lg" required> </div> </div> </div> </td> </tr> <tr> <td align="left"> <div class="user-form" id="mobile_field_label"> <div class="controls col-xs-offset-3 col-xs-9"> <div class="input-group"> <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span> <strong>@messages("mobile")</strong> : </div> </div> </div> </td> <td align="center"> <div class="user-form" id="mobile_field_value"> <div class="controls col-xs-offset-3 col-xs-9"> <div class="input-group"> <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span> <input type="tel" id="mobile" name="mobile" placeholder="Mobile" class="form-control input-lg" required> </div> </div> </div> </td> </tr> <tr> <td align="left"> <div class="user-form" id="email_field_label"> <div class="controls col-xs-offset-3 col-xs-9"> <div class="input-group"> <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span> <strong>@messages("email")</strong> : </div> </div> </div> </td> <td align="center"> <div class="user-form" id="email_field_value"> <div class="controls col-xs-offset-3 col-xs-9"> <div class="input-group"> <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span> <input type="email" id="email" name="email" placeholder="Email" class="form-control input-lg" required> </div> </div> </div> </td> </tr> </table> <br/> <div class="form-actions controls ynt-btn-xlarge"> <button type="submit" class="btn btn-primary ynt-btn-orange">Add</button> </div> </fieldset> </form> <div class="user-display" > <fieldset> <legend align="center"><h3>Registered Users</h3></legend> <table cellspacing="20"> <tr> <th>@messages("userid")</th> <th>@messages("firstname")</th> <th>@messages("lastname")</th> <th>@messages("mobile")</th> <th>@messages("email")</th> </tr> @for(user <- users){ <tr> <td>@user.id</td> <td>@user.firstName</td> <td>@user.lastName</td> <td>@user.mobile</td> <td>@user.email</td> <td><a href="@routes.UserController.deleteUser(user.id)">delete</a></td> </tr> } </table> </fieldset> </div> }
Add a new file messages in the conf package. And add these lines into the file
# App name app.name = My First Play #Title page.title = User Registration # Flash Scope flash.success = success flash.error = error #Labels userid=UserId firstname = FirstName lastname = LastName mobile = Mobile email = Email
Step 6 : Implement the Controller Layer (app.controller)
Add the UserController inside the controller package . It should look like below.
package controllers import com.google.inject.Inject import model.{User, UserForm} import play.api.mvc.{Action, Controller} import service.UserService import scala.concurrent.Future import play.api.i18n.{MessagesApi, Messages, I18nSupport} import play.api.libs.concurrent.Execution.Implicits._ class UserController @Inject() (userService: UserService, val messagesApi: MessagesApi) extends Controller with I18nSupport { def home = Action.async { implicit request => userService.listAllUsers map { users => Ok(views.html.user(UserForm.form,users)) } } def addUser() = Action.async { implicit request => UserForm.form.bindFromRequest.fold( // if any error in submitted data errorForm => Future.successful(Ok(views.html.user(errorForm,Seq.empty[User]))), data => { val newUser = User(0,data.firstName,data.lastName,data.mobile,data.email) userService.addUser(newUser).map(res => Redirect(routes.UserController.home()).flashing(Messages("flash.success") -> res) ) }) } def deleteUser(id : Long) = Action.async { implicit request => userService.deleteUser(id) map { res => Redirect(routes.UserController.home()) } } }
Now the final step will be to add the corresponding paths in routes(conf.routes)
#User GET /home controllers.UserController.home POST /add controllers.UserController.addUser GET /delete/:id controllers.UserController.deleteUser(id : Long)
Thats it !! We are done with the implementation of User Registration. Run the application to add and manage users
Open localhost:9000 in your browser
Click “Apply this script now !” button and that should create your required table in the given database
The UserDAOImpl doesn't compile. I get a "expected start of definition" error. The error position is the class keyword before UserDAOImpl.
ReplyDeleteI copied the code 1:1 so I have no idea why it doesn't work
Best regards,
Phil
It is because of the empty line between the @singleton tag and start of class definition . There tag should be just above the class definition .Thanks for the comment , will correct i now
DeleteI did not really understand what is the role of userForm in the user.scala.html, I see that it's only used in the controller.
ReplyDeleteWell in case we make use of the "play helpers" package for form handling in our views , then the play form(userForm) can be directly mapped and used.
DeleteEg :
@helper.form(action = @routes.UserController.addUser()) {
@helper.inputText(userForm("firstName"))
@helper.inputText(userForm("lastName"))
....
}
In this example I made use of plain HTML instead of helper packages.
Form handling can be done through HTML form , play helper form or mix of both
Thank you very much for your reply
DeleteThank you and wish you good luck :)
ReplyDeleteI just couldn't leave your website before telling you that I truly enjoyed the top quality info you present to your visitors? Will be back again frequently to check up on new posts. 188bet link
ReplyDelete