Fork me on GitHub
#beginners
<
2022-11-22
>
Tema Nomad07:11:53

If I develop SPA with reitit as a router lib, should I add it in deps part of project.clj for backend routing and in deps part of shadow-cljs.edn for frontend both? Because now I have it in deps of project.clj and I cant use it in my .cljs frontend files I need reitit.ring for my backend .clj files And I need these ones for my frontend .cljs files

reitit.frontend
reitit.frontend.easy
reitit.coercion.spec
So it looks odd if I need to double each library if I need it not only for backend, but for frontend too. Or shadow-cljs just reuse already downloaded libs in project.clj ?

Tema Nomad07:11:44

I know it, but my question was about to understand how it works if I use shadow-cljs as standalone tool

Rupert (All Street)08:11:19

Because frontend is compiled down from CLJS and CLJC to JavaScript - any CLJ files that you have are ignored/left out. So it means adding depdenencies with CLJ files to your (shadowCLJS) frontend shouldn't really make the compiled code any bigger (so you can probably add the dependency and just not worry about it).

Tema Nomad08:11:09

@UJVEQPAKS so its normal practice for production apps development to add the same lib into 2 places: 1 for backend (`leiningen` config) and 1 for frontend (`shadow-cljs` config)?

Rupert (All Street)08:11:46

We treat our forntend and backend as two separate apps (separate projectlj.clj) that communicate to each other over websockets. So their is no shared code for us unless we explicitly add a shared dependency (which we often do). That way it's really clear what is a frontend or backend dependency for us.

Rupert (All Street)08:11:13

I think what you are describing might be reasonably common practice.

🙏 1
valerauko12:11:29

> If I develop SPA with reitit as a router lib, should I add it in deps part of project.clj for backend routing and in deps part of shadow-cljs.edn for frontend both? yes.

👍 1
valerauko12:11:37

as noted above it's possible to make lein do everything but in my opinion it's easier to reason about your dependency trees if you have it explicitly defined separately for frontend and backend (especially, if i recall correctly you had them in separate folders altogether too)

Tema Nomad12:11:43

@vale yes. like this Btw, its proper way from shadow-cljs authors as they write in tutorial, I mean use shadow-cljs as a standalone tool, not via lein

Tema Nomad12:11:09

oh I need to add .DS_Store to nvim-tree filter 😄

Rupert (All Street)12:11:48

> oh I need to add .DS_Store to nvim-tree filter But then how else would everyone know you are using Apple hardware? 😄

Tema Nomad12:11:39

I think its not something cool in IT community)

valerauko12:11:21

ah you separed them under src huh? i have a bit different setup, but it's really a matter of preference i guess?

Rupert (All Street)12:11:35

Yeah - true - I guess it's cooler in management/sales/design.

👍 1
Tema Nomad12:11:45

@vale hmm interesting structure. It looks more clean.

Tema Nomad12:11:26

especially that you split all config files

Tema Nomad12:11:52

what gateway dir contains?

valerauko12:11:07

yes. there are very few things common -- maybe .clj-kondo config and .gitignore in my case?

Tema Nomad12:11:29

gitignore probably yes

valerauko12:11:39

as this is a docker-compose setup gateway contains the nginx.conf that decides what to route to the api and what should be handled by "frontend"

valerauko12:11:20

i'm sorry i don't have a repo for this right now but wait a few minutes and i'll make one

👍 1
Tema Nomad13:11:59

@vale if I just learn now I think it will be ok to up just 1 server with both frontend/backend parts (so without nginx like in your repo)? And split routes like you do it in nginx config, but in clj core file like this: /api* to backend /* to html-page that loads my js compiled via shadow-cljs file Is it good solution?

valerauko13:11:09

yes of course that works too! just make sure your server handles the js/css assets too then (reitit has a resource handler for this i believe)

Tema Nomad13:11:16

yes, ring/create-resource-handler already added! I started to investigate it when had favicon load issue 😄

Tema Nomad13:11:11

@vale hm, looks like I need to change my ring/create-default-handler from 404 text to frontend-handler to catch all requests except /api ? Since I manage proxy via clojure backend

Tema Nomad13:11:42

hmm probably its bad idea, this function specially for 404 and 405 etc errors

valerauko13:11:32

what do you want to achieve? if you just want to pass "everything except /api to frontend" then you'll have to serve your index.html like that, yes (and do the error routing on the frontend). this does have the negative effect that your server returns 200 ok, even for routes that end up with a "page missing" error. if you care about search engines finding your pages then you'll either need to share your route definitions between backend and frontend (should be possible with reitit but i've never tried) or do server-side rendering (on which i have https://github.com/valerauko/hoge/)

1
Tema Nomad13:11:07

Ok the current thread is done I think. I need a new one for routes 🙂

Tema Nomad02:11:28

@U04V70XH6 Need your opinion too 🙂

Tema Nomad13:11:58

How should I separate reitit routes when develop SPA with frontend/backend separation? The problem is: IF I put all /api requests to backend handler AND put every other requests to frontend html-page (with compiled js via shadow-cljs) AND I have some routes on frontend (via reitit.frontend) THEN (from @vale) > this does have the negative effect that your server returns 200 ok, even for routes that end up with a "page missing" error.

valerauko15:11:47

you might want to ask in #C7YF1SBT3

🙏 1
Tema Nomad02:11:36

@U04V70XH6 Need your opinion :)

seancorfield03:11:41

My opinion would be to ask in #C7YF1SBT3 since I don't use that library 🙂

Tema Nomad03:11:01

already asked, but how it works on your projects with compojure then? How do you separate back/front routes?

seancorfield03:11:18

We don't have any ClojureScript projects.

Tema Nomad03:11:22

but I dont know is this what I need or not 😄

seancorfield03:11:33

Our frontend is all JS (React, Redux, Immuatble, etc) -- because when we started that project (years ago), the cljs ecosystem wasn't stable/robust enough for us. We might make a different decision today. Maybe.

👍 1
valerauko03:11:33

i think it is what you need yes. the problem here isn't sharing the routes (paths and names) but that your route definitions will normally close over the handler functions on the server side and the reagent views on the frontend. including those in cljc is not trivial. the example you found is good: it's either lots of reader conditionals, or you end up separating the route definitions themselves from the handlers/views using an expander i have not done this myself so if you get it to work, please write a blog post about it because i'd love to see how it turns out

👍 1
Tema Nomad03:11:40

ok nice, will dive into it now

dumrat16:11:32

Do any of you have a good workflow going with htmx for auto-refresh? I find I have to manually refresh page when I change backend.

Rupert (All Street)17:11:30

I often use manual browser refresh (using Ctrl + R hotkey). In the past we've setup https://github.com/wkf/hawk to watch a directory and trigger a websocket/polling when files change causing a browser refresh. Probably overkill for most usecases though. Some other (non-clojure specific) solutions https://stackoverflow.com/questions/5588658/auto-reload-browser-when-i-save-changes-to-html-file-in-chrome: • https://livejs.com/<meta http-equiv="refresh" content="3" >

skylize17:11:45

Given an aliased ns and the name of a protocol in that ns, how do I translate it to the full type for use with instance??

(require '[my.ns :as my]

(let [bar my/get-a-maybe-foo]
  (instance? my.ns.Foo bar)  ; works
  (instance? my/Foo bar))    ; doesn't work

dpsutton17:11:10

(doc satisfies?)
-------------------------
clojure.core/satisfies?
([protocol x])
  Returns true if x satisfies the protocol
nil

dpsutton18:11:05

i think you are relying on the fact that there is an interface created by defprotocol. Which is a class. That has a fully qualified name but is not a var in the namespace. which is why my/Foo doesn’t work for instance. That’s checking for instance of a var which is false. The first check is instance of a class, which does work

dpsutton18:11:39

user=> (defprotocol Foo (foo [_]))
Foo
user=> (instance? user.Foo (reify Foo (foo [_])))
true
user=> (instance? Foo (reify Foo (foo [_])))
Execution error (ClassCastException) at user/eval133885 (REPL:62).
class clojure.lang.PersistentArrayMap cannot be cast to class java.lang.Class (clojure.lang.PersistentArrayMap is in unnamed module of loader 'app'; java.lang.Class is in module java.base of loader 'bootstrap')
user=> Foo
{:on user.Foo,
 :on-interface user.Foo,
 :sigs {:foo {:tag nil, :name foo, :arglists ([_]), :doc nil}},
 :var #'user/Foo,
 :method-map {:foo :foo},
 :method-builders {#'user/foo #object[user$eval133864$fn__133865
                                      "0x3f7b2d9f"
                                      "user$eval133864$fn__133865@3f7b2d9f"]}}
user=> (satisfies? Foo (reify Foo (foo [_])))
true

skylize18:11:20

Thanks 💕

Alex Miller (Clojure team)18:11:26

in general, it's usually a smell to be checking the type of a protocol like this. instance? only works with direct extensions (like deferecord and reify) and not external extensions (from extend-protocol or extend-type) or metadata extensions. satisfies? catches the first two but the last one. The best way to deal with the conditional behavior based on protocol type is to put it in the protocol so it's a polymorphic call (or add an additional protocol)

Alex Miller (Clojure team)18:11:00

so if you have a maybe-foo, you could call a method on the protocol and use an Object (and nil) extension fallback

skylize18:11:48

Thanks for pointing that out. So far, was only trying to use it as a quick fail in a test.

skylize18:11:23

Using generators to make instances, if it fails to match the protocol I know something went terribly wrong.

skylize20:11:14

What about checking if a value is a specific record type? Same example, but now Foo is a Record

(require '[my.ns :as my]

(let [bar my/get-a-maybe-foo]
  (instance? my.ns.Foo bar)  ; works
  (instance? my/Foo bar))    ; doesn't work

Alex Miller (Clojure team)20:11:17

that's fine - there's only one way to make a record, and only one way to check its type (via its generated class)

Alex Miller (Clojure team)20:11:00

Records are concrete values, protocols are not

skylize20:11:33

So is there a function I could call that would translate my/Foo into my.ns.Footo call instance? on? And will there be any gotchas here taking the same into ClojureScript?

Daniel Shriki18:11:16

using clojure.data.xml :

(xml/emit-str (xml/element "tagname" {:xmlns "someattr"} "content"))
results:
<?xml version=\"1.0\" encoding=\"UTF-8\"?><tagname xmlns:a=\"someattr\">content</tagname>
why can’t I remove this :a ns? why is it forced to have a ns? what it’s purpose? tnx 🙏

Alex Miller (Clojure team)19:11:44

that's how xml works. but you can set a default xmlns attribute at the top element

Daniel Shriki19:11:32

I have endpoint that request an xml file and this endpoint doesn’t accept the xml with :a or any ns . It’s a third party we are working with. I’m not sure what should I say to their technical team…

Alex Miller (Clojure team)19:11:17

there are some changes that were made on the most recent versions that may help with this

Daniel Shriki19:11:59

can you name a version-tag? or should I just take the most recent alpha?

Alex Miller (Clojure team)19:11:26

looking again, I think those changes were on the parsing side, not the emitting side

Alex Miller (Clojure team)19:11:22

what do you actually want as output?

Daniel Shriki19:11:44

<?xml version=\"1.0\" encoding=\"UTF-8\"?><tagname xmlns=\"someattr\">content</tagname>

Daniel Shriki19:11:05

just without :a, exactly as I specified in the function-call

Daniel Shriki19:11:22

I’m not familiar with xml so much, if there’s a protocol that require xmlns to be with some ns. I didn’t find something like this online so I don’t understand the behavior

Alex Miller (Clojure team)19:11:28

I think you might just be missing how to tell data.xml what you're doing, looking

🙏 1
Alex Miller (Clojure team)19:11:39

something like this works using the hiccup-y form:

(xml/alias-uri 's "")
(xml/emit-str (xml/sexp-as-element [::s/a {:xmlns ""} "content"]))
;;=> "<?xml version=\"1.0\" encoding=\"UTF-8\"?><a xmlns=\"\">content</a>"
which is how I usually use it

Alex Miller (Clojure team)19:11:07

not sure how that differs from what you started with

Alex Miller (Clojure team)19:11:23

I guess the difference is the tag that's expected to be encoded in a keyword

Daniel Shriki19:11:46

so somehow doing ::s/a helps :thinking_face:

Alex Miller (Clojure team)19:11:14

well that ::s expands to an encoded xml ns prefix

Alex Miller (Clojure team)19:11:43

user=> ::s/a
:xmlns.http%3A%2F%2Fsomeattr/a

Alex Miller (Clojure team)19:11:41

so this works too (assuming you match the ns url set here and used with the xml/alias-uri call above

(xml/emit-str (xml/element ::s/a, {:xmlns ""} "content"))

Daniel Shriki19:11:23

does it have to be an uri on the attribute?

Alex Miller (Clojure team)19:11:53

basically the tag name here is a, but only in the context of the default namespace . So if you want to refer to that specific a, you have to qualify by connecting it to the namespace. For ease of use, the namespace is encoded into a keyword alias.

Alex Miller (Clojure team)19:11:29

it has to be a uri by definition and usually they are are urls (but I don't think that's a constraint)

Alex Miller (Clojure team)19:11:23

user=> (xml/alias-uri 's1 "someattr")
nil
user=> (xml/emit-str (xml/element ::s1/a {:xmlns "someattr"} "content"))
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><a xmlns=\"someattr\">content</a>"
so, yes it works, no it's not a constraint

Daniel Shriki19:11:59

well, my actual usecase is:

(xml/emit-str (xml/element LifeLink {:xmlns "urn:lifelink-schema"} "content"))
with that specific tagname and that specific value.. so making sure I got it straight (cause I still don’t get the desired behavior)

Daniel Shriki19:11:12

it need to be something like-

(xml/emit-str (xml/element ::s/LifeLink {:xmlns "urn:lifelink-schema"} "content"))
?

Alex Miller (Clojure team)19:11:18

user=> (xml/alias-uri 's "urn:lifelink-schema")
nil
user=> (xml/emit-str (xml/element ::s/LifeLink {:xmlns "urn:lifelink-schema"} "content"))
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><LifeLink xmlns=\"urn:lifelink-schema\">content</LifeLink>"
is that not what you want?

Daniel Shriki19:11:34

exactly. only I’m getting a different result. I’m running 0.2.0-alpha6

Daniel Shriki19:11:10

I’m getting:

<?xml version=\"1.0\" encoding=\"UTF-8\"?><a:LifeLink xmlns=\"urn:lifelink-schema\" xmlns:a=\"\">content</a:LifeLink>
with a: on the tag

Daniel Shriki19:11:31

what version do you run? alpha8 perhaps?

Daniel Shriki19:11:01

tried with alpha7 & 8, both return error:

Syntax error reading source at (REPL:1:40).
Invalid token: ::s/LifeLink

Alex Miller (Clojure team)19:11:15

I should be on latest (I'm running out of the repo itself)

Daniel Shriki19:11:34

master branch?

Alex Miller (Clojure team)19:11:52

yeah, but should same as last alpha

Alex Miller (Clojure team)19:11:58

I can just run from deps too

Alex Miller (Clojure team)19:11:37

Invalid token: ::s/LifeLink
seems weird to me though unless you didn't call xml/alias-uri

Alex Miller (Clojure team)19:11:41

% clj -Sdeps '{:deps {org.clojure/data.xml {:mvn/version "0.2.0-alpha8"}}}'
Clojure 1.11.1
user=> (require '[clojure.data.xml :as xml])
nil
user=> (xml/alias-uri 's "urn:lifelink-schema")
nil
user=> (xml/emit-str (xml/element ::s/LifeLink {:xmlns "urn:lifelink-schema"} "content"))
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><LifeLink xmlns=\"urn:lifelink-schema\">content</LifeLink>"

Daniel Shriki19:11:48

aaaa right, I did miss the call for alias-uri

Alex Miller (Clojure team)20:11:29

I'm going to add/update some of more stuff on the docs btw, which is why I am taking the time to go through this, so hopefully it can be less confusing. You are not by any means the first to ask this here.

👍 1
Daniel Shriki20:11:41

If I can help with that somehow I would definitely will try to!

Daniel Shriki20:11:46

I’m just trying to understand. In which point in time I need to run (xml/alias-uri) ? At first I was thinking before I run emit-str, but I get this error even before I run the function, when the code compiles

Daniel Shriki20:11:15

Even when I’m doing this, I’m getting Invalid token: ::s/LifeLink

(defn xml-obj [agent]
  (xml/alias-uri 's "urn:lifelink-schema")
  [{:tag     ::s/LifeLink
    :attrs   {:xmlns "urn:lifelink-schema"}
    :content [{...}]}])

Alex Miller (Clojure team)20:11:43

the xml/alias-uri call should be at the top level of the namespace, alongside your defns

Daniel Shriki20:11:36

but now he pushes those attrs this to every other tag I have attrs:

xmlns=""
xmlns:c="urn:lifelink-schema"
for example:
<LifeLink
	xmlns=\"urn:lifelink-schema\">
	<LL
		xmlns=\"\"
		xmlns:a=\"urn:lifelink-schema\" a:LoginType=\"NORMAL\">
		<UserName>email</UserName>

Daniel Shriki20:11:47

and again, the a..

Alex Miller (Clojure team)20:11:32

can you post some example code that doesn't work?

Daniel Shriki20:11:20

yup. so it will be a bit long:

Daniel Shriki20:11:49

(xml/alias-uri 's "urn:lifelink-schema") 

(defn winflex-obj [agent]
  [{:tag     ::s/LifeLink
    :attrs   {:xmlns "urn:lifelink-schema"}
    :content [{:tag     :LL
               :attrs   {:LoginType "WF_AGENCY"}
               :content [{:tag     :UserName
                          :attrs   {}
                          :content (:email agent)}
                         {:tag     :InterfaceType
                          :attrs   {}
                          :content "NoGUI"}
                         {:tag     :WFCompanyCode
                          :attrs   {}
                          :content "somecode"}
                         {:tag     :WFCompanyPassword
                          :attrs   {}
                          :content (env/env :winflex-password)}
                         {:tag     :OutputType
                          :attrs   {}
                          :content "URL"}
                         {:tag     :Tool
                          :attrs   {}
                          :content {:tag     :Name
                                    :attrs   {}
                                    :content "WinFlex"}}]}
              {:tag     :WinFlex
               :attrs   {}
               :content {:tag     :Profile
                         :attrs   {:AutoCreate (env/env :winflex-auto-create)}
                         :content [{:tag     :FirstName
                                    :attrs   {}
                                    :content (:first_name agent)}
                                   {:tag     :LastName
                                    :attrs   {}
                                    :content (:last_name agent)}
                                   {:tag     :CompanyName
                                    :attrs   {}
                                    :content "Sproutt"}
                                   {:tag     :Phone
                                    :attrs   {}
                                    :content (:phone_number agent)}
                                   {:tag     :Email
                                    :attrs   {}
                                    :content (:email agent)}]}}]}])
been build with this recursive function:
(defn map->xml [m]
  (xml/emit-str
    (map (fn make-node [{:keys [tag attrs content]}]
           (if (map? content)
             (xml/element tag attrs (map make-node content))
             (xml/element tag attrs content)))
         m)))

Alex Miller (Clojure team)20:11:25

you need to use the s namespace prefix for any tags that are part of that namespace

Alex Miller (Clojure team)20:11:47

which is all of the tags I think

Alex Miller (Clojure team)20:11:51

instead of using the map format, you might find it easier to just use the built-in xml/sexp-as-element stuff and the hiccup style format - that last example link I posted shows that and it's a lot easier to read and edit

Alex Miller (Clojure team)20:11:38

although maybe it's from some common emitter you have

Daniel Shriki20:11:52

interesting, checking

Alex Miller (Clojure team)20:11:12

(xml/sexp-as-element
  [::s/LifeLink {:xmlns "urn:lifelink-schema"}
   [::s/LL {:LoginType "WF_AGENCY"}
    [::s/UserName (:email agent)]
    [::s/InterfaceType "NoGUI"] ...etc...]])

Daniel Shriki21:11:32

it’s kinda working, and this pattern really much succinct and easier. thanks a lot 🙏 would be happy to contribute if possible.

skylize19:11:12

Know of any articles covering useful generic patterns for testing protocols?

skylize19:11:35

Right now I am primarily working on 2 protocols that interact: 1 protocol is the base for a state monad, while the other describes requirements of the state handled by that monad. So anything that deals with testing interdependent protocols would be of particularly high value for me. 🙏

bschrag22:11:03

Any good ideas about how to update one of the value sets here? Say, add beagle to dogs?

> @breeds
{dogs #{samoyed cocker}, cats #{tiger calico}}

dpsutton22:11:57

decompose to the different parts. To add to a set use conj. (conj #{:a} :b) To update the value of a map, use update. (update {:a 1} inc) Put them together we have (update breeds-map 'dogs conj 'beagle) Finally, to update an atom we use swap!

👍 1
souenzzo22:11:33

And it is in an atom 🙂 (swap! breeds update 'dogs conj 'beagle)

👍 1
bschrag22:11:48

Cool. (I was almost there...) Any insight on why beagle got namespaced here, or how to avoid that? I'm guessing because hashmaps don't get traversed for internment? I'd used...

> (def breeds (atom '{dogs #{cocker samoyed} cats #{calico tiger}}))
...to create the initial value.

seancorfield22:11:43

Sounds like you use backtick instead of single quote?

`beagle vs 'beagle

seancorfield22:11:30

(and the cat show husband in me can't help point out that calico is not a breed of cat, it's a color 🙂 )

1
seancorfield22:11:03

(my partner is an international cat show judge so some of it rubs off on me, sorry!)

🐱 1
bschrag22:11:58

@U04V70XH6 Right you are---backtick. Not the right choice here. And I figured someone would call me out on "calico!" 🙂