css style id and class for builtin elements in hmacro

This commit is contained in:
Bo Yao 2018-07-28 11:07:49 -04:00
parent 4ef85ff0e0
commit 1b0fb3fb0b
6 changed files with 74 additions and 16 deletions

View file

@ -146,10 +146,22 @@ Then just wrap `h` for all html generation part. In the same examples above, it
(defparameter *dog2* (dog :id "dog2" :size 20 "some children"))
```
From version 0.2 (available in Aug 2018 Quicklisp), flute supports css style id and class attribute for builtin elements. For example `div#id-name.class1.class2`, So you can also write:
```lisp
(h (div#a.b "..."))
;; Provide additional class and attributes
(h (div#a.b :class "c" :onclick "fun()"))
```
That's all you need to know to define elements and generate html. Please reference the [API Reference](#api-reference) Section for detailed API.
# Change Logs
## 2018/07/28 Version 0.2-dev
- Support `element#id.class1.class2` in `H` macro for builtin elements;
- Jon Atack fix an error example in README.
## 2018/07/11 Version 0.1
- Current features, APIs and Tests.
# Motivation
Currently there're a few HTML generation library in Common Lisp, like [CL-WHO](https://edicl.github.io/cl-who/), [CL-MARKUP](https://github.com/arielnetworks/cl-markup) and [Spinneret](https://github.com/ruricolist/spinneret). They both have good features for generating standard HTML, but not very good at user element (components) that currently widely used in frontend: you need to define all of them as macros and to define components on top of these components, you'll have to make these components more complex macros to composite them. [Spinneret](https://github.com/ruricolist/spinneret) has a `deftag` feature, but `deftag` is still expand to a `defmacro`.

View file

@ -1,7 +1,7 @@
(defsystem flute
:author "Bo Yao <icerove@gmail.com>"
:license "MIT"
:version "0.1"
:version "0.2-dev"
:components ((:module "src"
:serial t
:components

View file

@ -152,12 +152,37 @@ When given :ASCII and :ATTR, it's possible to insert html text as a children, e.
(print-object (user-element-expand-to element) stream)
(call-next-method)))
(defun html-element-p (x)
(and (symbolp x) (not (keywordp x)) (gethash (collect-name-as-keyword x) *builtin-elements*)))
(defmacro h (&body body)
`(progn
,@(tree-leaves
body
(and (symbolp x) (not (keywordp x)) (gethash (collect-name-as-keyword x) *builtin-elements*))
(find-symbol (string (collect-name-as-keyword x)) :flute))))
(html-element-p x)
(multiple-value-bind (name id class) (collect-id-and-class x)
(if (or id class)
(make-!expanded :list (list (find-symbol (string-upcase name) :flute)
(coerce (append (when id (list :id id))
(when class (list :class class)))
'vector)))
(find-symbol (string-upcase name) :flute))))))
;;; Experimental
;; (when (find :illusion *features*)
;; (illusion:set-paren-reader
;; :flute
;; #'html-element-p
;; (lambda (stream indicator)
;; (multiple-value-bind (name id class) (collect-id-and-class indicator)
;; (if (or id class)
;; (list* (find-symbol (string-upcase name) :flute)
;; (coerce (append (when id (list :id))
;; (when class (list :class class)))
;; 'vector)
;; (illusion:cl-read-list stream))
;; (cons (find-symbol (string-upcase name) :flute)
;; (illusion:cl-read-list stream)))))))
(defmethod element-string ((element element))
(with-output-to-string (s)

View file

@ -12,7 +12,8 @@
:mkstr
:flatten)
(:import-from :alexandria
:make-keyword)
:make-keyword
:if-let)
(:export
;;; builtin HTML elements
;;; all html5 elements, e.g. div, nav, media, export in code except

View file

@ -10,12 +10,16 @@
(cdr kv)))
alist))
(defstruct !expanded list)
(defun tree-leaves%% (tree test result)
(if tree
(if (listp tree)
(cons
(tree-leaves%% (car tree) test result)
(tree-leaves%% (cdr tree) test result))
(let ((car-result (tree-leaves%% (car tree) test result))
(cdr-result (tree-leaves%% (cdr tree) test result)))
(if (!expanded-p car-result)
(append (!expanded-list car-result) cdr-result)
(cons car-result cdr-result)))
(if (funcall test tree)
(funcall result tree)
tree))))
@ -128,13 +132,13 @@
(do ((current-and-remains (collect-until-dot-or-sharp (string-downcase (string symbol)))
(collect-until-dot-or-sharp (cdr current-and-remains))))
((string= "" (car current-and-remains))
(values name id (format nil "~{~a~^ ~}" (nreverse class))))
(values name id (when class (format nil "~{~a~^ ~}" (nreverse class)))))
(case next-is
(:id (setf id (car current-and-remains)))
(:class (push (car current-and-remains) class))
(otherwise (setf name (car current-and-remains))))
(unless (string= "" (cdr current-and-remains))
(setf next-is (ecase (aref (cdr current-and-remains) 0)
(#\. :id)
(#\# :class))
(#\# :id)
(#\. :class))
(cdr current-and-remains) (subseq (cdr current-and-remains) 1))))))

View file

@ -371,6 +371,22 @@
</div>"
(element-string
(h (duck :id 5 :color "blue"
(img :href "duck.png"))))))))
(img :href "duck.png"))))))
(is (string=
"<div class=\"class1 class2\" id=\"has-id\">child</div>"
(element-string
(h (div#has-id.class1.class2 "child")))))
(is (string=
"<div class=\"class1 class2\" id=\"has-id\" onclick=\"func()\">child</div>"
(element-string
(h (div#has-id.class1.class2 :onclick "func()" "child")))))
(is (string=
"<div class=\"class1 class2\" id=\"has-id\" onclick=\"func()\">child</div>"
(element-string
(h (div#has-id.class1.class2 '(:onclick "func()") "child")))))
(is (string=
"<div id=\"has-id\" class=\"class1 class2 additional-class\">child</div>"
(element-string
(h (div.class1#has-id.class2 ':class "additional-class" "child")))))))
(run-all-tests)