-
-
Notifications
You must be signed in to change notification settings - Fork 340
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[LiveComponent] Allow binding LiveProp to URL query parameter
- Loading branch information
Showing
28 changed files
with
896 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
src/LiveComponent/assets/dist/Component/plugins/QueryStringPlugin.d.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import Component from '../index'; | ||
import { PluginInterface } from './PluginInterface'; | ||
export default class implements PluginInterface { | ||
private element; | ||
private mapping; | ||
attachToComponent(component: Component): void; | ||
private registerBindings; | ||
private updateUrl; | ||
} |
9 changes: 9 additions & 0 deletions
9
src/LiveComponent/assets/dist/Component/plugins/QueryStringPluging.d.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import Component from '../index'; | ||
import { PluginInterface } from './PluginInterface'; | ||
export default class implements PluginInterface { | ||
private element; | ||
private mapping; | ||
attachToComponent(component: Component): void; | ||
private registerBindings; | ||
private updateUrl; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export declare function setQueryParam(param: string, value: any): void; | ||
export declare function removeQueryParam(param: string): void; |
52 changes: 52 additions & 0 deletions
52
src/LiveComponent/assets/src/Component/plugins/QueryStringPlugin.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import Component from '../index'; | ||
import { PluginInterface } from './PluginInterface'; | ||
import { | ||
setQueryParam, removeQueryParam, | ||
} from '../../url_utils'; | ||
|
||
type QueryMapping = { | ||
name: string, | ||
} | ||
|
||
export default class implements PluginInterface { | ||
private element: Element; | ||
private mapping: Map<string,QueryMapping> = new Map; | ||
|
||
attachToComponent(component: Component): void { | ||
this.element = component.element; | ||
this.registerBindings(); | ||
|
||
component.on('connect', (component: Component) => { | ||
this.updateUrl(component); | ||
}); | ||
|
||
component.on('render:finished', (component: Component)=> { | ||
this.updateUrl(component); | ||
}); | ||
} | ||
|
||
private registerBindings(): void { | ||
const rawQueryMapping = (this.element as HTMLElement).dataset.liveQueryMapping; | ||
if (rawQueryMapping === undefined) { | ||
return; | ||
} | ||
|
||
const mapping = JSON.parse(rawQueryMapping) as {[p: string]: QueryMapping}; | ||
|
||
Object.entries(mapping).forEach(([key, config]) => { | ||
this.mapping.set(key, config); | ||
}) | ||
} | ||
|
||
private updateUrl(component: Component){ | ||
this.mapping.forEach((mapping, propName) => { | ||
const value = component.valueStore.get(propName); | ||
if (value === '' || value === null || value === undefined) { | ||
removeQueryParam(mapping.name); | ||
} else { | ||
setQueryParam(mapping.name, value); | ||
} | ||
|
||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
class AdvancedURLSearchParams extends URLSearchParams { | ||
set(name: string, value: any) { | ||
if (typeof value !== 'object') { | ||
super.set(name, value); | ||
} else { | ||
this.delete(name); | ||
if (Array.isArray(value)) { | ||
value.forEach((v) => { | ||
this.append(`${name}[]`, v); | ||
}); | ||
} else { | ||
Object.entries(value).forEach(([index, v]) => { | ||
this.append(`${name}[${index}]`, v as string); | ||
}); | ||
} | ||
} | ||
} | ||
|
||
delete(name: string) { | ||
super.delete(name); | ||
const pattern = new RegExp(`^${name}(\\[.*])?$`); | ||
for (const key of Array.from(this.keys())) { | ||
if (key.match(pattern)) { | ||
super.delete(key); | ||
} | ||
} | ||
} | ||
} | ||
|
||
export function setQueryParam(param: string, value: any) { | ||
const queryParams = new AdvancedURLSearchParams(window.location.search); | ||
|
||
queryParams.set(param, value); | ||
|
||
const url = urlFromQueryParams(queryParams); | ||
|
||
history.replaceState(history.state, '', url); | ||
} | ||
|
||
export function removeQueryParam(param: string) { | ||
const queryParams = new AdvancedURLSearchParams(window.location.search); | ||
|
||
queryParams.delete(param); | ||
|
||
const url = urlFromQueryParams(queryParams); | ||
|
||
history.replaceState(history.state, '', url); | ||
} | ||
|
||
function urlFromQueryParams(queryParams: URLSearchParams) { | ||
let queryString = ''; | ||
if (Array.from(queryParams.entries()).length > 0) { | ||
queryString += '?' + queryParams.toString(); | ||
} | ||
|
||
return window.location.origin + window.location.pathname + queryString + window.location.hash; | ||
} |
76 changes: 76 additions & 0 deletions
76
src/LiveComponent/assets/test/controller/query-binding.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
'use strict'; | ||
|
||
import {createTest, initComponent, shutdownTests} from '../tools'; | ||
import { getByText, waitFor } from '@testing-library/dom'; | ||
|
||
describe('LiveController query string binding', () => { | ||
afterEach(() => { | ||
shutdownTests(); | ||
}); | ||
|
||
it('doesn\'t initialize URL if props are not defined', async () => { | ||
await createTest({ prop: ''}, (data: any) => ` | ||
<div ${initComponent(data, { queryMapping: {prop: {name: 'prop'}}})}></div> | ||
`) | ||
|
||
expect(window.location.search).toEqual(''); | ||
}) | ||
|
||
it('initializes URL with defined props values', async () => { | ||
await createTest({ prop: 'foo'}, (data: any) => ` | ||
<div ${initComponent(data, { queryMapping: {prop: {name: 'prop'}}})}></div> | ||
`) | ||
|
||
expect(window.location.search).toEqual('?prop=foo'); | ||
}); | ||
|
||
it('properly handles array props in the URL', async () => { | ||
await createTest({ prop: ['foo', 'bar']}, (data: any) => ` | ||
<div ${initComponent(data, { queryMapping: {prop: {name: 'prop'}}})}></div> | ||
`) | ||
expect(decodeURIComponent(window.location.search)).toEqual('?prop[]=foo&prop[]=bar'); | ||
}); | ||
|
||
it('updates the URL when the props changed', async () => { | ||
const test = await createTest({ prop: ''}, (data: any) => ` | ||
<div ${initComponent(data, { queryMapping: {prop: {name: 'prop'}}})}></div> | ||
`) | ||
|
||
test.expectsAjaxCall() | ||
.expectUpdatedData({prop: 'foo'}); | ||
|
||
await test.component.set('prop', 'foo', true); | ||
|
||
expect(window.location.search).toEqual('?prop=foo'); | ||
}); | ||
|
||
it('updates the URL with props changed by the server', async () => { | ||
const test = await createTest({ prop: ''}, (data: any) => ` | ||
<div ${initComponent(data, {queryMapping: {prop: {name: 'prop'}}})}> | ||
Prop: ${data.prop} | ||
<button data-action="live#action" data-action-name="changeProp">Change prop</button> | ||
</div> | ||
`); | ||
|
||
test.expectsAjaxCall() | ||
.expectActionCalled('changeProp') | ||
.serverWillChangeProps((data: any) => { | ||
data.prop = 'foo'; | ||
}); | ||
|
||
getByText(test.element, 'Change prop').click(); | ||
|
||
await waitFor(() => expect(test.element).toHaveTextContent('Prop: foo')); | ||
|
||
expect(window.location.search).toEqual('?prop=foo'); | ||
}); | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.