Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elixir phoenix LiveView collapsible collapses on update

Problem

LiveView collapses my opened element.

Details

I have an element that starts off as being collapsed on page load:

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

If a user clicks on the collapsible, the collapsible has a class .is-active.

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

But liveview removes that class. Any idea how I can make sure that liveview ignores the parent element <div class="is-collapsible is-active"> but takes care of the children? My first thought was phx-update="ignore". But now I'm thinking I need to put the logic of the collapsible into the backend. :/

Additional info

I use bulma-collapsible with one css change:

// 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;
  }
}
like image 487
Joe Eifert Avatar asked Oct 27 '20 08:10

Joe Eifert


1 Answers

Front-end changes only

In order to use front-end only options I advise the following.

  • We would need to store state for that collapsible element.
  • We would need to restore collapsible state of that element on every socket channel update

For simplicity I will use plain javascript.

We need to modify button, and write function to store state (I used simple 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>

afterwards we need to write function that will restore that state from localstorage

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

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

And final moment we need to bind that function to phoenix_live_view socket update, we need to do it right after window has been loaded.

window.onload = init;

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

The purpose of setTimeout function is that socket updates is async operation, and we need to add some delay in order to have collapsible state restored. 10ms seems to be ok, but we can change it to any other debounce function, I just used it for simplicity, consider this is as proof of concept

Back-end changes only

I just made an example with default live phoenix structure
mix phx.new my_app --live

Added bootstrap to it (but I think bulma follows mostly the same rules) And modified phoenix live template to the following

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

so if there are any results and query is not empty thus will not be ever collapsed.

For your case I think it will be slightly the same

<a class="collapse_trigger">...</a>
<div class="is-collapsible <% if (@results && String.trim(@query) != "") do: "is-active", else: "" %>">
# content
</div>
like image 100
zhisme Avatar answered Sep 29 '22 04:09

zhisme