Adding search capabilities to a task list should not be a difficult thing to do. To illustrate my point, I am going to describe how to build the following sample application using the new BQueryRuntime API.
After creating a new website in Visual Studio 2005, the first thing I’m going to do is add the following ASP.NET controls to the form tag within my web page.
<div style="margin: 6px 0px;">
<asp:dropdownlist id="columnsDropDownList" runat="server"/>
<asp:dropdownlist id="formatsDropDownList" runat="server"/>
<asp:textbox id="searchPattern" runat="server"/>
<asp:button id="searchButton" runat="server" text="Search"/>
</div>
These controls make up my search bar. I am going to use the DropDownList controls to display the columns and formats the user selects to match the pattern they enter into the TextBox control. The Button control is used to kick off the search.
Next, I am going to add a GridView control to display the results of my search. In order to limit which columns are being displayed to the user I need to do two things. The first is adding the "autogeneratecolumns" attribute, set to false, to the gridview tag. The second is adding a BoundField control for each column I want to display to the user.
<asp:gridview id="gridView" autogeneratecolumns="False"
emptydatatext="Sorry, no matching records were found." runat="server">
<columns>
<asp:boundfield datafield="ProcessName" headertext="Process" readonly="True"/>
<asp:boundfield datafield="TaskName" headertext="Task" readonly="True"/>
<asp:boundfield datafield="ResponsibleName" headertext="Responsible" readonly="True"/>
<asp:boundfield datafield="TaskStatus" headertext="Status" readonly="True"/>
<asp:boundfield datafield="TaskStartDate" headertext="Start Date"
htmlencode="False" dataformatstring="{0:d}" readonly="True"/>
<asp:boundfield datafield="TaskDueDate" headertext="Due Date"
htmlencode="False" dataformatstring="{0:d}" readonly="True"/>
</columns>
</asp:gridview>
Since I like things to look nice, even a sample application, I selected the professional scheme from the auto format option of the GridView control to pretty everything up a bit. You don't need to do this yourself, I just couldn't help myself.
Now that I have my controls in place, it's time to add some code. The first thing I'm interested in doing is adding the TaskList.ProcessName and the TaskList.TaskName columns to my first DropDownList control. Both columns implement the IStringColumn interface which in turn represents all of the operations you are able to do with a string column. For this sample the only operation I'm interested in is the Like() method, so I'm going to add an adapter that implements the following interface to my project. The key point to note here is that all column operations return an ICriteria object.
public interface IStringColumnAdapter
{
ICriteria Like(string pattern);
}
The code for the adapter is simple enough. The constructor takes a string representing the name to display in the DropDownList and an IStringColumn column representing either the TaskList.ProcessName or TaskList.TaskName column.
using Teamplate.BLL.Data;
public class StringColumnAdapter : IStringColumnAdapter
{
private readonly string displayName;
private readonly IStringColumn column;
public StringColumnAdapter(string displayName, IStringColumn column)
{
this.displayName = displayName;
this.column = column;
}
public override string ToString()
{
return displayName;
}
public ICriteria Like(string pattern)
{
return column.Like(pattern);
}
}
Next, I'm going to add a Columns property to the code behind of my web page whose only responsibility is to build and return a list of IStringColumnAdapter(s). I've decided to use the lazy load pattern here to insure the columns member variable only gets built once.
private IList<IStringColumnAdapter> columns;
private IList<IStringColumnAdapter> Columns
{
get
{
if (columns == null)
{
columns = new List<IStringColumnAdapter>();
columns.Add(new StringColumnAdapter("Process", TaskList.ProcessName));
columns.Add(new StringColumnAdapter("Task", TaskList.TaskName));
}
return columns;
}
}
Now let's turn our attention towards the second DropDownList control. It's purpose is to allow the user to define how the pattern in the TextBox control should be interpreted. Remember we will be calling the Like() method on the selected string column. The Like() method accepts a pattern that is used to find a match. If you only know the first part of the name you want to search for, you can append a percent sign (%) to your pattern. This would in turn cause the Like() method to match all names that start with your pattern. Since this is not intuitive to the average user, I have decided to hide the details in the second DropDownList control. Let's continue by adding the following interface to the project.
public interface IFormatter
{
string Format(params object[] args);
}
The Formatter class itself is easy to build. Again the constructor takes a string representing the name to display in the DropDownList and a second string that represents how we should format the pattern.
public class Formatter : IFormatter
{
private readonly string displayName;
private readonly string format;
public Formatter(string displayName, string format)
{
this.displayName = displayName;
this.format = format;
}
public override string ToString()
{
return displayName;
}
public string Format(params object[] args)
{
return string.Format(format, args);
}
}
Next we add the Formats property to the code behind of the web page. Note how we have essentially replaced the hard to remember format strings with user friendly text strings.
private IList<IFormatter> formats;
private IList<IFormatter> Formats
{
get
{
if (formats == null)
{
formats = new List<IFormatter>();
formats.Add(new Formatter("Starts with", "{0}%"));
formats.Add(new Formatter("Contains", "%{0}%"));
formats.Add(new Formatter("Ends with", "%{0}"));
}
return formats;
}
}
It's now time to hookup our event handlers, load the DropDownList controls and of course obtain a new session token.
private string sessionToken;
private int userId;
protected void Page_Load(object sender, EventArgs e)
{
BSession bSession = new BSession();
bSession.Connect(string.Empty, string.Empty, string.Empty);
sessionToken = bSession.GetToken();
userId = bSession.GetUserId();
LoadControls();
HookupEventHandlers();
}
private void HookupEventHandlers()
{
searchButton.Click += delegate { FindMatchingTaskRecords(); };
}
private void LoadControls()
{
if (!IsPostBack)
{
columnsDropDownList.DataSource = Columns;
columnsDropDownList.DataBind();
formatsDropDownList.DataSource = Formats;
formatsDropDownList.DataBind();
}
}
Finally, it's time to add our search method. This is where all the magic happens. FindMatchingTaskRecords is called when the user clicks on the Button control. All we need to do here is ask the BQueryRuntime for a new query object that we populate with shiny new criteria objects. I've decided to limit my result set to only those records that have a Ready or Waiting status for which I'm responsible for. The search criteria selected by the user is handled by the SearchCriteria property. Simply format the search pattern using the Formatter selected by the user and then pass it to the selected StringColumnAdapter to generate a new ICriteria object. Add the generated ICriteria object to the query and then pass it off to the BQueryRuntime to be executed. Lastly, bind the DataSet to the GridView control and we are done.
private ICriteria SearchCriteria
{
get
{
IStringColumnAdapter selectedColumn = Columns[columnsDropDownList.SelectedIndex];
IFormatter selectedFormat = Formats[formatsDropDownList.SelectedIndex];
return selectedColumn.Like(selectedFormat.Format(searchPattern.Text));
}
}
private void FindMatchingTaskRecords()
{
BQueryRuntime runtime = new BQueryRuntime();
runtime.SetSessionToken(sessionToken);
IQuery query = runtime.CreateQuery(QueryType.TaskList);
query.AddCriteria(
TaskList.TaskStatus.MatchAny(TaskStatus.Ready | TaskStatus.Waiting)
.And(TaskList.ResponsibleId.EqualTo(userId))
.And(SearchCriteria));
gridView.DataSource = runtime.ExecuteDataSet(query);
gridView.DataBind();
}
That's it. Feel free to download the complete project [ here ], or if you prefer, you can download a Windows version of the project [ here ]. If you like what you see, or even if you don't, I'm open to comments.