|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionWhile building a web-based, enterprise wide system for a company spanning multiple continents, we frequently ran into localization-type issues that spanned the English language, let alone non-English languages. The most significant data problem we encountered within English-speaking countries was the entry of dates. The application initially accepted dates in textboxes, causing much confusion when the date was saved in the database. In the United States (US), the date 4/5/2003 is April 5th, 2003. In the United Kingdom (UK), this date is May 4th, 2003. To make matters worse, some UK users had US localization set on their computers, and some had UK localization set on their computers. There was no reliable method for determining the format of date the user really was using (mm/dd/yyy or dd/mm/yyyy) for data entry. The application displays dates in the format dd-Mon-yyyy which is unambiguous to the users. The users found typing that format clunky, and labels near the fields showing the proper format and addtional training of the users was of little or no help to the problem. Thus was born an idea for a date control based solely on list boxes. I chose to create a Composite Control over a User Control so I could deploy the control in more than one application simultaneously. BackgroundComposite controls must implement the The DateControl ClassPublic Properties
Public Events
To instantiate the control on a page, first you need to register it on the page: <%@ Register TagPrefix="custom" namespace="CustomControls"
assembly="YourDLLName"%>
Then in the HTML: <custom:datecontrol id="TheDate" runat="server" selectcurrentdate="true">
</custom:datecontrol>
How the Date Control WorksThe control consisits of three child controls but exposes only one value which is the combination of all three children. The control is pretty straightforward in the public string Value
{
get
{
string s = (string)ViewState["Value"];
if(s == null)
return String.Empty;
else
return s;
}
set
{
this.SetSelected(value);
ViewState["Value"] = value;
}
}
private void SetSelected(string When)
{
string[] ResultList;
Regex DateSplitter = new Regex("^\\d{1,2}\\/\\d{1,2}\\/\\d{4}$");
if(DateSplitter.IsMatch(When)) //The date has a good format
{
char divider = '/';
ResultList = When.Split(divider);
SelectedMonth = Int32.Parse(ResultList[0]);
SelectedDay = Int32.Parse(ResultList[1]);
SelectedYear = Int32.Parse(ResultList[2]);
}
else //When must be empty or not recognizable,
//so set the listboxes to the empty state
{
SelectedDay = -1;
SelectedMonth = -1;
SelectedYear = -1;
}
}
The control implements bool IPostBackDataHandler.LoadPostData(string postDataKey,
NameValueCollection postCollection)
{
int Day = 0;
int Month = 0;
int Year = 0;
bool Changed = false;
string MonthName;
if(postCollection[this.UniqueID.ToString() + ":Day"] != "")
{
Day = Int32.Parse(postCollection[this.UniqueID.ToString() +
":Day"]);
if (Day != this.SelectedDay)
{
this.SelectedDay = Day;
Changed = true;
}
}
else
{
if(this.SelectedDay != -1)
{
this.SelectedDay = -1;
Changed = true;
}
}
MonthName = postCollection[this.UniqueID.ToString() + ":Month"];
if (MonthName != "")
{
Month = Array.IndexOf(this.MonthArrayLong, MonthName);
if(Month != this.SelectedMonth)
{
this.SelectedMonth = Month;
Changed = true;
}
}
else
{
if(this.SelectedMonth != -1)
{
this.SelectedMonth = -1;
Changed = true;
}
}
if(postCollection[this.UniqueID.ToString() + ":Year"] != "")
{
Year = Int32.Parse(postCollection[this.UniqueID.ToString()
+ ":Year"]);
if (Year != this.SelectedYear)
{
this.SelectedYear = Year;
Changed = true;
}
}
else
{
if(this.SelectedYear != -1)
{
this.SelectedYear = -1;
Changed = true;
}
}
if(this.SelectedDay == -1 && this.SelectedMonth == -1 &&
this.SelectedYear == -1)
this.Value = "";
else
{
string NewDate = this.SelectedDay.ToString() + "/";
NewDate += this.SelectedMonth.ToString() + "/";
NewDate += this.SelectedYear.ToString();
this.Value = NewDate;
}
return Changed;
}
Next Step: ValidationAs you may have noticed by now, the control will return values like "4/-1/1975" or "-1/-1/2003". It will never return -1/-1/-1, as that is recognized and being empty and returns an empty string instead. Database date fields are notoriously picky, and will not accept a partial date like 4/2003 even though we use them in common speech and writing. The individual values set at -1 allow for effective validation of the control and contain values unacceptable to a database date field so that bad data cannot inadvertently enter the database. Instead of rolling the validation into the control, I created a validator to go along with the date control. Validation could occur via a custom validator, but I liked my own better for deploying the client script. The DateControlValidatorPublic Properties
The custom-written validator was much more of a challenge than the control. It works seamlessly with the Microsoft validators and behaves as they do with regard to UpLevel and DownLevel browsers and the
One error message cannot provide enough information for the user to differentiate between these problems and resolve the error condition easily. A custom-written validator must override protected override void AddAttributesToRender(HtmlTextWriter writer)
{
if(this.DetermineRenderUplevel() && this.EnableClientScript)
{
base.AddAttributesToRender(writer);
writer.AddAttribute("controltovalidate", this.ControlToValidate);
writer.AddAttribute("evaluationfunction", "DateControlIsValid");
writer.AddAttribute("display", this.Display.ToString());
writer.AddAttribute("style", "display:none");
writer.AddAttribute("errormessage", this.ErrorMessage);
if(ControlRequired)
writer.AddAttribute("required", "true");
else
writer.AddAttribute("required", "false");
}
}
The protected override void OnPreRender(EventArgs e)
{
if(this.DetermineRenderUplevel() && this.EnableClientScript)
{
string EOL = Environment.NewLine;
StringBuilder script = new StringBuilder();
//Make sure the MS standard javascript file is included
this.RegisterValidatorCommonScript();
//Include our own file for the control
script.Append("<script language="'javascript'" " +
"src='../include/date_control.js'></script>" + EOL);
Page.RegisterClientScriptBlock("DateValidate", script.ToString());
//Register this validator in the array of validators
//on the page, and the array of
//date controls on the page
string element = "document.all[\"" + ClientID + "\"]";
Page.RegisterArrayDeclaration("Date_Controls", element);
Page.RegisterArrayDeclaration("Page_Validators", element);
//Because the date control is a composite control,
//there is no single HTML control to
//bind the evaluation function to. Therefore,
//the MS javascript disables this validator.
//The client script below re-enables the validator when
//the page is submitted.
script.Remove(0, script.Length - 1);
script.Append("<script language="'javascript'">");
//This is the "Microsoft Preferred" method for enabling
//a control, but it causes the
//control to validate, causing an immediate
//error condition when the field is required.
// script.Append(sVbCrLf)
// script.Append("ValidatorEnable(" & ClientID & ", true);")
// Instead I am setting the enabled property directly
// of all registered date controls.
// the EOLs are for human readablity only
script.Append(EOL);
script.Append("var x;");
script.Append(EOL);
script.Append("for (x = 0; x < Date_Controls.length; x++)");
script.Append(EOL);
script.Append("Date_Controls[x].enabled = true;");
script.Append(EOL);
script.Append("</script>");
script.Append(EOL);
Page.RegisterStartupScript("SetEnabled", script.ToString());
}
}
Future ImprovementsThe
A VB.Net version of the
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||