Building a date picker component

First install component(1) with npm:

$ npm install -g component

Or if you don’t have node installed at all, on OSX execute:

$ (cd /usr/local && \
   curl -L# http://nodejs.org/dist/v0.8.15/node-v0.8.15-darwin-x86.tar.gz \
   | tar -zx --strip 1) \
  && npm install -g component \
  && printf "installed component(1) %s\n" $(component --version)

Next you’ll need to bootstrap a new component, you can do this manually or use the quick component-create(1) command:

$ component create datepicker
repo (username/project): component/datepicker
description: Datepicker ui component built on component/calendar
does this component have js? y  
does this component have css? y
does this component have html? 

      create : datepicker
      create : datepicker/index.js
      create : datepicker/datepicker.css
      create : datepicker/Makefile
      create : datepicker/Readme.md
      create : datepicker/History.md
      create : datepicker/.gitignore
      create : datepicker/component.json

Adding dependencies

Next cd into the new directory and install the component/calendar component as a dependency:

$ component install component/calendar

    install : component/calendar@master
        dep : component/range@master
    install : component/range@master
        dep : component/jquery@master
    install : component/jquery@master
        dep : component/emitter@master
    install : component/emitter@master
        sep : component/in-groups-of@master
    install : component/in-groups-of@master
      fetch : component/calendar:index.js
      fetch : component/calendar:lib/utils.js
      fetch : component/calendar:lib/template.js
      fetch : component/calendar:lib/calendar.js
      fetch : component/calendar:lib/days.js
      fetch : component/calendar:lib/calendar.css
      fetch : component/range:index.js
      fetch : component/jquery:index.js
      fetch : component/in-groups-of:index.js
      fetch : component/emitter:index.js
   complete : component/range
   complete : component/in-groups-of
   complete : component/emitter
   complete : component/jquery
   complete : component/calendar

If you cat the component.json file you’ll see that component-install(1) has added the dependency for us:

  cat component.json 
  {
    "name": "datepicker",
    "repo": "component/datepicker",
    "description": "Datepicker ui component built on component/calendar",
    "version": "0.0.1",
    "keywords": [],
    "dependencies": {
      "component/calendar": "*"
    },
    "development": {},
    "license": "MIT",
    "scripts": [
      "index.js"
    ],
    "styles": [
      "datepicker.css"
    ]

Creating the component

Like any other software it’s good to have a suite of tests, or at very least a functioning demo for developers and end-users. In this case we’ll just create example.html to test drive the date picker, create the file and add the following HTML:

<!DOCTYPE html>
<html>
  <head>
    <title>Datepicker</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <link rel="stylesheet" href="build/build.css">
    <script src="build/build.js"></script>
  </head>
  <body>
    <input type="text" name="date" placeholder="Choose a date">

    <script>
       var picker = require('datepicker');
       var el = document.querySelector('[name=date]');
       picker(el);
    </script>
  </body>
</html>

Our date picker is going to be pretty simple, we’re going to handle click events for the element passed to picker(), and then display the Calendar in a Popover instance, populating the input when a selection is made.

Before getting started you’ll want to install the popover component, event utility, and the optional “aurora” theme:

$ component install component/popover component/aurora component/event

Or with brace expansion:

$ component install component/{popover,aurora,event}

The following JavaScript snippet is what will go into ./index.js, the main entry-point script of the component. As you can see here the dependencies are explicitly required at the top of the file via the commonjs module system, making it very obvious what this module is interacting with. Next we export a single object, the Datepicker constructor itself which sets up a bit of event handling boilerplate.

var Calendar = require('calendar')
  , Popover = require('popover')
  , event = require('event')

module.exports = Datepicker;

function Datepicker(el) {
  if (!(this instanceof Datepicker)) return new Datepicker(el);
  this.el = el;
  event.bind(el, 'click', this.onclick.bind(this));
}

Datepicker.prototype.onclick = function(e){

};

NOTE: The use of “event” may seem low-level here, and that’s because it is, the event module is nothing more than a cross-browser event binding utility. These APIs are not designed to be cute, they’re designed to drastically reduce the dependency bloat, by only using what your component needs. “Cute” APIs are provided by higher level components such as the jQuery-like dom component, and are typically recommended for app-level use only

To finish up the JavaScript portion of the Datepicker instantiate a new Calendar in the constructor, and add a classname for styling purposes. Next in the .onclick handler a new Popover is passed the cal element for its content, and a datepicker-popover classname is given here for styling purposes as well, then finally the call to popover.show(this.el) shows the popover relative to the <input> element.

var Calendar = require('calendar')
  , Popover = require('popover')
  , event = require('event')

module.exports = Datepicker;

function Datepicker(el) {
  if (!(this instanceof Datepicker)) return new Datepicker(el);
  this.el = el;
  this.cal = new Calendar;
  this.cal.el.addClass('datepicker-calendar');
  event.bind(el, 'click', this.onclick.bind(this));
}

Datepicker.prototype.onclick = function(e){
  this.cal.on('change', this.onchange.bind(this));
  this.popover = new Popover(this.cal.el);
  this.popover.classname = 'datepicker-popover popover';
  this.popover.show(this.el);
};

Datepicker.prototype.onchange = function(date){
  el.value = date.getFullYear()
    + '/'
    + date.getMonth()
    + '/'
    + date.getDate();

  this.popover.hide();
};

Building the component

To build the component invoke the following component-build(1) command:

$ component build

Or simply use make, as the targets are already set up in the Makefile for you:

$ make

A new directory named ./build will now have been created for you with the resulting build.css and build.js files.

To make this process transparent in development I recommend using the watch command that some environments already have, for those that do not have it there’s watch(1). Before working on a component you may now simply watch the make command and chuck that process in the background to get out of your way:

$ watch make &

When you’re done invoke fg to foreground the job again, or kill it with:

$ kill %1

Styling the component

Without custom styling the Aurora calendar / popover themes will look something like the following, not exactly great for a date picker since the calendar already looks self-contained.

datepicker component before styling

Add the following CSS to ./datepicker.css to cancel out the popover border for the datepicker popover only:

.datepicker-calendar {
  font: 10px "Helvetica Neue", Helvetica, Arial, sans-serif;
}

.datepicker-popover .tip-arrow {
  top: auto;
}

.datepicker-popover .tip-inner {
  border: none;
}

Now re-build and you should have the following completed datepicker:

completed datepicker component

Publishing

To “publish” the component all you need to do is push it to GitHub and then install with component install myname/datepicker! No fighting over names, you can call them whatever you like. To increase discoverability of your component you can then add it to the Wiki component listing and the component-search(1) command will index it.

Get the code for this component at component/datepicker.

For more on components check out the component repo and the Wiki.