Fork me on GitHub
#clojurescript
<
2022-05-22
>
Noah Bogart00:05:01

I want to use a clojurescript library in a Typescript project. Is that possible? All searching i did gave info for the other direction (using an existing JavaScript’s library in clojurescript).

eighttrigrams00:05:42

That is possible with shadow-cljs, if you are the author of the lib. I tested that. For other libraries I don’t know.

👍 1
Noah Bogart01:05:53

It’s an open source library so I have access to the code. Is it just compiling it with a node target? Is there additional work I have to do to transform the persistent data structures produced by the clojurescript to javascript’s native data ones?

eighttrigrams01:05:39

Compiling to a node target will do the trick. shadow-cljs will compile everything using the closure compiler. What was important and which I did not understand at first, going through the shadow docs, was that one must annotate the public/entry functions to keep their names. Because otherwise all the names get minified and changed by the compiler.

👍 1
Noah Bogart01:05:15

I’ll read up on shadow-cljs! Thanks so much

👍 1
augustl08:05:27

you can also just use the cljs compiler I believe. You can wrap a clojurescript library in your own build, and create a bunch of functions in your own library annotated with ^:export. Then you can just use the compiler CS output from your cljs project in typescript

👍 1
erre lin07:05:26

I got stuck in my small project and would like to ask for help. The project idea is pretty simple: 1. refers to the index.html which is just a page containing a search bar, pretty much like the appearance of https://grep.app/ 2. User inputs a query (say, a title or an author name), hit Enter, and then the url should becomes for example. 3. The result, fetched from server database, displays on the page in step #2 The main libraries I use are reagent, re-frame, re-frame-http-fx, reitit, and http-kit. The problem: 1. when I tried to type in a word and hit enter, the page jumps to, say, localhost:9321/search?q=random+word for about 1 second and jumps back to the index 2. the url now becomes localhost:9321/?q=random+word . It seems the "/search" route just got stripped out, and there is no error message anywhere in the console or repl buffer (Cider within Emacs as my IDE) The codebase is not large but a bit messy ATM, so I'll try to provide the just needed part below On server side, I have two simple routes

;; request handlers are defined in (ns hand)
(def routes
 [
  ["/" {:name :index,
         :get hand/index-page}]

  ["/search" {:name :search,
               :get {:parameters {:query {:q string?}},
                     :responses {200 {:body {:result string?}}},
                     :handler (fn [{{{:keys [q]} :query} :parameters}]
                                (hand/test-result q)),
                     :middleware [[parameters-middleware]]}}]
 ])
On client side, I also have a router
(def routes
  [
   ["/"
    {:name :home,
     :view index-page,
     :text "Home",
     :controllers
     [{:start (fn [& params]
                (.log js/console "Entering the index.")),
       :stop  (fn [& params]
                (.log js/console "Leaving the index."))}]
     }]

   ["/search" {:name :search,
               :view (fn [params] [result-page params]),
               :params {:query {:q s/Str}},
               :controllers
               [{:parameters {:query [:q]},
                 :start (fn [& params]
                          (.log js/console "Returning search result")),
                 :stop (fn [& params]
                         (.log js/console "Leaving result"))}]}]
   ])
Also on client side, the search bar component looks like
[:input
    {:type "text",
     :id "uquery",
     :name "q",
     :placeholder "Search for title, barcode or ISBN ... ",
     :on-change #(dispatch [:search/user-input (-> % .-target .-value)]), ;; watch user input/query
     :on-key-press (fn [e]
                     (let [user-q (subscribe [:search/current-input])]
                       (if (= "Enter" (.-key e))
                         (dispatch [:search/user-query @user-q]))) ;; send equery to server
                     )}]
The :search/user-query event handler
(re-frame/reg-event-fx
 :search/user-query                     ; when user presses `Enter' key
 (fn [{db :db} [_ user-q]]
   {:http-xhrio {:method          :get,
                 :uri             "/search",
                 :timeout         8000,
                 :params          {:q user-q},
                 :response-format (ajax/json-response-format {:keywords? true}),
                 :on-success      [:search/good-response],
                 :on-failure      [:search/bad-response]},
    :db  (assoc db :loading? true)
    }))

(re-frame/reg-event-db
 :search/good-response
 (fn [db [_ response]]
   (-> db
    (assoc :loading? false)
    (assoc :data (js->clj response)))))
Some place must be wrong but I can't wrap my mind around this. Any advice? Thank you.

alpox09:05:53

You could try to use (.preventDefault e) in the event handler of the input (onkeypress) in case the unliked behavior comes from a surrounding form that got accidentally submitted. Other than that nothing comes to my mind.

👍 1
p-himik09:05:05

There's not nearly enough information to be able to figure out what's going on. If you can't find out what's wrong on your own, please create a minimal reproducible example. And chances are, just by the virtue of the process of creating such an example, you might figure out what the error is.

👍 1
erre lin09:05:45

Hi @U6JS7B99S, thanks for your suggestion. I did add (.preventDefault e) in my previous attempts but because I did it wrong, so the keyboard inputing is completely disabled. I modified the component a little bit this time

[:input
    {:type "text",

     ;; the same as my first post

     :on-key-press (fn [e]
                     (let [user-q (subscribe [:search/current-input])]
                       (if (= "Enter" (.-key e))
                         (do
                          (dispatch [:search/user-query @user-q])
                          (.preventDefault e)) ;; send equery to server
                     )}]
That is, first dispatch the event and then prevent the default. Yes, this time, it jumps to the /search route but the query parameters didn't get attached to the URL. I think this is the right direction to explore. Just wondering if you happen to know the reason for the missing query string this time? Thank you.

erre lin09:05:14

@U2FRKM4TW Thank you. I agree with you that the information provided is insufficient. Fearing that just throwing a github repo link would be as unhelpful and also impolite, I tried to extract the code snippets that I think are most relevant. But I do feel it's hard for me to organize all the necessary info in a short thread. @U6JS7B99S offers some pretty helpful insight. I can try on that first.

alpox09:05:46

@U02UW9X8DUG I do not see where you do your client-side routing so I cannot tell you much. The URL change in your browser has to be done by the client-side router in addition to the xhr query as that one doesn't involve the browsers location.

erre lin10:05:35

@U6JS7B99S Sure. The client-side routing is also simple. I currently registered just a home (`/`) and a search (`/search`) in my routes. First, there is a events/router.cljs where I register all the routing events

(re-frame/reg-event-db
 :router/initialize-router
 (fn [db _]
   (assoc db :current-route nil)))

(re-frame/reg-fx
 :router/fx-push-state        ; reg the fx
 (fn [route]
   (apply rfe/push-state route)))

(re-frame/reg-event-fx
 :router/eh-push-state
 (fn [_ [_ & route]] ;just pushing to history
   {:router/fx-push-state route}))

(re-frame/reg-event-db
:router/navigated
 (fn [db [_ new-match]]
    (let [old-match   (:current-route db)
          controllers (rfc/apply-controllers (:controllers old-match) new-match)]
      (assoc db :current-route (assoc new-match :controllers controllers)))))
Then there is a src/router.cljs ns that does the actual routing. The routes contain only two routes defined in my first post
(def router
  (rf/router routes {:data {}}))

;; on-navigate fn as 2nd arg for rfe/start!
(defn on-navigate [new-match]
  (when new-match
    (dispatch [:router/navigated new-match])))

;; to run before the app-mounting
(defn init-router []
  (rfe/start!
   router
   on-navigate
   {:use-fragment true}))

(defn router-component []
  (let [route @(subscribe [:router/current-route])
        view (or (get-in route [:data :view])
                 index-page)]
    [view (:query-params route)]
    ))
The code is almost the same as that provided in https://github.com/metosin/reitit/blob/master/examples/frontend-re-frame/src/cljs/frontend_re_frame/core.cljs. router-component will then be rendered by reagent.dom/render in the core.cljs , which I didn't show here. I'm not sure if this information is enough. I can provide more in some other format if you wish. I appreciate your help and time, understanding that it can be hard for others to help me debug online. So feel free to drop my case at any time. Many thanks.

alpox10:05:28

Maybe some more information would be beneficial: specifically where you dispatch the route change event (push-state)

erre lin12:05:53

@U6JS7B99S Sure. I tried to tidy the codebase a bit. Was thinking a gist might work but on second thought I think it's better to just show the repo. Under src/main there are client and server . ===================== client routing ============================== On the client side, the real routing is decided in router.cljs (https://github.com/Linerre/chaplin/blob/dev/src/main/client/router.cljs). But the push-state event is registered in events/router.cljs (https://github.com/Linerre/chaplin/blob/dev/src/main/client/events/router.cljs) In subs/router.cljs , the current matched route is being watched. (https://github.com/Linerre/chaplin/blob/dev/src/main/client/subs/search.cljs) ===================== client search =============================== The search bar is defined in components/search.cljs (https://github.com/Linerre/chaplin/blob/dev/src/main/client/components/search.cljs). The search events (keypress) is registered in events/search.cljs (https://github.com/Linerre/chaplin/blob/dev/src/main/client/events/search.cljs) In subs/search.cljs I tried to subscribe to the current user input in the search text box, so that the input value may be passed around. (https://github.com/Linerre/chaplin/blob/dev/src/main/client/subs/search.cljs) ====================== server side ============================== There isn't much happening on the server side, at the moment: 1. a server: https://github.com/Linerre/chaplin/blob/dev/src/main/server/server.clj 2. a server side router to send the index.html , plus all other resources (https://github.com/Linerre/chaplin/blob/dev/src/main/server/routes.clj) 3. handlers to handle different requests: https://github.com/Linerre/chaplin/blob/dev/src/main/server/handlers.clj Would the repo plus the brief explanation work for you as to taking a look into my problem? Again, thank you for your help 😃

alpox13:05:54

@U02UW9X8DUG Multiple things: • The sub in client.subs.search refers to :user-query but the value in the db is :user-input (So you currently get nil out) • The router definition for /search uses the key :params which should be parameters • You have to pass the query parameters when dispatching to the new route in client.events.search: :dispatch [:router/eh-push-state :search nil {:q user-q}]

👍 1
👏 1
alpox13:05:48

Btw. now your server-route returns some html assembled on the server. In an SPA you likely don't want that. You'd return some plain-data sent as EDN/Transit/JSON that you then save in the re-frame database and show to the user.

erre lin14:05:34

@U6JS7B99S Wow. Nice catch! • Yes, thank you so much for pointing out this typo. I should have been more cautious. • Thanks for this suggestion too! Different libraries seem to use different terms such as params ,`parameters` , I got confused sometimes .... • I learned this! Thanks. I knew I should pass {:q user-q} to the event vector, but I didn't understand how to handle the path-params , as mentioned in Reitit's doc: (push-state name path-params query-params) (https://cljdoc.org/d/metosin/reitit/0.5.18/api/reitit.frontend.easy#push-state). I have corrected them all and now the page jumps as expected and url contains query parameters. Thank you so much! This has been confusing me for a couple of days!😅 I'd like to confirm about your last suggestion, the data for SPA. Did you mean that I should: 1. Create all the views using cljs (reagent, reitit, re-frame stuff) on the client side 2. When it comes to fetching data from the server, just let the server responds with data as EDN/Transit/JSON. You were mentioning the /search route on the server right? Did I also make a mistake by sending the index.html in the root route / ? 3. Again, the client side updates the view according the data received from server? This is the first time I tried clojure/script and SPA, so a bit overwhelming. Your help is really really appreciated!👏👍

alpox14:05:44

1. Yes 2. Yes. The server responds to requests with EDN or Transit or JSON (Depending on your preference.) The index.html should be delivered by the root route otherwise your browser wouldn't see anything. This is correct. 3. Yes. The data is most likely then stored in the re-frame database and you just show the view / a new view according to the data you got.

👍 1
erre lin15:05:56

Thank you very much!👏🤓 Your advice and tips helped me quite a lot! You made my day.

🥳 1
alpox15:05:29

Happy to help