From 18600ed8c82cbf7cd8f5700fc3c1e7aba1288582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Mon, 5 Dec 2022 04:04:09 +0100 Subject: [PATCH] View::js()/on() returns null when JS chain is explicitly given (#1944) --- demos/_includes/DemoLookup.php | 2 +- demos/_unit-test/callback.php | 2 +- demos/interactive/modal.php | 12 ++++----- demos/javascript/js.php | 10 +++++-- js/.eslintrc.js | 2 +- public/css/agileui.less | 2 +- public/css/agileui.min.css | 2 +- src/CardDeck.php | 12 +++------ src/Crud.php | 25 +++++++++-------- src/Form/Control/Lookup.php | 2 +- src/Form/Control/Multiline.php | 6 ++--- src/JsCallback.php | 9 ++++++- src/JsFunction.php | 8 +----- src/Modal.php | 6 ++--- src/Table/Column/ActionMenu.php | 14 +++++++--- src/Table/Column/FilterPopup.php | 2 +- src/UserAction/ConfirmationExecutor.php | 4 +-- src/UserAction/StepExecutorTrait.php | 18 ++++++------- src/View.php | 36 ++++++++++++++++--------- tests/JsIntegrationTest.php | 23 ++++++++++++---- 20 files changed, 114 insertions(+), 83 deletions(-) diff --git a/demos/_includes/DemoLookup.php b/demos/_includes/DemoLookup.php index 6f29a3be8d..3fe6b93ebe 100644 --- a/demos/_includes/DemoLookup.php +++ b/demos/_includes/DemoLookup.php @@ -48,7 +48,7 @@ protected function initQuickNewRecord(): void $ret = [ new JsToast('Form submit!. Data are not save in demo mode.'), - (new Jquery('.atk-modal'))->modal('hide'), + (new Jquery())->closest('.atk-modal')->modal('hide'), ]; $row = $this->renderRow($form->model); diff --git a/demos/_unit-test/callback.php b/demos/_unit-test/callback.php index cf2a9d3dcc..9387908a57 100644 --- a/demos/_unit-test/callback.php +++ b/demos/_unit-test/callback.php @@ -36,6 +36,6 @@ return [ $table->jsReload(), new JsToast('Save'), - (new Jquery('.ui.modal.visible.active.front'))->modal('hide'), + (new Jquery())->closest('.ui.modal')->modal('hide'), ]; }); diff --git a/demos/interactive/modal.php b/demos/interactive/modal.php index 82a8718af4..6bc606f381 100644 --- a/demos/interactive/modal.php +++ b/demos/interactive/modal.php @@ -187,10 +187,10 @@ if ($page === 1) { Message::addTo($p)->set('Thanks for choosing us. We will be asking some questions along the way.'); $session->memorize('success', true); - $p->js(true, $prevAction->js(true)->show()); - $p->js(true, $nextAction->js(true)->show()); + $p->js(true, $prevAction->js()->show()); + $p->js(true, $nextAction->js()->show()); $p->js(true, $prevAction->js()->addClass('disabled')); - $p->js(true, $nextAction->js(true)->removeClass('disabled')); + $p->js(true, $nextAction->js()->removeClass('disabled')); } elseif ($page === 2) { $modelRegister = new Model(new Persistence\Array_()); $modelRegister->addField('name', ['caption' => 'Please enter your name (John)']); @@ -213,13 +213,13 @@ return $js; }); $p->js(true, $prevAction->js()->removeClass('disabled')); - $p->js(true, $nextAction->js(true)->addClass('disabled')); + $p->js(true, $nextAction->js()->addClass('disabled')); } elseif ($page === 3) { $name = $session->recall('name'); Message::addTo($p)->set("Thank you {$name} for visiting us! We will be in touch"); $session->memorize('success', true); - $p->js(true, $prevAction->js(true)->hide()); - $p->js(true, $nextAction->js(true)->hide()); + $p->js(true, $prevAction->js()->hide()); + $p->js(true, $nextAction->js()->hide()); } }); diff --git a/demos/javascript/js.php b/demos/javascript/js.php index a3aab98fd3..3a5468365d 100644 --- a/demos/javascript/js.php +++ b/demos/javascript/js.php @@ -35,13 +35,19 @@ $b = Button::addTo($app, ['Hide button B']); $b2 = Button::addTo($app, ['B']); -$b->js('click', $b2->js()->hide())->addClass('disabled')->addClass('disabled'); +$b->on('click', [ + $b->js()->addClass('disabled')->addClass('disabled'), + $b2->js()->hide(), +]); Header::addTo($app, ['on() method']); $b = Button::addTo($app, ['Hide button C and self']); $b2 = Button::addTo($app, ['C']); -$b->on('click', null, $b2->js()->hide())->hide(); +$b->on('click', null, [ + $b->js()->hide(), + $b2->js()->hide(), +]); Header::addTo($app, ['Callbacks']); diff --git a/js/.eslintrc.js b/js/.eslintrc.js index 021cd1b6c5..fbbceb1bf2 100644 --- a/js/.eslintrc.js +++ b/js/.eslintrc.js @@ -31,7 +31,7 @@ module.exports = { 'no-restricted-syntax': 'off', 'no-underscore-dangle': 'off', 'max-len': 'off', - 'prefer-template': ['off'], + 'prefer-template': 'off', 'no-unused-vars': ['error', { vars: 'all', args: 'none' }], 'padding-line-between-statements': ['error', { blankLine: 'always', diff --git a/public/css/agileui.less b/public/css/agileui.less index 27c2f1c150..a0391a231a 100644 --- a/public/css/agileui.less +++ b/public/css/agileui.less @@ -182,7 +182,7 @@ header.atk-topMenu.ui.menu .item > .label { .atk-mainContainer { flex: 1; .atk-mainContainerWrapper { - padding: 3em; + padding: 2em; } &.atk-admin-layout { margin-top: @adminMenuHeight; diff --git a/public/css/agileui.min.css b/public/css/agileui.min.css index bf5100a839..03d667ddac 100644 --- a/public/css/agileui.min.css +++ b/public/css/agileui.min.css @@ -1 +1 @@ -.atk-table-dropdown i.table-filter-off:after,.atk-table-dropdown i.table-filter-on:after{content:'\f150'}.ui.inverted.fixed.atk-admin-top-menu{border:1px solid rgba(255,255,255,.1);z-index:201}.atk-maestro-sidenav>.ui.grid{width:260px}.ui.sidebar.atk-sidenav .menu>.item.active{background:0 0}.atk-maestro-sidenav{cursor:pointer;color:#fff}.atk-maestro-sidenav>.ui.grid>.atk-maestro-menu-items{display:none}.atk-maestro-sidenav>.ui.grid>.atk-maestro-menu-items.atk-visible{display:flex}.ui.inverted.menu a.atk-maestro-sidenav.item{padding-left:34px}.atk-maestro-sidenav .ui.grid .row,.ui.input .ui.popup.calendar{padding:0}.ui.inverted.menu a.atk-maestro-sidenav.item i{margin-right:2px}.ui.inverted.menu a.atk-maestro-sidenav.item.active{background-color:#2185D0}.atk-maestro-sidenav .atk-submenu-toggle div{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center}.atk-maestro-sidenav:not(active) .atk-submenu-toggle:hover{background-color:rgba(255,255,255,.15)}.atk-maestro-sidenav .ui.grid .column{padding-top:8px;padding-bottom:8px}.atk-maestro-sidenav .ui.grid .atk-maestro-menu-title header{margin-left:-10px}.atk-maestro-sidenav .ui.grid .atk-maestro-menu-items .menu{margin-left:10px}.atk-maestro-sidenav.active .atk-maestro-menu-title{background-color:#2185D0}header.ui.fixed.horizontal.menu{min-height:48px}.ui.visible.left.sidebar~header.atk-topMenu.ui.menu.fixed{width:calc(100% - 260px)}.ui.visible.left.sidebar~.atk-mainContainer,.ui.visible.left.sidebar~footer.atk-footer{padding-left:260px}.ui.modal .atk-dialog-content .ui.dimmer.active,.ui.modal .atk-dialog-content.loading{min-height:100px}.atk-hide-loading-content.loading:before{background:rgba(255,255,255,.98)!important}.atk-layout{display:flex;min-height:100vh;flex-direction:column}.atk-topMenuGhost.ui.menu{display:none;margin:0;pointer-events:none;visibility:hidden}header.atk-topMenu.ui.fixed.menu~.atk-topMenuGhost{display:block}header.atk-topMenu.ui.menu .item>.label{margin-left:0}.atk-mainContainer{flex:1}.atk-mainContainer .atk-mainContainerWrapper{padding:3em}.atk-mainContainer.atk-admin-layout{margin-top:48px}footer.atk-footer .ui.divider,footer.atk-footer .ui.segment{margin:0}.ui.left.sidebar{z-index:200}.atk-right-panel{-webkit-transition:box-shadow .2s,right .2s;-moz-transition:box-shadow .2s,right .2s;-ms-transition:box-shadow .2s,right .2s;-o-transition:box-shadow .2s,right .2s;transition:box-shadow .2s,right .2s;bottom:0;background-color:#F9FAFB;box-shadow:0 0 0 rgba(27,28,29,0);height:99%;right:-100%;overflow-y:auto;position:fixed;width:40%;z-index:301}.atk-right-panel.atk-visible{box-shadow:0 0 5px rgba(27,28,29,.2);right:0}.atk-panel-close{color:#1B1C1D}i.atk-panel-warning{color:#F9FAFB}i.atk-panel-warning.atk-visible{color:#F2711C}.ui.left.sidebar.atk-sidenav{top:48px;width:260px}.atk-sidenav-content{overflow-x:hidden;overflow-y:auto;height:calc(100% - 98px)}.atk-table-dropdown{float:left}.atk-table-dropdown i{opacity:.3}.atk-table-dropdown i.table-filter-on:after{color:#1e90ff}.atk-table-dropdown .dropdown .menu{margin:1em -10px 0!important}.atk-table-column-header{white-space:pre-wrap}.atk-table-column-header:after{font-style:normal;font-weight:400;text-decoration:inherit;content:'';height:1em;width:auto;opacity:.8;margin:0 0 0 .5em;font-family:Icons;float:right}.atk-table-column-header.ascending:after{content:'\f0d8'}.atk-table-column-header.descending:after{content:'\f0d7'}.ui.sortable.table thead th:not(.sortable){cursor:default}.ui.step,.ui.steps .step{user-select:none}.atk-overflow-auto{overflow:auto;border:1px solid rgba(34,36,38,.15);margin-bottom:1em}.atk-overflow-auto>.ui.table{margin-top:0}.atk-cell-expanded{min-width:320px;max-width:640px;white-space:normal}.atk-overlay.pushable{height:100%;left:0;opacity:0;pointer-events:none;position:fixed;top:48px;width:100%;z-index:101}.atk-overlay.pushable .pusher:after{height:100%;opacity:1;width:100%}@media (max-width:991px){.ui.left.sidebar~header.atk-topMenu.ui.menu.fixed{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);width:100%!important}.ui.visible.left.sidebar~.atk-mainContainer,.ui.visible.left.sidebar~footer.atk-footer{padding-left:0}.ui.left.sidebar{opacity:0;pointer-events:none;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);visibility:visible}body.atk-sidenav-visible:after{left:0;position:fixed;top:0}body.atk-sidenav-visible .ui.left.sidebar{opacity:1;pointer-events:auto}body.atk-sidenav-visible .ui.left.sidebar .atk-leftMenuClose{display:block}body.atk-sidenav-visible .atk-overlay.pushable{opacity:1}}@media (min-width:768px) and (max-width:991px){.atk-right-panel{width:65%}}@media (max-width:767px){.atk-right-panel{width:95%}}@media print{.ui.visible.left.sidebar~.atk-mainContainer,.ui.visible.left.sidebar~footer.atk-footer{padding-left:0}.atk-topMenu,.atk-topMenuGhost,.ui.left.sidebar{display:none!important}}/*# sourceMappingURL=agileui.min.css.map */ \ No newline at end of file +.atk-table-dropdown i.table-filter-off:after,.atk-table-dropdown i.table-filter-on:after{content:'\f150'}.ui.inverted.fixed.atk-admin-top-menu{border:1px solid rgba(255,255,255,.1);z-index:201}.atk-maestro-sidenav>.ui.grid{width:260px}.ui.sidebar.atk-sidenav .menu>.item.active{background:0 0}.atk-maestro-sidenav{cursor:pointer;color:#fff}.atk-maestro-sidenav>.ui.grid>.atk-maestro-menu-items{display:none}.atk-maestro-sidenav>.ui.grid>.atk-maestro-menu-items.atk-visible{display:flex}.ui.inverted.menu a.atk-maestro-sidenav.item{padding-left:34px}.atk-maestro-sidenav .ui.grid .row,.ui.input .ui.popup.calendar{padding:0}.ui.inverted.menu a.atk-maestro-sidenav.item i{margin-right:2px}.ui.inverted.menu a.atk-maestro-sidenav.item.active{background-color:#2185D0}.atk-maestro-sidenav .atk-submenu-toggle div{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center}.atk-maestro-sidenav:not(active) .atk-submenu-toggle:hover{background-color:rgba(255,255,255,.15)}.atk-maestro-sidenav .ui.grid .column{padding-top:8px;padding-bottom:8px}.atk-maestro-sidenav .ui.grid .atk-maestro-menu-title header{margin-left:-10px}.atk-maestro-sidenav .ui.grid .atk-maestro-menu-items .menu{margin-left:10px}.atk-maestro-sidenav.active .atk-maestro-menu-title{background-color:#2185D0}header.ui.fixed.horizontal.menu{min-height:48px}.ui.visible.left.sidebar~header.atk-topMenu.ui.menu.fixed{width:calc(100% - 260px)}.ui.visible.left.sidebar~.atk-mainContainer,.ui.visible.left.sidebar~footer.atk-footer{padding-left:260px}.ui.modal .atk-dialog-content .ui.dimmer.active,.ui.modal .atk-dialog-content.loading{min-height:100px}.atk-hide-loading-content.loading:before{background:rgba(255,255,255,.98)!important}.atk-layout{display:flex;min-height:100vh;flex-direction:column}.atk-topMenuGhost.ui.menu{display:none;margin:0;pointer-events:none;visibility:hidden}header.atk-topMenu.ui.fixed.menu~.atk-topMenuGhost{display:block}header.atk-topMenu.ui.menu .item>.label{margin-left:0}.atk-mainContainer{flex:1}.atk-mainContainer .atk-mainContainerWrapper{padding:2em}.atk-mainContainer.atk-admin-layout{margin-top:48px}footer.atk-footer .ui.divider,footer.atk-footer .ui.segment{margin:0}.ui.left.sidebar{z-index:200}.atk-right-panel{-webkit-transition:box-shadow .2s,right .2s;-moz-transition:box-shadow .2s,right .2s;-ms-transition:box-shadow .2s,right .2s;-o-transition:box-shadow .2s,right .2s;transition:box-shadow .2s,right .2s;bottom:0;background-color:#F9FAFB;box-shadow:0 0 0 rgba(27,28,29,0);height:99%;right:-100%;overflow-y:auto;position:fixed;width:40%;z-index:301}.atk-right-panel.atk-visible{box-shadow:0 0 5px rgba(27,28,29,.2);right:0}.atk-panel-close{color:#1B1C1D}i.atk-panel-warning{color:#F9FAFB}i.atk-panel-warning.atk-visible{color:#F2711C}.ui.left.sidebar.atk-sidenav{top:48px;width:260px}.atk-sidenav-content{overflow-x:hidden;overflow-y:auto;height:calc(100% - 98px)}.atk-table-dropdown{float:left}.atk-table-dropdown i{opacity:.3}.atk-table-dropdown i.table-filter-on:after{color:#1e90ff}.atk-table-dropdown .dropdown .menu{margin:1em -10px 0!important}.atk-table-column-header{white-space:pre-wrap}.atk-table-column-header:after{font-style:normal;font-weight:400;text-decoration:inherit;content:'';height:1em;width:auto;opacity:.8;margin:0 0 0 .5em;font-family:Icons;float:right}.atk-table-column-header.ascending:after{content:'\f0d8'}.atk-table-column-header.descending:after{content:'\f0d7'}.ui.sortable.table thead th:not(.sortable){cursor:default}.ui.step,.ui.steps .step{user-select:none}.atk-overflow-auto{overflow:auto;border:1px solid rgba(34,36,38,.15);margin-bottom:1em}.atk-overflow-auto>.ui.table{margin-top:0}.atk-cell-expanded{min-width:320px;max-width:640px;white-space:normal}.atk-overlay.pushable{height:100%;left:0;opacity:0;pointer-events:none;position:fixed;top:48px;width:100%;z-index:101}.atk-overlay.pushable .pusher:after{height:100%;opacity:1;width:100%}@media (max-width:991px){.ui.left.sidebar~header.atk-topMenu.ui.menu.fixed{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);width:100%!important}.ui.visible.left.sidebar~.atk-mainContainer,.ui.visible.left.sidebar~footer.atk-footer{padding-left:0}.ui.left.sidebar{opacity:0;pointer-events:none;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);visibility:visible}body.atk-sidenav-visible:after{left:0;position:fixed;top:0}body.atk-sidenav-visible .ui.left.sidebar{opacity:1;pointer-events:auto}body.atk-sidenav-visible .ui.left.sidebar .atk-leftMenuClose{display:block}body.atk-sidenav-visible .atk-overlay.pushable{opacity:1}}@media (min-width:768px) and (max-width:991px){.atk-right-panel{width:65%}}@media (max-width:767px){.atk-right-panel{width:95%}}@media print{.ui.visible.left.sidebar~.atk-mainContainer,.ui.visible.left.sidebar~footer.atk-footer{padding-left:0}.atk-topMenu,.atk-topMenuGhost,.ui.left.sidebar{display:none!important}}/*# sourceMappingURL=agileui.min.css.map */ \ No newline at end of file diff --git a/src/CardDeck.php b/src/CardDeck.php index 80786bd79f..336067a8fe 100644 --- a/src/CardDeck.php +++ b/src/CardDeck.php @@ -146,7 +146,8 @@ public function setModel(Model $model, array $fields = null, array $extra = null $this->search->setModelCondition($this->model); } - if ($count = $this->initPaginator()) { + $count = $this->initPaginator(); + if ($count) { foreach ($this->model as $m) { $c = $this->cardHolder->add(Factory::factory([$this->card], ['useLabel' => $this->useLabel, 'useTable' => $this->useTable]))->addClass('segment'); $c->setModel($m, $fields); @@ -401,15 +402,10 @@ protected function renderView(): void */ private function getModelActions(string $appliesTo): array { - $actions = []; if ($appliesTo === Model\UserAction::APPLIES_TO_SINGLE_RECORD && $this->singleScopeActions !== []) { - foreach ($this->singleScopeActions as $action) { - $actions[] = $this->model->getUserAction($action); - } + $actions = array_map(fn ($v) => $this->model->getUserAction($v), $this->singleScopeActions); } elseif ($appliesTo === Model\UserAction::APPLIES_TO_NO_RECORDS && $this->noRecordScopeActions !== []) { - foreach ($this->noRecordScopeActions as $action) { - $actions[] = $this->model->getUserAction($action); - } + $actions = array_map(fn ($v) => $this->model->getUserAction($v), $this->noRecordScopeActions); } else { $actions = $this->model->getUserActions($appliesTo); } diff --git a/src/Crud.php b/src/Crud.php index e94001a039..638c6d2873 100644 --- a/src/Crud.php +++ b/src/Crud.php @@ -26,7 +26,7 @@ class Crud extends Grid /** @var bool|null should we use table column drop-down menu to display user actions? */ public $useMenuActions; - /** Collection of APPLIES_TO_NO_RECORDS Scope Model action menu item */ + /** @var array Collection of APPLIES_TO_NO_RECORDS Scope Model action menu item */ private array $menuItems = []; /** Model single scope action to include in table action column. Will include all single scope actions if empty. */ @@ -63,9 +63,9 @@ public function applySort(): void { parent::applySort(); - if ($this->getSortBy() && $this->menuItems !== []) { + if ($this->getSortBy()) { foreach ($this->menuItems as $item) { - // Remove previous click handler and attach new one using sort argument. + // remove previous click handler and attach new one using sort argument $this->container->js(true, $item['item']->js()->off('click.atk_crud_item')); $ex = $item['executor']; if ($ex instanceof UserAction\JsExecutorInterface) { @@ -194,8 +194,9 @@ protected function getJsGridAction(Model\UserAction $action): ?JsExpressionable case Model\UserAction::MODIFIER_DELETE: // use deleted record id to remove row, fallback to closest tr if id is not available. $js = $this->deletedId - ? (new Jquery('tr[data-id="' . $this->deletedId . '"]'))->transition('fade left') - : (new Jquery())->closest('tr')->transition('fade left'); + ? $this->js(false, null, 'tr[data-id="' . $this->deletedId . '"]') + : (new Jquery())->closest('tr'); + $js = $js->transition('fade left', new JsFunction([], [new JsExpression('this.remove()')])); break; default: @@ -229,7 +230,10 @@ protected function getNotifier(string $msg = null) protected function setItemsAction(): void { foreach ($this->menuItems as $k => $item) { - $this->container->js(true, $item['item']->on('click.atk_crud_item', $item['executor'])); + // hack - render executor action via MenuItem::on() into container + $item['item']->on('click.atk_crud_item', $item['executor']); + $jsAction = array_pop($item['item']->_jsActions['click.atk_crud_item']); + $this->container->js(true, $jsAction); } } @@ -274,15 +278,10 @@ private function _getReloadArgs() */ private function _getModelActions(string $appliesTo): array { - $actions = []; if ($appliesTo === Model\UserAction::APPLIES_TO_SINGLE_RECORD && $this->singleScopeActions !== []) { - foreach ($this->singleScopeActions as $action) { - $actions[] = $this->model->getUserAction($action); - } + $actions = array_map(fn ($v) => $this->model->getUserAction($v), $this->singleScopeActions); } elseif ($appliesTo === Model\UserAction::APPLIES_TO_NO_RECORDS && $this->noRecordScopeActions !== []) { - foreach ($this->noRecordScopeActions as $action) { - $actions[] = $this->model->getUserAction($action); - } + $actions = array_map(fn ($v) => $this->model->getUserAction($v), $this->noRecordScopeActions); } else { $actions = $this->model->getUserActions($appliesTo); } diff --git a/src/Form/Control/Lookup.php b/src/Form/Control/Lookup.php index 627393b86d..8b83c236af 100644 --- a/src/Form/Control/Lookup.php +++ b/src/Form/Control/Lookup.php @@ -259,7 +259,7 @@ protected function initQuickNewRecord(): void $form->model->save(); $ret = [ - (new Jquery('.atk-modal'))->modal('hide'), + (new Jquery())->closest('.atk-modal')->modal('hide'), ]; $row = $this->renderRow($form->model); diff --git a/src/Form/Control/Multiline.php b/src/Form/Control/Multiline.php index cfccaaf90d..8de6dfc16e 100644 --- a/src/Form/Control/Multiline.php +++ b/src/Form/Control/Multiline.php @@ -51,17 +51,17 @@ * For example, ypdating Grand Total field of all invoice items. * * $ml->onChange(function (array $rows) use ($form) { - * $grand_total = 0; + * $grandTotal = 0; * foreach ($rows as $row => $cols) { * foreach ($cols as $col) { * $fieldName = array_key_first($col); * if ($fieldName === 'total') { - * $grand_total += $col[$fieldName]; + * $grandTotal += $col[$fieldName]; * } * } * } * - * return $form->js(true, null, 'input[name="grand_total"]')->val($app->uiPersistence->typecastSaveField(new Field(['type' => 'atk4_money']), $grand_total)); + * return $form->js(false, null, 'input[name="grand_total"]')->val($app->uiPersistence->typecastSaveField(new Field(['type' => 'atk4_money']), $grandTotal)); * }, ['qty', 'price']); * * Finally, it's also possible to use Multiline for quickly adding records to a diff --git a/src/JsCallback.php b/src/JsCallback.php index 7ec8d1471d..58efa9b3f7 100644 --- a/src/JsCallback.php +++ b/src/JsCallback.php @@ -90,6 +90,13 @@ public function set($fx = null, $args = null) $response = $fx($chain, ...$values); + if (count($chain->_chain) === 0) { + // TODO should we create/pass $chain to $fx at all? + $chain = null; + } elseif ($response) { + // TODO throw when non-empty chain is to be ignored? + } + $ajaxec = $response ? $this->getAjaxec($response, $chain) : null; $this->terminateAjax($ajaxec); @@ -129,7 +136,7 @@ public function getAjaxec($response, $chain = null): string { $actions = []; - if ($chain && $chain->_chain) { + if ($chain !== null) { $actions[] = $chain; } diff --git a/src/JsFunction.php b/src/JsFunction.php index 715f0e1d27..0765e18016 100644 --- a/src/JsFunction.php +++ b/src/JsFunction.php @@ -56,13 +56,7 @@ public function jsRender(): string $output = 'function (' . implode(', ', $this->fxArgs) . ') {' . $pre; foreach ($this->fxStatements as $statement) { - if (!$statement) { - // null passed - continue; - } - - if ($statement instanceof JsChain && !$statement->_chain) { - // chain contains no statements, so probably is useless + if ($statement === null) { // TODO this should be not needed continue; } diff --git a/src/Modal.php b/src/Modal.php index cc552677a0..a0bc00e823 100644 --- a/src/Modal.php +++ b/src/Modal.php @@ -130,12 +130,12 @@ public function addContentCss($class): void */ public function jsShow(array $args = []) { - $js_chain = $this->js(); + $chain = $this->js(); if ($args !== []) { - $js_chain->data(['args' => $args]); + $chain->data(['args' => $args]); } - return $js_chain->modal('show'); + return $chain->modal('show'); } /** diff --git a/src/Table/Column/ActionMenu.php b/src/Table/Column/ActionMenu.php index 7fe9e88058..7ae676fd53 100644 --- a/src/Table/Column/ActionMenu.php +++ b/src/Table/Column/ActionMenu.php @@ -80,10 +80,16 @@ public function addActionMenuItem($item, $action = null, string $confirmMsg = '' $this->callbacks[$name] = $isDisabled; } - // set executor context. - $context = (new Jquery())->closest('.ui.button'); - - $this->table->on('click', '.i_' . $name, $action, [$this->table->jsRow()->data('id'), 'confirm' => $confirmMsg, 'apiConfig' => ['stateContext' => $context]]); + if ($action !== null) { + // set executor context + $context = (new Jquery())->closest('.ui.button'); + + $this->table->on('click', '.i_' . $name, $action, [ + $this->table->jsRow()->data('id'), + 'confirm' => $confirmMsg, + 'apiConfig' => ['stateContext' => $context], + ]); + } return $item; } diff --git a/src/Table/Column/FilterPopup.php b/src/Table/Column/FilterPopup.php index 70c29b1503..c4e60751e0 100644 --- a/src/Table/Column/FilterPopup.php +++ b/src/Table/Column/FilterPopup.php @@ -30,7 +30,7 @@ class FilterPopup extends Popup public $reload; /** - * The Table Column triggering the poupup. + * The Table Column triggering the popup. * This is need to simulate click in order to properly * close the popup window on "Clear". * diff --git a/src/UserAction/ConfirmationExecutor.php b/src/UserAction/ConfirmationExecutor.php index 6e1cf2615c..aceba2de30 100644 --- a/src/UserAction/ConfirmationExecutor.php +++ b/src/UserAction/ConfirmationExecutor.php @@ -137,8 +137,8 @@ public function executeModelAction(): void */ protected function jsSetBtnState(View $view): void { - $view->js(true, $this->ok->js(true)->off()); - $view->js(true, $this->cancel->js(true)->off()); + $view->js(true, $this->ok->js()->off()); + $view->js(true, $this->cancel->js()->off()); } /** diff --git a/src/UserAction/StepExecutorTrait.php b/src/UserAction/StepExecutorTrait.php index ff05329093..f3c805aeca 100644 --- a/src/UserAction/StepExecutorTrait.php +++ b/src/UserAction/StepExecutorTrait.php @@ -363,9 +363,9 @@ protected function jsSetBtnState(View $view, string $step): void } // reset button handler - $view->js(true, $this->execActionBtn->js(true)->off()); - $view->js(true, $this->nextStepBtn->js(true)->off()); - $view->js(true, $this->prevStepBtn->js(true)->off()); + $view->js(true, $this->execActionBtn->js()->off()); + $view->js(true, $this->nextStepBtn->js()->off()); + $view->js(true, $this->prevStepBtn->js()->off()); $view->js(true, $this->nextStepBtn->js()->removeClass('disabled')); $view->js(true, $this->execActionBtn->js()->removeClass('disabled')); } @@ -376,10 +376,10 @@ protected function jsSetBtnState(View $view, string $step): void protected function jsSetNextState(string $step): JsExpressionable { if ($this->isLastStep($step)) { - return $this->nextStepBtn->js(true)->hide(); + return $this->nextStepBtn->js()->hide(); } - return $this->nextStepBtn->js(true)->show(); + return $this->nextStepBtn->js()->show(); } /** @@ -388,10 +388,10 @@ protected function jsSetNextState(string $step): JsExpressionable protected function jsSetPrevState(string $step): JsExpressionable { if ($this->isFirstStep($step)) { - return $this->prevStepBtn->js(true)->hide(); + return $this->prevStepBtn->js()->hide(); } - return $this->prevStepBtn->js(true)->show(); + return $this->prevStepBtn->js()->show(); } /** @@ -400,10 +400,10 @@ protected function jsSetPrevState(string $step): JsExpressionable protected function jsSetExecState(string $step): JsExpressionable { if ($this->isLastStep($step)) { - return $this->execActionBtn->js(true)->show(); + return $this->execActionBtn->js()->show(); } - return $this->execActionBtn->js(true)->hide(); + return $this->execActionBtn->js()->hide(); } /** diff --git a/src/View.php b/src/View.php index d860810c0d..5ca33eba61 100644 --- a/src/View.php +++ b/src/View.php @@ -771,7 +771,7 @@ public function getHtml() * @param ($when is false ? null : JsExpressionable|null) $action JavaScript action * @param string|self|null $selector If you wish to override jQuery($selector) * - * @return Jquery + * @return ($action is null ? Jquery : null) */ public function js($when = false, $action = null, $selector = null) { @@ -781,16 +781,21 @@ public function js($when = false, $action = null, $selector = null) return $this->on($when, $selector, $action); } - $chain = new Jquery($selector ?? $this); - if ($when === true) { - $this->_jsActions[$when][] = $chain; - - if ($action !== null) { - $this->_jsActions[$when][] = $action; + if ($action !== null) { + $res = null; + } else { + $action = new Jquery($this); + if ($selector) { + $action->find($selector); } + $res = $action; + } + + if ($when === true) { + $this->_jsActions[$when][] = $action; } - return $chain; + return $res; } /** @@ -957,7 +962,7 @@ public function jsReload($args = [], $afterSuccess = null, $apiConfig = []) * @param string|JsExpressionable|\Closure|array|UserAction\ExecutorInterface|Model\UserAction|null $action code to execute * @param array $defaults Options * - * @return Jquery + * @return ($selector is null|string ? ($action is null ? Jquery : null) : ($action is null|array ? Jquery : null)) */ public function on(string $event, $selector = null, $action = null, array $defaults = null) { @@ -984,8 +989,13 @@ public function on(string $event, $selector = null, $action = null, array $defau $eventStatements = []; $actions = []; - $chain = new Jquery(); - $actions[] = $chain; + + if ($action !== null) { + $res = null; + } else { + $action = new Jquery(); + $res = $action; + } // set preventDefault and stopPropagation by default $eventStatements['preventDefault'] = $defaults['preventDefault'] ?? true; @@ -1041,7 +1051,7 @@ public function on(string $event, $selector = null, $action = null, array $defau } } elseif (is_array($action)) { $actions = array_merge($actions, $action); - } elseif ($action !== null) { + } else { $actions[] = $action; } @@ -1067,7 +1077,7 @@ public function on(string $event, $selector = null, $action = null, array $defau $this->_jsActions[$event][] = $eventChain; - return $chain; + return $res; } /** diff --git a/tests/JsIntegrationTest.php b/tests/JsIntegrationTest.php index 8018198a19..46e5e80595 100644 --- a/tests/JsIntegrationTest.php +++ b/tests/JsIntegrationTest.php @@ -30,7 +30,7 @@ public function testUniqueId2(): void static::assertNotSame($b1->name, $b2->name); } - public function testBasicChainFalse(): void + public function testChainFalse(): void { $v = new Button(['name' => 'b']); $j = $v->js()->hide(); @@ -39,7 +39,7 @@ public function testBasicChainFalse(): void static::assertSame('$(\'#b\').hide()', $j->jsRender()); } - public function testBasicChainTrue(): void + public function testChainTrue(): void { $v = new Button(['name' => 'b']); $j = $v->js(true)->hide(); @@ -50,7 +50,7 @@ public function testBasicChainTrue(): void })()', $v->getJs()); } - public function testBasicChainClick(): void + public function testChainClick(): void { $v = new Button(['name' => 'b']); $v->js('click')->hide(); @@ -65,7 +65,7 @@ public function testBasicChainClick(): void })()', $v->getJs()); } - public function testBasicChainClickEmpty(): void + public function testChainClickEmpty(): void { $v = new Button(['name' => 'b']); $v->js('click', null); @@ -75,11 +75,14 @@ public function testBasicChainClickEmpty(): void $(\'#b\').on(\'click\', function (event) { event.preventDefault(); event.stopPropagation(); + ' + . '$(this);' // this JS statement is not required + . ' }); })()', $v->getJs()); } - public function testBasicChainNested(): void + public function testChainNested(): void { $bb = new View(['ui' => 'buttons']); $b1 = Button::addTo($bb, ['name' => 'b1']); @@ -104,4 +107,14 @@ public function testBasicChainNested(): void $(\'#b1\').data(\'x\', \'y\'); })()', $bb->getJs()); } + + public function testChainNullReturn(): void + { + $v = new View(['name' => 'v']); + $js = $v->js(); + + static::assertNotNull($v->js(true, null)); // @phpstan-ignore-line + static::assertNull($v->js(true, $js)); // @phpstan-ignore-line + static::assertNull($v->on('click', $js)); // @phpstan-ignore-line + } }