File: UI\WebControls\TextBox.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="TextBox.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.UI.WebControls {
    using System;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Drawing.Design;
    using System.Globalization;
    using System.Web;
    using System.Web.UI;
    using System.Web.Util;
 
    /// <devdoc>
    /// <para>Interacts with the parser to build a <see cref='System.Web.UI.WebControls.TextBox'/> control.</para>
    /// </devdoc>
    public class TextBoxControlBuilder : ControlBuilder {
 
 
        /// <internalonly/>
        /// <devdoc>
        ///    Specifies whether white space literals are allowed.
        /// </devdoc>
        public override bool AllowWhitespaceLiterals() {
            return false;
        }
 
 
        public override bool HtmlDecodeLiterals() {
            // TextBox text gets rendered as an encoded attribute value or encoded content.
 
            // At parse time text specified as an attribute gets decoded, and so text specified as a
            // literal needs to go through the same process.
 
            return true;
        }
    }
 
 
 
    /// <devdoc>
    ///    <para>Constructs a text box and defines its properties.</para>
    /// </devdoc>
    [
    ControlBuilderAttribute(typeof(TextBoxControlBuilder)),
    ControlValueProperty("Text"),
    DataBindingHandler("System.Web.UI.Design.TextDataBindingHandler, " + AssemblyRef.SystemDesign),
    DefaultProperty("Text"),
    ValidationProperty("Text"),
    DefaultEvent("TextChanged"),
    Designer("System.Web.UI.Design.WebControls.PreviewControlDesigner, " + AssemblyRef.SystemDesign),
    ParseChildren(true, "Text"),
    SupportsEventValidation,
    ]
    public class TextBox : WebControl, IPostBackDataHandler, IEditableTextControl {
 
        private static readonly object EventTextChanged = new Object();
 
        private const string _textBoxKeyHandlerCall = "if (WebForm_TextBoxKeyHandler(event) == false) return false;";
 
        private const int DefaultMutliLineRows = 2;
        private const int DefaultMutliLineColumns = 20;
 
        /// <devdoc>
        /// <para>Initializes a new instance of the <see cref='System.Web.UI.WebControls.TextBox'/> class.</para>
        /// </devdoc>
        public TextBox() : base(HtmlTextWriterTag.Input) {
        }
 
 
        [
        DefaultValue(AutoCompleteType.None),
        Themeable(false),
        WebCategory("Behavior"),
        WebSysDescription(SR.TextBox_AutoCompleteType)
        ]
        public virtual AutoCompleteType AutoCompleteType {
            get {
                object obj = ViewState["AutoCompleteType"];
                return (obj == null) ? AutoCompleteType.None : (AutoCompleteType) obj;
            }
            set {
                if (value < AutoCompleteType.None || value > AutoCompleteType.Enabled) {
                    throw new ArgumentOutOfRangeException("value");
                }
                ViewState["AutoCompleteType"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets a value indicating whether an automatic
        ///       postback to the server will occur whenever the user changes the
        ///       content of the text box.</para>
        /// </devdoc>
        [
        DefaultValue(false),
        Themeable(false),
        WebCategory("Behavior"),
        WebSysDescription(SR.TextBox_AutoPostBack),
        ]
        public virtual bool AutoPostBack {
            get {
                object b = ViewState["AutoPostBack"];
                return((b == null) ? false : (bool)b);
            }
            set {
                ViewState["AutoPostBack"] = value;
            }
        }
 
 
        [
        DefaultValue(false),
        Themeable(false),
        WebCategory("Behavior"),
        WebSysDescription(SR.AutoPostBackControl_CausesValidation)
        ]
        public virtual bool CausesValidation {
            get {
                object b = ViewState["CausesValidation"];
                return((b == null) ? false : (bool)b);
            }
            set {
                ViewState["CausesValidation"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets the display
        ///       width of the text box in characters.</para>
        /// </devdoc>
        [
        WebCategory("Appearance"),
        DefaultValue(0),
        WebSysDescription(SR.TextBox_Columns)
        ]
        public virtual int Columns {
            get {
                object o = ViewState["Columns"];
                return((o == null) ? 0 : (int)o);
            }
            set {
                if (value < 0) {
                    throw new ArgumentOutOfRangeException("Columns", SR.GetString(SR.TextBox_InvalidColumns));
                }
                ViewState["Columns"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Gets or sets the maximum number of characters allowed in the text box.</para>
        /// </devdoc>
        [
        DefaultValue(0),
        Themeable(false),
        WebCategory("Behavior"),
        WebSysDescription(SR.TextBox_MaxLength),
        ]
        public virtual int MaxLength {
            get {
                object o = ViewState["MaxLength"];
                return((o == null) ? 0 : (int)o);
            }
            set {
                if (value < 0) {
                    throw new ArgumentOutOfRangeException("value");
                }
                ViewState["MaxLength"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>
        ///       Gets or sets the behavior mode of the text box.</para>
        /// </devdoc>
        [
        DefaultValue(TextBoxMode.SingleLine),
        Themeable(false),
        WebCategory("Behavior"),
        WebSysDescription(SR.TextBox_TextMode)
        ]
        public virtual TextBoxMode TextMode {
            get {
                object mode = ViewState["Mode"];
                return((mode == null) ? TextBoxMode.SingleLine : (TextBoxMode)mode);
            }
            set {
                if (value < TextBoxMode.SingleLine || value > TextBoxMode.Week) {
                    throw new ArgumentOutOfRangeException("value");
                }
                ViewState["Mode"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para>Whether the textbox is in read-only mode.</para>
        /// </devdoc>
        [
        Bindable(true),
        DefaultValue(false),
        Themeable(false),
        WebCategory("Behavior"),
        WebSysDescription(SR.TextBox_ReadOnly)
        ]
        public virtual bool ReadOnly {
            get {
                object o = ViewState["ReadOnly"];
                return((o == null) ? false : (bool)o);
            }
            set {
                ViewState["ReadOnly"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    <para> Gets or sets the display height of a multiline text box.</para>
        /// </devdoc>
        [
        DefaultValue(0),
        Themeable(false),
        WebCategory("Behavior"),
        WebSysDescription(SR.TextBox_Rows)
        ]
        public virtual int Rows {
            get {
                object o = ViewState["Rows"];
                return((o == null) ? 0 : (int)o);
            }
            set {
                if (value < 0) {
                    throw new ArgumentOutOfRangeException("Rows", SR.GetString(SR.TextBox_InvalidRows));
                }
                ViewState["Rows"] = value;
            }
        }
 
        /// <devdoc>
        ///    Determines whether the Text must be stored in view state, to
        ///    optimize the size of the saved state.
        /// </devdoc>
        private bool SaveTextViewState {
            get {
                // 
 
 
                // Must be saved when
                // 1. There is a registered event handler for SelectedIndexChanged
                // 2. Control is not enabled or visible, because the browser's post data will not include this control
                // 3. The instance is a derived instance, which might be overriding the OnTextChanged method
 
                if (TextMode == TextBoxMode.Password) {
                    return false;
                }
 
                if ((Events[EventTextChanged] != null) ||
                    (IsEnabled == false) ||
                    (Visible == false) ||
                    (ReadOnly) ||
                    (this.GetType() != typeof(TextBox))) {
                    return true;
                }
 
                return false;
            }
        }
 
 
        /// <devdoc>
        ///    <para>A protected property. Gets the HTML tag
        ///       for the text box control.</para>
        /// </devdoc>
        protected override HtmlTextWriterTag TagKey {
            get {
                if (TextMode == TextBoxMode.MultiLine)
                    return HtmlTextWriterTag.Textarea;
                else
                    return HtmlTextWriterTag.Input;
            }
        }
 
 
        /// <devdoc>
        ///    <para> Gets
        ///       or sets the text content of the text box.</para>
        /// </devdoc>
        [
        Localizable(true),
        Bindable(true, BindingDirection.TwoWay),
        WebCategory("Appearance"),
        DefaultValue(""),
        WebSysDescription(SR.TextBox_Text),
        PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty),
        Editor("System.ComponentModel.Design.MultilineStringEditor," + AssemblyRef.SystemDesign, typeof(UITypeEditor))
        ]
        public virtual string Text {
            get {
                string s = (string)ViewState["Text"];
                return((s == null) ? String.Empty : s);
            }
            set {
                ViewState["Text"] = value;
            }
        }
 
 
        [
        WebCategory("Behavior"),
        Themeable(false),
        DefaultValue(""),
        WebSysDescription(SR.PostBackControl_ValidationGroup)
        ]
        public virtual string ValidationGroup {
            get {
                string s = (string)ViewState["ValidationGroup"];
                return((s == null) ? string.Empty : s);
            }
            set {
                ViewState["ValidationGroup"] = value;
            }
        }
 
 
        /// <devdoc>
        ///    Gets or sets a value indicating whether the
        ///    text content wraps within the text box.
        /// </devdoc>
        [
        WebCategory("Layout"),
        DefaultValue(true),
        WebSysDescription(SR.TextBox_Wrap)
        ]
        public virtual bool Wrap {
            get {
                object b = ViewState["Wrap"];
                return((b == null) ? true : (bool)b);
            }
            set {
                ViewState["Wrap"] = value;
            }
        }
 
        // internal for unit testing
        internal virtual bool SupportsVCard {
            get {
                return Context != null && Context.Request.Browser["supportsVCard"] == "true";
            }
        }
 
        /// <devdoc>
        ///    <para>Occurs when the content of the text box is
        ///       changed upon server postback.</para>
        /// </devdoc>
        [
        WebCategory("Action"),
        WebSysDescription(SR.TextBox_OnTextChanged)
        ]
        public event EventHandler TextChanged {
            add {
                Events.AddHandler(EventTextChanged, value);
            }
            remove {
                Events.RemoveHandler(EventTextChanged, value);
            }
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// </devdoc>
        protected override void AddAttributesToRender(HtmlTextWriter writer) {
 
            // Make sure we are in a form tag with runat=server.
            Page page = Page;
            if (page != null) {
                page.VerifyRenderingInServerForm(this);
            }
 
            string uniqueID = UniqueID;
            if (uniqueID != null) {
                writer.AddAttribute(HtmlTextWriterAttribute.Name, uniqueID);
            }
 
            TextBoxMode mode = TextMode;
 
            if (mode == TextBoxMode.MultiLine) {
                // MultiLine renders as textarea
 
                int rows = Rows;
                int columns = Columns;
                bool adapterRenderZeroRowCol = false;
 
                if (!EnableLegacyRendering) {
                    // VSWhidbey 497755
                    if (rows == 0) {
                        rows = DefaultMutliLineRows;
                    }
                    if (columns == 0) {
                        columns = DefaultMutliLineColumns;
                    }
                }
 
                if (rows > 0 || adapterRenderZeroRowCol) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Rows, rows.ToString(NumberFormatInfo.InvariantInfo));
                }
 
                if (columns > 0 || adapterRenderZeroRowCol) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Cols, columns.ToString(NumberFormatInfo.InvariantInfo));
                }
 
                if (!Wrap) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Wrap,"off");
                }
 
                //VSO449020 Add MaxLength Support for mutiple lines textbox, since in HTML5 this attribute is supported for textarea.
                if (BinaryCompatibility.Current.TargetsAtLeastFramework472 &&  MaxLength > 0) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Maxlength, MaxLength.ToString(NumberFormatInfo.InvariantInfo));
                }
            }
            else {
                // Everything else renders as input
 
                if (mode != TextBoxMode.SingleLine || String.IsNullOrEmpty(Attributes["type"])) {
                    // If the developer specified a custom type (like an HTML 5 type), use that type instead of "text".
                    // The call to base.AddAttributesToRender at the end of this method will add the custom type if specified.
                    writer.AddAttribute(HtmlTextWriterAttribute.Type, GetTypeAttributeValue(mode));
                }
 
                AutoCompleteType autoCompleteType = AutoCompleteType;
                if (mode == TextBoxMode.SingleLine &&
                    autoCompleteType != AutoCompleteType.None &&
                    autoCompleteType != AutoCompleteType.Enabled &&
                    autoCompleteType != AutoCompleteType.Disabled &&
                    SupportsVCard) {
 
                    // Renders the vcard_name attribute so that client browsers can support autocomplete
                    string name = GetVCardAttributeValue(autoCompleteType);
                    writer.AddAttribute(HtmlTextWriterAttribute.VCardName, name);
                }
 
                if (autoCompleteType == AutoCompleteType.Disabled &&
                    (RenderingCompatibility >= VersionUtil.Framework45 || mode >= TextBoxMode.Color || (SupportsVCard && mode == TextBoxMode.SingleLine))) {
                    // Only render autocomplete="off" when one of the following is true
                    // - 4.5 or higher rendering compat is being used
                    // - any of the new HTML5 modes are being used
                    // - browser supports vCard AND mode is SingleLine (this is the legacy pre-4.5 behavior)
                    writer.AddAttribute(HtmlTextWriterAttribute.AutoComplete, "off");
                }
 
                if (autoCompleteType == AutoCompleteType.Enabled) {
                    // Since Enabled is a new value in .NET 4.5 we don't need back-compat switches
                    writer.AddAttribute(HtmlTextWriterAttribute.AutoComplete, "on");
                }
 
                if (mode != TextBoxMode.Password) {
                    // only render value if we're not a password
                    string s = Text;
                    if (s.Length > 0) {
                        writer.AddAttribute(HtmlTextWriterAttribute.Value, s);
                    }
                }
 
                int n = MaxLength;
                if (n > 0) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Maxlength, n.ToString(NumberFormatInfo.InvariantInfo));
                }
                n = Columns;
                if (n > 0) {
                    writer.AddAttribute(HtmlTextWriterAttribute.Size, n.ToString(NumberFormatInfo.InvariantInfo));
                }
            }
 
            if (ReadOnly) {
                writer.AddAttribute(HtmlTextWriterAttribute.ReadOnly, "readonly");
            }
 
            if (AutoPostBack && (page != null) && page.ClientSupportsJavaScript) {
                string onChange = null;
                if (HasAttributes) {
                    onChange = Attributes["onchange"];
                    if (onChange != null) {
                        onChange = Util.EnsureEndWithSemiColon(onChange);
                        Attributes.Remove("onchange");
                    }
                }
 
                PostBackOptions options = new PostBackOptions(this, String.Empty);
 
                // ASURT 98368
                // Need to merge the autopostback script with the user script
                if (CausesValidation) {
                    options.PerformValidation = true;
                    options.ValidationGroup = ValidationGroup;
                }
 
                if (page.Form != null) {
                    options.AutoPostBack = true;
                }
 
                onChange = Util.MergeScript(onChange, page.ClientScript.GetPostBackEventReference(options, true));
                writer.AddAttribute(HtmlTextWriterAttribute.Onchange, onChange);
 
                // VSWhidbey 482068: Enter key should be preserved in mult-line
                // textbox so the textBoxKeyHandlerCall should not be hooked up
                if (mode != TextBoxMode.MultiLine) {
                    string onKeyPress = _textBoxKeyHandlerCall;
                    if (HasAttributes) {
                        string userOnKeyPress = Attributes["onkeypress"];
                        if (userOnKeyPress != null) {
                            onKeyPress += userOnKeyPress;
                            Attributes.Remove("onkeypress");
                        }
                    }
                    writer.AddAttribute("onkeypress", onKeyPress);
                }
 
                if (EnableLegacyRendering) {
                    writer.AddAttribute("language", "javascript", false);
                }
            }
            else if (page != null) {
                page.ClientScript.RegisterForEventValidation(this.UniqueID, String.Empty);
            }
 
            if (Enabled && !IsEnabled && SupportsDisabledAttribute) {
                // We need to do the cascade effect on the server, because the browser
                // only renders as disabled, but doesn't disable the functionality.
                writer.AddAttribute(HtmlTextWriterAttribute.Disabled, "disabled");
            }
 
            base.AddAttributesToRender(writer);
        }
 
        /// <internalonly/>
        /// <devdoc>
        ///    Overridden to only allow literal controls to be added as Text property.
        /// </devdoc>
        protected override void AddParsedSubObject(object obj) {
            if (obj is LiteralControl) {
                Text = ((LiteralControl)obj).Text;
            }
            else {
                throw new HttpException(SR.GetString(SR.Cannot_Have_Children_Of_Type, "TextBox", obj.GetType().Name.ToString(CultureInfo.InvariantCulture)));
            }
        }
 
        internal static string GetTypeAttributeValue(TextBoxMode mode) {
            switch (mode) {
                case TextBoxMode.SingleLine: return "text";
                case TextBoxMode.Password: return "password";
                case TextBoxMode.Color: return "color";
                case TextBoxMode.Date: return "date";
                case TextBoxMode.DateTime: return "datetime";
                case TextBoxMode.DateTimeLocal: return "datetime-local";
                case TextBoxMode.Email: return "email";
                case TextBoxMode.Month: return "month";
                case TextBoxMode.Number: return "number";
                case TextBoxMode.Range: return "range";
                case TextBoxMode.Search: return "search";
                case TextBoxMode.Phone: return "tel";
                case TextBoxMode.Time: return "time";
                case TextBoxMode.Url: return "url";
                case TextBoxMode.Week: return "week";
                case TextBoxMode.MultiLine:
                    // falling through on purpose
                default:
                    // the default case could only happen if
                    //  - someone forces an out-of-range value as a TextBoxMode and passes it in
                    //  - a new TextBoxMode value gets added, in which case it should be handled as an explicit case above
                    throw new InvalidOperationException();
            }
        }
 
        internal static string GetVCardAttributeValue(AutoCompleteType type) {
            switch (type) {
                case AutoCompleteType.None:
                case AutoCompleteType.Disabled:
                case AutoCompleteType.Enabled:
                    // should not happen
                    throw new InvalidOperationException();
                case AutoCompleteType.Search:
                    return "search";
                case AutoCompleteType.HomeCountryRegion:
                    return "HomeCountry";
                case AutoCompleteType.BusinessCountryRegion:
                    return "BusinessCountry";
                default:
                    string result = Enum.Format(typeof(AutoCompleteType), type, "G");
                    // Business and Home properties need to be prefixed with "."
                    if (result.StartsWith("Business", StringComparison.Ordinal)) {
                        result = result.Insert(8, ".");
                    }
                    else if (result.StartsWith("Home", StringComparison.Ordinal)) {
                        result = result.Insert(4, ".");
                    }
                    return "vCard." + result;
            }
        }
 
        /// <internalonly/>
        protected internal override void OnPreRender(EventArgs e) {
            base.OnPreRender(e);
 
            Page page = Page;
            if ((page != null) && IsEnabled) {
                if (SaveTextViewState == false) {
                    // Store a client-side array of enabled control, so we can re-enable them on
                    // postback (in case they are disabled client-side)
                    // Postback is needed when view state for the Text property is disabled
                    page.RegisterEnabledControl(this);
                }
 
                if (AutoPostBack) {
                    page.RegisterWebFormsScript();
                    page.RegisterPostBackScript();
                    page.RegisterFocusScript();
                }
            }
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// <para>Loads the posted text box content if it is different
        /// from the last posting.</para>
        /// </devdoc>
        bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) {
            return LoadPostData(postDataKey, postCollection);
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// <para>Loads the posted text box content if it is different
        /// from the last posting.</para>
        /// </devdoc>
        protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) {
            ValidateEvent(postDataKey);
 
            string current = Text;
            string postData = postCollection[postDataKey];
 
            // VSWhidbey 442850: Everett had current.Equals(postData), and it is
            // equivalent to the option StringComparison.Ordinal in Whidbey
            if (!ReadOnly && !current.Equals(postData, StringComparison.Ordinal)) {
                Text = postData;
                return true;
            }
            return false;
        }
 
 
 
        /// <devdoc>
        /// <para> Raises the <see langword='TextChanged'/> event.</para>
        /// </devdoc>
        protected virtual void OnTextChanged(EventArgs e) {
            EventHandler onChangeHandler = (EventHandler)Events[EventTextChanged];
            if (onChangeHandler != null) onChangeHandler(this,e);
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// <para>Invokes the <see cref='System.Web.UI.WebControls.TextBox.OnTextChanged'/> method
        /// whenever posted data for the text box has changed.</para>
        /// </devdoc>
        void IPostBackDataHandler.RaisePostDataChangedEvent() {
            RaisePostDataChangedEvent();
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// <para>Invokes the <see cref='System.Web.UI.WebControls.TextBox.OnTextChanged'/> method
        /// whenever posted data for the text box has changed.</para>
        /// </devdoc>
        protected virtual void RaisePostDataChangedEvent() {
            if (AutoPostBack && !Page.IsPostBackEventControlRegistered) {
                // VSWhidbey 204824
                Page.AutoPostBackControl = this;
 
                if (CausesValidation) {
                     Page.Validate(ValidationGroup);
                }
             }
             OnTextChanged(EventArgs.Empty);
        }
 
 
        /// <internalonly/>
        /// <devdoc>
        /// </devdoc>
        protected internal override void Render(HtmlTextWriter writer) {
            RenderBeginTag(writer);
            //Dev10 Bug 483896: Original TextBox rendering in MultiLine mode suffers from the
            //problem of losing the first newline. We fixed this bug by always rendering a newline
            //before rendering the value of the Text property.
            if (TextMode == TextBoxMode.MultiLine) {
                //Dev11 Bug 437709 fix: We do not want to encode the extra new line that we are
                //rendering. However we are doing this only for 4.5 or later frameworks for back-compat.
                if (RenderingCompatibility >= VersionUtil.Framework45) {
                    writer.Write(System.Environment.NewLine);
                    HttpUtility.HtmlEncode(Text, writer);
                }
                else {
                    HttpUtility.HtmlEncode(System.Environment.NewLine + Text, writer);
                }
            }
            RenderEndTag(writer);
        }
 
        protected override object SaveViewState() {
            if (SaveTextViewState == false) {
                ViewState.SetItemDirty("Text", false);
            }
            return base.SaveViewState();
        }
    }
}