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']) ?>
|
||||
</button>
|
||||
</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">
|
||||
window.name = '<?= $this->protectId('Icinga') ?>';
|
||||
|
|
|
@ -35,7 +35,8 @@ class JavaScript
|
|||
'js/icinga/behavior/flyover.js',
|
||||
'js/icinga/behavior/expandable.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(
|
||||
|
|
|
@ -51,7 +51,8 @@ class StyleSheet
|
|||
'css/icinga/spinner.less',
|
||||
'css/icinga/compat.less',
|
||||
'css/icinga/print.less',
|
||||
'css/icinga/responsive.less'
|
||||
'css/icinga/responsive.less',
|
||||
'css/icinga/modal.less'
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
if (count > 0) {
|
||||
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 {
|
||||
if (_this.icinga.events.getLinkTargetFor($tr).attr('id') === 'col2') {
|
||||
if (_this.icinga.loader.getLinkTargetFor($tr).attr('id') === 'col2') {
|
||||
_this.icinga.ui.layout1col();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
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 icinga = _this.icinga;
|
||||
|
||||
// .closest is not required unless subelements to trigger this
|
||||
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');
|
||||
if (typeof $rememberedSubmittButton != 'undefined') {
|
||||
if ($form.has($rememberedSubmittButton)) {
|
||||
|
@ -253,151 +247,28 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
if ($button.length === 0) {
|
||||
if (typeof $button === 'undefined') {
|
||||
var $el;
|
||||
|
||||
if (typeof event.originalEvent !== 'undefined'
|
||||
&& typeof event.originalEvent.explicitOriginalTarget === 'object') { // Firefox
|
||||
$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 {
|
||||
$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]'))) {
|
||||
$button = $el;
|
||||
} else {
|
||||
icinga.logger.debug(
|
||||
_this.icinga.logger.debug(
|
||||
'events/submitForm: Can not determine submit button, using the first one in form'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof method === 'undefined') {
|
||||
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;
|
||||
_this.icinga.loader.submitForm($form, $autoSubmittedBy, $button);
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
@ -516,7 +387,7 @@
|
|||
}
|
||||
return false;
|
||||
}
|
||||
$target = _this.getLinkTargetFor($a);
|
||||
$target = icinga.loader.getLinkTargetFor($a);
|
||||
|
||||
formerUrl = $target.data('icingaUrl');
|
||||
if (typeof formerUrl !== 'undefined' && formerUrl.split(/#/)[0] === href.split(/#/)[0]) {
|
||||
|
@ -528,7 +399,7 @@
|
|||
return false;
|
||||
}
|
||||
} else {
|
||||
$target = _this.getLinkTargetFor($a);
|
||||
$target = icinga.loader.getLinkTargetFor($a);
|
||||
}
|
||||
|
||||
// Load link URL
|
||||
|
@ -542,63 +413,6 @@
|
|||
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) {
|
||||
$(event.target).parent().find('#search').attr('value', '');
|
||||
},
|
||||
|
|
|
@ -47,6 +47,146 @@
|
|||
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
|
||||
*
|
||||
|
@ -139,6 +279,7 @@
|
|||
});
|
||||
|
||||
req.$target = $target;
|
||||
req.$redirectTarget = $target;
|
||||
req.url = url;
|
||||
req.done(this.onResponse);
|
||||
req.fail(this.onFailure);
|
||||
|
@ -324,7 +465,7 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
this.redirectToUrl(redirect, req.$target, req);
|
||||
this.redirectToUrl(redirect, req.$redirectTarget, req);
|
||||
return true;
|
||||
},
|
||||
|
||||
|
@ -772,6 +913,58 @@
|
|||
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
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue