krishandley

krishandley 

window._hooks = {};

export default {
  mounted() {
    window._hooks[this.el.id] = this;
  },

  destroyed() {
    delete window._hooks[this.el.id];
  },
};

<my-thing … phx-hook=“Lit” />

You can also combine them with hooks with a little hack so you can use all the hook methods in the web components if needed.

2
Post #11
adw632

adw632

The Elixir Conf talk by Chris Nelson shows use of web components and how we can easily wrap anything. He also uses Lit (by Google) but there are many other choices, but Lit seems to be the most popular approach for making web components:

1
Post #12
csokun

csokun

shoelace is pretty impressive I can see myself adopting something like that :smiley:

0
Post #13
adw632

adw632

Nice one, aside from that and the live element hook for event ergonomics that is all the hooks one ever needs to do anything with custom elements/web components.

I am also looking at some other interesting ideas from @superchris, in particular his live_state solution for delivering state to custom elements over web sockets / phoenix channels as a total replacement for liveview.

My research suggests we can deliver state from our Phoenix backed to the front end in about the most streamlined fashion possible with live_state, and then on the front end we can deliver that state into web components using lit-state for full reactivity on the front end with whatever web components / custom elements we choose to use.

In fact I am also looking at going full PWA for my front end using create-lit-pwa and live_state (which also delivers events into our phoenix backend just like LiveView but it’s not LiveView) with no need for graphql whilst keeping my phoenix backend the same and just interacting with my contexts via live_state rather than LiveView.

The goal is web standards based UI, high adherence to web acccessibility with established web components and zero javascript framework nonsense, avoiding web API layers, and with a total overhead of just 5kb for my client app shell with PWA support.

If you think about it, all a front end needs is state delivered from the backend to render and the ability to send events to the backend and receive updated state to re-render. We don’t need complex API layers and bloated client side web frameworks with the available web standards already supported in all the browsers.

I feel every day that I’m getting closer to my utopia for web development and it’s likely LiveView will not be in that future but the similar idea applied to sharing state to the front end via live-state might certainly be.

In any event I will be publishing my learnings with some documented starter projects based on my explorations for others to follow.

7
Post #14
ouven

ouven

Hi, I still owe you an example. This is production code, that works since 4 years now.

The example’s purpose is to show a localized date time.

Starting with the custom component:

class MyLocalDatetime extends HTMLElement {
  format (name) {
    return ({
      long: { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' },
      short: {}
    })[name]
  }

  constructor () {
    super()
    this._format = 'short'
    this._date = null
  }

  _rerender () {
    this.innerHTML = new Date(this._date).toLocaleString([], this.format(this._format))
  }

  static get observedAttributes () {
    return ['iso-datetime', 'format']
  }

  attributeChangedCallback (name, _oldValue, newValue) {
    (({
      'iso-datetime': (newValue) => { this._date = newValue },
      'format': (newValue) => { this._format = newValue }
    })[name])(newValue)
    this._rerender()
  }
}

window.customElements.define('my-local-datetime', MyLocalDatetime)

I wrapped it in a live_view component:

  @doc """
    Renders a DateTime to a local datetime

    opts:
    - date: DateTime struct
  """
  attr :datetime, DateTime, required: true
  attr :format, :string, default: "short", values: ["short", "long"]
  attr :attrs, :global

  def my_local_datetime(assigns) do
    ~H"""
    <my-local-datetime {@attrs} format={@format} iso-datetime={@datetime} phx-update="ignore"><%= @datetime %></my-local-datetime>
    """
  end

When you change the datetime attribute in your live view, then the attributeChangedCallback is triggered and the content is rerenderd.

I try to avoid big frameworks, because they come and go and they all come and go with their own complexity. So I mostly write vanilla JS.
But you can also use this method to glue js libraries in you project. I have done this for maps to be integrated in live view, for instance too.

12
Post #15
chris808

chris808

After a few attempts, I managed to wrap the shoelace color picker component.

https://www.npmjs.com/package/phoenix-custom-event-hook makes things a lot simpler, but required some tweaking so I could access the new color value.

Sharing here in case anyone else is looking to do the same (feedback also appreciated as I’m very new to pheonix :))

Live component:

defmodule DemoWeb.ColorPicker do
  use DemoWeb, :live_component

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.12.0/cdn/themes/dark.css" />
      <script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.12.0/cdn/components/color-picker/color-picker.js"></script>
      <sl-color-picker
        id={@id}
        phx-hook="PhoenixCustomEvent"
        phx-send-events="sl-change"
        class="sl-theme-dark demo-color-picker"
        value={@value}
        inline={@inline}
        >
      </sl-color-picker>
      <style>
        .demo-color-picker::part(base) {
          background: rgba(0, 0, 0, 0.0);
          border: none;
        }
      </style>
    </div>
    """
  end
end

Add and tweak the custom hook in app.js:

import PhoenixCustomEvent from "phoenix-custom-event-hook";
PhoenixCustomEvent.serializeEvent = (event) => {
    return {
        value: event.target.value,
        detail: event.detail,
    };
};

Live view:

<.live_component module={DemoWeb.ColorPicker} id="this-picker" inline={:true} value={@my_color} />
...
  def handle_event("sl-change", params, socket) do
    {:noreply, assign(socket, my_color: params["value"])}
  end
1
Post #16
Sebb

Sebb

There is this related discussion:

Not about web components but a more generic approach.

(And also: a friendly reminder that I’m biting my nails till you publish some demo for Adobe Spectrum/shoelace + LV)

2
Post #17
Sebb

Sebb

TW3.4 is out and there is no change in this regard.

0
Post #18
cvkmohan

cvkmohan OP

My mistake. I think it is Tailwindcss 3.5, which comes with the Oxide engine. It is going to greatly simplify the build system it seems.

1
Post #19
— All posts loaded —