Merge pull request #3661 from Icinga/feature/preserve-scroll-position-upon-form-submits

Preserve scroll position upon form submits

(cherry picked from commit d8c4fcc517)
Signed-off-by: Johannes Meyer <johannes.meyer@icinga.com>
This commit is contained in:
Johannes Meyer 2019-04-16 08:04:15 +02:00
parent 4cdd83dcf4
commit 60e3201c66
3 changed files with 80 additions and 31 deletions

View File

@ -139,10 +139,15 @@
req.fail(this.onFailure); req.fail(this.onFailure);
req.complete(this.onComplete); req.complete(this.onComplete);
req.autorefresh = autorefresh; req.autorefresh = autorefresh;
req.method = method;
req.action = action; req.action = action;
req.addToHistory = true; req.addToHistory = true;
req.progressTimer = progressTimer; req.progressTimer = progressTimer;
if (url.match(/#/)) {
req.forceFocus = url.split(/#/)[1];
}
if (id) { if (id) {
this.requests[id] = req; this.requests[id] = req;
} }
@ -349,10 +354,7 @@
return true; return true;
} }
this.redirectToUrl( this.redirectToUrl(redirect, req.$target, req);
redirect, req.$target, req.url, req.getResponseHeader('X-Icinga-Rerender-Layout'), req.forceFocus,
req.getResponseHeader('X-Icinga-Refresh')
);
return true; return true;
}, },
@ -361,14 +363,20 @@
* *
* @param {string} url * @param {string} url
* @param {object} $target * @param {object} $target
* @param {string} origin * @param {XMLHttpRequest} referrer
* @param {boolean} rerenderLayout
*/ */
redirectToUrl: function (url, $target, origin, rerenderLayout, forceFocus, autoRefreshInterval) { redirectToUrl: function (url, $target, referrer) {
var icinga = this.icinga; var icinga = this.icinga,
rerenderLayout,
autoRefreshInterval,
forceFocus,
origin;
if (typeof rerenderLayout === 'undefined') { if (typeof referrer !== 'undefined') {
rerenderLayout = false; rerenderLayout = referrer.getResponseHeader('X-Icinga-Rerender-Layout');
autoRefreshInterval = referrer.autoRefreshInterval;
forceFocus = referrer.forceFocus;
origin = referrer.url;
} }
icinga.logger.debug( icinga.logger.debug(
@ -416,6 +424,7 @@
var req = this.loadUrl(url, $target); var req = this.loadUrl(url, $target);
req.forceFocus = url === origin ? forceFocus : null; req.forceFocus = url === origin ? forceFocus : null;
req.autoRefreshInterval = autoRefreshInterval; req.autoRefreshInterval = autoRefreshInterval;
req.referrer = referrer;
} }
} }
}, },
@ -448,9 +457,8 @@
this.failureNotice = null; this.failureNotice = null;
} }
var url = req.url;
this.icinga.logger.debug( this.icinga.logger.debug(
'Got response for ', req.$target, ', URL was ' + url 'Got response for ', req.$target, ', URL was ' + req.url
); );
this.processNotificationHeader(req); this.processNotificationHeader(req);
@ -570,6 +578,20 @@
rendered = true; rendered = true;
} }
var referrer = req.referrer;
if (typeof referrer === 'undefined') {
referrer = req;
}
var autoSubmit = false;
if (referrer.method === 'POST') {
var newUrl = this.icinga.utils.parseUrl(req.url);
var currentUrl = this.icinga.utils.parseUrl(req.$target.data('icingaUrl'));
if (newUrl.path === currentUrl.path && this.icinga.utils.objectsEqual(newUrl.params, currentUrl.params)) {
autoSubmit = true;
}
}
req.$target.data('icingaUrl', req.url); req.$target.data('icingaUrl', req.url);
this.icinga.ui.initializeTriStates($resp); this.icinga.ui.initializeTriStates($resp);
@ -583,13 +605,10 @@
} }
// .html() removes outer div we added above // .html() removes outer div we added above
this.renderContentToContainer($resp.html(), req.$target, req.action, req.autorefresh, req.forceFocus); this.renderContentToContainer($resp.html(), req.$target, req.action, req.autorefresh, req.forceFocus, autoSubmit);
if (oldNotifications) { if (oldNotifications) {
oldNotifications.appendTo($('#notifications')); oldNotifications.appendTo($('#notifications'));
} }
if (url.match(/#/)) {
setTimeout(this.icinga.ui.focusElement, 0, url.split(/#/)[1], req.$target);
}
if (newBody) { if (newBody) {
this.icinga.ui.fixDebugVisibility().triggerWindowResize(); this.icinga.ui.fixDebugVisibility().triggerWindowResize();
} }
@ -756,17 +775,22 @@
/** /**
* Smoothly render given HTML to given container * Smoothly render given HTML to given container
*/ */
renderContentToContainer: function (content, $container, action, autorefresh, forceFocus) { renderContentToContainer: function (content, $container, action, autorefresh, forceFocus, autoSubmit) {
// Container update happens here // Container update happens here
var scrollPos = false; var scrollPos = false;
var _this = this; var _this = this;
var containerId = $container.attr('id'); var containerId = $container.attr('id');
var activeElementPath = false; var activeElementPath = false;
var navigationAnchor = false;
var focusFallback = false; var focusFallback = false;
if (forceFocus && forceFocus.length) { if (forceFocus && forceFocus.length) {
if (typeof forceFocus === 'string') {
navigationAnchor = forceFocus;
} else {
activeElementPath = this.icinga.utils.getCSSPath($(forceFocus)); activeElementPath = this.icinga.utils.getCSSPath($(forceFocus));
}
} else if (document.activeElement && document.activeElement.id === 'search') { } else if (document.activeElement && document.activeElement.id === 'search') {
activeElementPath = '#search'; activeElementPath = '#search';
} else if (document.activeElement } else if (document.activeElement
@ -785,8 +809,8 @@
activeElementPath = this.icinga.utils.getCSSPath($activeElement); activeElementPath = this.icinga.utils.getCSSPath($activeElement);
} }
if (typeof containerId !== 'undefined') { if (! forceFocus && typeof containerId !== 'undefined') {
if (autorefresh) { if (autorefresh || autoSubmit) {
scrollPos = $container.scrollTop(); scrollPos = $container.scrollTop();
} else { } else {
scrollPos = 0; scrollPos = 0;
@ -844,7 +868,9 @@
} }
this.icinga.ui.assignUniqueContainerIds(); this.icinga.ui.assignUniqueContainerIds();
if (! activeElementPath) { if (navigationAnchor) {
setTimeout(this.icinga.ui.focusElement, 0, navigationAnchor, $container);
} else if (! activeElementPath) {
// Active element was not in this container // Active element was not in this container
if (! autorefresh) { if (! autorefresh) {
setTimeout(function() { setTimeout(function() {
@ -862,7 +888,7 @@
var $activeElement = $(activeElementPath); var $activeElement = $(activeElementPath);
if ($activeElement.length && $activeElement.is(':visible')) { if ($activeElement.length && $activeElement.is(':visible')) {
$activeElement.focus(); $activeElement[0].focus({preventScroll: autorefresh});
} else if (! autorefresh) { } else if (! autorefresh) {
if (focusFallback) { if (focusFallback) {
$(focusFallback.parent).find(focusFallback.child).focus(); $(focusFallback.parent).find(focusFallback.child).focus();
@ -874,14 +900,22 @@
}, 0); }, 0);
} }
if (scrollPos !== false) {
setTimeout($container.scrollTop.bind($container), 0, scrollPos);
}
var icinga = this.icinga; var icinga = this.icinga;
//icinga.events.applyHandlers($container); //icinga.events.applyHandlers($container);
icinga.ui.initializeControls($container); icinga.ui.initializeControls($container);
icinga.ui.fixControls(); icinga.ui.fixControls();
if (scrollPos !== false) {
$container.scrollTop(scrollPos);
// Fallback for browsers without support for focus({preventScroll: true})
setTimeout(function () {
if ($container.scrollTop() !== scrollPos) {
$container.scrollTop(scrollPos);
}
}, 0);
}
// Re-enable all click events (disabled as of performance reasons) // Re-enable all click events (disabled as of performance reasons)
// $('*').off('click'); // $('*').off('click');
}, },

View File

@ -567,18 +567,22 @@
$container.find('.controls').each(function() { $container.find('.controls').each(function() {
var $controls = $(this); var $controls = $(this);
if (! $controls.next('.fake-controls').length) { if (! $controls.prev('.fake-controls').length) {
var $tabs = $controls.find('.tabs', $controls); var $tabs = $controls.find('.tabs', $controls);
if ($tabs.length && $controls.children().length > 1 && ! $tabs.next('.tabs-spacer').length) { if ($tabs.length && $controls.children().length > 1 && ! $tabs.next('.tabs-spacer').length) {
$tabs.after($('<div class="tabs-spacer"></div>')); $tabs.after($('<div class="tabs-spacer"></div>'));
} }
var $fakeControls = $('<div class="fake-controls"></div>'); var $fakeControls = $('<div class="fake-controls"></div>');
$fakeControls.height($controls.height()).css({ $fakeControls.height($controls.height()).css({
display: 'block' // That's only temporary. It's reset in fixControls, which is called at the end of this
// function. Its purpose is to prevent the content from jumping up upon auto-refreshes.
// It works by making the fake-controls appear at the same vertical level as the controls
// and the height of the content then doesn't change when taking the controls out of the flow.
float: 'right'
}); });
$controls.css({ $controls.before($fakeControls).css({
position: 'fixed' position: 'fixed'
}).after($fakeControls); });
} }
}); });
@ -669,12 +673,15 @@
$container.find('.controls').each(function() { $container.find('.controls').each(function() {
var $controls = $(this); var $controls = $(this);
var $fakeControls = $controls.next('.fake-controls'); var $fakeControls = $controls.prev('.fake-controls');
$fakeControls.css({
float: '', // Set by initializeControls
height: $controls.height()
});
$controls.css({ $controls.css({
top: $container.offsetParent().position().top, top: $container.offsetParent().position().top,
width: $fakeControls.outerWidth() width: $fakeControls.outerWidth()
}); });
$fakeControls.height($controls.height());
}); });
var $statusBar = $container.children('.monitoring-statusbar'); var $statusBar = $container.children('.monitoring-statusbar');

View File

@ -352,6 +352,14 @@
return keys; return keys;
}, },
objectsEqual: function equals(obj1, obj2) {
return Object.keys(obj1)
.concat(Object.keys(obj2))
.every(function (key) {
return obj1[key] === obj2[key];
});
},
/** /**
* Cleanup * Cleanup
*/ */