Compare commits

...

11 Commits

Author SHA1 Message Date
Hadi Nategh
5ce0ce73de WIP getting calendar dnd working with new touch-dnd plugin 2021-08-10 16:18:23 +02:00
Hadi Nategh
19cf0faf60 Replace jquery-ui dnd with egw-touch-dnd package 2021-08-10 11:53:51 +02:00
Hadi Nategh
8ca0d0c197 Fix broken styling of sidebox app lists 2021-08-04 16:49:44 +02:00
Hadi Nategh
19135ab904 Replace nm column jquery-ui sorting with sortablejs 2021-08-04 14:48:02 +02:00
Hadi Nategh
41ccb921be Fix broken widget tree actions because of unexpected DOM Element object 2021-08-04 11:50:43 +02:00
Hadi Nategh
76d1d570f6 Get favorites list sorting working with Sortablejs 2021-08-04 11:26:57 +02:00
Hadi Nategh
c56812a42f Get et2_grid sortable working with sortablejs 2021-08-03 15:59:22 +02:00
Ralf Becker
8c8d8c2de9 allow to explicit register callbacks for the broadcasted push messages
egw.registerPush(Function)

The callback should have a bound context, if it requires one!
2021-08-02 15:37:44 +02:00
Hadi Nategh
24d5f01719 Get fw sidebox sortable items working with sortablejs and removes the jquery.sortable 2021-08-02 15:36:52 +02:00
Hadi Nategh
90b737bc18 Remove the tap-and-hold files 2021-07-29 15:10:12 +02:00
Hadi Nategh
063dff4c8f Remove tab-and-hold jquery plugin and use native touch events to implement something similar 2021-07-29 14:57:10 +02:00
16 changed files with 383 additions and 3999 deletions

View File

@ -19,7 +19,7 @@
import {egwAction,egwActionImplementation} from "./egw_action.js"; import {egwAction,egwActionImplementation} from "./egw_action.js";
import {getPopupImplementation} from "./egw_action_popup.js"; import {getPopupImplementation} from "./egw_action_popup.js";
import {EGW_AI_DRAG_OUT, EGW_AI_DRAG_OVER, EGW_AO_EXEC_THIS} from "./egw_action_constants.js"; import {EGW_AI_DRAG_OUT, EGW_AI_DRAG_OVER, EGW_AO_EXEC_THIS} from "./egw_action_constants.js";
import 'egw-touch-dnd/touch-dnd.js';
/** /**
* Register the drag and drop handlers * Register the drag and drop handlers
*/ */
@ -317,12 +317,9 @@ export function egwDragActionImplementation()
} }
}); });
} }
jQuery(node).draggable( let $draggable = jQuery(node).draggable({
{ delay: 250,
"distance": 20, clone: function(e) {
"cursor": "move",
"cursorAt": { top: -12, left: -12 },
"helper": function(e) {
// The helper function is called before the start function // The helper function is called before the start function
// is evoked. Call the given callback function. The callback // is evoked. Call the given callback function. The callback
// function will gather the selected elements and action links // function will gather the selected elements and action links
@ -351,9 +348,6 @@ export function egwDragActionImplementation()
// Return an empty div if the helper dom node is not set // Return an empty div if the helper dom node is not set
return ai.defaultDDHelper(ai.selected);//jQuery(document.createElement("div")).addClass('et2_egw_action_ddHelper'); return ai.defaultDDHelper(ai.selected);//jQuery(document.createElement("div")).addClass('et2_egw_action_ddHelper');
}, },
"start": function(e) {
return ai.helper != null;
},
revert: function(valid) revert: function(valid)
{ {
var dTarget = this; var dTarget = this;
@ -388,14 +382,11 @@ export function egwDragActionImplementation()
}, },
// Solves problem with scroll position changing in the grid // Solves problem with scroll position changing in the grid
// component // component
"refreshPositions": true, // "refreshPositions": true,
"scroll": false, });
//"containment": "document", $draggable.on('draggable:start', function(e) {
"iframeFix": true, return ai.helper != null;
"delay": 300 });
}
);
return true; return true;
} }
@ -550,19 +541,18 @@ export function egwDropActionImplementation()
if (node) if (node)
{ {
jQuery(node).droppable( let $droppable = jQuery(node).droppable({
{ accept: function(_draggable) {
"accept": function(_draggable) {
if (typeof _draggable.data("ddTypes") != "undefined") if (typeof _draggable.data("ddTypes") != "undefined")
{ {
var accepted = self._fetchAccepted( let accepted = self._fetchAccepted(
_callback.call(_context, "links", self, EGW_AO_EXEC_THIS)); _callback.call(_context, "links", self, EGW_AO_EXEC_THIS));
// Check whether all drag types of the selected objects // Check whether all drag types of the selected objects
// are accepted // are accepted
var ddTypes = _draggable.data("ddTypes"); let ddTypes = _draggable.data("ddTypes");
for (var i = 0; i < ddTypes.length; i++) for (let i = 0; i < ddTypes.length; i++)
{ {
if (accepted.indexOf(ddTypes[i]) != -1) if (accepted.indexOf(ddTypes[i]) != -1)
{ {
@ -573,21 +563,23 @@ export function egwDropActionImplementation()
return false; return false;
} }
}, },
"drop": function(event, ui) { hoverClass: "drop-hover",
var draggable = ui.draggable; });
var ddTypes = draggable.data("ddTypes"); $droppable.on('droppable:drop', function(event, ui) {
var selected = draggable.data("selected"); let draggable = ui.item;
let ddTypes = draggable.data("ddTypes");
let selected = draggable.data("selected");
var links = _callback.call(_context, "links", self, EGW_AO_EXEC_THIS); let links = _callback.call(_context, "links", self, EGW_AO_EXEC_THIS);
// Disable all links which only accept types which are not // Disable all links which only accept types which are not
// inside ddTypes // inside ddTypes
for (var k in links) for (let k in links)
{ {
var accepted = links[k].actionObj.acceptedTypes; let accepted = links[k].actionObj.acceptedTypes;
var enabled = false; let enabled = false;
for (var i = 0; i < ddTypes.length; i++) for (let i = 0; i < ddTypes.length; i++)
{ {
if (accepted.indexOf(ddTypes[i]) != -1) if (accepted.indexOf(ddTypes[i]) != -1)
{ {
@ -608,9 +600,9 @@ export function egwDropActionImplementation()
} }
// Check whether there is only one link // Check whether there is only one link
var cnt = 0; let cnt = 0;
var lnk = null; let lnk = null;
for (var k in links) for (let k in links)
{ {
if (links[k].enabled && links[k].visible) if (links[k].enabled && links[k].visible)
{ {
@ -638,8 +630,8 @@ export function egwDropActionImplementation()
// This is possible as the popup and the popup action // This is possible as the popup and the popup action
// object and the drop action object share same // object and the drop action object share same
// set of properties. // set of properties.
var popup = getPopupImplementation(); let popup = getPopupImplementation();
var pos = popup._getPageXY(event.originalEvent); let pos = popup._getPageXY(event);
// Don't add paste actions, this is a drop // Don't add paste actions, this is a drop
popup.auto_paste = false; popup.auto_paste = false;
@ -653,21 +645,14 @@ export function egwDropActionImplementation()
} }
// Set cursor back to auto. Seems FF can't handle cursor reversion // Set cursor back to auto. Seems FF can't handle cursor reversion
jQuery('body').css({cursor:'auto'}); jQuery('body').css({cursor:'auto'});
_aoi.triggerEvent(EGW_AI_DRAG_OUT,{event: event,ui:ui}); _aoi.triggerEvent(EGW_AI_DRAG_OUT,{event: event,ui:ui});
}, });
"over": function(event, ui) { $droppable.on('droppable:out', function(event,ui) {
_aoi.triggerEvent(EGW_AI_DRAG_OUT,{event: event,ui:ui});
});
$droppable.on('droppable:over', function(event, ui) {
_aoi.triggerEvent(EGW_AI_DRAG_OVER,{event: event,ui:ui}); _aoi.triggerEvent(EGW_AI_DRAG_OVER,{event: event,ui:ui});
}, });
"out": function(event,ui) {
_aoi.triggerEvent(EGW_AI_DRAG_OUT,{event: event,ui:ui});
},
"tolerance": "pointer",
hoverClass: "drop-hover",
// Greedy is for nested droppables - children consume the action
greedy: true
}
);
return true; return true;
} }

View File

@ -12,14 +12,12 @@
/*egw:uses /*egw:uses
vendor.bower-asset.jquery.dist.jquery; vendor.bower-asset.jquery.dist.jquery;
egw_menu; egw_menu;
/api/js/jquery/jquery-tap-and-hold/jquery.tapandhold.js;
*/ */
import {egwAction, egwActionImplementation, egwActionObject} from './egw_action.js'; import {egwAction, egwActionImplementation, egwActionObject} from './egw_action.js';
import {egwFnct} from './egw_action_common.js'; import {egwFnct} from './egw_action_common.js';
import {egwMenu, _egw_active_menu} from "./egw_menu.js"; import {egwMenu, _egw_active_menu} from "./egw_menu.js";
import {EGW_KEY_ENTER, EGW_KEY_MENU} from "./egw_action_constants.js"; import {EGW_KEY_ENTER, EGW_KEY_MENU} from "./egw_action_constants.js";
import "../jquery/jquery-tap-and-hold/jquery.tapandhold.js";
if (typeof window._egwActionClasses == "undefined") if (typeof window._egwActionClasses == "undefined")
window._egwActionClasses = {}; window._egwActionClasses = {};
@ -280,7 +278,39 @@ export function egwPopupActionImplementation()
return false; return false;
}; };
ai._handleTapHold = function (_node, _callback)
{
let holdTimer = 600;
let maxDistanceAllowed = 40;
let tapTimeout = null;
let startx = 0;
let starty = 0;
//TODO (todo-jquery): ATM we need to convert the possible given jquery dom node object into DOM Element, this
// should be no longer neccessary after removing jQuery nodes.
if (_node instanceof jQuery)
{
_node = _node[0];
}
_node.addEventListener('touchstart', function(e){
tapTimeout = setTimeout(function(event){
_callback(e);
}, holdTimer);
startx = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
starty = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
});
_node.addEventListener('touchend', function(){
clearTimeout(tapTimeout);
});
_node.addEventListener('touchmove', function(_event){
if (tapTimeout == null) return;
let e = _event.originalEvent;
let x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
let y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
if (Math.sqrt((x-startx)*(x-startx) + (y-starty)+(y-starty)) > maxDistanceAllowed) clearTimeout(tapTimeout);
});
}
/** /**
* Registers the handler for the context menu * Registers the handler for the context menu
* *
@ -292,14 +322,6 @@ export function egwPopupActionImplementation()
ai._registerContext = function(_node, _callback, _context) ai._registerContext = function(_node, _callback, _context)
{ {
var contextHandler = function(e) { var contextHandler = function(e) {
if(egwIsMobile())
{
if (e.originalEvent.which == 3)
{
// Enable onhold trigger till we define a better handler for tree contextmenu
// return;
}
}
//Obtain the event object //Obtain the event object
if (!e) if (!e)
@ -327,7 +349,7 @@ export function egwPopupActionImplementation()
}; };
// Safari still needs the taphold to trigger contextmenu // Safari still needs the taphold to trigger contextmenu
// Chrome has default event on touch and hold which acts like right click // Chrome has default event on touch and hold which acts like right click
jQuery(_node).bind('taphold', contextHandler); this._handleTapHold(_node, contextHandler);
jQuery(_node).on('contextmenu', contextHandler); jQuery(_node).on('contextmenu', contextHandler);
}; };

View File

@ -2078,30 +2078,19 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
self.selectPopup = null; self.selectPopup = null;
}; };
const $select = jQuery(select.getDOMNode()); const $select = jQuery(select.getDOMNode());
$select.find('.ui-multiselect-checkboxes').sortable({
placeholder:'ui-fav-sortable-placeholder', //todo (todo-jquery-ui): fix the sortable import statement
items:'li[class^="selcolumn_sortable_col"]', import('../../../node_modules/sortablejs/Sortable.min.js').then(function(){
cancel: 'li[class^="selcolumn_sortable_#"]', let sortablejs = Sortable.create(select.getDOMNode().getElementsByClassName('ui-multiselect-checkboxes')[0], {
cursor: "move", ghostClass: 'ui-fav-sortable-placeholder',
tolerance: "pointer", draggable: 'li[class^="selcolumn_sortable_col"]',
axis: 'y', filter: 'li[class^="selcolumn_sortable_#"]',
containment: "parent", direction: 'vertical',
delay: 250, //(millisecond) delay before the sorting should start delay: 25,
beforeStop: function(event, ui) {
jQuery('li[class^="selcolumn_sortable_#"]', this).css({
opacity: 1
});
},
start: function(event, ui){
jQuery('li[class^="selcolumn_sortable_#"]', this).css({
opacity: 0.5
});
},
sort: function (event, ui)
{
jQuery( this ).sortable("refreshPositions" );
}
}); });
}.bind(this));
$select.disableSelection(); $select.disableSelection();
$select.find('li[class^="selcolumn_sortable_"]').each(function(i,v){ $select.find('li[class^="selcolumn_sortable_"]').each(function(i,v){
// @ts-ignore // @ts-ignore

View File

@ -177,21 +177,25 @@ export class et2_favorites extends et2_dropdown_button implements et2_INextmatch
} }
}; };
//Add Sortable handler to nm fav. menu //todo (@todo-jquery-ui): replace the import statement
jQuery(this.menu).sortable({ /**
* todo (@todo-jquery-ui): the sorting does not work at the moment becuase of jquery-ui menu being used in order to create dropdown
items:'li:not([data-id$="add"])', * buttons menu. Once we replace the et2_widget_dropdown_button with web component this should be adapted
placeholder:'ui-fav-sortable-placeholder', * and working again.
delay: 250, //(millisecond) delay before the sorting should start **/
update: function () import('../../../node_modules/sortablejs/Sortable.min.js').then(function(){
{ let sortablejs = Sortable.create(this.menu[0], {
self.favSortedList = jQuery(this).sortable('toArray', {attribute:'data-id'}); ghostClass: 'ui-fav-sortable-placeholder',
draggable: 'li:not([data-id$="add"])',
self.egw().set_preference(self.options.app,'fav_sort_pref',self.favSortedList); delay: 25,
dataIdAttr:'data-id',
onSort: function(event){
self.favSortedList = sortablejs.toArray();
self.egw.set_preference(self.options.app,'fav_sort_pref', self.favSortedList );
sideBoxDOMNodeSort(self.favSortedList); sideBoxDOMNodeSort(self.favSortedList);
} }
}); });
}.bind(this));
// Add a listener on the delete to remove // Add a listener on the delete to remove
this.menu.on("click","div.ui-icon-trash", app[self.options.app], function() { this.menu.on("click","div.ui-icon-trash", app[self.options.app], function() {

View File

@ -117,6 +117,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
private wrapper = null; private wrapper = null;
private lastRowNode: null; private lastRowNode: null;
private sortablejs : Sortable = null;
/** /**
* Constructor * Constructor
* *
@ -943,56 +944,54 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
*/ */
set_sortable(sortable: boolean | Function) set_sortable(sortable: boolean | Function)
{ {
const $node = jQuery(this.getDOMNode()); const self = this;
if(!sortable) let tbody = this.getDOMNode().getElementsByTagName('tbody')[0];
if(!sortable && this.sortablejs)
{ {
$node.sortable("destroy"); this.sortablejs.destroy();
return; return;
} }
// Make sure rows have IDs, so sortable has something to return for (let i =0; i < tbody.children.length; i++)
jQuery('tr', this.tbody).each(function(index) { {
const $this = jQuery(this); if (!tbody.children[i].classList.contains('th') && !tbody.children[i].id)
{
tbody.children[i].setAttribute('id', i.toString());
}
}
// Header does not participate in sorting
if($this.hasClass('th')) return;
// If row doesn't have an ID, assign the index as ID //todo (todo-jquery-ui): fix the sortable import statement
if(!$this.attr("id")) $this.attr("id", index); import('../../../node_modules/sortablejs/Sortable.min.js').then(function(){
}); this.sortablejs = new Sortable(tbody,{
group: this.options.sortable_connectWith,
const self = this; draggable: "tr:not(.th)",
filter: this.options.sortable_cancel,
// Set up sortable ghostClass: this.options.sortable_placeholder,
$node.sortable({ dataIdAttr: 'id',
// Header does not participate in sorting onAdd:function (event) {
items: "> tbody > tr:not(.th)", if (typeof self.options.sortable_recieveCallback == 'function') {
distance: 15, self.options.sortable_recieveCallback.call(self, event, this, self.id);
cancel: this.options.sortable_cancel, }
placeholder: this.options.sortable_placeholder, },
containment: this.options.sortable_containment, onStart: function (event, ui) {
connectWith: this.options.sortable_connectWith, if (typeof self.options.sortable_startCallback == 'function') {
update: function(event, ui) { self.options.sortable_startCallback.call(self, event, this, self.id);
}
},
onSort: function (event) {
self.egw().json(sortable,[ self.egw().json(sortable,[
self.getInstanceManager().etemplate_exec_id, self.getInstanceManager().etemplate_exec_id,
$node.sortable("toArray"), self.sortablejs.toArray(),
self.id], self.id],
null, null,
self, self,
true true
).sendRequest(); ).sendRequest();
}, },
receive: function (event, ui) {
if (typeof self.sortable_recieveCallback == 'function') {
self.sortable_recieveCallback.call(self, event, ui, self.id);
}
},
start: function (event, ui) {
if (typeof self.options.sortable_startCallback == 'function') {
self.options.sortable_startCallback.call(self, event, ui, self.id);
}
}
}); });
}.bind(this));
} }
/** /**

View File

@ -25,7 +25,7 @@ import './fw_browser.js';
import './fw_ui.js'; import './fw_ui.js';
import './fw_classes.js'; import './fw_classes.js';
import '../jsapi/egw_inheritance.js'; import '../jsapi/egw_inheritance.js';
import "sortablejs/Sortable.min.js";
/** /**
* *
* @param {DOMWindow} window * @param {DOMWindow} window
@ -48,31 +48,16 @@ import '../jsapi/egw_inheritance.js';
init: function() init: function()
{ {
this._super.apply(this,arguments); this._super.apply(this,arguments);
let self = this;
this.setBottomLine(this.parent.entries); this.setBottomLine(this.parent.entries);
//Make the base Div sortable. Set all elements with the style "egw_fw_ui_sidemenu_entry_header"
//as handle this.elemDiv.classList.add('ui-sortable')
if(jQuery(this.elemDiv).data('uiSortable')) Sortable.create(this.elemDiv,{
{ onSort: function (evt) {
jQuery(this.elemDiv).sortable("destroy"); self.parent.isDraged = true;
} self.parent.refreshSort();
jQuery(this.elemDiv).sortable({
handle: ".egw_fw_ui_sidemenu_entry_header",
distance: 15,
start: function(event, ui)
{
var parent = ui.item.context._parent;
parent.isDraged = true;
parent.parent.startDrag.call(parent.parent);
}, },
stop: function(event, ui) direction: 'vertical'
{
var parent = ui.item.context._parent;
parent.parent.stopDrag.call(parent.parent);
parent.parent.refreshSort.call(parent.parent);
},
opacity: 0.7,
axis: 'y'
}); });
}, },
@ -107,31 +92,6 @@ import '../jsapi/egw_inheritance.js';
this._super.apply(this,arguments); this._super.apply(this,arguments);
this.sortCallback = _sortCallback; this.sortCallback = _sortCallback;
}, },
/**
*
* @returns {undefined}
*/
startDrag: function()
{
if (this.activeEntry)
{
jQuery(this.activeEntry.marker).show();
jQuery(this.elemDiv).sortable("refresh");
}
},
/**
*
* @returns {undefined}
*/
stopDrag: function()
{
if (this.activeEntry)
{
jQuery(this.activeEntry.marker).hide();
jQuery(this.elemDiv).sortable("refresh");
}
},
/** /**
* Called by the sidemenu elements whenever they were sorted. An array containing * Called by the sidemenu elements whenever they were sorted. An array containing

View File

@ -1,19 +0,0 @@
jQuery - Tap and Hold
=====================
This jQuery plugin lets you detect a tap and hold event on touch interfaces.
How to use it?
1) Add the jQuery Tap and Hold plugin into your HTML
<script src="jquery.tapandhold.js" type="text/javascript"></script>
2) Bind a tap and hold handler function to the tap and hold event of an element.
$("#myDiv").bind("taphold", function(event){
alert("This is a tap and hold!");
});
You can check a working example in examples/example1.html

View File

@ -1,17 +0,0 @@
<html>
<head>
<title>jQuery - Tap and Hold</title>
<script src="http://code.jquery.com/jquery-1.7.min.js" type="text/javascript"></script>
<script src="https://raw.github.com/zaubersoftware/jquery-tap-and-hold/master/jquery.tapandhold.js" type="text/javascript"></script>
<script type="text/javascript">
$(function(){
$(".tap").bind("taphold", function(){
alert("Hello Tap and Hold World!");
});
});
</script>
</head>
<body>
<div class="tap" style="width:200px; height:200px; background-color: blue; margin: 100px 300px;"></div>
</body>
</html>

View File

@ -1,136 +0,0 @@
/**
* Copyright (c) 2011 Zauber S.A. <http://www.zaubersoftware.com/>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author Guido Marucci Blas - guido@zaubersoftware.com
* @description Adds a handler for a custom event 'taphold' that handles a
* tap and hold on touch interfaces.
*/
(function($) {
var TAP_AND_HOLD_TRIGGER_TIMER = 600;
var MAX_DISTANCE_ALLOWED_IN_TAP_AND_HOLD_EVENT = 40;
var TOUCHSTART = "touchstart";
var TOUCHEND = "touchend";
var TOUCHMOVE = "touchmove";
// For debugging only
// var TOUCHSTART = "mousedown";
// var TOUCHEND = "mouseup";
// var TOUCHMOVE = "mousemove";
var tapAndHoldTimer = null;
function calculateEuclideanDistance(x1, y1, x2, y2) {
var diffX = (x2 - x1);
var diffY = (y2 - y1);
return Math.sqrt((diffX * diffX) + (diffY * diffY));
};
function onTouchStart(event) {
var e = event.originalEvent;
// Only start detector if and only if one finger is over the widget
if (!e.touches || (e.targetTouches.length === 1 && e.touches.length === 1)) {
startTapAndHoldDetector.call(this, event)
var element = $(this);
element.bind(TOUCHMOVE, onTouchMove);
element.bind(TOUCHEND, onTouchEnd);
} else {
stopTapAndHoldDetector.call(this);
}
};
function onTouchMove(event) {
if (tapAndHoldTimer == null) {
return;
}
var e = event.originalEvent;
var x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
var y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
var tapAndHoldPoint = $(this).data("taphold.point");
var euclideanDistance = calculateEuclideanDistance(tapAndHoldPoint.x, tapAndHoldPoint.y, x, y);
if (euclideanDistance > MAX_DISTANCE_ALLOWED_IN_TAP_AND_HOLD_EVENT) {
stopTapAndHoldDetector.call(this);
}
};
function onTouchEnd(event) {
stopTapAndHoldDetector.call(this);
};
function onTapAndHold(event) {
clear.call(this);
$(this).data("taphold.handler").call(this, event);
};
function clear() {
tapAndHoldTimer = null;
$(this).unbind(TOUCHMOVE, onTouchMove);
$(this).unbind(TOUCHEND, onTouchEnd);
};
function startTapAndHoldDetector(event) {
if (tapAndHoldTimer != null) {
return;
}
var self = this;
tapAndHoldTimer = setTimeout(function(){
onTapAndHold.call(self, event)
}, TAP_AND_HOLD_TRIGGER_TIMER);
// Stores tap x & y
var e = event.originalEvent;
var tapAndHoldPoint = {};
tapAndHoldPoint.x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
tapAndHoldPoint.y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
$(this).data("taphold.point", tapAndHoldPoint);
};
function stopTapAndHoldDetector() {
clearTimeout(tapAndHoldTimer);
clear.call(this);
};
$.event.special["taphold"] = {
setup: function() {
},
add: function(handleObj) {
$(this).data("taphold.handler", handleObj.handler);
if (handleObj.data) {
$(this).bind(TOUCHSTART, handleObj.data, onTouchStart);
} else {
$(this).bind(TOUCHSTART, onTouchStart);
}
},
remove: function(handleObj) {
stopTapAndHoldDetector.call(this);
if (handleObj.data) {
$(this).unbind(TOUCHSTART, handleObj.data, onTouchStart);
} else {
$(this).unbind(TOUCHSTART, onTouchStart);
}
},
teardown: function() {
}
};
})(jQuery);

View File

@ -781,32 +781,21 @@ export abstract class EgwApp
}) })
.addClass("ui-helper-clearfix"); .addClass("ui-helper-clearfix");
//Add Sortable handler to sideBox fav. menu //todo (@todo-jquery-ui): replace the import statement
jQuery('ul','#favorite_sidebox_'+this.appname).sortable({ import('../../../node_modules/sortablejs/Sortable.min.js').then(function(){
items:'li:not([data-id$="add"])', let el = document.getElementById('favorite_sidebox_'+this.appname).getElementsByTagName('ul')[0];
placeholder:'ui-fav-sortable-placeholder', let sortablejs = Sortable.create(el, {
delay:250, //(millisecond) delay before the sorting should start ghostClass: 'ui-fav-sortable-placeholder',
helper: function(event, item : any) { draggable: 'li:not([data-id$="add"])',
// We'll need to know which app this is for delay: 25,
item.attr('data-appname',self.appname); dataIdAttr:'data-id',
// Create custom helper so it can be dragged to Home onSort: function(event){
var h_parent = item.parent().parent().clone(); let favSortedList = sortablejs.toArray();
h_parent.find('li').not('[data-id="'+item.attr('data-id')+'"]').remove();
h_parent.appendTo('body');
return h_parent;
},
// @ts-ignore
refreshPositions: true,
update: function (event, ui)
{
// @ts-ignore
var favSortedList = jQuery(this).sortable('toArray', {attribute:'data-id'});
self.egw.set_preference(self.appname,'fav_sort_pref',favSortedList); self.egw.set_preference(self.appname,'fav_sort_pref',favSortedList);
self._refresh_fav_nm(); self._refresh_fav_nm();
} }
}); });
}.bind(this));
// Bind favorite de-select // Bind favorite de-select
var egw_fw = egw_getFramework(); var egw_fw = egw_getFramework();

View File

@ -20,6 +20,13 @@ egw.extend('jsonq', egw.MODULE_GLOBAL, function()
{ {
"use strict"; "use strict";
/**
* Explicit registered push callbacks
*
* @type {Function[]}
*/
let push_callbacks = [];
/** /**
* Queued json requests (objects with attributes menuaction, parameters, context, callback, sender and callbeforesend) * Queued json requests (objects with attributes menuaction, parameters, context, callback, sender and callbeforesend)
* *
@ -149,6 +156,34 @@ egw.extend('jsonq', egw.MODULE_GLOBAL, function()
}, 100); }, 100);
} }
return uid; return uid;
},
/**
* Register a callback to receive push broadcasts eg. in a popup or iframe
*
* It's also used internally by egw_message's push method to dispatch to the registered callbacks.
*
* @param {Function|PushData} data callback (with bound context) or PushData to dispatch to callbacks
*/
registerPush: function(data)
{
if (typeof data === "function")
{
push_callbacks.push(data);
}
else
{
for (let n in push_callbacks)
{
try {
push_callbacks[n].call(this, data);
}
// if we get an exception, we assume the callback is no longer available and remove it
catch (ex) {
push_callbacks.splice(n, 1);
}
}
}
} }
}; };

View File

@ -459,6 +459,9 @@ egw.extend('message', egw.MODULE_WND_LOCAL, function(_app, _wnd)
app_obj.push(pushData); app_obj.push(pushData);
} }
} }
// call the global registered push callbacks
this.registerPush(pushData);
} }
}; };

View File

@ -241,7 +241,6 @@ class Bundle
// generate api bundle // generate api bundle
$inc_mgr->include_js_file('/vendor/bower-asset/jquery/dist/jquery.js'); $inc_mgr->include_js_file('/vendor/bower-asset/jquery/dist/jquery.js');
$inc_mgr->include_js_file('/api/js/jquery/jquery.noconflict.js'); $inc_mgr->include_js_file('/api/js/jquery/jquery.noconflict.js');
$inc_mgr->include_js_file('/vendor/bower-asset/jquery-ui/jquery-ui.js');
$inc_mgr->include_js_file('/api/js/jsapi/jsapi.js'); $inc_mgr->include_js_file('/api/js/jsapi/jsapi.js');
$inc_mgr->include_js_file('/api/js/egw_json.js'); $inc_mgr->include_js_file('/api/js/egw_json.js');
$inc_mgr->include_js_file('/api/js/jsapi/egw.js'); $inc_mgr->include_js_file('/api/js/jsapi/egw.js');

View File

@ -465,7 +465,7 @@ export class et2_calendar_timegrid extends et2_calendar_view implements et2_IDet
{ {
var drop_date = dropEnd.date||false; var drop_date = dropEnd.date||false;
var event_data = timegrid._get_event_info(ui.draggable); var event_data = timegrid._get_event_info(ui.item);
var event_widget = timegrid.getWidgetById(event_data.widget_id); var event_widget = timegrid.getWidgetById(event_data.widget_id);
if(!event_widget) if(!event_widget)
{ {
@ -1272,7 +1272,7 @@ export class et2_calendar_timegrid extends et2_calendar_view implements et2_IDet
// Determine target node // Determine target node
var event = _data.event || false; var event = _data.event || false;
if(!event) return; if(!event) return;
if(_data.ui.draggable.hasClass('rowNoEdit')) return; if(_data.ui.item.hasClass('rowNoEdit')) return;
/* /*
We have to handle the drop in the normal event stream instead of waiting We have to handle the drop in the normal event stream instead of waiting
@ -1307,9 +1307,9 @@ export class et2_calendar_timegrid extends et2_calendar_view implements et2_IDet
case EGW_AI_DRAG_OVER: case EGW_AI_DRAG_OVER:
// Listen to the drag and update the helper with the time // Listen to the drag and update the helper with the time
// This part lets us drag between different timegrids // This part lets us drag between different timegrids
_data.ui.draggable.on('drag.et2_timegrid'+widget_object.id, drag_listener); _data.ui.item.on('drag.et2_timegrid'+widget_object.id, drag_listener);
_data.ui.draggable.on('dragend.et2_timegrid'+widget_object.id, function() { _data.ui.item.on('dragend.et2_timegrid'+widget_object.id, function() {
_data.ui.draggable.off('drag.et2_timegrid' + widget_object.id); _data.ui.item.off('drag.et2_timegrid' + widget_object.id);
}); });
// Remove formatting for out-of-view events (full day non-blocking) // Remove formatting for out-of-view events (full day non-blocking)
@ -1317,7 +1317,7 @@ export class et2_calendar_timegrid extends et2_calendar_view implements et2_IDet
jQuery('.calendar_calEventBody',_data.ui.helper).css('padding-top',''); jQuery('.calendar_calEventBody',_data.ui.helper).css('padding-top','');
// Disable invite / change actions for same calendar or already participant // Disable invite / change actions for same calendar or already participant
var event = _data.ui.draggable.data('selected')[0]; var event = _data.ui.item.data('selected')[0];
if(!event || event.id && event.id.indexOf('calendar') !== 0) if(!event || event.id && event.id.indexOf('calendar') !== 0)
{ {
event = false; event = false;
@ -1346,7 +1346,7 @@ export class et2_calendar_timegrid extends et2_calendar_view implements et2_IDet
// Triggered once, when something is dragged out of the timegrid // Triggered once, when something is dragged out of the timegrid
case EGW_AI_DRAG_OUT: case EGW_AI_DRAG_OUT:
// Stop listening // Stop listening
_data.ui.draggable.off('drag.et2_timegrid'+widget_object.id); _data.ui.item.off('drag.et2_timegrid'+widget_object.id);
// Remove highlighted time square // Remove highlighted time square
var timegrid = aoi.getWidget(); var timegrid = aoi.getWidget();
timegrid.gridHover.hide(); timegrid.gridHover.hide();

3496
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@
"@rollup/plugin-typescript": "^8.2.1", "@rollup/plugin-typescript": "^8.2.1",
"@types/jquery": "^3.5.5", "@types/jquery": "^3.5.5",
"@types/jqueryui": "^1.12.14", "@types/jqueryui": "^1.12.14",
"@types/sortablejs": "1.10.0",
"grunt": "^1.3.0", "grunt": "^1.3.0",
"grunt-contrib-cssmin": "^2.2.1", "grunt-contrib-cssmin": "^2.2.1",
"grunt-newer": "^1.3.0", "grunt-newer": "^1.3.0",
@ -48,7 +49,9 @@
"carbon-components": "^10.37.0", "carbon-components": "^10.37.0",
"carbon-web-components": "^1.14.1", "carbon-web-components": "^1.14.1",
"lit-element": "^2.5.1", "lit-element": "^2.5.1",
"lit-html": "^1.4.1" "lit-html": "^1.4.1",
"sortablejs": "^1.14.0",
"egw-touch-dnd": "^1.2.2"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"