Hello all,
I propose that the types for easy-handlers be extensible by the users. I have pieces of markup which are used throughout our site which collect information from several fields into a class (in our case tied to a database with clsql). The names of the fields are related, but diffierent from the lists, arrays and hashes of the easy-handlers, Dates and addresses are two good examples, but there are others. I would like to be able to teach the easy handler to collect these for me and pass them as parameters. This cannot be done with functions as the :parameter-type because the functions passed as :parameter-type do not take the parameter name.
There are two ways I think this could be done:
- The easier way: add a :paramater-reader arg to the parameter description, and if passed use it in lieu of the ((post|get)-)?parameter functions in compute-parameter. The following might do it (untested):
(defun compute-parameter (parameter-name parameter-type request-type &optional parameter-reader) ; <===== "Computes and returns the parameter(s) called PARAMETER-NAME and converts it/them according to the value of PARAMETER-TYPE. REQUEST-TYPE is one of :GET, :POST, or :BOTH." (when (member parameter-type '(list array hash-table)) (setq parameter-type (list parameter-type 'string))) (let ((parameter-reader (or parameter-reader ; <==== (ecase request-type (:get #'get-parameter) (:post #'post-parameter) (:both #'parameter))) ) (parameters (and (listp parameter-type) (case request-type (:get (get-parameters)) (:post (post-parameters)) (:both (append (get-parameters) (post-parameters))))))) (cond ((atom parameter-type) (compute-simple-parameter parameter-name parameter-type parameter-reader)) ((and (null (cddr parameter-type)) (eq (first parameter-type) 'list)) (compute-list-parameter parameter-name (second parameter-type) parameters)) ((and (null (cddr parameter-type)) (eq (first parameter-type) 'array)) (compute-array-parameter parameter-name (second parameter-type) parameters)) ((and (null (cddddr parameter-type)) (eq (first parameter-type) 'hash-table)) (compute-hash-table-parameter parameter-name (second parameter-type) parameters (or (third parameter-type) 'string) (or (fourth parameter-type) 'equal))) (t (error "Don't know what to do with parameter type ~S." parameter-type)))))
(defun make-defun-parameter (description default-parameter-type default-request-type) "Creates a keyword parameter to be used by DEFINE-EASY-HANDLER. DESCRIPTION is one of the elements of DEFINE-EASY-HANDLER's LAMBDA-LIST and DEFAULT-PARAMETER-TYPE and DEFAULT-REQUEST-TYPE are the global default values." (when (atom description) (setq description (list description))) (destructuring-bind (parameter-name &key (real-name (compute-real-name parameter-name)) parameter-type init-form request-type parameter-reader ) ; <==== description `(,parameter-name (or (and (boundp '*request*) (compute-parameter ,real-name ,(or parameter-type default-parameter-type) ,(or request-type default-request-type))) ,init-form))))
(This just occurred to me: request-type could be a function, with an obvious change to the ecase. This is a little more confusing but consistent with what parameter-type does---so it has the force of precedent---and requires a smaller change to the code.).
- The extensive way: Change the cond in compute-parameter to a switch via a hash table, whose values are the conversion handlers (or a cons with reader and converter). It may need two hash tables: one for simple type names (type is a symbol) and one for aggregates, (type is a cons, and switch on the first element). Create a macro, define-easy-type, or something, to populate the tables, and use it within easy-handlers.lisp to populate it with those which are now hardcoded in the cond and case switches. Users can then use the macro to define their own types.
- Another way: like above, but use methods instead of hash tables. This seems possible, but I'd have to think some more about it.
BTW: why the checks for null CDDRs and CDDDDRs in compute-parameter? Is this just nice, or are they essential?
If your interested in the second, I will work something up and send in a patch. I do not think it would break any existing code which did not peak too far into the easy-handlers stuff.
Let me know what you think.
Tim S
P.S.: Hunchentoot is a pleasure to work with!
Hi,
sorry for the delay.
On Fri, 25 May 2007 06:47:57 -0700 (PDT), Timothy Schaeffer tschaef@sbcglobal.net wrote:
I propose that the types for easy-handlers be extensible by the users. I have pieces of markup which are used throughout our site which collect information from several fields into a class (in our case tied to a database with clsql). The names of the fields are related, but diffierent from the lists, arrays and hashes of the easy-handlers, Dates and addresses are two good examples, but there are others. I would like to be able to teach the easy handler to collect these for me and pass them as parameters. This cannot be done with functions as the :parameter-type because the functions passed as :parameter-type do not take the parameter name.
I don't really understand what you're trying to do that can't be done with REAL-NAME and a function designator for PARAMETER-TYPE. Maybe you can provide an example?
I'd say that if it can't be done, then your task is probably too complicated for an "easy" handler, but maybe I'm missing something. The idea of DEFINE-EASY-HANDLER is that it is a convenience macro for 90% of the handlers one usually writes - the mundane ones. It was not intended as a general purpose tool for every conceivable Hunchentoot handler on Earth.
BTW: why the checks for null CDDRs and CDDDDRs in compute-parameter? Is this just nice, or are they essential?
It is there to check for syntax errors.
Hunchentoot is a pleasure to work with!
That's good to know... :)
Cheers, Edi.
Le mercredi 30 mai 2007 à 09:35 +0200, Edi Weitz a écrit :
Hi,
sorry for the delay.
On Fri, 25 May 2007 06:47:57 -0700 (PDT), Timothy Schaeffer tschaef@sbcglobal.net wrote:
I propose that the types for easy-handlers be extensible by the users. I have pieces of markup which are used throughout our site which collect information from several fields into a class (in our case tied to a database with clsql). The names of the fields are related, but diffierent from the lists, arrays and hashes of the easy-handlers, Dates and addresses are two good examples, but there are others. I would like to be able to teach the easy handler to collect these for me and pass them as parameters. This cannot be done with functions as the :parameter-type because the functions passed as :parameter-type do not take the parameter name.
I don't really understand what you're trying to do that can't be done with REAL-NAME and a function designator for PARAMETER-TYPE. Maybe you can provide an example?
Voici a simple example of what I'm doing:
(defun form-date-input (&key name (label "Date") value (required nil)) .... (with-html-output-to-string (s nil :indent t) ... a bunch of input fields for getting a date in pieces, whose id attr is based on the name paramater... ))
;; A function to collect date pieces from fields in markup produced ;; by form-date-input. parm-name must be the same as given in the ;; name parameter in form-date-input. (defun date-parameter (parm-name) (flet ((parm (item) (non-negative-integer-parameter ; guess what this does :) (conc parm-name "-date-" (string-downcase (to-string item)))))) (let ((yyyy (parm 'year)) (mm (parm 'month)) (dd (parm 'day))) (ignore-errors (clsql-sys::parse-datestring (format nil "~4,'0D-~2,'0D-~2,'0D" yyyy mm dd))))))
The function passed as :parameter-type takes only the parameter value as a string (right?), so I cannot do this there; I'm really collecting several fields into one value.
I'd say that if it can't be done, then your task is probably too complicated for an "easy" handler, but maybe I'm missing something. The idea of DEFINE-EASY-HANDLER is that it is a convenience macro for 90% of the handlers one usually writes - the mundane ones. It was not intended as a general purpose tool for every conceivable Hunchentoot handler on Earth.
Understood. I may have had a bit too much coffee. And some part of me doesn't like seeing a hardcoded list of types. Ars longa.
Maybe I'll roll my own, define-hairy-wart-covered-handler maybe.
I still think letting :request-type be a custom getter is a good idea though; it is an easy change and be consistent with the way :parameter-type works.
Hunchentoot is a pleasure to work with!
That's good to know... :)
It's good to be true [-)
Tim S