Thursday, May 18, 2006

Welcome

Welcome, and thank you for taking the time to visit.

Allow me to introduce myself - my name is Steven Berkovitz and I'm VP Development at MBC Development Ltd.

Over time MBC staff and I will be posting articles, technical information, how-to's, and most importantly, once proprietary code that we are making available to the open-source community.

ASP.NET Outlook-like Time Field

Another updated (and repost) of a Code Project article I authored




Introduction


The IT side of my company is almost completely service based, mostly involving on-site technicians. We have an internal e-Business portal that we use to track services rendered to the clients. The complaint I received most often from our technicians was how annoying it was to enter the time in and out for service calls. I was presenting three dropdowns for each time field: one for hour, minute and AM/PM. I had heard enough complaining, it was time to do something. Almost all of the time pickers I found on the net were either based on the same concept or a textbox with a masked input. I found these to be equally annoying as moving from field to field was sometimes cumbersome and input was often too restrictive.


One of my favorite Outlook features is the time picker used in appointments and tasks. It is a textbox that applies logic when it looses focus. It takes just about anything you can throw at it as input and computes a time in hh:mm tt format (i.e. 3:45 PM). I decided to duplicate this behavior and encompass it in an ASP.NET 2.0 web control. The standard TextBox control provided 99% of what I needed to accomplish so I derived my class from this one and started coding!


Background


It was decided that the control had to accept any of the following inputs:



  • 3:45 PM
  • 1:45a
  • 230a
  • 545p
  • 1843
  • 23
  • 21:30

It took me two attempts to get it right. My first attempt involved parsing the input for the location of the colon, reading each side into hours and minutes respectively and then searching for an 'a' or 'p' to indicate the time of the day. However, this proved to be troublesome when the input was not exactly as expected, like when a decimal was used instead, etc.


My final implementation handles this by splitting the text input into two components: a numeric component and a text component. I accomplish this with the following client-side JavaScript (complete JavaScript source code found in App_GlobalResources\OutlookTimeField.txt):


Client-side implementation

function UpdateTime(sender) { 
...
var numericPart = '';
var characterPart = '';
var i;
var current;

// Break the text input into numeric and
// character parts for easier parsing
for(i = 0; i < text.length; i++) {
current = text.charAt(i);

if(IsNumeric(current))
numericPart = numericPart + current;

if(IsCharacter(current))
characterPart = characterPart + current;
}
...
}

function IsNumeric(text) {
var validChars = '0123456789';
return (validChars.indexOf(text) > -1)
}

function IsCharacter(text) {
var validChars =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
return (validChars.indexOf(text) > -1)
}

After the text is split, figuring out what the user intended is relatively simple. First, if the characterPart contains an A or an a, treat it as AM (unless the hour is greater then 12), otherwise, default to PM. Second, depending on how many characters were entered in the numericPart, split the hours and minutes according to the following algorithm:

...
if(numericPart.length >= 4) {
hour = numericPart.substring(0, 2);
minute = numericPart.substring(2, 4);
} else if(numericPart.length == 3) {
hour = numericPart.substring(0, 1);
minute = numericPart.substring(1, 3);
} else if(numericPart.length == 2) {
hour = numericPart.substring(0, 2);
minute = '00';
} else if(numericPart.length == 1) {
hour = numericPart.substring(0, 1);
minute = '00';
} else {
// Just use the current hour
var d = new Date();
hour = d.getHours();

minute = '00';
}
...

Next, apply some 24-hour logic:

...
if(hour > 12) {
if(hour <= 24) {
hour -= 12;
dayPart = 'PM';
} else {
// If the hour is still > 12 then the
// user has inputed something that doesn't
// exist, so just use current hour

hour = (new Date()).getHours();

if(hour > 12) {
hour -= 12;
dayPart = 'PM';
} else {
dayPart = 'AM';
}
}
}

if(hour == 0) {
hour = 12;
dayPart = 'AM';
}
...

All that's left on the client-side is updating the sending textbox's value: sender.value = hour + ':' + minute + ' ' + dayPart;


Server-side implementation


There really isn't much to the server side code. First, override the Render method to add an onBlur event to the TextBox:

protected override void Render(HtmlTextWriter writer) {     
writer.AddAttribute(BLUR_ATTRIBUTE, "UpdateTime(this);");
base.Render(writer);
}

Second, override the OnPreRender method to inject the client-side script:

protected override void OnPreRender(EventArgs e) {
base.OnPreRender(e);
if (!Page.ClientScript.IsClientScriptBlockRegistered(this.GetType(),
SCRIPT_KEY)) {
Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
SCRIPT_KEY, Resources.ControlResources.OutlookTimeField, true);
}
}

And last, create a new property that returns a TimeSpan structure with the corresponding time:

public TimeSpan Time
{
get {
if (string.IsNullOrEmpty(Text))
return TimeSpan.Zero;
else
return TimeSpanHelper.GetTimeSpan(Text);
}
set { Text = TimeSpanHelper.ConvertTimeSpanToString(value); }
}

I won't go into the code that converts to and from the TimeSpan structure as it's relatively straightforward. You can find my implementation in App_Code\TimeSpanHelper.cs.


Using the code


Same as using any other web control, but for the sake of being thorough:

...
<%@ Register TagPrefix="mbc"
Namespace="Mbccs.WebControls" %>
...
<mbc:OutlookTimeField runat="server" ID="startTime"
AutoCompleteType="None" />
...

Points of interest



  • Set the AutoCompleteType property of the OutlookTimeField to None to prevent any browser annoyances.
  • This control is not localizable (only suitable for US/CAN)
  • I have not implemented this web control in a standalone DLL for simplicity and because there are tons of articles on doing this.
  • I am purposefully throwing a FormatException (this is not a bug) if you type in bogus data (that the control does not re-format) and attempt to post back. If you want to prevent this, you should:


    1. add a RegularExpressionValidator using the regular expression found in App_Code\TimeSpanHelper.cs or
    2. catch the exception and apply some custom logic - perhaps using a default date or the current date.

Pushing HTML content to a Blackberry

This is a re-post of an updated article I wrote for The Code Project


Blackberry showing pushed HTML content


Introduction


Blackberry's (developed by Research In Motion - RIM) have become a critical part of many corporate networks including IT, financial and government networks. Their always connected nature and rich content delivery services (through Blackberry Enterprise Server and Mobile Data Service) make them very inviting to developers to develop applications for. Out of the box, you can deliver HTML content to them with very little work. Their Java-based operating system allows for developers to create applications that deliver data in real time and can be interacted with using the devices intuitive user interface and full QWERTY keyboard.

I am going to concentrate on the most simplistic way to delivery content to the device using a HTML browser channel. My next article may include a custom application for the handheld.

In order to use this code you need


  1. A Blackberry Enterprise Server 4.0 or above
  2. A Blackberry handheld running the 4.0 operating system or above
  3. The IPPP service book installed (automatically installed through Enterprise Activation)
  4. IIS 5 or above to run the WebService I have created

How It Works


The Mobile Data Service is completely HTTP based. You establish an HTTP connection to your Blackberry Enterprise Server (default port is 8300) with some query parameters and POST containing your data. The handheld's browser listens on port 7874 for incoming pushes. Custom Blackberry HTTP headers specify information about the content and how to display it (including read and unread icons, title, etc). For simplicity sakes, I am going to let the handheld use its default icons.


Using the code


I decided to implement this as a WebService to allow for easy access from other internal applications. The code is pretty straight forward. There is one WebService method and one protected method that handles actually creating the HTTP request and firing it off to the MDS server. Configuration information about the MDS server is held in web.config and can be accessed using the built in ConfigurationSettings.AppSettings collection.


The WebService method has the following signature:

public virtual void PushToBrowser(string[] recipients,
string channelTitle,
string channelName,
string baseLocation,
string message)

To use, create a new application, add a web reference to it (I called mine BesUtils). The code below illustrates how you'd push a simple HTML document to a handheld

BesUtils.MdsPusher pusher = new BesUtils.MdsPusher();

string[] recip = {"steven@mbccs.com"};

try
{
pusher.PushToBrowser(recip, "Hello World", Guid.NewGuid().ToString(),
"", "<html><head></head><body>Hello World.</body></html>");
}
catch(Exception ex)
{
Console.Write(ex.ToString());
}

The image at the top of this article illustrates the icon and text that appears when a new message has been pushed to the handheld. You would click the icon to open the browser and it would take you directly to the document you pushed down.


Points of Interest


  • You need to keep channelName constant if you want the handheld to update the same channel, otherwise a new channel will be created each time you push to the handheld.
  • To remove channels, open your browser (on your handheld), and in the bookmarks section there is a new Node called "Browser Channels". You can remove the channels from here.