[英]How do I conditionally render more than one item in a repeat
我想使用條件 showIf 為其中一個節點生成一組重復的節點,如下所示:
div<id = "parent">
div<id = "child1">Child 1</div>
div<id = "child2">Child 2</div>
div<>Optional text for child 2</div>
</div>
為了產生這個,我可能會使用重復函數,如下所示:
div(id := "parent",
repeat(seqProp)(child =>
div(id := child.get.id),
showIf(child.transform(_.otionalText.nonEmpty))(div(child.optionalText.get))
)
)
但是無論我似乎嘗試以何種方式編寫此代碼,我都無法編譯上述代碼。 有人可以推薦我這樣做的好方法嗎?
筆記。 如果我有一個Seq[Frag]那么我可以在該序列上調用渲染。 但是showIf產生一個Binding ,它似乎隱式轉換為Modifier而不是Frag 。
這是一個棘手的問題,因為需要綁定來呈現 DOM 節點,而不是Modifier
s(因此可以在任何更改時相應地替換它們)。
首先, repeat
只跟蹤結構變化,所以需要結合2個綁定。 為避免泄漏,您可以在這種情況下使用repeatWithNested
。
其次, scalatags.generic.LowPriUtil#OptionFrag
允許你渲染Option
節點,所以你不需要擔心這里的showIf
。
考慮到這一點,假設您有一些模型類和序列:
case class C(id: String, optionalText: Option[String])
val seqProp = SeqProperty(C("a", Some("")))
你可以寫:
div(
id := "parent",
repeatWithNested(seqProp)((childProp, nested) => div(
nested(produce(childProp)(child => Seq(
div(id := child.id)(child.id).render,
child.optionalText.render,
)))
).render)
)
不幸的是,這會產生一個額外的嵌套div
,但會正確地對結構和值補丁做出反應。
您可以在此處查看此代碼: https : //scalafiddle.io/sf/BHG388f/0
如果您真的想避免這種情況,則必須犧牲其中一些屬性,例如在seqProp
上使用produce
並在構建器中創建parent
節點作為其根節點。
我將詳細說明我的場景,以更好地解釋上下文。 我有以下課程:
trait MenuItem {
val id: String
val label: String
val subMenu: Option[() => Future[Seq[MenuItem]]]
}
case class MenuNode(item: MenuItem, level: Int, subNodes: Seq[MenuNode])
菜單節點組織在樹中,根節點的級別從零開始,並隨着我們沿着樹向下遞增。 我希望能夠通過單擊來動態展開/折疊節點。 但是 DOM 不會匹配到這個層次結構 - 它會是扁平的。 因此,例如,我想創建一個 3 級食譜菜單,DOM 將類似於以下內容:
<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>
我最初嘗試編寫一個遞歸函數來遍歷樹,從而在遞歸時生成 DOM。 但是我退后了一步,意識到更好的方法是將樹(遞歸地)展平以按順序生成所有相關的 MenuNode。 然后我可以使用 SeqProperty 來管理我的樹的顯示方式。 然后當一個節點展開/折疊時,我只需要相應地更新 SeqProperty 的相關部分。 所以我在 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
這是我最終確定的 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
))
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.