Reputation: 777
I have a complex object with nested values which looks like this:
case class Gateway(RouteConfig: RouteConfig, gwType: Boolean, smmpp: Option[ConfigSMPP], modem: Option[ConfigModem])
Every object has values either in smmpp or in modem member. Then in my nested objects I have a list member. I wrote a form mapping step-by-step based on the Play documentation.
val gatewayForm: Form[SmsGateway] = Form(
mapping(
"smsRouteConfig" -> mapping(
"oracleId" -> optional(longNumber),
"smsType" -> optional(seq(text)),
)(RouteConfig.apply)(RouteConfig.unapply),
"gwType" -> boolean,
"smmpp" -> optional(mapping(
"nodeId" -> optional(text),
"systemType" -> optional(text),
)(ConfigSMPP.apply)(ConfigSMPP.unapply)),
"modem" -> optional(mapping(
"nodeId" -> optional(text),
"mdType" -> optional(text),
)(ConfigModem.apply)(ConfigModem.unapply))
)(SmsGateway.apply)(SmsGateway.unapply)
)
But now I have some problems:
1) The modem or smpp form must be shown if they have values or depending on the gwType boolean value(0-smpp, 1-modem). I don't have any idea how to do it.
2) Nested sequences must be shown in single string format, not repeated input boxes. For example if I have smsType with 3 values in a seq - I should see a text-box with 3 values split by a space or a comma and after submitting values from a text-box they must be converted to a seq again. I hope that somebody can tell me how to do it or help me understanding what I. should do if my wishes are unreal. Sorry for my bad English.
Upvotes: 0
Views: 1345
Reputation: 14401
Ad1. Since gwType
isn't an optional field you can display one of the optional forms based on the value of that field. Checking if one of modem or smpp have values is not required. gwType
provides enough information.
Ad2. Custom mapping between a html request and an underlying form object is done with last two parameters of the mapping method. If mapping is simple, one-to-one I would say, you usually use generated case class apply
and unapply
methods just like you have in your example. Since you have to map a basic text input into a sequence of strings and this requires manual parsing you have to write your own apply and unapply methods.
Since your form model is quite complex below I'm posting more simple example so you could grasp the idea more easily.
Let's take it as our model object.
case class Entity(stringSeq: Seq[String], int: Int)
In a view template we just want to have two text inputs as follows:
<form action="..." method="post">
Strings <input name="stringSeq"><br>
Int <input name="int"><br>
<button type="submit" value="submit">Send</button>
</form>
All we need to do is to write a form mapping with custom apply and unapply methods:
val form = Form[Entity](
mapping(
"string" -> nonEmptyText,
"int" -> number
)((string, int) => {
val seq = string.split(",").toSeq
Entity(seq, int)
})((form) => {
val string = form.stringSeq.mkString(",")
Option((string, form.int))
})
)
Instead of generated Entity.apply
we write a custom function which converts a tuple of raw request parameters into our domain object. It takes care of splitting provided string into a sequence of strings. In the second custom method in place of Entity.unapply
we do the opposite thing which is putting again items of a sequence into a single string and return a tuple with values taken from our model object.
This example should provide you with enough information how to solve the problem you have.
tl;dr
To keep your code more clean instead of writing these functions as anonymous you can define them explicitly.
object EntityForm {
val form = Form[Entity](
mapping(
"string" -> nonEmptyText,
"int" -> number
)(formTupleToEntity)(entityToFormTuple)
)
private def formTupleToEntity(string: String, int: Int): Entity = {
val seq = string.split(",").toSeq
Entity(seq, int)
}
private def entityToFormTuple(entity: Entity) = {
val string = entity.stringSeq.mkString(",")
Option((string, entity.int))
}
}
Upvotes: 3