Skip to content
Snippets Groups Projects
Commit 05df1f58 authored by Jan-Hendrik Willms's avatar Jan-Hendrik Willms Committed by Jan-Hendrik Willms
Browse files

adjust breakpoint for repositioning detection, adjust scroll handling and...

adjust breakpoint for repositioning detection, adjust scroll handling and general cleanup, fixes #3447, fixes #2557, fixes #3009, fixes #3073

Closes #3447, #2557, #3009, and #3073

Merge request studip/studip!3137
parent 0d664143
No related branches found
No related tags found
No related merge requests found
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* @type {[type]} * @type {[type]}
*/ */
function determineBreakpoint(element) { function determineBreakpoint(element) {
return $(element).closest('.ui-dialog-content').length > 0 ? '.ui-dialog-content' : 'body'; return $(element).closest('.ui-dialog-content').length > 0 ? '.ui-dialog-content' : '#content';
} }
/** /**
...@@ -48,49 +48,29 @@ function getScrollableParents(element, menu_width, menu_height) { ...@@ -48,49 +48,29 @@ function getScrollableParents(element, menu_width, menu_height) {
return elements; return elements;
} }
/** class ActionMenu
* Scroll handler for all scroll related events. {
* This will reposition the menu(s) according to the scrolled distance. static stash = new Map();
*/ static openMenus = [];
function scrollHandler(event) { static #secret = Symbol();
const data = $(event.target).data('action-menu-scroll-data'); static scrollHandlerState = false;
const diff_x = event.target.scrollLeft - data.left;
const diff_y = event.target.scrollTop - data.top;
data.menus.forEach((menu) => {
const offset = menu.offset();
menu.offset({
left: offset.left - diff_x,
top: offset.top - diff_y
});
});
data.left = event.target.scrollLeft;
data.top = event.target.scrollTop;
$(event.target).data('action-menu-scroll-data', data);
}
const stash = new Map();
const secret = typeof Symbol === 'undefined' ? Math.random().toString(36).substring(2, 15) : Symbol();
class ActionMenu {
/** /**
* Create menu using a singleton pattern for each element. * Create menu using a singleton pattern for each element.
*/ */
static create(element, position = true) { static create(element, position = true) {
const id = $(element).uniqueId().attr('id'); const id = $(element).uniqueId().attr('id');
const breakpoint = determineBreakpoint(element); const breakpoint = determineBreakpoint(element);
if (!stash.has(id)) { if (!ActionMenu.stash.has(id)) {
const menu_offset = $(element).offset().top + $('.action-menu-content', element).height(); const menu_offset = $(element).offset().top + $('.action-menu-content', element).height();
const max_offset = $(breakpoint).offset().top + $(breakpoint).height(); const max_offset = $(breakpoint).offset().top + $(breakpoint).height();
const reversed = menu_offset > max_offset; const reversed = menu_offset > max_offset;
stash.set(id, new ActionMenu(secret, element, reversed, position)); ActionMenu.stash.set(id, new ActionMenu(ActionMenu.#secret, element, reversed, position));
} }
return stash.get(id); return ActionMenu.stash.get(id);
} }
/** /**
...@@ -98,7 +78,7 @@ class ActionMenu { ...@@ -98,7 +78,7 @@ class ActionMenu {
* @return {[type]} [description] * @return {[type]} [description]
*/ */
static closeAll() { static closeAll() {
stash.forEach((menu) => menu.close()); this.stash.forEach((menu) => menu.close());
} }
/** /**
...@@ -106,61 +86,66 @@ class ActionMenu { ...@@ -106,61 +86,66 @@ class ActionMenu {
*/ */
constructor(passed_secret, element, reversed, position) { constructor(passed_secret, element, reversed, position) {
// Enforce use of create (would use a private constructor if I could) // Enforce use of create (would use a private constructor if I could)
if (secret !== passed_secret) { if (ActionMenu.#secret !== passed_secret) {
throw new Error('Cannot create ActionMenu. Use ActionMenu.create()!'); throw new Error('Cannot create ActionMenu. Use ActionMenu.create()!');
} }
const breakpoint = determineBreakpoint(element);
this.element = $(element); this.element = $(element);
this.menu = this.element; this.menu = this.element;
this.content = $('.action-menu-content', element); this.content = $('.action-menu-content', element);
this.is_reversed = reversed; this.is_reversed = reversed;
this.is_open = false; this.is_open = false;
this.position = position;
const additionalClasses = Object.values({ ...this.element[0].classList }).filter((item) => item != 'action-menu'); const additionalClasses = Object.values({ ...this.element[0].classList }).filter((item) => item != 'action-menu');
const menu_width = this.content.width(); const menu_width = this.content.width();
const menu_height = this.content.height(); const menu_height = this.content.height();
// Reposition the menu? // Reposition the menu?
if (position) { if (position) {
const form = this.element.closest('form');
if (form) {
const id = form.uniqueId().attr('id');
$('.action-menu-item input[type="image"]:not([form])', this.element).attr('form', id);
$('.action-menu-item button:not([form])', this.element).attr('form', id);
}
let parents = getScrollableParents(this.element, menu_width, menu_height); let parents = getScrollableParents(this.element, menu_width, menu_height);
if (parents.length > 0) { if (parents.length > 0) {
const form = this.element.closest('form');
if (form) {
const id = form.uniqueId().attr('id');
$('.action-menu-item input[type="image"]:not([form])', this.element).attr('form', id);
$('.action-menu-item button:not([form])', this.element).attr('form', id);
}
this.menu = $('<div class="action-menu-wrapper">').append(this.content); this.menu = $('<div class="action-menu-wrapper">').append(this.content);
$('.action-menu-icon', element).clone().data('action-menu-element', element).prependTo(this.menu); $('.action-menu-icon', element).clone().data('action-menu-element', element).prependTo(this.menu);
this.menu this.menu
.addClass(additionalClasses.join(' ')) .addClass(additionalClasses.join(' '))
.offset(this.element.offset()) .appendTo(parents[0]);
.appendTo(breakpoint); } else {
this.position = false;
// Always add breakpoint
parents.push(breakpoint);
parents.forEach((parent, index) => {
let data = $(parent).data('action-menu-scroll-data') || {
menus: [],
left: parent.scrollLeft,
top: parent.scrollTop
};
data.menus.push(this.menu);
$(parent).data('action-menu-scroll-data', data);
if (data.menus.length < 2) {
$(parent).scroll(scrollHandler);
}
});
} }
} }
this.update(); this.update();
} }
toggleScrollHandler(active) {
if (ActionMenu.scrollHandlerState === active) {
return;
}
ActionMenu.scrollHandlerState = active;
if (active) {
document.addEventListener('scroll', this.repositionAllMenus, true);
document.addEventListener('scrollend', this.repositionAllMenus, true);
} else {
document.removeEventListener('scroll', this.repositionAllMenus, true);
document.removeEventListener('scrollend', this.repositionAllMenus, true);
}
}
repositionAllMenus() {
ActionMenu.openMenus.forEach((menu) => menu.reposition());
}
/** /**
* Adds a class to the menu's element. * Adds a class to the menu's element.
*/ */
...@@ -191,8 +176,23 @@ class ActionMenu { ...@@ -191,8 +176,23 @@ class ActionMenu {
this.update(); this.update();
if (this.is_open) { if (this.is_open) {
this.reposition();
this.menu.find('.action-menu-icon').focus(); this.menu.find('.action-menu-icon').focus();
ActionMenu.openMenus.push(this);
} else {
ActionMenu.openMenus = ActionMenu.openMenus.filter(menu => menu !== this);
}
this.toggleScrollHandler(ActionMenu.openMenus.filter(menu => menu.position).length > 0);
}
reposition() {
if (!this.position) {
return;
} }
const offset = this.element.offset();
requestAnimationFrame(() => this.menu.offset(offset));
} }
/** /**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment