/* * Copyright 2011-2012 Jorge López Pérez * * This file is part of AgenDAV. * * AgenDAV is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * AgenDAV is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with AgenDAV. If not, see . */ // Useful names var ved = 'div.view_event_details'; var ced = '#com_event_dialog'; var dustbase = {}; $(document).ready(function() { // Load i18n strings // TODO: language load_i18n_strings(); // Dust.js i18n helper dust.helpers.i18n = function i18n(chunk, context, bodies, params) { var i18n_params = {}; var i18n_name = params.name; var i18n_type = params.type; delete params.name; delete params.type; for (var key in params) { if (params.hasOwnProperty(key)) { var param_name = '%' + key; i18n_params[param_name] = dust.helpers.tap(params[key], chunk, context); } } return chunk.write(t(i18n_type, i18n_name, i18n_params)); }; // Login page: focus first input field if ($('body').hasClass('loginpage')) { $('input:submit').button(); $('input[name="user"]').focus(); } else if ($('body').hasClass('prefspage')) { $('#prefs_tabs').tabs(); $('#prefs_buttons button').button(); $('#return_button').on('click', function() { window.location = base_app_url; return false; }); $('#save_button').on('click', function() { var thisform = $('#prefs_form'); proceed_send_ajax_form(thisform, function(data) { show_success( t('messages', 'info_prefssaved'), ''); }, function(data) { show_error(t('messages', 'error_invalidinput'), data); }, function(data) { }); }); } else if ($('body').hasClass('calendarpage')) { // Dust.js base context dustbase = dust.makeBase({ default_calendar_color: default_calendar_color, base_url: base_url, base_app_url: base_app_url, csrf_token_name: AgenDAVConf.prefs_csrf_token_name, enable_calendar_sharing: enable_calendar_sharing }); // Default colorpicker options set_default_colorpicker_options(); // Enable full calendar // TODO: configurable! $('#calendar_view').fullCalendar({ selectable: true, editable: true, firstDay: AgenDAVConf.prefs_firstday, timeFormat: { agenda: AgenDAVConf.prefs_timeformat + '{ - ' + AgenDAVConf.prefs_timeformat + '}', '': AgenDAVConf.prefs_timeformat }, columnFormat: { month: AgenDAVConf.prefs_format_column_month, week: AgenDAVConf.prefs_format_column_week, day: AgenDAVConf.prefs_format_column_day, table: AgenDAVConf.prefs_format_column_table }, titleFormat: { month: AgenDAVConf.prefs_format_title_month, week: AgenDAVConf.prefs_format_title_week, day: AgenDAVConf.prefs_format_title_day, table: AgenDAVConf.prefs_format_title_table }, currentTimeIndicator: true, weekMode: 'liquid', height: calendar_height(), windowResize: function(view) { $(this).fullCalendar('option', 'height', calendar_height()); }, header: { left: 'month,agendaWeek,agendaDay table', center: 'title', right: 'today prev,next' }, listTexts: { until: t('labels', 'repeatuntil'), past: t('labels', 'pastevents'), today: t('labels', 'today'), tomorrow: t('labels', 'tomorrow'), thisWeek: t('labels', 'thisweek'), nextWeek: t('labels', 'nextweek'), thisMonth: t('labels', 'thismonth'), nextMonth: t('labels', 'nextmonth'), future: t('labels', 'future'), week: 'W' }, // list/table options listSections: 'smart', listRange: 30, listPage: 7, monthNames: month_names_long(), monthNamesShort: month_names_short(), dayNames: day_names_long(), dayNamesShort: day_names_short(), buttonText: { today: t('labels', 'today'), month: t('labels', 'month'), week: t('labels', 'week'), day: t('labels', 'day'), table: t('labels', 'tableview') }, theme: true, // use jQuery UI themeing allDayText: t('labels', 'allday'), axisFormat: AgenDAVConf.prefs_timeformat, slotMinutes: 30, firstHour: 8, allDayDefault: false, loading: function(bool) { loading(bool); }, eventRender: event_render_callback, eventClick: event_click_callback, // Add new event by dragging. Click also triggers this event, // if you define dayClick and select there is some kind of // collision between them. select: slots_drag_callback, // Useful for creating events in agenda view selectHelper: select_helper, eventResize: event_resize_callback, eventDrop: event_drop_callback }); // Refresh link $('' +' ' +t('labels', 'refresh') + '') .appendTo('#calendar_view td.fc-header-right') .button() .on('click', function() { update_calendar_list(true); }) .before(''); // Date picker above calendar dust.render('datepicker_button', dustbase, function(err, out) { if (err != null) { show_error(t('messages', 'error_interfacefailure'), err.message); } else { $('#calendar_view span.fc-button-next') .after(out); $('#datepicker_fullcalendar') .datepicker({ changeYear: true, closeText: t('labels', 'cancel'), onSelect: function(date, text) { var d = $('#datepicker_fullcalendar').datepicker('getDate'); $('#calendar_view').fullCalendar('gotoDate', d); } }) .prev() .button() .on('click', function() { $('#datepicker_fullcalendar').datepicker('setDate', $('#calendar_view').fullCalendar('getDate')); $('#datepicker_fullcalendar').datepicker('show'); }); } }); $('#calendar_view').fullCalendar('renderEvent', { title: 'Little portal', start: '1985-02-15T00:00:00Z', end: '1985-02-15T23:59:59Z', allDay: true, editable: false, color: '#E78AEF' }, true); /************************************************************* * Calendar list events *************************************************************/ // Editing a calendar $('div.calendar_list').on('click', 'i.cfg', function(e) { e.stopPropagation(); var calentry = $(this).parent(); calendar_modify_dialog($(calentry[0]).data()); }) .on('click', 'li.available_calendar', function(e) { // Make calendar transparent toggle_calendar($(this)); }); // First time load: create calendar list update_calendar_list(true); $('#sidebar').on('click', '#toggle_all_shared_calendars', function(e) { var shared_cals = $('#shared_calendar_list').find('ul').children(); if ($(this).hasClass('hide_all')) { $.map(shared_cals, function(e, i) { hide_calendar($(e)); }); $(this) .removeClass('hide_all') .addClass('show_all') .find('i') .removeClass('icon-eye-close') .addClass('icon-eye-open'); } else { $.map(shared_cals, function(e, i) { show_calendar($(e)); }); $(this) .removeClass('show_all') .addClass('hide_all') .find('i') .removeClass('icon-eye-open') .addClass('icon-eye-close'); } }); // Help tooltips $('#sidebar div.buttons').find('img[title],span[title],a[title]').qtip({ position: { my: 'top left', at: 'bottom left' }, show: { delay: 600 }, style: { classes: 'ui-tooltip-bootstrap', tip: true } }); // Create calendar $('#calendar_add') .on('click', calendar_create_dialog); /************************************************************* * End of calendar list events *************************************************************/ /************************************************************* * Shortcuts *************************************************************/ $('#shortcut_add_event') .button({ icons: { primary: 'ui-icon-plusthick' } }) .on('click', function() { var start = fulldatetimestring($('#calendar_view').fullCalendar('getDate')); var data = { start: start, allday: false, view: 'month' }; // Unselect every single day/slot $('#calendar_view').fullCalendar('unselect'); event_field_form('new', data); }); } // Printing setup_print_tweaks(); // User menu $('#usermenu').qtip({ content: $('#usermenu_content'), position: { my: 'top center', at: 'bottom center' }, style: { tip: true, classes: 'ui-tooltip-bootstrap agendav-menu' }, show: { event: 'click', effect: false, delay: 0 }, hide: { event: 'unfocus' } }); }); /** * Used to calculate calendar view height */ var calendar_height = function calendar_height() { var offset = $('#calendar_view').offset(); return $(window).height() - Math.ceil(offset.top) - 30; }; /** * Used to show error messages */ var show_error = function show_error(title, message) { // Hide loading indicator loading(false); $('#popup').freeow(title, message, { classes: ['popup_error'], autoHide: false, showStyle: { opacity: 1, left: 0 }, hideStyle: { opacity: 0, left: '400px' } }); }; /** * Used to show success messages */ var show_success = function show_success(title, message) { $('#popup').freeow(title, message, { classes: ['popup_success'], autoHide: true, autoHideDelay: 2000, showStyle: { opacity: 1, left: 0 }, hideStyle: { opacity: 0, left: '400px' } }); }; /** * Gets data from body */ var get_data = function get_data(name) { return $.data($('body')[0], name); }; /** * Sets data on body */ var set_data = function set_data(name, value) { $.data($('body')[0], name, value); }; /** * Removes data from body */ var remove_data = function remove_data(name) { $.removeData($('body')[0], name); }; /** * Loads a form (via AJAX) to a specified div */ var load_generated_dialog = function load_generated_dialog(url, data, preDialogFunc, title, buttons, divname, width) { divname = '#' + divname; // Avoid double dialog opening if ($(divname).length != 0) { return false; } // Do it via POST var newid = generate_on_the_fly_form( base_app_url + 'event/modify', data); if (get_data('formcreation') == 'ok') { var thisform = $('#' + newid); var action = $(thisform).attr('action'); var formdata = $(thisform).serialize(); var dialog_ajax_req = $.ajax({ url: base_app_url + url, cache: false, type: 'POST', data: formdata, dataType: 'html' }); dialog_ajax_req.then(function() { loading(false); }); dialog_ajax_req.fail(function(jqXHR, textStatus, errorThrown) { show_error(t('messages', 'error_loading_dialog'), t('messages', 'error_oops') + ': ' + textStatus); }); dialog_ajax_req.done(function(data, textStatus, jqxHR) { $('body').append(data); $(divname).dialog({ autoOpen: true, buttons: buttons, title: title, minWidth: width, modal: true, open: function(event, ui) { preDialogFunc(); $(divname).dialog('option', 'position', 'center'); var buttons = $(event.target).parent().find('.ui-dialog-buttonset').children(); add_button_icons(buttons); }, close: function(ev, ui) { $(this).remove(); } }) }); // Remove generated form $(thisform).remove(); } else { // Error generating dialog on the fly? show_error(t('messages', 'error_interfacefailure'), t('messages', 'error_oops')); } }; /** * Sends a form via AJAX. * * This way we respect CodeIgniter CSRF tokens */ var proceed_send_ajax_form = function proceed_send_ajax_form(formObj, successFunc, exceptionFunc, errorFunc) { var url = $(formObj).attr('action'); var data = $(formObj).serialize(); // Mask body loading(true); var sendform_ajax_req = $.ajax({ url: url, cache: false, type: 'POST', data: data, dataType: 'json' }); sendform_ajax_req.then(function() { loading(false); }); sendform_ajax_req.fail(function(jqXHR, textStatus, errorThrown) { show_error(t('messages', 'error_interfacefailure'), t('messages', 'error_oops') + ':' + textStatus); set_data('lastoperation', 'failed'); errorFunc(); }); sendform_ajax_req.done(function(data, textStatus, jqXHR) { // "ERROR", "EXCEPTION" or "SUCCESS" var result = data.result; var message = data.message; if (result == 'ERROR') { set_data('lastoperation', 'failed'); show_error( t('messages', 'error_internal'), message); errorFunc(); } else if (result == 'EXCEPTION') { set_data('lastoperation', 'failed'); exceptionFunc(message); } else if (result == 'SUCCESS') { set_data('lastoperation', 'success'); successFunc(message); } else { show_error(t('messages', 'error_internal'), t('messages', 'error_oops') + ':' + result); } }); }; /** * Generates a dialog */ var show_dialog = function show_dialog(template, data, title, buttons, divname, width, pre_func) { dust.render(template, dustbase.push(data), function(err, out) { if (err != null) { show_error(t('messages', 'error_interfacefailure'), err.message); } else { $('body').append(out); $('#' + divname).dialog({ autoOpen: true, buttons: buttons, title: title, minWidth: width, modal: true, open: function(event, ui) { pre_func(); $(divname).dialog('option', 'position', 'center'); var buttons = $(event.target).parent().find('.ui-dialog-buttonset').children(); add_button_icons(buttons); }, close: function(ev, ui) { $(this).remove(); } }) } }); }; /** * Creates a form with a random id in the document, and returns it. * Defines each element in the second parameter as hidden fields */ var generate_on_the_fly_form = function generate_on_the_fly_form(action, data) { var random_id = ''; var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for( var i=0; i < 10; i++ ) random_id += possible.charAt(Math.floor(Math.random() * possible.length)); // Now we have our random id var form_gen = base_app_url + 'dialog_generator/on_the_fly_form/' + random_id; var csrf_ajax_gen = $.ajax({ url: form_gen, cache: false, type: 'POST', contentType: 'text', dataType: 'text', async: false // Let's wait }); csrf_ajax_gen.fail(function(jqXHR, textStatus, errorThrown) { // This is generally caused by expired session session_expired(); set_data('formcreation', 'failed'); }); csrf_ajax_gen.done(function(formdata, textStatus, jqXHR) { var hidden_fields = ''; $.each(data, function (i, v) { hidden_fields += ''; }); $(formdata) .append(hidden_fields) .attr('action' , action) .appendTo(document.body); set_data('formcreation', 'ok'); }); return random_id; }; /** * Destroys a dialog */ var destroy_dialog = function destroy_dialog(name) { $(name).dialog('close'); $(name).dialog('destroy'); $(name).remove(); }; /** * Sets datepicker options */ var set_default_datepicker_options = function set_default_datepicker_options() { // Localization (TODO: make this configurable!) $.datepicker.regional['custom'] = { closeText: t('labels', 'close'), prevText: t('labels', 'previous'), nextText: t('labels', 'next'), currentText: t('labels', 'today'), monthNames: month_names_long(), monthNamesShort: month_names_short(), dayNames: day_names_long(), dayNamesShort: day_names_short(), dayNamesMin: day_names_short(), weekHeader: 'Sm', firstDay: AgenDAVConf.prefs_firstday, isRTL: false, showMonthAfterYear: false, yearSuffix: ''}; $.datepicker.setDefaults($.datepicker.regional['custom']); $.datepicker.setDefaults({constrainInput: true}); $.datepicker.setDefaults({dateFormat: AgenDAVConf.prefs_dateformat}); }; /** * Sets a minDate on end_date */ var set_end_minDate = function set_end_minDate() { var elems = ced + ' input.start_date'; var eleme = ced + ' input.end_date'; var elemru = ced + ' input.recurrence_until'; var selected = $(elems).datepicker('getDate'); selected.setTime(selected.getTime()); $(eleme).datepicker('option', 'minDate', selected); $(elemru).datepicker('option', 'minDate', selected); }; /** * Sets recurrence options to be enabled or disabled */ var update_recurrence_options = function update_recurrence_options(newval) { if (newval == 'none') { $(ced + ' input.recurrence_count').val(''); $(ced + ' input.recurrence_until').val(''); $(ced + ' input.recurrence_count').attr('disabled', 'disabled'); $(ced + ' input.recurrence_count').addClass('ui-state-disabled'); $(ced + ' label[for="recurrence_count"]').addClass('ui-state-disabled'); $(ced + ' input.recurrence_until').attr('disabled', 'disabled'); $(ced + ' input.recurrence_until').datepicker('disable'); $(ced + ' input.recurrence_until').addClass('ui-state-disabled'); $(ced + ' label[for="recurrence_until"]').addClass('ui-state-disabled'); } else { enforce_exclusive_recurrence_field('recurrence_count', 'recurrence_until'); enforce_exclusive_recurrence_field('recurrence_until', 'recurrence_count'); } }; /*************************** * Event handling functions */ // Triggers a dialog for editing/creating events var event_field_form = function event_field_form(type, data) { var url_dialog = 'dialog_generator/'; var title; var action_verb; if (type == 'new') { url_dialog += 'create_event'; title = t('labels', 'createevent'); } else { url_dialog += 'edit_event'; title = t('labels', 'editevent'); } load_generated_dialog(url_dialog, data, function() { var start_datepicker_opts = { onSelect: function(dateText, inst) { // End date can't be previous to start date set_end_minDate(); } }; // Tabs $(ced + '_tabs').tabs(); $(ced + ' input.start_time').timePicker(AgenDAVConf.timepicker_base); $(ced + ' input.end_time').timePicker(AgenDAVConf.timepicker_base); $(ced + ' input.start_date').datepicker(start_datepicker_opts); $(ced + ' input.end_date').datepicker(); $(ced + ' input.recurrence_until').datepicker(); // Untouched value $(ced + ' input.end_time').data('untouched', true); // First time datepicker is run we need to set minDate on end date set_end_minDate(); // And recurrence options have to be enabled/disabled update_recurrence_options($(ced + ' select.recurrence_type').val()); // All day checkbox $(ced).on('change', 'input.allday', function() { // TODO: timepickers should update their values var current = $(ced + " input.start_date").datepicker('getDate'); set_end_minDate(); if ($(this).is(':checked')) { $(ced + ' input.start_time').hide(); $(ced + ' input.end_time').hide(); } else { $(ced + ' input.end_date').removeAttr('disabled'); $(ced + ' input.end_date').removeClass('ui-state-disabled'); $(ced + ' input.end_date').datepicker('setDate', current); $(ced + ' input.start_time').show(); $(ced + ' input.end_time').show(); } }); // Recurrence type $(ced).on('change', 'select.recurrence_type', function() { var newval = $(this).val(); update_recurrence_options($(this).val()); }); // Avoid having a value in both recurrence options (count / until) $(ced) .on('keyup', 'input.recurrence_count', function() { enforce_exclusive_recurrence_field('recurrence_count', 'recurrence_until'); }) .on('keyup change', 'input.recurrence_until', function() { enforce_exclusive_recurrence_field('recurrence_until', 'recurrence_count'); }); // Timepicker: keep 1h between start-end if on the same day // and end_time hasn't been changed by hand var origStart = $.timePicker(ced + ' input.start_time').getTime(); var origDur = $.timePicker(ced + ' input.end_time').getTime() - origStart.getTime(); $(ced).on('change', 'input.start_time', function() { if ($(ced + ' input.end_time').data('untouched')) { var start = $.timePicker(ced + ' input.start_time').getTime(); var dur = $.timePicker(ced + ' input.end_time').getTime() - origStart.getTime(); $.timePicker(ced + ' input.end_time').setTime(new Date(start.getTime() + dur)); origStart = start; } }); $(ced).on('change', 'input.end_time', function() { var durn = $.timePicker(this).getTime() - $.timePicker(ced + ' input.start_time').getTime(); if (durn != origDur) { $(this).data('untouched', false); } }); // Focus first field on creation if (type == 'new') { $('input[name="summary"]').focus(); } // Show 'Reminders' tab contents dust.render('reminders_table', dustbase.push(data), function(err, out) { if (err != null) { show_error(t('messages', 'error_interfacefailure'), err.message); } else { $('#tabs-reminders').html(out); reminders_manager(); } }); }, title, [ { 'text': t('labels', 'save'), 'class': 'addicon btn-icon-event-edit', 'click': function() { var thisform = $('#com_form'); proceed_send_ajax_form(thisform, function(data) { // Reload only affected calendars $.each(data, function(k, cal) { reload_event_source(cal); }); destroy_dialog(ced); }, function(data) { // Problem with form data show_error(t('messages', 'error_invalidinput'), data); }, function(data) { // Do nothing }); } }, { 'text': t('labels', 'cancel'), 'class': 'addicon btn-icon-cancel', 'click': function() { destroy_dialog(ced); } } ], 'com_event_dialog', 550); }; /* * Updates a single event fetching it from server */ var update_single_event = function update_single_event(event, new_data) { $.each(new_data, function (i, v) { event[i] = v; }); $('#calendar_view').fullCalendar('updateEvent', event); }; // Triggers a dialog for creating calendars var calendar_create_dialog = function calendar_create_dialog() { var form_url = base_app_url + 'calendar/create'; var title = t('labels', 'newcalendar'); var data = { applyid: 'calendar_create_form', frm: { action: form_url, method: 'post', csrf: get_csrf_token() } }; show_dialog('calendar_create_dialog', data, title, [ { 'text': t('labels', 'create'), 'class': 'addicon btn-icon-calendar-add', 'click': function() { var thisform = $('#calendar_create_form'); proceed_send_ajax_form(thisform, function(data) { destroy_dialog('#calendar_create_dialog'); update_calendar_list(false); }, function(data) { // Problem with form data show_error(t('messages', 'error_invalidinput'), data); }, function(data) { // Do nothing }); } }, { 'text': t('labels', 'cancel'), 'class': 'addicon btn-icon-cancel', 'click': function() { destroy_dialog('#calendar_create_dialog'); } } ], 'calendar_create_dialog', 400, function() { $('input.pick_color').colorPicker(); }); }; // Triggers a dialog for editing calendars var calendar_modify_dialog = function calendar_modify_dialog(calendar_obj) { var form_url = base_app_url + 'calendar/modify'; var title = t('labels', 'modifycalendar'); var data = calendar_obj; $.extend(data, { applyid: 'calendar_modify_form', frm: { action: form_url, method: 'post', csrf: get_csrf_token() } }); // Buttons for modification dialog var buttons_and_actions = [ { 'text': t('labels', 'deletecalendar'), 'class': 'addicon btn-icon-calendar-delete', 'click': function() { calendar_delete_dialog(calendar_obj); } }, { 'text': t('labels', 'save'), 'class': 'addicon btn-icon-calendar-edit', 'click': function() { var thisform = $('#calendar_modify_form'); proceed_send_ajax_form(thisform, function(data) { destroy_dialog('#calendar_modify_dialog'); // TODO remove specific calendar and update only its events update_calendar_list(false); }, function(data) { // Problem with form data show_error(t('messages', 'error_invalidinput'), data); }, function(data) { // Do nothing }); } }, { 'text': t('labels', 'cancel'), 'class': 'addicon btn-icon-cancel', 'click': function() { destroy_dialog('#calendar_modify_dialog'); } } ]; // On shared calendars, don't show 'Remove calendar' if (data.shared === true) { buttons_and_actions.splice(0, 1); } show_dialog('calendar_modify_dialog', data, title, buttons_and_actions, 'calendar_modify_dialog', 500, function() { $('input.pick_color').colorPicker(); $('#calendar_modify_dialog_tabs').tabs(); if (enable_calendar_sharing === true && data.shared !== true) { share_manager(); } }); }; /** * Shows the 'Delete calendar' dialog */ var calendar_delete_dialog = function calendar_delete_dialog(calendar_obj) { destroy_dialog('#calendar_modify_dialog'); var form_url = base_app_url + 'calendar/delete'; var title = t('labels', 'deletecalendar'); var data = calendar_obj; $.extend(data, { applyid: 'calendar_delete_form', frm: { action: form_url, method: 'post', csrf: get_csrf_token() } }); show_dialog('calendar_delete_dialog', data, title, [ { 'text': t('labels', 'yes'), 'class': 'addicon btn-icon-calendar-delete', 'click': function() { var thisform = $('#calendar_delete_form'); proceed_send_ajax_form(thisform, function(removed_calendar) { // Just remove deleted calendar $('.calendar_list li.available_calendar').each(function(index) { var thiscal = $(this).data(); if (thiscal.calendar == removed_calendar) { $('#calendar_view').fullCalendar('removeEventSource', thiscal.eventsource); $(this).remove(); return false; // stop looking for calendar } }); }, function(data) { show_error(t('messages', 'error_caldelete'), data); }, function() {}); // Destroy dialog destroy_dialog('#calendar_delete_dialog'); } }, { 'text': t('labels', 'cancel'), 'class': 'addicon btn-icon-cancel', 'click': function() { destroy_dialog('#calendar_delete_dialog'); } } ], 'calendar_delete_dialog', 500, function() { }); }; /* * Updates the calendar list and generates eventSources for fullcalendar */ var update_calendar_list = function update_calendar_list(maskbody) { if (maskbody) { loading(true); } var updcalendar_ajax_req = $.ajax({ url: base_app_url + 'calendar/all', cache: false, dataType: 'json', async: false // Let's wait }); updcalendar_ajax_req.then(function() { if (maskbody) { loading(false); } }); updcalendar_ajax_req.fail(function(jqXHR, textStatus, errorThrown) { show_error(t('messages', 'error_loading_calendar_list'), t('messages', 'error_oops') + textStatus); }); updcalendar_ajax_req.done(function(data, textStatus, jqXHR) { var was_transparent = {}; // Remove old eventSources and remove every list item $('.calendar_list li.available_calendar').each(function(index) { var data = $(this).data(); $('#calendar_view').fullCalendar('removeEventSource', data.eventsource); if ($(this).hasClass('transparent')) { was_transparent[data.calendar] = true; } $(this).remove(); }); var count = 0, count_shared = 0, own_calendars = document.createDocumentFragment(), shared_calendars = document.createDocumentFragment(), collected_event_sources = []; $.each(data, function(key, calendar) { count++; // Some values need to be generated if (calendar.color === undefined || calendar.color === false || calendar.color == null) { calendar.color = default_calendar_color; } else { calendar.color = calendar.color.substr(0,7); } calendar.fg = fg_for_bg(calendar.color); calendar.bordercolor = $.color.parse(calendar.color).scale('rgb', (calendar.fg == '#000000' ? 0.8 : 1.8)).toString(); var li = generate_calendar_entry(calendar); if (was_transparent[calendar.calendar]) { li.addClass('transparent'); } else { collected_event_sources.push($(li).data().eventsource); } if (calendar.shared == true) { count_shared++; shared_calendars.appendChild(li[0]); } else { own_calendars.appendChild(li[0]); } }); // No calendars? if (count == 0) { // Some CalDAV servers (e.g. DAViCal) create first calendar on first // login. Let's reload calendar list again var last_calendar_count = get_data('last_calendar_count'); if (last_calendar_count === undefined || last_calendar_count != '0') { set_data('last_calendar_count', 0); setTimeout(function() { update_calendar_list(false); }, 1); } else { // Calendar list received empty twice show_error(t('messages','notice_no_calendars'), ''); $('#shortcut_add_event').button('disable'); } } else { set_data('last_calendar_count', count); $('#own_calendar_list ul')[0] .appendChild(own_calendars); // Hide unused block if (count_shared == 0) { $('#shared_calendar_list').hide(); } else { $('#shared_calendar_list ul')[0] .appendChild(shared_calendars); $('#shared_calendar_list').show(); } // Add event sources while (count--) { $('#calendar_view').fullCalendar('addEventSource', collected_event_sources[count]); } $('#shortcut_add_event').button('enable'); } }); }; /** * Function used to query the server for events */ var generate_event_source = function generate_event_source(calendar) { var ajax_options = { // If #calendar is not used, Fullcalendar will be confused when // calling removeEventSource, and will remove all calendars url: base_app_url + 'event/all#' + calendar, cache: false, // TODO make timezone configurable data: { calendar: calendar }, error: function (jqXHR, textStatus, errorThrown) { if (jqXHR.status !== undefined && jqXHR.status == 401) { session_expired(); } else { show_error(t('messages', 'error_interfacefailure'), t('messages', 'error_loadevents', { '%cal' : calendar })); } }, startParamUTC: true, endParamUTC: true }; return ajax_options; }; /** * Keeps session alive * * n = refresh interval in miliseconds */ var session_refresh = function session_refresh(n) { var sessrefresh_ajax_req = $.ajax({ url: base_app_url + 'js_generator/keepalive', cache: false, method: 'GET', dataType: 'html' }); sessrefresh_ajax_req.done(function(data, textStatus, jqXHR) { if (data !== '') { // When data is not empty, it's usually JavaScript code // TODO think about using dataType: script here $('body').append(data); } else { setTimeout(function() { session_refresh(n); }, n); } }); sessrefresh_ajax_req.fail(function(jqXHR, textStatus, errorThrown) { session_expired(); }); }; /** * Adds button icons */ var add_button_icons = function add_button_icons(buttons) { buttons.filter('button.addicon') .removeClass('addicon') .removeClass('ui-button-text-only') .addClass('ui-button-text-icon-primary') .each(function(k, v) { var classes = $(v).attr('class').split(' '); $.each(classes, function(i, j) { if (j.match(/^btn-icon-/)) { $(v).prepend(''); $(v).removeClass(j); return false; } }); }); }; /** * Generates a new calendar entry */ var generate_calendar_entry = function generate_calendar_entry(data) { var eventsource = generate_event_source(data.calendar); eventsource.ignoreTimezone = true; // Ignore UTC offsets eventsource.color = data.color; eventsource.textColor = data.fg; eventsource.borderColor = data.bordercolor; // Shared calendars if (data.shared !== undefined && data.shared == true && data.write_access == '0') { eventsource.editable = false; } data.eventsource = eventsource; var $out; dust.render('calendar_list_entry', dustbase.push(data), function(err, out) { if (err != null) { show_error(t('messages', 'error_interfacefailure'), err.message); } else { $out = $(out); // Associate data + eventsource to new list item $out.data(data); // Disable text selection on this (useful for dblclick) $out.disableSelection(); $out.find('span[title],i[title]').qtip({ position: { my: 'top left', at: 'bottom left' }, show: { delay: 600 }, style: { classes: 'ui-tooltip-bootstrap', tip: true } }); } }); return $out; }; /** * Gets calendar data from its internal name */ var get_calendar_data = function get_calendar_data(c) { var data = undefined; $('.calendar_list li.available_calendar').each(function(index) { var thiscal = $(this).data(); if (thiscal.calendar == c) { data = thiscal; return false; // stop looking for calendar } }); return data; }; /** * Gets calendar display name from its internal name */ var get_calendar_displayname = function get_calendar_displayname(c) { var data = get_calendar_data(c); if (data === undefined || data.displayname === undefined) { return '(?)'; } else { return data.displayname; } }; /* * Reloads an event source */ var reload_event_source = function reload_event_source(cal) { var eventsource = undefined; $('.calendar_list li.available_calendar').each(function(index) { var thiscal = $(this).data(); if (thiscal.calendar == cal) { eventsource = thiscal.eventsource; return false; // stop looking for calendar } }); if (eventsource !== undefined) { $('#calendar_view').fullCalendar('removeEventSource', eventsource); $('#calendar_view').fullCalendar('addEventSource', eventsource); } else { show_error(t('messages', 'error_interfacefailure'), t('messages', 'error_calendarnotfound', {'%calendar' : cal })); } }; /* * Enforces the use of only one recurrence fields */ var enforce_exclusive_recurrence_field = function enforce_exclusive_recurrence_field(current, other) { if ($(ced + ' input.' + current).val() == '') { $(ced + ' input.' + other).removeAttr('disabled'); $(ced + ' input.' + other).removeClass('ui-state-disabled'); $(ced + ' label[for="' + other + '"]').removeClass('ui-state-disabled'); if (other == 'recurrence_until') { $(ced + ' input.' + other).datepicker('enable'); } } else { $(ced + ' input.' + other).attr('disabled', 'disabled'); $(ced + ' input.' + other).addClass('ui-state-disabled'); $(ced + ' input.' + other).val(''); $(ced + ' label[for="' + other + '"]').addClass('ui-state-disabled'); if (other == 'recurrence_until') { $(ced + ' input.' + other).datepicker('disable'); } } }; /* * Round a Date timestamp */ var timestamp = function timestamp(d) { return Math.round(d.getTime()/1000); }; /* * Returns a full date+time string which is easily parseable */ var fulldatetimestring = function fulldatetimestring(d) { if (d != undefined) { return $.fullCalendar.formatDate(d, 'yyyyMMddHHmmss'); } else { return undefined; } }; /** * Returns a foreground color for a given background */ var fg_for_bg = function fg_for_bg(color) { var colr = parseInt(color.substr(1), 16); var is_dark = (colr >>> 16) // R + ((colr >>> 8) & 0x00ff) // G + (colr & 0x0000ff) // B < 500; return (is_dark) ? '#ffffff' : '#000000'; }; /** * This method is called when a session has expired */ var session_expired = function session_expired() { $('.ui-dialog-content').dialog('close'); show_error(t('messages', 'error_sessexpired'), t('messages', 'error_loginagain')); setTimeout(function() { window.location = base_url; }, 2000); }; /** * Handles events on share calendar dialog */ var share_manager = function share_manager() { var manager = $('#calendar_share_table'); var new_entry_form = $('#calendar_share_add'); share_manager_no_entries_placeholder(); manager.on('click', '.calendar_share_delete', function(event) { $(this).parent().parent() .fadeOut('fast', function() { $(this).remove(); share_manager_no_entries_placeholder(); }); }); // Autocomplete caching var user_autocomplete_cache = {}, lastXhr; new_entry_form.find('#calendar_share_add_username') .autocomplete({ minLength: 3, source: function(request, response) { var term = request.term; if (term in user_autocomplete_cache) { response(user_autocomplete_cache[term]); return; } lastXhr = $.getJSON(base_app_url + 'caldav2json/principal_search', request, function(data, status, xhr) { user_autocomplete_cache[term] = data; if (xhr === lastXhr) { response(data); } }); }, focus: function( event, ui ) { $(this).val(ui.item.username); return false; }, select: function( event, ui ) { $(this).val(ui.item.username); return false; } }) .data('autocomplete')._renderItem = function(ul, item) { return $('
  • ') .data('item.autocomplete', item) .append(' ' + item.displayname + '' + ' <' + item.email + '>') .appendTo(ul); }; new_entry_form.on('click', '#calendar_share_add_button', function(event) { var new_user = $('#calendar_share_add_username').val(); var access = $('#calendar_share_add_write_access').val(); if (new_user != '') { // Check if new_user is already on list var already_added = false; manager.find('span.username') .each(function(index) { if (!already_added && $(this).text() == new_user) { already_added = true; $(this).parent().parent().effect('highlight', {}, 'slow'); } }); if (!already_added) { var new_row_data = { username: new_user, write_access: access }; dust.render('calendar_share_row', dustbase.push(new_row_data), function(err, out) { if (err != null) { show_error(t('messages', 'error_interfacefailure'), err.message); } else { manager.find('tbody').append(out); // Reset form $('#calendar_share_add_username').val(''); $('#calendar_share_add_write_access').val('0'); share_manager_no_entries_placeholder(); } }); } } }); }; /** * Shows the placeholder for empty share lists */ var share_manager_no_entries_placeholder = function share_manager_no_entries_placeholder() { var manager = $('#calendar_share_table'); if (manager.find('tbody tr').length == 1) { $('#calendar_share_no_rows').show(); } else { $('#calendar_share_no_rows').hide(); } }; /* * Reminders manager */ var reminders_manager = function reminders_manager() { var tab_reminders = $('#tabs-reminders'); var manager = $('#reminders_table'); initialize_date_and_time_pickers(tab_reminders); reminders_manager_no_entries_placeholder(); manager.on('click', '.reminder_delete', function(event) { $(this).parent().parent() .fadeOut('fast', function() { $(this).remove(); reminders_manager_no_entries_placeholder(); }); }); manager.parent().on('click', 'img.reminder_add_button', function(event) { var formdata = $(this).closest('tbody').serializeObject(); // Basic validations var proceed = false; var regexp_num = /^[0-9]+$/; if (formdata.is_absolute === false) { if (formdata.qty !== '' && regexp_num.test(formdata.qty) && formdata.interval !== '' && formdata.before !== '') { proceed = true; } } else { if (formdata.tdate !== '' && formdata.ttime !== '') { proceed = true; } } if (proceed === true) { var $new_reminder_row = $(this).closest('tr'); dust.render('reminder_row', dustbase.push(formdata), function(err, out) { if (err != null) { show_error(t('messages', 'error_interfacefailure'), err.message); } else { manager.find('tbody').append(out); $new_reminder_row.find('input').val(''); $new_reminder_row.find('select').val(''); initialize_date_and_time_pickers(tab_reminders); reminders_manager_no_entries_placeholder(); } }); } }); }; /* * Shows/hides reminders placeholder when no reminders are set up */ var reminders_manager_no_entries_placeholder = function reminders_manager_no_entries_placeholder() { var manager = $('#reminders_table'); if (manager.find('tbody tr').length == 1) { $('#reminders_no_rows').show(); } else { $('#reminders_no_rows').hide(); } } /** * Event render */ var event_render_callback = function event_render_callback(event, element) { var caldata = get_calendar_data(event.calendar); var data = $.extend({}, event, { caldata: caldata }); if (caldata !== undefined && caldata.shared === true && caldata.write_access == '0') { $.extend(data, { disable_actions: true }); } // Icons var icons = []; if (event.rrule != undefined) { icons.push('icon-repeat'); } if (event.reminders.length > 0) { icons.push('icon-bell'); } // Prepend icons if (icons.length != 0) { var icon_html = $(''); $.each(icons, function(n, i) { icon_html.append(''); }); if (!element.hasClass('fc-event-row')) { element.find('.fc-event-title').after(icon_html); } } dust.render('event_details_popup', dustbase.push(data), function(err, out) { if (err != null) { show_error(t('messages', 'error_interfacefailure'), err.message); } else { element.qtip({ content: { text: out, title: { text: event.title, button: true } }, position: { my: 'bottom center', at: 'top center', viewport: $('#calendar_view') }, style: { classes: 'view_event_details ui-tooltip-bootstrap', tip: true }, show: { target: $('#calendar_view'), event: false, solo: $('#calendar_view'), effect: false }, hide: { fixed: true, event: 'unfocus', effect: false }, events: { show: function (event, api) { // Attach modify and delete events $(this) .find('button.link_delete_event') .off('click') .on('click', function() { event_delete_dialog(); }) .end() .find('button.link_modify_event') .off('click') .on('click', function() { modify_event_handler(); }); $(window).on('keydown.tooltipevents', function(e) { if(e.keyCode === $.ui.keyCode.ESCAPE) { api.hide(e); } }) // Icons var links = api.elements.tooltip.find('div.actions').find('button.addicon').button(); add_button_icons(links); }, hide: function (event, api) { remove_data('current_event'); $(window).off('keydown.tooltipevents'); } } }); } }); }; /** * Event click */ var event_click_callback = function event_click_callback(event, jsEvent, view) { var current_event = get_data('current_event'); if (current_event == event) { $(ved).qtip('hide'); remove_data('current_event'); } else { set_data('current_event', event); $(this).qtip('show', jsEvent); } }; /** * Calendar slots dragging */ var slots_drag_callback = function slots_drag_callback(startDate, endDate, allDay, jsEvent, view) { var pass_allday = (view.name == 'month') ? false : allDay; var data = { start: fulldatetimestring(startDate), end: fulldatetimestring(endDate), allday: pass_allday, view: view.name }; // Unselect every single day/slot $('#calendar_view').fullCalendar('unselect'); event_field_form('new', data); }; /** * Select helper */ var select_helper = function select_helper(start,end) { return $('
    ') .text( $.fullCalendar.formatDates(start, end, AgenDAVConf.prefs_timeformat + '{ - ' + AgenDAVConf.prefs_timeformat + '}')); }; /** * Event resizing */ var event_resize_callback = function event_resize_callback(event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view ) { // Generate on-the-fly form var formid = generate_on_the_fly_form( base_app_url + 'event/alter', { uid: event.uid, calendar: event.calendar, etag: event.etag, view: view.name, dayDelta: dayDelta, minuteDelta: minuteDelta, allday: event.allDay, was_allday: event.was_allday, timezone: event.timezone, type: 'resize' }); if (get_data('formcreation') == 'ok') { var thisform = $('#' + formid); proceed_send_ajax_form(thisform, function(data) { // Users just want to know if something fails update_single_event(event, data); }, function(data) { show_error(t('messages', 'error_modfailed'), data); revertFunc(); }, function() { revertFunc(); }); } // Remove generated form $(thisform).remove(); }; /** * Event drag and drop */ var event_drop_callback = function event_drop_callback(event, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view) { // Generate on-the-fly form var formid = generate_on_the_fly_form( base_app_url + 'event/alter', { uid: event.uid, calendar: event.calendar, etag: event.etag, view: view.name, dayDelta: dayDelta, minuteDelta: minuteDelta, allday: event.allDay, was_allday: event.orig_allday, timezone: event.timezone, type: 'drag' }); if (get_data('formcreation') == 'ok') { var thisform = $('#' + formid); proceed_send_ajax_form(thisform, function(data) { // Users just want to know if something fails update_single_event(event, data); }, function(data) { show_error(t('messages', 'error_modfailed'), data); revertFunc(); }, function() { revertFunc(); }); } // Remove generated form $(thisform).remove(); }; // Delete link // TODO: check for rrule/recurrence-id (EXDATE, etc) var event_delete_dialog = function event_delete_dialog() { var form_url = base_app_url + 'event/delete'; var title = t('labels', 'deleteevent'); var data = get_data('current_event'); if (data === undefined) { show_error(t('messages', 'error_interfacefailure'), t('messages', 'error_current_event_not_loaded')); return; } $.extend(data, { applyid: 'event_delete_form', frm: { action: form_url, method: 'post', csrf: get_csrf_token() } }); show_dialog('event_delete_dialog', data, title, [ { 'text': t('labels', 'yes'), 'class': 'addicon btn-icon-event-delete', 'click': function() { var thisform = $('#event_delete_form'); proceed_send_ajax_form(thisform, function(rdata) { $('#calendar_view').fullCalendar('removeEvents', data.id); }, function(rdata) { show_error(t('messages', 'error_event_not_deleted'), data); }, function() {}); // Destroy dialog destroy_dialog('#event_delete_dialog'); } }, { 'text': t('labels', 'cancel'), 'class': 'addicon btn-icon-cancel', 'click': function() { destroy_dialog('#event_delete_dialog'); } } ], 'event_delete_dialog', 400, function() {}); // Close tooltip $(ved).qtip('hide'); return false; }; // Edit/Modify link var modify_event_handler = function modify_event_handler() { // TODO: check for rrule/recurrence-id // Data about this event var event_data = get_data('current_event'); if (event_data === undefined) { show_error(t('messages', 'error_interfacefailure'), t('messages', 'error_current_event_not_loaded')); return; } var data = { uid: event_data.uid, calendar: event_data.calendar, href: event_data.href, etag: event_data.etag, start: fulldatetimestring(event_data.start), end: fulldatetimestring(event_data.end), summary: event_data.title, location: event_data.location, allday: event_data.allDay, description: event_data.description, rrule: event_data.rrule, rrule_serialized: event_data.rrule_serialized, rrule_explained: event_data.rrule_explained, icalendar_class: event_data.icalendar_class, transp: event_data.transp, recurrence_id: event_data.recurrence_id, reminders: event_data.reminders, visible_reminders: event_data.visible_reminders, orig_start: fulldatetimestring($.fullCalendar.parseDate(event_data.orig_start)), orig_end: fulldatetimestring($.fullCalendar.parseDate(event_data.orig_end)) }; // Close tooltip $(ved).qtip('hide'); event_field_form('modify', data); return false; }; // Shows a calendar var show_calendar = function show_calendar(calendar_obj) { $('#calendar_view').fullCalendar('addEventSource', calendar_obj.data().eventsource); calendar_obj.removeClass('transparent'); }; // Hides a calendar var hide_calendar = function hide_calendar(calendar_obj) { $('#calendar_view').fullCalendar('removeEventSource', calendar_obj.data().eventsource); calendar_obj.addClass('transparent'); }; // Toggles calendar visibility var toggle_calendar = function toggle_calendar(calendar_obj) { if (calendar_obj.hasClass('transparent')) { show_calendar(calendar_obj); } else { hide_calendar(calendar_obj); } }; // Initializes datepickers and timepickers var initialize_date_and_time_pickers = function initialize_date_and_time_pickers(obj) { obj.find('.needs-datepicker').datepicker(); obj.find('.needs-timepicker').timePicker(AgenDAVConf.timepicker_base); }; // Gets csrf token value var get_csrf_token = function get_csrf_token() { return $.cookie(AgenDAVConf.prefs_csrf_cookie_name); } // Loading indicator var loading = function loading(status) { if (status === false) { $('#loading').hide(); } else { $('#loading').show(); } } // Printing helpers var beforePrint = function beforePrint() { // Prepare calendar for printing $('#calendar_view').addClass('printing'); $('#calendar_view').fullCalendar('render'); }; var afterPrint = function afterPrint() { $('#calendar_view').removeClass('printing'); $('#calendar_view').fullCalendar('render'); }; // Apply printing helpers to document var setup_print_tweaks = function setup_print_tweaks() { if (window.matchMedia) { var mediaQueryList = window.matchMedia('print'); mediaQueryList.addListener(function(mql) { if (mql.matches) { beforePrint(); } else { afterPrint(); } }); } window.onbeforeprint = beforePrint; window.onafterprint = afterPrint; }; // vim: sw=2 tabstop=2