Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add doc for URL binding feature #1346

Merged
merged 1 commit into from
Jan 17, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions src/LiveComponent/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2081,6 +2081,8 @@ then render it manually after:

{{ form_widget(form.todoItems.vars.button_add, { label: '+ Add Item', attr: { class: 'btn btn-outline-primary' } }) }}

.. _validation:

Validation (without a Form)
---------------------------

Expand Down Expand Up @@ -2302,6 +2304,130 @@ You can also trigger a specific "action" instead of a normal re-render:
#}
>

Changing the URL when a LiveProp changes
----------------------------------------

.. versionadded:: 2.14

The ``url`` option was introduced in Live Components 2.14.

If you want the URL to update when a ``LiveProp`` changes, you can do that with the ``url`` option::

// src/Components/SearchModule.php
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    // src/Twig/Components/SearchModule.php

right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other examples in the doc live in src/Components, not sure why this one should be different?

namespace App\Components;

use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent]
class SearchModule
{
use DefaultActionTrait;

#[LiveProp(writable: true, url: true)]
public string $query = '';
}

Now, when the user changes the value of the ``query`` prop, a query parameter in the URL will be updated to reflect the
new state of your component, for example: ``https://my.domain/search?query=my+search+string``.

If you load this URL in your browser, the ``LiveProp`` value will be initialized using the query string
(e.g. ``my search string``).

.. note::

The URL is changed via ``history.replaceState()``. So no new entry is added.

.. warning::

You can use multiple components with URL bindings in the same page, as long as bound field names don't collide.
Otherwise, you will observe unexpected behaviors.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Someone may have suggested adding this, I say remove it. I think this is self-evident (e.g. if you have 2 variables defined in the same function with the same name, unexpected things will happen).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you suggest removing the full warning block or just the last line?


Supported Data Types
~~~~~~~~~~~~~~~~~~~~

You can use scalars, arrays and objects in your URL bindings:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You say objects but then don't show an example below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was the purpose of the last example: { foo: 'bar', baz: 42 }. Those values are the "serialized' ones, maybe I should change the table header to something like JavaScript prop values?


============================================ =================================================
JavaScript ``prop`` value URL representation
============================================ =================================================
``'some search string'`` ``prop=some+search+string``
``42`` ``prop=42``
``['foo', 'bar']`` ``prop[0]=foo&prop[1]=bar``
``{ foo: 'bar', baz: 42 }`` ``prop[foo]=bar&prop[baz]=42``


When a page is loaded with a query parameter that's bound to a ``LiveProp`` (e.g. ``/search?query=my+search+string``),
the value - ``my search string`` - goes through the hydration system before it's set onto the property. If a value can't
be hydrated, it will be ignored.

Multiple Query Parameter Bindings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can use as many URL bindings as you want in your component. To ensure the state is fully represented in the URL,
all bound props will be set as query parameters, even if their values didn't change.

For example, if you declare the following bindings::

// ...
#[AsLiveComponent]
class SearchModule
{
#[LiveProp(writable: true, url: true)]
public string $query = '';

#[LiveProp(writable: true, url: true)]
public string $mode = 'fulltext';

// ...
}


And you only set the ``query`` value, then your URL will be updated to
``https://my.domain/search?query=my+query+string&mode=fulltext``.

Validating the Query Parameter Values
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Like any writable ``LiveProp``, because the user can modify this value, you should consider adding
:ref:`validation <validation>`. When you bind a ``LiveProp`` to the URL, the initial value is not automatically
validated. To validate it, you have to set up a `PostMount hook`_::

// ...
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\UX\LiveComponent\ValidatableComponentTrait;
use Symfony\UX\TwigComponent\Attribute\PostMount;

#[AsLiveComponent]
class SearchModule
{
use ValidatableComponentTrait;

#[LiveProp(writable: true, url: true)]
public string $query = '';

#[LiveProp(writable: true, url: true)]
#[Assert\NotBlank]
public string $mode = 'fulltext';

#[PostMount]
public function postMount(): void
{
// Validate 'mode' field without throwing an exception, so the component can be mounted anyway and a
// validation error can be shown to the user
if (!$this->validateField('mode', false)) {
// Do something when validation fails
}
}

// ...
}

.. note::

You can use `validation groups`_ if you want to use specific validation rules only in the PostMount hook.

.. _emit:

Communication Between Components: Emitting Events
Expand Down Expand Up @@ -3315,3 +3441,5 @@ bound to Symfony's BC policy for the moment.
.. _`Symfony's built-in form theming techniques`: https://symfony.com/doc/current/form/form_themes.html
.. _`pass content to Twig Components`: https://symfony.com/bundles/ux-twig-component/current/index.html#passing-blocks
.. _`Twig Component debug command`: https://symfony.com/bundles/ux-twig-component/current/index.html#debugging-components
.. _`PostMount hook`: https://symfony.com/bundles/ux-twig-component/current/index.html#postmount-hook
.. _`validation groups`: https://symfony.com/doc/current/form/validation_groups.html