app/assets/javascripts/slices/app/views/calendar_view.js
// Calendar View
slices.CalendarView = Backbone.View.extend({
// -- Config --
events: {
'click [data-action="hide"]' : 'hide',
'click [data-action="prev-month"]' : 'prevMonth',
'click [data-action="next-month"]' : 'nextMonth',
'mousedown time' : 'onClickDay'
},
className: 'calendar-modal',
template: Handlebars.compile(
'<span class="background" data-action="hide"></span>' +
'<div class="calendar">' +
'<table>' +
'<thead>' +
'<tr>' +
'<td data-action="prev-month">←</td>' +
'<th class="month" colspan="5">{{monthName}}</th>' +
'<td data-action="next-month">→</td>' +
'</tr>' +
'<tr>' +
'<th class="weekday">M</th>' +
'<th class="weekday">T</th>' +
'<th class="weekday">W</th>' +
'<th class="weekday">T</th>' +
'<th class="weekday">F</th>' +
'<th class="weekday">S</th>' +
'<th class="weekday">S</th>' +
'</tr>' +
'</thead>' +
'<tbody>{{{body}}}</tbody>' +
'</table>' +
'</div>'
),
bodyTemplate: Handlebars.compile(
'{{#each this}}' +
'<tr>' +
'{{#each this}}' +
'<td>' +
'<time class="{{klass}}" datetime="{{value}}">' +
'{{date}}' +
'</time>' +
'</td>' +
'{{/each}}' +
'</tr>' +
'{{/each}}'
),
// -- Init --
initialize: function() {
_.bindAll(this);
$(document).on('slices:willSubmit', this.remove);
this.setValue(this.options.value);
this.$el.appendTo('body');
this.$el.attr('data-state', 'preparing');
this.$el.hide();
},
// -- Rendering --
render: function() {
this.$el.html(this.template(this));
this.updatePosition();
return this;
},
body: function() {
var data = [],
today = moment().startOf('day'),
thisMonth = this.month.clone(),
nextMonth = thisMonth.clone().add('months', 1),
firstMonday = thisMonth.clone().day(1),
finalSunday = nextMonth.clone().day(7),
date = firstMonday.clone();
while (date <= finalSunday) {
if (date.day() === 1) data.push([]);
var obj = {
date : date.date(),
value : date.format(),
klass : []
};
if (date.is(today)) obj.klass.push('today');
else if (date < thisMonth) obj.klass.push('prev-month');
else if (date >= nextMonth) obj.klass.push('next-month');
if (this.value) {
if (date.is(this.value.clone().startOf('day'))) obj.klass.push('highlight');
}
obj.klass = obj.klass.join(' ');
data[data.length - 1].push(obj);
date.add('days', 1);
}
return this.bodyTemplate(data);
},
monthName: function() {
return this.month.format('MMMM YYYY');
},
// -- Event Handlers --
onClickDay: function(event) {
var day = $(event.target),
value = moment(day.attr('datetime'));
if (this.value) {
value.hours(this.value.hours()).minutes(this.value.minutes());
}
this.setValue(value);
this.render();
this.options.onChange && this.options.onChange();
},
// -- Actions --
show: function() {
this.resetMonth();
this.$el.show();
this.render();
this.updatePosition();
this.$el.attr('data-state', 'showing');
},
hide: function() {
this.$el.attr('data-state', 'hiding');
var self = this;
setTimeout(function() { self.$el.hide() }, 250);
},
toggle: function() {
switch (this.$el.data('state')) {
case 'preparing':
case 'hiding':
this.show(); break;
case 'showing':
this.hide(); break;
}
},
prevMonth: function() {
this.month.subtract('months', 1);
this.render();
},
nextMonth: function() {
this.month.add('months', 1);
this.render();
},
setValue: function(newValue) {
this.value = moment(newValue);
this.resetMonth();
},
resetMonth: function() {
this.month = (this.value || moment()).clone().startOf('month');
},
remove: function() {
this.$el.remove();
},
// -- Helpers --
updatePosition: function() {
var calendar = this.$el.find('.calendar'),
anchor = this.anchor,
offset = anchor.offset();
offset.top += anchor.outerHeight();
offset.top = offset.top + 'px';
offset.left += anchor.outerWidth() / 2;
offset.left -= calendar.outerWidth() / 2;
offset.left = offset.left + 'px';
calendar.css(offset);
}
});