簡體   English   中英

Elixir phoenix LiveView 可折疊在更新時折疊

[英]Elixir phoenix LiveView collapsible collapses on update

問題

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

僅前端更改

為了使用僅限前端的選項,我建議如下。

  • 我們需要為該可折疊元素存儲狀態。
  • 我們需要在每次套接字通道更新時恢復該元素的可折疊狀態

為簡單起見,我將使用純 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函數的目的是 socket 更新是異步操作,我們需要添加一些延遲才能恢復可折疊狀態。 10ms 似乎沒問題,但我們可以將其更改為任何其他去抖動函數,我只是為了簡單起見使用它,認為這是概念證明

僅后端更改

我剛剛用默認的活鳳凰結構做了一個例子
mix phx.new my_app --live

向它添加了引導程序(但我認為 bulma 遵循基本相同的規則)並將 phoenix live 模板修改為以下內容

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

根據@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);

一個眾所周知的,如果不是優雅的解決方案是在mount()上的那個元素上addEventListener並將它放回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) },
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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