Pages

Wednesday, March 21, 2012

How To Handle OnChange / SelectedIndexChanged Type Event In JavaScript For The AJAX ComboBox

If you have ever used the AJAX ComboBox control and needed to interact with it client side in JavaScript, you will notice it's a bit of a different animal than it's ASP.NET server control counterpart: the DropDownList. If you want to get either the selected text or its value from a traditional DropDownList, all you needed to do was add an JavaScript function call to the 'onchange' client side property on the control like below:
<asp:DropDownList ID="AspNetDDL1" runat="server" AppendDataBoundItems="true" 
onchange="dropDownListOnChange(this);">
<asp:ListItem Text="( Select a Color )" Value="0" />
<asp:ListItem Text="Red" Value="1" />
<asp:ListItem Text="White" Value="2" />
<asp:ListItem Text="Blue" Value="3" />
</asp:DropDownList>
The JavaScript method for getting the value from an ASP.NET DropDownList is just as trivial as shown below. It will fire the alert each time a valid selection is made.
function dropDownListOnChange(sender) {
if (sender.value = 1) {
alert('You selected Red!');
}
else if (sender.value = 2) {
alert('You selected White!');
}
else if (sender.value = 3) {
alert('You selected Blue!');
}
}
However the AJAX ComboBox is a bit different and not just some user control inheriting from the ASP.NET DropDownList with all of the same properties and events. The AJAX ComboBox is built from the ground up, and when rendered to the client is comprised of (4) main parts:

- A text box showing the selected item.
- A button to press to show the list (usually a down arrow)
- An unordered list with all of the items bound.
- A hidden field with the selected value.

Now the common misconception is to try and handle client side events on the textbox or the unordered list like 'blur', 'keydown', 'keyup', 'change', or 'mousemove'. The problem is none of the events used individually or collectively (jQuery allows binding to multiple events easy) work perfectly. It requires the user to tab off the field, move the mouse, or some other action before the JS function gets called. That's not ideal at all as the selection from the ComboBox might be the only control on a page, or the last one on a page, and the client doesn't do any of the actions required. If for example the result of your wired up JS function is to show/hide other controls, then you want the JS function to fire immediately without delay and as it should.

The answer lies in some 'hard to find' documented events that can have functions assigned to handle these events. Specifically for this topic we are speaking of the add_propertyChanged event handler. We need to wire up this handler to our AJAX ComboBox client side which will then allow us to get the selected item or value client-side without having to make an expensive server trip. The documentation on the event handler is below:

Sys.Component.propertyChanged Event

As a side note, always keep this advice in mind. If you are a web developer and especially an ASP.NET webforms developer, it's all too easy to get lazy and inefficient by doing everything server-side. Why? Because you can! And how do we masquerade expensive server trips? With AJAX UpdatePanels, yeah! Don't get me wrong, I really like asynchronous postbacks with UpdatePanels and use them often, but make sure to use them when you actually need to go server side to make a call. If you make a dropdownlist, radio button, checkbox, or other server control go server side just to show another hidden row, add a control, or change some visual state, then you are bloating the time it takes to run your code and making a bad choice but not handling things that can take place 100% client side on the client. With all the JS explosion recently with libraries like jQuery there is no reason not to handle these types of needs client side.

Back on the AJAX ComboBox, we need to assign a function to the add_propertyChanged event handler on our control. Remember to get the ClientID of the value for server controls, as you cannot just pass in the raw ID.
function AJAXComboBox1_Change() {
//Add event to ui_ddlStrategicObjective AJAX Combobox:
$find('<%=AJAXComboBox1.ClientID %>').add_propertyChanged(function (sender, e) {
if (e.get_propertyName() == 'selectedIndex') {
//Gets the selected index from the HiddenField associated with the AJAX Combobox control.
var selectedIndex = sender.get_hiddenFieldControl().value;
////Gets the selected text from the AJAX ComboBox
var selectedText = sender.get_textBoxControl().value;

//Just sample JS code - you could do anything here:
//show/hide controls, alerts, change client settings, etc.
if (sender.value = 1) {
alert('You selected Red!');
}
else if (sender.value = 2) {
alert('You selected White!');
}
else if (sender.value = 3) {
alert('You selected Blue!');
}
}
});

//AJAX's pageLoad function automatically provides a handler for the 'load' event
function pageLoad() {
//Make call to handle selection changes to the 'AJAXComboBox1' AJAX Combo Box
AJAXComboBox1_Change()
}
Take notice above how we call our new function 'AJAXComboBox1_Change' in pageLoad(). You cannot interact with a control until it has been initialized (i.e. you cannot call add_propertyChanged on a control unless the control actually exists). A control does not exist until the initialization phase of the ASP.NET AJAX framework is complete. We can use AJAX's 'pageLoad()' method as this script needs to be ran during postbacks and partial postbacks is using UpdatePanels. The pageLoad function automatically provides a handler for the 'load' event. You can read about AJAX Client Events in the link below.

ASP.NET AJAX Client Life-Cycle Events

In conclusion there are a few 'big picture' points to make about this entry. First and foremost, when doing web development, try and make your applications as efficient as possible by doing operations client side that don't need to be done on the server (even if they can be done that way).

Second, the AJAX ComboBox may not be a widely used control going forward because its main draw which is the JS support for typing in values and quickly selecting them has been superseded in my opinion by things like the jQuery AutoComplete extender. Dropdowns in general are probably a good choice for the 1-25ish item range, but anything more bloats the page size. In this case making async callbacks to get values real time (like with the jQuery autocomplete) is a better idea. However, it does not mean there aren't a ton of apps already using the AJAX ComboBox, and the need to interact with it client side may present itself someday. If this 'someday' comes up (which it usually does), you will now be prepared to capture its values in JavaScript.

Wednesday, March 14, 2012

Get A Silverlight Control's Current Instance For Communicating Via The HTML Bridge

If you have a SilverLight control on an ASP.NET webpage, odds are eventually you will need to communicate with it, and this is done via JavaScript and the HTML Bridge. However you might find that the accessing the control's current state after user manipulation is not as straight forward as the documentation from the MSDN indicates.

In the MSDN and most examples, the suggestion is made that you explicitly register an instance of a scriptable type (your control's class) in the App class or the Page class. However there is a big difference on these (2) and also in the exact instance that you choose to register.

If in my control the main class is named 'MySLControl', so I decide to register its type in the 'Application_Startup' event of App.xaml like below:

Dim _MySLControl As New Silverlight.Custom.MySLControl
HtmlPage.RegisterScriptableObject("SLControl", _MySLControl)
The above will work perfectly and you will then be able to access your exposed <ScriptableMember()> types from JavaScript. However, there is a catch - the registered instance is a New instance of MySLControl so it will not contain the state of the control when called by JS.

So let's say you have a custom built online MP3 player you built with a 'Playlist' created by the user within the control. You want to use the HTML Bridge from your hosting ASP.NET app to communicate with the control and get some details on the playlist. If you access the registered control instance as coded above, you will not have access to any of the control's state after the user has interacted with it. Why? We registered a New instance and are not using the actual control's instance.

The change is (2) fold: First, move the registration of the type to a late event in the Page (control) itself like a wired up 'ControlLoaded' event as typical for many Silverlight controls. Second, register the current instance of the control and not a new instance. The code is displayed below:

Private Sub ControlLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
'Register this control type so it can be accessible via JavaScript.
'This allows other platforms like ASP.NET to have a medium to communicate and raise events within the control.
'MUST use this instance or the current control state will not be accessible by the JS calling it via the HTML bridge.
HtmlPage.RegisterScriptableObject("SLControl", Me)
End Sub
The result? When you access your Silverlight control via JavaScript you will have access to the control in its current state including any interactions or manipulation done to the control by the client.