7240 lines
271 KiB
JavaScript
7240 lines
271 KiB
JavaScript
/**
|
|
* kronolith.js - Base application logic.
|
|
*
|
|
* TODO: loadingImg()
|
|
*
|
|
* Copyright 2008-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (GPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/gpl.
|
|
*
|
|
* @author Jan Schneider <jan@horde.org>
|
|
*/
|
|
|
|
/* Kronolith object. */
|
|
KronolithCore = {
|
|
// Vars used and defaulting to null/false:
|
|
// weekSizes, daySizes,
|
|
// groupLoading, colorPicker, duration, timeMarker, monthDays,
|
|
// allDays, eventsWeek, initialized
|
|
|
|
view: '',
|
|
ecache: $H(),
|
|
cacheStart: null,
|
|
cacheEnd: null,
|
|
holidays: [],
|
|
tcache: $H(),
|
|
eventsLoading: {},
|
|
loading: 0,
|
|
viewLoading: [],
|
|
fbLoading: 0,
|
|
redBoxLoading: false,
|
|
date: Date.today(),
|
|
tasktype: 'incomplete',
|
|
knl: {},
|
|
wrongFormat: $H(),
|
|
mapMarker: null,
|
|
map: null,
|
|
mapInitialized: false,
|
|
freeBusy: $H(),
|
|
search: 'future',
|
|
effectDur: 0.4,
|
|
macos: navigator.appVersion.indexOf('Mac') != -1,
|
|
orstart: null,
|
|
orend: null,
|
|
lastRecurType: 'None',
|
|
uatts: null,
|
|
ucb: null,
|
|
resourceACCache: { choices: [], map: $H() },
|
|
paramsCache: null,
|
|
attendees: [],
|
|
resources: [],
|
|
|
|
/**
|
|
* Flag that indicates if the event currently displayed in the event
|
|
* properties window is a recurring event.
|
|
*
|
|
* @type boolean
|
|
*/
|
|
recurs: false,
|
|
|
|
/**
|
|
* The location that was open before the current location.
|
|
*
|
|
* @var string
|
|
*/
|
|
lastLocation: '',
|
|
|
|
/**
|
|
* The currently open location.
|
|
*
|
|
* @var string
|
|
*/
|
|
openLocation: '',
|
|
|
|
/**
|
|
* The current (main) location.
|
|
*
|
|
* This is different from openLocation as it isn't updated for any
|
|
* locations that are opened in a popup view, e.g. events.
|
|
*
|
|
* @var string
|
|
*/
|
|
currentLocation: '',
|
|
|
|
kronolithBody: $('kronolithBody'),
|
|
|
|
onException: function(parentfunc, r, e)
|
|
{
|
|
/* Make sure loading images are closed. */
|
|
this.loading--;
|
|
if (!this.loading) {
|
|
$('kronolithLoading').hide();
|
|
}
|
|
this.closeRedBox();
|
|
HordeCore.notify(HordeCore.text.ajax_error, 'horde.error');
|
|
parentfunc(r, e);
|
|
},
|
|
|
|
setTitle: function(title)
|
|
{
|
|
document.title = Kronolith.conf.name + ' :: ' + title;
|
|
return title;
|
|
},
|
|
|
|
// url = (string) URL to redirect to
|
|
// hash = (boolean) If true, url is treated as hash information to alter
|
|
// on the current page
|
|
redirect: function(url, hash)
|
|
{
|
|
if (hash) {
|
|
window.location.hash = escape(url);
|
|
window.location.reload();
|
|
} else {
|
|
HordeCore.redirect(url);
|
|
}
|
|
},
|
|
|
|
go: function(fullloc, data)
|
|
{
|
|
if (!this.initialized) {
|
|
this.go.bind(this, fullloc, data).defer();
|
|
return;
|
|
}
|
|
|
|
if (this.viewLoading.size()) {
|
|
this.viewLoading.push([ fullloc, data ]);
|
|
return;
|
|
}
|
|
|
|
var locParts = fullloc.split(':');
|
|
var loc = locParts.shift();
|
|
|
|
if (this.openLocation == fullloc) {
|
|
return;
|
|
}
|
|
|
|
this.viewLoading.push([ fullloc, data ]);
|
|
|
|
if (loc != 'search') {
|
|
HordeTopbar.searchGhost.reset();
|
|
}
|
|
|
|
this.switchTaskView(false);
|
|
|
|
switch (loc) {
|
|
case 'day':
|
|
case 'week':
|
|
case 'workweek':
|
|
case 'month':
|
|
case 'year':
|
|
case 'agenda':
|
|
case 'tasks':
|
|
this.closeView(loc);
|
|
var locCap = loc.capitalize();
|
|
$('kronolithNav' + locCap).up().addClassName('horde-active');
|
|
|
|
switch (loc) {
|
|
case 'day':
|
|
case 'agenda':
|
|
case 'week':
|
|
case 'workweek':
|
|
case 'month':
|
|
case 'year':
|
|
var date = locParts.shift();
|
|
if (date) {
|
|
date = this.parseDate(date);
|
|
} else {
|
|
date = this.date;
|
|
}
|
|
|
|
if (this.view != 'agenda' &&
|
|
this.view == loc &&
|
|
date.getYear() == this.date.getYear() &&
|
|
((loc == 'year') ||
|
|
(loc == 'month' && date.getMonth() == this.date.getMonth()) ||
|
|
((loc == 'week' || loc == 'workweek') && date.getRealWeek() == this.date.getRealWeek()) ||
|
|
((loc == 'day' || loc == 'agenda') && date.dateString() == this.date.dateString()))) {
|
|
this.setViewTitle(date, loc);
|
|
this.addHistory(fullloc);
|
|
this.loadNextView();
|
|
return;
|
|
}
|
|
|
|
this.addHistory(fullloc);
|
|
this.view = loc;
|
|
this.date = date;
|
|
this.updateView(date, loc);
|
|
var dates = this.viewDates(date, loc);
|
|
this.loadEvents(dates[0], dates[1], loc);
|
|
$('kronolithView' + locCap).appear({
|
|
duration: this.effectDur,
|
|
queue: 'end',
|
|
afterFinish: function() {
|
|
if (loc == 'week' || loc == 'workweek' || loc == 'day') {
|
|
this.calculateRowSizes(loc + 'Sizes', 'kronolithView' + locCap);
|
|
if ($('kronolithTimeMarker')) {
|
|
this.positionTimeMarker();
|
|
}
|
|
if ($('kronolithTimeMarker')) {
|
|
$('kronolithTimeMarker').show();
|
|
}
|
|
// Scroll to the work day start time.
|
|
$('kronolithView' + locCap).down('.kronolithViewBody').scrollTop = 9 * this[loc + 'Sizes'].height;
|
|
}
|
|
this.loadNextView();
|
|
}.bind(this)
|
|
});
|
|
$('kronolithLoading' + loc).insert($('kronolithLoading').remove());
|
|
this.updateMinical(date, loc);
|
|
|
|
break;
|
|
|
|
case 'tasks':
|
|
var tasktype = locParts.shift() || this.tasktype;
|
|
|
|
|
|
this.switchTaskView(true);
|
|
$('kronolithCurrent')
|
|
.update(this.setTitle(Kronolith.text.tasks));
|
|
if (this.view == loc && this.tasktype == tasktype) {
|
|
this.addHistory(fullloc);
|
|
this.loadNextView();
|
|
return;
|
|
}
|
|
if (!$w('all complete incomplete future').include(tasktype)) {
|
|
this.loadNextView();
|
|
return;
|
|
}
|
|
|
|
this.addHistory(fullloc);
|
|
this.view = loc;
|
|
this.tasktype = tasktype;
|
|
$w('All Complete Incomplete Future').each(function(tasktype) {
|
|
$('kronolithTasks' + tasktype).up().removeClassName('horde-active');
|
|
});
|
|
$('kronolithTasks' + this.tasktype.capitalize()).up().addClassName('horde-active');
|
|
this.loadTasks(this.tasktype);
|
|
$('kronolithView' + locCap).appear({
|
|
duration: this.effectDur,
|
|
queue: 'end',
|
|
afterFinish: function() {
|
|
this.loadNextView();
|
|
}.bind(this) });
|
|
$('kronolithLoading' + loc).insert($('kronolithLoading').remove());
|
|
this.updateMinical(this.date);
|
|
|
|
break;
|
|
|
|
default:
|
|
if (!$('kronolithView' + locCap)) {
|
|
break;
|
|
}
|
|
this.addHistory(fullloc);
|
|
this.view = loc;
|
|
$('kronolithView' + locCap).appear({
|
|
duration: this.effectDur,
|
|
queue: 'end',
|
|
afterFinish: function() {
|
|
this.loadNextView();
|
|
}.bind(this) });
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case 'search':
|
|
var cals = [], time = locParts[0], term = locParts[1],
|
|
query = Object.toJSON({ title: term });
|
|
|
|
if (!($w('all past future').include(time))) {
|
|
this.loadNextView();
|
|
return;
|
|
}
|
|
|
|
this.addHistory(fullloc);
|
|
this.search = time;
|
|
$w('All Past Future').each(function(time) {
|
|
$('kronolithSearch' + time).up().removeClassName('horde-active');
|
|
});
|
|
$('kronolithSearch' + this.search.capitalize()).up().addClassName('horde-active');
|
|
this.closeView('agenda');
|
|
this.view = 'agenda';
|
|
this.updateView(null, 'search', term);
|
|
$H(Kronolith.conf.calendars).each(function(type) {
|
|
$H(type.value).each(function(calendar) {
|
|
if (calendar.value.show) {
|
|
cals.push(type.key + '|' + calendar.key);
|
|
}
|
|
});
|
|
});
|
|
$('kronolithAgendaNoItems').hide();
|
|
this.startLoading('search', query);
|
|
|
|
HordeCore.doAction('searchEvents', {
|
|
cals: Object.toJSON(cals),
|
|
query: query,
|
|
time: this.search
|
|
}, {
|
|
callback: function(r) {
|
|
// Hide spinner.
|
|
this.loading--;
|
|
if (!this.loading) {
|
|
$('kronolithLoading').hide();
|
|
}
|
|
if (r.view != 'search' ||
|
|
r.query != this.eventsLoading.search) {
|
|
return;
|
|
}
|
|
if (Object.isUndefined(r.events)) {
|
|
$('kronolithAgendaNoItems').show();
|
|
return;
|
|
}
|
|
delete this.eventsLoading.search;
|
|
$H(r.events).each(function(calendars) {
|
|
$H(calendars.value).each(function(events) {
|
|
this.createAgendaDay(events.key);
|
|
$H(events.value).each(function(event) {
|
|
event.value.calendar = calendars.key;
|
|
event.value.start = Date.parse(event.value.s);
|
|
event.value.end = Date.parse(event.value.e);
|
|
this.insertEvent(event, events.key, 'agenda');
|
|
}, this);
|
|
}, this);
|
|
}, this);
|
|
}.bind(this)
|
|
});
|
|
|
|
$('kronolithViewAgenda').appear({
|
|
duration: this.effectDur,
|
|
queue: 'end',
|
|
afterFinish: function() {
|
|
this.loadNextView();
|
|
}.bind(this) });
|
|
$('kronolithLoadingagenda').insert($('kronolithLoading').remove());
|
|
this.updateMinical(this.date);
|
|
break;
|
|
|
|
case 'event':
|
|
// Load view first if necessary.
|
|
if (!this.view ) {
|
|
this.viewLoading.pop();
|
|
this.go(Kronolith.conf.login_view);
|
|
this.go.bind(this, fullloc, data).defer();
|
|
return;
|
|
}
|
|
|
|
if (this.currentLocation == fullloc) {
|
|
this.loadNextView();
|
|
return;
|
|
}
|
|
|
|
this.addHistory(fullloc, false);
|
|
switch (locParts.length) {
|
|
case 0:
|
|
// New event.
|
|
this.editEvent();
|
|
break;
|
|
case 1:
|
|
// New event on a certain date.
|
|
this.editEvent(null, null, locParts[0]);
|
|
break;
|
|
default:
|
|
// Editing event.
|
|
var date = locParts.pop(),
|
|
event = locParts.pop(),
|
|
calendar = locParts.join(':');
|
|
this.editEvent(calendar, event, date);
|
|
break;
|
|
}
|
|
this.loadNextView();
|
|
break;
|
|
|
|
case 'task':
|
|
// Load view first if necessary.
|
|
if (!this.view ) {
|
|
this.viewLoading.pop();
|
|
this.go('tasks');
|
|
this.go.bind(this, fullloc, data).defer();
|
|
return;
|
|
}
|
|
|
|
this.switchTaskView(true);
|
|
switch (locParts.length) {
|
|
case 0:
|
|
this.addHistory(fullloc, false);
|
|
this.editTask();
|
|
break;
|
|
case 2:
|
|
this.addHistory(fullloc, false);
|
|
this.editTask(locParts[0], locParts[1]);
|
|
break;
|
|
}
|
|
this.loadNextView();
|
|
break;
|
|
|
|
case 'calendar':
|
|
if (!this.view) {
|
|
this.viewLoading.pop();
|
|
this.go(Kronolith.conf.login_view);
|
|
this.go.bind(this, fullloc, data).defer();
|
|
return;
|
|
}
|
|
this.addHistory(fullloc, false);
|
|
this.editCalendar(locParts.join(':'));
|
|
this.loadNextView();
|
|
break;
|
|
|
|
default:
|
|
this.loadNextView();
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Removes the last loaded view from the stack and loads the last added
|
|
* view, if the stack is still not empty.
|
|
*
|
|
* We want to load views from a LIFO queue, because the queue is only
|
|
* building up if the user switches to another view while the current view
|
|
* still loads. In that case we can go directly to the most recently
|
|
* clicked view and drop the remaining queue.
|
|
*/
|
|
loadNextView: function()
|
|
{
|
|
var current = this.viewLoading.shift();
|
|
if (this.viewLoading.size()) {
|
|
var next = this.viewLoading.pop();
|
|
this.viewLoading = [];
|
|
if (current[0] != next[0] || current[1] || next[1]) {
|
|
this.go(next[0], next[1]);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Rebuilds one of the calendar views for a new date.
|
|
*
|
|
* @param Date date The date to show in the calendar.
|
|
* @param string view The view that's rebuilt.
|
|
* @param mixed data Any additional data that might be required.
|
|
*/
|
|
updateView: function(date, view, data)
|
|
{
|
|
this.holidays = [];
|
|
|
|
switch (view) {
|
|
case 'day':
|
|
var today = Date.today();
|
|
this.dayEvents = [];
|
|
this.dayGroups = [];
|
|
this.allDayEvents = [];
|
|
$('kronolithCurrent')
|
|
.update(this.setViewTitle(date, view, data));
|
|
$('kronolithViewDay')
|
|
.down('.kronolithAllDayContainer')
|
|
.store('date', date.dateString());
|
|
$('kronolithEventsDay').store('date', date.dateString());
|
|
if (date.equals(today)) {
|
|
this.addTimeMarker('kronolithEventsDay');
|
|
}
|
|
break;
|
|
|
|
case 'week':
|
|
case 'workweek':
|
|
this.dayEvents = [];
|
|
this.dayGroups = [];
|
|
this.allDayEvents = [];
|
|
this.allDays = {};
|
|
this.eventsWeek = {};
|
|
var what = view == 'week' ? 'Week' : 'Workweek',
|
|
div = $('kronolithEvents' + what).down('div'),
|
|
th = $('kronolithView' + what + 'Head').down('.kronolithWeekDay'),
|
|
td = $('kronolithView' + what + 'Head').down('tbody td').next('td'),
|
|
hourRow = $('kronolithView' + what + 'Body').down('tr'),
|
|
dates = this.viewDates(date, view),
|
|
today = Date.today(),
|
|
day, dateString, i, hourCol;
|
|
|
|
$('kronolithCurrent')
|
|
.update(this.setViewTitle(date, view, data));
|
|
|
|
for (i = 0; i < 24; i++) {
|
|
day = dates[0].clone();
|
|
hourCol = hourRow.down('td').next('td');
|
|
while (hourCol) {
|
|
hourCol.removeClassName('kronolith-today');
|
|
if (day.equals(today)) {
|
|
hourCol.addClassName('kronolith-today');
|
|
}
|
|
hourCol = hourCol.next('td');
|
|
day.next().day();
|
|
}
|
|
hourRow = hourRow.next('tr');
|
|
}
|
|
day = dates[0].clone();
|
|
|
|
for (i = 0; i < (view == 'week' ? 7 : 5); i++) {
|
|
dateString = day.dateString();
|
|
this.allDays['kronolithAllDay' + dateString] = td.down('div');
|
|
this.eventsWeek['kronolithEvents' + what + dateString] = div;
|
|
div.store('date', dateString)
|
|
.writeAttribute('id', 'kronolithEvents' + what + dateString);
|
|
th.store('date', dateString)
|
|
.down('span').update(day.toString('dddd, d'));
|
|
td.removeClassName('kronolith-today');
|
|
this.allDays['kronolithAllDay' + dateString]
|
|
.writeAttribute('id', 'kronolithAllDay' + dateString)
|
|
.store('date', dateString);
|
|
if (day.equals(today)) {
|
|
td.addClassName('kronolith-today');
|
|
this.addTimeMarker('kronolithEvents' + what + dateString);
|
|
}
|
|
new Drop(td.down('div'));
|
|
div = div.next('div');
|
|
th = th.next('td');
|
|
td = td.next('td');
|
|
day.next().day();
|
|
}
|
|
break;
|
|
|
|
case 'month':
|
|
var tbody = $('kronolith-month-body'),
|
|
dates = this.viewDates(date, view),
|
|
day = dates[0].clone();
|
|
|
|
$('kronolithCurrent')
|
|
.update(this.setViewTitle(date, view, data));
|
|
|
|
// Remove old rows. Maybe we should only rebuild the calendars if
|
|
// necessary.
|
|
tbody.childElements().each(function(row) {
|
|
if (row.identify() != 'kronolithRowTemplate') {
|
|
row.purge();
|
|
row.remove();
|
|
}
|
|
});
|
|
|
|
// Build new calendar view.
|
|
this.monthDays = {};
|
|
while (!day.isAfter(dates[1])) {
|
|
tbody.insert(this.createWeekRow(day, date.getMonth(), dates).show());
|
|
day.next().week();
|
|
}
|
|
this.equalRowHeights(tbody);
|
|
|
|
break;
|
|
|
|
case 'year':
|
|
var month;
|
|
|
|
$('kronolithCurrent').update(this.setViewTitle(date, view, data));
|
|
|
|
// Build new calendar view.
|
|
for (month = 0; month < 12; month++) {
|
|
$('kronolithYear' + month).update(this.createYearMonth(date.getFullYear(), month, 'kronolithYear').show());
|
|
}
|
|
|
|
break;
|
|
|
|
case 'agenda':
|
|
case 'search':
|
|
// Agenda days are only created on demand, if there are any events
|
|
// to add.
|
|
if (view == 'agenda') {
|
|
var dates = this.viewDates(date, view);
|
|
$('kronolithCurrent')
|
|
.update(this.setViewTitle(date, view, data));
|
|
$('kronolithSearchNavigation').up().up().hide();
|
|
} else {
|
|
$('kronolithCurrent')
|
|
.update(this.setViewTitle(date, view, data));
|
|
$('kronolithSearchNavigation').up().up().show();
|
|
}
|
|
|
|
// Remove old rows. Maybe we should only rebuild the calendars if
|
|
// necessary.
|
|
tbody = $('kronolithViewAgendaBody').childElements().each(function(row) {
|
|
if (row.identify() != 'kronolithAgendaTemplate' &&
|
|
row.identify() != 'kronolithAgendaNoItems') {
|
|
row.purge();
|
|
row.remove();
|
|
}
|
|
});
|
|
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Sets the browser title of the calendar views.
|
|
*
|
|
* @param Date date The date to show in the calendar.
|
|
* @param string view The view that's displayed.
|
|
* @param mixed data Any additional data that might be required.
|
|
*/
|
|
setViewTitle: function(date, view, data)
|
|
{
|
|
switch (view) {
|
|
case 'day':
|
|
return this.setTitle(date.toString('D'));
|
|
|
|
case 'week':
|
|
case 'workweek':
|
|
var dates = this.viewDates(date, view);
|
|
return this.setTitle(dates[0].toString(Kronolith.conf.date_format) + ' - ' + dates[1].toString(Kronolith.conf.date_format));
|
|
|
|
case 'month':
|
|
return this.setTitle(date.toString('MMMM yyyy'));
|
|
|
|
case 'year':
|
|
return this.setTitle(date.toString('yyyy'));
|
|
|
|
case 'agenda':
|
|
var dates = this.viewDates(date, view);
|
|
return this.setTitle(dates[0].toString(Kronolith.conf.date_format) + ' - ' + dates[1].toString(Kronolith.conf.date_format));
|
|
|
|
case 'search':
|
|
return this.setTitle(Kronolith.text.searching.interpolate({ term: data })).escapeHTML();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Closes the currently active view.
|
|
*/
|
|
closeView: function(loc)
|
|
{
|
|
$w('Day Workweek Week Month Year Tasks Agenda').each(function(a) {
|
|
a = $('kronolithNav' + a);
|
|
if (a) {
|
|
a.up().removeClassName('horde-active');
|
|
}
|
|
});
|
|
if (this.view && this.view != loc) {
|
|
$('kronolithView' + this.view.capitalize()).fade({
|
|
duration: this.effectDur,
|
|
queue: 'end'
|
|
});
|
|
this.view = null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates a single row of day cells for usage in the month and multi-week
|
|
* views.
|
|
*
|
|
* @param Date date The first day to show in the row.
|
|
* @param integer month The current month. Days not from the current
|
|
* month get the kronolith-other-month CSS class
|
|
* assigned.
|
|
* @param array viewDates Array of Date objects with the start and end
|
|
* dates of the view.
|
|
*
|
|
* @return Element The element rendering a week row.
|
|
*/
|
|
createWeekRow: function(date, month, viewDates)
|
|
{
|
|
var day = date.clone(), today = new Date().dateString(),
|
|
row, cell, dateString;
|
|
|
|
// Create a copy of the row template.
|
|
row = $('kronolithRowTemplate').clone(true);
|
|
row.removeAttribute('id');
|
|
|
|
// Fill week number and day cells.
|
|
cell = row.down()
|
|
.setText(date.getRealWeek())
|
|
.store('date', date.dateString())
|
|
.next();
|
|
while (cell) {
|
|
dateString = day.dateString();
|
|
this.monthDays['kronolithMonthDay' + dateString] = cell;
|
|
cell.id = 'kronolithMonthDay' + dateString;
|
|
cell.store('date', dateString);
|
|
cell.removeClassName('kronolith-other-month').removeClassName('kronolith-today');
|
|
if (day.getMonth() != month) {
|
|
cell.addClassName('kronolith-other-month');
|
|
}
|
|
if (dateString == today) {
|
|
cell.addClassName('kronolith-today');
|
|
}
|
|
new Drop(cell);
|
|
cell.store('date', dateString)
|
|
.down('.kronolith-day')
|
|
.store('date', dateString)
|
|
.update(day.getDate());
|
|
|
|
cell = cell.next();
|
|
day.add(1).day();
|
|
}
|
|
|
|
return row;
|
|
},
|
|
|
|
/**
|
|
* Creates a table row for a single day in the agenda view, if it doesn't
|
|
* exist yet.
|
|
*
|
|
* @param string date The day to show in the row.
|
|
*
|
|
* @return Element The element rendering a week row.
|
|
*/
|
|
createAgendaDay: function(date)
|
|
{
|
|
// Exit if row exists already.
|
|
if ($('kronolithAgendaDay' + date)) {
|
|
return;
|
|
}
|
|
|
|
// Create a copy of the row template.
|
|
var body = $('kronolithViewAgendaBody'),
|
|
row = $('kronolithAgendaTemplate').clone(true);
|
|
|
|
// Fill week number and day cells.
|
|
row.store('date', date)
|
|
.down()
|
|
.setText(this.parseDate(date).toString('D'))
|
|
.next()
|
|
.writeAttribute('id', 'kronolithAgendaDay' + date);
|
|
row.removeAttribute('id');
|
|
|
|
// Insert row.
|
|
var nextRow;
|
|
body.childElements().each(function(elm) {
|
|
if (elm.retrieve('date') > date) {
|
|
nextRow = elm;
|
|
throw $break;
|
|
}
|
|
});
|
|
if (nextRow) {
|
|
nextRow.insert({ before: row.show() });
|
|
} else {
|
|
body.insert(row.show());
|
|
}
|
|
|
|
return row;
|
|
},
|
|
|
|
/**
|
|
* Creates a table for a single month in the year view.
|
|
*
|
|
* @param integer year The year.
|
|
* @param integer month The month.
|
|
* @param string idPrefix If present, each day will get a DOM ID with this
|
|
* prefix
|
|
*
|
|
* @return Element The element rendering a month table.
|
|
*/
|
|
createYearMonth: function(year, month, idPrefix)
|
|
{
|
|
// Create a copy of the month template.
|
|
var table = $('kronolithYearTemplate').clone(true),
|
|
tbody = table.down('tbody');
|
|
table.removeAttribute('id');
|
|
tbody.writeAttribute('id', 'kronolithYearTable' + month);
|
|
|
|
// Set month name.
|
|
table.down('tr.kronolith-minical-nav th')
|
|
.store('date', year.toPaddedString(4) + (month + 1).toPaddedString(2) + '01')
|
|
.update(Date.CultureInfo.monthNames[month]);
|
|
|
|
// Build month table.
|
|
this.buildMinical(tbody, new Date(year, month, 1), null, idPrefix, year);
|
|
|
|
return table;
|
|
},
|
|
|
|
equalRowHeights: function(tbody)
|
|
{
|
|
var children = tbody.childElements();
|
|
children.invoke('setStyle', { height: (100 / (children.size() - 1)) + '%' });
|
|
},
|
|
|
|
/**
|
|
* Calculates some dimensions for the day and week view.
|
|
*
|
|
* @param string storage Property name where the dimensions are stored.
|
|
* @param string view DOM node ID of the view.
|
|
*/
|
|
calculateRowSizes: function(storage, view)
|
|
{
|
|
if (!Object.isUndefined(this[storage])) {
|
|
return;
|
|
}
|
|
|
|
var td = $(view).down('.kronolithViewBody tr td').next('td'),
|
|
layout = td.getLayout(),
|
|
spacing = td.up('table').getStyle('borderSpacing');
|
|
|
|
// FIXME: spacing is hardcoded for IE 7 because it doesn't know about
|
|
// border-spacing, but still uses it. WTF?
|
|
spacing = spacing ? parseInt($w(spacing)[1], 10) : 2;
|
|
this[storage] = {};
|
|
this[storage].height = layout.get('margin-box-height') + spacing;
|
|
this[storage].spacing = this[storage].height - layout.get('padding-box-height') - layout.get('border-bottom');
|
|
},
|
|
|
|
/**
|
|
* Adds a horizontal ruler representing the current time to the specified
|
|
* container.
|
|
*
|
|
* @param string|Element The container of the current day.
|
|
*/
|
|
addTimeMarker: function(container)
|
|
{
|
|
if ($('kronolithTimeMarker')) {
|
|
$('kronolithTimeMarker').remove();
|
|
this.timeMarker.stop();
|
|
}
|
|
$(container).insert(new Element('div', { id: 'kronolithTimeMarker' }).setStyle({ position: 'absolute' }).hide());
|
|
this.timeMarker = new PeriodicalExecuter(this.positionTimeMarker.bind(this), 60);
|
|
},
|
|
|
|
/**
|
|
* Updates the horizontal ruler representing the current time.
|
|
*/
|
|
positionTimeMarker: function()
|
|
{
|
|
var today = Date.today(), now;
|
|
|
|
switch (this.view) {
|
|
case 'day':
|
|
if (!this.date.equals(today)) {
|
|
$('kronolithTimeMarker').remove();
|
|
this.timeMarker.stop();
|
|
return;
|
|
}
|
|
break;
|
|
case 'week':
|
|
case 'workweek':
|
|
if ($('kronolithTimeMarker').up().retrieve('date') != today.dateString()) {
|
|
var newContainer = this.eventsWeek['kronolithEvents' + (this.view == 'week' ? 'Week' : 'Workweek') + today.dateString()];
|
|
$('kronolithTimeMarker').remove();
|
|
if (newContainer) {
|
|
this.addTimeMarker(newContainer);
|
|
} else {
|
|
this.timeMarker.stop();
|
|
}
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
$('kronolithTimeMarker').remove();
|
|
this.timeMarker.stop();
|
|
return;
|
|
}
|
|
|
|
now = new Date();
|
|
$('kronolithTimeMarker').setStyle({ top: ((now.getHours() * 60 + now.getMinutes()) * this[this.view + 'Sizes'].height / 60 | 0) + 'px' });
|
|
},
|
|
|
|
/**
|
|
* Rebuilds the mini calendar.
|
|
*
|
|
* @param Date date The date to show in the calendar.
|
|
* @param string view The view that's displayed, determines which days in
|
|
* the mini calendar are highlighted.
|
|
*/
|
|
updateMinical: function(date, view)
|
|
{
|
|
// Update header.
|
|
$('kronolithMinicalDate')
|
|
.store('date', date.dateString())
|
|
.update(date.toString('MMMM yyyy'));
|
|
|
|
this.buildMinical($('kronolith-minical').down('tbody'), date, view);
|
|
},
|
|
|
|
/**
|
|
* Creates a mini calendar suitable for the navigation calendar and the
|
|
* year view.
|
|
*
|
|
* @param Element tbody The table body to add the days to.
|
|
* @param Date date The date to show in the calendar.
|
|
* @param string view The view that's displayed, determines which days
|
|
* in the mini calendar are highlighted.
|
|
* @param string idPrefix If present, each day will get a DOM ID with this
|
|
* prefix
|
|
* @param integer year If present, generating mini calendars for the
|
|
* year view of this year.
|
|
*/
|
|
buildMinical: function(tbody, date, view, idPrefix, year)
|
|
{
|
|
var dates = this.viewDates(date, 'month'),
|
|
day = dates[0].clone(),
|
|
date7 = date.clone().add(1).week(),
|
|
today = Date.today(),
|
|
week = this.viewDates(this.date, 'week'),
|
|
workweek = this.viewDates(this.date, 'workweek'),
|
|
dateString, td, tr, i;
|
|
|
|
// Remove old calendar rows. Maybe we should only rebuild the minical
|
|
// if necessary.
|
|
tbody.childElements().invoke('remove');
|
|
|
|
for (i = 0; i < 42; i++) {
|
|
dateString = day.dateString();
|
|
// Create calendar row and insert week number.
|
|
if (day.getDay() == Kronolith.conf.week_start) {
|
|
tr = new Element('tr');
|
|
tbody.insert(tr);
|
|
td = new Element('td', { className: 'kronolith-minical-week' })
|
|
.store('weekdate', dateString);
|
|
td.update(day.getRealWeek());
|
|
tr.insert(td);
|
|
weekStart = day.clone();
|
|
weekEnd = day.clone();
|
|
weekEnd.add(6).days();
|
|
}
|
|
|
|
// Insert day cell.
|
|
td = new Element('td').store('date', dateString);
|
|
if (day.getMonth() != date.getMonth()) {
|
|
td.addClassName('kronolith-other-month');
|
|
} else if (!Object.isUndefined(idPrefix)) {
|
|
td.id = idPrefix + dateString;
|
|
}
|
|
|
|
// Highlight days currently being displayed.
|
|
if (view &&
|
|
((view == 'month' && this.date.between(dates[0], dates[1])) ||
|
|
(view == 'week' && day.between(week[0], week[1])) ||
|
|
(view == 'workweek' && day.between(workweek[0], workweek[1])) ||
|
|
(view == 'day' && day.equals(this.date)) ||
|
|
(view == 'agenda' && !day.isBefore(date) && day.isBefore(date7)))) {
|
|
td.addClassName('kronolith-selected');
|
|
}
|
|
|
|
// Highlight today.
|
|
if (day.equals(today) &&
|
|
(Object.isUndefined(year) ||
|
|
(day.getYear() + 1900 == year &&
|
|
date.getMonth() == day.getMonth()))) {
|
|
td.addClassName('kronolith-today');
|
|
}
|
|
td.insert(new Element('a').update(day.getDate()));
|
|
tr.insert(td);
|
|
day.next().day();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Inserts a calendar entry in the sidebar menu.
|
|
*
|
|
* @param string type The calendar type.
|
|
* @param string id The calendar id.
|
|
* @param object cal The calendar object.
|
|
* @param Element div Container DIV where to add the entry (optional).
|
|
*/
|
|
insertCalendarInList: function(type, id, cal, div)
|
|
{
|
|
var noItems, calendar, link;
|
|
if (!div) {
|
|
div = this.getCalendarList(type, cal.owner);
|
|
}
|
|
noItems = div.previous();
|
|
if (noItems &&
|
|
noItems.tagName == 'DIV' &&
|
|
noItems.className == 'horde-info') {
|
|
noItems.hide();
|
|
}
|
|
link = new Element('span', { className: type != 'resourcegroup' ? (cal.show ? 'horde-resource-on' : 'horde-resource-off') : 'horde-resource-none' })
|
|
.insert(cal.name.escapeHTML());
|
|
calendar = new Element('div')
|
|
.store('calendar', id)
|
|
.store('calendarclass', type)
|
|
.setStyle({ backgroundColor: cal.bg, color: cal.fg });
|
|
if (type != 'holiday' && type != 'external') {
|
|
calendar.insert(
|
|
new Element('span', { className: 'horde-resource-edit-' + cal.fg.substring(1) })
|
|
.setStyle({ backgroundColor: cal.bg, color: cal.fg })
|
|
.insert('►'));
|
|
}
|
|
calendar.insert(
|
|
new Element('div', { className: 'horde-resource-link' })
|
|
.insert(link));
|
|
this.addShareIcon(cal, link);
|
|
div.insert(calendar);
|
|
if (cal.show) {
|
|
this.addCalendarLegend(type, id, cal);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Add the share icon after the calendar name in the calendar list.
|
|
*
|
|
* @param object cal A calendar object from Kronolith.conf.calendars.
|
|
* @param Element element The calendar element in the list.
|
|
*/
|
|
addShareIcon: function(cal, element)
|
|
{
|
|
if (cal.owner && cal.perms) {
|
|
$H(cal.perms).each(function(perm) {
|
|
if (perm.key != 'type' &&
|
|
((Object.isArray(perm.value) && perm.value.size()) ||
|
|
(!Object.isArray(perm.value) && perm.value))) {
|
|
element.insert(' ').insert(new Element('img', { src: Kronolith.conf.images.attendees.replace(/fff/, cal.fg.substring(1)), title: Kronolith.text.shared }));
|
|
throw $break;
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Rebuilds the list of calendars.
|
|
*/
|
|
updateCalendarList: function()
|
|
{
|
|
var ext = $H(), extNames = $H(),
|
|
extContainer = $('kronolithExternalCalendars');
|
|
|
|
$H(Kronolith.conf.calendars.internal).each(function(cal) {
|
|
this.insertCalendarInList('internal', cal.key, cal.value);
|
|
}, this);
|
|
|
|
if (Kronolith.conf.tasks) {
|
|
$H(Kronolith.conf.calendars.tasklists).each(function(cal) {
|
|
this.insertCalendarInList('tasklists', cal.key, cal.value);
|
|
}, this);
|
|
}
|
|
|
|
if (Kronolith.conf.calendars.resource) {
|
|
$H(Kronolith.conf.calendars.resource).each(function(cal) {
|
|
this.insertCalendarInList('resource', cal.key, cal.value);
|
|
}, this);
|
|
}
|
|
|
|
if (Kronolith.conf.calendars.resourcegroup) {
|
|
$H(Kronolith.conf.calendars.resourcegroup).each(function(cal) {
|
|
this.insertCalendarInList('resourcegroup', cal.key, cal.value);
|
|
}, this);
|
|
}
|
|
|
|
$H(Kronolith.conf.calendars.external).each(function(cal) {
|
|
var parts = cal.key.split('/'), api = parts.shift();
|
|
if (!ext.get(api)) {
|
|
ext.set(api, $H());
|
|
}
|
|
ext.get(api).set(parts.join('/'), cal.value);
|
|
extNames.set(api, cal.value.api ? cal.value.api : Kronolith.text.external_category);
|
|
});
|
|
ext.each(function(api) {
|
|
extContainer
|
|
.insert(new Element('div', { className: 'horde-sidebar-split' }))
|
|
.insert(new Element('div')
|
|
.insert(new Element('h3')
|
|
.insert(new Element('span', { className: 'horde-expand', title: HordeSidebar.text.expand })
|
|
.insert({ bottom: extNames.get(api.key).escapeHTML() })))
|
|
.insert(new Element('div', { id: 'kronolithExternalCalendar' + api.key, className: 'horde-resources', style: 'display:none' })));
|
|
api.value.each(function(cal) {
|
|
this.insertCalendarInList('external', api.key + '/' + cal.key, cal.value, $('kronolithExternalCalendar' + api.key));
|
|
}, this);
|
|
}, this);
|
|
|
|
$H(Kronolith.conf.calendars.remote).each(function(cal) {
|
|
this.insertCalendarInList('remote', cal.key, cal.value);
|
|
}, this);
|
|
|
|
if (Kronolith.conf.calendars.holiday) {
|
|
$H(Kronolith.conf.calendars.holiday).each(function(cal) {
|
|
if (cal.value.show) {
|
|
this.insertCalendarInList('holiday', cal.key, cal.value);
|
|
}
|
|
}, this);
|
|
} else {
|
|
$('kronolithAddholiday').up().hide();
|
|
$('kronolithHolidayCalendars').hide();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns the DIV container that holds all calendars of a certain type.
|
|
*
|
|
* @param string type A calendar type
|
|
*
|
|
* @return Element The container of the calendar type.
|
|
*/
|
|
getCalendarList: function(type, personal)
|
|
{
|
|
switch (type) {
|
|
case 'internal':
|
|
return personal
|
|
? $('kronolithMyCalendars')
|
|
: $('kronolithSharedCalendars');
|
|
case 'resource':
|
|
return $('kronolithResourceCalendars');
|
|
case 'resourcegroup':
|
|
return $('kronolithResourceGroups');
|
|
case 'tasklists':
|
|
return personal
|
|
? $('kronolithMyTasklists')
|
|
: $('kronolithSharedTasklists');
|
|
case 'external':
|
|
return $('kronolithExternalCalendars');
|
|
case 'remote':
|
|
return $('kronolithRemoteCalendars');
|
|
case 'holiday':
|
|
return $('kronolithHolidayCalendars');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Loads a certain calendar, if the current view is still a calendar view.
|
|
*
|
|
* @param string type The calendar type.
|
|
* @param string calendar The calendar id.
|
|
*/
|
|
loadCalendar: function(type, calendar)
|
|
{
|
|
if (Kronolith.conf.calendars[type][calendar].show &&
|
|
$w('day workweek week month year agenda').include(this.view)) {
|
|
var dates = this.viewDates(this.date, this.view);
|
|
this.deleteCache([type, calendar]);
|
|
this.loadEvents(dates[0], dates[1], this.view, [[type, calendar]]);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Toggles a calendars visibility.
|
|
*
|
|
* @param string type The calendar type.
|
|
* @param string calendar The calendar id.
|
|
*/
|
|
toggleCalendar: function(type, calendar)
|
|
{
|
|
var elt = $('kronolithMenuCalendars').select('div').find(function(div) {
|
|
return div.retrieve('calendarclass') == type &&
|
|
div.retrieve('calendar') == calendar;
|
|
}).down('.horde-resource-link').down('span');
|
|
|
|
Kronolith.conf.calendars[type][calendar].show = !Kronolith.conf.calendars[type][calendar].show;
|
|
elt.toggleClassName('horde-resource-on');
|
|
elt.toggleClassName('horde-resource-off');
|
|
|
|
if (Kronolith.conf.calendars[type][calendar].show) {
|
|
this.addCalendarLegend(type, calendar, Kronolith.conf.calendars[type][calendar]);
|
|
} else {
|
|
this.deleteCalendarLegend(type, calendar);
|
|
}
|
|
|
|
switch (this.view) {
|
|
case 'month':
|
|
case 'agenda':
|
|
if (Object.isUndefined(this.ecache.get(type)) ||
|
|
Object.isUndefined(this.ecache.get(type).get(calendar))) {
|
|
this.loadCalendar(type, calendar);
|
|
} else {
|
|
var allEvents = this.kronolithBody.select('div').findAll(function(el) {
|
|
return el.retrieve('calendar') == type + '|' + calendar;
|
|
});
|
|
if (this.view == 'month' && Kronolith.conf.max_events) {
|
|
var dates = this.viewDates(this.date, this.view);
|
|
if (elt.hasClassName('horde-resource-off')) {
|
|
var day, more, events, calendars = [];
|
|
$H(Kronolith.conf.calendars).each(function(type) {
|
|
$H(type.value).each(function(cal) {
|
|
if (cal.value.show) {
|
|
calendars.push(type.key + '|' + cal.key);
|
|
}
|
|
});
|
|
});
|
|
allEvents.each(function(el) {
|
|
if (el.retrieve('calendar').startsWith('holiday|')) {
|
|
this.holidays = this.holidays.without(el.retrieve('eventid'));
|
|
}
|
|
el.remove();
|
|
}, this);
|
|
for (var date = dates[0]; !date.isAfter(dates[1]); date.add(1).days()) {
|
|
day = this.monthDays['kronolithMonthDay' + date.dateString()];
|
|
more = day.select('.kronolithMore');
|
|
events = day.select('.kronolith-event');
|
|
if (more.size() &&
|
|
events.size() < Kronolith.conf.max_events) {
|
|
more[0].purge();
|
|
more[0].remove();
|
|
events.invoke('remove');
|
|
calendars.each(function(calendar) {
|
|
this.insertEvents([date, date], 'month', calendar);
|
|
}, this);
|
|
}
|
|
}
|
|
} else {
|
|
this.insertEvents(dates, 'month', type + '|' + calendar);
|
|
}
|
|
} else {
|
|
allEvents.invoke('toggle');
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'year':
|
|
case 'week':
|
|
case 'workweek':
|
|
case 'day':
|
|
if (Object.isUndefined(this.ecache.get(type)) ||
|
|
Object.isUndefined(this.ecache.get(type).get(calendar))) {
|
|
this.loadCalendar(type, calendar);
|
|
} else {
|
|
this.insertEvents(this.viewDates(this.date, this.view), this.view);
|
|
}
|
|
break;
|
|
|
|
case 'tasks':
|
|
if (type != 'tasklists') {
|
|
break;
|
|
}
|
|
var tasklist = calendar.substr(6);
|
|
if (elt.hasClassName('horde-resource-off')) {
|
|
$('kronolithViewTasksBody').select('tr').findAll(function(el) {
|
|
return el.retrieve('tasklist') == tasklist;
|
|
}).invoke('remove');
|
|
} else {
|
|
this.loadTasks(this.tasktype, [ tasklist ]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ($w('tasklists remote external holiday resource').include(type)) {
|
|
calendar = type + '_' + calendar;
|
|
}
|
|
HordeCore.doAction('saveCalPref', { toggle_calendar: calendar });
|
|
},
|
|
|
|
/**
|
|
* Propagates a SELECT drop down list with the editable calendars.
|
|
*
|
|
* @param string id The id of the SELECT element.
|
|
*/
|
|
updateCalendarDropDown: function(id)
|
|
{
|
|
$(id).update();
|
|
['internal', 'remote'].each(function(type) {
|
|
$H(Kronolith.conf.calendars[type]).each(function(cal) {
|
|
if (cal.value.edit) {
|
|
$(id).insert(new Element('option', { value: type + '|' + cal.key })
|
|
.setStyle({ backgroundColor: cal.value.bg, color: cal.value.fg })
|
|
.update(cal.value.name.escapeHTML()));
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Adds a calendar entry to the print legend.
|
|
*
|
|
* @param string type The calendar type.
|
|
* @param string id The calendar id.
|
|
* @param object cal The calendar object.
|
|
*/
|
|
addCalendarLegend: function(type, id, cal)
|
|
{
|
|
$('kronolith-legend').insert(
|
|
new Element('span')
|
|
.insert(cal.name.escapeHTML())
|
|
.store('calendar', id)
|
|
.store('calendarclass', type)
|
|
.setStyle({ backgroundColor: cal.bg, color: cal.fg })
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Deletes a calendar entry from the print legend.
|
|
*
|
|
* @param string type The calendar type.
|
|
* @param string id The calendar id.
|
|
*/
|
|
deleteCalendarLegend: function(type, id)
|
|
{
|
|
var legend = $('kronolith-legend').select('span').find(function(span) {
|
|
return span.retrieve('calendarclass') == type &&
|
|
span.retrieve('calendar') == id;
|
|
});
|
|
if (legend) {
|
|
legend.remove();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Opens a tab in a form.
|
|
*
|
|
* @param Element The A element of a tab.
|
|
*/
|
|
openTab: function(elt)
|
|
{
|
|
var dialog = elt.up('form'), tab = $(elt.id.replace(/Link/, 'Tab')),
|
|
field;
|
|
dialog.select('.kronolithTabsOption').invoke('hide');
|
|
dialog.select('.tabset li').invoke('removeClassName', 'horde-active');
|
|
tab.show();
|
|
elt.up().addClassName('horde-active');
|
|
if (elt.id == 'kronolithEventLinkMap') {
|
|
if (!this.mapInitialized) {
|
|
this.initializeMap();
|
|
}
|
|
}
|
|
field = tab.down('textarea');
|
|
if (!field) {
|
|
field = tab.down('input');
|
|
}
|
|
if (field) {
|
|
try {
|
|
field.focus();
|
|
} catch (e) {}
|
|
}
|
|
switch (tab.identify()) {
|
|
case 'kronolithEventTabAttendees':
|
|
this.attendeeStartDateHandler(this.getFBDate());
|
|
break;
|
|
case 'kronolithEventTabResources':
|
|
this.resourceStartDateHandler(this.getFBDate());
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Sets the load signature and show the loading spinner.
|
|
*
|
|
* @param string resource The loading resource.
|
|
* @param string signatrue The signature for this request.
|
|
*/
|
|
startLoading: function(resource, signature)
|
|
{
|
|
this.eventsLoading[resource] = signature;
|
|
this.loading++;
|
|
$('kronolithLoading').show();
|
|
},
|
|
|
|
/**
|
|
*/
|
|
loadEvents: function(firstDay, lastDay, view, calendars)
|
|
{
|
|
var loading = false;
|
|
|
|
if (typeof calendars == 'undefined') {
|
|
calendars = [];
|
|
$H(Kronolith.conf.calendars).each(function(type) {
|
|
$H(type.value).each(function(cal) {
|
|
if (cal.value.show) {
|
|
calendars.push([type.key, cal.key]);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
calendars.each(function(cal) {
|
|
var startDay = firstDay.clone(), endDay = lastDay.clone(),
|
|
cals = this.ecache.get(cal[0]);
|
|
|
|
if (typeof cals != 'undefined' &&
|
|
typeof cals.get(cal[1]) != 'undefined') {
|
|
cals = cals.get(cal[1]);
|
|
while (!Object.isUndefined(cals.get(startDay.dateString())) &&
|
|
startDay.isBefore(endDay)) {
|
|
if (view != 'year') {
|
|
this.insertEvents([startDay, startDay], view, cal.join('|'));
|
|
}
|
|
startDay.add(1).day();
|
|
}
|
|
while (!Object.isUndefined(cals.get(endDay.dateString())) &&
|
|
(!startDay.isAfter(endDay))) {
|
|
if (view != 'year') {
|
|
this.insertEvents([endDay, endDay], view, cal.join('|'));
|
|
}
|
|
endDay.add(-1).day();
|
|
}
|
|
if (startDay.compareTo(endDay) > 0) {
|
|
return;
|
|
}
|
|
}
|
|
var start = startDay.dateString(), end = endDay.dateString(),
|
|
calendar = cal.join('|');
|
|
loading = true;
|
|
this.startLoading(calendar, start + end);
|
|
this.storeCache($H(), calendar, null, true);
|
|
|
|
HordeCore.doAction('listEvents', {
|
|
start: start,
|
|
end: end,
|
|
cal: calendar,
|
|
sig: start + end,
|
|
view: view
|
|
}, {
|
|
callback: function(r) {
|
|
this.loadEventsCallback(r, true);
|
|
}.bind(this)
|
|
});
|
|
}, this);
|
|
|
|
if (!loading && view == 'year') {
|
|
this.insertEvents([firstDay, lastDay], 'year');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Callback method for inserting events in the current view.
|
|
*
|
|
* @param object r The ajax response object.
|
|
* @param boolean createCache Whether to create a cache list entry for the
|
|
* response, if none exists yet. Useful for
|
|
* (not) adding individual events to the cache
|
|
* if it doesn't match any cached views.
|
|
*/
|
|
loadEventsCallback: function(r, createCache)
|
|
{
|
|
// Hide spinner.
|
|
this.loading--;
|
|
if (!this.loading) {
|
|
$('kronolithLoading').hide();
|
|
}
|
|
|
|
var start = this.parseDate(r.sig.substr(0, 8)),
|
|
end = this.parseDate(r.sig.substr(8, 8)),
|
|
dates = [start, end],
|
|
currentDates;
|
|
|
|
this.storeCache(r.events || {}, r.cal, dates, createCache);
|
|
|
|
// Check if this is the still the result of the most current request.
|
|
if (r.sig != this.eventsLoading[r.cal]) {
|
|
return;
|
|
}
|
|
delete this.eventsLoading[r.cal];
|
|
|
|
// Check if the calendar is still visible.
|
|
var calendar = r.cal.split('|');
|
|
if (!Kronolith.conf.calendars[calendar[0]][calendar[1]].show) {
|
|
return;
|
|
}
|
|
|
|
// Check if the result is still for the current view.
|
|
currentDates = this.viewDates(this.date, this.view);
|
|
if (r.view != this.view ||
|
|
!start.between(currentDates[0], currentDates[1])) {
|
|
|
|
return;
|
|
}
|
|
|
|
if (this.view == 'day' ||
|
|
this.view == 'week' ||
|
|
this.view == 'workweek' ||
|
|
this.view == 'month' ||
|
|
this.view == 'agenda' ||
|
|
(this.view == 'year' && !$H(this.eventsLoading).size())) {
|
|
this.insertEvents(dates, this.view, r.cal);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Reads events from the cache and inserts them into the view.
|
|
*
|
|
* If inserting events into day and week views, the calendar parameter is
|
|
* ignored, and events from all visible calendars are inserted instead.
|
|
* This is necessary because the complete view has to be re-rendered if
|
|
* events are not in chronological order.
|
|
* The year view is specially handled too because there are no individual
|
|
* events, only a summary of all events per day.
|
|
*
|
|
* @param Array dates Start and end of dates to process.
|
|
* @param string view The view to update.
|
|
* @param string calendar The calendar to update.
|
|
*/
|
|
insertEvents: function(dates, view, calendar)
|
|
{
|
|
switch (view) {
|
|
case 'day':
|
|
case 'week':
|
|
case 'workweek':
|
|
// The day and week views require the view to be completely
|
|
// loaded, to correctly calculate the dimensions.
|
|
if (this.viewLoading.size() || this.view != view) {
|
|
this.insertEvents.bind(this, [dates[0].clone(), dates[1].clone()], view, calendar).defer();
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
var day = dates[0].clone(),
|
|
viewDates = this.viewDates(this.date, this.view),
|
|
date, more, title, titles, events, monthDay, busyHours;
|
|
while (!day.isAfter(dates[1])) {
|
|
// Skip if somehow events slipped in though the view is gone.
|
|
if (!day.between(viewDates[0], viewDates[1])) {
|
|
if (window.console) {
|
|
window.console.trace();
|
|
}
|
|
day.next().day();
|
|
continue;
|
|
}
|
|
|
|
date = day.dateString();
|
|
switch (view) {
|
|
case 'day':
|
|
case 'week':
|
|
case 'workweek':
|
|
this.dayEvents = [];
|
|
this.dayGroups = [];
|
|
this.allDayEvents = [];
|
|
if (view == 'day') {
|
|
$$('.kronolith-event').invoke('remove');
|
|
} else {
|
|
this.eventsWeek['kronolithEvents' + (view == 'week' ? 'Week' : 'Workweek') + date]
|
|
.select('.kronolith-event')
|
|
.invoke('remove');
|
|
this.allDays['kronolithAllDay' + date]
|
|
.childElements()
|
|
.invoke('remove');
|
|
}
|
|
break;
|
|
|
|
case 'month':
|
|
monthDay = this.monthDays['kronolithMonthDay' + date];
|
|
monthDay.select('div')
|
|
.findAll(function(el) { return el.retrieve('calendar') == calendar; })
|
|
.invoke('remove');
|
|
break;
|
|
|
|
case 'year':
|
|
titles = [];
|
|
busyHours = 0;
|
|
}
|
|
|
|
if (view == 'month' || view == 'agenda') {
|
|
events = this.getCacheForDate(date, calendar);
|
|
} else {
|
|
events = this.getCacheForDate(date);
|
|
}
|
|
events.sortBy(this.sortEvents).each(function(event) {
|
|
var insertBefore;
|
|
switch (view) {
|
|
case 'month':
|
|
case 'agenda':
|
|
if (calendar.startsWith('holiday|')) {
|
|
if (this.holidays.include(event.key)) {
|
|
return;
|
|
}
|
|
this.holidays.push(event.key);
|
|
}
|
|
if (view == 'month' && Kronolith.conf.max_events) {
|
|
more = monthDay.down('.kronolithMore');
|
|
if (more) {
|
|
more.purge();
|
|
more.remove();
|
|
}
|
|
}
|
|
if (view == 'month') {
|
|
if (Kronolith.conf.max_events) {
|
|
var events = monthDay.select('.kronolith-event');
|
|
if (events.size() >= Kronolith.conf.max_events) {
|
|
if (date == (new Date().dateString())) {
|
|
// This is today.
|
|
if (event.value.al || event.value.end.isBefore()) {
|
|
// No room for all-day or finished events.
|
|
this.insertMore(date);
|
|
return;
|
|
}
|
|
var remove, max;
|
|
// Find an event that is earlier than now or
|
|
// later then the current event.
|
|
events.each(function(elm) {
|
|
var calendar = elm.retrieve('calendar').split('|'),
|
|
event = this.ecache.get(calendar[0]).get(calendar[1]).get(date).get(elm.retrieve('eventid'));
|
|
if (event.start.isBefore()) {
|
|
remove = elm;
|
|
throw $break;
|
|
}
|
|
if (!max || event.start.isAfter(max)) {
|
|
max = event.start;
|
|
remove = elm;
|
|
}
|
|
}, this);
|
|
if (remove) {
|
|
remove.purge();
|
|
remove.remove();
|
|
insertBefore = this.findInsertBefore(events.without(remove), event, date);
|
|
} else {
|
|
this.insertMore(date);
|
|
return;
|
|
}
|
|
} else {
|
|
// Not today.
|
|
var allDays = events.findAll(function(elm) {
|
|
var calendar = elm.retrieve('calendar').split('|');
|
|
return this.ecache.get(calendar[0]).get(calendar[1]).get(date).get(elm.retrieve('eventid')).al;
|
|
}.bind(this));
|
|
if (event.value.al) {
|
|
// We want one all-day event.
|
|
if (allDays.size()) {
|
|
// There already is an all-day event.
|
|
if (event.value.x == Kronolith.conf.status.confirmed ||
|
|
event.value.x == Kronolith.conf.status.tentative) {
|
|
// But is there a less important
|
|
// one?
|
|
var status = [Kronolith.conf.status.free, Kronolith.conf.status.cancelled];
|
|
if (event.value.x == Kronolith.conf.status.confirmed) {
|
|
status.push(Kronolith.conf.status.tentative);
|
|
}
|
|
var free = allDays.detect(function(elm) {
|
|
var calendar = elm.retrieve('calendar').split('|');
|
|
return status.include(this.ecache.get(calendar[0]).get(calendar[1]).get(date).get(elm.retrieve('eventid')).x);
|
|
}.bind(this));
|
|
if (!free) {
|
|
this.insertMore(date);
|
|
return;
|
|
}
|
|
insertBefore = free.next();
|
|
free.purge();
|
|
free.remove();
|
|
} else {
|
|
// No.
|
|
this.insertMore(date);
|
|
return;
|
|
}
|
|
} else {
|
|
// Remove the last event to make room
|
|
// for this one.
|
|
var elm = events.pop();
|
|
elm.purge();
|
|
elm.remove();
|
|
insertBefore = events.first();
|
|
}
|
|
} else {
|
|
if (allDays.size() > 1) {
|
|
// We don't want more than one all-day
|
|
// event.
|
|
var elm = allDays.pop();
|
|
// Remove element from events as well.
|
|
events = events.without(elm);
|
|
elm.purge();
|
|
elm.remove();
|
|
insertBefore = this.findInsertBefore(events, event, date);
|
|
} else {
|
|
// This day is full.
|
|
this.insertMore(date);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
this.insertMore(date);
|
|
} else {
|
|
insertBefore = this.findInsertBefore(events, event, date);
|
|
}
|
|
} else {
|
|
var events = monthDay.select('.kronolith-event');
|
|
insertBefore = this.findInsertBefore(events, event, date);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'year':
|
|
title = '';
|
|
if (event.value.al) {
|
|
title += Kronolith.text.allday;
|
|
} else {
|
|
title += event.value.start.toString('t') + '-' + event.value.end.toString('t');
|
|
}
|
|
if (event.value.t) {
|
|
title += ': ' + event.value.t.escapeHTML();
|
|
}
|
|
if (event.value.x == Kronolith.conf.status.tentative ||
|
|
event.value.x == Kronolith.conf.status.confirmed) {
|
|
busyHours += event.value.start.getElapsed(event.value.end) / 3600000;
|
|
}
|
|
titles.push(title);
|
|
return;
|
|
}
|
|
this.insertEvent(event, date, view, insertBefore);
|
|
}, this);
|
|
|
|
switch (view) {
|
|
case 'agenda':
|
|
if ($('kronolithViewAgendaBody').select('tr').length > 2) {
|
|
$('kronolithAgendaNoItems').hide();
|
|
} else {
|
|
$('kronolithAgendaNoItems').show();
|
|
}
|
|
break;
|
|
|
|
case 'year':
|
|
var td = $('kronolithYear' + date);
|
|
if (td.className == 'kronolith-minical-empty') {
|
|
continue;
|
|
}
|
|
if (td.hasClassName('kronolith-today')) {
|
|
td.className = 'kronolith-today';
|
|
} else {
|
|
td.className = '';
|
|
}
|
|
if (titles.length) {
|
|
td.addClassName('kronolithHasEvents');
|
|
if (busyHours > 0) {
|
|
td.addClassName(this.getHeatmapClass(busyHours));
|
|
busyHours = 0;
|
|
}
|
|
td.down('a').writeAttribute('nicetitle', Object.toJSON(titles));
|
|
}
|
|
}
|
|
|
|
day.next().day();
|
|
}
|
|
// Workaround Firebug bug.
|
|
Prototype.emptyFunction();
|
|
},
|
|
|
|
findInsertBefore: function(events, event, date)
|
|
{
|
|
var insertBefore, insertSort;
|
|
events.each(function(elm) {
|
|
var calendar = elm.retrieve('calendar').split('|'),
|
|
existing = this.ecache
|
|
.get(calendar[0])
|
|
.get(calendar[1])
|
|
.get(date)
|
|
.get(elm.retrieve('eventid'));
|
|
if (event.value.sort < existing.sort &&
|
|
(!insertSort || existing.sort < insertSort)) {
|
|
insertBefore = elm;
|
|
insertSort = existing.sort;
|
|
}
|
|
}, this);
|
|
return insertBefore;
|
|
},
|
|
|
|
getHeatmapClass: function(hours)
|
|
{
|
|
return 'heat' + Math.min(Math.ceil(hours / 2), 6);
|
|
},
|
|
|
|
/**
|
|
* Creates the DOM node for an event bubble and inserts it into the view.
|
|
*
|
|
* @param object event A Hash member with the event to insert.
|
|
* @param string date The day to update.
|
|
* @param string view The view to update.
|
|
* @param Element before Insert the event before this element (month view).
|
|
*/
|
|
insertEvent: function(event, date, view, before)
|
|
{
|
|
var calendar = event.value.calendar.split('|');
|
|
event.value.nodeId = ('kronolithEvent' + view + event.value.calendar + date + event.key).replace(new RegExp('[^a-zA-Z0-9]', 'g'), '');
|
|
|
|
var _createElement = function(event) {
|
|
var className ='kronolith-event';
|
|
switch (event.value.x) {
|
|
case 3:
|
|
className += ' kronolith-event-cancelled';
|
|
break;
|
|
case 1:
|
|
case 4:
|
|
className += ' kronolith-event-tentative';
|
|
break;
|
|
}
|
|
var el = new Element('div', { id: event.value.nodeId, className: className })
|
|
.store('calendar', event.value.calendar)
|
|
.store('eventid', event.key);
|
|
if (!Object.isUndefined(event.value.aj)) {
|
|
el.store('ajax', event.value.aj);
|
|
}
|
|
return el;
|
|
};
|
|
|
|
switch (view) {
|
|
case 'day':
|
|
case 'week':
|
|
case 'workweek':
|
|
var storage = view + 'Sizes',
|
|
what = view == 'week' ? 'Week' : 'Workweek',
|
|
div = _createElement(event),
|
|
margin = view == 'day' ? 1 : 3,
|
|
style = { backgroundColor: Kronolith.conf.calendars[calendar[0]][calendar[1]].bg,
|
|
color: Kronolith.conf.calendars[calendar[0]][calendar[1]].fg };
|
|
|
|
div.writeAttribute('title', event.value.t);
|
|
|
|
if (event.value.al) {
|
|
if (view == 'day') {
|
|
$('kronolithViewDay').down('.kronolithAllDayContainer').insert(div.setStyle(style));
|
|
} else {
|
|
var allDay = this.allDays['kronolithAllDay' + date],
|
|
existing = allDay.childElements(),
|
|
weekHead = $('kronolithView' + what + 'Head');
|
|
if (existing.size() == 3) {
|
|
if (existing[2].className != 'kronolithMore') {
|
|
existing[2].purge();
|
|
existing[2].remove();
|
|
allDay.insert({ bottom: new Element('span', { className: 'kronolithMore' }).store('date', date).insert(Kronolith.text.more) });
|
|
}
|
|
} else {
|
|
allDay.insert(div.setStyle(style));
|
|
if (event.value.pe) {
|
|
div.addClassName('kronolithEditable');
|
|
var layout = div.getLayout(),
|
|
minLeft = weekHead.down('.kronolith-first-col').getWidth() + this[storage].spacing + (parseInt(div.getStyle('marginLeft'), 10) || 0),
|
|
minTop = weekHead.down('thead').getHeight() + this[storage].spacing + (parseInt(div.getStyle('marginTop'), 10) || 0),
|
|
maxLeft = weekHead.getWidth() - layout.get('margin-box-width'),
|
|
maxTop = weekHead.down('thead').getHeight() + weekHead.down('.kronolith-all-day').getHeight(),
|
|
opts = {
|
|
threshold: 5,
|
|
parentElement: function() {
|
|
return $('kronolithView' + what).down('.kronolith-view-head');
|
|
},
|
|
snap: function(x, y) {
|
|
return [Math.min(Math.max(x, minLeft), maxLeft),
|
|
Math.min(Math.max(y, minTop), maxTop - div.getHeight())];
|
|
}
|
|
};
|
|
var d = new Drag(event.value.nodeId, opts);
|
|
div.store('drags', []);
|
|
Object.extend(d, {
|
|
event: event,
|
|
innerDiv: new Element('div'),
|
|
midnight: this.parseDate(date)
|
|
});
|
|
div.retrieve('drags').push(d);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
var midnight = this.parseDate(date),
|
|
resizable = event.value.pe && (Object.isUndefined(event.value.vl) || event.value.vl),
|
|
innerDiv = new Element('div', { className: 'kronolith-event-info' }),
|
|
minHeight = 0, parentElement, draggerTop, draggerBottom,
|
|
elapsed = (event.value.start.getHours() - midnight.getHours()) * 60 + (event.value.start.getMinutes() - midnight.getMinutes());
|
|
switch (view) {
|
|
case 'day':
|
|
parentElement = $('kronolithEventsDay');
|
|
break;
|
|
case 'week':
|
|
parentElement = this.eventsWeek['kronolithEventsWeek' + date];
|
|
break;
|
|
case 'workweek':
|
|
parentElement = this.eventsWeek['kronolithEventsWorkweek' + date];
|
|
break;
|
|
}
|
|
if (event.value.fi) {
|
|
div.addClassName('kronolithFirst');
|
|
if (resizable) {
|
|
draggerTop = new Element('div', { id: event.value.nodeId + 'top', className: 'kronolithDragger kronolithDraggerTop' }).setStyle(style);
|
|
}
|
|
} else {
|
|
innerDiv.setStyle({ top: 0 });
|
|
}
|
|
if (event.value.la) {
|
|
div.addClassName('kronolithLast');
|
|
if (resizable) {
|
|
draggerBottom = new Element('div', { id: event.value.nodeId + 'bottom', className: 'kronolithDragger kronolithDraggerBottom' }).setStyle(style);
|
|
}
|
|
} else {
|
|
innerDiv.setStyle({ bottom: 0 });
|
|
}
|
|
|
|
div.setStyle({
|
|
top: (elapsed * this[storage].height / 60 | 0) + 'px',
|
|
width: 100 - margin + '%'
|
|
})
|
|
.insert(innerDiv.setStyle(style));
|
|
if (draggerTop) {
|
|
div.insert(draggerTop);
|
|
}
|
|
if (draggerBottom) {
|
|
div.insert(draggerBottom);
|
|
}
|
|
parentElement.insert(div);
|
|
if (draggerTop) {
|
|
minHeight += draggerTop.getHeight();
|
|
}
|
|
if (draggerBottom) {
|
|
minHeight += draggerBottom.getHeight();
|
|
}
|
|
if (!minHeight) {
|
|
minHeight = parseInt(innerDiv.getStyle('lineHeight'), 10)
|
|
+ (parseInt(innerDiv.getStyle('paddingTop'), 10) || 0)
|
|
+ (parseInt(innerDiv.getStyle('paddingBottom'), 10) || 0);
|
|
}
|
|
div.setStyle({ height: Math.max(Math.round(event.value.start.getElapsed(event.value.end) / 60000) * this[storage].height / 60 - this[storage].spacing | 0, minHeight) + 'px' });
|
|
|
|
if (event.value.pe) {
|
|
div.addClassName('kronolithEditable');
|
|
div.store('drags', []);
|
|
// Number of pixels that cover 10 minutes.
|
|
var step = this[storage].height / 6,
|
|
stepX, minLeft, maxLeft, maxTop,
|
|
minBottom, maxBottom, dragBottomHeight;
|
|
if (draggerBottom) {
|
|
// Height of bottom dragger
|
|
dragBottomHeight = draggerBottom.getHeight();
|
|
}
|
|
if (draggerTop) {
|
|
// Bottom-most position (maximum y) of top dragger
|
|
maxTop = div.offsetTop
|
|
- draggerTop.getHeight()
|
|
- parseInt(innerDiv.getStyle('lineHeight'), 10);
|
|
if (draggerBottom) {
|
|
maxTop += draggerBottom.offsetTop;
|
|
}
|
|
}
|
|
if (draggerBottom) {
|
|
// Top-most position (minimum y) of bottom dragger (upper
|
|
// edge)
|
|
minBottom = div.offsetTop
|
|
+ parseInt(innerDiv.getStyle('lineHeight'), 10);
|
|
// Bottom-most position (maximum y) of bottom dragger
|
|
// (upper edge)
|
|
maxBottom = 24 * this[storage].height
|
|
+ dragBottomHeight;
|
|
if (draggerTop) {
|
|
minBottom += draggerTop.getHeight();
|
|
}
|
|
}
|
|
// Height of the whole event div
|
|
var divHeight = div.getHeight(),
|
|
// Maximum height of the whole event div
|
|
maxDiv = 24 * this[storage].height - divHeight,
|
|
// Whether the top dragger is dragged, vs. the bottom
|
|
// dragger
|
|
opts = {
|
|
threshold: 5,
|
|
constraint: 'vertical',
|
|
scroll: this.kronolithBody,
|
|
nodrop: true,
|
|
parentElement: function() {
|
|
return parentElement;
|
|
}
|
|
};
|
|
|
|
if (draggerTop) {
|
|
opts.snap = function(x, y) {
|
|
y = Math.max(0, step * (Math.min(maxTop, y - this.scrollTop) / step | 0));
|
|
return [0, y];
|
|
}.bind(this);
|
|
var d = new Drag(event.value.nodeId + 'top', opts);
|
|
Object.extend(d, {
|
|
event: event,
|
|
innerDiv: innerDiv,
|
|
midnight: midnight
|
|
});
|
|
div.retrieve('drags').push(d);
|
|
}
|
|
|
|
if (draggerBottom) {
|
|
opts.snap = function(x, y) {
|
|
y = Math.min(maxBottom + dragBottomHeight + KronolithCore[storage].spacing, step * ((Math.max(minBottom, y - this.scrollTop) + dragBottomHeight + KronolithCore[storage].spacing) / step | 0)) - dragBottomHeight - KronolithCore[storage].spacing;
|
|
return [0, y];
|
|
}.bind(this);
|
|
var d = new Drag(event.value.nodeId + 'bottom', opts);
|
|
Object.extend(d, {
|
|
event: event,
|
|
innerDiv: innerDiv,
|
|
midnight: midnight
|
|
});
|
|
div.retrieve('drags').push(d);
|
|
}
|
|
|
|
if (view == 'week' || view == 'workweek') {
|
|
var dates = this.viewDates(midnight, view);
|
|
minLeft = this.eventsWeek['kronolithEvents' + what + dates[0].dateString()].offsetLeft - this.eventsWeek['kronolithEvents' + what + date].offsetLeft;
|
|
maxLeft = this.eventsWeek['kronolithEvents' + what + dates[1].dateString()].offsetLeft - this.eventsWeek['kronolithEvents' + what + date].offsetLeft;
|
|
stepX = (maxLeft - minLeft) / (view == 'week' ? 6 : 4);
|
|
}
|
|
var d = new Drag(div, {
|
|
threshold: 5,
|
|
nodrop: true,
|
|
parentElement: function() { return parentElement; },
|
|
snap: function(x, y) {
|
|
x = (view == 'week' || view == 'workweek')
|
|
? Math.max(minLeft, stepX * ((Math.min(maxLeft, x - (x < 0 ? stepX : 0)) + stepX / 2) / stepX | 0))
|
|
: 0;
|
|
y = Math.max(0, step * (Math.min(maxDiv, y - this.scrollTop) / step | 0));
|
|
return [x, y];
|
|
}.bind(this)
|
|
});
|
|
Object.extend(d, {
|
|
divHeight: divHeight,
|
|
startTop: div.offsetTop,
|
|
event: event,
|
|
midnight: midnight,
|
|
stepX: stepX
|
|
});
|
|
div.retrieve('drags').push(d);
|
|
}
|
|
|
|
var
|
|
// The current column that we're probing for available space.
|
|
column = 1,
|
|
// The number of columns in the current conflict group.
|
|
columns,
|
|
// The column width in the current conflict group.
|
|
width,
|
|
// The first event that conflict with the current event.
|
|
conflict = false,
|
|
// The conflict group where this event should go.
|
|
pos = this.dayGroups.length,
|
|
// The event below that the current event fits.
|
|
placeFound = false,
|
|
// The minimum (virtual) duration of each event, defined by the
|
|
// minimum height of an event DIV.
|
|
minMinutes = (minHeight + this[storage].spacing) * 60 / this[storage].height;
|
|
|
|
// this.dayEvents contains all events of the current day.
|
|
// this.dayGroups contains conflict groups, i.e. all events that
|
|
// conflict with each other and share a set of columns.
|
|
//
|
|
// Go through all events that have been added to this day already.
|
|
this.dayEvents.each(function(ev) {
|
|
// Due to the minimum height of an event DIV, events might
|
|
// visually overlap, even if they physically don't.
|
|
var minEnd = ev.start.clone().add(minMinutes).minutes(),
|
|
end = ev.end.isAfter(minEnd) ? ev.end : minEnd;
|
|
|
|
// If it doesn't conflict with the current event, go ahead.
|
|
if (!end.isAfter(event.value.start)) {
|
|
return;
|
|
}
|
|
|
|
// Found a conflicting event, now find its conflict group.
|
|
for (pos = 0; pos < this.dayGroups.length; pos++) {
|
|
if (this.dayGroups[pos].indexOf(ev) != -1) {
|
|
// Increase column for each conflicting event in this
|
|
// group.
|
|
this.dayGroups[pos].each(function(ce) {
|
|
var minEnd = ce.start.clone().add(minMinutes).minutes(),
|
|
end = ce.end.isAfter(minEnd) ? ce.end : minEnd;
|
|
if (end.isAfter(event.value.start)) {
|
|
column++;
|
|
}
|
|
});
|
|
throw $break;
|
|
}
|
|
}
|
|
}, this);
|
|
event.value.column = event.value.columns = column;
|
|
|
|
if (Object.isUndefined(this.dayGroups[pos])) {
|
|
this.dayGroups[pos] = [];
|
|
}
|
|
this.dayGroups[pos].push(event.value);
|
|
|
|
// See if the current event had to add yet another column.
|
|
columns = Math.max(this.dayGroups[pos][0].columns, column);
|
|
|
|
// Update the widths of all events in a conflict group.
|
|
width = 100 / columns;
|
|
this.dayGroups[pos].each(function(ev) {
|
|
ev.columns = columns;
|
|
$(ev.nodeId).setStyle({ width: width - margin + '%', left: (width * (ev.column - 1)) + '%' });
|
|
});
|
|
this.dayEvents.push(event.value);
|
|
|
|
div = innerDiv;
|
|
break;
|
|
|
|
case 'month':
|
|
var monthDay = this.monthDays['kronolithMonthDay' + date],
|
|
div = _createElement(event)
|
|
.setStyle({ backgroundColor: Kronolith.conf.calendars[calendar[0]][calendar[1]].bg,
|
|
color: Kronolith.conf.calendars[calendar[0]][calendar[1]].fg });
|
|
div.writeAttribute('title', event.value.t);
|
|
if (before) {
|
|
before.insert({ before: div });
|
|
} else {
|
|
monthDay.insert(div);
|
|
}
|
|
if (event.value.pe) {
|
|
div.setStyle({ cursor: 'move' });
|
|
new Drag(event.value.nodeId, { threshold: 5, parentElement: function() { return $('kronolith-month-body'); }, snapToParent: true });
|
|
}
|
|
if (Kronolith.conf.max_events) {
|
|
var more = monthDay.down('.kronolithMore');
|
|
if (more) {
|
|
monthDay.insert({ bottom: more.remove() });
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'agenda':
|
|
var div = _createElement(event)
|
|
.setStyle({ backgroundColor: Kronolith.conf.calendars[calendar[0]][calendar[1]].bg,
|
|
color: Kronolith.conf.calendars[calendar[0]][calendar[1]].fg });
|
|
this.createAgendaDay(date);
|
|
$('kronolithAgendaDay' + date).insert(div);
|
|
break;
|
|
}
|
|
|
|
this.setEventText(div, event.value,
|
|
{ time: view == 'agenda' || Kronolith.conf.show_time })
|
|
.observe('mouseover', div.addClassName.curry('kronolith-selected'))
|
|
.observe('mouseout', div.removeClassName.curry('kronolith-selected'));
|
|
},
|
|
|
|
/**
|
|
* Re-renders the necessary parts of the current view, if any event changes
|
|
* in those parts require re-rendering.
|
|
*
|
|
* @param Array dates The date strings of days to re-render.
|
|
*/
|
|
reRender: function(dates)
|
|
{
|
|
switch (this.view) {
|
|
case 'week':
|
|
case 'workweek':
|
|
case 'day':
|
|
dates.each(function(date) {
|
|
date = this.parseDate(date);
|
|
this.insertEvents([ date, date ], this.view);
|
|
}, this);
|
|
break;
|
|
case 'month':
|
|
dates.each(function(date) {
|
|
var day = this.monthDays['kronolithMonthDay' + date];
|
|
day.select('.kronolith-event').each(function(event) {
|
|
if (event.retrieve('calendar').startsWith('holiday')) {
|
|
delete this.holidays[event.retrieve('eventid')];
|
|
}
|
|
event.remove();
|
|
}, this);
|
|
day.select('.kronolithMore').invoke('remove');
|
|
date = this.parseDate(date);
|
|
this.loadEvents(date, date, 'month');
|
|
}, this);
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns all dates of the current view that contain (recurrences) of a
|
|
* certain event.
|
|
*
|
|
* @param String cal A calendar string.
|
|
* @param String eventid An event id.
|
|
*
|
|
* @return Array A list of date strings that contain a recurrence of the
|
|
* event.
|
|
*/
|
|
findEventDays: function(cal, eventid)
|
|
{
|
|
cal = cal.split('|');
|
|
var cache = this.ecache.get(cal[0]).get(cal[1]),
|
|
dates = this.viewDates(this.date, this.view),
|
|
day = dates[0], days = [], dateString;
|
|
while (!day.isAfter(dates[1])) {
|
|
dateString = day.dateString();
|
|
if (cache.get(dateString).get(eventid)) {
|
|
days.push(dateString);
|
|
}
|
|
day.add(1).days();
|
|
}
|
|
return days;
|
|
},
|
|
|
|
/**
|
|
* Adds a "more..." button to the month view cell that links to the days,
|
|
* or moves it to the buttom.
|
|
*
|
|
* @param string date The date string of the day cell.
|
|
*/
|
|
insertMore: function(date)
|
|
{
|
|
var monthDay = this.monthDays['kronolithMonthDay' + date],
|
|
more = monthDay.down('.kronolithMore');
|
|
if (more) {
|
|
monthDay.insert({ bottom: more.remove() });
|
|
} else {
|
|
monthDay.insert({ bottom: new Element('span', { className: 'kronolithMore' }).store('date', date).insert(Kronolith.text.more) });
|
|
}
|
|
},
|
|
|
|
setEventText: function(div, event, opts)
|
|
{
|
|
var calendar = event.calendar.split('|'),
|
|
span = new Element('span'),
|
|
time, end;
|
|
opts = Object.extend({ time: false }, opts || {});
|
|
|
|
div.update();
|
|
if (event.ic) {
|
|
div.insert(new Element('img', { src: event.ic, className: 'kronolithEventIcon' }));
|
|
}
|
|
if (opts.time && !event.al) {
|
|
time = new Element('span', { className: 'kronolith-time' })
|
|
.insert(event.start.toString(Kronolith.conf.time_format));
|
|
if (!event.start.equals(event.end)) {
|
|
end = event.end.clone();
|
|
if (end.getHours() == 23 &&
|
|
end.getMinutes() == 59 &&
|
|
end.getSeconds() == 59) {
|
|
end.add(1).second();
|
|
}
|
|
time.insert('-' + end.toString(Kronolith.conf.time_format));
|
|
}
|
|
div.insert(time).insert(' ');
|
|
}
|
|
div.insert(event.t.escapeHTML());
|
|
div.insert(span);
|
|
if (event.a) {
|
|
span.insert(' ')
|
|
.insert(new Element('img', { src: Kronolith.conf.images.alarm.replace(/fff/, Kronolith.conf.calendars[calendar[0]][calendar[1]].fg.substr(1)), title: Kronolith.text.alarm + ' ' + event.a }));
|
|
}
|
|
if (event.r) {
|
|
span.insert(' ')
|
|
.insert(new Element('img', { src: Kronolith.conf.images.recur.replace(/fff/, Kronolith.conf.calendars[calendar[0]][calendar[1]].fg.substr(1)), title: Kronolith.text.recur[event.r] }));
|
|
} else if (event.bid) {
|
|
div.store('bid', event.bid);
|
|
span.insert(' ')
|
|
.insert(new Element('img', { src: Kronolith.conf.images.exception.replace(/fff/, Kronolith.conf.calendars[calendar[0]][calendar[1]].fg.substr(1)), title: Kronolith.text.recur.exception }));
|
|
}
|
|
return div;
|
|
},
|
|
|
|
/**
|
|
* Finally removes events from the DOM and the cache.
|
|
*
|
|
* @param string calendar A calendar name.
|
|
* @param string event An event id. If empty, all events from the
|
|
* calendar are deleted.
|
|
*/
|
|
removeEvent: function(calendar, event)
|
|
{
|
|
this.deleteCache(calendar, event);
|
|
this.kronolithBody.select('div.kronolith-event').findAll(function(el) {
|
|
return el.retrieve('calendar') == calendar &&
|
|
(!event || el.retrieve('eventid') == event);
|
|
}).invoke('remove');
|
|
},
|
|
|
|
/**
|
|
* Removes all events that reprensent exceptions to the event series
|
|
* represented by uid.
|
|
*
|
|
* @param string calendar A calendar name.
|
|
* @param string uid An event uid.
|
|
*/
|
|
removeException: function(calendar, uid)
|
|
{
|
|
this.kronolithBody.select('div.kronolith-event').findAll(function(el) {
|
|
if (el.retrieve('calendar') == calendar && el.retrieve('bid') == uid) {
|
|
this.removeEvent(calendar, el.retrieve('eventid'));
|
|
}
|
|
}.bind(this));
|
|
},
|
|
|
|
/**
|
|
* Calculates the event's start and end dates based on some drag and drop
|
|
* information.
|
|
*/
|
|
calculateEventDates: function(event, storage, step, offset, height, start, end)
|
|
{
|
|
if (!Object.isUndefined(start)) {
|
|
event.start = start;
|
|
event.end = end;
|
|
}
|
|
event.start.set({
|
|
hour: offset / this[storage].height | 0,
|
|
minute: Math.round(offset % this[storage].height / step) * 10
|
|
});
|
|
var hour = (offset + height + this[storage].spacing) / this[storage].height | 0,
|
|
minute = Math.round((offset + height + this[storage].spacing) % this[storage].height / step) * 10,
|
|
second = 0;
|
|
if (hour == 24) {
|
|
hour = 23;
|
|
minute = 59;
|
|
second = 59;
|
|
}
|
|
event.end.set({
|
|
hour: hour,
|
|
minute: minute,
|
|
second: second
|
|
});
|
|
},
|
|
|
|
switchTaskView: function(on)
|
|
{
|
|
if (on) {
|
|
$('kronolithNewEvent', 'kronolithNewTask').compact()[0]
|
|
.replace(Kronolith.conf.new_task);
|
|
$('kronolithQuickEvent').addClassName('kronolithNewTask');
|
|
$('kronolithHeader').down('.kronolithPrev').up().addClassName('disabled');
|
|
$('kronolithHeader').down('.kronolithNext').up().addClassName('disabled');
|
|
} else {
|
|
$('kronolithNewEvent', 'kronolithNewTask').compact()[0]
|
|
.replace(Kronolith.conf.new_event);
|
|
$('kronolithQuickEvent').removeClassName('kronolithNewTask');
|
|
$('kronolithHeader').down('.kronolithPrev').up().removeClassName('disabled');
|
|
$('kronolithHeader').down('.kronolithNext').up().removeClassName('disabled');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns the task cache storage names that hold the tasks of the
|
|
* requested task type.
|
|
*
|
|
* @param string tasktype The task type.
|
|
*
|
|
* @return array The list of task cache storage names.
|
|
*/
|
|
getTaskStorage: function(tasktype)
|
|
{
|
|
var tasktypes;
|
|
if (tasktype == 'all' || tasktype == 'future') {
|
|
tasktypes = [ 'complete', 'incomplete' ];
|
|
} else {
|
|
tasktypes = [ tasktype ];
|
|
}
|
|
return tasktypes;
|
|
},
|
|
|
|
/**
|
|
* Loads tasks, either from cache or from the server.
|
|
*
|
|
* @param integer tasktype The tasks type (all, incomplete, complete, or
|
|
* future).
|
|
* @param Array tasksLists The lists from where to obtain the tasks.
|
|
*/
|
|
loadTasks: function(tasktype, tasklists)
|
|
{
|
|
var tasktypes = this.getTaskStorage(tasktype), loading = false,
|
|
spinner = $('kronolithLoading');
|
|
|
|
if (Object.isUndefined(tasklists)) {
|
|
tasklists = [];
|
|
$H(Kronolith.conf.calendars.tasklists).each(function(tasklist) {
|
|
if (tasklist.value.show)
|
|
{
|
|
tasklists.push(tasklist.key.substring(6));
|
|
}
|
|
});
|
|
}
|
|
|
|
tasktypes.each(function(type) {
|
|
tasklists.each(function(list) {
|
|
if (Object.isUndefined(this.tcache.get(type)) ||
|
|
Object.isUndefined(this.tcache.get(type).get(list))) {
|
|
loading = true;
|
|
this.loading++;
|
|
spinner.show();
|
|
HordeCore.doAction('listTasks', {
|
|
type: type,
|
|
list: list
|
|
}, {
|
|
callback: function(r) {
|
|
this.loadTasksCallback(r, true);
|
|
}.bind(this)
|
|
});
|
|
}
|
|
}, this);
|
|
}, this);
|
|
|
|
if (!loading) {
|
|
tasklists.each(function(list) {
|
|
this.insertTasks(tasktype, list);
|
|
}, this);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Callback method for inserting tasks in the current view.
|
|
*
|
|
* @param object r The ajax response object.
|
|
* @param boolean createCache Whether to create a cache list entry for the
|
|
* response, if none exists yet. Useful for
|
|
* (not) adding individual tasks to the cache
|
|
* without assuming to have all tasks of the
|
|
* list.
|
|
*/
|
|
loadTasksCallback: function(r, createCache)
|
|
{
|
|
// Hide spinner.
|
|
this.loading--;
|
|
if (!this.loading) {
|
|
$('kronolithLoading').hide();
|
|
}
|
|
|
|
this.storeTasksCache(r.tasks || {}, r.type, r.list, createCache);
|
|
|
|
// Check if result is still valid for the current view.
|
|
// There could be a rare race condition where two responses for the
|
|
// same task(s) arrive in the wrong order. Checking this too, like we
|
|
// do for events seems not worth it.
|
|
var tasktypes = this.getTaskStorage(this.tasktype),
|
|
tasklist = Kronolith.conf.calendars.tasklists['tasks/' + r.list];
|
|
if (this.view != 'tasks' ||
|
|
!tasklist || !tasklist.show ||
|
|
!tasktypes.include(r.type)) {
|
|
return;
|
|
}
|
|
this.insertTasks(this.tasktype, r.list);
|
|
},
|
|
|
|
/**
|
|
* Reads tasks from the cache and inserts them into the view.
|
|
*
|
|
* @param integer tasktype The tasks type (all, incomplete, complete, or
|
|
* future).
|
|
* @param string tasksList The task list to be drawn.
|
|
*/
|
|
insertTasks: function(tasktype, tasklist)
|
|
{
|
|
var tasktypes = this.getTaskStorage(tasktype), now = new Date();
|
|
|
|
$('kronolithViewTasksBody').select('tr').findAll(function(el) {
|
|
return el.retrieve('tasklist') == tasklist;
|
|
}).invoke('remove');
|
|
|
|
tasktypes.each(function(type) {
|
|
if (!this.tcache.get(type)) {
|
|
return;
|
|
}
|
|
var tasks = this.tcache.get(type).get(tasklist);
|
|
$H(tasks).each(function(task) {
|
|
switch (tasktype) {
|
|
case 'complete':
|
|
if (!task.value.cp) {
|
|
return;
|
|
}
|
|
break;
|
|
case 'incomplete':
|
|
if (task.value.cp ||
|
|
(!Object.isUndefined(task.value.start) &&
|
|
task.value.start.isAfter(now))) {
|
|
return;
|
|
}
|
|
break;
|
|
case 'future':
|
|
if (task.value.cp ||
|
|
Object.isUndefined(task.value.start) ||
|
|
!task.value.start.isAfter(now)) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
this.insertTask(task);
|
|
}, this);
|
|
}, this);
|
|
|
|
if ($('kronolithViewTasksBody').select('tr').length > 2) {
|
|
$('kronolithTasksNoItems').hide();
|
|
} else {
|
|
$('kronolithTasksNoItems').show();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates the DOM node for a task and inserts it into the view.
|
|
*
|
|
* @param object task A Hash with the task to insert
|
|
*/
|
|
insertTask: function(task)
|
|
{
|
|
var row = $('kronolithTasksTemplate').clone(true),
|
|
col = row.down(), tagc;
|
|
|
|
|
|
row.removeAttribute('id');
|
|
row.store('tasklist', task.value.l);
|
|
row.store('taskid', task.key);
|
|
col.addClassName('kronolithTask' + (!!task.value.cp ? 'Completed' : ''));
|
|
col.setStyle({
|
|
backgroundColor: Kronolith.conf.calendars.tasklists['tasks/' + task.value.l].bg,
|
|
color: Kronolith.conf.calendars.tasklists['tasks/' + task.value.l].fg,
|
|
textIndent: task.value.i + 'em'
|
|
});
|
|
col.insert(task.value.n.escapeHTML());
|
|
if (!Object.isUndefined(task.value.due)) {
|
|
var now = new Date();
|
|
if (!now.isBefore(task.value.due)) {
|
|
col.addClassName('kronolithTaskDue');
|
|
}
|
|
col.insert(new Element('span', { className: 'kronolithSeparator' }).update(' · '));
|
|
col.insert(new Element('span', { className: 'kronolithDate' }).update(task.value.due.toString(Kronolith.conf.date_format)));
|
|
if (task.value.r) {
|
|
col.insert(' ')
|
|
.insert(new Element('img', { src: Kronolith.conf.images.recur.replace(/fff/, Kronolith.conf.calendars.tasklists['tasks/' + task.value.l].fg.substr(1)), title: Kronolith.text.recur[task.value.r] }));
|
|
}
|
|
}
|
|
|
|
if (!Object.isUndefined(task.value.sd)) {
|
|
col.insert(new Element('span', { className: 'kronolithSeparator' }).update(' · '));
|
|
col.insert(new Element('span', { className: 'kronolithInfo' }).update(task.value.sd.escapeHTML()));
|
|
}
|
|
|
|
if (task.value.t && task.value.t.size() > 0) {
|
|
tagc = new Element('ul', { className: 'horde-tags' });
|
|
task.value.t.each(function(x) {
|
|
tagc.insert(new Element('li').update(x.escapeHTML()));
|
|
});
|
|
col.insert(tagc);
|
|
}
|
|
row.insert(col.show());
|
|
this.insertTaskPosition(row, task);
|
|
},
|
|
|
|
/**
|
|
* Inserts the task row in the correct position.
|
|
*
|
|
* @param Element newRow The new row to be inserted.
|
|
* @param object newTask A Hash with the task being added.
|
|
*/
|
|
insertTaskPosition: function(newRow, newTask)
|
|
{
|
|
var rows = $('kronolithViewTasksBody').select('tr'),
|
|
rowTasklist, rowTaskId, rowTask, parentFound;
|
|
// The first row is a template, ignoring.
|
|
for (var i = 2; i < rows.length; i++) {
|
|
rowTasklist = rows[i].retrieve('tasklist');
|
|
rowTaskId = rows[i].retrieve('taskid');
|
|
if (newTask.value.p) {
|
|
if (rowTaskId == newTask.value.p) {
|
|
parentFound = true;
|
|
continue;
|
|
}
|
|
if (!parentFound) {
|
|
continue;
|
|
}
|
|
}
|
|
rowTask = this.tcache.inject(null, function(acc, list) {
|
|
if (acc) {
|
|
return acc;
|
|
}
|
|
if (!Object.isUndefined(list.value.get(rowTasklist))) {
|
|
return list.value.get(rowTasklist).get(rowTaskId);
|
|
}
|
|
});
|
|
|
|
if (Object.isUndefined(rowTask)) {
|
|
// TODO: Throw error
|
|
return;
|
|
}
|
|
if (!this.isTaskAfter(newTask.value, rowTask)) {
|
|
break;
|
|
}
|
|
}
|
|
rows[--i].insert({ after: newRow.show() });
|
|
},
|
|
|
|
/**
|
|
* Analyzes which task should be drawn first.
|
|
*
|
|
* TODO: Very incomplete, only a dummy version
|
|
*/
|
|
isTaskAfter: function(taskA, taskB)
|
|
{
|
|
// TODO: Make all ordering system
|
|
if ((taskA.p || taskB.p) && taskA.p != taskB.p) {
|
|
return !taskA.p;
|
|
}
|
|
return (taskA.pr >= taskB.pr);
|
|
},
|
|
|
|
/**
|
|
* Completes/uncompletes a task.
|
|
*
|
|
* @param string tasklist The task list to which the tasks belongs.
|
|
* @param string taskid The id of the task.
|
|
* @param boolean|string complete True if the task is completed, a
|
|
* due date if there are still
|
|
* incomplete recurrences.
|
|
*/
|
|
toggleCompletion: function(tasklist, taskid, complete)
|
|
{
|
|
// Update the cache.
|
|
var task = this.tcache.inject(null, function(acc, list) {
|
|
if (acc) {
|
|
return acc;
|
|
}
|
|
if (!Object.isUndefined(list.value.get(tasklist))) {
|
|
return list.value.get(tasklist).get(taskid);
|
|
}
|
|
});
|
|
if (Object.isUndefined(task)) {
|
|
// This shouldn't happen.
|
|
this.toggleCompletionClass(taskid);
|
|
return;
|
|
}
|
|
if (Object.isUndefined(complete) || complete === true) {
|
|
task.cp = !task.cp;
|
|
}
|
|
|
|
if (this.tcache.get(task.cp ? 'complete' : 'incomplete')) {
|
|
this.tcache.get(task.cp ? 'complete' : 'incomplete').get(tasklist).set(taskid, task);
|
|
}
|
|
if (this.tcache.get(task.cp ? 'incomplete' : 'complete')) {
|
|
this.tcache.get(task.cp ? 'incomplete' : 'complete').get(tasklist).unset(taskid);
|
|
}
|
|
|
|
// Remove row if necessary.
|
|
var row = this.getTaskRow(taskid);
|
|
if (!row) {
|
|
return;
|
|
}
|
|
if ((this.tasktype == 'complete' && !task.cp) ||
|
|
((this.tasktype == 'incomplete' || this.tasktype == 'future_incomplete') && task.cp) ||
|
|
((complete === true) && (this.tasktype == 'future'))) {
|
|
|
|
row.fade({
|
|
duration: this.effectDur,
|
|
afterFinish: function() {
|
|
row.purge();
|
|
row.remove();
|
|
|
|
//Check if items remained in interface
|
|
if ($('kronolithViewTasksBody').select('tr').length < 3) {
|
|
$('kronolithTasksNoItems').show();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update due date if necessary.
|
|
if (!Object.isUndefined(complete) && complete !== true) {
|
|
var now = new Date(), due = Date.parse(complete);
|
|
row.down('span.kronolithDate')
|
|
.update(due.toString(Kronolith.conf.date_format));
|
|
if (now.isBefore(due)) {
|
|
row.down('td.kronolithTaskCol')
|
|
.removeClassName('kronolithTaskDue');
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Toggles the CSS class to show that a task is completed/uncompleted.
|
|
*
|
|
* @param string taskid The id of the task.
|
|
*/
|
|
toggleCompletionClass: function(taskid)
|
|
{
|
|
var row = this.getTaskRow(taskid);
|
|
if (!row) {
|
|
return;
|
|
}
|
|
var col = row.down('td.kronolithTaskCol');
|
|
col.toggleClassName('kronolithTask');
|
|
col.toggleClassName('kronolithTaskCompleted');
|
|
},
|
|
|
|
/**
|
|
* Returns the table row of a task.
|
|
*
|
|
* @param string taskid The id of the task.
|
|
*
|
|
* @return Element The table row of the task list, if found.
|
|
*/
|
|
getTaskRow: function(taskid)
|
|
{
|
|
return $('kronolithViewTasksBody').select('tr').find(function(el) {
|
|
return el.retrieve('taskid') == taskid;
|
|
});
|
|
},
|
|
|
|
editTask: function(tasklist, id, desc)
|
|
{
|
|
if (this.redBoxLoading) {
|
|
return;
|
|
}
|
|
|
|
if (Object.isUndefined(HordeImple.AutoCompleter.kronolithTaskTags)) {
|
|
this.editTask.bind(this, tasklist, id, desc).defer();
|
|
return;
|
|
}
|
|
|
|
this.closeRedBox();
|
|
this.quickClose();
|
|
this.redBoxOnDisplay = RedBox.onDisplay;
|
|
RedBox.onDisplay = function() {
|
|
if (this.redBoxOnDisplay) {
|
|
this.redBoxOnDisplay();
|
|
}
|
|
try {
|
|
$('kronolithTaskForm').focusFirstElement();
|
|
} catch(e) {}
|
|
RedBox.onDisplay = this.redBoxOnDisplay;
|
|
}.bind(this);
|
|
|
|
this.openTab($('kronolithTaskForm').down('.tabset a.kronolithTabLink'));
|
|
$('kronolithTaskForm').enable();
|
|
$('kronolithTaskForm').reset();
|
|
HordeImple.AutoCompleter.kronolithTaskTags.reset();
|
|
$('kronolithTaskSave').show().enable();
|
|
$('kronolithTaskDelete').show().enable();
|
|
$('kronolithTaskForm').down('.kronolithFormActions .kronolithSeparator').show();
|
|
this.updateTasklistDropDown();
|
|
this.disableAlarmMethods('Task');
|
|
this.knl.kronolithTaskDueTime.markSelected();
|
|
if (id) {
|
|
RedBox.loading();
|
|
this.updateTaskParentDropDown(tasklist);
|
|
this.updateTaskAssigneeDropDown(tasklist);
|
|
HordeCore.doAction('getTask', {
|
|
list: tasklist,
|
|
id: id
|
|
}, {
|
|
callback: this.editTaskCallback.bind(this)
|
|
});
|
|
$('kronolithTaskTopTags').update();
|
|
} else {
|
|
$('kronolithTaskId').clear();
|
|
$('kronolithTaskOldList').clear();
|
|
$('kronolithTaskList').setValue(Kronolith.conf.tasks.default_tasklist);
|
|
this.updateTaskParentDropDown(Kronolith.conf.tasks.default_tasklist);
|
|
this.updateTaskAssigneeDropDown(Kronolith.conf.tasks.default_tasklist);
|
|
$('kronolithTaskParent').setValue('');
|
|
$('kronolithTaskAssignee').setValue('');
|
|
//$('kronolithTaskLocation').setValue('http://');
|
|
HordeCore.doAction('listTopTags', {}, {
|
|
callback: this.topTagsCallback.curry('kronolithTaskTopTags', 'kronolithTaskTag')
|
|
});
|
|
$('kronolithTaskPriority').setValue(3);
|
|
if (Kronolith.conf.tasks.default_due) {
|
|
this.setDefaultDue();
|
|
}
|
|
if (desc) {
|
|
$('kronolithTaskDescription').setValue(desc);
|
|
}
|
|
this.toggleRecurrence(false, 'None');
|
|
$('kronolithTaskDelete').hide();
|
|
this.redBoxLoading = true;
|
|
RedBox.showHtml($('kronolithTaskDialog').show());
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Callback method for showing task forms.
|
|
*
|
|
* @param object r The ajax response object.
|
|
*/
|
|
editTaskCallback: function(r)
|
|
{
|
|
if (!r.task) {
|
|
RedBox.close();
|
|
this.go(this.lastLocation);
|
|
return;
|
|
}
|
|
|
|
var task = r.task;
|
|
|
|
/* Basic information */
|
|
$('kronolithTaskId').setValue(task.id);
|
|
$('kronolithTaskOldList').setValue(task.l);
|
|
$('kronolithTaskList').setValue(task.l);
|
|
$('kronolithTaskTitle').setValue(task.n);
|
|
$('kronolithTaskParent').setValue(task.p);
|
|
$('kronolithTaskAssignee').setValue(task.as);
|
|
//$('kronolithTaskLocation').setValue(task.l);
|
|
if (task.dd) {
|
|
$('kronolithTaskDueDate').setValue(task.dd);
|
|
}
|
|
if (task.dt) {
|
|
$('kronolithTaskDueTime').setValue(task.dt);
|
|
this.knl.kronolithTaskDueTime.setSelected(task.dt);
|
|
}
|
|
$('kronolithTaskDescription').setValue(task.de);
|
|
$('kronolithTaskPriority').setValue(task.pr);
|
|
$('kronolithTaskCompleted').setValue(task.cp);
|
|
|
|
/* Alarm */
|
|
if (task.a) {
|
|
this.enableAlarm('Task', task.a);
|
|
if (task.m) {
|
|
$('kronolithTaskAlarmDefaultOff').checked = true;
|
|
$H(task.m).each(function(method) {
|
|
if (!$('kronolithTaskAlarm' + method.key)) {
|
|
return;
|
|
}
|
|
$('kronolithTaskAlarm' + method.key).setValue(1);
|
|
if ($('kronolithTaskAlarm' + method.key + 'Params')) {
|
|
$('kronolithTaskAlarm' + method.key + 'Params').show();
|
|
}
|
|
$H(method.value).each(function(param) {
|
|
var input = $('kronolithTaskAlarmParam' + param.key);
|
|
if (!input) {
|
|
return;
|
|
}
|
|
if (input.type == 'radio') {
|
|
input.up('form').select('input[type=radio]').each(function(radio) {
|
|
if (radio.name == input.name &&
|
|
radio.value == param.value) {
|
|
radio.setValue(1);
|
|
throw $break;
|
|
}
|
|
});
|
|
} else {
|
|
input.setValue(param.value);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
} else {
|
|
$('kronolithTaskAlarmOff').setValue(true);
|
|
}
|
|
|
|
/* Recurrence */
|
|
if (task.r) {
|
|
this.setRecurrenceFields(false, task.r);
|
|
} else {
|
|
this.toggleRecurrence(false, 'None');
|
|
}
|
|
|
|
HordeImple.AutoCompleter.kronolithTaskTags.reset(task.t);
|
|
|
|
if (!task.pe) {
|
|
$('kronolithTaskSave').hide();
|
|
$('kronolithTaskForm').disable();
|
|
} else {
|
|
HordeCore.doAction('listTopTags', {}, {
|
|
callback: this.topTagsCallback.curry('kronolithTaskTopTags', 'kronolithTaskTag')
|
|
});
|
|
}
|
|
|
|
if (!task.pd) {
|
|
$('kronolithTaskDelete').show();
|
|
}
|
|
if (!task.pe && !task.pd) {
|
|
$('kronolithTaskForm').down('.kronolithFormActions .kronolithSeparator').hide();
|
|
}
|
|
|
|
this.setTitle(task.n);
|
|
this.redBoxLoading = true;
|
|
RedBox.showHtml($('kronolithTaskDialog').show());
|
|
|
|
/* Hide alarm message for this task. */
|
|
if (r.msgs) {
|
|
r.msgs = r.msgs.reject(function(msg) {
|
|
if (msg.type != 'horde.alarm') {
|
|
return false;
|
|
}
|
|
var alarm = msg.flags.alarm;
|
|
if (alarm.params && alarm.params.notify &&
|
|
alarm.params.notify.show &&
|
|
alarm.params.notify.show.tasklist &&
|
|
alarm.params.notify.show.task &&
|
|
alarm.params.notify.show.tasklist == task.l &&
|
|
alarm.params.notify.show.task == task.id) {
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Propagates a SELECT drop down list with the editable task lists.
|
|
*/
|
|
updateTasklistDropDown: function()
|
|
{
|
|
var tasklist = $('kronolithTaskList');
|
|
tasklist.update();
|
|
$H(Kronolith.conf.calendars.tasklists).each(function(cal) {
|
|
if (cal.value.edit) {
|
|
tasklist.insert(new Element('option', { value: cal.key.substring(6) })
|
|
.setStyle({ backgroundColor: cal.value.bg, color: cal.value.fg })
|
|
.update(cal.value.name.escapeHTML()));
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Propagates a SELECT drop down list with the tasks of a task list.
|
|
*
|
|
* @param string list A task list ID.
|
|
*/
|
|
updateTaskParentDropDown: function(list)
|
|
{
|
|
var parents = $('kronolithTaskParent');
|
|
parents.update(new Element('option', { value: '' })
|
|
.update(Kronolith.text.no_parent));
|
|
HordeCore.doAction('listTasks', {
|
|
type: 'future_incomplete',
|
|
list: list
|
|
}, {
|
|
ajaxopts: { asynchronuous: false },
|
|
callback: function(r) {
|
|
$H(r.tasks).each(function(task) {
|
|
parents.insert(new Element('option', { value: task.key })
|
|
.setStyle({ textIndent: task.value.i + 'em' })
|
|
.update(task.value.n.escapeHTML()));
|
|
});
|
|
}.bind(this)
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Propagates a SELECT drop down list with the users of a task list.
|
|
*
|
|
* @param string list A task list ID.
|
|
*/
|
|
updateTaskAssigneeDropDown: function(list)
|
|
{
|
|
var assignee = $('kronolithTaskAssignee');
|
|
assignee.update(new Element('option', { value: '' })
|
|
.update(Kronolith.text.no_assignee));
|
|
$H(Kronolith.conf.calendars.tasklists['tasks/' + list].users).each(function(user) {
|
|
assignee.insert(new Element('option', { value: user.key })
|
|
.update(user.value.escapeHTML()));
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Sets the default due date and time for tasks.
|
|
*/
|
|
setDefaultDue: function()
|
|
{
|
|
if ($F('kronolithTaskDueDate') || $F('kronolithTaskDueTime')) {
|
|
return;
|
|
}
|
|
$('kronolithTaskDueDate').setValue(new Date().add(Kronolith.conf.tasks.default_due_days).days().toString(Kronolith.conf.date_format));
|
|
if (Kronolith.conf.tasks.default_due_time == 'now') {
|
|
$('kronolithTaskDueTime').setValue(new Date().toString(Kronolith.conf.time_format));
|
|
} else {
|
|
var date = new Date();
|
|
date.setHours(Kronolith.conf.tasks.default_due_time.replace(/:.*$/, ''));
|
|
date.setMinutes(0);
|
|
$('kronolithTaskDueTime').setValue(date.toString(Kronolith.conf.time_format));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Finally removes tasks from the DOM and the cache.
|
|
*
|
|
* @param string list A task list name.
|
|
* @param string task A task id. If empty, all tasks from the list are
|
|
* deleted.
|
|
*/
|
|
removeTask: function(list, task)
|
|
{
|
|
this.deleteTasksCache(list, task);
|
|
$('kronolithViewTasksBody').select('tr').findAll(function(el) {
|
|
return el.retrieve('tasklist') == list &&
|
|
(!task || el.retrieve('taskid') == task);
|
|
}).invoke('remove');
|
|
this.removeEvent('tasklists|tasks/' + list, task ? '_tasks' + task : null);
|
|
if ($('kronolithViewTasksBody').select('tr').length > 2) {
|
|
$('kronolithTasksNoItems').hide();
|
|
} else {
|
|
$('kronolithTasksNoItems').show();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Submits the task edit form to create or update a task.
|
|
*/
|
|
saveTask: function()
|
|
{
|
|
if (this.wrongFormat.size() ||
|
|
(($F('kronolithTaskAlarmOn')) && $F('kronolithTaskDueDate').length == 0)) {
|
|
HordeCore.notify(Kronolith.text.fix_form_values, 'horde.warning');
|
|
return;
|
|
}
|
|
|
|
var tasklist = $F('kronolithTaskOldList'),
|
|
target = $F('kronolithTaskList'),
|
|
taskid = $F('kronolithTaskId'),
|
|
viewDates = this.viewDates(this.date, this.view),
|
|
start = viewDates[0].dateString(),
|
|
end = viewDates[1].dateString();
|
|
|
|
HordeImple.AutoCompleter.kronolithTaskTags.shutdown();
|
|
$('kronolithTaskSave').disable();
|
|
this.startLoading('tasklists|tasks/' + target, start + end + this.tasktype);
|
|
this.loading++;
|
|
HordeCore.doAction(
|
|
'saveTask',
|
|
$H($('kronolithTaskForm').serialize({ hash: true })).merge({
|
|
sig: start + end + this.tasktype,
|
|
view: this.view,
|
|
view_start: start,
|
|
view_end: end
|
|
}), {
|
|
callback: function(r) {
|
|
if (r.tasks && taskid) {
|
|
this.removeTask(tasklist, taskid);
|
|
}
|
|
this.loadTasksCallback(r, false);
|
|
this.loadEventsCallback(r, false);
|
|
if (r.tasks) {
|
|
this.closeRedBox();
|
|
this.go(this.lastLocation);
|
|
} else {
|
|
$('kronolithTaskSave').enable();
|
|
}
|
|
}.bind(this)
|
|
}
|
|
);
|
|
},
|
|
|
|
quickSaveTask: function()
|
|
{
|
|
var text = $F('kronolithQuicktaskQ'),
|
|
viewDates = this.viewDates(this.date, 'tasks'),
|
|
start = viewDates[0].dateString(),
|
|
end = viewDates[1].dateString(),
|
|
params = {
|
|
sig: start + end + this.tasktype,
|
|
view: 'tasks',
|
|
view_start: start,
|
|
view_end: end,
|
|
tasklist: Kronolith.conf.tasks.default_tasklist,
|
|
text: text
|
|
};
|
|
|
|
this.closeRedBox();
|
|
this.startLoading('tasklists|tasks/' + Kronolith.conf.tasks.default_tasklist,
|
|
params.sig);
|
|
this.loading++;
|
|
HordeCore.doAction('quickSaveTask', params, {
|
|
callback: function(r) {
|
|
this.loadTasksCallback(r, false);
|
|
this.loadEventsCallback(r, false);
|
|
if (!r.tasks || !$H(r.tasks).size()) {
|
|
this.editTask(null, null, text);
|
|
} else {
|
|
$('kronolithQuicktaskQ').value = '';
|
|
}
|
|
}.bind(this)
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Opens the form for editing a calendar.
|
|
*
|
|
* @param string calendar Calendar type and calendar id, separated by '|'.
|
|
*/
|
|
editCalendar: function(calendar)
|
|
{
|
|
if (this.redBoxLoading) {
|
|
return;
|
|
}
|
|
|
|
this.closeRedBox();
|
|
this.quickClose();
|
|
|
|
var type = calendar.split('|')[0], cal = calendar.split('|')[1];
|
|
if (!$w('internal tasklists remote holiday resource resourcegroup').include(type)) {
|
|
return;
|
|
}
|
|
|
|
if (cal &&
|
|
(Object.isUndefined(Kronolith.conf.calendars[type]) ||
|
|
Object.isUndefined(Kronolith.conf.calendars[type][cal])) &&
|
|
(type == 'internal' || type == 'tasklists')) {
|
|
HordeCore.doAction('getCalendar', {
|
|
cal: cal
|
|
}, {
|
|
callback: function(r) {
|
|
if (r.calendar) {
|
|
Kronolith.conf.calendars[type][cal] = r.calendar;
|
|
this.insertCalendarInList(type, cal, r.calendar);
|
|
$('kronolithSharedCalendars').show();
|
|
this.editCalendar(type + '|' + cal);
|
|
} else {
|
|
this.go(this.lastLocation);
|
|
}
|
|
}.bind(this)
|
|
});
|
|
return;
|
|
}
|
|
|
|
this.redBoxOnDisplay = RedBox.onDisplay;
|
|
RedBox.onDisplay = function() {
|
|
if (this.redBoxOnDisplay) {
|
|
this.redBoxOnDisplay();
|
|
}
|
|
try {
|
|
$('kronolithCalendarForm' + type).focusFirstElement();
|
|
} catch(e) {}
|
|
RedBox.onDisplay = this.redBoxOnDisplay;
|
|
}.bind(this);
|
|
|
|
if ($('kronolithCalendarDialog')) {
|
|
this.redBoxLoading = true;
|
|
RedBox.showHtml($('kronolithCalendarDialog').show());
|
|
this.editCalendarCallback(calendar);
|
|
} else {
|
|
RedBox.loading();
|
|
HordeCore.doAction('chunkContent', {
|
|
chunk: 'calendar'
|
|
}, {
|
|
callback: function(r) {
|
|
if (r.chunk) {
|
|
this.redBoxLoading = true;
|
|
RedBox.showHtml(r.chunk);
|
|
['internal', 'tasklists'].each(function(type) {
|
|
$('kronolithC' + type + 'PGList').observe('change', function() {
|
|
$('kronolithC' + type + 'PG').setValue(1);
|
|
this.permsClickHandler(type, 'G');
|
|
}.bind(this));
|
|
}, this);
|
|
this.editCalendarCallback(calendar);
|
|
} else {
|
|
this.closeRedBox();
|
|
}
|
|
}.bind(this)
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Callback for editing a calendar. Fills the edit form with the correct
|
|
* values.
|
|
*
|
|
* @param string calendar Calendar type and calendar id, separated by '|'.
|
|
*/
|
|
editCalendarCallback: function(calendar)
|
|
{
|
|
calendar = calendar.split('|');
|
|
var type = calendar[0];
|
|
calendar = calendar.length == 1 ? null : calendar[1];
|
|
|
|
var form = $('kronolithCalendarForm' + type),
|
|
firstTab = form.down('.tabset a.kronolithTabLink'),
|
|
info;
|
|
|
|
form.enable();
|
|
form.reset();
|
|
if (firstTab) {
|
|
this.openTab(firstTab);
|
|
}
|
|
$('kronolithCalendarDialog').select('.kronolithCalendarDiv').invoke('hide');
|
|
$('kronolithCalendar' + type + '1').show();
|
|
form.select('.kronolithCalendarContinue').invoke('enable');
|
|
$('kronolithC' + type + 'PUNew', 'kronolithC' + type + 'PGNew').compact().each(function(elm) {
|
|
if (elm.tagName == 'SELECT') {
|
|
$A(elm.options).each(function(option) {
|
|
option.writeAttribute('disabled', false);
|
|
});
|
|
}
|
|
});
|
|
|
|
var newCalendar = !calendar;
|
|
if (calendar &&
|
|
(Object.isUndefined(Kronolith.conf.calendars[type]) ||
|
|
Object.isUndefined(Kronolith.conf.calendars[type][calendar]))) {
|
|
if (type != 'remote') {
|
|
this.closeRedBox();
|
|
this.go(this.lastLocation);
|
|
return;
|
|
}
|
|
newCalendar = true;
|
|
}
|
|
if (type == 'resourcegroup') {
|
|
this.updateResourcegroupSelect();
|
|
}
|
|
if (newCalendar) {
|
|
switch (type) {
|
|
case 'internal':
|
|
HordeImple.AutoCompleter.kronolithCalendarinternalTags.reset();
|
|
// Fall through.
|
|
case 'tasklists':
|
|
$('kronolithCalendar' + type + 'LinkExport').up('span').hide();
|
|
break;
|
|
case 'remote':
|
|
if (calendar) {
|
|
$('kronolithCalendarremoteUrl').setValue(calendar);
|
|
$('kronolithCalendarremoteId').setValue(calendar);
|
|
}
|
|
break;
|
|
case 'holiday':
|
|
$('kronolithCalendarholidayDriver').update();
|
|
$H(Kronolith.conf.calendars.holiday).each(function(calendar) {
|
|
if (calendar.value.show) {
|
|
return;
|
|
}
|
|
$('kronolithCalendarholidayDriver').insert(
|
|
new Element('option', { value: calendar.key })
|
|
.setStyle({ color: calendar.value.fg, backgroundColor: calendar.value.bg })
|
|
.insert(calendar.value.name.escapeHTML())
|
|
);
|
|
});
|
|
break;
|
|
}
|
|
$('kronolithCalendar' + type + 'Id').clear();
|
|
var color = '#', i;
|
|
for (i = 0; i < 3; i++) {
|
|
color += (Math.random() * 256 | 0).toColorPart();
|
|
}
|
|
$('kronolithCalendar' + type + 'Color').setValue(color).setStyle({ backgroundColor: color, color: Color.brightness(Color.hex2rgb(color)) < 125 ? '#fff' : '#000' });
|
|
form.down('.kronolithCalendarDelete').hide();
|
|
$('kronolithCalendarinternalImportButton').hide();
|
|
} else {
|
|
info = Kronolith.conf.calendars[type][calendar];
|
|
|
|
$('kronolithCalendar' + type + 'Id').setValue(calendar);
|
|
$('kronolithCalendar' + type + 'Name').setValue(info.name);
|
|
$('kronolithCalendar' + type + 'Color').setValue(info.bg).setStyle({ backgroundColor: info.bg, color: info.fg });
|
|
$('kronolithCalendarinternalImportButton').hide();
|
|
|
|
switch (type) {
|
|
case 'internal':
|
|
HordeImple.AutoCompleter.kronolithCalendarinternalTags.reset(Kronolith.conf.calendars.internal[calendar].tg);
|
|
$('kronolithCalendar' + type + 'ImportCal').setValue('internal_' + calendar);
|
|
if ($('kronolithCalendar' + type + 'LinkImport')) {
|
|
if (info.edit) {
|
|
$('kronolithCalendar' + type + 'LinkImport').up('li').show();
|
|
} else {
|
|
$('kronolithCalendar' + type + 'LinkImport').up('li').hide();
|
|
}
|
|
}
|
|
$('kronolithCalendar' + type + 'UrlFeed').setValue(info.feed);
|
|
$('kronolithCalendar' + type + 'EmbedUrl').setValue(info.embed);
|
|
// Fall through.
|
|
case 'tasklists':
|
|
$('kronolithCalendar' + type + 'Description').setValue(info.desc);
|
|
if ($('kronolithCalendar' + type + 'LinkExport')) {
|
|
$('kronolithCalendar' + type + 'LinkExport').up('span').show();
|
|
$('kronolithCalendar' + type + 'Export').href = type == 'internal'
|
|
? Kronolith.conf.URI_CALENDAR_EXPORT.interpolate({ calendar: calendar })
|
|
: Kronolith.conf.tasks.URI_TASKLIST_EXPORT.interpolate({ tasklist: calendar.substring(6) });
|
|
}
|
|
$('kronolithCalendar' + type + 'LinkUrls').up().show();
|
|
if (info.caldav) {
|
|
$('kronolithCalendar' + type + 'UrlCaldav').setValue(info.caldav);
|
|
$('kronolithCalendar' + type + 'Caldav').show();
|
|
} else {
|
|
$('kronolithCalendar' + type + 'Caldav').hide();
|
|
}
|
|
$('kronolithCalendar' + type + 'UrlWebdav').setValue(info.sub);
|
|
break;
|
|
case 'remote':
|
|
$('kronolithCalendarremoteUrl').setValue(calendar);
|
|
$('kronolithCalendarremoteDescription').setValue(info.desc);
|
|
$('kronolithCalendarremoteUsername').setValue(info.user);
|
|
$('kronolithCalendarremotePassword').setValue(info.password);
|
|
break;
|
|
case 'resourcegroup':
|
|
$('kronolithCalendarresourcegroupDescription').setValue(info.desc);
|
|
$('kronolithCalendarresourcegroupmembers').setValue(info.members);
|
|
break;
|
|
case 'resource':
|
|
$('kronolithCalendarresourceDescription').setValue(info.desc);
|
|
$('kronolithCalendarresourceResponseType').setValue(info.response_type);
|
|
$('kronolithCalendarresourceExport').href = Kronolith.conf.URI_RESOURCE_EXPORT.interpolate({ calendar: calendar });
|
|
}
|
|
}
|
|
|
|
if (newCalendar || info.owner) {
|
|
if (type == 'internal' || type == 'tasklists') {
|
|
this.updateGroupDropDown([['kronolithC' + type + 'PGList', this.updateGroupPerms.bind(this, type)],
|
|
['kronolithC' + type + 'PGNew']]);
|
|
$('kronolithC' + type + 'PBasic').show();
|
|
$('kronolithC' + type + 'PAdvanced').hide();
|
|
$('kronolithC' + type + 'PNone').setValue(1);
|
|
if ($('kronolithC' + type + 'PAllShow')) {
|
|
$('kronolithC' + type + 'PAllShow').disable();
|
|
}
|
|
$('kronolithC' + type + 'PGList').disable();
|
|
$('kronolithC' + type + 'PGPerms').disable();
|
|
$('kronolithC' + type + 'PUList').disable();
|
|
$('kronolithC' + type + 'PUPerms').disable();
|
|
$('kronolithC' + type + 'PAdvanced').select('tr').findAll(function(tr) {
|
|
return tr.retrieve('remove');
|
|
}).invoke('remove');
|
|
$('kronolithCalendar' + type + 'LinkUrls').up().show();
|
|
form.down('.kronolithColorPicker').show();
|
|
if (type == 'internal') {
|
|
HordeCore.doAction('listTopTags', {}, {
|
|
callback: this.topTagsCallback.curry('kronolithCalendarinternalTopTags', 'kronolithCalendarTag')
|
|
});
|
|
}
|
|
form.down('.kronolithCalendarSubscribe').hide();
|
|
form.down('.kronolithCalendarUnsubscribe').hide();
|
|
if ($('kronolithCalendar' + type + 'LinkPerms')) {
|
|
$('kronolithCalendar' + type + 'LinkPerms').up('span').show();
|
|
}
|
|
if (!Object.isUndefined(info) && info.owner) {
|
|
this.setPermsFields(type, info.perms);
|
|
}
|
|
}
|
|
if (type == 'remote' || type == 'internal' || type == 'tasklists') {
|
|
if (newCalendar ||
|
|
(type == 'internal' && calendar == Kronolith.conf.user) ||
|
|
(type == 'tasklists' && calendar == 'tasks/' + Kronolith.conf.user)) {
|
|
form.select('.kronolithCalendarDelete').invoke('hide');
|
|
} else {
|
|
form.select('.kronolithCalendarDelete').invoke('show');
|
|
}
|
|
}
|
|
form.down('.kronolithCalendarSave').show();
|
|
form.down('.kronolithFormActions .kronolithSeparator').show();
|
|
} else {
|
|
form.disable();
|
|
form.down('.kronolithColorPicker').hide();
|
|
form.down('.kronolithCalendarDelete').hide();
|
|
form.down('.kronolithCalendarSave').hide();
|
|
if (type == 'internal' || type == 'tasklists') {
|
|
$('kronolithCalendar' + type + 'UrlCaldav').enable();
|
|
$('kronolithCalendar' + type + 'UrlAccount').enable();
|
|
$('kronolithCalendar' + type + 'UrlWebdav').enable();
|
|
if (type == 'internal') {
|
|
$('kronolithCalendar' + type + 'UrlFeed').enable();
|
|
$('kronolithCalendar' + type + 'EmbedUrl').enable();
|
|
if (info.edit) {
|
|
$('kronolithCalendarinternalImport').enable();
|
|
if (info.del) {
|
|
$('kronolithCalendarinternalImportOver').enable();
|
|
}
|
|
$('kronolithCalendarinternalImportButton').show().enable();
|
|
}
|
|
}
|
|
HordeImple.AutoCompleter.kronolithCalendarinternalTags.disable();
|
|
if (Kronolith.conf.calendars[type][calendar].show) {
|
|
form.down('.kronolithCalendarSubscribe').hide();
|
|
form.down('.kronolithCalendarUnsubscribe').show().enable();
|
|
} else {
|
|
form.down('.kronolithCalendarSubscribe').show().enable();
|
|
form.down('.kronolithCalendarUnsubscribe').hide();
|
|
}
|
|
form.down('.kronolithFormActions .kronolithSeparator').show();
|
|
if ($('kronolithCalendar' + type + 'LinkPerms')) {
|
|
$('kronolithCalendar' + type + 'LinkPerms').up('span').hide();
|
|
}
|
|
} else {
|
|
form.down('.kronolithFormActions .kronolithSeparator').hide();
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Updates the select list in the resourcegroup calendar dialog.
|
|
*/
|
|
updateResourcegroupSelect: function()
|
|
{
|
|
if (!Kronolith.conf.calendars.resource) {
|
|
return;
|
|
}
|
|
$('kronolithCalendarresourcegroupmembers').update();
|
|
$H(Kronolith.conf.calendars.resource).each(function(r) {
|
|
var o = new Element('option', { value: r.value.id }).update(r.value.name.escapeHTML());
|
|
$('kronolithCalendarresourcegroupmembers').insert(o);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Handles clicks on the radio boxes of the basic permissions screen.
|
|
*
|
|
* @param string type The calendar type, 'internal' or 'taskslists'.
|
|
* @param string perm The permission to activate, 'None', 'All', or
|
|
* 'Group'.
|
|
*/
|
|
permsClickHandler: function(type, perm)
|
|
{
|
|
$('kronolithC' + type + 'PAdvanced')
|
|
.select('input[type=checkbox]')
|
|
.invoke('setValue', 0);
|
|
$('kronolithC' + type + 'PAdvanced').select('tr').findAll(function(tr) {
|
|
return tr.retrieve('remove');
|
|
}).invoke('remove');
|
|
|
|
switch (perm) {
|
|
case 'None':
|
|
if ($('kronolithC' + type + 'PAllShow')) {
|
|
$('kronolithC' + type + 'PAllShow').disable();
|
|
}
|
|
$('kronolithC' + type + 'PGList').disable();
|
|
$('kronolithC' + type + 'PGPerms').disable();
|
|
$('kronolithC' + type + 'PUList').disable();
|
|
$('kronolithC' + type + 'PUPerms').disable();
|
|
break;
|
|
case 'All':
|
|
$('kronolithC' + type + 'PAllShow').enable();
|
|
$('kronolithC' + type + 'PGList').disable();
|
|
$('kronolithC' + type + 'PGPerms').disable();
|
|
$('kronolithC' + type + 'PUList').disable();
|
|
$('kronolithC' + type + 'PUPerms').disable();
|
|
var perms = {
|
|
'default': Kronolith.conf.perms.read,
|
|
'guest': Kronolith.conf.perms.read
|
|
};
|
|
if ($F('kronolithC' + type + 'PAllShow')) {
|
|
perms['default'] |= Kronolith.conf.perms.show;
|
|
perms['guest'] |= Kronolith.conf.perms.show;
|
|
}
|
|
this.setPermsFields(type, perms);
|
|
break;
|
|
case 'G':
|
|
if ($('kronolithC' + type + 'PAllShow')) {
|
|
$('kronolithC' + type + 'PAllShow').disable();
|
|
}
|
|
$('kronolithC' + type + 'PGList').enable();
|
|
$('kronolithC' + type + 'PGPerms').enable();
|
|
$('kronolithC' + type + 'PUList').disable();
|
|
$('kronolithC' + type + 'PUPerms').disable();
|
|
var group = $F('kronolithC' + type + 'PGSingle')
|
|
? $F('kronolithC' + type + 'PGSingle')
|
|
: $F('kronolithC' + type + 'PGList');
|
|
this.insertGroupOrUser(type, 'group', group, true);
|
|
$('kronolithC' + type + 'PGshow_' + group).setValue(1);
|
|
$('kronolithC' + type + 'PGread_' + group).setValue(1);
|
|
if ($F('kronolithC' + type + 'PGPerms') == 'edit') {
|
|
$('kronolithC' + type + 'PGedit_' + group).setValue(1);
|
|
} else {
|
|
$('kronolithC' + type + 'PGedit_' + group).setValue(0);
|
|
}
|
|
$('kronolithC' + type + 'PGdel_' + group).setValue(0);
|
|
if ($('kronolithC' + type + 'PGdelegate_' + group)) {
|
|
$('kronolithC' + type + 'PGdelegate_' + group).setValue(0);
|
|
}
|
|
break;
|
|
case 'U':
|
|
if ($('kronolithC' + type + 'PAllShow')) {
|
|
$('kronolithC' + type + 'PAllShow').disable();
|
|
}
|
|
$('kronolithC' + type + 'PGList').disable();
|
|
$('kronolithC' + type + 'PGPerms').disable();
|
|
$('kronolithC' + type + 'PUList').enable();
|
|
$('kronolithC' + type + 'PUPerms').enable();
|
|
var users = $F('kronolithC' + type + 'PUList').strip();
|
|
users = users ? users.split(/\s*(?:,|\n)\s*/) : [];
|
|
users.each(function(user) {
|
|
if (!this.insertGroupOrUser(type, 'user', user, true)) {
|
|
return;
|
|
}
|
|
$('kronolithC' + type + 'PUshow_' + user).setValue(1);
|
|
$('kronolithC' + type + 'PUread_' + user).setValue(1);
|
|
if ($F('kronolithC' + type + 'PUPerms') == 'edit') {
|
|
$('kronolithC' + type + 'PUedit_' + user).setValue(1);
|
|
} else {
|
|
$('kronolithC' + type + 'PUedit_' + user).setValue(0);
|
|
}
|
|
$('kronolithC' + type + 'PUdel_' + user).setValue(0);
|
|
if ($('kronolithC' + type + 'PUdelegate_' + user)) {
|
|
$('kronolithC' + type + 'PUdelegate_' + user).setValue(0);
|
|
}
|
|
}, this);
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Populates the permissions field matrix.
|
|
*
|
|
* @param string type The calendar type, 'internal' or 'taskslists'.
|
|
* @param object perms An object with the resource permissions.
|
|
*/
|
|
setPermsFields: function(type, perms)
|
|
{
|
|
if (this.groupLoading) {
|
|
this.setPermsFields.bind(this, type, perms).defer();
|
|
return;
|
|
}
|
|
|
|
var allperms = $H(Kronolith.conf.perms),
|
|
advanced = false, users = [],
|
|
basic, same, groupPerms, groupId, userPerms;
|
|
$H(perms).each(function(perm) {
|
|
switch (perm.key) {
|
|
case 'default':
|
|
case 'guest':
|
|
if (Object.isUndefined(same)) {
|
|
same = perm.value;
|
|
} else if (Object.isUndefined(basic) &&
|
|
same == perm.value &&
|
|
(perm.value == Kronolith.conf.perms.read ||
|
|
perm.value == (Kronolith.conf.perms.read | Kronolith.conf.perms.show))) {
|
|
basic = perm.value == Kronolith.conf.perms.read ? 'all_read' : 'all_show';
|
|
} else if (perm.value != 0) {
|
|
advanced = true;
|
|
}
|
|
break;
|
|
case 'creator':
|
|
if (perm.value != 0) {
|
|
advanced = true;
|
|
}
|
|
break;
|
|
case 'groups':
|
|
if (!Object.isArray(perm.value)) {
|
|
$H(perm.value).each(function(group) {
|
|
if (!this.insertGroupOrUser(type, 'group', group.key)) {
|
|
return;
|
|
}
|
|
if (!$('kronolithC' + type + 'PGshow_' + group.key)) {
|
|
// Group doesn't exist anymore.
|
|
delete perm.value[group.key];
|
|
return;
|
|
}
|
|
groupPerms = group.value;
|
|
groupId = group.key;
|
|
}, this);
|
|
if (Object.isUndefined(basic) &&
|
|
$H(perm.value).size() == 1 &&
|
|
(groupPerms == (Kronolith.conf.perms.show | Kronolith.conf.perms.read) ||
|
|
groupPerms == (Kronolith.conf.perms.show | Kronolith.conf.perms.read | Kronolith.conf.perms.edit))) {
|
|
basic = groupPerms == (Kronolith.conf.perms.show | Kronolith.conf.perms.read) ? 'group_read' : 'group_edit';
|
|
} else {
|
|
advanced = true;
|
|
}
|
|
}
|
|
break;
|
|
case 'users':
|
|
if (!Object.isArray(perm.value)) {
|
|
$H(perm.value).each(function(user) {
|
|
if (user.key != Kronolith.conf.user) {
|
|
if (!this.insertGroupOrUser(type, 'user', user.key)) {
|
|
return;
|
|
}
|
|
if (!$('kronolithC' + type + 'PUshow_' + user.key)) {
|
|
// User doesn't exist anymore.
|
|
delete perm.value[user.key];
|
|
return;
|
|
}
|
|
// Check if we already have other basic permissions.
|
|
if (Object.isUndefined(userPerms) &&
|
|
!Object.isUndefined(basic)) {
|
|
advanced = true;
|
|
}
|
|
// Check if all users have the same permissions.
|
|
if (!Object.isUndefined(userPerms) &&
|
|
userPerms != user.value) {
|
|
advanced = true;
|
|
}
|
|
userPerms = user.value;
|
|
if (!advanced &&
|
|
(userPerms == (Kronolith.conf.perms.show | Kronolith.conf.perms.read) ||
|
|
userPerms == (Kronolith.conf.perms.show | Kronolith.conf.perms.read | Kronolith.conf.perms.edit))) {
|
|
basic = userPerms == (Kronolith.conf.perms.show | Kronolith.conf.perms.read) ? 'user_read' : 'user_edit';
|
|
users.push(user.key);
|
|
} else {
|
|
advanced = true;
|
|
}
|
|
}
|
|
}, this);
|
|
}
|
|
break;
|
|
}
|
|
|
|
allperms.each(function(baseperm) {
|
|
if (baseperm.key == 'all') {
|
|
return;
|
|
}
|
|
switch (perm.key) {
|
|
case 'default':
|
|
case 'guest':
|
|
case 'creator':
|
|
if (baseperm.value & perm.value) {
|
|
$('kronolithC' + type + 'P' + perm.key + baseperm.key).setValue(1);
|
|
}
|
|
break;
|
|
case 'groups':
|
|
$H(perm.value).each(function(group) {
|
|
if (baseperm.value & group.value) {
|
|
$('kronolithC' + type + 'PG' + baseperm.key + '_' + group.key).setValue(1);
|
|
}
|
|
});
|
|
break;
|
|
case 'users':
|
|
$H(perm.value).each(function(user) {
|
|
if (baseperm.value & user.value &&
|
|
user.key != Kronolith.conf.user) {
|
|
$('kronolithC' + type + 'PU' + baseperm.key + '_' + user.key).setValue(1);
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}.bind(this));
|
|
|
|
if (advanced) {
|
|
this.activateAdvancedPerms(type);
|
|
} else {
|
|
switch (basic) {
|
|
case 'all_read':
|
|
$('kronolithC' + type + 'PAll').setValue(1);
|
|
$('kronolithC' + type + 'PAllShow').setValue(0);
|
|
$('kronolithC' + type + 'PAllShow').enable();
|
|
break;
|
|
case 'all_show':
|
|
$('kronolithC' + type + 'PAll').setValue(1);
|
|
$('kronolithC' + type + 'PAllShow').setValue(1);
|
|
$('kronolithC' + type + 'PAllShow').enable();
|
|
break;
|
|
case 'group_read':
|
|
case 'group_edit':
|
|
var setGroup = function(group) {
|
|
if ($('kronolithC' + type + 'PGList').visible()) {
|
|
$('kronolithC' + type + 'PGList').setValue(group);
|
|
if ($('kronolithC' + type + 'PGList').getValue() != group) {
|
|
// Group no longer exists.
|
|
this.permsClickHandler(type, 'None');
|
|
}
|
|
} else if ($('kronolithC' + type + 'PGSingle').getValue() != group) {
|
|
// Group no longer exists.
|
|
this.permsClickHandler(type, 'None');
|
|
}
|
|
}.bind(this, groupId);
|
|
if (this.groupLoading) {
|
|
setGroup.defer();
|
|
} else {
|
|
setGroup();
|
|
}
|
|
$('kronolithC' + type + 'PG').setValue(1);
|
|
$('kronolithC' + type + 'PGPerms').setValue(basic.substring(6));
|
|
$('kronolithC' + type + 'PAdvanced').hide();
|
|
$('kronolithC' + type + 'PBasic').show();
|
|
$('kronolithC' + type + 'PGPerms').enable();
|
|
break;
|
|
case 'user_read':
|
|
case 'user_edit':
|
|
$('kronolithC' + type + 'PUList').enable().setValue(users.join(', '));
|
|
$('kronolithC' + type + 'PU').setValue(1);
|
|
$('kronolithC' + type + 'PUPerms').setValue(basic.substring(5));
|
|
$('kronolithC' + type + 'PAdvanced').hide();
|
|
$('kronolithC' + type + 'PBasic').show();
|
|
$('kronolithC' + type + 'PUPerms').enable();
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Propagates a SELECT drop down list with the groups.
|
|
*
|
|
* @param array params A two-dimensional array with the following values
|
|
* in each element:
|
|
* - The id of the SELECT element.
|
|
* - A callback method that is invoked with the group
|
|
* list passes as an argument.
|
|
*/
|
|
updateGroupDropDown: function(params)
|
|
{
|
|
this.groupLoading = true;
|
|
params.each(function(param) {
|
|
var elm = $(param[0]), options = elm.childElements();
|
|
options.invoke('remove');
|
|
elm.up('form').disable();
|
|
});
|
|
HordeCore.doAction('listGroups', {}, {
|
|
callback: function(r) {
|
|
var groups;
|
|
if (r.groups) {
|
|
groups = $H(r.groups);
|
|
params.each(function(param) {
|
|
groups.each(function(group) {
|
|
$(param[0]).insert(new Element('option', { value: group.key }).update(group.value.escapeHTML()));
|
|
});
|
|
});
|
|
}
|
|
params.each(function(param) {
|
|
$(param[0]).up('form').enable();
|
|
if (param[1]) {
|
|
param[1](groups);
|
|
}
|
|
});
|
|
this.groupLoading = false;
|
|
}.bind(this)
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Updates the group permission interface after the group list has
|
|
* been loaded.
|
|
*
|
|
* @param string type The calendar type, 'internal' or 'taskslists'.
|
|
* @param Hash groups The list of groups.
|
|
*/
|
|
updateGroupPerms: function(type, groups)
|
|
{
|
|
$('kronolithC' + type + 'PGSingle').clear();
|
|
if (!groups) {
|
|
$('kronolithC' + type + 'PGNew').up('div').hide();
|
|
$('kronolithC' + type + 'PG').up('span').hide();
|
|
} else {
|
|
$('kronolithC' + type + 'PGNew').up('div').show();
|
|
$('kronolithC' + type + 'PG').up('span').show();
|
|
if (groups.size() == 1) {
|
|
$('kronolithC' + type + 'PGName')
|
|
.update('"' + groups.values()[0].escapeHTML() + '"')
|
|
.show();
|
|
$('kronolithC' + type + 'PGSingle').setValue(groups.keys()[0]);
|
|
$('kronolithC' + type + 'PGList').hide();
|
|
} else {
|
|
$('kronolithC' + type + 'PGName').hide();
|
|
$('kronolithC' + type + 'PGList').show();
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Inserts a group or user row into the advanced permissions interface.
|
|
*
|
|
* @param string type The calendar type, 'internal' or
|
|
* 'taskslists'.
|
|
* @param what string Either 'group' or 'user'.
|
|
* @param group string The group id or user name to insert.
|
|
* Defaults to the value of the drop down.
|
|
* @param stay_basic boolean Enforces to NOT switch to the advanced
|
|
* permissions screen.
|
|
*
|
|
* @return boolean Whether a row has been inserted.
|
|
*/
|
|
insertGroupOrUser: function(type, what, id, stay_basic)
|
|
{
|
|
var elm = $(what == 'user' ? 'kronolithC' + type + 'PUNew' : 'kronolithC' + type + 'PGNew');
|
|
if (id) {
|
|
elm.setValue(id);
|
|
}
|
|
var value = elm.getValue();
|
|
if (!value) {
|
|
if (id) {
|
|
HordeCore.notify(Kronolith.text.invalid_user + ': ' + id, 'horde.error');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
var tr = elm.up('tr'),
|
|
row = tr.clone(true).store('remove', true),
|
|
td = row.down('td'),
|
|
clearName = elm.tagName == 'SELECT' ? elm.options[elm.selectedIndex].text: elm.getValue();
|
|
|
|
td.update();
|
|
td.insert(clearName.escapeHTML())
|
|
.insert(new Element('input', { type: 'hidden', name: (what == 'user' ? 'u' : 'g') + '_names[' + value + ']', value: value }));
|
|
row.select('input[type=checkbox]').each(function(input) {
|
|
input.writeAttribute('name', input.name.replace(/\[.*?$/, '[' + value + ']'))
|
|
.writeAttribute('id', input.id.replace(/new/, value))
|
|
.next()
|
|
.writeAttribute('for', input.id);
|
|
});
|
|
tr.insert({ before: row });
|
|
|
|
if (elm.tagName == 'SELECT') {
|
|
elm.options[elm.selectedIndex].writeAttribute('disabled', true);
|
|
elm.selectedIndex = 0;
|
|
} else {
|
|
elm.clear();
|
|
}
|
|
|
|
if (!stay_basic) {
|
|
this.activateAdvancedPerms(type);
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Activates the advanced permissions.
|
|
*
|
|
* @param string type The calendar type, 'internal' or 'taskslists'.
|
|
*/
|
|
activateAdvancedPerms: function(type)
|
|
{
|
|
[$('kronolithC' + type + 'PNone'),
|
|
$('kronolithC' + type + 'PU'),
|
|
$('kronolithC' + type + 'PG')].each(function(radio) {
|
|
radio.checked = false;
|
|
});
|
|
if ($('kronolithC' + type + 'PAll')) {
|
|
$('kronolithC' + type + 'PAll').checked = false;
|
|
}
|
|
$('kronolithC' + type + 'PBasic').hide();
|
|
$('kronolithC' + type + 'PAdvanced').show();
|
|
},
|
|
|
|
/**
|
|
* Opens the next screen of the calendar management wizard.
|
|
*
|
|
* @param string type The calendar type.
|
|
*/
|
|
calendarNext: function(type)
|
|
{
|
|
var i = 1;
|
|
while (!$('kronolithCalendar' + type + i).visible()) {
|
|
i++;
|
|
}
|
|
$('kronolithCalendar' + type + i).hide();
|
|
$('kronolithCalendar' + type + (++i)).show();
|
|
if (this.colorPicker) {
|
|
this.colorPicker.hide();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Submits the calendar form to save the calendar data.
|
|
*
|
|
* @param Element form The form node.
|
|
*
|
|
* @return boolean Whether the save request was successfully sent.
|
|
*/
|
|
saveCalendar: function(form)
|
|
{
|
|
if (this.colorPicker) {
|
|
this.colorPicker.hide();
|
|
}
|
|
var data = form.serialize({ hash: true });
|
|
|
|
if (data.type == 'holiday') {
|
|
this.insertCalendarInList('holiday', data.driver, Kronolith.conf.calendars.holiday[data.driver]);
|
|
this.toggleCalendar('holiday', data.driver);
|
|
form.down('.kronolithCalendarSave').enable();
|
|
this.closeRedBox();
|
|
this.go(this.lastLocation);
|
|
return;
|
|
}
|
|
|
|
if (data.name.empty()) {
|
|
HordeCore.notify(data.type == 'tasklists' ? Kronolith.text.no_tasklist_title : Kronolith.text.no_calendar_title, 'horde.warning');
|
|
$('kronolithCalendar' + data.type + 'Name').focus();
|
|
return false;
|
|
}
|
|
HordeCore.doAction('saveCalendar', data, {
|
|
callback: this.saveCalendarCallback.bind(this, form, data)
|
|
});
|
|
return true;
|
|
},
|
|
|
|
calendarImport: function(form, disableForm)
|
|
{
|
|
if ($F('kronolithCalendarinternalImport')) {
|
|
HordeCore.notify(Kronolith.text.import_warning, 'horde.message');
|
|
this.loading++;
|
|
$('kronolithLoading').show();
|
|
var name = 'kronolithIframe' + Math.round(Math.random() * 1000),
|
|
iframe = new Element('iframe', { src: 'about:blank', name: name, id: name }).setStyle({ display: 'none' });
|
|
document.body.insert(iframe);
|
|
form.enable();
|
|
form.target = name;
|
|
form.submit();
|
|
if (disableForm) {
|
|
form.disable();
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Callback method after saving a calendar.
|
|
*
|
|
* @param Element form The form node.
|
|
* @param object data The serialized form data.
|
|
* @param object r The ajax response object.
|
|
*/
|
|
saveCalendarCallback: function(form, data, r)
|
|
{
|
|
var type = form.id.replace(/kronolithCalendarForm/, '');
|
|
|
|
// If saving the calendar changed the owner, we need to delete
|
|
// and re-insert the calendar.
|
|
if (r.deleted) {
|
|
this.deleteCalendar(type, data.calendar);
|
|
delete data.calendar;
|
|
}
|
|
if (r.saved) {
|
|
this.calendarImport(form, false);
|
|
var cal = r.calendar, id;
|
|
if (data.calendar) {
|
|
var color = {
|
|
backgroundColor: cal.bg,
|
|
color: cal.fg
|
|
},
|
|
legendSpan;
|
|
id = data.calendar;
|
|
this.getCalendarList(type, cal.owner).select('div').each(function(element) {
|
|
if (element.retrieve('calendar') == id) {
|
|
var link = element.down('.horde-resource-link span');
|
|
element.setStyle(color);
|
|
link.update(cal.name.escapeHTML());
|
|
this.addShareIcon(cal, link);
|
|
throw $break;
|
|
}
|
|
}, this);
|
|
this.kronolithBody.select('div').each(function(el) {
|
|
if (el.retrieve('calendar') == type + '|' + id) {
|
|
el.setStyle(color);
|
|
}
|
|
});
|
|
legendSpan = $('kronolith-legend').select('span')
|
|
.find(function(span) {
|
|
return span.retrieve('calendarclass') == type &&
|
|
span.retrieve('calendar') == id;
|
|
});
|
|
if (legendSpan) {
|
|
legendSpan.setStyle(color).update(cal.name.escapeHTML());
|
|
}
|
|
Kronolith.conf.calendars[type][id] = cal;
|
|
} else {
|
|
id = r.id;
|
|
if (!Kronolith.conf.calendars[type]) {
|
|
Kronolith.conf.calendars[type] = [];
|
|
}
|
|
Kronolith.conf.calendars[type][id] = cal;
|
|
this.insertCalendarInList(type, id, cal);
|
|
this.storeCache($H(), [type, id], this.viewDates(this.date, this.view), true);
|
|
if (type == 'tasklists') {
|
|
this.storeTasksCache($H(), this.tasktype, id.replace(/^tasks\//, ''), true);
|
|
}
|
|
}
|
|
if (type == 'remote') {
|
|
this.loadCalendar(type, id);
|
|
}
|
|
}
|
|
form.down('.kronolithCalendarSave').enable();
|
|
this.closeRedBox();
|
|
this.go(this.lastLocation);
|
|
},
|
|
|
|
/**
|
|
* Deletes a calendar and all its events from the interface and cache.
|
|
*
|
|
* @param string type The calendar type.
|
|
* @param string calendar The calendar id.
|
|
*/
|
|
deleteCalendar: function(type, calendar)
|
|
{
|
|
var container = this.getCalendarList(type, Kronolith.conf.calendars[type][calendar].owner),
|
|
noItems = container.previous(),
|
|
div = container.select('div').find(function(element) {
|
|
return element.retrieve('calendar') == calendar;
|
|
}),
|
|
arrow = div.down('span');
|
|
arrow.purge();
|
|
arrow.remove();
|
|
div.purge();
|
|
div.remove();
|
|
if (noItems &&
|
|
noItems.tagName == 'DIV' &&
|
|
noItems.className == 'horde-info' &&
|
|
!container.childElements().size()) {
|
|
noItems.show();
|
|
}
|
|
this.deleteCalendarLegend(type, calendar);
|
|
this.removeEvent(type + '|' + calendar);
|
|
this.deleteCache([type, calendar]);
|
|
if (type == 'tasklists' && this.view == 'tasks') {
|
|
this.removeTask(calendar.replace(/^tasks\//, ''));
|
|
}
|
|
delete Kronolith.conf.calendars[type][calendar];
|
|
},
|
|
|
|
/**
|
|
* Parses a date attribute string into a Date object.
|
|
*
|
|
* For other strings use Date.parse().
|
|
*
|
|
* @param string date A yyyyMMdd date string.
|
|
*
|
|
* @return Date A date object.
|
|
*/
|
|
parseDate: function(date)
|
|
{
|
|
var d = new Date(date.substr(0, 4), date.substr(4, 2) - 1, date.substr(6, 2));
|
|
if (date.length == 12) {
|
|
d.setHours(date.substr(8, 2));
|
|
d.setMinutes(date.substr(10, 2));
|
|
}
|
|
return d;
|
|
},
|
|
|
|
/**
|
|
* Calculates first and last days being displayed.
|
|
*
|
|
* @var Date date The date of the view.
|
|
* @var string view A view name.
|
|
*
|
|
* @return array Array with first and last day of the view.
|
|
*/
|
|
viewDates: function(date, view)
|
|
{
|
|
var start = date.clone(), end = date.clone();
|
|
|
|
switch (view) {
|
|
case 'week':
|
|
case 'workweek':
|
|
if (view == 'workweek') {
|
|
start.add(1).days();
|
|
}
|
|
start.moveToBeginOfWeek(view == 'week' ? Kronolith.conf.week_start : 1);
|
|
end = start.clone();
|
|
end.moveToEndOfWeek(Kronolith.conf.week_start);
|
|
if (view == 'workweek') {
|
|
end.add(Kronolith.conf.week_start == 0 ? -1 : -2).days();
|
|
}
|
|
break;
|
|
case 'month':
|
|
start.setDate(1);
|
|
start.moveToBeginOfWeek(Kronolith.conf.week_start);
|
|
end.moveToLastDayOfMonth();
|
|
end.moveToEndOfWeek(Kronolith.conf.week_start);
|
|
break;
|
|
case 'year':
|
|
start.setDate(1);
|
|
start.setMonth(0);
|
|
end.setMonth(11);
|
|
end.moveToLastDayOfMonth();
|
|
break;
|
|
case 'agenda':
|
|
end.add(6).days();
|
|
break;
|
|
}
|
|
|
|
return [start, end];
|
|
},
|
|
|
|
/**
|
|
* Stores a set of events in the cache.
|
|
*
|
|
* For dates in the specified date ranges that don't contain any events,
|
|
* empty cache entries are created so that those dates aren't re-fetched
|
|
* each time.
|
|
*
|
|
* @param object events A list of calendars and events as returned
|
|
* from an ajax request.
|
|
* @param string calendar A calendar string or array.
|
|
* @param string dates A date range in the format yyyymmddyyyymmdd
|
|
* as used in the ajax response signature.
|
|
* @param boolean createCache Whether to create a cache list entry for the
|
|
* response, if none exists yet.
|
|
*/
|
|
storeCache: function(events, calendar, dates, createCache)
|
|
{
|
|
if (Object.isString(calendar)) {
|
|
calendar = calendar.split('|');
|
|
}
|
|
|
|
// Create cache entry for the calendar.
|
|
if (!this.ecache.get(calendar[0])) {
|
|
if (!createCache) {
|
|
return;
|
|
}
|
|
this.ecache.set(calendar[0], $H());
|
|
}
|
|
if (!this.ecache.get(calendar[0]).get(calendar[1])) {
|
|
if (!createCache) {
|
|
return;
|
|
}
|
|
this.ecache.get(calendar[0]).set(calendar[1], $H());
|
|
}
|
|
var calHash = this.ecache.get(calendar[0]).get(calendar[1]);
|
|
|
|
// Create empty cache entries for all dates.
|
|
if (!!dates) {
|
|
var day = dates[0].clone(), date;
|
|
while (!day.isAfter(dates[1])) {
|
|
date = day.dateString();
|
|
if (!calHash.get(date)) {
|
|
if (!createCache) {
|
|
return;
|
|
}
|
|
if (!this.cacheStart || this.cacheStart.isAfter(day)) {
|
|
this.cacheStart = day.clone();
|
|
}
|
|
if (!this.cacheEnd || this.cacheEnd.isBefore(day)) {
|
|
this.cacheEnd = day.clone();
|
|
}
|
|
calHash.set(date, $H());
|
|
}
|
|
day.add(1).day();
|
|
}
|
|
}
|
|
|
|
var cal = calendar.join('|');
|
|
$H(events).each(function(date) {
|
|
// We might not have a cache for this date if the event lasts
|
|
// longer than the current view
|
|
if (!calHash.get(date.key)) {
|
|
return;
|
|
}
|
|
|
|
// Store calendar string and other useful information in event
|
|
// objects.
|
|
$H(date.value).each(function(event) {
|
|
event.value.calendar = cal;
|
|
event.value.start = Date.parse(event.value.s);
|
|
event.value.end = Date.parse(event.value.e);
|
|
});
|
|
|
|
// Store events in cache.
|
|
calHash.set(date.key, calHash.get(date.key).merge(date.value));
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Stores a set of tasks in the cache.
|
|
*
|
|
* @param Hash tasks The tasks to be stored.
|
|
* @param string tasktypes The task type that's being stored.
|
|
* @param string tasklist The task list to which the tasks belong.
|
|
* @param boolean createCache Whether to create a cache list entry for the
|
|
* response, if none exists yet.
|
|
*/
|
|
storeTasksCache: function(tasks, tasktypes, tasklist, createCache)
|
|
{
|
|
var taskHashes = {}, cacheExists = {};
|
|
|
|
if (tasktypes == 'all' || tasktypes == 'future') {
|
|
tasktypes = [ 'complete', 'incomplete' ];
|
|
} else {
|
|
tasktypes = [ tasktypes ];
|
|
}
|
|
|
|
tasktypes.each(function(tasktype) {
|
|
cacheExists[tasktype] = false;
|
|
if (!this.tcache.get(tasktype)) {
|
|
if (!createCache) {
|
|
return;
|
|
}
|
|
this.tcache.set(tasktype, $H());
|
|
}
|
|
if (!tasklist) {
|
|
return;
|
|
}
|
|
if (!this.tcache.get(tasktype).get(tasklist)) {
|
|
if (!createCache) {
|
|
return;
|
|
}
|
|
this.tcache.get(tasktype).set(tasklist, $H());
|
|
cacheExists[tasktype] = true;
|
|
} else {
|
|
cacheExists[tasktype] = true;
|
|
}
|
|
taskHashes[tasktype] = this.tcache.get(tasktype).get(tasklist);
|
|
}, this);
|
|
|
|
$H(tasks).each(function(task) {
|
|
var tasktype = task.value.cp ? 'complete' : 'incomplete';
|
|
if (!cacheExists[tasktype]) {
|
|
return;
|
|
}
|
|
if (!Object.isUndefined(task.value.s)) {
|
|
task.value.start = Date.parse(task.value.s);
|
|
}
|
|
if (!Object.isUndefined(task.value.du)) {
|
|
task.value.due = Date.parse(task.value.du);
|
|
}
|
|
taskHashes[tasktype].set(task.key, task.value);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Deletes an event or a complete calendar from the cache.
|
|
*
|
|
* @param string calendar A calendar string or array.
|
|
* @param string event An event ID or empty if deleting the calendar.
|
|
* @param string day A specific day to delete in yyyyMMdd form.
|
|
*/
|
|
deleteCache: function(calendar, event, day)
|
|
{
|
|
if (Object.isString(calendar)) {
|
|
calendar = calendar.split('|');
|
|
}
|
|
if (!this.ecache.get(calendar[0]) ||
|
|
!this.ecache.get(calendar[0]).get(calendar[1])) {
|
|
return;
|
|
}
|
|
if (event) {
|
|
this.ecache.get(calendar[0]).get(calendar[1]).each(function(day) {
|
|
day.value.unset(event);
|
|
});
|
|
} else if (day) {
|
|
this.ecache.get(calendar[0]).get(calendar[1]).unset(day);
|
|
} else {
|
|
this.ecache.get(calendar[0]).unset(calendar[1]);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Deletes tasks from the cache.
|
|
*
|
|
* @param string list A task list string.
|
|
* @param string task A task ID. If empty, all tasks from the list are
|
|
* deleted.
|
|
*/
|
|
deleteTasksCache: function(list, task)
|
|
{
|
|
this.deleteCache([ 'external', 'tasks/' + list ], task);
|
|
$w('complete incomplete').each(function(type) {
|
|
if (!Object.isUndefined(this.tcache.get(type)) &&
|
|
!Object.isUndefined(this.tcache.get(type).get(list))) {
|
|
if (task) {
|
|
this.tcache.get(type).get(list).unset(task);
|
|
} else {
|
|
this.tcache.get(type).unset(list);
|
|
}
|
|
}
|
|
}, this);
|
|
},
|
|
|
|
/**
|
|
* Return all events for a single day from all displayed calendars merged
|
|
* into a single hash.
|
|
*
|
|
* @param string date A yyyymmdd date string.
|
|
*
|
|
* @return Hash An event hash which event ids as keys and event objects as
|
|
* values.
|
|
*/
|
|
getCacheForDate: function(date, calendar)
|
|
{
|
|
if (calendar) {
|
|
var cals = calendar.split('|');
|
|
if (!this.ecache.get(cals[0]) ||
|
|
!this.ecache.get(cals[0]).get(cals[1])) {
|
|
return $H();
|
|
}
|
|
var x = this.ecache.get(cals[0]).get(cals[1]).get(date);
|
|
return this.ecache.get(cals[0]).get(cals[1]).get(date);
|
|
}
|
|
|
|
var events = $H();
|
|
this.ecache.each(function(type) {
|
|
type.value.each(function(cal) {
|
|
if (!Kronolith.conf.calendars[type.key][cal.key].show) {
|
|
return;
|
|
}
|
|
events = events.merge(cal.value.get(date));
|
|
});
|
|
});
|
|
return events;
|
|
},
|
|
|
|
/**
|
|
* Helper method for Enumerable.sortBy to sort events first by start time,
|
|
* second by end time reversed.
|
|
*
|
|
* @param Hash event A hash entry with the event object as the value.
|
|
*
|
|
* @return string A comparable string.
|
|
*/
|
|
sortEvents: function(event)
|
|
{
|
|
return event.value.sort;
|
|
},
|
|
|
|
/**
|
|
* Adds a new location to the history and displays it in the URL hash.
|
|
*
|
|
* This is not really a history, because only the current and the last
|
|
* location are stored.
|
|
*
|
|
* @param string loc The location to save.
|
|
* @param boolean save Whether to actually save the location. This should
|
|
* be false for any location that are displayed on top
|
|
* of another location, i.e. in a popup view.
|
|
*/
|
|
addHistory: function(loc, save)
|
|
{
|
|
location.hash = encodeURIComponent(loc);
|
|
this.lastLocation = this.currentLocation;
|
|
if (Object.isUndefined(save) || save) {
|
|
this.currentLocation = loc;
|
|
}
|
|
this.openLocation = loc;
|
|
},
|
|
|
|
/**
|
|
* Loads an external page.
|
|
*
|
|
* @param string loc The URL of the page to load.
|
|
*/
|
|
loadPage: function(loc)
|
|
{
|
|
window.location.assign(loc);
|
|
},
|
|
|
|
searchSubmit: function(e)
|
|
{
|
|
this.go('search:' + this.search + ':' + $F('horde-search-input'));
|
|
},
|
|
|
|
searchReset: function(e)
|
|
{
|
|
HordeTopbar.searchGhost.reset();
|
|
},
|
|
|
|
/**
|
|
* Event handler for HordeCore:showNotifications events.
|
|
*/
|
|
showNotification: function(e)
|
|
{
|
|
if (!e.memo.flags ||
|
|
!e.memo.flags.alarm ||
|
|
!e.memo.flags.growl ||
|
|
!e.memo.flags.alarm.params.notify.ajax) {
|
|
return;
|
|
}
|
|
|
|
var growl = e.memo.flags.growl, link = growl.down('A');
|
|
|
|
if (link) {
|
|
link.observe('click', function(ee) {
|
|
ee.stop();
|
|
HordeCore.Growler.ungrowl(growl);
|
|
this.go(e.memo.flags.alarm.params.notify.ajax);
|
|
}.bind(this));
|
|
}
|
|
},
|
|
|
|
/* Keydown event handler */
|
|
keydownHandler: function(e)
|
|
{
|
|
if (e.stopped) {
|
|
return;
|
|
}
|
|
|
|
var kc = e.keyCode || e.charCode,
|
|
form = e.findElement('FORM'), trigger = e.findElement();
|
|
|
|
switch (trigger.id) {
|
|
case 'kronolithEventLocation':
|
|
if (kc == Event.KEY_RETURN && $F('kronolithEventLocation')) {
|
|
this.initializeMap(true);
|
|
this.geocode($F('kronolithEventLocation'));
|
|
e.stop();
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case 'kronolithCalendarinternalUrlCaldav':
|
|
case 'kronolithCalendarinternalUrlWebdav':
|
|
case 'kronolithCalendarinternalUrlAccount':
|
|
case 'kronolithCalendarinternalUrlFeed':
|
|
case 'kronolithCalendartasklistsUrlCaldav':
|
|
case 'kronolithCalendartasklistsUrlWebdav':
|
|
case 'kronolithCalendartasklistsUrlAccount':
|
|
if (String.fromCharCode(kc) != 'C' ||
|
|
(this.macos && !e.metaKey) ||
|
|
(!this.macos && !e.ctrlKey)) {
|
|
e.stop();
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (form) {
|
|
switch (kc) {
|
|
case Event.KEY_RETURN:
|
|
switch (form.identify()) {
|
|
case 'kronolithEventForm':
|
|
if (e.element().tagName != 'TEXTAREA') {
|
|
this.saveEvent();
|
|
e.stop();
|
|
}
|
|
break;
|
|
|
|
case 'kronolithTaskForm':
|
|
if (e.element().tagName != 'TEXTAREA') {
|
|
this.saveTask();
|
|
e.stop();
|
|
}
|
|
break;
|
|
|
|
case 'kronolithQuickinsertForm':
|
|
this.quickSaveEvent();
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithCalendarForminternal':
|
|
case 'kronolithCalendarFormtasklists':
|
|
case 'kronolithCalendarFormremote':
|
|
// Disabled for now, we have to also catch Continue buttons.
|
|
//var saveButton = form.down('.kronolithCalendarSave');
|
|
//saveButton.disable();
|
|
//if (!this.saveCalendar(form)) {
|
|
// saveButton.enable();
|
|
//}
|
|
//e.stop();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case Event.KEY_ESC:
|
|
switch (form.identify()) {
|
|
case 'kronolithQuickinsertForm':
|
|
case 'kronolithQuicktaskForm':
|
|
this.quickClose();
|
|
break;
|
|
case 'kronolithEventForm':
|
|
case 'kronolithTaskForm':
|
|
Horde_Calendar.hideCal();
|
|
this.closeRedBox();
|
|
this.go(this.lastLocation);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
switch (kc) {
|
|
case Event.KEY_ESC:
|
|
Horde_Calendar.hideCal();
|
|
this.closeRedBox();
|
|
break;
|
|
}
|
|
},
|
|
|
|
keyupHandler: function(e)
|
|
{
|
|
switch (e.element().readAttribute('id')) {
|
|
case 'kronolithEventLocation':
|
|
if ($F('kronolithEventLocation') && Kronolith.conf.maps.driver) {
|
|
$('kronolithEventMapLink').show();
|
|
} else if (Kronolith.conf.maps.driver) {
|
|
$('kronolithEventMapLink').hide();
|
|
this.removeMapMarker();
|
|
}
|
|
return;
|
|
|
|
case 'kronolithEventStartTime':
|
|
case 'kronolithEventEndTime':
|
|
var field = $(e.element().readAttribute('id')), kc = e.keyCode;
|
|
|
|
switch(e.keyCode) {
|
|
case Event.KEY_UP:
|
|
case Event.KEY_DOWN:
|
|
case Event.KEY_RIGHT:
|
|
case Event.KEY_LEFT:
|
|
return;
|
|
default:
|
|
if ($F(field) !== this.knl[field.identify()].getCurrentEntry()) {
|
|
this.knl[field.identify()].markSelected(null);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
},
|
|
|
|
clickHandler: function(e, dblclick)
|
|
{
|
|
if (e.isRightClick() || typeof e.element != 'function') {
|
|
return;
|
|
}
|
|
|
|
var elt = e.element(),
|
|
orig = e.element(),
|
|
id, tmp, calendar;
|
|
|
|
while (Object.isElement(elt)) {
|
|
id = elt.readAttribute('id');
|
|
|
|
switch (id) {
|
|
case 'kronolithNewEvent':
|
|
this.go('event');
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithNewTask':
|
|
this.go('task');
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithQuickEvent':
|
|
if (this.view == 'tasks') {
|
|
RedBox.showHtml($('kronolithQuicktask').show());
|
|
} else {
|
|
this.updateCalendarDropDown('kronolithQuickinsertCalendars');
|
|
$('kronolithQuickinsertCalendars').setValue(Kronolith.conf.default_calendar);
|
|
RedBox.showHtml($('kronolithQuickinsert').show());
|
|
}
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithQuickinsertSave':
|
|
this.quickSaveEvent();
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithQuicktaskSave':
|
|
this.quickSaveTask();
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithQuickinsertCancel':
|
|
case 'kronolithQuicktaskCancel':
|
|
this.quickClose();
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithGotoToday':
|
|
var view = this.view;
|
|
if (!$w('day workweek week month year agenda').include(view)) {
|
|
view = Kronolith.conf.login_view;
|
|
}
|
|
this.go(view + ':' + new Date().dateString());
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithEventAllday':
|
|
this.toggleAllDay();
|
|
break;
|
|
|
|
case 'kronolithEventAlarmDefaultOn':
|
|
this.disableAlarmMethods('Event');
|
|
break;
|
|
|
|
case 'kronolithTaskAlarmDefaultOn':
|
|
this.disableAlarmMethods('Task');
|
|
break;
|
|
|
|
case 'kronolithEventAlarmPrefs':
|
|
HordeCore.redirect(HordeCore.addURLParam(
|
|
Kronolith.conf.prefs_url,
|
|
{
|
|
group: 'notification'
|
|
}
|
|
));
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithTaskAlarmPrefs':
|
|
if (Kronolith.conf.tasks.prefs_url) {
|
|
HordeCore.redirect(HordeCore.addURLParam(
|
|
Kronolith.conf.tasks.prefs_url,
|
|
{
|
|
group: 'notification'
|
|
}
|
|
));
|
|
}
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithEventLinkNone':
|
|
case 'kronolithEventLinkDaily':
|
|
case 'kronolithEventLinkWeekly':
|
|
case 'kronolithEventLinkMonthly':
|
|
case 'kronolithEventLinkYearly':
|
|
case 'kronolithEventLinkLength':
|
|
case 'kronolithTaskLinkNone':
|
|
case 'kronolithTaskLinkDaily':
|
|
case 'kronolithTaskLinkWeekly':
|
|
case 'kronolithTaskLinkMonthly':
|
|
case 'kronolithTaskLinkYearly':
|
|
case 'kronolithTaskLinkLength':
|
|
this.toggleRecurrence(
|
|
id.startsWith('kronolithEvent'),
|
|
id.substring(id.startsWith('kronolithEvent') ? 18 : 17));
|
|
break;
|
|
|
|
case 'kronolithEventRepeatDaily':
|
|
case 'kronolithEventRepeatWeekly':
|
|
case 'kronolithEventRepeatMonthly':
|
|
case 'kronolithEventRepeatYearly':
|
|
case 'kronolithEventRepeatLength':
|
|
case 'kronolithTaskRepeatDaily':
|
|
case 'kronolithTaskRepeatWeekly':
|
|
case 'kronolithTaskRepeatMonthly':
|
|
case 'kronolithTaskRepeatYearly':
|
|
case 'kronolithTaskRepeatLength':
|
|
this.toggleRecurrence(
|
|
id.startsWith('kronolithEvent'),
|
|
id.substring(id.startsWith('kronolithEvent') ? 20 : 19));
|
|
break;
|
|
|
|
case 'kronolithEventSave':
|
|
if (!elt.disabled) {
|
|
this._checkDate($('kronolithEventStartDate'));
|
|
this._checkDate($('kronolithEventEndDate'));
|
|
if ($F('kronolithEventAttendees') && $F('kronolithEventId')) {
|
|
$('kronolithEventSendUpdates').setValue(0);
|
|
$('kronolithEventDiv').hide();
|
|
$('kronolithUpdateDiv').show();
|
|
e.stop();
|
|
break;
|
|
}
|
|
}
|
|
case 'kronolithEventSendUpdateYes':
|
|
if (this.uatts) {
|
|
this.uatts.u = true;
|
|
} else {
|
|
$('kronolithEventSendUpdates').setValue(1);
|
|
}
|
|
case 'kronolithEventSendUpdateNo':
|
|
if (this.uatts) {
|
|
this.doDragDropUpdate(this.uatts, this.ucb);
|
|
this.uatts = null;
|
|
this.ucb = null;
|
|
this.closeRedBox();
|
|
$('kronolithUpdateDiv').hide();
|
|
$('kronolithEventDiv').show();
|
|
} else if (!elt.disabled) {
|
|
this.saveEvent();
|
|
}
|
|
e.stop();
|
|
break;
|
|
case 'kronolithEventConflictYes':
|
|
this.doSaveEvent();
|
|
e.stop();
|
|
break;
|
|
case 'kronolithEventConflictNo':
|
|
$('kronolithConflictDiv').hide();
|
|
$('kronolithEventDiv').show();
|
|
e.stop();
|
|
break;
|
|
case 'kronolithEventSaveAsNew':
|
|
if (!elt.disabled) {
|
|
$('kronolithEventSendUpdates').setValue(1);
|
|
this.saveEvent(true);
|
|
}
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithTaskSave':
|
|
if (!elt.disabled) {
|
|
this.saveTask();
|
|
}
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithEventDeleteCancel':
|
|
$('kronolithDeleteDiv').hide();
|
|
$('kronolithEventDiv').show();
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithEventSendCancellationYes':
|
|
$('kronolithRecurDeleteAll').enable();
|
|
$('kronolithRecurDeleteCurrent').enable();
|
|
$('kronolithRecurDeleteFuture').enable();
|
|
this.paramsCache.sendupdates = 1;
|
|
case 'kronolithEventSendCancellationNo':
|
|
$('kronolithRecurDeleteAll').enable();
|
|
$('kronolithRecurDeleteCurrent').enable();
|
|
$('kronolithRecurDeleteFuture').enable();
|
|
$('kronolithCancellationDiv').hide();
|
|
this.delete_verified = true;
|
|
case 'kronolithEventDelete':
|
|
if ((Kronolith.conf.confirm_delete || this.recurs) && !this.delete_verified) {
|
|
$('kronolithEventDiv').hide();
|
|
$('kronolithDeleteDiv').show();
|
|
e.stop();
|
|
break;
|
|
} else {
|
|
$('kronolithEventDiv').hide();
|
|
this.delete_verified = false;
|
|
}
|
|
// Fallthrough
|
|
case 'kronolithRecurDeleteAll':
|
|
case 'kronolithRecurDeleteCurrent':
|
|
case 'kronolithRecurDeleteFuture':
|
|
case 'kronolithEventDeleteConfirm':
|
|
if (elt.disabled) {
|
|
e.stop();
|
|
break;
|
|
}
|
|
elt.disable();
|
|
var cal = $F('kronolithEventCalendar'),
|
|
eventid = $F('kronolithEventId');
|
|
if (id != 'kronolithEventSendCancellationNo' &&
|
|
id != 'kronolithEventSendCancellationYes') {
|
|
this.paramsCache = {
|
|
cal: cal,
|
|
id: eventid,
|
|
rstart: $F('kronolithEventRecurOStart'),
|
|
cstart: this.cacheStart.toISOString(),
|
|
cend: this.cacheEnd.toISOString()
|
|
};
|
|
switch (id) {
|
|
case 'kronolithRecurDeleteAll':
|
|
this.paramsCache.r = 'all';
|
|
break;
|
|
case 'kronolithRecurDeleteCurrent':
|
|
this.paramsCache.r = 'current';
|
|
break;
|
|
case 'kronolithRecurDeleteFuture':
|
|
this.paramsCache.r = 'future';
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (id != 'kronolithEventSendCancellationNo'
|
|
&& id != 'kronolithEventSendCancellationYes'
|
|
&& $F('kronolithEventAttendees')) {
|
|
|
|
$('kronolithDeleteDiv').hide();
|
|
$('kronolithCancellationDiv').show();
|
|
e.stop();
|
|
break;
|
|
}
|
|
|
|
this.kronolithBody.select('div').findAll(function(el) {
|
|
return el.retrieve('calendar') == cal &&
|
|
el.retrieve('eventid') == eventid;
|
|
}).invoke('hide');
|
|
var viewDates = this.viewDates(this.date, this.view),
|
|
start = viewDates[0].toString('yyyyMMdd'),
|
|
end = viewDates[1].toString('yyyyMMdd');
|
|
this.paramsCache.sig = start + end + (Math.random() + '').slice(2);
|
|
this.paramsCache.view_start = start;
|
|
this.paramsCache.view_end = end;
|
|
|
|
HordeCore.doAction('deleteEvent', this.paramsCache, {
|
|
callback: function(elt,r) {
|
|
if (r.deleted) {
|
|
var days;
|
|
if (this.view == 'month' ||
|
|
this.view == 'week' ||
|
|
this.view == 'workweek' ||
|
|
this.view == 'day') {
|
|
days = this.findEventDays(cal, eventid);
|
|
days.each(function(day) {
|
|
this.refreshResources(day, cal, eventid);
|
|
}.bind(this));
|
|
}
|
|
this.removeEvent(cal, eventid);
|
|
if (r.uid) {
|
|
this.removeException(cal, r.uid);
|
|
}
|
|
this.loadEventsCallback(r, false);
|
|
if (days && days.length) {
|
|
this.reRender(days);
|
|
}
|
|
} else {
|
|
this.kronolithBody.select('div').findAll(function(el) {
|
|
return el.retrieve('calendar') == cal &&
|
|
el.retrieve('eventid') == eventid;
|
|
}).invoke('show');
|
|
}
|
|
elt.enable();
|
|
}.curry(elt).bind(this)
|
|
});
|
|
|
|
$('kronolithDeleteDiv').hide();
|
|
$('kronolithEventDiv').show();
|
|
this.closeRedBox();
|
|
this.go(this.lastLocation);
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithTaskDelete':
|
|
if (elt.disabled) {
|
|
e.stop();
|
|
break;
|
|
}
|
|
|
|
elt.disable();
|
|
var tasklist = $F('kronolithTaskOldList'),
|
|
taskid = $F('kronolithTaskId');
|
|
|
|
HordeCore.doAction('deleteTask', {
|
|
list: tasklist,
|
|
id: taskid
|
|
}, {
|
|
callback: function(r) {
|
|
if (r.deleted) {
|
|
this.removeTask(tasklist, taskid);
|
|
} else {
|
|
elt.enable();
|
|
$('kronolithViewTasksBody').select('tr').find(function(el) {
|
|
return el.retrieve('tasklist') == tasklist &&
|
|
el.retrieve('taskid') == taskid;
|
|
}).toggle();
|
|
}
|
|
}.bind(this)
|
|
});
|
|
|
|
var taskrow = $('kronolithViewTasksBody').select('tr').find(function(el) {
|
|
return el.retrieve('tasklist') == tasklist &&
|
|
el.retrieve('taskid') == taskid;
|
|
});
|
|
if (taskrow) {
|
|
taskrow.hide();
|
|
}
|
|
this.closeRedBox();
|
|
this.go(this.lastLocation);
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithCinternalPMore':
|
|
case 'kronolithCinternalPLess':
|
|
case 'kronolithCtasklistsPMore':
|
|
case 'kronolithCtasklistsPLess':
|
|
var type = id.match(/kronolithC(.*)P/)[1];
|
|
$('kronolithC' + type + 'PBasic').toggle();
|
|
$('kronolithC' + type + 'PAdvanced').toggle();
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithCinternalPNone':
|
|
case 'kronolithCinternalPAll':
|
|
case 'kronolithCinternalPG':
|
|
case 'kronolithCinternalPU':
|
|
case 'kronolithCtasklistsPNone':
|
|
case 'kronolithCtasklistsPAll':
|
|
case 'kronolithCtasklistsPG':
|
|
case 'kronolithCtasklistsPU':
|
|
var info = id.match(/kronolithC(.*)P(.*)/);
|
|
this.permsClickHandler(info[1], info[2]);
|
|
break;
|
|
|
|
case 'kronolithCinternalPAllShow':
|
|
case 'kronolithCtasklistsPAllShow':
|
|
var type = id.match(/kronolithC(.*)P/)[1];
|
|
this.permsClickHandler(type, 'All');
|
|
break;
|
|
|
|
case 'kronolithCinternalPAdvanced':
|
|
case 'kronolithCtasklistsPAdvanced':
|
|
var type = id.match(/kronolithC(.*)P/)[1];
|
|
if (orig.tagName != 'INPUT') {
|
|
break;
|
|
}
|
|
this.activateAdvancedPerms(type);
|
|
if (orig.name.match(/u_.*||new/)) {
|
|
this.insertGroupOrUser(type, 'user');
|
|
}
|
|
break;
|
|
|
|
case 'kronolithCinternalPUAdd':
|
|
case 'kronolithCinternalPGAdd':
|
|
case 'kronolithCtasklistsPUAdd':
|
|
case 'kronolithCtasklistsPGAdd':
|
|
var info = id.match(/kronolithC(.*)P(.)/);
|
|
this.insertGroupOrUser(info[1], info[2] == 'U' ? 'user' : 'group');
|
|
break;
|
|
|
|
case 'kronolithNavDay':
|
|
case 'kronolithNavWeek':
|
|
case 'kronolithNavWorkweek':
|
|
case 'kronolithNavMonth':
|
|
case 'kronolithNavYear':
|
|
case 'kronolithNavAgenda':
|
|
this.go(id.substring(12).toLowerCase() + ':' + this.date.dateString());
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithNavTasks':
|
|
this.go('tasks');
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithTasksAll':
|
|
case 'kronolithTasksComplete':
|
|
case 'kronolithTasksIncomplete':
|
|
case 'kronolithTasksFuture':
|
|
this.go('tasks:' + id.substring(14).toLowerCase());
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithMinicalDate':
|
|
this.go('month:' + orig.retrieve('date'));
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolith-minical':
|
|
if (orig.id == 'kronolith-minical-prev') {
|
|
var date = this.parseDate($('kronolithMinicalDate').retrieve('date'));
|
|
date.previous().month();
|
|
this.updateMinical(date, date.getMonth() == this.date.getMonth() ? this.view : undefined);
|
|
e.stop();
|
|
return;
|
|
}
|
|
if (orig.id == 'kronolith-minical-next') {
|
|
var date = this.parseDate($('kronolithMinicalDate').retrieve('date'));
|
|
date.next().month();
|
|
this.updateMinical(date, date.getMonth() == this.date.getMonth() ? this.view : null);
|
|
e.stop();
|
|
return;
|
|
}
|
|
|
|
var tmp = orig;
|
|
if (tmp.tagName.toLowerCase() != 'td') {
|
|
tmp = tmp.up('td');
|
|
}
|
|
if (tmp) {
|
|
if (tmp.retrieve('weekdate') &&
|
|
tmp.hasClassName('kronolith-minical-week')) {
|
|
this.go('week:' + tmp.retrieve('weekdate'));
|
|
} else if (tmp.retrieve('date') &&
|
|
!tmp.hasClassName('empty')) {
|
|
this.go('day:' + tmp.retrieve('date'));
|
|
}
|
|
}
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithEventsDay':
|
|
var date = this.date.clone();
|
|
date.add(Math.round((e.pointerY() - elt.cumulativeOffset().top + elt.up('.kronolithViewBody').scrollTop) / this.daySizes.height * 2) * 30).minutes();
|
|
this.go('event:' + date.toString('yyyyMMddHHmm'));
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithViewMonth':
|
|
if (orig.hasClassName('kronolith-first-col')) {
|
|
var date = orig.retrieve('date');
|
|
if (date) {
|
|
this.go('week:' + date);
|
|
e.stop();
|
|
return;
|
|
}
|
|
}
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithViewYear':
|
|
var tmp = orig;
|
|
if (tmp.tagName.toLowerCase() != 'td' && tmp.tagName.toLowerCase() != 'th') {
|
|
tmp = tmp.up('td');
|
|
}
|
|
if (tmp) {
|
|
if (tmp.retrieve('weekdate') &&
|
|
tmp.hasClassName('kronolith-minical-week')) {
|
|
this.go('week:' + tmp.retrieve('weekdate'));
|
|
} else if (tmp.hasClassName('kronolithMinicalDate')) {
|
|
this.go('month:' + tmp.retrieve('date'));
|
|
} else if (tmp.retrieve('date') &&
|
|
!tmp.hasClassName('empty')) {
|
|
this.go('day:' + tmp.retrieve('date'));
|
|
}
|
|
}
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithViewAgendaBody':
|
|
var tmp = orig;
|
|
if (tmp.tagName != 'TR') {
|
|
tmp = tmp.up('tr');
|
|
}
|
|
if (tmp && tmp.retrieve('date')) {
|
|
this.go('day:' + tmp.retrieve('date'));
|
|
}
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithSearchButton':
|
|
this.go('search:' + this.search + ':' + $F('horde-search-input'));
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithSearchFuture':
|
|
if (this.search != 'future') {
|
|
this.go('search:future:' + $F('horde-search-input'));
|
|
}
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithSearchPast':
|
|
if (this.search != 'past') {
|
|
this.go('search:past:' + $F('horde-search-input'));
|
|
}
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithSearchAll':
|
|
if (this.search != 'all') {
|
|
this.go('search:all:' + $F('horde-search-input'));
|
|
}
|
|
e.stop();
|
|
break;
|
|
case 'kronolithEventToTimeslice':
|
|
var params = $H();
|
|
params.set('e', $('kronolithEventId').value);
|
|
params.set('cal', $('kronolithEventCalendar').value);
|
|
params.set('t', $('kronolithEventTimesliceType').value);
|
|
params.set('c', $('kronolithEventTimesliceClient').value);
|
|
HordeCore.doAction('toTimeslice', params);
|
|
break;
|
|
case 'kronolithEventDialog':
|
|
case 'kronolithTaskDialog':
|
|
Horde_Calendar.hideCal();
|
|
return;
|
|
|
|
case 'kronolithCalendarDialog':
|
|
if (this.colorPicker) {
|
|
this.colorPicker.hide();
|
|
}
|
|
return;
|
|
|
|
case 'kronolithEditRecurCurrent':
|
|
case 'kronolithEditRecurFuture':
|
|
$('kronolithEventStartDate').setValue(this.orstart);
|
|
$('kronolithEventEndDate').setValue(this.orend);
|
|
if (id == 'kronolithEditRecurCurrent') {
|
|
this.toggleRecurrence('Exception');
|
|
} else {
|
|
this.toggleRecurrence(this.lastRecurType);
|
|
}
|
|
return;
|
|
case 'kronolithEditRecurAll':
|
|
this.toggleRecurrence(this.lastRecurType);
|
|
break;
|
|
case 'kronolithEventUrlToggle':
|
|
$('kronolithEventUrlDisplay').hide();
|
|
$('kronolithEventUrl').show();
|
|
e.stop();
|
|
return;
|
|
case 'kronolithCalendarinternalImportButton':
|
|
// Used when user has edit perms to a shared calendar.
|
|
this.calendarImport(elt.up('form'), true);
|
|
break;
|
|
}
|
|
|
|
// Caution, this only works if the element has definitely only a
|
|
// single CSS class.
|
|
switch (elt.className) {
|
|
case 'kronolithPrev':
|
|
case 'kronolithNext':
|
|
var newDate = this.date.clone(),
|
|
offset = elt.className == 'kronolithPrev' ? -1 : 1;
|
|
switch (this.view) {
|
|
case 'day':
|
|
case 'agenda':
|
|
newDate.add(offset).day();
|
|
break;
|
|
case 'week':
|
|
case 'workweek':
|
|
newDate.add(offset).week();
|
|
break;
|
|
case 'month':
|
|
newDate.add(offset).month();
|
|
break;
|
|
case 'year':
|
|
newDate.add(offset).year();
|
|
break;
|
|
}
|
|
this.go(this.view + ':' + newDate.dateString());
|
|
e.stop();
|
|
return;
|
|
|
|
case 'horde-add':
|
|
this.go('calendar:' + id.replace(/kronolithAdd/, ''));
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithTabLink':
|
|
this.openTab(elt);
|
|
e.stop();
|
|
break;
|
|
|
|
case 'horde-cancel':
|
|
this.closeRedBox();
|
|
this.resetMap();
|
|
this.go(this.lastLocation);
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithEventTag':
|
|
HordeImple.AutoCompleter.kronolithEventTags.addNewItemNode(elt.getText());
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithCalendarTag':
|
|
HordeImple.AutoCompleter.kronolithCalendarinternalTags.addNewItemNode(elt.getText());
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithTaskTag':
|
|
HordeImple.AutoCompleter.kronolithTaskTags.addNewItemNode(elt.getText());
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithEventGeo':
|
|
this.initializeMap(true);
|
|
this.geocode($F('kronolithEventLocation'));
|
|
e.stop();
|
|
break;
|
|
|
|
case 'kronolithTaskRow':
|
|
if (elt.retrieve('taskid')) {
|
|
this.go('task:' + elt.retrieve('tasklist') + ':' + elt.retrieve('taskid'));
|
|
}
|
|
e.stop();
|
|
return;
|
|
|
|
case 'horde-resource-edit-000':
|
|
case 'horde-resource-edit-fff':
|
|
this.go('calendar:' + elt.up().retrieve('calendarclass') + '|' + elt.up().retrieve('calendar'));
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithMore':
|
|
this.go('day:' + elt.retrieve('date'));
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithDatePicker':
|
|
id = elt.readAttribute('id');
|
|
Horde_Calendar.open(id, Date.parseExact($F(id.replace(/Picker$/, 'Date')), Kronolith.conf.date_format));
|
|
e.stop();
|
|
return;
|
|
|
|
case 'kronolithColorPicker':
|
|
var input = elt.previous();
|
|
this.colorPicker = new ColorPicker({
|
|
color: $F(input),
|
|
offsetParent: elt,
|
|
update: [[input, 'value'],
|
|
[input, 'background']]
|
|
});
|
|
e.stop();
|
|
return;
|
|
}
|
|
|
|
if (elt.hasClassName('kronolith-event')) {
|
|
if (!Object.isUndefined(elt.retrieve('ajax'))) {
|
|
this.go(elt.retrieve('ajax'));
|
|
} else {
|
|
this.go('event:' + elt.retrieve('calendar') + ':' + elt.retrieve('eventid') + ':' + elt.up().retrieve('date'));
|
|
}
|
|
e.stop();
|
|
return;
|
|
} else if (elt.hasClassName('kronolithMonthDay')) {
|
|
if (orig.hasClassName('kronolith-day')) {
|
|
var date = orig.retrieve('date');
|
|
if (date) {
|
|
this.go('day:' + date);
|
|
e.stop();
|
|
return;
|
|
}
|
|
}
|
|
this.go('event:' + elt.retrieve('date'));
|
|
e.stop();
|
|
return;
|
|
} else if (elt.hasClassName('kronolithWeekDay')) {
|
|
this.go('day:' + elt.retrieve('date'));
|
|
e.stop();
|
|
return;
|
|
} else if (elt.hasClassName('kronolithEventsWeek') ||
|
|
elt.hasClassName('kronolithEventsWorkweek') ||
|
|
elt.hasClassName('kronolithAllDayContainer')) {
|
|
var date = elt.retrieve('date');
|
|
if (elt.hasClassName('kronolithAllDayContainer')) {
|
|
date += 'all';
|
|
} else {
|
|
date = this.parseDate(date);
|
|
date.add(Math.round((e.pointerY() - elt.cumulativeOffset().top + elt.up('.kronolithViewBody').scrollTop) / (elt.hasClassName('kronolithEventsWeek') ? this.weekSizes.height : this.workweekSizes.height) * 2) * 30).minutes();
|
|
date = date.toString('yyyyMMddHHmm');
|
|
}
|
|
this.go('event:' + date);
|
|
e.stop();
|
|
return;
|
|
} else if (elt.hasClassName('kronolithTaskCheckbox')) {
|
|
var taskid = elt.up('tr.kronolithTaskRow', 0).retrieve('taskid'),
|
|
tasklist = elt.up('tr.kronolithTaskRow', 0).retrieve('tasklist');
|
|
this.toggleCompletionClass(taskid);
|
|
|
|
HordeCore.doAction('toggleCompletion', {
|
|
list: tasklist,
|
|
id: taskid
|
|
}, {
|
|
callback: function(r) {
|
|
if (r.toggled) {
|
|
this.toggleCompletion(tasklist, taskid, r.toggled);
|
|
if (r.toggled !== true) {
|
|
this.toggleCompletionClass(taskid);
|
|
}
|
|
} else {
|
|
this.toggleCompletionClass(taskid);
|
|
}
|
|
}.bind(this)
|
|
});
|
|
|
|
e.stop();
|
|
return;
|
|
} else if (elt.hasClassName('kronolithCalendarSave')) {
|
|
if (!elt.disabled) {
|
|
elt.disable();
|
|
if (!this.saveCalendar(elt.up('form'))) {
|
|
elt.enable();
|
|
}
|
|
}
|
|
e.stop();
|
|
break;
|
|
} else if (elt.hasClassName('kronolithCalendarContinue')) {
|
|
if (elt.disabled) {
|
|
e.stop();
|
|
break;
|
|
}
|
|
|
|
elt.disable();
|
|
var form = elt.up('form'),
|
|
type = form.id.replace(/kronolithCalendarForm/, ''),
|
|
i = 1;
|
|
while (!$('kronolithCalendar' + type + i).visible()) {
|
|
i++;
|
|
}
|
|
if (type == 'remote') {
|
|
var params = { url: $F('kronolithCalendarremoteUrl') };
|
|
if (i == 1) {
|
|
if (!$F('kronolithCalendarremoteUrl')) {
|
|
HordeCore.notify(Kronolith.text.no_url, 'horde.warning');
|
|
e.stop();
|
|
break;
|
|
}
|
|
|
|
HordeCore.doAction('getRemoteInfo', params, {
|
|
asynchronous: false,
|
|
callback: function(r) {
|
|
if (r.success) {
|
|
if (r.name) {
|
|
$('kronolithCalendarremoteName').setValue(r.name);
|
|
}
|
|
if (r.desc) {
|
|
$('kronolithCalendarremoteDescription').setValue(r.desc);
|
|
}
|
|
this.calendarNext(type);
|
|
this.calendarNext(type);
|
|
} else if (r.auth) {
|
|
this.calendarNext(type);
|
|
} else {
|
|
elt.enable();
|
|
}
|
|
}.bind(this)
|
|
});
|
|
|
|
}
|
|
if (i == 2) {
|
|
if ($F('kronolithCalendarremoteUsername')) {
|
|
params.user = $F('kronolithCalendarremoteUsername');
|
|
params.password = $F('kronolithCalendarremotePassword');
|
|
}
|
|
|
|
HordeCore.doAction('getRemoteInfo', params, {
|
|
callback: function(r) {
|
|
if (r.success) {
|
|
if (r.name &&
|
|
!$F('kronolithCalendarremoteName')) {
|
|
$('kronolithCalendarremoteName').setValue(r.name);
|
|
}
|
|
if (r.desc &&
|
|
!$F('kronolithCalendarremoteDescription')) {
|
|
$('kronolithCalendarremoteDescription').setValue(r.desc);
|
|
}
|
|
this.calendarNext(type);
|
|
} else {
|
|
if (r.auth) {
|
|
HordeCore.notify(Kronolith.text.wrong_auth, 'horde.warning');
|
|
}
|
|
elt.enable();
|
|
}
|
|
}.bind(this)
|
|
});
|
|
}
|
|
e.stop();
|
|
break;
|
|
}
|
|
this.calendarNext(type);
|
|
e.stop();
|
|
break;
|
|
} else if (elt.hasClassName('kronolithCalendarDelete')) {
|
|
var form = elt.up('form'),
|
|
type = form.id.replace(/kronolithCalendarForm/, ''),
|
|
calendar = $F('kronolithCalendar' + type + 'Id');
|
|
|
|
if ((type == 'tasklists' &&
|
|
!window.confirm(Kronolith.text.delete_tasklist)) ||
|
|
(type != 'tasklists' &&
|
|
!window.confirm(Kronolith.text.delete_calendar))) {
|
|
e.stop();
|
|
break;
|
|
}
|
|
|
|
if (!elt.disabled) {
|
|
elt.disable();
|
|
|
|
HordeCore.doAction('deleteCalendar', {
|
|
type: type,
|
|
calendar: calendar
|
|
}, {
|
|
callback: function(r) {
|
|
if (r.deleted) {
|
|
this.deleteCalendar(type, calendar);
|
|
}
|
|
this.closeRedBox();
|
|
this.go(this.lastLocation);
|
|
}.bind(this)
|
|
});
|
|
}
|
|
e.stop();
|
|
break;
|
|
} else if (elt.hasClassName('kronolithCalendarSubscribe') ||
|
|
elt.hasClassName('kronolithCalendarUnsubscribe')) {
|
|
var form = elt.up('form');
|
|
this.toggleCalendar($F(form.down('input[name=type]')),
|
|
$F(form.down('input[name=calendar]')));
|
|
this.closeRedBox();
|
|
this.go(this.lastLocation);
|
|
e.stop();
|
|
break;
|
|
} else if (elt.tagName == 'INPUT' &&
|
|
(elt.name == 'event_alarms[]' ||
|
|
elt.name == 'task[alarm_methods][]')) {
|
|
if (elt.name == 'event_alarms[]') {
|
|
$('kronolithEventAlarmOn').setValue(1);
|
|
$('kronolithEventAlarmDefaultOff').setValue(1);
|
|
} else {
|
|
$('kronolithTaskAlarmOn').setValue(1);
|
|
$('kronolithTaskAlarmDefaultOff').setValue(1);
|
|
}
|
|
if ($(elt.id + 'Params')) {
|
|
if (elt.getValue()) {
|
|
$(elt.id + 'Params').show();
|
|
} else {
|
|
$(elt.id + 'Params').hide();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
var calClass = elt.retrieve('calendarclass');
|
|
if (calClass) {
|
|
this.toggleCalendar(calClass, elt.retrieve('calendar'));
|
|
e.stop();
|
|
return;
|
|
}
|
|
|
|
elt = elt.up();
|
|
}
|
|
// Workaround Firebug bug.
|
|
Prototype.emptyFunction();
|
|
},
|
|
|
|
/**
|
|
* Handles date selections from a date picker.
|
|
*/
|
|
datePickerHandler: function(e)
|
|
{
|
|
var field = e.element().previous();
|
|
field.setValue(e.memo.toString(Kronolith.conf.date_format));
|
|
this.updateTimeFields(field.identify());
|
|
},
|
|
|
|
/**
|
|
* Handles moving an event to a different day in month view and all day
|
|
* events in weekly/daily views.
|
|
*/
|
|
onDrop: function(e)
|
|
{
|
|
var drop = e.element(),
|
|
el = e.memo.element;
|
|
|
|
if (drop == el.up()) {
|
|
return;
|
|
}
|
|
|
|
var lastDate = this.parseDate(el.up().retrieve('date')),
|
|
newDate = this.parseDate(drop.retrieve('date')),
|
|
diff = newDate.subtract(lastDate),
|
|
eventid = el.retrieve('eventid'),
|
|
cal = el.retrieve('calendar'),
|
|
viewDates = this.viewDates(this.date, this.view),
|
|
start = viewDates[0].toString('yyyyMMdd'),
|
|
end = viewDates[1].toString('yyyyMMdd'),
|
|
sig = start + end + (Math.random() + '').slice(2),
|
|
events = this.getCacheForDate(lastDate.toString('yyyyMMdd'), cal),
|
|
attributes = $H({ offDays: diff }),
|
|
event = events.find(function(e) { return e.key == eventid; });
|
|
|
|
drop.insert(el);
|
|
this.startLoading(cal, sig);
|
|
if (event.value.r) {
|
|
attributes.set('rday', lastDate);
|
|
attributes.set('cstart', this.cacheStart);
|
|
attributes.set('cend', this.cacheEnd);
|
|
}
|
|
var uatts = {
|
|
cal: cal,
|
|
id: eventid,
|
|
view: this.view,
|
|
sig: sig,
|
|
view_start: start,
|
|
view_end: end,
|
|
att: Object.toJSON(attributes)
|
|
},
|
|
callback = function(r) {
|
|
if (r.events) {
|
|
// Check if this is the still the result of the
|
|
// most current request.
|
|
if (r.sig == this.eventsLoading[r.cal]) {
|
|
var days;
|
|
if ((this.view == 'month' &&
|
|
Kronolith.conf.max_events) ||
|
|
this.view == 'week' ||
|
|
this.view == 'workweek' ||
|
|
this.view == 'day') {
|
|
days = this.findEventDays(cal, eventid);
|
|
}
|
|
this.removeEvent(cal, eventid);
|
|
if (days && days.length) {
|
|
this.reRender(days);
|
|
}
|
|
}
|
|
$H(r.events).each(function(days) {
|
|
$H(days.value).each(function(event) {
|
|
if (event.value.c.startsWith('tasks/')) {
|
|
var tasklist = event.value.c.substr(6),
|
|
task = event.key.substr(6),
|
|
taskObject;
|
|
if (this.tcache.get('incomplete') &&
|
|
this.tcache.get('incomplete').get(tasklist) &&
|
|
this.tcache.get('incomplete').get(tasklist).get(task)) {
|
|
taskObject = this.tcache.get('incomplete').get(tasklist).get(task);
|
|
taskObject.due = Date.parse(event.value.s);
|
|
this.tcache.get('incomplete').get(tasklist).set(task, taskObject);
|
|
}
|
|
}
|
|
}, this);
|
|
}, this);
|
|
}
|
|
this.loadEventsCallback(r, false);
|
|
$H(r.events).each(function(days) {
|
|
$H(days.value).each(function(event) {
|
|
if (event.key == eventid) {
|
|
this.refreshResources(days.key, cal, eventid, lastDate.toString('yyyyMMdd'), event);
|
|
}
|
|
}.bind(this))
|
|
}.bind(this));
|
|
}.bind(this);
|
|
|
|
if (event.value.mt) {
|
|
$('kronolithEventDiv').hide();
|
|
$('kronolithUpdateDiv').show();
|
|
RedBox.showHtml($('kronolithEventDialog').show());
|
|
this.uatts = uatts;
|
|
this.ucb = callback;
|
|
} else {
|
|
this.doDragDropUpdate(uatts, callback);
|
|
}
|
|
},
|
|
|
|
onDragStart: function(e)
|
|
{
|
|
if (this.view == 'month') {
|
|
return;
|
|
}
|
|
|
|
var elt = e.element();
|
|
|
|
if (elt.hasClassName('kronolithDragger')) {
|
|
elt.up().addClassName('kronolith-selected');
|
|
DragDrop.Drags.getDrag(elt).top = elt.cumulativeOffset().top;
|
|
} else if (elt.hasClassName('kronolithEditable')) {
|
|
elt.addClassName('kronolith-selected').setStyle({ left: 0, width: (this.view == 'week' || this.view == 'workweek') ? '90%' : '95%', zIndex: 1 });
|
|
}
|
|
|
|
this.scrollTop = $('kronolithView' + this.view.capitalize())
|
|
.down('.kronolithViewBody')
|
|
.scrollTop;
|
|
this.scrollLast = this.scrollTop;
|
|
},
|
|
|
|
onDrag: function(e)
|
|
{
|
|
if (this.view == 'month') {
|
|
return;
|
|
}
|
|
|
|
var elt = e.element(),
|
|
drag = DragDrop.Drags.getDrag(elt);
|
|
storage = this.view + 'Sizes',
|
|
step = this[storage].height / 6;
|
|
|
|
if (!drag.event) {
|
|
return;
|
|
}
|
|
|
|
var event = drag.event.value;
|
|
|
|
if (elt.hasClassName('kronolithDragger')) {
|
|
// Resizing the event.
|
|
var div = elt.up(),
|
|
top = drag.ghost.cumulativeOffset().top,
|
|
scrollTop = $('kronolithView' + this.view.capitalize()).down('.kronolithViewBody').scrollTop,
|
|
offset = 0,
|
|
height;
|
|
|
|
// Check if view has scrolled since last call.
|
|
if (scrollTop != this.scrollLast) {
|
|
offset = scrollTop - this.scrollLast;
|
|
this.scrollLast = scrollTop;
|
|
}
|
|
if (elt.hasClassName('kronolithDraggerTop')) {
|
|
offset += top - drag.top;
|
|
height = div.offsetHeight - offset;
|
|
div.setStyle({
|
|
top: (div.offsetTop + offset) + 'px'
|
|
});
|
|
offset = drag.ghost.offsetTop;
|
|
drag.top = top;
|
|
} else {
|
|
offset += top - drag.top;
|
|
height = div.offsetHeight + offset;
|
|
offset = div.offsetTop;
|
|
drag.top = top;
|
|
}
|
|
div.setStyle({
|
|
height: height + 'px'
|
|
});
|
|
|
|
this.calculateEventDates(event, storage, step, offset, height);
|
|
drag.innerDiv.update('(' + event.start.toString(Kronolith.conf.time_format) + ' - ' + event.end.toString(Kronolith.conf.time_format) + ') ' + event.t.escapeHTML());
|
|
} else if (elt.hasClassName('kronolithEditable')) {
|
|
// Moving the event.
|
|
if (Object.isUndefined(drag.innerDiv)) {
|
|
drag.innerDiv = drag.ghost.down('.kronolith-event-info');
|
|
}
|
|
if ((this.view == 'week') || (this.view == 'workweek')) {
|
|
var offsetX = Math.round(drag.ghost.offsetLeft / drag.stepX);
|
|
event.offsetDays = offsetX;
|
|
this.calculateEventDates(event, storage, step, drag.ghost.offsetTop, drag.divHeight, event.start.clone().addDays(offsetX), event.end.clone().addDays(offsetX));
|
|
} else {
|
|
event.offsetDays = 0;
|
|
this.calculateEventDates(event, storage, step, drag.ghost.offsetTop, drag.divHeight);
|
|
}
|
|
event.offsetTop = drag.ghost.offsetTop - drag.startTop;
|
|
drag.innerDiv.update('(' + event.start.toString(Kronolith.conf.time_format) + ' - ' + event.end.toString(Kronolith.conf.time_format) + ') ' + event.t.escapeHTML());
|
|
elt.clonePosition(drag.ghost, { offsetLeft: (this.view == 'week' || this.view == 'workweek') ? -2 : 0 });
|
|
}
|
|
},
|
|
|
|
onDragEnd: function(e)
|
|
{
|
|
if (this.view == 'month') {
|
|
return;
|
|
}
|
|
|
|
if (!e.element().hasClassName('kronolithDragger') &&
|
|
!e.element().hasClassName('kronolithEditable')) {
|
|
return;
|
|
}
|
|
|
|
var div = e.element(),
|
|
drag = DragDrop.Drags.getDrag(div),
|
|
event = drag.event;
|
|
|
|
|
|
if (event.value.al) {
|
|
return;
|
|
}
|
|
var date = drag.midnight,
|
|
storage = this.view + 'Sizes',
|
|
step = this[storage].height / 6,
|
|
dates = this.viewDates(date, this.view),
|
|
start = dates[0].dateString(),
|
|
end = dates[1].dateString(),
|
|
sig = start + end + (Math.random() + '').slice(2),
|
|
element, attributes;
|
|
|
|
div.removeClassName('kronolith-selected');
|
|
if (!Object.isUndefined(drag.innerDiv)) {
|
|
this.setEventText(drag.innerDiv, event.value);
|
|
}
|
|
this.startLoading(event.value.calendar, sig);
|
|
if (!Object.isUndefined(event.value.offsetTop)) {
|
|
attributes = $H({ offDays: event.value.offsetDays,
|
|
offMins: Math.round(event.value.offsetTop / step) * 10 });
|
|
element = div;
|
|
} else if (div.hasClassName('kronolithDraggerTop')) {
|
|
attributes = $H({ start: event.value.start });
|
|
element = div.up();
|
|
} else if (div.hasClassName('kronolithDraggerBottom')) {
|
|
attributes = $H({ end: event.value.end });
|
|
element = div.up();
|
|
} else {
|
|
attributes = $H({ start: event.value.start,
|
|
end: event.value.end });
|
|
element = div;
|
|
}
|
|
if (event.value.r) {
|
|
attributes.set('rstart', event.value.s);
|
|
attributes.set('rend', event.value.e);
|
|
attributes.set('cstart', this.cacheStart);
|
|
attributes.set('cend', this.cacheEnd);
|
|
}
|
|
element.retrieve('drags').invoke('destroy');
|
|
var uatts = {
|
|
cal: event.value.calendar,
|
|
id: event.key,
|
|
view: this.view,
|
|
sig: sig,
|
|
view_start: start,
|
|
view_end: end,
|
|
att: Object.toJSON(attributes)
|
|
},
|
|
callback = function(r) {
|
|
// Check if this is the still the result of the most current
|
|
// request.
|
|
if (r.events &&
|
|
r.sig == this.eventsLoading[r.cal]) {
|
|
if (event.value.rs) {
|
|
var d = new Date(event.value.s);
|
|
this.refreshResources(d.toString('yyyyMMdd'), event.value.calendar, event.key)
|
|
}
|
|
this.removeEvent(event.value.calendar, event.key);
|
|
}
|
|
this.loadEventsCallback(r, false);
|
|
}.bind(this);
|
|
|
|
if (event.value.mt) {
|
|
$('kronolithEventDiv').hide();
|
|
$('kronolithUpdateDiv').show();
|
|
RedBox.showHtml($('kronolithEventDialog').show());
|
|
this.uatts = uatts;
|
|
this.ucb = callback;
|
|
} else {
|
|
this.doDragDropUpdate(uatts, callback);
|
|
}
|
|
},
|
|
|
|
doDragDropUpdate: function(att, cb)
|
|
{
|
|
HordeCore.doAction('updateEvent', att, {
|
|
callback: cb
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Refresh any resource calendars bound to the given just-updated event.
|
|
* Clears the old resource event from UI and cache, and clears the cache
|
|
* for the days of the new event, in order to allow listEvents to refresh
|
|
* the UI.
|
|
*
|
|
* @param string dt The current/new date for the event (yyyyMMdd).
|
|
* @param string cal The calendar the event exists in.
|
|
* @param string eventid The eventid that is changing.
|
|
* @param string last_dt The previous date for the event, if known. (yyyyMMdd).
|
|
* @param object event The event object (if a new event) dt is ignored.
|
|
*
|
|
*/
|
|
refreshResources: function(dt, cal, eventid, last_dt, event)
|
|
{
|
|
var events = this.getCacheForDate(dt, cal),
|
|
update_cals = [], r_dates;
|
|
|
|
if (!event) {
|
|
event = events.find(function(e) { return e.key == eventid; });
|
|
}
|
|
if (!dt) {
|
|
dt = new Date(event.value.s);
|
|
} else {
|
|
dt = this.parseDate(dt);
|
|
}
|
|
if (event) {
|
|
$H(event.value.rs).each(function(r) {
|
|
var r_cal = ['resource', r.value.calendar],
|
|
r_events = this.getCacheForDate(last_dt, r_cal.join('|')),
|
|
r_event, day, end;
|
|
|
|
if (r_events) {
|
|
r_event = r_events.find(function(e) { return e.value.uid == event.value.uid });
|
|
if (r_event) {
|
|
this.removeEvent(r_cal, r_event.key);
|
|
day = new Date(r_event.value.s);
|
|
end = new Date(r_event.value.s);
|
|
while (!day.isAfter(end)) {
|
|
this.deleteCache(r_cal, null, day.toString('yyyyMMdd'));
|
|
day.add(1).day();
|
|
}
|
|
day = new Date(event.value.s);
|
|
end = new Date(event.value.e);
|
|
|
|
while (!day.isAfter(end)) {
|
|
this.deleteCache(r_cal, null, day.toString('yyyyMMdd'));
|
|
day.add(1).day();
|
|
}
|
|
} else {
|
|
// Don't know the previous date/time so just nuke the cache.
|
|
this.deleteCache(r_cal);
|
|
}
|
|
} else {
|
|
this.deleteCache(r_cal);
|
|
}
|
|
update_cals.push(r_cal);
|
|
}.bind(this));
|
|
|
|
if (update_cals.length) {
|
|
dates = this.viewDates(dt, this.view);
|
|
// Ensure we also grab the full length of the events.
|
|
if (dates[0].isAfter(dt)) {
|
|
dates[0] = dt;
|
|
}
|
|
var dt_end = new Date(event.value.e);
|
|
if (dt_end.isAfter(dates[1])) {
|
|
dates[1] = dt_end;
|
|
}
|
|
this.loadEvents(dates[0], dates[1], this.view, update_cals);
|
|
}
|
|
}
|
|
},
|
|
|
|
editEvent: function(calendar, id, date, title)
|
|
{
|
|
if (this.redBoxLoading) {
|
|
return;
|
|
}
|
|
if (Object.isUndefined(HordeImple.AutoCompleter.kronolithEventTags)) {
|
|
this.editEvent.bind(this, calendar, id, date).defer();
|
|
return;
|
|
}
|
|
|
|
this.closeRedBox();
|
|
this.quickClose();
|
|
this.redBoxOnDisplay = RedBox.onDisplay;
|
|
RedBox.onDisplay = function() {
|
|
if (this.redBoxOnDisplay) {
|
|
this.redBoxOnDisplay();
|
|
}
|
|
try {
|
|
$('kronolithEventForm').focusFirstElement();
|
|
} catch(e) {}
|
|
if (Kronolith.conf.maps.driver &&
|
|
$('kronolithEventLinkMap').up().hasClassName('horde-active') &&
|
|
!this.mapInitialized) {
|
|
|
|
this.initializeMap();
|
|
}
|
|
RedBox.onDisplay = this.redBoxOnDisplay;
|
|
}.bind(this);
|
|
this.attendees = [];
|
|
this.resources = [];
|
|
this.updateCalendarDropDown('kronolithEventTarget');
|
|
this.toggleAllDay(false);
|
|
this.openTab($('kronolithEventForm').down('.tabset a.kronolithTabLink'));
|
|
this.disableAlarmMethods('Event');
|
|
this.knl.kronolithEventStartTime.markSelected();
|
|
this.knl.kronolithEventEndTime.markSelected();
|
|
$('kronolithEventForm').reset();
|
|
this.resetMap();
|
|
HordeImple.AutoCompleter.kronolithEventAttendees.reset();
|
|
HordeImple.AutoCompleter.kronolithEventTags.reset();
|
|
HordeImple.AutoCompleter.kronolithEventResources.reset();
|
|
if (Kronolith.conf.maps.driver) {
|
|
$('kronolithEventMapLink').hide();
|
|
}
|
|
$('kronolithEventSave').show().enable();
|
|
$('kronolithEventSaveAsNew').show().enable();
|
|
$('kronolithEventDelete').show().enable();
|
|
$('kronolithEventDeleteConfirm').enable();
|
|
$('kronolithEventTarget').show();
|
|
$('kronolithEventTargetRO').hide();
|
|
$('kronolithEventForm').down('.kronolithFormActions .kronolithSeparator').show();
|
|
$('kronolithEventExceptions').clear();
|
|
if (id) {
|
|
// An id passed to this function indicates we are editing an event.
|
|
RedBox.loading();
|
|
var attributes = { cal: calendar, id: id, date: date };
|
|
// Need the current st and et of this instance.
|
|
var events = this.getCacheForDate(date.toString('yyyyMMdd'), calendar);
|
|
if (events) {
|
|
var ev = events.find(function(e) { return e.key == id; });
|
|
if (ev[1].r) {
|
|
attributes.rsd = ev[1].start.dateString();
|
|
attributes.red = ev[1].end.dateString();
|
|
}
|
|
}
|
|
HordeCore.doAction('getEvent', attributes, {
|
|
callback: this.editEventCallback.bind(this)
|
|
});
|
|
$('kronolithEventTopTags').update();
|
|
} else {
|
|
// This is a new event.
|
|
HordeCore.doAction('listTopTags', {}, {
|
|
callback: this.topTagsCallback.curry('kronolithEventTopTags', 'kronolithEventTag')
|
|
});
|
|
var d;
|
|
if (date) {
|
|
if (date.endsWith('all')) {
|
|
date = date.substring(0, date.length - 3);
|
|
$('kronolithEventAllday').setValue(true);
|
|
this.toggleAllDay(true);
|
|
}
|
|
d = this.parseDate(date);
|
|
} else {
|
|
d = new Date();
|
|
}
|
|
if (title) {
|
|
$('kronolithEventTitle').setValue(title);
|
|
}
|
|
$('kronolithEventId').clear();
|
|
$('kronolithEventCalendar').clear();
|
|
$('kronolithEventTarget').setValue(Kronolith.conf.default_calendar);
|
|
$('kronolithEventDelete').hide();
|
|
$('kronolithEventStartDate').setValue(d.toString(Kronolith.conf.date_format));
|
|
$('kronolithEventStartTime').setValue(d.toString(Kronolith.conf.time_format));
|
|
this.updateFBDate(d);
|
|
d.add(1).hour();
|
|
this.duration = 60;
|
|
$('kronolithEventEndDate').setValue(d.toString(Kronolith.conf.date_format));
|
|
$('kronolithEventEndTime').setValue(d.toString(Kronolith.conf.time_format));
|
|
$('kronolithEventLinkExport').up('span').hide();
|
|
$('kronolithEventSaveAsNew').hide();
|
|
$('kronolithEventUrlDisplay').hide();
|
|
$('kronolithEventUrl').show();
|
|
this.toggleRecurrence(true, 'None');
|
|
$('kronolithEventEditRecur').hide();
|
|
this.enableAlarm('Event', Kronolith.conf.default_alarm);
|
|
this.redBoxLoading = true;
|
|
RedBox.showHtml($('kronolithEventDialog').show());
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Generates ajax request parameters for requests to save events.
|
|
*
|
|
* @return object An object with request parameters.
|
|
*/
|
|
saveEventParams: function()
|
|
{
|
|
var viewDates = this.viewDates(this.date, this.view),
|
|
params = {
|
|
sig: viewDates[0].dateString() + viewDates[1].dateString(),
|
|
view: this.view
|
|
};
|
|
if (this.cacheStart) {
|
|
params.view_start = this.cacheStart.dateString();
|
|
params.view_end = this.cacheEnd.dateString();
|
|
}
|
|
return params;
|
|
},
|
|
|
|
/**
|
|
* Submits the event edit form to create or update an event.
|
|
*/
|
|
saveEvent: function(asnew)
|
|
{
|
|
this.validateEvent(asnew);
|
|
},
|
|
|
|
/**
|
|
* Perform any preliminary checks necessary. doSaveEvent will be called from
|
|
* the callback if checks are successful.
|
|
*
|
|
*/
|
|
validateEvent: function(asnew)
|
|
{
|
|
if (this.wrongFormat.size()) {
|
|
HordeCore.notify(Kronolith.text.fix_form_values, 'horde.warning');
|
|
return;
|
|
}
|
|
|
|
// Check that there are no conflicts.
|
|
if (Kronolith.conf.has_resources && $F('kronolithEventResourceIds')) {
|
|
HordeCore.doAction(
|
|
'checkResources',
|
|
{
|
|
s: this.getDate('start').toISOString(),
|
|
e: this.getDate('end').toISOString(),
|
|
i: $F('kronolithEventId'),
|
|
c: $F('kronolithEventCalendar'),
|
|
r: $F('kronolithEventResourceIds')
|
|
},
|
|
{
|
|
callback: this.validateEventCallback.curry(asnew).bind(this)
|
|
}
|
|
);
|
|
} else {
|
|
this.validateEventCallback(asnew, {});
|
|
}
|
|
},
|
|
|
|
validateEventCallback: function(asnew, r)
|
|
{
|
|
var conflict = false;
|
|
|
|
$H(r).each(function(a) {
|
|
// 3 == Kronolith::RESPONSE_DECLINED
|
|
if (a.value == 3) {
|
|
$('kronolithEventDiv').hide();
|
|
$('kronolithConflictDiv').show();
|
|
conflict = true;
|
|
return;
|
|
}
|
|
});
|
|
if (!conflict) {
|
|
this.doSaveEvent(asnew);
|
|
}
|
|
},
|
|
|
|
doSaveEvent: function(asnew)
|
|
{
|
|
var cal = $F('kronolithEventCalendar'),
|
|
target = $F('kronolithEventTarget'),
|
|
eventid = $F('kronolithEventId'),
|
|
params;
|
|
|
|
if (this.mapInitialized) {
|
|
$('kronolithEventMapZoom').value = this.map.getZoom();
|
|
}
|
|
|
|
params = $H($('kronolithEventForm').serialize({ hash: true }))
|
|
.merge(this.saveEventParams());
|
|
params.set('as_new', asnew ? 1 : 0);
|
|
if (this.cacheStart) {
|
|
params.set('cstart', this.cacheStart.toISOString());
|
|
params.set('cend', this.cacheEnd.toISOString());
|
|
}
|
|
HordeImple.AutoCompleter.kronolithEventTags.shutdown();
|
|
$('kronolithEventSave').disable();
|
|
$('kronolithEventSaveAsNew').disable();
|
|
$('kronolithEventDelete').disable();
|
|
this.startLoading(target, params.get('sig'));
|
|
HordeCore.doAction('saveEvent', params, {
|
|
callback: function(r) {
|
|
if (!asnew && r.events && eventid) {
|
|
this.removeEvent(cal, eventid);
|
|
}
|
|
this.loadEventsCallback(r, false);
|
|
|
|
// Refresh bound exceptions
|
|
var calendar = cal.split('|'), refreshed = false;
|
|
$H(r.events).each(function(d) {
|
|
$H(d.value).each(function(evt) {
|
|
if (evt.value.bid) {
|
|
var cache = this.getCacheForDate(this.findEventDays(cal, evt.key, cal));
|
|
cache.each(function(entry) {
|
|
if (entry.value.bid == evt.value.bid && evt.value.c != calendar[1]) {
|
|
this.removeEvent(cal, entry.key);
|
|
}
|
|
}.bind(this));
|
|
}
|
|
if (!refreshed && ((evt.key == eventid) || !eventid) && evt.value.rs) {
|
|
this.refreshResources(null, cal, eventid, false, evt);
|
|
refreshed = true;
|
|
}
|
|
}.bind(this))
|
|
}.bind(this));
|
|
|
|
if (r.events) {
|
|
this.resetMap();
|
|
this.closeRedBox();
|
|
this.go(this.lastLocation);
|
|
} else {
|
|
$('kronolithEventSave').enable();
|
|
$('kronolithEventSaveAsNew').enable();
|
|
$('kronolithEventDelete').enable();
|
|
}
|
|
$('kronolithUpdateDiv').hide();
|
|
$('kronolithConflictDiv').hide();
|
|
$('kronolithEventDiv').show();
|
|
}.bind(this)
|
|
});
|
|
},
|
|
|
|
quickSaveEvent: function()
|
|
{
|
|
var text = $F('kronolithQuickinsertQ'),
|
|
cal = $F('kronolithQuickinsertCalendars'),
|
|
params;
|
|
|
|
params = $H($('kronolithEventForm').serialize({ hash: true }))
|
|
.merge(this.saveEventParams());
|
|
params.set('text', text);
|
|
params.set('cal', cal);
|
|
|
|
this.closeRedBox();
|
|
this.startLoading(cal, params.get('sig'));
|
|
HordeCore.doAction('quickSaveEvent', params, {
|
|
callback: function(r) {
|
|
this.loadEventsCallback(r, false);
|
|
if (r.error) {
|
|
this.editEvent(null, null, null, text);
|
|
} else {
|
|
$('kronolithQuickinsertQ').value = '';
|
|
}
|
|
}.bind(this)
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Closes and resets the quick event form.
|
|
*/
|
|
quickClose: function()
|
|
{
|
|
$('kronolithQuickinsertQ').value = '';
|
|
if ($('kronolithQuicktaskQ')) {
|
|
$('kronolithQuicktaskQ').value = '';
|
|
}
|
|
this.closeRedBox();
|
|
},
|
|
|
|
topTagsCallback: function(update, tagclass, r)
|
|
{
|
|
$('kronolithEventTabTags').select('label').invoke('show');
|
|
if (!r.tags) {
|
|
$(update).update();
|
|
return;
|
|
}
|
|
|
|
var t = new Element('ul', { className: 'horde-tags' });
|
|
r.tags.each(function(tag) {
|
|
if (tag == null) {
|
|
return;
|
|
}
|
|
t.insert(new Element('li', { className: tagclass }).update(tag.escapeHTML()));
|
|
});
|
|
$(update).update(t);
|
|
},
|
|
|
|
/**
|
|
* Callback method for showing event forms.
|
|
*
|
|
* @param object r The ajax response object.
|
|
*/
|
|
editEventCallback: function(r)
|
|
{
|
|
if (!r.event) {
|
|
RedBox.close();
|
|
this.go(this.lastLocation);
|
|
return;
|
|
}
|
|
|
|
var ev = r.event;
|
|
|
|
if (!Object.isUndefined(ev.ln)) {
|
|
this.loadPage(ev.ln);
|
|
this.closeRedBox();
|
|
return;
|
|
}
|
|
|
|
/* Basic information */
|
|
$('kronolithEventId').setValue(ev.id);
|
|
$('kronolithEventCalendar').setValue(ev.ty + '|' + ev.c);
|
|
$('kronolithEventTarget').setValue(ev.ty + '|' + ev.c);
|
|
$('kronolithEventTargetRO').update(Kronolith.conf.calendars[ev.ty][ev.c].name.escapeHTML());
|
|
$('kronolithEventTitle').setValue(ev.t);
|
|
$('kronolithEventLocation').setValue(ev.l);
|
|
$('kronolithEventTimezone').setValue(ev.tz);
|
|
if (ev.l && Kronolith.conf.maps.driver) {
|
|
$('kronolithEventMapLink').show();
|
|
}
|
|
if (ev.uhl) {
|
|
$('kronolithEventUrlDisplay').down().update(ev.uhl);
|
|
$('kronolithEventUrlDisplay').show();
|
|
$('kronolithEventUrl').hide();
|
|
}
|
|
else {
|
|
$('kronolithEventUrlDisplay').hide();
|
|
$('kronolithEventUrl').show();
|
|
}
|
|
|
|
if (ev.u) {
|
|
$('kronolithEventUrl').setValue(ev.u);
|
|
}
|
|
|
|
$('kronolithEventAllday').setValue(ev.al);
|
|
|
|
if (ev.r && ev.rsd && ev.red) {
|
|
// Save the original datetime, so we can properly create the
|
|
// exception.
|
|
var osd = Date.parse(ev.rsd + ' ' + ev.st);
|
|
var oed = Date.parse(ev.red + ' ' + ev.et);
|
|
|
|
$('kronolithEventRecurOStart').setValue(osd.toString('s'));
|
|
$('kronolithEventRecurOEnd').setValue(oed.toString('s'));
|
|
|
|
// ...and put the same value in the form field to replace the
|
|
// date of the initial series.
|
|
$('kronolithEventStartDate').setValue(ev.sd);
|
|
$('kronolithEventEndDate').setValue(ev.ed);
|
|
// Save the current datetime in case we are not editing 'all'
|
|
this.orstart = ev.rsd;
|
|
this.orend = ev.red;
|
|
} else {
|
|
$('kronolithEventStartDate').setValue(ev.sd);
|
|
$('kronolithEventEndDate').setValue(ev.ed);
|
|
$('kronolithEventRecurEnd').clear();
|
|
$('kronolithEventRecurOStart').clear();
|
|
$('kronolithEventRecurOEnd').clear();
|
|
this.orstart = null;
|
|
this.orend = null;
|
|
}
|
|
|
|
$('kronolithEventStartTime').setValue(ev.st);
|
|
this.knl.kronolithEventStartTime.setSelected(ev.st);
|
|
this.updateFBDate(Date.parseExact(ev.sd, Kronolith.conf.date_format));
|
|
$('kronolithEventEndTime').setValue(ev.et);
|
|
this.knl.kronolithEventEndTime.setSelected(ev.et);
|
|
this.duration = Math.abs(Date.parse(ev.e).getTime() - Date.parse(ev.s).getTime()) / 60000;
|
|
this.toggleAllDay(ev.al);
|
|
$('kronolithEventStatus').setValue(ev.x);
|
|
$('kronolithEventDescription').setValue(ev.d);
|
|
$('kronolithEventPrivate').setValue(ev.pv);
|
|
$('kronolithEventLinkExport').up('span').show();
|
|
$('kronolithEventExport').href = Kronolith.conf.URI_EVENT_EXPORT.interpolate({ id: ev.id, calendar: ev.c, type: ev.ty });
|
|
|
|
/* Alarm */
|
|
if (ev.a) {
|
|
this.enableAlarm('Event', ev.a);
|
|
if (ev.m) {
|
|
$('kronolithEventAlarmDefaultOff').checked = true;
|
|
$H(ev.m).each(function(method) {
|
|
$('kronolithEventAlarm' + method.key).setValue(1);
|
|
if ($('kronolithEventAlarm' + method.key + 'Params')) {
|
|
$('kronolithEventAlarm' + method.key + 'Params').show();
|
|
$H(method.value).each(function(param) {
|
|
var input = $('kronolithEventAlarmParam' + param.key);
|
|
if (input.type == 'radio') {
|
|
input.up('form').select('input[type=radio]').each(function(radio) {
|
|
if (radio.name == input.name &&
|
|
radio.value == param.value) {
|
|
radio.setValue(1);
|
|
throw $break;
|
|
}
|
|
});
|
|
} else {
|
|
input.setValue(param.value);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
$('kronolithEventAlarmOff').setValue(true);
|
|
}
|
|
|
|
/* Recurrence */
|
|
if (ev.r) {
|
|
this.setRecurrenceFields(true, ev.r);
|
|
$('kronolithRecurDelete').show();
|
|
$('kronolithNoRecurDelete').hide();
|
|
$('kronolithEventEditRecur').show();
|
|
this.recurs = true;
|
|
} else if (ev.bid) {
|
|
$('kronolithRecurDelete').hide();
|
|
$('kronolithNoRecurDelete').show();
|
|
$('kronolithEventEditRecur').hide();
|
|
var div = $('kronolithEventRepeatException');
|
|
div.down('span').update(ev.eod);
|
|
this.toggleRecurrence(true, 'Exception');
|
|
this.recurs = false;
|
|
} else {
|
|
$('kronolithRecurDelete').hide();
|
|
$('kronolithNoRecurDelete').show();
|
|
$('kronolithEventEditRecur').hide();
|
|
this.toggleRecurrence(true, 'None');
|
|
this.recurs = false;
|
|
}
|
|
|
|
/* Attendees */
|
|
if (!Object.isUndefined(ev.at)) {
|
|
HordeImple.AutoCompleter.kronolithEventAttendees.reset(ev.at.pluck('l'));
|
|
ev.at.each(this.addAttendee.bind(this));
|
|
if (this.fbLoading) {
|
|
$('kronolithFBLoading').show();
|
|
}
|
|
}
|
|
|
|
/* Resources */
|
|
if (!Object.isUndefined(ev.rs)) {
|
|
var rs = $H(ev.rs);
|
|
HordeImple.AutoCompleter.kronolithEventResources.reset(rs.values().pluck('name'));
|
|
rs.each(function(r) { this.addResource(r.value, r.key); }.bind(this));
|
|
if (this.fbLoading) {
|
|
$('kronolithResourceFBLoading').show();
|
|
}
|
|
}
|
|
|
|
/* Tags */
|
|
HordeImple.AutoCompleter.kronolithEventTags.reset(ev.tg);
|
|
|
|
/* Geo */
|
|
if (ev.gl) {
|
|
$('kronolithEventLocationLat').value = ev.gl.lat;
|
|
$('kronolithEventLocationLon').value = ev.gl.lon;
|
|
$('kronolithEventMapZoom').value = Math.max(1, ev.gl.zoom);
|
|
}
|
|
|
|
if (!ev.pe) {
|
|
$('kronolithEventSave').hide();
|
|
HordeImple.AutoCompleter.kronolithEventTags.disable();
|
|
$('kronolithEventTabTags').select('label').invoke('hide');
|
|
} else {
|
|
HordeCore.doAction('listTopTags', {}, {
|
|
callback: this.topTagsCallback.curry('kronolithEventTopTags', 'kronolithEventTag')
|
|
});
|
|
}
|
|
if (!ev.pd) {
|
|
$('kronolithEventDelete').hide();
|
|
$('kronolithEventTarget').hide();
|
|
$('kronolithEventTargetRO').show();
|
|
}
|
|
|
|
this.setTitle(ev.t);
|
|
this.redBoxLoading = true;
|
|
RedBox.showHtml($('kronolithEventDialog').show());
|
|
|
|
/* Hide alarm message for this event. */
|
|
if (r.msgs) {
|
|
r.msgs = r.msgs.reject(function(msg) {
|
|
if (msg.type != 'horde.alarm') {
|
|
return false;
|
|
}
|
|
var alarm = msg.flags.alarm;
|
|
if (alarm.params && alarm.params.notify &&
|
|
alarm.params.notify.show &&
|
|
alarm.params.notify.show.calendar &&
|
|
alarm.params.notify.show.event &&
|
|
alarm.params.notify.show.calendar == ev.c &&
|
|
alarm.params.notify.show.event == ev.id) {
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Adds an attendee row to the free/busy table.
|
|
*
|
|
* @param object attendee An attendee object with the properties:
|
|
* - e: email address
|
|
* - l: the display name of the attendee
|
|
*/
|
|
addAttendee: function(attendee)
|
|
{
|
|
if (typeof attendee == 'string') {
|
|
if (attendee.include('@')) {
|
|
HordeCore.doAction('parseEmailAddress', {
|
|
email: attendee
|
|
}, {
|
|
callback: function (r) {
|
|
if (r.email) {
|
|
this.addAttendee({ e: r.email, l: attendee });
|
|
}
|
|
}.bind(this)
|
|
});
|
|
return;
|
|
} else {
|
|
attendee = { l: attendee };
|
|
}
|
|
}
|
|
|
|
if (attendee.e) {
|
|
this.attendees.push(attendee);
|
|
this.fbLoading++;
|
|
HordeCore.doAction('getFreeBusy', {
|
|
email: attendee.e
|
|
}, {
|
|
callback: function(r) {
|
|
this.fbLoading--;
|
|
if (!this.fbLoading) {
|
|
$('kronolithFBLoading').hide();
|
|
}
|
|
if (!Object.isUndefined(r.fb)) {
|
|
this.freeBusy.get(attendee.l)[1] = r.fb;
|
|
this.insertFreeBusy(attendee.l, this.getFBDate());
|
|
}
|
|
}.bind(this)
|
|
});
|
|
}
|
|
|
|
var tr = new Element('tr'), response, i;
|
|
this.freeBusy.set(attendee.l, [ tr ]);
|
|
attendee.r = attendee.r || 1;
|
|
switch (attendee.r) {
|
|
case 1: response = 'None'; break;
|
|
case 2: response = 'Accepted'; break;
|
|
case 3: response = 'Declined'; break;
|
|
case 4: response = 'Tentative'; break;
|
|
}
|
|
tr.insert(new Element('td')
|
|
.writeAttribute('title', attendee.l)
|
|
.addClassName('kronolithAttendee' + response)
|
|
.insert(attendee.e ? attendee.e.escapeHTML() : attendee.l.escapeHTML()));
|
|
for (i = 0; i < 24; i++) {
|
|
tr.insert(new Element('td', { className: 'kronolithFBUnknown' }));
|
|
}
|
|
$('kronolithEventAttendeesList').down('tbody').insert(tr);
|
|
},
|
|
|
|
resetFBRows: function()
|
|
{
|
|
this.attendees.each(function(attendee) {
|
|
var row = this.freeBusy.get(attendee.l)[0];
|
|
row.update();
|
|
|
|
attendee.r = attendee.r || 1;
|
|
switch (attendee.r) {
|
|
case 1: response = 'None'; break;
|
|
case 2: response = 'Accepted'; break;
|
|
case 3: response = 'Declined'; break;
|
|
case 4: response = 'Tentative'; break;
|
|
}
|
|
row.insert(new Element('td')
|
|
.writeAttribute('title', attendee.l)
|
|
.addClassName('kronolithAttendee' + response)
|
|
.insert(attendee.e ? attendee.e.escapeHTML() : attendee.l.escapeHTML()));
|
|
for (i = 0; i < 24; i++) {
|
|
row.insert(new Element('td', { className: 'kronolithFBUnknown' }));
|
|
}
|
|
}.bind(this));
|
|
this.resources.each(function(resource) {
|
|
var row = this.freeBusy.get(resource)[0],
|
|
tdone = row.down('td');
|
|
row.update();
|
|
row.update(tdone);
|
|
for (i = 0; i < 24; i++) {
|
|
row.insert(new Element('td', { className: 'kronolithFBUnknown' }));
|
|
}
|
|
}.bind(this));
|
|
},
|
|
|
|
addResource: function(resource, id)
|
|
{
|
|
var v, response = 1;
|
|
if (!id) {
|
|
// User entered
|
|
this.resourceACCache.choices.each(function(i) {
|
|
if (i.name == resource) {
|
|
v = i.code;
|
|
throw $break;
|
|
} else {
|
|
v = false;
|
|
}
|
|
}.bind(this));
|
|
} else {
|
|
// Populating from an edit event action
|
|
v = id;
|
|
response = resource.response;
|
|
resource = resource.name;
|
|
}
|
|
|
|
switch (response) {
|
|
case 1: response = 'None'; break;
|
|
case 2: response = 'Accepted'; break;
|
|
case 3: response = 'Declined'; break;
|
|
case 4: response = 'Tentative'; break;
|
|
}
|
|
var att = {
|
|
'resource': v
|
|
},
|
|
tr, i;
|
|
if (att.resource) {
|
|
this.fbLoading++;
|
|
HordeCore.doAction('getFreeBusy', att, {
|
|
callback: this.addResourceCallback.curry(resource).bind(this)
|
|
});
|
|
tr = new Element('tr');
|
|
this.freeBusy.set(resource, [ tr ]);
|
|
tr.insert(new Element('td')
|
|
.writeAttribute('title', resource)
|
|
.addClassName('kronolithAttendee' + response)
|
|
.insert(resource.escapeHTML()));
|
|
for (i = 0; i < 24; i++) {
|
|
tr.insert(new Element('td', { className: 'kronolithFBUnknown' }));
|
|
}
|
|
$('kronolithEventResourcesList').down('tbody').insert(tr);
|
|
this.resourceACCache.map.set(resource, v);
|
|
$('kronolithEventResourceIds').value = this.resourceACCache.map.values();
|
|
} else {
|
|
HordeCore.notify(Kronolith.text.unknown_resource + ': ' + resource, 'horde.error');
|
|
}
|
|
},
|
|
|
|
removeResource: function(resource)
|
|
{
|
|
var row = this.freeBusy.get(resource)[0];
|
|
row.purge();
|
|
row.remove();
|
|
this.resourceACCache.map.unset(resource);
|
|
$('kronolithEventResourceIds').value = this.resourceACCache.map.values();
|
|
},
|
|
|
|
addResourceCallback: function(resource, r)
|
|
{
|
|
this.fbLoading--;
|
|
if (!this.fbLoading) {
|
|
$('kronolithResourceFBLoading').hide();
|
|
}
|
|
if (Object.isUndefined(r.fb)) {
|
|
return;
|
|
}
|
|
this.resources.push(resource);
|
|
this.freeBusy.get(resource)[1] = r.fb;
|
|
this.insertFreeBusy(resource);
|
|
},
|
|
|
|
/**
|
|
* Removes an attendee row from the free/busy table.
|
|
*
|
|
* @param string attendee The display name of the attendee.
|
|
*/
|
|
removeAttendee: function(attendee)
|
|
{
|
|
var row = this.freeBusy.get(attendee)[0];
|
|
row.purge();
|
|
row.remove();
|
|
},
|
|
|
|
normalizeAttendee: function(attendee)
|
|
{
|
|
var pattern = /:(.*);/;
|
|
var match = pattern.exec(attendee);
|
|
if (match) {
|
|
return match[1].split(',');
|
|
}
|
|
return [attendee];
|
|
},
|
|
|
|
checkOrganizerAsAttendee: function()
|
|
{
|
|
if (HordeImple.AutoCompleter.kronolithEventAttendees.selectedItems.length == 1 &&
|
|
HordeImple.AutoCompleter.kronolithEventAttendees.selectedItems.first().rawValue != Kronolith.conf.email) {
|
|
// Invite the organizer of this event to the new event.
|
|
HordeImple.AutoCompleter.kronolithEventAttendees.addNewItemNode(Kronolith.conf.email);
|
|
this.addAttendee(Kronolith.conf.email);
|
|
}
|
|
},
|
|
|
|
getFBDate: function ()
|
|
{
|
|
var startDate = $('kronolithFBDate').innerHTML.split(' ');
|
|
if (startDate.length > 1) {
|
|
startDate = startDate[1];
|
|
} else {
|
|
startDate = startDate[0];
|
|
}
|
|
return Date.parseExact(startDate, Kronolith.conf.date_format);
|
|
},
|
|
|
|
/**
|
|
* Updates rows with free/busy information in the attendees table.
|
|
*
|
|
* @param string attendee An attendee display name as the free/busy
|
|
* identifier.
|
|
* @param date start An optinal start date for f/b info. If omitted,
|
|
* $('kronolithEventStartDate') is used.
|
|
*/
|
|
insertFreeBusy: function(attendee, start)
|
|
{
|
|
if (!$('kronolithEventDialog').visible() ||
|
|
!this.freeBusy.get(attendee)) {
|
|
return;
|
|
}
|
|
var fb = this.freeBusy.get(attendee)[1],
|
|
tr = this.freeBusy.get(attendee)[0],
|
|
td = tr.select('td')[1],
|
|
div = td.down('div'), start;
|
|
if (!fb) {
|
|
return;
|
|
}
|
|
|
|
if (!td.getWidth()) {
|
|
this.insertFreeBusy.bind(this, attendee, start).defer();
|
|
return;
|
|
}
|
|
|
|
if (div) {
|
|
div.purge();
|
|
div.remove();
|
|
}
|
|
if (!start) {
|
|
start = Date.parseExact($F('kronolithEventStartDate'), Kronolith.conf.date_format);
|
|
}
|
|
var end = start.clone().add(1).days(),
|
|
width = td.getWidth(),
|
|
fbs = this.parseDate(fb.s),
|
|
fbe = this.parseDate(fb.e);
|
|
|
|
|
|
if (start.isBefore(fbs) || end.isBefore(fbs) || start.isAfter(fbe)) {
|
|
return;
|
|
}
|
|
|
|
tr.select('td').each(function(td, i) {
|
|
if (i != 0) {
|
|
td.className = 'kronolithFBFree';
|
|
}
|
|
i++;
|
|
});
|
|
div = new Element('div').setStyle({ position: 'relative', height: td.offsetHeight + 'px' });
|
|
td.insert(div);
|
|
$H(fb.b).each(function(busy) {
|
|
var left, from = Date.parse(busy.key).addSeconds(1),
|
|
to = Date.parse(busy.value).addSeconds(1);
|
|
if (!end.isAfter(from) || to.isBefore(start)) {
|
|
return;
|
|
}
|
|
if (from.isBefore(start)) {
|
|
from = start.clone();
|
|
}
|
|
if (to.isAfter(end)) {
|
|
to = end.clone();
|
|
}
|
|
if (to.getHours() === 0 && to.getMinutes() === 0) {
|
|
to.add(-1).minutes();
|
|
}
|
|
left = from.getHours() + from.getMinutes() / 60;
|
|
div.insert(new Element('div', { className: 'kronolithFBBusy' }).setStyle({ zIndex: 1, top: 0, left: (left * width) + 'px', width: (((to.getHours() + to.getMinutes() / 60) - left) * width) + 'px' }));
|
|
});
|
|
|
|
},
|
|
|
|
fbStartDateOnChange: function()
|
|
{
|
|
if (!$F('kronolithEventStartDate')) {
|
|
this._checkDate($('kronolithEventStartDate'));
|
|
return;
|
|
}
|
|
this.fbStartDateHandler(Date.parseExact($F('kronolithEventStartDate'), Kronolith.conf.date_format));
|
|
},
|
|
|
|
/**
|
|
* @param Date start The start date.
|
|
*/
|
|
fbStartDateHandler: function(start)
|
|
{
|
|
this.updateFBDate(start);
|
|
this.resetFBRows();
|
|
// Need to check visisbility - multiple changes will break the display
|
|
// due to the use of .defer() in insertFreeBusy().
|
|
if ($('kronolithEventTabAttendees').visible()) {
|
|
this.attendeeStartDateHandler(start);
|
|
}
|
|
if ($('kronolithEventTabResources').visible()) {
|
|
this.resourceStartDateHandler(start);
|
|
}
|
|
},
|
|
|
|
attendeeStartDateHandler: function(start)
|
|
{
|
|
this.attendees.each(function(attendee) {
|
|
this.insertFreeBusy(attendee.l, start);
|
|
}, this);
|
|
},
|
|
|
|
resourceStartDateHandler: function(start)
|
|
{
|
|
this.resources.each(function(resource) {
|
|
this.insertFreeBusy(resource, start);
|
|
}, this);
|
|
},
|
|
|
|
nextFreebusy: function()
|
|
{
|
|
this.fbStartDateHandler(this.getFBDate().addDays(1));
|
|
},
|
|
|
|
prevFreebusy: function()
|
|
{
|
|
this.fbStartDateHandler(this.getFBDate().addDays(-1));
|
|
},
|
|
|
|
/**
|
|
* @start Date object
|
|
*/
|
|
updateFBDate: function(start)
|
|
{
|
|
$('kronolithFBDate').update(start.toString('dddd') + ' ' + start.toString(Kronolith.conf.date_format));
|
|
$('kronolithResourceFBDate').update(start.toString('dddd') + ' ' + start.toString(Kronolith.conf.date_format));
|
|
},
|
|
|
|
/**
|
|
* Toggles the start and end time fields of the event edit form on and off.
|
|
*
|
|
* @param boolean on Whether the event is an all-day event, i.e. the time
|
|
* fields should be turned off. If not specified, the
|
|
* current state is toggled.
|
|
*/
|
|
toggleAllDay: function(on)
|
|
{
|
|
var end = this.getDate('end'),
|
|
old = $('kronolithEventStartTimeLabel').getStyle('visibility') == 'hidden';
|
|
if (Object.isUndefined(on)) {
|
|
on = $('kronolithEventStartTimeLabel').getStyle('visibility') == 'visible';
|
|
}
|
|
if (end) {
|
|
if (on) {
|
|
if (end.getHours() == 0 && end.getMinutes() == 0) {
|
|
end.add(-1).minute();
|
|
}
|
|
} else if (old) {
|
|
end.setHours(23);
|
|
end.setMinutes(59);
|
|
}
|
|
$('kronolithEventEndDate').setValue(end.toString(Kronolith.conf.date_format));
|
|
$('kronolithEventEndTime').setValue(end.toString(Kronolith.conf.time_format));
|
|
}
|
|
$('kronolithEventStartTimeLabel').setStyle({ visibility: on ? 'hidden' : 'visible' });
|
|
$('kronolithEventEndTimeLabel').setStyle({ visibility: on ? 'hidden' : 'visible' });
|
|
},
|
|
|
|
/**
|
|
* Enables the alarm in the event or task form and sets the correct value
|
|
* and unit.
|
|
*
|
|
* @param string type The object type, either 'Event' or 'Task'.
|
|
* @param integer alarm The alarm time in seconds.
|
|
*/
|
|
enableAlarm: function(type, alarm) {
|
|
if (!alarm) {
|
|
return;
|
|
}
|
|
type = 'kronolith' + type + 'Alarm';
|
|
$(type + 'On').setValue(true);
|
|
[10080, 1440, 60, 1].each(function(unit) {
|
|
if (alarm % unit === 0) {
|
|
$(type + 'Value').setValue(alarm / unit);
|
|
$(type + 'Unit').setValue(unit);
|
|
throw $break;
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Disables all custom alarm methods in the event form.
|
|
*/
|
|
disableAlarmMethods: function(type) {
|
|
$('kronolith' + type + 'TabReminder').select('input').each(function(input) {
|
|
if (input.name == (type == 'Event' ? 'event_alarms[]' : 'task[alarm_methods][]')) {
|
|
input.setValue(0);
|
|
if ($(input.id + 'Params')) {
|
|
$(input.id + 'Params').hide();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Toggles the recurrence fields of the event and task edit forms.
|
|
*
|
|
* @param boolean event Whether to use the event form.
|
|
* @param string recur The recurrence part of the field name, i.e. 'None',
|
|
* 'Daily', etc.
|
|
*/
|
|
toggleRecurrence: function(event, recur)
|
|
{
|
|
var prefix = 'kronolith' + (event ? 'Event' : 'Task');
|
|
if (recur == 'Exception') {
|
|
if (!$(prefix + 'RepeatException').visible()) {
|
|
$(prefix + 'TabRecur').select('div').invoke('hide');
|
|
$(prefix + 'RepeatException').show();
|
|
}
|
|
} else if (recur != 'None') {
|
|
var div = $(prefix + 'Repeat' + recur),
|
|
length = $(prefix + 'RepeatLength');
|
|
this.lastRecurType = recur;
|
|
if (!div.visible()) {
|
|
$(prefix + 'TabRecur').select('div').invoke('hide');
|
|
div.show();
|
|
length.show();
|
|
$(prefix + 'RepeatType').show();
|
|
}
|
|
switch (recur) {
|
|
case 'Daily':
|
|
case 'Weekly':
|
|
case 'Monthly':
|
|
case 'Yearly':
|
|
var recurLower = recur.toLowerCase();
|
|
if (div.down('input[name=recur_' + recurLower + '][value=1]').checked) {
|
|
div.down('input[name=recur_' + recurLower + '_interval]').disable();
|
|
} else {
|
|
div.down('input[name=recur_' + recurLower + '_interval]').enable();
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (length.down('input[name=recur_end_type][value=date]').checked) {
|
|
$(prefix + 'RecurDate').enable();
|
|
$(prefix + 'RecurPicker').setStyle({ visibility: 'visible' });
|
|
} else {
|
|
$(prefix + 'RecurDate').disable();
|
|
$(prefix + 'RecurPicker').setStyle({ visibility: 'hidden' });
|
|
}
|
|
if (length.down('input[name=recur_end_type][value=count]').checked) {
|
|
$(prefix + 'RecurCount').enable();
|
|
} else {
|
|
$(prefix + 'RecurCount').disable();
|
|
}
|
|
} else {
|
|
$(prefix + 'TabRecur').select('div').invoke('hide');
|
|
$(prefix + 'RepeatType').show();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Fills the recurrence fields of the event and task edit forms.
|
|
*
|
|
* @param boolean event Whether to use the event form.
|
|
* @param object recur The recurrence object from the ajax response.
|
|
*/
|
|
setRecurrenceFields: function(event, recur)
|
|
{
|
|
var scheme = Kronolith.conf.recur[recur.t],
|
|
schemeLower = scheme.toLowerCase(),
|
|
prefix = 'kronolith' + (event ? 'Event' : 'Task'),
|
|
div = $(prefix + 'Repeat' + scheme);
|
|
$(prefix + 'Link' + scheme).setValue(true);
|
|
if (scheme == 'Monthly' || scheme == 'Yearly') {
|
|
div.down('input[name=recur_' + schemeLower + '_scheme][value=' + recur.t + ']').setValue(true);
|
|
}
|
|
if (scheme == 'Weekly') {
|
|
div.select('input[type=checkbox]').each(function(input) {
|
|
if (input.name == 'weekly[]' &&
|
|
input.value & recur.d) {
|
|
input.setValue(true);
|
|
}
|
|
});
|
|
}
|
|
if (recur.i == 1) {
|
|
div.down('input[name=recur_' + schemeLower + '][value=1]').setValue(true);
|
|
} else {
|
|
div.down('input[name=recur_' + schemeLower + '][value=0]').setValue(true);
|
|
div.down('input[name=recur_' + schemeLower + '_interval]').setValue(recur.i);
|
|
}
|
|
if (!Object.isUndefined(recur.e)) {
|
|
$(prefix + 'RepeatLength').down('input[name=recur_end_type][value=date]').setValue(true);
|
|
$(prefix + 'RecurDate').setValue(Date.parse(recur.e).toString(Kronolith.conf.date_format));
|
|
} else if (!Object.isUndefined(recur.c)) {
|
|
$(prefix + 'RepeatLength').down('input[name=recur_end_type][value=count]').setValue(true);
|
|
$(prefix + 'RecurCount').setValue(recur.c);
|
|
} else {
|
|
$(prefix + 'RepeatLength').down('input[name=recur_end_type][value=none]').setValue(true);
|
|
}
|
|
$(prefix + 'Exceptions').setValue(recur.ex || '');
|
|
if ($(prefix + 'Completions')) {
|
|
$(prefix + 'Completions').setValue(recur.co || '');
|
|
}
|
|
this.toggleRecurrence(event, scheme);
|
|
},
|
|
|
|
/**
|
|
* Returns the Date object representing the date and time specified in the
|
|
* event form's start or end fields.
|
|
*
|
|
* @param string what Which fields to parse, either 'start' or 'end'.
|
|
*
|
|
* @return Date The date object or null if the fields can't be parsed.
|
|
*/
|
|
getDate: function(what) {
|
|
var dateElm, timeElm, date, time;
|
|
if (what == 'start') {
|
|
dateElm = 'kronolithEventStartDate';
|
|
timeElm = 'kronolithEventStartTime';
|
|
} else {
|
|
dateElm = 'kronolithEventEndDate';
|
|
timeElm = 'kronolithEventEndTime';
|
|
}
|
|
date = Date.parseExact($F(dateElm), Kronolith.conf.date_format)
|
|
|| Date.parse($F(dateElm));
|
|
if (date) {
|
|
time = Date.parseExact($F(timeElm), Kronolith.conf.time_format);
|
|
if (!time) {
|
|
time = Date.parse($F(timeElm));
|
|
}
|
|
if (time) {
|
|
date.setHours(time.getHours());
|
|
date.setMinutes(time.getMinutes());
|
|
}
|
|
}
|
|
return date;
|
|
},
|
|
|
|
checkDate: function(e) {
|
|
this._checkDate(e.element());
|
|
},
|
|
|
|
_checkDate: function(elm)
|
|
{
|
|
if ($F(elm)) {
|
|
var date = Date.parseExact($F(elm), Kronolith.conf.date_format) || Date.parse($F(elm));
|
|
if (date) {
|
|
elm.setValue(date.toString(Kronolith.conf.date_format));
|
|
this.wrongFormat.unset(elm.id);
|
|
} else {
|
|
HordeCore.notify(Kronolith.text.wrong_date_format.interpolate({ wrong: $F(elm), right: new Date().toString(Kronolith.conf.date_format) }), 'horde.warning');
|
|
this.wrongFormat.set(elm.id, true);
|
|
}
|
|
} else {
|
|
HordeCore.notify(Kronolith.text.wrong_date_format.interpolate({ wrong: $F(elm), right: new Date().toString(Kronolith.conf.date_format) }), 'horde.warning');
|
|
this.wrongFormat.set(elm.id, true);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Attaches a KeyNavList drop down to one of the time fields.
|
|
*
|
|
* @param string|Element field A time field (id).
|
|
*
|
|
* @return KeyNavList The drop down list object.
|
|
*/
|
|
attachTimeDropDown: function(field)
|
|
{
|
|
var list = [], d = new Date(), time, opts;
|
|
|
|
d.setHours(0);
|
|
d.setMinutes(0);
|
|
do {
|
|
time = d.toString(Kronolith.conf.time_format);
|
|
list.push({ l: time, v: time });
|
|
d.add(30).minutes();
|
|
} while (d.getHours() !== 0 || d.getMinutes() !== 0);
|
|
|
|
field = $(field);
|
|
opts = {
|
|
list: list,
|
|
domParent: field.up('.kronolithDialog'),
|
|
onChoose: function(value) {
|
|
if (value) {
|
|
field.setValue(value);
|
|
}
|
|
this.updateTimeFields(field.identify());
|
|
}.bind(this)
|
|
};
|
|
|
|
this.knl[field.id] = new KeyNavList(field, opts);
|
|
|
|
return this.knl[field.id];
|
|
},
|
|
|
|
checkTime: function(e) {
|
|
var elm = e.element();
|
|
if ($F(elm)) {
|
|
var time = Date.parseExact(new Date().toString(Kronolith.conf.date_format) + ' ' + $F(elm), Kronolith.conf.date_format + ' ' + Kronolith.conf.time_format) || Date.parse(new Date().toString('yyyy-MM-dd ') + $F(elm));
|
|
if (time) {
|
|
elm.setValue(time.toString(Kronolith.conf.time_format));
|
|
this.wrongFormat.unset(elm.id);
|
|
} else {
|
|
HordeCore.notify(Kronolith.text.wrong_time_format.interpolate({ wrong: $F(elm), right: new Date().toString(Kronolith.conf.time_format) }), 'horde.warning');
|
|
this.wrongFormat.set(elm.id, true);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Updates the start time in the event form after changing the end time.
|
|
*/
|
|
updateStartTime: function(date) {
|
|
var start = this.getDate('start'), end = this.getDate('end');
|
|
if (!start) {
|
|
return;
|
|
}
|
|
if (!date) {
|
|
date = end;
|
|
}
|
|
if (!date) {
|
|
return;
|
|
}
|
|
if (start.isAfter(end)) {
|
|
$('kronolithEventStartDate').setValue(date.toString(Kronolith.conf.date_format));
|
|
$('kronolithEventStartTime').setValue($F('kronolithEventEndTime'));
|
|
}
|
|
this.duration = Math.abs(date.getTime() - start.getTime()) / 60000;
|
|
},
|
|
|
|
/**
|
|
* Updates the end time in the event form after changing the start time.
|
|
*/
|
|
updateEndTime: function() {
|
|
var date = this.getDate('start');
|
|
if (!date) {
|
|
return;
|
|
}
|
|
date.add(this.duration).minutes();
|
|
$('kronolithEventEndDate').setValue(date.toString(Kronolith.conf.date_format));
|
|
$('kronolithEventEndTime').setValue(date.toString(Kronolith.conf.time_format));
|
|
},
|
|
|
|
/**
|
|
* Event handler for scrolling the mouse over the date field.
|
|
*
|
|
* @param Event e The mouse event.
|
|
* @param string field The field name.
|
|
*/
|
|
scrollDateField: function(e, field) {
|
|
var date = Date.parseExact($F(field), Kronolith.conf.date_format);
|
|
if (!date || (!e.wheelData && !e.detail)) {
|
|
return;
|
|
}
|
|
date.add(e.wheelData > 0 || e.detail < 0 ? 1 : -1).days();
|
|
$(field).setValue(date.toString(Kronolith.conf.date_format));
|
|
switch (field) {
|
|
case 'kronolithEventStartDate':
|
|
this.updateEndTime();
|
|
break;
|
|
case 'kronolithEventEndDate':
|
|
this.updateStartTime(date);
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Event handler for scrolling the mouse over the time field.
|
|
*
|
|
* @param Event e The mouse event.
|
|
* @param string field The field name.
|
|
*/
|
|
scrollTimeField: function(e, field) {
|
|
var time = Date.parseExact($F(field), Kronolith.conf.time_format) || Date.parse($F(field)),
|
|
newTime, minute;
|
|
if (!time || (!e.wheelData && !e.detail)) {
|
|
return;
|
|
}
|
|
|
|
newTime = time.clone();
|
|
newTime.add(e.wheelData > 0 || e.detail < 0 ? 10 : -10).minutes();
|
|
minute = newTime.getMinutes();
|
|
if (minute % 10) {
|
|
if (e.wheelData > 0 || e.detail < 0) {
|
|
minute = minute / 10 | 0;
|
|
} else {
|
|
minute = (minute - 10) / 10 | 0;
|
|
}
|
|
minute *= 10;
|
|
newTime.setMinutes(minute);
|
|
}
|
|
if (newTime.getDate() != time.getDate()) {
|
|
if (newTime.isAfter(time)) {
|
|
newTime = time.clone().set({ hour: 23, minute: 59 });
|
|
} else {
|
|
newTime = time.clone().set({ hour: 0, minute: 0 });
|
|
}
|
|
}
|
|
|
|
$(field).setValue(newTime.toString(Kronolith.conf.time_format));
|
|
this.updateTimeFields(field);
|
|
|
|
/* Mozilla bug https://bugzilla.mozilla.org/show_bug.cgi?id=502818
|
|
* Need to stop or else multiple scroll events may be fired. We
|
|
* lose the ability to have the mousescroll bubble up, but that is
|
|
* more desirable than having the wrong scrolling behavior. */
|
|
if (Prototype.Browser.Gecko && !e.stop) {
|
|
Event.stop(e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Updates the time fields of the event dialog after either has been
|
|
* changed.
|
|
*
|
|
* @param string field The id of the field that has been changed.
|
|
*/
|
|
updateTimeFields: function(field)
|
|
{
|
|
switch (field) {
|
|
case 'kronolithEventStartDate':
|
|
this.fbStartDateHandler(Date.parseExact($F(field), Kronolith.conf.date_format));
|
|
case 'kronolithEventStartTime':
|
|
this.updateEndTime();
|
|
break;
|
|
case 'kronolithEventEndDate':
|
|
case 'kronolithEventEndTime':
|
|
this.updateStartTime();
|
|
this.fbStartDateHandler(Date.parseExact($F('kronolithEventStartDate'), Kronolith.conf.date_format));
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Closes a RedBox overlay, after saving its content to the body.
|
|
*/
|
|
closeRedBox: function()
|
|
{
|
|
if (!RedBox.getWindow()) {
|
|
return;
|
|
}
|
|
var content = RedBox.getWindowContents();
|
|
if (content) {
|
|
document.body.insert(content.hide());
|
|
}
|
|
RedBox.close();
|
|
},
|
|
|
|
// By default, no context onShow action
|
|
contextOnShow: Prototype.emptyFunction,
|
|
|
|
// By default, no context onClick action
|
|
contextOnClick: Prototype.emptyFunction,
|
|
|
|
// Map
|
|
initializeMap: function(ignoreLL)
|
|
{
|
|
if (this.mapInitialized) {
|
|
return;
|
|
}
|
|
var layers = [];
|
|
if (Kronolith.conf.maps.providers) {
|
|
Kronolith.conf.maps.providers.each(function(l) {
|
|
var p = new HordeMap[l]();
|
|
$H(p.getLayers()).values().each(function(e) {layers.push(e);});
|
|
});
|
|
}
|
|
|
|
this.map = new HordeMap.Map[Kronolith.conf.maps.driver]({
|
|
elt: 'kronolithEventMap',
|
|
delayed: true,
|
|
layers: layers,
|
|
markerDragEnd: this.onMarkerDragEnd.bind(this),
|
|
mapClick: this.afterClickMap.bind(this)
|
|
});
|
|
|
|
if ($('kronolithEventLocationLat').value && !ignoreLL) {
|
|
var ll = { lat:$('kronolithEventLocationLat').value, lon: $('kronolithEventLocationLon').value };
|
|
// Note that we need to cast the value of zoom to an integer here,
|
|
// otherwise the map display breaks.
|
|
this.placeMapMarker(ll, true, $('kronolithEventMapZoom').value - 0);
|
|
}
|
|
//@TODO: check for Location field - and if present, but no lat/lon value, attempt to
|
|
// geocode it.
|
|
this.map.display();
|
|
this.mapInitialized = true;
|
|
},
|
|
|
|
resetMap: function()
|
|
{
|
|
this.mapInitialized = false;
|
|
$('kronolithEventLocationLat').value = null;
|
|
$('kronolithEventLocationLon').value = null;
|
|
$('kronolithEventMapZoom').value = null;
|
|
if (this.mapMarker) {
|
|
this.map.removeMarker(this.mapMarker, {});
|
|
this.mapMarker = null;
|
|
}
|
|
if (this.map) {
|
|
this.map.destroy();
|
|
this.map = null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Callback for handling marker drag end.
|
|
*
|
|
* @param object r An object that implenents a getLonLat() method to obtain
|
|
* the new location of the marker.
|
|
*/
|
|
onMarkerDragEnd: function(r)
|
|
{
|
|
var ll = r.getLonLat();
|
|
$('kronolithEventLocationLon').value = ll.lon;
|
|
$('kronolithEventLocationLat').value = ll.lat;
|
|
var gc = new HordeMap.Geocoder[Kronolith.conf.maps.geocoder](this.map.map, 'kronolithEventMap');
|
|
gc.reverseGeocode(ll, this.onReverseGeocode.bind(this), this.onGeocodeError.bind(this) );
|
|
},
|
|
|
|
/**
|
|
* Callback for handling a reverse geocode request.
|
|
*
|
|
* @param array r An array of objects containing the results. Each object in
|
|
* the array is {lat:, lon:, address}
|
|
*/
|
|
onReverseGeocode: function(r)
|
|
{
|
|
if (!r.length) {
|
|
return;
|
|
}
|
|
$('kronolithEventLocation').value = r[0].address;
|
|
},
|
|
|
|
onGeocodeError: function(r)
|
|
{
|
|
$('kronolithEventGeo_loading_img').toggle();
|
|
HordeCore.notify(Kronolith.text.geocode_error + ' ' + r, 'horde.error');
|
|
},
|
|
|
|
/**
|
|
* Callback for geocoding calls.
|
|
*/
|
|
onGeocode: function(r)
|
|
{
|
|
$('kronolithEventGeo_loading_img').toggle();
|
|
r = r.shift();
|
|
if (r.precision) {
|
|
zoom = r.precision * 2;
|
|
} else {
|
|
zoom = null;
|
|
}
|
|
this.ensureMap(true);
|
|
this.placeMapMarker({ lat: r.lat, lon: r.lon }, true, zoom);
|
|
},
|
|
|
|
geocode: function(a) {
|
|
if (!a) {
|
|
return;
|
|
}
|
|
$('kronolithEventGeo_loading_img').toggle();
|
|
var gc = new HordeMap.Geocoder[Kronolith.conf.maps.geocoder](this.map.map, 'kronolithEventMap');
|
|
gc.geocode(a, this.onGeocode.bind(this), this.onGeocodeError);
|
|
},
|
|
|
|
/**
|
|
* Place the event marker on the map, at point ll, ensuring it exists.
|
|
* Optionally center the map on the marker and zoom. Zoom only honored if
|
|
* center is set, and if center is set, but zoom is null, we zoomToFit().
|
|
*
|
|
*/
|
|
placeMapMarker: function(ll, center, zoom)
|
|
{
|
|
if (!this.mapMarker) {
|
|
this.mapMarker = this.map.addMarker(
|
|
ll,
|
|
{ draggable: true },
|
|
{
|
|
context: this,
|
|
dragend: this.onMarkerDragEnd
|
|
});
|
|
} else {
|
|
this.map.moveMarker(this.mapMarker, ll);
|
|
}
|
|
|
|
if (center) {
|
|
this.map.setCenter(ll, zoom);
|
|
if (!zoom) {
|
|
this.map.zoomToFit();
|
|
}
|
|
}
|
|
$('kronolithEventLocationLon').value = ll.lon;
|
|
$('kronolithEventLocationLat').value = ll.lat;
|
|
},
|
|
|
|
/**
|
|
* Remove the event marker from the map. Called after clearing the location
|
|
* field.
|
|
*/
|
|
removeMapMarker: function()
|
|
{
|
|
if (this.mapMarker) {
|
|
this.map.removeMarker(this.mapMarker, {});
|
|
$('kronolithEventLocationLon').value = null;
|
|
$('kronolithEventLocationLat').value = null;
|
|
}
|
|
|
|
this.mapMarker = false;
|
|
},
|
|
|
|
/**
|
|
* Ensures the map tab is visible and sets UI elements accordingly.
|
|
*/
|
|
ensureMap: function(ignoreLL)
|
|
{
|
|
if (!this.mapInitialized) {
|
|
this.initializeMap(ignoreLL);
|
|
}
|
|
var dialog = $('kronolithEventForm');
|
|
dialog.select('.kronolithTabsOption').invoke('hide');
|
|
dialog.select('.tabset li').invoke('removeClassName', 'horde-active');
|
|
$('kronolithEventTabMap').show();
|
|
$('kronolithEventLinkMap').up().addClassName('horde-active');
|
|
},
|
|
|
|
/**
|
|
* Callback that gets called after a new marker has been placed on the map
|
|
* due to a single click on the map.
|
|
*
|
|
* @return object o { lonlat: }
|
|
*/
|
|
afterClickMap: function(o)
|
|
{
|
|
this.placeMapMarker(o.lonlat, false);
|
|
var gc = new HordeMap.Geocoder[Kronolith.conf.maps.geocoder](this.map.map, 'kronolithEventMap');
|
|
gc.reverseGeocode(o.lonlat, this.onReverseGeocode.bind(this), this.onGeocodeError.bind(this) );
|
|
},
|
|
|
|
/* Onload function. */
|
|
onDomLoad: function()
|
|
{
|
|
var dateFields, timeFields;
|
|
|
|
/* Initialize the starting page. */
|
|
var tmp = location.hash;
|
|
if (!tmp.empty() && tmp.startsWith('#')) {
|
|
tmp = (tmp.length == 1) ? '' : tmp.substring(1);
|
|
}
|
|
if (tmp.empty()) {
|
|
this.updateView(this.date, Kronolith.conf.login_view);
|
|
$('kronolithView' + Kronolith.conf.login_view.capitalize()).show();
|
|
}
|
|
HordeCore.doAction('listCalendars', {}, { callback: this.initialize.bind(this, tmp) });
|
|
|
|
RedBox.onDisplay = function() {
|
|
this.redBoxLoading = false;
|
|
}.bind(this);
|
|
RedBox.duration = this.effectDur;
|
|
|
|
$('kronolithEventStartDate', 'kronolithEventEndDate', 'kronolithTaskDueDate').compact().invoke('observe', 'blur', this.checkDate.bind(this));
|
|
var timeFields = $('kronolithEventStartTime', 'kronolithEventEndTime', 'kronolithTaskDueTime').compact();
|
|
timeFields.invoke('observe', 'blur', this.checkTime.bind(this));
|
|
timeFields.each(function(field) {
|
|
var dropDown = this.attachTimeDropDown(field);
|
|
field.observe('click', function() { dropDown.show(); });
|
|
}, this);
|
|
$('kronolithEventStartDate', 'kronolithEventStartTime').invoke('observe', 'change', this.updateEndTime.bind(this));
|
|
$('kronolithEventEndDate', 'kronolithEventEndTime').invoke('observe', 'change', function() { this.updateStartTime(); }.bind(this));
|
|
|
|
if (Kronolith.conf.has_tasks) {
|
|
$('kronolithTaskDueDate', 'kronolithTaskDueTime').compact().invoke('observe', 'focus', this.setDefaultDue.bind(this));
|
|
$('kronolithTaskList').observe('change', function() {
|
|
this.updateTaskParentDropDown($F('kronolithTaskList'));
|
|
this.updateTaskAssigneeDropDown($F('kronolithTaskList'));
|
|
}.bind(this));
|
|
}
|
|
|
|
document.observe('keydown', KronolithCore.keydownHandler.bindAsEventListener(KronolithCore));
|
|
document.observe('keyup', KronolithCore.keyupHandler.bindAsEventListener(KronolithCore));
|
|
document.observe('click', KronolithCore.clickHandler.bindAsEventListener(KronolithCore));
|
|
document.observe('dblclick', KronolithCore.clickHandler.bindAsEventListener(KronolithCore, true));
|
|
|
|
// Mouse wheel handler.
|
|
dateFields = [ 'kronolithEventStartDate', 'kronolithEventEndDate' ];
|
|
timeFields = [ 'kronolithEventStartTime', 'kronolithEventEndTime' ];
|
|
if (Kronolith.conf.has_tasks) {
|
|
dateFields.push('kronolithTaskDueDate');
|
|
timeFields.push('kronolithTaskDueTime');
|
|
}
|
|
dateFields.each(function(field) {
|
|
$(field).observe(Prototype.Browser.Gecko ? 'DOMMouseScroll' : 'mousewheel', this.scrollDateField.bindAsEventListener(this, field));
|
|
}, this);
|
|
timeFields.each(function(field) {
|
|
$(field).observe(Prototype.Browser.Gecko ? 'DOMMouseScroll' : 'mousewheel', this.scrollTimeField.bindAsEventListener(this, field));
|
|
}, this);
|
|
|
|
$('kronolithEventStartDate').observe('change', this.fbStartDateOnChange.bind(this));
|
|
$('kronolithFBDatePrev').observe('click', this.prevFreebusy.bind(this));
|
|
$('kronolithFBDateNext').observe('click', this.nextFreebusy.bind(this));
|
|
$('kronolithResourceFBDatePrev').observe('click', this.prevFreebusy.bind(this));
|
|
$('kronolithResourceFBDateNext').observe('click', this.nextFreebusy.bind(this));
|
|
|
|
this.updateMinical(this.date);
|
|
},
|
|
|
|
initialize: function(location, r)
|
|
{
|
|
Kronolith.conf.calendars = r.calendars;
|
|
this.updateCalendarList();
|
|
HordeSidebar.refreshEvents();
|
|
$('kronolithLoadingCalendars').hide();
|
|
$('kronolithMenuCalendars').show();
|
|
this.initialized = true;
|
|
|
|
/* Initialize the starting page. */
|
|
if (!location.empty()) {
|
|
this.go(decodeURIComponent(location));
|
|
} else {
|
|
this.go(Kronolith.conf.login_view);
|
|
}
|
|
|
|
/* Start polling. */
|
|
new PeriodicalExecuter(function()
|
|
{
|
|
HordeCore.doAction('poll');
|
|
$(kronolithGotoToday).update(Date.today().toString(Kronolith.conf.date_format));
|
|
},
|
|
60
|
|
);
|
|
}
|
|
|
|
};
|
|
|
|
/* Initialize global event handlers. */
|
|
document.observe('dom:loaded', KronolithCore.onDomLoad.bind(KronolithCore));
|
|
document.observe('DragDrop2:drag', KronolithCore.onDrag.bindAsEventListener(KronolithCore));
|
|
document.observe('DragDrop2:drop', KronolithCore.onDrop.bindAsEventListener(KronolithCore));
|
|
document.observe('DragDrop2:end', KronolithCore.onDragEnd.bindAsEventListener(KronolithCore));
|
|
document.observe('DragDrop2:start', KronolithCore.onDragStart.bindAsEventListener(KronolithCore));
|
|
document.observe('Horde_Calendar:select', KronolithCore.datePickerHandler.bindAsEventListener(KronolithCore));
|
|
document.observe('FormGhost:reset', KronolithCore.searchReset.bindAsEventListener(KronolithCore));
|
|
document.observe('FormGhost:submit', KronolithCore.searchSubmit.bindAsEventListener(KronolithCore));
|
|
document.observe('HordeCore:showNotifications', KronolithCore.showNotification.bindAsEventListener(KronolithCore));
|
|
if (Prototype.Browser.IE) {
|
|
$('kronolithBody').observe('selectstart', Event.stop);
|
|
}
|
|
|
|
/* Extend AJAX exception handling. */
|
|
HordeCore.onException = HordeCore.onException.wrap(KronolithCore.onException.bind(KronolithCore));
|