src/Js/JsExpression.php
<?php
declare(strict_types=1);
namespace Atk4\Ui\Js;
use Atk4\Core\DiContainerTrait;
use Atk4\Data\Persistence;
use Atk4\Ui\Exception;
use Atk4\Ui\View;
class JsExpression implements JsExpressionable
{
use DiContainerTrait;
public string $template;
/** @var array<int|string, mixed> */
public array $args;
/**
* @param array<int|string, mixed> $args
*/
public function __construct(string $template = '', array $args = [])
{
$this->template = $template;
$this->args = $args;
}
#[\Override]
public function jsRender(): string
{
$namelessCount = 0;
$res = preg_replace_callback(
'~\[[\w]*\]|{[\w]*}~',
function ($matches) use (&$namelessCount): string {
$identifier = substr($matches[0], 1, -1);
// allow template to contain []
if ($identifier === '') {
$identifier = $namelessCount++;
}
if (!isset($this->args[$identifier])) {
throw (new Exception('Tag is not defined in template'))
->addMoreInfo('tag', $identifier)
->addMoreInfo('template', $this->template);
}
$value = $this->args[$identifier];
// no escaping for "{}"
if ($matches[0][0] === '{' && is_string($value)) {
return $value;
}
$valueStr = $this->_jsEncode($value);
if ($value instanceof JsExpressionable && !str_ends_with($valueStr, ';')) {
$valueStr = '(' . $valueStr . ')';
}
return $valueStr;
},
$this->template
);
return trim($res);
}
/**
* @param mixed $value
*/
protected function _jsEncode($value): string
{
if ($value instanceof JsExpressionable) {
$res = $value->jsRender();
} elseif ($value instanceof View) {
$res = $this->_jsEncode('#' . $value->getHtmlId());
} elseif (is_array($value)) {
$array = [];
$assoc = !array_is_list($value);
foreach ($value as $k => $v) {
$v = $this->_jsEncode($v);
$k = $this->_jsEncode($k);
if (!$assoc) {
$array[] = $v;
} else {
$array[] = $k . ': ' . $v;
}
}
if ($assoc) {
$res = '{' . implode(', ', $array) . '}';
} else {
$res = '[' . implode(', ', $array) . ']';
}
} elseif (is_string($value)) {
$res = json_encode($value, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR);
$res = '\'' . str_replace(['\'', '\"'], ['\\\'', '"'], substr($res, 1, -1)) . '\'';
} elseif (is_bool($value)) {
$res = $value ? 'true' : 'false';
} elseif (is_int($value)) {
// IMPORTANT: always convert large integers to string, otherwise numbers can be rounded by JS
$res = abs($value) < (2 ** 53) ? (string) $value : $this->_jsEncode((string) $value);
} elseif (is_float($value)) {
$res = Persistence\Sql\Expression::castFloatToString($value);
} elseif ($value === null) {
$res = 'null';
} else {
throw (new Exception('Argument is not renderable to JS'))
->addMoreInfo('arg', $value);
}
return $res;
}
}