1
0
Fork 0
forked from 0x2620/oxjs

more calendar improvements (keyboard navigation etc.)

This commit is contained in:
rolux 2011-05-25 18:36:55 +02:00
commit c661f3a883
4 changed files with 418 additions and 165 deletions

View file

@ -35,6 +35,7 @@ Ox.Calendar = function(options, self) {
height: 256,
range: [1000, 3000],
selected: '',
showTypes: ['date', 'place', 'person', 'other'],
width: 256,
zoom: 8
})
@ -43,6 +44,32 @@ Ox.Calendar = function(options, self) {
.css({
width: self.options.width + 'px',
height: self.options.height + 'px'
})
.bindEvent({
anyclick: function() {
that.gainFocus();
},
key_0: function() {
panToSelected();
},
key_equal: function() {
zoomBy(1);
},
key_escape: function() {
selectEvent('');
},
key_left: function() {
panBy(-self.$content.width() / 2 * getSecondsPerPixel() * 1000);
},
key_minus: function() {
zoomBy(-1);
},
key_right: function() {
panBy(self.$content.width() / 2 * getSecondsPerPixel() * 1000);
},
key_shift_0: function() {
zoomToSelected();
}
});
self.options.events.forEach(function(event) {
@ -231,6 +258,35 @@ Ox.Calendar = function(options, self) {
}
];
self.$toolbar = Ox.Bar({
size: 24
})
.appendTo(that);
Ox.print(self.options.showTypes)
self.$typeSelect = Ox.Select({
items: [
{id: 'date', title: 'Dates', checked: self.options.showTypes.indexOf('date') > -1},
{id: 'place', title: 'Places', checked: self.options.showTypes.indexOf('place') > -1},
{id: 'person', title: 'People', checked: self.options.showTypes.indexOf('person') > -1},
{id: 'other', title: 'Other', checked: self.options.showTypes.indexOf('other') > -1}
],
max: -1,
min: 1,
title: 'Show...',
width: 80
})
.css({margin: '4px'})
.bindEvent({
change: function(data) {
self.options.showTypes = data.selected.map(function(type) {
return type.id;
});
renderCalendar();
}
})
.appendTo(self.$toolbar);
self.$container = new Ox.Element()
.addClass('OxCalendarContainer')
.css({
@ -456,28 +512,51 @@ Ox.Calendar = function(options, self) {
}
function formatEvent(event) {
return formatEventRange(event) + '<br/>' + formatEventDuration(event);
}
function formatEventDuration(event) {
// fixme: still wrong because of different number of leap days
var date = new Date(getEventDuration(event)),
strings = [],
values = {
years: date.getUTCFullYear() - 1970,
days: Ox.getDayOfTheYear(date, true) - 1,
hours: date.getUTCHours(),
minutes: date.getUTCMinutes(),
seconds: date.getUTCMilliseconds()
};
//Ox.print('****', values);
['year', 'day', 'hour', 'minute', 'second'].forEach(function(key) {
var value = values[key + 's'];
value && strings.push(value + ' ' + key + (value > 1 ? 's' : ''));
});
return strings.join(' ');
}
function formatEventRange(event) {
var isFullDays = Ox.formatDate(event.start, '%H:%M:%S', true) == '00:00:00' &&
Ox.formatDate(event.end, '%H:%M:%S', true) == '00:00:00',
isOneDay = isFullDays && event.end - event.start == 86400000, // fixme: wrong, DST
isOneDay = isFullDays && getEventDuration(event) == 86400000, // fixme: wrong, DST
isSameDay = Ox.formatDate(event.start, '%Y-%m-%d', true) ==
Ox.formatDate(event.end, '%Y-%m-%d', true),
isSameYear = event.start.getUTCFullYear() == event.end.getUTCFullYear(),
timeFormat = isFullDays ? '' : ', %H:%M:%S',
str = Ox.formatDate(event.start, '%a, %b %e', true);
string = Ox.formatDate(event.start, '%a, %b %e', true);
if (isOneDay || isSameDay || !isSameYear) {
str += Ox.formatDate(event.start, ', %Y' + timeFormat, true);
string += Ox.formatDate(event.start, ', %Y' + timeFormat, true);
}
if (!isOneDay && !isSameDay) {
str += Ox.formatDate(event.end, ' - %a, %b %e, %Y' + timeFormat, true);
string += Ox.formatDate(event.end, ' - %a, %b %e, %Y' + timeFormat, true);
}
if (isSameDay) {
str += Ox.formatDate(event.end, ' - ' + timeFormat.replace(', ', ''), true);
string += Ox.formatDate(event.end, ' - ' + timeFormat.replace(', ', ''), true);
}
return str;
return string;
}
function getCalendarEvent() {
var ms = self.options.width * getSecondsPerPixel() * 1000;
function getCalendarEvent(zoom) {
var ms = self.options.width * getSecondsPerPixel(zoom || self.options.zoom) * 1000;
return {
start: new Date(+self.options.date - ms / 2),
end: new Date(+self.options.date + ms / 2)
@ -495,13 +574,23 @@ Ox.Calendar = function(options, self) {
return event;
}
function getEventCenter(event) {
return new Date(+event.start + getEventDuration(event) / 2);
}
function getEventDuration(event) {
return event.end - event.start;
}
function getEventElement(event, zoom) {
var left = getPosition(event.start, zoom),
width = Math.max(getPosition(event.end, zoom) - left, 1);
Ox.print(event)
return new Ox.Element()
.addClass('OxEvent' + (
event.id == self.options.selected ? ' OxSelected' : ''
))
.addClass('OxEvent' +
(event.type ? ' Ox' + Ox.toTitleCase(event.type) : '' ) +
(event.id == self.options.selected ? ' OxSelected' : '')
)
.css({
left: left + 'px',
width: width + 'px'
@ -513,7 +602,15 @@ Ox.Calendar = function(options, self) {
}
function getEventElementById(id) {
var $element;
$('.OxLine > .OxEvent').each(function() {
var $this = $(this);
if ($this.data('id') == id) {
$element = $this;
return false;
}
});
return $element;
};
function getMouseDate(e) {
@ -538,6 +635,14 @@ Ox.Calendar = function(options, self) {
return 1 / getPixelsPerSecond(zoom);
}
function getSelectedEvent() {
var event = null;
if (self.options.selected !== '') {
event = getEventById(self.options.selected);
}
return event;
}
function getBackgroundElements(zoom) {
// fixme: duplicated
var $elements = [],
@ -634,9 +739,7 @@ Ox.Calendar = function(options, self) {
self.options.date = deltaZ == -1 ?
new Date(2 * +self.options.date - +getMouseDate(e)) :
new Date((+self.options.date + +getMouseDate(e)) / 2)
self.options.zoom += deltaZ;
self.$zoomInput.options({value: self.options.zoom});
renderCalendar();
zoomBy(deltaZ);
}
}
self.mousewheel = true;
@ -653,6 +756,38 @@ Ox.Calendar = function(options, self) {
);
}
function panBy(ms) {
Ox.print('panBY', ms)
panTo(new Date(+self.options.date + ms));
}
function panTo(date) {
var delta = (date - self.options.date) / 1000 * getPixelsPerSecond(),
ms = 250 * Math.max(Math.abs(delta) / (self.$content.width() / 2), 1);
self.$content.animate({
marginLeft: -delta + 'px'
}, ms, function() {
self.options.date = date;
renderCalendar();
self.$content.css({
marginLeft: 0
});
});
};
function panToSelected() {
if (self.options.selected !== '') {
panTo(getEventCenter(getSelectedEvent()));
}
}
function renderBackground() {
getBackgroundElements(self.options.zoom).forEach(function($element) {
$element.appendTo(self.$background);
});
}
function renderCalendar() {
$('.OxBackground').empty();
$('.OxEvent').remove();
@ -664,21 +799,24 @@ Ox.Calendar = function(options, self) {
);
}
function renderBackground() {
getBackgroundElements(self.options.zoom).forEach(function($element) {
$element.appendTo(self.$background);
});
}
function renderEvents() {
var calendarEvent = getCalendarEvent();
lineEvents = [];
lineEvents = [],
types = ['date']; //['date', 'place', 'person', 'other'];
self.options.events.filter(function(event) {
// filter out events outside the visible area
return overlaps(event, calendarEvent);
// filter out events with types not shown
// and events outside the visible area
return self.options.showTypes.indexOf(event.type) > -1
&& overlaps(event, calendarEvent);
}).sort(function(a, b) {
// sort events by duration, descending
return (b.end - b.start) - (a.end - a.start);
// sort events
if (a.type != b.type) {
return types.indexOf(b.type) - types.indexOf(a.type);
} else if (a.start < b.start || a.start > b.start) {
return a.start - b.start;
} else {
return (b.end - b.start) - (a.end - a.start);
}
}).forEach(function(event, i) {
var line = lineEvents.length;
// traverse lines
@ -729,23 +867,64 @@ Ox.Calendar = function(options, self) {
});
}
function selectEvent(id) {
function selectEvent(id, $element) {
self.$content.find('.OxSelected').removeClass('OxSelected');
if (id) {
self.options.selected = id;
($element || getEventElementById(id)).addClass('OxSelected');
// fixme: map event should also be 'select', not 'selectplace'
that.triggerEvent('select', {id: id});
} else {
if (self.options.selected !== '') {
self.options.selected = '';
that.triggerEvent('select', {id: ''});
}
}
}
function singleclick(event, e) {
var $target = $(e.target),
id = $target.data('id');
if ($target.is('.OxLine > .OxEvent')) {
self.options.selected = id;
self.$content.find('.OxSelected').removeClass('OxSelected');
$target.addClass('OxSelected');
// fixme: map event should also be 'select', not 'selectplace'
that.triggerEvent('select', {
id: id
});
selectEvent(id, $target);
} else {
self.options.date = getMouseDate(e);
renderCalendar();
selectEvent('');
panTo(getMouseDate(e));
}
}
function zoomBy(delta) {
zoomTo(self.options.zoom + delta);
}
function zoomTo(zoom) {
self.options.zoom = zoom;
self.$zoomInput.options({value: zoom});
renderCalendar();
}
function zoomToSelected() {
if (self.options.selected !== '') {
var event = getSelectedEvent(),
eventDuration = getEventDuration(event),
zoom = getZoom();
if (zoom == self.options.zoom) {
panToSelected();
} else {
self.options.date = getEventCenter(event);
zoomTo(zoom);
}
}
function getZoom() {
var zoom;
Ox.loop(32, 0, function(z) {
var calendarDuration = getEventDuration(getCalendarEvent(z));
if (calendarDuration > eventDuration) {
zoom = z;
return false;
}
});
return zoom;
}
}