@@ -384,31 +384,61 @@ live property on your component to the value ``edit``. The
384384``data-action="live#update" `` is Stimulus code that triggers
385385the update.
386386
387- Updating a Field via Custom JavaScript
388- --------------------------------------
387+ Working with the Component in JavaScript
388+ ----------------------------------------
389389
390- Sometimes you might want to change the value of a field via your own
391- custom JavaScript. Suppose you have the following field inside your component:
390+ Want to change the value of a model or even trigger an action from your
391+ own custom JavaScript? No problem, thanks to a JavaScript ``Component ``
392+ object, which is attached to each root component element.
392393
393- .. code-block :: twig
394+ For example, to write your custom JavaScript, you create a Stimulus
395+ controller and put it around (or attached to) your root component element:
394396
395- <input
396- id="favorite-food"
397- data-model="favoriteFood"
398- >
397+ .. code-block :: javascript
398+
399+ // assets/controllers/some-custom-controller.js
400+ // ...
401+
402+ export default class extends Controller {
403+ connect () {
404+ // when the live component inside of this controller is initialized,
405+ // this method will be called and you can access the Component object
406+ this .element .addEventListener (' live:connect' , (event ) => {
407+ this .component = event .detail .component ;
408+ });
409+ }
410+
411+ // some Stimulus action triggered, for example, on user click
412+ toggleMode () {
413+ // e.g. set some live property called "mode" on your component
414+ this .component .set (' mode' , ' editing' );
415+ // you can also say
416+ this .component .mode = ' editing' ;
417+
418+ // or call an action
419+ this .action (' save' , { arg1: ' value1' });
420+ // you can also say:
421+ this .save ({ arg1: ' value1' });
422+ }
423+ }
399424
400- To set the value of this field via custom JavaScript (e.g. a Stimulus controller),
425+ You can also access the ``Component `` object via a special property
426+ on the root component element:
427+
428+ .. code-block :: javascript
429+
430+ const component = document .getElementById (' id-on-your-element' ).__component ;
431+ component .mode = ' editing' ;
432+
433+ Finally, you can also set the value of a model field directly. However,
401434be sure to *also * trigger a ``change `` event so that live components is notified
402435of the change:
403436
404437.. code-block :: javascript
405438
406- const input = document .getElementById (' favorite-food' );
439+ const rootElement = document .getElementById (' favorite-food' );
407440 input .value = ' sushi' ;
408441
409- element .dispatchEvent (new Event (' input' , { bubbles: true }));
410-
411- // if you have data-model="on(change)|favoriteFood", use the "change" event
412442 element .dispatchEvent (new Event (' change' , { bubbles: true }));
413443
414444 Loading States
@@ -1636,13 +1666,6 @@ To validate only on "change", use the ``on(change)`` modifier:
16361666 class="{{ this.getError('post.content') ? 'has-error' : '' }}"
16371667 >
16381668
1639- Interacting with JavaScript
1640- ---------------------------
1641-
1642- TODO:
1643- - events - like live:connect
1644- - the Component object
1645-
16461669 Polling
16471670-------
16481671
@@ -1687,49 +1710,82 @@ Nested Components
16871710
16881711Need to nest one live component inside another one? No problem! As a
16891712rule of thumb, **each component exists in its own, isolated universe **.
1690- This means that nesting one component inside another could be really
1691- simple or a bit more complex, depending on how inter-connected you want
1692- your components to be.
1713+ This means that if a parent component re-renders, it won't automatically
1714+ cause the child to re-render (but it *may * - keep reading). Or, if
1715+ a model in a child updates, it won't also update that model in its parent
1716+ (but it *can * - keep reading).
16931717
1694- Here are a few helpful things to know:
1718+ The parent-child system is *smart *. And with a few tricks, you can make
1719+ it behave exactly like you need.
16951720
16961721Each component re-renders independent of one another
16971722~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16981723
1699- If a parent component re-renders, the child component will *not * (most
1700- of the time) be updated, even though it lives inside the parent. Each
1701- component is its own, isolated universe.
1702-
1703- But this is not always what you want. For example, suppose you have a
1704- parent component that renders a form and a child component that renders
1705- one field in that form. When you click a "Save" button on the parent
1706- component, that validates the form and re-renders with errors -
1707- including a new ``error `` value that it passes into the child:
1724+ If a parent component re-renders, this may or may not cause the child
1725+ component to send its own Ajax request to re-render. What determines
1726+ that? Let's look at an example of a todo list component with a child
1727+ that renders the total number of todo items:
17081728
17091729.. code-block :: twig
17101730
1711- {# templates/components/post_form.html.twig #}
1731+ {# templates/components/todo_list.html.twig #}
1732+ <div {{ attributes }}>
1733+ <input data-model="listName">
17121734
1713- {{ component('textarea_field', {
1714- value: this.content,
1715- error: this.getError('content')
1716- }) }}
1735+ {% for todo in todos %}
1736+ ...
1737+ {% endfor %}
17171738
1718- In this situation, when the parent component re-renders after clicking
1719- "Save", you *do * want the updated child component (with the validation
1720- error) to be rendered. And this *will * happen automatically. Why?
1721- because the live component system detects that the **parent component
1722- has changed how it's rendering the child **.
1739+ {{ component('todo_footer', {
1740+ count: todos|length
1741+ }) }}
1742+ </div>
1743+
1744+ Suppose the user updates the ``listName `` model and the parent component
1745+ re-renders. In this case, the child component will *not * re-render. Why?
1746+ Because the live components system will detect that none of the values passed
1747+ *into * ``todo_footer `` (just ``count `` in this case). Have change. If no inputs
1748+ to the child changed, there's no need to re-render it.
1749+
1750+ But if the user added a *new * todo item and the number of todos changed from
1751+ 5 to 6, this *would * change the ``count `` value that's passed into the ``todo_footer ``.
1752+ In this case, immediately after the parent component re-renders, the child
1753+ request will make a second Ajax request to render itself. Smart!
1754+
1755+ Child components keep their modifiable LiveProp values
1756+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1757+
1758+ But suppose that the ``todo_footer `` in the previous example also has
1759+ an ``isVisible `` ``LiveProp(writable: true) `` property which starts as
1760+ ``true `` but can be changed (via a link click) to ``false ``. Will
1761+ re-rendering the child cause this to be reset back to its original
1762+ value? Nope! When the child component re-renders, it will keep the
1763+ current value for any of its writable props.
1764+
1765+ What if you *do * want your entire child component to re-render (including
1766+ resetting writable live props) when some value in the parent changes? This
1767+ can be done by manually giving your component a ``data-live-id `` attribute
1768+ that will change if the component should be totally re-rendered:
1769+
1770+ .. code-block :: twig
1771+
1772+ {# templates/components/todo_list.html.twig #}
1773+ <div {{ attributes }}>
1774+ <!-- ... -->
17231775
1724- This may not always be perfect, and if your child component has its own
1725- ``LiveProp `` that has changed since it was first rendered, that value
1726- will be lost when the parent component causes the child to re-render. If
1727- you have this situation, use ``data-model-map `` to map that child
1728- ``LiveProp `` to a ``LiveProp `` in the parent component, and pass it into
1729- the child when rendering.
1776+ {{ component('todo_footer', {
1777+ count: todos|length,
1778+ 'data-live-id': 'todo-footer-'~todos|length
1779+ }) }}
1780+ </div>
1781+
1782+ In this case, if the number of todos change, then the ``data-live-id ``
1783+ attribute of the component will also change. This signals that the
1784+ component should re-render itself completely, discarding any writable
1785+ LiveProp values.
17301786
1731- Actions, methods and model updates in a child do not affect the parent
1732- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1787+ Actions in a child do not affect the parent
1788+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17331789
17341790Again, each component is its own, isolated universe! For example,
17351791suppose your child component has:
@@ -1750,42 +1806,17 @@ Suppose a child component has a:
17501806
17511807.. code-block :: html
17521808
1753- <textarea data-model =" markdown_value " data-action =" live#update" >
1809+ <textarea data-model =" value " data-action =" live#update" >
17541810
17551811When the user changes this field, this will *only * update the
1756- ``markdown_value `` field in the *child * component… because (yup, we're
1812+ ``value `` field in the *child * component… because (yup, we're
17571813saying it again): each component is its own, isolated universe.
17581814
17591815However, sometimes this isn't what you want! Sometimes, in addition to
17601816updating the child component's model, you *also * want to update a model
17611817on the *parent * component.
17621818
1763- To help with this, whenever a model updates, a ``live:update-model ``
1764- event is dispatched. All components automatically listen to this event.
1765- This means that, when the ``markdown_value `` model is updated in the
1766- child component, *if * the parent component *also * has a model called
1767- ``markdown_value `` it will *also * be updated. This is done as a
1768- "deferred" update
1769- (i.e. :ref: `updateDefer() <deferring-a-re-render-until-later >`).
1770-
1771- If the model name in your child component (e.g. ``markdown_value ``) is
1772- *different * than the model name in your parent component
1773- (e.g. ``post.content ``), you have two options. First, you can make sure
1774- both are set by leveraging both the ``data-model `` and ``name ``
1775- attributes:
1776-
1777- .. code-block :: twig
1778-
1779- <textarea
1780- data-model="markdown_value"
1781- name="post[content]"
1782- >
1783-
1784- In this situation, the ``markdown_value `` model will be updated on the
1785- child component (because ``data-model `` takes precedence over ``name ``).
1786- But if any parent components have a ``markdown_value `` model *or * a
1787- ``post.content `` model (normalized from ``post[content ``]`), their model
1788- will also be updated.
1819+ TODO: still need to update this:
17891820
17901821A second option is to wrap your child element in a special
17911822``data-model-map `` element:
0 commit comments