I have a series of checkbox inputs and I'd like to uncheck all the boxes when the user clicks a button. Checking the inputs doesn't seem to set an attr, so I'm unsure if/how to reset the "checked" prop. I'd like to do this in pure CLJS, no extra DOM manipulation libraries, please. If there is a smarter way to do this within the Reagent framework, that would also be an acceptable/helpful answer.
(defn clear-order []
(map #(set! (.-checked %) false) (.getElementsByTagName js/document "input")))
This gets all my inputs, and maybe does what it's supposed to, but doesn't actually uncheck my inputs.
Like someone said in a comment, you need to use something eager and not map, which is lazy. run! is like an eager map. Also HTMLCollections aren't seqable, so you can use goog.array/toArray to get something that is.
Putting that together:
(ns foo.core
(:require
[goog.array :as garray]))
(defn clear-order []
(run! #(set! (.-checked %) false)
(garray/toArray (.getElementsByTagName js/document "input"))))
Try this:
(doseq [something (goog.array.toArray (.getElementsByTagName js/document "input"))]
(js/console.log something))
Related
I'm new to Clojure, and hickory, and the idea of zippers.
What I want to do is, I want to use selectors to go to one location in an HTML document. And then, I want to be able to navigate from that location, up to a parent element, and then get 2nd sibling from that point.
Is this possible to do with hickory? From what I understand, it seems as though I only have the option of using selectors, or navigating the HTML in a zipper structure, but I can't figure out how to do both, or if that's even possible.
You could do something like this:
(:require
[hickory.select :as s]
[hickory.convert :as convert]
[clojure.zip :as z]
...
(let [html (convert/hiccup-to-hickory (list [:div
[:div {:class "didya"} "nevertheless"]]
[:div "possible"]
[:div "geometric"]))]
(-> (s/select-locs (s/class "didya") html)
(first)
(z/up)
(z/right)
(z/right)
(z/node)))
The forest library can do this easily. There is
a video from the last Clojure Conj
many examples also
docs are ongoing.
I have this:
(defn page1 []
(layout/render
"index.html"
({:articles (db/get-articles)})))
The function
db/get-articles
returns a list of objects which have the key body. I need to parse the body of the articles and replace, if exists, a substring "aaa12aaa" with "bbb13bbb", "aaa22aaa" with "bbb23bbb" and so on in the bodies. How can I do that so it also won't consume plenty of RAM? Is using regex effective?
UPDATE:
The pattern I need to replace is : "[something="X" something else/]". where X is a number and it's unknown. I need to change X.
There can be many such patterns to replace or none.
I would just use Java's String.replace or String.replaceAll or clojure.string functions: replace/replace-first.
I wouldn't waste time for premature optimisations and first measure if the simple solution works. I am not sure how big the article contents are but I guess it shouldn't be an issue.
If it turns out you really need to optimise then maybe you should switch to streaming the contents of your articles from your data storage and either implement replace manually or using a library like streamflyer to perform modifications on the fly before sending the article contents to the HTTP response stream.
Something like this should be plenty fast:
(mapv
(fn [{:keys [body] :as m}]
(assoc m :body
(reduce-kv
(fn [body re repl]
(string/replace body re repl))
body
{"aaa12aaa" "bbb13bbb",
"aaa22aaa" "bbb23bbb"})))
[{:body "xy aaa12aaa fo aaa22aaa"}])
If you can guarantee that the string only occurs once you can replace replace by replace-first.
Regex works great in clojure:
(ns clj.core
(:use tupelo.core)
(:require
[clojure.string :as str]
)
(spyx (str/replace "xyz-aaa12aaa-def" #"aaa12aaa" "bbb13bbb"))
;=> (str/replace "xyz-aaa12aaa-def" #"aaa12aaa" "bbb13bbb") => "xyz-bbb13bbb-def"
In a small app I'm building that uses Reagent and Re-frame I'm using multi-methods to dispatch which page should be shown based on a value in the app state:
(defmulti pages :name)
(defn main-panel []
(let [current-route (re-frame/subscribe [:current-route])]
(fn []
;...
(pages #current-route))))
and then I have methods such as:
(defmethod layout/pages :register [_] [register-page])
where the register-page function would generate the actual view:
(defn register-page []
(let [registration-form (re-frame/subscribe [:registration-form])]
(fn []
[:div
[:h1 "Register"]
;...
])))
I tried changing my app so that the methods generated the pages directly, as in:
(defmethod layout/pages :register [_]
(let [registration-form (re-frame/subscribe [:registration-form])]
(fn []
[:div
[:h1 "Register"]
;...
])))
and that caused no page to ever be rendered. In my main panel I changed the call to pages to square brackets so that Reagent would have visibility into it:
(defn main-panel []
(let [current-route (re-frame/subscribe [:current-route])]
(fn []
;...
[pages #current-route])))
and that caused the first visited page to work, but after that, clicking on links (which causes current-route to change) has no effect.
All the namespaces defining the individual methods are required in the file that is loaded first, that contains the init function, and the fact that I can pick any single page and have it displayed proves the code is loading (then, switching to another page doesn't work):
https://github.com/carouselapps/ninjatools/blob/master/src/cljs/ninjatools/core.cljs#L8-L12
In an effort to debug what's going on, I defined two routes, :about and :about2, one as a function and one as a method:
(defn about-page []
(fn []
[:div "This is the About Page."]))
(defmethod layout/pages :about [_]
[about-page])
(defmethod layout/pages :about2 [_]
(fn []
[:div "This is the About 2 Page."]))
and made the layout print the result of calling pages (had to use the explicit call instead of the square brackets of course). The wrapped function, the one that works, returns:
[#object[ninjatools$pages$about_page "function ninjatools$pages$about_page(){
return (function (){
return new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"div","div",1057191632),"This is the About Page."], null);
});
}"]]
while the method returns:
#object[Function "function (){
return new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"div","div",1057191632),"This is the About 2 Page."], null);
}"]
If I change the method to be:
(defmethod layout/pages :about2 [_]
[(fn []
[:div "This is the About 2 Page."])])
that is, returning the function in a vector, then, it starts to work. And if I make the reverse change to the wrapped function, it starts to fail in the same manner as the method:
(defn about-page []
(fn []
[:div "This is the About Page."]))
(defmethod layout/pages :about [_]
about-page)
Makes a bit of sense as Reagent's syntax is [function] but it was supposed to call the function automatically.
I also started outputting #current-route to the browser, as in:
[:main.container
[alerts/view]
[pages #current-route]
[:div (pr-str #current-route)]]
and I verified #current-route is being modified correctly and the output updated, just not [pages #current-route].
The full source code for my app can be found here: https://github.com/carouselapps/ninjatools/tree/multi-methods
Update: corrected the arity of the methods following Michał Marczyk's answer.
So, a component like this: [pages #some-ratom]
will rerender when pages changes or #some-ratom changes.
From reagent's point of view, pages hasn't changed since last time, it is still the same multi-method it was before. But #some-ratom might change, so that could trigger a rerender.
But when this rerender happens it will be done using a cached version of pages. After all, it does not appear to reagent that pages has changed. It is still the same multimethod it was before.
The cached version of pages will, of course, be the first version of pages which was rendered - the first version of the mutlimethod and not the new version we expect to see used.
Reagent does this caching because it must handle Form-2 functions. It has to keep the returned render function.
Bottom line: because of the caching, multimethods won't work very well, unless you find a way to completely blow up the component and start again, which is what the currently-top-voted approach does:
^{:key #current-route} [pages #current-route]
Of course, blowing up the component and starting again might have its own unwelcome implications (depending on what local state is held in that component).
Vaguely Related Background:
https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#appendix-a---lifting-the-lid-slightly
https://github.com/Day8/re-frame/wiki/When-do-components-update%3F
I don't have all the details, but apparently, when I was rendering pages like this:
[:main.container
[alerts/view]
[pages #current-route]]
Reagent was failing to notice that pages depended on the value of #current-route. The Chrome React plugin helped me figure it out. I tried using a ratom instead of a subscription and that seemed to work fine. Thankfully, telling Reagent/React the key to an element is easy enough:
[:main.container
[alerts/view]
^{:key #current-route} [pages #current-route]]
That works just fine.
The first problem that jumps out at me is that your methods take no arguments:
(defmethod layout/pages :register [] [register-page])
^ arglist
Here you have an empty arglist, but presumably you'll be calling this multimethod with one or two arguments (since its dispatch function is a keyword and keywords can be called with one or two arguments).
If you want to call this multimethod with a single argument and just ignore it inside the body of the :register method, change the above to
(defmethod layout/pages :register [_] [register-page])
^ argument to be ignored
Also, I expect you'll probably want to call pages yourself like you previously did (that is, revert the change to square brackets that you mentioned in the question).
This may or may not fix the app – there may be other problems – but it should get you started. (The multimethod will definitely not work with those empty arglists if you pass in any arguments.)
How about if you instead have a wrapper pages-component function which is a regular function that can be cached by reagent. It would look like this:
(defn pages-component [state]
(layout/pages #state))
The docstrings for some Clojure functions (e.g. defrecord) are quite long. When running Clojure at in a terminal window, I'd like to be able to in effect send doc's output through a pager such as more (or less). If someone has written a pager function in Clojure, then I think I could use it along with something like:
(with-out-str (doc defrecord))
Or if there is a standard Java class that implements a pager, I can figure out how to send the output to that.
Alternatively, how can I send the output of doc to a shell command? This doesn't do the job:
(clojure.java.shell/sh "more" :in (with-out-str (doc defrecord))))
[This topic is difficult to search: "more", "less", and "doc" are obviously very common terms, and things like "java pager" bring up pages discussing ways to break text up into pages for formatting documents.]
You can use jline for this. If you call setPaginationEnabled, with true, and use the printColumns method, on your jline ConsoleReader, it will page.
But, if you are trying to do this at a standard Leiningen REPL, things get more complicated. The current version of Leiningen v2 uses REPL-y, which uses jline internally, but doesn't use printColumns, so the jline pagination is ignored.
You can, however, get the current Leiningen REPL height through REPL-y's ConsoleReader, in reply.reader.simple-jline/jline-state, and use it to partition the doc string.
(defmacro doc2 [x]
`(let [h# (-> #reply.reader.simple-jline/jline-state :reader (.. getTerminal getHeight) (- 4))
[s1# s2#] (split-at h# (-> ~x clojure.repl/doc with-out-str clojure.string/split-lines))]
(doseq [x# s1#] (println x#))
(doseq [i# (partition-all h# s2#)]
(println "\n<more>")
(read-line)
(doseq [x# i#] (println x#)))))
You would want to put this macro in your profiles.clj under the :repl profile.
{:user {:plugins [...]}
:repl {:repl-options {:init (defmacro doc2 [x] ...)}}}
This would put the doc2 macro in the user namespace when you load the repl.
echo "(doc defrecord)" |clj|more
[this may seem like my problem is with Compojure, but it isn't - it's with Clojure]
I've been pulling my hair out on this seemingly simple issue - but am getting nowhere.
I am playing with Compojure (a light web framework for Clojure) and I would just like to generate a web page showing showing my list of todos that are in a PostgreSQL database.
The code snippets are below (left out the database connection, query, etc - but that part isn't needed because specific issue is that the resulting HTML shows nothing between the <body> and </body> tags).
As a test, I tried hard-coding the string in the call to main-layout, like this:
(html (main-layout "Aki's Todos" "Haircut<br>Study Clojure<br>Answer a question on Stackoverfolw")) - and it works fine.
So the real issue is that I do not believe I know how to build up a string in Clojure. Not the idiomatic way, and not by calling out to Java's StringBuilder either - as I have attempted to do in the code below.
A virtual beer, and a big upvote to whoever can solve it! Many thanks!
=============================================================
;The master template (a very simple POC for now, but can expand on it later)
(defn main-layout
"This is one of the html layouts for the pages assets - just like a master page"
[title body]
(html
[:html
[:head
[:title title]
(include-js "todos.js")
(include-css "todos.css")]
[:body body]]))
(defn show-all-todos
"This function will generate the todos HTML table and call the layout function"
[]
(let [rs (select-all-todos)
sbHTML (new StringBuilder)]
(for [rec rs]
(.append sbHTML (str rec "<br><br>")))
(html (main-layout "Aki's Todos" (.toString sbHTML)))))
=============================================================
Again, the result is a web page but with nothing between the body tags. If I replace the code in the for loop with println statements, and direct the code to the repl - forgetting about the web page stuff (ie. the call to main-layout), the resultset gets printed - BUT - the issue is with building up the string.
Thanks again.
~Aki
for is lazy, and in your function it's never being evaluated. Change for to doseq.
user> (let [rs ["foo" "bar"]
sbHTML (new StringBuilder)]
(for [rec rs]
(.append sbHTML (str rec "<br><br>")))
(.toString sbHTML))
""
user> (let [rs ["foo" "bar"]
sbHTML (new StringBuilder)]
(doseq [rec rs]
(.append sbHTML (str rec "<br><br>")))
(.toString sbHTML))
"foo<br><br>bar<br><br>"
You could also use reduce and interpose, or clojure.string/join from clojure.string, or probably some other options.
user> (let [rs ["foo" "bar"]]
(reduce str (interpose "<br><br>" rs)))
"foo<br><br>bar"
user> (require 'clojure.string)
nil
user> (let [rs ["foo" "bar"]]
(clojure.string/join "<br><br>" rs))
"foo<br><br>bar"
You would like to use the re-gsub like this:
(require 'clojure.contrib.str-utils) ;;put in head for enabling us to use re-gsub later on
(clojure.contrib.str-utils/re-gsub #"\newline" "<br><br>" your-string-with-todos-separated-with-newlines)
This last line will result in the string you like. The require-part is, as you maybe already know, there to enable the compiler to reach the powerful clojure.contrib.str-utils library without importing it to your current namespace (which could potentially lead to unnescessary collisions when the program grows).
re- is for reg-exp, and lets you define a reg-exp of the form #"regexp", which to replace all instances that is hit by the regexp with the argument afterwards, applied to the third argument. The \newline is in this case clojures way of expressing newlines in regexps as well as strings and the character we are looking for.
What I think you really wanted to do is to make a nifty ordered or unordered list in html-format. These can be done with [hiccup-page-helpers][2] (if you don't have them you probably have a compojure from the time before it got splited up in compojure, hiccup and more, since you use the html-function).
If you want to use hiccup-page-helpers, use the command re-split from the clojure.contrib.str-utils mentioned above in this fashion:
(use 'hiccup.page-helpers) ;;watch out for namespace collisions, since all the functions in hiccup.page-helpers got into your current namespace.
(unordered-list (clojure.contrib.str-utils/re-split #"\newline" your-string-with-todos-separated-with-newlines))
which should render a neat
<ul>
<li>todo-item1</li>
<li>todo-item2</li>
</ul>
(and yes, there is an ordered-list command that works the same way!)
In the last line of clojure code above, all you todos gets into a (list "todo1" "todo2") which is immediately consumed by hiccup.page-helpers unordered-list function and is there converted to an html-ized list.
Good luck with compojure and friends!