// // 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$ // $LastChangedBy$ // $LastChangedDate$ // $HeadURL$ // /** * @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; };