forked from 0x2620/oxjs
more calendar improvements (keyboard navigation etc.)
This commit is contained in:
parent
b18500d133
commit
c661f3a883
4 changed files with 418 additions and 165 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue