Saturday, January 27, 2007

How to get the SmartAutoCompleteExtender to work with ASP.NET AJAX 1.0 RTM

If you are using the SmartAutoCompleteExtender by Infinities Loop with pre-release versions of Atlas, and you just upgraded to the new ASP.NET AJAX 1.0 RTM release, you will have to make some minor changes to the SmartAutoCompleteExtender code for it to work again.

1. In the project where the SmartAutoCompleteExtender resides, add a reference to the AjaxControlToolkit.dll assembly. This is now required since the AutoCompleteExtender, on which the SmartAutoCompleteExtender is based, is now part of the AJAX Control Toolkit project. Download it and add a reference to its assembly in your own project(s). (Thank you Dave for reminding me!)

2. In SmartAutoCompleteBehavior.js, go to the end of the file and replace the last line with the following. Note the namespace of the base AutoCompleteBehavior has changed.

i88.UI.SmartAutoCompleteBehavior.registerClass(
'i88.UI.SmartAutoCompleteBehavior',
AjaxControlToolkit.AutoCompleteBehavior);

3. In SmartAutoCompleteExtender.cs, add a using reference to the AjaxControlToolkit namespace.

using AjaxControlToolkit;

4. In SmartAutoCompleteExtender.cs, go to the method GetScriptReferences() and change the first reference line with the following. Note the parameters to the call need to be changed.

references.Add(new ScriptReference(
"AjaxControlToolkit.AutoComplete.AutoCompleteBehavior.js",
"AjaxControlToolkit"));

5. In SmartAutoCompleteExtender.cs, go to the method GetScriptDescriptors() and change the line containing serviceURL to servicePath.

if (!string.IsNullOrEmpty(ServicePath))
{
    sbd.AddProperty("servicePath", ResolveClientUrl(ServicePath));
}

6. In SmartAutoCompleteExtender.cs, go to the method GetScriptDescriptors() and change the line containing completionElementID to completionList.

if (!string.IsNullOrEmpty(CompletionListElementID))
{
    sbd.AddElementProperty("completionList", CompletionListElementID);
}

If you made the necessary reference changes to the new assemblies and updated your Web.config you should be up and running in no time.

Download sample VS2005 SP1 Web Application solution

62 comments:

SingleGuy said...

I foloowed your instructions but I am getting this error:
The type or namespace name 'AutoCompleteExtender' could not be found (are you missing a using directive or an assembly reference?)

Leo Vildosola said...

You are absolutely right. I use ReSharper and it takes care of ensuring all my using statements are accounted for. Please try adding the following to your code.

using AjaxControlToolkit;

SingleGuy said...

Thank you very much - it compiled.
I have one more question - if you can help me I would really appreciate it.

I would like to use SmartAutoCompleteExtender just like the built in AutoCompleteExtender - calling a webservice to return results from a database.

I would just like to add one more parameter - a user selected value from a drop down list elsewhere on the page.

Your help is much appreciated.

Leo Vildosola said...

If I understand correctly, you would like to pass an additional parameter to your web service that would be read from another element's value. That would mean that you'd want to add a property to the SmartAutoCompleteExtender that will allow you to specify the identifer of the element from which to get the current value and pass it as an additional parameter to the web service. In order to do this you would have to override the call to the web service in the _onTimerTick handler, so instead of calling the base implementation you would call the web service yourself with the new parameter.

Hmmm... Perhaps that may be more work than necessary. I don't think I would modify the SmartAutoCompleteExtender for this. Consider the following possible alternate approach.

Place your drop down list inside an UpdatePanel. Make sure that the drop down list has its AutoPostBack property set to allow you to process any changes to the drop down on the server, thus allowing you to have the latest value. Set the SmarAutoCompleteExtender's UpdateMode property to Callback. On the callback handler you will have access to the latest selected value from the drop down list and you can then call the web service yourself with the extra parameter.

The reason I would consider doing it outside of the SmartAutoCompleteExtender is because I would think that you are looking to do something that may not really be suitable as an extension of the control. It appears to me to be something specific to solving a particular problem. For this, consider a pattern, such as the one I described above, instead of extending the control. In the future, if you need to pass more parameters to the same or other web services you can do so by using the same pattern and not have to worry about changing the control.

SingleGuy said...

Thank you very much for responding.

I am having another issue.
I followed your instructions and the changes to the source code and the project compiled fine.

I then copied the Infinity.web.dll file to the bin folder of my project.

I registered the tag at the top of the page and I wrote code to use the SmartAutoCompleteExtender.

I am getting javascript errors in both IE and FireFox.

Did I miss something?
Do I have to include the SmartAutoCompleteExtender.js file somewhere?

Thank you for your help.

Leo Vildosola said...

Make sure that you add SmartAutoCompleteExtender.js to your project in the same folder as SmartAutoCompleteExtender.cs. Once added, make sure to set its Build Action in the Properties of the file to "Embedded Resource". Once you do this rebuild and it should now find the file.

SingleGuy said...

I followeed your instructions and I am still getting a javascript error.

SingleGuy said...

there is ScriptPath parameter. Should I be using it? how?

Thanks for your help.

Leo Vildosola said...

You need to make sure you have a ScriptManager instance in your page. Either add the following to your master page or the page in which you define the SmartAutoCompleteExtender.

<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true" />

Please visit the AJAX Control Toolkit site at CodePlex for more information.

SingleGuy said...

I have a ScriptManager already in the page since I am using other AJAX controls.

Ahy other suggestions?

Leo Vildosola said...

Sorry, it is difficult to troubleshoot your setup without looking at the code.

The only other thing I can think of is that the namespace of the SmartAutoCompleteExtender.js and that specified in the SmartAutoCompleteExtender.cs file do not match.

Other than that, I am afraid I am out of ideas.

Anonymous said...

I also have got a Javascript error - 'Sys.InvalidOperationException'. 'servicURL' is not a property or an existing field.

Any help would be much appreciated

Leo Vildosola said...

I looked at the code in detail. It looks like they changed the name of the behavior property from serviceURL to servicePath probably to be consistent with the property name in AutoCompleteExtender. Please see my update to the steps above. Look at step 5, which should fix your problem.

Anonymous said...

Does the callback method have to be part of the Page, or could it also be in a control ( ascx file ) that resides within the page?

Thanks

Leo Vildosola said...

It can definitely be part of the Web User Control.

Anonymous said...

I put the code in my project and tried to incorporate it with what I am working on.

I created a folder called SmartAutoCompleteControl and put both the SmartAutoCompleteExtender.cs and SmartAutoCompleteBehavior.js files within it.
I changed the Build Action for the js file to Embedded Resource.

When I run my code I get an error stating that the assembly does not contain a Web resource with name XYZ.Web.UI.Controls.Extenders.SmartAutoCompleteControl.SmartAutoCompleteBehavior.js.

I debugged through the code and stopped to see the resources in the assembly, and it was there.

I am not sure why when running the app, it is complaining that the resource is not there.

Has anyone see this before?

Thanks in advance for your help.

Leo Vildosola said...

First of all, just to double check, there should be 5 files added to that folder in your project.

In any case, make sure you have the following line at the top of your SmartAutoCompleteExtender.cs file right after the using statements.

[assembly: WebResource("XYZ.Web.UI.Controls.Extenders.SmartAutoCompleteControl.SmartAutoCompleteBehavior.js", "text/javascript")]

I don't recall if the original version has that in place. Then make sure you update the method GetScriptReferences to point to your behavior file.

references.Add(new ScriptReference("XYZ.Web.UI.Controls.Extenders.SmartAutoCompleteControl.SmartAutoCompleteBehavior.js", "XYZ.Web"));

Note that I am assuming that the namespace in the behavior is "XYZ.Web". Change it to match the namespace you define in the behavior.

Let us know if you manage to get it going.

Unknown said...

I've updated the files following the instructions, but still end up with a javascript error. The odd thing is that it only affects IE, and effectively kills the extender.

The error message from IE is "Sys.Debug" is null or not an object"

This problem arises when the extender is called. Can't seem to be able to track down the issue, so I'm grateful for any help!

Thanks in advance!

Leo Vildosola said...

It would seem to me that what you are missing is the reference to the AjaxControlToolkit, which in turn will add the reference to the core Ajax javascript libraries. Can you please make sure you have the following line in the method GetScriptReferences in SmartAutoCompleteExtender.cs?

references.Add(new ScriptReference("AjaxControlToolkit.AutoComplete.AutoCompleteBehavior.js", "AjaxControlToolkit"));

Sys.Debug is part of the core Ajax namespaces and it looks like you are missing the reference to it.

Unknown said...

The strange thing is that it compiles and subsequently works perfectly in both Opera and Firefox.

The GetScriptReferences method contains the following two calls:

references.Add(new ScriptReference("AjaxControlToolkit.AutoComplete.AutoCompleteBehavior.js", "AjaxControlToolkit"));

references.Add(new ScriptReference("Infinity.Web.Resources.SmartAutoCompleteBehavior.js", "Infinity.Web"));

The AjaxControlToolkit is also included in the file.

Is there a way for you to post a working version of the extender so that we can find out if we have other problems or if we're just plain stupid? :)

Thanks!

Anonymous said...

Leo, thanks for your suggestion.

I was able to get past the problem where it was not recognizing the js file as a web resource.
I ended up adding the line [assembly: WebResource("XYZ.Web.UI.Controls.Extenders.SmartAutoCompleteControl.SmartAutoCompleteBehavior.js", "text/javascript")] to AssemblyInfo.cs.

Things work when I don't assign a value to CompletionListElementID.

When I assign a value of a div tag to this property so that I can style the results, I get a js error stating "this._completionListElement is null or not an object".

I did assign the ClientID of the div in the Page_Load method.

Has anyone run into this before?

Anonymous said...

A lot of times, we have to add controls to the page at run time, not at design time. I'm working on a AutoCompleteTextBox that will internally generate AutoCompleteExtender.

AutoCompleteTextBox inherits from TextBox, and it contains an AutoCompleteExtender field. For some reason, the following script is missing from the page that contains my AutoCompleteTextbox web control.

===========================

Sys.Application.add_init(function() {

$create(AjaxControlToolkit.AutoCompleteBehavior, {"completionSetCount":12,"id":"AutoCompleteExtender1","minimumPrefixLength":2,"serviceMethod":"GetCompletionList","servicePath":"Services/PeopleSearchWebService.asmx"}, null, null, $get("myTextBox"));

});

==========================


I also tried to create a composite control that includes a textbox and AutoCompleteExtender, but that didn’t work either.


Any help is greatly appreciated

-lilys

Leo Vildosola said...

Christian, I took the latest code from Infinities blog and followed the steps to make the changes. I then created a VS2005 solution that has this assembly with the code already corrected. Note that I also added code to the behavior file (.js) that fixed some bugs I had encountered myself. Hope this helps.

ial, I have added a step that covers your issue. I had written about this on Infinities original post. It is covered there. Refer to step 6.

lilys, what you are trying to do is not as straight forward as you might think. I have created just what you ask in another project. I will try to get it isolated and put it in the sample I just posted as an update. I have it working with the RC version of AJAX but I will have to test it with the RTM. I hope you are not in a rush for this. ;-)

Unknown said...

Thanks a lot for the project. However, I am still getting the same error message.

Since this issue seems to be isolated to occur on my machine, I am assuming that the problem lies in my copy of VS or IE.

Thanks again!

Anonymous said...

I need have a scenario where I need to check on a button click that the text in the textbox is one of the entries returned from the suggestions.
I tried registering an array with the results in the codebehind handler that gets the suggestions, but that array does not seem to be created on the javascript side.
How can I make the suggestion values available to the javascript on the page so that it can be compared with what the user has typed?

Thank you

Leo Vildosola said...

sw-emg, You could override the function _setText in the SmartAutoCompleteBehavior to allow you to determine if the selection was done from the list. _setText is a function in the AutoCompleteBehavior that is called when the user selects an item from the list.

// Overriden to track selection from list.
_setText: function(text)
{
// Allow the base implementation to do its work.
i88.UI.SmartAutoCompleteBehavior.callBaseMethod(this, '_setText', [text]);

// Set your flag here.
}

Essentially, the logic would be something like:

If the text changes, clear the flag that tells you a value was selected from the list. Set the flag when the user selects an item from the list (i.e. the function _setText is called).

You can also add a property to the SmartAutoCompleteExtender that would be used to hold the identifier of a hidden field that would hold the result of this check. You can use this field as your flag holder. Set it from _setText. On a postback you can verify the value of this field to see if the user selected from the list.

Anonymous said...

Thanks for your suggestions.
I was able to do what I wanted by using a hidden field and using that as a buffer which the extender wrote to and the page checked at the time of submission.

Tom Phelan said...

You saved me hours and hours of work.

Thanks!

Unknown said...

How can I hit SmartAutoComplete_DoSearch() function whenever keypress down? This required at least 3 characters to hit this event on the cs file.
thanks,

Leo Vildosola said...

There's a property called MinimumPrefixLength that you can set to 1 if you want it to trigger the postback starting at the first key pressed.

Anonymous said...

Hi,

How can I send an additional parameter into the webmethod (in the codebehind)? I need the method to return the values based on some session variable's current value?

Leo Vildosola said...

You can try adding a property to the class AutoCompleteEventArgs to hold your new value. Then add the value you want to pass to this property in the method _updateCallback in SmartAutoCompleteBehavior.js. Without trying it this should allow you to pass that additional parameter.

SingleGuy said...

I am working with SmartAutoCompleteExtender to try to get it to work.

I am getting 2 Javascript errors:

Error 1:

Sys.Preview has no properties
i88.UI.SmartAutoCompleteBehavior.registerClass(
'i88.UI.SmartAutoCompleteBehavior...

Error 2:

a.beginUpdate is not a function
createCallback(function(), Object callbackTargetID=ateCity completionInterval=50, null, null, input#txtCity.BodyText)ScriptResource.ax...

I am getting some results from the database but most are missing.

Please help me.
Thank you

Leo Vildosola said...

I don't know what could be going wrong. Other than potentially not having the correct references. Perhaps you can review the steps and see if you have them all in place.

Anonymous said...

Yikes. This seems like it should be so easy to see, but actually it's a nightmare. I really like the idea of what you've done, but if I try to add to the behavior, I get the same error that someone above got:

Assembly 'Infinity.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' contains a Web resource with name 'Infinity.Web.Resources.SmartAutoCompleteBehavior.js', but does not contain an embedded resource with name 'Infinity.Web.Resources.SmartAutoCompleteBehavior.js
[assembly:

When I look at *your* file, I see this below as the resource name:

WebResource("Infinity.Web.Resources.SmartAutoCompleteBehavior.js", "text/javascript")]

Yet, above, in your comments, you have something like XYZ.Web.UI.yada.yada, which is not at all like the solution you provide.

So, why are we getting this error? The person above stated they had solved the problem, but when I change the name to what he has (changing xyz to infinity, right?), I still get the error.

How can something so simple be so complex? I even went into ildasm, to see what the resource name was, and according to the manifest it is just as the error says. I have included all the changes you made, yet, I cannot seem to get this to work after 8 hours of messing with this!

I would truly appreciate an explanation of what this error *means*, and how to go about fixing it. You also mention moving the js file around. Why are you suggesting this, when your application example has it in the 'Resources' folder? It's all so confusing...Ack.

Anonymous said...

I'm having the same problem with the assembly error as the two above. I guess what I (and obviously at least twp others) don't 'get' is what is this assembly path referring to?

Is it a physical path from the web root? Is it a namespace + physical path? Is it trying to find the .js file? What is the *point* of the assembly type?

I so wanted to be able to use this for a demo on Monday, but it looks like I'm going to have to use the web service version, which violates every architecture rule that exists in our company, and hence I will get chopped apart for using it.

Leo Vildosola said...

In order for the line

[assembly: WebResource("Infinity.Web.Resources.SmartAutoCompleteBehavior.js", "text/javascript")]

to work you must make sure that the file SmartAutoCompleteBehavior.js has its Build Action set to Embedded. Without it the resource will not be found.

The fully qualified name of the resource (i.e. Infinity.Web.Resources.SmartAutoCompleteBehavior.js) must match the folder structure. For example, if I move the file to MyResources then the line would change to

[assembly: WebResource("Infinity.Web.MyResources.SmartAutoCompleteBehavior.js", "text/javascript")]

where Infinity.Web is the name of the assembly and MyResources is the folder within that assembly. This can be a deeper folder hierarchy.

Can you tell me if at least you can make the sample I provide work? If not, then I can start from there to see how I can clarify it further.

Anonymous said...

Hi Leo:

Thanks so much for responding so quickly. I just don't understand why it isn't working, since I am doing exactly as you specify. I had already known about the 'Embedded' property so that wasn't the issue.

I am using your exact folder structure. The only thing I did was convert the app to vb, since the company I'm contracting with only allows vb.net (please just shoot me now). I also changed the root namespace to blank so it works exactly like a c# assembly (why M$ adds that wrinkle to make it harder for c# to vb code to work is beyond me, but beside the point).

So, in a nutshell, my assembly name for the extender project is 'Infinity.Web'. I have a 'Resources' folder hanging off that project, and the name of the js file is SmartAutoControlExtender.js

I have in my assembly attribute:

< Assembly: WebResource("Infinity.Web.Resources. SmartAutoCompleteBehavior.js", "text/javascript") >

and my addresource code is: (blanks intentionally put in so the whole line shows up in Mozilla)

references.Add(New ScriptReference("Infinity.Web. Resources.SmartAutoCompleteBehavior.js", "Infinity.Web"))

So, as you can see, this is exactly like your example, except I get the error mentioned a couple of times above.

I have not made any changes to code except to convert to vb.net, and I have been doing c# and vb.net since 2001, so I feel very good about the conversion (well, obviously something with the assembly is off and I am stupid about this or we would not be talking! ;-)).

Anonymous said...

In the js file properties window I do have 'copy to output directory' disabled (ie, don't copy). I don't see how this info is of any use though.

Leo Vildosola said...

No need to copy. I don't have experience using this in VB. However, it sounds like you may be having issues with how embedded resources are exposed. That's just a guess. Unfortunately, I don't know enough about how VB deals with these to tell you. Sorry. Perhaps you can try Infinities blog and ask if anyone has used the extender in VB.

Leo Vildosola said...

Rick, would it be possible for you to send me the sample VB project? If so, send me the zip to "lvildosola at htcal dot com". I'd be happy to take a look at it. Maybe I can see what may be the problem.

Eric Lemes said...

Leo,

I'm with the same problem of lily's with the MaskedEditExtender.

The Sys.Application.add_init(function() {
isn't in the source code when creating a composite control that uses a extender.

Can you help me?

Leo Vildosola said...

Sorry, I am out of context. Can you send me a sample to "lvildosola at htcal dot com"? I will take a look at your code and see if I can make it work. Going back and forth through posts will make it very long and difficult to diagnose. Once solved we can post the necessary changes for others to learn.

Anonymous said...

HI, i downloded your sample code and got it building. I added 2 textbox controls to the Default.apsx page present in the Sandbox Project.
How can i pass the text of these 2 textbox controls to the SmartAutoComplete_DoSearch method.

Unknown said...

Hi,I went through this blog and felt someone will help me with this issue.

I'm using SmartAutoCompleteExtender by infinite loops in a Datagrid also I have added reference to AjaxControlToolkit.dll in my project.There is SmartAutoCompleteExtender control in the FOOTER Template of the Datagrid in a webpage.But when I run the application I'm getting the following error "The control with ID 'SmartAutoCompleteExtender1' requires a ScriptManager on the page. The ScriptManager must appear before any controls that need it."......
Though there is a ScriptManager before the SmartAutoCompleteExtender control in the page.
Thanks In Advance........

Leo Vildosola said...

Hmmm. Can you try to add a ScriptManager instance in your Master page if you have one? That's how I always use it and I haven't encountered this problem. I don't know if there's anything specific that you may need to do with GridView controls.

Unknown said...

Hi Leo,

Thanks for your reply....
I don't have any master page in my application.What else can be done to resolve this issue?

Leo Vildosola said...

Make sure your ScriptManager definition is the first element in your form. Other than that I don't know why it would fail. If you have a snippet of code that you can send my way I would be happy to take a look at it and see if I can see something else.

Unknown said...

Hi I'm using SmartAutoCompleteExtender control by infinite loops.
I want to fetch data from the database when the following event fires:

Public Sub SmartAutoComplete_DoSearch(ByVal sender As Object, ByVal e As i88.AutoCompleteEventArgs)

.....

End Sub

But what ever code I placed in this event never executes(not even able to write to a text file also ) except this line :

e.CompletionItems = SomeStringArray

I couldn't understand the reason for this ...

Thanks In Advance

Leo Vildosola said...

roopa, do you mean that the event is raised and that the assignment works (i.e. e.CompletionItems = SomeStringArray
) but when you try to do other things in the event handler, like read from the database, it fails? Or are you saying that the event is not raised at all and therefore the method is not executed? Please clarify.

Unknown said...

Hi Leo,

Thanks for your response.

Yes ,I mean that the event is raised and that the assignment works (i.e. e.CompletionItems = SomeStringArray
) but when I try to do other things in the event handler, like read from the database, it fails.

Leo Vildosola said...

Okay. I am assuming that you are using a postback so there's no reason why it would time out unless your call to the database is taking way too long. Can you perhaps post a portion of the error you are getting? When you say it fails, how exactly does this happen? An exception, nothing?

Unknown said...

Thanks you once again

I'm trying to fetch the data from database put it into an string array (like strArray) and then assign the array as

e.CompletionItems = strArray

so when I try to do as above,auto completion behavior of SmartAutoCompleteExtender control is not happening without throwing any error/exception.

Instead if I simply do the following in the SmartAutoComplete_DoSearch event handler

Dim items(3) As String
items(0) = "One"
items(1) = "two"
e.CompletionItems = items

then the auto completion is happening.

What could be reason for this ...

Leo Vildosola said...

Hmmm. It is difficult to tell you what the reason for it might be. The only thing I can think of is that an exception is being thrown and you can catching it somewhere and swallowing it. Therefore, you are not seeing it in the UI and it is not doing what you expect it to do. If you could send me a bigger sample snippet of your code to lvildosola at htcal dot com I would be happy to look at it and see if something like that is happening.

Anonymous said...

Works great, except when I try to select an item with the tab key. When I select an item with the tab key, 'null' gets displayed in the textbox. It works fine if I select an item with the mouse, however.

Leo Vildosola said...

I had a similar problem at one point, although I don't recall exactly what it was. I would suspect that in the SmartAutoCompleteBehavior.js file the _selectIndex is not being initialized and therefore the text is undefined, or in your case set to null by default.

Anonymous said...

Nope. this._selectIndex isn't null. I also checked this._completionListElement.childNodes[this._selectIndex].firstChild.nodeValue, and that's not null either. If I add alert boxes for both, within the _onKeyDown: function(evt) method, I can see the values.

I suspect the error involves the i88.UI.SmartAutoCompleteBehavior.callBaseMethod. I'll keep digging...

Anonymous said...

Ok, I think I fixed the TAB problem.

I downloaded the AJAX Toolkit solution. In the AutoCompleteBehavior.js file, I commented out the following line:

var text = (item && item.firstChild) ? item.firstChild.nodeValue : null;

and replaced it with the following:

var text = item;

Rebuilt the AJAX Toolkit dll, re-ran my project and, lo and behold, the tab key works like a charm.

Anonymous said...

Hi.
I was trying to use the autocomplete with a custom javascript function which returns an array of string.

Can anyone halp me with that?

thanks

Anonymous said...

Hi,

Good work. I tried your tab issue fix it works but return key and mouse event selection returns [object] value. Do you have any fix for the same?

thanks
Viji

Leo Vildosola said...

Viji, it has been a while since I have used this code. Off the top of my head I could not tell you a solution. You may apply similar logic as for the Tab fix. You would just need to override the correct JavaScript event handler(s) to handle it. The Return key should be easily added in the same place as the Tab key. For the mouse you will probably have to override an existing event handler.

Mark said...

Hello, i don't understand why if i put the SmartAutoCompleteExtender control in a contentplaceholder doesn't work the proprety CompletionListElementID and so i can't style the panel!!

could you help me?