New interface TabbedForm for activate first tab with error input (#5472)

* New interface TabbedForm for activate first tab with error input

* Update CHANGELOG.md

* New interface TabbedFormModel for activate first tab with error input

* Test for TabbedFormModel

Co-authored-by: Lucas Bartholemy <luke-@users.noreply.github.com>
This commit is contained in:
Yuriy Bakhtin 2022-01-11 20:24:32 +03:00 committed by GitHub
parent a35a8f91fb
commit 154266f188
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 304 additions and 21 deletions

View File

@ -3,5 +3,6 @@
- Fix #5434: Hide disabled next/prev buttons on guide first/last steps
- Fix #5456: `canImpersonate` only possible for SystemAdmins
- Enh #5472: New interface `TabbedFormModel` for activate first tab with error input
- Enh #5224: Add reply-to email in the settings
- Enh #5471: On the pending approval page, add grouped actions and custom columns

View File

@ -0,0 +1,42 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2021 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\ui\form\interfaces;
/**
* Interface TabbedFormModel
* It is related for Model classes only
*
* @property-read array $tabs
*
* @since 1.11.0
*/
interface TabbedFormModel
{
/**
* Initialize tabs for the Form
*
* Example of the result:
* [
* [
* 'label' => 'First tab',
* 'view' => 'first-tab-view',
* 'linkOptions' => ['class' => 'first-tab-style'],
* 'fields' => ['name', 'email', 'password'], // Define all fields from the tab which may have errors after submit in order to make this tab active
* ],
* [
* 'label' => 'Second tab',
* 'view' => 'second-tab-view',
* 'linkOptions' => ['class' => 'second-tab-style'],
* 'fields' => ['description', 'tags'],
* ],
* ]
*
* @return array
*/
public function getTabs(): array;
}

View File

@ -0,0 +1,64 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2021 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\ui\form\widgets;
use humhub\modules\ui\form\interfaces\TabbedFormModel;
use humhub\widgets\Tabs;
use yii\base\Model;
/**
* Class TabsForm
*
* @since 1.11.0
*/
class FormTabs extends Tabs
{
/**
* @var TabbedFormModel
*/
public $form;
/**
* @inheritdoc
*/
protected function beforeSortItems()
{
$this->initTabbedFormItems();
parent::beforeSortItems();
}
private function initTabbedFormItems()
{
if (!($this->form instanceof TabbedFormModel && $this->form instanceof Model)) {
return;
}
$items = $this->form->tabs;
if (empty($items)) {
return;
}
$this->items = $items;
if (!$this->form->hasErrors()) {
return;
}
// Find first error with field and activate that tab
$errorFields = array_keys($this->form->getErrors());
foreach ($this->items as $t => $tab) {
if (!empty(array_intersect($tab['fields'], $errorFields))) {
$this->items[$t]['active'] = true;
// Stop on first found error
return;
}
}
}
}

View File

@ -0,0 +1,90 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2021 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace tests\codeception\models;
use humhub\modules\ui\form\interfaces\TabbedFormModel;
use yii\base\Model;
class TestTabbedFormModel extends Model implements TabbedFormModel
{
/**
* @var string
*/
public $firstname;
/**
* @var string
*/
public $lastname;
/**
* @var string
*/
public $email;
/**
* @var int
*/
public $countryId;
/**
* @var int
*/
public $stateId;
/**
* @var int
*/
public $cityId;
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'firstname' => 'First name',
'lastname' => 'Last name',
'email' => 'E-mail address',
'countryId' => 'Country',
'stateId' => 'State',
'cityId' => 'City',
];
}
/**
* @inheritdoc
*/
public function rules()
{
return [
[['firstname', 'lastname', 'email'], 'string'],
[['countryId', 'stateId', 'cityId'], 'integer'],
[['email', 'countryId'], 'required'],
];
}
/**
* @inheritdoc
*/
public function getTabs(): array
{
return [
[
'label' => 'First tab',
'view' => 'tab-first',
'fields' => ['firstname', 'lastname', 'email'],
],
[
'label' => 'Second tab',
'view' => 'tab-second',
'fields' => ['countryId', 'stateId', 'cityId'],
]
];
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2021 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace tests\codeception\unit;
use humhub\modules\ui\form\widgets\ActiveForm;
use humhub\modules\ui\form\widgets\FormTabs;
use tests\codeception\_support\HumHubDbTestCase;
use tests\codeception\models\TestTabbedFormModel;
class TabbedFormTest extends HumHubDbTestCase
{
public function testTabbedForm()
{
// Create a model with not filled required fields
$tabbedForm = new TestTabbedFormModel();
$tabbedForm->email = 'email@test.local';
$this->assertFalse($tabbedForm->validate());
// Create a form with tabs
$form = ActiveForm::begin(['action' => '/test']);
$tabs = new FormTabs([
'form' => $tabbedForm,
'viewPath' => '@ui/tests/codeception/views',
'params' => ['form' => $form, 'tabbedForm' => $tabbedForm],
]);
$this->assertTrue($tabs->beforeRun());
$result = $tabs->run();
$tabs->afterRun($result);
ActiveForm::end();
// Check the second tab is active with error in the empty field Country ID
$this->assertArrayNotHasKey('active', $tabs->items[0]);
$this->assertArrayHasKey('active', $tabs->items[1]);
$this->assertTrue($tabs->items[1]['active']); // <- Second tab is active
$tabbedForm->countryId = 10;
$this->assertTrue($tabbedForm->validate());
}
}

View File

@ -0,0 +1,17 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2021 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
use humhub\modules\ui\form\widgets\ActiveForm;
use tests\codeception\models\TestTabbedFormModel;
/* @var $form ActiveForm */
/* @var $tabbedForm TestTabbedFormModel */
?>
<?= $form->field($tabbedForm, 'firstname')->textInput() ?>
<?= $form->field($tabbedForm, 'lastname')->textInput() ?>
<?= $form->field($tabbedForm, 'email')->textInput() ?>

View File

@ -0,0 +1,17 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2021 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
use humhub\modules\ui\form\widgets\ActiveForm;
use tests\codeception\models\TestTabbedFormModel;
/* @var $form ActiveForm */
/* @var $tabbedForm TestTabbedFormModel */
?>
<?= $form->field($tabbedForm, 'countryId')->textInput() ?>
<?= $form->field($tabbedForm, 'stateId')->textInput() ?>
<?= $form->field($tabbedForm, 'cityId')->textInput() ?>

View File

@ -64,27 +64,6 @@ class Tabs extends \yii\bootstrap\Tabs
return false;
}
$index = 0;
foreach ($this->items as $key => $item) {
if (isset($item['view'])) {
$view = $item['view'];
if ($this->viewPath && strpos($view, '@') === false) {
$view = $this->viewPath . '/'.$item['view'];
}
$this->items[$key]['content'] = $this->render($view, $this->getParams($item));
unset($item['view']);
unset($item['params']);
}
if (!isset($item['sortOrder'])) {
// keep stable sorting by adding counter (otherwise equal sorOrders will destroy index ordering)
$this->items[$key]['sortOrder'] = 1000 + ($index * 10);
}
$index++;
}
$this->sortItems();
return true;
@ -135,11 +114,40 @@ class Tabs extends \yii\bootstrap\Tabs
$this->items[] = $item;
}
/**
* Before sorts the items
*/
protected function beforeSortItems()
{
$index = 0;
foreach ($this->items as $key => $item) {
if (isset($item['view'])) {
$view = $item['view'];
if ($this->viewPath && strpos($view, '@') === false) {
$view = $this->viewPath . '/'.$item['view'];
}
$this->items[$key]['content'] = $this->render($view, $this->getParams($item));
unset($item['view']);
unset($item['params']);
}
if (!isset($item['sortOrder'])) {
// keep stable sorting by adding counter (otherwise equal sorOrders will destroy index ordering)
$this->items[$key]['sortOrder'] = 1000 + ($index * 10);
}
$index++;
}
}
/**
* Sorts the item attribute by sortOrder
*/
private function sortItems()
{
$this->beforeSortItems();
usort($this->items, function ($a, $b) {
if ($a['sortOrder'] == $b['sortOrder']) {
return 0;