// // 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; ithe name of the element *
  • 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.
  • *
  • the map containing all user inputs as string (req.data)
  • *
  • the form object
  • * @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. * * @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. *
  • If no setter is set at the component, the primitive property * of the data object is changed.
  • *
  • 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
  • *
  • If the setter is explicitly set to null, no changes are made at all.
  • * @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: *
  • Array of arrays [ [val, display], [val, display], .. ]
  • *
  • Array of objects [ {value:val, display:display}, .. ]
  • *
  • Array of strings [ display, display, .. ] In this case, * the index position of the string will be the value.
  • * @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 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 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() {}; /** * 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; };