bartblast

bartblast

Creator of Hologram

It’s time to implement JavaScript interop for Hologram!

Eventually, there will be Hologram wrappers for most APIs, but sometimes we’ll need escape hatches - especially when working with legacy apps or integrating 3rd party libraries. Things like:

  • Integrating charting libraries (Chart.js, D3, etc.)
  • Using JavaScript-only APIs (Web APIs, browser-specific features)
  • Calling into existing JavaScript codebases
  • Leveraging the vast npm ecosystem

How would you envision a Hologram <> JavaScript interop API/DSL?

Some questions to spark discussion:

  • Should it be declarative or imperative?
  • How should data be marshaled between Elixir and JavaScript?
  • What would the ideal developer experience look like?
  • Are there specific JavaScript libraries you’re eager to use?
  • How should errors be handled across the boundary?

I have some ideas brewing, but I don’t want to bias the discussion by sharing them upfront. I’m genuinely curious about your use cases and what would make the most sense from a developer ergonomics perspective.

Looking forward to hearing your thoughts!

Showing Posts 1 to 10

Eiji

Eiji

For libraries I would prefer to call JS code without any changes. This way would make integrations simple and not depend on the Hologram or any other package. The most what we can do here is to query element using Hologram and pass it to JS code (something like an assign), so an Elixir term would be translated to JS DOM element.

For everything else I would like to have a Hologram wrappers. This way we should be able to even re-write JS library in Elixir with a proper DOM and WebAPI bindings. Depending on case some of those are short term (like simply read/write some client state) or long term (rewritten/new libraries that would be compiled to JS).

It would be interesting to write a library in Elixir and export it in a legacy or modern JS. One configuration thing would handle a lot of use cases in my opinion.

bartblast

bartblast OP

Creator of Hologram

Thanks for the input @Eiji! Could you share some rough code snippets showing how you’d envision the API/DSL? Even pseudocode would help.

Eiji

Eiji

Not sure how Hologram translates things, but in very short something like:

window = Hologram.DOM.get_window()
document = Hologram.DOM.get_document(window)
# 1:1 map of DOM APIs with Elixir naming
element = Hologram.DOM.query_selector(document, "#element-id")
map = Hologram.DOM.dataset(element)
# or 
value = Hologram.DOM.dataset(element, "key")

Hologram.DOM.add_event_listener(element, "click", fn event ->
  # …
end)
# or
Hologram.DOM.add_event_listener(element, "click", {:action, :action_name})
# or
Hologram.DOM.add_event_listener(element, "click", {:command :command_name})
# best if all of above would be supported

For JS we could have something like:

# For a nice error messages like:
# Failed to init lib_name, error: "Syntax error at …"
Hologram.JS.init_lib_with(:lib_name, ~JS"""
  JSLib.init({…})
""")

The point of above snippet is that we could call it in every init or action. This way we could for example initialise an editor in a tabbed only when switching to the right tab for example.

jam

jam

I was thinking it might be as simple as:

def template do
  ~JS"""
    # import js libs here
    # write your js code here
  """
end 

I’m not sure how calling Hologram actions and commands from the js code should work. I’m assuming that would be needed. Maybe some sort of shorthand that can be parsed when scanning the JS block?

Edit: I think we’d also need a way to execute JS outside of a template context. One example would be using the npm package idb which makes indexeddb easier to work with.

Maybe it could be as simple as dropping a .js file somewhere that Hologram would pick up and bundle and allow importing of its functions into Hologram. Maybe something like:

defmodule MyComponent do
  use Hologram.Component

  alias Hologram.JS.IDB # there is a corresponding IDB.js which houses the IDB logic written entirely in js

  def action(…) do
    …
    IDB.something(…)
  end
end

These thoughts are a bit off the cuff so take them for what you will :slightly_smiling_face:

bartblast

bartblast OP

Creator of Hologram

Thanks Eiji. Can you also give some use case for the code?

For example, assuming you could do this:
element = Hologram.DOM.query_selector(document, "#element-id")
what would you do with the element?

And in what cases you would need to use this:
Hologram.DOM.add_event_listener(element, "click", {:action, :action_name})
outside of .holo templates?

Eiji

Eiji

As in example code I was fetching the data-* attributes using dataset API and adding an event listeners for it.
https://caniuse.com/dataset

click event is rarely used also in LiveView, but it’s just a generic example that everyone would understand. There could be a custom events (especially when integrating with 3rd party JS libraries). Sometimes you want to temporarily add some event and the remove it. In some cases you want to stop event propagation based on some conditions.

There are tons of examples for all those cases. One of them is a “click away” implementation in vanilla JS that is possible by using a event.target element and contains API.

Such event listener may be called once and removed to not cause any conflicts with the rest of code.

See also a guide for creating and dispatching custom events:

bartblast

bartblast OP

Creator of Hologram

I think I understand the technical approach you’re proposing, but I’m trying to dig deeper into the specific use cases to make sure we’re solving real problems.

Regarding your example with <div id="element-id" data-user="123" data-role="admin"></div> - how would this work in practice since that div would already be managed by Hologram’s template system? Are you thinking about scenarios where you’re rendering some portions of the page outside of Hologram’s templates and then managing them through Hologram? Or are you envisioning querying elements that are already part of your .holo templates?

I’m trying to think through specific use cases and focus on the most common ones that are actually worth implementing. I don’t want to build features just for the sake of it.

The custom event listeners from 3rd party libraries - that makes total sense to me as a clear interop need. But for things like “click away” patterns or event propagation control, my thinking is that the whole goal of JS interop shouldn’t be to create a separate way of doing things in Hologram. The primary reason should be interoping with 3rd party libs and escape hatches.

For the rest, I’d rather cover it within Hologram’s way of thinking. For example - there’s no $click_away event, but there definitely will be. Same with event bubbling - Hologram should probably allow you to manage that within its template system rather than requiring you to drop down to imperative DOM manipulation.

Does that make sense? I’m trying to draw a line between “this needs JS interop because it’s inherently about external libraries and/or escape hatches” vs “this should be a first-class Hologram feature.”

What do you think?

olivermt

olivermt

For me the usecase is very straight forward and I suggest you even use it both as a thing to prototype with and as a showcase of interop.

I want to do a <div id=”map”/> and then initialize google maps on it.

In regular liveview this would just mean setting phx-update=”ignore” on it and start mapping JS hook events.

In Hologram I have no idea what would be most idiomatic since I haven’t really dug in yet due to lack of said JS interop.

I propose three usecases:

  1. Get the corners of the map (essentially the lon/lat of top left + lower right)
  2. Add a marker (ideally with some props like marker color etc)
  3. Delete a marker (this means you need to thread through back to Hologram the ID of an added marker)
Eiji

Eiji

Well … since you are changing Elixir code to JavaScript people would expect all JS bindings or they would write their own scripts. It’s really hard to tell what API would be needed as each app requires completely different things.

For example you may think that since Hologram handles all communication the XMLHTTPRequest is not needed any more. That’s true for requests send to same server, but what about 3rd party APIs? In many cases you want to move as much as possible to client, so the server focus on it’s core stuff.

data-* attributes may not be a best example, but what about classlist methods? Do you prefer to just add/remove/toggle class or maintain a list of classes? Use cases? It’s rather completely or almost completely up to developer creativity and specific project needs.

There are way to many cases to just mention them not even saying about describing or discussing each one.

However sure, you may be interested to focus on things that Hologram templates does not cover (better or worse). Anyway you would have lots of work with bindings for Canvas API, push notifications, sounds and many, many more … Today in browser you can do literally everything including FPS games. Of course nobody expects APIs related to FPS games in that early stage, but you would have to support everything sooner or later.

At the very end you can do a best practices to avoid working on attributes if the same could be done in templates. However I would not be surprised if one day someone would come with a huge template example and start to complain that all the logic has to be done in it as there are no JS bindings available that would split templates and business logic. Again everything depends on case. There is no rule to cover all cases, so better to support as much as possible.

If one day you would do so then people instead of complaining would rewrite the JavaScript libraries into Hologram components as with complete API, the migration would be very simple, easily work in legacy browsers and everything could be done with same code style (see the Elixir naming in Hologram.DOM mentioned previously).

kingdomcoder

kingdomcoder

Hi, @bartblast

Have you considered ideas from Elm?

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 94592 917
New
cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
AstonJ
Since we have deprecated our Erlang sections (as we have dedicated Erlang Forums now) let’s add this thread for those who’d like to post ...
New
Null-logic-0
What IDE or editor are you using for Elixir development? Personally, I use Zed, and I really like it, but sometimes I wish there were a ...
New
maennchen
:warning: Security advisory: Decimal DoS vulnerability A vulnerability has been published for decimal where very large exponents can cau...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews