You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
414 lines
12 KiB
414 lines
12 KiB
/*
|
|
** Zabbix
|
|
** Copyright (C) 2001-2023 Zabbix SIA
|
|
**
|
|
** This program is free software; you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation; either version 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program; if not, write to the Free Software
|
|
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
**/
|
|
|
|
|
|
/*
|
|
* Since function addPopupValues can be defined by several dashboard widgets, the variable addPopupValues should be
|
|
* defined in global scope and always re-written with function right before usage. Do this in all widgets where it is
|
|
* needed.
|
|
*/
|
|
let old_addPopupValues = null;
|
|
|
|
if (typeof addPopupValues === 'undefined') {
|
|
window.addPopupValues = null;
|
|
}
|
|
|
|
(function($) {
|
|
$.widget('zbx.sortable_tree', $.extend({}, $.ui.sortable.prototype, {
|
|
options: {
|
|
// jQuery UI sortable options:
|
|
cursor: 'grabbing',
|
|
placeholder: 'placeholder',
|
|
forcePlaceholderSize: true,
|
|
toleranceElement: '> div',
|
|
forceHelperSize: true,
|
|
tolerance: 'intersect',
|
|
handle: '.drag-icon',
|
|
items: '.tree-item',
|
|
helper: 'clone',
|
|
revert: 10,
|
|
opacity: .75,
|
|
scrollSpeed: 20,
|
|
|
|
// Custom options:
|
|
parent_change_delay: 0,
|
|
parent_expand_delay: 600,
|
|
indent_size: 15,
|
|
max_depth: 10
|
|
},
|
|
|
|
_create: function() {
|
|
$.ui.sortable.prototype._create.apply(this, arguments);
|
|
},
|
|
|
|
_mouseDrag: function(event) {
|
|
const opt = this.options;
|
|
|
|
// Compute the helpers position.
|
|
this.position = this._generatePosition(event);
|
|
this.positionAbs = this._convertPositionTo('absolute');
|
|
|
|
if (!this.lastPositionAbs) {
|
|
this.lastPositionAbs = this.positionAbs;
|
|
}
|
|
|
|
// Do scrolling.
|
|
if (this.options.scroll) {
|
|
if (this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
|
|
|
|
if ((this.overflowOffset.top + this.scrollParent[0].offsetHeight)
|
|
- event.pageY < opt.scrollSensitivity) {
|
|
this.scrollParent[0].scrollTop = this.scrollParent[0].scrollTop + opt.scrollSpeed;
|
|
}
|
|
else if (event.pageY - this.overflowOffset.top < opt.scrollSensitivity) {
|
|
this.scrollParent[0].scrollTop = this.scrollParent[0].scrollTop - opt.scrollSpeed;
|
|
}
|
|
|
|
if ((this.overflowOffset.left + this.scrollParent[0].offsetWidth)
|
|
- event.pageX < opt.scrollSensitivity) {
|
|
this.scrollParent[0].scrollLeft = this.scrollParent[0].scrollLeft + opt.scrollSpeed;
|
|
}
|
|
else if (event.pageX - this.overflowOffset.left < opt.scrollSensitivity) {
|
|
this.scrollParent[0].scrollLeft = this.scrollParent[0].scrollLeft - opt.scrollSpeed;
|
|
}
|
|
}
|
|
else {
|
|
if (event.pageY - $(document).scrollTop() < opt.scrollSensitivity) {
|
|
$(document).scrollTop($(document).scrollTop() - opt.scrollSpeed);
|
|
}
|
|
else if ($(window).height() - (event.pageY - $(document).scrollTop()) < opt.scrollSensitivity) {
|
|
$(document).scrollTop($(document).scrollTop() + opt.scrollSpeed);
|
|
}
|
|
|
|
if (event.pageX - $(document).scrollLeft() < opt.scrollSensitivity) {
|
|
$(document).scrollLeft($(document).scrollLeft() - opt.scrollSpeed);
|
|
}
|
|
else if ($(window).width() - (event.pageX - $(document).scrollLeft()) < opt.scrollSensitivity) {
|
|
$(document).scrollLeft($(document).scrollLeft() + opt.scrollSpeed);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Regenerate the absolute position used for position checks.
|
|
this.positionAbs = this._convertPositionTo('absolute');
|
|
|
|
const prev_offset_top = this.placeholder.offset().top;
|
|
|
|
// Set the helper position.
|
|
if (!this.options.axis || this.options.axis !== 'y') {
|
|
this.helper[0].style.left = this.position.left + 'px';
|
|
}
|
|
|
|
if (!this.options.axis || this.options.axis !== 'x') {
|
|
this.helper[0].style.top = this.position.top + 'px';
|
|
}
|
|
|
|
this.hovering = this.hovering ? this.hovering : null;
|
|
this.changing_parent = this.changing_parent ? this.changing_parent : null;
|
|
this.mouseentered = this.mouseentered ? this.mouseentered : false;
|
|
|
|
if (this.changing_parent) {
|
|
clearTimeout(this.changing_parent);
|
|
}
|
|
|
|
// re-arrange
|
|
for (let i = this.items.length - 1; i >= 0; i--) {
|
|
|
|
// Cache variables and intersection, continue if no intersection.
|
|
const item = this.items[i];
|
|
const itemElement = item.item[0];
|
|
const intersection = this._intersectsWithPointer(item);
|
|
|
|
if (!intersection) {
|
|
continue;
|
|
}
|
|
|
|
// Cannot intersect with itself.
|
|
if (itemElement != this.currentItem[0]
|
|
&& this.placeholder[(intersection == 1) ? 'next' : 'prev']()[0] != itemElement
|
|
&& !$.contains(this.placeholder[0], itemElement)
|
|
&& (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true)) {
|
|
if (!this.hovering && !$(itemElement).hasClass('opened')) {
|
|
$(itemElement).addClass('hovering');
|
|
|
|
this.hovering = setTimeout(() => {
|
|
$(itemElement)
|
|
.removeClass('closed')
|
|
.addClass('opened');
|
|
|
|
this.refreshPositions();
|
|
}, opt.parent_expand_delay);
|
|
}
|
|
|
|
if (!this.mouseentered) {
|
|
$(itemElement).mouseenter();
|
|
this.mouseentered = true;
|
|
}
|
|
|
|
this.direction = (intersection == 1) ? 'down' : 'up';
|
|
|
|
if (this._intersectsWithSides(item)) {
|
|
$(itemElement).removeClass('hovering').mouseleave();
|
|
this.mouseentered = false;
|
|
|
|
if (this.hovering) {
|
|
clearTimeout(this.hovering);
|
|
this.hovering = null;
|
|
}
|
|
this._rearrange(event, item);
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
|
|
this._trigger('change', event, this._uiHash());
|
|
break;
|
|
}
|
|
}
|
|
|
|
const parent_item = $(this.placeholder.parent()).closest('.tree-item');
|
|
const level = +$(this.placeholder.parent()).attr('data-depth');
|
|
const child_levels = this._levelsUnder(this.currentItem[0]) + 1;
|
|
|
|
let prev_item = this.placeholder[0].previousSibling ? $(this.placeholder[0].previousSibling) : null;
|
|
let next_item = this.placeholder[0].nextSibling ? $(this.placeholder[0].nextSibling) : null;
|
|
let direction_moved = null;
|
|
let levels_moved = 0;
|
|
|
|
if (prev_item !== null) {
|
|
while (prev_item[0] === this.currentItem[0] || prev_item[0] === this.helper[0]
|
|
|| prev_item[0].className.indexOf('tree-item') == -1) {
|
|
if (prev_item[0].previousSibling) {
|
|
prev_item = $(prev_item[0].previousSibling);
|
|
}
|
|
else {
|
|
prev_item = null;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (next_item !== null) {
|
|
while (next_item[0] === this.currentItem[0] || next_item[0] === this.helper[0]
|
|
|| next_item[0].className.indexOf('tree-item') == -1) {
|
|
if (next_item[0].nextSibling) {
|
|
next_item = $(next_item[0].nextSibling);
|
|
}
|
|
else {
|
|
next_item = null;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parent_item.get(0) === this.currentItem[0]) {
|
|
$(this.element[0]).append(this.placeholder[0]);
|
|
this._trigger('stop', event, this._uiHash());
|
|
|
|
return false;
|
|
}
|
|
|
|
this.beyondMaxLevels = 0;
|
|
|
|
/*
|
|
* If item is moved to the left and it is last element of the list, add it as a child element to the
|
|
* element before.
|
|
*/
|
|
if (parent_item !== null && next_item === null
|
|
&& (this.positionAbs.left <= parent_item.offset().left
|
|
|| this.positionAbs.left <= opt.indent_size * -0.6)) {
|
|
direction_moved = 'left';
|
|
}
|
|
// If item is moved to the right and there is sibling element before, put it as a child of it.
|
|
else if (prev_item !== null && this.positionAbs.left >= prev_item.offset().left + opt.indent_size) {
|
|
direction_moved = 'right';
|
|
}
|
|
|
|
if (direction_moved) {
|
|
levels_moved = Math.floor(
|
|
Math.abs(parent_item.offset().left - this.positionAbs.left) / opt.indent_size
|
|
);
|
|
}
|
|
|
|
$('.highlighted-parent').removeClass('highlighted-parent');
|
|
|
|
if (direction_moved === 'right' && levels_moved) {
|
|
const drop_to = prev_item;
|
|
const hovered_branch_depth = drop_to[0].getElementsByClassName('tree-list')[0].dataset.depth;
|
|
|
|
this._isAllowed(prev_item, level, level + child_levels);
|
|
|
|
if (hovered_branch_depth < this.options.max_depth + 1) {
|
|
this.changing_parent = setTimeout(() => {
|
|
$(drop_to)
|
|
.addClass('highlighted-parent opened')
|
|
.removeClass('closed');
|
|
|
|
if (prev_offset_top && (prev_offset_top <= prev_item.offset().top)) {
|
|
$('>.tree-list', drop_to).prepend(this.placeholder);
|
|
}
|
|
else {
|
|
$('>.tree-list', drop_to).append(this.placeholder);
|
|
}
|
|
|
|
this.refreshPositions();
|
|
}, opt.parent_change_delay);
|
|
}
|
|
}
|
|
|
|
else if (direction_moved === 'left' && levels_moved) {
|
|
let drop_to = $(this.currentItem[0]).closest('.tree-item');
|
|
let one_before = null;
|
|
|
|
while (levels_moved > 0) {
|
|
if ($(drop_to).parent().closest('.tree-item').length) {
|
|
one_before = drop_to;
|
|
drop_to = $(drop_to).parent().closest('.tree-item');
|
|
}
|
|
levels_moved--;
|
|
}
|
|
|
|
$(drop_to).addClass('highlighted-parent');
|
|
|
|
this.changing_parent = setTimeout(() => {
|
|
if (one_before && one_before.length) {
|
|
$(this.placeholder).insertAfter(one_before);
|
|
}
|
|
else {
|
|
$('>.tree-list', drop_to).append(this.placeholder);
|
|
}
|
|
|
|
if (drop_to.children('.tree-list').children('li:visible:not(.ui-sortable-helper)').length < 1) {
|
|
drop_to.removeClass('opened');
|
|
}
|
|
this.refreshPositions();
|
|
}, opt.parent_change_delay);
|
|
|
|
this._isAllowed(prev_item, level, level + child_levels);
|
|
}
|
|
else {
|
|
$(this.placeholder.parent().closest('.tree-item')).addClass('highlighted-parent');
|
|
this._isAllowed(prev_item, level, level + child_levels);
|
|
}
|
|
|
|
// Post events to containers.
|
|
this._contactContainers(event);
|
|
|
|
// Call callbacks.
|
|
this._trigger('sort', event, this._uiHash());
|
|
|
|
this.lastPositionAbs = this.positionAbs;
|
|
|
|
return false;
|
|
},
|
|
|
|
_mouseStop: function(event, noPropagation) {
|
|
if (!event) {
|
|
return;
|
|
}
|
|
|
|
$('.highlighted-parent').removeClass('highlighted-parent');
|
|
this.placeholder.removeClass('sortable-error');
|
|
|
|
if (this.changing_parent) {
|
|
clearTimeout(this.changing_parent);
|
|
}
|
|
|
|
if (this.beyondMaxLevels > 0) {
|
|
this.reverting = true;
|
|
|
|
if (this.domPosition.prev) {
|
|
$(this.domPosition.prev).after(this.placeholder);
|
|
}
|
|
else {
|
|
$(this.domPosition.parent).prepend(this.placeholder);
|
|
}
|
|
|
|
this._trigger('revert', event, this._uiHash());
|
|
this.refreshPositions();
|
|
this._clear(event, noPropagation);
|
|
}
|
|
else {
|
|
const parent_id = this.placeholder.parent().closest('.tree-item').data('id');
|
|
const item_id = $(this.currentItem[0]).data('id');
|
|
|
|
$(`[name="navtree.parent.${item_id}"]`).val(parent_id);
|
|
|
|
if (this.options.revert) {
|
|
const self = this;
|
|
const cur = self.placeholder.offset();
|
|
|
|
self.reverting = true;
|
|
|
|
$(this.helper).animate({
|
|
left: cur.left - this.offset.parent.left - self.margins.left
|
|
+ ((this.offsetParent[0] == document.body) ? 0 : this.offsetParent[0].scrollLeft),
|
|
top: cur.top - this.offset.parent.top - self.margins.top
|
|
+ ((this.offsetParent[0] == document.body) ? 0 : this.offsetParent[0].scrollTop)
|
|
}, parseInt(this.options.revert, 10) || 500, function() {
|
|
self._clear(event);
|
|
});
|
|
}
|
|
else {
|
|
this._clear(event, noPropagation);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
_isAllowed: function(parentItem, level, levels) {
|
|
if (this.options.max_depth + 1 > 0 && (this.options.max_depth + 1 < levels
|
|
|| +this.placeholder.closest('[data-depth]').attr('data-depth') > this.options.max_depth + 1)) {
|
|
this.placeholder.addClass('sortable-error');
|
|
this.beyondMaxLevels = levels - this.options.max_depth + 1;
|
|
}
|
|
else {
|
|
this.placeholder.removeClass('sortable-error');
|
|
this.beyondMaxLevels = 0;
|
|
}
|
|
},
|
|
|
|
_levelsUnder: function(item) {
|
|
const depths = [];
|
|
let levels;
|
|
|
|
$('.tree-list', item).not(':empty').each(function(i, item) {
|
|
levels = 0;
|
|
|
|
while ($('.tree-list', item).length) {
|
|
item = $('.tree-list', item).not(':empty');
|
|
levels++;
|
|
}
|
|
|
|
depths.push(levels);
|
|
});
|
|
|
|
return depths.length ? Math.max.apply(null, depths) : 0;
|
|
}
|
|
}));
|
|
|
|
$.zbx.sortable_tree.prototype.options = $.extend({}, $.ui.sortable.prototype.options,
|
|
$.zbx.sortable_tree.prototype.options
|
|
);
|
|
})(jQuery);
|