hoplon

The :hoplon: ClojureScript Web Framework - http://hoplon.io/
bobcalco 2018-12-24T13:42:45.028500Z

OK so I created a new luminus project using +hoplon and there is a rendering issue for the navbar I'm trying to figure out. The properties don't appear to be set correctly for the navbar to display the Home and About links, and the navbutton doesn't toggle expand/contract when you click it. What is bothering me is a message I get in the Chrome devtools console when I attempt to correct it and reload attempts to do its magic: "Failed to execute 'write' on 'Document': It isn't possible to write into a document from an asynchronously loaded external script unless it is explicitly opened. - safe.js:135"

bobcalco 2018-12-24T13:46:04.031700Z

This is easy to reproduce: lein new luminus <project-name> +hoplon +postgres +oauth. Then compile the JS, and start it with 'lein run' or 'lein repl" then (start), and in a separate console 'lein figwheel'. Opent it in chrome and then open the devtools console. Modify any element of the navbar to attempt to correct the problem, this at message followed by ("../<project-name>/core.js" "../<project-name>/app.js") utils.cljs?rel=XXXXX:71

flyboarder 2018-12-24T18:46:46.032900Z

@bob592 I cant really help with lein or luminus although I would suggest updating the dependencies to hoplon 7.2 and then make sure you have your attribute providers installed.

flyboarder 2018-12-24T18:48:04.033700Z

Although hoplon works with lein there is no official support, ie. no documentation or examples - PR’s welcome!

bobcalco 2018-12-24T19:06:52.034100Z

@flyboarder When you say "have your attribute providers installed" what do you mean?

flyboarder 2018-12-24T19:07:20.034600Z

@bob592 thats the latest change that everyone seems to miss

flyboarder 2018-12-24T19:07:26.034800Z

a quick demo:

bobcalco 2018-12-24T19:07:55.035400Z

I'm still new so I don't know what not to look out for. 🙂

flyboarder 2018-12-24T19:07:55.035500Z

(ns ^{:hoplon/page "index.html"} pages.index
  (:require [hoplon.core  :as h :refer [div ul li html head title body h1 span p button text]]
            [javelin.core :as j :refer [cell cell=]]
            [hoplon.jquery]))

flyboarder 2018-12-24T19:08:10.036Z

^ the last line above

flyboarder 2018-12-24T19:08:19.036300Z

[hoplon.jquery]

flyboarder 2018-12-24T19:08:58.037100Z

this will “install” your attribute providers - the implementation of how to handle properties/attributes of an element

flyboarder 2018-12-24T19:09:20.037500Z

currently there is hoplon.jquery and hoplon.goog

flyboarder 2018-12-24T19:10:15.037900Z

you will need 7.x for those

bobcalco 2018-12-24T19:18:37.040100Z

So the luminus template uses 7.1, includes hoplon.jquery (but not hoplon.goog). I am trying to see if upgrading to 7.2 helps now. I also have the option to use boot instead of lein; however, this particular project needs to deploy to heroku which AFAIK requires lein.

flyboarder 2018-12-24T19:20:07.041100Z

@bob592 lein/boot are just build tools - there is not infrastructure limitations on which you need to use, also only use either hoplon.jquery or hoplon.goog never both

bobcalco 2018-12-24T19:20:38.041700Z

I understand that - my only point was, I am happy to use boot to get support but I'll have to rejigger that into a lein build to deploy to heroku, is all.

flyboarder 2018-12-24T19:20:59.042300Z

right - what I meant was that you could deploy with boot too

bobcalco 2018-12-24T19:20:59.042400Z

There is a +boot option with luminus so I may try that just get things working

bobcalco 2018-12-24T19:24:26.045300Z

OK well - the main issue is that structurally the luminus project is mounting the SPA onto an "app" div. I was nervous about refactoring anything when something as simple as fixing the faulty navbar code was throwing up problems such as the one I mentioned above. But really I want to use hoplon precisely to overcome the limitations of mounting a div - specifically, I want to control the "whole page" structure, including which CSS and JS files to include based on criteria like "is the user logged in?"

bobcalco 2018-12-24T19:26:16.047300Z

What I would like to understand is this: How should I structure my hoplon code to achieve a multi-page SPA navigating via secretary or some other routing mechanism? The SPA should render completely different styles based on whether the user is a) logged in, and b) logged in as an administrator.

bobcalco 2018-12-24T19:26:54.048Z

I will figure out the server side, and will limit my questions here to the canonical way to build a hoplon SPA.

bobcalco 2018-12-24T19:27:13.048500Z

using javelin and probably castra as well

bobcalco 2018-12-24T19:28:45.049400Z

THere is something "not right" in the luminus template project including hoplon - i get the sense it's not doing anything "the hoplon way" but since I don't really know what that is, I'm unclear how to improve it.

flyboarder 2018-12-24T19:29:23.050Z

sure this is what I do with hoplon all day - firstly can you show me the cljs index page for that project?

bobcalco 2018-12-24T19:30:30.050900Z

There is no index page - that's my point 🙂. There are core.cljs and ajax.cljs, which I'll show next:

bobcalco 2018-12-24T19:31:37.051500Z

clojurescript
(ns testproject.core
  (:require [testproject.ajax :refer [load-interceptors!]]
            [ajax.core :refer [GET]]
            [cljsjs.jquery]
            [goog.events :as events]
            [goog.history.EventType :as HistoryEventType]
            [hoplon.core
             :as h
             :include-macros true]
            [hoplon.jquery]
            [javelin.core
             :refer [cell]
             :refer-macros [cell= dosync]]
            [markdown.core :refer [md-&gt;html]]
            [secretary.core :as secretary])
  (:import goog.History))

(defonce selected-page (cell :home))

(defonce docs (cell nil))

(defn nav-link [uri title page expanded?]
  (h/li :class (cell= {:active (= page selected-page)
                       :nav-item true})
    (h/a :class "nav-link"
         :href uri
         :click #(do
                   (reset! expanded? false)
                   (secretary/dispatch! uri))
         title)))

(defn navbar []
  (let [expanded? (cell false)]
    (h/nav :class "navbar navbar-dark navbar-expand-md bg-primary"
      (h/button :class "navbar-toggler hidden-sm-up"
                :click #(swap! expanded? not)
                "☰")
      (h/div :class (cell= {:collapse true
                            :navbar-toggleable-xs true
                            :is-open expanded?})
       (h/a :class "navbar-brand" :href "/" "the-agonist")
       (h/ul {:class "nav navbar-nav"}
         (nav-link "#/" "Home" :home expanded?)
         (nav-link "#/about" "About" :about expanded?))))))

(defn about []
  (h/div :class "container"
    (h/div :class "row"
      (h/div :class "col-md-12"
        (h/img :src "/img/warning_clojure.png")))))

(defn home []
  (h/div :class "container"
    (h/div :html (cell= (md-&gt;html docs)))))

(h/defelem page []
  (h/div :id "app"
    (navbar)
    (cell=
     (case selected-page
       :home (home)
       :about (about)))))

;; -------------------------
;; Routes
(secretary/set-config! :prefix "#")

(secretary/defroute "/" []
 (reset! selected-page :home))

(secretary/defroute "/about" []
 (reset! selected-page :about))

;; -------------------------
;; History
;; must be called after routes have been defined
(defn hook-browser-navigation! []
 (doto (History.)
   (events/listen
     HistoryEventType/NAVIGATE
     (fn [event]
       (secretary/dispatch! (.-token event))))
   (.setEnabled true)))

;; -------------------------
;; Initialize app
(defn fetch-docs! []
  (GET "/docs" {:handler #(reset! docs %)}))

(defn mount-components []
  (js/jQuery #(.replaceWith (js/jQuery "#app") (page))))

(defn init! []
  (load-interceptors!)
  (hook-browser-navigation!)
  (mount-components)
  (fetch-docs!))

bobcalco 2018-12-24T19:31:59.051700Z

ajax.cljs:

bobcalco 2018-12-24T19:32:41.052100Z

(ns testproject.ajax
  (:require [ajax.core :as ajax]))

(defn local-uri? [{:keys [uri]}]
  (not (re-find #"^\w+?://" uri)))

(defn default-headers [request]
  (if (local-uri? request)
    (-&gt; request
        (update :headers #(merge {"x-csrf-token" js/csrfToken} %)))
    request))

(defn load-interceptors! []
  (swap! ajax/default-interceptors
         conj
         (ajax/to-interceptor {:name "default headers"
                               :request default-headers})))

flyboarder 2018-12-24T19:33:20.052700Z

@bob592 oh my - that is not a simple hoplon setup at all, there are many advanced examples in use here

bobcalco 2018-12-24T19:33:34.052900Z

testproject.routes.home.clj:

bobcalco 2018-12-24T19:34:06.053500Z

(ns testproject.routes.home
  (:require [testproject.layout :as layout]
            [testproject.db.core :as db]
            [compojure.core :refer [defroutes GET]]
            [ring.util.http-response :as response]
            [<http://clojure.java.io|clojure.java.io> :as io]))

(defn home-page []
  (layout/render "home.html"))

(defroutes home-routes
  (GET "/" []
       (home-page))
  (GET "/docs" []
       (-&gt; (response/ok (-&gt; "docs/docs.md" io/resource slurp))
           (response/header "Content-Type" "text/plain; charset=utf-8"))))

bobcalco 2018-12-24T19:34:29.053800Z

There are also oath routes but those aren't relevant here

bobcalco 2018-12-24T19:34:54.054600Z

Yes, this is a kitchen sink project a la carte crafted by my specific command line invocation

bobcalco 2018-12-24T19:35:08.054900Z

home.html:

flyboarder 2018-12-24T19:35:25.055300Z

I really wouldnt go about creating a hoplon app with those examples - way too much complexity for a SPA

bobcalco 2018-12-24T19:36:13.056Z

&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
      &lt;meta charset="UTF-8"/&gt;
      &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
      &lt;title&gt;Welcome to testproject&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id="navbar"&gt;&lt;/div&gt;
    &lt;div id="app"&gt;
      &lt;div class="container-fluid"&gt;
        &lt;div class="card-deck"&gt;
          &lt;div class="card-block"&gt;
            &lt;h4&gt;Welcome to testproject&lt;/h4&gt;
            &lt;p&gt;If you're seeing this message, that means you haven't yet compiled your ClojureScript!&lt;/p&gt;
            &lt;p&gt;Please run &lt;code&gt;lein figwheel&lt;/code&gt; to start the ClojureScript compiler and reload the page.&lt;/p&gt;
            &lt;h4&gt;For better ClojureScript development experience in Chrome follow these steps:&lt;/h4&gt;
            &lt;ul&gt;
              &lt;li&gt;Open DevTools
              &lt;li&gt;Go to Settings ("three dots" icon in the upper right corner of DevTools &gt; Menu &gt; Settings F1 &gt; General &gt; Console)
              &lt;li&gt;Check-in "Enable custom formatters"
              &lt;li&gt;Close DevTools
              &lt;li&gt;Open DevTools
            &lt;/ul&gt;
            &lt;p&gt;See &lt;a href="<http://www.luminusweb.net/docs/clojurescript.md>"&gt;ClojureScript&lt;/a&gt; documentation for further details.&lt;/p&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    &lt;!-- scripts and styles --&gt;
    {% style "/assets/bootstrap/css/bootstrap.min.css" %}
    {% style "/assets/font-awesome/css/all.css" %}
    {% style "/css/screen.css" %}

    {% script "/assets/jquery/jquery.min.js" %}
    {% script "/assets/font-awesome/js/all.js" %}
    {% script "/assets/tether/dist/js/tether.min.js" %}
    {% script "/assets/bootstrap/js/bootstrap.min.js" %}

    &lt;script type="text/javascript"&gt;
        var csrfToken = "{{csrf-token}}";
    &lt;/script&gt;
    {% script "/js/app.js" %}
  &lt;/body&gt;
&lt;/html&gt;

bobcalco 2018-12-24T19:37:28.057600Z

Right well ... this is the situation I constantly find myself trying to start a serious project in clojure no matter the framework. Either the samples are too complex and don't do something right, and I end up cycling on that, or they're too bare-bones to be useful.

bobcalco 2018-12-24T19:38:02.058600Z

I want to do hoplon the hoplon way, and make use of javelin and castra the right way

flyboarder 2018-12-24T19:38:03.058700Z

Sure - let’s get you setup on a good code basis right now - here is an index page:

flyboarder 2018-12-24T19:38:26.059200Z

(h/defelem app [attr kids]
  (let [attr (assoc attr :css {:background "#f9f9fb"})]
    (h/html
      (h/head
        (h/link :rel "stylesheet" :href "<https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-beta.40/css/uikit.min.css>")
        (h/link :rel "stylesheet" :href "/css/theme.css"))
      (h/body attr kids))))

bobcalco 2018-12-24T19:38:29.059300Z

and I want the backend to play nice with it while letting me flesh out well designed APIs

flyboarder 2018-12-24T19:40:01.060200Z

and this is how you call your app:

(ns app.index
      (:require [app.ui :as ui]))

;; App Main ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(ui/app)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

flyboarder 2018-12-24T19:40:52.060700Z

^ with this anything you pass to ui/app as a child element will become the body contents

bobcalco 2018-12-24T19:42:14.062200Z

how do you deal with routing in the SPA? The luminus template obviously uses secretary keyed off the cell selected-page

flyboarder 2018-12-24T19:42:47.063200Z

I prefer using bidi and the helpers I created within hoplon/brew

bobcalco 2018-12-24T19:43:03.063800Z

ok - i'll take a look at that. I'm not wed to luminus' choices

bobcalco 2018-12-24T19:43:07.064Z

obviously

flyboarder 2018-12-24T19:43:53.064400Z

then I use this macro for swapping the pages based on bidi routes

bobcalco 2018-12-24T19:47:52.065800Z

love the G.K. Chesterton quote LOL

flyboarder 2018-12-24T19:48:03.066Z

hahaha thanks 😛

bobcalco 2018-12-24T19:48:36.066800Z

OK so I'm going to try this from scratch. I'm not sure how to rejigger the luminus project to make sure things are wired correctly.

flyboarder 2018-12-24T19:48:36.066900Z

oh also latest brew version is actually 7.2.0-SNAPSHOT

flyboarder 2018-12-24T19:48:52.067300Z

it follows hoplon versioning

bobcalco 2018-12-24T19:54:37.069300Z

ok so please summarize for me: what files should I create on the CLJS side to replace core.cljs and ajax.cljs?

bobcalco 2018-12-24T19:55:04.069900Z

I'm going to TRY this within the luminous project since the server side is fine (other than loading the template page, which I'll change)

flyboarder 2018-12-24T19:55:43.070500Z

well it kinda depends on what you are using to compile your cljs

bobcalco 2018-12-24T19:55:59.070900Z

and this is where boot vs lein matters right?

flyboarder 2018-12-24T19:56:09.071100Z

correct, personally I use boot and the boot-shadow task

flyboarder 2018-12-24T19:56:24.071400Z

which uses shadow-cljs under the hood to compile

flyboarder 2018-12-24T19:56:37.071800Z

you could also use boot-cljs which was the original cljs compiler for boot

flyboarder 2018-12-24T19:56:48.072300Z

or use lein and it’s tools (which I am not familiar)

bobcalco 2018-12-24T19:58:14.072600Z

it's using lein-cljsbuild

bobcalco 2018-12-24T19:58:44.073300Z

my constraint appears to be that I have to use lein, since this is going to be deployed to heroku

bobcalco 2018-12-24T19:59:03.074100Z

heroku requires projects.clj to recognize the app as a clojure app and invoke whatever backend voodoo it does

bobcalco 2018-12-24T19:59:12.074400Z

project.clj*

bobcalco 2018-12-24T19:59:25.074700Z

howeer

flyboarder 2018-12-24T19:59:28.075Z

that depends on which heroku pack you are using

flyboarder 2018-12-24T19:59:32.075300Z

there are ones for boot

bobcalco 2018-12-24T19:59:35.075400Z

however* for the sake of learning I will be happy to use boot

flyboarder 2018-12-24T20:00:36.076700Z

either way you should be able to set the main cljs namespace to compile yes?

bobcalco 2018-12-24T20:00:43.076900Z

yes

flyboarder 2018-12-24T20:01:00.077300Z

awesome so just require hoplon.core :as h

flyboarder 2018-12-24T20:01:21.077600Z

then this is a basic html page:

flyboarder 2018-12-24T20:02:03.078Z

(h/html
  (h/head
    (h/title "example page"))
  (h/body
    (h/h1 "Hello, Hoplon")

flyboarder 2018-12-24T20:02:48.078900Z

you plop that right in the file as a top-level form, which means the form will run when the file is loaded on the page

bobcalco 2018-12-24T20:06:07.081Z

one thing that looks like it doesn't fit the hoplon-boot way (so to speak) is the :cljsbuild for the :dev profile ":figwheel {:on-jsload "testproject.core/mount-components" -- getting figwheel to work with it will also need to be refactored.

flyboarder 2018-12-24T20:07:38.082200Z

oh yeah - figwheel is it’s own thing, I wouldn’t use it right away although if you wrap the example above in a function and call it from figwheel it should work

flyboarder 2018-12-24T20:08:13.082700Z

actually using the app example should work out-of-the-box

flyboarder 2018-12-24T20:08:31.082900Z

(h/defelem app [attr kids]
  (let [attr (assoc attr :css {:background "#f9f9fb"})]
    (h/html
      (h/head
        (h/link :rel "stylesheet" :href "<https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-beta.40/css/uikit.min.css>")
        (h/link :rel "stylesheet" :href "/css/theme.css"))
      (h/body attr kids))))

flyboarder 2018-12-24T20:09:20.083600Z

just replace the testproject.core/mount-components with yournamespace/app

bobcalco 2018-12-24T20:11:28.084500Z

i'm not clear how to integrate bidi / hoplon brew but let me get this first first...

bobcalco 2018-12-24T20:11:35.084700Z

this far* first

bobcalco 2018-12-24T20:11:39.084900Z

sheesh can't type today

bobcalco 2018-12-24T20:11:52.085200Z

It's almost beer o'clock this xmas eve. 🙂

flyboarder 2018-12-24T20:12:01.085500Z

yeah me too - coffee #3

bobcalco 2018-12-24T20:47:01.086700Z

It would be great if at least the hoplon and hoplon-castra templates were up to date (hint, hint). The hoplon castra template uses 6.0.0-alpha17 version of hoplon 🤪

bobcalco 2018-12-24T20:47:53.087800Z

I really want to start from scratch, the simplest setup I can, but the templates are generating project artifacts that appear to be a few years out of date

flyboarder 2018-12-24T20:49:15.089Z

@bob592 yeah the examples are all out of date - it would be great if they were generated from a common set of dependencies and then could be all updated at once….. I dont have time to go through and fix them all up right now - trying to push an app out by new-years

flyboarder 2018-12-24T20:50:29.089200Z

have you tried boot-new?

flyboarder 2018-12-24T20:50:46.089600Z

that is probably the most up to date template

bobcalco 2018-12-24T20:52:03.090100Z

boot -d boot/new new -t hoplon -n test-app

bobcalco 2018-12-24T20:52:23.090600Z

that's what I used. you mean start with an empty boot project?

bobcalco 2018-12-24T20:52:36.090900Z

boot -d boot/new new boot-new -n test-app ??

flyboarder 2018-12-24T20:53:12.091200Z

looks like that is also a bit out of date, never mind

bobcalco 2018-12-24T20:53:18.091600Z

sigh

bobcalco 2018-12-24T20:55:39.093Z

is there an example anywhere on github of an up-to-date hoplon/javelin (and ideally castra) project I can use as a guide even if I have to copy the pages manually?

flyboarder 2018-12-24T20:57:44.094500Z

probably not - all the recent 7.x development has been for existing apps. Let me look into getting some of the examples updated. one sec….

1😀
bobcalco 2018-12-24T20:59:05.095800Z

I know you're busy and appreciate anything you can do. I really want to give hoplon/javelin the college try. It's just not clear how since all the samples contradict what seems to be the new way to do things in the lastest edition, and none of the agnostic third parties (like luminus) quite got the gist of it either.

bobcalco 2018-12-24T21:00:11.096700Z

I really like the dataflow idea of javelin and the clean html api of hoplon (vs e.g. hiccup) based on what I see, but how to get it working properly in a way that I can bend to my needs is not yet clear.

flyboarder 2018-12-24T21:08:15.097Z

wow all these examples are so old!

flyboarder 2018-12-24T21:08:34.097400Z

im trying to find one that is a good example and they are all way complex

flyboarder 2018-12-24T21:10:40.097700Z

@bob592 this is actually the best example

flyboarder 2018-12-24T21:11:00.098500Z

it needs to be updated still, ie. replace the page macro with an ns form

flyboarder 2018-12-24T21:11:29.098900Z

and include the hoplon namespaces - but it’s fairly easy to read and understand that page

bobcalco 2018-12-24T21:24:06.101300Z

So I understand that page - but not so much how to integrate hoplon with a backend that has multiple API endpoints and several logical "pages" (and in the case of one of the apps I need to build, a separate style altogether based on user type and whether they are logged in or not.

flyboarder 2018-12-24T21:25:34.102400Z

so in all of those examples there is only 1 page - you will need to implement a template macro to support different page views or pages

flyboarder 2018-12-24T21:28:04.103700Z

I have many more advanced template macros that integrate with things like bidi for route based templating or state based templating

flyboarder 2018-12-24T21:29:11.105Z

for example here are some bidi routes

flyboarder 2018-12-24T21:29:35.106Z

(def dash-routes
  [#{"" "/"} {"route1"  {true :route1}
              "route2"   {true :route2}
              "route3"    {true :route3}
              "route4"    {true :route4}
              true       :default}])

flyboarder 2018-12-24T21:29:59.106400Z

you can use these with the bidi route-tpl macro

flyboarder 2018-12-24T21:30:14.106600Z

(hoplon.bidi/route-tpl
      dash-routes
      :route1   (page1)
      :route2  (page2)
      :route3   (page3)
      :route4  (page4))

bobcalco 2018-12-24T21:31:36.107600Z

I think that instead of templates you guys should take time to create a living tutorial that walks the user step by step through creating a complete demo "by hand" with backend API and a non-trivial front end that renders multiple pages. Templates obscure certain design decisions and definitely get out of date quickly. Maybe something that branches off the "Modern CLJS" tutorial (which has something like 22 chapters now, and walks through various technologies like Om, Reagent/re-frame, etc.

flyboarder 2018-12-24T21:33:17.109Z

thats a great idea, although many of those decisions depend on what you want your app to be, remember hoplon is just a library to replace working with the DOM, everything else is a developers decision to implement

bobcalco 2018-12-24T21:33:41.109500Z

right I get that actually

bobcalco 2018-12-24T21:34:11.110Z

but it's connecting it to the "everything else" around it that I'm trying to get my head around

bobcalco 2018-12-24T21:34:21.110300Z

and luminus' template didn't really help 🙂

flyboarder 2018-12-24T21:44:35.111900Z

@bob592 I can always jump on a video chat and walk you through some of our apps - that might give you a better idea of how we do things