ClojureScript - Getting Om + echartsjs to work

clojurescript

#1

Hi, I’m new to cljs, and om.
I’m trying to transform this cljs script

(ns omtu.core
  (:require [goog.dom :as gdom]
            ;; [cljsjs.echarts :as baidu]
            [om.next  :as om :refer-macros [defui]]
            [om.dom   :as dom]
            [js.echarts]
            [taoensso.timbre :as timbre
             :refer-macros [log  trace  debug  info  warn  error  fatal  report
                            logf tracef debugf infof warnf errorf fatalf reportf
                            spy get-env]]))
(enable-console-print!)

(defn render []
  (set! (.-innerHTML (js/document.getElementById "scatterp"))
        "<h1>Hello Chestnut!</h1>"))

(defn render-plot []
  (let [element   (js/document.getElementById "scatterp")
        the-chart (.init js/echarts element)
        option    {:title   {:text "Simple"}
                   :tooltip {}
                   :legend  {:data ["Sales"]}
                   :xAxis   {:data ["aik" "amidi" "chiffon shirt" "pants" "heels" "socks"]}
                   :yAxis   {}
                   :series  [{:name "Sales"
                              :type "bar"
                              :data [5, 20, 36, 10, 20, 30]}]}]
    (.setOption the-chart (clj->js option))))

(spy :warn "render-plot")
(render-plot)

to work with om next :

(defui SPlot0
  Object
  (render [this]
    (let [element     (.createElement js/document "DIV")
          element-2   (dom/div #js {:height 400 :width 500} "holder")
          the-chart (js/echarts.init element)
          option    {:title   {:text "Simple Minded"}
                     :tooltip {}
                     :legend  {:data ["Sales"] }
                     :xAxis   {:data ["aik" "amidi" "chiffon shirt" "pants" "heels" "socks"]}
                     :yAxis   {}
                     :series  [{:name "Sales"
                                :type "bar"
                                :data [5, 20, 36, 10, 20, 30]}]}
          ]
      (set! (.-option the-chart) (clj->js option))
      ;; (.setOption the-chart (clj->js option))
      element)))

(def splot0 (om/factory SPlot0))

(js/ReactDOM.render (splot0) (gdom/getElement "scatterp"))

When I use element-2 I get :

TypeError: this.dom.getContext is not a function

And When I use element I get :

Error: omtu.core/SPlot0.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.

#2

Perhaps I should have asked more succinctly :
what is the difference between creating a plain js dom element like (.createElement js/document "DIV")
and creating one using om wrappers om.dom/div?


#3

You’re right, they are different. There’s a couple of different ways to approach how they’re different, one conceptually, and one practically. One of the strengths of React is that it abstracts the DOM: the developer is rarely operating on the DOM directly. Instead, you’re operating on a virtual DOM which the React engine then transforms into the DOM when the DOM needs to be rendered. From that point of view, it makes sense that doing direct DOM manipulation in a component isn’t going to be the same as using the om.dom wrappers.

A practical way to approach it is to look at the om.dom source to see what’s going on. There are actually two files:

This is more challenging (at least to me), as I haven’t dug into this area of the source before. Glancing through the source, I do see a create-element function, which in turn calls js/React.createElement. That’s another indication that something potentially quite different is going on.

I know I haven’t explained the particulars of what’s different between the two, but I think this is enough to understand why calling returning the results of (.createElement js/document ,,,) isn’t going to work as you’d expect it to.

Taking a look at your code again, one difference between the two scripts is that the first is selecting a dom element, rather than creating one. I’d take a look at other examples of using js libraries in Om.next. Here’s a tutorial that does something similar:

http://book.fulcrologic.com/#_taking_control_of_the_sub_dom_d3_etc

Fulcro is kind of like Om.next.next: it started out as a library on top of Om.next but has just recently forked. One difference you’ll see is the macro defsc, which provides a DRYer way of writing defui.

Based on that (and translating back from defsc to defui), I’d rough out your example as (untested):

(defn render-plot [component props]
  (let [element (dom/node component)
        the-chart (.init js/echarts element)
        option {:title   {:text "Simple Minded"}
                :tooltip {}
                :legend  {:data ["Sales"] }
                :xAxis   {:data ["aik" "amidi" "chiffon shirt" "pants" "heels" "socks"]}
                :yAxis   {}
                :series  [{:name "Sales"
                           :type "bar"
                           :data [5, 20, 36, 10, 20, 30]}]}]
    (.setOption the-chart (clj->js option))))

(defui SPlot0
  Object
  (componentDidMount [this]
    (render-plot this (om/props this)))
  (render [this]
    (dom/div nil "some placeholder text")))

(def splot0 (om/factory SPlot0))

I really like the ideas behind Om.next, but have struggled with it as-is. Fulcro has made a lot of that easier, and Tony Kay’s videos and documentation are a great resource. There’s still not as much out there as I’d like with respect to working with plain js and React javascript components, but there’s enough to get going. I encourage you to take a look.


#4

Thanks a lot @grzm for your full answer.
(Sorry it took this long to thank, I have been moved back to some back-end tasks ever since)


#5