From 115f420996fe6cdc71b2c8f860363d3534970b78 Mon Sep 17 00:00:00 2001 From: nathangray Date: Thu, 14 Jul 2016 10:58:54 -0600 Subject: [PATCH] Implement drag to invite / move to for planner by user view --- calendar/js/et2_widget_event.js | 2 +- calendar/js/et2_widget_planner.js | 160 ++++++++++++++++++- calendar/js/et2_widget_planner_row.js | 221 ++++++++++++++++++++++++++ 3 files changed, 375 insertions(+), 8 deletions(-) diff --git a/calendar/js/et2_widget_event.js b/calendar/js/et2_widget_event.js index 711143949a..28773edf44 100644 --- a/calendar/js/et2_widget_event.js +++ b/calendar/js/et2_widget_event.js @@ -840,7 +840,7 @@ var et2_calendar_event = (function(){ "use strict"; return et2_valueWidget.exten if(!this._actionObject) { // Get the top level element - timegrid or so - var objectManager = this.getParent().getParent()._actionObject || + var objectManager = this.getParent()._actionObject || this.getParent().getParent()._actionObject || egw_getAppObjectManager(true).getObjectById(this._parent._parent._parent.id) || egw_getAppObjectManager(true); this._actionObject = objectManager.getObjectById('calendar::'+this.options.value.row_id); } diff --git a/calendar/js/et2_widget_planner.js b/calendar/js/et2_widget_planner.js index a5ee654ae7..eee4fe0834 100644 --- a/calendar/js/et2_widget_planner.js +++ b/calendar/js/et2_widget_planner.js @@ -236,10 +236,15 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e */ resize:function(event, ui) { - planner._drag_helper(this,{ - top:ui.position.top, - left: ui.position.left + ui.helper.width() - },ui.helper.outerHeight()); + if(planner.options.group_by == 'month') + { + var position = {left: event.clientX, top: event.clientY}; + } + else + { + var position = {top:ui.position.top, left: ui.position.left + ui.helper.width()}; + } + planner._drag_helper(this,position,ui.helper.outerHeight()); } }); }) @@ -1215,6 +1220,51 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e var aoi = new et2_action_object_impl(this,this.getDOMNode()); + /** + * Determine if we allow a dropped event to use the invite/change actions, + * and enable or disable them appropriately + * + * @param {egwAction} action + * @param {et2_calendar_event} event The event widget being dragged + * @param {egwActionObject} target Planner action object + */ + var _invite_enabled = function(action, event, target) + { + var event = event.iface.getWidget(); + var planner = target.iface.getWidget() || false; + //debugger; + if(event === planner || !event || !planner || + !event.options || !event.options.value.participants || !planner.options.owner + ) + { + return false; + } + var owner_match = false; + var own_row = false; + + for(var id in event.options.value.participants) + { + planner.iterateOver(function(row) { + // Check scroll section or header section + if(row.div.hasClass('drop-hover') || row.div.has(':hover')) + { + owner_match = owner_match || row.node.dataset[planner.options.group_by] === ''+id; + own_row = (row === event.getParent()); + } + }, this, et2_calendar_planner_row); + + } + var enabled = !owner_match && + // Not inside its own row + !own_row; + + widget_object.getActionLink('invite').enabled = enabled; + widget_object.getActionLink('change_participant').enabled = enabled; + + // If invite or change participant are enabled, drag is not + widget_object.getActionLink('egw_link_drop').enabled = !enabled; + }; + aoi.doTriggerEvent = function(_event, _data) { // Determine target node @@ -1294,7 +1344,7 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e this._init_links_dnd(widget_object.manager, action_links); - widget_object.updateActionLinks(action_links); + //widget_object.updateActionLinks(action_links); this._actionObject = widget_object; }, @@ -1311,6 +1361,8 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e var self = this; var drop_action = mgr.getActionById('egw_link_drop'); + var drop_change_participant = mgr.getActionById('change_participant'); + var drop_invite = mgr.getActionById('invite'); var drag_action = mgr.getActionById('egw_link_drag'); // Check if this app supports linking @@ -1371,11 +1423,83 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e ).sendRequest(); } },true); + + drop_action.acceptedTypes = ['default','link']; + drop_action.hideOnDisabled = true; + + // Create the drop action for moving events between planner rows + var invite_action = function(action, source, target) { + + // Extract link IDs + var links = []; + var id = ''; + for(var i = 0; i < source.length; i++) + { + // Check for no ID (invalid) or same manager (dragging an event) + if(!source[i].id) continue; + if(source[i].manager === target.manager) + { + + // Find the row, could have dropped on an event + var row = target.iface.getWidget(); + while(target.parent && row.instanceOf && !row.instanceOf(et2_calendar_planner_row)) + { + target = target.parent; + row = target.iface.getWidget(); + } + + // Leave the helper there until the update is done + var loading = action.ui.helper.clone(true).appendTo(jQuery('body')); + + // and add a loading icon so user knows something is happening + if(jQuery('.calendar_timeDemo',loading).length == 0) + { + jQuery('.calendar_calEventHeader',loading).addClass('loading'); + } + else + { + jQuery('.calendar_timeDemo',loading).after('
'); + } + + var event_data = egw.dataGetUIDdata(source[i].id).data; + et2_calendar_event.recur_prompt(event_data, function(button_id) { + if(button_id === 'cancel' || !button_id) + { + return; + } + var add_owner = jQuery.extend([],row.node.dataset.participants); + + egw().json('calendar.calendar_uiforms.ajax_invite', [ + button_id==='series' ? event_data.id : event_data.app_id, + add_owner, + action.id === 'change_participant' ? + jQuery.extend([],source[i].iface.getWidget().getParent().node.dataset.participants) : + [] + ], + function() { loading.remove();} + ).sendRequest(true); + }); + // Ok, stop. + return false; + } + } + }; + + drop_change_participant = mgr.addAction('drop', 'change_participant', egw.lang('Move to'), egw.image('participant'), invite_action,true); + drop_change_participant.acceptedTypes = ['calendar']; + drop_change_participant.hideOnDisabled = true; + + drop_invite = mgr.addAction('drop', 'invite', egw.lang('Invite'), egw.image('participant'), invite_action,true); + drop_invite.acceptedTypes = ['calendar']; + drop_invite.hideOnDisabled = true; } if(actionLinks.indexOf(drop_action.id) < 0) { actionLinks.push(drop_action.id); } + actionLinks.push(drop_invite.id); + actionLinks.push(drop_change_participant.id); + // Accept other links, and files dragged from the filemanager // This does not handle files dragged from the desktop. They are // handled by et2_nextmatch, since it needs DOM stuff @@ -1400,7 +1524,7 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e { actionLinks.push(drag_action.id); } - drag_action.set_dragType('link'); + drag_action.set_dragType(['link','calendar']); }, /** @@ -1864,7 +1988,29 @@ var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.e else { // Find the correct row so we know which month, then get the offset - var row = jQuery(document.elementFromPoint(x, y)).closest('.calendar_plannerRowWidget'); + var hidden_nodes = []; + var row = null; + // Hide any drag or tooltips that may interfere + do + { + row = document.elementFromPoint(x, y); + if(this.div.has(row).length == 0) + { + hidden_nodes.push(jQuery(row).hide()); + } + else + { + break; + } + } while(row.nodeName !== 'BODY'); + // Restore hidden nodes + for(var i = 0; i < hidden_nodes.length; i++) + { + hidden_nodes[i].show(); + } + row = jQuery(row).closest('.calendar_plannerRowWidget'); + + var row_widget = null; for(var i = 0; i < this._children.length && row.length > 0; i++) { diff --git a/calendar/js/et2_widget_planner_row.js b/calendar/js/et2_widget_planner_row.js index 7f11b6a780..0222e12bd4 100644 --- a/calendar/js/et2_widget_planner_row.js +++ b/calendar/js/et2_widget_planner_row.js @@ -73,6 +73,8 @@ var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget this.set_label(this.options.label); this._draw(); + + this._link_actions([]); return true; }, @@ -96,6 +98,225 @@ var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget } }, + /** + * Link the actions to the DOM nodes / widget bits. + * + * @param {object} actions {ID: {attributes..}+} map of egw action information + */ + _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_getObjectManager(this.getInstanceManager().app,true,1); + objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId,2) || objectManager; + var parent = objectManager.getObjectById(this.id,1) || objectManager.getObjectById(this._parent.id,1) || objectManager; + if(!parent) + { + debugger; + egw.debug('error','No parent objectManager found'); + 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 aoi = new et2_action_object_impl(this,this.getDOMNode()); + var planner = this.getParent(); + + for(var i = 0; i < parent.children.length; i++) + { + var parent_finder = jQuery(parent.children[i].iface.doGetDOMNode()).find(this.div); + if(parent_finder.length > 0) + { + parent = parent.children[i]; + break; + } + } + + // Determine if we allow a dropped event to use the invite/change actions + var _invite_enabled = function(action, event, target) + { + var event = event.iface.getWidget(); + var row = target.iface.getWidget() || false; + if(event === row || !event || !row || + !event.options || !event.options.value.participants + ) + { + return false; + } + + var owner_match = false; + var own_row = event.getParent() === row; + + for(var id in event.options.value.participants) + { + owner_match = owner_match || row.node.dataset.participants === ''+id; + } + + var enabled = !owner_match && + // Not inside its own timegrid + !own_row; + + widget_object.getActionLink('invite').enabled = enabled; + widget_object.getActionLink('change_participant').enabled = enabled; + + // If invite or change participant are enabled, drag is not + widget_object.getActionLink('egw_link_drop').enabled = !enabled; + }; + + 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' && widget_object.getActionLink('egw_link_drop').enabled) + { + this.getWidget().getParent()._event_drop.call( + jQuery('.calendar_d-n-d_timeCounter',_data.ui.helper)[0], + this.getWidget().getParent(), event, _data.ui, + this.getWidget() + ); + } + var drag_listener = function(_event, ui) { + if(planner.options.group_by === 'month') + { + var position = {left: _event.clientX, top: _event.clientY}; + } + else + { + var position = {top:ui.position.top, left: ui.position.left - jQuery(this).parent().offset().left}; + } + aoi.getWidget().getParent()._drag_helper( + jQuery('.calendar_d-n-d_timeCounter',ui.helper)[0], + position,0 + ); + + var event = _data.ui.draggable.data('selected')[0]; + if(!event || event.id && event.id.indexOf('calendar') !== 0) + { + event = false; + } + if(event) + { + _invite_enabled( + widget_object.getActionLink('invite').actionObj, + event, + widget_object + ); + } + }; + var time = jQuery('.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_row'+widget_object.id, drag_listener); + _data.ui.draggable.on('dragend.et2_timegrid_row'+widget_object.id, function() { + _data.ui.draggable.off('drag.et2_timegrid_row' + widget_object.id); + }); + widget_object.iface.getWidget().div.addClass('drop-hover'); + + // Disable invite / change actions for same calendar or already participant + var event = _data.ui.draggable.data('selected')[0]; + if(!event || event.id && event.id.indexOf('calendar') !== 0) + { + event = false; + } + if(event) + { + _invite_enabled( + widget_object.getActionLink('invite').actionObj, + event, + widget_object + ); + } + 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_row'+widget_object.id); + // Remove highlight + widget_object.iface.getWidget().div.removeClass('drop-hover'); + + // 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 + )); + } + else + { + widget_object.setAOI(aoi); + } + this._actionObject = widget_object; + + // Delete all old objects + widget_object.clear(); + widget_object.unregisterActions(); + + // Go over the widget & add links - this is where we decide which actions are + // 'allowed' for this widget at this time + var action_links = this._get_action_links(actions); + + this.getParent()._init_links_dnd(widget_object.manager, action_links); + + widget_object.updateActionLinks(action_links); + }, + + /** + * Get all action-links / id's of 1.-level actions from a given action object + * + * Here we are only interested in drop events. + * + * @param actions + * @returns {Array} + */ + _get_action_links: function(actions) + { + var action_links = []; + // TODO: determine which actions are allowed without an action (empty actions) + for(var i in actions) + { + var action = actions[i]; + if(action.type == 'drop') + { + action_links.push(typeof action.id != 'undefined' ? action.id : i); + } + } + return action_links; + }, + /** * Draw the individual divs for weekends and events */