Skip to content
This repository was archived by the owner on Feb 27, 2024. It is now read-only.

Commit 18b17da

Browse files
committed
Support for raw JSON attributes
1 parent 8338a72 commit 18b17da

File tree

8 files changed

+109
-31
lines changed

8 files changed

+109
-31
lines changed

app/app/View/Components/ToVueProp.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Contracts\View\View;
77
use Illuminate\View\Component;
88
use ProtoneMedia\SpladeCore\Attributes\VueProp;
9+
use ProtoneMedia\SpladeCore\Attributes\VuePropRaw;
910
use Psr\SimpleCache\CacheInterface;
1011

1112
class ToVueProp extends Component
@@ -39,6 +40,7 @@ public function __construct(
3940
#[VueProp] public array $defaultArray = ['foo'],
4041
#[VueProp] public array|bool|string $multipleTypes = ['foo'],
4142
#[VueProp(as: 'renamed')] public string $name = 'renamed-foo',
43+
#[VuePropRaw] public string $json = '{"foo":"bar"}',
4244
) {
4345
}
4446

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
<script setup></script>
22
<div>
3-
<p>Mixed: <span v-text="mixed" /></p>
4-
<p>String: <span v-text="string" /></p>
5-
<p>Default String: <span v-text="defaultString" /></p>
6-
<p>Nullable String: <span v-text="nullableString" /></p>
7-
<p>Int: <span v-text="int" /></p>
8-
<p>Bool: <span v-text="bool" /></p>
9-
<p>Array: <span v-text="array" /></p>
10-
<p>Object: <span v-text="object" /></p>
11-
<p>Nullable Int: <span v-text="nullableInt" /></p>
12-
<p>Nullable Bool: <span v-text="nullableBool" /></p>
13-
<p>Nullable Array: <span v-text="nullableArray" /></p>
14-
<p>Nullable Object: <span v-text="nullableObject" /></p>
15-
<p>Default Int: <span v-text="defaultInt" /></p>
16-
<p>Default Bool: <span v-text="defaultBool" /></p>
17-
<p>Default Array: <span v-text="defaultArray" /></p>
18-
<p>Multiple Types: <span v-text="multipleTypes" /></p>
19-
<p>Data From Method: <span v-text="dataFromMethod" /></p>
20-
<p>Renamed: <span v-text="renamed" /></p>
3+
<p>Mixed: <span v-text="mixed" /> (<span v-text="typeof mixed" />)</p>
4+
<p>String: <span v-text="string" /> (<span v-text="typeof string" />)</p>
5+
<p>Default String: <span v-text="defaultString" /> (<span v-text="typeof defaultString" />)</p>
6+
<p>Nullable String: <span v-text="nullableString" /> (<span v-text="typeof nullableString" />)</p>
7+
<p>Int: <span v-text="int" /> (<span v-text="typeof int" />)</p>
8+
<p>Bool: <span v-text="bool" /> (<span v-text="typeof bool" />)</p>
9+
<p>Array: <span v-text="array" /> (<span v-text="typeof array" />)</p>
10+
<p>Object: <span v-text="object" /> (<span v-text="typeof object" />)</p>
11+
<p>Nullable Int: <span v-text="nullableInt" /> (<span v-text="typeof nullableInt" />)</p>
12+
<p>Nullable Bool: <span v-text="nullableBool" /> (<span v-text="typeof nullableBool" />)</p>
13+
<p>Nullable Array: <span v-text="nullableArray" /> (<span v-text="typeof nullableArray" />)</p>
14+
<p>Nullable Object: <span v-text="nullableObject" /> (<span v-text="typeof nullableObject" />)</p>
15+
<p>Default Int: <span v-text="defaultInt" /> (<span v-text="typeof defaultInt" />)</p>
16+
<p>Default Bool: <span v-text="defaultBool" /> (<span v-text="typeof defaultBool" />)</p>
17+
<p>Default Array: <span v-text="defaultArray" /> (<span v-text="typeof defaultArray" />)</p>
18+
<p>Multiple Types: <span v-text="multipleTypes" /> (<span v-text="typeof multipleTypes" />)</p>
19+
<p>Data From Method: <span v-text="dataFromMethod" /> (<span v-text="typeof dataFromMethod" />)</p>
20+
<p>Renamed: <span v-text="renamed" /> (<span v-text="typeof renamed" />)</p>
21+
<p>JSON: <span v-text="json" /> (<span v-text="typeof json" />)</p>
2122
</div>

app/tests/Browser/ToVuePropTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ public function it_can_pass_blade_props_as_vue_rops()
3030
->assertSee('Default Array: [ "foo" ]')
3131
->assertSee('Multiple Types: [ "foo" ]')
3232
->assertSee('Data From Method: [ "foo", "bar", "baz" ]')
33-
->assertSee('Renamed: renamed-foo');
33+
->assertSee('Renamed: renamed-foo')
34+
->assertSee('JSON: { "foo": "bar" } (object)');
3435
});
3536
}
3637
}

app/tests/Unit/Console/__snapshots__/BuildComponentsTest__it_builds_the_components__12.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const props = defineProps({
2121
defaultArray: { type: Array, default: JSON.parse('[\u0022foo\u0022]') },
2222
multipleTypes: { type: [Array, String, Boolean], default: JSON.parse('[\u0022foo\u0022]') },
2323
renamed: { type: String, default: 'renamed-foo' },
24+
json: { default: JSON.parse('{\u0022foo\u0022:\u0022bar\u0022}') },
2425
dataFromMethod: { type: Array, default: null },
2526
})
2627
const spladeRender = h({

src/Attributes/VuePropRaw.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace ProtoneMedia\SpladeCore\Attributes;
4+
5+
#[Attribute]
6+
class VuePropRaw extends VueProp
7+
{
8+
}

src/BladeViewExtractor.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Illuminate\Support\Str;
1111
use InvalidArgumentException;
1212
use ProtoneMedia\SpladeCore\Facades\SpladePlugin;
13+
use ProtoneMedia\SpladeCore\View\Factory;
1314

1415
class BladeViewExtractor
1516
{
@@ -320,8 +321,15 @@ protected function extractDefinePropsFromScript(): array
320321
{
321322
$bladePropsAsVueProps = Collection::make($this->getBladePropsThatArePassedAsVueProps())
322323
->map(function (object $specs) {
323-
$type = is_array($specs->type) ? '['.implode(',', $specs->type).']' : "{$specs->type}";
324-
$default = Js::from($specs->default)->toHtml();
324+
$default = $specs->raw
325+
? Factory::convertJsonToJavaScriptExpression($specs->default)
326+
: Js::from($specs->default)->toHtml();
327+
328+
$type = null;
329+
330+
if (! $specs->raw) {
331+
$type = is_array($specs->type) ? '['.implode(',', $specs->type).']' : "{$specs->type}";
332+
}
325333

326334
return $type
327335
? "{type: {$type}, default: {$default}}"

src/ComponentSerializer.php

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
use JsonSerializable;
1212
use ProtoneMedia\SpladeCore\Attributes\Vue;
1313
use ProtoneMedia\SpladeCore\Attributes\VueProp;
14+
use ProtoneMedia\SpladeCore\Attributes\VuePropRaw;
1415
use ProtoneMedia\SpladeCore\Attributes\VueRef;
16+
use ReflectionAttribute;
1517
use ReflectionClass;
1618
use ReflectionMethod;
1719
use ReflectionProperty;
@@ -163,6 +165,23 @@ public static function getDataFromComponentClass(string $componentClass): array
163165
return $values;
164166
}
165167

168+
private static function getVuePropAttribute(ReflectionProperty|ReflectionMethod $propertyOrMethod): ?ReflectionAttribute
169+
{
170+
$prop = $propertyOrMethod->getAttributes(VueProp::class);
171+
172+
if (! empty($prop)) {
173+
return $prop[0];
174+
}
175+
176+
$propRaw = $propertyOrMethod->getAttributes(VuePropRaw::class);
177+
178+
if (! empty($propRaw)) {
179+
return $propRaw[0];
180+
}
181+
182+
return null;
183+
}
184+
166185
/**
167186
* Same as getPropsFromComponent() but for a component class, so
168187
* without any values.
@@ -186,11 +205,13 @@ public static function getPropsFromComponentClass(string $componentClass): array
186205
continue;
187206
}
188207

189-
if (empty($vuePropAttributes = $property->getAttributes(VueProp::class))) {
208+
$vuePropAttribute = static::getVuePropAttribute($property);
209+
210+
if (! $vuePropAttribute) {
190211
continue;
191212
}
192213

193-
$as = $vuePropAttributes[0]->getArguments()['as'] ?? $name;
214+
$as = $vuePropAttribute->getArguments()['as'] ?? $name;
194215

195216
$defaultValue = $property->getDefaultValue();
196217

@@ -202,6 +223,7 @@ public static function getPropsFromComponentClass(string $componentClass): array
202223

203224
$values[$as] = (object) [
204225
'default' => $defaultValue,
226+
'raw' => $vuePropAttribute->getName() === VuePropRaw::class,
205227
'type' => static::mapTypeToVueType($property->getType()),
206228
'value' => null,
207229
];
@@ -222,14 +244,17 @@ public static function getPropsFromComponentClass(string $componentClass): array
222244
continue;
223245
}
224246

225-
if (empty($vuePropAttributes = $method->getAttributes(VueProp::class))) {
247+
$vuePropAttribute = static::getVuePropAttribute($method);
248+
249+
if (! $vuePropAttribute) {
226250
continue;
227251
}
228252

229-
$as = $vuePropAttributes[0]->getArguments()['as'] ?? $name;
253+
$as = $vuePropAttribute->getArguments()['as'] ?? $name;
230254

231255
$values[$as] = (object) [
232256
'default' => null,
257+
'raw' => $vuePropAttribute->getName() === VuePropRaw::class,
233258
'type' => static::mapTypeToVueType($method->getReturnType()),
234259
'value' => null,
235260
];
@@ -260,11 +285,13 @@ public function getPropsFromComponent(): array
260285
continue;
261286
}
262287

263-
if (empty($vuePropAttributes = $property->getAttributes(VueProp::class))) {
288+
$vuePropAttribute = static::getVuePropAttribute($property);
289+
290+
if (! $vuePropAttribute) {
264291
continue;
265292
}
266293

267-
$as = $vuePropAttributes[0]->getArguments()['as'] ?? $name;
294+
$as = $vuePropAttribute->getArguments()['as'] ?? $name;
268295

269296
$value = $property->isInitialized($this->component)
270297
? $property->getValue($this->component)
@@ -301,6 +328,7 @@ public function getPropsFromComponent(): array
301328

302329
$values[$as] = (object) [
303330
'default' => $defaultValue,
331+
'raw' => $vuePropAttribute->getName() === VuePropRaw::class,
304332
'type' => static::mapTypeToVueType($property->getType()),
305333
'value' => $this->getSerializedPropertyValue($value),
306334
];
@@ -321,14 +349,17 @@ public function getPropsFromComponent(): array
321349
continue;
322350
}
323351

324-
if (empty($vuePropAttributes = $method->getAttributes(VueProp::class))) {
352+
$vuePropAttribute = static::getVuePropAttribute($method);
353+
354+
if (! $vuePropAttribute) {
325355
continue;
326356
}
327357

328-
$as = $vuePropAttributes[0]->getArguments()['as'] ?? $name;
358+
$as = $vuePropAttribute->getArguments()['as'] ?? $name;
329359

330360
$values[$as] = (object) [
331361
'default' => null,
362+
'raw' => $vuePropAttribute->getName() === VuePropRaw::class,
332363
'type' => static::mapTypeToVueType($method->getReturnType()),
333364
'value' => Container::getInstance()->call([$this->component, $name]),
334365
];
@@ -340,7 +371,7 @@ public function getPropsFromComponent(): array
340371
/**
341372
* Maps a PHP type to a Vue type.
342373
*/
343-
public static function mapTypeToVueType(?ReflectionType $type = null): array|string|null
374+
public static function mapTypeToVueType(ReflectionType $type = null): array|string|null
344375
{
345376
if ($type instanceof \ReflectionUnionType) {
346377
$types = collect($type->getTypes())

src/View/Factory.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
class Factory extends BaseFactory
1313
{
14+
const REQUIRED_JSON_FLAGS = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_THROW_ON_ERROR;
15+
1416
/**
1517
* Whether to track Splade components.
1618
*/
@@ -93,6 +95,28 @@ public function pushSpladeTemplate($id, $value): void
9395
);
9496
}
9597

98+
/**
99+
* Convert the given JSON to a JavaScript expression.
100+
*
101+
* @param string $json
102+
* @param int $flags
103+
* @return string
104+
*
105+
* @throws \JsonException
106+
*/
107+
public static function convertJsonToJavaScriptExpression($json, $flags = 0)
108+
{
109+
if ($json === '[]' || $json === '{}') {
110+
return $json;
111+
}
112+
113+
if (Str::startsWith($json, ['"', '{', '['])) {
114+
return "JSON.parse('".substr(json_encode($json, $flags | static::REQUIRED_JSON_FLAGS), 1, -1)."')";
115+
}
116+
117+
return $json;
118+
}
119+
96120
/**
97121
* Temporarily store the passed attributes, render the component and
98122
* push it to the Splade templates stack. Then return a generic Vue
@@ -139,7 +163,9 @@ public function renderComponent()
139163
$key = 'v-bind:'.Str::kebab($key);
140164
}
141165

142-
$attributes[$key] = Js::from($specs->value)->toHtml();
166+
$attributes[$key] = $specs->raw
167+
? static::convertJsonToJavaScriptExpression($specs->value)
168+
: Js::from($specs->value)->toHtml();
143169
});
144170

145171
$attrs = $attributes->toHtml();

0 commit comments

Comments
 (0)