August 2008 - Posts

When setting up class inheritance, you may have seen the error that the method or property is undefined.  This results from the fact that a property or method exists in the base class, or is not explicitly defined or overridden in the derived class.  This will result from not calling initializeBase in the constructor.  By adding this in and making the code look like the following:

 function TerminatedBankState() {

    TerminatedBankState.initializeBase(this);

}

TerminatedBankState.prototype = { .. }

TerminatedBankState.registerClass("TerminatedBankState", BaseBankState);

The instance of the terminated bank state object now has references to the properties and methods from the BaseBankState object.

Posted by bmains | with no comments
Filed under:

Sorry for the long title, but I wanted to post about some of the differences between the controls and extenders client component's client state, which are defined for AjaxControlToolkit.ControlBase and AjaxControlToolkit.BehaviorBase on the client.  In developing an AJAX Control Toolkit control, I've come to find some of the differences.  One of the key differences is with client state.

Client State is a hidden field mechanism that stores data about the current state of the AJAX control.  For an AJAX control toolkit extender that supports client state, on the client the get_ClientState and set_ClientState properties are avaialble to read/write data to this hidden variable.  This works really easily on the client, plus it allows the server to access the value through the ClientState property, and load/save the client state value.

For a control, however, a control developer needs to override the SupportsClientState property, and return true.  Furthermore, there are no equivalent properties on the client.  Instead, the get_clientStateField and set_clientStateField get and set the reference to the field, so it's possible to read and write the values using this reference through get_clientStateField().value.  This only works if SupportsClientState is set to true.  Controls also have to use the LoadClientState and SaveClientState methods to load/save state.  State is loaded whenever LostPostData occurs and saved when the hidden field is registered.

Another interesting variation is that the control base class uses the ScriptManager's RegisterHiddenField, whereas the extender creates its own hidden field reference so that the developer can work directly with the ClientState property.

 

Posted by bmains | with no comments
Filed under:

One of the errors that frustrated me when developing AJAX controls was when I got <Namespace> is undefined, even though the script appeared to be correct.  I couldn't figure it out right away because the class was registered correctly and the namespace was defined properly as well.  So what could be the issue?

To help make AJAX development easier, I use a CodeSmith script to generate the shell AJAX component code, which works really well.  This time when I got the error, it was for a component I created manually without the script.  As soon as I replaced my code with the CodeSmith template generation code, the component began to work.

What this means is that while sometimes the error may be a missing component reference (like the System.Web.Extensions assembly or the AJAX Control TOolkit dll), more often the issue could be an issue with the formatting of your script, like a missing comma or curly brace, something like that.  Something small that even VS wasn't picking up with its limited error checking.

So, a word of caution: write your script, and check it twice Big Smile

Posted by bmains | with no comments
Filed under:

When using TDD practices, you may think ahead a little bit to a situation in the future.  For instance, suppose you have this:

public class CustomerClassification
{
   public CustomerClassification(string name, string type)
   {
      this.Name = name;
      this.Type = type;
   }

   public string Name
   {
      get { return _name; }
      set { _name = value; }
   }

  public string Type
  {
     get { return _type; }
     set { _type = value; }
   }
}


It's tempting to think that the property values will need to be change for some reason in the future, even though your current tests and current development of the application don't identify a need for a setter.  I'd recommend that you leave out the setter until you actually see a need for adding it in.  The reason is that this is more inline with TDD (implement it only when you need it), plus setters may cause problems in the future (changing values that shouldn't be changed as the constructor would be the only point of entry).

A perspective in the light of TDD... this may not always be a valid reason and I'm definitely not against setters,but it's something to consider.

Posted by bmains | with no comments
Filed under:

Every AJAX control or extender you build has an ID on the client, accessible through get_id and set_id.  This ID is a unique ID and doesn't have to match the ID of the control it's given on the server, but the script registration process ensures that the server and client match.  So, using the ID, $find can reference the client component of any AJAX control on the page like so:

<script language="javascript" type="text/javascript">
function pageLoad()
{
 var tabs = $find("<%= tc.ClientID %>");
 //do something
}
</script>

<ajax:TabContainer ID="tc" runat="server">
 <!-- Tab Panels -->
</ajax:TabContainer>

The client component has a handy set of features, such as getting or setting the active tab index (through get_activeTabIndex and set_activeTabIndex) or polling the collection of tabs.  Each AJAX component has a client architecture you can take advantage of; it may take some getting used to that fact, but if you dig through the AJAX control toolkit code available on http://www.codeplex.com, you'll learn more about what is available to you on the client.

Posted by bmains | 2 comment(s)
Filed under:

I may have mentioned somewhere in my blog that AJAX components go through a process of describing the properties and events (at least) so that the server component can push down values to the client.  This process happens in the GetScriptDescriptors method.  The ScriptDescriptor,which has three deriviatives, notes a component's properties and events by using the following approach:

ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor("<full client class name>", targetControl.ClientID);
descriptor.AddProperty("highlightCellOnMouseOver", this.HighlightCellOnMouseOver);
descriptor.AddProperty("highlightRowOnMouseOver", this.HighlightRowOnMouseOver);

return new ScriptDescriptor[] { descriptor }; 

Notice a few things: first off, the AJAX component I'm creating is a custom extender.  You can tell this because I used ScriptBehaviorDescriptor (I would have used ScriptControlDescriptor for an AJAX control).  The AddProperty method adds properties for the description process; in this way, the AddProperty method passes along a value established on the server to the client, to act as a default value of sorts.

In reality, on the client side those properties have the following definition:

 get_highlightCellOnMouseOver : function()
{
 return this._highlightCellOnMouseOver;
},

set_highlightCellOnMouseOver : function(value)
{
 if (this._highlightCellOnMouseOver != value)
 {
  this._highlightCellOnMouseOver = value;
  this.raisePropertyChanged("highlightCellOnMouseOver");
 }
},

get_highlightRowOnMouseOver : function()
{
 return this._highlightRowOnMouseOver;
},

set_highlightRowOnMouseOver : function(value)
{
 if (this._highlightRowOnMouseOver != value)
 {
  this._highlightRowOnMouseOver = value;
  this.raisePropertyChanged("highlightRowOnMouseOver");
 }
},

So theoretically they can be changed during the client's lifecycle, but at least a default value is established on the server.

Posted by bmains | with no comments
Filed under:

I previously posted about the format that a web service method has to be in for the AutoCompleteExtender to work.  This blog post focuses on some of the other capabilities of this extender.  In an example web method, I concatenate text and return the concatenation using LINQ as follows.

 [WebMethod]

public

 

 string[] GetTopSearchPhrases(string prefixText, int count) {

  SearchManager manager = SearchManager.GetManager();

 

 

  SearchPhraseCollection phraseCollection = manager.GetTopSearchPhrases(prefixText, count);

 

return (from p in phraseCollection select string.Format("{0} ({1} occurrence(s))"
    
, p.Phrase, p.ResultCount.ToString())).ToArray();

 

 

 

}

What I want to do is get the results as:

Microsoft .NET (21 occurence(s))
Microscopes (122 occurence(s))

And so on.  But, when the user selects an item in the list, I do not want the parenthesized text to appear in the textbox.  To trim this, I added a javascript event handler for the OnClientItemSelected event, that does this:

//Ensures that the parenthesized text isn't passed on

function

 

autoComplete_itemSelected(sender, e) {

 

 var text = e.get_text();

 

if (text.indexOf('(') > 0) {

  text = text.substring(0, text.indexOf(

 

'('));

 

if (text != null && text.length > 0)

 

    text = text.trimEnd();

 }

 

 

 var targetElement = sender.get_element();

 

 

 targetElement.value = text;

}

What this does is it gets the text of the item currently selected.  Then, if the text contains a parenthesis, it strips it out.  If the text happens to be not null, it's trimmed (to remove the extra spacing from " (" in the text; I'd rather programmatically trim than do a -1 just in case I change the formatting of the output string later and forget to change the accompanying event handler.

Lastly, the sender is the AutoCompleteExtender client class, and get_element() returns a reference to the target textbox.  The value is overridden (as the AutoCompleteExtender assigns the selected value to the textbox, but our event handler occurs after that assignment, wiping out any changes already performed).

You may wonder where get_text() came from; the event argument is AutoCompleteItemEventArgs, which has a get_text(), get_value(), and get_item() properties.  The latter is information about the item itself, whereas the first two contain the information about the items in the list.  You wouldn't know without the documentation what this event arg was.  I found this out by looking in the AJAX control toolkit code myself.  I could have put a debugger breakpoint in the code and figured it out that way as well.

 

 

Posted by bmains | 3 comment(s)
Filed under:

 When developing AJAX-ified controls using the AJAX framework from microsoft and the AJAX control toolkit, you have to rethink the control development approach a little bit.  After all, the key to making AJAX controls work is rendering code on the client-side, and being the AJAX uses managed scripts, the scripting process has to be well thought out. It's not that overtly difficult to create custom controls, but it does usually require large amounts of code.  I start off my scripts from a script that I have for CodeSmith.  This script is really helpful in that regard.

Although preparation is needed, a paradigm shift is also needed as well.  Before control developers spent time rendering logic in the server-side, writing out the HTML to the browser in the Render method.  Now, with ASP.NET AJAX, it's beneficial to render that logic on the client-side instead.  This is because controls can take advantage of more features this way.

For instance, take the CheckBoxList control.  How advantageous would it be to be able to dynamically add/remove items to the list?  However, the logic exists on the server side to create the interface, and I ran into a problem this way because the client code had to know what the server is rendering in order for it to take advantage.

If all of the rendering was done on the client-side, this moves the logic to one place, which allows client-side refreshing of the interface (even rebinding from data streamed through a web service).  This really makes controls more advanced, but more cumbersome as client-side code becomes more verbose and difficult to manage.

Posted by bmains | with no comments
Filed under:

 Although the presentation is already done, the powerpoint and code samples I used for a presentation on AJAX custom controls and extenders is available on the Central Penn's .NET user group web site at: http://www.central-penn.net in the downloads section.

The Central Penn .NET user group is a group for the central Pennsylvania that discusses anything .NET related.  They have some good speakers at their presentations, so if you get a chance, come on out the third Tuesday of the month.  You may catch me presenting again sometime in the future; I am planning on speaking for their Code Camp the first Saturday of December.

Posted by bmains | with no comments
Filed under:

 If you've worked with the AutoCompleteExtender, you know that the extender returns a nice drop down list that only contains items with the keywords entered.  As you type more and more characters, the results in the list are filtered by the characters currently entered, until the item sought after is the only one left, or no items are left.

Personally, I had a really hard time getting this to work, and what I didn't realize is that the service requires a specific layout.  This layout should be in the form:

[WebMethod]
public string[] GetAutoCompleteResults(string prefixText, int count) { }

or, if using a context key:

[WebMethod]
public string[] GetAutoCompleteResults(string prefixText, int count, string contextKey) { }

Then the AutoCompleteExtender can hook up to the service and call this method, providing real-time filtered results.

Posted by bmains | with no comments
Filed under:

Connecting to web services in AJAX is really easy.  To setup a web service that can be called on the client, simply do the following.  Create a web service in Visual Studio; add one to the web project by right-clicking on the desired folder and selecting the new item option.  Select the web service option, which creates an ASMX.  This is the extension for a service in the web project.

Add the following attribute:  [System.Web.Scripts.Service.ScriptService]  When the project runs, a client proxy is generated automatically for you, one that matches your namespace/class name.  If you create a service in namespace Mains.Examples, with a service name of ArticleCodeGenerator, the following can be run on the client without having to write any code:

<script language="javascript">
    function pageLoad()
    {
        Mains.Examples.ArticleCodeGenerator.GenerateOutline(onSuccess, onFailure, $get('<%= lblTemplate.ClientID %>'));
    }

    function onSuccess(results, userContext, methodName)
    {
        //called when successful call to service
        //results equals the object returned from the service
        //userContext is the reference to the label
    }

    function onFailure(results, userContext, methodName)
    {
        //called when failure occurs
    }
</script>

<asp:Label id="lblTemplate" runat="server" />

The way the service method is setup is illustrated in the following:

ServiceMethod( <any parameters separated by commas>, <success callback>, <failed callback>, <context>);

The success or failed callback has to meet the template shown above; results contains a reference to the object returned from the method.  If an actual object and not a primitive type, the object is converted using JSON.  The context object can in reality be anything, and is often passed a value to the UI control (as shown in many examples).

Add a reference to the service to the script manager:

<asp:ScriptManager ...>
   <Services>
       <asp:ScriptReference Path="..path to ASMX" />
   </Services>
</asp:ScriptManager>

That's how easy it is to include web services into AJAX.  Even cooler is how Visual Studio provides intellisense for this object.

Posted by bmains | 2 comment(s)
Filed under: