diff --git a/.php_cs.dist b/.php_cs.dist index 3bdb35dd70..381d180894 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -21,6 +21,11 @@ return PhpCsFixer\Config::create() ], // disable some too strict rules + 'phpdoc_types' => [ + // keep enabled, but without "alias" group to not fix + // "Callback" to "callback" in phpdoc + 'groups' => ['simple', 'meta'] + ], 'phpdoc_types_order' => [ 'null_adjustment' => 'always_last', 'sort_algorithm' => 'none', diff --git a/behat-travis.yml b/behat-travis.yml index c6cdc1fbbf..f6c1be7e71 100644 --- a/behat-travis.yml +++ b/behat-travis.yml @@ -2,7 +2,7 @@ default: suites: my_suite: contexts: - - FeatureContext + - atk4\ui\behat\FeatureContext - Behat\MinkExtension\Context\MinkContext extensions: Behat\MinkExtension: diff --git a/demos/_unit-test/console.php b/demos/_unit-test/console.php new file mode 100644 index 0000000000..f19036e13b --- /dev/null +++ b/demos/_unit-test/console.php @@ -0,0 +1,20 @@ +urlTrigger = 'console_test'; + +$console = \atk4\ui\Console::addTo($app, ['sse' => $sse]); + +$console->set(function ($console) { + $console->output('Executing test process...'); + $console->output('Now trying something dangerous..'); + echo 'direct output is captured'; + + throw new \atk4\core\Exception('BOOM - exceptions are caught'); +}); diff --git a/demos/_unit-test/console_exec.php b/demos/_unit-test/console_exec.php new file mode 100644 index 0000000000..ca1250e96e --- /dev/null +++ b/demos/_unit-test/console_exec.php @@ -0,0 +1,13 @@ +urlTrigger = 'console_test'; + +$console = \atk4\ui\Console::addTo($app, ['sse' => $sse]); +$console->exec('/bin/pwd'); diff --git a/demos/_unit-test/console_run.php b/demos/_unit-test/console_run.php new file mode 100644 index 0000000000..48aed9d353 --- /dev/null +++ b/demos/_unit-test/console_run.php @@ -0,0 +1,32 @@ +log('info', 'Console will automatically pick up output from all DebugTrait objects'); + $this->debug('debug'); + $this->emergency('emergency'); + $this->alert('alert'); + $this->critical('critical'); + $this->error('error'); + $this->warning('warning'); + $this->notice('notice'); + $this->info('info'); + + return 123; + } +}); + +$sse = jsSSE::addTo($app); +$sse->urlTrigger = 'console_test'; + +$console = \atk4\ui\Console::addTo($app, ['sse' => $sse]); +$console->runMethod($testRunClass::addTo($app), 'test'); diff --git a/demos/_unit-test/crud.php b/demos/_unit-test/crud.php new file mode 100644 index 0000000000..8753dab316 --- /dev/null +++ b/demos/_unit-test/crud.php @@ -0,0 +1,28 @@ +getAction('edit'); +$edit->ui = ['execButton' => [\atk4\ui\Button::class, 'EditMe', 'blue']]; +$edit->description = 'edit'; + +$delete = $m->getAction('delete'); +$delete->ui = []; +$delete->description = 'delete'; + +$add = $m->getAction('add'); +$add->ui = ['execButton' => [\atk4\ui\Button::class, 'AddMe', 'blue']]; +$add->description = 'Add'; + +$g = \atk4\ui\CRUD::addTo($app, ['ipp' => 10, 'menu' => ['class' => ['atk-grid-menu']]]); +$g->setModel($m); + +$g->addQuickSearch(['name'], true); diff --git a/demos/_unit-test/exception_test.php b/demos/_unit-test/exception.php similarity index 63% rename from demos/_unit-test/exception_test.php rename to demos/_unit-test/exception.php index 838215fbb6..03ff107718 100644 --- a/demos/_unit-test/exception_test.php +++ b/demos/_unit-test/exception.php @@ -2,11 +2,17 @@ namespace atk4\ui\demo; +use atk4\ui\CallbackLater; + require_once __DIR__ . '/../atk-init.php'; // JUST TO TEST Exceptions and Error throws -$modal = \atk4\ui\Modal::addTo($app); +$cb = CallbackLater::addTo($app); +$cb->urlTrigger = 'm_cb'; + +$modal = \atk4\ui\Modal::addTo($app, ['cb' => $cb]); +$modal->name = 'm_test'; $modal->set(function ($m) use ($modal) { throw new \Exception('TEST!'); @@ -15,7 +21,10 @@ $button = \atk4\ui\Button::addTo($app, ['Test modal exception']); $button->on('click', $modal->show()); -$modal2 = \atk4\ui\Modal::addTo($app); +$cb1 = CallbackLater::addTo($app); + +$cb1->urlTrigger = 'm2_cb'; +$modal2 = \atk4\ui\Modal::addTo($app, ['cb' => $cb1]); $modal2->set(function ($m) use ($modal2) { trigger_error('error triggered'); diff --git a/demos/_unit-test/post.php b/demos/_unit-test/post.php new file mode 100644 index 0000000000..79eb7d063b --- /dev/null +++ b/demos/_unit-test/post.php @@ -0,0 +1,17 @@ +name = 'test_form'; + +$f->addField('f1')->set('v1'); + +$f->onSubmit(function ($f) { + return new jsToast('Post ok'); +}); diff --git a/demos/_unit-test/reload.php b/demos/_unit-test/reload.php new file mode 100644 index 0000000000..4b7bbdd617 --- /dev/null +++ b/demos/_unit-test/reload.php @@ -0,0 +1,24 @@ + 'segment']); +$v->set('Test'); +$v->name = 'reload'; + +$b = Button::addTo($app)->set('Reload'); +$b->on('click', new jsReload($v)); + +$cb = Callback::addTo($app); +$cb->urlTrigger = 'c_reload'; + +\atk4\ui\Loader::addTo($app, ['cb' => $cb])->set(function ($page) { + $v = View::addTo($page, ['ui' => 'segment'])->set('loaded'); +}); diff --git a/demos/_unit-test/sse.php b/demos/_unit-test/sse.php new file mode 100644 index 0000000000..baacd4aae3 --- /dev/null +++ b/demos/_unit-test/sse.php @@ -0,0 +1,24 @@ +set('This will trigger a network request for testing sse...'); + +$sse = \atk4\ui\jsSSE::addTo($app); +// url trigger must match php_unit test in sse provider. +$sse->urlTrigger = 'see_test'; + +$v->js(true, $sse->set(function () use ($sse) { + $sse->send(new jsExpression('console.log("test")')); + $sse->send(new jsExpression('console.log("test")')); + $sse->send(new jsExpression('console.log("test")')); + $sse->send(new jsExpression('console.log("test")')); + + // non-SSE way + return $sse->send(new jsExpression('console.log("test")')); +})); diff --git a/demos/atk-init.php b/demos/atk-init.php index dadcbb3abf..bd7943c5e3 100644 --- a/demos/atk-init.php +++ b/demos/atk-init.php @@ -49,9 +49,6 @@ $app->initLayout(new $layout()); $layout = $app->layout; -// Need for phpunit only for producing right url. -$layout->name = 'atk_admin'; -$layout->id = $layout->name; if ($layout instanceof \atk4\ui\Layout\Navigable) { $layout->addMenuItem(['Welcome to Agile Toolkit', 'icon' => 'gift'], [$demosUrl . 'index']); diff --git a/demos/collection/actions.php b/demos/collection/actions.php index 2d4cf629ba..fed02eaeb5 100644 --- a/demos/collection/actions.php +++ b/demos/collection/actions.php @@ -2,6 +2,8 @@ namespace atk4\ui\demo; +use atk4\ui\Button; + require_once __DIR__ . '/../atk-init.php'; \atk4\ui\Button::addTo($app, ['js Event Executor', 'small right floated basic blue', 'iconRight' => 'right arrow']) @@ -39,13 +41,13 @@ $app->add($grid = new \atk4\ui\GridLayout(['columns' => 3])); -$grid->add($executor = new \atk4\ui\ActionExecutor\Basic(), 'r1c1'); +$grid->add($executor = new \atk4\ui\ActionExecutor\Basic(['executorButton' => [Button::class, 'Import', 'primary']]), 'r1c1'); $executor->setAction($action); $executor->ui = 'segment'; $executor->description = 'Execute action using "Basic" executor and path="." argument'; $executor->setArguments(['path' => '.']); -$executor->onHook(\atk4\ui\ActionExecutor\Basic::HOOK_AFTER_EXECUTE, function ($x, $ret) { - return new \atk4\ui\jsToast('Files imported: ' . $ret); +$executor->onHook(\atk4\ui\ActionExecutor\Basic::HOOK_AFTER_EXECUTE, function ($x) { + return new \atk4\ui\jsToast('Done!'); }); $grid->add($executor = new \atk4\ui\ActionExecutor\ArgumentForm(), 'r1c2'); @@ -53,7 +55,7 @@ $executor->description = 'ArgumentForm executor will ask user about arguments'; $executor->ui = 'segment'; $executor->onHook(\atk4\ui\ActionExecutor\Basic::HOOK_AFTER_EXECUTE, function ($x, $ret) { - return new \atk4\ui\jsToast('Files imported: ' . $ret); + return new \atk4\ui\jsToast('Imported!'); }); $grid->add($executor = new \atk4\ui\ActionExecutor\Preview(), 'r1c3'); @@ -63,7 +65,7 @@ $executor->description = 'Displays preview in console prior to executing'; $executor->setArguments(['path' => '.']); $executor->onHook(\atk4\ui\ActionExecutor\Basic::HOOK_AFTER_EXECUTE, function ($x, $ret) { - return new \atk4\ui\jsToast('Files imported: ' . $ret); + return new \atk4\ui\jsToast('Confirm!'); }); \atk4\ui\CRUD::addTo($app, ['ipp' => 5])->setModel($files); diff --git a/demos/collection/tablefilter.php b/demos/collection/tablefilter.php index 639354062c..d957b8fd32 100644 --- a/demos/collection/tablefilter.php +++ b/demos/collection/tablefilter.php @@ -6,7 +6,8 @@ //For popup positioning to work correctly, table need to be inside a view segment. $view = \atk4\ui\View::addTo($app, ['ui' => 'basic segment']); -$g = \atk4\ui\Grid::addTo($view); +// Important: menu class added for Behat testing. +$g = \atk4\ui\Grid::addTo($view, ['menu' => ['class' => ['atk-grid-menu']]]); $m = new CountryLock($db); $m->addExpression('is_uk', $m->expr('if([iso] = [country], 1, 0)', ['country' => 'GB']))->type = 'boolean'; diff --git a/demos/interactive/card-action.php b/demos/interactive/card-action.php index 31c5cc4290..39860a1765 100644 --- a/demos/interactive/card-action.php +++ b/demos/interactive/card-action.php @@ -2,6 +2,8 @@ namespace atk4\ui\demo; +use atk4\ui\Button; + require_once __DIR__ . '/../atk-init.php'; \atk4\ui\Button::addTo($app, ['Card Deck', 'small right floated basic blue', 'iconRight' => 'right arrow']) @@ -25,10 +27,10 @@ 'args' => [ 'note' => ['type' => 'string', 'required' => true], ], - 'callback' => function ($m) { - return 'Note to client is sent.'; + 'callback' => function ($m, $note) { + return 'Note to client is sent: ' . $note; }, ]); $c->addSection('Client Country:', $client, ['iso', 'numcode', 'phonecode'], true); -$c->addClickAction($notify, null, [$client->get('id')]); +$c->addClickAction($notify, new Button(['Send Note']), [$client->get('id')]); diff --git a/demos/interactive/wizard.php b/demos/interactive/wizard.php index 03356011f3..097100ec61 100644 --- a/demos/interactive/wizard.php +++ b/demos/interactive/wizard.php @@ -2,6 +2,7 @@ namespace atk4\ui\demo; +use atk4\ui\Callback; use atk4\ui\Wizard; require_once __DIR__ . '/../atk-init.php'; @@ -9,8 +10,7 @@ /** * Demonstrates how to use a wizard. */ -$t = Wizard::addTo($app); - +$t = Wizard::addTo($app, ['stepCallback' => Callback::addTo($app, ['urlTrigger' => 'demo_wizard'])]); // First step will automatcally be active when you open page first. It // will contain the 'Next' button with a link. $t->addStep('Welcome', function (Wizard $w) { @@ -26,6 +26,8 @@ // to store wizard-specific variables $t->addStep(['Set DSN', 'icon' => 'configure', 'description' => 'Database Connection String'], function (Wizard $w) { $f = \atk4\ui\Form::addTo($w); + // IMPORTANT - needed for php_unit Wizard test. + $f->name = 'w_form'; $f->addField('dsn', 'Connect DSN', ['required' => true])->placeholder = 'mysql://user:pass@db-host.example.com/mydb'; $f->onSubmit(function (\atk4\ui\Form $form) use ($w) { diff --git a/demos/obsolete/notify2.php b/demos/obsolete/notify2.php index 52c4a7be46..4e7b0dda44 100644 --- a/demos/obsolete/notify2.php +++ b/demos/obsolete/notify2.php @@ -25,6 +25,9 @@ public function init(): void $head = \atk4\ui\Header::addTo($app, ['Notification Types']); $form = \atk4\ui\Form::addTo($app, ['segment']); +// Unit test only. +$form->name = 'notify'; + \atk4\ui\Label::addTo($form, ['Some of notification options that can be set.', 'top attached'], ['AboveFields']); $form->buttonSave->set('Show'); $form->setModel(new $notifierClass($db), false); diff --git a/features/basicexecutor.feature b/features/basicexecutor.feature new file mode 100644 index 0000000000..7f8e16ef66 --- /dev/null +++ b/features/basicexecutor.feature @@ -0,0 +1,24 @@ +Feature: Executor + Testing basic action executor + + Scenario: basic + Given I am on "collection/actions.php" + And I press button "Import" + And wait for callback + Then Toast display should contains text "Done!" + + Scenario: form + Given I am on "collection/actions.php" + And I press button "Run" + And wait for callback + Then I should see "Must not be empty" + Then I fill in "path" with "." + Then I press button "Run" + And wait for callback + Then Toast display should contains text "Imported!" + + Scenario: preview + Given I am on "collection/actions.php" + And I press button "Confirm" + And wait for callback + Then Toast display should contains text "Confirm!" diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/atk4/ui/behat/FeatureContext.php similarity index 75% rename from features/bootstrap/FeatureContext.php rename to features/bootstrap/atk4/ui/behat/FeatureContext.php index 5531835b1f..ed5e45ae13 100644 --- a/features/bootstrap/FeatureContext.php +++ b/features/bootstrap/atk4/ui/behat/FeatureContext.php @@ -1,5 +1,7 @@ getMink()->getSession($name); } /** - * Wait for a certain time in ms. + * Sleep for a certain time in ms. * - * @Then I wait :arg1 + * @Then I sleep :arg1 ms * * @param $arg1 */ - public function iWait($arg1) + public function iSleep($arg1) { $this->getSession()->wait($arg1); } @@ -66,6 +68,25 @@ public function iPressButton($arg1) $button->click(); } + /** + * @Then I press menu button :arg1 using class :arg2 + */ + public function iPressMenuButtonUsingClass($arg1, $arg2) + { + $menu = $this->getSession()->getPage()->find('css', '.ui.menu.' . $arg2); + if (!$menu) { + throw new \Exception('Unable to find a menu with class ' . $arg2); + } + + $link = $menu->find('xpath', '//a[text()="' . $arg1 . '"]'); + if (!$link) { + throw new \Exception('Unable to find menu with title ' . $arg1); + } + + $script = '$("#' . $link->getAttribute('id') . '").click()'; + $this->getSession()->executeScript($script); + } + /** * @Given I click link :arg1 */ @@ -75,6 +96,23 @@ public function iClickLink($arg1) $link->click(); } + /** + * @Then I click filter column name :arg1 + */ + public function iClickFilterColumnName($arg1) + { + $column = $this->getSession()->getPage()->find('css', "th[data-column='" . $arg1 . "']"); + if (!$column) { + throw new \Exception('Unable to find a column ' . $arg1); + } + $icon = $column->find('css', 'i'); + if (!$icon) { + throw new \Exception('Column does not contain clickable icon.'); + } + $script = '$("#' . $icon->getAttribute('id') . '").click()'; + $this->getSession()->executeScript($script); + } + /** * @Given I click tab with title :arg1 * @@ -182,6 +220,17 @@ public function modalIsOpenWithText($arg1) } } + /** + * @Then Active tab should be :arg1 + */ + public function activeTabShouldBe($arg1) + { + $tab = $this->getSession()->getPage()->find('css', '.ui.tabular.menu > .item.active'); + if ($tab->getText() !== $arg1) { + throw new \Exception('Active tab is not ' . $arg1); + } + } + /** * @Then Modal is showing text :arg1 inside tag :arg2 * @@ -249,6 +298,14 @@ public function toastDisplayShouldContainText($arg1) } } + /** + * @Then I wait for toast to hide + */ + public function iWaitForToastToHide() + { + $this->getSession()->wait(20000, '$(".ui.toast-container").children().length === 0'); + } + /** * @Then I select value :arg1 in lookup :arg2 * @@ -280,6 +337,45 @@ public function iSelectValueInLookup($arg1, $arg2) $this->getSession()->executeScript($script); } + /** + * @Then I search grid for :arg1 + */ + public function iSearchGridFor($arg1) + { + $search = $this->getSession()->getPage()->find('css', 'input.atk-grid-search'); + if (!$search) { + throw new \Exception('Unable to find search input.'); + } + + $search->setValue($arg1); + } + + /** + * @Then I click icon using css :arg1 + */ + public function iClickIconUsingCss($arg1) + { + $icon = $this->getSession()->getPage()->find('css', $arg1); + if (!$icon) { + throw new \Exception('Unable to find search remove icon.'); + } + + $icon->click(); + } + + /** + * Wait for an element, usually an auto trigger element, to show that loading has start" + * Example, when entering value in jsSearch for grid. We need to auto trigger to fire before + * doing waiting for callback. + * $arg1 should represent the element selector for jQuery. + * + * @Then I wait for loading to start in :arg1 + */ + public function iWaitForLoadingToStartIn($arg1) + { + $this->getSession()->wait(2000, '$("' . $arg1 . '").hasClass("loading")'); + } + /** * @Then I test javascript */ diff --git a/features/card.feature b/features/card.feature new file mode 100644 index 0000000000..47a702c02b --- /dev/null +++ b/features/card.feature @@ -0,0 +1,12 @@ +Feature: Card + Testing card with model action + + Scenario: + Given I am on "interactive/card-action.php" + And I press button "Send Note" + And wait for callback + Then Modal is showing text "Note" inside tag "label" + When I fill in "note" with "This is a test note" + Then I press Modal button "Notify" + And wait for callback + Then Toast display should contains text "This is a test note" diff --git a/features/crud.feature b/features/crud.feature new file mode 100644 index 0000000000..557db196a1 --- /dev/null +++ b/features/crud.feature @@ -0,0 +1,44 @@ +Feature: Crud + Testing crud add, edit, delete using search + + Scenario: + Given I am on "_unit-test/crud.php" + + Scenario: add + Then I press menu button "Add" using class "atk-grid-menu" + And wait for callback + And I sleep "500" ms + Then I fill in "name" with "Test" + Then I fill in "iso" with "TT" + Then I fill in "iso3" with "TTT" + Then I fill in "numcode" with "123" + Then I fill in "phonecode" with "1" + Then I press button "AddMe" + And wait for callback + Then Toast display should contains text "Form Submit" + + Scenario: search + Then I search grid for "kingdom" +# make sure auto query trigger + And I wait for loading to start in "button.atk-search-button" + And wait for callback + Then I should see "United Kingdom" + + Scenario: edit + Then I press button "Edit" + And wait for callback + Then Modal is open with text "Edit Country" + Then I press button "EditMe" + And wait for callback + Then Toast display should contains text "Form Submit" + And wait for callback +# make sure search query stick + Then I should see "United Kingdom" + + Scenario: delete + Then I press button "Delete" +# Js Modal transition + And I sleep "250" ms + Then I press button "Ok" + And wait for callback + Then I should not see "United Kingdom" diff --git a/features/filter.feature b/features/filter.feature index 70aca79ff5..900c0480e3 100644 --- a/features/filter.feature +++ b/features/filter.feature @@ -4,4 +4,12 @@ Feature: Filter Scenario: Given I am on "collection/tablefilter.php" Then I should see "Clear Filters" - + Then I click filter column name "name" + When I fill in "value" with "united kingdom" + Then I press button "Set" + And wait for callback + And wait for callback + Then I should see "United Kingdom" + Then I press menu button "Clear Filters" using class "atk-grid-menu" + And wait for callback + Then I should see "Australia" diff --git a/features/grid.feature b/features/grid.feature new file mode 100644 index 0000000000..717e0fa8f3 --- /dev/null +++ b/features/grid.feature @@ -0,0 +1,18 @@ +Feature: Grid + Testing grid search + + Scenario: search + Given I am on "collection/grid.php" + Then I search grid for "kingdom" +# make sure auto query trigger + And I wait for loading to start in "button.atk-search-button" + And wait for callback + Then I should see "United Kingdom" + Then I press button "Test" + And wait for callback + Then Toast display should contains text "United Kingdom" + Then I wait for toast to hide +# click search remove icon + Then I click icon using css "i.atk-remove-icon" + And wait for callback + Then I should not see "United Kingdom" diff --git a/features/js.feature b/features/js.feature index 1c671170ee..275cf1c94d 100644 --- a/features/js.feature +++ b/features/js.feature @@ -21,7 +21,7 @@ Feature: JS And I don't see button "Hide button C" # wait for DOM to complete button hiding - And I wait 500 + And I sleep "500" ms When I press button "Callback Test" And wait for callback diff --git a/features/tab.feature b/features/tab.feature new file mode 100644 index 0000000000..7c034f352e --- /dev/null +++ b/features/tab.feature @@ -0,0 +1,11 @@ +Feature: Tab + Testing Tab + + Scenario: + Given I am on "interactive/tabs.php" + And wait for callback + Then Active tab should be "Default Active Tab" + Then I should see "This is the active tab by default" + Then I click tab with title "Dynamic Lorem Ipsum" + And wait for callback + Then I should see "you will see a different text" diff --git a/src/Console.php b/src/Console.php index 94a6b6e05f..05244a7390 100644 --- a/src/Console.php +++ b/src/Console.php @@ -81,7 +81,10 @@ public function set($callback = null, $event = null) $this->event = $event; } - $this->sse = jsSSE::addTo($this); + if (!$this->sse) { + $this->sse = jsSSE::addTo($this); + } + $this->sse->set(function () use ($callback) { $this->sseInProgress = true; diff --git a/src/FormField/TreeItemSelector.php b/src/FormField/TreeItemSelector.php index 3ccd808d9e..2e32f75779 100644 --- a/src/FormField/TreeItemSelector.php +++ b/src/FormField/TreeItemSelector.php @@ -69,7 +69,7 @@ class TreeItemSelector extends Generic /** * Callback for onTreeChange. * - * @var null | jsCallback + * @var jsCallback|null */ private $cb; diff --git a/src/Grid.php b/src/Grid.php index 084bcfb618..7e22e62921 100644 --- a/src/Grid.php +++ b/src/Grid.php @@ -144,7 +144,7 @@ public function init(): void $this->app ? $this->app->stickyGet($this->paginator->name) : $this->stickyGet($this->paginator->name); } - $this->stickyGet('_q'); + $this->app ? $this->app->stickyGet('_q') : $this->stickyGet('_q'); } /** diff --git a/src/ItemsPerPageSelector.php b/src/ItemsPerPageSelector.php index d728555d3d..9bdf10bce5 100644 --- a/src/ItemsPerPageSelector.php +++ b/src/ItemsPerPageSelector.php @@ -36,7 +36,7 @@ class ItemsPerPageSelector extends View /** * The callback function. * - * @var callback|null + * @var Callback|null */ public $cb; diff --git a/src/Loader.php b/src/Loader.php index 8a6d255d1f..a11d411b69 100644 --- a/src/Loader.php +++ b/src/Loader.php @@ -46,7 +46,9 @@ public function init(): void $this->shim = [View::class, 'class' => ['padded segment'], 'style' => ['min-height' => '7em']]; } - $this->cb = Callback::addTo($this, ['appSticky' => $this->appStickyCb]); + if (!$this->cb) { + $this->cb = Callback::addTo($this, ['appSticky' => $this->appStickyCb]); + } } /** diff --git a/src/Modal.php b/src/Modal.php index 3d09cec981..fdebd4e428 100644 --- a/src/Modal.php +++ b/src/Modal.php @@ -91,7 +91,9 @@ public function enableCallback() { $this->cb_view = View::addTo($this); $this->cb_view->stickyGet('__atk_m', $this->name); - $this->cb = CallbackLater::addTo($this->cb_view, ['appSticky' => $this->appStickyCb]); + if (!$this->cb) { + $this->cb = CallbackLater::addTo($this->cb_view, ['appSticky' => $this->appStickyCb]); + } $this->cb->set(function () { if ($this->cb->triggered() && $this->fx) { diff --git a/src/Popup.php b/src/Popup.php index 0dfb20ca49..4c0873a00d 100644 --- a/src/Popup.php +++ b/src/Popup.php @@ -57,7 +57,7 @@ class Popup extends View /** * The callback use to generate dynamic content. * - * @var callback|null + * @var Callback|null */ public $cb; diff --git a/src/VirtualPage.php b/src/VirtualPage.php index 3d24dede43..e36785d8c7 100644 --- a/src/VirtualPage.php +++ b/src/VirtualPage.php @@ -13,7 +13,7 @@ */ class VirtualPage extends View { - /** @var callback */ + /** @var Callback */ public $cb; /** @var callable Optional callback function of virtual page */ diff --git a/src/Wizard.php b/src/Wizard.php index 4774c1399d..e385a2a0ae 100644 --- a/src/Wizard.php +++ b/src/Wizard.php @@ -15,7 +15,7 @@ class Wizard extends View /** * Callback, that triggers selection of a step. * - * @var callable + * @var Callback */ public $stepCallback; @@ -60,7 +60,10 @@ class Wizard extends View public function init(): void { parent::init(); - $this->stepCallback = Callback::addTo($this, ['urlTrigger' => $this->name]); + + if (!$this->stepCallback) { + $this->stepCallback = Callback::addTo($this, ['urlTrigger' => $this->name]); + } $this->currentStep = (int) $this->stepCallback->triggered() ?: 0; diff --git a/template/semantic-ui/js-search.html b/template/semantic-ui/js-search.html index 5fff2be3c0..1128f53136 100644 --- a/template/semantic-ui/js-search.html +++ b/template/semantic-ui/js-search.html @@ -1,6 +1,7 @@ +