Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve callbacks url generation 2 #1651

Draft
wants to merge 12 commits into
base: develop
Choose a base branch
from
8 changes: 0 additions & 8 deletions demos/interactive/modal.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,14 +230,6 @@
}
});

$previousAction->on('click', $stepModal->js()->atkReloadView(
['url' => $stepModal->cb->getJsUrl(), 'urlOptions' => ['move' => 'previous']]
));

$nextAction->on('click', $stepModal->js()->atkReloadView(
['url' => $stepModal->cb->getJsUrl(), 'urlOptions' => ['move' => 'next']]
));

// Bind display modal to page display button.
$menuBar = View::addTo($app, ['ui' => 'buttons']);
$button = Button::addTo($menuBar)->set('Multi Step Modal');
Expand Down
2 changes: 1 addition & 1 deletion src/Callback.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function setUrlTrigger(string $trigger = null): void
{
$this->urlTrigger = $trigger ?? $this->name;

$this->getOwner()->stickyGet(self::URL_QUERY_TRIGGER_PREFIX . $this->urlTrigger);
// $this->getOwner()->stickyGet(self::URL_QUERY_TRIGGER_PREFIX . $this->urlTrigger);
}

public function getUrlTrigger(): string
Expand Down
23 changes: 23 additions & 0 deletions src/Js/JsReload.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,31 @@ public function __construct(View $view, array $args = [], JsExpressionable $afte
$this->includeStorage = $includeStorage;
}

public function dumpRenderTree(View $view, bool $rec = false): void
{
if ($view->issetOwner() && $view->getOwner() instanceof View) {
$this->dumpRenderTree($view->getOwner(), true);
}

echo get_class($view);

echo "\n" . (!$rec ? "\n\n" : '');
}

public function jsRender(): string
{
/*ini_set('output_buffering', (string) (1024 * 1024));
ob_start();
$this->dumpRenderTree($this->view);
// test URL: /demos/interactive/modal.php?__atk_m=atk_layout_maestro_modal_5&__atk_cbtarget=atk_layout_maestro_modal_5_view_callbacklater&__atk_cb_atk_layout_maestro_modal_5_view_callbacklater=ajax&__atk_json=1
$url = $this->view->jsUrl(['__atk_reload' => $this->view->name]);
echo 'actual: ';
var_dump($url);
echo 'expected: string(166) "modal.php?__atk_m=atk_layout_maestro_modal_5&__atk_cb_atk_layout_maestro_modal_5_view_callbacklater=ajax&__atk_reload=atk_layout_maestro_modal_5_view_demos_viewtester"' . "\n";
ob_end_flush();

exit;*/

$final = (new Jquery($this->view))
->atkReloadView(
[
Expand Down
71 changes: 66 additions & 5 deletions src/View.php
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ public function removeAttr($name)
*/
public function url($page = []): string
{
return $this->getApp()->url($page, false, $this->_getStickyArgs());
return $this->getApp()->url($page, false, array_merge($this->getRunningCallbackArgs(false, is_array($page) ? $page : []), $this->getStickyArgs()));
}

/**
Expand All @@ -490,16 +490,77 @@ public function url($page = []): string
*/
public function jsUrl($page = []): string
{
return $this->getApp()->jsUrl($page, false, $this->_getStickyArgs());
return $this->getApp()->jsUrl($page, false, array_merge($this->getRunningCallbackArgs(false, is_array($page) ? $page : []), $this->getStickyArgs()));
}

protected function getRunningCallbackArgs(bool $isTerminated, array $page): array
{
$args = [];
foreach ($this->elements as $v) {
if ($v instanceof Callback) { // @phpstan-ignore-line
if (($page[Callback::URL_QUERY_TARGET] ?? null) === $v->getUrlTrigger()) {
mvorisek marked this conversation as resolved.
Show resolved Hide resolved
$isTerminated = true;
}

if ($isTerminated && $v->isTriggered() && $v->canTrigger()) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ibelar does $v->canTrigger() make sense here and any idea how to fix the remaining failing Behat tests?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ibelar does $v->canTrigger() make sense here and any idea how to fix the remaining failing Behat tests?

Yes, canTrigger method will check for a reload. Normally, JsCallback should not run during a reload.

Copy link
Contributor

@ibelar ibelar Jul 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see why the test is failing. ViewTester reload is missing Callback arguments. Thus when reload is call, the callback method is not run.

I think the reason is that ViewTester is added directly to Modal, while the callback is added to Button

$button->on('click', $vp1Modal->show());

Resulting in

App -> Button -> Cb
    -> Modal -> ViewTester (reload)

Thus the callback is not part of the Modal render tree... missing its arguments when reload URL is rendered.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thus the callback is not part of the Modal render tree...

any idea how to solve it?

Copy link
Member Author

@mvorisek mvorisek Aug 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ibelar the issue is here:

'uri' => $this->view->jsUrl(['__atk_reload' => $this->view->name]),

  1. in Improve callbacks url generation #1646 you have removed mergeStickyArgsFromChildView which allowed to delegate sticky args from it's children.
  2. in this PR, sticky args are collected from the render tree, but the URL must be obtained thru terminating callback
  3. here, hovewer the URL is obtained thru $this->view which is placed correctly within the Modal render tree, but the Modal is not delegating it's callback sticky args as mergeStickyArgsFromChildView was removed

To better understand the issue, I have pushed a debug code in 53a0352#diff-0741d95fd53e7a9a50ab8b371b0d621c3eaf3cb460c79dfe5a886deaec0814a9R68 , when you open the test URL, the expected and actual value must be the same.

I belive, mergeStickyArgsFromChildView is needed. Or how else should the $this->view->jsUrl(... in JsReload work?

With 29ecd83#diff-c5bb4a733c5ddd3bb0744ef4c29a8fd1e0bd5999fcf06b7f3cfef4c3d1926ce3R640-R668 Behat passes.

Here is a summary of problems to solve:

  1. traverse all callbacks that CAN trigger (can be solved easily but by brute force by analysis whole reachable render tree) - temporary implemented by analysis stack trace
  2. identify THE LAST TERMINATING callback - temporary implemented by pretending that all Modals can terminate - I need a help with this, as terminating callback must be identified from the render tree (not by the user requested terminating callback from URL, as it can differ)

$args[Callback::URL_QUERY_TRIGGER_PREFIX . $v->getUrlTrigger()] = $v->getTriggeredValue();
}
}
}

$parentRenderView = null;
if ($this->issetOwner() && $this->getOwner() instanceof self) {
$parentRenderView = $this->getOwner();
} // else

if (($this instanceof Modal || $this instanceof Panel\Content/* || $this instanceof Panel\Right no direct callback, must use something like mergeStickyArgsFromChildView */) && $this->cb !== null && $this->cb->isTriggered() && $this->cb->canTrigger()) { // hack for modals placed outside the render tree with possible callbacks
$isTerminated = true; // fake terminated detection to support https://github.com/atk4/ui/blob/8014b6c1cb5beb103f337af8ace5ac350f73ce19/src/JsReload.php#L58 URL built not thru callback

$stacktrace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS);
foreach (array_slice($stacktrace, 1, null, true) as $k => $stackframe) {
if (($stackframe['object'] ?? null) instanceof self && ($stackframe['object'] ?? null) !== $this) {
$parentRenderView = $stackframe['object'];
foreach (array_slice($stacktrace, $k + 1) as $stackframe2) {
if (($stackframe2['object'] ?? null) === $parentRenderView && $stackframe2['function'] === 'getRunningCallbackArgs') {
$parentRenderView = null; // already called
}
}

break;
} elseif (($stackframe['object'] ?? null) instanceof App && ($stackframe['function'] ?? null) === 'run') {
break;
}
}

if ($parentRenderView !== null) {
$a = [
array_map(function ($v) {
$v['class'] = isset($v['object']) ? get_class($v['object']) : 'x';
unset($v['object']);

return $v;
}, $stacktrace),
$parentRenderView !== null ? $parentRenderView->getRunningCallbackArgs($isTerminated, $page) : '-----',
$page,
];
// print_r($a);
}
}

if ($parentRenderView !== null) {
$args = array_merge($parentRenderView->getRunningCallbackArgs($isTerminated, $page), $args);
}

return $args;
}

/**
* Get sticky arguments defined by the view and parents (including API).
*/
protected function _getStickyArgs(): array
protected function getStickyArgs(): array
{
if ($this->issetOwner()) {
$stickyArgs = array_merge($this->getOwner()->_getStickyArgs(), $this->stickyArgs);
if ($this->issetOwner() && $this->getOwner() instanceof self) {
$stickyArgs = array_merge($this->getOwner()->getStickyArgs(), $this->stickyArgs);
} else {
$stickyArgs = $this->stickyArgs;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/CallbackTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public function testViewUrlCallback(): void
$this->simulateCallbackTriggering($cb);

$expectedUrlCbApp = '?' . Callback::URL_QUERY_TRIGGER_PREFIX . 'aa=callback&' . Callback::URL_QUERY_TARGET . '=aa';
$expectedUrlCb = '?' . /* Callback::URL_QUERY_TRIGGER_PREFIX . 'aa=1&' . */ Callback::URL_QUERY_TRIGGER_PREFIX . 'bb=callback&' . Callback::URL_QUERY_TARGET . '=bb';
$expectedUrlCb = '?' . Callback::URL_QUERY_TRIGGER_PREFIX . 'aa=1&' . Callback::URL_QUERY_TRIGGER_PREFIX . 'bb=callback&' . Callback::URL_QUERY_TARGET . '=bb';
self::assertSame($expectedUrlCbApp, $cbApp->getUrl());
self::assertSame($expectedUrlCb, $cb->getUrl());

Expand Down