Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

-Added `hidden` inputs

-Added default values to the show-formlet macro
-Re-organized the show declarations a little bit
-Added `hidden` to the exported symbols list for the :formlets package
  • Loading branch information...
commit 059674ac42bf7fcdc280e253a4f4d4cf900bb0ef 1 parent 0f16a49
ingram authored
Showing with 115 additions and 94 deletions.
  1. +5 −12 README.md
  2. +65 −73 formlets.lisp
  3. +16 −7 macros.lisp
  4. +1 −1  package.lisp
  5. +28 −1 test.lisp
View
17 README.md
@@ -6,14 +6,8 @@ An implementation of self-validating formlets for Hunchentoot.
News
----
-- big codebase re-organization (it now makes liberal use of classes and methods to reduce complexity)
- - the declaration format has changed (for the better, hopefully, but you still need to change it slightly)
- - `show-formlet` is now a function that takes a formlet name and no magic variables
- - validator functions now automatically redirect to the referer, so you don't need to specify a `source-function`
- - validator functions now communicate via the `session` instead of calling back to the previous function
-- support added for inputs of type `checkbox`, `checkbox-set`, `radio-set`, `select` and `multi-select`
-- added new predicates `not-blank?`, `same-as?`, `picked-more-than?`, `picked-fewer-than?` and `picked-exactly?`
-- multiple formlets can now co-exist on one page
+- added support for field-type `hidden`
+- `show-formlet` now accepts the keyword arg `:default-values` which takes a list of default values to populate the form with. These values will be used unless the user has already entered information, in which case their inputs will be displayed instead.
Goals
-----
@@ -35,7 +29,8 @@ A `define-formlet` and `show-formlet` call is all that should be required to dis
Automatically wraps the generated form in a UL and provides CSS classes and ids as hooks for the designers, making the look and feel easily customizable with an external stylesheet.
### Completeness
-The system will eventually support the full complement of HTML form elements, like `hidden`, as well as higher-level inputs, like `date` or `slider`. Currently, it only supports `password`, `text`, `textarea`, `file`, `checkbox` (and `checkbox-set`), `radio-set` (a stand-alone radio button is kind of pointless), `select` (and `multi-select`) and `recaptcha`.
+Currently, it supports the complete set of HTML form fields excepting `reset` (`hidden`, `password`, `text`, `textarea`, `file`, `checkbox` (and `checkbox-set`), `radio-set` (a stand-alone radio button is kind of pointless), `select` (and `multi-select`)) and `recaptcha`.
+The system will eventually support higher-level inputs, like `date` or `slider`.
Semi-Goals
---------
@@ -56,7 +51,6 @@ Usage
-----
### Predicates
-
Formlets now includes a number of predicate generators for external use. These cover the common situations so that you won't typically have to pass around raw `lambdas`. They all return predicate functions as output.
The following four are pretty self explanatory. Longer/shorter checks the length of a string. `matches?` passes if the given regex returns a result for the given input, and `mismatches?` is the opposite. `not-blank?` makes sure that a non-"" value was passed, and `same-as?` checks that the field value is `string=` to the specified value.
@@ -80,7 +74,6 @@ Finally, the newly added set-predicates expect a list of values as input from th
+ `picked-exactly?` Num -> ([String] -> Bool)
### Tutorial
-
To see some example code, check out the `test.lisp` file (to see it in action, load the `formlets-test` system). An example form declaration using a general validation message:
(define-formlet (login :submit "Login" :general-validation (#'check-password "I see what you did there. ಠ_ಠ"))
@@ -97,7 +90,7 @@ You would display the above formlet as follows:
(define-easy-handler (login-page :uri "/") ()
(form-template (show-formlet login)))
-An instance of the `formlet` named `login` is created as part of the `define-formlet` call above. Calling `show-formlet` with the appropriate formlet name causes the full HTML of the formlet to be generated. If any values appropiate for this formlet are found in session, they will be displayed as default form values (passwords and recaptcha fields are never stored in session, so even if you redefine the `password` `show` method to display its value, it will not). If any errors appropriate for this formlet are present, they are `show`n alongside the associated input.
+An instance of the `formlet` named `login` is created as part of the `define-formlet` call above. Calling `show-formlet` with the appropriate formlet name causes the full HTML of the formlet to be generated. If any values appropiate for this formlet are found in session (or if you passed in a set using the `:default-values` argument to `show-formlet`), they will be displayed as default form values (passwords and recaptcha fields are never stored in session, so even if you redefine the `password` `show` method to display its value, it will not). If any errors appropriate for this formlet are present, they are `show`n alongside the associated input.
An example form using individual input validation:
View
138 formlets.lisp
@@ -15,25 +15,23 @@
(validation-functions :accessor validation-functions :initarg :validation-functions :initform nil)
(default-value :reader default-value :initarg :default-value :initform nil)
(error-messages :accessor error-messages :initarg :error-messages :initform nil)))
-
-(defclass text (formlet-field) ())
-(defclass textarea (formlet-field) ())
-(defclass password (formlet-field) ())
-(defclass file (formlet-field) ())
-(defclass checkbox (formlet-field) ())
-
-(defclass formlet-field-set (formlet-field)
+(defclass hidden (formlet-field) ())
+(defclass text (formlet-field) ())
+(defclass textarea (formlet-field) ())
+(defclass password (formlet-field) ())
+(defclass file (formlet-field) ())
+(defclass checkbox (formlet-field) ())
+
+(defclass formlet-field-set (formlet-field)
((value-set :reader value-set :initarg :value-set :initform nil))
(:documentation "This class is for fields that show the user a list of options"))
+(defclass select (formlet-field-set) ())
+(defclass radio-set (formlet-field-set) ())
-(defclass select (formlet-field-set) ())
-(defclass radio-set (formlet-field-set) ())
-
-(defclass formlet-field-return-set (formlet-field-set) ()
+(defclass formlet-field-return-set (formlet-field-set) ()
(:documentation "This class is specifically for fields that return multiple values from the user"))
-
-(defclass multi-select (formlet-field-return-set) ())
-(defclass checkbox-set (formlet-field-return-set) ())
+(defclass multi-select (formlet-field-return-set) ())
+(defclass checkbox-set (formlet-field-return-set) ())
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; METHODS
;;;;;;;;;;post-value
@@ -85,70 +83,64 @@
(defmethod show ((formlet formlet) &optional values errors)
(with-slots (error-messages name enctype) formlet
(html-to-stout
- (when (and (not (every #'null errors)) error-messages)
- (htm (:span :class "general-error" (str (show (car errors))))))
- (:form :name (name formlet) :id name :action (format nil "/validate-~(~a~)" name) :enctype enctype :method "post"
+ (when (and (not (every #'null errors)) error-messages)
+ (htm (:span :class "general-error"
+ (dolist (s (car errors))
+ (htm (:p (str s)))))))
+ (:form :name (string-downcase name) :id (string-downcase name) :action (format nil "/validate-~(~a~)" name) :enctype enctype :method "post"
(:ul :class "form-fields"
(loop for a-field in (fields formlet)
for e in errors
for v in values
- do (htm (:li (:span :class "label" (str (string-capitalize (regex-replace-all "-" (name a-field) " "))))
- (str (show a-field v (when (and e (not error-messages)) e))))))
+ do (str (show a-field v (when (and e (not error-messages)) e))))
(:li (:span :class "label") (:input :type "submit" :class "submit" :value (submit formlet))))))))
-(defmethod show ((list-of-string list) &optional v e)
- "A method for showing error output in the Formlets module"
- (declare (ignore v e))
- (when list-of-string
- (html-to-str (:span :class "formlet-error" (dolist (s list-of-string) (htm (:p (str s))))))))
-
-(defmethod show ((field formlet-field) &optional value error)
- (html-to-str (:input :name (name field) :value value :class "text-box") (str (show error))))
-
-(defmethod show ((field textarea) &optional value error)
- (html-to-str (:textarea :name (name field) (str value)) (str (show error))))
-
-(defmethod show ((field password) &optional value error)
- (html-to-str (:input :name (name field) :type "password" :class "text-box") (str (show error))))
-
-(defmethod show ((field file) &optional value error)
- (html-to-str (:input :name (name field) :type "file" :class "file") (str (show error))))
-
-(defmethod show ((field select) &optional value error)
- (html-to-str (:select :name (name field)
- (loop for v in (value-set field)
- do (htm (:option :value v :selected (when (string= v value) "selected") (str v)))))
- (str (show error))))
-
-(defmethod show ((field checkbox) &optional value error)
- (html-to-str (:input :type "checkbox" :name (name field) :value (name field)
- :checked (when (string= (name field) value) "checked"))
- (str (show error))))
-
-(defmethod show ((field radio-set) &optional value error)
- (html-to-str (loop for v in (value-set field)
- do (htm (:span :class "input+label"
- (:input :type "radio" :name (name field) :value v
- :checked (when (string= v value) "checked"))
- (str v))))
- (str (show error))))
-
-
-(defmethod show ((field multi-select) &optional value error)
- (html-to-str (:select :name (name field) :multiple "multiple" :size 5
- (loop for v in (value-set field)
- do (htm (:option :value v
- :selected (when (member v value :test #'string=) "selected")
- (str v)))))
- (str (show error))))
-
-(defmethod show ((field checkbox-set) &optional value error)
- (html-to-str (loop for v in (value-set field)
- do (htm (:span :class "input+label"
- (:input :type "checkbox" :name (name field) :value v
- :checked (when (member v value :test #'string=) "checked"))
- (str v))))
- (str (show error))))
+(defmethod show ((field hidden) &optional value error)
+ (html-to-str (:input :name (name field) :value value :type "hidden")))
+
+(defmacro define-show (field-type &body body)
+ `(defmethod show ((field ,field-type) &optional value error)
+ (html-to-str
+ (:li (:span :class "label" (str (string-capitalize (regex-replace-all "-" (name field) " "))))
+ ,@body
+ (when error (htm (:span :class "formlet-error"
+ (dolist (s error)
+ (htm (:p (str s)))))))))))
+
+(define-show formlet-field (:input :name (name field) :value value :class "text-box"))
+(define-show textarea (:textarea :name (name field) (str value)))
+(define-show password (:input :name (name field) :type "password" :class "text-box"))
+(define-show file (:input :name (name field) :type "file" :class "file"))
+
+(define-show select
+ (:select :name (name field)
+ (loop for v in (value-set field)
+ do (htm (:option :value v :selected (when (string= v value) "selected") (str v))))))
+
+(define-show checkbox
+ (:input :type "checkbox" :name (name field) :value (name field)
+ :checked (when (string= (name field) value) "checked")))
+
+(define-show radio-set
+ (loop for v in (value-set field)
+ do (htm (:span :class "input+label"
+ (:input :type "radio" :name (name field) :value v
+ :checked (when (string= v value) "checked"))
+ (str v)))))
+
+(define-show multi-select
+ (:select :name (name field) :multiple "multiple" :size 5
+ (loop for v in (value-set field)
+ do (htm (:option :value v
+ :selected (when (member v value :test #'string=) "selected")
+ (str v))))))
+
+(define-show checkbox-set
+ (loop for v in (value-set field)
+ do (htm (:span :class "input+label"
+ (:input :type "checkbox" :name (name field) :value v
+ :checked (when (member v value :test #'string=) "checked"))
+ (str v)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; PREDICATES
(defmacro define-predicate (name (&rest args) &body body)
View
23 macros.lisp
@@ -48,16 +48,25 @@
(session-value :formlet-name) ',name)
(redirect (referer)))))))))))
-(defmacro show-formlet (formlet-name)
+(defun ensure-list-length (list desired-length)
+ (assert (and (integerp desired-length) (< 0 desired-length)))
+ (cond ((= (length list) desired-length) list)
+ ((> (length list) desired-length) (butlast list (- (length list) desired-length)))
+ ((< (length list) desired-length)
+ (append list (make-list (- desired-length (length list)))))))
+
+(defmacro show-formlet (formlet-name &key default-values)
"Shortcut for displaying a formlet.
It outputs the formlet HTML to standard-out (with indenting).
If this is the last submitted formlet in session, display field values and errors, then clear out the formlet-related session information."
- `(let ((val (if (eq (session-value :formlet-name) ',formlet-name)
- (session-value :formlet-values)
- (make-list (length (formlets::fields ,formlet-name)))))
- (err (if (eq (session-value :formlet-name) ',formlet-name)
- (session-value :formlet-errors)
- (make-list (length (formlets::fields ,formlet-name))))))
+ `(let* ((default-val ,default-values)
+ (val (cond ((eq (session-value :formlet-name) ',formlet-name)
+ (session-value :formlet-values))
+ (default-val (ensure-list-length default-val (length (formlets::fields ,formlet-name))))
+ (t (make-list (length (formlets::fields ,formlet-name))))))
+ (err (if (eq (session-value :formlet-name) ',formlet-name)
+ (session-value :formlet-errors)
+ (make-list (length (formlets::fields ,formlet-name))))))
(show ,formlet-name val err)
(when (eq (session-value :formlet-name) ',formlet-name)
(delete-session-value :formlet-name)
View
2  package.lisp
@@ -3,7 +3,7 @@
(:import-from :cl-ppcre :regex-replace-all :split :scan)
(:import-from :drakma :http-request)
(:export :formlet :formlet-field
- :text :textarea :password :file :checkbox :select :radio-set :checkbox-set :multi-select
+ :hidden :text :textarea :password :file :checkbox :select :radio-set :checkbox-set :multi-select
:*public-key* :*private-key* :recaptcha
:validate :show :post-value :show-formlet :define-formlet
:longer-than? :shorter-than? :matches? :mismatches? :file-type? :file-smaller-than? :not-blank? :same-as? :picked-more-than? :picked-fewer-than? :picked-exactly?)
View
29 test.lisp
@@ -4,10 +4,25 @@
formlets:*private-key* "your private key"
*show-lisp-errors-p* t)
+(defparameter *css* ".form-fields { list-style: none; padding-bottom: 0px; }
+.form-fields li { margin-bottom: 10px; clear: both; }
+.form-fields input { margin: 0px; }
+.form-fields .text-box { border: 1px solid #1c2a51; width: 250px; }
+.form-fields textarea { border: 1px solid #1c2a51; width: 246px; height: 60px; }
+.submit { border: 1px solid #1c2a51; background-color: #1c2a51; color: #fff; font-weight: bold; padding: 3px 8px; }
+.form-fields .label { width: 130px; display: block; float: left; text-align: right; padding-right: 5px; height: 20px; }
+
+.general-error p, .formlet-error p { border: 1px solid #900; background-color: #faa; margin: 0px; padding: 2px 5px; }
+.general-error p { margin: 10px 0px 0px 195px; width: 236px; }
+
+.formlet-error { position: absolute; padding: 2px; margin-left: 5px; }
+.general-error { position: relative; }")
+
(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")
+ (:style :type "text/css" (str *css*))
(:title ,@title))
(:body ,@body))))
@@ -69,6 +84,16 @@
(:p (str user-name))
(:p (str password))))
+(define-formlet
+ (default-values-form :submit "Nope")
+ ((minus-one hidden)
+ (one text :validation ((longer-than? 3) "It has to be longer than 3"))
+ (two textarea)
+ (three checkbox)
+ (four checkbox-set :value-set ("one" "two" "three")))
+ (page-template (:title "You got it")
+ (:p (str (write-to-string (post-parameters*))))))
+
(define-easy-handler (test-page :uri "/") ()
(page-template (:title "Formlets Test Page")
(:p (str (session-value :formlet-name)))
@@ -77,6 +102,8 @@
(:hr) (show-formlet test-form)
(:hr) (show-formlet test-form-two)
(:hr) (show-formlet test-form-three)
- (:hr) (show-formlet faux-login-form)))
+ (:hr) (show-formlet faux-login-form)
+ (:hr) (show-formlet default-values-form
+ :default-values (list "Something" "Something else" "A textarea! Yay!" "three" (list "two")))))
(defvar *web-server* (start (make-instance 'acceptor :port 4141)))
Please sign in to comment.
Something went wrong with that request. Please try again.