Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Beautiful Prototype autocomplete Easy to Use

4.36/5 (6 votes)
31 Jan 2011CPOL3 min read 34.8K   704  
A beautiful ASP.NET Ajax Webcontrol which is easy to use
Image 1

Introduction

I love web creation, and I love beautiful Web graphic interfaces. Last time, I searched a beautiful ASP.NET autosuggest webcontrol with the possibility to write in 2 lines per item.

I did a research, and found this elegant control:

The editor of the control provides the library JavaScript file and a CSS file with a PHP example to do it.

I decided to do it an ASP.NET webcontrol, and I tried to design it for a very simple utilization like Microsoft Widgets.

This control is compatible with all navigators.

Background

This control needs 3 basics things to run:

  1. You must provide the ServiceMethod property, it might be an .asmx file or .svc file
  2. You must provide the ServicePath property, it's the name of the method of your webservice or wcfservice to call to get formatted data to bind to the control.
  3. On this service method implementation, you must respect the json format result. The string must be formatted like that:
    JavaScript
    { results: [" 
    "{ id: '1', value: 'TOTO,TETE', info: 'FireFox' }," 
    "{ id: '2', value: 'TATA,TYTY', info: 'Chrome' }," 
    "{ id: '3', value: 'TITI,TNTN', info: 'Safari' } ] }" 

The autosuggest functionality works with 'value' property.

Using the Code

This control is a very simple CompositeControl. It's a Textbox for the suggestion with an associated HTML hidden input to store the current selected id.

OnPreRender method does several things:

It adds the references of the JavaScript and CSS file on the document.

Sometimes, the CSS file did a reference to embedded pictures. I extracted this code of the CSS file to put it the server side.

I already explained this manipulation to get the picture address in assembly in my other article ASP.NET Combobox dropdownlist with Images.

And finally, it loads a JavaScript function which binds the textbox to the service to invoke when a key is pressed and the focus is in the textbox.

C#
protected override void OnPreRender(EventArgs e)
{
	base.OnPreRender(e);

	if (!this.DesignMode)
	{
	// Test for ScriptManager and register if it exists
	// we need it for PageRequestManager manipulation in prerender
	ScriptManager sm = ScriptManager.GetCurrent(Page);

	if (sm == null)
		throw new HttpException
		("A ScriptManager control must exist on the current page.");

	//Add js and css files resource
	string cssdd = Page.ClientScript.GetWebResourceUrl
		(GetType(), "ClassLibrary1.style.styledautosuggest.css");
	string scriptJQuery = Page.ClientScript.GetWebResourceUrl(this.GetType(), 
				"ClassLibrary1.script.bsn.AutoSuggest_2.1.3.js");

	HtmlLink lnk = new HtmlLink();
	lnk.Href = cssdd;
	lnk.Attributes["rel"] = "stylesheet";
	lnk.Attributes["type"] = "text/css";

	HtmlGenericControl jq = new HtmlGenericControl("script");
	jq.Attributes.Add("language", "JavaScript");
	jq.Attributes.Add("type", "text/javascript");
	jq.Attributes.Add("src", scriptJQuery);
	jq.Attributes.Add("alt", "bsn.AutoSuggest_2.1.3.js");

	//Put one time the style whose gives reference to arrow of the combo
	if (!Page.Items.Contains("styleAlreadyPut"))
	{
	//get URL of embedded pictures
	string urlImage_as_pointer_gif = Page.ClientScript.GetWebResourceUrl
			(this.GetType(), "ClassLibrary1.img.as_pointer.gif");
	string urlImage_as_pointer_png = Page.ClientScript.GetWebResourceUrl
			(this.GetType(), "ClassLibrary1.img.as_pointer.png");
	string urlImage_hl_corner_bl_gif = Page.ClientScript.GetWebResourceUrl
			(this.GetType(), "ClassLibrary1.img.hl_corner_bl.gif");
	string urlImage_hl_corner_br_gif = Page.ClientScript.GetWebResourceUrl
			(this.GetType(), "ClassLibrary1.img.hl_corner_br.gif");
	string urlImage_hl_corner_tl_gif = Page.ClientScript.GetWebResourceUrl
			(this.GetType(), "ClassLibrary1.img.hl_corner_tl.gif");
	string urlImage_hl_corner_tr_gif = Page.ClientScript.GetWebResourceUrl
			(this.GetType(), "ClassLibrary1.img.hl_corner_tr.gif");
	string urlImage_li_corner_png = Page.ClientScript.GetWebResourceUrl
			(this.GetType(), "ClassLibrary1.img.li_corner.png");
	string urlImage_ul_corner_png = Page.ClientScript.GetWebResourceUrl
			(this.GetType(), "ClassLibrary1.img.ul_corner.png");
	string urlImage_ul_corner_bl_gif = Page.ClientScript.GetWebResourceUrl
			(this.GetType(), "ClassLibrary1.img.ul_corner_bl.gif");
	string urlImage_ul_corner_br_gif = Page.ClientScript.GetWebResourceUrl
			(this.GetType(), "ClassLibrary1.img.ul_corner_br.gif");
	string urlImage_ul_corner_tl_gif = Page.ClientScript.GetWebResourceUrl
			(this.GetType(), "ClassLibrary1.img.ul_corner_tl.gif");
	string urlImage_ul_corner_tr_gif = Page.ClientScript.GetWebResourceUrl
			(this.GetType(), "ClassLibrary1.img.ul_corner_tr.gif");
   
	//--------------------------------------------------------
	StringBuilder strStyleas_pointer_gif = new StringBuilder();
	strStyleas_pointer_gif.Append(" div.autosuggest { ")
	   .Append(" position: absolute; ")
	   .Append(" background-position: top; ")
	   .Append(" background-repeat: no-repeat; ")
	   .Append(" padding: 10px 0 0 0; ")
	   .Append(" background-image:url('" + urlImage_as_pointer_gif + "');")
   .Append(" } ");
	HtmlGenericControl styleas_pointer_gif = new HtmlGenericControl("style");
	styleas_pointer_gif.Attributes.Add("type", "text/css");
	styleas_pointer_gif.Controls.Add(new LiteralControl
			( strStyleas_pointer_gif.ToString()));
	Page.Header.Controls.Add(styleas_pointer_gif);
	...
	...
	...
	...
	...
    }	
	StringBuilder autoSuggestParam = new StringBuilder();
	string ServiceUrl = ServicePath + "/" + ServiceMethod + "?";

	autoSuggestParam.Append(" var options = { ")
		 .Append("script:\"" + ServiceUrl + "\", ")
		 .Append("varname:\"input\",")
		 .Append("json:true,")
		 .Append("shownoresults:false,")
		 .Append("maxresults:10000,")
		 .Append("callback: function (obj) 
			{ document.getElementById('" + 
			hiddenValue.ClientID + "').value = obj.id; }};");
	autoSuggestParam.Append(" var as_json = new bsn.AutoSuggest
			('" + txtAutoSuggest.ClientID + "', options); ");

	//add the control
	LiteralControl lc = new LiteralControl
		("<script language="javascript">" + autoSuggestParam + "</script>");
	this.Controls.Add(lc);

Now it's interesting to see inside bsn.AutoSuggest_2.1.3.js JavaScript file in detail.

When I downloaded bsn.AutoSuggest files, the 'script' property of the var option (just above) was script:"test.php?id=..".

The script value (test.php?id=..) allow finally to invoke an XMLHttpRequest and call asynchronously the page test.php with some GET parameters to bind data:

JavaScript
_b.Ajax.prototype.makeRequest = function(url, meth, onComp, onErr) {

    if (meth != "POST")
        meth = "GET";

    this.onComplete = onComp;
    this.onError = onErr;

    var pointer = this;

    // branch for native XMLHttpRequest object
    if (window.XMLHttpRequest) {

        this.req = new XMLHttpRequest();
        this.req.onreadystatechange = function() { pointer.processReqChange() };
        this.req.open("GET", url, true); //
        this.req.send(null);
        // branch for IE/Windows ActiveX version
    }
    else if (window.ActiveXObject) {

        this.req = new ActiveXObject("Microsoft.XMLHTTP");
        if (this.req) {
            this.req.onreadystatechange = function() { pointer.processReqChange() };
            this.req.open(meth, url, true);
            this.req.send();
        }
    }
};

So I said to myself, I must try to keep the same mechanic to call web service and WCF service.

I learnt that it's possible to invoke a webservice with GET parameters through XMLHttpRequest.

The script value must be formatted like this: ServicePath + "/" + ServiceMethod + "?param=p" here very simply: Webservice.asmx/getResults?input=...

To authorize this call, you must add a section in Web.config:

XML
    <webservices>
      <protocols>
        <add name="HttpGet">
        <add name="HttpPost">
      </add>
    </add>
</protocols></webservices>

To call a WCF service, it's exactly the same mechanism: Service.svc/getResults?input=...

It's interesting to note that to invoke a WCF Contract Method from Ajax request, the method must have the attribute [WebGet()] and the WCF service class must have the attribute [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)].

And similarly, to invoke a webservice from Ajax request, the method must have the attribute [ScriptMethod] and the webservice class must have the attribute [ScriptService].

A last point, when we receive the response of the webservice, it's a wrapped response with XML, so we must strip XML.

WCF is better because it sends a clean response... :)

JavaScript
_b.AutoSuggest.prototype.setSuggestions = function(req, input) {

    // if field input no longer matches what was passed to the request
    // don't show the suggestions
    //
    if (input != this.fld.value)
        return false;

    this.aSug = [];

    if (this.oP.json) {

        var txtResponse = req.responseText;
        var jsondata = '';

        // web service case
        if (txtResponse.charAt(0) == '<') {
        
            /*
            <string xmlns="http://tempuri.org/">
                { results: [{ id: '1', value: 'Foobar', info: 'Cheshire' },
                            { id: '2', value: 'Foobarfly', info: 'Shropshire' },
                            { id: '3', value: 'Foobarnacle', info: 'Essex' } ] }
            </string>*/
            //STRIP XML and HTML tags ( tratment)    
            var matchTag = /<(?:.|\s)*?>/g;
            txtResponse = txtResponse.replace(matchTag, "");
            jsondata = eval('(' + txtResponse + ')');
        }
        //WCF service case
        else 
        {
            /*{"d":"   { results: 
            [{ id: \"1\", value: \"Foobar\", info: \"Cheshire\" },
            { id: \"2\", value: \"Foobarfly\", info: \"Shropshire\" },
            { id: \"3\", value: \"Foobarnacle\", info: \"Essex\" } ] }    "}*/

            var alphaChars = /^([a-zA-Z])$/;

            if (alphaChars.test(txtResponse.charAt(2))) 
            {
                txtResponse = txtResponse.replace("{\"d\":\"", "");
                txtResponse = txtResponse.substring(0, txtResponse.length - 2);
                jsondata = eval('(' + txtResponse + ')');
            }
        }

        for (var i = 0; i < jsondata.results.length; i++) {
            this.aSug.push({ 'id': jsondata.results[i].id, 
	   'value': jsondata.results[i].value, 'info': jsondata.results[i].info });
        }
    }
    else {
        var xml = req.responseXML;

        // traverse xml
        //
        var results = xml.getElementsByTagName('results')[0].childNodes;

        for (var i = 0; i < results.length; i++) {
            if (results[i].hasChildNodes())
                this.aSug.push({ 'id': results[i].getAttribute('id'), 
		'value': results[i].childNodes[0].nodeValue, 
		'info': results[i].getAttribute('info') });
        }    }

    this.idAs = "as_" + this.fld.id;

    this.createList(this.aSug);
};

Points of Interest

It was interesting to learn how to invoke .NET services with the basic JavaScript object XMLHttpRequest.

Once you see that, you can have a little idea about how ajaxtoolkit works with its ScriptManager.

History

  • 1st February, 2011: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)