1.5' gem 'stimulus-rails', '~> 1.3'" /> 1.5' gem 'stimulus-rails', '~> 1.3'" /> 1.5' gem 'stimulus-rails', '~> 1.3'"/>

Why is rails 7 turbo_frame or turbo_stream not rendering chartkick or rails_chart?

251 Views Asked by At

I am using the following:

gem "rails", "7.0.8" 
gem "sprockets-rails"                        
gem "jsbundling-rails"
gem 'turbo-rails', '~> 1.5'  
gem 'stimulus-rails', '~> 1.3'
gem "cssbundling-rails"       
gem 'importmap-rails', '~> 1.2', '>= 1.2.3'
gem 'rails_charts', '~> 0.0.6'
#gem 'chartkick', '~>5.0.5'

With this code below I can basically, follow the flow all the way through. That is the turbo_stream.update will render the partial, fill in the data, then send the response back to the browser and replace foobar with the rails_charts script. But it seems the script code does not execute and therefore not render chart. I tried adding console.log JS calls in the partial and these do not render either. When I switch this to a turbo_frame, the test console.log JS calls render, but the chart still does not. I tried with chartkick as well - same difference.

I looked many of the diff stack overflow Q&A including this one Chartkick not loading in Ruby on Rails 7 https://github.com/hotwired/turbo-rails/issues/416 and several others, nothing has helped.

I did see that https://github.com/railsjazz/rails_charts in the "Contributing section" has "support turbo streams?" - which indicates turbo is not supported - but then I see the JS below and it has the turbo event listeners in there.

I feel this will be an amazing flow if I can get them working. For now, am reverting back to full page loads sadly until I can sort this out - any thoughts or help possible?

===

format.turbo_stream do
   render turbo_stream: [
      turbo_stream.update("avg_wait_area",
                         partial: "wait_stats/avg_wait_chart",
                              locals: { mon_stats: @mon_stats })
        ]
    end


in _avg_wait_chart.turbo_stream.erb
    <div id="avg_wait_area">
      foobar
    </div>



div id="rails_charts_339d948522be9104817e0757052eebc2663a61dc" class="box" style="width: 100%; height: 450px">
          <script>
            if (!window.RailsCharts) {
              window.RailsCharts = {}
              window.RailsCharts.charts = {}
            }

            function init_rails_charts_339d948522be9104817e0757052eebc2663a61dc(e) {
              if (document.documentElement.hasAttribute("data-turbolinks-preview")) return;
              if (document.documentElement.hasAttribute("data-turbo-preview")) return;

              <!-- RailsCharts::StackedBarChart -->
              var chartDom = document.getElementById('rails_charts_339d948522be9104817e0757052eebc2663a61dc');

              if (!chartDom) { return }

              var lib = ("echarts" in window) ? window.echarts : echarts;
              var chart = lib.init(chartDom, null, { "locale": null, "renderer": "canvas" });
              var option = {"series":[{"data":[0.24,2.15,3.1,3.1,4.54,5.3,5.77,6.25,9.36,16.81,22.97],"type":"bar","stack":{}}],"xAxis":{"type":"value"},"yAxis":{"type":"category","data":["A","b","c","d","f","g","h","i","j","k","l"]},"tooltip":{"trigger":"axis","axisPointer":{"type":"shadow"}},"toolbox":{"feature":{"saveAsImage":{}}},"title":{"text":"Average Wait Days - Dec-2023"},"visualMap":{"show":false,"min":0,"max":25,"orient":"horizontal"},"grid":{"containLabel":true,"left":0}};
              option && chart.setOption(option);

              window.RailsCharts.charts["rails_charts_339d948522be9104817e0757052eebc2663a61dc"] = chart;
            }

            function destroy_rails_charts_339d948522be9104817e0757052eebc2663a61dc(e) {
              var chart = window.RailsCharts.charts["rails_charts_339d948522be9104817e0757052eebc2663a61dc"];
              if (chart) {
                chart.dispose()
              }
              delete window.RailsCharts.charts["rails_charts_339d948522be9104817e0757052eebc2663a61dc"];
            }

            window.addEventListener('load', init_rails_charts_339d948522be9104817e0757052eebc2663a61dc);
            window.addEventListener('turbo:load', init_rails_charts_339d948522be9104817e0757052eebc2663a61dc);
            window.addEventListener('turbolinks:load', init_rails_charts_339d948522be9104817e0757052eebc2663a61dc);

            document.addEventListener("turbolinks:before-render", destroy_rails_charts_339d948522be9104817e0757052eebc2663a61dc);
            document.addEventListener("turbo:before-render", destroy_rails_charts_339d948522be9104817e0757052eebc2663a61dc);
          </script>
        </div>

Again, I tried all of the above.

1

There are 1 best solutions below

2
Alex On

Problem is none of those events are fired when rendering a turbo_stream which means chart is not initialized. Solution is pretty simple: don't use event listeners at all. To make it even better wrap it in Stimulus:

bin/rails g stimulus chart
// app/javascript/controllers/chart_controller.js

import { Controller } from "@hotwired/stimulus"
import "echarts";
import "echarts/theme/dark";

// Connects to data-controller="chart"
export default class extends Controller {
  static values = {
    option: Object,
    theme: String,
    locale: String,
    renderer: String,
  }

  connect() {
    if (document.documentElement.hasAttribute("data-turbo-preview")) return;
    this.chart = echarts.init(this.element, this.themeValue, { locale: this.localeValue, renderer: this.rendererValue });
    this.chart.setOption(this.optionValue);
  }

  disconnect() {
    this.chart?.dispose()
  }
}

Override generate_rails_chart helper to use this stimulus controller instead of generating inline javascript:

# app/helpers/application_helper.rb

# this helper method is used by all the other more specific chart helpers
def generate_rails_chart(klass, data, options = {})
  chart = klass.new(data, options)

  tag.div class: "w-full h-[450px]", data: {
    controller:           :chart,
    chart_option_value:   chart.option,
    chart_theme_value:    chart.theme,
    chart_locale_value:   chart.locale,
    chart_renderer_value: chart.renderer
  }
end

Render on a page or in a turbo stream partial:

<%= line_chart({1=>10, 2=>20, 3=>15}) %>

or from controller:

respond_to do |format|
  format.turbo_stream do
    render turbo_stream: turbo_stream.update(
      "avg_wait_area",
      helpers.line_chart({1=>10, 2=>20, 3=>15})
    )
  end
end

This is what should be generated by chart helpers, there is no javascript code here:

<div class="w-full h-[450px]"
  data-controller="chart"
  data-chart-option-value="{&quot;series&quot;:[{&quot;data&quot;:[10,20,15],&quot;type&quot;:&quot;line&quot;}],&quot;xAxis&quot;:{&quot;type&quot;:&quot;category&quot;,&quot;data&quot;:[1,2,3]},&quot;yAxis&quot;:{&quot;type&quot;:&quot;value&quot;},&quot;tooltip&quot;:{&quot;trigger&quot;:&quot;axis&quot;},&quot;toolbox&quot;:{&quot;feature&quot;:{&quot;saveAsImage&quot;:{}}}}"
  data-chart-renderer-value="canvas"
></div>

Stimulus takes care of the rest, regardless of how you render it: on a page or turbo stream. I've tested it.