2

I'd like to use jQueryUI Slider as a widget allowing to configure business hours.

So I did this models:

WEEKDAYS = [
  (1, _("Monday")),
  (2, _("Tuesday")),
  (3, _("Wednesday")),
  (4, _("Thursday")),
  (5, _("Friday")),
  (6, _("Saturday")),
  (7, _("Sunday")),
]

class OpeningHours(models.Model)
    comp = models.ForeignKey('Company')
    weekday = models.IntegerField(choices=WEEKDAYS, unique=True)
    from_hour = models.TimeField()
    to_hour = models.TimeField()

class Company(models.Model):
    name = models.CharField(max_length=100)
    logo = models.FileField(upload_to='company_logos')

I know the markup to do the controllers such as:

<div class="slider-block">
    <div class="slider-end">18:00</div>
    <div class="slider"></div>
    <div class="slider-start">08:00</div>
    <div class="slider-day">Tue</div>
</div>

Which gives something similar (with some additionnal css/js)

enter image description here

But I'm unable to implement it as a form widget

Pierre de LESPINAY
  • 44,700
  • 57
  • 210
  • 307
  • What exactly are you struggling with? – XORcist Aug 31 '12 at 15:17
  • Possibly related: http://stackoverflow.com/questions/4707192/django-how-to-build-a-custom-form-widget – XORcist Aug 31 '12 at 15:21
  • 1
    The slider isn't tied to a form input, so you'll have to add callbacks to the slider `stop` event and populate some hidden inputs accordingly so that a form can post this data. To render such a thing and the scripting required for it, you'll need to make a custom form widget and override its `render` method to add this extra DIV, hidden input, and script markup – Yuji 'Tomita' Tomita Sep 01 '12 at 00:01
  • 1
    It would be much easier to make this a one off- and hard code all of this. All you need is your form field values as hidden inputs which would need to be populated by the values in this slider widget whenever it changes. – Yuji 'Tomita' Tomita Sep 01 '12 at 00:02
  • Thank you, I didn't thought about hidden inputs populated with javascript. – Pierre de LESPINAY Sep 03 '12 at 07:07

1 Answers1

2

Thanks @YujiTomita for comments and here is my solution:

The basic idea is to generate a formset with hidden inputs, one of which is calling some css/javascript.
And javascript will populate the values.

widget

class OpeningHoursWidget(forms.HiddenInput):
    class Media:
        js = (
            'https://ajax.googleapis.com/[...]jquery-ui.min.js',
            'js/business_hours.js',
        )
        css = {'all': (
            'https://ajax.googleapis.com/[...]jquery-ui.custom.css',
            'css/business_hours.css',
        )}

form

class UserOpeningHoursForm(forms.ModelForm):
    class Meta:
        model = OpeningHours
        fields = ('weekday', 'from_hour', 'to_hour')
        widgets = {
            'weekday': forms.HiddenInput(attrs={'class': 'hours-weekday'}),
            'from_hour': OpeningHoursWidget(attrs={'class': 'hours-start'}),
            'to_hour': forms.HiddenInput(attrs={'class': 'hours-end'}),
        }

view

UserOpeningHoursFormSet = formset_factory(UserOpeningHoursForm, extra=0)
obj['formset'] = UserOpeningHoursFormSet(initial=hours)

template

    <div id="slider-block-hidden">
        <div class="slider-end"></div>
        <div class="slider"></div>
        <div class="slider-start"></div>
        <div class="slider-day"></div>
    </div>
{{ formset.media }}
{% for hours in formset %}
    <div class="slider-block">
    {{ hours }}
    </div>
{% endfor %}

javascript (with jquery)

$(function() {
    days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
    $('.slider-block').each(function () {
        start = $(this).find('.hours-start').val();
        end = $(this).find('.hours-end').val();
        day_id = $(this).find('.hours-weekday').val();
        hidden_slider = $('#slider-block-hidden').clone();
        $(this).append(hidden_slider.find('.slider-end').text(end));
        $(this).append((slider = hidden_slider.find('.slider')));
        $(this).append(hidden_slider.find('.slider-start').text(start));
        $(this).append(hidden_slider.find('.slider-day').text(days[day_id]));
        slider.slider({
            orientation: "vertical",
            range: true,
            min: 0,
            max: 1440,
            step: 15,
            values: [ ttm(start), ttm(end) ],
            slide: function( event, ui ) {
                $(this).siblings('.slider-start').text(mtt(ui.values[0]));
                $(this).siblings('.slider-end').text(mtt(ui.values[1]));
            }
        });
    });
});

ttm() and mtt() are functions for minutes-time conversions/formatting

css

#slider-block-hidden {
    display: none;
}

.slider-block {
    text-align: center;
    display: inline-block;
    margin: 10px;
}

.slider {
    margin: 10px;
    height: 150px;
}

Maybe there would be a way to subclass widget's render to avoid much javascript.

Pierre de LESPINAY
  • 44,700
  • 57
  • 210
  • 307