Skip to main content

Selecting date range with YUI3 Calendar

This is a fully UI related article about YUI3 Calendar widget. Whereas official documentation page and provided examples (here and here) are good enough to start with, there are still many pitfalls when you're going to customize the widget. My use case required a date range selection feature with a nice calendar window open only when a user clicks on text input fields. Also the selected day should be highlighted in the calendar whenever a user clicks again on the filled text input field (a nice example with YUI2 Calendar widget). This is how one date input field looks like:

Add calendar container and date fields on the web page
First of all you need to add a container div for the calendar:
<div id="calendar-container"> </div>
You will provide this id to the Calendar constructor later either as a bounding box or a content box. The difference is in html markup generated by YUI3. I'll use a bounding box below in order to manipulate position of calendar window.

Also you will need input text fields on your page to be filled with dates:
<input id="startDate" type="text"/>
<input id="endDate" type="text"/>

Initialize YUI3
To get started with YUI3, you should perform two steps:
  1. Load the YUI javascript file on your web page.
  2. Create and configure a new YUI instance.
Please refer to this manual for code snippets. The only difference is that we'll use two modules: calendar and datatype-date:
YUI().use('calendar', 'datatype-date',  function(yui3) {
// Calendar code will be here
}

Initialize Calendar
Here we'll define some variables to be used in functions later and the calendar instance itself.
// Our calendar bounding div id
var boundingBoxId = "#calendar-container",
// This flag used to track mouse position
    isMouseOverCalendar = false,
// A text field element that stores the date chosen in calendar
    currentValueContainer = '',
    calendar = new yui3.Calendar({
        boundingBox: boundingBoxId,
        width: "300px"
    });

Register event handler functions
Here we'll define event handlers for several DOM events (focus, blur, mouseover, mouseout) and for a selectionChange event of our calendar:
// These are text fields' ids to store dates in
var dateFields = ["#startDate", "#endDate"];

// To show calendar when user clicks on text fields
yui3.on("focus", function(event) { showCalendar(event) }, dateFields);
// To hide calendar when text fields loose focus
yui3.on("blur", function() { hideCalendar() }, dateFields);

// Tracking mouse position
yui3.on("mouseover", function () {
    isMouseOverCalendar = true;
}, boundingBoxId);
yui3.on("mouseout", function () {
    isMouseOverCalendar = false;
}, boundingBoxId);

// On date selection change we update value of a text field and hide calendar window
calendar.on("selectionChange", function (event) {
    var newDate = event.newSelection[0];
    yui3.one(currentValueContainer).set("value", yui3.DataType.Date.format(newDate));
    isMouseOverCalendar = false;
    hideCalendar();
});

Show Calendar function
This is the most complex part of functionality to show the calendar properly. See inline comments for the details:
var showCalendar = function (event) {
    // It's a text field that a user clicked on
    currentValueContainer = event.target;

    // Saving position of a text field to calculate position of calendar
    var xy = yui3.one(currentValueContainer).getXY(), 
    // Getting current date value in the text field
        dateString = yui3.one(currentValueContainer).get("value");

    // Clearing previously selected dates if any
    calendar.deselectDates();
    // If the text field had some date value before
    if (dateString) {
        // Parsing the date string into JS Date value
        var date = yui3.DataType.Date.parse(dateString);
        if (date) {
            // Highlighting the date stored in the text field
            calendar.selectDates(date);
        } else {
            date = new Date();
        }
        // Setting calendar date to show corresponding month
        calendar.set("date", date);
    } else {
        calendar.set("date", new Date());
    }
    // Finally render the calendar window
    calendar.render();

    // Required styles to show calendar in a proper position
    yui3.one(boundingBoxId).setStyles({
        display: "block",
        position: "absolute"
    });
    // Set calendar window position right below the text field
    yui3.one(boundingBoxId).setXY([xy[0], xy[1] + 20]);
};
I should give some remarks here:
  • This doesn't fully work in IE - you will loose the currently selected date when you click on the text field again. That's because IE cannot parse dateString into valid JS Date but returns null instead.
  • The single instance of Calendar is used on the page. That's why we have to deselect old date, parse date value stored in the text field and select this date in calendar manually. However, you benefit from it when you have many date selection text fields on the page (in my case there are 3 date ranges, thus, 6 date text fields).
  • Check YUI3 Calendar API page for more details.

Hide Calendar function
This function simply hides the calendar window from the page.
var hideCalendar = function () {
    if (!isMouseOverCalendar) {
        yui3.one(boundingBoxId).setStyle("display", "none");
    }
};
That's it. It works flawlessly in Chrome and Firefox, also quite nice in IE8 except loosing selected date when clicking text fields again. YUI version used is 3.4.1.

Comments

  1. This comment has been removed by a blog administrator.

    ReplyDelete
  2. Hi..same code iam using in yui3 calendar development..its not working..please help me to find.
    My code

    YUI().use('calendar', 'datatype-date', function(Y) {
    var boundingBoxId = "#calendar-container",
    isMouseOverCalendar = false,
    currentValueContainer = '',
    calendar = new Y.Calendar({
    boundingBox: boundingBoxId,
    width: "300px"
    });

    var dateFields = ["#startDate", "#endDate"];

    Y.on("focus", showCalendar, dateFields);
    Y.on("blur", hideCalendar, dateFields);

    Y.on("mouseover", function () {
    isMouseOverCalendar = true;
    }, boundingBoxId);

    Y.on("mouseout", function () {
    isMouseOverCalendar = false;
    }, boundingBoxId);

    calendar.on("selectionChange", function (event) {
    var newDate = event.newSelection[0];
    Y.one(currentValueContainer).set("value", Y.DataType.Date.format(newDate));
    isMouseOverCalendar = false;
    hideCalendar();
    });

    var showCalendar = function (event) {
    // It's a text field that a user clicked on
    currentValueContainer = event.target;

    // Saving position of a text field to calculate position of calendar
    var xy = Y.one(currentValueContainer).getXY(),
    // Getting current date value in the text field
    dateString = Y.one(currentValueContainer).get("value");

    // Clearing previously selected dates if any
    calendar.deselectDates();
    // If the text field had some date value before
    if (dateString) {
    // Parsing the date string into JS Date value
    var date = Y.DataType.Date.parse(dateString);
    if (date) {
    // Highlighting the date stored in the text field
    calendar.selectDates(date);
    } else {
    date = new Date();
    }
    // Setting calendar date to show corresponding month
    calendar.set("date", date);
    } else {
    calendar.set("date", new Date());
    }
    // Finally render the calendar window
    calendar.render();

    // Required styles to show calendar in a proper position
    Y.one(boundingBoxId).setStyles({
    display: "block",
    position: "absolute"
    });
    // Set calendar window position right below the text field
    Y.one(boundingBoxId).setXY([xy[0], xy[1] + 20]);
    };

    var hideCalendar = function () {
    if (!isMouseOverCalendar) {
    Y.one(boundingBoxId).setStyle("display", "none");
    }
    };

    });


    thanks.

    ReplyDelete
    Replies
    1. Hi, indeed it didn't work properly until I modified the following code snippet in the article:

      // To show calendar when user clicks on text fields
      yui3.on("focus", function(event) { showCalendar(event) }, dateFields);
      // To hide calendar when text fields loose focus
      yui3.on("blur", function() { hideCalendar() }, dateFields);

      Please check if it works for you.

      Delete

Post a Comment

Popular posts from this blog

DynamicReports and Spring MVC integration

This is a tutorial on how to exploit DynamicReports reporting library in an existing  Spring MVC based web application. It's a continuation to the previous post where DynamicReports has been chosen as the most appropriate solution to implement an export feature in a web application (for my specific use case). The complete code won't be provided here but only the essential code snippets together with usage remarks. Also I've widely used this tutorial that describes a similar problem for an alternative reporting library. So let's turn to the implementation description and start with a short plan of this how-to: Adding project dependencies. Implementing the Controller part of the MVC pattern. Modifying the View part of the MVC pattern. Modifying web.xml. Adding project dependencies I used to apply Maven Project Builder throughout my Java applications, thus the dependencies will be provided in the Maven format. Maven project pom.xml file: net.sourcefo

Connection to Amazon Neptune endpoint from EKS during development

This small article will describe how to connect to Amazon Neptune database endpoint from your PC during development. Amazon Neptune is a fully managed graph database service from Amazon. Due to security reasons direct connections to Neptune are not allowed, so it's impossible to attach a public IP address or load balancer to that service. Instead access is restricted to the same VPC where Neptune is set up, so applications should be deployed in the same VPC to be able to access the database. That's a great idea for Production however it makes it very difficult to develop, debug and test applications locally. The instructions below will help you to create a tunnel towards Neptune endpoint considering you use Amazon EKS - a managed Kubernetes service from Amazon. As a side note, if you don't use EKS, the same idea of creating a tunnel can be implemented using a Bastion server . In Kubernetes we'll create a dedicated proxying pod. Prerequisites. Setting up a tunnel.

Elasticsearch CORS with basic authentication setup

This is a short "recipe" article explaining how to configure remote ElasticSearch instance to support CORS requests and basic authentication using Apache HTTP Server 2.4. Proxy To start with, we need to configure Apache to proxy requests to the Elasticsearch instance. By default, Elasticsearch is running on the port 9200: ProxyPass /elastic http://localhost:9200/ ProxyPassReverse /elastic http://localhost:9200/ Basic authentication Enabling basic authentication is easy. By default, Apache checks the user credentials against the local file which you can create using the following command: /path/to/htpasswd -c /usr/local/apache/password/.htpasswd_elasticsearch elasticsearchuser Then you'll need to use the following directives to allow only authenticated users to access your content: AuthType Basic AuthName "Elastic Server" AuthUserFile /usr/local/apache/password/.htpasswd_elasticsearch Require valid-user For more complex setups such as LDAP-based