Coder Social home page Coder Social logo

simpleui's Introduction

SimpleUI

Clojure backend for for htmx. Previously known as ctmx.

Rationale

htmx enables web developers to create powerful webapps without writing any Javascript. Whenever hx-* attributes are included in html the library will update the dom in response to user events. The architecture is simpler and pages load more quickly than in Javascript-oriented webapps.

SimpleUI is a backend accompaniment which makes htmx even easier to use. It works in conjunction with hiccup for rendering and reitit for routing.

Getting started

Getting started is easy with clojure tools and the excellent kit framework.

clojure -Ttools install com.github.seancorfield/clj-new '{:git/tag "v1.2.381"}' :as new
clojure -Tnew create :template io.github.kit-clj :name yourname/guestbook
cd guestbook
make repl
(kit/sync-modules)
(kit/install-module :kit/simpleui)

Quit the process, make repl then

(go)

Visit localhost:3000. To reload changes

(reset)

Usage

First require the library

(require '[simpleui.core :refer :all])

The core of SimpleUI is the defcomponent macro.

(defcomponent ^:endpoint hello [req my-name]
  [:div#hello "Hello " my-name])

This defines an ordinary function which also expands to an endpoint /hello.

To use our endpoint we call make-routes

;; make-routes generates a reitit handler with the root page at /demo
;; and all subcomponents on their own routes
(make-routes
  "/demo"
  (fn [req]
    (page ;; page renders the rest of the page, htmx script etc
      [:div
       [:label "What is your name?"]
       [:input {:name "my-name" :hx-patch "hello" :hx-target "#hello"}]
       (hello req "")])))

Here the only active element is the text input. On the input's default action (blur) it will request to /hello and replace #hello with the server response. We are using hello both as a function and an endpoint. When called as an endpoint arguments are set based on the http parameter my-name.

The first argument to defcomponent is always the req object

component stack

SimpleUI retains a call stack of nested components. This is used to set ids and values in the sections below.

ids and values

In the above example we use a fixed id #hello. If a component exists multiple times you may set id automatically.

[:div.my-component {:id id} ...]

SimpleUI also provides optional path and value functions.

[:input {:type "text" :name (path "first-name") :value (value "first-name")}]
[:input {:type "text" :name (path "last-name") :value (value "last-name")}]

These are unique for each instance of a component and make it easy to retain state over stateless http requests.

Note: path and value only work when id is set at the top level of the component. SimpleUI uses id to record the position of the component in the component stack.

Component Arrays

If you are using the component stack on a page, you must invoke simpleui.rt/map-indexed instead of clojure.core/map. This is because the index of the array forms part of the component stack.

(def data [{:first-name "Fred" :last-name "Smith"}
           {:first-name "Ocean" :last-name "Leader"}])

(defcomponent table-row [req index first-name last-name]
  [:tr ...])

...

[:table
  (rt/map-indexed table-row req data)]

Parameter Casting

htmx submits all parameters as strings. It can be convenient to cast parameters to the required type

(defcomponent my-component [req ^:long int-argument ^:boolean boolean-argument] ...)

You may also cast within the body of defcomponent

[:div
  (if ^:boolean (value "grumpy")
    "Cheer up!"
    "How are you?")]

Casts available include the following

  • ^:long Casts to long
  • ^:long-option Casts to long (ignores empty string)
  • ^:double Casts to double
  • ^:double-option Casts to double (ignores empty string)
  • ^:longs Casts to array of longs
  • ^:doubles Casts to array of doubles
  • ^:array Puts into an array
  • ^:boolean True when (contains? #{"true" "on"} argument). Useful with checkboxes.
  • ^:boolean-true True when (not= argument "false")
  • ^:edn Reads string into edn
  • ^:keyword Casts to keyword
  • ^:nullable Ensures the strings "", "nil" and "null" are parsed as nil
  • ^:trim Trims string and sets it to nil when empty

Additional Parameters

In most cases htmx will supply all required parameters. If you need to include extra ones, set the hx-vals attribute. To serialize the map as json in initial page renders, you should call simpleui.render/walk-attrs on your returned html body (example).

[:button.delete
  {:hx-delete "trash-can"
   :hx-vals {:hard-delete true}}
   "Delete"]

Commands

Commands provide a shorthand to indicate custom actions.

(defcomponent ^:endpoint component [req command]
  (case command
    "print" (print req)
    "save" (save req))
  [:div
    [:button {:hx-post "component:print"} "Print"]
    [:button {:hx-post "component:save"} "Save"]])

command will be bound to the value after the colon in any endpoints.

Action at a distance (hx-swap-oob)

Best to avoid, but sometimes too convenient to resist. htmx provides the hx-swap-oob attribute for updating multiple dom elements within a single response. In SimpleUI we must only provide the additional elements when htmx is updating, not in the initial render

(defcomponent my-component [req]
  (list
    (when top-level?
      [:div.side-element
       {:id (path "path/to/side-element")
        :hx-swap-oob "true"}
        ...])
    [:div.main-element {:id id} ...]))

Be very careful to only include hx-swap-oob elements when top-level? is true.

Responses

By default SimpleUI expects components to return hiccup vectors which are rendered into html.

nil returns http 204 - No Content and htmx will not update the dom.

You may also return an explicit ring map if you wish. A common use case is to refresh the page after an operation is complete

(defcomponent my-component [req]
  (case (:request-method req)
    :post
    (do
      (save-to-db ...)
      simpleui.response/hx-refresh)
    :get ...))

simpleui.response/hx-refresh sets the "HX-Refresh" header to "true" and htmx will refresh the page.

Script Responses

htmx will execute any script tags you include.

[:script "alert('Application successful')"]

You can also mix scripts with visual content.

Hanging Components

If you don't include components in an initial render, reference them as symbols so they are still available as endpoints.

(defcomponent ^:endpoint next-month [req] [:p "next-month"])
(defcomponent ^:endpoint previous-month [req] [:p "previous-month"])

(defcomponent ^:endpoint calendar [req]
              next-month
              previous-month
              [:div#calendar ...])

Extra hints

htmx does not include disabled fields when submitting requests. If you wish to retain state in this case use the following pattern.

[:input {:type "text" :name (path "input") :value (value "input") :disabled disabled?}]
(when disabled?
  [:input {:type "hidden" :name (path "input") :value (value "input")}])

Advanced Usage

SimpleUI makes it possible to build dynamic forms, for details please see advanced usage.

Testing

lein auto test

Integration tests are run with puppeteer against the demo subproject.

cd demo
clj -M:run

In a separate tab

cd test-integration
npm i
node index.js

License

Copyright © 2023 Matthew Molloy

This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.

This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.

simpleui's People

Contributors

ieugen avatar ramblurr avatar the-alchemist avatar whamtet avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

simpleui's Issues

Latest ctmx version in luminus template.

here are the steps to reproduce the failure.

  1. Generate a new project.
lein new luminus helloctmx +http-kit +ctmx
  1. Run it with lein run.
    It works well with old version ctmx 1.4.3 in project.clj as dependency.

  2. Update the ctmx version to latest 1.4.8, and re-run the project.
    We see error like this:

java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')
        at clojure.lang.Numbers.inc(Numbers.java:139)

Minor version usually should be compatible, so maybe you can check on this when free, thanks!

Luminus template is having "Added +ctmx option (#544)" last year, but not listed in the table below.
https://github.com/luminus-framework/luminus-template

I think it is better add ctmx in the table if luminus template officially support it, do you think so?

The demo references ctmx 1.4.9, but Maven only has up to 1.4.8

The demo references ctmx 1.4.9, but Maven only has up to version 1.4.8.

Error when running lein run or lein deps:

Could not find artifact ctmx:ctmx:jar:1.4.9 in central (https://repo1.maven.org/maven2/)
Could not find artifact ctmx:ctmx:jar:1.4.9 in clojars (https://repo.clojars.org/)
This could be due to a typo in :dependencies, file system permissions, or network issues.
If you are behind a proxy, try setting the 'http_proxy' environment variable.

Routing broken since forward to backwards slash change

ctmx is broken since this commit: dc622ed

Example stacktract from the demo:

java.util.regex.PatternSyntaxException: Unexpected internal error near index 1
\
	at java.base/java.util.regex.Pattern.error(Pattern.java:2028)
	at java.base/java.util.regex.Pattern.compile(Pattern.java:1789)
	at java.base/java.util.regex.Pattern.<init>(Pattern.java:1430)
	at java.base/java.util.regex.Pattern.compile(Pattern.java:1069)
	at java.base/java.lang.String.split(String.java:3153)
	at java.base/java.lang.String.split(String.java:3199)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:167)
	at clojure.lang.Reflector.invokeInstanceMethod(Reflector.java:102)
	at ctmx.rt$path.invokeStatic(rt.cljc:85)
	at ctmx.rt$path.invoke(rt.cljc:78)
	at demo.routes.click_to_edit$this__10244__auto____10436.invoke(click_to_edit.clj:26)
	at demo.routes.click_to_edit$routes$fn__10444.invoke(click_to_edit.clj:44)
	at reitit.ring$ring_handler$fn__12381.invoke(ring.cljc:329)
	at clojure.lang.AFn.applyToHelper(AFn.java:154)

How to handle sibling components?

How do you idiomatically handle components that appear as siblings in the stack? For example, if you have a Container component and a Pane component, and you want the container component to have two panes, what would you do to uniquely identify each pane?

I can see how you might hack around with map-indexed, but you can imagine situations where e.g. the Pane components are separated by something between them irregularly – that is, they aren't in a repeating pattern.

Fix warnings for clojure 1.11

Hi,

Started a new project and I get these warnings:

WARNING: parse-long already refers to: #'clojure.core/parse-long in namespace: ctmx.rt, being replaced by: #'ctmx.rt/parse-long
WARNING: parse-double already refers to: #'clojure.core/parse-double in namespace: ctmx.rt, being replaced by: #'ctmx.rt/parse-double
WARNING: parse-boolean already refers to: #'clojure.core/parse-boolean in namespace: ctmx.rt, being replaced by: #'ctmx.rt/parse-boolean

I think they should be fixed eventually.
Having this to track that down.

rt/mapped-index and forms.. how should it work?

(note: This is probably not a bug as much as it is a user support question. I'm happy to take these questions to a different medium if you want.)

I think I must be doing something wrong because I'm not seeing the benefit of rt/map-indexed.

See in the code snippet below. When GET'd from the top-level there are no issues.

But when the form is submitted the function/component params (idx and person-id) are nil. So I am forced to calculate these from the params map. Also notice the lack of (value "..") usage.. I've not figured out when using that actually helps.

Am I doing something wrong?

(defcomponent ^:endpoint a-form [req idx person-id]
   ;; why is (value "person-id") always nil both on GET and POST
  (tap> {:person-id-value (value "person-id")})

  (ctmx/with-req req
    (let [params    (-> req :form-params form/json-params-pruned)
          idx       (or idx (:idx params))
          person-id (or person-id (:person-id params))
          person    (cond post? (update-person! (:person-id params) (:name params) (:email params))
                          :else (person-by-id person-id))]
      ;; (tap> {:idx idx :person-id person-id :person person :this id :params params :raw-params (:params req)})
      [:li {:id id :style (str (when (= 0 (mod idx 2)) "background-color: #ccc;") " list-style:none;")}
       [:form {:hx-post "a-form"}
        [:input {:type "hidden" :name (path "idx") :value idx}]
        [:input {:type "hidden" :name (path "person-id") :value person-id}]
        [:input {:type "text" :name (path "name") :value (:person/name person)}]
        [:input {:type "email" :name (path "email") :value (:person/email person)}]
        [:button {:type :submit :hx-target (hash ".")} "Save"]]])))

(defcomponent ^:endpoint mapped-forms [req]
  [:div
   [:h2 "People"]
   [:ul
    (rt/map-indexed a-form req (map :person/person-id (vals @people)))]])

The issue seems to arise when using the mapped component as an endpoint itself.

In this trivial example one could have a single <form> pulled up to the top level and submit all person records at once.

But in my real use case, the number of records is large and I only want to submit on a person-by-person basis.

rt/map-indexed makes it difficult to use params in the component signature

Suppose I'm displaying a table.. here I am rendering the rows:

(ctmx/defcomponent ^:endpoint demo-table-row [{:keys [db] :as req} idx ^:array pair]
  [:tr
   [:td (first pair)]
   [:td (second pair)]
   [:td [:a {:href "#"} "Edit"]]])

(ctmx/defcomponent ^:endpoint demo-table [{:keys [db] :as req}]
  [:div {:id id}
   [:table
    [:thead
     [:th "Col 1"]
     [:th "Col 2"]
     [:th "Action"]]
    (rt/map-indexed demo-table-row req (partition 2 (repeatedly 20 #(rand-int 100))))]])

This will produce a nice table like this:
image

Now I want to allow the user to use the edit link to edit the numbers in the row... our demo-table-row component now becomes:

(ctmx/defcomponent ^:endpoint demo-table-row [{:keys [db] :as req} idx  pair]
  (tap> {:raw-parm (:params req)
         :pair-param pair
         :qp (-> req :query-params)
         :pair-value (value "pair")})
  (let [edit? (Boolean/valueOf (-> req :query-params (get "edit?")))
        pair (or  (value "pair") pair)]
    [:tr {:id id :hx-include (str (hash ".") " input")}
     [:td
      (if edit?
        [:input {:type :number :value (first pair) :name (path "pair") :style "width: 4rem;"}]
        (first pair))]
     [:td
      (if edit?
        [:input {:type :number :value (second pair) :name (path "pair") :style "width: 4rem;"}]
        (second pair))]
     [:td
      (if edit?
        (list
         [:button {:type :submit :class "btn btn-gray-high" :hx-post "demo-table-row" :hx-target (hash ".")} "Save"]
         [:button {:type :submit :class "ml-2 btn btn-gray-high" :hx-get "demo-table-row" :hx-vals {:edit? false} :hx-target (hash ".")} "Cancel"])
        [:a {:class "link-blue" :href "#"
             :hx-get "demo-table-row" :hx-target (hash ".") :hx-vals {:edit? true (path "pair") pair}} "Edit"])]]))

image

Some notes about this implementation:

  1. It works! One can edit the table rows inline... but..

  2. I have to yank the edit? param out of the request map directly.
    Normally I would add ^:boolean edit? to the component's function signature. But I can't do that in this case because the component is called by rt/map-indexed which doesn't know about that param. In normal clojure you would probably (partial ..) the mapped function, but that doesn't work in ctmx.

  3. Notice the gymnastics necessary to get the pair value: pair (or (value "pair") pair).
    When the component is called by map-indexed while rendering the parent, the function's arg pair is provided. But when the form is submitted the function arg pair is nil and it must be accessed with the value function.

  4. I'm using hx-include because you cannot include <form> tags inside tables, one could rewrite this with divs instead of tables and and a <form> enclosing each "row" instead of using hx-include. This is unrelated to the point of this issue.

Fix some clj-kondo linting warnings for defcomponent / provide clj-kondo config OOTB

Hi,

Since ctmx is using macros - this causes clj-kondo to complain since it does not know the shape.
We can use :lint-as ans some other tricks to avoid seeing the complains.
A clj-kondo configuration can be shipped with the library so all users can benefit.
https://github.com/clj-kondo/clj-kondo/blob/master/doc/config.md#exporting-and-importing-configuration

In .clj-kondo/config.edn

{:lint-as {ctmx.core/defcomponent clojure.core/fn}}

Or configure in each ns

(ns my-ns
  {:clj-kondo/config '{:lint-as {ctmx.core/defcomponent clojure.core/fn}}}

Document that parameters need to be keywordized

I integrated ctmx into a template stack I use that runs pedestal+reitit. Out of the box any ctmx components relying on POST params were not working. The root issue was that the form parameters were not being convert to keywords. reitit.http.interceptors doesn't include an interceptor out of the box for this. reitit.http.interceptors.parameters only keywordizes query params.

The solution is to use the ring keyword-params middleware and wrap it in an interceptor:

(ns foo (:require
   [reitit.http.interceptors.parameters :as parameters]
  [ring.middleware.keyword-params :as keyword-params]))

(def keyword-params-interceptor
  {:name ::keyword-params
   :enter (fn [ctx]
            (let [request (:request ctx)]
              (assoc ctx :request
                     (keyword-params/keyword-params-request request))))})

(def default-interceptors [
                           ;; add other interceptors as you see fit
                           (parameters/parameters-interceptor)
                           keyword-params-interceptor])

; use default-interceptors in your routes

For ctmx it might be useful to mention in the docs/readme that the library expects all parameters to be keywordized.

json-params docs out of date

This snippet from the README no longer works:

(json-params
  {:store-name "My Store"
   :customers_0_first-name "Joe"
   :customers_0_last-name "Smith"
   :customers_1_first-name "Jane"
   :customers_1_last-name "Doe"})

;; {:store-name "My Store"
;;  :customers [{:first-name "Joe" :last-name "Smith"}
;;              {:first-name "Jane" :last-name "Doe"}]}

It actually results in:

{:store-name "My Store"
    :customers {:first-name ["Joe" "Jane"]
                :last-name ["Smith" "Doe"]}}

I think this is related to the changing of the position of the index in the flattened structure:

bcae2b7

The example should actually be:

(form/json-params
  {:store-name "My Store"
   :0_customers_first-name "Joe"
   :0_customers_last-name "Smith"
   :1_customers_first-name "Jane"
   :1_customers_last-name "Doe"})

How to choose routes?

I'm trying to migrate this project https://github.com/rajasegar/htmx-calendar/ to ctmx to better understand the library and tech stack.

I got this working https://github.com/ieugen/jlp/ .

I know that make-routes builds the routes for me, but it also hides stuff from me.
I am new to all the stack so I might not make sense.

How can I have routes like this?

(defn calendar-routes []
  (ctmx/make-routes
   "/"
   (fn [req]
     (html5-response
      (calendar req))))
  [(ctmx/make-routes
    "/next"
    (fn [req]
      (markup req (-> (LocalDate/now) (.plusDays 1)))))
   (ctmx/make-routes
    "/previous"
    (fn [req]
      (markup req (-> (LocalDate/now) (.minusDays 1)))))])

Specifying the endpoint URL for components

Hi there,

I was wondering what your thoughts were about being able to specify a given component's endpoint URL, so as not to clutter the top level.

The overall page might be at "www.site.com/dashboard" but the lower level components might be tucked away.

For example a table component on the 'dashboard' page might be "www.site.com/component/ui-table/table"
The row component of that table component might have an endpoint of "www.site.com/component/ui-table/row"

Is this something that is possible now, or would I need to do some tweaking?

Adding components to routes

Hi Matthew,

I'm having fun figuring out your library, but I have a question.

What is the intended way to add the routes for components not directly in the dependency tree?

For example the 'click to edit' demo here

There are two components form-edit and form-ro, and the route declaration.

When you macroexpand the make-routes in the demo there is no mention of the other component being accessible in the routes:

(macroexpand 
  `(make-routes "/edit-demo"
      (fn [req]
         (form-ro req "Joes" "Blow" "[email protected]"))))

you get:

Class: clojure.lang.PersistentVector
Contents: 
  0. "/edit-demo"
  1. [ "" { :get ( ctmx.rt/redirect "/edit-demo/" ) } ]
  2. [ "/" { :get ( clojure.core/fn [ miracleworld.routes.demo/req ] ( miracleworld.routes.demo/form-ro miracleworld.routes.demo/req "Joes" "Blow" "[email protected]" ) ) } ]
  3. [ "/form-ro" ( clojure.core/fn [ x__9847__auto__ ] ( clojure.core/-> x__9847__auto__ miracleworld.routes.demo/form-ro ctmx.render/snippet-response ) ) ]

I understand that the example is incomplete, ie. I think that we need to wrap the components so they are in a response map when we are returning them {:status 200 :headers ... :body ... } and actually set the routes to be used by the server, etc. When I do this, I still get a 404 response for the /edit-demo/form-edit Or /form-edit endpoint.

Just wondering what is the best way to add form-edit to the routes? Or is it something that I'm missing?

Thanks again for the interesting library.

(I’m using the latest version of the code from the repo as of today)

Term `middleware` is confusing; other thoughts

First, thanks for getting back to me regarding my previous question, and for making this library in the first place. I've enjoyed exploring it so far!

I'm following up on a message I dropped in the clojurians #ctmx channel.

Generally, when I see the word middleware – especially in a ring context – I think of a function like this:

(defn wrap-hx-redirect [handler]
  (fn [req]
    (let [req (handler req)]
      (update req :headers assoc "hx-redirect" "/"))))

That is, I expect a function that takes a handler and returns a new handler that typically invokes the passed handler with the req argument.

ctmx's ^{:req middleware} metadata is confusing, as it doesn't behave as we typically expect middleware to behave; middleware is rather expected to be something akin to a request transformer, i.e. a simpler function of type req -> req.

A few thoughts.

To start, I'm wondering if there a reason that you've created ctmx's middleware such that it is capable of transforming the req map passed into a component, but cannot transform the response map returned by it? My hunch is that this restriction is intentional, though it isn't immediately clear to me why that might be. (Perhaps you feel the semantics of the interaction between ctmx's middleware and ring's middleware stack to be a bit tricky to define, particularly in the case of nested components? Just guessing here.)

I ask because component middleware seems like a fairly elegant way to separate view and controller concerns, but this approach is fairly limited if one cannot update headers in the response object, for example.

If this restriction is intentional, then I might consider changing the name from middleware to something else to reduce confusion.

But I should also ask: am I barking up the wrong tree here? Do you recommend a different strategy for making modifications to the response object that extend beyond the :body key? Imagine that one is making a login component, for example, where login success should add :user-identity info to a :session map on the response object. How would you go about that currently?

Use the safer hiccup2.core/html to prevent XSS

ctmx is using hiccup.core/html to render partial responses. hiccup.core/html doesn't escape strings, so it makes apps vulnerable to XSS. This is what the new ns hiccup2.core provides, a default that escapes strings.

I already use hiccup2.core/html to render the entire pages, but during security testing I discovered that XSS can appear in rendered partials.

Here is a little workaround. It should be loaded early on during app start.

(ns example
(:require
   [ctmx.render :as ctmx.render]
   [hiccup2.core :as hiccup2]))

;; Ensure ctmx is using the XSS safe hiccup render function
(alter-var-root #'ctmx.render/html (constantly
                                    #(-> % ctmx.render/walk-attrs hiccup2/html str)))

In this issue I'm not necessarily asking for the default to be changed (that might not be backwards compatible), but rather I wanted to bring it to the attention of the community.

At least I suspect some documentation is in order, and perhaps an option/toggle.

clojurescript included by default

I'm not sure how other projects in the clj/cljs ecosystem handle this, but because simpleui depends on hiccups, that means the big fat cljs dependency is included in my uberjar.

I tried adding
:exclusions [macchiato/hiccups] to my simpleui/simpleui in deps.edn, but then I get:

Compiling Clojure...
Execution error (FileNotFoundException) at simpleui.core/loading (core.clj:1).
Could not locate cljs/env__init.class, cljs/env.clj or cljs/env.cljc on classpath.

Is it possible to make the cljs dependency optional? I'm trying to slim down my uberjar because I'm deploying on a raspberry pi which can be sensitive to big fat jars.

hx-vals not working out of the box with the kit template

Out of the box :hx-vals isn't working properly:

  (page
    (render/walk-attrs
      [:button {:hx-vals {:action "play-pause"} :type :submit}]))
  ;; => {:body "<!DOCTYPE html>\n<html><button hx-vals=\"{&quot;action&quot;:&quot;play-pause&quot;}\" type=\"submit\"></button></html>",
  ;;     :headers {"Content-Type" "text/html"},
  ;;     :status 200}

walk-attrs is producing a json formatted string:

  (render/walk-attrs
      [:button {:hx-vals {:action "play-pause"} :type :submit}])
  ;; => [:button {:hx-vals "{\"action\":\"play-pause\"}", :type :submit}]

But the page function provided by the template is html encoding the quotes.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.