I'm not sure whether this is an issue with my use of cl-who (specifically with-html-output-to-string and with-html-output) or an issue with my understanding of Common Lisp (as this is my first project using Lisp).
I created a function to create form fields:
(defun form-field (type name label)
(cl-who:with-html-output (*standard-output* nil)
(:div :class "field"
(:label :for name label)
(:input :type type :name name))))
When using this function, ie: (form-field "text" "username" "Username") the parameter label seems to be ignored... the HTML output is:
<div class="field"><label for="username"></label>
<input type="text" name="username"/></div>
instead of the expected output:
<div class="field"><label for="username">Username</label>
<input type="text" name="username"/></div>
If I modify the function and add a print statement:
(defun form-field (type name label)
(cl-who:with-html-output (*standard-output* nil)
(print label)
(:div :class "field"
(:label :for name label)
(:input :type type :name name))))
The "Username" string is successfully output (but still ignored in the HTML)... any ideas what might cause this?
Keep in mind, I'm calling this function within a cl-who:with-html-output-to-string for use with hunchentoot.
This situation is described in the CL-WHO evaluation rules under "A form which is neither a string nor a keyword..." (:label :for name label) falls under that rule, and it's just evaluated, but it doesn't output anything so it has no effect. One easy fix: use (str label) instead.
Related
I have a :get route with easy-routes that, when hit, runs one function (quick-test). The function quick-test returns two values. Both are strings.
(easy-routes:defroute test-file ("/test-file" :method :get) (&get filename)
(quick-test filename))
However, when the form is sent to "/test-file", only one line is visible in the dom.
I then tried using multiple value bind. But the result is the same.
(easy-routes:defroute test-file ("/test-file" :method :get) (&get filename)
(multiple-value-bind (a b) (quick-test filename)
(values (format nil "~a" a)
(format nil "~a" b))))
My goal is to be able to run quick test and see all returned values on the dom.
Is there a way to do that?
For completeness, here is the form:
<form id="form" action="/test-file" method="get">
<input type="text" name="filename" placeholder="type the filename"/>
<input type="submit" value="Send"/>
</form>
Values can return more values, but if your function expects some expression, it takes only the first returned value and discards the rest. For example, floor returns two values, but + takes only the first:
> (floor 5 2)
2
1
> (+ 2 (floor 5 2))
4
This question can also help you: values function in Common Lisp
Defroute probably does the same and takes only the first value.
You can fix your route for example like this:
(easy-routes:defroute test-file ("/test-file" :method :get) (&get filename)
(multiple-value-bind (a b) (quick-test filename)
(format nil "~a, ~a" a b)))
The usual way to generate HTML using CL-WHO is by using the macros with-html-output and with-html-output-to-string. This uses special syntax. For example:
(let ((id "greeting")
(message "Hello!"))
(cl-who:with-html-output-to-string (*standard-output*)
((:p :id id) message)))
Is it possible to write the data ((:p :id id) message) as a list instead of using the macro syntax shown above? For example, I would like to define the HTML as a list like this:
(let* ((id "greeting")
(message "Hello!")
(the-html `((:p :id ,id) ,message)))
;; What should I do here to generate HTML using CL-WHO?
)
Can CL-WHO take a normal Lisp list and produce HTML from the list?
You want to insert code into an expression.
Actually you would need eval:
(let* ((id "greeting")
(message "Hello!")
(the-html `((:p :id ,id) ,message)))
(eval `(cl-who:with-html-output-to-string (*standard-output*)
,the-html)))
But this is not good to use eval.
But a macro contains an implicite eval.
I would define a macro for this and call the macro:
(defun html (&body body)
`(cl-who:with-html-output-to-string (*standard-output*)
,#body))
;; but still one doesn't get rid of the `eval` ...
;; one has to call:
(let* ((id "greeting")
(message "Hello!")
(the-html `((:p :id ,id) ,message)))
(eval `(html ,the-html)))
I want to be able to handle a form which uses a dynamic amount of form fields
e.g.
(form :action "/theaction"
:method "post"
(input :type "text" :name "firstinput") (:br)
(dotimes (i n)
(:input :type "text" :name (FORMAT nil "INPUT~a" i)))
(:input :type "submit" :value "submit" :name "submit"))
how can I define the handler as the &rest is not accepted and would not allow me to access the variable names which I obviously need for further processing.
(define-easy-handler (theaction :uri "/theaction") (&special-rest all-params)
(log-message* :INFO "~a" all-params))
-> "(("firstinput" . "value") ("INPUT0" . "value") ("INPUT1" . "value") ...)"
A possibility would be to pre-define all variables up to e.g. 100, but this would seem rather cumbersome and unintuitive.
The lambda-list of define-easy-handler is just a shortcut for using lower-level calls. You can get more extensive access to parameters by using functions like like GET-PARAMETER and POST-PARAMETER. You can get an alist of all parameters by using get-parameters* (or post-parameters*) in the body of the handler.
Question: How to force a s-exp to be evaluated before passing to a function expecting parameter of string type or else.?
This code is fine (no error):
(setf (slot-value (ole sheet :range "A1:B1") 'value)
`(("123" "456"))))
However, when passing a s-exp such as (first line) instead of "123":
(setf line '("123" "456"))
(setf (slot-value (ole sheet :range "A1:B1") 'value)
`(((first line) (second line)))))
There is error message:
The value FIRST is not of the expected type (OR
STRING
FIXNUM
SINGLE-FLOAT
DOUBLE-FLOAT).
[Condition of type TYPE-ERROR]
Restarts:
0: [RETRY] Retry SLIME REPL evaluation request.
1: [*ABORT] Return to SLIME's top level.
2: [ABORT-BREAK] Reset this thread
3: [ABORT] Kill this thread
I know what the problem is. But is there way get rid of this restriction? Because we would need to put the line of code inside a loop, so can't fix the parameter value to such as "123"...
I try using macro:
(defmacro set-line (par1 par2)
`(setf (slot-value (ole sheet :range "A1:B1") 'value)
`((,par1 ,par2))))
However, it doesn't work. Still the same error message saying the wrong type (first line)...
(set-line (first line) (second line))
Also I don't know how to escape the backquote ` if it appears inside macro body. I have tried put a \ in front of backquote inside the macro body, but then the \ would also appear upon macro expand.
`(setf xxx ` <--- another backquote
`(setf xxx \` <--- this can't escape backquote inside macro body
Thanks.
Background: when playing with Win32 OLE Excel example in:
https://github.com/quek/cl-win32ole/blob/master/example/excel.lisp
You are missing the basics of quotation. Your expression
? `(("123" "456")) ; quasiquote
(("123" "456"))
is a list containing a list and can also be created using the function LIST as
? (list (list "123" "456")) ; list
(("123" "456"))
To use expressions, you can either use unquote (note the preceding comma)
? `((,(first line) ,(second line))) ; quasiquote with unquote
(("123" "456"))
or, better
? `(,line)
(("123" "456"))
or use list :
? (list line)
(("123" "456"))
Hunchentoot/cl-who Page Composition
I'm trying to put together a few pages in hunchentoot as an experiment, and I'm running into an unexpected wall. As an example, I have the following template macro.
(defmacro page-template ((&key title) &body body)
`(with-html-output-to-string
(*standard-output* nil :prologue t :indent t)
(:html :xmlns "http://www.w3.org/1999/xhtml" :xml\:lang "en" :lang "en"
(:head (:meta :http-equiv "Content-Type" :content "text/html;charset=utf-8")
(:title ,(format nil "~#[~A - ~]Test Site" title)))
(:body ,#body))))
Now when I have a pure text page, or one filled with html literals like
(define-easy-handler (test-page :uri "/") ()
(page-template (:title "Splash Page") (:p "Testing testing")))
everything is a-ok. The page outputs properly and I can see te efforts of my code instantly. However, when I have a page which is made up of redundant elements, it's not as simple. For example, lets say I have a page on which for whatever reason I want to display three RSS newsfeeds. This is a sufficiently complex component that I want to abstract it out, so to my minnd, I should be able to do something like
(define-easy-handler (test-feed :uri "/feeds") ()
(page-template (:title "Splash Page")
(publish-newsfeed "http://nf-one.html")
(publish-newsfeed "http://nf-two.html")
(publish-newsfeed "http://nf-three.html")))
(defmacro publish-newsfeed (url &optional (item-limit 5))
(flet ((get-text (s-tree node-path) (car (last (xmls-tools:find-subtree s-tree node-path)))))
(let ((rss-feed (xmls:parse (drakma:http-request url))))
`(:div :class "rss-feed"
(:a :href ,(get-text rss-feed '("channel" "link")) :target "_top" (:h1 ,(get-text rss-feed '("channel" "title"))))
(:ul ,#(mapcar #'(lambda (item)
`(:li (:a :href ,(get-text item '("link")) :target "_top" (:h2 ,(get-text item '("title"))))
(:p :class "date" ,(get-text item '("pubDate")))
(:p ,(get-text item '("description")))))
(let ((items (xmls-tools:find-all-children (xmls-tools:find-subtree rss-feed '("channel")) "item")))
(if (> (length items) item-limit) (subseq items 0 item-limit) items))))))))
But the result of the above is a "Server Error" page. I'm not quire sure why; page-template is a macro so the calls to publish-newsfeed shouldn't be expanded until they're in the context of with-html-output-to-string. Can anyone tell me what I'm doing wrong?
Also, on closer inspection of the various Hunchentoot/cl-who tutorials, none of them seems to do this kind of page composition. Can anyone with some Hunchentoot experience tell me what the correct/canonical way of decomposing a page into components is?
EDIT:
Correct response by Ramarren below; the with-html-output macros work under different evaluation rules. The version of publish-newsfeed that would actually work in this situation is actually
(defun publish-newsfeed (url &optional (item-limit 5))
(flet ((get-text (s-tree node-path) (car (last (xmls-tools:find-subtree s-tree node-path)))))
(let* ((rss-feed (xmls:parse (drakma:http-request url)))
(items (xmls-tools:find-all-children (xmls-tools:find-subtree rss-feed '("channel")) "item"))
(ltd-items (if (> (length items) item-limit) (subseq items 0 item-limit) items)))
(with-html-output
(*standard-output* nil :indent t)
(:div :class "rss-feed"
(:a :href (get-text rss-feed '("channel" "link")) :target "_top" (:h1 (str (get-text rss-feed '("channel" "title")))))
(:ul (dolist (item ltd-items)
(htm (:li (:h2 (:a :href (get-text item '("link")) :target "_top" (str (get-text item '("title")))))
(:p :class "date" (str (get-text item '("pubDate"))))
(:p (str (get-text item '("description")))))))))))))
Note the removal of mapcar for dolist (I'm a Schemer, don't give me too much of a hard time about liking lambdas, but they weren't the right choice here), and the use of htm to escape blocks of html s-exps (h-exps?) that wouldn't otherwise be in context for with-html-output. Finally, I had to wrap text but NOT :href properties in (str ) to get them to expand dynamically.
The macro with-html-output-to-string expands its body using special evaluation rules. In particular, any not recognized forms are left as is, which means macros are not expanded before the html generating code is generated, which means by the time your publish-newsfeed macro is expanded by the standard compiler it is no longer in context ofwith-html-output-to-string. This is clear when expanding the macro manually, especially using slime macroexpansion feature.
To make it work you should make publish-newsfeed a function and use with-html-output inside it with the same stream (either assume `standard-output everywhere or pass the stream explicitly).