6

问题

LiveView 折叠我打开的元素。

细节

我有一个元素在页面加载时开始折叠:

<a class="collapse_trigger">...</a>
<div class="is-collapsible">
# content updated by liveview
</div>

如果用户单击可折叠项,则可折叠项具有一个类.is-active

<a class="collapse_trigger">...</a>
<div class="is-collapsible is-active">
# content
</div>

但是 liveview 删除了该类。知道如何确保 liveview 忽略父元素<div class="is-collapsible is-active">但照顾孩子吗?我的第一个想法是phx-update="ignore"。但现在我想我需要将可折叠的逻辑放入后端。:/

附加信息

我使用bulma-collapsible进行一次 CSS 更改:

// the following is necessary because liveview does not work well with the bulma-collapsible. Otherwise elements would stay open but can be closed by clicking them twice.
.is-collapsible{
  height: 0;
  &.is-active{
    height: auto;
  }
}
4

3 回答 3

1

仅前端更改

为了使用仅前端选项,我建议以下内容。

  • 我们需要为该可折叠元素存储状态。
  • 我们需要在每次套接字通道更新时恢复该元素的可折叠状态

为简单起见,我将使用纯 JavaScript。

我们需要修改按钮,并编写函数来存储状态(我使用简单的localStorage)

<a class="collapse_trigger" onclick="memoizeCollapsibleState()">...</a>

<script type="text/javascript">
  function memoizeCollapsibleState() {
    if (!localStorage.getItem('collapsibleState')) {
      localStorage.setItem('collapsibleState', true)
    } else {
      localStorage.removeItem('collapsibleState')
    }
  }
</script>

之后我们需要编写函数来从本地存储中恢复该状态

function restoreCollapsibleState() {
  var collapsibleEl = document.getElementById('collapseExample');

  if (localStorage.getItem('collapsibleState')) {
    collapsibleEl.classList.add('is-active')
  }
}

最后一刻我们需要将该函数绑定到phoenix_live_view套接字更新,我们需要在加载窗口后立即执行此操作。

window.onload = init;

function init() {
  liveSocket.getSocket().channels[0].onMessage = function (e, t, n) {
    setTimeout(restoreCollapsibleState, 10)
    return t
  } 
}

函数的目的setTimeout是套接字更新是异步操作,我们需要添加一些延迟才能恢复可折叠状态。10ms 似乎没问题,但我们可以将其更改为任何其他去抖动功能,我只是为了简单起见,认为这是概念证明

仅后端更改

我刚刚用默认的 live phoenix 结构做了一个例子
mix phx.new my_app --live

为其添加了引导程序(但我认为 bulma 遵循大致相同的规则)并将 phoenix 实时模板修改为以下

<div class="collapse <%= if (@results && String.trim(@query) != ""), do: "show", else: "" %>" id="collapseExample">
  <div class="card card-body">
    <%= for {app, _vsn} <- @results do %>
      <p value="<%= app %>"><%= app %></p>
    <% end %>
  </div>
</div>

因此,如果有任何结果并且查询不为空,则永远不会折叠。

对于你的情况,我认为它会略有不同

<a class="collapse_trigger">...</a>
<div class="is-collapsible <% if (@results && String.trim(@query) != "") do: "is-active", else: "" %>">
# content
</div>
于 2021-02-12T11:36:04.540 回答
0

根据@zhisme 的回答,我创建了一个可行的解决方案。唯一的要求是可折叠项具有 id。

function initCollapsibles() {
  const bulmaCollapsibleInstances = bulmaCollapsible.attach('.is-collapsible');
  bulmaCollapsibleInstances.forEach(bulmaCollapsibleInstance => {
    const key = 'collapsible_' + bulmaCollapsibleInstance.element.id;
    // some id's from other pages might leak over if we don't clean the localstorage on page load
    localStorage.removeItem(key)

    bulmaCollapsibleInstance.on('before:expand', (e) => {
      localStorage.setItem(key, true)
    })

    bulmaCollapsibleInstance.on('after:collapse', (e) => {
      localStorage.removeItem(key)
    })
  });
}

function restoreCollapsiblesState() {
  const collapsibles = Array.prototype.slice.call(document.querySelectorAll('.is-collapsible'), 0);
  collapsibles.forEach(el => {
    if (localStorage.getItem('collapsible_' + el.id)) {
      // I'd love to call the collapsible open method, but haven't figured out how to get that instance without creating a new one or storing it to the window globally.
      el.classList.add('is-active')
    }
  });
}

document.addEventListener('phx:update', restoreCollapsiblesState);
document.addEventListener('DOMContentLoaded', initCollapsibles);
于 2021-02-21T13:32:11.943 回答
-1

一个众所周知的,如果不是优雅的解决方案是,你addEventListener在那个元素上mount(),然后把它放回去update()。这是想法

Utils.onDetailsTagState = {
  mount(storeEl) {
    let detailsTag = storeEl.el.closest("details")
    if (detailsTag) {
      storeEl.expand = detailsTag.open
      detailsTag.addEventListener("toggle", event => {
        storeEl.expand = event.target.open
      })
    }
  },
  update(storeEl) {
    let detailsTag = storeEl.el.closest("details")
    if (detailsTag) { detailsTag.open = storeEl.expand }
  }
}

Hooks.callaspable = {
  mounted() { Utils.onDetailsTagState.mount(this) },
  updated() { Utils.onDetailsTagState.update(this) },
}
于 2020-10-27T18:18:02.743 回答