chartjs / chartjs-adapter-luxon

Luxon adapter for Chart.js
MIT License
33 stars 24 forks source link

Detect when loaded #62

Closed freecorvette closed 1 year ago

freecorvette commented 1 year ago

Hello, I'm loading: https://cdn.jsdelivr.net/npm/chart.js https://cdn.jsdelivr.net/npm/luxon https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon

asnchronously and I need to detect when they are loaded, since each one depends on the previous and drawing the chart depends on all three. For chart and luxon, I'm able to find out when they are loaded using:

typeof Chart != "undefined" and // typeof Chart._adapters._date == "undefined" typeof luxon !== "undefined"

however I'm not able to figure out a way to tell when chartjs-adapter-luxon was loaded. It seems to override some methods in Chart._adapters._date, but I don't know which ones. Do you have any advice?

Note: I cannot use the <script onload> event reliably, since it doesn't trigger on cached versions (i.e. when the browser requests witj If-modified-since and the server responds with status code 304 not modified).

stockiNail commented 1 year ago

@freecorvette having a look your issue, let me say that

typeof Chart !== "undefined" <---- tests CHART.JS
and
typeof Chart._adapters._date !== "undefined" <---- tests CHARTJS-ADAPTER-LUXON and not LUXON

Luxon is a backend library:

CHART.JS
   |
CHARTJS-ADAPTER-LUXON
   |
LUXON

That said, if you have (typeof Chart !== 'undefined' && typeof Chart._adapters._date !== "undefined') you should be almost sure to have everything loaded. Anyway I'm curious to know how you can guarantee that the adpater will load when chartjs and luxon are already loaded (if I'm not wrong this is mandatory).

freecorvette commented 1 year ago

@stockiNail I made a mistake in my original post.

The problem is that, right after loading CHART.JS (and CHART.JS only), typeof Chart._adapters._date !== "undefined" is also true. CHART.JS sets the date adapter. CHARTJS-ADAPTER-LUXON only redefines it. So the original question remains: how to check that CHARTJS-ADAPTER-LUXON was loaded?

To make sure CHARTJS-ADAPTER-LUXON loads only after both CHART.JS and LUXON are loaded, I'm currently loading CHART.JS first, then only when it's loaded I'm loading LUXON. And when LUXON is also loaded, I'm loading CHARTJS-ADAPTER-LUXON. I guess I could load CHART.JS and LUXON in parallel, but that's not a priority in the app, at the moment.

stockiNail commented 1 year ago

The problem is that, right after loading CHART.JS (and CHART.JS only), typeof Chart._adapters._date !== "undefined" is also true. CHART.JS sets the date adapter.

Yes, CHART.JS set a default adapter which is throwing an exception on all functions.

CHARTJS-ADAPTER-LUXON only redefines it. So the original question remains: how to check that CHARTJS-ADAPTER-LUXON was loaded?

Luxon adapter contains a private property (_id) set to 'luxon'.

https://github.com/chartjs/chartjs-adapter-luxon/blob/1b1c23073eb364bd5bd25de6161a19be44d5b5fc/src/index.js#L18

Therefore, as workaround, you could check that property, something like:

const adapterInstance = new Chart._adapters._date();
if (adapterInstance._id === 'luxon') {
  ... // Luxon adapter is loaded
}

As said, _id is a private option.

EDIT

When I wrote "private", I meant because the property is starting with _ and not because there is the @private tag

freecorvette commented 1 year ago

@stockiNail, the private member check workaround works, thanks for the detailed answer! In case anyone else needs it, I'm posting below my loading setup (the whole point was to load all scripts asynchronously on the web page and do it in parallel as much as possible -- that is, chain only scripts that depend on each other):

<script type="text/javascript">
  function checkLoaded() {
    if (typeof $ === "undefined") {
      return "jquery not loaded";
    }
    if (typeof duDatepicker === "undefined") {
      return "duDatepicker not loaded";
    }
    if (typeof luxon === "undefined") {
      return "luxon not loaded";
    }
    if (typeof $.fn.chosen === "undefined") {
      return "chosen not loaded";
    }
    if (typeof Chart === "undefined") {
      return "chart not loaded";
    }
    if (new Chart._adapters._date()._id !== 'luxon') {
      return "chart date adapter not loaded";
    }
    if (typeof myInitPageFunction === "undefined") {
      return "main js not loaded";
    }
    return "";
  }

  function onScriptsLoaded(src) {
    if (onScriptsLoaded.run) {
      // prevent any stray call
      return;
    }
    var error = checkLoaded();
    if (error) {
      //console.log(error);
      // script onload won't fire on cached (HTTP 304) scripts, so set up a timer as well
      clearTimeout(onScriptsLoaded.t);
      onScriptsLoaded.t = setTimeout(onScriptsLoaded, 1000);
      return;
    }
    onScriptsLoaded.run = true;
    //console.log("all libs loaded, run page init function");
    $(myInitPageFunction);
  }

  function loadScript(params) {
    const script = document.createElement('script');
    script.id = params.id;
    script.src = params.url;
    script.async = params.async;
    script.onload = function() {
      if (params.scripts && params.scripts.length) {
        for (var i = 0; i < params.scripts.length; i++) {
          loadScript(params.scripts[i]);
        }
      }
      if (params.onload) {
        params.onload(this.id);
      }
    }

    document.head.append(script);
  }

  // load jQuery, THEN jQuery Chosen plugin
  loadScript({id: 'jquery', url: 'https://code.jquery.com/jquery-3.6.0.min.js', async: true, onload: onScriptsLoaded, scripts: [
    {id: 'jschosen', url: '/javascript/jquery/plugins/chosen/chosen.jquery.min.js', async: true, onload: onScriptsLoaded}
  ]});

  // load ChartJS, THEN Luxon, THEN ChartJS Adapter Luxon
  loadScript({id: 'chart', url: 'https://cdn.jsdelivr.net/npm/chart.js@^3', async: true, onload: onScriptsLoaded, scripts: [
    {id: 'jsluxon', url: 'https://cdn.jsdelivr.net/npm/luxon@^2', async: true, onload: onScriptsLoaded, scripts: [
      {id: 'jsluxon-adapter', url: 'https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@^1', async: true, onload: onScriptsLoaded}
    ]}
  ]});

  // load duDatePicker plugin
  loadScript({id: 'dudatepicker', url: '/javascript/jquery/plugins/material-date-range-picker/dist/duDatepicker.min.js', async: true, onload: onScriptsLoaded});

  // load page main script
  loadScript({id: 'main', url: '/javascript/main.min.js', async: true, onload: onScriptsLoaded});
</script>