Skip to content

Commit

Permalink
Do not refresh a container when form input was changed or a form elem…
Browse files Browse the repository at this point in the history
…ent is focused

Listen for changes in form elements and abort all reloads that contain a form with
at least one changed form element. Do not refresh containers that contain a focused form
element, except of elements with autofocus, to preserve form elements with a dropdown.
Only focus autofocus elements when there is currently no other selection.

refs #7146
refs #5537
fixes #7162
  • Loading branch information
majentsch committed Sep 19, 2014
1 parent f1d3b72 commit ef2f332
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 11 deletions.
3 changes: 2 additions & 1 deletion library/Icinga/Web/JavaScript.php
Expand Up @@ -26,7 +26,8 @@ class JavaScript
'js/icinga/behavior/tooltip.js',
'js/icinga/behavior/sparkline.js',
'js/icinga/behavior/tristate.js',
'js/icinga/behavior/navigation.js'
'js/icinga/behavior/navigation.js',
'js/icinga/behavior/form.js'
);

protected static $vendorFiles = array(
Expand Down
104 changes: 104 additions & 0 deletions public/js/icinga/behavior/form.js
@@ -0,0 +1,104 @@
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}

/**
* Controls behavior of form elements, depending reload and
*/
(function(Icinga, $) {

"use strict";

Icinga.Behaviors = Icinga.Behaviors || {};

var Form = function (icinga) {
Icinga.EventListener.call(this, icinga);
this.on('keyup change', 'form input', this.onChange, this);

// store the modification state of all input fields
this.inputs = {};
};
Form.prototype = new Icinga.EventListener();

/**
* @param evt
*/
Form.prototype.onChange = function (evt) {
var el = evt.target;
var form = evt.data.self.uniqueFormName($(el).closest('form')[0] || {});
evt.data.self.inputs[form] = evt.data.self.inputs[form] || {};
if (el.value !== '') {
evt.data.self.inputs[form][el.name] = true;
} else {
evt.data.self.inputs[form][el.name] = false;
}
};

/**
* Try to generate an unique form name using the action
* and the name of the given form element
*
* @param form {HTMLFormElement} The
* @returns {String} The unique name
*/
Form.prototype.uniqueFormName = function(form)
{
return (form.name || 'undefined') + '.' + (form.action || 'undefined');
};

/**
* Mutates the HTML before it is placed in the DOM after a reload
*
* @param content {String} The content to be rendered
* @param $container {jQuery} The target container where the html will be rendered in
* @param action {String} The action-url that caused the reload
* @param autorefresh {Boolean} Whether the rendering is due to an autoRefresh
*
* @returns {string|NULL} The content to be rendered, or NULL, when nothing should be changed
*/
Form.prototype.renderHook = function(content, $container, action, autorefresh) {
var origFocus = document.activeElement;
var containerId = $container.attr('id');
var icinga = this.icinga;
var self = this.icinga.behaviors.form;
var changed = false;
$container.find('form').each(function () {
var form = self.uniqueFormName(this);
if (autorefresh) {
// check if an element in this container was changed
$(this).find('input').each(function () {
var name = this.name;
if (self.inputs[form] && self.inputs[form][name]) {
icinga.logger.debug(
'form input: ' + form + '.' + name + ' was changed and aborts reload...'
);
changed = true;
}
});
} else {
// user-triggered reload, forget all changes to forms in this container
self.inputs[form] = null;
}
});
if (changed) {
return null;
}
if (
// is the focus among the elements to be replaced?
$container.has(origFocus).length &&
// is an autorefresh
autorefresh &&

// and has focus
$(origFocus).length &&
!$(origFocus).hasClass('autofocus') &&
$(origFocus).closest('form').length
) {
icinga.logger.debug('Not changing content for ' + containerId + ' form has focus');
return null;
}
return content;
};

Icinga.Behaviors.Form = Form;

}) (Icinga, jQuery);
5 changes: 3 additions & 2 deletions public/js/icinga/events.js
Expand Up @@ -75,8 +75,9 @@
}
});

$('input.autofocus', el).focus();

if (document.activeElement === document.body) {
$('input.autofocus', el).focus();
}
var searchField = $('#menu input.search', el);
// Remember initial search field value if any
if (searchField.length && searchField.val().length) {
Expand Down
20 changes: 13 additions & 7 deletions public/js/icinga/loader.js
Expand Up @@ -661,6 +661,7 @@
// Container update happens here
var scrollPos = false;
var self = this;
var origFocus = document.activeElement;
var containerId = $container.attr('id');
if (typeof containerId !== 'undefined') {
if (autorefresh) {
Expand All @@ -670,13 +671,18 @@
}
}

var origFocus = document.activeElement;
if (
// Do not reload menu when search field has content
(containerId === 'menu' && $(origFocus).length && $(origFocus).val().length)
// TODO: remove once #7146 is solved
|| (containerId !== 'menu' && typeof containerId !== 'undefined' && autorefresh && origFocus && $(origFocus).closest('form').length && $container.has($(origFocus)) && $(origFocus).closest('#' + containerId).length && ! $(origFocus).hasClass('autosubmit'))) {
this.icinga.logger.debug('Not changing content for ', containerId, ' form has focus');
var discard = false;
$.each(self.icinga.behaviors, function(name, behavior) {
if (behavior.renderHook) {
var changed = behavior.renderHook(content, $container, action, autorefresh);
if (!changed) {
discard = true;
} else {
content = changed;
}
}
});
if (discard) {
return;
}

Expand Down
2 changes: 1 addition & 1 deletion public/js/icinga/logger.js
Expand Up @@ -37,7 +37,7 @@
/**
* Raise or lower current log level
*
* Messages blow this threshold will be silently discarded
* Messages below this threshold will be silently discarded
*/
setLevel: function (level) {
if ('undefined' !== typeof this.numericLevel(level)) {
Expand Down

0 comments on commit ef2f332

Please sign in to comment.