Merge branch 'feature/activity-indicators-for-form-submits-8369'

resolves #8369
This commit is contained in:
Johannes Meyer 2015-08-21 13:38:24 +02:00
commit a0b559f032
20 changed files with 586 additions and 52 deletions

View File

@ -27,6 +27,7 @@ class LoginForm extends Form
$this->setRequiredCue(null); $this->setRequiredCue(null);
$this->setName('form_login'); $this->setName('form_login');
$this->setSubmitLabel($this->translate('Login')); $this->setSubmitLabel($this->translate('Login'));
$this->setProgressLabel($this->translate('Logging in'));
} }
/** /**

View File

@ -344,9 +344,10 @@ class ResourceConfigForm extends ConfigForm
'submit', 'submit',
'resource_validation', 'resource_validation',
array( array(
'ignore' => true, 'ignore' => true,
'label' => $this->translate('Validate Configuration'), 'label' => $this->translate('Validate Configuration'),
'decorators' => array('ViewHelper') 'data-progress-label' => $this->translate('Validation In Progress'),
'decorators' => array('ViewHelper')
) )
); );
$this->addDisplayGroup( $this->addDisplayGroup(

View File

@ -464,9 +464,10 @@ class UserBackendConfigForm extends ConfigForm
'submit', 'submit',
'backend_validation', 'backend_validation',
array( array(
'ignore' => true, 'ignore' => true,
'label' => $this->translate('Validate Configuration'), 'label' => $this->translate('Validate Configuration'),
'decorators' => array('ViewHelper') 'data-progress-label' => $this->translate('Validation In Progress'),
'decorators' => array('ViewHelper')
) )
); );
$this->addDisplayGroup( $this->addDisplayGroup(

View File

@ -39,7 +39,7 @@
</td> </td>
<td data-base-target="_self"> <td data-base-target="_self">
<?php if ($i > 0): ?> <?php if ($i > 0): ?>
<button type="submit" name="backend_newpos" class="link-like icon-only" value="<?= sprintf( <button type="submit" name="backend_newpos" class="link-like icon-only animated move-up" value="<?= sprintf(
'%s|%s', '%s|%s',
$backendNames[$i], $backendNames[$i],
$i - 1 $i - 1
@ -53,7 +53,7 @@
</button> </button>
<?php endif; ?> <?php endif; ?>
<?php if ($i + 1 < count($backendNames)): ?> <?php if ($i + 1 < count($backendNames)): ?>
<button type="submit" name="backend_newpos" class="link-like icon-only" value="<?= sprintf( <button type="submit" name="backend_newpos" class="link-like icon-only animated move-down" value="<?= sprintf(
'%s|%s', '%s|%s',
$backendNames[$i], $backendNames[$i],
$i + 1 $i + 1

View File

@ -81,6 +81,13 @@ class Form extends Zend_Form
*/ */
protected $submitLabel; protected $submitLabel;
/**
* Label to use for showing the user an activity indicator when submitting the form
*
* @var string
*/
protected $progressLabel;
/** /**
* The url to redirect to upon success * The url to redirect to upon success
* *
@ -263,6 +270,29 @@ class Form extends Zend_Form
return $this->submitLabel; return $this->submitLabel;
} }
/**
* Set the label to use for showing the user an activity indicator when submitting the form
*
* @param string $label
*
* @return $this
*/
public function setProgressLabel($label)
{
$this->progressLabel = $label;
return $this;
}
/**
* Return the label to use for showing the user an activity indicator when submitting the form
*
* @return string
*/
public function getProgressLabel()
{
return $this->progressLabel;
}
/** /**
* Set the url to redirect to upon success * Set the url to redirect to upon success
* *
@ -638,6 +668,12 @@ class Form extends Zend_Form
public function setUseFormAutosubmit($state = true) public function setUseFormAutosubmit($state = true)
{ {
$this->useFormAutosubmit = (bool) $state; $this->useFormAutosubmit = (bool) $state;
if ($this->useFormAutosubmit) {
$this->setAttrib('data-progress-element', 'header-' . $this->getId());
} else {
$this->removeAttrib('data-progress-element');
}
return $this; return $this;
} }
@ -738,11 +774,13 @@ class Form extends Zend_Form
'submit', 'submit',
'btn_submit', 'btn_submit',
array( array(
'ignore' => true, 'ignore' => true,
'label' => $submitLabel, 'label' => $submitLabel,
'decorators' => array( 'data-progress-label' => $this->getProgressLabel(),
'decorators' => array(
'ViewHelper', 'ViewHelper',
array('HtmlTag', array('tag' => 'div')) array('Spinner', array('separator' => '')),
array('HtmlTag', array('tag' => 'div', 'class' => 'buttons'))
) )
) )
); );
@ -801,9 +839,17 @@ class Form extends Zend_Form
&& ! array_key_exists('disabledLoadDefaultDecorators', $options) && ! array_key_exists('disabledLoadDefaultDecorators', $options)
) { ) {
$options['decorators'] = static::$defaultElementDecorators; $options['decorators'] = static::$defaultElementDecorators;
if (! isset($options['data-progress-label']) && ($type === 'submit'
|| ($type === 'button' && isset($options['type']) && $options['type'] === 'submit'))
) {
array_splice($options['decorators'], 1, 0, array(array('Spinner', array('separator' => ''))));
}
} }
} else { } else {
$options = array('decorators' => static::$defaultElementDecorators); $options = array('decorators' => static::$defaultElementDecorators);
if ($type === 'submit') {
array_splice($options['decorators'], 1, 0, array(array('Spinner', array('separator' => ''))));
}
} }
$el = parent::createElement($type, $name, $options); $el = parent::createElement($type, $name, $options);
@ -1160,10 +1206,16 @@ class Form extends Zend_Form
'form' => $this 'form' => $this
)); ));
} else { } else {
$this->addDecorator('Description', array('tag' => 'h1')); if ($this->getDescription() !== null) {
if ($this->getUseFormAutosubmit()) { $this->addDecorator('Description', array('tag' => 'h1', 'escape' => !$this->getUseFormAutosubmit()))
$this->addDecorator('Autosubmit', array('accessible' => true)) ->addDecorator(
->addDecorator('HtmlTag', array('tag' => 'div', 'class' => 'header')); 'HtmlTag',
array(
'tag' => 'div',
'class' => 'header',
'id' => 'header-' . $this->getId()
)
);
} }
$this->addDecorator('FormDescriptions') $this->addDecorator('FormDescriptions')
@ -1210,6 +1262,25 @@ class Form extends Zend_Form
return $name; return $name;
} }
/**
* Retrieve form description
*
* This will return the escaped description with the autosubmit warning icon if form autosubmit is enabled.
*
* @return string
*/
public function getDescription()
{
$description = parent::getDescription();
if ($description && $this->getUseFormAutosubmit()) {
$autosubmit = $this->_getDecorator('Autosubmit', array('accessible' => true));
$autosubmit->setElement($this);
$description = $autosubmit->render($this->getView()->escape($description));
}
return $description;
}
/** /**
* Set the action to submit this form against * Set the action to submit this form against
* *

View File

@ -96,7 +96,10 @@ class Help extends Zend_Form_Decorator_Abstract
$helpContent = $this->getView()->icon( $helpContent = $this->getView()->icon(
'help', 'help',
$description . ($description && $requirement ? ' ' : '') . $requirement, $description . ($description && $requirement ? ' ' : '') . $requirement,
array('aria-hidden' => $this->accessible ? 'true' : 'false') array(
'class' => 'help',
'aria-hidden' => $this->accessible ? 'true' : 'false'
)
) . $helpContent; ) . $helpContent;
} }

View File

@ -0,0 +1,48 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Form\Decorator;
use Zend_Form_Decorator_Abstract;
use Icinga\Application\Icinga;
use Icinga\Web\View;
/**
* Decorator to add a spinner next to an element
*/
class Spinner extends Zend_Form_Decorator_Abstract
{
/**
* Return the current view
*
* @return View
*/
protected function getView()
{
return Icinga::app()->getViewRenderer()->view;
}
/**
* Add a spinner icon to a form element
*
* @param string $content The html rendered so far
*
* @return string The updated html
*/
public function render($content = '')
{
$spinner = '<div '
. ($this->getOption('id') !== null ? ' id="' . $this->getOption('id') . '"' : '')
. 'class="spinner ' . ($this->getOption('class') ?: '') . '"'
. '>'
. $this->getView()->icon('spin6')
. '</div>';
switch ($this->getPlacement()) {
case self::APPEND:
return $content . $spinner;
case self::PREPEND:
return $spinner . $content;
}
}
}

View File

@ -38,6 +38,11 @@ class Wizard
*/ */
const BTN_PREV = 'btn_prev'; const BTN_PREV = 'btn_prev';
/**
* The name and id of the element for showing the user an activity indicator when advancing the wizard
*/
const PROGRESS_ELEMENT = 'wizard_progress';
/** /**
* This wizard's parent * This wizard's parent
* *
@ -606,7 +611,7 @@ class Wizard
'type' => 'submit', 'type' => 'submit',
'value' => $pages[1]->getName(), 'value' => $pages[1]->getName(),
'label' => t('Next'), 'label' => t('Next'),
'decorators' => array('ViewHelper') 'decorators' => array('ViewHelper', 'Spinner')
) )
); );
} elseif ($index < count($pages) - 1) { } elseif ($index < count($pages) - 1) {
@ -655,8 +660,21 @@ class Wizard
); );
} }
$page->setAttrib('data-progress-element', static::PROGRESS_ELEMENT);
$page->addElement(
'note',
static::PROGRESS_ELEMENT,
array(
'order' => 99, // Ensures that it's shown on the right even if a sub-class adds another button
'decorators' => array(
'ViewHelper',
array('Spinner', array('id' => static::PROGRESS_ELEMENT))
)
)
);
$page->addDisplayGroup( $page->addDisplayGroup(
array(static::BTN_PREV, static::BTN_NEXT), array(static::BTN_PREV, static::BTN_NEXT, static::PROGRESS_ELEMENT),
'buttons', 'buttons',
array( array(
'decorators' => array( 'decorators' => array(

View File

@ -68,7 +68,7 @@ class DeleteCommentCommandForm extends CommandForm
'ignore' => true, 'ignore' => true,
'escape' => false, 'escape' => false,
'type' => 'submit', 'type' => 'submit',
'class' => 'link-like', 'class' => 'link-like spinner',
'label' => $this->getView()->icon('trash'), 'label' => $this->getView()->icon('trash'),
'title' => $this->translate('Delete this comment'), 'title' => $this->translate('Delete this comment'),
'decorators' => array('ViewHelper') 'decorators' => array('ViewHelper')

View File

@ -68,7 +68,7 @@ class DeleteDowntimeCommandForm extends CommandForm
'ignore' => true, 'ignore' => true,
'escape' => false, 'escape' => false,
'type' => 'submit', 'type' => 'submit',
'class' => 'link-like', 'class' => 'link-like spinner',
'label' => $this->getView()->icon('trash'), 'label' => $this->getView()->icon('trash'),
'title' => $this->translate('Delete this downtime'), 'title' => $this->translate('Delete this downtime'),
'decorators' => array('ViewHelper') 'decorators' => array('ViewHelper')

View File

@ -120,9 +120,10 @@ class MonitoringWizard extends Wizard implements SetupWizard
'submit', 'submit',
'backend_validation', 'backend_validation',
array( array(
'ignore' => true, 'ignore' => true,
'label' => t('Validate Configuration'), 'label' => t('Validate Configuration'),
'decorators' => array('ViewHelper') 'data-progress-label' => t('Validation In Progress'),
'decorators' => array('ViewHelper')
) )
); );
$page->getDisplayGroup('buttons')->addElement($page->getElement('backend_validation')); $page->getDisplayGroup('buttons')->addElement($page->getElement('backend_validation'));

View File

@ -186,10 +186,6 @@ table.avp {
} }
} }
.autosubmit-warning {
display: none;
}
.object-features { .object-features {
label { label {
font-weight: normal; font-weight: normal;

View File

@ -3,11 +3,18 @@
use Icinga\Web\Wizard; use Icinga\Web\Wizard;
?> ?>
<form id="<?= $form->getName(); ?>" name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>"> <form
id="<?= $form->getName(); ?>"
name="<?= $form->getName(); ?>"
enctype="<?= $form->getEncType(); ?>"
method="<?= $form->getMethod(); ?>"
action="<?= $form->getAction(); ?>"
data-progress-element="<?= Wizard::PROGRESS_ELEMENT; ?>"
>
<h2><?= $this->translate('Modules', 'setup.page.title'); ?></h2> <h2><?= $this->translate('Modules', 'setup.page.title'); ?></h2>
<p><?= $this->translate('The following modules were found in your Icinga Web 2 installation. To enable and configure a module, just tick it and click "Next".'); ?></p> <p><?= $this->translate('The following modules were found in your Icinga Web 2 installation. To enable and configure a module, just tick it and click "Next".'); ?></p>
<?php foreach ($form->getElements() as $element): ?> <?php foreach ($form->getElements() as $element): ?>
<?php if (! in_array($element->getName(), array(Wizard::BTN_PREV, Wizard::BTN_NEXT, $form->getTokenElementName(), $form->getUidElementName()))): ?> <?php if (! in_array($element->getName(), array(Wizard::BTN_PREV, Wizard::BTN_NEXT, Wizard::PROGRESS_ELEMENT, $form->getTokenElementName(), $form->getUidElementName()))): ?>
<div class="module"> <div class="module">
<h3><label for="<?= $element->getId(); ?>"><strong><?= $element->getLabel(); ?></strong></label></h3> <h3><label for="<?= $element->getId(); ?>"><strong><?= $element->getLabel(); ?></strong></label></h3>
<label for="<?= $element->getId(); ?>"><?= $element->getDescription(); ?></label> <label for="<?= $element->getId(); ?>"><?= $element->getDescription(); ?></label>
@ -20,5 +27,6 @@ use Icinga\Web\Wizard;
<div class="buttons"> <div class="buttons">
<?= $form->getElement(Wizard::BTN_PREV); ?> <?= $form->getElement(Wizard::BTN_PREV); ?>
<?= $form->getElement(Wizard::BTN_NEXT); ?> <?= $form->getElement(Wizard::BTN_NEXT); ?>
<?= $form->getElement(Wizard::PROGRESS_ELEMENT); ?>
</div> </div>
</form> </form>

View File

@ -9,7 +9,14 @@ use Icinga\Web\Wizard;
<h1><?= ucwords($moduleName) . ' ' . $this->translate('Module'); ?></h1> <h1><?= ucwords($moduleName) . ' ' . $this->translate('Module'); ?></h1>
<?= $wizard->getRequirements(); ?> <?= $wizard->getRequirements(); ?>
<?php endforeach ?> <?php endforeach ?>
<form id="<?= $form->getName(); ?>" name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>"> <form
id="<?= $form->getName(); ?>"
name="<?= $form->getName(); ?>"
enctype="<?= $form->getEncType(); ?>"
method="<?= $form->getMethod(); ?>"
action="<?= $form->getAction(); ?>"
data-progress-element="<?= Wizard::PROGRESS_ELEMENT; ?>"
>
<?= $form->getElement($form->getTokenElementName()); ?> <?= $form->getElement($form->getTokenElementName()); ?>
<?= $form->getElement($form->getUidElementName()); ?> <?= $form->getElement($form->getUidElementName()); ?>
<div class="buttons"> <div class="buttons">
@ -21,6 +28,7 @@ use Icinga\Web\Wizard;
} }
echo $btn; echo $btn;
?> ?>
<?= $form->getElement(Wizard::PROGRESS_ELEMENT); ?>
<div class="requirements-refresh"> <div class="requirements-refresh">
<?php $title = $this->translate('You may also need to restart the web-server for the changes to take effect!'); ?> <?php $title = $this->translate('You may also need to restart the web-server for the changes to take effect!'); ?>
<?= $this->qlink( <?= $this->qlink(

View File

@ -20,11 +20,20 @@ use Icinga\Web\Wizard;
<?php endif ?> <?php endif ?>
<?php endforeach ?> <?php endforeach ?>
</div> </div>
<form id="<?= $form->getName(); ?>" name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>" class="summary"> <form
id="<?= $form->getName(); ?>"
name="<?= $form->getName(); ?>"
enctype="<?= $form->getEncType(); ?>"
method="<?= $form->getMethod(); ?>"
action="<?= $form->getAction(); ?>"
data-progress-element="<?= Wizard::PROGRESS_ELEMENT; ?>"
class="summary"
>
<?= $form->getElement($form->getTokenElementName()); ?> <?= $form->getElement($form->getTokenElementName()); ?>
<?= $form->getElement($form->getUidElementName()); ?> <?= $form->getElement($form->getUidElementName()); ?>
<div class="buttons"> <div class="buttons">
<?= $form->getElement(Wizard::BTN_PREV); ?> <?= $form->getElement(Wizard::BTN_PREV); ?>
<?= $form->getElement(Wizard::BTN_NEXT)->setAttrib('class', 'finish'); ?> <?= $form->getElement(Wizard::BTN_NEXT)->setAttrib('class', 'finish'); ?>
<?= $form->getElement(Wizard::PROGRESS_ELEMENT); ?>
</div> </div>
</form> </form>

View File

@ -369,9 +369,10 @@ class WebWizard extends Wizard implements SetupWizard
'submit', 'submit',
'backend_validation', 'backend_validation',
array( array(
'ignore' => true, 'ignore' => true,
'label' => t('Validate Configuration'), 'label' => t('Validate Configuration'),
'decorators' => array('ViewHelper') 'data-progress-label' => t('Validation In Progress'),
'decorators' => array('ViewHelper')
) )
); );
$page->getDisplayGroup('buttons')->addElement($page->getElement('backend_validation')); $page->getDisplayGroup('buttons')->addElement($page->getElement('backend_validation'));

View File

@ -80,3 +80,254 @@
transform: rotate(359deg); transform: rotate(359deg);
} }
} }
@-moz-keyframes move-vertical {
0% {
-moz-transform: translate(0, 100%);
-o-transform: translate(0, 100%);
-webkit-transform: translate(0, 100%);
transform: translate(0, 100%);
}
17% {
-moz-transform: translate(0, 66%);
-o-transform: translate(0, 66%);
-webkit-transform: translate(0, 66%);
transform: translate(0, 66%);
}
33% {
-moz-transform: translate(0, 33%);
-o-transform: translate(0, 33%);
-webkit-transform: translate(0, 33%);
transform: translate(0, 33%);
}
50% {
-moz-transform: translate(0, 0);
-o-transform: translate(0, 0);
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
67% {
-moz-transform: translate(0, -33%);
-o-transform: translate(0, -33%);
-webkit-transform: translate(0, -33%);
transform: translate(0, -33%);
}
83% {
-moz-transform: translate(0, -66%);
-o-transform: translate(0, -66%);
-webkit-transform: translate(0, -66%);
transform: translate(0, -66%);
}
100% {
-moz-transform: translate(0, -100%);
-o-transform: translate(0, -100%);
-webkit-transform: translate(0, -100%);
transform: translate(0, -100%);
}
}
@-webkit-keyframes move-vertical {
0% {
-moz-transform: translate(0, 100%);
-o-transform: translate(0, 100%);
-webkit-transform: translate(0, 100%);
transform: translate(0, 100%);
}
17% {
-moz-transform: translate(0, 66%);
-o-transform: translate(0, 66%);
-webkit-transform: translate(0, 66%);
transform: translate(0, 66%);
}
33% {
-moz-transform: translate(0, 33%);
-o-transform: translate(0, 33%);
-webkit-transform: translate(0, 33%);
transform: translate(0, 33%);
}
50% {
-moz-transform: translate(0, 0);
-o-transform: translate(0, 0);
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
67% {
-moz-transform: translate(0, -33%);
-o-transform: translate(0, -33%);
-webkit-transform: translate(0, -33%);
transform: translate(0, -33%);
}
83% {
-moz-transform: translate(0, -66%);
-o-transform: translate(0, -66%);
-webkit-transform: translate(0, -66%);
transform: translate(0, -66%);
}
100% {
-moz-transform: translate(0, -100%);
-o-transform: translate(0, -100%);
-webkit-transform: translate(0, -100%);
transform: translate(0, -100%);
}
}
@-o-keyframes move-vertical {
0% {
-moz-transform: translate(0, 100%);
-o-transform: translate(0, 100%);
-webkit-transform: translate(0, 100%);
transform: translate(0, 100%);
}
17% {
-moz-transform: translate(0, 66%);
-o-transform: translate(0, 66%);
-webkit-transform: translate(0, 66%);
transform: translate(0, 66%);
}
33% {
-moz-transform: translate(0, 33%);
-o-transform: translate(0, 33%);
-webkit-transform: translate(0, 33%);
transform: translate(0, 33%);
}
50% {
-moz-transform: translate(0, 0);
-o-transform: translate(0, 0);
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
67% {
-moz-transform: translate(0, -33%);
-o-transform: translate(0, -33%);
-webkit-transform: translate(0, -33%);
transform: translate(0, -33%);
}
83% {
-moz-transform: translate(0, -66%);
-o-transform: translate(0, -66%);
-webkit-transform: translate(0, -66%);
transform: translate(0, -66%);
}
100% {
-moz-transform: translate(0, -100%);
-o-transform: translate(0, -100%);
-webkit-transform: translate(0, -100%);
transform: translate(0, -100%);
}
}
@-ms-keyframes move-vertical {
0% {
-moz-transform: translate(0, 100%);
-o-transform: translate(0, 100%);
-webkit-transform: translate(0, 100%);
transform: translate(0, 100%);
}
17% {
-moz-transform: translate(0, 66%);
-o-transform: translate(0, 66%);
-webkit-transform: translate(0, 66%);
transform: translate(0, 66%);
}
33% {
-moz-transform: translate(0, 33%);
-o-transform: translate(0, 33%);
-webkit-transform: translate(0, 33%);
transform: translate(0, 33%);
}
50% {
-moz-transform: translate(0, 0);
-o-transform: translate(0, 0);
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
67% {
-moz-transform: translate(0, -33%);
-o-transform: translate(0, -33%);
-webkit-transform: translate(0, -33%);
transform: translate(0, -33%);
}
83% {
-moz-transform: translate(0, -66%);
-o-transform: translate(0, -66%);
-webkit-transform: translate(0, -66%);
transform: translate(0, -66%);
}
100% {
-moz-transform: translate(0, -100%);
-o-transform: translate(0, -100%);
-webkit-transform: translate(0, -100%);
transform: translate(0, -100%);
}
}
@keyframes move-vertical {
0% {
-moz-transform: translate(0, 100%);
-o-transform: translate(0, 100%);
-webkit-transform: translate(0, 100%);
transform: translate(0, 100%);
}
17% {
-moz-transform: translate(0, 66%);
-o-transform: translate(0, 66%);
-webkit-transform: translate(0, 66%);
transform: translate(0, 66%);
}
33% {
-moz-transform: translate(0, 33%);
-o-transform: translate(0, 33%);
-webkit-transform: translate(0, 33%);
transform: translate(0, 33%);
}
50% {
-moz-transform: translate(0, 0);
-o-transform: translate(0, 0);
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
67% {
-moz-transform: translate(0, -33%);
-o-transform: translate(0, -33%);
-webkit-transform: translate(0, -33%);
transform: translate(0, -33%);
}
83% {
-moz-transform: translate(0, -66%);
-o-transform: translate(0, -66%);
-webkit-transform: translate(0, -66%);
transform: translate(0, -66%);
}
100% {
-moz-transform: translate(0, -100%);
-o-transform: translate(0, -100%);
-webkit-transform: translate(0, -100%);
transform: translate(0, -100%);
}
}

View File

@ -107,6 +107,38 @@ form.inline {
display: inline; display: inline;
} }
div.spinner {
display: inline-block;
margin-top: 0.2em;
margin-left: 0.25em;
i {
visibility: hidden;
&.active {
visibility: visible;
&:before {
.animate(spin 2s infinite linear);
}
}
&:before {
margin: 0; // Disables wobbling
}
}
}
button.animated.active {
&.move-up i:before {
.animate(move-vertical 500ms infinite linear);
}
&.move-down i:before {
.animate(move-vertical 500ms infinite linear reverse);
}
}
button, .button-like { button, .button-like {
font-size: 0.9em; font-size: 0.9em;
font-weight: bold; font-weight: bold;
@ -252,12 +284,18 @@ form div.element {
form label { form label {
display: inline-block; display: inline-block;
vertical-align: top;
margin-top: 0.25em;
margin-right: 1em; margin-right: 1em;
font-size: 0.9em; font-size: 0.9em;
width: 10em; width: 10em;
} }
form div.element > * { form div.element i.help:before {
margin-top: 0.25em;
}
label ~ * {
vertical-align: top; vertical-align: top;
} }
@ -295,11 +333,11 @@ select.grant-permissions {
width: auto; width: auto;
} }
label ~ input, label ~ select { label ~ input, label ~ select, label ~ textarea {
margin-left: 1.35em; margin-left: 1.3em;
} }
label + i ~ input, label + i ~ select { label + i ~ input, label + i ~ select, label + i ~ textarea {
margin-left: 0; margin-left: 0;
} }
@ -307,6 +345,21 @@ button.noscript-apply {
margin-left: 0.5em; margin-left: 0.5em;
} }
i.autosubmit-warning {
display: inline-block;
margin-left: 0.2em;
margin-top: 0.25em;
&:before {
margin: 0; // Disables wobbling
}
&.spinning:before {
content: '\e874'; // icon-spin6
.animate(spin 2s infinite linear);
}
}
html.no-js i.autosubmit-warning { html.no-js i.autosubmit-warning {
.sr-only; .sr-only;
} }

View File

@ -212,6 +212,7 @@
var method = $form.attr('method'); var method = $form.attr('method');
var encoding = $form.attr('enctype'); var encoding = $form.attr('enctype');
var $button = $('input[type=submit]:focus', $form).add('button[type=submit]:focus', $form); var $button = $('input[type=submit]:focus', $form).add('button[type=submit]:focus', $form);
var progressTimer;
var $target; var $target;
var data; var data;
@ -227,11 +228,11 @@
icinga.logger.debug('events/submitForm: Button is event.currentTarget'); 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]')) && $el.is(':focus')) {
$button = $el; $button = $el;
} else { } else {
icinga.logger.debug( 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 last one in form'
); );
} }
} }
@ -246,8 +247,12 @@
encoding = 'application/x-www-form-urlencoded'; encoding = 'application/x-www-form-urlencoded';
} }
if (typeof autosubmit === 'undefined') {
autosubmit = false;
}
if ($button.length === 0) { if ($button.length === 0) {
$button = $('input[type=submit]', $form).add('button[type=submit]', $form).first(); $button = $('button[type=submit]', $form).add('input[type=submit]', $form).last();
} }
if ($button.length) { if ($button.length) {
@ -271,7 +276,7 @@
if (method === 'GET') { if (method === 'GET') {
var dataObj = $form.serializeObject(); var dataObj = $form.serializeObject();
if (typeof autosubmit === 'undefined' || ! autosubmit) { if (! autosubmit) {
if ($button.length && $button.attr('name') !== 'undefined') { if ($button.length && $button.attr('name') !== 'undefined') {
dataObj[$button.attr('name')] = $button.attr('value'); dataObj[$button.attr('name')] = $button.attr('value');
} }
@ -289,7 +294,7 @@
$form.find(':input:not(:disabled)').prop('disabled', true); $form.find(':input:not(:disabled)').prop('disabled', true);
}, 0); }, 0);
if (! typeof autosubmit === 'undefined' && autosubmit) { if (autosubmit) {
if ($button.length) { if ($button.length) {
// We're autosubmitting the form so the button has not been clicked, however, // We're autosubmitting the form so the button has not been clicked, however,
// to be really safe, we're disabling the button explicitly, just in case.. // to be really safe, we're disabling the button explicitly, just in case..
@ -310,7 +315,7 @@
data = $form.serializeArray(); data = $form.serializeArray();
} }
if (typeof autosubmit === 'undefined' || ! autosubmit) { if (! autosubmit) {
if ($button.length && $button.attr('name') !== 'undefined') { if ($button.length && $button.attr('name') !== 'undefined') {
if (encoding === 'multipart/form-data') { if (encoding === 'multipart/form-data') {
data.append($button.attr('name'), $button.attr('value')); data.append($button.attr('name'), $button.attr('value'));
@ -328,7 +333,55 @@
// Note that disabled form inputs will not be enabled via JavaScript again // Note that disabled form inputs will not be enabled via JavaScript again
$form.find(':input:not(#search):not(:disabled)').prop('disabled', true); $form.find(':input:not(#search):not(:disabled)').prop('disabled', true);
icinga.loader.loadUrl(url, $target, data, method); // Show a spinner depending on how the form is being submitted
if (autosubmit && typeof $el !== 'undefined' && $el.next().hasClass('autosubmit-warning')) {
$el.next().addClass('spinning');
} 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.autosubmit-warning', $progressElement).addClass('spinning');
}
}
}
icinga.loader.loadUrl(url, $target, data, method).progressTimer = progressTimer;
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();

View File

@ -42,13 +42,15 @@
/** /**
* Load the given URL to the given target * Load the given URL to the given target
* *
* @param {string} url URL to be loaded * @param {string} url URL to be loaded
* @param {object} target Target jQuery element * @param {object} target Target jQuery element
* @param {object} data Optional parameters, usually for POST requests * @param {object} data Optional parameters, usually for POST requests
* @param {string} method HTTP method, default is 'GET' * @param {string} method HTTP method, default is 'GET'
* @param {string} action How to handle the response ('replace' or 'append'), default is 'replace' * @param {string} action How to handle the response ('replace' or 'append'), default is 'replace'
* @param {boolean} autorefresh Whether the cause is a autorefresh or not
* @param {object} progressTimer A timer to be stopped when the request is done
*/ */
loadUrl: function (url, $target, data, method, action, autorefresh) { loadUrl: function (url, $target, data, method, action, autorefresh, progressTimer) {
var id = null; var id = null;
// Default method is GET // Default method is GET
@ -131,6 +133,7 @@
req.autorefresh = autorefresh; req.autorefresh = autorefresh;
req.action = action; req.action = action;
req.addToHistory = true; req.addToHistory = true;
req.progressTimer = progressTimer;
if (id) { if (id) {
this.requests[id] = req; this.requests[id] = req;
@ -537,6 +540,10 @@
return; return;
} }
if (typeof req.progressTimer !== 'undefined') {
this.icinga.timer.unregister(req.progressTimer);
}
// .html() removes outer div we added above // .html() removes outer div we added above
this.renderContentToContainer($resp.html(), req.$target, req.action, req.autorefresh); this.renderContentToContainer($resp.html(), req.$target, req.action, req.autorefresh);
if (oldNotifications) { if (oldNotifications) {
@ -638,6 +645,10 @@
req.$target.data('icingaUrl', url); req.$target.data('icingaUrl', url);
} }
if (typeof req.progressTimer !== 'undefined') {
this.icinga.timer.unregister(req.progressTimer);
}
if (req.status > 0) { if (req.status > 0) {
this.icinga.logger.error( this.icinga.logger.error(
req.status, req.status,