// SpryValidationTextarea.js - version 0.15 - Spry Pre-Release 1.6
//
// Copyright (c) 2006. Adobe Systems Incorporated.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of Adobe Systems Incorporated nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

var Spry;
if (!Spry) Spry = {};
if (!Spry.Widget) Spry.Widget = {};

Spry.Widget.BrowserSniff = function ()
{
    var b = navigator.appName.toString();
    var up = navigator.platform.toString();
    var ua = navigator.userAgent.toString();

    this.mozilla = this.ie = this.opera = r = false;
    var re_opera = /Opera.([0-9\.]*)/i;
    var re_msie = /MSIE.([0-9\.]*)/i;
    var re_gecko = /gecko/i;
    var re_safari = /safari\/([\d\.]*)/i;

    if (ua.match(re_opera))
    {
        r = ua.match(re_opera);
        this.opera = true;
        this.version = parseFloat(r[1]);
    } else if (ua.match(re_msie))
    {
        r = ua.match(re_msie);
        this.ie = true;
        this.version = parseFloat(r[1]);
    } else if (ua.match(re_safari))
    {
        this.safari = true;
        this.version = 1.4;
    } else if (ua.match(re_gecko))
    {
        var re_gecko_version = /rv:\s*([0-9\.]+)/i;
        r = ua.match(re_gecko_version);
        this.mozilla = true;
        this.version = parseFloat(r[1]);
    }
    this.windows = this.mac = this.linux = false;

    this.Platform = ua.match(/windows/i) ? "windows" :
					(ua.match(/linux/i) ? "linux" :
					(ua.match(/mac/i) ? "mac" :
					ua.match(/unix/i) ? "unix" : "unknown"));
    this[this.Platform] = true;
    this.v = this.version;

    if (this.safari && this.mac && this.mozilla)
    {
        this.mozilla = false;
    }
};

Spry.is = new Spry.Widget.BrowserSniff();


Spry.Widget.ValidationTextarea = function (element, options)
{

    options = Spry.Widget.Utils.firstValid(options, {});
    this.flags = { locked: false };
    this.options = {};
    this.element = element;
    this.init(element);

    if (!this.isBrowserSupported())
    {
        return;
    }

    options.useCharacterMasking = Spry.Widget.Utils.firstValid(options.useCharacterMasking, true);
    options.hint = Spry.Widget.Utils.firstValid(options.hint, '');
    options.isRequired = Spry.Widget.Utils.firstValid(options.isRequired, true);
    options.additionalError = Spry.Widget.Utils.firstValid(options.additionalError, false);

    Spry.Widget.Utils.setOptions(this, options);
    Spry.Widget.Utils.setOptions(this.options, options);

    if (options.additionalError)
        this.additionalError = this.getElement(options.additionalError);

    //make sure we validate at least on submit
    var validateOn = ['submit'].concat(Spry.Widget.Utils.firstValid(this.options.validateOn, []));
    validateOn = validateOn.join(",");
    this.validateOn = 0;
    this.validateOn = this.validateOn | (validateOn.indexOf('submit') != -1 ? Spry.Widget.ValidationTextarea.ONSUBMIT : 0);
    this.validateOn = this.validateOn | (validateOn.indexOf('blur') != -1 ? Spry.Widget.ValidationTextarea.ONBLUR : 0);
    this.validateOn = this.validateOn | (validateOn.indexOf('change') != -1 ? Spry.Widget.ValidationTextarea.ONCHANGE : 0);

    if (Spry.Widget.ValidationTextarea.onloadDidFire)
    {
        this.attachBehaviors();
    } else
    {
        Spry.Widget.ValidationTextarea.loadQueue.push(this);
    }
};

Spry.Widget.ValidationTextarea.ONCHANGE = 1;
Spry.Widget.ValidationTextarea.ONBLUR = 2;
Spry.Widget.ValidationTextarea.ONSUBMIT = 4;

Spry.Widget.ValidationTextarea.INITIAL = 'Initial';
Spry.Widget.ValidationTextarea.REQUIRED = 'Required';
Spry.Widget.ValidationTextarea.INVALID = 'Invalid Format';
Spry.Widget.ValidationTextarea.MINIMUM = 'Minimum Number of Chars Not Met';
Spry.Widget.ValidationTextarea.MAXIMUM = 'Maximum Number of Chars Exceeded';
Spry.Widget.ValidationTextarea.VALID = 'Valid';

Spry.Widget.ValidationTextarea.prototype.init = function (element)
{
    this.element = this.getElement(element);
    this.event_handlers = [];

    this.requiredClass = "textareaRequiredState";
    this.invalidCharsMaxClass = "textareaMaxCharsState";
    this.invalidCharsMinClass = "textareaMinCharsState";
    this.validClass = "textareaValidState";
    this.focusClass = "textareaFocusState";
    this.hintClass = "textareaHintState";
    this.textareaFlashClass = "textareaFlashState";

    this.isMaxInvalid = false;
    this.isMinInvalid = false;
    this.isRequireInvalid = false;

    this.safariClicked = false;
    this.state = Spry.Widget.ValidationTextarea.INITIAL;
};

Spry.Widget.ValidationTextarea.prototype.destroy = function ()
{
    if (this.event_handlers)
        for (var i = 0; i < this.event_handlers.length; i++)
        {
            Spry.Widget.Utils.removeEventListener(this.event_handlers[i][0], this.event_handlers[i][1], this.event_handlers[i][2], false);
        }
    try { delete this.element; } catch (err) { }
    try { delete this.input; } catch (err) { }
    try { delete this.counterEl; } catch (err) { }
    try { delete this.form; } catch (err) { }
    try { delete this.event_handlers; } catch (err) { }
    try { this.cursorPosition.destroy(); } catch (err) { }
    try { delete this.cursorPosition; } catch (err) { }
    try { this.initialCursor.destroy(); } catch (err) { }
    try { delete this.initialCursor; } catch (err) { }

    var q = Spry.Widget.Form.onSubmitWidgetQueue;
    var qlen = q.length;
    for (var i = 0; i < qlen; i++)
    {
        if (q[i] == this)
        {
            q.splice(i, 1);
            break;
        }
    }
};

Spry.Widget.ValidationTextarea.prototype.isDisabled = function ()
{
    return this.input && (this.input.disabled || this.input.readOnly) || !this.input;
};

Spry.Widget.ValidationTextarea.prototype.getElement = function (ele)
{
    if (ele && typeof ele == "string")
        return document.getElementById(ele);
    return ele;
};


Spry.Widget.ValidationTextarea.addLoadListener = function (handler)
{
    if (typeof window.addEventListener != 'undefined')
    {
        window.addEventListener('load', handler, false);
    } else if (typeof document.addEventListener != 'undefined')
    {
        document.addEventListener('load', handler, false);
    } else if (typeof window.attachEvent != 'undefined')
    {
        window.attachEvent('onload', handler);
    }
};

Spry.Widget.ValidationTextarea.processLoadQueue = function (handler)
{
    Spry.Widget.ValidationTextarea.onloadDidFire = true;
    var q = Spry.Widget.ValidationTextarea.loadQueue;
    var qlen = q.length;
    for (var i = 0; i < qlen; i++)
    {
        q[i].attachBehaviors();
    }
};

Spry.Widget.ValidationTextarea.onloadDidFire = false;
Spry.Widget.ValidationTextarea.loadQueue = [];
Spry.Widget.ValidationTextarea.addLoadListener(Spry.Widget.ValidationTextarea.processLoadQueue);
Spry.Widget.ValidationTextarea.addLoadListener(function ()
{
    Spry.Widget.Utils.addEventListener(window, "unload", Spry.Widget.Form.destroyAll, false);
});

Spry.Widget.ValidationTextarea.prototype.isBrowserSupported = function ()
{
    return Spry.is.ie && Spry.is.v >= 5 && Spry.is.windows
		||
	Spry.is.mozilla && Spry.is.v >= 1.4
		||
	Spry.is.safari
		||
	Spry.is.opera && Spry.is.v >= 9;
};

/* 
* register our input to different event notifiers 
*
*/
Spry.Widget.ValidationTextarea.prototype.attachBehaviors = function ()
{
    if (this.element)
    {
        if (this.element.nodeName == "TEXTAREA")
        {
            this.input = this.element;
        } else
        {
            this.input = Spry.Widget.Utils.getFirstChildWithNodeNameAtAnyLevel(this.element, "TEXTAREA");
        }
    }
    if (this.options && this.options.counterType && (this.options.counterType == 'chars_count' || this.options.counterType == 'chars_remaining'))
    {
        this.counterEl = document.getElementById(this.options.counterId);
        this.counterChar();
    }

    if (this.input)
    {
        this.input.setAttribute("AutoComplete", "off");
        this.putHint();
        this.cursorPosition = new Spry.Widget.SelectionDescriptor(this.input);

        var self = this;
        this.event_handlers = [];

        //attach the pattern related event handlers (to stop invalid keys) 
        if (this.useCharacterMasking)
        {
            if (Spry.is.ie)
            {
                this.event_handlers.push([this.input, "propertychange", function (e) { return self.onKeyEvent(e || event); } ]);
                this.event_handlers.push([this.input, "drop", function (e) { return self.onDrop(e || event); } ]);
                this.event_handlers.push([this.input, "keypress", function (e) { return self.onKeyPress(e || event); } ]);
            } else
            {
                this.event_handlers.push([this.input, "keydown", function (e) { return self.onKeyDown(e); } ]);
                this.event_handlers.push([this.input, "keypress", function (e) { return self.safariKeyPress(e); } ]);
                this.event_handlers.push([this.input, "keyup", function (e) { return self.safariValidate(e); } ]);
                if (Spry.is.safari)
                {
                    this.event_handlers.push([this.input, "mouseup", function (e) { return self.safariMouseUp(e); } ]);
                    this.event_handlers.push([this.input, "mousedown", function (e) { return self.safariMouseDown(e); } ]);
                } else
                {
                    //Firefox bug: 355219
                    //this.event_handlers.push([this.input, "input", function(e) { self.onKeyEvent(e); return true;}]);
                    this.event_handlers.push([this.input, "dragdrop", function (e) { return self.onKeyEvent(e); } ]);
                    this.event_handlers.push([this.input, "dragenter", function (e) { self.removeHint(); return self.onKeyDown(e); } ]);
                    this.event_handlers.push([this.input, "dragexit", function (e) { return self.putHint(); } ]);
                }
            }
            // we need to save an initial state in case of invalid input
            this.event_handlers.push([this.input, "keydown", function (e) { return self.onKeyDown(e || event); } ]);
        }

        this.event_handlers.push([this.input, "focus", function (e) { return self.onFocus(e || event); } ]);
        this.event_handlers.push([this.input, "mousedown", function (e) { return self.onMouseDown(e || event); } ]);
        this.event_handlers.push([this.input, "blur", function (e) { return self.onBlur(e || event); } ]);

        if (this.validateOn & Spry.Widget.ValidationTextarea.ONCHANGE)
        {
            if (Spry.is.ie)
            {
                this.event_handlers.push([this.input, "propertychange", function (e) { return self.onChange(e || event); } ]);
                this.event_handlers.push([this.input, "drop", function (e) { return self.onChange(e || event); } ]);
            } else
            {
                this.event_handlers.push([this.input, "keydown", function (e) { return self.onKeyDown(e); } ]);
                this.event_handlers.push([this.input, "keypress", function (e) { return self.safariChangeKeyPress(e); } ]);
                this.event_handlers.push([this.input, "keyup", function (e) { return self.safariChangeValidate(e); } ]);
                if (Spry.is.safari)
                {
                    this.event_handlers.push([this.input, "mouseup", function (e) { return self.safariChangeMouseUp(e); } ]);
                    this.event_handlers.push([this.input, "mousedown", function (e) { return self.safariMouseDown(e); } ]);
                } else
                {
                    // Firefox bug: 355219
                    //this.event_handlers.push([this.input, "input", function(e) { return self.onChange(e); }]);
                    this.event_handlers.push([this.input, "dragdrop", function (e) { return self.onChange(e); } ]);
                    this.event_handlers.push([this.input, "dragenter", function (e) { self.removeHint(); return self.onKeyDown(e); } ]);
                    this.event_handlers.push([this.input, "dragexit", function (e) { return self.putHint(); } ]);
                }
            }
        }
        // The counter should be called directly when no enforcement or change restrictions exists
        if (!(this.validateOn & Spry.Widget.ValidationTextarea.ONCHANGE) && !this.useCharacterMasking)
        {
            if (Spry.is.ie)
            {
                this.event_handlers.push([this.input, "propertychange", function (e) { return self.counterChar(); } ]);
                this.event_handlers.push([this.input, "drop", function (e) { return self.counterChar(); } ]);
            } else
            {
                this.event_handlers.push([this.input, "keypress", function (e) { return self.counterChar(); } ]);
                this.event_handlers.push([this.input, "keyup", function (e) { return self.counterChar(); } ]);
                if (Spry.is.safari)
                {
                    this.event_handlers.push([this.input, "mouseup", function (e) { return self.counterChar(); } ]);
                } else
                {
                    // Firefox bug: 355219
                    //this.event_handlers.push([this.input, "input", function(e) { return self.onChange(e); }]);
                    this.event_handlers.push([this.input, "dragdrop", function (e) { return self.counterChar(); } ]);
                }
            }
        }

        for (var i = 0; i < this.event_handlers.length; i++)
        {
            Spry.Widget.Utils.addEventListener(this.event_handlers[i][0], this.event_handlers[i][1], this.event_handlers[i][2], false);
        }

        this.form = Spry.Widget.Utils.getFirstParentWithNodeName(this.input, "FORM");
        if (this.form)
        {
            if (!this.form.attachedSubmitHandler && !this.form.onsubmit)
            {
                this.form.onsubmit = function (e) { e = e || event; return Spry.Widget.Form.onSubmit(e, e.srcElement || e.currentTarget) };
                this.form.attachedSubmitHandler = true;
            }
            if (!this.form.attachedResetHandler)
            {
                Spry.Widget.Utils.addEventListener(this.form, "reset", function (e) { e = e || event; return Spry.Widget.Form.onReset(e, e.srcElement || e.currentTarget) }, false);
                this.form.attachedResetHandler = true;
            }
            // add the currrent widget to the "onSubmit" check queue;
            Spry.Widget.Form.onSubmitWidgetQueue.push(this);
        }
    }
    this.saveState();
};

Spry.Widget.ValidationTextarea.prototype.onTyping = function (e)
{
    if (this.input.disabled == true || this.input.readOnly == true)
    {
        return;
    }

    if (!this.initialCursor)
    {
        this.initialCursor = this.cursorPosition;
    }
    // on IE a stack overflow appears
    if (this.flags.locked)
    {
        return true;
    }

    var val = this.input.value;

    var ret = true;

    if (this.flags.hintOn)
    {
        return true;
    }
    if (e && this.input && this.options && this.options.maxChars > 0 && ret)
    {
        if (val.length > this.options.maxChars &&
							((!Spry.Widget.Utils.isSpecialKey(e) && this.cursorPosition.start == this.cursorPosition.end) ||
				 			 (Spry.Widget.Utils.isSpecialKey(e) && val != this.initialValue) ||
				 				this.cursorPosition.start != this.cursorPosition.end)
			 )
        {
            // cut the extra chars and display error
            this.flags.locked = true;
            var initial = this.initialValue;
            var start = this.initialCursor.start;
            var end = this.initialCursor.end;
            if (initial.length && this.initialCursor.end < initial.length)
            {
                // we try to behave more like maxlength textfield
                var tmp = end - start + this.options.maxChars - initial.length;
                var newValue = initial.substring(0, start) + val.substring(start, start + tmp) + initial.substring(end, initial.length < this.options.maxChars ? initial.length : this.options.maxChars);
                end = start + tmp;
            } else
            {
                var newValue = val.substring(0, this.options.maxChars);
                end = start = this.options.maxChars;
            }
            if (Spry.is.ie)
            {
                this.input.innerText = newValue;
            } else
            {
                this.input.value = newValue;
            }
            this.redTextFlash();
            this.cursorPosition.moveTo(end, end);
            this.flags.locked = false;
            ret = false;
        } else
        {
            this.setState(Spry.Widget.ValidationTextarea.VALID);
            this.isMaxInvalid = false;
        }
    }
    this.counterChar();
    return ret;
};

Spry.Widget.ValidationTextarea.prototype.validateMinRequired = function (val)
{
    var oldInvalid = false;
    if (typeof this.notFireMinYet == 'undefined')
    {
        this.notFireMinYet = false;
    } else
    {
        oldInvalid = true;
        this.notFireMinYet = true;
    }
    if (this.onBlurOn)
    {
        this.notFireMinYet = true;
    } else if (!this.onKeyEventOn)
    {
        this.notFireMinYet = true;
    }

    if (this.input && this.options && this.options.isRequired)
    {
        if (val.length > 0 && this.isRequireInvalid && (!this.hint || (this.hint && !this.flags.hintOn) || (this.hint && val != this.hint)))
        {
            this.switchClassName(this.validClass);
            this.setState(Spry.Widget.ValidationTextarea.VALID);
            this.isRequireInvalid = false;
        } else if ((val.length == 0 || !(!this.hint || (this.hint && !this.flags.hintOn) || (this.hint && val != this.hint))) && (!this.isRequireInvalid || oldInvalid))
        {
            if (this.notFireMinYet || Spry.is.ie)
            {
                this.switchClassName(this.requiredClass);
                this.setState(Spry.Widget.ValidationTextarea.REQUIRED);
            }
            this.isRequireInvalid = true;
            this.isMinInvalid = false;
        }
    }
    if (this.input && this.options && this.options.minChars > 0 && !this.isRequireInvalid)
    {
        if (val.length >= this.options.minChars && (!this.hint || (this.hint && !this.flags.hintOn) || (this.hint && val != this.hint)) && this.isMinInvalid)
        {
            this.switchClassName(this.validClass);
            this.setState(Spry.Widget.ValidationTextarea.VALID);
            this.isMinInvalid = false;
        } else if ((val.length < this.options.minChars || (this.hint && val == this.hint && this.flags.hintOn)) && !this.isMinInvalid)
        {
            this.switchClassName(this.invalidCharsMinClass);
            this.setState(Spry.Widget.ValidationTextarea.MINIMUM);
            this.isMinInvalid = true;
        }
    }
};
Spry.Widget.ValidationTextarea.prototype.counterChar = function ()
{
    if (!this.counterEl || !this.options || !this.options.counterType || (this.options.counterType != 'chars_remaining' && this.options.counterType != 'chars_count'))
    {
        return;
    }

    if (this.options.counterType == 'chars_remaining')
    {
        if (this.options.maxChars > 0)
        {
            if (this.flags.hintOn)
            {
                this.setCounterElementValue(this.options.maxChars);
            } else
            {
                if (this.options.maxChars > this.input.value.length)
                {
                    this.setCounterElementValue(this.options.maxChars - this.input.value.length);
                } else
                {
                    this.setCounterElementValue(0);
                }
            }
        }
    } else
    {
        if (this.flags.hintOn)
        {
            this.setCounterElementValue(0);
        } else
        {
            if (this.useCharacterMasking && typeof this.options.maxChars != 'undefined' && this.options.maxChars < this.input.value.length)
            {
                this.setCounterElementValue(this.options.maxChars);
            } else
            {
                this.setCounterElementValue(this.input.value.length);
            }
        }
    }
};

Spry.Widget.ValidationTextarea.prototype.setCounterElementValue = function (val)
{
    if (this.counterEl.nodeName.toLowerCase() != 'input' &&
			this.counterEl.nodeName.toLowerCase() != 'textarea' &&
			this.counterEl.nodeName.toLowerCase() != 'select' &&
			this.counterEl.nodeName.toLowerCase() != 'img')
    {
        this.counterEl.innerHTML = val;
    }
};
Spry.Widget.ValidationTextarea.prototype.reset = function ()
{
    this.removeHint();
    this.removeClassName(this.requiredClass);
    this.removeClassName(this.invalidCharsMinClass);
    this.removeClassName(this.invalidCharsMaxClass);
    this.removeClassName(this.validClass);
    this.setState(Spry.Widget.ValidationTextarea.INITIAL);
    var self = this;
    setTimeout(function () { self.putHint(); self.counterChar(); }, 10);
};

Spry.Widget.ValidationTextarea.prototype.validate = function ()
{
    if (this.input.disabled == true || this.input.readOnly == true)
    {
        return true;
    }

    var val = this.input.value;
    this.validateMinRequired(val);

    var ret = !this.isMinInvalid && !this.isRequireInvalid;

    if (ret && this.options.maxChars > 0 && !this.useCharacterMasking)
    {
        if (val.length <= this.options.maxChars || (this.hint && this.hint == val && this.flags.hintOn))
        {
            this.switchClassName(this.validClass);
            this.setState(Spry.Widget.ValidationTextarea.VALID);
            this.isMaxInvalid = false;
        } else
        {
            this.switchClassName(this.invalidCharsMaxClass);
            this.setState(Spry.Widget.ValidationTextarea.MAXIMUM);
            this.isMaxInvalid = true;
        }
    }
    ret = ret && !this.isMaxInvalid;
    if (ret)
    {
        this.switchClassName(this.validClass);
    }
    this.counterChar();
    return ret;
};

Spry.Widget.ValidationTextarea.prototype.setState = function (newstate)
{
    this.state = newstate;
};

Spry.Widget.ValidationTextarea.prototype.getState = function ()
{
    return this.state;
};

Spry.Widget.ValidationTextarea.prototype.removeHint = function ()
{
    if (this.flags.hintOn)
    {
        this.flags.locked = true;
        this.input.value = "";
        this.flags.locked = false;
        this.flags.hintOn = false;
        this.removeClassName(this.hintClass);
    }
};

Spry.Widget.ValidationTextarea.prototype.putHint = function ()
{
    if (this.hint && this.input.value == "")
    {
        this.flags.hintOn = true;
        this.input.value = this.hint;
        this.addClassName(this.hintClass);
    }
};

Spry.Widget.ValidationTextarea.prototype.redTextFlash = function ()
{
    var self = this;
    this.addClassName(this.textareaFlashClass);
    setTimeout(function ()
    {
        self.removeClassName(self.textareaFlashClass)
    }, 200);
};


Spry.Widget.ValidationTextarea.prototype.onKeyPress = function (e)
{
    //ENTER has length 2 on IE Windows, so will exceed maxLength on proximity
    if (Spry.is.ie && Spry.is.windows && e.keyCode == 13)
    {
        if ((this.initialCursor.length + this.options.maxChars - this.input.value.length) < 2)
        {
            Spry.Widget.Utils.stopEvent(e);
            return false;
        }
    }
};

Spry.Widget.ValidationTextarea.prototype.onKeyDown = function (e)
{
    this.saveState();
    this.keyCode = e.keyCode;
    return true;
};

/*
* hadle for the max chars restrictions
* if key pressed or the input text is invalid it returns false
* 
*/
Spry.Widget.ValidationTextarea.prototype.onKeyEvent = function (e)
{
    // on IE we look only for this input value changes
    if (e.type == 'propertychange' && e.propertyName != 'value')
    {
        return true;
    }

    var allow = this.onTyping(e);

    if (!allow)
    {
        Spry.Widget.Utils.stopEvent(e);
    }
    //return allow;
};

/*
* handle for the min or required value
* if the input text is invalid it returns false
* 
*/
Spry.Widget.ValidationTextarea.prototype.onChange = function (e)
{
    if (Spry.is.ie && e && e.type == 'propertychange' && e.propertyName != 'value')
    {
        return true;
    }

    if (this.flags.drop)
    {
        //delay this if it's a drop operation
        var self = this;
        setTimeout(function ()
        {
            self.flags.drop = false;
            self.onChange(null);
        }, 0);
        return true;
    }
    if (this.flags.hintOn)
    {
        return true;
    }
    this.onKeyEventOn = true;
    var answer = this.validate();
    this.onKeyEventOn = false;
    return answer;
};

Spry.Widget.ValidationTextarea.prototype.onMouseDown = function (e)
{
    if (this.flags.active)
    {
        //mousedown fires before focus
        //avoid double saveState on first focus by mousedown by checking if the control has focus
        //do nothing if it's not focused because saveState will be called onfocus
        this.saveState();
    }
};

Spry.Widget.ValidationTextarea.prototype.onDrop = function (e)
{
    //mark that a drop operation is in progress to avoid race conditions with event handlers for other events
    //especially onchange and onfocus
    this.flags.drop = true;
    this.removeHint();

    if (Spry.is.ie)
    {
        var rng = document.body.createTextRange();
        rng.moveToPoint(e.x, e.y);
        rng.select();
    }

    this.saveState();
    this.flags.active = true;
    this.addClassName(this.focusClass);
};

Spry.Widget.ValidationTextarea.prototype.onFocus = function (e)
{
    if (this.flags.drop)
    {
        return;
    }
    this.removeHint();
    this.saveState();
    this.flags.active = true;
    this.addClassName(this.focusClass);
};

Spry.Widget.ValidationTextarea.prototype.onBlur = function (e)
{
    this.removeClassName(this.focusClass);

    if (this.validateOn & Spry.Widget.ValidationTextarea.ONBLUR)
    {
        this.onBlurOn = true;
        this.validate();
        this.onBlurOn = false;
    }

    this.flags.active = false;
    var self = this;
    setTimeout(function () { self.putHint(); }, 10);
};

Spry.Widget.ValidationTextarea.prototype.safariMouseDown = function (e)
{
    this.safariClicked = true;
};
Spry.Widget.ValidationTextarea.prototype.safariChangeMouseUp = function (e)
{
    if (!this.safariClicked)
    {
        this.onKeyDown(e);
        return this.safariChangeValidate(e, false);
    } else
    {
        this.safariClicked = false;
        return true;
    }
};

Spry.Widget.ValidationTextarea.prototype.safariMouseUp = function (e)
{
    if (!this.safariClicked)
    {
        this.onKeyDown(e);
        return this.safariValidate(e, false);
    } else
    {
        this.safariClicked = false;
        return true;
    }
};

Spry.Widget.ValidationTextarea.prototype.safariKeyPress = function (e)
{
    this.safariFlag = new Date();
    return this.safariValidate(e, true);
};

Spry.Widget.ValidationTextarea.prototype.safariValidate = function (e, recall)
{
    if (e.keyCode && Spry.Widget.Utils.isSpecialKey(e) && e.keyCode != 8 && e.keyCode != 46)
    {
        return true;
    }
    var answer = this.onTyping(e);

    // the answer to this is not yet final - we schedule another closing check
    if (new Date() - this.safariFlag < 1000 && recall)
    {
        var self = this;
        setTimeout(function () { self.safariValidate(e, false); }, 1000);
    }
    return answer;
};

Spry.Widget.ValidationTextarea.prototype.safariChangeKeyPress = function (e)
{
    this.safariChangeFlag = new Date();
    return this.safariChangeValidate(e, true);
};

Spry.Widget.ValidationTextarea.prototype.safariChangeValidate = function (e, recall)
{

    if (e.keyCode && Spry.Widget.Utils.isSpecialKey(e) && e.keyCode != 8 && e.keyCode != 46)
    {
        return true;
    }
    var answer = this.onChange(e);

    // the answer to this is not yet final - we schedule another closing check
    if (new Date() - this.safariChangeFlag < 1000 && recall)
    {
        var self = this;
        setTimeout(function () { self.safariChangeValidate(e, false); }, 1000 - new Date() + this.safariChangeFlag);
    }
    return answer;
};

/*
* save an initial state of the input to restore if the value is invalid
* 
*/
Spry.Widget.ValidationTextarea.prototype.saveState = function (e)
{

    // we don't need this initial value that is already invalid
    if (this.options.maxChars > 0 && this.input.value.length > this.options.maxChars)
    {
        return;
    }
    this.cursorPosition.update();
    if (!this.flags.hintOn)
    {
        this.initialValue = this.input.value;
    } else
    {
        this.initialValue = '';
    }
    this.initialCursor = this.cursorPosition;
    return true;
};

Spry.Widget.ValidationTextarea.prototype.checkClassName = function (ele, className)
{
    if (!ele || !className)
    {
        return false;
    }
    if (typeof ele == 'string')
    {
        ele = document.getElementById(ele);
        if (!ele)
        {
            return false;
        }
    }
    if (!ele.className)
    {
        ele.className = ' ';
    }
    return ele;
};

Spry.Widget.ValidationTextarea.prototype.switchClassName = function (className)
{
    var classes = [this.invalidCharsMaxClass, this.validClass, this.requiredClass, this.invalidCharsMinClass];

    for (var k = 0; k < classes.length; k++)
    {
        if (classes[k] != className)
        {
            this.removeClassName(classes[k]);
        }
    }

    this.addClassName(className);
};

Spry.Widget.ValidationTextarea.prototype.addClassName = function (clssName)
{
    var ele = this.checkClassName(this.element, clssName);
    var add = this.checkClassName(this.additionalError, clssName);

    if (!ele || ele.className.search(new RegExp("\\b" + clssName + "\\b")) != -1)
    {
        return;
    }
    this.element.className += ' ' + clssName;
    if (add)
        add.className += ' ' + clssName;
};

Spry.Widget.ValidationTextarea.prototype.removeClassName = function (className)
{
    var ele = this.checkClassName(this.element, className);
    var add = this.checkClassName(this.additionalError, className);
    if (!ele)
    {
        return;
    }
    ele.className = ele.className.replace(new RegExp("\\s*\\b" + className + "\\b", "g"), '');
    if (add)
    {
        add.className = add.className.replace(new RegExp("\\s*\\b" + className + "\\b", "g"), '');
    }
};

/**
* SelectionDescriptor is a wrapper for input type text selection methods and properties 
* as implemented by various  browsers
*/
Spry.Widget.SelectionDescriptor = function (element)
{
    this.element = element;
    this.update();
};

Spry.Widget.SelectionDescriptor.prototype.update = function ()
{
    if (Spry.is.ie && Spry.is.windows)
    {
        var sel = this.element.ownerDocument.selection;
        if (this.element.nodeName == "TEXTAREA")
        {
            if (sel.type != 'None')
            {
                try { var range = sel.createRange(); } catch (err) { return; }
                if (range.parentElement() == this.element)
                {
                    var range_all = this.element.ownerDocument.body.createTextRange();
                    range_all.moveToElementText(this.element);
                    for (var sel_start = 0; range_all.compareEndPoints('StartToStart', range) < 0; sel_start++)
                    {
                        range_all.moveStart('character', 1);
                    }
                    this.start = sel_start;
                    // create a selection of the whole this.element
                    range_all = this.element.ownerDocument.body.createTextRange();
                    range_all.moveToElementText(this.element);
                    for (var sel_end = 0; range_all.compareEndPoints('StartToEnd', range) < 0; sel_end++)
                    {
                        range_all.moveStart('character', 1);
                    }
                    this.end = sel_end;
                    this.length = this.end - this.start;
                    // get selected and surrounding text
                    this.text = range.text;
                }
            }
        } else if (this.element.nodeName == "INPUT")
        {
            try { this.range = sel.createRange(); } catch (err) { return; }
            this.length = this.range.text.length;
            var clone = this.range.duplicate();
            this.start = -clone.moveStart("character", -10000);
            clone = this.range.duplicate();
            clone.collapse(false);
            this.end = -clone.moveStart("character", -10000);
            this.text = this.range.text;
        }
    } else
    {
        var tmp = this.element;
        var selectionStart = 0;
        var selectionEnd = 0;

        try { selectionStart = tmp.selectionStart; } catch (err) { }
        try { selectionEnd = tmp.selectionEnd; } catch (err) { }

        if (Spry.is.safari)
        {
            if (selectionStart == 2147483647)
            {
                selectionStart = 0;
            }
            if (selectionEnd == 2147483647)
            {
                selectionEnd = 0;
            }
        }
        this.start = selectionStart;
        this.end = selectionEnd;
        this.length = selectionEnd - selectionStart;
        this.text = this.element.value.substring(selectionStart, selectionEnd);
    }
};
Spry.Widget.SelectionDescriptor.prototype.destroy = function ()
{
    try { delete this.range } catch (err) { }
    try { delete this.element } catch (err) { }
};

Spry.Widget.SelectionDescriptor.prototype.moveTo = function (start, end)
{
    if (Spry.is.ie && Spry.is.windows)
    {
        if (this.element.nodeName == "TEXTAREA")
        {
            var ta_range = this.element.createTextRange();
            this.range = this.element.createTextRange();
            this.range.move("character", start);
            this.range.moveEnd("character", end - start);

            var c1 = this.range.compareEndPoints("StartToStart", ta_range);
            if (c1 < 0)
            {
                this.range.setEndPoint("StartToStart", ta_range);
            }

            var c2 = this.range.compareEndPoints("EndToEnd", ta_range);
            if (c2 > 0)
            {
                this.range.setEndPoint("EndToEnd", ta_range);
            }
        } else if (this.element.nodeName == "INPUT")
        {
            this.range = this.element.ownerDocument.selection.createRange();
            this.range.move("character", -10000);
            this.start = this.range.moveStart("character", start);
            this.end = this.start + this.range.moveEnd("character", end - start);
        }
        this.range.select();
    } else
    {
        this.start = start;
        try { this.element.selectionStart = start; } catch (err) { }
        this.end = end;
        try { this.element.selectionEnd = end; } catch (err) { }
    }
    this.ignore = true;
    this.update();
};

//////////////////////////////////////////////////////////////////////
//
// Spry.Widget.Form - common for all widgets
//
//////////////////////////////////////////////////////////////////////

if (!Spry.Widget.Form) Spry.Widget.Form = {};
if (!Spry.Widget.Form.onSubmitWidgetQueue) Spry.Widget.Form.onSubmitWidgetQueue = [];

if (!Spry.Widget.Form.validate)
{
    Spry.Widget.Form.validate = function (vform)
    {
        var isValid = true;
        var isElementValid = true;
        var q = Spry.Widget.Form.onSubmitWidgetQueue;
        var qlen = q.length;
        for (var i = 0; i < qlen; i++)
        {
            if (!q[i].isDisabled() && q[i].form == vform)
            {
                isElementValid = q[i].validate();
                isValid = isElementValid && isValid;
            }
        }
        return isValid;
    }
};

if (!Spry.Widget.Form.onSubmit)
{
    Spry.Widget.Form.onSubmit = function (e, form)
    {
        if (Spry.Widget.Form.validate(form) == false)
        {
            return false;
        }
        return true;
    };
};

if (!Spry.Widget.Form.onReset)
{
    Spry.Widget.Form.onReset = function (e, vform)
    {
        var q = Spry.Widget.Form.onSubmitWidgetQueue;
        var qlen = q.length;
        for (var i = 0; i < qlen; i++)
        {
            if (!q[i].isDisabled() && q[i].form == vform && typeof (q[i].reset) == 'function')
            {
                q[i].reset();
            }
        }
        return true;
    };
};

if (!Spry.Widget.Form.destroy)
{
    Spry.Widget.Form.destroy = function (form)
    {
        var q = Spry.Widget.Form.onSubmitWidgetQueue;
        for (var i = 0; i < Spry.Widget.Form.onSubmitWidgetQueue.length; i++)
        {
            if (q[i].form == form && typeof (q[i].destroy) == 'function')
            {
                q[i].destroy();
                i--;
            }
        }
    }
};

if (!Spry.Widget.Form.destroyAll)
{
    Spry.Widget.Form.destroyAll = function ()
    {
        var q = Spry.Widget.Form.onSubmitWidgetQueue;
        for (var i = 0; i < Spry.Widget.Form.onSubmitWidgetQueue.length; i++)
        {
            if (typeof (q[i].destroy) == 'function')
            {
                q[i].destroy();
                i--;
            }
        }
    }
};

//////////////////////////////////////////////////////////////////////
//
// Spry.Widget.Utils
//
//////////////////////////////////////////////////////////////////////

if (!Spry.Widget.Utils) Spry.Widget.Utils = {};

Spry.Widget.Utils.setOptions = function (obj, optionsObj, ignoreUndefinedProps)
{
    if (!optionsObj)
        return;
    for (var optionName in optionsObj)
    {
        if (ignoreUndefinedProps && optionsObj[optionName] == undefined)
            continue;
        obj[optionName] = optionsObj[optionName];
    }
};

Spry.Widget.Utils.firstValid = function ()
{
    var ret = null;
    for (var i = 0; i < Spry.Widget.Utils.firstValid.arguments.length; i++)
    {
        if (typeof (Spry.Widget.Utils.firstValid.arguments[i]) != 'undefined')
        {
            ret = Spry.Widget.Utils.firstValid.arguments[i];
            break;
        }
    }
    return ret;
};

Spry.Widget.Utils.specialSafariNavKeys = ",63232,63233,63234,63235,63272,63273,63275,63276,63277,63289,";

Spry.Widget.Utils.specialCharacters = ",8,9,16,17,18,20,27,33,34,35,36,37,38,39,40,45,46,91,92,93,144,192,63232,";
Spry.Widget.Utils.specialCharacters += Spry.Widget.Utils.specialSafariNavKeys;

Spry.Widget.Utils.isSpecialKey = function (ev)
{
    return Spry.Widget.Utils.specialCharacters.indexOf("," + ev.keyCode + ",") != -1;
};

Spry.Widget.Utils.getFirstChildWithNodeNameAtAnyLevel = function (node, nodeName)
{
    var elements = node.getElementsByTagName(nodeName);
    if (elements)
    {
        return elements[0];
    }
    return null;
};

Spry.Widget.Utils.getFirstParentWithNodeName = function (node, nodeName)
{
    while (node.parentNode
			&& node.parentNode.nodeName.toLowerCase() != nodeName.toLowerCase()
			&& node.parentNode.nodeName != 'BODY')
    {
        node = node.parentNode;
    }

    if (node.parentNode && node.parentNode.nodeName.toLowerCase() == nodeName.toLowerCase())
    {
        return node.parentNode;
    } else
    {
        return null;
    }
};

Spry.Widget.Utils.destroyWidgets = function (container)
{
    if (typeof container == 'string')
    {
        container = document.getElementById(container);
    }

    var q = Spry.Widget.Form.onSubmitWidgetQueue;
    for (var i = 0; i < Spry.Widget.Form.onSubmitWidgetQueue.length; i++)
    {
        if (typeof (q[i].destroy) == 'function' && Spry.Widget.Utils.contains(container, q[i].element))
        {
            q[i].destroy();
            i--;
        }
    }
};

Spry.Widget.Utils.contains = function (who, what)
{
    if (typeof who.contains == 'object')
    {
        return what && who && (who == what || who.contains(what));
    } else
    {
        var el = what;
        while (el)
        {
            if (el == who)
            {
                return true;
            }
            el = el.parentNode;
        }
        return false;
    }
};

Spry.Widget.Utils.addEventListener = function (element, eventType, handler, capture)
{
    try
    {
        if (element.addEventListener)
            element.addEventListener(eventType, handler, capture);
        else if (element.attachEvent)
            element.attachEvent("on" + eventType, handler, capture);
    }
    catch (e) { }
};

Spry.Widget.Utils.removeEventListener = function (element, eventType, handler, capture)
{
    try
    {
        if (element.removeEventListener)
            element.removeEventListener(eventType, handler, capture);
        else if (element.detachEvent)
            element.detachEvent("on" + eventType, handler, capture);
    }
    catch (e) { }
};

Spry.Widget.Utils.stopEvent = function (ev)
{
    try
    {
        this.stopPropagation(ev);
        this.preventDefault(ev);
    }
    catch (e) { }
};

/**
* Stops event propagation
* @param {Event} ev the event
*/
Spry.Widget.Utils.stopPropagation = function (ev)
{
    if (ev.stopPropagation)
    {
        ev.stopPropagation();
    }
    else
    {
        ev.cancelBubble = true;
    }
};

/**
* Prevents the default behavior of the event
* @param {Event} ev the event
*/
Spry.Widget.Utils.preventDefault = function (ev)
{
    if (ev.preventDefault)
    {
        ev.preventDefault();
    }
    else
    {
        ev.returnValue = false;
    }
};

