The often requested Popup Calendar or as some may call it, Date Picker, is now finally available in the Yahoo CDN hosted YUI Gallery!

The YUI Popup Calendar component extends YUI’s Calendar Widget as well as a few other Widget modules you likely have already loaded on the page giving you a very lightweight (1.6KB minified) way to add date picker like functionality to your web page or application.

Additional details, examples, and more can be found by following the links below. I hope that you find this module helpful and I’ll continue to develop on it so please feel free to contact me with bug reports and feature requests.

This multi-part series will take you through everything from how to get a basic Node.js static file server going, to different way’s to deliver the content to the user, common pitfalls, caching, and when to use Node.js to serve files vs other solutions.

I’m going to assume you already have Node.js up running on your computer and that you know javascript. This code was developed on v0.6.1-pre and I will make an effort to update it and the version number as things change with Node.js.

First thing your going to do is create a new file which will house our server code.

touch server.js

There are a few native Node.js modules that we’ll need and a few more will be added as this series continues.

var http = require('http'),
    path = require('path'),
    fs = require('fs'),
    util = require('util');

We are pulling in the following modules:

Create the server

Within the scope of this part of the tutorial we only need to access the HTTP module once for its createServer() method. We create a HTTP server which calls the _handler callback when a request event is made and start listening on a specified port.

http.createServer(_handler).listen(3000);
console.log('Server running at http://127.0.0.1:3000/');

Now set up the request handler which will house all of the code that handles each and every request that comes into your server. We’ll also define some variables which we’ll use later.

function _handler(req, res) {
    var root = "..",
        url = "",
        contentType = "text/plain",
        filePath = "";

        //All following code goes here
}

Because this is strictly a file server we only want to support the GET method and reject any other requests. To do this we need to reference the request object passed to our callback, an instance of http.ServerRequest stored in req

if (req.method !== 'GET') { //If the request method doesn't equal 'GET'
    res.writeHead(405); //Write the HTTP status to the response head
    res.end('Unsupported request method', 'utf8'); //End and send the response
    return;
}

On most web servers when you send a request to the server without a file you will get some type of default file usually the index.html. Because this is a file server, if they aren’t requesting a file, they are going to get an error. After we determine that they are requesting a file we do simple string concatenation to build the file path and then pass it to the path.exists() method. This method will test the file path for existence and then pass that information to the callback, serveRequestedFile in this case.

if ('.' + req.url !== './') {
    filePath = root + req.url;
    path.exists(filePath, serveRequestedFile);		
} else {
    res.writeHead(400);
    res.end('A file must be requested', 'utf8');
    return;
}

Stream your files

Now that we have set up the http server, verified that they are using the correct method, and that they are requesting a file, we need to serve that file to them. To do this we will use our previously mentioned serveRequestedFile() function which will receive the status of our file inquiry which we need to test before proceeding.

function serveRequestedFile(file) {
    if (file === false) {
        res.writeHead(404);
        res.end();
        return;
    }

    //Following code will go here

}

Because we aren’t implementing any caching mechanism yet we don’t want to read the whole file into memory before serving. To get around that we use the File System’s fs.createReadStrem() method. This method returns a Readable Stream which emits a number of events. We will set up an error event handler in the event there is a server error and the file cannot be served to the user.

var stream = fs.createReadStream(filePath);

stream.on('error', function(error) {
    res.writeHead(500);
    res.end();
    return;
});

Before we send the data to the user we should set the files mime type. For simplicity sake I have included the mime object within the function itself. On a true file server you would likely import a much larger mime list.

var mimeTypes = {
    '.js' : 'text/javascript',
    '.css' : 'text/css',
    '.gif' : 'image/gif'
}

contentType = mimeTypes[path.extname(filePath)];

We have now verified that the file exists, created a stream instance for the file in question, set up basic error handling, and found the files mime type. The only thing left is to actually send the file to the user. We first set the content type and the status in he header then use Utilities util.pump() method to push the previously initiated stream to the user. The pump method reads the data from the readable stream stream and sends it to the writable stream res. If there is an error in this process or the stream is closed the third arugment, the callback, is called.

res.setHeader('Content-Type', contentType);
res.writeHead(200);

util.pump(stream, res, function(error) {
    //Only called when the res is closed or an error occurs
    res.end();
    return;
});

Start the server and start serving files!

node server.js

I hope you enjoyed the first part of this series and were able to get your own file server up and running. I have included a working example of the above code if you want to see it in its entirety. If you need further help feel free to comment below, mention me on twitter, or join #Node.js on irc.freenode.net.

This information is accurate for YUI 3.5.0pr2 - there are cross browser issues with the following method on previous versions.

From time to time you may find yourself loading modules that aren’t available. Whether it be from a user supplied modules list or an unreliable source, the YUI instance confg gives you an easy way to check if there are any errors while loading your modules.

A common YUI config and use statement would look like the following.

YUI({
  modules: { // Define your custom module meta data
    'custom-module' : {
      fullpath: 'path/to/module.js'
    }
  }
}).use('node', 'custom-module', function(Y) {
  // Your code here
});

If for some reason the loader was unable to fetch a required module and you wanted to act on that event, the loader provides you with an onFailure method that is called with an object that has details about the modules it failed to fetch.

To use this method you would amend the above code to include that method.

YUI({
  modules: { // Define your custom module meta data
    'custom-module' : {
      fullpath: 'path/to/module.js'
    }
  },
  onFailure: function(error) {
  	// Error handling code
  }
}).use('node', 'custom-module', function(Y) {
  // Your code here
});

The error object is structured.

{
  data: Array[], // Contains a list of the requested modules
  msg: "Failed to load path/to/module", // A string error message
  success: false // A bool representation of the success of the requests
}

It’s that simple, by setting up your onFailure callback you can easily handle any errors that may arise by an improperly structured config or use statement.

Warning

If there is a failure in your initial module request the onFailure callback will be called before the Y instance is set-up. This means that no YUI methods will be available and calls to them will cause your application to fail. In this case the this property will point to YUI.

More Reading

In the following example the onFailure callback will be called if at any time there is a failure while trying to load modules. You will need to keep this in mind if your doing any lazy loading of modules so that you can appropriately handle the errors.

YUI({
  modules: { // Define your custom module meta data
    'good-module' : {
      fullpath: 'correct/path/to/good-module.js'
    },
    'bad-module' : {
      fullpath: 'path/to/bad-module'
    }
  },
  onFailure: function(error) {
  	// I'm going to be called when the request for bad-module is made
  }
}).use('node', 'good-module', function(Y) {

  Y.use('bad-module', function() {
  	// More code here
  });

});

The error.msg property string will contain comma delimited messages about which values failed.

YUI({
  modules: { // Define your custom module meta data
    'wrong-module' : {
      fullpath: 'path/to/wrong-module'
    },
    'bad-module' : {
      fullpath: 'path/to/bad-module'
    }
  },
  onFailure: function(error) {
  	console.log(error);
  }
}).use('node', 'wrong-module', 'bad-module', function(Y) {
  // Code here
});
{
  data: Array[22],
  msg: "Failed to load path/to/wrong-module,Failed to load path/to/bad-module",
  success: false
}

This method only works if the module has been defined. You will not receive an error for the crazy-module.

YUI({
  modules: { // Define your custom module meta data
    'wrong-module' : {
      fullpath: 'path/to/wrong-module'
    }
  },
  onFailure: function(error) {
  	console.log(error);
  }
}).use('node', 'wrong-module', 'crazy-module', function(Y) {
  // Code here
});

You will however get a string error logged to the console in addition to the onFailure if the undefined module comes before the defined custom modules.

YUI({
  modules: { // Define your custom module meta data
    'wrong-module' : {
      fullpath: 'path/to/wrong-module'
    }
  },
  onFailure: function(error) {
  	console.log(error);
  }
}).use('node', 'crazy-module', 'wrong-module', function(Y) {
  // Code here
});
"yui: NOT loaded: crazy-module"

After a year of trying to get the time to roll my own blog I cut my losses and set up with tumblr. The plan is to write a quality article every week on web development but you will probably see an odd one here and there about other things that I find interesting.

I hope you enjoy the things to come and I look forward to your comments.

-Jeff Pihach

YUI Popup Calendar

The YUI Popup Calendar component extends YUI’s Calendar Widget as well as a few other Widget modules you likely have already loaded on the page giving you a very lightweight (1.6KB minified) way to add date picker like functionality to your web page or application.

You can find the code via Github here: YUI Gallery fork

How to use

Almost all of the configuration properties for Y.PopupCalendar are for Y.Calendar and the other modules that it extends.

HTML

<form class="yui3-skin-sam">
	<input name="name" type="text" autocomplete="off"/>
	<input name="date" type="text" autocomplete="off"/>
	<input name="phone" type="text" autocomplete="off"/>
</form>

CSS

In order to style this calendar like the YUI default calendar you need to add the class .yui3-skin-sam to a parent container of the calendar.

To have the calendar positioned overtop of other contents on your page and to hide when you click away the following two classes need to be added to your css

.yui3-popup-calendar {
  position: absolute;
}

.yui3-popup-calendar-hidden {
  display: none;
}

Javascript

YUI().use('popup-calendar', function(Y) {

  var input =  Y.one('form input[name=date]'),
      popupcalendar = new Y.PopupCalendar({
    // Popup Calendar config properties
    // input: the text input that will trigger
    // the calendar to show
    input: input,

    // focus the calendar on input focus for
    // keyboard control (default is false)
    autoFocusOnFieldFocus: false,

    // depending on the browser you may  
    // need to have all tabindex's defined on
    // inputs, you can use this attribute to do
    // it so you don't have to (default is false);
    // doesn't specify the tabindex then this is required
    autoTabIndexFormElements: false,

    // Calendar config properties
    width:'400px',
    showPrevMonth: true,
    showNextMonth: true,
    date: new Date(),

    // Widget Position Align configuration properties
    // This is now optional and defaults to the following
    align: {
      node: input,
        points: [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.TR]
    }
  });

  // The popup calendar makes no assumptions as to
  // what you want to do with the selected date
  // so it is up to you to do something with the emitted event.
  popupcalendar.on('dateSelected', function(e) {
    var d = e.newSelection[0];
    popupcalendar.get('input').set('value', d.getDate() + "/" + d.getMonth() + "/" + d.getFullYear());
  });

});

Repositioning the calendar on resize

If the user resizes the browser once the calendar is open they run the risk of covering it up. To remedy this, you simply need to interact with widget-position-align on resize

Y.on('resize', function(e) {

  // Use the resize event object `e` and the position of the
  // bounding box popupcalendar.get('boundingBox'); to
  // check if the calendar is outside of the viewport
  // use that calculation to determine the points to set the
  // new alignment points to

  // Change the points that the calendar aligns with
  popupcalendar.align: {
    node: input,
    points: [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BR]
  }
});

Known Bugs

Opera in Ubuntu (possibly other os’s as well) has an odd keyboard navigation highlighting alignment issue with the Calendar. The calendar is still functional albeit a little odd to navigate via keyboard.

Hitting enter when navigating the calendar by keyboard does not select a date in IE7 & IE8