Fork me on GitHub
#hoplon
<
2018-12-24
>
bobcalco13:12:45

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"

bobcalco13:12:04

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

flyboarder18:12:46

@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.

flyboarder18:12:04

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

bobcalco19:12:52

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

flyboarder19:12:20

@bob592 thats the latest change that everyone seems to miss

flyboarder19:12:26

a quick demo:

bobcalco19:12:55

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

flyboarder19:12:55

(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]))

flyboarder19:12:10

^ the last line above

flyboarder19:12:19

[hoplon.jquery]

flyboarder19:12:58

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

flyboarder19:12:20

currently there is hoplon.jquery and hoplon.goog

flyboarder19:12:15

you will need 7.x for those

bobcalco19:12:37

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.

flyboarder19:12:07

@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

bobcalco19:12:38

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.

flyboarder19:12:59

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

bobcalco19:12:59

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

bobcalco19:12:26

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?"

bobcalco19:12:16

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.

bobcalco19:12:54

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

bobcalco19:12:13

using javelin and probably castra as well

bobcalco19:12:45

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.

flyboarder19:12:23

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

bobcalco19:12:30

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

bobcalco19:12:37

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->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->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!))

bobcalco19:12:41

(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)
    (-> 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})))

flyboarder19:12:20

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

bobcalco19:12:34

testproject.routes.home.clj:

bobcalco19:12:06

(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]
            [ :as io]))

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

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

bobcalco19:12:29

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

bobcalco19:12:54

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

flyboarder19:12:25

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

bobcalco19:12:13

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

    <!-- scripts and styles -->
    {% 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" %}

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

bobcalco19:12:28

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.

bobcalco19:12:02

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

flyboarder19:12:03

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

flyboarder19:12:26

(h/defelem app [attr kids]
  (let [attr (assoc attr :css {:background "#f9f9fb"})]
    (h/html
      (h/head
        (h/link :rel "stylesheet" :href "")
        (h/link :rel "stylesheet" :href "/css/theme.css"))
      (h/body attr kids))))

bobcalco19:12:29

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

flyboarder19:12:01

and this is how you call your app:

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

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

flyboarder19:12:52

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

bobcalco19:12:14

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

flyboarder19:12:47

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

bobcalco19:12:03

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

flyboarder19:12:53

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

bobcalco19:12:52

love the G.K. Chesterton quote LOL

flyboarder19:12:03

hahaha thanks 😛

bobcalco19:12:36

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.

flyboarder19:12:36

oh also latest brew version is actually 7.2.0-SNAPSHOT

flyboarder19:12:52

it follows hoplon versioning

bobcalco19:12:37

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

bobcalco19:12:04

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)

flyboarder19:12:43

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

bobcalco19:12:59

and this is where boot vs lein matters right?

flyboarder19:12:09

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

flyboarder19:12:24

which uses shadow-cljs under the hood to compile

flyboarder19:12:37

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

flyboarder19:12:48

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

bobcalco19:12:14

it's using lein-cljsbuild

bobcalco19:12:44

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

bobcalco19:12:03

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

bobcalco19:12:12

project.clj*

flyboarder19:12:28

that depends on which heroku pack you are using

flyboarder19:12:32

there are ones for boot

bobcalco19:12:35

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

flyboarder20:12:36

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

flyboarder20:12:00

awesome so just require hoplon.core :as h

flyboarder20:12:21

then this is a basic html page:

flyboarder20:12:03

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

flyboarder20:12:48

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

bobcalco20:12:07

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.

flyboarder20:12:38

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

flyboarder20:12:13

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

flyboarder20:12:31

(h/defelem app [attr kids]
  (let [attr (assoc attr :css {:background "#f9f9fb"})]
    (h/html
      (h/head
        (h/link :rel "stylesheet" :href "")
        (h/link :rel "stylesheet" :href "/css/theme.css"))
      (h/body attr kids))))

flyboarder20:12:20

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

bobcalco20:12:28

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

bobcalco20:12:35

this far* first

bobcalco20:12:39

sheesh can't type today

bobcalco20:12:52

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

flyboarder20:12:01

yeah me too - coffee #3

bobcalco20:12:01

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 :zany_face:

bobcalco20:12:53

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

flyboarder20:12:15

@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

flyboarder20:12:29

have you tried boot-new?

flyboarder20:12:46

that is probably the most up to date template

bobcalco20:12:03

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

bobcalco20:12:23

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

bobcalco20:12:36

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

flyboarder20:12:12

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

bobcalco20:12:39

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?

flyboarder20:12:44

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….

bobcalco20:12:05

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.

bobcalco21:12:11

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.

flyboarder21:12:15

wow all these examples are so old!

flyboarder21:12:34

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

flyboarder21:12:40

@bob592 this is actually the best example

flyboarder21:12:00

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

flyboarder21:12:29

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

bobcalco21:12:06

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.

flyboarder21:12:34

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

flyboarder21:12:04

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

flyboarder21:12:11

for example here are some bidi routes

flyboarder21:12:35

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

flyboarder21:12:59

you can use these with the bidi route-tpl macro

flyboarder21:12:14

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

bobcalco21:12:36

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.

flyboarder21:12:17

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

bobcalco21:12:41

right I get that actually

bobcalco21:12:11

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

bobcalco21:12:21

and luminus' template didn't really help 🙂

flyboarder21:12:35

@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