This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-11-22
Channels
- # aleph (5)
- # announcements (9)
- # babashka (9)
- # beginners (127)
- # cherry (1)
- # cider (48)
- # clj-kondo (5)
- # cljdoc (1)
- # clojure (70)
- # clojure-berlin (1)
- # clojure-europe (57)
- # clojure-france (2)
- # clojure-germany (1)
- # clojure-nl (2)
- # clojure-norway (4)
- # clojure-uk (1)
- # clojurescript (2)
- # css (1)
- # cursive (6)
- # emacs (6)
- # gratitude (1)
- # honeysql (5)
- # introduce-yourself (5)
- # jobs-discuss (7)
- # joyride (1)
- # kaocha (3)
- # lsp (1)
- # malli (9)
- # nbb (2)
- # off-topic (91)
- # pathom (7)
- # pedestal (14)
- # re-frame (4)
- # reitit (67)
- # shadow-cljs (46)
- # spacemacs (3)
- # squint (3)
- # tools-build (14)
- # tools-deps (1)
- # vim (3)
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
?I know it, but my question was about to understand how it works if I use shadow-cljs
as standalone tool
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).
@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)?
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.
> 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.
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)
@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
oh I need to add .DS_Store
to nvim-tree filter 😄
> oh I need to add .DS_Store
to nvim-tree filter
But then how else would everyone know you are using Apple hardware? 😄
LUL ))
I think its not something cool in IT community)
ah you separed them under src huh? i have a bit different setup, but it's really a matter of preference i guess?
@vale hmm interesting structure. It looks more clean.
especially that you split all config files
what gateway
dir contains?
yes. there are very few things common -- maybe .clj-kondo config and .gitignore in my case?
gitignore probably yes
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"
i'm sorry i don't have a repo for this right now but wait a few minutes and i'll make one
hehe nice
@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?
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)
yes, ring/create-resource-handler
already added!
I started to investigate it when had favicon
load issue 😄
@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
hmm probably its bad idea, this function specially for 404 and 405 etc errors
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/)
Ok the current thread is done I think. I need a new one for routes 🙂
@U04V70XH6 Need your opinion too 🙂
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.
@U04V70XH6 Need your opinion :)
My opinion would be to ask in #C7YF1SBT3 since I don't use that library 🙂
already asked, but how it works on your projects with compojure then? How do you separate back/front routes?
We don't have any ClojureScript projects.
ahh I see
@vale btw I found this https://github.com/metosin/reitit/blob/master/doc/advanced/shared_routes.md
but I dont know is this what I need or not 😄
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.
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
ok nice, will dive into it now
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.
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" >
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
(doc satisfies?)
-------------------------
clojure.core/satisfies?
([protocol x])
Returns true if x satisfies the protocol
nil
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
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
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)
so if you have a maybe-foo, you could call a method on the protocol and use an Object (and nil) extension fallback
Thanks for pointing that out. So far, was only trying to use it as a quick fail in a test.
Using generators to make instances, if it fails to match the protocol I know something went terribly wrong.
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
that's fine - there's only one way to make a record, and only one way to check its type (via its generated class)
Records are concrete values, protocols are not
So is there a function I could call that would translate my/Foo
into my.ns.Foo
to call instance?
on? And will there be any gotchas here taking the same into ClojureScript?
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 🙏that's how xml works. but you can set a default xmlns attribute at the top element
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…
there are some changes that were made on the most recent versions that may help with this
can you name a version-tag? or should I just take the most recent alpha?
looking again, I think those changes were on the parsing side, not the emitting side
what do you actually want as output?
<?xml version=\"1.0\" encoding=\"UTF-8\"?><tagname xmlns=\"someattr\">content</tagname>
just without :a, exactly as I specified in the function-call
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
I think you might just be missing how to tell data.xml what you're doing, looking
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 itnot sure how that differs from what you started with
I guess the difference is the tag that's expected to be encoded in a keyword
so somehow doing ::s/a helps :thinking_face:
well that ::s expands to an encoded xml ns prefix
user=> ::s/a
:xmlns.http%3A%2F%2Fsomeattr/a
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"))
does it have to be an uri on the attribute?
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.
it has to be a uri by definition and usually they are are urls (but I don't think that's a constraint)
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 constraintwell, 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)it need to be something like-
(xml/emit-str (xml/element ::s/LifeLink {:xmlns "urn:lifelink-schema"} "content"))
?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?exactly. only I’m getting a different result. I’m running 0.2.0-alpha6
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 tagwhat version do you run? alpha8 perhaps?
tried with alpha7 & 8, both return error:
Syntax error reading source at (REPL:1:40).
Invalid token: ::s/LifeLink
I should be on latest (I'm running out of the repo itself)
master branch?
yeah, but should same as last alpha
I can just run from deps too
Invalid token: ::s/LifeLink
seems weird to me though unless you didn't call xml/alias-uri% 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>"
aaaa right, I did miss the call for alias-uri
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.
If I can help with that somehow I would definitely will try to!
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
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 [{...}]}])
the xml/alias-uri
call should be at the top level of the namespace, alongside your defns
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>
and again, the a..
can you post some example code that doesn't work?
yup. so it will be a bit long:
(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)))
you need to use the s
namespace prefix for any tags that are part of that namespace
which is all of the tags I think
::s/LL
etcinstead 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
although maybe it's from some common emitter you have
interesting, checking
(xml/sexp-as-element
[::s/LifeLink {:xmlns "urn:lifelink-schema"}
[::s/LL {:LoginType "WF_AGENCY"}
[::s/UserName (:email agent)]
[::s/InterfaceType "NoGUI"] ...etc...]])
it’s kinda working, and this pattern really much succinct and easier. thanks a lot 🙏 would be happy to contribute if possible.
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. 🙏
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}}
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!
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.Sounds like you use backtick instead of single quote?
`beagle vs 'beagle
(and the cat show husband in me can't help point out that calico
is not a breed of cat, it's a color 🙂 )
(my partner is an international cat show judge so some of it rubs off on me, sorry!)
@U04V70XH6 Right you are---backtick. Not the right choice here. And I figured someone would call me out on "calico!" 🙂