Wednesday, February 28, 2007

Cross client (Web/Notes) validation using JavaScript

Show n' Tell Thursday

Update 02.03.07

I've been working on a simple method for cross-client validation ever since I discovered how powerful the JS-methods in the Notes client was. Of course, it can't compare to the JS-engines in the web-browser, but you can still have a lot of (geek-) fun with what's available.

Since this is a Show'n Tell Thursday post, it's only reasonable to post the validation-demo database for people to play with.

If you just want to se how it works, I've prepared two flash-animations demoing how it looks in Notes, and how it looks on the web.

In the Notes client

On the web

For the web, I'm using Dexagogo's lovely validation library. I've also overloaded Notes' _doClick function so that it only runs if the form is valid (look in the database for the code).

In notes, the DOM is too simple to be able to do any real animation. I've made a simple "yellow fade". To achieve this, and to always have the error-messages available to view for the user, I've had to use a frameset. The validation script in notes is pretty crude.

The script that both Notes and Web use

And finally, the database. In Notes, the demo is the "WithValidation" frameset. On the web, open the "FramedValidation" form (forgot to rename the form to a better name before I uploaded the zip). FramedValidation is also used in Notes as the main form.

Other fun things you can do with JS in the Client:

Run LotusScript by triggering Entering/Exiting events
document.forms[0].fieldName.focus();
document.forms[0].fieldName.blur();


Use LS libraries (JS2LS anyone?)

To do this, fill value of field equal to a function-call (e.g. ..value="Call refreshForm()").

In the Entering event on the field, execute the value. Or, you could have a field for the method you're after, and just focus() on the field. You can hide the field with a layer (put it in a subform to make it re-usable).

Fill data in fields across framesets
FrameName.do...rms[0].fieldName.value="value";

Fill data into computed fields
document.forms[0].fieldName.value="value";
(same with editable fields)

Use regular expression to validate fields.

Some of the stuff that I couldn't get to work:

Cross frameset event-triggering.

Using focus() to jump from a Native OS Field (with focus) to a Notes-field. Most people probably use either one or the other type, so this is probably not a big issue.

Click on buttons/actions using click(). They don't seem to be in the Notes' DOM.

Tell me the rest. I've probably (hopefully) just scratched the surface. :)

Update (02.03.07): Per Kevin's request, I've added a form where you can make per-form configurations. I placed a field on the demo-form with a @DbLookup that gets the configuration. Using eval, I convert the config-field to an object.

The structure of the validations is that of an Object literal. Once you understand how it works, it's really quite simple.

Do a google search on "Object literal" or "JSON" for more information.

I've also added a simple menu in notes, and a simple JS-testing frameset.


v0.001 (old)

v0.002 (new)

12 comments:

Kevin Pettitt said...

Tommy, this is a very impressive piece of code and looks really slick. The one thing that would keep me from making regular use of this is the apparent need to hard-code the field names into the validation script for a given form. If you can populate the classNameTitles variable dynamically, perhaps from a field on the form that reads in a list of required fields and their data types from some config doc, that would be huge.

One of my primary goals at the moment is building a standard Notes database that even novice developers can build on, but that has some nice touches like this in it. I don't want to require those folks to touch javascript just to use a basic function like validation, but if I can completely shield them from that need, I would love to make this code part of the picture.

Since I'm not a javascript guru I don't know offhand if my idea is easy or not.

Tommy Valand said...

I'm not entirely certain, but I think that should be possible..

In the JS that's in modern browsers it's possible using the eval method. I think it's possible with the version of JS in the Notes Client as well.
var f = document.forms[0];
var classNameTitles = eval(f.validationConfiguration.value)

Tommy Valand said...

I updated the DB to do what you requested. See if it's more like what you need..

Kevin Pettitt said...

Tommy, thanks for putting in the configuration stuff. I can see now a practical way to present users with a simple config form that asks what fields to require, then builds the javascript automatically that then gets pulled into the process. I tried to adapt your approach a bit by encapsulating all the logic from the form onto a subform, and hit a snag because there is no "onsubmit" event on subforms.

Since there are several pieces of event code and two flavors of JSHeader on the form, I'm concerned about novice developers overlooking something when creating new forms. If ALL of that logic can be centralized somehow such that the only thing they needed to do was add a subform and edit a config doc, there would be less chance of a screw up.

Maybe there is a way to use passthru html on the subform to get the onsubmit working?

Tommy Valand said...

Pass thru HTML-script tags are ignored in the Notes-client. It is also not possible to bind events (e.g. document.forms[0].onsubmit = function(){alert("Hello world"")} ).

The only possibility I can see is using an applet to somehow call the JS validation and submit if the return-value is true. I haven't written Java in about three years, so I don't know if I can help you in that department..

Do you have any Java/Notes developers in your company that can test this? If you have/it works, please let me know how you did it.. :D

Kevin Pettitt said...

I'm starting to fiddle with java a bit myself but being a "company of one" I can't lean on anyone else with more experience in that area.

Stepping back though, I would point out that at least for me the design goal here is "to have a single subform that will encapsulate all the validation code necessary to validate a document for both the Notes client and web". Note that the word "javascript" is not in that definition.

So, although I really like the client side javascript validation in terms of its cosmetics, I could forego that benefit and fall back to a method I already use (see http://www.lotusguru.com/lotusguru/LGBlog.nsf/d6plinks/KPET-6GXQBK) on the client side, which relies on Lotusscript.

The question then is how to trigger the validation javascript from a subform on the web, and since pass-thru html does work on the web, maybe that is something worth pursuing.

I also wonder if @URLOpen could be used somehow in the client querysave event, in lieu of passthru html.

Tommy Valand said...

On the web it's "no problem" to overload the _doClick function that Notes uses for actions.

Put this code in the JS-header on the sub-/form, or in a JS-library:
/* beginning */
var f;

function load(){
f = document.forms[0];
/* add className/titles to elements */
setTitleClassNames();
/* overload Notes' _doClick */
addValidation();
/*if you're using Dexagogo's validation library, initialize validation */
valid = new Validation(f);
}

function addValidation(){
_doClickValid=_doClick; /* overloading notes' _doClick */
_doClick=function(v,o,t,h){
if(valid.validate()){ /*if form is valid, continue */
return _doClickValid(v,o,t,h);
}
else {
return false;
}
}
}
/*
If you already have something in onload on the form, use prototype or some other library with nice event-functionality to bind the event (doesn't overwrite the events in the form).

E.g. protoype: Event.observe(window,'load',load);
*/
window.onload = load;

/* end */

Most of this code is in the web JS-header on the FramedValidation-form in the demo-db.

If you use an action-button with:
@Command([FileSave]);
@Command([FileCloseWindow]);

to save, the onsubmit-event is triggered.

You can then write:
document.forms[0].onsubmit = yourValidation;

or, if you're using prototype:

Event.observe(document.forms[0],
'submit',
yourValidation);

If I misunderstood your need, please let me know. Not sure what you mean about pass-through-html doesn't work on the web. In the notes-client, it is ignored (if my earlier statements gave you that conclusion).

Kevin Pettitt said...

Tommy, I was not saying that pass-thru html didn't work on the web, but it sounds like we can forget that approach altogether.

What I did to try and use your code in a subform was simply copy all the event code from the "FramedValidation" form into a new subform. The problem I encountered was that subforms in Notes don't have an "onsubmit" event in which to place code, so I am looking for an alternative place for the line

return Validation.validate();

that appears in the form's onsubmit event.

If there is a way to simply add something like

window.onsubmit = return Validation.validate();

to the subform JSHeader, that's what I'm looking for.

Hope that makes sense.

Tommy Valand said...

The onsubmit only fires when you have a save-action like this:
@Command([FileSave]);
@Command([FileCloseWindow]);

For the events, you have to use a function. It's not much that has to be added.

document.forms[0].onsubmit = function(){return Validation.validate();}

or:

function doValidation(){
return Validation.validate();
}
document.forms[0].onsubmit = doValidation;

Kevin Pettitt said...

Tommy,

I took this snippet:

function doValidation(){
return Validation.validate();
}
document.forms[0].onsubmit = doValidation;

and placed it to the JSHeader of my subform (right before function yellowFade(target){...), but the validation didn't trigger when clicking the existing Save button (i.e. "Lagre"). I have moved every other piece of js header/event code from the form to my new subform, and this is the only piece that differs from your example db.

I tried putting the subform at the top of the form, the bottom of the form (i.e. before and after the $$HTMLHead field). I haven't been able to make it work, but I'm sure we're getting close.

Tommy Valand said...
This comment has been removed by the author.
Tommy Valand said...

My "Lagre"/Save button does not contain
@Command([FileCloseWindow]).

Therefore, the onsubmit-event will never fire. To add validation to a "FileSave"-only-button, you have to hijack notes' _doClick:

var valid;

function addValidation(){
valid=new Validation(document.forms[0]);

_doClickValid=_doClick; /* overloading notes' _doClick */
_doClick=function(v,o,t,h){
if(valid.validate()){ /*if form is valid, continue */
return _doClickValid(v,o,t,h);
}
else {
return false;
}
}
}

window.onload=addValidation;

Or (with Prototype JS)

Event.observe(window,'load',addValidation)

Read more about Notes' doClick here: http://www-1.ibm.com/support/docview.wss?uid=swg21102939