In Play! 1, it was possible to get the current index inside a loop, with the following code:
#{list items:myItems, as: 'item'}
<li>Item ${item_index} is ${item}</li>
#{/list}
Is there an equivalent in Play2, to do something like that?
#for(item <- myItems) {
<li>Item ??? is #item</li>
}
Same question for the _isLast and _isFirst.
ps: this question is quite similar, but the solution implied to modify the code to return a Tuple (item, index) instead of just a list of item.
Yes, zipWithIndex is built-in feature fortunately there's more elegant way for using it:
#for((item, index) <- myItems.zipWithIndex) {
<li>Item #index is #item</li>
}
The index is 0-based, so if you want to start from 1 instead of 0 just add 1 to currently displayed index:
<li>Item #{index+1} is #item</li>
PS: Answering to your other question - no, there's no implicit indexes, _isFirst, _isLast properties, anyway you can write simple Scala conditions inside the loop, basing on the values of the zipped index (Int) and size of the list (Int as well).
#for((item, index) <- myItems.zipWithIndex) {
<div style="margin-bottom:20px;">
Item #{index+1} is #item <br>
#if(index == 0) { First element }
#if(index == myItems.size-1) { Last element }
#if(index % 2 == 0) { ODD } else { EVEN }
</div>
}
The answer in the linked question is basically what you want to do. zipWithIndex converts your list (which is a Seq[T]) into a Seq[(T, Int)]:
#list.zipWithIndex.foreach{case (item, index) =>
<li>Item #index is #item</li>
}
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
))
}
}
How can I apply constrains to list of list, similarly to what I can do to simple list:
list_size: uint;
my_list: list of uint;
keep my_list.size() == list_size;
keep for each (item) using index (item_index) in my_list { item == item_index;};
My intention is to create something like:
list_size:uint;
grosslist_size:uint;
my_grosslist: list of list of uint;
keep my_grosslist.size() == grosslist_size;
keep for each (grossitem) using index (grossindex)in my_grosslist {
grossitem.size() == list_size;
// keep for each (item) using index (item_index) in grossitem {
// item == item_index + grossindex * 100;
// };
};
How can I write 3 lines commented above using Specman syntax?
Please note that constrains are for instance only, in reality I'll need to apply much more sophisticated ones rather than indexing list items...
Thanks in advance.
The code you wrote is indeed the correct usage of list-of-list. Note that there was a missing space and the additional 'keep' is not needed for the internal for each. other than that, it works.
<'
extend sys {
list_size:uint;
grosslist_size:uint;
my_grosslist: list of list of uint;
keep my_grosslist.size() == grosslist_size;
keep for each (grossitem) using index (grossindex) in my_grosslist {
grossitem.size() == list_size;
for each (item) using index (item_index) in grossitem {
item == item_index + grossindex * 100;
};
};
};
'>
Using a for loop, how can I iterate through a list, with the ability to not iterate over the very last element in the list.
In my case, I would like to not iterate over the first element, and need to iterate through backwards, here is what I have:
for( thing <- things.reverse) {
//do some computation iterating over every element;
//break out of loop when first element is reached
}
You can drop the first item before you reverse it:
for(thing <- things.drop(1).reverse) {
}
For lists, drop(1) is the same as tail if the list is non-empty:
for(thing <- things.tail.reverse) {
}
or you could do the reverse first and use dropRight:
for(thing <- things.reverse.dropRight(1)) {
}
You can also use init if the list is non-empty:
for(thing <- things.reverse.init) {
}
As mentioned by RĂ©gis, for(thing <- things.tail.reverse) {}
I am trying to iterate in a playframework view, but without success for now. I have the following structure:
#if(list != null) {
for(a <- 0 to list.size()/5)
{
// some html, where I want to get the value of a
for(b <- a*5 to a*5+5) // here I want to use the a value again
{
some html
}
}
So my question is how to get the current index of the loop so that I will be able to use it.
You should combine it in one for loop:
#if(list != null) {
#for{a <- 0 to list.size()/5
b <- a*5 to a*5+5}
yield html
}
}
And use option instead of null checking.
Also you can use map function to transform your list. See details in Play documentation - http://www.playframework.com/documentation/2.0/ScalaTemplates
Im trying to make an order form with Play 2 and Scala.
Here is what it was before grouping:
<table>
#items.zipWithIndex.map {
case (item, index) =>
#itemRow(item, index)
}
</table>
itemRow definition
#itemRow(index: Int, item: Item) = {
<tr>
<td>
#(index+1)
</td>
<td>
#item.name
</td>
<td>
<input type="hidden" name="#requestForm("items")("[" + index + "]")("itemId").name" value="#item.id">
<input type="text" name="items[#index].count" value="#requestForm("items")("[" + index + "]")("count").value">
</td>
</tr>
}
At first I tried naive implementation
#items.groupBy(item => item.category).map {
case (categoryId, itemsInCategory) =>
<table>
#itemsInCategory.zipWithIndex.map {
case (item, index) =>
#itemRow(item, index)
}
</table>
}
But there is a problem, indexes in each category starts with 0.
So, http request is something like that:
# category 1
items[0].id = 1
items[0].count = 1
items[1].id = 2
items[1].count = 2
# category 2
items[0].id = 3
items[0].count = 1
items[1].id = 4
items[1].count = 5
And it is causes to values being overriden.
I need my indexes for be consecutive within the form, like this:
# category 1
items[0].id = 1
items[0].count = 1
items[1].id = 2
items[1].count = 2
# category 2
items[2].id = 3
items[2].count = 1
items[3].id = 4
items[3].count = 5
So there is questions
For functional programmers:
Can I make index variable shared for all groups?
For Play 2.0 or web programmers:
Is there another way to make form with variable count of repeated values?
How to avoid sending this bunch of items with 0 count?
I have no experience with Play so I can't comment on the Play specific questions (maybe it already provides helpers for what you want), but on the scala librayr alone you can do something like this:
#items.sortBy(item => item.category).zipWithIndex.groupBy{ case (item, _) => item.category}.map {
case (categoryId, indexedItemsInCategory) =>
<table>
#indexedItemsInCategory.map {
case (item, index) =>
#itemRow(item, index)
}
</table>
}
The idea is to first sort the items by category and then zip them with the corresponding indexes. Then only you group them by category (which should be fast as the list is already sorted).