_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;")
So let's assume it will work. Now we have to integrate it into the downloader example.
(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.'"))
(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))))
:initform (make-instance 'string-field
:input-size 20
:validators (list (make-instance 'alpha-char-and-space-validator))))
Let's do it in two steps
(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.
(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
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
- How we can modify the outpu according to some values
- How to get information into one component (via initargs)
- How to give feedback to the user on missing values be it in form
of some text of some visual changes
- How to get UCW playing togehter with CSS
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