diff --git a/calendar/js/app.js b/calendar/js/app.js index daa8ee699b..1990d647d5 100644 --- a/calendar/js/app.js +++ b/calendar/js/app.js @@ -995,7 +995,10 @@ 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; + if(_action.parent.data && _action.parent.data.nextmatch) + { + 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]+)$/); var backup = _action.data; @@ -1005,7 +1008,7 @@ app.classes.calendar = AppJS.extend( return; } matches = id.match(/^([a-z_-]+)([0-9]+)/i); - if (matches) + if (matches && js_integration_data) { var app = matches[1]; _action.data.url = window.egw_webserverUrl+'/index.php?'; @@ -2346,7 +2349,7 @@ jQuery.extend(app.classes.calendar,{ 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); + (startDate == endDate ? '' : ' - ' + date(egw.preference('dateformat'),endDate)); }, etemplates: ['calendar.planner'], group_by: function(state) { diff --git a/calendar/js/calendar_favorite_portlet.js b/calendar/js/calendar_favorite_portlet.js index 494d0d8aa3..0c5c952406 100644 --- a/calendar/js/calendar_favorite_portlet.js +++ b/calendar/js/calendar_favorite_portlet.js @@ -32,7 +32,11 @@ observer: function(_msg, _app, _id, _type, _msg_type, _targetapp) var event = egw.dataGetUIDdata('calendar::'+_id); if(event && event.data && event.data.date) { - var new_cache_id = app.classes.calendar._daywise_cache_id(event.data.date); + var new_cache_id = app.classes.calendar._daywise_cache_id( + event.data.date, + // Make sure to use the right owner, not current calendar state + this.portlet.settings.favorite.state.owner || '' + ); var daywise = egw.dataGetUIDdata(new_cache_id); daywise = daywise ? daywise.data : []; if(_type === 'delete') diff --git a/calendar/js/et2_widget_daycol.js b/calendar/js/et2_widget_daycol.js index 55aa2c8388..bc584d1ade 100644 --- a/calendar/js/et2_widget_daycol.js +++ b/calendar/js/et2_widget_daycol.js @@ -104,9 +104,7 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea destroy: function() { this._super.apply(this, arguments); - // In some cases, app.calendar code is unloaded before all the etemplates are destroyed - //egw.dataUnregisterUID(app.calendar.DAYWISE_CACHE_ID+'::'+this.options.date); - egw.dataUnregisterUID('calendar_daywise::'+this.options.date); + egw.dataUnregisterUID(app.classes.calendar._daywise_cache_id(this.options.date,this.options.owner),false,this); }, /** @@ -759,7 +757,7 @@ jQuery.extend(et2_calendar_daycol, egw.window.et2_calendar_daycol.holiday_cache[year] = egw.json( 'calendar_timegrid_etemplate_widget::ajax_get_holidays', [year] - ).sendRequest(); + ).sendRequest(true); } cache = egw.window.et2_calendar_daycol.holiday_cache[year]; if(typeof cache.done == 'function') @@ -769,7 +767,11 @@ jQuery.extend(et2_calendar_daycol, egw.window.et2_calendar_daycol.holiday_cache[this.year] = response.response[0].data||undefined; egw.window.setTimeout(jQuery.proxy(function() { - this.widget.day_class_holiday(); + // Make sure widget hasn't been destroyed while we wait + if(typeof this.widget.free == 'undefined') + { + this.widget.day_class_holiday(); + } },this),1); },{widget:widget,year:year})); return {}; diff --git a/calendar/js/et2_widget_event.js b/calendar/js/et2_widget_event.js index 7c27f1a451..6785951fad 100644 --- a/calendar/js/et2_widget_event.js +++ b/calendar/js/et2_widget_event.js @@ -480,7 +480,7 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM], // objects widget_object = objectManager.insertObject(false, new egwActionObject( 'calendar::'+this.id, objectManager, new et2_event_action_object_impl(this,this.getDOMNode()), - objectManager.manager.getActionById(this.id) || objectManager.manager + this._actionManager || objectManager.manager.getActionById(this.id) || objectManager.manager )); } else diff --git a/calendar/js/et2_widget_planner.js b/calendar/js/et2_widget_planner.js index d68a874e97..500c7e1618 100644 --- a/calendar/js/et2_widget_planner.js +++ b/calendar/js/et2_widget_planner.js @@ -110,12 +110,19 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize this.update_timer = null; this.setDOMNode(this.div[0]); + + this.registeredCallbacks = []; }, destroy: function() { this._super.apply(this, arguments); this.div.off(); + for(var i = 0; i < this.registeredCallbacks.length; i++) + { + egw.dataUnregisterUID(this.registeredCallbacks[i],false,this); + } + // date_helper has no parent, so we must explicitly remove it this.date_helper.destroy(); this.date_helper = null; @@ -140,9 +147,121 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize // and get ours this._link_actions(this.options.actions || this._parent.options.actions || []); + // Automatically bind drag and resize for every event using jQuery directly + // - no action system - + var planner = this; + + /** + * If user puts the mouse over an event, then we'll set up resizing so + * they can adjust the length. Should be a little better on resources + * than binding it for every calendar event. + */ + this.div.on('mouseover', '.calendar_calEvent:not(.ui-resizable):not(.rowNoEdit)', function() { + + // Load the event + planner._get_event_info(this); + var that = this; + + //Resizable event handler + $j(this).resizable + ({ + distance: 10, + grid: [5, 10000], + autoHide: false, + handles: 'e', + 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 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 = planner._get_event_info(this); + var event_widget = planner.getWidgetById('event_'+event_data.id); + var sT = event_widget.options.value.start_m; + if (typeof this.dropEnd != 'undefined') + { + var eT = parseInt(this.dropEnd.getUTCHours() * 60) + parseInt(this.dropEnd.getUTCMinutes()); + e.data.duration = ((eT - sT)/60) * 3600; + + if(event_widget) + { + event_widget.options.value.end_m = eT; + event_widget.options.value.duration = e.data.duration; + } + + // Leave the helper there until the update is done + var loading = ui.helper.clone().appendTo(ui.helper.parent()); + + // and add a loading icon so user knows something is happening + $j('.calendar_timeDemo',loading).after('
'); + + $j(this).trigger(e); + + // That cleared the resize handles, so remove for re-creation... + $j(this).resizable('destroy'); + + // Remove loading, done or not + loading.remove(); + } + // Clear the helper, re-draw + if(event_widget) + { + event_widget._parent.position_event(event_widget); + } + }, + + /** + * Triggered during the resize, on the drag of the resize handler + * + * @param {event} event + * @param {Object} ui + */ + resize:function(event, ui) + { + planner._drag_helper(this,{ + top:ui.position.top, + left: ui.position.left + ui.helper.width() + },ui.helper.outerHeight()); + } + }); + }); + + // Customize and override some draggable settings + this.div.on('dragcreate','.calendar_calEvent:not(.rowNoEdit)', function(event,ui) { + $j(this).draggable('option','cursorAt',false); + }) + .on('dragstart', '.calendar_calEvent', function(event,ui) { + $j('.calendar_calEvent',ui.helper).width($j(this).width()) + .height($j(this).outerHeight()) + .css('top', '').css('left','') + .appendTo(ui.helper); + ui.helper.width($j(this).width()); + }); return true; }, - + /** * These handle the differences between the different group types. * They provide the different titles, labels and grouping @@ -379,8 +498,6 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize if(this.update_timer === null) { this.update_timer = window.setTimeout(jQuery.proxy(function() { - this.widget.update_timer = null; - this.widget.value = this.widget._fetch_data(); this.widget._drawGrid(); @@ -395,6 +512,7 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize { this.widget.change(); } + this.widget.update_timer = null; },{widget:this,"trigger":trigger}),ET2_GRID_INVALIDATE_TIMEOUT); } }, @@ -677,10 +795,14 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize { // prev. week var left = new Date(t); + left.setUTCHours(0); + left.setUTCMinutes(0); left.setUTCDate(left.getUTCDate() - 7); // next week var right = new Date(t); + right.setUTCHours(0); + right.setUTCMinutes(0); right.setUTCDate(right.getUTCDate() + 7); title = this._scroll_button('left',left.toJSON()) + title + this._scroll_button('right',right.toJSON()); @@ -871,31 +993,109 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize */ _link_actions: function(actions) { - // Get the parent? Might be a grid row, might not. Either way, it is - // just a container with no valid actions - var objectManager = egw_getAppObjectManager(true); - var parent = this; - var om = false; - while(parent && om !== objectManager) + if(!this._actionObject) { - if(parent.id && objectManager.getObjectById(parent.id)) + // Get the parent? Might be a grid row, might not. Either way, it is + // just a container with no valid actions + var objectManager = egw_getObjectManager(this.getInstanceManager().app,true,1); + objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId,2) || objectManager; + var parent = objectManager.getObjectById(this.id,3) || objectManager.getObjectById(this._parent.id,3) || objectManager; + if(!parent) { - om = objectManager.getObjectById(parent.id); - break; + debugger; + egw.debug('error','No parent objectManager found') + return; + } + + for(var i = 0; i < parent.children.length; i++) + { + var parent_finder = jQuery('#'+this.div.id, parent.children[i].iface.doGetDOMNode()); + if(parent_finder.length > 0) + { + parent = parent.children[i]; + break; + } } - parent = parent.getParent(); - } - if(!om) - { - om = objectManager.getObjectById(this.getInstanceManager().uniqueId); } - if(!om) return; + // This binds into the egw action system. Most user interactions (drag to move, resize) + // are handled internally using jQuery directly. + var widget_object = this._actionObject || parent.getObjectById(this.id); - var widget_object = om.getObjectById(this.id); - if(widget_object == null) + var aoi = new et2_action_object_impl(this,this.getDOMNode()); + + aoi.doTriggerEvent = function(_event, _data) { + + // Determine target node + var event = _data.event || false; + if(!event) return; + if(_data.ui.draggable.hasClass('rowNoEdit')) return; + + /* + We have to handle the drop in the normal event stream instead of waiting + for the egwAction system so we can get the helper, and destination + */ + if(event.type === 'drop') + { + this.getWidget()._event_drop.call($j('.calendar_d-n-d_timeCounter',_data.ui.helper)[0],this.getWidget(),event, _data.ui); + } + var drag_listener = function(event, ui) { + aoi.getWidget()._drag_helper($j('.calendar_d-n-d_timeCounter',ui.helper)[0],{ + top:ui.position.top, + left: ui.position.left - $j(this).parent().offset().left + },0); + }; + var time = $j('.calendar_d-n-d_timeCounter',_data.ui.helper); + switch(_event) + { + // Triggered once, when something is dragged into the timegrid's div + case EGW_AI_DRAG_OVER: + // Listen to the drag and update the helper with the time + // This part lets us drag between different timegrids + _data.ui.draggable.on('drag.et2_timegrid'+widget_object.id, drag_listener); + _data.ui.draggable.on('dragend.et2_timegrid'+widget_object.id, function() { + _data.ui.draggable.off('drag.et2_timegrid' + widget_object.id); + }); + if(time.length) + { + // The out will trigger after the over, so we count + time.data('count',time.data('count')+1); + } + else + { + _data.ui.helper.prepend('
'); + } + + break; + + // Triggered once, when something is dragged out of the timegrid + case EGW_AI_DRAG_OUT: + // Stop listening + _data.ui.draggable.off('drag.et2_timegrid'+widget_object.id); + // Remove any highlighted time squares + $j('[data-date]',this.doGetDOMNode()).removeClass("ui-state-active"); + + // Out triggers after the over, count to not accidentally remove + time.data('count',time.data('count')-1); + if(time.length && time.data('count') <= 0) + { + time.remove(); + } + break; + } + }; + + if (widget_object == null) { + // Add a new container to the object manager which will hold the widget + // objects + widget_object = parent.insertObject(false, new egwActionObject( + this.id, parent, aoi, + this._actionManager || parent.manager.getActionById(this.id) || parent.manager + ),EGW_AO_FLAG_IS_CONTAINER); + } + else { - widget_object = om.addObject(this.id, null, EGW_AO_FLAG_IS_CONTAINER); + widget_object.setAOI(aoi); } // Go over the widget & add links - this is where we decide which actions are // 'allowed' for this widget at this time @@ -957,21 +1157,22 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize { return; } - - // Link the entries - egw.json(self.egw().getAppName()+".etemplate_widget_link.ajax_link.etemplate", - dropped.id.split('::').concat([links]), - function(result) { - if(result) - { - this.egw().message('Linked'); - } - }, - self, - true, - self - ).sendRequest(); - + if(links.length && dropped && dropped.iface.getWidget() && dropped.iface.getWidget().instanceOf(et2_calendar_event)) + { + // Link the entries + egw.json(self.egw().getAppName()+".etemplate_widget_link.ajax_link.etemplate", + dropped.id.split('::').concat([links]), + function(result) { + if(result) + { + this.egw().message('Linked'); + } + }, + self, + true, + self + ).sendRequest(); + } },true); } if(actionLinks.indexOf(drop_action.id) < 0) @@ -1027,6 +1228,80 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize return action_links; }, + /** + * Show the current time while dragging + * Used for resizing as well as drag & drop + */ + _drag_helper: function(element, position ,height) + { + var time = this._get_time_from_position(position.left, position.top); + element.dropEnd = time; + var formatted_time = jQuery.datepicker.formatTime( + egw.preference("timeformat") == 12 ? "h:mmtt" : "HH:mm", + { + hour: time.getUTCHours(), + minute: time.getUTCMinutes(), + seconds: 0, + timezone: 0 + }, + {"ampm": (egw.preference("timeformat") == "12")} + ); + + element.innerHTML = '
'+formatted_time+'
'; + + //$j(element).width($j(helper).width()); + }, + + /** + * Handler for dropping an event on the timegrid + */ + _event_drop: function(planner, event,ui) { + var e = new jQuery.Event('change'); + e.originalEvent = event; + e.data = {start: 0}; + if (typeof this.dropEnd != 'undefined') + { + var drop_date = this.dropEnd.toJSON() ||false; + + var event_data = planner._get_event_info(ui.draggable); + var event_widget = planner.getWidgetById('event_'+event_data.id); + if(event_widget) + { + event_widget._parent.date_helper.set_value(drop_date); + event_widget.options.value.start = new Date(event_widget._parent.date_helper.getValue()); + + // Leave the helper there until the update is done + var loading = ui.helper.clone().appendTo(ui.helper.parent()); + // and add a loading icon so user knows something is happening + $j('.calendar_timeDemo',loading).after('
'); + + event_widget.recur_prompt(function(button_id) { + if(button_id === 'cancel' || !button_id) return; + //Get infologID if in case if it's an integrated infolog event + if (event_data.app === 'infolog') + { + // If it is an integrated infolog event we need to edit infolog entry + egw().json('stylite_infolog_calendar_integration::ajax_moveInfologEvent', + [event_data.id, event_widget.options.value.start||false], + function() {loading.remove();} + ).sendRequest(true); + } + else + { + //Edit calendar event + egw().json('calendar.calendar_uiforms.ajax_moveEvent', [ + button_id==='series' ? event_data.id : event_data.app_id,event_data.owner, + event_widget.options.value.start, + planner.options.owner||egw.user('account_id') + ], + function() { loading.remove();} + ).sendRequest(true); + } + }); + } + } + }, + /** * Use the egw.data system to get data from the calendar list for the * selected time span. @@ -1036,6 +1311,12 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize { var value = []; var fetch = false; + for(var i = 0; i < this.registeredCallbacks.length; i++) + { + egw.dataUnregisterUID(this.registeredCallbacks[i],false,this); + } + this.registeredCallbacks.splice(0,this.registeredCallbacks.length); + // Remember previous day to avoid multi-days duplicating var last_data = []; @@ -1066,7 +1347,17 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize else { fetch = true; + // Assume it's empty, if there is data it will be filled later + egw.dataStoreUID(cache_id, []); } + this.registeredCallbacks.push(cache_id); + egw.dataRegisterUID(cache_id, function(data) { + if(data && data.length) + { + this.invalidate(false); + } + }, this, this.getInstanceManager().execId,this.id); + t.setUTCDate(t.getUTCDate() + 1); } while(t < end); @@ -1279,7 +1570,7 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize if (this.onevent_change) { var event_data = this._get_event_info(dom_node); - var event_widget = this.getWidgetById(event_data.id); + var event_widget = this.getWidgetById('event_'+event_data.id); et2_calendar_event.recur_prompt(event_data, jQuery.proxy(function(button_id, event_data) { // No need to continue if(button_id === 'cancel') return false; @@ -1343,7 +1634,7 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize { // Click on a header, we can go there _ev.data = jQuery.extend({},_ev.target.parentNode.dataset, _ev.target.dataset); - debugger; + // Handle it locally var old_start = this.options.start_date; if(_ev.data.date) @@ -1411,15 +1702,15 @@ var et2_calendar_planner = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResize x = Math.round(x); y = Math.round(y); - var nodes = $j('.calendar_calAddEvent[data-hour]',this.div).removeClass('drop-hover').filter(function() { - var offset = $j(this).offset(); - var range={x:[offset.left,offset.left+$j(this).outerWidth()],y:[offset.top,offset.top+$j(this).outerHeight()]}; - - var i = (x >=range.x[0] && x <= range.x[1]) && (y >= range.y[0] && y <= range.y[1]); - return i; - }).addClass("drop-hover"); - - return nodes; + + var rel_x = Math.min(x / $j('.calendar_eventRows',this.div).width(),1); + + var rel_time = (new Date(this.options.end_date) - new Date(this.options.start_date))*rel_x/1000; + this.date_helper.set_value(this.options.start_date); + var interval = egw.preference('interval','calendar') || 30; + this.date_helper.set_minutes(Math.round(rel_time / (60 * interval))*interval); + + return new Date(this.date_helper.getValue()); }, /** diff --git a/calendar/js/et2_widget_timegrid.js b/calendar/js/et2_widget_timegrid.js index 27a59fb2ec..61ebf1466d 100644 --- a/calendar/js/et2_widget_timegrid.js +++ b/calendar/js/et2_widget_timegrid.js @@ -651,6 +651,7 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz aoi.doTriggerEvent = function(_event, _data) { // Determine target node + debugger; var event = _data.event || false; if(!event) return; if(_data.ui.draggable.hasClass('rowNoEdit')) return; @@ -711,7 +712,7 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz // objects widget_object = parent.insertObject(false, new egwActionObject( this.id, parent, aoi, - parent.manager.getActionById(this.id) || parent.manager + this._actionManager|| parent.manager.getActionById(this.id) || parent.manager )); } else diff --git a/calendar/templates/default/app.css b/calendar/templates/default/app.css index 2cf3fbbb38..ecd8e95319 100644 --- a/calendar/templates/default/app.css +++ b/calendar/templates/default/app.css @@ -676,6 +676,7 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget border: dashed white 1px; border-radius: 1px; } + .calendar_timeDemo { position: absolute; bottom: 0; @@ -683,6 +684,7 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget background-color: #808080; color: white; width: 100%; + min-width: 5em; align: center; border:dashed white 1px; border-radius: 1px; diff --git a/calendar/templates/pixelegg/app.css b/calendar/templates/pixelegg/app.css index bbca91db8f..8e8fa52895 100755 --- a/calendar/templates/pixelegg/app.css +++ b/calendar/templates/pixelegg/app.css @@ -11,7 +11,7 @@ * @package calendar * @version $Id$ */ -/* $Id: app.css 53111 2015-07-15 16:29:10Z nathangray $ */ +/* $Id: app.css 53324 2015-08-06 17:14:20Z nathangray $ */ /*Media print classes*/ @media print { .th td, @@ -685,6 +685,7 @@ e.g. the div with class calendar_calTimeGrid is generated by the timeGridWidget background-color: #808080; color: white; width: 100%; + min-width: 5em; align: center; border: dashed white 1px; border-radius: 1px;