Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble with building up a string in Clojure

Tags:

clojure

[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

like image 515
Aki Iskandar Avatar asked Feb 26 '23 10:02

Aki Iskandar


2 Answers

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"
like image 162
Brian Carper Avatar answered Mar 07 '23 17:03

Brian Carper


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!

like image 25
claj Avatar answered Mar 07 '23 15:03

claj