How to call JS events like `show_modal` from a LiveView Hook?

Hey Guys,

I’m building a Command Palette, with Meilisearch as the search engine.

With a phx-mounted event, the command palette can be opened using show_modal(@id), or same with a global event involving 1 key.

i.e.

phx-window-keyup={show_modal()}
phx-key="p"

But I want to open the Command Palette, using Cmd+p, so I created a hook.

<div
  id={@id}
  phx-mounted={@show && show_modal(@id)}
  phx-remove={hide_modal(@id)}
  data-cancel={JS.exec(@on_cancel, "phx-remove")}
  class="relative z-50 hidden"
  phx-hook="CommandPalette"
>

But I had to rewrite the logic, which is already there in show_modal, and it’s not even close! (There’s no transition, or focus on 1st focus-sable element!)

const CommandPalette = {
  mounted() {
    document.addEventListener("keydown", (e) => {
      if (e.key === "k" && e.metaKey) {
        e.preventDefault();
        this.el.focus();
      } else if (e.key === "p" && e.metaKey) {
        e.preventDefault();

        const bg = document.querySelector(`#${this.el.id}-bg`);
        const container = document.querySelector(`#${this.el.id}-container`);
        const content = document.querySelector(`#${this.el.id}-content`);

        this.el.style.display = "block";
        bg.style.display = "block";
        bg.classList.add("opacity-100");
        container.style.display = "block";
        document.body.classList.add("overflow-hidden");
        content.focus();
      }
    });
  },
};

export default CommandPalette;

So, how do I go about calling the show_modal function from the Command Palette, without a round-trip to the server?
I want the Command Palette to be snappy and also be able to reuse the serialized JS commands that came down from the server.


How to trigger this, from within Hook?

phx-window-keyup='[["show",{"display":null,"time":200,"to":"#command-palette","transition":[[],[],[]]}],["show",{"display":null,"time":200,"to":"#command-palette-bg","transition":[["transition-all","transform","ease-out","duration-300"],["opacity-0"],["opacity-100"]]}],["show",{"display":null,"time":200,"to":"#command-palette-container","transition":[["transition-all","transform","ease-out","duration-300"],["opacity-0","translate-y-4","sm:translate-y-0","sm:scale-95"],["opacity-100","translate-y-0","sm:scale-100"]]}],["add_class",{"time":200,"names":["overflow-hidden"],"to":"body","transition":[[],[],[]]}],["focus_first",{"to":"#command-palette-content"}]]'

Go here and search on the page for execjs

1 Like

Thanks @cmo,

I don’t know how I missed this entirely.

I didn’t go through the whole documentation, and this is what happens. Learning by trial and error.

<div
   id={@id}
   phx-mounted={@show && show_modal(@id)}
   phx-remove={hide_modal(@id)}
   data-cancel={JS.exec(@on_cancel, "phx-remove")}

   data-show-modal={show_modal(@id)}          # We can serialize the JS commands
   phx-hook="CommandPalette"
>

Then execJS on UI side!!!

const CommandPalette = {
  mounted() {
    document.addEventListener("keydown", (e) => {
      if (e.key === "k" && e.metaKey) {
        e.preventDefault();

        // Execute serialized JS
        liveSocket.execJS(this.el, this.el.getAttribute("data-show-modal")); 
      }
    });
  },
};

export default CommandPalette;

Here’s a nice article: