I'm a front ender by trade but I've been asked to hop on to a Scala project which leverages the Play framework so includes Twirl. I'm trying to create a template which displays slightly different outputs depending on the string which is passed in.
Here is my code:
#(status: String)(implicit messages: Messages)
#{
if(status == "00") {
val pageTitle = "Page title"
val appStatus = "className"
val title = "message"
val subTitle = "message"
val step1Status = "className"
val step2Status = "className"
val step3Status = "className"
val image1 = "/customs/assets/images/image.png"
val image2 = "/customs/assets/images/image.png"
val image3 = "/customs/assets/images/image.png"
val optionalHeading = ""
val optionalParagraph = ""
val listContents = "<li>#Messages('message')</li><li>#Messages('message')</li>"
val optionalLink = "<br /><a class='button' href='#routes.DashboardController.display(custom)' role='button'>#Messages('message')</a>"
}
if(status == "01") {
//Other variables
}
if(status == "04") {
//Etc...
}
}
#layout(${pageTitle}) {
<div class="content__body">
<div class="hero">
<div class="${appStatus}">
<h1 class="bold-large">#Messages(${title})</h1>
<p>
${afterSubTitle}
</p>
<ol class="appstatus-steps">
<li><span class="${step1Status}"><img alt="Complete" title="Complete" src=" + ${image1} + ">#Messages("messages.Received")</span></li>
<li><span class="${step2Status}"><img alt="Complete" title="Complete" src=" + ${image2} + ">#Messages("messages.Processing")</span></li>
<li><span class="${step3Status}"><img alt="Complete" title="Complete" src=" + ${image3} + ">#Messages("messages.Decision")</span></li>
</ol>
</div>
</div>
${optionalHeading}
${optionalParagraph}
<h2>#Messages("messages.next")</h2>
<ul class="list list-bullet">
${listContents}
</ul>
${optionalLink}
</div>
}
So, as you can see, the idea is that this page is called with a code (status) and based on that string, a number of variables are defined which alter the way the page is formed; content/css classes/images etc.
This doesn't work (you may be surprised to learn!)
It's really unclear to me WHY it doesn't though. I thought that you accessed variables using a ${Variable} format, but it's possible that you're supposed to use the #Variable format instead.
Even when I try to change them to the #Variable way, I'm still getting problems and I loosely understand that this is to do with scope in Scala/Twirl and there's a "defining" keyword which can be used. I've read some documentation but I'm not getting it.
Could anyone shed some light on this?
EDIT
Okay, let's just consider the below code instead:
#(status: String)(implicit messages: Messages)
#{
if(status == "00") {
val myClass = "custom-class-1"
}
if (status == "01") {
val myClass = "custom-class-2"
}
#layout("page title") {
<div class="#myClass">This is a div</div>
}
So 'all I want to do' :) is be able to define a variety of variables based on the status and then use those variables in the template of the page.
Does that make sense? I'm so new to Scala so Tuples/Defining is lost on me at the moment, but I am learning!
You seem to be more used to javascript variables and scopes :)
In Scala and Twirl, if you only define a variable (in this case it's a value because it's immutable) inside an if statement, the value is only accessible inside the if block
One solution could be to have the if statement to return a tuple with all the variables, and use the power of assignment operation, like:
val (myA, myB, myC) = if(something) {
val a = "a"
val b = "b"
val c = "c"
(a,b,c) // this will return the 3 variables/values because if statements are an expression, and expressions return something
} else {
val a = "A"
val b = "B"
val c = "C"
(a,b,c)
}
// now you have a,b,c defined in this scope and you can do whatever you need with them
if you want to return different variables depending on the if statement, you will have more complications. a solution would be to make the not needed variables null or an Option
Let me know if you need anything else better explained :)
EDIT: solution for minified edited example
I don't have anything here to check if it compiles, but it should be something like:
#(status: String)(implicit messages: Messages)
#{
val myClass = if(status == "00") {
"custom-class-1"
} else if (status == "01") {
"custom-class-2"
} else {
""
}
#layout("page title") {
<div class="#myClass">This is a div</div>
}
Alternative using pattern matching:
replace the if/else statement with:
val myClass = status match {
case "00" => "custom-class-1"
case "01" => "custom-class-2"
case _ => ""
}
(the case _ is like "in every other case")
The answer by #pedrorijio is almost exactly correct, but the actual code which worked for me in the end was this:
#myClass = #{status match {
case "00" => "custom-class-1"
case "01" => "custom-class-2"
}}
Thank you for the assistance!
Related
I would like to produce a repeating set of nodes using a conditional showIf for one of the nodes something like the following:
div<id = "parent">
div<id = "child1">Child 1</div>
div<id = "child2">Child 2</div>
div<>Optional text for child 2</div>
</div>
To produce this I might use the repeat function something like the following:
div(id := "parent",
repeat(seqProp)(child =>
div(id := child.get.id),
showIf(child.transform(_.otionalText.nonEmpty))(div(child.optionalText.get))
)
)
But no matter what way I seem to try to write this I cannot get the above code to compile. Can someone recommend me a good way do do this?
NOTE. If I have a Seq[Frag] then I can call render on that sequence. But showIf produces a Binding which seems to have an implicit conversion to a Modifier but not to a Frag.
This is a tricky one due to the requirement for bindings to render DOM nodes, not Modifiers (so they can be replaced accordingly on any change).
First of all, repeat tracks only structure changes, so you need to combine 2 bindings. To avoid leaks, you can use repeatWithNested in that case.
Secondly, scalatags.generic.LowPriUtil#OptionFrag allows you to render Option nodes, so you don't need to worry about showIf here.
Taking that into account, assuming you have some model class and sequence:
case class C(id: String, optionalText: Option[String])
val seqProp = SeqProperty(C("a", Some("")))
You could write:
div(
id := "parent",
repeatWithNested(seqProp)((childProp, nested) => div(
nested(produce(childProp)(child => Seq(
div(id := child.id)(child.id).render,
child.optionalText.render,
)))
).render)
)
This unfortunately produces an additional nested div, but reacts to both structure and value patches correctly.
You can see this code in action here: https://scalafiddle.io/sf/BHG388f/0
If you really wanted to avoid that, you'd have to sacrifice some of these properties e.g. by using produce on the seqProp and creating the parent as its root node inside the builder.
I will elaborate a bit on my scenario to explain better the context. I have the following classes:
trait MenuItem {
val id: String
val label: String
val subMenu: Option[() => Future[Seq[MenuItem]]]
}
case class MenuNode(item: MenuItem, level: Int, subNodes: Seq[MenuNode])
Menu nodes are organised in a tree, with level starting at zero for the root node and incrementing as we go down the tree. I want to be able to dynamically expand/collapse a node by clicking on it. But the DOM will not match up to this hierarchy - it will be flat. So say for example I want to create a 3 level menu of recipes, the DOM would be something like the following:
<div class="list-group">
<button class="list-group-item menu-item menu-level-1">Vegetables</button>
<button class="list-group-item menu-item menu-level-2">Carrot</button>
<button class="list-group-item menu-item action-item">Soup</button>
<button class="list-group-item menu-item action-item">Coleslaw</button>
<button class="list-group-item menu-item menu-level-2">Potatoes</button>
<button class="list-group-item menu-item menu-level-1">Fruits</button>
<button class="list-group-item menu-item menu-level-2">Apple</button>
<button class="list-group-item menu-item action-item">Tart</button>
<button class="list-group-item menu-item action-item">Cider</button>
<button class="list-group-item menu-item menu-level-2">Orange</button>
</div>
I originally approached this trying to write a recursive function to go through the tree producing the DOM as I recurse. But I've taken a step back and realised a better approach would be to flatten the tree (recursively) to produce all relevant MenuNodes in a sequence. I could then use a SeqProperty to manage how my tree is displayed. Then when a node is expanded/collapsed I only have to update the relevant parts of the SeqProperty accordingly. So I added the following definitions to MenuNode:
def flatten(): Seq[MenuNode] = flatten(subNodes.toList, Seq())
private def flatten(nodes: List[MenuNode], slots: Seq[MenuNode]): Seq[MenuNode] = nodes match {
case h :: t =>
// Add this node and any sub-slots after it
flatten(t, (slots :+ h) ++ h.flatten())
case _ =>
slots
}
def isSlot(node: MenuNode) = level == node.level && item.id == node.item.id
And here is my finalised MenuView:
class MenuView(model: ModelProperty[MenuModel]) extends View with Futures {
val seqProp = SeqProperty(model.get.rootNode.flatten())
def getTemplate: Modifier = {
div(cls := "list-group",
repeat(seqProp) { slot =>
button(cls := "list-group-item " + itemStyle(slot.get),
onclick := { () => handleClick(slot) },
slot.get.item.label
).render
}
)
}
model.subProp(_.rootNode).listen { node =>
// Find the first difference between previous and current
val prevSlots = seqProp.get
val curSlots = node.flatten()
prevSlots.indexWhere(curSlots)(! _.isSlot(_)) match {
case i if i > 0 =>
// Replace the slot that was toggled
seqProp.replace(i - 1, 1, curSlots(i - 1))
(curSlots.size - prevSlots.size) match {
case diff if diff > 0 =>
// Expand. Insert the new ones
seqProp.insert(i, curSlots.drop(i).take(diff): _*)
case diff =>
// Collapse. Remove the difference
seqProp.remove(i, -diff)
}
case _ =>
seqProp.set(curSlots)
}
}
def itemStyle(node: MenuNode) = "menu-item " +
(if (node.hasSubMenu) s"menu-level-${node.level}"
else "action-item") + (if (node.isActive) " item-active" else "")
def handleClick(node: Property[MenuNode]): Unit =
if (node.get.hasSubMenu) {
if (! node.get.isExpanded) node.get.expand().success { expanded =>
model.subProp(_.rootNode).set(model.get.rootNode.replace(expanded))
}
else {
model.subProp(_.rootNode).set(model.get.rootNode.replace(node.get.collapse()))
}
}
else {
val vector = node.get.vector
model.set(model.get.copy(
rootNode = model.get.rootNode.activate(vector),
activated = vector
))
}
}
I am trying to setup a variable in the scala template. Loop through the roles that user have , if found out the user is customer , then do something with the input. If not then do something else.
But scala isnt that simple , it won't compile on following code.
#var = #{ if(user != null){
#for(role <- user.roles.filter(_.getName()=="customer")) {
var=#customer(input)
}
}
}
#if( var == null){
var=#others(input)
}
It gives me two errors
t.scala.html:275:: identifier expected but 'for' found.
[error] #for(role <- user.roles.filter(_.getName()=="customer"))
t.scala.html:278: expected start of definition
Also , is there a better way to do this in scala ? Thanks
My reference : Scala template set variable
Update:
My goal was trying to do something like below , but in scala template:
result=null
for role in User.roles:
if(role == "customer"):
result=customer(xyz)
break
if(result==null):
result = others(xyz)
To set up a for loop inside of an if statement in a Scala template, you don't need to assign a variable. You can simply use an if block in the template where you want to display stuff. For example
#if(user != null) {
#for(role <- user.roles.filter(_.getName()=="customer")) {
#customer(input)
#* Do other stuff related to 'role' and 'input' here *#
}
} else {
#* Do something else *#
}
For further reference I encourage you to look at the documentation for Play templates. If you really want to define a variable you could do it using the defining helper:
#defining(user.getFirstName() + " " + user.getLastName()) { fullName =>
<div>Hello #fullName</div>
}
Instead of defining a variable you could also define a resusable block, which might be useful in your case. For example,
#customer_loop(input: String) = {
#if(user != null) {
#for(role <- user.roles.filter(_.getName()=="customer")) {
#customer(input)
#* Do other stuff related to 'role' and 'input' here *#
}
} else {
#* Do something else *#
}
}
To declare a variable do
#import scala.Any; var result:Any=null //where Any is the datatype accoding to your requirement
To reassign its value do
#{result = "somevalue"}
So the solution accoding to the pseudo you provided
#import java.lang.String; var result:String=null
#import scala.util.control._;val loop = new Breaks;
#loop.breakable {
#for(role <- roleList) {
#if(role.equals("customer")) {
#{
result = "somevalue"
}
#{loop.break};
}
}
}
#if(result==null){
#{result="notfound"}
}
Also check Similar1,Similar2
In my Play application I've a configuration like this:
social {
twitter {
url="https://twitter.com"
logo="images/twitter.png"
}
facebook {
url="https://www.facebook.com"
logo="images/facebook.png"
}
}
Ho do I iterate over all the social entries to get url and logo for each entry?
<table border="0" cellspacing="0" cellpadding="2"><tr>
#configuration.getConfig("social").map { config =>
#for(item <- config.entrySet) {
<td><a href="item.getString("url")">
<img src="#routes.Assets.at("item.getString("logo")").absoluteURL()" width="24" height="24"/></a></td>
}
}
</table>
Of course, item.getString in the snippet here above does not work... it just shows what I'm trying to achieve.
The final objective would be to be able to add any further social url without having to modify the page template.
If you change the config to:
"social" : [
{
name="twitter",
url="https://twitter.com",
logo="images/twitter.png"
},
{
name="facebook",
url="https://www.facebook.com",
logo="images/facebook.png"
}
]
You could do it like this:
#(message: String)(implicit request: RequestHeader)
#import play.api.Play.current
<table border="0" cellspacing="0" cellpadding="2"><tr>
#current.configuration.getConfigList("social").get.map { config =>
<td><a href="#config.getString("url")">
<img src="#routes.Assets.at(config.getString("logo").get).absoluteURL()" width="24" height="24"/></a></td>
}
</table>
For posterity, here's another way to iterate over a nested config like you had. I prefer that format to the array one and I'd rather make my config cleaner than the code.
import collection.JavaConversions._
val socialConfig = ConfigFactory.load.getConfig("social")
socialConfig.root.map { case (name: String, configObject: ConfigObject) =>
val config = configObject.toConfig
println(config.getString("url"))
println(config.getString("logo"))
}
I'm sure the OP could convert this into a Twirl template. That's about as clean as I can get it.
In case you're using Java, for a config like:
"social" : [
{
name="twitter",
url="https://twitter.com",
logo="images/twitter.png"
},
{
name="facebook",
url="https://www.facebook.com",
logo="images/facebook.png"
}
]
this will work:
ConfigList socials = ConfigFactory().load.getList("social")
for (ConfigValue cv : socials) {
Config c = ((ConfigObject)cv).toConfig();
System.out.println(c.getString("url"));
System.out.println(c.getString("logo"));
}
socialConfig.root.map does not work.
Here is my solution -
private val firstSegmentRE = """^(\w+)[\.*].*$""".r
// convert "aaa.bbb.ccc" to "aaa"
private def parseFirstSegment(fullPath: String) : Option[String] = {
if (fullPath.contains("."))
fullPath match {
case firstSegmentRE(segment) => Some(segment)
case _ => None
}
else
Some(fullPath)
}
// for all keys in white list get a map of key -> config
protected def subConfigMap(config: Config, whiteList: List[String],
configName: String) : ErrorOr[Map[String, Config]] = {
// This will traverse the whole config and flatten down to the leaves..
val leafKeys : List[String] =
config.entrySet()
.asScala
.map(e => e.getKey)
.toList
// Remove all after the first dot
val nextLevelKeys : List[String] =
leafKeys.map(parseFirstSegment)
.collect {
case Some(firstSegment) => firstSegment
}
.distinct
val keysToSearch = nextLevelKeys.filter(whiteList.contains)
// we have a list of valid first level children
// parse out subconfigs and convert to map
keysToSearch.traverseErrorOr( key =>
extractSubConfig(config, key, configName).map((key, _))
)
.map(_.toMap)
}
Where extractSubConfig is a method which produces an ERROR / Config (a scalaz disjunction) and traverseErrorOr is a method to traverse a list and either process all the elements or fail and return failed disjunction if failed at any point. This method can be done without scalaz, just posting an answer to help people if they want.
Since collection.JavaConversions has since been deprecated in favor of collection.JavaConverters (and this question is the top result on a search for how to iterate through a Typesafe Config in Scala), I wanted to suggest a more modern version of Cole's great answer:
import collection.JavaConverters._
val socialConfig = ConfigFactory.load.getConfig("social")
for ( (name: String, configObject: ConfigObject) <- socialConfig.root.asScala) {
println(name) // prints "twitter" or "facebook"
val config = configObject.toConfig
println(config.getString("url"))
println(config.getString("logo"))
}
To be clear, socialConfig.root.asScala yields a standard Scala Map[String, ConfigObject] and you can iterate over it however you'd like.
import collection.JavaConversions._
val socialConfig = ConfigFactory.load.getConfig("social")
val socialConfigMap = socialConfig
.root()
.entrySet()
.asScala
.map(socialEntry => {
println(socialEntry.getKey)
val socialEntryConfig = socialEntry.getValue.asInstanceOf[ConfigObject].toConfig
println(socialEntryConfig.getString("url"))
println(socialEntryConfig.getString("logo"))
})
For the config mentioned originally in question:
social {
twitter {
url="https://twitter.com"
logo="images/twitter.png"
}
facebook {
url="https://www.facebook.com"
logo="images/facebook.png"
}
}
A solution in Java is:
val socialConfig = ConfigFactory.load.getConfig("social");
ConfigList socials = socialConfig.getList("social");
Map<String, Object> map = socials.root().unwrapped();
for (Map.Entry<String, Object> cv : map.entrySet()) {
Map<String, String> c = (Map<String, String>)cv.getValue();
System.out.println(cv.getKey());
System.out.println(c.getOrDefault("url", ""));
System.out.println(c.getOrDefault("logo", ""));
}
I have a question for which I couldn't find an answer anywhere in the web. I have a lift-based web application written on Scala. There is a form in my application which is driven by a snippet. I use Mapper for my models, and I use override def validations ... to define model's validation parameters. The template code looks like this:
<form class="lift:GroupManager.addGroup?form=POST&action=add">
<div class="row collapse" style="width: 30em">
<div class="small-8 columns">
<span class="group-name prefix radius"></span>
</div>
<div class="small-4 columns">
<span class="submit button small postfix radius">Add New Group</span>
</div>
</div>
</span>
</form>
The snippet code:
def addGroup = {
object groupName extends RequestVar[String]("")
def doAddGroup() = {
val group = Group.create.createdAt(new java.util.Date).groupName(groupName.is)
group.validate match {
case Nil => {
group.save
S.notice("Group '" + group.groupName.is + "' has been added successfully")
}
case errors:List[FieldError] => {
S.error(errors)
}
}
}
".group-name" #> SHtml.text(groupName.is, groupName(_), "id" -> "group-name") &
".submit" #> SHtml.submit("Add group", doAddGroup)
}
The model's validations override:
object groupName extends MappedString(this, 700) {
override def dbColumnName = "group_name"
override def dbNotNull_? = true
override def validations = valMinLen(1, S.?("Group name should not be empty!")) _ ::
valMaxLen(700, S.?("Group name should not be more than 700 characters long!")) _ ::
valUnique(S.?("Group name should be unique!")) _ ::
super.validations
}
Now everything is perfect except the fact that when invalid data is provided, S.error is used to inform user about problems. What I want to do is to highlight the form element (by applying class="error") in which the data is invalid and add a <span> element with the error message in front of the form element. Is there a simple way to do that with Lift framework? Or I should just traverse through errors:List[FieldError] and fetch field information from that array, which does not seem too elegant to me?
Play framework if else case is not working here
If userprofile.useraccountid, useraccount.id have the same value then not views that id user on view page
my code in the view..
#(userprofiles: List[UserProfile],myFriend:models.MyFriend,userprofile:models.UserProfile,useraccount:models.UserAccount)
#helper.form(action = routes.Application.createMyFriend) {
<br/><br/><br/>
#for(userprofile <- userprofiles){
#if(userprofile.useraccountid != useraccount.id) {
<img src="#routes.Assets.at("images/img2.png")" width="200" height="200" />
<br>
<h5>#userprofile.name</h5>
<h5>#userprofile.useraccountid</h5>=<h5>#useraccount.id</h5>
<h6>#userprofile.gender</h6>
<h6>#userprofile.date_of_birth</h6>
<div class="actions">
<input type="submit" class="btn primary" value="+1 Add As Friend" title="Send Friend Request">
</div>
<br/>
}
}
}
when checking the condition the database values are views in the view page and
#if(userprofile.useraccountid != useraccount.id)
if change the condition to
#if(userprofile.useraccountid == useraccount.id)
Nothing will in the view page.
In this code when run the program the code section
<h5>#userprofile.useraccountid</h5>=<h5>#useraccount.id</h5>
The id's are same here , and that show in the view then the idea is not false.. for example 15=15.
Here the 2 id are same but the checking in the if case is not work properly... or the coding is not right.
Edit
This is in application
def listMyFriend = Action { implicit request =>
var cid=request.session.get("userId")
println("aa",cid)
if (request.session.get("userId") == None) {
Results.Redirect("/")
}
else {
val userprofiles:UserProfile=null
val userprofileId = request.session.get("userId").get.toLong//userProfileId
val userprofile = UserProfile.findUserByAccountId(userprofileId).get
println(userprofile)
/* val myfriendId = request.session.get("myFriendId").get.toLong//myFriendId
val myfriend = MyFriend.friendidByUserIsAccepted(myfriendId,true)
println(myfriend)*/
myFriendForm.bindFromRequest.fold(
errors => BadRequest(views.html.myFriend(errors, userprofile,myfriend,myfrnd)),
myFriend => {
println("errors")
val myFriendOpt = UserProfile.myFriend(userprofile.id.get)
println(myFriendOpt)
myFriendOpt match {
case None =>
}
Results.Redirect("/myFriend")
})
}
}
You have shadowing issues in your code: userprofile is both defined as a parameter of your template and as the variable you get out of the for comprehension.
#(userprofiles: List[UserProfile],myFriend:models.MyFriend,userprofile:models.UserProfile,useraccount:models.UserAccount)
here ---^
#helper.form(action = routes.Application.createMyFriend) {
<br/><br/><br/>
#for(userprofile <- userprofiles){
and here ---^
Try renaming one of the two and sort out which one you want to refer to in your if.