I have ...
a Seq[RoleId] defining all roles that a user can get granted
a Userwith a property roles: Seq[RoleId], where the roles are those that the user has got granted
a Play controller preparing a form and providing the user (including her roles) and the Seq of available RoleIds to the html page as part of the form's data (including the mapping)
a Twirl template showing a checkbox for each available role
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 ?
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.
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.)
Related
I use mongo-scala-driver 2.9.0 and this is a function saving a user's Recommendation List to MongoDB. The argument streamRecs is An Array of (productId:Int, score:Double). Now i want to insert a document consisting of an useId and its relevant reconmendation list recs. However, there is an error in the line val doc:Document = Document("userId" -> userId,"recs"->recs). Does anyone know what goes wrong?
def saveRecsToMongoDB(userId: Int, streamRecs: Array[(Int, Double)])(implicit mongoConfig: MongoConfig): Unit ={
val streamRecsCollection = ConnHelper.mongoClient.getDatabase(mongoConfig.db).getCollection(STREAM_RECS_COLLECTION)
streamRecsCollection.findOneAndDelete(equal("userId",userId))
val recs: Array[Document] = streamRecs.map(item=>Document("productId"->item._1,"score"->item._2))
val doc:Document = Document("userId" -> userId,"recs"->recs)
streamRecsCollection.insertOne(doc)
}
the document i want to insert into MongoDB is like this(it means an
user and his recommendation products and scores):
{
"_id":****,
"userId":****,
"recs":[
{
"productId":****,
"score":****
},
{
"productId":****,
"score":****
},
{
"productId":****,
"score":****
},
{
"productId":****,
"score":****
}
]
}
When creating a BSON document, declare the Bson type explicitly for each value in the key/value pair, like so:
/* Compose Bson document */
val document = Document(
"email" -> BsonString("email#domain.com"),
"token" -> BsonString("some_random_string"),
"created" -> BsonDateTime(org.joda.time.DateTime.now.toDate)
)
To see an example, please check https://code.linedrop.io/articles/Storing-Object-in-MongoDB.
If I have an incoming JSON of following structure
[
{
"personId" : 12,
"name": "John Doe",
"age": 48,
"birthdate": "12/1/1954",
"relationships": [
{
"relationType":"parentOf",
"value" : "Johnny walker"
},
{
"relationType":"sonOf",
"value" : "Charles Smith"
}
]
},
{
"personId" : 13,
"name": "Merry Christmas",
"age": 28,
"birthdate": "12/1/1985",
"relationships": [
{
"relationType":"sisteOf",
"value" : "Will Smith"
},
{
"relationType":"cousinOf",
"value" : "Brad Pitt"
}
]
}
]
And requirement is that for each Person record controller will have to carve out relationships array and store each record from it in a separate relationship table with personId association while persisting this incoming JSON.
And subsequently when querying these persons records system will have to lookup relationships for each person from relationships table and inject them to form the same above looking JSON to give back to UI for rendering.
What's the best efficient way to perform this "carve out" and later "inject" operations using Play framework in Scala? (using Slick in persistent layer APIs) I have looked at this JSON transformation link and json.pickBranch in there but not quite sure if that'll be fully applicable here for "carve out" and "inject" use cases for preparing JSON shown in the example. are there any elegant ideas?
One way, which is pretty straightforward, is to use case classes along with Play Json inceptions
import play.api.libs.json.Json
case class Relationship(relationType: String, value: String)
object Relationship {
implicit val RelationshipFormatter = Json.format[Relationship]
}
case class Person(personId: String, name: String, age: Int, birthdate: String, relationships: Seq[Relationship]) {
def withRelationships(relationship: Seq[Relationship]) = copy(relationships = relationships ++ relationship)
}
object Person {
implicit val PersonFormatter = Json.format[Person]
}
Now you can convert a json value to Person by using the following code, provided that jsValue is a json value of type JsValue (which in play controllers you can get by request.body.asJson):
Json.fromJson[Person](jsValue)
In Play controllers, you can
For converting a Person to json you can use the following code provided that person is a value of type Person in your context:
Json.toJson(person)
The only remaining thing is your Slick schemas which is pretty straight forward.
One option is to use a simple schema for Person, without relations and one schema for Relation with a foreign key to Person table. Then you need to find all relations associated with a specific Person, namely person and then append them to that person by calling the withRelationships method which gives you a new Person which you can serve as json:
val person = .... // find person
val relations = .... // find relationships associated with this person
val json = Json.toJson(person.withRelationships(relations))
I have this:
temp = place where (_.name matches p2) fetch()
Now, what i have to do to get just two field of that results? For example name and id.
Thanks in advance :)
With rogue, you can use :
.select()
In your case :
temp = place where (_.name matches p2) select(_.name, _.id) fetch()
More example are available here (go to line 174) :
Rogue QueryTest.scala
please find below code for getting selected filed in mongodb.
db.user.find( { role: 'admin' }, { name: 1, id: 1 } )
You need to use the projection parameter which can limit the results to specific fields:
val q = MongoDBObject.empty
val fields = MongoDBObject("userid" ->, name" -> 1)
for (x <- mongoColl.find(q, fields)) println(x)
Assuming the basic connection and collections are properly referenced, the above code would return only userId and name.
From the front-end, I'm performing a jQuery POST with the following data:
[{name: "Kevin", age: 100, favoriteOperator:"="}]
This POST hits /sample URL.
In my controller, I read in the POST's body using:
def submit = Action { implicit request =>
val maybeRequestAsFormUrlEncoded: Option[Map[String, Seq[String]]] =
request.body.asFormUrlEncoded
Printing out map shows:
Map([{"name" : "Kevin", "age" : 100, "favoriteOperator" :" -> List("}])
Why is my favoriteOperator showing up as " -> List(" rather than "="?
Quite sure asFormUrlEncoded expects input like:
queryString=abc,def
anotherQueryString=blabla
which then is transformed into your map as:
Map("queryString" -> List("abc", "def"), "anotherQueryString" -> List("blabla"))
(I'm actually not so sure if abc,def and blabla really are deserialized into a list, that idea just comes because you get a list in your sample). Anyways, important is that asFormUrlEncoded expects key-value pairs separated by a = sign, that's why your string is taken apart like that. See also http://en.wikipedia.org/wiki/Url_encoding.
You might want to look into Json deserializers in play, as your request has a Json format:
http://www.playframework.com/documentation/2.2.x/ScalaJson
file upload code
request.body.file("image").map { ing =>
val target = new java.io.File(s"./uploads/${ing.filename}")
ing.ref.moveTo(target, true)
}
How do you connect the ing.filename to the AboutImages "image" object so I can update the databases!
this is the nobel:
object AboutImages {
val images = {
get[Long]("about_us_images.id") ~
get[String]("about_us_images.image") ~
get[Option[Date]]("about_us_images.created_at") ~
get[Option[Date]]("about_us_images.updated_at") ~
get[Option[Int]]("about_us_images.position") ~
get[String]("about_us_images.name") map {
case id~image~created_at~updated_at~position~name => AboutImages (id, image, created_at, updated_at, position, name)
}
}
the is the form:
val details: Form[AboutImages] = Form(
mapping(
"id" -> longNumber,
"image" -> text,
"created_at" -> optional(date),
"updated_at" -> optional(date),
"position" -> optional(number),
"name" -> nonEmptyText
)(AboutImages.apply)
(AboutImages.unapply)
)
Not entirely sure I understand your question — are you having problems accessing the form components other than the file upload?
If so, take a look at the post Play file upload form with additional fields.