diff --git a/demos/interactive/accordion-nested.php b/demos/interactive/accordion-nested.php index 50e44ee539..2ac01dc94c 100644 --- a/demos/interactive/accordion-nested.php +++ b/demos/interactive/accordion-nested.php @@ -5,25 +5,17 @@ namespace Atk4\Ui\Demos; use Atk4\Ui\Accordion; -use Atk4\Ui\Button; use Atk4\Ui\Form; use Atk4\Ui\Header; use Atk4\Ui\LoremIpsum; use Atk4\Ui\Message; -use Atk4\Ui\View; /** @var \Atk4\Ui\App $app */ require_once __DIR__ . '/../init-app.php'; -/* -Button::addTo($app, ['View Form input split in Accordion section', 'class.small right floated basic blue' => true, 'iconRight' => 'right arrow']) - ->link(['accordion-in-form']); -View::addTo($app, ['ui' => 'clearing divider']); -*/ - Header::addTo($app, ['Nested accordions']); -$addAccordionFunc = function ($view, $maxDepth = 2, $level = 0) use (&$addAccordionFunc) { +$addAccordionFunc = function ($view, $maxDepth, $level = 0) use (&$addAccordionFunc) { $accordion = Accordion::addTo($view, ['type' => ['styled', 'fluid']]); // static section @@ -38,27 +30,24 @@ $i2 = $accordion->addSection('Dynamic Text', function ($v) use ($addAccordionFunc, $maxDepth, $level) { Message::addTo($v, ['Every time you open this accordion item, you will see a different text', 'ui' => 'tiny message']); LoremIpsum::addTo($v, ['size' => 2]); - if ($level < $maxDepth) { - $addAccordionFunc($v, $maxDepth, $level + 1); - } + + $addAccordionFunc($v, $maxDepth, $level + 1); }); // dynamic section - form view $i3 = $accordion->addSection('Dynamic Form', function ($v) use ($addAccordionFunc, $maxDepth, $level) { Message::addTo($v, ['Loading a form dynamically.', 'ui' => 'tiny message']); $form = Form::addTo($v); - $form->addControl('Email'); + $form->addControl('email'); $form->onSubmit(function (Form $form) { - return $form->success('Subscribed ' . $form->model->get('Email') . ' to newsletter.'); + return $form->success('Subscribed ' . $form->model->get('email') . ' to newsletter.'); }); - if ($level < $maxDepth) { - $addAccordionFunc($v, $maxDepth, $level + 1); - } + $addAccordionFunc($v, $maxDepth, $level + 1); }); return $accordion; }; // add accordion structure -$addAccordionFunc($app); +$addAccordionFunc($app, 4); diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 20e7b22418..1f30f145d6 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -159,9 +159,6 @@ parameters: - path: 'src/Form/Control/Checkbox.php' message: '~^Access to an undefined property Atk4\\Ui\\Jquery::\$checked\.$~' - - - path: 'src/Form/Layout/Section/Accordion.php' - message: '~^Call to an undefined method Atk4\\Ui\\View::jsOpen\(\)\.$~' - path: 'src/JsVueService.php' message: '~^Call to an undefined method Atk4\\Ui\\JsChain::createAtkVue\(\)\.$~' diff --git a/public/agileui.less b/public/agileui.less index 2c1bc721b9..eac004df22 100644 --- a/public/agileui.less +++ b/public/agileui.less @@ -43,7 +43,7 @@ @tabletWidth: 768px; @adminMenuHeight: 48px; @atkFooterHeight : 50px; -@menuBorder: 1px solid rgba(255,255,255,0.10); +@menuBorder: 1px solid rgba(255,255,255,0.1); @atkSlidePanelColor: @offWhite; diff --git a/src/Accordion.php b/src/Accordion.php index df7be92d75..71c97fa4f4 100644 --- a/src/Accordion.php +++ b/src/Accordion.php @@ -45,7 +45,6 @@ public function addSection($title, \Closure $callback = null, $icon = 'dropdown' // if there is callback action, then use VirtualPage if ($callback) { $section->virtualPage = VirtualPage::addTo($section, ['ui' => '']); - $section->virtualPage->stickyGet('__atk-dyn-section', '1'); $section->virtualPage->set($callback); } @@ -152,22 +151,15 @@ public function getSectionIdx(AccordionSection $section) return $idx; } - /** - * Check if accordion section is dynamic. - */ - public function isDynamicSection(): bool - { - return isset($_GET['__atk-dyn-section']); - } - protected function renderView(): void { if ($this->type) { $this->addClass($this->type); } - // Only set Accordion in Top container. Otherwise Nested accordion won't work. - if (!$this->getClosestOwner($this, AccordionSection::class) && !$this->isDynamicSection()) { + // initialize top accordion only, otherwise nested accordion won't work + // https://github.com/fomantic/Fomantic-UI/issues/254 + if ($this->getClosestOwner(AccordionSection::class) === null) { $this->js(true)->accordion($this->settings); } diff --git a/src/Form/Control.php b/src/Form/Control.php index 91b7a6655d..62b0a7a4c0 100644 --- a/src/Form/Control.php +++ b/src/Form/Control.php @@ -125,8 +125,7 @@ protected function renderTemplateToHtml(string $region = null): string { $output = parent::renderTemplateToHtml($region); - /** @var Form|null $form */ - $form = $this->getClosestOwner($this, Form::class); + $form = $this->getClosestOwner(Form::class); return $form !== null ? $form->fixOwningFormAttrInRenderedHtml($output) : $output; } diff --git a/src/Form/Layout/Section/Accordion.php b/src/Form/Layout/Section/Accordion.php index b71abfbe83..38e24e4015 100644 --- a/src/Form/Layout/Section/Accordion.php +++ b/src/Form/Layout/Section/Accordion.php @@ -30,7 +30,7 @@ protected function init(): void $jsError = [$form->js()->form('add prompt', $fieldName, $str)]; // if a form control is part of an accordion section, it will open that section. - $section = $form->getClosestOwner($form->getControl($fieldName), AccordionSection::class); + $section = $form->getControl($fieldName)->getClosestOwner(AccordionSection::class); if ($section) { $jsError[] = $section->getOwner()->jsOpen($section); } diff --git a/src/Popup.php b/src/Popup.php index 187e0790f4..fd8c0e8437 100644 --- a/src/Popup.php +++ b/src/Popup.php @@ -107,17 +107,15 @@ protected function init(): void ->addMoreInfo('owner', $this->getOwner()); } - if (($this->triggerBy instanceof Menu - || $this->triggerBy instanceof MenuItem - || $this->triggerBy instanceof Dropdown) && $this->triggerOn === null - ) { - $this->triggerOn = 'hover'; - } - - if ( - $this->triggerBy instanceof Button && $this->triggerOn === null - ) { - $this->triggerOn = 'click'; + if ($this->triggerOn === null) { + if ($this->triggerBy instanceof Menu + || $this->triggerBy instanceof MenuItem + || $this->triggerBy instanceof Dropdown + ) { + $this->triggerOn = 'hover'; + } elseif ($this->triggerBy instanceof Button) { + $this->triggerOn = 'click'; + } } $this->popOptions = array_merge($this->popOptions, [ diff --git a/src/View.php b/src/View.php index 38f7f4cb33..85a0fd6509 100644 --- a/src/View.php +++ b/src/View.php @@ -297,24 +297,25 @@ public function add($object, $region = null): AbstractView } /** - * Get objects closest owner which is instance of particular class. + * Get closest owner which is instance of particular class. * - * If there are no such owner (or grand-owner etc.) object, then return. + * @template T of View * - * Note: this is internal method, but should be public because other objects - * should be able to call it. + * @param class-string $class + * + * @phpstan-return T|null */ - public function getClosestOwner(self $object, string $class): ?self + public function getClosestOwner(string $class): ?self { - if ($object->issetOwner()) { + if (!$this->issetOwner()) { return null; } - if ($object->getOwner() instanceof $class) { - return $object->getOwner(); + if ($this->getOwner() instanceof $class) { + return $this->getOwner(); } - return $this->getClosestOwner($object->getOwner(), $class); + return $this->getOwner()->getClosestOwner($class); } // }}} diff --git a/tests-behat/accordion.feature b/tests-behat/accordion.feature index 21d836cfa6..c1c3268180 100644 --- a/tests-behat/accordion.feature +++ b/tests-behat/accordion.feature @@ -4,3 +4,19 @@ Feature: Accordion Given I am on "form/form-section-accordion.php" Then I should see "Email" Then I fill in "email" with "xxx@xxx.com" + + Scenario: Nested Accordion + Given I am on "interactive/accordion-nested.php" + Then I click using selector "xpath((//div[text()='Static Text'])[1])" + Then I click using selector "xpath((//div[text()='Static Text'])[1])" + Then I click using selector "xpath((//div[text()='Static Text'])[1])" + Then I click using selector "xpath((//div[text()='Static Text'])[2])" + Then I click using selector "xpath((//div[text()='Dynamic Text'])[3])" + Then I click using selector "xpath((//div[text()='Dynamic Text'])[3])" + Then I click using selector "xpath((//div[text()='Dynamic Text'])[3])" + Then I click using selector "xpath((//div[text()='Dynamic Form'])[4])" + Then I click using selector "xpath((//div[text()='Dynamic Form'])[4])" + Then I click using selector "xpath((//div[text()='Dynamic Form'])[4])" + Then I fill in "email" with "xxx@xxx.com" + When I press button "Save" + Then I should see "Subscribed xxx@xxx.com to newsletter."