ClojureScript for the DevExtreme React Grid

Oliver's Blog
22 August 2017

I had a question recently about ClojureScript and how easy it would be to use the DevExtreme React Grid with that language and environment. ClojureScript is a functional language, which is a concept that works very well with React.

The CLJSJS repository provides an easy way for ClojureScript developers to depend on JavaScript libraries (in their own words), but JavaScript libraries need to be packaged and maintained in a specific format for this to work. I’m leaving this approach for later, since right now the React Grid has not reached a full release yet.

Meanwhile I found a nice recipe in this post by Tomer Weller, on the topic of including arbitrary JavaScript libraries in a ClojureScript project. I created a demo project, which is available here: https://github.com/oliversturm/demo-dx-react-grid-clojurescript

In case you have never heard about Clojure or ClojureScript, I recommend reading Danial Higginbotham’s book Clojure for the Brave and True and this very nice ClojureScript tutorial.

Requirements to build or try the demo

You will need both a working Node environment and an installation of Leiningen. Follow the two links in the previous sentence for installation files and instructions – both are quick and very straight-forward and you shouldn’t run into trouble.

How to build the demo yourself

Of course you can skip this part and just get the whole thing from github – in that case scroll down to find the instructions for running the demo. If you would like to do it yourself, read on.

The steps I followed are based on the post linked above, but I will also describe the demo code itself.

Project setup and library import

1 — Set up the project

Use this command to create a new project (feel free to modify the name of course):

lein new reagent-frontend demo-dx-react-grid-clojurescript

Now initialize the project with a Node package.json file (it’s okay to accept all the defaults):

npm init

Install the required JavaScript packages:

npm install --save-dev bootstrap webpack react react-dom react-bootstrap @devexpress/dx-react-core @devexpress/dx-react-grid @devexpress/dx-react-grid-bootstrap3

2 — Create the JavaScript bundle

Create the file webpack.config.js with the following content (there should be no need to modify anything in here):

const webpack = require('webpack');
const path = require('path');

const BUILD_DIR = path.resolve(__dirname, 'public', 'js');
const APP_DIR = path.resolve(__dirname, 'src', 'js');

const config = {
  entry: `${APP_DIR}/main.js`,
  output: {
    path: BUILD_DIR,
    filename: 'bundle.js'
  }
};

module.exports = config;

Create the file src/js/main.js with the content below. This is the main file used by webpack, and it configures all the JavaScript dependencies to be made available in the window context. It also means that webpack finds the actual requirements and includes the correct packages in the bundle.

window.deps = {
  react: require('react'),
  'react-dom': require('react-dom'),
  'dx-react-core': require('@devexpress/dx-react-core'),
  'dx-react-grid': require('@devexpress/dx-react-grid'),
  'dx-react-grid-bootstrap3': require('@devexpress/dx-react-grid-bootstrap3')
};

window.React = window.deps['react'];
window.ReactDOM = window.deps['react-dom'];

For simplicity, add a build entry to the scripts section of package.json and remove the test entry. Your scripts section should look like this:

...
  "scripts": {
    "build": "webpack -p"
  },
...

Now run the build script to generate the bundle:

npm run build

3 — Modify project.clj for the bundle

Edit the file project.clj. First, change the dependencies block (within the first few lines) to the following to make sure that Reagent uses React and ReactDOM from the bundle in place of its own versions.

:dependencies [[org.clojure/clojure "1.8.0" :scope "provided"]
               [org.clojure/clojurescript "1.9.908" :scope "provided"]
               [reagent "0.7.0" :exclusions [cljsjs/react cljsjs/react-dom]]]

Second, insert the following block right behind both lines starting with :optimizations. This changes both compiler profiles to include the bundle instead of the standard CLJSJS libraries for React and ReactDOM. (Note that it is correct for the ReactDOM name to read cljsjs.react.dom, i.e. with a . (dot) instead of a dash.)

:foreign-libs [{:file "public/js/bundle.js"
                :provides ["cljsjs.react" "cljsjs.react.dom" "webpack.bundle"]}]

If you have trouble finding the right place to insert the last block, please double-check with the complete version from my demo.

4 — Copy stylesheets and fonts

The React Grid requires some Bootstrap stylesheets and font files to work correctly. It should be possible to include these in the bundle, but I went the easier way here to include them separately, not least because the Grid also supports Material UI as an alternative UI platform.

Copy the stylesheets and font files to the public directory (use Windows copy andmd commands if you are indeed on Windows):

cp node_modules/bootstrap/dist/css/bootstrap*min.css public/css
mkdir public/fonts
cp node_modules/bootstrap/dist/fonts/* public/fonts

Edit public/index.html and add the lines to load the Bootstrap stylesheets. Your head section should look like this:

<head>
  <meta charset="utf-8">
  <meta content="width=device-width, initial-scale=1" name="viewport">
  <link href="css/bootstrap.min.css" rel="stylesheet" type="text/css">
  <link href="css/bootstrap-theme.min.css" rel="stylesheet" type="text/css">
  <link href="css/site.css" rel="stylesheet" type="text/css">
</head>

Demo source code

What remains is to implement the file src/demo-dx-react-grid-clojurescript/core.cljs to render a React Grid. First, add the bundle to the :require part of the namespace declaration:

(ns demo-dx-react-grid-clojurescript.core
  (:require
   [reagent.core :as r]
   [webpack.bundle]))

Here is my complete function home-page:

(defn home-page []
  (let [g (aget js/window "deps" "dx-react-grid")
        sorting-state (aget g "SortingState")
        local-sorting (aget g "LocalSorting")

        bs3 (aget js/window "deps" "dx-react-grid-bootstrap3")
        grid (aget bs3 "Grid")
        table-view (aget bs3 "TableView")
        table-header-row (aget bs3 "TableHeaderRow")

        columns [{:name "name" :title "Name"}
                 {:name "age" :title "Age"}]
        rows [{:name "Oliver" :age 37}
              {:name "Bert" :age 52}
              {:name "Jill" :age 31}]]
    [:div
     [:h2 "DevExtreme React Grid"]
     [:> grid {:columns columns :rows rows }
      [:> sorting-state
       {:onSortingChange (fn [sorting] (.log js/console
                                             "sorting changed" sorting))}]
      [:> local-sorting]
      [:> table-view ]
      [:> table-header-row {:allowSorting true}]]]))

The first block of code in the function defines a few local values using let. This call retrieves the object deps.dx-react-grid from the window context (where the webpack main.js put it previously):

(aget js/window "deps" "dx-react-grid")

The same thing is done with the line that assigns the bs3 value. From these two top level JavaScript objects, the SortingState, LocalSorting, Grid, TableView and TableHeaderRow objects can be retrieved, and they are stored in Clojure values.

As the final part of the let instruction, columns and rows are populated for demo purposes.

The home-page function renders a result using the slightly extended Hiccup syntax implemented by the Reagent interface library for React. For example, this line begins rendering the Grid component, passing the previously arranged values as columns and rows React properties:

[:> grid {:columns columns :rows rows}
...

The nested elements are rendered using similar syntax and follow the normal structure of the React Grid. I have included an event handler on the SortingState (or sorting-state) for illustration purposes.

Running the demo (mine or yours)

If you cloned my project, you will need to build the JavaScript bundle before running the demo. If you were following the steps to do it yourself, you should have already done this above. These are the required commands:

npm install
npm run build

With the bundle available, you can then run the demo by executing this:

lein figwheel

The command instructs Leiningen to run the project using the figwheel environment. On the console, you will see a few downloads of required components, a compilation step, hopefully no error messages (please check, especially in case things go wrong!) and finally this line:

Prompt will show when Figwheel connects to your application

At the same time, Figwheel tries to open the main page of the application in the browser. This works fine for me, but just in case you don’t see a browser page coming up, you can open it manually by connecting to http://localhost:3449 or opening the file public/index.html.

When the browser page loads, Figwheel changes its prompt to read app:cljs.user=>, which means that the built-in REPL is ready for interaction.

The browser should now show the working React Grid with three rows of demo data. Functionality is limited because I included only a few basic plugins - feel free to play around and extend, and let me know if you encounter any issues!

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
No Comments

Please login or register to post comments.