Skip to content

Commit 32b0587

Browse files
committed
rebase and returns
1 parent 051b6ef commit 32b0587

File tree

11 files changed

+38
-133
lines changed

11 files changed

+38
-133
lines changed

src/LiveComponent/CHANGELOG.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
# CHANGELOG
22

3-
## 2.29.0
4-
5-
- Add new `mapPath` options (default `false`) to `UrlMapping` of a `LiveProp`
6-
to allow the prop to be mapped to the path instead of the query in the url.
7-
83
## 2.28.0
94

105
- Add new modifiers for input validations, useful to prevent uneccessary HTTP requests:
116
- `min_length` and `max_length`: validate length from textual input elements
127
- `min_value` and `max_value`: validate value from numeral input elements
138

9+
- Add new `mapPath` options (default `false`) to `UrlMapping` of a `LiveProp`
10+
to allow the prop to be mapped to the path instead of the query in the url.
11+
1412
```twig
1513
<!-- Do not trigger model update until 3 characters are typed -->
1614
<input data-model="min_length(3)|username" type="text" value="" />

src/LiveComponent/assets/dist/Backend/BackendResponse.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ export default class {
44
private liveUrl;
55
constructor(response: Response);
66
getBody(): Promise<string>;
7-
getLiveUrl(): Promise<string | null>;
7+
getLiveUrl(): string | null;
88
}

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 4 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ class BackendResponse {
112112
}
113113
return this.body;
114114
}
115-
async getLiveUrl() {
115+
getLiveUrl() {
116116
if (undefined === this.liveUrl) {
117-
this.liveUrl = await this.response.headers.get('X-Live-Url');
117+
this.liveUrl = this.response.headers.get('X-Live-Url');
118118
}
119119
return this.liveUrl;
120120
}
@@ -1805,109 +1805,7 @@ class ExternalMutationTracker {
18051805
return element.tagName === 'FONT' && element.getAttribute('style') === 'vertical-align: inherit;';
18061806
}
18071807
}
1808-
function isValueEmpty(value) {
1809-
if (null === value || value === '' || undefined === value || (Array.isArray(value) && value.length === 0)) {
1810-
return true;
1811-
}
1812-
if (typeof value !== 'object') {
1813-
return false;
1814-
}
1815-
for (const key of Object.keys(value)) {
1816-
if (!isValueEmpty(value[key])) {
1817-
return false;
1818-
}
1819-
}
1820-
return true;
1821-
}
1822-
function toQueryString(data) {
1823-
const buildQueryStringEntries = (data, entries = {}, baseKey = '') => {
1824-
Object.entries(data).forEach(([iKey, iValue]) => {
1825-
const key = baseKey === '' ? iKey : `${baseKey}[${iKey}]`;
1826-
if ('' === baseKey && isValueEmpty(iValue)) {
1827-
entries[key] = '';
1828-
}
1829-
else if (null !== iValue) {
1830-
if (typeof iValue === 'object') {
1831-
entries = { ...entries, ...buildQueryStringEntries(iValue, entries, key) };
1832-
}
1833-
else {
1834-
entries[key] = encodeURIComponent(iValue)
1835-
.replace(/%20/g, '+')
1836-
.replace(/%2C/g, ',');
1837-
}
1838-
}
1839-
});
1840-
return entries;
1841-
};
1842-
const entries = buildQueryStringEntries(data);
1843-
return Object.entries(entries)
1844-
.map(([key, value]) => `${key}=${value}`)
1845-
.join('&');
1846-
}
1847-
function fromQueryString(search) {
1848-
search = search.replace('?', '');
1849-
if (search === '')
1850-
return {};
1851-
const insertDotNotatedValueIntoData = (key, value, data) => {
1852-
const [first, second, ...rest] = key.split('.');
1853-
if (!second) {
1854-
data[key] = value;
1855-
return value;
1856-
}
1857-
if (data[first] === undefined) {
1858-
data[first] = Number.isNaN(Number.parseInt(second)) ? {} : [];
1859-
}
1860-
insertDotNotatedValueIntoData([second, ...rest].join('.'), value, data[first]);
1861-
};
1862-
const entries = search.split('&').map((i) => i.split('='));
1863-
const data = {};
1864-
entries.forEach(([key, value]) => {
1865-
value = decodeURIComponent(String(value || '').replace(/\+/g, '%20'));
1866-
if (!key.includes('[')) {
1867-
data[key] = value;
1868-
}
1869-
else {
1870-
if ('' === value)
1871-
return;
1872-
const dotNotatedKey = key.replace(/\[/g, '.').replace(/]/g, '');
1873-
insertDotNotatedValueIntoData(dotNotatedKey, value, data);
1874-
}
1875-
});
1876-
return data;
1877-
}
1878-
class UrlUtils extends URL {
1879-
has(key) {
1880-
const data = this.getData();
1881-
return Object.keys(data).includes(key);
1882-
}
1883-
set(key, value) {
1884-
const data = this.getData();
1885-
data[key] = value;
1886-
this.setData(data);
1887-
}
1888-
get(key) {
1889-
return this.getData()[key];
1890-
}
1891-
remove(key) {
1892-
const data = this.getData();
1893-
delete data[key];
1894-
this.setData(data);
1895-
}
1896-
getData() {
1897-
if (!this.search) {
1898-
return {};
1899-
}
1900-
return fromQueryString(this.search);
1901-
}
1902-
setData(data) {
1903-
this.search = toQueryString(data);
1904-
}
1905-
}
1906-
class HistoryStrategy {
1907-
static replace(url) {
1908-
history.replaceState(history.state, '', url);
1909-
}
1910-
}
1808+
19111809
class UnsyncedInputsTracker {
19121810
constructor(component, modelElementResolver) {
19131811
this.elementEventListeners = [
@@ -2255,7 +2153,7 @@ class Component {
22552153
return response;
22562154
}
22572155
this.processRerender(html, backendResponse);
2258-
const liveUrl = await backendResponse.getLiveUrl();
2156+
const liveUrl = backendResponse.getLiveUrl();
22592157
if (liveUrl) {
22602158
history.replaceState(history.state, '', new URL(liveUrl + window.location.hash, window.location.origin));
22612159
}

src/LiveComponent/assets/src/Backend/BackendResponse.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ export default class {
1515
return this.body;
1616
}
1717

18-
async getLiveUrl(): Promise<string | null> {
18+
getLiveUrl(): string | null {
1919
if (undefined === this.liveUrl) {
20-
this.liveUrl = await this.response.headers.get('X-Live-Url');
20+
this.liveUrl = this.response.headers.get('X-Live-Url');
2121
}
2222

2323
return this.liveUrl;

src/LiveComponent/assets/src/Component/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ export default class Component {
328328
}
329329

330330
this.processRerender(html, backendResponse);
331-
const liveUrl = await backendResponse.getLiveUrl();
331+
const liveUrl = backendResponse.getLiveUrl();
332332
if (liveUrl) {
333333
history.replaceState(
334334
history.state,

src/LiveComponent/doc/index.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2740,9 +2740,9 @@ The ``query`` value will appear in the URL like ``/search?query=my+important+que
27402740
Map the parameter to path instead of query
27412741
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27422742

2743-
.. versionadded:: 2.29
2743+
.. versionadded:: 2.28
27442744

2745-
The ``mapPath`` option was added in LiveComponents 2.29.
2745+
The ``mapPath`` option was added in LiveComponents 2.28.
27462746

27472747
Instead of setting the ``LiveProp`` as a query parameter, it can be set as route parameter
27482748
by passing the ``mapPath`` option to the ``UrlMapping`` defined for the ``LiveProp``::

src/LiveComponent/src/EventListener/LiveComponentSubscriber.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,6 @@ public function onKernelView(ViewEvent $event): void
258258
$mountedComponent = $request->attributes->get('_mounted_component');
259259
if (!$request->attributes->get('_component_default_action', false)) {
260260
// On custom action, props may be updated by the server side
261-
// @todo discuss name responseProps
262-
// @todo maybe always set in, including default action and use only this, ignoring `props` and `updated` for UrlFactory ?
263261
$liveRequestData = $request->attributes->get('_live_request_data');
264262
$liveRequestData['responseProps'] = (array) $mountedComponent->getComponent();
265263
$request->attributes->set('_live_request_data', $liveRequestData);

src/LiveComponent/src/EventListener/LiveUrlSubscriber.php

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,20 @@ public function onKernelResponse(ResponseEvent $event): void
3636
if (!$event->isMainRequest()) {
3737
return;
3838
}
39+
3940
$request = $event->getRequest();
4041
if (!$request->attributes->has('_live_component')) {
4142
return;
4243
}
4344

44-
$newUrl = null;
45-
if ($previousLocation = $request->headers->get(self::URL_HEADER)) {
46-
$liveProps = $this->getLivePropsToMap($request);
47-
$newUrl = $this->urlFactory->createFromPreviousAndProps($previousLocation, $liveProps['path'], $liveProps['query']);
45+
$newLiveUrl = null;
46+
if ($previousLiveUrl = $request->headers->get(self::URL_HEADER)) {
47+
$liveProps = $this->getLivePropsFromRequest($request);
48+
$newLiveUrl = $this->urlFactory->createFromPreviousAndProps($previousLiveUrl, $liveProps['path'], $liveProps['query']);
4849
}
4950

50-
if ($newUrl) {
51-
$event->getResponse()->headers->set(self::URL_HEADER, $newUrl);
51+
if ($newLiveUrl) {
52+
$event->getResponse()->headers->set(self::URL_HEADER, $newLiveUrl);
5253
}
5354
}
5455

@@ -59,7 +60,13 @@ public static function getSubscribedEvents(): array
5960
];
6061
}
6162

62-
private function getLivePropsToMap(Request $request): array
63+
/**
64+
* @return array{
65+
* path: array<string, mixed>,
66+
* query: array<string, mixed>
67+
* }
68+
*/
69+
private function getLivePropsFromRequest(Request $request): array
6370
{
6471
$componentName = $request->attributes->get('_live_component');
6572
$metadata = $this->metadataFactory->getMetadata($componentName);

src/LiveComponent/src/Util/UrlFactory.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,19 @@ private function replaceQueryString($url, array $props): string
7474
$queryString;
7575
}
7676

77-
// Keep the query parameters of the previous request
77+
/**
78+
* Keep the query parameters of the previous request.
79+
*/
7880
private function getPreviousQueryParameters(string $query): array
7981
{
8082
parse_str($query, $previousQueryParams);
8183

8284
return $previousQueryParams;
8385
}
8486

85-
// Symfony router will set props in query if they do not match route parameter
87+
/**
88+
* Symfony router will set props in query if they do not match route parameter.
89+
*/
8690
private function getRemnantProps(string $newUrl): array
8791
{
8892
parse_str(parse_url($newUrl)['query'] ?? '', $remnantQueryParams);

src/LiveComponent/tests/Unit/EventListener/LiveUrlSubscriberTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public function testDoNothing(
7272
$this->assertNull($response->headers->get('X-Live-Url'));
7373
}
7474

75-
public function getData(): iterable
75+
public static function provideTestUrlFactoryReceivesPathAndQuertyPropsFromRequestData(): iterable
7676
{
7777
yield 'prop_without_matching_property' => [
7878
'liveRequestData' => [
@@ -137,9 +137,9 @@ public function getData(): iterable
137137
}
138138

139139
/**
140-
* @dataProvider getData
140+
* @dataProvider provideTestUrlFactoryReceivesPathAndQuertyPropsFromRequestData
141141
*/
142-
public function testProps(
142+
public function testUrlFactoryReceivesPathAndQuertyPropsFromRequestData(
143143
array $liveRequestData,
144144
array $expectedPathProps = [],
145145
array $expectedQueryProps = [],

0 commit comments

Comments
 (0)