text: //
  // Jala Project [http://opensvn.csie.org/traccgi/jala]
  //
  // Copyright 2004 ORF Online und Teletext GmbH
  //
  // Licensed under the Apache License, Version 2.0 (the ``License'');
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //    http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an ``AS IS'' BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  //
  // $Revision: 304 $
  // $LastChangedBy: robert $
  // $LastChangedDate: 2007-10-02 17:26:46 +0200 (Die, 02 Okt 2007) $
  // $HeadURL: http://dev.orf.at/source/jala/trunk/code/Form.js $
  //
  
  
  /**
   * @fileoverview This class can be used to render forms and to validate
   * and store user submits. Further types of form components can be added
   * by subclassing jala.Form.Component.Input.
   */
  
  
  // Define the global namespace for Jala modules
  if (!global.jala) {
     global.jala = {};
  }
  
  /**
   * HelmaLib dependencies
   */
  app.addRepository("modules/core/Object.js");
  app.addRepository("modules/helma/Html.js");
  app.addRepository("modules/helma/Http.js");
  
  /**
   * Jala dependencies
   */
  app.addRepository(getProperty("jala.dir", "modules/jala") + "/code/I18n.js");
  
  /**
   * Constructs a new Form instance
   * @class A class that renders forms, validates submitted form data and
   * stores the data in a specified object.
   * @param {String} name The name of the form
   * @param {Object} dataObj An optional object used to retrieve values
   * to display in the form input fields contained in this Form instance.
   * @returns A newly created Form instance
   * @constructor
   */
  jala.Form = function(name, dataObj) {
  
     /**
      * Private field containing the tracker used during validation
      * @type jala.Form.Tracker
      * @private
      */
     var tracker = undefined;
     
     /**
      * Private field containing all components of this form
      * @type Array
      * @private
      */
     var components = [];
  
     /**
      * Private field containing the CSS class name of this form instance.
      * @type String
      */
     var className;
  
     /**
      * Private field containing the default error message
      * @type String
      * @private
      */
     var errorMessage;
  
     /**
      * Readonly reference to the name of the form
      * @type String
      */
     this.name = name;     // for doc purposes only, readonly-access through the getter function
     this.__defineGetter__("name", function() {   return name;   });
  
     /**
      * Sets the data object which is being edited by this form. This object
      * is used to get the default values when first printing the form and 
      * - if no other object is provided - receives the changed values in save.
      * @param {Object} dataObj The object which is being edited by this form.
      * @see #save
      */
     this.setDataObject = function(newDataObj) {
        dataObj = newDataObj;
        return;
     };
  
     /**
      * Returns the data object containing the values used
      * for rendering the form.
      * @returns The data object of this jala.Form instance
      */
     this.getDataObject = function() {
        return dataObj;
     };
  
  
     /**
      * The default getter function for this form. Unless a getter
      * is specified for the component, this function is called
      * to retrieve the original value of a field.
      * When called, the scope is set to the data object and
      * the name of the element is the sole argument.
      * @see jala.Form.Component.Input#getValue
      * @type Function
      */
     this.getter;      // for doc purposes only
  
     // that's where the value really is stored:
     var getter = jala.Form.propertyGetter;
  
     this.__defineGetter__("getter", function() {   return getter;   });
     this.__defineSetter__("getter", function(newGetter) {
        if (newGetter instanceof Function) {
           getter = newGetter;
        }
     });
  
  
     /**
      * The default setter function for this form. Unless a getter
      * is specified for the component, this function is called to
      * store the a value of a field.
      * When called, the scope is set to the data object and
      * the name and value of the element are provided as arguments.
      * @see jala.Form.Component.Input#setValue
      * @type Function
      */
     this.setter;      // for doc purposes only
  
     // that's where the value really is stored:
     var setter = jala.Form.propertySetter;
  
     this.__defineGetter__("setter", function() {   return setter;   });
     this.__defineSetter__("setter", function(newSetter) {
        if (newSetter instanceof Function) {
           setter = newSetter;
        }
     });
  
     var tracker = undefined;
     
     /**
      * Sets the tracker object this form instance uses for collecting
      * error messages and parsed values.
      * @param {jala.Form.Tracker} newTracker
      */
     this.setTracker = function(newTracker) {
        if (newTracker instanceof jala.Form.Tracker){
           tracker = newTracker;
        }
        return;
     };
  
     /**
      * Returns the tracker object this form instance uses for collecting
      * error messages and parsed values.
      * @returns tracker object
      * @type jala.Form.Tracker
      */
     this.getTracker = function() {
        return tracker;
     };
  
     /**
      * Contains the default component skin
      * @type Skin
      */
     this.componentSkin = createSkin("<% param.error prefix=' ' suffix='\n' %><% param.label prefix=' ' suffix='\n' %><% param.controls prefix=' ' suffix='\n' %><% param.help prefix=' ' suffix='\n' %>");
  
     /**
      * Contains a map of component objects.
      * @type Object
      */
     this.components = {};
  
     /**
      * Returns an array containing the components
      * of this jala.Form instance.
      * @returns The components of this jala.Form instance.
      * @type Array
      */
     this.listComponents = function() {
        return components;
     };
  
     /**
      * Adds a component to this jala.Form instance
      * @param {jala.Form.Component.Input} component
      */
     this.addComponent = function(component) {
        component.setForm(this);
        components.push(component);
        this.components[component.name] = component;
        return;
     };
  
     /**
      * Returns true if this instance of jala.Form contains at least
      * one component doing a file upload.
      * @see jala.Form.Component#containsFileUpload
      * @type Boolean
      */
     this.containsFileUpload = function() {
        for (var i=0; i<components.length; i++) {
           if (components[i].containsFileUpload() == true) {
              return true;
           }
        }
        return false;
     };
  
     /**
      * Returns the class name set for this form instance.
      * @returns class name
      * @type String
      */
     this.getClassName = function() {
        return className;
     };
  
     /**
      * Sets an extra classname for this form instance
      * @param {String} newClassName new classname
      */
     this.setClassName = function(newClassName) {
        className = newClassName;
        return;
     };
  
     /**
      * Returns the general error message printed above the form
      * if any of the components didn't validate.
      * @returns error message
      * @type String
      */
     this.getErrorMessage = function() {
        return errorMessage;
     };
  
     /**
      * Sets the general error message printed above the form if any
      * of the components didn't validate.
      * @param {String} newErrorMessage error message
      */
     this.setErrorMessage = function(newErrorMessage) {
        errorMessage = newErrorMessage;
        return;
     };
  
     /**
      * Returns true if this instance of jala.Form holds a jala.Form.Tracker
      * instance and at least one error has been set on this tracker.
      * @returns true if an error has been encountered.
      * @type Boolean
      */
     this.hasError = function() {
        if (tracker) {
           return tracker.hasError();
        }
        return false;
     };
  
     /**
      * If this instance of jala.Form holds a jala.Form.Tracker
      * instance it returns the number of components that didn't
      * validate.
      * @returns Number of components that didn't validate.
      * @type Number
      */
     this.countErrors = function() {
        if (tracker) {
           return tracker.countErrors();
        }
        return 0;
     };
  
     /**
      * Main constructor body
      */
     if (!dataObj) {
        dataObj = {};
     }
  
     return this;
  };
  
  /** @ignore */
  jala.Form.prototype.toString = function() {
     return "[jala.Form]";
  };
  
  /**
   * The HTML renderer used by jala.Form
   * @type helma.Html
   */
  jala.Form.html = new helma.Html();
  
  /**
   * Constant used by require function to define that a component
   * should not validate if userinput is shorter than a given length.
   * Value: "minlength"
   * @type String
   * @final
   */
  jala.Form.MINLENGTH     = "minlength";
  
  /**
   * Constant used by require function to define that a component
   * should not validate if userinput exceeds a maximum length.
   * Value: "maxlength"
   * @type String
   * @final
   */
  jala.Form.MAXLENGTH     = "maxlength";
  
  /**
   * Constant used by require function to define that a component
   * should validate only if the user did provide input.
   * Value: "require"
   * @type String
   * @final
   */
  jala.Form.REQUIRE       = "require";
  
  /**
   * Constant used by require function to define that a select or
   * radio component should validate only if the user input is contained
   * in the list of options provided.
   * Value: "checkoptions"
   * @type String
   * @final
   */
  jala.Form.CHECKOPTIONS  = "checkoptions";
  
  /**
   * Constant used by require function to define that a file upload
   * component should validate only if the file's content type is
   * in the list of allowed content types provided.
   * Value: "contenttype"
   * @type String
   * @final
   */
  jala.Form.CONTENTTYPE   = "contenttype"; 
  
  /**
   * Constant used by require function to define that an image upload
   * component should validate only if the image's width is less than
   * the value provided.
   * Value: "maxwidth"
   * @type String
   * @final
   */
  jala.Form.MAXWIDTH      = "maxwidth";
  
  /**
   * Constant used by require function to define that an image upload
   * component should validate only if the image's width is more than
   * the value provided.
   * Value: "minwidth"
   * @type String
   * @final
   */
  jala.Form.MINWIDTH      = "minwidth";
  
  /**
   * Constant used by require function to define that an image upload
   * component should validate only if the image's height is less than
   * the value provided.
   * Value: "maxheight"
   * @type String
   * @final
   */
  jala.Form.MAXHEIGHT     = "maxheight";
  
  /**
   * Constant used by require function to define that an image upload
   * component should validate only if the image's height is more than
   * the value provided.
   * Value: "min-height"
   * @type String
   * @final
   */
  jala.Form.MINHEIGHT     = "minheight";
  
  /**
   * Utility to set up the prototype, constructor, superclass and superconstructor
   * properties to support an inheritance strategy that can chain constructors and methods.
   * @param {Function} subClass the object which inherits superClass' functions
   * @param {Function} superClass the object to inherit
   */
  jala.Form.extend = function(subClass, superClass) {
     var f = function() {};
     f.prototype = superClass.prototype;
  
     subClass.prototype = new f();
     subClass.prototype.constructor = subClass;
     subClass.superClass = superClass.prototype;
     subClass.superConstructor = superClass;
     return;
  };
  
  /**
   * Parses a plain javascript object tree and configures a
   * new jala.Form instance according to the properties.
   * Propertynames are matched with constants and setter-functions,
   * the property "type" is used to create new component objects.
   * @param {Object} config object tree containing config
   * @returns A newly created jala.Form instance based on the config specified
   * @type jala.Form
   */
  jala.Form.create = function(config, dataObj) {
     if (!config || !config.name || !config.components) {
        return null;
     }
     var form = new jala.Form(config.name, dataObj);
     if (config.legend) {
        form.setLegend(config.legend);
     }
     if (config.className) {
        form.setClassName(config.className);
     }
     if (config.errorMessage) {
        form.setErrorMessage(config.errorMessage);
     }
     if (config.getter) {
        form.getter = config.getter;
     }
     if (config.setter) {
        form.setter = config.setter;
     }
     if (config.components) {
        jala.Form.createComponents(form, config.components);
     }
     return form;
  };
  
  /**
   * Parses an array of plain js objects and tries to create components.
   * @param {jala.Form | jala.Form.Component.Fieldset} container
   *        Object whose addComponent method is used for adding new components.
   * @param {Array} arr Array of plain javascript objects used as config.
   * @private
   */
  jala.Form.createComponents = function(container, arr) {
     var form = (container.form) ? container.form : container;
     var components = [];
     var element;
     for (var i=0; i<arr.length; i++) {
        element = arr[i];
        var clazzName = (element["type"]) ? element["type"].titleize() : "Input";
        var constr = jala.Form.Component[clazzName];
        if (!constr) {
           // invalid constructor:
           var logStr = "jala.Form encountered unknown component type " + element["type"] + " in config of form ";
           logStr += (container.form) ? container.form.name : container.name;
           app.log(logStr);
           continue;
        }
        var name = element.name;
        if (!name && element.label) {
           name = element.label.toAlphanumeric().toLowerCase();
        } else if (!name && constr == jala.Form.Component.Fieldset) {
           var str = "fieldset";
           while(container.components[str]) {
              str += "1";
           }
           name = str;
        } else if (!name) {
           // couldn't find a name for the component:
           var logStr = "jala.Form encountered component of type " + clazzName.toLowerCase() + " without name or label property in config of form ";
           logStr += (container.form) ? container.form.name : container.name;
           app.log(logStr);
           continue;
        }
        var component = new constr(name);
        component.setForm(form);
        for (var key in element) {
           switch(key) {
              case "name":
              case "type":
                 break;
              case "messages":
                 for (var msgName in element[key]) {
                    component.setMessage(msgName, element[key][msgName]);
                 }
                 break;
              case "components":
                 jala.Form.createComponents(component, element[key]);
                 break;
              case "getter":
              case "setter":
              case "validator":
                 component[key] = element[key];
                 break;
              case jala.Form.REQUIRE:
                 component.require(key, element[key]);
                 break;
              default:
                 // check if key matches a constant:
                 if (jala.Form[key.toUpperCase()] == key.toLowerCase()) {
                    component.require(key.toLowerCase(), element[key]);
                 } else {
                    // call setter functions for all fields from config object:
                    // note: String.prototype.titleize from the helma.core module
                    // would uppercase the first letter, but lowercases all ensuing
                    // characters (maxLength would become Maxlength).
                    // note: use try/catch to detect if the setter method really exists
                    // because a check using if(component[method]) would fail for
                    // inherited methods even though executing the inherited method works.
                    try {
                       component["set" + key.charAt(0).toUpperCase() + key.substring(1)](element[key]);
                    } catch (e) {
                       // invalid field for this component
                       app.log("jala.Form encountered unknown field " + key + " in config of form " + component.form.name);
                    }
                 }
                 break;
           }
        }
        container.addComponent(component);
     }
     return;
  };
  
  /**
   * Static validator function to test values for being a valid email address.
   * @param {String} name name of the property being validated.
   * @param {String} value value in form input
   * @param {Object} reqData the whole request-data-object,
             in case properties depend on each other
   * @param {jala.Form} formObj instance of jala.Form
   * @returns Error message or null
   * @type String
   */
  jala.Form.isEmail = function(name, value, reqData, formObj) {
     if (value && !value.trim().isEmail()) {
        return "Please enter a valid email address.";
     }
     return null;
  };
  
  /**
   * Static validator function to test values for being a valid url.
   * @param {String} name name of the property being validated.
   * @param {String} value value in form input
   * @param {Object} reqData the whole request-data-object,
             in case properties depend on each other
   * @param {jala.Form} formObj instance of jala.Form
   * @returns Error message or null
   * @type String
   */
  jala.Form.isUrl = function(name, value, reqData, formObj) {
     if (value && !helma.Http.evalUrl(value)) {
        return "Please enter a valid URL (web address).";
     }
     return null;
  };
  
  /**
   * Renders the opening form tag
   * @private
   */
  jala.Form.prototype.renderFormOpen = function() {
     var formAttr = {
        id     : this.createDomId(),
        name   : this.name,
        action : (req.action == "main") ? "" : req.action,
        method : "post"
     };
     var className = this.getClassName();
     if (className) {
        formAttr["class"] = className;
     }
     if (this.containsFileUpload()) {
        // if there is an upload element, use multipart-enctype
        formAttr.enctype = "multipart/form-data";
     }
     jala.Form.html.openTag("form", formAttr);
     return;
  };
  
  /**
   * Renders this form including all components to response.
   */
  jala.Form.prototype.render = function() {
  
     this.renderFormOpen();
     res.write("\n");
     
     // print optional general error message
     var errorMessage = this.getErrorMessage();
     if (this.hasError() && errorMessage) {
        jala.Form.html.element(
           "div",
           gettext(errorMessage, this.countErrors()),
           {id: this.createDomId("error"), "class": "formError"}
        );
        res.write("\n");
     }
  
     // loop through elements
     var components  = this.listComponents();
     for (var i=0; i<components.length; i++) {
        components[i].render();
     }
  
     jala.Form.html.closeTag("form");
     return;
  };
  
  /**
   * renders the form as a string
   * @returns rendered form
   * @type String
   */
  jala.Form.prototype.renderAsString = function(param) {
     res.push();
     this.render(param);
     return res.pop();
  };
  
  /**
   * Creates a DOM identifier based on the arguments passed. The
   * resulting Id will be prefixed with the name of the form.
   * All arguments will be chained using camel casing.
   * @returns The DOM Id
   * @type String
   */
  jala.Form.prototype.createDomId = function(/* [part1][, part2][, ...] */) {
     res.push();
     res.write(this.name.charAt(0).toLowerCase());
     res.write(this.name.substring(1));
     for (var i=0;i<arguments.length;i++) {
        if (arguments[i]) {
           res.write(arguments[i].charAt(0).toUpperCase());
           res.write(arguments[i].substring(1));
        }
     }
     return res.pop();
  };
  
  /**
   * Validates user input from a submitted form by calling each
   * component's validate method.
   * @param {Object} reqData Optional submitted form data. If not specified
   * req.data is used.
   * @returns tracker object with error fields set.
   * @type jala.Form.Tracker
   */
  jala.Form.prototype.validate = function(reqData) {
     var tracker = new jala.Form.Tracker(reqData || req.data);
     var components = this.listComponents();
     for (var i=0; i<components.length; i++) {
        components[i].validate(tracker);
     }
     this.setTracker(tracker);
     return tracker;
  };
  
  /**
   * Sets the parsed values on an object. By default the internally 
   * stored tracker and data objects are used, but those may be 
   * overridden here.
   * @param {jala.Form.Tracker} tracker (optional) tracker object
   *       holding parsed data from form input.
   * @param {Object} destObj (optional) object whose values will be changed.
   *       By default the dataObj passed to the constructor or to
   *       setDataObject is used.
   */
  jala.Form.prototype.save = function(tracker, destObj) {
     tracker = tracker || this.getTracker();
     destObj = destObj || this.getDataObject();
     var components = this.listComponents();
     for (var i=0; i<components.length; i++) {
        components[i].save(tracker, destObj);
     }
     return;
  };
  
  /**
   * Parses form input, applies check functions and stores the values
   * if the form does validate. Otherwise this method returns false
   * without saving so that the form can be reprinted with error messages.
   * @param {Object} reqData input from form
   * @param {Object} destObj object whose values should be chanegd
   * @returns False if one of the checks failed,
   *          true if the element was saved correctly.
   * @type Boolean
   */
  jala.Form.prototype.handle = function(reqData, destObj) {
     var tracker = this.validate(reqData);
     if (tracker.hasError()) {
        return false;
     } else {
        this.save(tracker, destObj);
        return true;
     }
  };
  
  /**
   * Renders the whole form to response
   */
  jala.Form.prototype.render_macro = function() {
     this.render();
     return;
  };
  
  /**
   * Returns the id (equal to the name) of the form
   * @returns The id of this Form instance
   * @type String
   */
  jala.Form.prototype.id_macro = function() {
     res.write(this.name);
     return;
  };
  
  /**
   * Returns the name (equal to the id) of the form
   * @returns The name of this Form instance
   * @type String
   */
  jala.Form.prototype.name_macro = function() {
     res.write(this.name);
     return;
  };
  
  /**
   * Returns the class name of the form
   * @returns The class name of this Form instance
   * @type String
   */
  jala.Form.prototype.class_macro = function() {
     var className = this.getClassName();
     if (className) {
        res.write(className);
     }
     return;
  };
  
  
  
  /**
   * Writes the form opening tag to response
   */
  jala.Form.prototype.open_macro = function() {
     this.renderFormOpen();
     return;
  };
  
  /**
   * Writes the form closing tag to response
   */
  jala.Form.prototype.close_macro = function() {
     jala.Form.html.closeTag("form");
     return;
  };
  
  /**
   * The abstract base class for all components.
   * @constructor
   */
  jala.Form.Component = function Component(name) {
  
     if (!name) {
        throw "jala.Form.Component: missing component name";
     }
     
     /**
      * The Form this component belongs to
      * @type jala.Form
      * @private
      */
     var form;
  
     /**
      * Private field containing the CSS class name of this component
      * @type String
      */
     var className;
     
     /**
      * Readonly reference to name of component
      * @type String
      */
     this.name;     // for doc purposes only, readonly-access is through the getter function
     this.__defineGetter__("name", function() {   return name;   });
  
     
     /**
      * Readonly reference to instance of jala.Form.
      * @type jala.Form
      */
     this.form;     // for doc purposes only, readonly-access through the getter function
     this.__defineGetter__("form", function() {   return form;   });
     
     /**
      * Attaches this component to an instance of jala.Form.
      * @param {jala.Form} newForm form object
      * @private
      */
     this.setForm = function(newForm) {
        form = newForm;
        return;
     };
  
     /**
      * Returns the type of component. This is the lowercase'd name of the
      * constructor function.
      * @type String
      */
     this.getType = function() {
        return this.constructor.name.toLowerCase();
     };
  
     /**
      * Returns the class name set for this component.
      * @returns class name
      * @type String
      */
     this.getClassName = function() {
        return className;
     };
  
     /**
      * Sets an extra classname for this component
      * @param {String} newClassName new classname
      */
     this.setClassName = function(newClassName) {
        className = newClassName;
        return;
     };
  
     /**
      * Function defining wheter a component contains a file upload or not.
      * This value is used to render a form tag with the attribute
      * enctype=multipart/form-data.
      * Subclasses of jala.Form.Component that use a file upload element,
      * have to override this function and let it return true.
      * @type Boolean
      */
     this.containsFileUpload = function() {
        return false;
     };
     
     return this;
  };
  
  /** @ignore */
  jala.Form.Component.prototype.toString = function() {
     return "[" + this.constructor.name + " component '" + this.name + "']";
  };
  
  /**
   * Creates a DOM identifier based on the name of the form,
   * the name of the component and an additional string.
   * The items will be chained using camel casing.
   * @param {String} idPart Optional string appended to component's id.
   * @returns The DOM Id
   * @type String
   */
  jala.Form.Component.prototype.createDomId = function(idPart) {
     return this.form.createDomId(this.name, idPart);
  }
  
  /**
   * Function to render a component.
   * Subclasses of jala.Form.Component may override this function.
   */
  jala.Form.Component.prototype.render = function() {
     return;
  };
  
  /**
   * Function to validate a component.
   * Subclasses of jala.Form.Component may override this function.
   * @param {jala.Form.Tracker} tracker object tracking errors and holding
   *    parsed values and request data.
   */
  jala.Form.Component.prototype.validate = function(tracker) {
     return tracker;
  };
  
  /**
   * Function to save the data of a component.
   * Subclasses of jala.Form.Component may override this function.
   */
  jala.Form.Component.prototype.save = function(destObj, val) {
     return;
  };
  
  /**
   * Constructs a new Fieldset instance
   * @class Instances of this class represent a form fieldset containing
   * numerous form components
   * @param {String} name The name of the fieldset
   * @returns A newly created Fieldset instance
   * @constructor
   */
  jala.Form.Component.Fieldset = function Fieldset(name) {
     jala.Form.Component.Fieldset.superConstructor.apply(this, arguments);
     
     /**
      * Private field containing the components of this fieldset
      * @type Array
      * @private
      */
     var components = [];
  
     /**
      * Private field containing the legend of this fieldset
      * @type String
      * @private
      */
     var legend;
  
     /**
      * Contains a map of all component objects of this fieldset
      */
     this.components = {};
  
     /**
      * Returns an array containing the components
      * of this jala.Form.Component.Fieldset instance.
      * @returns The components of this jala.Form instance.
      * @type Array
      */
     this.listComponents = function() {
        return components;
     };
  
     /**
      * Adds a component to this jala.Form.Component.Fieldset instance
      * @param {jala.Form.Component.Input} component
      */
     this.addComponent = function(component) {
        component.setForm(this.form);
        components.push(component);
        this.components[component.name] = component;
        return;
     };
  
     /**
      * Returns true if this instance of jala.Form.Component.Fieldset
      * contains at least one component doing a file upload.
      * @see jala.Form.Component#containsFileUpload
      * @type Boolean
      */
     this.containsFileUpload = function() {
        for (var i=0; i<components.length; i++) {
           if (components[i].containsFileUpload() == true) {
              return true;
           }
        }
        return false;
     };
  
     /**
      * Returns the legend of the fieldset.
      * @returns legend
      * @type String
      */
     this.getLegend = function() {
        return legend;
     };
  
     /**
      * Sets the legend text.
      * @param {String} newLegend legend to use when printing the fieldset.
      */
     this.setLegend = function(newLegend) {
        legend = newLegend;
        return;
     };
  
     /**
      * Attaches this fieldset and all components to an instance of
      * jala.Form.
      * @param {jala.Form} newForm form object
      * @private
      */
     this.setForm = function(newForm) {
        form = newForm;
        for (var i=0; i<components.length; i++) {
           components[i].setForm(newForm);
        }
        return;
     };
  
  };
  // extend jala.Form.Component
  jala.Form.extend(jala.Form.Component.Fieldset, jala.Form.Component);
  
  /**
   * Renders all components within the fieldset.
   */
  jala.Form.Component.Fieldset.prototype.render = function() {
  
     var attr = {};
     var className = this.getClassName();
     if (className) {
        attr["class"] = className;
     }
     jala.Form.html.openTag("fieldset", attr);
     res.write("\n");
  
     // optional legend
     var legend = this.getLegend();
     if (legend != null) {
        res.write(" ");
        jala.Form.html.element("legend", legend);
        res.write("\n");
     }
  
     // loop through elements
     var components  = this.listComponents();
     for (var i=0; i<components.length; i++) {
        components[i].render();
     }
  
     jala.Form.html.closeTag("fieldset");
     res.write("\n");
     return;
  };
  
  /**
   * Validates all components within the fieldset.
   * @param {jala.Form.Tracker} tracker
   */
  jala.Form.Component.Fieldset.prototype.validate = function(tracker) {
     var components  = this.listComponents();
     for (var i=0; i<components.length; i++) {
        components[i].validate(tracker);
     }
     return;
  };
  
  /**
   * Saves all components within the fieldset.
   * @param {jala.Form.Tracker} tracker
   * @param {Object} destObj
   */
  jala.Form.Component.Fieldset.prototype.save = function(tracker, destObj) {
     var components  = this.listComponents();
     for (var i=0; i<components.length; i++) {
        components[i].save(tracker, destObj);
     }
     return;
  };
  
  /**
   * @class Subclass of jala.Form.Component that allows rendering a skin
   * within a form.
   * @base jala.Form.Component
   * @param {String} name The name of the component, used as the name of the skin
   * @returns A newly created Skin component instance
   * @constructor
   */
  jala.Form.Component.Skin = function Skin(name) {
     jala.Form.Component.Skin.superConstructor.apply(this, arguments);
     
     /**
      * Private field containing the handler object
      */
     var handler = undefined;
     
     /**
      * Returns the handler object for the skin.
      * @type Object
      */
     this.getHandler = function() {
        return handler;
     };
  
     /**
      * Sets the handler to use when rendering the skin.
      * By default, the form's data object is used a handler.
      * @param {Object} newHandler new skin handler object.
      */
     this.setHandler = function(newHandler) {
        handler = newHandler;
        return;
     };
  
     return this;
  };
  // extend jala.Form.Component
  jala.Form.extend(jala.Form.Component.Skin, jala.Form.Component);
  
  /**
   * Renders the skin named by this component to the response.
   */
  jala.Form.Component.Skin.prototype.render = function() {
     var handler = this.getHandler() || this.form.getDataObject();
     if (handler != null && handler instanceof HopObject) {
        handler.renderSkin(this.name, this);
     } else {
        res.write("Skin component '" + this.name + "' unhandled");
     }
     return;
  };
  
  /**
   * Creates a new input component instance.
   * @class Instances of this class represent a single form input field.
   * @param {String} name Name of the component, used as name of the html control.
   * @constructor
   */
  jala.Form.Component.Input = function Input(name) {
     jala.Form.Component.Input.superConstructor.apply(this, arguments);
  
     /**
      * Private map containing the requirements that need to be met
      */
     var requirements = {};
     
     /**
      * Private map containing messages to use when a requirement is not met
      */
     var messages = {};
  
     /**
      * Private field containing the label of this component
      * @type String
      */
     var label;
  
     /**
      * Private field containing the help text of this component
      * @type String
      */
     var help;
     
     /**
      * Sets a requirement for this component.
      * If function is called without arguments, jala.Form.REQUIRE
      * is set to true.
      * @param {String} key String defining the type of requirement,
      *             constants in jala.Form may be used.
      * @param {Object} val Value of the requirement.
      * @param {String} msg Optional error message if requirement
      *             is not fulfilled.
      */
     this.require = function(key, val, msg) {
        if (arguments.length == 0) {
           // set default value for arguments
           key = jala.Form.REQUIRE;
           val = true;
        }
        requirements[key] = val;
        if (msg) {
           this.setMessage(key, msg);
        }
        return;
     };
  
     /**
      * Returns the requirement value for a given key.
      * @param {String} key String defining the type of requirement,
      *             constants in jala.Form may be used.
      * @type Object
      */
     this.getRequirement = function(key) {
        return requirements[key];
     };
  
     /**
      * Sets a custom error message
      * @param {String} key String defining the type of requirement,
      *             constants in jala.Form may be used.
      * @param {String} msg Error message
      */
     this.setMessage = function(key, msg) {
        messages[key] = msg;
        return;
     };
    
     /**
      * Returns a specific message for a config element.
      * @param {String} key The key of the message as defined by
      *          the constants in jala.Form.* (e.g. "require",
      *          "maxlength", "minlength" ...
      * @param {String} defaultMsg the message to use when no message
      *          was defined.
      * @param {Object} args One or more arguments passed to the gettext
      * message processor which will replace {0}, {1} etc.
      * @returns rendered message
      * @type String
      */
     this.getMessage = function(key, defaultMsg, args) {
        var arr = [(messages[key]) ? messages[key] : defaultMsg];
        for (var i=2; i<arguments.length; i++) {
           arr.push(arguments[i]);
        }
        return gettext.apply(null, arr);
     };
  
     /**
      * Returns the label set for this component.
      * @returns label
      * @type String
      */
     this.getLabel = function() {
        return label;
     };
  
     /**
      * Sets the label for this component
      * @param {String} newLabel new label
      */
     this.setLabel = function(newLabel) {
        label = newLabel;
        return;
     };
  
     /**
      * Returns the help text set for this component.
      * @returns help text
      * @type String
      */
     this.getHelp = function() {
        return help;
     };
  
     /**
      * Sets the help text for this component
      * @param {String} newHelp new help text
      */
     this.setHelp = function(newHelp) {
        help = newHelp;
        return;
     };
  
     /**
      * The getter function for this component. If set, the
      * function is called to retrieve the original value of the
      * field. When called, the scope is set to the data object and
      * the name of the element is the sole argument.
      * @see #getValue
      * @type Function
      */
     this.getter;      // for doc purposes only
  
     // that's where the values really are stored:
     var getter, setter, validator;
  
     this.__defineGetter__("getter", function() {   return getter;   });
     this.__defineSetter__("getter", function(newGetter) {
        if (newGetter instanceof Function) {
           getter = newGetter;
        } else {
           throw "Invalid argument: getter must be a function";
        }
        return;
     });
  
     /**
      * The setter function for this component. If set, the
      * function is called to store the new value of the
      * field. When called, the scope is set to the data object and
      * the name and value of the element are provided as arguments.
      * @see #setValue
      * @type Function
      */
     this.setter;      // for doc purposes only
  
     this.__defineGetter__("setter", function() {   return setter;   });
     this.__defineSetter__("setter", function(newSetter) {
        if (newSetter instanceof Function) {
           setter = newSetter;
        } else {
           throw "Invalid argument: setter must be a function";
        }
        return;
     });
  
     /**
      * The validator function for this component. If set, the
      * function is called with the scope set to the data object
      * and with four arguments:
      * <li>the name of the element</li>
      * <li>the parsed value of the element if all requirements have
      *     been fulfilled. E.g., for a date editor, the parsed value would
      *     be a date object.</li>
      * <li>the map containing all user inputs as string (req.data)</li>
      * <li>the form object</li>
      * @see #validate
      * @type Function
      */
     this.validator;
  
     this.__defineGetter__("validator", function() {   return validator;   });
     this.__defineSetter__("validator", function(newValidator) {
        if (newValidator instanceof Function) {
           validator = newValidator;
        } else {
           throw "Invalid argument: validator must be a function";
        }
        return;
     });
  
     return this;
  };
  // extend jala.Form.Component
  jala.Form.extend(jala.Form.Component.Input, jala.Form.Component);
  
  /**
   * Validates the input provided to this component. First,
   * checkRequirements is called. If no error occurs, the input
   * is parsed using parseValue and passed on to the validator
   * function.
   * @see #checkRequirements
   * @see #parseValue
   * @param {jala.Form.Tracker} tracker Tracker object collecting
   *       request data, error messages and parsed values.
   */
  jala.Form.Component.Input.prototype.validate = function(tracker) {
     var error = this.checkRequirements(tracker.reqData);
     if (error != null) {
        tracker.errors[this.name] = error;
     } else {
        tracker.values[this.name] = this.parseValue(tracker.reqData);
        if (this.validator) {
           error = this.validator.call(
              this.form.getDataObject(),
              this.name,
              tracker.values[this.name],
              tracker.reqData,
              this.form
           );
           if (error != null) {
              tracker.errors[this.name] = error;
           }
        }
     }
     return;
  };
  
  /**
   * Saves the parsed value using setValue.
   * @see #setValue
   * @param {jala.Form.Tracker} tracker Tracker object collecting
   *       request data, error messages and parsed values.
   * @param {Object} destObj (optional) object whose values will be changed.
   */
  jala.Form.Component.Input.prototype.save = function(tracker, destObj) {
     this.setValue(destObj, tracker.values[this.name]);
     return;
  };
  
  /**
   * Retrieves the property which is edited by this component.
   * <ul>
   * <li>If no getter is given, the method returns the primitive property
   *    of the data object with the same name as the component.</li>
   * <li>If a getter function is defined, it is executed with the scope
   *    of the data object and the return value is used as default value.
   *    The name of the component is passed to the getter function
   *    as an argument.</li>
   * </ul>
   * @returns The value of the property
   * @type String|Number|Date
   */
  jala.Form.Component.Input.prototype.getValue = function() {
     if (this.form.getTracker()) {
        // handling re-rendering
        return null;
     } else {
        var getter = (this.getter) ? this.getter : this.form.getter;
        return getter.call(this.form.getDataObject(), this.name);
     }
  };
  
  /**
   * Sets a property of the object passed as argument to the given value.
   * <li>If no setter is set at the component, the primitive property
   *    of the data object is changed.</li>
   * <li>If a setter function is defined it is executed with the data object
   *    as scope and with the name and new value provided as arguments</li>
   * <li>If the setter is explicitly set to null, no changes are made at all.</li>
   * @param {Object} destObj (optional) object whose values will be changed.
   * @param {Object} value The value to set the property to
   * @returns True in case the update was successful, false otherwise.
   * @see jala.Form#setter
   */
  jala.Form.Component.Input.prototype.setValue = function(destObj, value) {
     // default value for this.setter is undefined, so if it has been
     // set to explicitly null, we don't save the value. in this case,
     // we assume, the property is handled outside of jala.Form or purposely
     // ignored at all.
     if (this.setter !== null) {
        var setter = (this.setter) ? this.setter : this.form.setter;
        setter.call(destObj, this.name, value);
     }
     return;
  };
  
  /**
   * Renders this component including label, error and help messages directly
   * to response.
   */
  jala.Form.Component.Input.prototype.render = function() {
     var className = (this.getRequirement(jala.Form.REQUIRE) == true) ? "require" : "optional";
     if (this.getClassName()) {
        className += " " + this.getClassName();
     }
     var tracker = this.form.getTracker();
     if (tracker && tracker.errors[this.name]) {
        className += " error";
     }
  
     jala.Form.html.openTag("div",
        {id: this.createDomId(),
         "class": "component " + className}
     );
     res.write("\n");
     renderSkin(this.form.componentSkin, this);
     jala.Form.html.closeTag("div");
     res.write("\n");
     return;
  };
  
  /**
   * If the error tracker holds an error message for this component,
   * it is wrapped in a div-tag and returned as a string.
   * @returns Rendered string
   * @type String
   */
  jala.Form.Component.Input.prototype.renderError = function() {
     var tracker = this.form.getTracker();
     if (tracker && tracker.errors[this.name]) {
        return jala.Form.html.elementAsString("div",
           tracker.errors[this.name],
           {"class": "errorText"}
        );
     }
     return null;
  };
  
  /**
   * Returns the rendered label of this component
   * @returns The rendered label of this component
   * @type String
   */
  jala.Form.Component.Input.prototype.renderLabel = function() {
     return jala.Form.html.elementAsString(
        "label",
        this.getLabel() || "",
        {"for": this.createDomId("control")}
     );
  };
  
  /**
   * If this component contains a help message, it is wrapped in
   * a div-tag and returned as a string.
   * @returns The rendered help message
   * @type String
   */
  jala.Form.Component.Input.prototype.renderHelp = function() {
     var help = this.getHelp();
     if (help) {
        return jala.Form.html.elementAsString(
           "div",
           help,
           {"class": "helpText"}
        );
     }
     return null;
  };
  
  
  /**
   * Renders this component including label, error and help messages
   * directly to response
   */
  jala.Form.Component.Input.prototype.render_macro = function() {
     this.render();
     return;
  };
  
  /**
   * Renders the control(s) of this component
   */
  jala.Form.Component.Input.prototype.controls_macro = function() {
     var attr = this.getControlAttributes();
     var tracker = this.form.getTracker()
     if (tracker) {
        this.renderControls(attr, null, tracker.reqData);
     } else {
        this.renderControls(attr, this.getValue());
     }
     return;
  };
  
  /**
   * Renders this component's error message (if set) directly to response
   */
  jala.Form.Component.Input.prototype.error_macro = function() {
     res.write(this.renderError());
     return;
  };
  
  /**
   * Renders this component's label.
   */
  jala.Form.Component.Input.prototype.label_macro = function() {
     res.write(this.renderLabel());
     return;
  };
  
  /**
   * Renders this component's help text, if set.
   */
  jala.Form.Component.Input.prototype.help_macro = function() {
     res.write(this.renderHelp());
     return;
  };
  
  /**
   * Renders this component's id
   * @see jala.Form#createDomId
   */
  jala.Form.Component.Input.prototype.id_macro = function() {
     res.write(this.createDomId());
     return;
  };
  
  /**
   * Renders this component's name
   */
  jala.Form.Component.Input.prototype.name_macro = function() {
     res.write(this.name);
     return;
  };
  
  /**
   * Renders this component's type
   */
  jala.Form.Component.Input.prototype.type_macro = function() {
     res.write(this.getType());
     return;
  };
  
  /**
   * Renders this component's class name.
   * Note that this is just the class name that has been explicitly
   * assigned using setClassName.
   * @see #setClassName
   */
  jala.Form.Component.Input.prototype.class_macro = function() {
     var className = this.getClassName();
     if (className) {
        res.write(className);
     }
     return;
  };
  
  
  /**
   * Creates a new attribute object for this element.
   * @returns Object with properties id, name, class
   * @type Object
   */
  jala.Form.Component.Input.prototype.getControlAttributes = function() {
     var attr = {
        id: this.createDomId("control"),
        name: this.name,
        "class": this.getType() 
     };
     if (this.getClassName()) {
        attr["class"] += " " + this.getClassName();
     }
     return attr;
  };
  
  /**
   * Checks user input for maximum length, minimum length and require
   * if the corresponding options have been set using the require method.
   * @param {Object} reqData request data
   * @returns String containing error message or null if everything is ok.
   * @type String
   * @see #require
   */
  jala.Form.Component.Input.prototype.checkLength = function(reqData) {
     var require   = this.getRequirement(jala.Form.REQUIRE);
     var minLength = this.getRequirement(jala.Form.MINLENGTH);
     var maxLength = this.getRequirement(jala.Form.MAXLENGTH);
     
     if (require && (reqData[this.name] == null || reqData[this.name].trim() == "")) {
        return this.getMessage(jala.Form.REQUIRE, "Please enter text into this field.");
     } else if (maxLength && reqData[this.name].length > maxLength) {
        return this.getMessage(jala.Form.MAXLENGTH, "Input for this field is too long ({0} characters). Please enter no more than {1} characters.",
                                   reqData[this.name].length, maxLength);
     } else if (minLength) {
        // set an error if the element is required but the input is too short
        // but don't throw an error if the element is optional and empty
        if (reqData[this.name].length < minLength &&
            (require || (!require && reqData[this.name].length > 0))) {
           return this.getMessage(jala.Form.MINLENGTH, "Input for this field is too short ({0} characters). Please enter at least {1} characters.",
                 reqData[this.name].length, minLength);
        }
     }
     return null;
  };
  
  /**
   * Checks user input against options set using the require method.
   * @param {Object} reqData request data
   * @returns String containing error message or null if everything is ok.
   * @type String
   * @see #checkLength
   * @see #require
   */
  jala.Form.Component.Input.prototype.checkRequirements = function(reqData) {
     return this.checkLength(reqData);
  };
  
  /**
   * Parses the string input from the form and creates the datatype that
   * is edited with this component. For the input component this method
   * is not of much use, but subclasses that edit other datatypes may use
   * it. For example, a date editor should convert the user input from string
   * to a date object.
   * @param {Object} reqData request data
   * @returns parsed value
   * @type Object
   */
  jala.Form.Component.Input.prototype.parseValue = function(reqData) {
     return reqData[this.name];
  };
  
  /**
   * Renders the html form elements to the response.
   * This method shall be overridden by subclasses of input component.
   * @param {Object} attr Basic attributes for the html form elements.
   * @param {Object} value Value to be used for rendering this element.
   * @param {Object} reqData Request data for the whole form. This argument is
   *       passed only if the form is re-rendered after an error occured.
   */
  jala.Form.Component.Input.prototype.renderControls = function(attr, value, reqData) {
     attr.value = (reqData) ? reqData[this.name] : value;
     if (this.getRequirement(jala.Form.MAXLENGTH)) {
        attr.maxlength = this.getRequirement(jala.Form.MAXLENGTH);
     }
     jala.Form.html.input(attr);
     return;
  };
  
  /**
   * Constructs a newly created Password component instance
   * @class Subclass of jala.Form.Component.Input which renders and validates a
   * password input tag.
   * @base jala.Form.Component.Input
   * @param {String} name Name of the component, used as name of the html controls.
   * @returns A newly created Password component instance
   * @constructor
   */
  jala.Form.Component.Password = function Password(name) {
     jala.Form.Component.Password.superConstructor.apply(this, arguments);
     return this;
  };
  // extend jala.Form.Component.Input
  jala.Form.extend(jala.Form.Component.Password, jala.Form.Component.Input);
  
  /**
   * Renders a password input tag to the response.
   * @param {Object} attr Basic attributes for this element.
   * @param {Object} value Value to be used for rendering this element.
   * @param {Object} reqData Request data for the whole form. This argument is
   *       passed only if the form is re-rendered after an error occured.
   */
  jala.Form.Component.Password.prototype.renderControls = function(attr, value, reqData) {
     attr.value = (reqData) ? reqData[this.name] : value;
     if (this.getRequirement(jala.Form.MAXLENGTH)) {
        attr.maxlength = this.getRequirement(jala.Form.MAXLENGTH);
     }
     jala.Form.html.password(attr);
     return;
  };
  
  /**
   * Constructs a newly created Hidden component instance
   * @class Subclass of jala.Form.Component.Input which renders and validates a
   * hidden input tag.
   * @base jala.Form.Component.Input
   * @param {String} name Name of the component, used as name of the html controls.
   * @returns A newly created Hidden component instance
   * @constructor
   */
  jala.Form.Component.Hidden = function Hidden(name) {
     jala.Form.Component.Hidden.superConstructor.apply(this, arguments);
     return this;
  };
  // extend jala.Form.Component.Input
  jala.Form.extend(jala.Form.Component.Hidden, jala.Form.Component.Input);
  
  /**
   * Renders this component directly to response. For a hidden tag, this is
   * just an input element, no div tag or anything.
   */
  jala.Form.Component.Hidden.prototype.render = function() {
     var attr = this.getControlAttributes();
     var tracker = this.form.getTracker()
     if (tracker) {
        this.renderControls(attr, null, tracker.reqData);
     } else {
        this.renderControls(attr, this.getValue());
     }
     return;
  };
  
  /**
   * Renders a hidden input tag to the response.
   * @param {Object} attr Basic attributes for this element.
   * @param {Object} value Value to be used for rendering this element.
   * @param {Object} reqData Request data for the whole form. This argument is
   *       passed only if the form is re-rendered after an error occured.
   */
  jala.Form.Component.Hidden.prototype.renderControls = function(attr, value, reqData) {
     attr.value = (reqData) ? reqData[this.name] : value;
     jala.Form.html.hidden(attr);
     return;
  };
  
  /**
   * Constructs a new Textarea component.
   * @class Subclass of jala.Form.Component.Input which renders and validates a
   * textarea input field.
   * @base jala.Form.Component.Input
   * @param {String} name Name of the component, used as name of the html controls.
   * @returns A newly created Textarea component instance
   * @constructor
   */
  jala.Form.Component.Textarea = function Textarea(name) {
     jala.Form.Component.Textarea.superConstructor.apply(this, arguments);
  
     var rows, cols = undefined;
  
     /**
      * Returns the row numbers for this component.
      * @returns row numbers
      * @type String
      */
     this.getRows = function() {
        return rows;
     };
  
     /**
      * Sets the row numbers for this component.
      * @param {String} newRows new row numbers
      */
     this.setRows = function(newRows) {
        rows = newRows;
        return;
     };
  
     /**
      * Returns the col numbers for this component.
      * @returns col numbers
      * @type String
      */
     this.getCols = function() {
        return cols;
     };
  
     /**
      * Sets the col numbers for this component.
      * @param {String} newCols new col numbers
      */
     this.setCols = function(newCols) {
        cols = newCols;
        return;
     };
     
     return this;
  };
  // extend jala.Form.Component.Input
  jala.Form.extend(jala.Form.Component.Textarea, jala.Form.Component.Input);
  
  /**
   * Renders a textarea input field to the response.
   * @param {Object} attr Basic attributes for this element.
   * @param {Object} value Value to be used for rendering this element.
   * @param {Object} reqData Request data for the whole form. This argument is
   *       passed only if the form is re-rendered after an error occured.
   */
  jala.Form.Component.Textarea.prototype.renderControls = function(attr, value, reqData) {
     attr.value = (reqData) ? reqData[this.name] : value;
     attr.rows = this.getRows() || 5;
     attr.cols = this.getCols() || 25;
     jala.Form.html.textArea(attr);
     return;
  };
  
  /**
   * Constructs a new Date component instance
   * @class Subclass of jala.Form.Component.Input which renders and validates a
   * date editor.
   * @base jala.Form.Component.Input
   * @param {String} name Name of the component, used as name of the html controls.
   * @returns A newly created Date component
   * @constructor
   */
  jala.Form.Component.Date = function Date(name) {
     jala.Form.Component.Date.superConstructor.apply(this, arguments);
  
     var dateFormat = "d.M.yyyy H:m";
     var dateFormatObj;
  
     /**
      * Returns the date format for this component.
      * @returns date format object
      * @type java.text.SimpleDateFormat
      */
     this.getDateFormat = function() {
        if (!dateFormatObj || dateFormatObj.toPattern() != dateFormat) {
           dateFormatObj = new java.text.SimpleDateFormat(dateFormat);
        }
        return dateFormatObj;
     };
  
     /**
      * Sets the date format for this component.
      * @param {String} newDateFormat new date format
      */
     this.setDateFormat = function(newDateFormat) {
        dateFormat = newDateFormat;
        return;
     };
  
     return this;
  };
  // extend jala.Form.Component.Input
  jala.Form.extend(jala.Form.Component.Date, jala.Form.Component.Input);
  
  /**
   * Renders a textarea tag to the response.
   * @param {Object} attr Basic attributes for this element.
   * @param {Object} value Value to be used for rendering this element.
   * @param {Object} reqData Request data for the whole form. This argument is
   *       passed only if the form is re-rendered after an error occured.
   */
  jala.Form.Component.Date.prototype.renderControls = function(attr, value, reqData) {
     if (reqData) {
        attr.value = reqData[this.name];
     } else  if (value instanceof Date) {
        attr.value = this.getDateFormat().format(value);
     }
     if (this.getRequirement(jala.Form.MAXLENGTH)) {
        attr.maxlength = this.getRequirement(jala.Form.MAXLENGTH);
     }
     jala.Form.html.input(attr);
     return;
  };
  
  /**
   * Validates user input from a date editor.
   * @param {Object} reqData request data
   * @returns null if everything is ok or string containing error message
   * @type String
   */
  jala.Form.Component.Date.prototype.checkRequirements = function(reqData) {
     try {
        this.parseValue(reqData);
        return null;
     } catch(e) {
        return this.getMessage("invalid", "This date cannot be parsed.");
     }
  };
  
  /**
   * Parses the string input from the form and converts it to a date object.
   * Throws an error if the string cannot be parsed.
   * @param {Object} reqData request data
   * @returns parsed date value
   * @type Date
   */
  jala.Form.Component.Date.prototype.parseValue = function(reqData) {
     return this.getDateFormat().parse(reqData[this.name]);
  };
  
  /**
   * Constructs a new Select component instance
   * @class Subclass of jala.Form.Component.Input which renders and validates a
   * dropdown element.
   * @base jala.Form.Component.Input
   * @param {String} name Name of the component, used as name of the html controls.
   * @returns A newly created Select component
   * @constructor
   */
  jala.Form.Component.Select = function Select(name) {
     jala.Form.Component.Select.superConstructor.apply(this, arguments);
     
     var options, firstOption = undefined;
     
     /**
      * Returns the option list for this component.
      */
     this.getOptions = function() {
        return options;
     };
     
     /**
      * Sets the option list for this component.
      * The argument may either be an array that will be used as option list,
      * or a function that is called when the option component is rendered and
      * has to return an option array.
      * For both arrays those formats are allowed:
      * <li>Array of arrays <code>[ [val, display], [val, display], .. ]</code></li>
      * <li>Array of objects <code>[ {value:val, display:display}, .. ]</code></li>
      * <li>Array of strings <code>[ display, display, .. ]</code> In this case,
      *    the index position of the string will be the value.</li>
      * @param {Array Function} newOptions Array or function defining option list.
      */
     this.setOptions = function(newOptions) {
        options = newOptions;
        return;
     };
     
     /**
      * Returns the text that should be displayed if no value is selected.
      * @type String
      */
     this.getFirstOption = function() {
        return firstOption;
     };
     
     /**
      * Sets the text that is displayed if no value is selected
      * @param {String} newFirstOption text to display as first option element.
      */
     this.setFirstOption = function(newFirstOption) {
        firstOption = newFirstOption;
        return;
     };
    
     return this;
  };
  // extend jala.Form.Component.Input
  jala.Form.extend(jala.Form.Component.Select, jala.Form.Component.Input);
  
  /**
   * Renders a dropdown element to the response.
   * @param {Object} attr Basic attributes for this element.
   * @param {Object} value Value to be used for rendering this element.
   * @param {Object} reqData Request data for the whole form. This argument is
   *       passed only if the form is re-rendered after an error occured.
   */
  jala.Form.Component.Select.prototype.renderControls = function(attr, value, reqData) {
     value = (reqData) ? reqData[this.name] : value;
     jala.Form.html.dropDown(attr, this.parseOptions(), value, this.getFirstOption());
     return;
  };
  
  /**
   * Validates user input from a dropdown element by making sure that
   * the option value list contains the user input.
   * @see jala.Form.Component.Select#checkOptions
   * @param {Object} reqData request data
   * @returns string containing error message or null if everything is ok.
   * @type String
   */
  jala.Form.Component.Select.prototype.checkRequirements = function(reqData) {
     return this.checkOptions(reqData);
  };
  
  /**
   * Creates an array of options for a dropdown element or a
   * group of radiobuttons. If options field of this element's
   * config is an array, that array is returned.
   * If options is a function, its return value is returned.
   * @returns array of options
   * @type Array
   */
  jala.Form.Component.Select.prototype.parseOptions = function() {
     var options = this.getOptions();
     if (options != null) {
        if (options instanceof Array) {
           return options;
        } else if (options instanceof Function) {
           return options.call(this.form.getDataObject(), this.name);
        }
     }
     return [];
  };
  
  /**
   * Checks user input for optiongroups: Unless require("checkoptions")
   * has ben set to false, the user input must exist in the option array.
   * @param {Object} reqData request data
   * @returns null if everything is ok or string containing error message
   * @type String
   */
  jala.Form.Component.Select.prototype.checkOptions = function(reqData) {
     // if field is required, an empty option is not allowed:
     var found = (!this.getRequirement(jala.Form.REQUIRE) && !reqData[this.name]);
     if (!found) {
        if (this.getRequirement(jala.Form.CHECKOPTIONS) === false) {
           // exit, if option check shall be suppressed
           return null;
        }
        var options = this.parseOptions();
        var val = reqData[this.name];
        for (var i=0; i<options.length; i++) {
           if ((options[i] instanceof Array) && options[i].length > 0) {
              // option is an array (1st element = value, 2nd = display)
              if (options[i][0] == reqData[this.name]) {
                 found = true;
                 break;
              }
           } else if (options[i].value && options[i].display) {
              // option is an object with fields value + display
              if (options[i].value == reqData[this.name]) {
                 found = true;
                 break;
              }
           } else {
              // option is a string, value is index number
              if (i == reqData[this.name]) {
                 found = true;
                 break;
              }
           }
        }
     }
     if (!found) {
        return "Please select a valid option.";
     }
     return null;
  };
  
  /**
   * Creates a new Radio component instance
   * @class Subclass of jala.Form.Component.Input which renders and validates a
   * set of radio buttons.
   * @base jala.Form.Component.Select
   * @param {String} name Name of the component, used as name of the html controls.
   * @returns A newly created Radio component
   * @constructor
   */
  jala.Form.Component.Radio = function Radio(name) {
     jala.Form.Component.Radio.superConstructor.apply(this, arguments);
     return this;
  };
  // extend jala.Form.Component.Select
  jala.Form.extend(jala.Form.Component.Radio, jala.Form.Component.Select);
  
  /**
   * Renders a set of radio buttons to the response.
   * @param {Object} attr Basic attributes for this element.
   * @param {Object} value Value to be used for rendering this element.
   */
  jala.Form.Component.Radio.prototype.renderControls = function(attr, value) {
     var options = this.parseOptions();
     var optionAttr, optionDisplay;
     for (var i=0; i<options.length; i++) {
        optionAttr = attr.clone({}, false);
        optionAttr.id += i;
        if ((options[i] instanceof Array) && options[i].length > 0) {
           optionAttr.value = options[i][0];
           optionDisplay = options[i][1];
        } else if (options[i].value && options[i].display) {
           optionAttr.value = options[i].value;
           optionDisplay = options[i].display;
        } else {
           optionAttr.value = i;
           optionDisplay = options[i];
        }
        if (String(value) == String(optionAttr.value)) {
           optionAttr.checked = "checked";
        }
        jala.Form.html.radioButton(optionAttr);
        res.write(optionDisplay);
        jala.Form.html.tag("br");
     }
     return;
  };
  
  /**
   * Validates user input from a set of radio buttons and makes sure that
   * option value list contains the user input.
   * @see jala.Form.Component.Select#checkOptions
   * @param {Object} reqData request data
   * @returns null if everything is ok or string containing error message
   * @type String
   */
  jala.Form.Component.Radio.prototype.checkRequirements = function(reqData) {
     return this.checkOptions(reqData);
  };
  
  /**
   * Creates a new Checkbox component instance
   * @class Subclass of jala.Form.Component.Input which renders and validates a
   * checkbox.
   * @base jala.Form.Component.Input
   * @param {String} name Name of the component, used as name of the html controls.
   * @returns A newly created Checkbox component instance
   * @constructor
   */
  jala.Form.Component.Checkbox = function Checkbox(name) {
     jala.Form.Component.Checkbox.superConstructor.apply(this, arguments);
     return this;
  };
  // extend jala.Form.Component.Input
  jala.Form.extend(jala.Form.Component.Checkbox, jala.Form.Component.Input);
  
  /**
   * Renders an checkbox to the response.
   * @param {Object} attr Basic attributes for this element.
   * @param {Object} value Value to be used for rendering this element.
   */
  jala.Form.Component.Checkbox.prototype.renderControls = function(attr, value, reqData) {
     if (value == 1 || (reqData && reqData[this.name] == "1")) {
        attr.checked = "checked";
     }
     attr.value = "1";
     jala.Form.html.checkBox(attr);
     return;
  };
  
  /**
   * Parses the string input from the form. For a checked box, the value is 1,
   * for an unchecked box the value is 0.
   * @param {Object} reqData request data
   * @returns parsed value
   * @type Number
   */
  jala.Form.Component.Checkbox.prototype.parseValue = function(reqData) {
     return (reqData[this.name] == "1") ? 1 : 0;
  };
  
  /**
   * Validates user input from checkbox.
   * @param {Object} reqData request data
   * @returns null if everything is ok or string containing error message
   * @type String
   */
  jala.Form.Component.Checkbox.prototype.checkRequirements = function(reqData) {
     if (reqData[this.name] && reqData[this.name] != "1") {
        return this.getMessage("invalid", "The value of this checkbox is invalid.");
     }
     return null;
  };
  
  /**
   * Creates a new File component instance
   * @class Subclass of jala.Form.Component.Input which renders and validates a
   * file upload.
   * @base jala.Form.Component.Input
   * @param {String} name Name of the component, used as name of the html controls.
   * @returns A newly created File component
   * @constructor
   */
  jala.Form.Component.File = function File(name) {
     jala.Form.Component.File.superConstructor.apply(this, arguments);
  
     this.containsFileUpload = function() {
        return true;
     };
  
     return this;
  };
  // extend jala.Form.Component.Input
  jala.Form.extend(jala.Form.Component.File, jala.Form.Component.Input);
  
  /**
   * Renders a file input tag to the response.
   * @param {Object} attr Basic attributes for this element.
   * @param {Object} value Value to be used for rendering this element.
   * @param {Object} reqData Request data for the whole form. This argument is
   *       passed only if the form is re-rendered after an error occured.
   */
  jala.Form.Component.File.prototype.renderControls = function(attr, value, reqData) {
     var contentType = this.getRequirement(jala.Form.CONTENTTYPE);
     if (contentType) {
        attr.accept = (contentType instanceof Array) ? contentType.join(",") : contentType;
     }
     jala.Form.html.file(attr);
     return;
  };
  
  /**
   * Validates a file upload by making sure it's there (if REQUIRE is set),
   * checking the file size, the content type and by trying to construct an image.
   * @param {Object} reqData request data
   * @param {jala.Form.Tracker} tracker jala.Form.Tracker object storing possible error messages
   * @returns null if everything is ok or string containing error message
   * @type String
   */
  jala.Form.Component.File.prototype.checkRequirements = function(reqData) {
  
     if (reqData[this.name].contentLength == 0) {
        // no upload
        if (this.getRequirement(jala.Form.REQUIRE) == true) {
           return this.getMessage(jala.Form.REQUIRE, "File upload is required.");
        } else {
           // no further checks necessary, exit here
           return null;
        }
     }
  
     var maxLength = this.getRequirement(jala.Form.MAXLENGTH);
     if (maxLength && reqData[this.name].contentLength > maxLength) {
        return this.getMessage(jala.Form.MAXLENGTH, "This file is too big ({0} bytes), maximum allowed size {1} bytes.",
              reqData[this.name].contentLength, maxLength);
     }
     
     var contentType = this.getRequirement(jala.Form.CONTENTTYPE);
     if (contentType) {
        var arr = (contentType instanceof Array) ? contentType : [contentType];
        if (arr.indexOf(reqData[this.name].contentType) == -1) {
           return this.getMessage(jala.Form.CONTENTTYPE, "The file type {0} is not allowed.",
              reqData[this.name].contentType);
        }
     }
     
     return null;
  };
  
  
  /**
   * Creates a new Image component instance
   * @class Subclass of jala.Form.Component.File which renders a file upload
   * and validates uploaded files as images.
   * @base jala.Form.Component.File
   * @param {String} name Name of the component, used as name of the html controls.
   * @returns A newly created Image component
   * @constructor
   */
  // FIXME: see below
  jala.Form.Component.Image = function() {};
  
  /**
   * @ignore
   * FIXME: JSDoc has some sever problems with this class.
   * It's somehow due to the named method ("Image") that it
   * always appears as global static object.
   * Wrapping the method in another function which immediately
   * is executed seems to solve this problem and could be used
   * as a work-around for similar issues.
   */
  jala.Form.Component.Image = (function() {
     return function Image(name) {
        jala.Form.Component.Image.superConstructor.apply(this, arguments);
        return this;
     };
  })();
  
  // extend jala.Form.Component.File
  jala.Form.extend(jala.Form.Component.Image, jala.Form.Component.File);
  
  /**
   * Validates an image upload by making sure it's there (if REQUIRE is set),
   * checking the file size, the content type and by trying to construct an image.
   * If the file is an image, width and height limitations set by require are
   * checked.
   * @param {Object} reqData request data
   * @type String
   */
  jala.Form.Component.Image.prototype.checkRequirements = function(reqData) {
     var re = this.constructor.superConstructor.prototype.checkRequirements.call(this, reqData);
     if (re) {
        return re;
     }
  
     if (reqData[this.name].contentLength > 0) {
        var helmaImg = undefined;
        try {
           helmaImg = new Image(reqData[this.name]);
        } catch (imgError) {
           return this.getMessage("invalid", "This image file can't be processed.");
        }
     
        var maxWidth = this.getRequirement(jala.Form.MAXWIDTH);
        if (maxWidth && helmaImg.getWidth() > maxWidth) {
           return this.getMessage("maxwidth", "This image is too wide.");
        }
        
        var minWidth = this.getRequirement(jala.Form.MINWIDTH);
        if (minWidth && helmaImg.getWidth() < minWidth) {
           return this.getMessage("minwidth", "This image is not wide enough.");
        }
        
        var maxHeight = this.getRequirement(jala.Form.MAXHEIGHT);
        if (maxHeight && helmaImg.getHeight() > maxHeight) {
           return this.getMessage("maxheight", "This image is too tall.");
        }
        
        var minHeight = this.getRequirement(jala.Form.MINHEIGHT);
        if (minHeight && helmaImg.getHeight() < minHeight) {
           return this.getMessage("minheight", "This image is not tall enough.");
        }
     }
  
     return null;
  };
  
  
  
  /**
   * Creates a new Button component instance
   * @class Subclass of jala.Form.Component.Input which renders a button.
   * @base jala.Form.Component.Input
   * @param {String} name Name of the component, used as name of the html controls.
   * @returns A newly created Button component
   * @constructor
   */
  jala.Form.Component.Button = function Button(name) {
     jala.Form.Component.Button.superConstructor.apply(this, arguments);
  
     /**
      * Private field containing the value of the button (ie. the visible text)
      * @type String
      */
     var value;
     
     /**
      * Returns the value set for this button.
      * @returns value
      * @type String
      */
     this.getValue = function() {
        return value;
     };
  
     /**
      * Sets the value for this button.
      * @param {String} newValue new value
      */
     this.setValue = function(newValue) {
        value = newValue;
        return;
     };
  
     return this;
  };
  // extend jala.Form.Component
  jala.Form.extend(jala.Form.Component.Button, jala.Form.Component.Input);
  
  /**
   * Renders a button to the response.
   * @param {Object} attr Basic attributes for this element.
   * @param {Object} value Value to be used for rendering this element.
   * @param {Object} reqData Request data for the whole form. This argument is
   *       passed only if the form is re-rendered after an error occured.
   */
  jala.Form.Component.Button.prototype.render = function(attr, value, reqData) {
     var classStr = (this.getClassName()) ? " " + this.getClassName() : "";
     var attr = {
        id: this.createDomId(),
        "class": "component" + classStr
     };
     jala.Form.html.openTag("div", attr);
     res.write("\n ");
  
     this.renderControls(this.getControlAttributes(), this.getValue());
     res.write("\n");
     
     jala.Form.html.closeTag("div");
     res.write("\n");
     return;
  };
  
  /**
   * Creates a new attribute object for this button.
   * @returns Object with all attributes set for this button.
   * @type Object
   */
  jala.Form.Component.Button.prototype.renderControls = function(attr, value) {
     if (value) {
        attr.value = value;
     }
     jala.Form.html.button(attr);
     return;
  };
  
  
  /**
   * Creates a new Submit component instance
   * @class Subclass of jala.Form.Component.Button which renders a submit button.
   * @base jala.Form.Component.Button
   * @param {String} name Name of the component, used as name of the html controls.
   * @returns A newly created Submit component
   * @constructor
   */
  jala.Form.Component.Submit = function Submit(name) {
     jala.Form.Component.Submit.superConstructor.apply(this, arguments);
  
     return this;
  };
  // extend jala.Form.Component.Button
  jala.Form.extend(jala.Form.Component.Submit, jala.Form.Component.Button);
  
  /**
   * Creates a new attribute object for this button.
   * @returns Object with all attributes set for this button.
   * @type Object
   */
  jala.Form.Component.Submit.prototype.renderControls = function(attr, value) {
     if (value) {
        attr.value = value;
     }
     jala.Form.html.submit(attr);
     return;
  };
  
  
  /**
   * static default getter function used to return a field 
   * from the data object.
   * @param {String} name Name of the property.
   * @type Object
   */
  jala.Form.propertyGetter = function(name, value) {
     return this[name];
  };
  
  /**
   * static default setter function used to change a field 
   * of the data object.
   * @param {String} name Name of the property.
   * @param {Object} value New value of the property.
   */
  jala.Form.propertySetter = function(name, value) {
     this[name] = value;
  };
  
  
  /**
   * A generic container for error-messages and values
   * @class Instances of this class can contain error-messages and values
   * @constructor
   * @type jala.Form.Tracker
   */
  jala.Form.Tracker = function(reqData) {
  
     /**
      * A map containing input from request data
      * @type Object
      */
     this.reqData = reqData;
  
     /**
      * A map containing parsed values (only for those fields that didn't
      * fail during checkRequirements method).
      * @type Object
      */
     this.values = {};
  
     /**
      * A map containing error messages
      * @type Object
      */
     this.errors = {};
  
     return this;
  };
  
  /** @ignore */
  jala.Form.Tracker.toString = function() {
     return "[jala.Form.Tracker]";
  };
  
  /** @ignore */
  jala.Form.Tracker.prototype.toString = jala.Form.Tracker.toString;
  
  /**
   * Returns true if an error has been set for at least one component.
   * @returns true if form encountered an error.
   * @type Boolean
   */
  jala.Form.Tracker.prototype.hasError = function() {
     for (var keys in this.errors) {
        return true;
     }
     return false;
  };
  
  /**
   * Returns the number of components for which this instance has
   * tracked an error.
   * @returns Number of components that did not validate.
   * @type Number
   */
  jala.Form.Tracker.prototype.countErrors = function() {
     var ct = 0;
     for (var keys in this.errors) {
        ct++;
     }
     return ct;
  };
  
  /**
   * Helper method.
   * @private
   */
  jala.Form.Tracker.prototype.debug = function() {
     for (var key in this.errors) {
        res.debug(key + ":" + this.errors[key]);
     }
     return;
  };
  
  
  