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:
- You must provide the
ServiceMethod
property, it might be an .asmx file or .svc file - 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. - On this service method implementation, you must respect the json format result. The
string
must be formatted like that:
{ 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.
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (!this.DesignMode)
{
ScriptManager sm = ScriptManager.GetCurrent(Page);
if (sm == null)
throw new HttpException
("A ScriptManager control must exist on the current page.");
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");
if (!Page.Items.Contains("styleAlreadyPut"))
{
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); ");
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:
_b.Ajax.prototype.makeRequest = function(url, meth, onComp, onErr) {
if (meth != "POST")
meth = "GET";
this.onComplete = onComp;
this.onError = onErr;
var pointer = this;
if (window.XMLHttpRequest) {
this.req = new XMLHttpRequest();
this.req.onreadystatechange = function() { pointer.processReqChange() };
this.req.open("GET", url, true);
this.req.send(null);
}
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:
<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... :)
_b.AutoSuggest.prototype.setSuggestions = function(req, input) {
if (input != this.fld.value)
return false;
this.aSug = [];
if (this.oP.json) {
var txtResponse = req.responseText;
var jsondata = '';
if (txtResponse.charAt(0) == '<') {
var matchTag = /<(?:.|\s)*?>/g;
txtResponse = txtResponse.replace(matchTag, "");
jsondata = eval('(' + txtResponse + ')');
}
else
{
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;
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