Repository navigation
Expand file tree
/
Copy pathJsExpression.php
More file actions
131 lines (110 loc) · 4.09 KB
/
Copy pathJsExpression.php
File metadata and controls
131 lines (110 loc) · 4.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
<?php
declare(strict_types=1);
namespace Atk4\Ui;
use Atk4\Core\DiContainerTrait;
/**
* Implements a class that can be mapped into arbitrary JavaScript expression.
*/
class JsExpression implements JsExpressionable
{
use DiContainerTrait;
/** @var string */
public $template;
/** @var array */
public $args = [];
public function __construct(string $template = '', array $args = [])
{
$this->template = $template;
$this->args = $args;
}
/**
* Converts this arbitrary JavaScript expression into string.
*/
public function jsRender(): string
{
$namelessCount = 0;
$res = preg_replace_callback(
'~\[[\w]*\]|{[\w]*}~',
function ($matches) use (&$namelessCount) {
$identifier = substr($matches[0], 1, -1);
// Allow template to contain []
if ($identifier === '') {
$identifier = $namelessCount++;
}
if (!isset($this->args[$identifier])) {
throw (new Exception('Tag not defined in template for JsExpression'))
->addMoreInfo('tag', $identifier)
->addMoreInfo('template', $this->template);
}
$value = $this->args[$identifier];
// No escaping for {}
if ($matches[0][0] === '{') {
return $value;
}
if (is_object($value) && $value instanceof JsExpressionable) {
$value = '(' . $value->jsRender() . ')';
} elseif (is_object($value)) {
$value = $this->_json_encode($value->toString());
} else {
$value = $this->_json_encode($value);
}
return $value;
},
$this->template
);
return trim($res);
}
/**
* Provides replacement for json_encode() that will respect JsExpressionable objects
* and call jsRender() for them instead of escaping.
*/
protected function _json_encode($arg): string
{
/*
* This function is very similar to json_encode, however it will traverse array
* before encoding in search of JsExpressionable objects. Those would
* be replaced with their jsRendering.
*/
if (is_object($arg)) {
if ($arg instanceof JsExpressionable) {
$result = $arg->jsRender();
return $result;
}
throw (new Exception('Not sure how to represent this object in JSON'))
->addMoreInfo('obj', $arg);
} elseif (is_array($arg)) {
$array = [];
// is array associative? (hash)
$assoc = $arg !== array_values($arg);
foreach ($arg as $key => $value) {
$value = $this->_json_encode($value);
$key = $this->_json_encode($key);
if (!$assoc) {
$array[] = $value;
} else {
$array[] = $key . ':' . $value;
}
}
if ($assoc) {
$string = '{' . implode(',', $array) . '}';
} else {
$string = '[' . implode(',', $array) . ']';
}
} elseif (is_string($arg)) {
$string = json_encode($arg, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR);
} elseif (is_bool($arg)) {
$string = json_encode($arg);
} elseif (is_int($arg)) {
// IMPORTANT: always convert large integers to string, otherwise numbers can be rounded by JS
$string = json_encode(abs($arg) < (2 ** 53) ? $arg : (string) $arg);
} elseif (is_float($arg)) {
$string = json_encode($arg);
} elseif ($arg === null) {
$string = json_encode($arg);
} else {
throw (new Exception('Unable to json_encode value - unknown type'))
->addMoreInfo('arg', var_export($arg, true));
}
return $string;
}
}