ucwTutorialAddingAValidatorToTheExample

_ucw Tutorial

_ucwTutorialWritingYourOwnValidator

Now we have all the things in place to write our own validator so let's integrate it into our small "application".

First we like to modify our validator ot accept alphanumeric characters and spaces. I think it's now a good time to introduce ucwProgrammingTips.

So let's see how we can do modify our validator functions, we currently have:

(defun check-on-alpha-chars (some-string)
  "Break out the validation test to a simple test 
on a string."
  (assert (stringp some-string)) ; just to be sure
  ;; (inspect some-string)
  (and (not (string= "" some-string))
       (every #'alpha-char-p some-string)))

Now I'd argue it's worth having a new validator which accepts Characters and space. The both can come in hany sometimes. So let's write it:

(defun check-on-alpha-and-space-chars (some-string)
  "Extension to the CHECK-ON-ALPHA-CHARS function 
accept a #\Space additionaly."
  (assert(stringp some-string))
  (and (not (string= "" some-string))
       (every #'(lambda(a-char) 
                  (or (alpha-char-p a-char)
                      (char= #\Space a-char))) some-string)))

Looks a bit redundant, but I'm too lazy yet to work on this. So let's accept it for now. It seems this works the intended way:

 (check-on-alpha-and-space-chars "eins")
T
TUTORIAL> (check-on-alpha-and-space-chars "eins1")
NIL
TUTORIAL> (check-on-alpha-and-space-chars "eins zwei drei")
T
TUTORIAL> (check-on-alpha-and-space-chars "eins zwei drei;")

Integrating a validator into the example

So let's assume it will work. Now we have to integrate it into the downloader example.

Let's add a new validator first

        (defclass alpha-char-and-space-validator (validator)
  ()
  (:documentation "Validator to check it's argument on Alpha chars 
and space, usefule e.g for Name like 'Foo Bar' or the like 
not useful for 'Charles 2.'"))
   

Add a new method for this validator

      (defmethod validate ((field string-field) (validator alpha-char-and-space-validator))
  (let ((value (client-value field)))
    (and value 
         (stringp  value)
         (check-on-alpha-and-space-chars value))))
  

Modify the GET-DOWNLOADER-NAME component

Here the relevant change:
        :initform (make-instance 'string-field
                             :input-size 20
                             :validators (list (make-instance 'alpha-char-and-space-validator)))) 
    

Modify the action

Now this is getting a bit more infolving. We have to redirect according to the input. Now if we won't get a valid name (read it consists not just out of alpha chars and spaces) we have to redisplay the page. However the user may have no idea why he/she founds itself on the same page as before. So it may be a good idea to give him/her some sort of feedback.

Let's do it in two steps

  1. Just modfify the action which does the redirection without any comment
  2. Add some feedback to the page to let the person know what we expect from him/her
I modified the actions this way:
(defaction present-downloader-data ((form get-downloader-name))
  (let* ((view-value (value (name-input-field form)))
         downloader)
    (if (validp (name-input-field form))
        (progn
          (setf downloader (fetch-downloader view-value))
          (if downloader
              (incf (downloads downloader))
              (setf downloader (make-downloader :name view-value)))
          (clsql:update-records-from-instance downloader)
          (answer downloader))
        (answer (call 'get-downloader-name :name-input-valid-p nil)))))

As you can see the changes were minimal the only addition was an IF form. But see that the return value of this function is either (ANSWER DOWNLOADER) or (ANSWER (CALL ....

I call the generic function VALIDP which itself turns into call to VALIDATE on every registered VALIDATOR for this input field.

What we do not get is some sort of feedback. So if you type in e.g test1 you will see the same page again with the text field emptied. Now we have to decide wheter we just inform the user with some text or maybe with some other visual effect. In the forms example from UCW you see that the JavaScript validator renders a red border around such fields. That's a nice thing, so let us try something similiar.

There's one thing left to decide, should we show the "wrong" input or not? Maybe the user just mistyped an 0 for an O, he may have a long name and dislike printing it again...

I leave that as an "excercise" to the reader. For now we just forget about it.

Now we solve the whole stuff in steps again.

Modify the GET-DOWNLOADER_NAME Component

We add another slot which tells whether the given field was valid or not. This could probably also done another way. We could that the initial value of the NAME-INPUT-FIELD to the wrong value and call VALIDP on it. Please try this way yourself (hint check (setf (client-value or set (lisp-value)), but for now we just add another slot:
(defcomponent get-downloader-name (simple-window-component)
  ((name-input-field-valid-p :accessor name-input-field-valid-p
                             :initform t
                             :initarg :name-input-valid-p)
...

Of course we have to change the render method no to display differently, depending on the value of this slot. I wrote it this way:

(if (name-input-field-valid-p form)
                   (<:td :class "download-query" (render (name-input-field form)))
                   (progn
                     (<:td :class "download-query"
                           (<:span :class "missing-or-wrong-input"
                                   (render (name-input-field form))))
2                     (<:td "Just alpha chars and spaces allowed")))))

However I think one has to be carful with such things. IMHO the login in a render method should only deal with one thing "Data Presentation to the user". This, so we have to be very careful not to include too much Model related stuff here. Of course the view has not idea about validity to some extend this must be decided elswhere, but we have to get the information to the view somehow, so I think this an acceptable tradeof.

Now the :class "css-class" is meant to be used in conjunction with cascading stylesheets, we currently do not have one. But we can test it nevertheless because I added another A page with wrong input now looks like this: Get Downloadeer with wrong data

GET-DOWNLOADER-NAME redisplay

So that seems to work. There is one questoin left. Why had I to write:

(answer (call 'get-downloader-name :name-input-valid-p nil))

Now for that we have to check the documentation of call. There you'll find:

"Stop the execution of the current action and pass control to
a freshly created component of type COMPONENT-TYPE.

COMPONENT-INIT-ARGS are passed directly to the underlying make-instance call. This form will return if and when the call'd component calls answer, the value returned by this form is whatever the call'd component passed to answer.

The important things is the word 'freshly'. My first try to just SETF the new added component must fail then of course. So I either have to provide the information then for the freshly create component or alternativly I can do the following, instead of '(call (.... ) to write:

 (progn
          (setf (name-input-field-valid-p form) nil)
          (refresh-component form)))))

I think the second approach is appropriate here, but YMMV of course.

Now the rendered pages in source code contain:

td class="download-query"
  >

So let's try to get a CSS style-sheet into play

Adding a CSS style sheet to our pages

We can't imagine that the ucw crew has not provided any hook to get a stylesheet into it so let's try the following got to SIMPLE-WINDOW-COMPONENT and type M-. on it (some lisp should be running as should slime) then we get find:

(stylesheet :accessor window-component.stylesheet :initarg :stylesheet :initform nil :documentation "The URL of the css file to use as a stylesheet for this window.")

Now for that to you use I assume one has to add a few links to the creation of an application, e.g to point to static pages. But that's a bit too much for today therefor I tried simply:

 (:default-initargs
      :title "Downloader"
    :stylesheet "/css/ucw-tutorial.css")

Well I'm using apache here and so this migh work and indedd I got look an the following output with wrong or missing data: Stylsheet example with Apache and UCW

Now you probably miss the css-file here is it's content:

span.missing-or-wrong-input{ 
  padding: 3px;
  border-style:solid;
  border-color:Red;
  border-width:2px;
 }

Conclusion

Now that's it for today. Our pages get into some shape finally, we learned

I guess whith this information you get an idea on what you can do with UCW, the tutorial will go one if I'll find the time, however this will not be possible fo the next 3-4 days. I invite you to play around with the sources a bit and if you feel you like to get something stated better, ask a questions (the best would be to add to this Wiki of course.

Sorry, but I won't find the time till next week (first week of April).

The latest UCW Tutorial sources 2006-03-19

Next: ucwTutorialIntroducingTal ; Previous: ucwTutorialWritingYourOwnValidator


This page is linked from: ucw Tutorial   ucwTutorialIntroducingTal   ucwTutorialWritingYourOwnValidator  

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