user1576088
user1576088

Reputation: 67

Checkbox lists in Play with Scala

I have ...

What I'd like to achieve is a list of checkboxes where every checkbox who's value is part of the user's roles is checked. So the list should show which of the available roles are granted to the user, as shown in this very refined prototype:

Name:  [Doe, John]
Roles: [ ] Admin
       [x] Manager
       [x] Service Desk
       [ ] Jack of all trades

if the user's name is John Doe with the roles Manager and Service Desk.

That seems rather simple, but I can't find a way to achieve it without some hacks (like circumventing the form and moving the role date to the Twirl template as a regular parameter; writing a custom mapper in the form handling code etc.). Isn't there a way to do it in the Play way without all that boilerplate ?

I googled hard, but I couldn't find any example that seemed to do it right. And Play's form processing docs weren't helpful either.

Any ideas ?

Upvotes: 0

Views: 552

Answers (2)

Michael Pigott
Michael Pigott

Reputation: 11

The Play library provides an inputCheckboxGroup which works the same way as the inputRadioGroup.

In your Play controller:

def userForm(implicit messages: Messages): Form[UserFormData] = Form(
  mapping(
    "firstName" -> nonEmptyText(minLength = 2, maxLength = 30),
    "lastName" -> nonEmptyText(minLength = 2, maxLength = 40),
    "email" -> email,
    "roleAssignmentIds" -> seq(text)
  )(UserFormData.apply)(UserFormData.unapply)
)
val roleAssignmentOptions : List[(String, String)] = List("1" -> "Admin", "2" -> "Manager", "3" -> "Service Desk", "4" -> "Jack of All Trades")

Then in your Play Template:

@helper.inputCheckboxGroup(form("roleAssignmentIds"), roleAssignmentOptions)

This will display a list of checkboxes. The IDs ("1".."4") will be sent back back to the server when the checkboxes are checked. (If you fill the form in advance, you need to fill it in with the IDs - "1".."4" - as well.)

Upvotes: 1

user1576088
user1576088

Reputation: 67

Upon refining my question I came up with a solution that worked.

I use the following form, which uses the case classes below to hold its data. As you can see there's a nested mapping inside, representing a list of roles and their state with regard to the user's roles (true meaning that the user has the role, false otherwise):

def userForm(implicit messages: Messages): Form[UserFormData] = Form(
  mapping(
    "firstName" -> nonEmptyText(minLength = 2, maxLength = 30),
    "lastName" -> nonEmptyText(minLength = 2, maxLength = 40),
    "email" -> email,
    "roleAssignments" -> seq(
      mapping(
        "roleIdStr" -> nonEmptyText,
        "isAssigned" -> boolean
      )(RoleAssignment.apply)(RoleAssignment.unapply)
    )
  )(UserFormData.apply)(UserFormData.unapply)
)

case class UserFormData(firstName: String, lastName: String, email: String, roleAssignments: Seq[RoleAssignment])
case class RoleAssignment(roleIdStr: String, isAssigned: Boolean)

The controller just populates the form with data from the db:

def user(id: Long) = Action.async { implicit request =>
  managerService.retrieveByIdWithRoles(id, Some(request.identity), request.remoteAddress).map { case (user, roles) =>
    Ok(views.html.admin.administration.show_user(
      userForm.fill(UserFormData(user.firstName, user.lastName, user.email, roleAssignments(roles)))))
  }
}

private def roleAssignments(roles: Seq[Role]) = {
  val roleIds = roles.map(_.id)
  Roles.all.map { case id =>
    RoleAssignment(id.toString, roleIds.contains(id))
  }
}

And in the view template I repeat over the roleAssignments and use the index provided by repeatWithIndexto access the seq's elements:

@repeatWithIndex(userForm("roleAssignments")) { (roleAssignment, index) =>
  @b4.checkbox(userForm(s"roleAssignments[$index].isAssigned"), '_text -> userForm(s"roleAssignments[$index].roleIdStr").value, '_custom -> true, 'readonly -> true)
}

Thank you @cchantep for pushing me to try harder.

Upvotes: 1

Related Questions