Live load JS or CSS file

Hi,

I’d like to create a LiveComponent (actually an Surface component) to handle a Leaflet map.
Is there a way to have live view load JS/CSS into the page only when the component is mounted. Something like dynamically adding the following to the head section.

<link rel="stylesheet" href={{ Routes.static_path(@conn, "/css/leaflet.css") }} >
<script type="text/javascript" src={{ Routes.static_path(@conn, "/js/leaflet.js") }} />
1 Like

Can’t you just add your code into the render of the LiveComponent? (with @socket instead of @conn)

leaflet.css and leaflet.js are not my code. It’s provided by Leafet so I just addeed these files to priv/static. I’d like to have them loaded (added in the HTML <head> section) when my map component is mounted.

I imagine something like this should work:

mounted() {
  const style = document.createElement("link");
  style.href = "path/to/css/file.css";
  style.rel = "stylesheet";
  document.head.appendChild(style);
}

That should create a <link> tag pointing to your css in the document head. You may also want to check if the file was already loaded so you don’t append the link multiple times, maybe with just a module variable and doing something like if(!css_loaded) { ... }.
I’m doing something similar for a personal library that lazy loads component definitions and it works fine.

3 Likes

Here is mounted hook:

    mounted() {
        const style = document.createElement("link");
        style.href = "/css/leaflet.css";
        style.rel = "stylesheet";
        (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(style);

        const js = document.createElement("script");
        js.src = "/js/leaflet.js";
        js.type = "text/javascript";
        (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(js);
   
        const mapid = this.el.id;
        var map = L.map(mapid).setView([51.505, -0.09], 13);
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        }).addTo(map);
    }

Back to the browser, I can see this error in the browser console:

Uncaught ReferenceError: L is not defined
    mounted ScubananaWeb.Live.Components.Map.hooks.js:14

Displaying the windows object in the console, I can see there’s a L object !
I thinks L is not initialized yet when I create the map (may be JS file is loading). Is there a way to delay it ?
I tried defer: false but it doesn’t seem to work.

Found it !

Just move map creation API class to javascript element onLoad function. Here is the working code:

mounted() {
        const mapid = this.el.id;
        const style = document.createElement("link");
        style.href = "/css/leaflet.css";
        style.rel = "stylesheet";
        (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(style);

        const js = document.createElement("script");
        js.src = "/js/leaflet.js";
        js.type = "text/javascript";
        js.onload = function () {
            var map = L.map(mapid).setView([51.505, -0.09], 13);
            L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
            }).addTo(map);
        };
        (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(js);
   
    }
1 Like

I create a hook to eval script in page

// app.js
const Hooks = {}
Hooks.evaljs = { mounted(){ eval(`//id:${this.el.id}\r\n`+this.el.innerHTML) } }
let liveSocket = new LiveSocket("/live", Socket, { hooks: Hooks })
//page.html.heex
<noscript>
  <script phx-hook="evaljs" id="eval0">
  alert("run in liveview")
  console.log(this) // the `this` is of mounted function 
  </script>
</noscript>