mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-28 16:24:04 +02:00
Merge branch 'feature/modals'
This commit is contained in:
commit
10d8715d1d
@ -74,6 +74,19 @@ $innerLayoutScript = $this->layout()->innerLayout . '.phtml';
|
|||||||
<?= $this->icon('angle-double-up', $this->translate('Collapse'), ['class' => 'collapse-icon']) ?>
|
<?= $this->icon('angle-double-up', $this->translate('Collapse'), ['class' => 'collapse-icon']) ?>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="modal-ghost">
|
||||||
|
<div>
|
||||||
|
<section class="modal-window">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1></h1>
|
||||||
|
<button type="button"><?= $this->icon('cancel') ?></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-area">
|
||||||
|
<div id="modal-content" data-base-target="modal-content" tabindex data-no-icinga-ajax></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script type="text/javascript" src="<?= $this->href($jsfile) ?>"></script>
|
<script type="text/javascript" src="<?= $this->href($jsfile) ?>"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.name = '<?= $this->protectId('Icinga') ?>';
|
window.name = '<?= $this->protectId('Icinga') ?>';
|
||||||
|
@ -35,7 +35,8 @@ class JavaScript
|
|||||||
'js/icinga/behavior/flyover.js',
|
'js/icinga/behavior/flyover.js',
|
||||||
'js/icinga/behavior/expandable.js',
|
'js/icinga/behavior/expandable.js',
|
||||||
'js/icinga/behavior/filtereditor.js',
|
'js/icinga/behavior/filtereditor.js',
|
||||||
'js/icinga/behavior/selectable.js'
|
'js/icinga/behavior/selectable.js',
|
||||||
|
'js/icinga/behavior/modal.js'
|
||||||
);
|
);
|
||||||
|
|
||||||
protected static $vendorFiles = array(
|
protected static $vendorFiles = array(
|
||||||
|
@ -51,7 +51,8 @@ class StyleSheet
|
|||||||
'css/icinga/spinner.less',
|
'css/icinga/spinner.less',
|
||||||
'css/icinga/compat.less',
|
'css/icinga/compat.less',
|
||||||
'css/icinga/print.less',
|
'css/icinga/print.less',
|
||||||
'css/icinga/responsive.less'
|
'css/icinga/responsive.less',
|
||||||
|
'css/icinga/modal.less'
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
110
public/css/icinga/modal.less
Normal file
110
public/css/icinga/modal.less
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#layout > #modal {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
background-color: rgba(0, 0, 0, .6);
|
||||||
|
opacity: 0;
|
||||||
|
font-size: @font-size;
|
||||||
|
line-height: @line-height;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity .2s ease-in; // This is coupled with a `setTimout` in modal.js
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#modal-content {
|
||||||
|
display: flex;
|
||||||
|
flex: 10;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
> .content {
|
||||||
|
padding: 1em;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#modal-ghost {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-area {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: .25em 0;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
position: absolute;
|
||||||
|
top: 1em;
|
||||||
|
left: .5em;
|
||||||
|
|
||||||
|
background-color: @gray;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: white;
|
||||||
|
height: 1em;
|
||||||
|
line-height: 1em;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 1em;
|
||||||
|
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-ms-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> button > i {
|
||||||
|
position: absolute;
|
||||||
|
left: 0.25em;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
font-size: .7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
> button > .icon:before {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h1 {
|
||||||
|
padding: .25em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-window {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
background-color: white;
|
||||||
|
border-radius: .5em;
|
||||||
|
box-shadow: 0 0 2em 0 rgba(0, 0, 0, .6);
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-height: 80%;
|
||||||
|
min-height: 40vh;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 60em;
|
||||||
|
}
|
@ -414,9 +414,9 @@
|
|||||||
var count = table.selections().length;
|
var count = table.selections().length;
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
var query = table.toQuery();
|
var query = table.toQuery();
|
||||||
_this.icinga.loader.loadUrl(query, _this.icinga.events.getLinkTargetFor($tr));
|
_this.icinga.loader.loadUrl(query, _this.icinga.loader.getLinkTargetFor($tr));
|
||||||
} else {
|
} else {
|
||||||
if (_this.icinga.events.getLinkTargetFor($tr).attr('id') === 'col2') {
|
if (_this.icinga.loader.getLinkTargetFor($tr).attr('id') === 'col2') {
|
||||||
_this.icinga.ui.layout1col();
|
_this.icinga.ui.layout1col();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
198
public/js/icinga/behavior/modal.js
Normal file
198
public/js/icinga/behavior/modal.js
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
/*! Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
|
||||||
|
|
||||||
|
;(function(Icinga, $) {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Icinga.Behaviors = Icinga.Behaviors || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Behavior for modal dialogs.
|
||||||
|
*
|
||||||
|
* @param icinga {Icinga} The current Icinga Object
|
||||||
|
*/
|
||||||
|
var Modal = function(icinga) {
|
||||||
|
Icinga.EventListener.call(this, icinga);
|
||||||
|
|
||||||
|
this.icinga = icinga;
|
||||||
|
this.$layout = $('#layout');
|
||||||
|
this.$ghost = $('#modal-ghost');
|
||||||
|
|
||||||
|
this.on('submit', '#modal form', this.onFormSubmit, this);
|
||||||
|
this.on('change', '#modal form select.autosubmit', this.onFormAutoSubmit, this);
|
||||||
|
this.on('change', '#modal form input.autosubmit', this.onFormAutoSubmit, this);
|
||||||
|
this.on('click', '[data-icinga-modal]', this.onModalToggleClick, this);
|
||||||
|
this.on('click', '#layout > #modal', this.onModalLeave, this);
|
||||||
|
this.on('click', '.modal-header > button', this.onModalClose, this);
|
||||||
|
this.on('keydown', this.onKeyDown, this);
|
||||||
|
};
|
||||||
|
|
||||||
|
Modal.prototype = new Icinga.EventListener();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for toggling modals. Shows the link target in a modal dialog.
|
||||||
|
*
|
||||||
|
* @param event {Event} The `onClick` event triggered by the clicked modal-toggle element
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
Modal.prototype.onModalToggleClick = function(event) {
|
||||||
|
var _this = event.data.self;
|
||||||
|
var $a = $(event.currentTarget);
|
||||||
|
var url = $a.attr('href');
|
||||||
|
var $modal = _this.$ghost.clone();
|
||||||
|
var $urlTarget = _this.icinga.loader.getLinkTargetFor($a);
|
||||||
|
|
||||||
|
// Add view=compact, we don't want controls in a modal
|
||||||
|
url = _this.icinga.utils.addUrlParams(url, { 'view': 'compact' });
|
||||||
|
|
||||||
|
// Set the toggle's base target on the modal to use it as redirect target
|
||||||
|
$modal.data('redirectTarget', $urlTarget);
|
||||||
|
|
||||||
|
// Final preparations, the id is required so that it's not `display:none` anymore
|
||||||
|
$modal.attr('id', 'modal');
|
||||||
|
_this.$layout.append($modal);
|
||||||
|
|
||||||
|
var req = _this.icinga.loader.loadUrl(url, $modal.find('#modal-content'));
|
||||||
|
req.addToHistory = false;
|
||||||
|
req.done(function () {
|
||||||
|
_this.setTitle($modal, req.$target.data('icingaTitle').replace(/\s::\s.*/, ''));
|
||||||
|
_this.show($modal);
|
||||||
|
_this.focus($modal);
|
||||||
|
});
|
||||||
|
req.fail(function (req, _, errorThrown) {
|
||||||
|
if (req.status >= 500) {
|
||||||
|
// Yes, that's done twice (by us and by the base fail handler),
|
||||||
|
// but `renderContentToContainer` does too many useful things..
|
||||||
|
_this.icinga.loader.renderContentToContainer(req.responseText, $urlTarget, req.action);
|
||||||
|
} else if (req.status > 0) {
|
||||||
|
var msg = $(req.responseText).find('.error-message').text();
|
||||||
|
if (msg && msg !== errorThrown) {
|
||||||
|
errorThrown += ': ' + msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
_this.icinga.loader.createNotice('error', errorThrown);
|
||||||
|
}
|
||||||
|
|
||||||
|
_this.hide($modal);
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for form submits within a modal.
|
||||||
|
*
|
||||||
|
* @param event {Event} The `submit` event triggered by a form within the modal
|
||||||
|
* @param $autoSubmittedBy {jQuery} The element triggering the auto submit, if any
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
Modal.prototype.onFormSubmit = function(event, $autoSubmittedBy) {
|
||||||
|
var _this = event.data.self;
|
||||||
|
var $form = $(event.currentTarget).closest('form');
|
||||||
|
var $modal = $form.closest('#modal');
|
||||||
|
|
||||||
|
var req = _this.icinga.loader.submitForm($form, $autoSubmittedBy);
|
||||||
|
req.$redirectTarget = $modal.data('redirectTarget');
|
||||||
|
req.done(function (data, textStatus, req) {
|
||||||
|
if (req.getResponseHeader('X-Icinga-Redirect')) {
|
||||||
|
_this.hide($modal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for form auto submits within a modal.
|
||||||
|
*
|
||||||
|
* @param event {Event} The `change` event triggered by a form input within the modal
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
Modal.prototype.onFormAutoSubmit = function(event) {
|
||||||
|
return event.data.self.onFormSubmit(event, $(event.currentTarget));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for closing the modal. Closes it when the user clicks on the overlay.
|
||||||
|
*
|
||||||
|
* @param event {Event} The `click` event triggered by clicking on the overlay
|
||||||
|
*/
|
||||||
|
Modal.prototype.onModalLeave = function(event) {
|
||||||
|
var _this = event.data.self;
|
||||||
|
var $target = $(event.target);
|
||||||
|
|
||||||
|
if ($target.is('#modal')) {
|
||||||
|
_this.hide($target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for closing the modal. Closes it when the user clicks on the close button.
|
||||||
|
*
|
||||||
|
* @param event {Event} The `click` event triggered by clicking on the close button
|
||||||
|
*/
|
||||||
|
Modal.prototype.onModalClose = function(event) {
|
||||||
|
var _this = event.data.self;
|
||||||
|
|
||||||
|
_this.hide($(event.currentTarget).closest('#modal'));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for closing the modal. Closes it when the user pushed ESC.
|
||||||
|
*
|
||||||
|
* @param event {Event} The `keydown` event triggered by pushing a key
|
||||||
|
*/
|
||||||
|
Modal.prototype.onKeyDown = function(event) {
|
||||||
|
var _this = event.data.self;
|
||||||
|
|
||||||
|
if (event.which === 27) {
|
||||||
|
_this.hide(_this.$layout.children('#modal'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make final preparations and add the modal to the DOM
|
||||||
|
*
|
||||||
|
* @param $modal {jQuery} The modal element
|
||||||
|
*/
|
||||||
|
Modal.prototype.show = function($modal) {
|
||||||
|
$modal.addClass('active');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a title for the modal
|
||||||
|
*
|
||||||
|
* @param $modal {jQuery} The modal element
|
||||||
|
* @param title {string} The title
|
||||||
|
*/
|
||||||
|
Modal.prototype.setTitle = function($modal, title) {
|
||||||
|
$modal.find('.modal-header > h1').html(title);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Focus the modal
|
||||||
|
*
|
||||||
|
* @param $modal {jQuery} The modal element
|
||||||
|
*/
|
||||||
|
Modal.prototype.focus = function($modal) {
|
||||||
|
this.icinga.ui.focusElement($modal.find('.modal-window'));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the modal and remove it from the DOM
|
||||||
|
*
|
||||||
|
* @param $modal {jQuery} The modal element
|
||||||
|
*/
|
||||||
|
Modal.prototype.hide = function($modal) {
|
||||||
|
$modal.removeClass('active');
|
||||||
|
// Using `setTimeout` here to let the transition finish
|
||||||
|
setTimeout(function () {
|
||||||
|
$modal.remove();
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
Icinga.Behaviors.Modal = Modal;
|
||||||
|
|
||||||
|
})(Icinga, jQuery);
|
@ -222,25 +222,19 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
autoSubmitForm: function (event) {
|
autoSubmitForm: function (event) {
|
||||||
return event.data.self.submitForm(event, true);
|
return event.data.self.submitForm(event, $(event.currentTarget));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
submitForm: function (event, autosubmit) {
|
submitForm: function (event, $autoSubmittedBy) {
|
||||||
var _this = event.data.self;
|
var _this = event.data.self;
|
||||||
var icinga = _this.icinga;
|
|
||||||
// .closest is not required unless subelements to trigger this
|
// .closest is not required unless subelements to trigger this
|
||||||
var $form = $(event.currentTarget).closest('form');
|
var $form = $(event.currentTarget).closest('form');
|
||||||
var url = $form.attr('action');
|
|
||||||
var method = $form.attr('method');
|
|
||||||
var encoding = $form.attr('enctype');
|
|
||||||
var $button = $('input[type=submit]:focus', $form).add('button[type=submit]:focus', $form);
|
|
||||||
var progressTimer;
|
|
||||||
var $target;
|
|
||||||
var data;
|
|
||||||
|
|
||||||
|
var $button;
|
||||||
var $rememberedSubmittButton = $form.data('submitButton');
|
var $rememberedSubmittButton = $form.data('submitButton');
|
||||||
if (typeof $rememberedSubmittButton != 'undefined') {
|
if (typeof $rememberedSubmittButton != 'undefined') {
|
||||||
if ($form.has($rememberedSubmittButton)) {
|
if ($form.has($rememberedSubmittButton)) {
|
||||||
@ -253,151 +247,28 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($button.length === 0) {
|
if (typeof $button === 'undefined') {
|
||||||
var $el;
|
var $el;
|
||||||
|
|
||||||
if (typeof event.originalEvent !== 'undefined'
|
if (typeof event.originalEvent !== 'undefined'
|
||||||
&& typeof event.originalEvent.explicitOriginalTarget === 'object') { // Firefox
|
&& typeof event.originalEvent.explicitOriginalTarget === 'object') { // Firefox
|
||||||
$el = $(event.originalEvent.explicitOriginalTarget);
|
$el = $(event.originalEvent.explicitOriginalTarget);
|
||||||
icinga.logger.debug('events/submitForm: Button is event.originalEvent.explicitOriginalTarget');
|
_this.icinga.logger.debug('events/submitForm: Button is event.originalEvent.explicitOriginalTarget');
|
||||||
} else {
|
} else {
|
||||||
$el = $(event.currentTarget);
|
$el = $(event.currentTarget);
|
||||||
icinga.logger.debug('events/submitForm: Button is event.currentTarget');
|
_this.icinga.logger.debug('events/submitForm: Button is event.currentTarget');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($el && ($el.is('input[type=submit]') || $el.is('button[type=submit]'))) {
|
if ($el && ($el.is('input[type=submit]') || $el.is('button[type=submit]'))) {
|
||||||
$button = $el;
|
$button = $el;
|
||||||
} else {
|
} else {
|
||||||
icinga.logger.debug(
|
_this.icinga.logger.debug(
|
||||||
'events/submitForm: Can not determine submit button, using the first one in form'
|
'events/submitForm: Can not determine submit button, using the first one in form'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof method === 'undefined') {
|
_this.icinga.loader.submitForm($form, $autoSubmittedBy, $button);
|
||||||
method = 'POST';
|
|
||||||
} else {
|
|
||||||
method = method.toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof encoding === 'undefined') {
|
|
||||||
encoding = 'application/x-www-form-urlencoded';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof autosubmit === 'undefined') {
|
|
||||||
autosubmit = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($button.length === 0) {
|
|
||||||
$button = $('input[type=submit]', $form).add('button[type=submit]', $form).first();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($button.length) {
|
|
||||||
// Activate spinner
|
|
||||||
if ($button.hasClass('spinner')) {
|
|
||||||
$button.addClass('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
$target = _this.getLinkTargetFor($button);
|
|
||||||
} else {
|
|
||||||
$target = _this.getLinkTargetFor($form);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! url) {
|
|
||||||
// Use the URL of the target container if the form's action is not set
|
|
||||||
url = $target.closest('.container').data('icinga-url');
|
|
||||||
}
|
|
||||||
|
|
||||||
icinga.logger.debug('Submitting form: ' + method + ' ' + url, method);
|
|
||||||
|
|
||||||
if (method === 'GET') {
|
|
||||||
var dataObj = $form.serializeObject();
|
|
||||||
|
|
||||||
if (! autosubmit) {
|
|
||||||
if ($button.length && $button.attr('name') !== 'undefined') {
|
|
||||||
dataObj[$button.attr('name')] = $button.attr('value');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
url = icinga.utils.addUrlParams(url, dataObj);
|
|
||||||
} else {
|
|
||||||
if (encoding === 'multipart/form-data') {
|
|
||||||
data = new window.FormData($form[0]);
|
|
||||||
} else {
|
|
||||||
data = $form.serializeArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! autosubmit) {
|
|
||||||
if ($button.length && $button.attr('name') !== 'undefined') {
|
|
||||||
if (encoding === 'multipart/form-data') {
|
|
||||||
data.append($button.attr('name'), $button.attr('value'));
|
|
||||||
} else {
|
|
||||||
data.push({
|
|
||||||
name: $button.attr('name'),
|
|
||||||
value: $button.attr('value')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable all form controls to prevent resubmission except for our search input
|
|
||||||
// Note that disabled form inputs will not be enabled via JavaScript again
|
|
||||||
if ($target.attr('id') === $form.closest('.container').attr('id')) {
|
|
||||||
$form.find(':input:not(#search):not(:disabled)').prop('disabled', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show a spinner depending on how the form is being submitted
|
|
||||||
if (autosubmit && typeof $el !== 'undefined' && $el.siblings('.spinner').length) {
|
|
||||||
$el.siblings('.spinner').first().addClass('active');
|
|
||||||
} else if ($button.length && $button.is('button') && $button.hasClass('animated')) {
|
|
||||||
$button.addClass('active');
|
|
||||||
} else if ($button.length && $button.attr('data-progress-label')) {
|
|
||||||
var isInput = $button.is('input');
|
|
||||||
if (isInput) {
|
|
||||||
$button.prop('value', $button.attr('data-progress-label') + '...');
|
|
||||||
} else {
|
|
||||||
$button.html($button.attr('data-progress-label') + '...');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use a fixed width to prevent the button from wobbling
|
|
||||||
$button.css('width', $button.css('width'));
|
|
||||||
|
|
||||||
progressTimer = icinga.timer.register(function () {
|
|
||||||
var label = isInput ? $button.prop('value') : $button.html();
|
|
||||||
var dots = label.substr(-3);
|
|
||||||
|
|
||||||
// Using empty spaces here to prevent centered labels from wobbling
|
|
||||||
if (dots === '...') {
|
|
||||||
label = label.slice(0, -2) + ' ';
|
|
||||||
} else if (dots === '.. ') {
|
|
||||||
label = label.slice(0, -1) + '.';
|
|
||||||
} else if (dots === '. ') {
|
|
||||||
label = label.slice(0, -2) + '. ';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInput) {
|
|
||||||
$button.prop('value', label);
|
|
||||||
} else {
|
|
||||||
$button.html(label);
|
|
||||||
}
|
|
||||||
}, null, 100);
|
|
||||||
} else if ($button.length && $button.next().hasClass('spinner')) {
|
|
||||||
$('i', $button.next()).addClass('active');
|
|
||||||
} else if ($form.attr('data-progress-element')) {
|
|
||||||
var $progressElement = $('#' + $form.attr('data-progress-element'));
|
|
||||||
if ($progressElement.length) {
|
|
||||||
if ($progressElement.hasClass('spinner')) {
|
|
||||||
$('i', $progressElement).addClass('active');
|
|
||||||
} else {
|
|
||||||
$('i.spinner', $progressElement).addClass('active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var req = icinga.loader.loadUrl(url, $target, data, method);
|
|
||||||
req.forceFocus = autosubmit ? $(event.currentTarget) : $button.length ? $button : null;
|
|
||||||
req.progressTimer = progressTimer;
|
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -516,7 +387,7 @@
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$target = _this.getLinkTargetFor($a);
|
$target = icinga.loader.getLinkTargetFor($a);
|
||||||
|
|
||||||
formerUrl = $target.data('icingaUrl');
|
formerUrl = $target.data('icingaUrl');
|
||||||
if (typeof formerUrl !== 'undefined' && formerUrl.split(/#/)[0] === href.split(/#/)[0]) {
|
if (typeof formerUrl !== 'undefined' && formerUrl.split(/#/)[0] === href.split(/#/)[0]) {
|
||||||
@ -528,7 +399,7 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$target = _this.getLinkTargetFor($a);
|
$target = icinga.loader.getLinkTargetFor($a);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load link URL
|
// Load link URL
|
||||||
@ -542,63 +413,6 @@
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect the link/form target for a given element (link, form, whatever)
|
|
||||||
*/
|
|
||||||
getLinkTargetFor: function($el)
|
|
||||||
{
|
|
||||||
var targetId;
|
|
||||||
|
|
||||||
// If everything else fails, our target is the first column...
|
|
||||||
var $target = $('#col1');
|
|
||||||
|
|
||||||
// ...but usually we will use our own container...
|
|
||||||
var $container = $el.closest('.container');
|
|
||||||
if ($container.length) {
|
|
||||||
$target = $container;
|
|
||||||
}
|
|
||||||
|
|
||||||
// You can of course override the default behaviour:
|
|
||||||
if ($el.closest('[data-base-target]').length) {
|
|
||||||
targetId = $el.closest('[data-base-target]').data('baseTarget');
|
|
||||||
|
|
||||||
// Simulate _next to prepare migration to dynamic column layout
|
|
||||||
// YES, there are duplicate lines right now.
|
|
||||||
if (targetId === '_next') {
|
|
||||||
if (this.icinga.ui.hasOnlyOneColumn()) {
|
|
||||||
targetId = 'col1';
|
|
||||||
$target = $('#' + targetId);
|
|
||||||
} else {
|
|
||||||
if ($el.closest('#col2').length) {
|
|
||||||
this.icinga.ui.moveToLeft();
|
|
||||||
}
|
|
||||||
targetId = 'col2';
|
|
||||||
$target = $('#' + targetId);
|
|
||||||
}
|
|
||||||
} else if (targetId === '_self') {
|
|
||||||
$target = $el.closest('.container');
|
|
||||||
targetId = $target.attr('id');
|
|
||||||
} else if (targetId === '_main') {
|
|
||||||
targetId = 'col1';
|
|
||||||
$target = $('#' + targetId);
|
|
||||||
this.icinga.ui.layout1col();
|
|
||||||
} else {
|
|
||||||
$target = $('#' + targetId);
|
|
||||||
if (! $target.length) {
|
|
||||||
this.icinga.logger.warn('Link target "#' + targetId + '" does not exist in DOM.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hardcoded layout switch unless columns are dynamic
|
|
||||||
if ($target.attr('id') === 'col2') {
|
|
||||||
this.icinga.ui.layout2col();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $target;
|
|
||||||
},
|
|
||||||
|
|
||||||
clearSearch: function (event) {
|
clearSearch: function (event) {
|
||||||
$(event.target).parent().find('#search').attr('value', '');
|
$(event.target).parent().find('#search').attr('value', '');
|
||||||
},
|
},
|
||||||
|
@ -47,6 +47,146 @@
|
|||||||
this.icinga.timer.register(this.autorefresh, this, 500);
|
this.icinga.timer.register(this.autorefresh, this, 500);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
submitForm: function ($form, $autoSubmittedBy, $button) {
|
||||||
|
var icinga = this.icinga;
|
||||||
|
var url = $form.attr('action');
|
||||||
|
var method = $form.attr('method');
|
||||||
|
var encoding = $form.attr('enctype');
|
||||||
|
var progressTimer;
|
||||||
|
var $target;
|
||||||
|
var data;
|
||||||
|
|
||||||
|
if (typeof method === 'undefined') {
|
||||||
|
method = 'POST';
|
||||||
|
} else {
|
||||||
|
method = method.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof encoding === 'undefined') {
|
||||||
|
encoding = 'application/x-www-form-urlencoded';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof $autoSubmittedBy === 'undefined') {
|
||||||
|
$autoSubmittedBy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof $button === 'undefined') {
|
||||||
|
$button = $('input[type=submit]:focus', $form).add('button[type=submit]:focus', $form);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($button.length === 0) {
|
||||||
|
$button = $('input[type=submit]', $form).add('button[type=submit]', $form).first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($button.length) {
|
||||||
|
// Activate spinner
|
||||||
|
if ($button.hasClass('spinner')) {
|
||||||
|
$button.addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = this.getLinkTargetFor($button);
|
||||||
|
} else {
|
||||||
|
$target = this.getLinkTargetFor($form);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! url) {
|
||||||
|
// Use the URL of the target container if the form's action is not set
|
||||||
|
url = $target.closest('.container').data('icinga-url');
|
||||||
|
}
|
||||||
|
|
||||||
|
icinga.logger.debug('Submitting form: ' + method + ' ' + url, method);
|
||||||
|
|
||||||
|
if (method === 'GET') {
|
||||||
|
var dataObj = $form.serializeObject();
|
||||||
|
|
||||||
|
if (! $autoSubmittedBy) {
|
||||||
|
if ($button.length && $button.attr('name') !== 'undefined') {
|
||||||
|
dataObj[$button.attr('name')] = $button.attr('value');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
url = icinga.utils.addUrlParams(url, dataObj);
|
||||||
|
} else {
|
||||||
|
if (encoding === 'multipart/form-data') {
|
||||||
|
data = new window.FormData($form[0]);
|
||||||
|
} else {
|
||||||
|
data = $form.serializeArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $autoSubmittedBy) {
|
||||||
|
if ($button.length && $button.attr('name') !== 'undefined') {
|
||||||
|
if (encoding === 'multipart/form-data') {
|
||||||
|
data.append($button.attr('name'), $button.attr('value'));
|
||||||
|
} else {
|
||||||
|
data.push({
|
||||||
|
name: $button.attr('name'),
|
||||||
|
value: $button.attr('value')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable all form controls to prevent resubmission except for our search input
|
||||||
|
// Note that disabled form inputs will not be enabled via JavaScript again
|
||||||
|
if ($target.attr('id') === $form.closest('.container').attr('id')) {
|
||||||
|
$form.find(':input:not(#search):not(:disabled)').prop('disabled', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show a spinner depending on how the form is being submitted
|
||||||
|
if ($autoSubmittedBy && $autoSubmittedBy.siblings('.spinner').length) {
|
||||||
|
$autoSubmittedBy.siblings('.spinner').first().addClass('active');
|
||||||
|
} else if ($button.length && $button.is('button') && $button.hasClass('animated')) {
|
||||||
|
$button.addClass('active');
|
||||||
|
} else if ($button.length && $button.attr('data-progress-label')) {
|
||||||
|
var isInput = $button.is('input');
|
||||||
|
if (isInput) {
|
||||||
|
$button.prop('value', $button.attr('data-progress-label') + '...');
|
||||||
|
} else {
|
||||||
|
$button.html($button.attr('data-progress-label') + '...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a fixed width to prevent the button from wobbling
|
||||||
|
$button.css('width', $button.css('width'));
|
||||||
|
|
||||||
|
progressTimer = icinga.timer.register(function () {
|
||||||
|
var label = isInput ? $button.prop('value') : $button.html();
|
||||||
|
var dots = label.substr(-3);
|
||||||
|
|
||||||
|
// Using empty spaces here to prevent centered labels from wobbling
|
||||||
|
if (dots === '...') {
|
||||||
|
label = label.slice(0, -2) + ' ';
|
||||||
|
} else if (dots === '.. ') {
|
||||||
|
label = label.slice(0, -1) + '.';
|
||||||
|
} else if (dots === '. ') {
|
||||||
|
label = label.slice(0, -2) + '. ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInput) {
|
||||||
|
$button.prop('value', label);
|
||||||
|
} else {
|
||||||
|
$button.html(label);
|
||||||
|
}
|
||||||
|
}, null, 100);
|
||||||
|
} else if ($button.length && $button.next().hasClass('spinner')) {
|
||||||
|
$('i', $button.next()).addClass('active');
|
||||||
|
} else if ($form.attr('data-progress-element')) {
|
||||||
|
var $progressElement = $('#' + $form.attr('data-progress-element'));
|
||||||
|
if ($progressElement.length) {
|
||||||
|
if ($progressElement.hasClass('spinner')) {
|
||||||
|
$('i', $progressElement).addClass('active');
|
||||||
|
} else {
|
||||||
|
$('i.spinner', $progressElement).addClass('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var req = this.loadUrl(url, $target, data, method);
|
||||||
|
req.forceFocus = $autoSubmittedBy ? $autoSubmittedBy : $button.length ? $button : null;
|
||||||
|
req.progressTimer = progressTimer;
|
||||||
|
return req;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the given URL to the given target
|
* Load the given URL to the given target
|
||||||
*
|
*
|
||||||
@ -139,6 +279,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
req.$target = $target;
|
req.$target = $target;
|
||||||
|
req.$redirectTarget = $target;
|
||||||
req.url = url;
|
req.url = url;
|
||||||
req.done(this.onResponse);
|
req.done(this.onResponse);
|
||||||
req.fail(this.onFailure);
|
req.fail(this.onFailure);
|
||||||
@ -324,7 +465,7 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.redirectToUrl(redirect, req.$target, req);
|
this.redirectToUrl(redirect, req.$redirectTarget, req);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -772,6 +913,58 @@
|
|||||||
return $notice;
|
return $notice;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect the link/form target for a given element (link, form, whatever)
|
||||||
|
*/
|
||||||
|
getLinkTargetFor: function($el)
|
||||||
|
{
|
||||||
|
// If everything else fails, our target is the first column...
|
||||||
|
var $col1 = $('#col1');
|
||||||
|
var $target = $col1;
|
||||||
|
|
||||||
|
// ...but usually we will use our own container...
|
||||||
|
var $container = $el.closest('.container');
|
||||||
|
if ($container.length) {
|
||||||
|
$target = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can of course override the default behaviour:
|
||||||
|
if ($el.closest('[data-base-target]').length) {
|
||||||
|
var targetId = $el.closest('[data-base-target]').data('baseTarget');
|
||||||
|
|
||||||
|
// Simulate _next to prepare migration to dynamic column layout
|
||||||
|
// YES, there are duplicate lines right now.
|
||||||
|
if (targetId === '_next') {
|
||||||
|
if (this.icinga.ui.hasOnlyOneColumn()) {
|
||||||
|
$target = $col1;
|
||||||
|
} else {
|
||||||
|
if ($el.closest('#col2').length) {
|
||||||
|
this.icinga.ui.moveToLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = $('#col2');
|
||||||
|
}
|
||||||
|
} else if (targetId === '_self') {
|
||||||
|
$target = $el.closest('.container');
|
||||||
|
} else if (targetId === '_main') {
|
||||||
|
$target = $col1;
|
||||||
|
this.icinga.ui.layout1col();
|
||||||
|
} else {
|
||||||
|
$target = $('#' + targetId);
|
||||||
|
if (! $target.length) {
|
||||||
|
this.icinga.logger.warn('Link target "#' + targetId + '" does not exist in DOM.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hardcoded layout switch unless columns are dynamic
|
||||||
|
if ($target.attr('id') === 'col2') {
|
||||||
|
this.icinga.ui.layout2col();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $target;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smoothly render given HTML to given container
|
* Smoothly render given HTML to given container
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user