Telerik blogs

Introduction

My good friend Michael Crump showed how to create an Appointment Tracking app with the RadDatePicker and RadTimePicker in this excellent post. I’m going to start where Michael left off, adding additional business rules and logic by interacting with controls.

If you haven’t read his post, you can download the project from here. I will start from this project to add in the additional functionality.

Design Goals

Imagine that the business has approached the development team and is complaining that users are selecting departure dates that are prior to the arrival date, and there is a desire to make the scheduler more “user friendly”.  The additional requirements are as follows:

  • Provide better visual cues to guide the user
  • Format the dates Day MMM DD, YYYY
  • Prevent selecting Arrival Time prior to selecting Arrival Date
  • Prevent selecting Departure Time prior to selecting Departure Date
  • Provide for resetting all form fields
  • Prevent selecting Departure Date that is prior to Arrival Date
  • Prevent selecting Departure Date/Time that is prior to Arrival Date/Time

The new screen design is shown in Figure 1.

image
Figure 1 – Updated Screen Mockup

We also want to add a message box so that if the user enters bad data, they are warned prior to clearing the bad data, similar to Figure 2.

image
Figure 2 – Error message

Setting the Initial State

We can set the initial state for the controls through the data-win-options attributes (and this can be conveniently done through Blend) or through JavaScript.  In this case, since there is a requirement to reset all of the form fields, I’m going to create a method in JavaScript, but before we do that, we need to have references to the controls.

Start by opening up default.js.  First, add variables to hold refererences to all of the controls we need.  Next, add an app.onready function, and assign the winControls to the variables.  I also add a call t othe setup() function, which I will write shortly.  This is shown in Listing 1.

var appointmentName;
var arrivalDate;
var arrivalTime;
var departureDate;
var departureTime;
 
app.onready = function () {
    appointmentName = document.getElementById("appointmentInput");
    arrivalDate = document.getElementById('arrivalDateInput').winControl;
    arrivalTime = document.getElementById('arrivalTimeInput').winControl;
    departureDate = document.getElementById('departureDateInput').winControl;
    departureTime = document.getElementById('departureTimeInput').winControl;
    setup();
};
 
Listing 1

Configuring the Controls

To set the initial state of our controls, I am going to take advantage of the richness of the RadDatePicker and the RadTimePicker controls.

Setting the Watermark

Both the RadDatePicker and the RadTimePicker allow for customizing the watermark text by setting the “emptyContent” property. I create the setup() function and set the initial values for the watermarks in Listing 2.

function setup() {
    arrivalDate.emptyContent = 'Select arrival date';
    arrivalTime.emptyContent = 'Select arrival date first';
    departureDate.emptyContent = 'Select departure date';
    departureTime.emptyContent = 'Select departure date first';
};
Listing 2

NOTE: If not set, it will default to “Select Date” for the RadDatePicker and “Select Time” for the RadTimePicker.

Setting the Display Format

There are a lot of configuration options for setting the display format for dates and times. The RadControls take full advantage of the WinJS libraries and date and time formatting. 

Date Formatting

Some of the date formatting options are listed in the table below:

Date Format Example
{dayofweek.full} Friday
{dayofweek.abbreviated(3)} Fri
{month.full} January
{month.abbreviated(3)} Jan
{month.integer} 1
{month.integer(2)} 01
{date.integer} 8
{date.integer(2)} 08
{year.full} 2012
{year.abbreviated(2)} 12

I want to set the format so that 11/6/2012 would display as “Sat, Nov 06, 2012” (as shown in Figure 3. 

image
Figure 3

To set the proper date format we use the “displayValueFormat” property for both controls in our application, adding the lines shown in Listing 3 to the setup() method.

arrivalDate.displayValueFormat = '{dayofweek.abbreviated(3)} {month.abbreviated(3)} '
    + '‎{day.integer(2)}, ‎{year.full}';
departureDate.displayValueFormat = '{dayofweek.abbreviated(3)} {month.abbreviated(3)} '
    + '‎{day.integer(2)}, ‎{year.full}';
 
Listing 3
Time Formatting

There aren’t as many options for formatting how time displays, as can be expected.  If your machine is formatted to display time in 24 hour format, you can not display AM/PM at all.  Some of the available formats are display in the following table.

Time Format Example
{hour.integer} 9
{hour.integer(2)} 09
{minute.integer} 5
{minute.integer(2)} 05
{period.abbreviated} AM
{period.abbreviated(2)} A

The requirements don’t call for a specific formatting, and I prefer two digit times, so I set the displayValueFormat property in the setup() method for the time pickers as shown in Listing 4.

arrivalTime.displayValueFormat = '{hour.integer(2)}‎:‎{minute.integer(2)}';
departureTime.displayValueFormat = '{hour.integer(2)}‎:‎{minute.integer(2)}';
 
Listing 4

Preventing selecting times before selecting dates

Our next two requirements are to prevent selecting the Arrival Time or Departure Time prior to selecting the appropriate dates.  The  controls have two properties, “isreadOnly” and “enabled”.  Technically, setting “isReadOnly = true” and “enabled = false” would both work.  However, “enabled = false” also hides the control, giving a less than positive experience (as shown in Figure 4).

image
Figure 4

A better experience is to use “isReadOnly = true” so the controls don’t pop in and out.  As the watermarks explain “Select arrival date first” and “Select departure date first”, the users are informed of why they can’t select a time if a date hasn’t been selected, giving a much more pleasant experience.  Setting the controls to read only are the last two lines for the setup() method, and are shown in Listing 5.

arrivalTime.isReadOnly = true;
departureTime.isReadOnly = true;
 
Listing 5

Clearing the Controls

It is a common requirement to enable users to start over with data entry forms by providing a “Clear” button. It is also a good idea to have the users confirm that they really want to clear the form to prevent data loss due to accidental clicks.  WinJS provides the Flyout control for just this reason.  The user can click (or tap) on a button in the Flyout to execute the command, or click/tap anywhere else in the application to make the Flyout simply disappear.

Creating the Flyout Control in HTML

Flyout controls show standard HTML in essentially a message box.  This HTML will only be visible when the Flyout control is shown though JavaScript, so it doesn’t affect our page layout.  The div for the control and the HTML it contains is shown in Listing 6.

<div id="clearFlyout" 
 data-win-control="WinJS.UI.Flyout" 
 aria-label="Clear confirmation"> 
    Are you sure you want <br /> 
    to clear all controls? 
 <br /><br /> 
 <p align="center"> 
 <button id="confirmClearButton">Clear</button> 
 </p> 
</div> 
 
Listing 6

Showing the Flyout Control

To show the Flyout control we create another variable to hold the reference to the control (we’ll need this later to hide it again), and call show() from the click event of the “Clear” button.  The “show” command takes the DOM element that will anchor the location of the Flyout, as well as additional parameters to refine the positioning – placement and alignment respectively. 

var clearFlyout;
 
app.onready = function () {
 //content already covered not shown for brevity 
    clearFlyout = document.getElementById('clearFlyout').winControl;
 var clearCmd = document.getElementById('clearButton');
    clearCmd.addEventListener('click', function () {
 //Show the flyout based on the location of the clearCmd 
        clearFlyout.show(clearCmd,'top','center');
    });
};
 
Listing 7

Wiring up the Confirm Clear Button

Our final task for this requirement is to wire up the confirmation button contained in the Flyout to call a method that will clear the form values and call the setup() method when clicked.  Wiring up the confirmClearButton is added to the app.onready method and is shown in Listing 8.

//added to app.onready 
var confirmClearCommand = 
 document.getElementById('confirmClearButton');
confirmClearCommand.addEventListener('click', clearAllItems);
Listing 8

The final task that we need to do in the clearAllItems() method is to hide the Flyout.  The code is shown in Listing 9.

function clearAllItems(e) {
    appointmentName.value = null;
    arrivalDate.value = null;
    arrivalTime.value = null;
    departureDate.value = null;
    departureDate.minValue = null;
    departureTime.value = null;
 document.getElementById("appointmentOutput").innerText = "";
    clearFlyout.hide();
    setup();
};
 
Listing 9

Preventing Bad Data

The final two requirements are designed to prevent users from adding incorrect data.  When the Arrival Date changes, the code needs to:

  • Change the ArrivalTime to not be readonly and change the watermark,
  • Set the minValue for the Departure date to the Arrival Date,
  • Clear out the DepartureDate if it’s less than the Arrival Date,
  • Clear out the DepartureTime if the dates are the same and DepartureTime < ArrivalTime

When the Departure Date changes, the code needs to:

  • Change the DepartureTime to not be readonly and change the watermark

We can do this very simply because of the richness of the controls and their interactions through JavaScript.

Adding Change Event Listeners

Adding the event listeners is straight forward JavaScript added to the app.onready function.

arrivalDate.addEventListener('change', dateChange);
departureDate.addEventListener('change', dateChange);
 
Listing 10

The event handler simply checks the controls to make sure the rules have been properly followed.  The full code is shown in Listing 11. 

function dateChange(e) {
 if (e.target.element.id === arrivalDate.element.id) {
 if (arrivalDate.value !== null) {
            arrivalTime.isReadOnly = false;
            arrivalTime.emptyContent = 'Select arrival time';
            departureDate.minValue = arrivalDate.value;
 if (departureDate.value !== null && departureDate.value < arrivalDate.value) {
 var message = "Departure date must be greater than or equal arrival date. \r\n"
                                + "Departure date will be cleared.";
                showErrorMessage(message);
                clearDepartureDate();
            } else if (departureDate.value === arrivalDate.value &&
                arrivalTime.value !== null && departureTime.value !== null && 
                arrivalTime.value > departureTime.value) {
 var message = "Departure time must be equal to or later than arrival time. \r\n"
                                + "Departure time will be cleared.";
                showErrorMessage(message);
                departureTime.value = null;
            }
        }
    }
 else {
 if (e.target.value !== null) {
            departureTime.emptyContent = 'Select departure time';
            departureTime.isReadOnly = false;
        }
    }
};
function clearDepartureDate() {
    departureDate.value = null;
    departureTime.value = null;
    departureTime.isReadOnly = true;
    departureTime.emptyContent = 'Select departure date first';
}
function showErrorMessage(message) {
 var msg = new Windows.UI.Popups.MessageDialog(message);
    msg.showAsync();
};
Listing 11

The logic certainly isn’t production worthy, but the main item I want to show is how easily a control’s behavior can be changed based on the state of other controls on the page. In particular, as shown in Listing 12, when there is a valid date selected in the ArrivalDate RadDatePicker, I update the watermark as well as allow input into the ArrivalTime RadTimePicker, and set the minValue of the DepartureDate to the ArrivalDate.

if (arrivalDate.value !== null) {
    arrivalTime.isReadOnly = false;
    arrivalTime.emptyContent = 'Select arrival time';
    departureDate.minValue = arrivalDate.value;
};
Listing 12

Summary

In this post I showed you how easy it is to incorporate the RadDatePicker as the RadTimePicker into your application with business logic and validation.  With very few lines of code, your application can help guide your users down the path of success. 

Download the source code here.


Japikse
About the Author

Phil Japikse

is an international speaker, a Microsoft MVP, ASPInsider, INETA Community Champion, MCSD, CSM/ CSP, and a passionate member of the developer community. Phil has been working with .Net since the first betas, developing software for over 20 years, and heavily involved in the agile community since 2005. Phil also hosts the Hallway Conversations podcast (www.hallwayconversations.com) and serves as the Lead Director for the Cincinnati .Net User’s Group (http://www.cinnug.org). You can follow Phil on twitter via www.twitter.com/skimedic, or read his personal blog at www.skimedic.com/blog.

 

Comments

Comments are disabled in preview mode.