in Search
 
Home Blogs Forums Marketplace Files
 
 
 

Moving From One Task To The Next...

  • Search Your TaskList By Date

    In <my last post/> I explained how you might add a search bar to your TaskList. I'm going to follow up with another sample that demonstrates how to build a search bar that selects tasks by date. To make selecting a date for my sample easy to do, I've decided to use <The Coolest DHTML / JavaScript Calendar/> that I could find.

    Search By Date

    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 0;">
    <asp:dropdownlist id="columnsDropDownList" runat="server" />
    <asp:dropdownlist id="commandsDropDownList" runat="server" />
    <asp:textbox id="date" runat="server" />
    <script type="text/javascript">
    Calendar.setup({
    inputField : "date", // id of the input field
    ifFormat : "%m/%d/%Y", // format of the input field
    showsTime : false,
    singleClick : true
    });
    </script>
    <asp:button id="searchButton" runat="server" text="Search" />
    </div>

    I am going to use the DropDownList controls to display the columns and operators the user selects to match the date they pick from the popup calendar associated with the TextBox control. The Button control is used to kick off the search.

    To get the Calendar control to work, you need to add the following lines to the head tag.

    <script type="text/javascript" src="calendar/calendar.js"></script>
    <script type="text/javascript" src="calendar/calendar-setup.js"></script>
    <script type="text/javascript" src="calendar/lang/calendar-en.js"></script>
    <link rel="stylesheet" type="text/css" media="all" href="calendar/skins/aqua/theme.css" />

    Next, I'm going to add a GridView control to display the results of my search.

    <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>

    With the controls in place, it's time to start adding some code. The first thing I'm going to do is build a DateColumnDecorator class that implements the IDateColumn interface.

    using System;
    using Teamplate.BLL.Data;
    public class DateColumnDecorator : IDateColumn
    {
    private readonly string displayName;
    private readonly IDateColumn column;
    public DateColumnDecorator(string displayName, IDateColumn column)
    {
    this.displayName = displayName;
    this.column = column;
    }

    public override string ToString()
    {
    return displayName;
    }

    public string ColumnName
    {
    get { return column.ColumnName; }
    }
    public ICriteria GreaterThan(DateTime value)
    {
    return column.GreaterThan(value);
    }
    // more methods }

    The primary purpose of the decorator class is to wrap another IDateColumn instance in order to override the ToString method. By default the ToString method returns the value of the ColumnName property. Although useful, this is not what I want to display to the user in the DropDownList control. All of the other methods on the decorator simply delegate to the IDateColumn instance being wrapped.

    The DateColumnDecorator is put to work in the Columns property as seen below.

    private IList<IDateColumn> columns;
    private IList<IDateColumn> Columns
    {
    get
    {
    if (columns == null)
    {
    columns = new List<IDateColumn>();
    columns.Add(new DateColumnDecorator("Due Date", TaskList.TaskDueDate));
    columns.Add(new DateColumnDecorator("Start Date", TaskList.TaskStartDate));
    }
    return columns;
    }
    }

    My second DropDownList control allows the user to select an operator that describes how they want to compare against the selected date. The goal here is to translate several unique method signatures into one general purpose signature. The easiest way I could think of to do this was to introduce a command pattern.

    I'm going to start with the following abstract class whose primary responsibility is to handle the display name functionality.

    using System;
    using Teamplate.BLL.Data;

    public abstract class DateCommand
    {
    private readonly string displayName;

    public DateCommand(string displayName)
    {
    this.displayName = displayName;
    }

    public override string ToString()
    {
    return displayName;
    }

    public abstract ICriteria Execute(IDateColumn column, DateTime value);
    }

    For each operator that I want to appear in the DropDownList control, I am going to create a class that inherits from the abstract DateCommand class. Now all I need to do is override the Execute method and call the corresponding method on the IDateColumn instance being passed in to the Execute method.

    using System;
    using Teamplate.BLL.Data;

    public class GreaterThan : DateCommand
    {
    public GreaterThan() : base(">")
    {
    }

    public override ICriteria Execute(IDateColumn column, DateTime value)
    {
    return column.GreaterThan(value);
    }
    }

    This well-known, although often overlooked, technique is called double-dispatch.

    using System;
    using Teamplate.BLL.Data;

    public class GreaterThanOrEqualTo : DateCommand
    {
    public GreaterThanOrEqualTo() : base(">=")
    {
    }

    public override ICriteria Execute(IDateColumn column, DateTime value)
    {
    return column.GreaterThanOrEqualTo(value);
    }
    }

    The command objects adapt each call on a unique method signature of the IDateColumn instance into a general purpose method called Execute. When I add a Commands property to my code behind, all of my command objects are added to a list of type DateCommand. Nothing like a little polymorphism to spice things up.

    private IList<DateCommand> commands;
    private IList<DateCommand> Commands
    {
    get
    {
    if (commands == null)
    {
    commands = new List<DateCommand>();
    commands.Add(new GreaterThan());
    commands.Add(new GreaterThanOrEqualTo());
    commands.Add(new EqualTo());
    commands.Add(new LessThanOrEqualTo());
    commands.Add(new LessThan());
    }
    return commands;
    }
    }

    With everything in its place, 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();

    commandsDropDownList.DataSource = Commands;
    commandsDropDownList.DataBind();
    }
    }

    And finally, it's time to bring the new BQueryRuntime API into play. Pay attention to the SearchCriteria property as that's where all of the magic takes place.

    private ICriteria SearchCriteria
    {
    get
    {
    IDateColumn selectedColumn = Columns[columnsDropDownList.SelectedIndex];
    DateCommand selectedCommand = Commands[commandsDropDownList.SelectedIndex];

    return selectedCommand.Execute(selectedColumn, DateTime.Parse(date.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/>. If you like what you see, or even if you don't, I'm open to comments.

  • Adding Search To Your TaskList

    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.

    Search By Name

    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.

  • Building A Simple TaskList

    Until recently, building a customized task list involved having an intimate knowledge with our database schema. The BQueryRuntime API, new to Captaris Workflow 6.5, was designed to change all that by introducing a simple model that would allow you to develop a query without the need to poke around in our database tables.

    The BQueryRuntime class has only two responsibilities. The first is to create and return a query object and the second is to execute a query object and return the results in a DataSet. Both operations can be seen in the following sample:
    using System;
    using System.Data;
    using System.Web.UI;
    using Teamplate.BLL;
    using Teamplate.BLL.Data;

    namespace SimpleTaskList
    {
    public partial class _Default : Page
    {
    protected void Page_Load(object sender, EventArgs e)
    {
    BSession bSession = new BSession();
    bSession.Connect(string.Empty, string.Empty, string.Empty);

    gridView.DataSource = SelectAllTasks(bSession.GetToken());
    gridView.DataBind();
    }

    private DataSet SelectAllTasks(string sessionToken)
    {
    BQueryRuntime runtime = new BQueryRuntime();
    runtime.SetSessionToken(sessionToken);

    IQuery query = runtime.CreateQuery(QueryType.TaskList); return runtime.ExecuteDataSet(query); } } }

    The above sample is useful if you only have a few task records in your database. Since this is generally not the case, you need a way to limit the result set to only those records you are interested in. This is done by adding criteria to the query object. Criteria objects are created using the following syntax:

        ColumnName.Operator(valueToCompare)

    The task list columns themselves are made available via the TaskList column collection object as seen in the following example:
    using System;
    using System.Data;
    using System.Web.UI;
    using Teamplate.BLL;
    using Teamplate.BLL.Data;

    namespace SimpleTaskList
    {
    public partial class _Default : Page
    {
    protected void Page_Load(object sender, EventArgs e)
    {
    BSession bSession = new BSession();
    bSession.Connect(string.Empty, string.Empty, string.Empty);

    gridView.DataSource = SelectAllTasksFor(bSession.GetUserId(), bSession.GetToken());
    gridView.DataBind();
    }

    private DataSet SelectAllTasksFor(int userId, string sessionToken)
    {
    BQueryRuntime runtime = new BQueryRuntime();
    runtime.SetSessionToken(sessionToken);

    IQuery query = runtime.CreateQuery(QueryType.TaskList);
    query.AddCriteria(TaskList.TaskStatus.MatchAny(TaskStatus.Ready | TaskStatus.Waiting)); query.AddCriteria(TaskList.ResponsibleId.EqualTo(userId)); return runtime.ExecuteDataSet(query);
    }
    }
    }
    That's it. Simply ask the BQueryRuntime to create a query object, add some criteria objects to limit the result set, and then ask the BQueryRuntime to execute your query object and return you a nice new DataSet.

This Blog

Post Calendar

<December 2008>
SuMoTuWeThFrSa
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910

Post Categories

Syndication

  Privacy    Site Terms   Contact Administrator