FunFormKit Quick Start `````````````````````` Version 0.4.1, written for *Webware for Python* 0.8 .. contents:: **Contents** Creating a Form Definition ========================== In this guide we'll use the example of a registration page. The code for this example is in ``FunFormKit/Examples/Register.py`` The ``FormDefinition`` describes your form. You give it the URL of the servlet that handles the results of the form, a list of fields, and occasionally some other information. The ``FormDefinition`` is entirely static. Once you've created a definition, you cannot change its fields or any other attributes. This is necessary because the servlet will be accessed a number of times to generate, process, and potentially regenerate the form. If the form definition changes from one access to the next, the results will be unpredictable. Unpredictable programs are buggy programs. There are ways to make the contents of the form dynamic, however, which is addressed later. The FormDefinition and the Fields are all objects. They are created like normal objects. For instance, here's a form that asks for a username and password:: from FunFormKit.Form import * from FunFormKit.Field import * from FunFormKit import Validator formDef = FormDefinition( "Register", [TextField("firstName", maxLength=20, validators=[Validator.NotEmpty()]), TextField("middleInitial", description="MI", size=1, maxLength=1), TextField("lastName", maxLength=50, size=20, validators=[Validator.NotEmpty()]), TextField("email", size=20, validators=[Validator.NotEmpty(), Validator.Email()]), TextareaField("address", rows=4, cols=40), PasswordVerifyField("password", size=20, validators=[Validator.NotEmpty()]), SelectField("gender", selections=[("male", "Male"), ("female", "Female")], nullInput="Gender"), TextField("age", size=2, maxLength=2, validators=[Validator.AsInt()]), SubmitButton("submit", methodToInvoke="register"), ]) ``FormDefinition`` requires two arguments: the URL of the servlet that handles the form, and a list of fields. There are other optional arguments, see `The Form Definition`_ in the `User Guide`. .. _`The Form Definition`: UserGuide.html#the-form-definition .. _`User Guide`: UserGuide.html The Fields are objects as well. The ``Field`` module contains all of the fields that come with FunFormKit, and it is fairly safe to do ``from FunFormKit.Field import *`` -- almost all the exported simples are field classes that end in ``Field``. See `Field Definitions`_ in the `User Guide`_ for more, or `Special Fields`_ and `Normal Fields`_ for descriptions of all the available fields. .. _`Field Definitions`: UserGuide.html#field-definitions .. _`User Guide`: UserGuide.html .. _`Special Fields`: UserGuide.html#special-fields .. _`Normal Fields`: UserGuide.html#normal-fields There are some default arguments all Fields. They all take a name (e.g. ``"username"``, ``"password"``, ``"submit"``). They also all take two optional keyword arguments: ``description`` and ``validators``. ``description`` is useful in form generation, where it is used by the automatic layout to describe the field (though if you use ``mixedCase`` style names, FunFormKit will split the name into words). In a submit button, it is the text on the button. ``validators`` is a list of ``ValidatorConverters``. These are located in the ``Validator`` module. Validation and conversion occur at the same time -- this is useful since conversion usually implies some sort of validation. For instance, if you want an integer (using ``Validator.AsInt``), the converter has to signal an error if the user entered ``ten`` instead of ``10``. Create Your Servlet =================== The next step is creating a servlet to handle the form. We'll be using the same form definition as given above. The basic template for a servlet is like this:: # [imports and the form definition is assigned to the # formDef variable] # The layout doesn't need to be global, but we'll reuse it # later so we'll put it here. It's explained after the # renderForm method. layout = [("firstName*", "middleInitial", "lastName*"), "email*", "address", ":password", ("gender", "age*"), "submit", ] class Register(ExamplePage, FormServlet): def __init__(self): ExamplePage.__init__(self) FormServlet.__init__(self, formDef) In ``__init__``, we invoke ``FormServlet.__init__`` with the form definition that this servlet handles, or a list of such form definitions. As always, we must also call ``__init__`` for each of the base classes. :: # continued... def writeContent(self): # .processForm() is inherited from FormServlet formProcessed, data = self.processForm() if formProcessed: self.renderForm() else: self.write('Thanks for signing up!') In ``writeContent`` we process the form and potentially render the form. ``.processForm()`` handles any form data that was submitted, potentially collecting errors, or if no form has yet been submitted it simply does nothing. It returns a tuple, ``(formProcessed, data)``. ``formProcessed`` is a boolean, and it tells you if a form was successfully submitted. I'll explain ``data`` later. If no form was submitted, we should render the form -- here we put the rendering in its own method. :: # continued... def renderForm(self): renderableForm = self.renderableForm() self.write(renderableForm.htFormLayout(layout, spacing=2)) In ``renderForm`` we attain a ``RenderableForm``, via the ``.renderableForm()`` method. The ``RenderableForm`` is essentially a wrapper around the ``FormDefinition``, with certain ephemeral values attached -- error messages, previously entered values, default values, and per-field options. Some of these values -- the default values and per-field options -- can optionally be passed to ``.renderableForm()`` to control the form. We use the layout we defined to pass to the ``RenderableForm.htFormLayout`` method. The layout is a list, where each item in the list is a row in the form. If you want multiple fields on one row, you use a sublist with the names of those fields. Another renderer is ``RenderableForm.htFormTable``, which needs no arguments, and displays the form in a simpler table, one field per row. We using ``spacing=2`` to space the fields on the line. You might notice that the ``password`` field was indicated with ``":password"`` -- since the ``password`` field was compound, it will already include it's only labels ("Password" and "Verify"), and the ``:`` suppresses the extra label that ``htFormLayout`` would put on it. Lastly, we use a ``*`` at the end of a name to add a red star, which implies to the user that the field is required. :: # continued... def register(self, fields): # ... do something to register user in database here ... # fields is a dictionary, filled from the form, field # names as keys. self.session().setValue('userInfo', fields) The ``register`` method is called because we passed the argument ``methodToInvoke="register"`` to the ``SubmitButton``. You can put multiple submit buttons in your form, each with a different method. Alternately, you can give no ``methodToInvoke``, and the fields will be passed back in ``data`` from ``.processForm()`` All your processing would happen in ``writeContent()`` in that case. ``fields`` is simple a dictionary, with field names for the keys and the user input for the values. More Complex Forms ================== Actually, in this section we will use the same form as before, except that we will allow a user to edit their record instead of just entering new information. (This example is located in ``FunFormKit/Examples/EditUser.py``) To do this we will use the same form definition, changing only the servlet code:: from FunFormKit.Form import * from WebKit.Examples.ExamplePage import ExamplePage from Register import formDef, layout class EditUser(ExamplePage, FormServlet): def __init__(self): ExamplePage.__init__(self) FormServlet.__init__(self, formDef) def writeContent(self): formProcessed, data = self.processForm() if formProcessed: self.write('You user information has been saved!') else: fields = self.session().value('userInfo', None) if not fields: self.write('You must Register first!') else: self.renderForm(fields) def renderForm(self, fields): # Normally the form definition will point to Register. # We set this special form option to change that: optionSet = {'form': {'handlerServletURL': 'EditUser'}} rf = self.renderableForm(defaults=fields, optionSet=optionSet) self.write(rf.htFormLayout(layout)) def register(self, fields): self.session().setValue('userInfo', fields) You'll notice we are able to reuse most of the form we used in ``Register``, and that this servlet looks very similar. The biggest change is in ``renderForm``, where we use options to change the form action, and where we set up default values. The ``defaults`` argument to ``renderableForm`` sets the defaults for the form -- it is simply a dictionary, with field names for the keys, and the default value for the value. Another common thing to do when setting up your renderable form is to make a dynamic select box. We don't have a dynamic select box in our example, but a select box looks like this:: formDef = FormDefinition(... [SelectField("section")]...) We didn't indicate the selections, so we'll have to do that when we render the form:: def renderForm(self): optionSet = {'section': {'selections': [('a', 'Section A'), ('b', 'Section B')]}} rf = self.renderableField(optionSet=optionSet) self.write(rf.htFormTable()) Hopefully that is self-explanatory. Creating Your Own Validator/Converters ====================================== While a number of validators are included already, you may very well want to make your own. I will go further -- you should create your own, as it is important *all* data validation occurs as a validator (otherwise your users won't get a chance to correct their mistakes). Consider the simple case where you want to validate that a username exists -- perhaps for a form that sends inter-user messages, or for a login page (this code can be seen in ``FunFormKit/Examples/Login.py``) We'll imagine you have a function ``usernameExists(username)`` which returns True or False. To create the validator:: from FunFormKit.Validator import ValidatorConverter class UsernameExists(ValidatorConverter): def validate(self, value): if usernameExists(value): return None else: return "That username does not exist" As you can see, the validator returns None when the value is valid and an error message when it isn't. This is a little backwards -- None is a false value after all, yet it means that the validation succeded. But success is more boring than failure. For intruction purposes, let's look up the user ID during this process. It's not nearly as important to do conversion as validation -- it's perfectly fine to do the conversion later -- but often the two go together. In this case, imagine a function ``usernameToUserID(username)``. We'll say that ``usernameToUserID`` returns None when the username does not exist, and we'll avoid using ``usernameExists`` altogether:: from FunFormKit.Validator import ValidatorConverter, InvalidField class UsernameToUserID(ValidatorConverter): def convert(self, username): userID = usernameToUserID(username) if userID is None: raise InvalidField, "That username does not exist" else: return userID As you see, when there's an error when converting the value we raise an exception (``Validator.InvalidField``) with an argument of the error message. We don't have to put both steps together -- we can chain the validator (``UsernameExists``) and the converter (``UsernameToUserID``) by using ``validators=[UsernameExists(), UsernameToUserID()]`` in our field definition. But sometimes it's more efficient to put steps together. Now, we want to know if the username matches the password. Imagine we have a function ``correctPassword(userID, password)``:: from FunFormKit import FormValidator class ValidatePassword(FormValidator): def validate(self, fieldDict): if correctPassword(fieldDict["username"], fieldDict["password"]): return None else: return {"password": "Password is incorrect"} The ``fieldDict`` is the dictionary of *processed* values for each field, so you'll get the user ID not the username (assuming we've used the ``UsernameToUserID`` validator/convert). We return None on success, or a string or dictionary on failure. If it's a string, the error shows up at the top of the form. If it's a dictionary, it gives errors for individual fields (the ``password`` field in this case). Now we need to create a form definition that includes the code we just created:: from FunFormKit.Form import FormDefinition from FunFormKit import Field formDef = FormDefinition( 'Login', [Field.TextField('username', maxLength=10, validators=[UsernameToUserID()]), Field.PasswordField('password', size=10, maxLength=20), Field.SubmitButton('submit', description='Login', methodToInvoke='login'), ], formValidators=[ValidatePassword()]) For More Information ==================== The `User Guide`_ presents a lot more information on all the fields, validators, and form validators that are included with FunFormKit. It also contains more detailed descriptions of usage and all available options. .. _`User Guide`: UserGuide.html