jQuery Datepicker Fully Localized
Recently, I've run into a situation where I needed to display a datepicker, fully localized.
tl;dr
I modified the jQuery Calendars plugin to do this:
Requirements
When I say "fully localized", I mean this:
- non-english
- the calendar is not Gregorian
- even the numbers should be converted
jQuery UI
My first thought was, we're using jQuery UI, and it has a datepicker. It supports localization but only to the extent that it sets the week days and month to the proper language. In my case, it met requirement 1 but not 2 or 3.
I definitely did not want to re-invent an algorithm for every calendar I would need...and I did not want to create my own datepicker from scratch.
jQuery Calendars
I moved on and continued looking for a jQuery plugin that supported a non-gregorian calendar. I'm pretty sure this is a common problem for any country that does not use the Gregorian calendar.
A bit of searching, and this popped up on my screen: jQuery Calendars
At first, I was pretty skeptical because the site looked fairly old. However, it did have support for the calendar I was looking for (Persian/Jalali), and more.
Upon further inspection, this plugin seemed to do a lot more than the jQuery UI one. The site has a lot of documentation and examples of how to use the various features, like the datepicker and localization, but it didn't have simple examples. I was determined to figure it out.
I downloaded the 1.14 version of the calendars package from this page and unzipped it.
~/Downloads/jquery.calendars.package-1.1.4] ls | wc -l
230
Damn, that's a lot of files.
To use the localized datepicker, I had to include a bunch of files:
<!-- CSS styles for the datepicker (there are other themes included with the zip package) -->
<link href="/js/jquery.calendars/jquery.calendars.picker.css" rel="stylesheet" type="text/css" />
<!-- The datepicker required the base and the "plus" module -->
<script type="text/javascript" src="/js/jquery.calendars/jquery.calendars.js"></script>
<script type="text/javascript" src="/js/jquery.calendars/jquery.calendars.plus.js"></script>
<script type="text/javascript" src="/js/jquery.calendars/jquery.calendars.picker.js"></script>
<!-- The persian calendar -->
<script type="text/javascript" src="/js/jquery.calendars/jquery.calendars.persian.js"></script>
<!-- The localization files for the datepicker and the persian calendar -->
<script type="text/javascript" src="/js/jquery.calendars/jquery.calendars.picker-fa.js"></script>
<script type="text/javascript" src="/js/jquery.calendars/jquery.calendars.persian-fa.js"></script>
<script type="text/javascript">
$(function(){
/* apply the calendarsPicker (there is also .datepick for just the datepicker in Gregorian format) */
$('.calendarspicker').calendarsPicker({
calendar: $.calendars.instance('persian', 'fa')
});
});
</script>
Phew, got it working.
The client would be happy with just this but I wanted to go that extra mile to convert the English numbers to Farsi. If I could read Chinese, and had a Chinese datepicker with all the text in Chinese, in a Chinese calendar, I would want to see the numbers in Chinese too, probably.
These set of plugins had so many features, I figured the code should be organized in such a way that it was easily extendible. Ironically, I did find this page AFTER I hacked away at the code.
Extending the plugin(s)
I looked through the source code and found where it was displaying the days and years in the datepicker, so I made it call a function to translate the numbers in each of those places. Since everything was set up pretty cleanly, I only had to add it to the proper localization file jquery.calendars.picker-fa.js
and add it to the appropriate place in the datepicker code. I added it to the options list of the jquery.calendars.picker.js
file.
I ended up doing 2 things.
1. Translating the numbers
To translate the Persian numbers, it's a simple 1 to 1 mapping of 0-9.
(function($) {
$.calendars.picker.regional['fa'] = {
/* other options */
...
isRTL: true,
translateNumberFunction: function(num){
var string_value = num.toString();
var mapping = {
// I originally had the farsi characters for `farsix`
// jekyll was having issues converting these farsi numbers
// it worked fine on OS X but not in Ubuntu @.@
0: 'farsi0',
1: 'farsi1',
2: 'farsi2',
3: 'farsi3',
4: 'farsi4',
5: 'farsi5',
6: 'farsi6',
7: 'farsi7',
8: 'farsi8',
9: 'farsi9',
};
for (x in mapping) {
string_value = string_value.replace(new RegExp(x, 'g'), mapping[x]);
}
return string_value;
}
};
Doing it like this means, if another language needs to convert the numbers, they just need to add this translation function to the appropriate localization file. Right now, it calls the function in 3 places: the year, the days and the actual date string that gets put into the input box. I think it gets called once per day and once per year whenever the user brings up the datepicker or changes months, so it can be optimized.
2. Processing the input
If I stopped here, I would have to process the Jalali date in Persian numbers in the backend code. That is something I didn't want to do with every single date passed to the system.
When I was looking through the documentation, I saw the conversion tab. This thing knows how to convert numbers. Why not just show the Persian date and pass back the Gregorian date? That way I can continue processing the dates without any changes to the backend! I didn't want to have to add extra javascript code to every template, so the I did this:
<!-- the form element that brings up the date picker and shows the localized date -->
<input class="calendarspicker" type="input" data-gregorian-field="input[name=due_date]" />
<!-- the actual value that gets passed along the form post to the backend -->
<input name="due_date" type="hidden" />
Simple. Just populate another field along with the initial date picker field and the proper value will get passed along on form submission.
The code to convert the selected localized date to the Gregorian date was also simple:
// get an instance of the default calendar, which is in Gregorian
var gCalendar = $.calendars.instance();
// normalize to julian date and go from julian to gregorian
var gDate = gCalendar.fromJD(inst.selectedDates[i].toJD());
I also added a disableInput
option to jquery.calendars.picker.js
to prevent the user from changing the date using the input. I know this isn't the best solution to handle keyboard input in the text field but it works for now.
In the end, include all those files above and change the initializing code to something like this:
$(function(){
$('.calendarspicker').calendarsPicker({
calendar: $.calendars.instance('persian', 'fa'),
disableInput: true
});
});
I couldn't find the project on github, so I created a repo and applied my changeset.
I would like to thank Keith Wood for creating/maintaining this very useful plugin!