Creating Search Ajax Web Control in ASP.NET 2.0 using Client Callbacks

Introduction
 

Hi today I’m going to show you the power of ASP.NET 2.0 Client Callbacks and how could it help us to implement real life Ajax web control.

 
Background
 

The main idea to show you how to implement control which will allow you to submit some data without postbacks. It will be CompositeControl consist of

  • HtmlInputButton
  • TextBox

Actually it will be TextBox with button. When you click the button or press the Enter the data from TextBox submit to server and then response from server returns to client where it processed by the client side code.

 
Code
using System;
using System.ComponentModel;
using System.Security.Permissions;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
 
namespace Controls
{
    [
    AspNetHostingPermission(SecurityAction.Demand,
        Level = AspNetHostingPermissionLevel.Minimal),
    AspNetHostingPermission(SecurityAction.InheritanceDemand,
        Level = AspNetHostingPermissionLevel.Minimal),
    DefaultEvent("AsyncClick"),
    DefaultProperty("Text"),

    ToolboxData("<{0}:Register runat=\"server\"> </{0}:Register>"),

    ]
 

    public class SearchEdit : CompositeControl, ICallbackEventHandler

    {
        #region Properties and fields

        private TextBox txtSearch;

        private String _ClientClickHandler;

        private HtmlInputButton btnSearch;

 
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]

        [Description("Name of Callback ClientScript function with such signature (args, context)")]

        public String ClientClickHandler

        {

            get { return _ClientClickHandler; }

            set { _ClientClickHandler = value; }

        }
 

        private static readonly object EventAsyncClickKey =new object();

 
 
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]

        [Description("Edit Text")]

 
        public string Text
        {
            get
            {

                EnsureChildControls();

                return txtSearch.Text;
            }
            set
            {

                EnsureChildControls();

                txtSearch.Text = value;

            }
 
        }
     private string _ButtonText;
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Description("ButtonText")]
 

        public string ButtonText

        {
            get
            {
               return _ButtonText;
            }
            set
            {
              _ButtonText = value;
            }
        }      

        #endregion

        #region Events
        //this event will be Async i.e. without postbacks

        //so you can act with it like with a normal Event in your server code

        public class AsyncArgs : EventArgs
        {
            public string Text;

            public AsyncArgs(string text)

            {

                Text = text;

            }
        }

        public delegate void AsyncClickEventHandler(object sender, AsyncArgs e);

 
        // The AsyncClick event.
        [
        Category("AsyncAction"),

        Description("Raised when the user clicks the button or press enter.")

        ]
        public event AsyncClickEventHandler AsyncClick
        {
            add
            {

                Events.AddHandler(EventAsyncClickKey, value);

            }
            remove
            {

                Events.RemoveHandler(EventAsyncClickKey, value);

            }
        }
 

        protected virtual void OnAsyncClick(AsyncArgs e)

        {
            AsyncClickEventHandler AsyncClick =
                (AsyncClickEventHandler)Events[EventAsyncClickKey];
            if (AsyncClick != null)
            {

                AsyncClick(this, e);

            }
        }
 
        #endregion
 
 
        #region RegisterScript

        private void RegisterOnClickScript()

        {

            if (_ClientClickHandler != string.Empty && _ClientClickHandler != null)

            {

                RegisterClientCallbackScript();

                btnSearch.Attributes.Clear();

                //when we click the button we call client side function SendValue(this)

                //it has such look "+ this.ID+"SendValue(this); in the case we

                //want to have more then one such control on the Page

                btnSearch.Attributes.Add("onclick", "return "+ this.ID+"SendValue(this);");

                StringBuilder sb = new StringBuilder();

 

                sb.Append("function " + this.ID + "SendValue(edt){");

                sb.Append(" var name=document.getElementById('" + this.ID + "_" + txtSearch.ID + "').value;");

                sb.Append(this.ID+"CallServer(name,'');");

                sb.Append("}");

                sb.Append("\n");

                this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), this.ID , sb.ToString(), true);
              
            }
        }

        private void RegisterClientCallbackScript()

        {
 
            ClientScriptManager m = Page.ClientScript;

            string cbReference = m.GetCallbackEventReference(this, "args", _ClientClickHandler, "");

            string strCallback = "function "+this.ID+"CallServer(args,context){" + cbReference + ";}";

            Page.ClientScript.RegisterClientScriptBlock(this.GetType(), this.ID+"CallServer", strCallback, true);

 
        }
 
        #endregion
 
        #region Rendering and Creating Controls

        private void RenderControlToCell(HtmlTextWriter writer, Control ctrl)

        {

            writer.RenderBeginTag(HtmlTextWriterTag.Td);

            ctrl.RenderControl(writer);

            writer.RenderEndTag();

        }
 

        protected override void Render(HtmlTextWriter writer)

        {

            AddAttributesToRender(writer);

            btnSearch.Value = _ButtonText;

            writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "1", false);

            writer.RenderBeginTag(HtmlTextWriterTag.Table);

            writer.RenderBeginTag(HtmlTextWriterTag.Tr);

            RenderControlToCell(writer, txtSearch);

            RenderControlToCell(writer, btnSearch);

            writer.RenderEndTag();

            writer.RenderEndTag();

        }
        protected override void CreateChildControls()
        {
 

            Controls.Clear();

            btnSearch = new HtmlInputButton();

            txtSearch = new TextBox();

            txtSearch.ID = "txtSearch";

            btnSearch.ID = "btnSearch";

            RegisterOnClickScript();

//when the user press Enter we don’t want pastback we want to call function SendValue

            txtSearch.Attributes.Add("onkeydown", "if(event.which || event.keyCode){if ((event.which == 13) || (event.keyCode == 13)) {document.getElementById('" + this.ID + "_" + btnSearch.ID + "').click();return false;}} else {return true}; ");

            txtSearch.AutoPostBack = false;; 

            this.Controls.Add(txtSearch);
            this.Controls.Add(btnSearch);
        }
       
        #endregion
 
 
        #region ICallbackEventHandler Members
        private string _CallbackArgs;

        [Description("gets or sets args value wich passes to client script")]

        public string CallbackArgs

        {

            get { return _CallbackArgs; }

            set { _CallbackArgs = value; }

        }
 
        public string GetCallbackResult()
        {
            return _CallbackArgs;
        }
 

        public void RaiseCallbackEvent(string eventArgument)

        {

            OnAsyncClick(new AsyncArgs(eventArgument));

        }
 
        #endregion
    }
}
 

Some comments. Our control inherits from CompositeControl and implements ICallbackEventHandler interface. Look to implementation of

GetCallbackResult

Returns the results of a callback event that targets a control.

RaiseCallbackEvent

Processes a callback event that targets a control.

in the code above.

 
Control Usage

In web.config we add such piece of code:

   <pages>

      <controls>

        <addtagPrefix="myControl"namespace="Controls"assembly="Controls"></add>

      </controls>

    </pages>

In Default.aspx such code(I added two controls to show that they can work together):

 

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">
    <title>Untitled Page</title>
 
</head>

<script language="javascript" type="text/javascript">

<!--

//this function is callback function which shows the server response

function ShowSearchResult(args, context){ 

 document.getElementById("SearchResult").innerHTML =args;

}
 
// -->
</script>
<body>
 <form id="form1" runat="server">
        <div>

            <myControl:SearchEdit ID="btnedtSearch" runat="server" ClientClickHandler="ShowSearchResult"

                OnAsyncClick="btnedtSearch_AsyncClick" Font-Bold="True" Font-Italic="True" Font-Names="Arial Black"

                Font-Size="Larger" Width="225px" ButtonText="Search" ></myControl:SearchEdit>

             

            <myControl:SearchEdit ID="SearchEdit1" runat="server" ClientClickHandler="ShowSearchResult"

                Font-Bold="True" Font-Italic="True" Font-Names="Arial Black" Font-Size="Larger"

                Width="243px" OnAsyncClick="btnedtSearch_AsyncClick" ButtonText="Advanced Search"/>

        </div>
        <div id="SearchResult"></div>
    </form> </body>
</html>

Then double click to the one of controls in designer and we get in a code behind file something like this:

 protected void btnedtSearch_AsyncClick(object sender, SearchEdit.AsyncArgs e)
    {
       
    }

Let’s do our server side code more realistic.

 

Server side code

We implement some simple search from file. So we create text file with some .NET SDK words.

Look the code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Web.UI;
using Controls;
 
public partial class _Default : Page
{

    protected void Page_Load(object sender, EventArgs e)

    {
 
    }
 

    protected void btnedtSearch_AsyncClick(object sender, SearchEdit.AsyncArgs e)

    {

        searchText = e.Text;

        (sender as SearchEdit).CallbackArgs = getFilteredWordsInHtmlFormat();

    }
  
    private static string searchText;

    // this function returns HTML wrapped words that match the search word from client side

    protected string getFilteredWordsInHtmlFormat()

    {
 
        string[] wordList = GetWords();

        Array.Sort(wordList, new CaseInsensitiveComparer());

        String[] filteredList = Array.FindAll(wordList, IsMatch);

        return GetHtmlFromArray(filteredList);
    }
 

    private static string GetHtmlFromArray(string[] returnValue)

    {
        string result = string.Empty;

        for (int i = 0; i < returnValue.Length; i++)

        {

            result += returnValue[i] + "<br>";

        }

        result = "<p><b>Search result:</b><br>" + result + "</p>";

        return result;
    }
    //read words to string array from file

    private string[] GetWords()

    {

        List<String> words = new List<string>();

        FileStream file = new FileStream(Server.MapPath("~/App_Data/words.txt"), FileMode.Open,

                       FileAccess.Read);

        StreamReader reader = new StreamReader(file);

        String word;
        while ((word = reader.ReadLine()) != null)
        {
            words.Add(word);
        }

        file.Close();

        return words.ToArray();
    }
 

    //The Predicate that defines the conditions of the elements to search for.

    //Represents the method that defines a set of criteria and determines whether the specified object meets those criteria.

    private static bool IsMatch(String s)

    {
               

        if (s.ToLower().IndexOf(searchText.ToLower()) >= 0)

        {
            return true;
       }
        else
        {
            return false;
        }
    }
 
  
}

So our control is ready to use.


It works without postbacks and was checked in Opera and Firefox.


 (?) Kigorw 2006 special for www.ajaxline.com

Comments

Please provide me the source code of this aplication.

Creating Search Ajax Web Control in ASP.NET 2.0 using Client Callbacks

Hi

I gone thru ur code and it's very gud..I do have small requirement, in this i created composit control lets say grifview in composite control, on input in textbox, this gridview wil b displayed in floating div and on click of row, it populates textbox and make it hidable..this is done till now. But i get stuck, if user doesnot select any row and press esc to hide this..How to implement this..pls do reply..

Sonia, this is very very old example. I'm strongly advice you to write custom javascript without any kind of asp.net controls(which is hard to develop). Use usercontrol with javascript. Look to asp.net MVC and use philosophy of it.

Hi,
I liked your code..and the way it is implemented is also excellent. Can u help me implement the full-text indexing feature of sql 2005? Can it be implemented through stored procedure?I will wait for your reply
thanks and regards
Guria

thanx but actually i need a bit of help in coding for my project ...which is to build up a matrimonial site..and for that i need a search control in which i have to search according to religion and all ...so if u have some simpler coding in Asp.net can u please help me out...

Are you human

You can use simple HTML-formatting tags(like <b>, <ul>, <code> and others)

Wow free space!
Super hosting provider might be here!

Is it Google?