From 925c29ce9da86818c686dedd90ca9ef9c2242783 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Tue, 30 Jun 2015 22:26:59 +0000 Subject: [PATCH] Calendar et2 conversion work in progress. - Some context menu actions - Reduce code duplication in views - Fix views still using iframe --- calendar/inc/class.calendar_ui.inc.php | 5 +- calendar/inc/class.calendar_uiforms.inc.php | 60 +- calendar/inc/class.calendar_uiviews.inc.php | 49 +- calendar/js/app.js | 617 ++++++++++---------- calendar/js/et2_widget_event.js | 2 +- calendar/js/et2_widget_planner.js | 75 +-- calendar/js/et2_widget_timegrid.js | 99 +--- calendar/templates/default/sidebox.xet | 2 +- 8 files changed, 444 insertions(+), 465 deletions(-) diff --git a/calendar/inc/class.calendar_ui.inc.php b/calendar/inc/class.calendar_ui.inc.php index 6860f64731..f1afe77111 100644 --- a/calendar/inc/class.calendar_ui.inc.php +++ b/calendar/inc/class.calendar_ui.inc.php @@ -417,7 +417,7 @@ class calendar_ui } $this->view = $states['view'] = $func; } - $this->view_menuaction = $this->view == 'listview' ? 'calendar.calendar_uilist.listview' : 'calendar.calendar_uiviews.'.$this->view; + $this->view_menuaction = $this->view == 'listview' ? 'calendar.calendar_uilist.listview' : 'calendar.calendar_uiviews.index'; if ($this->debug > 0 || $this->debug == 'manage_states') $this->bo->debug_message('uical::manage_states(%1) session was %2, states now %3',True,$set_states,$states_session,$states); // save the states in the session only when we are in calendar @@ -705,7 +705,7 @@ class calendar_ui ), array( 'text' => lang('yearview'), - 'value' => '{"view":"year", "menuaction":"calendar.calendar_uiviews.year"}', + 'value' => '{"view":"year", "menuaction":"calendar.calendar_uiviews.index"}', 'selected' => $this->view == 'year', ), array( @@ -836,6 +836,7 @@ class calendar_ui { $event['app_id'] .= ':'.$event['recur_date']; } + $event['parts'] = implode(",\n",$this->bo->participants($event,true)); // Change dates foreach(calendar_egw_record::$types['date-time'] as $field) diff --git a/calendar/inc/class.calendar_uiforms.inc.php b/calendar/inc/class.calendar_uiforms.inc.php index 01c530933f..919fbff138 100644 --- a/calendar/inc/class.calendar_uiforms.inc.php +++ b/calendar/inc/class.calendar_uiforms.inc.php @@ -2523,7 +2523,6 @@ class calendar_uiforms extends calendar_ui $date =& $this->bo->so->startOfDay($date); $date->setUser(); } - error_log("Loading event for " . $date); $event = $this->bo->read($eventId, $date, true); $preserv['actual_date'] = $date; // remember the date clicked @@ -2595,6 +2594,65 @@ class calendar_uiforms extends calendar_ui } } + /** + * Change the status via ajax + * @param string $eventId + * @param integer $user + * @param string $status + */ + function ajax_status($eventId, $uid, $status) + { + list($eventId, $date) = explode(':',$eventId); + $old_event=$event=$this->bo->read($eventId); + + // If we have a recuring event for a particular day, make an exception + if ($event['recur_type'] != MCAL_RECUR_NONE && $date) + { + $date = new egw_time($date, egw_time::$user_timezone); + if (!empty($event['whole_day'])) + { + $date =& $this->bo->so->startOfDay($date); + $date->setUser(); + } + $event = $this->bo->read($eventId, $date, true); + $preserv['actual_date'] = $date; // remember the date clicked + + // For DnD, always create an exception + $this->_create_exception($event,$preserv); + unset($event['id']); + $date = $date->format('ts'); + } + if($event['participants'][$uid]) + { + $q = $r = null; + calendar_so::split_status($event['participants'][$uid],$q,$r); + $event['participants'][$uid] = $status = calendar_so::combine_status($status,$q,$r); + $this->bo->set_status($event['id'],$uid,$status,0,true); + } + $conflicts=$this->bo->update($event); + + $response = egw_json_response::get(); + if(!is_array($conflicts)) + { + // Directly update stored data. If event is still visible, it will + // be notified & update itself. + $this->to_client($event); + $response->call('egw.dataStoreUID','calendar::'.$event['id'].($date?':'.$date:''),$event); + } + else + { + $response->call( + 'egw_openWindowCentered2', + $GLOBALS['egw_info']['server']['webserver_url'].'/index.php?menuaction=calendar.calendar_uiforms.edit + &cal_id='.$event['id'] + .'&start='.$event['start'] + .'&end='.$event['end'] + .'&non_interactive=true' + .'&cancel_needs_refresh=true', + '',750,410); + } + } + /** * imports a mail as Calendar * diff --git a/calendar/inc/class.calendar_uiviews.inc.php b/calendar/inc/class.calendar_uiviews.inc.php index 195f9c06f5..7062c3fb74 100644 --- a/calendar/inc/class.calendar_uiviews.inc.php +++ b/calendar/inc/class.calendar_uiviews.inc.php @@ -211,7 +211,7 @@ class calendar_uiviews extends calendar_ui /** * Show the last view or the default one, if no last */ - function index($content) + function index($content=array()) { if($content['merge']) { @@ -245,7 +245,6 @@ class calendar_uiviews extends calendar_ui $GLOBALS['egw_info']['flags']['nonavbar'] = true; $this->manage_states($_GET); $old_calendar = $this->{$this->view}(); - echo $old_calendar; return; } @@ -493,6 +492,7 @@ class calendar_uiviews extends calendar_ui $content .= html::a_href(html::image('phpgwapi','first',lang('previous'),$options=' alt="<<"'),array( 'menuaction' => $this->view_menuaction, 'date' => date('Ymd',strtotime('-1 year',strtotime($this->date))), + 'view' => 'year' )); $content .= ''."\n"; } @@ -506,6 +506,7 @@ class calendar_uiviews extends calendar_ui $content .= html::a_href(html::image('phpgwapi','last',lang('next'),$options=' alt=">>"'),array( 'menuaction' => $this->view_menuaction, 'date' => date('Ymd',strtotime('+1 year',strtotime($this->date))), + 'view' => 'year' )); $content .= ''."\n"; } @@ -2872,6 +2873,14 @@ class calendar_uiviews extends calendar_ui '" data-date ="'.$this->bo->date2string($event['start']).'|'.$data['popup'].'">'."\n".$data['html'].$indent."\n"; } + /** + * Get the actions for the non-list views + * + * We use the actions from the list as a base, and only change what we have to + * to get it to work outside of a nextmatch. + * + * @return Array + */ protected static function get_actions() { // Just copy from the list, but change to match our needs @@ -2889,10 +2898,11 @@ class calendar_uiviews extends calendar_ui { $action['enabled'] = 'javaScript:app.calendar.is_event'; } - //$action['disableClass'] = 'view_row'; - //$action['hideOnDisabled'] = true; } + $actions['copy']['open'] = '{"app": "calendar", "type": "add", "extra": "cal_id=$id&action=copy"}'; + $actions['copy']['onExecute'] = 'javaScript:app.calendar.action_open'; + foreach($actions['status']['children'] as $id => &$status) { $status = array( @@ -2901,21 +2911,22 @@ class calendar_uiviews extends calendar_ui 'onExecute' => 'javaScript:app.calendar.status' ); } - /* - $actions['drag_calendar'] = array( - 'dragType' => array('calendar'), - 'type' => 'drag', - 'enabled' => 'javaScript:app.calendar.is_event' - ); - /* - Calendar DnD is handled internally - $actions['drop_calendar'] = array( - 'acceptedTypes' => array('calendar'), - 'type' => 'drop', - 'onExecute' => 'javaScript:app.calendar.move' - ); - * - */ + + if ($actions['filemanager']) + { + $actions['filemanager']['url'] = '/index.php?'. $actions['filemanager']['url']; + $actions['filemanager']['onExecute'] = 'javaScript:app.calendar.action_open'; + } + if ($actions['infolog_app']) + { + $actions['infolog_app']['open'] = '{"app": "infolog", "type": "add", "extra": "type=task&action=$app&action_id=$id"}'; + $actions['infolog_app']['onExecute'] = 'javaScript:app.calendar.action_open'; + } + if ($actions['timesheet']) + { + $actions['timesheet']['open'] = '{"app": "timesheet", "type": "add", "extra": "link_app[]=$app&link_id[]=$id"}'; + $actions['timesheet']['onExecute'] = 'javaScript:app.calendar.action_open'; + } return $actions; } diff --git a/calendar/js/app.js b/calendar/js/app.js index cc3da6b849..24cd45db7c 100644 --- a/calendar/js/app.js +++ b/calendar/js/app.js @@ -18,6 +18,13 @@ /** * UI for calendar * + * Calendar has multiple different views of the same data. All the templates + * for the different view are loaded at the start, then the view objects + * in app.classes.calendar.views are used to manage the different views. + * update_state() is used to change the state between the different views. + * + * The event widgets and the nextmatch get the data from egw.data. + * * @augments AppJS */ app.classes.calendar = AppJS.extend( @@ -32,181 +39,6 @@ app.classes.calendar = AppJS.extend( */ sidebox_et2: null, - /** - * etemplates and settings for the different views some (day view) - * use more than one template, some use the same template as others, - * most need different handling for their various attributes. - * - * Attributes are setter: function to calculate value - */ - views: { - day: { - etemplates: ['calendar.view','calendar.todo'], - set_start_date: function(state) { - return state.date ? new Date(state.date) : new Date(); - }, - set_end_date: function(state) { - var d = state.date ? new Date(state.date) : new Date(); - d.setUTCHours(23); - return d; - }, - set_owner: function(state) { - return state.owner || 0; - }, - set_show_weekend: function(state) - { - state.days = '1'; - return parseInt(egw.preference('days_in_weekview','calendar')) == 7; - } - }, - day4: { - etemplates: ['calendar.view'], - set_start_date: function(state) { - return state.date ? new Date(state.date) : new Date(); - }, - set_end_date: function(state) { - var d = state.date ? new Date(state.date) : new Date(); - d.setUTCHours(24*4-1); - return d; - }, - set_owner: function(state) { - return state.owner || 0; - }, - set_show_weekend: function(state) - { - state.days = '4'; - return parseInt(egw.preference('days_in_weekview','calendar')) == 7; - } - }, - week: { - etemplates: ['calendar.view'], - set_start_date: function(state) { - return app.calendar.date.start_of_week(state.date || new Date()); - }, - set_end_date: function(state) { - var d = app.calendar.date.start_of_week(state.date || new Date()); - // Always 7 days, we just turn weekends on or off - d.setUTCHours(24*7-1); - return d; - }, - set_owner: function(state) { - return state.owner || 0; - }, - set_show_weekend: function(state) - { - state.days = '' + (state.days >= 5 ? state.days : egw.preference('days_in_weekview','calendar') || 7); - return parseInt(state.days) == 7; - } - }, - weekN: { - etemplates: ['calendar.view'], - set_start_date: function(state) { - return app.calendar.date.start_of_week(state.date || new Date()); - }, - set_end_date: function(state) { - var d = app.calendar.date.start_of_week(state.date || new Date()); - // Always 7 days, we just turn weekends on or off - d.setUTCHours(24*7-1); - return d; - }, - set_show_weekend: function(state) - { - state.days = '' + (state.days >= 5 ? state.days : egw.preference('days_in_weekview','calendar') || 7); - return parseInt(state.days) == 7; - } - }, - month: { - etemplates: ['calendar.view'], - set_start_date: function(state) { - var d = state.date ? new Date(state.date) : new Date(); - d.setUTCDate(1); - d.setUTCHours(0); - d.setUTCMinutes(0); - d.setUTCSeconds(0); - state.date = d.toJSON(); - return app.calendar.date.start_of_week(d); - }, - set_end_date: function(state) { - var d = state.date ? new Date(state.date) : new Date(); - d = new Date(d.getFullYear(),d.getUTCMonth() + 1, 0); - var week_start = app.calendar.date.start_of_week(d); - if(week_start < d) week_start.setUTCHours(24*7); - week_start.setUTCHours(week_start.getUTCHours()-1); - return week_start; - }, - }, - - planner: { - etemplates: ['calendar.planner'], - set_group_by: function(state) { - return state.cat_id? state.cat_id : (state.sortby ? state.sortby : 0); - }, - set_start_date: function(state) { - var d = state.date ? new Date(state.date) : new Date(); - if(state.sortby && state.sortby === 'month') - { - d.setUTCDate(1); - } - else if (!state.planner_days) - { - if(d.getUTCDate() < 15) - { - d.setUTCDate(1); - return app.calendar.date.start_of_week(d); - } - else - { - return app.calendar.date.start_of_week(d); - } - } - return d; - }, - set_end_date: function(state) { - var d = state.date ? new Date(state.date) : new Date(); - if(state.sortby && state.sortby === 'month') - { - d.setUTCDate(0); - d.setUTCFullYear(d.getUTCFullYear() + 1); - } - else if (state.planner_days) - { - d.setUTCDate(d.getUTCDate() + parseInt(state.planner_days)-1); - } - else if (app.calendar.state.last) - { - d = new Date(app.calendar.state.last); - } - else if (!state.planner_days) - { - if (d.getUTCDate() < 15) - { - d.setUTCDate(0); - d.setUTCMonth(d.getUTCMonth()+1); - d = app.calendar.date.end_of_week(d); - } - else - { - d.setUTCMonth(d.getUTCMonth()+1); - d = app.calendar.date.end_of_week(d); - } - } - return d; - }, - set_owner: function(state) { - return state.owner || 0; - } - }, - - listview: { - etemplates: ['calendar.list'], - set_start_date: function(state) - { - var d = state.date ? new Date(state.date) : new Date(); - return d; - } - } - }, - /** * Current internal state */ @@ -327,23 +159,23 @@ app.classes.calendar = AppJS.extend( var hidden = typeof this.state.view !== 'undefined'; var all_loaded = true; // Record the templates for the views so we can switch between them - for(var view in this.views) + for(var view in app.classes.calendar.views) { - var index = this.views[view].etemplates.indexOf(_name) + var index = app.classes.calendar.views[view].etemplates.indexOf(_name) if(index > -1) { - this.views[view].etemplates[index] = _et2; + app.classes.calendar.views[view].etemplates[index] = _et2; // If a template disappears, we want to release it $j(_et2.DOMContainer).one('clear',jQuery.proxy(function() { this.view[index] = _name; - },{view: this.views[view], index: index, name: _name})); + },{view: app.classes.calendar.views[view], index: index, name: _name})); if(this.state.view === view) { hidden = false; } } - this.views[view].etemplates.forEach(function(et) {all_loaded = all_loaded && typeof et !== 'string';}); + app.classes.calendar.views[view].etemplates.forEach(function(et) {all_loaded = all_loaded && typeof et !== 'string';}); } // Start hidden, except for current view @@ -445,10 +277,9 @@ app.classes.calendar = AppJS.extend( { return _url.replace(/menuaction=[^&]+/, 'menuaction=calendar.calendar_uilist.listview&ajax=true'); } - else if (this.sidebox_et2 && typeof this.views[state.view] == 'undefined') + else if (this.sidebox_et2 && typeof app.classes.calendar.views[state.view] == 'undefined') { this.sidebox_et2.getWidgetById('iframe').set_src(_url); - this.sidebox return true; } } @@ -461,89 +292,6 @@ app.classes.calendar = AppJS.extend( return false; }, - /** - * Drag and Drop - * - * - */ - drag_n_drop: function() - { - var that = this; - - //jQuery Calendar Event selector - var $iframeBody = jQuery("body") - //mouseover event handler for calendar tooltip - .on("mouseover", "div[data-tooltip]",function(){ - var $ttp = jQuery(this); - //Check if the tooltip is already initialized - if (!$ttp.data('uiTooltip')) - { - $ttp.tooltip({ - items: "[data-tooltip]", - show: false, - content: function() - { - var elem = $ttp; - if (elem.is("[data-tooltip]")) - return this.getAttribute('data-tooltip') ; - }, - track:true, - - open: function(event,ui){ - ui.tooltip.removeClass("ui-tooltip"); - ui.tooltip.addClass("calendar_uitooltip"); - if (this.scrollHeight > this.clientHeight) - { - // bind on tooltip close event - $ttp.on("tooltipclose", function (event, ui){ - // bind hover handler on tooltip helper in order to be able to freeze the tooltip and scrolling - ui.tooltip.hover( - function () { - var $ttp_helper = jQuery(this); - if (this.scrollHeight > this.clientHeight) $ttp_helper.stop(true).fadeTo(100, 1); - }, - function () { - var $ttp_helper = jQuery(this); - $ttp_helper.fadeOut("100", function(){$ttp_helper.remove();}); - } - ); - }); - } - } - }); - } - else - { - $ttp.tooltip('enable'); - } - }) - - // mousedown event handler for calendar tooltip to remove disable tooltip - .on("mousedown", "div[data-tooltip]", function(){ - var $ttp = jQuery(this); - // Make sure the tooltip initialized before calling it - if ($ttp.data('uiTooltip')) - { - $ttp.tooltip("disable"); - } - }) - - //Click event handler for integrated apps - .on("click","div.calendar_plannerEvent",function(ev){ - var eventId = ev.currentTarget.getAttribute('data-date').split("|")[1]; - var startDate = ev.currentTarget.getAttribute('data-date').split("|")[0]; - var recurrFlag = ev.currentTarget.getAttribute('data-date').split("|")[2]; - if (recurrFlag == "n") - { - egw.open(eventId,'calendar','edit'); - } - else - { - that.edit_series(eventId,startDate); - } - }) - }, - /** * Setup and handle sortable calendars. * @@ -590,7 +338,7 @@ app.classes.calendar = AppJS.extend( start: function () { // Put owners into row IDs - app.calendar.views[state.view].etemplates[0].widgetContainer.iterateOver(function(widget) { + app.classes.calendar.views[state.view].etemplates[0].widgetContainer.iterateOver(function(widget) { widget.div.parents('tr').attr('data-owner',widget.options.owner); },this,et2_calendar_timegrid) }, @@ -638,18 +386,18 @@ app.classes.calendar = AppJS.extend( var end = null; // Get the view to calculate - if (app.calendar.views && app.calendar.state.view && app.calendar.views[app.calendar.state.view].set_end_date) + var view = app.classes.calendar.views[app.calendar.state.view] || false; + if (view) { if(direction > 0) { - start = app.calendar.views[app.calendar.state.view].set_end_date({date:start}); + start = view.end_date({date:start}); } else { - start = app.calendar.views[app.calendar.state.view].set_start_date({date:start}); + start = view.start_date({date:start}); } start.setUTCDate(start.getUTCDate()+direction); - end = app.calendar.views[app.calendar.state.view].set_end_date({date:start}); } // Calculate the current difference, and move else if(app.calendar.state.first && app.calendar.state.last) @@ -663,7 +411,7 @@ app.classes.calendar = AppJS.extend( end = new Date(end.valueOf() + (delta * direction)); } - app.calendar.update_state({date: start}); + app.calendar.update_state({date:app.calendar.date.toString(start)}); return false; } @@ -1100,6 +848,31 @@ app.classes.calendar = AppJS.extend( } }, + /** + * Application links from non-list events + * + * @param {egwAction} _action + * @param {egwActionObject[]} _events + */ + action_open: function(_action, _events) + { + var id = _events[0].id.split('::'); + if(_action.data.open) + { + var open = JSON.parse(_action.data.open) || {}; + var extra = open.extra || ''; + + extra = extra.replace(/(\$|%24)app/,id[0]).replace(/(\$|%24)id/,id[1]); + this.egw.open(open.id_data||'',open.app,open.type,extra); + } + else if (_action.data.url) + { + var url = _action.data.url; + url = url.replace(/(\$|%24)app/,id[0]).replace(/(\$|%24)id/,id[1]); + this.egw.open_link(url); + } + }, + /** * Change status (via AJAX) * @@ -1119,11 +892,17 @@ app.classes.calendar = AppJS.extend( switch(button_id) { case 'exception': - + egw().json( + 'calendar.calendar_uiforms.ajax_status', + [event_data.app_id, egw.user('account_id'), _action.data.id] + ).sendRequest(true); break; case 'series': case 'single': - this.egw.open(event_data.id, event_data.app||'calendar', 'edit', {date:event_data.start}); + egw().json( + 'calendar.calendar_uiforms.ajax_status', + [event_data.id, egw.user('account_id'), _action.data.id] + ).sendRequest(true); break; case 'cancel': default: @@ -1163,7 +942,7 @@ app.classes.calendar = AppJS.extend( _action.data.url = _action.data.url.replace(/(\$|%24)id/,id); _action.data.url = _action.data.url.replace(/(\$|%24)app/,app); - nm_action(_action, _senders); + nm_action(_action, _senders,false,{ids:[id]}); _action.data.url = backup_url; // restore url }, @@ -1179,7 +958,7 @@ app.classes.calendar = AppJS.extend( */ cal_open: function(_action, _senders) { - + var js_integration_data = _action.parent.data.nextmatch.options.settings.js_integration_data || this.et2.getArrayMgr('content').data.nm.js_integration_data; var id = _senders[0].id; var matches = id.match(/^(?:calendar::)?([0-9]+):([0-9]+)$/); @@ -1381,7 +1160,7 @@ app.classes.calendar = AppJS.extend( } var changed = []; var new_state = jQuery.extend({}, this.state); - if (typeof _set == 'object') + if (typeof _set === 'object') { for(var s in _set) { @@ -1457,7 +1236,7 @@ app.classes.calendar = AppJS.extend( state = JSON.parse(state); } } - if(typeof state.state != 'object' || !state.state.view) + if(typeof state.state !== 'object' || !state.state.view) { state.state = {view: 'week'}; } @@ -1466,15 +1245,14 @@ app.classes.calendar = AppJS.extend( state.state.date = new Date(); } - // Hide other views - for(var _view in this.views) + for(var _view in app.classes.calendar.views) { - if(state.state.view != _view && this.views[_view]) + if(state.state.view != _view && app.classes.calendar.views[_view]) { - for(var i = 0; i < this.views[_view].etemplates.length; i++) + for(var i = 0; i < app.classes.calendar.views[_view].etemplates.length; i++) { - $j(this.views[_view].etemplates[i].DOMContainer).hide(); + $j(app.classes.calendar.views[_view].etemplates[i].DOMContainer).hide(); } } } @@ -1484,9 +1262,9 @@ app.classes.calendar = AppJS.extend( } // Check for a supported client-side view - if(this.views[state.state.view] && + if(app.classes.calendar.views[state.state.view] && // Check that the view is instanciated - typeof this.views[state.state.view].etemplates[0] !== 'string' && this.views[state.state.view].etemplates[0].widgetContainer + typeof app.classes.calendar.views[state.state.view].etemplates[0] !== 'string' && app.classes.calendar.views[state.state.view].etemplates[0].widgetContainer ) { // Doing an update - this includes the selected view, and the sidebox @@ -1494,7 +1272,7 @@ app.classes.calendar = AppJS.extend( // cause infinite loops. this.state_update_in_progress = true; - var view = this.views[state.state.view]; + var view = app.classes.calendar.views[state.state.view]; // Sanitize owner switch(typeof state.state.owner) @@ -1534,7 +1312,7 @@ app.classes.calendar = AppJS.extend( state.state.view === 'month' ? 0 : // Calculate based on weeks in the month state.state.owner.length > (this.egw.config('calview_no_consolidate','phpgwapi') || 5) ? 1 : state.state.owner.length; - var grid = this.views[this.state.view] ? this.views[this.state.view].etemplates[0].widgetContainer.getWidgetById('view') : false; + var grid = view.etemplates[0].widgetContainer.getWidgetById('view'); /* If the count is different, we need to have the correct number (just remove all & re-create) @@ -1545,7 +1323,7 @@ app.classes.calendar = AppJS.extend( { // Need to redo the number of grids var value = []; - state.state.first = view.set_start_date(state.state).toJSON(); + state.state.first = view.start_date(state.state).toJSON(); // We'll modify this one, so it needs to be a new object var date = new Date(state.state.first); @@ -1553,7 +1331,7 @@ app.classes.calendar = AppJS.extend( switch(state.state.view) { case 'month': - var end = state.state.last = view.set_end_date(state.state); + var end = state.state.last = view.end_date(state.state); grid_count = Math.ceil((end - date) / (1000 * 60 * 60 * 24) / 7); // fall through case 'weekN': @@ -1572,7 +1350,7 @@ app.classes.calendar = AppJS.extend( state.state.last=val.end_date.toJSON(); break; default: - var end = state.state.last = view.set_end_date(state.state); + var end = state.state.last = view.end_date(state.state); for(var owner = 0; owner < grid_count && owner < state.state.owner.length; owner++) { value.push({ @@ -1600,16 +1378,16 @@ app.classes.calendar = AppJS.extend( if(typeof view[updater] === 'function') { var value = view[updater].call(this,state.state); - if(updater === 'set_start_date') state.state.first = value.toJSON(); - if(updater === 'set_end_date') state.state.last = value.toJSON(); + if(updater === 'start_date') state.state.first = this.date.toString(value); + if(updater === 'end_date') state.state.last = this.date.toString(value); // Set value for(var i = 0; i < view.etemplates.length; i++) { view.etemplates[i].widgetContainer.iterateOver(function(widget) { - if(typeof widget[updater] === 'function') + if(typeof widget['set_'+updater] === 'function') { - widget[updater](value); + widget['set_'+updater](value); } }, this, et2_valueWidget); } @@ -1649,11 +1427,15 @@ app.classes.calendar = AppJS.extend( } this.state = jQuery.extend({},state.state); + var formatDate = new Date(this.state.date); + formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000); + egw_app_header(view.header(state.state),'calendar'); + if(state.state.view === 'listview') { state.state.startdate = state.state.date; state.state.col_filter = {participant: state.state.owner}; - var nm = this.views[_view].etemplates[0].widgetContainer.getWidgetById('nm'); + var nm = view.etemplates[0].widgetContainer.getWidgetById('nm'); nm.applyFilters(state.state); } @@ -1771,7 +1553,10 @@ app.classes.calendar = AppJS.extend( } // setting internal state now, that linkHandler does not intercept switching from listview to any old view this.state = jQuery.extend({},state.state); - $j(this.sidebox_et2.getInstanceManager().DOMContainer).show(); + if(this.sidebox_et2) + { + $j(this.sidebox_et2.getInstanceManager().DOMContainer).show(); + } var query = jQuery.extend({menuaction: menuaction},state.state||{}); @@ -1911,6 +1696,18 @@ app.classes.calendar = AppJS.extend( * All take either a Date object or full date with timestamp (Z) */ date: { + toString: function(date) + { + // Ensure consistent formatting using UTC, avoids problems with comparison + // and timezones + if(typeof date === 'string') date = new Date(date); + return date.getUTCFullYear() +'-'+ + sprintf("%02d",date.getUTCMonth()+1) + '-'+ + sprintf("%02d",date.getUTCDate()) + 'T'+ + sprintf("%02d",date.getUTCHours()) + ':'+ + sprintf("%02d",date.getUTCMinutes()) + ':'+ + sprintf("%02d",date.getUTCSeconds()) + 'Z'; + }, start_of_week: function(date) { var d = new Date(date); @@ -1976,5 +1773,235 @@ app.classes.calendar = AppJS.extend( app.calendar.update_state({view: 'week', date: date.getValue()}); }); + }, + View: { + // List of etemplates to show for this view + etemplates: ['calendar.view'], + + /** + * Translated label for header + * @param {Object} state + * @returns {string} + */ + header: function(state) { + var formatDate = new Date(state.date); + formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000); + return date(egw.preference('dateformat'),formatDate); + }, + + /** + * Get the start date for this view + * @param {Object} state + * @returns {Date} + */ + start_date: function(state) { + var d = state.date ? new Date(state.date) : new Date(); + d.setUTCHours(0); + d.setUTCMinutes(0); + d.setUTCSeconds(0); + d.setUTCMilliseconds(0); + return d; + }, + /** + * Get the end date for this view + * @param {Object} state + * @returns {Date} + */ + end_date: function(state) { + var d = state.date ? new Date(state.date) : new Date(); + d.setUTCHours(23); + d.setUTCMinutes(59); + d.setUTCSeconds(59); + d.setUTCMilliseconds(0); + return d; + }, + owner: function(state) { + return state.owner || 0; + }, + show_weekend: function(state) + { + return parseInt(egw.preference('days_in_weekview','calendar')) == 7; + }, + extend: function(sub) + { + return jQuery.extend({},this,{_super:this},sub); + } } }); + +/** +* etemplates and settings for the different views some (day view) +* use more than one template, some use the same template as others, +* most need different handling for their various attributes. +* +* Not using the standard Class.extend here because it hides the members, +* and we want to be able to look inside them. This is done seperately instead +* of inside the normal object to allow access to the View object. +*/ + +jQuery.extend(app.classes.calendar,{ + views: { + day: app.classes.calendar.prototype.View.extend({ + header: function(state) { + return egw.lang('Day view') + ': ' + app.calendar.View.header.call(this, state); + }, + etemplates: ['calendar.view','calendar.todo'], + start_date: function(state) { + var d = app.calendar.View.start_date.call(this, state); + state.date = app.calendar.date.toString(d); + return d; + }, + show_weekend: function(state) { + state.days = '1'; + + return app.calendar.View.show_weekend.call(this,state); + } + }), + day4: app.classes.calendar.prototype.View.extend({ + header: function(state) { + return egw.lang('Four days view') + ': ' + app.calendar.View.header.call(this, state); + }, + end_date: function(state) { + var d = app.calendar.View.end_date.call(this,state); + state.days = '4'; + d.setUTCHours(24*4-1); + d.setUTCMinutes(59); + d.setUTCSeconds(59); + d.setUTCMilliseconds(0); + return d; + } + }), + week: app.classes.calendar.prototype.View.extend({ + header: function(state) { + return egw.lang('Week view') + ': ' + app.calendar.View.header.call(this, state); + }, + start_date: function(state) { + return app.calendar.date.start_of_week(state.date || new Date()); + }, + end_date: function(state) { + var d = app.calendar.date.start_of_week(state.date || new Date()); + // Always 7 days, we just turn weekends on or off + d.setUTCHours(24*7-1); + d.setUTCMinutes(59); + d.setUTCSeconds(59); + d.setUTCMilliseconds(0); + state.days = '' + (state.days >= 5 ? state.days : egw.preference('days_in_weekview','calendar') || 7); + return d; + }, + show_weekend: function(state) + { + return parseInt(state.days) === 7; + } + }), + weekN: app.classes.calendar.prototype.View.extend({ + header: function(state) { + return egw.lang('Multiple week view') + ': ' + app.calendar.View.header.call(this, state); + }, + start_date: function(state) { + return app.calendar.date.start_of_week(state.date || new Date()); + }, + end_date: function(state) { + state.days = '' + (state.days >= 5 ? state.days : egw.preference('days_in_weekview','calendar') || 7); + + var d = app.calendar.date.start_of_week(state.date || new Date()); + // Always 7 days, we just turn weekends on or off + d.setUTCHours(24*7-1); + return d; + }, + }), + month: app.classes.calendar.prototype.View.extend({ + header: function(state) + { + var formatDate = new Date(state.date); + formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000); + return egw.lang('Month view') + ':' + egw.lang(date('F',formatDate)) + ' ' + date('Y',formatDate); + }, + start_date: function(state) { + var d = app.calendar.View.start_date.call(this,state) + d.setUTCDate(1); + state.date = app.calendar.date.toString(d); + return app.calendar.date.start_of_week(d); + }, + end_date: function(state) { + var d = app.calendar.View.end_date.call(this,state) + d = new Date(d.getFullYear(),d.getUTCMonth() + 1, 0); + var week_start = app.calendar.date.start_of_week(d); + if(week_start < d) week_start.setUTCHours(24*7); + week_start.setUTCHours(week_start.getUTCHours()-1); + return week_start; + }, + }), + + planner: app.classes.calendar.prototype.View.extend({ + header: function(state) { + var startDate = new Date(state.first); + startDate = new Date(startDate.valueOf() + startDate.getTimezoneOffset() * 60 * 1000); + + var endDate = new Date(state.last); + endDate = new Date(endDate.valueOf() + endDate.getTimezoneOffset() * 60 * 1000); + return egw.lang('Planner view') + ': ' + date(egw.preference('dateformat'),startDate) + + ' - ' + date(egw.preference('dateformat'),endDate); + }, + etemplates: ['calendar.planner'], + group_by: function(state) { + return state.cat_id? state.cat_id : (state.sortby ? state.sortby : 0); + }, + start_date: function(state) { + var d = app.calendar.View.start_date.call(this, state); + if(state.sortby && state.sortby === 'month') + { + d.setUTCDate(1); + } + else if (!state.planner_days) + { + if(d.getUTCDate() < 15) + { + d.setUTCDate(1); + return app.calendar.date.start_of_week(d); + } + else + { + return app.calendar.date.start_of_week(d); + } + } + return d; + }, + end_date: function(state) { + var d = app.calendar.View.end_date.call(this, state); + if(state.sortby && state.sortby === 'month') + { + d.setUTCDate(0); + d.setUTCFullYear(d.getUTCFullYear() + 1); + } + else if (state.planner_days) + { + d.setUTCDate(d.getUTCDate() + parseInt(state.planner_days)-1); + } + else if (app.calendar.state.last) + { + d = new Date(app.calendar.state.last); + } + else if (!state.planner_days) + { + if (d.getUTCDate() < 15) + { + d.setUTCDate(0); + d.setUTCMonth(d.getUTCMonth()+1); + d = app.calendar.date.end_of_week(d); + } + else + { + d.setUTCMonth(d.getUTCMonth()+1); + d = app.calendar.date.end_of_week(d); + } + } + return d; + } + }), + + listview: app.classes.calendar.prototype.View.extend({ + header: function() {return egw.lang('List view');}, + etemplates: ['calendar.list'] + }) + }} +); \ No newline at end of file diff --git a/calendar/js/et2_widget_event.js b/calendar/js/et2_widget_event.js index ca063ba175..0b9066c157 100644 --- a/calendar/js/et2_widget_event.js +++ b/calendar/js/et2_widget_event.js @@ -285,7 +285,7 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM], } if(this.options.value.participants[egw.user('account_id')] && this.options.value.participants[egw.user('account_id')][0] == 'U') { - icons.push(''); + icons.push(''); } return icons; }, diff --git a/calendar/js/et2_widget_planner.js b/calendar/js/et2_widget_planner.js index 4678279490..7016db30aa 100644 --- a/calendar/js/et2_widget_planner.js +++ b/calendar/js/et2_widget_planner.js @@ -604,57 +604,27 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize title += ' '+t.getUTCFullYear(); // previous links - /* - $prev = $t_arr; - $prev['day'] = 1; - if ($prev['month']-- <= 1) - { - $prev['month'] = 12; - $prev['year']--; - } - if ($this->bo->date2ts($prev) < $start-20*DAY_s) - { - $prev['day'] = $this->day; - $full = $this->bo->date2string($prev); - if ($this->day >= 15) $prev = $t_arr; // we stay in the same month - $prev['day'] = $this->day < 15 ? 15 : 1; - $half = $this->bo->date2string($prev); - $title = html::a_href(html::image('phpgwapi','first',lang('back one month'),$options=' alt="<<"'),array( - 'menuaction' => $this->view_menuaction, - 'date' => $full, - )) . '   '. - html::a_href(html::image('phpgwapi','left',lang('back half a month'),$options=' alt="<"'),array( - 'menuaction' => $this->view_menuaction, - 'date' => $half, - )) . '   '.$title; - } + var prev = new Date(t); + prev.setUTCDate(1); + prev.setUTCMonth(prev.getUTCMonth()-1); + + var full = prev.toJSON(); + prev.setUTCDate(start.getUTCDate()); + if (prev.getUTCDate() >= 15) prev = new Date(t); // we stay in the same month + prev.setUTCDate(start.getUTCDate() < 15 ? 15 : 1); + var half = prev.toJSON(); + title = this._scroll_button('first',full) + this._scroll_button('left',half) + title; + + // next links - $next = $t_arr; - if ($next['month']++ >= 12) - { - $next['month'] = 1; - $next['year']++; - } - // dont show next scales, if there are more then 10 days in the next month or there is no next month - $days_in_next_month = (int) date('d',$end = $start+$days*DAY_s); - if ($days_in_next_month <= 10 || date('m',$end) == date('m',$t)) - { - if ($this->day >= 15) $next = $t_arr; // we stay in the same month - $next['day'] = $this->day; - $full = $this->bo->date2string($next); - if ($this->day < 15) $next = $t_arr; // we stay in the same month - $next['day'] = $this->day < 15 ? 15 : 1; - $half = $this->bo->date2string($next); - $title .= '   '.html::a_href(html::image('phpgwapi','right',lang('forward half a month'),$options=' alt=">>"'),array( - 'menuaction' => $this->view_menuaction, - 'date' => $half, - )). '   '. - html::a_href(html::image('phpgwapi','last',lang('forward one month'),$options=' alt=">>"'),array( - 'menuaction' => $this->view_menuaction, - 'date' => $full, - )); - } - */ + var next = new Date(t); + next.setUTCMonth(next.getUTCMonth()+1); + next.setUTCDate(start.getUTCDate() < 15 ? 15 : 1); + half = next.toJSON(); + next.setUTCMonth(next.getUTCMonth()+1); + full = next.toJSON(); + + title += this._scroll_button('right',half) + this._scroll_button('last',full); } else { @@ -840,9 +810,9 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize * Create a pagination button, and inserts it * */ - _scroll_button: function() + _scroll_button: function(image, date) { - + return ''; }, /** @@ -1339,7 +1309,6 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize else if (!jQuery.isEmptyObject(_ev.target.dataset)) { // Click on a header, we can go there - debugger _ev.data = jQuery.extend({},_ev.target.parentNode.dataset, _ev.target.dataset); this.change(_ev); } diff --git a/calendar/js/et2_widget_timegrid.js b/calendar/js/et2_widget_timegrid.js index e72dd848b5..65cda02647 100644 --- a/calendar/js/et2_widget_timegrid.js +++ b/calendar/js/et2_widget_timegrid.js @@ -188,97 +188,6 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz return this.dropEnd; }; - this.div.on('mouseover', '.calendar_calEvent:not(.ui-resizable):not(.rowNoEdit)', function() { - // Load the event - timegrid._get_event_info(this); - var that = this; - - //Resizable event handler - $j(this).resizable - ({ - distance: 10, - grid: [10000,timegrid.rowHeight], - autoHide: true, - handles: 's,se', - containment:'parent', - - /** - * Triggered when the resizable is created. - * - * @param {event} event - * @param {Object} ui - */ - create:function(event, ui) - { - var resizeHelper = event.target.getAttribute('data-resize'); - if (resizeHelper == 'WD' || resizeHelper == 'WDS') - { - jQuery(this).resizable('destroy'); - } - }, - - /** - * Triggered at start of resizing a calEvent - * - * @param {event} event - * @param {Object} ui - */ - start:function(event, ui) - { - this.dropStart = timegrid._get_time_from_position(ui.element[0].getBoundingClientRect().left,ui.element[0].getBoundingClientRect().top).last(); - this.dropDate = timegrid._get_event_info(this).start; - }, - - /** - * Triggered at the end of resizing the calEvent. - * - * @param {event} event - * @param {Object} ui - */ - stop:function(event, ui) - { - var e = new jQuery.Event('change'); - e.originalEvent = event; - e.data = {duration: 0}; - var event_data = timegrid._get_event_info(this); - var event_widget = timegrid.getWidgetById(event_data.id); - - var sT = parseInt(this.dropStart.attr('data-hour'))* 60 + parseInt(this.dropStart.attr('data-minute')); - if (typeof this.dropEnd != 'undefined' && this.dropEnd.length == 1) - { - var eT = parseInt(this.dropEnd.attr('data-hour') * 60) + parseInt(this.dropEnd.attr('data-minute')); - e.data.duration = ((eT - sT)/60) * 3600; - - - - if(event_widget) - { - event_widget.options.value.duration = e.data.duration; - } - $j(this).trigger(e); - - - // That cleared the resize handles, so remove for re-creation... - $j(this).resizable('destroy'); - } - // Clear the helper, re-draw - event_widget.set_value(event_widget.options.value); - }, - - /** - * Triggered during the resize, on the drag of the resize handler - * - * @param {event} event - * @param {Object} ui - */ - resize:function(event, ui) - { - // Add 5px to make sure it doesn't land right on the edge of a div - drag_helper.call(this,event,ui.element[0],ui.helper.outerHeight()+5); - } - }); - }); - // Customize and override some draggable settings this.div.on('dragcreate','.calendar_calEvent:not(.rowNoEdit)', function(event,ui) { $j(this).draggable('option','cursorAt',false); @@ -1192,7 +1101,7 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz * Set which user owns this. Owner is passed along to the individual * days. * - * @param {number} _owner Account ID + * @param {number|number[]} _owner Account ID * @returns {undefined} */ set_owner: function(_owner) @@ -1203,7 +1112,11 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz this.owner.set_value(typeof _owner == "string" || typeof _owner == "number" ? _owner : jQuery.extend([],_owner)); this.options.owner = _owner;//this.owner.getValue(); - if(old !== this.options.owner && this.isAttached()) + if(this.isAttached() && ( + typeof old == "number" && typeof _owner == "number" && old !== this.options.owner || + // Array of ids will not compare as equal + ((typeof old === 'object' || typeof _owner === 'object') && old.toString() !== _owner.toString()) + )) { this.invalidate(true); } diff --git a/calendar/templates/default/sidebox.xet b/calendar/templates/default/sidebox.xet index d3779d2665..0abd19efdd 100644 --- a/calendar/templates/default/sidebox.xet +++ b/calendar/templates/default/sidebox.xet @@ -30,7 +30,7 @@ Egroupware - +