formsscalaplayframeworkplayframework-2.6

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 ?


Solution

  • 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.