ucwTutorialGettingABitFurther

ucw Tutorial

Till now we just have something barely running. We have not spend any time on making it prettier or "safer". Now you can just get away with it in a tutorial for a while. However I have to take you an a small excurseProgrammingRants

I'll try to show you today how you can validate your inputs within the ucw-framework. Ok, let's re-check our defcomponent get-downloader-name

(defcomponent get-downloader-name (simple-window-component)
  ((name-input-field 
    :accessor name-input-field
    :initform (make-instance 'string-field
                             :input-size 20
                             :validators (list (make-instance 'not-empty-validator)))) 
   ;; the above is one such extension string-field is a text-field whcih 
   ;; will get a 'string' as input
   (submit-button :accessor submit-button
                  :initform (make-instance 'submit-button)))
  ;; some like buttons some don't, let us use it to 
  ;; give mouse lovers something to click on
  (:default-initargs
      :title "Downloader")
    (:documentation "A component is a decorated CLOS class you can use 
the same arguments as in 'normal' classes plus some extensions."))

Well you see the line with the ':validator', AFAIKT this must have been added to UCW in the last few montsh, and I think this is a "good thing". However we do not have used the validation function till now, and well now it's arguable if the validation should not take place automaticall. Just if you explain you are not interested in getting it run it will.

And example on how to react to validation problems can be found also in the provided examples. Here's how Marco has done it in the example:

(defaction refresh-component :after ((form example-form))
  (setf (messages form) '())
  (flet ((push-message-unless-valid (field name)
           (multiple-value-bind (validp failed-validators)
               (validp field)
             (unless validp
               (push (format nil "~A failed~{ ~A~} check~P."
We can learn quite abit about this code

Let's check it, SLIME to the help please. Type M-. and type in ucw:validp You strand here:

(defgeneric validp (form-field))

(defmethod validp ((field form-field)) (loop for validator in (validators field) when (null (validate field validator)) collect validator into failed-validators finally (return (values (null failed-validators) failed-validators))))

I'd argue this suggests that ordering of the validators does matter. Just let us keep that aside for now, maybe we come back here one day wondering why one validator fails but others work or maybe you like to try it out yourself.

As yes and we learn also that the UCW crew are not fans of the "standard loop" macros ;-)

What do we like to check?

Well I wrote in ucwTutorialExample, that we like to get the name of that person, but if this person does not like to give the name, he can put in any nonsense, however for the time beeing we like to get something back. So I stated that I'm going to ask for the 'not-empty-validator. So the name suggests that it tests if something is there.

Now we get into intersting grounds. The UCW stuff has different levels on input validation. AFAIKT the first level is a JavaScript level, using it has a remarkable effect. If JavaScript is enabled, the input fields appearance can change while you are still typing. This is a "new" interface feeling for Web appliations and has a glorious name 'Asynchronous Javascript and XML' (Ajax). Oh, well yes there it is Javascript....

The predefined 'validator-functions' (be it Java or Lisp can be found in ucw_dev/src/components/form.lisp), it seems the UCW crew does use Common Lisp to generate this JavaScript snippets. I have not yet looked into it and do not have an idea on how good it works, but at least it seems to work.

How the validators are structured

The validators are in a hierachy of objects starting with
(defclass validator ()
  ())

Direct below do you find:

;;;; There are three main parts to the validating api:

;;;; 1 - a javascript function which checks if the current value is valid or not.

;;;; 2 - two javascript functions which handle the valid/invalid cases

;;;; 3 - a lisp function whcih checks if the current value is valid or not.

Typos are as-is from the sources. It seems there is a protocol to obey.

(defmethod generate-javascript ((field generic-html-input) (validator validator))
  `(if (,(generate-javascript-check field validator))
       (,(generate-javascript-valid-handler field validator)
         (document.get-element-by-id ,(dom-id field)))
       (progn
         (,(generate-javascript-invalid-handler field validator)

So the check taked place on some the generic function 'generate-javascript-check'. I strongly suggest you look around in forms.lisp to get an idea. I guess what you did not expect to see (at least I was a bit suprises is this line: (document.get-element-by-id ,(dom-id field)

This seems to be parenscript and Ajax in action, the funky use of it in the form example is probably done here:

 (defmethod javascript-invalid-handler ((field t) (validator validator))
  `(unless (dojo.html.has-class (document.get-element-by-id ,(dom-id field)) "ucw-form-field-invalid")
     (dojo.html.add-class (document.get-element-by-id ,(dom-id field))
                          "ucw-form-field-invalid")))

This wrapped a class (in this case an css class around the fault element. You decide how it look by providing the proper 'ucs-form-field-invalid', but let's stop here, I let have allowed them to get me away from what I wanted to write really.

the not-empty-validator

Now this is what I have to find..., Here is it's implementation (although I guess you would have figured out yourself
(defclass not-empty-validator (validator)
  ())

(defmethod validate ((field form-field) (validator not-empty-validator)) (and (client-value field) (not (string= "" (client-value field)))))

I guess you wouldn have implemented it the same way. Now the only thing you have to check is 'client-value'. This will normally contains the input from the user, but it may or may not be misused also to define defaults for the fields. So it's hopefully now clear how to use this validator stuff.

We'll see later if this assumptions are the right ones. If you find out I'm wrong, please correct this part.

Calling the validation functions

Now to get something shown we better give some feedback if some validation fails. Because we are into "learning" I wrote down the minimal stuff to show this validaton in action. I did not integrate it into the existin solution yet. So here we go:
(defcomponent get-some-input (simple-window-component)
  ((input-field :accessor input-field
                :initform (make-instance 
                           'string-field
                           :input-size 20
                           :validators (list (make-instance 'not-empty-validator))))))

(defmethod render ((win get-some-input)) ((defentry-point "get-some-input.ucw" (:application *downloader-application*) () (call 'get-some-input)) (defcomponent valid-input-answer (simple-window-component) ())

(defcomponent invalid-input-answer (simple-window-component) ())

(defmethod render ((win valid-input-answer)) (declare (ignore win)) (<:p "The input was valid"))

(defmethod render ((win invalid-input-answer)) (declare (ignore win)) (<:p "The input was invalid"))

(defaction show-answer ((form get-some-input)) (if (validp (input-field form)) (call 'valid-input-answer) call 'invalid-input-answer)))

Now this is a bunch of stuff, but don't get shy. the first defcomponent declaration is known to you. I just ommitted the submit button. So for the example you will see something like this: >User input page You hit RET and now it depends what you have filled in. If there was something you will get see the VALID-INPUT-ANSWER response otherwise INVALID-INPUT-ANSWER. Now the really easy to understand decison taks place here:

(defaction show-answer ((form get-some-input))
  (if (validp (input-field form))                ; 1)
      (call 'valid-input-answer)                 ; 2)
      (call 'invalid-input-answer)))              ; 3)

Now the validation (a server side validation in Common Lisp) take place at 1), depending on the input to we branch to the VALID-INPUT-ANSWER page 2) or the INVALID-ANSWER-PAGE 3).

So in principal you can not decide whether you would write a "normal" application or a web application. I'm running a bit out of time so let that be enough for today

Conclusion

I hope I could give you an idea on how input validaton works in the UCW Framework. It's up to you do decide how you like to validate, the UCW crew has provided you with at least two ways
  1. client side validation (via Javasript embedded and written in Common Lisp. the Parenscript way of doing things. This was not shown here
  2. server side validation with a validation protocol. I showed how to use it today. We'll have to see when we'll have to write our own stuff...

Any Feedback to this pages are very welcome. I hope you like this pages a bit :)

Next: ucwTutorialWritingYourOwnValidator ; Previous: ucwTutorialGettingFromOnePlaceToAnother


This page is linked from: ucw Tutorial   ucwTutorialGettingFromOnePlaceToAnother   ucwTutorialWritingYourOwnValidator  

CLiki pages can be edited by anyone at any time. Imagine a fearsomely comprehensive disclaimer of liability. Now fear, comprehensively