Browse Source

Edit inline

Francois DROUHARD 901 2 months ago
parent
commit
089b64e82a

+ 9 - 0
assets/controllers.json

@@ -1,5 +1,14 @@
 {
     "controllers": {
+        "@symfony/ux-live-component": {
+            "live": {
+                "enabled": true,
+                "fetch": "eager",
+                "autoimport": {
+                    "@symfony/ux-live-component/dist/live.min.css": true
+                }
+            }
+        },
         "@symfony/ux-turbo": {
             "turbo-core": {
                 "enabled": true,

+ 1 - 0
composer.json

@@ -56,6 +56,7 @@
         "symfony/string": "6.4.*",
         "symfony/translation": "6.4.*",
         "symfony/twig-bundle": "6.4.*",
+        "symfony/ux-live-component": "^2.22",
         "symfony/ux-turbo": "^2.22",
         "symfony/validator": "6.4.*",
         "symfony/web-link": "6.4.*",

+ 160 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "93a34e1144d74e541d8c5372fbe3fbab",
+    "content-hash": "72d1aae4950c09e0f13d6f91ead99417",
     "packages": [
         {
             "name": "doctrine/cache",
@@ -5905,6 +5905,91 @@
             ],
             "time": "2024-09-25T14:18:03+00:00"
         },
+        {
+            "name": "symfony/ux-live-component",
+            "version": "v2.22.1",
+            "dist": {
+                "type": "zip",
+                "url": "http://nexus.intra.cnaf/repository/composer-proxy/symfony/ux-live-component/v2.22.1/symfony-ux-live-component-v2.22.1.zip",
+                "reference": "060e0c64e64125a4dfbf37dec281157faade1feb",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "symfony/deprecation-contracts": "^2.5|^3.0",
+                "symfony/property-access": "^5.4.5|^6.0|^7.0",
+                "symfony/stimulus-bundle": "^2.9",
+                "symfony/ux-twig-component": "^2.8",
+                "twig/twig": "^3.8.0"
+            },
+            "conflict": {
+                "symfony/config": "<5.4.0"
+            },
+            "require-dev": {
+                "doctrine/annotations": "^1.0",
+                "doctrine/collections": "^1.6.8|^2.0",
+                "doctrine/doctrine-bundle": "^2.4.3",
+                "doctrine/orm": "^2.9.4",
+                "doctrine/persistence": "^2.5.2|^3.0",
+                "phpdocumentor/reflection-docblock": "5.x-dev",
+                "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+                "symfony/expression-language": "^5.4|^6.0|^7.0",
+                "symfony/form": "^5.4|^6.0|^7.0",
+                "symfony/framework-bundle": "^5.4|^6.0|^7.0",
+                "symfony/options-resolver": "^5.4|^6.0|^7.0",
+                "symfony/phpunit-bridge": "^6.1|^7.0",
+                "symfony/property-info": "^5.4|^6.0|^7.0",
+                "symfony/security-bundle": "^5.4|^6.0|^7.0",
+                "symfony/serializer": "^5.4|^6.0|^7.0",
+                "symfony/twig-bundle": "^5.4|^6.0|^7.0",
+                "symfony/validator": "^5.4|^6.0|^7.0",
+                "zenstruck/browser": "^1.2.0",
+                "zenstruck/foundry": "^2.0"
+            },
+            "type": "symfony-bundle",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/ux",
+                    "name": "symfony/ux"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\UX\\LiveComponent\\": "src/"
+                }
+            },
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Live components for Symfony",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "components",
+                "symfony-ux",
+                "twig"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-12-07T10:13:15+00:00"
+        },
         {
             "name": "symfony/ux-turbo",
             "version": "v2.22.1",
@@ -5994,6 +6079,80 @@
             ],
             "time": "2024-12-05T14:25:02+00:00"
         },
+        {
+            "name": "symfony/ux-twig-component",
+            "version": "v2.22.1",
+            "dist": {
+                "type": "zip",
+                "url": "http://nexus.intra.cnaf/repository/composer-proxy/symfony/ux-twig-component/v2.22.1/symfony-ux-twig-component-v2.22.1.zip",
+                "reference": "9b347f6ca2d9e18cee630787f0a6aa453982bf18",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+                "symfony/deprecation-contracts": "^2.2|^3.0",
+                "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
+                "symfony/property-access": "^5.4|^6.0|^7.0",
+                "twig/twig": "^3.8"
+            },
+            "conflict": {
+                "symfony/config": "<5.4.0"
+            },
+            "require-dev": {
+                "symfony/console": "^5.4|^6.0|^7.0",
+                "symfony/css-selector": "^5.4|^6.0|^7.0",
+                "symfony/dom-crawler": "^5.4|^6.0|^7.0",
+                "symfony/framework-bundle": "^5.4|^6.0|^7.0",
+                "symfony/phpunit-bridge": "^6.0|^7.0",
+                "symfony/stimulus-bundle": "^2.9.1",
+                "symfony/twig-bundle": "^5.4|^6.0|^7.0",
+                "symfony/webpack-encore-bundle": "^1.15"
+            },
+            "type": "symfony-bundle",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/ux",
+                    "name": "symfony/ux"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\UX\\TwigComponent\\": "src/"
+                }
+            },
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Twig components for Symfony",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "components",
+                "symfony-ux",
+                "twig"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-12-07T18:05:50+00:00"
+        },
         {
             "name": "symfony/validator",
             "version": "v6.4.17",

+ 2 - 0
config/bundles.php

@@ -14,4 +14,6 @@ return [
     Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
     Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
     Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
+    Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true],
+    Symfony\UX\LiveComponent\LiveComponentBundle::class => ['all' => true],
 ];

+ 5 - 0
config/packages/twig_component.yaml

@@ -0,0 +1,5 @@
+twig_component:
+    anonymous_template_directory: 'components/'
+    defaults:
+        # Namespace & directory for components
+        App\Twig\Components\: 'components/'

+ 5 - 0
config/routes/ux_live_component.yaml

@@ -0,0 +1,5 @@
+live_component:
+    resource: '@LiveComponentBundle/config/routes.php'
+    prefix: '/_components'
+    # adjust prefix to add localization to your components
+    #prefix: '/{_locale}/_components'

+ 1 - 0
package.json

@@ -7,6 +7,7 @@
         "@hotwired/turbo": "^7.1.1 || ^8.0",
         "@picocss/pico": "^2.0.6",
         "@symfony/stimulus-bridge": "^3.2.0",
+        "@symfony/ux-live-component": "file:vendor/symfony/ux-live-component/assets",
         "@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets",
         "@symfony/webpack-encore": "^5.0.0",
         "core-js": "^3.38.0",

+ 56 - 0
src/Twig/Components/InlineEditFilm.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace App\Twig\Components;
+
+use App\Entity\Film;
+use Doctrine\ORM\EntityManagerInterface;
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\Validator\Constraints as Assert;
+use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
+use Symfony\UX\LiveComponent\Attribute\LiveAction;
+use Symfony\UX\LiveComponent\Attribute\LiveProp;
+use Symfony\UX\LiveComponent\DefaultActionTrait;
+use Symfony\UX\LiveComponent\ValidatableComponentTrait;
+
+#[AsLiveComponent]
+class InlineEditFilm extends AbstractController
+{
+    use DefaultActionTrait;
+    use ValidatableComponentTrait;
+
+    /** This allows us to have a data-model="food.name" */
+    #[LiveProp(writable: ['name'])]
+    /** When we validate, we want to also validate the Food object */
+    #[Assert\Valid]
+    public Film $film;
+
+    /** Tracks whether the component is in "edit" mode or not */
+    #[LiveProp]
+    public bool $isEditing = false;
+
+    /**
+     * A temporary message to show to the user.
+     *
+     * This is purposely not a LiveProp: this is a "temporary" value that
+     * will only show one time.
+     */
+    public ?string $flashMessage = null;
+
+    #[LiveAction]
+    public function activateEditing()
+    {
+        $this->isEditing = true;
+    }
+
+    #[LiveAction]
+    public function save(EntityManagerInterface $entityManager)
+    {
+        // if validation fails, this throws an exception & the component re-renders
+        $this->validate();
+
+        $this->isEditing = false;
+
+        // in a real app, we would save!
+        $entityManager->flush();
+    }
+}

+ 24 - 0
symfony.lock

@@ -233,6 +233,18 @@
             "templates/base.html.twig"
         ]
     },
+    "symfony/ux-live-component": {
+        "version": "2.22",
+        "recipe": {
+            "repo": "gitlab.si.cnaf.info/cnaf/flex/recipes",
+            "branch": "main",
+            "version": "2.6",
+            "ref": "73e69baf18f47740d6f58688c5464b10cdacae06"
+        },
+        "files": [
+            "config/routes/ux_live_component.yaml"
+        ]
+    },
     "symfony/ux-turbo": {
         "version": "2.22",
         "recipe": {
@@ -242,6 +254,18 @@
             "ref": "9dd2778a116b6e5e01e5e1582d03d5a9e82630de"
         }
     },
+    "symfony/ux-twig-component": {
+        "version": "2.22",
+        "recipe": {
+            "repo": "gitlab.si.cnaf.info/cnaf/flex/recipes",
+            "branch": "main",
+            "version": "2.13",
+            "ref": "67814b5f9794798b885cec9d3f48631424449a01"
+        },
+        "files": [
+            "config/packages/twig_component.yaml"
+        ]
+    },
     "symfony/validator": {
         "version": "6.4",
         "recipe": {

+ 39 - 0
templates/components/InlineEditFilm.html.twig

@@ -0,0 +1,39 @@
+<div {{ attributes }}>
+    <div>
+        {% if isEditing %}
+            {# The form isn't used, but allows the user to hit enter to save. #}
+            <form>
+                <fieldset role="group">
+                    {% set error = this.getError('film.name') %}
+
+                    <div>
+                        <input
+                            type="text"
+                            data-model="film.name"
+                            autofocus
+                            id="film_name"
+                        />
+                    </div>
+
+                    <button
+                        data-action="live#action:prevent"
+                        data-live-action-param="save"
+                    ><i class="fa fa-save"></i></button>
+
+                    {% if error %}
+                        <div class="invalid-feedback">{{ error.message }}</div>
+                    {% endif %}
+                </fieldset>
+                
+            </form>
+        {% else %}
+            {{ film.name }}
+            <a
+                data-action="live#action"
+                data-live-action-param="activateEditing"
+            >
+                <i class="fa fa-edit"></i>
+            </a>
+        {% endif %}
+    </div>
+</div>

+ 1 - 1
templates/index/index.html.twig

@@ -21,7 +21,7 @@
             <td>
             <a href="{{ path('app_index', {'id': film.id} ) }}"><i class="fa fa-edit"></i></a>
             </td>
-            <td>{{ film.name }}</td>
+            <td>{{ component('InlineEditFilm', { film: film}) }}</td>
             <td>{{ film.realisateur }}</td>
             <td>{{ film.genre }}</td>
         </tr>

+ 10 - 0
yarn.lock

@@ -987,6 +987,11 @@
     loader-utils "^2.0.0"
     schema-utils "^3.0.0"
 
+"@symfony/ux-live-component@file:vendor/symfony/ux-live-component/assets":
+  version "1.0.0"
+  dependencies:
+    idiomorph "^0.3.0"
+
 "@symfony/ux-turbo@file:vendor/symfony/ux-turbo/assets":
   version "0.1.0"
 
@@ -1936,6 +1941,11 @@ icss-utils@^5.0.0, icss-utils@^5.1.0:
   resolved "http://nexus.intra.cnaf/repository/npm-public/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
   integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
 
+idiomorph@^0.3.0:
+  version "0.3.0"
+  resolved "http://nexus.intra.cnaf/repository/npm-public/idiomorph/-/idiomorph-0.3.0.tgz#f6675bc5bef1a72c94021e43141a3f605d2d6288"
+  integrity sha512-UhV1Ey5xCxIwR9B+OgIjQa+1Jx99XQ1vQHUsKBU1RpQzCx1u+b+N6SOXgf5mEJDqemUI/ffccu6+71l2mJUsRA==
+
 immutable@^5.0.2:
   version "5.0.3"
   resolved "http://nexus.intra.cnaf/repository/npm-public/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1"