lispcommon-lisphunchentootcl-whoparenscript

Generating inline javascript with cl-who, parenscript and hunchentoot


I'm trying to generate inline javascript, but I have to put the parenscript code inside (:script) and (str) tags using cl-who. ps, ps*, ps-inline and ps-inline* don't seem to make much difference to the generated js.

Is the usual way to write a macro to avoid code duplication, or is there a better way?

Here's my program:

(in-package #:ps-test)

(defmacro standard-page ((&key title) &body body)
  `(with-html-output-to-string (*standard-output* nil :prologue t :indent t)
     (:html 
      :lang "en"
      (:head 
       (:meta :http-equiv "Content-Type" 
          :content    "text/html;charset=utf-8")
       (:title ,title)
           (:link :type "text/css" 
              :rel "stylesheet"
              :href "/style.css"))
      (:body 
       ,@body))))     

(defun main ()
  (with-html-output (*standard-output* nil :indent t :prologue nil)
    (standard-page (:title "Parenscript test")
      (:div (str "Hello worldzors"))
        (:script :type "text/javascript"
             (str (ps (alert "Hello world as well")))))))

(define-easy-handler (docroot :uri "/") ()
  (main))

(defun start-ps-test ()
  (setf (html-mode) :html5)
  (setf *js-string-delimiter* #\")
  (start (make-instance 'hunchentoot:easy-acceptor :port 8080)))

(defun stop-ps-test ()
  (stop *server*))

(defvar *server* (start-ps-test))

Solution

  • Macros are fine in this use case. The trick is that macros are expanded in a specific order. Say you define a js macro: when macroexpansion encounters with-html-output, the inner call to your macros (js (alert "Ho Ho Ho")) looks like a function call, and is left as-is in the generated code. If your js macro then expands into (:script ...), then the system will complain that :script is an unknown function (assuming you didn't actually name a function like that). You should emit an enclosing (who:htm ...) expression to interpret the code using CL-WHO's code walker.

    (defmacro js (code)
      `(who:htm
         (:script :type "text/javascript" (who:str (ps:ps ,code)))))
    

    This only works in the context of an enclosing with-html-output.

    For inline Javascript, you don't want to have a <script> tag around it, and you can generally simply use ps-inline:

    (who:with-html-output (*standard-output*)
      (:a :href (ps:ps-inline (void 0))
        "A link where the usual HREF behavior is canceled."))
    
    ;; prints:
    ;; 
    ;; <a href='javascript:void(0)'>A link where the usual HREF behavior is canceled.</a>
    

    But feel free to use a macro if you often do the same thing:

    (defmacro link (&body body)
      `(who:htm (:a :href #.(ps:ps-inline (void 0)) ,@body)))
    
    (who:with-html-output (*standard-output*) (link "Link"))
    
    ;; prints:
    ;;
    ;; <a href='javascript:void(0)'>Link</a>