common-lisphunchentootcl-who

CL-WHO not displaying any HTML after conditional


Sorry for the probably stupid question, but I'm new to Common Lisp (I migrated from Racket) and so far I'm having an absolute blast building a web app with Hunchentoot, cl-who, and a few other miscellaneous packages, but I've recently run into an issue I can't solve: I'm trying to loop through a hash and display its values (which are structs) if the hash is not empty. If it is, I want to display an "this is empty" message. However, cl-who is only outputting the HTML that comes after the call. Here is my code:

(tbnl:define-easy-handler (index :uri "/") () 
  "Landing page." 
  (setf (tbnl:content-type*) "text/html") 
  (with-html-ouptut (*standard-output*) 
    (:html 
     (:head (:title "Chattr: Neo-BBS")) 
     (:body 
      (:div :id "header" 
            :style "text-align:center;" 
            (:h1 "Welcome to Chattr") 
            (:h3 "Please select the sub-board you would like to chat 
            on.")) 
      (if (> (hash-table-size *boards*) 0) 
          (dolist (board (hash-table-values *boards*)) 
            (htm 
             (:span (html-display board)) (:br)))
          (htm 
           (:b "Sorry, there aren't any boards. Why not create 
                one?") (:br))) 
      (:a :href "/new-board" "Create New Board"))))) 

So in this case, "Create New Board" is showing up, but neither the bold text nor the header are. However, if I move the header after the if, it shows up.

I've struggled with this for upwards of six hours, does anyone have any hints for me? Thanks!


Solution

  • Return a string

    From http://weitz.de/hunchentoot:

    Request handlers do their work by modifying the reply object if necessary and by eventually returning the response body in the form of a string or a binary sequence.

    The handler should return a string and here the only thing that is emitted is the last value being printed (because write returns its argument)1. That's why you only see the last element. You need to use with-html-output-to-string; generally you add a variable out in the binding, but in most cases that variable is not used (at least not here). You can use *standard-output* but you have to be careful. Better rebind *standard-output* in the smallest scope where it is useful. It is possible to use the stream associated with the current response, if you prefer not building a string first .

    As an alternative, they can also call SEND-HEADERS and write directly to a stream.

    Use hash-table-count

    You are using hash-table-size instead of hash-table-count, which means that if you have an empty table, the size (i.e. the capacity) is positive but you don't show anything because the dolist doesn't do anything. By the way, you can also iterate over hash-tables with maphash or loop:

    (maphash (lambda (key board)
               (declare (ignore key))
               (htm ...)) 
             *boards*)
    
    (loop
      for board being the hash-values of *boards*
      do (htm ...))
    

    1. You don't see any write statement, but they are emitted by the macro. You can macroexpand forms yourself, or use Slime to do it quickly.