A Popup DatePicker

One commonly requested feature of Dijit is to allow a DateTextBox to contain a clickable image outside of the input that triggers the popup _Calendar class to display, rather than display when the element is focused. It opens up countless levels of complication internally: design consideration, a11y states, etc. But the concept is really quite simple, and even easier to implement. Shall we?

In this brief Dojo 1.2+ tutorial, we’re going to walk through the steps needed to make a dojo.query “plugin” that will add the desired behavior to any input that accepts a .value assignment. The end result will be a simple API:

dojo.addOnLoad(function(){
     dojo.query("input.wantingACalendar").addPicker().removeClass("wantingACalendar");
});

The addPicker method will add an icon after each of the matched input’s with class=”wantingACalendar”, wire the click events, and manipulate a shared instance of a Calendar. (In this example we’ll use the DojoX Animated Clander from Shane O’Sullivan: dojox.widget.Calendar) We will be using a shared instance of a Calendar for all inputs, as you can really only be acting on one input at a time, at least in this UI pattern.

Start with a skeleton HTML page, including Tundra, DojoX Calendar CSS, and dojo.js:

<html>
<head>
 
	<title>Sample Dojo / Dijit Page</title>
 
	<!-- a Dijit theme, and Calendar CSS: -->
	<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.2/dijit/themes/tundra/tundra.css" />
	<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.2/dojox/widget/Calendar/Calendar.css" />
 
	<!-- load Dojo -->
	<script src="https://ajax.googleapis.com/ajax/libs/dojo/1.2/dojo/dojo.xd.js"></script>
 
</head>
<body class="tundra">
 
   <h1>Sample</h1>
   <label for="foo">Select A Date:</label><input id="foo" name="foo" type="text" />
 
</body>
</html>

This won’t do anything just yet, but sets the page up perfectly for us. The rest of our code will code in a single <script> tag immediately following the dojo.js script tag. We’ll require the Calendar code, and register an addOnLoad function to execute when the required resources are solved:

<script type="text/javascript">
    dojo.require("dojox.widget.Calendar");
    dojo.addOnLoad(function(){
    // plugin code goes in here
    });
</script>

The rest of this JavaScript goes inside the addOnLoad function. The first thing to do would be to create the plugin method by extending dojo.NodeList:

dojo.extend(dojo.NodeList, {
      addPicker: function(){
            // plugin code goes in here.
            return this; // dojo.NodeList
      }
});

We’ve added a method to dojo.NodeList called addPicker, but it doesn’t do anything yet except for the “most important part” … by returning ‘this’ from our method, we allow ‘chaining’ to continue, so you can call other dojo.NodeList methods (like .removeClass) after using the .addPicker() method.

The .addPicker code needs to create an image for each of the matched elements in the NodeList. For that we can use .forEach (another NodeList method). The code path is relatively straightforward:

dojo.extend(dojo.NodeList, {
	// the guts of the "plugin"
	addPicker: function(args){
		this.forEach(function(n){
			// add an image after the input we're targeting for clickage
			var img = dojo.doc.createElement('img');
			dojo.attr(img, {
				// change this to something better:
				src:"https://archive.dojotoolkit.org/nightly/checkout/demos/resources/silk/icons/date_magnify.png", 
				alt:"calendar",
				style:{ cursor:"pointer" }
			})
			dojo.place(img, n, "after");
 
			dojo.connect(img, "onclick", function(e){
				// tell popup which node to send onChange value to
				calendar._pushChangeTo = n; 
				// and open the popup below the targeted input
				dijit.popup.open({ 
					popup: calendar,
					around: n
				});
			})
 
		});
		return this; // dojo.NodeList
	}
});

We create an img variable (a DomNode), give it attributes with dojo.attr, place it after our target node (n) with dojo.place, and call dojo.connect to register the onclick. The magic happens inside the onclick function, and if you were paying attention you noticed we’re referencing an undefined variable ‘calendar’ in two places. Once to store as a reference to the associated input on the ‘calendar’ object, and again in the dijit.popup.open function. This ‘calendar’ variable is our (thus far) uncreated DojoX Calendar instance we plan to share between inputs. We should make that now, immediately before the dojo.extend() call:

// create a new instance of a Calendar
var calendar = new dojox.widget.Calendar({
	onChange: function(val){
		// when the value changes, hide the popup 
		// and set a value to our input 
		dijit.popup.close(calendar);
		console.log(val, typeof val);
		this._pushChangeTo.value = dojo.date.locale.format(val);
	},
	destroy: function(){
		// avoid leaving a ref in object for IE?
		delete this._pushChangeTo;
		this.inherited(arguments);
	}
});

So we create a single Calendar instance we can reference as ‘calendar’ within this addOnLoad function. The two functions we’re including here are onChange and destroy. The destroy is simply there to cleanup the node reference that is stashed on the Calendar as part of the icon’s onclick function (calendar._pushChangeTo = n), and may not truly be necessary. Good to be safe.

The onChange function simply closes the popup, and set’s the .value property of the _pushChangeTo node (populated onclick of the icon, remember) using dojo.date.locale to format the date into something pretty.

And that’s it. Simply locate the input(s) you wish to make datepickers, and run the command:

dojo.query("input").addPicker();

This of course can use a plain dijit._Calendar, or any of the various mix and matches of DojoX Calendars available, and could easily be extended into allowing special formatting on the input value. This is left as an exercise to you, reader.

Worth noting: As of Dojo 1.3 there is a dojo.create function, which could reduce the code to create the image and register the onclick to:

dojo.create("img", { 
 	src:"calendar.png",
 	alt:"celendar",
 	style:{ cursor:"pointer" },
	onclick: function(e){
		calendar._pushChangeTo = n; 
		dijit.popup.open({ 
			popup: calendar,
			around: n
		});
	}
}, n, "after");

… if that fits your coding style. Happy Dojo’ing.

Tags: