簡體   English   中英

如何有條件地重復渲染多個項目

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM