Skip to content

fulcrologic/fulcro

Repository files navigation

Untangled Client

The client library for the Untangled Web framework.

What follows is a quick start tutorial that assumes at least a passing familiarity with ClojureScript and Figwheel. For an more information & depth, checkout the links in Learn more.

untangled client

Release: untangled client Snapshot: untangled client

The following instructions assume:

  • You’re using leiningen

  • You’d like to be able to use Cursive REPL integration with IntelliJ

  • You’ll use Chrome and would like to have nice support for looking at cljs data structures in the browser and console log messages.

In addition to the base untangled client library, the following are the minimum requirements for a project file:

  • Choose a version of clj/cljs

  • Choose a version of Om (Untangled requires Om, but treats it as a provided dependency)

If you copy/paste the following file into a project.clj it will serve as a good start:

(defproject client-demo "1.0.0"
  :description "Untangled Client Quickstart"
  :dependencies [[org.clojure/clojure "1.8.0"]
                 [org.clojure/clojurescript "1.8.51"]
                 [org.omcljs/om "1.0.0-alpha46"]
                 [navis/untangled-client "0.5.6"]]

  ; needed or compiled js files won't get cleaned
  :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" "i18n/out"]

  ; needed for macros and our recommended figwheel setup
  :source-paths ["dev/server" "src/client"]

  :cljsbuild {:builds [{:id           "dev"
                        :source-paths ["dev/client" "src/client"]
                        :figwheel     true
                        :compiler     {:main                 "cljs.user"
                                       :asset-path           "js/compiled/dev"
                                       :output-to            "resources/public/js/compiled/app.js"
                                       :output-dir           "resources/public/js/compiled/dev"
                                       :recompile-dependents true
                                       :optimizations        :none}}]}

  ; figwheel dependency and chrome data structure formatting tools (formatting cljs in source debugging and logging)
  :profiles {:dev {:dependencies [[figwheel-sidecar "0.5.7"]
                                  [binaryage/devtools "0.6.1"]]}})

Create the directories as follows (OSX/Linux):

mkdir -p src/client/app dev/client/cljs dev/server resources/public/css resources/public/js script

then create a base HTML file in resources/public/index.html:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Application</title>
        <link rel="stylesheet" href="css/app.css">
    </head>
    <body>
          <div id="app"></div>
        <script src="js/compiled/app.js"></script>
    </body>
</html>

and an empty CSS file:

touch resources/public/css/app.css

Make the application itself, with an initial state, in src/client/app/core.cljs:

(ns app.core
  (:require [untangled.client.core :as uc]))

; The application itself, create, and store in an atom for a later DOM mount and dev mode debug analysis
; of the application.
; The initial state is the starting data for the entire UI
; see dev/client/user.cljs for the actual DOM mount
(defonce app (atom (uc/new-untangled-client)))

Notice that making the application is a single line of code.

then create the base UI in src/client/app/ui.cljs:

(ns app.ui
  (:require [om.next :as om :refer-macros [defui]]
            [untangled.client.mutations :as mut]
            [untangled.client.core :as uc]
            [om.dom :as dom]))

;; A UI node, with a co-located query of app state and a definition of the application's initial state.
;; The `:once` metadata ensures that figwheel does not redefine the static component with each re-render
(defui ^:once Root
  static uc/InitialAppState
  (initial-state [this params] {:ui/react-key "ROOT"
                                :some-data    42})
  static om/IQuery
  (query [this] [:ui/react-key :some-data])
  Object
  (render [this]
    (let [{:keys [ui/react-key some-data]} (om/props this)]
      (dom/div #js {:key react-key}
        (str "Hello world: " some-data)))))

Create an application entry point for development mode in dev/client/cljs/user.cljs:

(ns cljs.user
  (:require
    [cljs.pprint :refer [pprint]]
    [devtools.core :as devtools]
    [untangled.client.logging :as log]
    [untangled.client.core :as uc]
    [app.ui :as ui]
    [app.core :as core]))

;; Enable browser console
(enable-console-print!)

;; Set overall browser loggin level
(log/set-level :debug)

;; Enable devtools in chrome for data structure formatting
(defonce cljs-build-tools (devtools/install!))

;; Mount the Root UI component in the DOM div named "app"
(swap! core/app uc/mount ui/Root "app")

technically, only the ns declaration and last line are necessary.

We don’t use the lein plugin for figwheel, as we’d rather have IntelliJ REPL integration, which we find works better with a figwheel sidecar setup.

The setup can read the cljs builds from the project file, and can also support specifying which builds you’d like to initially start via JVM options (e.g. -Dtest -Ddev will cause it to build the test and dev builds).

To get this, place the following in dev/server/user.clj:

(ns user
  (:require [figwheel-sidecar.system :as fig]
            [com.stuartsierra.component :as component]))

(def figwheel-config (fig/fetch-config))
(def figwheel (atom nil))

(defn start-figwheel
  "Start Figwheel on the given builds, or defaults to build-ids in `figwheel-config`."
  ([]
   (let [props (System/getProperties)
         all-builds (->> figwheel-config :data :all-builds (mapv :id))]
     (start-figwheel (keys (select-keys props all-builds)))))
  ([build-ids]
   (let [default-build-ids (-> figwheel-config :data :build-ids)
         build-ids (if (empty? build-ids) default-build-ids build-ids)
         preferred-config (assoc-in figwheel-config [:data :build-ids] build-ids)]
     (reset! figwheel (component/system-map
                        :figwheel-system (fig/figwheel-system preferred-config)
                        :css-watcher (fig/css-watcher {:watch-paths ["resources/public/css"]})))
     (println "STARTING FIGWHEEL ON BUILDS: " build-ids)
     (swap! figwheel component/start)
     (fig/cljs-repl (:figwheel-system @figwheel)))))

and you’ll also want the following startup script in script/figwheel.clj:

(require '[user :refer [start-figwheel]])

(start-figwheel)

and now you can either start figwheel from the command prompt with:

lein run -m clojure.main script/figwheel.clj

or from Cursive in IntelliJ with a run profile:

  • Local REPL

  • Use clojure main in a normal JVM, not an NREPL

  • Under Parameters, add: script/figwheel.clj

Once you’ve started figwheel you should be able to browse to:

and see the UI. Any changes you make to the UI or to the CSS will automatically reload.

We recommend going through the Untangled Tutorial, which you should clone and work through on your local machine.

An Untanged template is in progress. A pretty complete version is available at https://github.com/awkay/untangled-template-workspace and has:

  • Full stack with sample UI for login/sign up.

  • Newer version of figwheel (better errors, etc.)

  • Bootstrap CSS

  • Examples of adding REST routes to the server

  • Examples of hooking into the Ring handlers

  • Sample tests for the server and client

  • Uberjar building

  • Deployment to Heroku (or similar environments)

  • CI (command-line runnable) testing for UI (via karma) and server

  • Devcards