Book Image

YUI 2.8: Learning the Library

Book Image

YUI 2.8: Learning the Library

Overview of this book

The YUI Library is a set of utilities and controls written in JavaScript for building Rich Internet Applications, across all major browsers and independently of any server technology. There's a lot of functionality baked into YUI, but getting to and understanding that functionality is not for the faint of heart. This book gives you a clear picture of YUI through a step-by-step approach, packed with lots of examples.YUI 2.8: Learning the Library covers all released (non-beta) components of the YUI 2.8 Library in detail with plenty of working examples, looking at the classes that make up each component and the properties and methods that can be used. It includes a series of practical examples to reinforce how each component should/can be used, showing its use to create complex, fully featured, cross-browser, Web 2.0 user interfaces. It has been updated from its first edition with the addition of several chapters covering several new controls and enriched with lots of experience of using them.You will learn to create a number of powerful JavaScript controls that can be used straightaway in your own applications. Besides giving you a deep understanding of the YUI library, this book will expand your knowledge of object-oriented JavaScript programming, as well as strengthen your understanding of the DOM and CSS. The final chapter describes many of the tools available to assist you the developer in debugging, maintaining, and ensuring the best quality in your code. In this new edition, all the examples have been updated to use the most recent coding practices and style and new ones added to cover newer components. Since the basic documentation for the library is available online, the focus is on providing insight and experience.The authors take the reader from beginner to advanced-level YUI usage and understanding.
Table of Contents (18 chapters)
YUI 2.8 Learning the Library
Credits
About the Authors
About the Reviewers
Preface

Implementing a Calendar


To complete this example, the only tool other than the YUI that you'll need is a basic text editor. Native support for the YUI is provided by some web authoring software packages, most notably Aptana, an open source application that has been dubbed "Dreamweaver Killer". However, I always find that writing code manually while learning something is much more beneficial.

It is very quick and easy to add the Calendar, as the basic default implementation requires very little configuration. It can be especially useful in forms where the visitor must enter a date. Checking that a date has been entered correctly and in the correct format takes valuable processing time, but using the YUI Calendar means that dates are always exactly as you expect them to be.

So far we've spent most of this chapter looking at a lot of the theoretical issues surrounding the library; I don't know about you, but I think it's definitely time to get on with some actual coding!

The initial HTML page

Our first example page contains a simple text field and an image that once clicked will display the Calendar control on the page, thereby allowing a date to be selected and added to the input. Begin with the following basic HTML page:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>YUI Calendar Control Example</title>
<script type="text/javascript"
src="yui/build/yahoo-dom-event/yahoo-dom-event.js">
</script>
<script type="text/javascript"
src="yui/build/calendar/calendar-min.js">
</script>
<link rel="stylesheet" type="text/css"
href="yui/build/calendar/assets/skins/sam/calendar.css">
<style type="text/css">
input { margin:0px 10px 0px 10px;}
</style>
</head>
<body class="yui-skin-sam">
<div>
<label>Please enter your date of birth:</label>
<input type="text" name="dobfield" id="dobfield">
<img id="calico" src="icons/cal.png"
alt="Open the Calendar control">
</div>
<div id="mycal"></div>
</body>
</html>

We begin with a valid DOCTYPE declaration, a must in any web page. A question that often pops up in the YUI forums is why HTML4 Strict? Experience has shown that it is the one more reliably implemented across all browsers. Support for XHTML is uneven across browsers and HTML5 is not yet a standard. Let us call HTML4 Strict the A-grade supported doc type; others might just work but are not tested and some are positively known not to work. Remember, Yahoo! collects an unbeatable quantity of statistics and this doc type is the one that they have found that provides the best and most consistent user experience to their millions of visitors per day.

For validity, we can also add the lang attribute to the opening <html> tag and for good measure, enforce the utf-8 character set. Nothing so far is YUI-specific, but coding in this way every time is a good habit.

We link to the stylesheet used to control the appearance of the Calendar Control, which is handled in this example by the sam skin within the <link> tag. Accordingly, we also need to add the appropriate class name to the <body> tag.

Following this, we link to the required library files with <script> tags; the Calendar Control is relatively simple and requires just the YAHOO, Dom, and Event utilities (using the aggregated yahoo-dom-event.js file for efficiency), as well as the component source file calendar-min.js.

A brief <style> tag finishes the <head> section of the page with some CSS relevant to this particular example, and the <body> of the page at this stage contains just two <div> elements: the first holds a <label>, the text field, and a calendar icon (which can be used to launch the control), while the second holds the calendar control. When viewed in a browser, the page at this point should appear like this:

Note

The calendar icon used in this example was taken, with gratitude from Mark Carson at http://markcarson.com.

We need to create a <script> tag to contain our code. As an HTML page loads many of its components asynchronously, we cannot be sure what HTML elements get loaded when and when they get appended to the DOM. In this case, we cannot be sure the calendar icon will be there by the time the script starts executing so right before the closing </body> tag, add the following code:

<script type="text/javascript">
YAHOO.util.Event.onDOMReady(function () {
var Dom = YAHOO.util.Dom, Event = YAHOO.util.Event, Lang = YAHOO.Lang;
});
</script>

We first call the .onDOMReady() method of the Event Utility, which will call the function that is supplied as its argument when the DOM is ready to be used. We could even be more specific by using the method .onAvailable() to ask for our calendar icon but we would gain little by doing that.

Instead of supplying .onDOMReady() with a reference to a function to call, we are defining the function right on the spot. This is part of the flexibility that JavaScript has built-in: you can define a function anywhere you need it and you don't even need to give it a name. An anonymous function has a further advantage. We have already mentioned that the whole of the YUI Library takes a single name in the Global Namespace: YAHOO, which is a good thing because this namespace gets easily cluttered with variables and functions and what not. You might think that you control what goes into the Global Namespace. That is not the case.

All the members of both window and document have aliases in the Global Namespace. The properties window.location, document.location, window.document.location and plain location all point to the very same property. The property window.name can simply be called name; many programmers using the variable name don't even realize they are using a DOM property. Proprietary extensions in different browsers add extra members in this namespace. Worried enough already?

At some point someone decides to make some extra money by adding some paid banners, or provide directions by embedding some active map into the page and it will include some code. Some of this code is sloppy and further pollutes the Global Namespace.

The Global Namespace is not a comfortable place to be in.

An anonymous function takes no name at all in any namespace. We leave no litter behind; we get no pollution from outside.

Let's go back to the earlier code. Within that anonymous function we are declaring a set of three variables: Dom, Event, and Lang. We call these "shortcuts". They point to the most frequently used classes in the core utilities. Having the whole YUI stored under the single global name YAHOO has a cost: all its members have long names, all starting with YAHOO. We don't want to type those long names all the time so we create aliases for those we use more frequently. We might not use them all; in this example we won't use Lang. A tool we will see in the last chapter, JSLint, will tell us which variables went unused so we may well have this set of shortcuts as a template and let JSLint tell us if we overdid it. We may add additional shortcuts as needed. These shortcuts are not copies of the classes but additional references to the very same class; they don't waste time or memory, in fact, in the end they save on both.

The anonymous function provides another benefit, it creates a sandbox. Every variable or function declared in it will be local to that function and accessible to all functions within. The shortcuts we have created are visible to all the code contained in the sandbox, we can use them freely within, but are completely invisible outside the sandbox because they are local to the anonymous function. That is why we had to spell out the whole YAHOO.util.Event.onDOMReady outside the sandbox, because the Event shortcut is not visible there (and has not been executed yet, but we would not be able to even if it had been). If we declare a variable called name within the sandbox, we would not be using window.name accidentally, we would still be able to access that one by its full name if we need to, but if we refer to name it would be our very own variable.

The use of sandboxes has become the standard in YUI programming. In fact, YUI3 uses sandboxing extensively. The other alternative, creating your own namespace, which was the recommended way not long ago, is no longer used for most application code. Formerly you would piggyback your code in a branch under the YAHOO global variable. Most examples packed with the YUI library use the YAHOO.example namespace. The examples in the first edition of this book used YAHOO.yuibook. You did not take any space in the Global Namespace; you simply used some space you knew the YUI library did not use.

Creating your own namespace is still done for libraries. As anything created within the sandbox cannot be seen outside of it, you can't create libraries in sandboxes because they cannot be seen elsewhere. For application code, which is not called from anywhere outside of the sandbox itself, keeping everything hidden within is perfectly fine.

Now we can add the extremely brief code that's required to actually produce the calendar. Within the braces of our anonymous function, add the following code:

//create the calendar object, specifying the container
var myCal = new YAHOO.widget.Calendar("mycal");
//draw the calendar on screen
myCal.render();
//hide it again straight away
myCal.hide();

This is all that we need to create the Calendar; we simply define myCal as a new Calendar object, specifying the ID of the HTML element that will hold it as an argument of the constructor.

Once we have a Calendar object, we can call the .render() method on it to create the calendar and display it on the page. No arguments are required for this method. As we want the calendar to be displayed when its icon is clicked, we hide the calendar from view straight away.

To display the calendar when the icon for it is clicked, we'll need one more function. Add the following code beneath the .hide() method:

//define the showCal function which shows the calendar
var showCal = function() {
//show the calendar
myCal.show();
}

As we've said, JavaScript allows us to create functions anywhere. This function is contained within the anonymous function that is our sandbox; a function within a function. We can do this any number of levels deep. An inner function can access all the local variables of the functions it is contained in; thus we can refer to myCal from inside showCal.

Functions can be stored in variables; in fact, naming a function is nothing more than saying in what variable the function will be stored. Here we are storing the function that shows the calendar in a variable called showCal, effectively declaring a function named showCal. We prefer this notation to highlight where it is being stored.

The keyword var in front of the variable name is vital. JavaScript allows us to use variables without declaring them previously. Variables used without being declared are created automatically in the Global Namespace. We don't want this to happen. We want showCal to be in the sandbox, that is why we declare the variable showCal with the var keyword and initialize it to a function. But don't worry, if we forget the var keyword, the JSLint utility will warn us.

To have the showCal function executed when the icon for it is clicked, we'll need to listen for the click event. We can use method YAHOO.util.Event.addListener() but we already have a shortcut called Event and the YUI also gives us a shorter alias for .addListener() so we can add the following code beneath the .hide() method:

Event.on("calico", "click", showCal);

Save the file that we've just created as calendar.html or similar in your yuisite directory. If you view it in your browser now and click on the Calendar icon, you should see this:

The calendar is automatically configured to display the current date, although this is something that can be changed using the configuration object mentioned earlier.

Now that we can call up the Calendar Control by clicking on our Calendar icon, we need to customize it slightly. Unless the person completing the form is very young, they will need to navigate through a large number of calendar pages in order to find their date of birth. This is where the Calendar Navigator interface comes into play.

We can easily enable this feature using a configuration object passed into the Calendar constructor. Alter your code so that it appears as follows:

//create the calendar object, using container & config object
var myCal = new YAHOO.widget.Calendar("mycal", {navigator:true});

Clicking on the Month or Year label will now open an interface that allows your visitors to navigate directly to any given month and year:

The configuration object can be used to set a range of calendar configuration properties including the original month and year displayed by the Calendar, the minimum and maximum dates available to the calendar, a title for the calendar, a close button, and various other properties.

Let's update our Calendar instance so that it features a title and a close button. Add the following properties to the literal object in our constructor:

//create the calendar object, specifying the container and a literal
//configuration object
var myCal = new YAHOO.widget.Calendar("mycal", {navigator:true, title:"Choose your Date Of Birth", close:true});

This is what our Calendar should now look like:

Configuration properties like those we have just set can also be set outside of the constructor by using the .setProperty() method. Let's use these to alter our Calendar so that the first column header is set to Monday instead of Sunday. Add the following code directly before the call to the .render() method:

//configure the calendar to begin on Monday
myCal.cfg.setProperty("start_weekday", "1");

When the calendar is displayed now, Monday will be the first day instead of Sunday:

Finally, we need to add some additional code that will allow the date that is selected to be inserted into the text field. We can do this using some of the custom events defined by the calendar classes.

Highly eventful

Both the Calendar and CalendarGroup classes have a series of custom events defined for them that allow for easily listening and reacting to interesting moments during any calendar or calendar group interaction.

The two classes both have the same set of events defined for them, which include:

  • beforeDeselectEvent: Fired before a cell is deselected

  • beforeHideEvent: Fired just before the calendar is hidden

  • beforeHideNavEvent: Fired just before the calendar navigator is hidden

  • beforeRenderEvent: Fired before the calendar is drawn on screen

  • beforeSelectEvent: Fired before a cell is selected

  • beforeShowEvent: Fired just before the calendar is shown

  • beforeShowNavEvent: Fired just before the calendar navigator is shown

  • changePageEvent: Fired once the current calendar page has been changed

  • clearEvent: Fired once the calendar has been cleared

  • deselectEvent: Fired once the cell has been deselected

  • hideEvent: Fired once the calendar has been hidden

  • hideNavEvent: Fired once the calendar navigator has been hidden

  • renderEvent: Fired once the calendar has been drawn on screen

  • resetEvent: Fired once the calendar has been reset

  • selectEvent: Fired once a cell, or range of cells, has been selected

  • showEvent: Fired once the calendar has been shown

  • showNavEvent: Fired once the calendar navigator has been shown

This rich event system allows you to easily watch for cells being selected or deselected, month panel changes, render events, or even the reset method being called, and add code to deal with these key moments effectively. As you can see, most of the events form pairs of before and after events, which allows you to easily cancel or abort an operation before it has any visual impact by returning false in the before event.

Let's now take a look at how these custom Calendar events can be used. First define the function that will handle the select event; add the following code directly after the showCall() function:

//attach listener for click event on calendar icon
Event.on("calico", "click", showCal);
//define the ripDate function which gets the selected date
var ripDate = function(type, args) {
}
//subscribe to the select event on Calendar cells
myCal.selectEvent.subscribe(ripDate);

Every time the select event is detected, our ripDate function will be executed. The type and args objects are automatically provided to us by the control; the args object is what we are interested in here, because it gives us easy access to an array of information about our Calendar.

Now, within the curly braces of the ripDate() function set the following variables:

//get the date components
var dates = args[0],
date = dates[0],
theYear = date[0],
theMonth = date[1],
theDay = date[2];

The first item in the args array is an array of selected dates, so we first save this to the variable dates. As this is a single-select calendar, only the first item of the dates array will contain data, so this is also saved to a variable: the date variable.

Each date is itself an array, with the first item corresponding to the year, the second item equaling the month, and the third item mapped to the individual date. All of these values are saved into variables.

var theDate = theMonth + "/" + theDay + "/" + theYear;

This part of the function uses standard concatenation techniques to build a string containing the individual date components in the format in which we want to present them (so that, for example, it would be extremely easy to express dates in UK format, where the date appears before the month):

//get a reference to the text field
var field = Dom.get("dobfield");
//insert the formatted date into the text field
field.value = theDate;
//hide the calendar once more
myCal.hide();

Finally, we use the very handy Dom utility's .get() method to grab a reference to the text field, set the value of the text field to our date string, and then hide the calendar once more.

Save the file once more and view it again in your browser of choice. After clicking the calendar icon and choosing a date, it should be displayed in the text field:

At this point, we can take a brief look at how we can override the default styling of the calendar. When we added the calendar navigator there was no visible clue for the user to show that the month and year could be clicked. When the cursor hovers over them, they change, but the user might never notice that. We might want to correct that so that the month and year have the same white background as the navigation arrows. This can be done with the following simple CSS rule, which should be inserted into the <style> tag in the <head> of our document:

.yui-skin-sam .yui-calendar a.calnav {
border: thin solid silver;
background: white;
}

Because we're using the default sam skin, we should begin the selector with the yui-skin-sam class name then the class name of the calendar container and finally that of the month and year. Other elements of the calendar, such as the navigation arrows, can easily be styled in this way. Using a DOM explorer to expose the names of other parts of the calendar is also an easy way to change other elements of the calendar. Our calendar should now appear like this:

Note

We should never change the original YUI files.

Changing the YUI files may seem harmless at first, but when a new version is released we find ourselves desperately trying to locate the changes we made in the old version to apply them to the new one, or we have to forgo updating. Besides, we preclude ourselves from using the CDNs to load the library files. There is always a way to override—as we just did—redefine or subclass the original CSS styles or JavaScript objects in our own, separate application code.

The DateMath class

In addition to the two classes catering for two different types of calendar, a class, YAHOO.widget.DateMath, defines a series of utilities for performing simple mathematical calculations or comparisons on dates. It has only a small number of static properties and a small set of methods. There are no events defined in this class and no configuration attributes. It is very much like JavaScript's own Math class that you can use without creating an instance.

All of its methods return either a Boolean value indicating whether the comparison was true or false, or a modified date object. Some of them will be used very frequently, while others will be used only rarely (but are still very useful).

Our date of birth calendar isn't really appropriate for seeing how the DateMath calls can be used. In order to examine some of the available methods, we should create a new calendar. In a blank page of your text editor, begin with the following HTML:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>YUI MathDate Class Example</title>
<link rel="stylesheet"
type="text/css"
href="yui/build/calendar/assets/skins/sam/calendar.css">
<script type="text/javascript"
src="yui/build/yahoo-dom-event/yahoo-dom-event.js">
</script>
<script type="text/javascript"
src="yui/build/calendar/calendar-min.js">
</script>
</head>
<body class="yui-skin-sam">
<div id="mycal"></div>
<div id="results"></div>
</body>
</html>

This very simple page will form the basis of our example. It looks very much like the previous example; in fact, all our examples will look pretty much the same, so, from now on, we'll only point out the components we'll need to include—and we already know the many alternative ways of fetching them—the HTML that goes in the body and the script that always goes right before the closing </body> tag.

Formerly, the DateMath class was only available along the Calendar Control, whether you meant to show a calendar or not. Now, it can be loaded separately, but we will use the Calendar in this example so we load the bundle.

Next, add the following <script> tag to the <body> tag of the page, directly below the results <div> tag:

<script type="text/javascript">
// create the sandbox when the DOM is ready
YAHOO.util.Event.onDOMReady(function() {
// shortcuts
var Dom = YAHOO.util.Dom, Calendar = YAHOO.widget.Calendar, DateMath = YAHOO.widget.DateMath;
});
</script>

As usual, we wait until the DOM is ready and then we create our sandbox. Within it, we create our shortcuts. Instead of the generic ones, we create shortcuts for the classes we will use in this example. Inside of our sandbox, we add:

//create the calendar object, specifying the container
var myCal = new Calendar("mycal");
//draw the calendar on screen
myCal.render();
//we will find out things about today's date,
// which we get from the Calendar control.
var today = myCal.today;

We create the calendar in the same way as in the previous example and render it on the page. This time, we don't need to worry about hiding it again as it will be a permanent fixture of the page. As we do some math on today's date, we read it from the calendar and keep it handy. We keep adding:

//we will pile up the results here:
var results = "";
// ** Our code will go here **
//insert the results into the page
Dom.get("results").innerHTML = results;

Performance analysis still shows that inserting HTML markup into the innerHTML property of a DOM element is, by far, faster than creating and appending DOM elements one by one. So, we create a variable to hold our results and then insert it into the container we created for them.

We won't have much user interaction with the Calendar Control, but we will use several of its tables of strings to assemble our results. Insert the following code where indicated above:

//get and display today's date
var dayNum = today.getDay(),
dayString = myCal.Locale.WEEKDAYS_LONG[dayNum],
date = today.getDate(),
monthNum = today.getMonth(),
monthString = myCal.Locale.MONTHS_LONG[monthNum],
year = today.getFullYear();
//put them all together
results += "<p>Today is " + dayString + ", " + date + " " + monthString + " " + year + "<\/p>";

Once we have this, we can get references to the date and month numerical representations and from these we can get the full day name and month name using Locale.WEEKDAYS_LONG[dayNum] and Locale.MONTHS_LONG[monthNum].

The Locale object is automatically created by the control and contains localized day and month names. It is primarily used to add new locales and specify alternative day and month names. English is available by default so we can simply read the properties and pull out what we want.

We can see some all-uppercase property names in this code, a naming convention usually reserved for constants. JavaScript has no constants, every variable can be changed; however, we use this convention to indicate our intent: these variables are not meant to be changed in application code. In this case, we would change them if we added other locales, but that would be customizing the library. Our application code would never change them.

Another naming convention worth mentioning is that of variables or functions starting with an underscore. Those are meant to be private members. JavaScript has no provision for private members so using the underscore is the way for the developer to tell everybody to stay away. There are many reasons for the developer to do it and a big one for you to stay away from them: they are not part of the contract; if the developer finds a better way to do things, private variables might suddenly disappear. Many developers find those variables through a debugger and use them. That is not a good idea; there will always be a public, safe way of achieving the same.

Once we have the information we need, it is simple enough to concatenate everything into our result string. Your page should look similar to the following screenshot:

Now we can have some fun with a few of the DateMath methods. First, add the following directly beneath our last block of code:

//work out date in 10 days time
DataMath classDataMath classmethodsvar futureDate = DateMath.add(today, DateMath.DAY, 10);
results += "<p>In ten days time it will be " + futureDate + "<\/p>";

We can use the .add() method of the YAHOO.widget.DateMath class to add a specified amount of time to a date object. The .add() method takes three arguments. The first is the date object on which the addition should be carried out, the second is one of the built-in constants representing the unit to be added (which could be days, weeks, months, or years), and the final argument is the actual amount to be added.

For the purposes of this example, I have left the futureDate field in full UTC format, but we could easily extract just the parts of the date that we want, just as we did to get the today's date.

Let's now look at the almost identical .subtract() method. Add the following code:

//work out date two months ago
var pastDate = DateMath.subtract(today, DateMath.MONTH, 2);
results += "<p>Two months ago the date was " + pastDate + "<\/p>";

You can see how easy the DateMath class makes addition and subtraction of date objects. The class has other useful methods such as the .getDayOffset() and .getWeekNumber() methods. We can expose the functionality of these two methods with the following code:

//work out day and week numbers of current date
var numberOfDays = DateMath.getDayOffset(today, year);
results += "<p>" + numberOfDays + " days have elapsed so far this year<\/p>";
var weekNumber = DateMath.getWeekNumber(today);
results += "<p>We are in week number "+ weekNumber + "<\/p>";

Save the file as datemath.html and view it in your browser of choice: