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:
(nsdemo-dx-react-grid-clojurescript.core(:require[reagent.core:as r][webpack.bundle]))
Here is my complete function home-page
:
(defnhome-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](.logjs/console"sorting changed" sorting))}][:> local-sorting][:> table-view ][:> table-header-row {:allowSortingtrue}]]]))
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!