Oliver's Blog

ClojureScript for the DevExtreme React Grid

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!

Published Aug 22 2017, 10:23 AM by
Bookmark and Share

Comments

Mohamed Al Zayani

What would be the reason for someone to use this instead of normal ES6/JSX?

August 23, 2017 8:50 AM

Oliver Sturm (DevExpress)

Mohamed,

Reasons for using ClojureScript certainly vary, but the main point about it is that functional programming techniques are very common in the JavaScript world these days, from basic patterns all the way to functionally oriented libraries like React, and that JavaScript itself doesn't make it as easy as you'd wish to adhere to those principles.

Here is a blog post that takes the position that functional programming in JavaScript is actually an antipattern: hackernoon.com/functional-programming-in-javascript-is-an-antipattern-58526819f21e

This is a post about choosing ClojureScript over BLOCKED SCRIPT m.oursky.com/why-i-chose-clojure-over-javascript-24f045daab7e

I like ClojureScript myself, for the reasons I've already mentioned. It facilitates the programming style I like to use anyway (that's FP) and that works great with my favorite JavaScript platforms today (that's React, Redux etc), and by enforcing this style it helps me write more maintainable code than I would probably achieve by trying to follow FP approaches using JavaScript alone.

Hope this helps!

August 25, 2017 3:07 AM
LIVE CHAT

Chat is one of the many ways you can contact members of the DevExpress Team.
We are available Monday-Friday between 7:30am and 4:30pm Pacific Time.

If you need additional product information, write to us at info@devexpress.com or call us at +1 (818) 844-3383

FOLLOW US

DevExpress engineers feature-complete Presentation Controls, IDE Productivity Tools, Business Application Frameworks, and Reporting Systems for Visual Studio, along with high-performance HTML JS Mobile Frameworks for developers targeting iOS, Android and Windows Phone. Whether using WPF, ASP.NET, WinForms, HTML5 or Windows 10, DevExpress tools help you build and deliver your best in the shortest time possible.

Copyright © 1998-2017 Developer Express Inc.
All trademarks or registered trademarks are property of their respective owners