【问题标题】:How do I conditionally render more than one item in a repeat如何在重复中有条件地呈现多个项目
【发布时间】:2020-03-21 22:25:31
【问题描述】:

我想为其中一个节点使用条件 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] 那么我可以在那个序列上调用 render 。但是 showIf 会产生一个 Binding,它似乎隐式转换为 Modifier 而不是 Frag

【问题讨论】:

    标签: scala scala.js udash


    【解决方案1】:

    这是一个棘手的问题,因为需要绑定来渲染 DOM 节点,而不是 Modifiers(因此可以在任何更改时相应地替换它们)。

    首先,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 作为其根节点。

    【讨论】:

      【解决方案2】:

      我将详细说明我的场景以更好地解释上下文。我有以下课程:

      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
          ))
      }
      

      }

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-12-07
        • 2011-11-19
        • 2021-05-11
        • 2011-10-19
        • 2013-09-11
        • 2021-03-01
        • 2022-01-10
        相关资源
        最近更新 更多