Click here to Skip to main content
15,895,746 members
Articles / Web Development / XHTML

AJAX WAS Here - Part 3 : Auto Complete TextBox

Rate me:
Please Sign up or sign in to vote.
4.79/5 (35 votes)
2 May 20058 min read 483.5K   3.7K   158  
A custom AJAX - ASP.NET control.
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

using WCPierce.Web.UI;

[assembly:TagPrefix("WCPierce.Web.UI.WebControls", "wcp")]
namespace WCPierce.Web.UI.WebControls
{
	/// <summary>
	/// AutoCompleteTextBox is similar to the WinForm ComboBox control.  As the 
	/// user types into the box, the enter is "auto-completed" based on values 
	/// databound to the TextBox by the developer.
	/// </summary>
  [DefaultProperty("Text"), ToolboxData("<{0}:AutoCompleteTextBox runat=server></{0}:AutoCompleteTextBox>")]
  public class AutoCompleteTextBox : System.Web.UI.WebControls.TextBox
  {
    #region Member Variables

    /// <summary>
    /// The (relative) path to the AutoCompleteTextBox JavaScript file.
    /// </summary>
    private string _scriptPath = string.Empty;

    /// <summary>
    /// For using databinding with your AutoCompleteTextBox
    /// </summary>
    private object _dataSource = null;

    /// <summary>
    /// Data returned to the client is in the form of "entry"-newline-
    /// "entry"-newline...If you wanted to get cute, we could return in an XML
    ///  format.
    /// </summary>
    private static readonly string _FormatString = "{0}\n";

    /// <summary>
    /// If a ScriptPath isn't specified, check the web.config file for the 
    /// following key.
    /// </summary>
    private static readonly string _ScriptPath = "AutoCompleteTextBox.ScriptPath";

    /// <summary>
    /// CSS Class name for the list item of the dropdownlist.
    /// </summary>
    private string _listItemCssClass = string.Empty;

    /// <summary>
    /// CSS Class name for the "hover" effect of the list item of the dropdownlist.
    /// </summary>
    private string _listItemHoverCssClass = string.Empty;

    #endregion

    #region Public Properties

    /// <summary>
    /// The path to the AutoComplete.js file.  If you leave it blank, it will
    /// automatically look in the web.config for the value under the key
    /// "AutoCompleteTextBox.ScriptPath".  Should be a path relative to the 
    /// application root i.e. "~\scripts\AutoCompleteTextBox.js".
    /// </summary>
    public string ScriptPath
    {
      get 
      { 
        if( _scriptPath != string.Empty )
          return ResolveUrl(_scriptPath); 

        try
        {
          return ResolveUrl(System.Configuration.ConfigurationSettings.AppSettings[AutoCompleteTextBox._ScriptPath]);
        }
        catch 
        { 
          return null;
        }
      }
      set { _scriptPath = value; }
    }

    /// <summary>
    /// CSS Class name for the list item of the dropdownlist.
    /// </summary>
    public string ListItemCssClass
    {
      get { return _listItemCssClass; }
      set { _listItemCssClass = value; }
    }

    /// <summary>
    /// CSS Class name for the "hover" effect of the list item of the dropdownlist.
    /// </summary>
    public string ListItemHoverCssClass
    {
      get { return _listItemHoverCssClass; }
      set { _listItemHoverCssClass = value; }
    }

    /// <summary>
    /// For use with databinding.
    /// </summary>
    [Bindable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue((string) null)]
    public virtual object DataSource
    {
      get
      {
        return _dataSource;
      }
      set
      {
        if (((value != null) && !(value is IListSource)) && !(value is IEnumerable))
        {
          throw new ArgumentException("Invalid_DataSource_Type: " + this.ID);
        }
        _dataSource = value;
      }
    }

    /// <summary>
    /// For use with databinding.
    /// </summary>
    public virtual string DataTextField
    {
      get 
      { 
        object o = this.ViewState["DataTextField"];
        if (o != null)
        {
          return (string)o;
        }
        return string.Empty;
      }
      set
      {
        this.ViewState["DataTextField"] = value;
      }
    }

    /// <summary>
    /// For use with databinding.
    /// </summary>
    public virtual string DataTextFormatString
    {
      get
      {
        object o = this.ViewState["DataTextFormatString"];
        if (o != null)
        {
          return (string)o;
        }
        return string.Empty;
      }
      set
      {
        this.ViewState["DataTextFormatString"] = value;
      }
    }

    /// <summary>
    /// For use with databinding.
    /// </summary>
    [DefaultValue("")]
    public virtual string DataMember
    {
      get
      {
        object o = this.ViewState["DataMember"];
        if (o != null)
        {
          return (string)o;
        }
        return string.Empty;
      }
      set
      {
        this.ViewState["DataMember"] = value;
      }
    }

    #endregion

    #region Overrides

    /// <summary>
    /// Render this control to the output parameter specified.
    /// </summary>
    /// <param name="output">The HTML writer to write out to.</param>
    protected override void Render(HtmlTextWriter output)
    {
      string uId = this.UniqueID;
      string newUid = uId.Replace(":", "_");
      string divId = newUid + "_Div";
      string jsId = newUid + "_Js";

      StringBuilder acScript = new StringBuilder();
      acScript.Append("<script type=\"text/javascript\">");
      acScript.AppendFormat("var {0} = new AutoCompleteTextBox('{1}','{2}');{0}.ListItemClass='{3}';{0}.ListItemHoverClass='{4}';", jsId, newUid, divId, this.ListItemCssClass, this.ListItemHoverCssClass);
      acScript.Append("</script>");

      Page.RegisterStartupScript(newUid, acScript.ToString());

      base.Attributes.Add("AutoComplete", "False");
      base.Render(output);
      output.Write(String.Format("<div id='{0}'></div>", divId));
    }

    /// <summary>
    /// Register our common scripts and do default PreRendering.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnPreRender(EventArgs e)
    {
      this._RegisterCommonScripts();
      base.OnPreRender(e);
    }

    /// <summary>
    /// Only fire the OnTextChanged event if this control is the target and it
    /// is a Call Back
    /// </summary>
    /// <param name="e"></param>
    protected override void OnTextChanged(EventArgs e)
    {  
      if( Page.Request.Params["__EVENTTARGET"] == this.UniqueID && CallBackHelper.IsCallBack )
      {
        base.OnTextChanged( e );
      }
    }

    /// <summary>
    /// The original idea was to have the Auto Complete Text Box behave like a
    /// normal Data Bindable control.  But, alas, I couldn't figure that out.
    /// Thank you M$ for the databinding code.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnDataBinding(EventArgs e)
    {
      base.OnDataBinding(e);

      IEnumerable ie = DataSourceHelper.GetResolvedDataSource(this.DataSource, this.DataMember);
      StringBuilder sb = new StringBuilder();

      if( ie != null )
      {
        bool useTextField = false;
        bool useFormatString = false;
        string textField = DataTextField;
        string formatString = DataTextFormatString;
        if( textField.Length != 0 )
        {
          useTextField = true;
        }
        if( formatString.Length != 0 )
        {
          useFormatString = true;
        }
        foreach( object o in ie )
        {
          if( useTextField )
          {
            if( textField.Length > 0)
            {
              sb.AppendFormat( AutoCompleteTextBox._FormatString, DataBinder.GetPropertyValue(o, textField, formatString) );
            }
          }
          else
          {
            if( useFormatString )
            {
              sb.AppendFormat( AutoCompleteTextBox._FormatString, string.Format(formatString, o) );
            }
            else
            {
              sb.AppendFormat( AutoCompleteTextBox._FormatString, o.ToString() );
            }
          }  // useTextField
        }  // foreach
      } // ie != null

      // Remove trailing '\n'
      if( sb.Length > 1 )
        sb.Remove(sb.Length-1, 1);


      CallBackHelper.Write( sb.ToString() );
    }


    /// <summary>
    /// Perhaps in the future, I will figure out how to make this work.  Before
    /// you email me with the answer please try using an AutoCompleteTextBox in
    /// a DataGrid with ViewState disabled.
    /// </summary>
    public override void DataBind()
    {
      // Do Nothing
    }

    /// <summary>
    /// What's the point if the developer turns on AutoPostBack?
    /// </summary>
    public override bool AutoPostBack
    {
      get { return false; }
    }


    #endregion

    #region Public Methods

    /// <summary>
    /// For now, Developer's must call this method to bind to their 
    /// Auto Complete Text Box.
    /// </summary>
    public virtual void BindData()
    {
      this.OnDataBinding(EventArgs.Empty);
    }

    #endregion

    #region Helper Methods

    /// <summary>
    /// Add a reference to the JavaScript, but only once per page.
    /// </summary>
    private void _RegisterCommonScripts()
    {
      if (!this.Page.IsClientScriptBlockRegistered("AutoCompleteTextBox"))
      {
        StringBuilder script = new StringBuilder();
        script.AppendFormat("<script type='text/javascript' src='{0}'></script>", this.ScriptPath);
        this.Page.RegisterClientScriptBlock("AutoCompleteTextBox", script.ToString());
      }
    }

    #endregion
  }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions