Skip to content

Commit

Permalink
Unimplement JsExpressionable from JsCallback (#1955)
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek committed Dec 21, 2022
1 parent e0e9b34 commit 975b785
Show file tree
Hide file tree
Showing 16 changed files with 124 additions and 64 deletions.
9 changes: 4 additions & 5 deletions demos/_unit-test/sse.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@
$sse->setUrlTrigger('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\')'));
for ($i = 0; $i < 4; ++$i) {
$sse->send(new JsExpression('console.log([])', ['test ' . $i]));
}

// non-SSE way
return new JsToast('SSE sent, see browser console log');
}));
})->jsExecute());
2 changes: 1 addition & 1 deletion demos/form-control/multiline.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
$inventory = new MultilineItem($app->db);
$inventory->getField($inventory->fieldName()->item)->ui['multiline'] = [Form\Control\Multiline::TABLE_CELL => ['width' => 2]];
$inventory->getField($inventory->fieldName()->inv_date)->ui['multiline'] = [Form\Control\Multiline::TABLE_CELL => ['width' => 2]];
$inventory->getField($inventory->fieldName()->inv_date)->ui['multiline'] = [Form\Control\Multiline::TABLE_CELL => ['width' => 2]];
$inventory->getField($inventory->fieldName()->inv_time)->ui['multiline'] = [Form\Control\Multiline::TABLE_CELL => ['width' => 2]];
$inventory->getField($inventory->fieldName()->country_id)->ui['multiline'] = [Form\Control\Multiline::TABLE_CELL => ['width' => 3]];
$inventory->getField($inventory->fieldName()->qty)->ui['multiline'] = [Form\Control\Multiline::TABLE_CELL => ['width' => 2]];
$inventory->getField($inventory->fieldName()->box)->ui['multiline'] = [Form\Control\Multiline::TABLE_CELL => ['width' => 2]];
Expand Down
2 changes: 1 addition & 1 deletion docs/callbacks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ When you trigger callback, you'll see the output::
{"success": true, "message": "Success", "eval": "alert(\"ok\")"}

This is how JsCallback renders actions and sends them back to the browser. In order to retrieve and execute actions,
you'll need a JavaScript routine. Luckily JsCallback also implements JsExpressionable, so it, in itself is an action.
you'll need a JavaScript routine. Luckily JsCallback can be passed to :php:meth:`View::on()` as a JS action.

Let me try this again. JsCallback is an :ref:`js_action` which will execute request towards a callback-URL that will
execute PHP method returning one or more :ref:`js_action` which will be received and executed by the original action.
Expand Down
9 changes: 5 additions & 4 deletions src/CardDeck.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,9 @@ protected function initActionExecutor(Model\UserAction $action): ExecutorInterfa
* Return proper js statement for afterExecute hook on action executor
* depending on return type, model loaded and action scope.
*
* @param string|array|JsExpressionable|Model|null $return
* @param string|JsExpressionable|array<int, JsExpressionable>|Model|null $return
*
* @return array|object
* @return JsExpressionable|array<int, JsExpressionable>
*/
protected function jsExecute($return, Model\UserAction $action)
{
Expand All @@ -242,10 +242,9 @@ protected function jsExecute($return, Model\UserAction $action)
}

/**
* Return jsNotifier object.
* Override this method for setting notifier based on action or model value.
*/
protected function getNotifier(Model\UserAction $action, string $msg = null): object
protected function getNotifier(Model\UserAction $action, string $msg = null): JsExpressionable
{
$notifier = Factory::factory($this->notifyDefault);
if ($msg) {
Expand All @@ -257,6 +256,8 @@ protected function getNotifier(Model\UserAction $action, string $msg = null): ob

/**
* Js expression return when action afterHook executor return a Model.
*
* @return array<int, JsExpressionable>
*/
protected function jsModelReturn(Model\UserAction $action, string $msg = 'Done!'): array
{
Expand Down
9 changes: 2 additions & 7 deletions src/Console.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,9 @@ public function set($fx = null, $event = null)
return $this;
}

/**
* Return JavaScript expression to execute console.
*
* @return JsExpressionable
*/
public function jsExecute()
public function jsExecute(): JsExpressionable
{
return $this->sse;
return $this->sse->jsExecute();
}

private function escapeOutputHtml(string $message): string
Expand Down
2 changes: 2 additions & 0 deletions src/Crud.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ protected function initActionExecutor(Model\UserAction $action)
* depending on return type, model loaded and action scope.
*
* @param string|null $return
*
* @return array<int, JsExpressionable>
*/
protected function jsExecute($return, Model\UserAction $action): array
{
Expand Down
6 changes: 3 additions & 3 deletions src/JsCallback.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Atk4\Ui;

class JsCallback extends Callback implements JsExpressionable
class JsCallback extends Callback
{
/** @var array Holds information about arguments passed in to the callback. */
public $args = [];
Expand Down Expand Up @@ -47,7 +47,7 @@ protected function flattenArray(array $response): array
return $res;
}

public function jsRender(): string
public function jsExecute(): JsExpression
{
$this->getApp(); // assert has App

Expand All @@ -57,7 +57,7 @@ public function jsRender(): string
'confirm' => $this->confirm,
'apiConfig' => $this->apiConfig,
'storeName' => $this->storeName,
])->jsRender();
]);
}

/**
Expand Down
17 changes: 6 additions & 11 deletions src/JsExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function jsRender(): string
$namelessCount = 0;
$res = preg_replace_callback(
'~\[[\w]*\]|{[\w]*}~',
function ($matches) use (&$namelessCount) {
function ($matches) use (&$namelessCount): string {
$identifier = substr($matches[0], 1, -1);

// Allow template to contain []
Expand All @@ -51,7 +51,7 @@ function ($matches) use (&$namelessCount) {
$value = $this->args[$identifier];

// no escaping for "{}"
if ($matches[0][0] === '{') {
if ($matches[0][0] === '{' && is_string($value)) {
return $value;
}

Expand All @@ -74,15 +74,10 @@ function ($matches) use (&$namelessCount) {
*/
protected function _jsEncode($arg): string
{
if (is_object($arg)) {
if ($arg instanceof JsExpressionable) {
$result = $arg->jsRender();
if (is_object($arg) && $arg instanceof JsExpressionable) {
$result = $arg->jsRender();

return $result;
}

throw (new Exception('Not sure how to represent this object in JSON'))
->addMoreInfo('obj', $arg);
return $result;
} elseif (is_array($arg)) {
$array = [];
$assoc = !array_is_list($arg);
Expand Down Expand Up @@ -115,7 +110,7 @@ protected function _jsEncode($arg): string
} elseif ($arg === null) {
$string = 'null';
} else {
throw (new Exception('Unsupported argument type'))
throw (new Exception('Argument is not renderable to JS'))
->addMoreInfo('arg', $arg);
}

Expand Down
22 changes: 10 additions & 12 deletions src/JsFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class JsFunction implements JsExpressionable
/** @var array */
public $fxArgs;

/** @var array */
/** @var array<int, JsExpressionable> */
public $fxStatements = [];

/** @var bool add preventDefault(event) to generated method */
Expand All @@ -28,12 +28,19 @@ class JsFunction implements JsExpressionable
/** @var string Indent of target code (not one indent level) */
public $indent = ' ';

/**
* @param array<int, JsExpressionable|null>|array<string, mixed> $statements
*/
public function __construct(array $args, array $statements)
{
$this->fxArgs = $args;

foreach ($statements as $key => $value) {
if (is_int($key)) {
if ($value === null) { // TODO this should be not needed
continue;
}

$this->fxStatements[] = $value;
} else {
$this->{$key} = $value;
Expand All @@ -56,18 +63,9 @@ public function jsRender(): string
$output = 'function (' . implode(', ', $this->fxArgs) . ') {'
. $pre;
foreach ($this->fxStatements as $statement) {
if ($statement === null) { // TODO this should be not needed
continue;
}

if ($statement instanceof JsExpressionable) {
$statement = $statement->jsRender();
} else {
throw (new Exception('Incorrect statement for JsFunction'))
->addMoreInfo('statement', $statement);
}
$js = $statement->jsRender();

$output .= "\n" . $this->indent . ' ' . $statement . (!preg_match('~[;}]\s*$~', $statement) ? ';' : '');
$output .= "\n" . $this->indent . ' ' . $js . (!preg_match('~[;}]\s*$~', $js) ? ';' : '');
}

$output .= "\n" . $this->indent . '}';
Expand Down
4 changes: 2 additions & 2 deletions src/JsSse.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected function init(): void
}
}

public function jsRender(): string
public function jsExecute(): JsExpression
{
$this->getApp(); // assert has App

Expand All @@ -53,7 +53,7 @@ public function jsRender(): string
$options['closeBeforeUnload'] = $this->closeBeforeUnload;
}

return (new Jquery($this->getOwner() /* TODO element and loader element should be passed explicitly */))->atkServerEvent($options)->jsRender();
return (new Jquery($this->getOwner() /* TODO element and loader element should be passed explicitly */))->atkServerEvent($options);
}

/**
Expand Down
3 changes: 0 additions & 3 deletions src/UserAction/ConfirmationExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,6 @@ private function jsShowAndLoad(array $urlArgs, array $apiConfig): array
];
}

/**
* Return js expression that will trigger action executor.
*/
public function jsExecute(array $urlArgs = []): array
{
if (!$this->action) {
Expand Down
4 changes: 4 additions & 0 deletions src/UserAction/JsExecutorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@

namespace Atk4\Ui\UserAction;

use Atk4\Ui\JsExpressionable;

/**
* Add js trigger for executing an action.
*/
interface JsExecutorInterface extends ExecutorInterface
{
/**
* Return js expression that will trigger action executor.
*
* @return array<int, JsExpressionable>
*/
public function jsExecute(array $urlArgs): array;
}
3 changes: 0 additions & 3 deletions src/UserAction/ModalExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,6 @@ public function assignTrigger(View $view, array $urlArgs = [], string $when = 'c
return $this;
}

/**
* Generate js for triggering action.
*/
public function jsExecute(array $urlArgs = []): array
{
if (!$this->actionInitialized) {
Expand Down
26 changes: 22 additions & 4 deletions src/View.php
Original file line number Diff line number Diff line change
Expand Up @@ -960,8 +960,8 @@ public function jsReload($args = [], $afterSuccess = null, $apiConfig = [])
* @see http://agile-ui.readthedocs.io/en/latest/js.html
*
* @param string $event JavaScript event
* @param ($action is null|array ? string|JsExpressionable|\Closure|array|UserAction\ExecutorInterface|Model\UserAction : string|array) $selector Optional jQuery-style selector
* @param string|JsExpressionable|\Closure|array|UserAction\ExecutorInterface|Model\UserAction|null $action code to execute
* @param ($action is null|array ? string|JsExpressionable|JsCallback|\Closure|array|UserAction\ExecutorInterface|Model\UserAction : string|array) $selector Optional jQuery-style selector
* @param string|JsExpressionable|JsCallback|\Closure|array|UserAction\ExecutorInterface|Model\UserAction|null $action code to execute
*
* @return ($selector is null|string ? ($action is null ? Jquery : null) : ($action is null|array ? Jquery : null))
*/
Expand Down Expand Up @@ -1000,6 +1000,22 @@ public function on(string $event, $selector = null, $action = null, array $defau
$eventStatements['preventDefault'] = $defaults['preventDefault'] ?? true;
$eventStatements['stopPropagation'] = $defaults['stopPropagation'] ?? true;

$lazyJsRenderFx = function (\Closure $fx): JsExpressionable {
return new class($fx) implements JsExpressionable {
public \Closure $fx;

public function __construct(\Closure $fx)
{
$this->fx = $fx;
}

public function jsRender(): string
{
return ($this->fx)()->jsRender();
}
};
};

// Dealing with callback action.
if ($action instanceof \Closure || (is_array($action) && ($action[0] ?? null) instanceof \Closure)) {
if (is_array($action)) {
Expand All @@ -1021,7 +1037,7 @@ public function on(string $event, $selector = null, $action = null, array $defau
return $action($chain, ...$args);
}, $arguments);

$actions[] = $cb;
$actions[] = $lazyJsRenderFx(fn () => $cb->jsExecute());
} elseif ($action instanceof UserAction\ExecutorInterface || $action instanceof Model\UserAction) {
// Setup UserAction executor.
$ex = $action instanceof Model\UserAction ? $this->getExecutorFactory()->create($action, $this) : $action;
Expand All @@ -1044,11 +1060,13 @@ public function on(string $event, $selector = null, $action = null, array $defau
if ($defaults['apiConfig'] ?? null) {
$ex->apiConfig = $defaults['apiConfig'];
}
$actions[] = $ex;
$actions[] = $lazyJsRenderFx(fn () => $ex->jsExecute());
$ex->executeModelAction($arguments);
} else {
throw new Exception('Executor must be of type UserAction\JsCallbackExecutor or extend View and implement UserAction\JsExecutorInterface');
}
} elseif ($action instanceof JsCallback) {
$actions[] = $lazyJsRenderFx(fn () => $action->jsExecute());
} elseif (is_array($action)) {
$actions = array_merge($actions, $action);
} else {
Expand Down
Loading

0 comments on commit 975b785

Please sign in to comment.