Skip to content

JavaScript Interop

PyView uses the Phoenix LiveView JavaScript client. You can customize its behavior through global configuration.

Hooks let you integrate third-party JavaScript libraries with your LiveView. Use them when you need to:

  • Initialize a JS library on an element (maps, charts, rich text editors)
  • Handle client-side interactions that need server communication (drag and drop, drawing)
  • Manage JS library lifecycle as elements are added/removed from the DOM

Define hooks on window.Hooks before pyview’s script loads:

window.Hooks = window.Hooks ?? {};
window.Hooks.MyMap = {
mounted() {
// Element added to DOM - initialize your library
this.map = new MapLibrary(this.el);
},
updated() {
// Element's attributes changed - refresh if needed
this.map.refresh();
},
destroyed() {
// Element removed - cleanup
this.map.cleanup();
}
};

Attach hooks to elements with phx-hook. The element must have a unique id:

<div id="map" phx-hook="MyMap" data-lat="{{lat}}" data-lng="{{lng}}"></div>
  • mounted() - Element added to DOM
  • updated() - Element’s attributes/content changed
  • destroyed() - Element removed from DOM
  • beforeUpdate() - Before DOM update
  • disconnected() - Socket disconnected
  • reconnected() - Socket reconnected

Push events from JavaScript to your LiveView’s handle_event:

// In your hook
this.pushEvent("marker-clicked", { lat: 45.5, lng: -122.6 });

Handle events pushed from the server via socket.push_event():

// In your hook's mounted()
this.handleEvent("highlight-marker", ({ id }) => {
this.map.highlightMarker(id);
});

Read data attributes from this.el:

mounted() {
const parks = JSON.parse(this.el.dataset.parks);
parks.forEach(park => this.map.addMarker(park));
}

Maps with Leaflet - The maps example shows bidirectional communication: clicking a marker pushes an event to the server, and selecting from a list pushes an event back to pan the map.

Drag and Drop with SortableJS - The kanban example uses hooks to integrate SortableJS, sending task reorder events to the server when cards are dragged between columns.

For advanced customization, define window.LiveViewConfig before pyview’s script loads.

Use the head_content parameter of defaultRootTemplate:

from pyview.template.root_template import defaultRootTemplate
from markupsafe import Markup
root_template = defaultRootTemplate(
head_content=Markup("""
<script>
window.LiveViewConfig = {
dom: {
onBeforeElUpdated(fromEl, toEl) {
// Custom logic here
}
}
};
</script>
""")
)
OptionDescription
hooksAdditional hooks (merged with window.Hooks)
paramsAdditional connection params
uploadersCustom file upload handlers
domDOM update callbacks

Full Attribute Sync for phx-update="ignore"

Section titled “Full Attribute Sync for phx-update="ignore"”

Phoenix’s phx-update="ignore" preserves content but only syncs data-* attributes. For web components that need all attributes synced (like class, aria-*, etc.) while preserving content, use LiveViewConfig:

window.LiveViewConfig = {
dom: {
onBeforeElUpdated(fromEl, toEl) {
if (fromEl.getAttribute("phx-update") === "ignore") {
for (const { name, value } of toEl.attributes) {
if (fromEl.getAttribute(name) !== value) {
fromEl.setAttribute(name, value);
}
}
for (const { name } of [...fromEl.attributes]) {
if (!toEl.hasAttribute(name)) {
fromEl.removeAttribute(name);
}
}
}
}
}
};

This syncs all attributes for every phx-update="ignore" element. For selective behavior, add a custom attribute check:

// Only sync when data-sync-attrs is present
if (fromEl.getAttribute("phx-update") === "ignore" &&
fromEl.hasAttribute("data-sync-attrs")) {
// ... sync attributes
}
<my-component phx-update="ignore" data-sync-attrs class="{{state}}">
<!-- Content preserved, all attributes synced -->
</my-component>
ModeAttributesContent
(default)All syncedSynced
ignoreOnly data-*Preserved
streamSyncedManaged

The LiveSocket instance is available on window.liveSocket:

liveSocket.enableDebug()
liveSocket.enableLatencySim(1000)
liveSocket.disableLatencySim()