From 2116ca734fbad3734aa3eb23c90975586d8b9a79 Mon Sep 17 00:00:00 2001 From: YUI Builder Date: Tue, 15 Dec 2009 14:13:47 -0800 Subject: [PATCH] gallery-2009.12.15-22 jafl gallery-formmgr --- src/gallery-formmgr/build.properties | 6 + src/gallery-formmgr/build.xml | 6 + src/gallery-formmgr/js/FormManager.js | 1334 +++++++++++++++++++++++++ 3 files changed, 1346 insertions(+) create mode 100644 src/gallery-formmgr/build.properties create mode 100644 src/gallery-formmgr/build.xml create mode 100644 src/gallery-formmgr/js/FormManager.js diff --git a/src/gallery-formmgr/build.properties b/src/gallery-formmgr/build.properties new file mode 100644 index 0000000000..d23409d9f3 --- /dev/null +++ b/src/gallery-formmgr/build.properties @@ -0,0 +1,6 @@ +builddir=../../../builder/componentbuild +component=gallery-formmgr +component.jsfiles=FormManager.js +component.requires=node-base,substitute +#component.supersedes= +#component.optional= diff --git a/src/gallery-formmgr/build.xml b/src/gallery-formmgr/build.xml new file mode 100644 index 0000000000..5a148901ac --- /dev/null +++ b/src/gallery-formmgr/build.xml @@ -0,0 +1,6 @@ + + + Form Manager build file + + + diff --git a/src/gallery-formmgr/js/FormManager.js b/src/gallery-formmgr/js/FormManager.js new file mode 100644 index 0000000000..e7063c201b --- /dev/null +++ b/src/gallery-formmgr/js/FormManager.js @@ -0,0 +1,1334 @@ +"use strict"; + +/********************************************************************** + *

FormManager provides support for initializing a form, pre-validating + * user input, and displaying messages returned by the server.

+ * + *

Required Markup Structure

+ * + *

Each element (or tighly coupled set of elements) must be contained by + * an element that has the CSS class formmgr-row. Within each + * row, validation messages are displayed inside the container with CSS + * class formmgr-message-text. + * + *

When a message is displayed inside a row, the CSS class + * formmgr-has{type} is placed on the row container and the + * containing fieldset (if any), where {type} is the message + * type passed to displayMessage().

+ * + *

Initializing the Form

+ * + *

Default values can be either encoded in the markup or passed to the + * FormManager constructor via config.default_value_map. (The + * former method is obviously better for progressive enhancement.) The + * values passed to the constructor override the values encoded in the + * markup.

+ * + *

prepareForm() must be called before the form is + * displayed. To initialize focus to the first element in a form, call + * initFocus(). If the form is in an overlay, you can delay + * these calls until just before showing the overlay.

+ * + *

The default values passed to the constructor are inserted by + * populateForm(). (This is automatically called by + * prepareForm().)

+ * + *

Displaying Messages

+ * + *

To display a message for a single form row, call + * displayMessage(). To display a message for the form in + * general, call displayFormMessage(). These functions can be + * used for initializing the error display when the page loads, for + * displaying the results of pre-validation, and for displaying the results + * of submitting a form via XHR.

+ * + *

Specifying Validations

+ * + *

The following classes can be applied to a form element for + * pre-validation:

+ * + *
+ *
yiv-required
+ *
Value must not be empty.
+ * + *
yiv-length:[x,y]
+ *
String must be at least x characters and at most y characters. + * At least one of x and y must be specified.
+ * + *
yiv-integer:[x,y]
+ *
The integer value must be at least x and at most y. + * x and y are both optional.
+ * + *
yiv-decimal:[x,y]
+ *
The decimal value must be at least x and at most y. Exponents are + * not allowed. x and y are both optional.
+ *
+ * + *

If we ever need to allow exponents, we can use yiv-float.

+ * + *

The following functions allow additional pre-validation to be + * attached to individual form elements:

+ * + *
+ *
setRegex()
+ *
Sets the regular expression that must match in order for the value + * to be acceptable.
+ * + *
setFunction()
+ *
Sets the function that must return true in order for the value to + * be acceptable. The function is called in the scope of the Form + * object with the arguments: the form and the element.
+ *
+ * + *

setErrorMessages() specifies the error message to be + * displayed when a pre-validation check fails.

+ * + *

Functions are expected to call displayMessage() + * directly.

+ * + *

More complex pre-validations can be added by overriding + * postValidateForm(), described below.

+ * + *

Derived classes may also override the following functions:

+ * + *
+ *
prePrepareForm(arguments passed to prepareForm)
+ *
Called before filling in default values for the form elements. + * Return false to cancel dialog.
+ * + *
postPrepareForm(arguments passed to prepareForm)
+ *
Called after filling in default values for the form elements.
+ * + *
postValidateForm(form)
+ *
Called after performing the basic pre-validations. Returns + * true if the form contents are acceptable. Reports error if there + * is a problem.
+ *
+ * + * @module gallery-formmgr + * @class FormManager + * @constructor + * @param form_name {String} The name attribute of the HTML form. + * @param config {Object} Configuration. + * status_node is an optional element in which to display + * overall status. default_value_map is an optional + * mapping of form element names to default values. Default values + * encoded in the markup will be merged into this map, but values + * passed to the constructor will take precedence. + */ + +function FormManager( + /* string */ form_name, + /* object */ config) // {status_node, default_value_map} +{ + if (arguments.length === 0) // derived class prototype + { + return; + } + + if (!config) + { + config = {}; + } + + this.form_name = form_name; + this.status_node = Y.one(config.status_node); + this.enabled = true; + + // default values for form elements + + this.default_value_map = config.default_value_map; + + // pre-validation methods + + this.validation = + { + fn: {}, // function for validating each element id + regex: {} // regex for validating each element id + }; + + // error messages + + this.validation_msgs = {}; // message list, keyed on type, for each element id + + this.has_messages = false; + this.has_errors = false; + + // buttons -- disabled during submission + + this.button_list = []; + this.user_button_list = []; + + // file uploading is nasty + + this.has_file_inputs = false; +} + +// CSS class pattern bookends + +var class_re_prefix = '(?:^|\\s)(?:'; +var class_re_suffix = ')(?:\\s|$)'; + +// pre-validation classes + +var required_class = 'yiv-required'; +var length_class_re = /(?:^|\s+)yiv-length:\[([0-9]+)?,([1-9][0-9]*)?\](?:\s+|$)/; +var integer_class_re = /(?:^|\s+)yiv-integer(?::\[([-+]?[0-9]+)?,([-+]?[0-9]+)?\])?(?:\s+|$)/; +var decimal_class_re = /(?:^|\s+)yiv-decimal(?::\[([-+]?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+))?,([-+]?(?:[0-9]+\.?|[0-9]+\.[0-9]+|\.[0-9]+))?\])?(?:\s+|$)/; + +/** + * Regular expression used to determine if a value is an integer. + * This can be localized, e.g., allow for thousands separator. + * + * @config Y.FormManager.integer_value_re + * @type {RegExp} + * @static + */ +FormManager.integer_value_re = /^[-+]?[0-9]+$/; + +/** + * Regular expression used to determine if a value is a decimal number. + * This can be localized, e.g., use the correct decimal separator. + * + * @config Y.FormManager.decimal_value_re + * @type {RegExp} + * @static + */ +FormManager.decimal_value_re = /^[-+]?(?:[0-9]+\.?|[0-9]*\.[0-9]+)$/; + +/** + * The CSS class which marks each row of the form. Typically, each element + * (or a very tightly coupled set of elements) is placed in a separate row. + * + * @property Y.FormManager.row_marker_class + * @type {String} + */ +FormManager.row_marker_class = 'formmgr-row'; + +/** + * The CSS class which marks the container for the status message within a + * row of the form. + * + * @property Y.FormManager.status_marker_class + * @type {String} + */ +FormManager.status_marker_class = 'formmgr-message-text'; + +/** + * The CSS class placed on status_node when it is empty. + * + * @property Y.FormManager.status_none_class + * @type {String} + */ +FormManager.status_none_class = 'formmgr-status-hidden'; + +/** + * The CSS class placed on status_node when + * displayFormMessage() is called with + * error=false. + * + * @property Y.FormManager.status_success_class + * @type {String} + */ +FormManager.status_success_class = 'formmgr-status-success'; + +/** + * The CSS class placed on status_node when + * displayFormMessage() is called with + * error=true. + * + * @property Y.FormManager.status_failure_class + * @type {String} + */ +FormManager.status_failure_class = 'formmgr-status-failure'; + +/** + * The prefix for all CSS classes placed on a form row when pre-validation + * fails. The full CSS class is formed by appending the value from + * Y.FormManager.status_order. + * + * @property Y.FormManager.row_status_prefix + * @type {String} + */ +FormManager.row_status_prefix = 'formmgr-has'; + +var status_pattern = FormManager.status_success_class+'|'+FormManager.status_failure_class; +var row_status_pattern = FormManager.row_status_prefix + '([^\\s]+)'; +var row_status_regex = new RegExp(class_re_prefix + row_status_pattern + class_re_suffix); + +/** + *

Map of localizable strings used by pre-validation.

+ * + *
+ *
validation_error
+ *
Displayed in status_node by notifyErrors() when pre-validation fails.
+ *
required_string
+ *
Displayed when yiv-required fails on an input field.
+ *
required_menu
+ *
Displayed when yiv-required fails on a select element.
+ *
length_too_short, length_too_long, length_out_of_range
+ *
Displayed when yiv-length fails on an input field.
+ *
integer, integer_too_small, integer_too_large, integer_out_of_range
+ *
Displayed when yiv-integer fails on an input field.
+ *
decimal, decimal_too_small, decimal_too_large, decimal_out_of_range
+ *
Displayed when yiv-decimal fails on an input field.
+ *
+ * + * @config Y.FormManager.Strings + * @type {Object} + * @static + */ +FormManager.Strings = +{ + validation_error: 'Correct errors in the highlighted fields before continuing.', + + required_string: 'This field requires a value.', + required_menu: 'This field is required. Choose a value from the pull-down list.', + + length_too_short: 'Enter text that is at least {min} characters or longer.', + length_too_long: 'Enter text that is up to {max} characters long.', + length_out_of_range: 'Enter text that is {min} to {max} characters long.', + + integer: 'Enter a whole number (no decimal point).', + integer_too_small: 'Enter a number that is {min} or higher (no decimal point).', + integer_too_large: 'Enter a number that is {max} or lower (no decimal point).', + integer_out_of_range: 'Enter a number between or including {min} and {max} (no decimal point).', + + decimal: 'Enter a number.', + decimal_too_small: 'Enter a number that is {min} or higher.', + decimal_too_large: 'Enter a number that is {max} or lower.', + decimal_out_of_range: 'Enter a number between or including {min} and {max}.' +}; + +/** + *

Names of supported status values, highest precedence first. Default: + * [ 'error', 'warn', 'success', 'info' ]

+ * + *

This is static because it links to CSS rules that define the + * appearance of each status type: .formmgr-has{status}

+ * + * @config Y.FormManager.status_order + * @type {Array} + * @static + */ +FormManager.status_order = +[ + 'error', + 'warn', + 'success', + 'info' +]; + +/** + * Get the precedence of the given status name. + * + * @method Y.FormManager.getStatusPrecedence + * @static + * @param status {String} The name of the status value. + * @return {int} The position in the status_order array. + */ +FormManager.getStatusPrecedence = function( + /* string */ status) +{ + for (var i=0; itrue if new_status takes precedence over orig_status + */ +FormManager.statusTakesPrecendence = function( + /* string */ orig_status, + /* string */ new_status) +{ + return (!orig_status || FormManager.getStatusPrecedence(new_status) < FormManager.getStatusPrecedence(orig_status)); +}; + +/** + * Get the status of the given fieldset or form row. + * + * @method Y.FormManager.getElementStatus + * @static + * @param e {String|Object} The descriptor or DOM element. + * @return {mixed} The status (String) or false. + */ +FormManager.getElementStatus = function( + /* string/object */ e) +{ + var m = Y.one(e).get('className').match(row_status_regex); + if (m && m.length) + { + return m[1]; + } + else + { + return false; + } +}; + +function getId( + /* string/Node/object */ e) +{ + if (Y.Lang.isString(e)) + { + return e.replace(/^#/, ''); + } + else if (e instanceof Y.Node) + { + return e.get('id'); + } + else + { + return e.id; + } +} + +function hasLimit( + /* string */ s) +{ + return (!Y.Lang.isUndefined(s) && s.length > 0); +} + +function populateForm1() +{ + var collect_buttons = (this.button_list.length === 0); + + for (var i=0; i= 0 && + e.options[ e.selectedIndex ].value !== v.toString()) + { + e.selectedIndex = -1; + } + } + else if (name == 'textarea') + { + e.value = v; + } + } +} + +FormManager.prototype = +{ + /* ********************************************************************* + * Access functions. + */ + + /** + * @return {DOM} The form DOM element. + */ + getForm: function() + { + if (!this.form) + { + this.form = document.forms[ this.form_name ]; + } + return this.form; + }, + + /** + * @return {boolean} true if the form contains file inputs. These require special treatment when submitting via XHR. + */ + hasFileInputs: function() + { + return this.has_file_inputs; + }, + + /** + * Set the default values for all form elements. + * + * @param default_value_map {Object} Mapping of form element names to values. + */ + setDefaultValues: function( + /* object */ default_value_map) + { + this.default_value_map = default_value_map; + }, + + /** + * Set the default values for a single form element. + * + * @param field_name {String} The form element name. + * @param default_value {String|Int|Float} The default value. + */ + setDefaultValue: function( + /* string*/ field_name, + /* string */ default_value) + { + this.default_value_map[ field_name ] = default_value; + }, + + /** + * Store the current form values in default_value_map. + */ + saveCurrentValuesAsDefault: function() + { + this.default_value_map = {}; + this.button_list = []; + populateForm1.call(this); + }, + + /* ********************************************************************* + * Validation control + */ + + /** + * Set the validation function for a form element. + * + * @param id {String|Object} The selector for the element or the element itself + * @param f {Function|String|Object} + * The function to call after basic validations succeed. If this + * is a String, it is resolved in the scope of the FormManager + * object. If this is an object, it must be {fn:, + * scope:}. The function will then be invoked in the + * specified scope. + */ + setFunction: function( + /* string */ id, + /* function/string/obj */ f) + { + this.validation.fn[ getId(id) ] = f; + }, + + /** + *

Set the regular expression used to validate the field value.

+ * + *

Since there is no default message for failed regular + * expression validation, this function will complain if you have not + * already called setErrorMessages() or + * addErrorMessage to specify an error message.

+ * + * @param id {String|Object} The selector for the element or the element itself + * @param regex {String|RegExp} The regular expression to use + * @param flags {String} If regex is a String, these are the flags used to construct a RegExp. + */ + setRegex: function( + /* string */ id, + /* string/RegExp */ regex, + /* string */ flags) // ignored if regex is RegExp object + { + id = getId(id); + + if (Y.Lang.isString(regex)) + { + this.validation.regex[id] = new RegExp(regex, flags); + } + else + { + this.validation.regex[id] = regex; + } + + if (!this.validation_msgs[id] || !this.validation_msgs[id].regex) + { + Y.log(Y.substitute('No error message provided for regex validation of {id}!', {id:id}), 'error', 'FormManager'); + } + }, + + /** + *

Set the error messages for a form element. This can be used to + * override the default messages for individual elements

+ * + *

The valid error types are:

+ *
+ *
required
+ *
min_length
+ *
{min} and {max} are replaced
+ *
max_length
+ *
{min} and {max} are replaced
+ *
integer
+ *
{min} and {max} are replaced
+ *
decimal
+ *
{min} and {max} are replaced
+ *
regex
+ *
This must be set for elements which validate with regular expressions.
+ *
+ * + * @param id {String|Object} The selector for the element or the element itself + * @param map {Object} Map of error types to error messages. + */ + setErrorMessages: function( + /* string */ id, + /* object */ map) + { + this.validation_msgs[ getId(id) ] = map; + }, + + /** + * Set one particular error message for a form element. + * + * @param id {String|Object} The selector for the element or the element itself + * @param error_type {String} The error message type. Refer to setErrorMessages() for details. + * @param msg {String} The error message + */ + addErrorMessage: function( + /* string */ id, + /* string */ error_type, + /* string */ msg) + { + id = getId(id); + if (!this.validation_msgs[id]) + { + this.validation_msgs[id] = {}; + } + this.validation_msgs[id][error_type] = msg; + }, + + /** + * Reset all values in the form to the defaults specified in the markup. + */ + clearForm: function() + { + this.clearMessages(); + this.form.reset(); + this.postPopulateForm(); + }, + + /** + * Reset all values in the form to the defaults passed to the constructor. + */ + populateForm: function() + { + if (!this.default_value_map) + { + this.default_value_map = {}; + } + + this.clearMessages(); + + populateForm1.call(this); + + // let derived class adjust + + this.postPopulateForm(); + }, + + /** + * Hook for performing additional actions after + * populateForm() completes. + */ + postPopulateForm: function() + { + }, + + /** + * Check if form values have been modified. + * + * @return {boolean} false if all form elements have the default values passed to the constructor + */ + isChanged: function() + { + for (var i=0; itrue if both pre & post hooks are happy + */ + prepareForm: function() + { + this.getForm(); + + if (!this.prePrepareForm.apply(this, arguments)) + { + return false; + } + + // clear all errors + + this.clearMessages(); + + // fill in starting values + + this.populateForm(); + + return this.postPrepareForm.apply(this, arguments); + }, + + /** + * Hook called before prepareForm() executes. + * + * @return {boolean} false cancels prepareForm(). + */ + prePrepareForm: function() + { + return true; + }, + + /** + * Hook called after prepareForm() executes. + * + * @return {boolean} Return value from this function is returned by prepareForm(). + */ + postPrepareForm: function() + { + return true; + }, + + /** + * Set focus to first input field. If a page contains multiple forms, + * only call this for one of them. + */ + initFocus: function() + { + for (var i=0; i parseInt(m[2], 10)) + { + Y.log(e[i].name+' has min_length > max_length', 'error', 'FormManager'); + } + + if (e[i].value && hasLimit(m[1]) && + e[i].value.length < parseInt(m[1], 10)) + { + if (msg_list && msg_list.min_length) + { + msg = msg_list.min_length; + } + msg = Y.substitute(msg, {min: parseInt(m[1], 10), max: parseInt(m[2], 10)}); + + this.displayMessage(e[i], msg, 'error'); + status = false; + continue; + } + if (e[i].value && hasLimit(m[2]) && + e[i].value.length > parseInt(m[2], 10)) + { + if (msg_list && msg_list.max_length) + { + msg = msg_list.max_length; + } + msg = Y.substitute(msg, {min: parseInt(m[1], 10), max: parseInt(m[2], 10)}); + + this.displayMessage(e[i], msg, 'error'); + status = false; + continue; + } + } + + var m = e[i].className.match(integer_class_re); + if (m && m.length) + { + var msg = null; + if (msg_list && msg_list.integer) + { + msg = msg_list.integer; + } + else if (hasLimit(m[1]) && + hasLimit(m[2])) + { + if (parseInt(m[1], 10) > parseInt(m[2], 10)) + { + Y.log(e[i].name+' has min_value > max_value', 'error', 'FormManager'); + } + + msg = FormManager.Strings.integer_out_of_range; + } + else if (hasLimit(m[1])) + { + msg = FormManager.Strings.integer_too_small; + } + else if (hasLimit(m[2])) + { + msg = FormManager.Strings.integer_too_large; + } + else + { + msg = FormManager.Strings.integer; + } + msg = Y.substitute(msg, {min: parseInt(m[1], 10), max: parseInt(m[2], 10)}); + + var value = parseInt(e[i].value, 10); + if (e[i].value && + (!FormManager.integer_value_re.test(e[i].value) || + (hasLimit(m[1]) && value < parseInt(m[1], 10)) || + (hasLimit(m[2]) && value > parseInt(m[2], 10)))) + { + this.displayMessage(e[i], msg, 'error'); + status = false; + continue; + } + } + + var m = e[i].className.match(decimal_class_re); + if (m && m.length) + { + var msg = null; + if (msg_list && msg_list.decimal) + { + msg = msg_list.decimal; + } + else if (hasLimit(m[1]) && + hasLimit(m[2])) + { + if (parseFloat(m[1]) > parseFloat(m[2])) + { + Y.log(e[i].name+' has min_value > max_value', 'error', 'FormManager'); + } + + msg = FormManager.Strings.decimal_out_of_range; + } + else if (hasLimit(m[1])) + { + msg = FormManager.Strings.decimal_too_small; + } + else if (hasLimit(m[2])) + { + msg = FormManager.Strings.decimal_too_large; + } + else + { + msg = FormManager.Strings.decimal; + } + msg = Y.substitute(msg, {min: parseFloat(m[1], 10), max: parseFloat(m[2], 10)}); + + var value = parseFloat(e[i].value); + if (e[i].value && + (!FormManager.decimal_value_re.test(e[i].value) || + (hasLimit(m[1]) && value < parseFloat(m[1])) || + (hasLimit(m[2]) && value > parseFloat(m[2])))) + { + this.displayMessage(e[i], msg, 'error'); + status = false; + continue; + } + } + } + + if (this.validation.regex[e_id] && + !this.validation.regex[e_id].test(e[i].value)) + { + this.displayMessage(e[i], msg_list ? msg_list.regex : null, 'error'); + status = false; + continue; + } + + var f = this.validation.fn[e_id]; + if (Y.Lang.isFunction(f)) + { + var scope = this; + } + else if (Y.Lang.isString(f)) + { + var scope = this; + f = scope[f]; + } + else if (f && f.scope) + { + var scope = f.scope; + f = (Y.Lang.isString(f.fn) ? scope[f.fn] : f.fn); + } + else + { + f = null; + } + + if (f && !f.call(scope, this.form, Y.one(e[i]))) + { + status = false; + continue; + } + } + + if (!this.postValidateForm(this.form)) + { + status = false; + } + + if (!status) + { + this.notifyErrors(); + } + + return status; + }, + + /** + * Hook called at the end of validateForm(). This is the + * best place to put holistic validations that touch multiple form + * elements. + * + * @return {boolean} false if validation fails + */ + postValidateForm: function( + /* DOM element */ form) + { + return true; + }, + + /* ********************************************************************* + * Buttons can be disabled during submission. + */ + + /** + * Register a button that can be disabled. Buttons contained within + * the form DOM element are automatically registered. + * + * @param el {String|Object} The selector for the element or the element itself + */ + registerButton: function( + /* string/object */ el) + { + var info = + { + e: Y.one(el) + }; + + this.user_button_list.push(info); + }, + + /** + * Enable all the registered buttons. + */ + enableForm: function() + { + this.setFormEnabled(true); + }, + + /** + * Disable all the registered buttons. + */ + disableForm: function() + { + this.setFormEnabled(false); + }, + + /** + * Set the enabled state all the registered buttons. + * + * @param enabled {boolean} true to enable the form, false to disable the form + */ + setFormEnabled: function( + /* boolean */ enabled) + { + this.enabled = enabled; + + var disabled = ! enabled; + for (var i=0; itrue if there are any messages displayed, of any type + */ + hasMessages: function() + { + return this.has_messages; + }, + + /** + * @return {boolean} true if there are any error messages displayed + */ + hasErrors: function() + { + return this.has_errors; + }, + + /** + * Get the message type displayed for the row containing the specified element. + * + * @param e {String|Object} The selector for the element or the element itself + * @return {mixed} The status (String) or false. + */ + getRowStatus: function( + /* id/object */ e) + { + var p = Y.one(e).ancestor('.'+FormManager.row_marker_class); + return FormManager.getElementStatus(p); + }, + + /** + * Clear all messages in status_node and the form rows. + */ + clearMessages: function() + { + this.has_messages = false; + this.has_errors = false; + + if (this.status_node) + { + this.status_node.set('innerHTML', ''); + this.status_node.replaceClass(status_pattern, FormManager.status_none_class); + } + + for (var i=0; itrue if the form row should be scrolled into view + */ + displayMessage: function( + /* id/object */ e, + /* string */ msg, + /* string */ type, + /* boolean */ scroll) + { + if (Y.Lang.isUndefined(scroll)) + { + scroll = true; + } + + e = Y.one(e); + var p = e.ancestor('.'+FormManager.row_marker_class); + if (p && FormManager.statusTakesPrecendence(FormManager.getElementStatus(p), type)) + { + if (msg) + { + p.all('.'+FormManager.status_marker_class).set('innerHTML', msg); + } + + p.removeClass(row_status_pattern); + p.addClass(FormManager.row_status_prefix + type); + + var fieldset = e.ancestor('fieldset'); + if (fieldset && FormManager.statusTakesPrecendence(FormManager.getElementStatus(fieldset), type)) + { + fieldset.removeClass(row_status_pattern); + fieldset.addClass(FormManager.row_status_prefix + type); + } + + if (!this.has_messages && scroll && e.get('offsetHeight') > 0) + { + p.scrollIntoView(); + try + { + e.focus(); + } + catch (ex) + { + // no way to determine in IE if this will fail + } + } + + this.has_messages = true; + if (type == 'error') + { + this.has_errors = true; + } + } + }, + + /** + * Displays a generic message in status_node stating that + * the form data failed to validate. Override this if you want to get + * fancy. + */ + notifyErrors: function() + { + this.displayFormMessage(FormManager.Strings.validation_error, true, false); + }, + + /** + * Display a message in status_node. + * + * @param msg {String} The message + * @param error {boolean} true if the message is an error + * @param scroll {boolean} true if status_node should be scrolled into view + */ + displayFormMessage: function( + /* string */ msg, + /* boolean */ error, + /* boolean */ scroll) + { + if (Y.Lang.isUndefined(scroll)) + { + scroll = true; + } + + if (this.status_node) + { + if (!this.status_node.innerHTML) + { + this.status_node.replaceClass( + FormManager.status_none_class, + (error ? FormManager.status_failure_class : + FormManager.status_success_class)); + this.status_node.set('innerHTML', msg); + } + + if (scroll) + { + this.status_node.scrollIntoView(); + } + } + else + { + Y.log(msg, 'error', 'FormManager'); + } + } +}; + +Y.FormManager = FormManager;