Friday, December 30, 2011

HTML ComboBox control (and ASP.NET MVC sample)

Introduction

Combo box control in desktop applications is different from normal <select> HTML element in ability to type in arbitrary text:

Recently I've been searching for an easy option of implementing the above UI functionality in cross-browser manner within ASP.NET MVC application. After googling for some time I didn't find any solution according my requirements. On many forums I came across link to jQuery UI sample combo box widget which demos how Autocomplete widget can be extended to address similar requirements
The concept is simple, the widget extends specific <select> element by hiding it, adding input element and button on it's place and setting up Autocomplete widget to use <select> options as data source.
Please note that combo box widget is not included into jQuery UI bundle (as opposed to Autocomplete) and after you add to your page jQuery and jQuery UI scripts you'll also have to add custom combo box code snippet (script + CSS) from the above page.
After trying out the widget I've ended up with following issues:
  1. The control lets type in text but is not supposed to submit any custom input value
  2. The text typed is being validated against available options and if no corresponding text is found in <option> then user input is reverted
  3. CSS provided has cross-browser rendering issue

When I played a bit with the widget I recevied custom solution which is based on jQuery UI Autocomplete and Combobox sample:

  1. Text different from <select> options can be submitted to the server
  2. CSS was fixed to work in various browsers (tested in FF9, Chrome, IE 6, 7, 8, 9)

HTML output for the widget looks the following way:

As you can see the extended <select> is hidden, and input with name {select.name}Custom is rendered next to it.

Using the control

In order to use the widget on your page your need to:

  1. References jQuery
  2. Reference jQuery UI with auto complete included
  3. Reference ComboboxWidget.js script
  4. Include ComboBox.cs styles

All the above files can be downloaded from here. Includes in ASP.NET MVC views may look as goes bellow:

<script type="text/javascript" src="<%=Url.Content("~/JavaScripts/jquery-1.7.1_min.js")%>"></script>
<script type="text/javascript" src="<%=Url.Content("~/JavaScripts/jquery-ui-1.8.16.custom.min.js"/>
<link rel="stylesheet" type="text/css" href="<%=Url.Content("~/Style/ComboBox.css")%>"/>
<script type="text/javascript" src="<%=Url.Content("~/JavaScripts/ComboboxWidget.js")%>"></script>

Assume we have the following view model which is used in strongly typed view:
public class SampleViewModel
{
public string SelectedId { get; set; }
public string SelectedIdCustom { get; set; }
public Dictionary SelectOptions { get; set; }

public bool IsCustomValue
{
get
{
return SelectedIdCustom != null &&
!SelectOptions.Values.Contains(SelectedIdCustom);
}
}
}

Rendering <select> element and binding the widget in the view is a follows:

<%= Html.DropDownListFor(m => m.SelectedId, new SelectList(Model.SelectOption, "Key", "Value"))%>

$(document).ready(function () {
$("#SelectedId").combobox();
});

If the above element is placed in a form for action "Save" the controller for processing combo box values looks the following way:

public ViewResult Save(SampleViewModel viewModel)
{
GetSelectOptions(viewModel); // Fetch DB values and fill in SelectOptions dictionary

if (viewModel.IsCustomValue) // Is user typed in custom text
{
SaveNewValue(viewModel.SelectedIdCustom); // Do some actions with the value
}
else // a user has selected one of existing options
{
SaveExistingValue(viewModel.SelectedIdCustom); // Do some actions
}

return View(viewModel);
}

Thursday, December 8, 2011

Selenium Web Driver animation synchronization

A simple test case:
  • A user clicks on a button
  • A pop-up slides down
  • The user clicks close button in the pop-up
  • The pop-op is sliding up and closes

That's a simple GUI use case which uses animation (gradual resizing). Normally when automating a pup-up via Selenium you'll click a button, after that check visibility of a DIV and then click the close button. The problem is that the browser won't handle any clicks until animation is over.

Using Thread.Sleep() or any similar execution delay is not a good technique (slow, hard to choose optimal value for both slow and fast environments etc.). Bellow you may find a C# extension method for Selenium IWebDriver interface which utilizes polling mechanism in order trace element style changes and decide whether animation is over:


public static void WaitForCssStyleChange(this IWebDriver webDriver, string xPath,
bool failOnNoCHange = true)
{
var i = 0; // poll counter
var cumulative = 0; // the number of CSS/style checks that didn't find any changes
var element = webDriver.FindElementEx(By.XPath(xPath));
var originalCss = element == null ? String.Empty : element.GetAttribute("class");
var originalStyle = element == null ? String.Empty : element.GetAttribute("style");
var prevCss = originalCss;
var prevStyle = originalStyle;

while (i < SeleniumConfiguration.PollingThreshold) // Do polls until threshold is exceeded
{
element = webDriver.FindElementEx(By.XPath(xPath));
if (element != null)
{
var css = element.GetAttribute("class");
var style = element.GetAttribute("style");

// if the previous CSS/Style is same - increase the counter
if ((css == prevCss || style == prevStyle)) cumulative++;
if (cumulative > 2) return; // most like animation is over

prevCss = css;
prevStyle = style;
}

Thread.Sleep(SeleniumConfiguration.PollingPeriod * 100);
i++;
}

if (element == null) throw new TimeoutException("Element not found "); // fail if no element
if (failOnNoCHange && (originalCss == prevCss) && (originalStyle == prevCss))
throw new TimeoutException("Element was not changed"); //require element style/CSS change
}

The approach is similar to a common AJAX synchronization solution via polling (where a test waits for some element to become present or visible) . The difference is that in our case we are trying not to register a single DOM change, but see when element's style is in progress of changing and when it stops. Style changes stopped - animation completed and the element is ready for use.

Worked for me in IE8, 9 and FF.

Thursday, November 3, 2011

TFS workitems filter: combine conditions

Just in case you have a blurry eye and can't find how to combine filter conditions with AND, OR and proper arrange priorities (brackets):




After many hours with JIRA and Assembla it took me some time to find the "Group clauses" function in TFS :)

Monday, July 18, 2011

CC.NET - NAnt integration problem

Today I was working on migration of our continuous integration system from old test server to a new one. A year ago the system was built upon:
1. CruiseControl.NET-1.5.7256.1
2. NAnt 0.90
3. Silk SVN 1.5.2
4. Windows 2003 x86

Today I downloaded and installed the latest versions of CC.NET (CruiseControl.NET-1.6.7981.1) and tried to set everything up and running on Windows 2008 R2 x64.

After I've finally made CC.NET dashboard working: it needs integrated mode for the app pool and updated Handler mappings for the application (by default I received all of them disabled) using 'Edit Feature Permissions -> Read, Script, Execute' option on the corresponding page - I've forced build and received the following message in the build log:
...
<LastModificationDate>17.07.2011 14:34:52</LastModificationDate>
</integrationProperties>
<build date="2011-07-18 14:34:45" buildtime="00:00:07" error="true"
buildcondition="ForceBuild">NAnt 0.90 (Build 0.90.3780.0; release; 08.05.2010)
Copyright (C) 2001-2010 Gerry Shaw
http://nant.sourceforge.net

Try 'nant -help' for more information

Duplicate value '
' for command-line argument '-target'.

</build>
</cruisecontrol>

By the way, NAnt was executed in the following way:

<nant>
<executable>$(nant.executable)</executable>
<buildFile>$(nant_build_file).build</buildFile>
<buildArgs>
-D:svn.exe="$(svn.executable)"
-D:svn.base.path="$(svn_base_path)"
-D:svn.path="$(svn_base_path)$(svn_project_path)"
-D:result.file.postfix="$(output_file_postfix)"
-D:output.dir="$(project_output_dir)"
-D:publish.dir="$(project_publish_dir)"
-D:MSBuild-2.0="$(msbuild_2.0.executable)"
-D:MSBuild-3.5="$(msbuild_3.5.executable)"
-D:MSBuild-4.0="$(msbuild_4.0.executable)"
$(nant_parameters)
</buildArgs>
<buildTimeoutSeconds>$(nant_time_out)</buildTimeoutSeconds>
</nant>

Playing with CC.NET config files, NAnt build script and googling for around 2 hours didn't end up with any result.

Finally I stopped CC.NET service, opened the installation directory and replaced all the contents with the files of CruiseControl.NET-1.5.7256.1 from old machine. No CC.NET configs or build files where changed after, I started the service and forced build via dashboard. NAnt execution went fine, a build was successfully published.

To summarize, seems like the latest CC.NET version has some integration issue with NAnt or there's some change to CC.NET command which is not reflcted in the on-line documentation.

Tuesday, May 31, 2011

DropDownCheckBoxes: Using control

In this post I'll give more details about the DropDownCheckBoxes control.
After you've referenced the assembly and registered the control within application you may add its' markup to .aspx pages (see introduction post about how to add the control to the project).

From development point of view programming the control is no way different from using standard CheckBoxList control. It has same events (in fact only one is of interest - OnSelectedIndexChanged) and members (except those relevant to new rendering and client side behavior).

Example to start with

Let's have a look at an example and comment it. Bellow is a markup snippet from .aspx file:

...
<asp:DropDownCheckBoxes ID="yearsDropDownCheckBoxes" runat="server"
OnSelectedIndexChanged="checkBoxes_SelcetedIndexChanged"
AddJQueryReference="True" UseButtons="True" UseSelectAllNode="True">
<Style SelectBoxWidth="160" DropDownBoxBoxWidth="160" DropDownBoxBoxHeight="80" />
<Texts SelectBoxCaption="Year" />
</asp:DropDownCheckBoxes>
...
<div style="padding-top: 20px;">
<h1>
Selected items (updated whenever postback from any of the controls occurs)
</h1>
<asp:Panel ID="selectedItemsPanel" runat="server">
</asp:Panel>
</div>

Code snippet from the corresponding code-behind file:

protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
var years = new int[20];
var currentYear = DateTime.Now.Year;

for (int i = 0; i < years.Length; i++)
years[i] = currentYear--;

yearsDropDownCheckBoxes.DataSource = years;
yearsDropDownCheckBoxes.DataBind();
}
}

protected void checkBoxes_SelcetedIndexChanged(object sender, EventArgs e)
{
selectedItemsPanel.Controls.Clear();

foreach (ListItem item in (sender as ListControl).Items)
{
if (item.Selected)
selectedItemsPanel.Controls.Add(
new Literal()
{ Text = item.Text + " : " + item.Value + newLineConst } // HTML line break
);
}
}

The page contains a drop down check boxes for selecting years and a panel bellow which displays checked items whenever a postback from the control occurs (code for other controls from the picture was cut above):


From the example we see that a control yearsDropDownCheckBoxes is added to a page, the event handler checkBoxes_SelcetedIndexChanged for SelcetedIndexChanged event is assigned and the page load handler performs data binding for the control. Whenever a user clicks 'OK' button in the drop down list a postback goes to server and panel in the bottom of the page is filled with all checked items. Everything is straightforward and no different from using controls inherited from ListControl(including CheckBoxList).

Postback options & behavior

Properties DropDownCheckBoxes.AutoPostBack (default value is 'false') and DropDownCheckBoxes.UseButtons (default value is 'true') control the way postbacks from the control arise:

1. AutoPostBack == false && UseButtons == false - in this case no 'OK/Cancel' buttons are displayed in the bottom of the drop down box, no postback occurs when a user clicks outside control and drop down gets hidden. The server side event will be fired (if there's a handler specified) during postback from any other control from the page (in case selection change is detected). The behavior in this case is similar to other ASP.NET controls with AutoPostBack set to false;

2. AutoPostBack == true && UseButtons == false - no action buttons ( 'OK/Cancel' ) are displayed, postback occurs whenever a user leaves the expanded drop down by clicking outside it.

3. UseButtons == true (AutoPostBack value doesn't matter) - in this case there're action buttons displayed in the bottom of the drop down box. Postback is triggered only when a user clicks 'OK' button. Clicking 'Cancel' or outside the control collapses the drop down and restores the check box selection prior to opening the control.

JQuery dependency

JQuery library is delivered within the control assembly and the reference to it is automatically added to the page by the control. In case you have JQuery referenced on the page (e.g. in a master page) script errors may occur.

In order to turn off automatic reference adding set DropDownCheckBoxes.AddJQueryReference to 'false'.

Other control properties

  • DropDownCheckBoxes.UseSelectAllNode - if 'true' then there's a 'Select all' check box added on top of other check boxes in the drop down. Clicking the check box changes all other check boxes' to corresponding state.
  • DropDownCheckBoxes.Texts - a complex property that allow to change default texts of the control elements (caption, button names, 'Select all' check box), e.g. for localization.
  • DropDownCheckBoxes.CssClass - specifies CSS class name applied to the control (specifically the DIV element which wraps all the contents). Setting this peoperty has the same effect as using DropDownCheckBoxes.Style.SelectBoxCssClass property.
  • DropDownCheckBoxes.Style - a complex property that allows applying CSS classes to the control and also directly specify sizes of the control elements (height of the select box, width and height of drop down box).

NOTE: The control can work within update panel without any adjustments.

The project page at Codeplex can be found here.

Monday, May 30, 2011

DropDownCheckBoxes: Introduction

DropDownCheckBoxes is a server control for ASP.NET web forms which provides the functionality of a standard CheckBoxList control in a form of a drop down list (the standard DropDownList control doesn't have any multi-select option). The key benefit of such approach is that it keeps space on a web page by dynamically displaying check boxes. The image bellow shows a sample use of the control:


About control


Browser compatibility: FireFox, Safari, Chrome, Opera (latest versions, May 2011), Internet Explorer 8.0

Server side: ASP.NET Web Forms 3.5, 4.0, can work with both synchronous postbacks and asynchronous when the control is placed within UpdatePanel control

The control is directly inherited from CheckBoxList control and thus can be used in place of it (easy migration is possible):
public class DropDownCheckBoxes : CheckBoxList, IPostBackEventHandler
{
...

The assembly containing control class also has embedded web resources used by the control which are automatically streamed to the browser:
  • Client side JavaScript
  • CSS file
  • Button images
  • jQuery script

Note: The jQuery JavaScript library is required for control proper functioning. The library is delivered within the assembly and is automatically referenced whenever the control is added to a page. To avoid duplicate references this behavior can be turned off by setting 'IncludeJqueryScript' property.

Getting started


The process of adding the control to project is absolutely the same as for other server controls.

1. In order to start using the control in you ASP.NET application get the control assembly from project's download page.

2. After you have DropDownCheckBoxes.dll downloaded you should add references to the assembly. Right click the web application project node in the solution explorer, choose 'Add reference' and in the dialog appeared find path to the downloaded .DLL file.


3. Register the control by adding web.config setting (if there's already <system.web><pages><controls> section - update it and don't create a duplicate):
<system.web>
<pages>
<controls>
<add tagPrefix="asp" namespace="Saplin.Controls" assembly="DropDownCheckBoxes"/>
</controls>
</pages>
</system.web>

4. Put the control to .aspx page
<asp:DropDownCheckBoxes ID="yearsDropDownCheckBoxes" runat="server">
</asp:DropDownCheckBoxes>

The project page at Codeplex can be found here.