calendar improvements (api, css, utc)

This commit is contained in:
rolux 2011-05-25 11:22:16 +02:00
commit b18500d133
5 changed files with 253 additions and 194 deletions

View file

@ -133,7 +133,7 @@ Calendar
position: absolute;
}
.OxCalendar .OxDate {
.OxCalendar .OxEvent {
position: absolute;
height: 16px;
text-overflow: ellipsis;
@ -141,9 +141,15 @@ Calendar
white-space: nowrap;
cursor: pointer;
}
.OxCalendar .OxLine > .OxDate {
background: -moz-linear-gradient(top, rgba(255, 64, 64, 0.75), rgba(255, 0, 0, 0.75));
background: -webkit-gradient(linear, left top, left bottom, from(rgba(255, 32, 32, 1)), to(rgba(224, 0, 0, 1)));
.OxCalendar .OxLine > .OxEvent {
border-radius: 2px;
background: -moz-linear-gradient(top, rgba(255, 32, 32, 0.9), rgba(224, 0, 0, 0.9));
background: -webkit-linear-gradient(top, rgba(255, 32, 32, 0.9), rgba(224, 0, 0, 0.9));
color: rgb(255, 255, 255);
}
.OxCalendar .OxLine > .OxEvent.OxSelected {
background: -moz-linear-gradient(top, rgba(128, 128, 255, 0.9), rgba(112, 112, 224, 0.9));
background: -webkit-linear-gradient(top, rgba(128, 128, 255, 0.9), rgba(112, 112, 224, 0.9));
}
.OxCalendar .OxTimeline {
@ -152,7 +158,7 @@ Calendar
//overflow: hidden;
}
.OxCalendar .OxTimeline .OxDate {
.OxCalendar .OxTimeline .OxEvent {
position: absolute;
border-radius: 0;
cursor: ew-resize;

View file

@ -7,21 +7,23 @@ Ox.Calendar <f:Ox.Element> Basic calendar object
(options, self) -> <f> Calendar object
options <o> Options object
date <d|new Date()> UTC Date on which the calendar is centered
dates <[o]|[]> Date objects to be displayed
end <s> End of the event (UTC Date, as string)
name <s> Name of the event
start <s> Start of the event (UTC Date, as string)
type <s> Type of the event (like "person")
events <[o]|[]> Event objects to be displayed
alternativeNames <[s]> Array of alternative names
end <s> End of the event (UTC Date, as string)
id <s> Id of the event
name <s> Name of the event
start <s> Start of the event (UTC Date, as string)
type <s> Type of the event (like "person")
height <n|256> Height in px
range <[n]|[100, 5101]> Start and end year of the calendar
selected <s|''> Id of the selected event
width <n|256> Width in px
zoom <n|8> Initial zoom level
self <o> Shared private variable
@*/
// Fixme: switch to UTC
// Fixme: in options.dates, replace "stop" with "end"
// Fixme: create a variable-resolution date type (with end that is _inclusive_)
// Fixme: create a variable-resolution event type (with end that is _inclusive_)
Ox.Calendar = function(options, self) {
@ -29,9 +31,10 @@ Ox.Calendar = function(options, self) {
var that = new Ox.Element({}, self)
.defaults({
date: new Date(),
dates: [],
events: [],
height: 256,
range: [1000, 3000],
selected: '',
width: 256,
zoom: 8
})
@ -42,6 +45,12 @@ Ox.Calendar = function(options, self) {
height: self.options.height + 'px'
});
self.options.events.forEach(function(event) {
event.id = Ox.isUndefined(event.id) ? Ox.uid() : event.id;
event.start = Ox.parseDate(event.start, true);
event.end = Ox.parseDate(event.end, true);
});
self.maxZoom = 32;
self.minLabelWidth = 80;
self.overlayWidths = [Math.round(self.options.width / 16)];
@ -346,7 +355,7 @@ Ox.Calendar = function(options, self) {
}
function doubleclick(event, e) {
if ($(e.target).is(':not(.OxLine > .OxDate)')) {
if ($(e.target).is(':not(.OxLine > .OxEvent)')) {
if (self.options.zoom < self.maxZoom) {
self.options.date = new Date(
(+self.options.date + +getMouseDate(e)) / 2
@ -358,7 +367,7 @@ Ox.Calendar = function(options, self) {
}
function dragstart(event, e) {
if ($(e.target).is(':not(.OxLine > .OxDate)')) {
if ($(e.target).is(':not(.OxLine > .OxEvent)')) {
self.drag = {x: e.clientX};
}
}
@ -446,61 +455,67 @@ Ox.Calendar = function(options, self) {
renderCalendar();
}
function formatDate(date) {
var isFullDays = Ox.formatDate(date.start, '%H:%M:%S') == '00:00:00' &&
Ox.formatDate(date.stop, '%H:%M:%S') == '00:00:00',
isOneDay = isFullDays && date.stop - date.start == 86400000, // fixme: wrong, DST
isSameDay = Ox.formatDate(date.start, '%Y-%m-%d') ==
Ox.formatDate(date.stop, '%Y-%m-%d'),
isSameYear = date.start.getFullYear() == date.stop.getFullYear(),
function formatEvent(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
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(date.start, '%a, %b %e');
str = Ox.formatDate(event.start, '%a, %b %e', true);
if (isOneDay || isSameDay || !isSameYear) {
str += Ox.formatDate(date.start, ', %Y' + timeFormat);
str += Ox.formatDate(event.start, ', %Y' + timeFormat, true);
}
if (!isOneDay && !isSameDay) {
str += Ox.formatDate(date.stop, ' - %a, %b %e, %Y' + timeFormat);
str += Ox.formatDate(event.end, ' - %a, %b %e, %Y' + timeFormat, true);
}
if (isSameDay) {
str += Ox.formatDate(date.stop, ' - ' + timeFormat.replace(', ', ''));
str += Ox.formatDate(event.end, ' - ' + timeFormat.replace(', ', ''), true);
}
return str;
}
function getCalendarDate() {
function getCalendarEvent() {
var ms = self.options.width * getSecondsPerPixel() * 1000;
return {
start: new Date(+self.options.date - ms / 2),
stop: new Date(+self.options.date + ms / 2)
end: new Date(+self.options.date + ms / 2)
};
}
function getDateByName(name) {
var date = {};
Ox.forEach(self.options.dates, function(v) {
if (v.name == name) {
date = v;
function getEventById(id) {
var event = null;
Ox.forEach(self.options.events, function(v) {
if (v.id == id) {
event = v;
return false;
}
});
return date;
return event;
}
function getDateElement(date, zoom) {
var left = getPosition(date.start, zoom),
width = Math.max(getPosition(date.stop, zoom) - left, 1);
function getEventElement(event, zoom) {
var left = getPosition(event.start, zoom),
width = Math.max(getPosition(event.end, zoom) - left, 1);
return new Ox.Element()
.addClass('OxDate')
.addClass('OxEvent' + (
event.id == self.options.selected ? ' OxSelected' : ''
))
.css({
left: left + 'px',
width: width + 'px'
})
.data({
name: date.name
id: event.id
})
.html('&nbsp;' + date.name);
.html('&nbsp;' + event.name);
}
function getEventElementById(id) {
};
function getMouseDate(e) {
return new Date(+self.options.date + (
e.clientX - that.offset().left - self.options.width / 2 - 1
@ -557,10 +572,10 @@ Ox.Calendar = function(options, self) {
n = Math.ceil(self.options.width * 1.5/* * 16*/ / width);
Ox.loop(-n, n + 1, function(i) {
$elements.push(
getDateElement({
getEventElement({
name: unit.name(value + i),
start: new Date(unit.date(value + i)),
stop: new Date(unit.date(value + i + 1))
end: new Date(unit.date(value + i + 1))
}, zoom)
.addClass(Ox.mod(value + i, 2) == 0 ? 'even' : 'odd')
);
@ -592,11 +607,11 @@ Ox.Calendar = function(options, self) {
function mousemove(e) {
var $target = $(e.target),
date, title;
if ($target.is('.OxLine > .OxDate')) {
date = getDateByName($target.data('name'));
title = '<span class="OxBright">' + date.name + '</span><br/>' +
formatDate(date);
event, title;
if ($target.is('.OxLine > .OxEvent')) {
event = getEventById($target.data('id'));
title = '<span class="OxBright">' + event.name + '</span><br/>' +
formatEvent(event);
} else {
title = Ox.formatDate(getMouseDate(e), '%a, %b %e, %Y, %H:%M:%S');
}
@ -630,20 +645,20 @@ Ox.Calendar = function(options, self) {
}, 250);
}
function overlaps(date0, date1) {
function overlaps(eventA, eventB) {
return (
date0.start >= date1.start && date0.start < date1.stop
eventA.start >= eventB.start && eventA.start < eventB.end
) || (
date1.start >= date0.start && date1.start < date0.stop
eventB.start >= eventA.start && eventB.start < eventA.end
);
}
function renderCalendar() {
$('.OxBackground').empty();
$('.OxDate').remove();
$('.OxEvent').remove();
renderBackground();
renderTimelines();
renderDates();
renderEvents();
self.$statusbar.html(
Ox.formatDate(self.options.date, '%a, %b %e, %Y, %H:%M:%S (%s)', true)
);
@ -655,24 +670,24 @@ Ox.Calendar = function(options, self) {
});
}
function renderDates() {
var calendarDate = getCalendarDate();
lineDates = [];
self.options.dates.filter(function(date) {
// filter out dates outside the visible area
return overlaps(date, calendarDate);
function renderEvents() {
var calendarEvent = getCalendarEvent();
lineEvents = [];
self.options.events.filter(function(event) {
// filter out events outside the visible area
return overlaps(event, calendarEvent);
}).sort(function(a, b) {
// sort dates by duration, descending
return (b.stop - b.start) - (a.stop - a.start);
}).forEach(function(date, i) {
var line = lineDates.length;
// sort events by duration, descending
return (b.end - b.start) - (a.end - a.start);
}).forEach(function(event, i) {
var line = lineEvents.length;
// traverse lines
Ox.forEach(lineDates, function(dates, line_) {
Ox.forEach(lineEvents, function(events, line_) {
var fits = true;
// traverse dates in line
Ox.forEach(dates, function(date_) {
// traverse events in line
Ox.forEach(events, function(event_) {
// if overlaps, check next line
if (overlaps(date, date_)) {
if (overlaps(event, event_)) {
fits = false;
return false;
}
@ -682,24 +697,24 @@ Ox.Calendar = function(options, self) {
return false;
}
});
if (line == lineDates.length) {
lineDates[line] = [];
if (line == lineEvents.length) {
lineEvents[line] = [];
}
lineDates[line].push(date);
lineEvents[line].push(event);
});
$('.OxLine').remove();
lineDates.forEach(function(dates, line) {
lineEvents.forEach(function(events, line) {
var $line = new Ox.Element()
.addClass('OxLine')
.css({
top: ((line + 1) * 16) + 'px'
})
.appendTo(self.$content);
dates.sort(function(a, b) {
// sort dates by start, ascending
events.sort(function(a, b) {
// sort events by start, ascending
return a.start - b.start;
}).forEach(function(date) {
getDateElement(date).appendTo($line);
}).forEach(function(event) {
getEventElement(event).appendTo($line);
});
});
}
@ -714,8 +729,21 @@ Ox.Calendar = function(options, self) {
});
}
function selectEvent(id) {
}
function singleclick(event, e) {
if ($(e.target).is(':not(.OxLine > .OxDate)')) {
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
});
} else {
self.options.date = getMouseDate(e);
renderCalendar();
}

View file

@ -7,6 +7,7 @@ Ox.CalendarDate <f> CalendarDate
start <d> start date
stop <d> stop date
@*/
// fixme: unused
Ox.CalendarDate = function(options) {
var self = {},

View file

@ -1445,6 +1445,27 @@ Ox.makeYear = function(date, utc) {
return Ox.isDate(date) ? Ox.getFullYear(date, utc) : parseInt(date);
};
/*@
Ox.parseDate(f) Takes a string ('YYYY-MM-DD HH:MM:SS') and returns a date
str <s> string
utc <b|false> If true, Date is UTC
> +Ox.parseDate('1970', true)
0
@*/
Ox.parseDate = function(str, utc) {
var def = [, 1, 1, 0, 0, 0];
val = /(\d+)-?(\d+)?-?(\d+)? ?(\d+)?:?(\d+)?:?(\d+)?/(str);
val.shift();
val = val.map(function(v, i) {
return v || def[i];
});
val[1]--;
return utc
? new Date(Date.UTC(val[0], val[1], val[2], val[3], val[4], val[5]))
: new Date(val[0], val[1], val[2], val[3], val[4], val[5])
};
//@ Ox.setDate <f> Set the day of a date, optionally UTC
// see Ox.setSeconds for source code
//@ Ox.setDay <f> Set the weekday of a date, optionally UTC