Friday, March 2, 2012

Django HTML5 date input widget with i18n support and jQueryUI DatePicker fallback

Here's my manual for creating a HTML5 form date input with jQueryUI Datepicker and multilingual support.

What we need is:
1. django-floppyforms
2. jQueryUI Datepicker + jQuery 3. Modernizr javascript library
and of course - Django
You need to have some basic python, django & jquery knowledge.
Make sure multilingual support is enabled:
USE_I18N = True

and request context available for template
#settings.py

TEMPLATE_CONTEXT_PROCESSORS = (
  # ...
  'django.core.context_processors.request',
  # ...
)
In your base template, you can use LANGUAGE_CODE variable.
<html lang="{{ LANGUAGE_CODE }}">

There are 3 ways to get this running

1. Totally django way - prividing django form & widget with the "lang" variable"

Your calendar widget needs to gain information about the language, so you need to pass a variable to your form, which then sends this variable to the widget.
def add_view(request):
    
    form = SomeForm(data = request.POST, lang=request.LANGUAGE_CODE)
    if form.is_valid():
        do_some_stuff()

from django import forms
from common.widgets import DatePicker

class SomeForm(forms.ModelForm):
    data = forms.CharField()
    when = forms.DateField()

    def __init__(self, *args, **kwargs):
        lang = kwargs.pop('lang')
        super(SomeForm, self).__init__(*args, **kwargs)
        
        self.fields['when'].widget = DatePicker(lang=lang)
import floppyforms as forms
from django.conf import settings

class DatePicker(forms.DateInput):
    template = "common/datepicker.html"

    def __init__(self, *args, **kwargs):
        self.lang = kwargs.pop('lang')
        super(DatePicker, self).__init__(*args, **kwargs)
    
    def get_context(self, name, value, attrs):
        ctx = super(DatePicker, self).get_context(name, value, attrs)
        ctx['lang'] = self.lang
        ctx['STATIC_URL'] = settings.STATIC_URL
        return ctx
The below, could be attached to python render() method in the widget, but I think this is more elegant way, and thanks to floppyforms we get native HTML5 date widget if available. I am aware of that floppyforms has an example of html5 datepicker, but the way the floppyforms author checks for date type doesn't seem to work - that's why I've used modernizr.
{# "common/datepicker.html" #}
{% include "floppyforms/input.html" %}

<script type="text/javascript">
    if (!Modernizr.inputtypes.date) {
       $(function () {
         $.datepicker.setDefaults($.datepicker.regional['{{ lang }}']);
         $('#{{ attrs.id }}').datepicker({showOn: "both", buttonImageOnly: true, dateFormat: "yy-mm-dd", buttonImage: "{{ STATIC_URL }}new/img/calendar.gif"})} )}
</script>

What the above code does is check if
<input type="date" />
is available. If not, it runs the jqueryUI calendar widget.
The javascript attached is to the specified selector. I've decided to go this way as I needed the calendar widget on ajax loaded content.
This is a Django (well.. not the sortest way).

2. Javascript way. Less django form messing.

You can achieve the similar effect without prividing both SomeForm & DatePicker widget with the lang variable.
import floppyforms as forms

class DatePicker(forms.DateInput):
    template = "common/datepicker.html"
Let the form know the widget it should use
from django import forms
from common.widgets import DatePicker

class SomeForm(models.Model):
    data = forms.CharField()
    when = forms.DateField(widget=DatePicker)
In the widget template code, you need jQuery for language detection.
{# "common/datepicker.html" #}
{% include "floppyforms/input.html" %}

<script type="text/javascript">
    if (!Modernizr.inputtypes.date) {
       
         $(function () {
         var lang = $('html').attr('lang');
         $.datepicker.setDefaults($.datepicker.regional[lang]);
         $('#{{ attrs.id }}').datepicker({showOn: "both", buttonImageOnly: true, dateFormat: "yy-mm-dd", buttonImage: "{{ STATIC_URL }}new/img/calendar.gif"})} )}
</script>
but I'm affraid this might not work on ajax loaded content where you need your calendar widget.

3. Django way, without messing the widget code too much.

Another way could be simply providing the form with data-lang attribute. The form would then look as follows:
from django import forms
from common.widgets import DatePicker

class SomeForm(forms.ModelForm):
    data = forms.CharField()
    when = forms.DateField()

    def __init__(self, *args, **kwargs)
        lang = kwargs.pop('lang')
        super(SomeForm, self).__init__(*args, **kwargs)

        self.fields['when'].widget = DatePicker(attrs={'data-lang': lang})
and then in the datepicker.html
{# "common/datepicker.html" #}
{% include "floppyforms/input.html" %}

<script type="text/javascript">
    if (!Modernizr.inputtypes.date) {
       $(function () {
         $.datepicker.setDefaults($.datepicker.regional[$('#{{ attrs.id }}').attr('data-lang'));
         $('#{{ attrs.id }}').datepicker({showOn: "both", buttonImageOnly: true, dateFormat: "yy-mm-dd", buttonImage: "{{ STATIC_URL }}new/img/calendar.gif"})} )}
</script>

The 2 ways above have no {{ STATIC_URL }} in widget template available. You need to get it yourself, via templatetag, or simply using hardcoded url.

Hope someone gets some tips from this manual

There for sure are many other ways, feel free to share them. Remember to include javascript modernizr, jQuery and jQuery files!

No comments: