소스 검색

Merge pull request #2484 from lutangar/list-editable-choice

List editable choice
Grégoire Paris 9 년 전
부모
커밋
a107e8ff3c
4개의 변경된 파일274개의 추가작업 그리고 41개의 파일을 삭제
  1. 25 0
      Resources/config/twig.xml
  2. 17 1
      Resources/views/CRUD/list_choice.html.twig
  3. 179 23
      Tests/Twig/Extension/SonataAdminExtensionTest.php
  4. 53 17
      Twig/Extension/SonataAdminExtension.php

+ 25 - 0
Resources/config/twig.xml

@@ -4,12 +4,37 @@
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
 
+    <parameters>
+        <parameter key="sonata.admin.twig.extension.x_editable_type_mapping" type="collection">
+            <parameter key="choice">select</parameter>
+            <parameter key="boolean">select</parameter>
+            <parameter key="text">text</parameter>
+            <parameter key="textarea">textarea</parameter>
+            <parameter key="html">textarea</parameter>
+            <parameter key="email">email</parameter>
+            <parameter key="string">text</parameter>
+            <parameter key="smallint">text</parameter>
+            <parameter key="bigint">text</parameter>
+            <parameter key="integer">number</parameter>
+            <parameter key="decimal">number</parameter>
+            <parameter key="currency">number</parameter>
+            <parameter key="percent">number</parameter>
+            <parameter key="url">url</parameter>
+            <parameter key="date">date</parameter>
+        </parameter>
+    </parameters>
+
     <services>
         <service id="sonata.admin.twig.extension" class="Sonata\AdminBundle\Twig\Extension\SonataAdminExtension">
             <tag name="twig.extension"/>
 
             <argument type="service" id="sonata.admin.pool" />
             <argument type="service" id="logger" on-invalid="ignore" />
+
+            <call method="setXEditableTypeMapping">
+                <argument>%sonata.admin.twig.extension.x_editable_type_mapping%</argument>
+            </call>
+
         </service>
     </services>
 </container>

+ 17 - 1
Resources/views/CRUD/list_choice.html.twig

@@ -11,9 +11,25 @@ file that was distributed with this source code.
 
 {% extends admin.getTemplate('base_list_field') %}
 
+{% set is_editable =
+    field_description.options.editable is defined and
+    field_description.options.editable and
+    admin.isGranted('EDIT', object)
+%}
+{% set x_editable_type = field_description.type|sonata_xeditable_type %}
+
+{% if is_editable and x_editable_type %}
+    {% block field_span_attributes %}
+        {% spaceless %}
+            {{ parent() }}
+            data-source="{{ field_description|sonata_xeditable_choices|json_encode }}"
+        {% endspaceless %}
+    {% endblock %}
+{% endif %}
+
 {% block field %}
 {% spaceless %}
-    {% if field_description.options.choices  is defined %}
+    {% if field_description.options.choices is defined %}
         {% if field_description.options.multiple is defined and field_description.options.multiple==true and value is iterable %}
 
             {% set result = '' %}

+ 179 - 23
Tests/Twig/Extension/SonataAdminExtensionTest.php

@@ -76,6 +76,11 @@ class SonataAdminExtensionTest extends \PHPUnit_Framework_TestCase
      */
     private $logger;
 
+    /**
+     * @var string[]
+     */
+    private $xEditableTypeMapping;
+
     public function setUp()
     {
         date_default_timezone_set('Europe/London');
@@ -87,8 +92,25 @@ class SonataAdminExtensionTest extends \PHPUnit_Framework_TestCase
         $this->pool->setAdminClasses(array('fooClass' => array('sonata_admin_foo_service')));
 
         $this->logger = $this->getMock('Psr\Log\LoggerInterface');
+        $this->xEditableTypeMapping = array(
+            'choice'     => 'select',
+            'boolean'    => 'select',
+            'text'       => 'text',
+            'textarea'   => 'textarea',
+            'html'       => 'textarea',
+            'email'      => 'email',
+            'string'     => 'text',
+            'smallint'   => 'text',
+            'bigint'     => 'text',
+            'integer'    => 'number',
+            'decimal'    => 'number',
+            'currency'   => 'number',
+            'percent'    => 'number',
+            'url'        => 'url',
+        );
 
         $this->twigExtension = new SonataAdminExtension($this->pool, $this->logger);
+        $this->twigExtension->setXEditableTypeMapping($this->xEditableTypeMapping);
 
         $loader = new StubFilesystemLoader(array(
             __DIR__.'/../../../Resources/views/CRUD',
@@ -148,8 +170,8 @@ class SonataAdminExtensionTest extends \PHPUnit_Framework_TestCase
 
         $this->admin->expects($this->any())
             ->method('trans')
-            ->will($this->returnCallback(function ($id) {
-                return $id;
+            ->will($this->returnCallback(function ($id, $parameters = array(), $domain = null) use ($translator) {
+                return $translator->trans($id, $parameters, $domain);
             }));
 
         $this->adminBar = $this->getMock('Sonata\AdminBundle\Admin\AdminInterface');
@@ -219,6 +241,12 @@ class SonataAdminExtensionTest extends \PHPUnit_Framework_TestCase
             ->method('getOptions')
             ->will($this->returnValue($options));
 
+        $this->fieldDescription->expects($this->any())
+            ->method('getOption')
+            ->will($this->returnCallback(function ($name, $default = null) use ($options) {
+                return isset($options[$name]) ? $options[$name] : $default;
+            }));
+
         $this->fieldDescription->expects($this->any())
             ->method('getTemplate')
             ->will($this->returnCallback(function () use ($type) {
@@ -639,9 +667,9 @@ EOT
                 'choice',
                 'Foo',
                 array('catalogue' => 'SonataAdminBundle', 'choices' => array(
-                    'Foo'     => 'action_delete',
-                    'Status2' => 'Alias2',
-                    'Status3' => 'Alias3',
+                    'Foo'         => 'action_delete',
+                    'Status2'     => 'Alias2',
+                    'Status3'     => 'Alias3',
                 )),
             ),
             array(
@@ -701,9 +729,9 @@ EOT
                 'choice',
                 array('Foo', 'Status3'),
                 array('catalogue' => 'SonataAdminBundle', 'choices' => array(
-                    'Foo'     => 'action_delete',
-                    'Status2' => 'Alias2',
-                    'Status3' => 'Alias3',
+                    'Foo'         => 'action_delete',
+                    'Status2'     => 'Alias2',
+                    'Status3'     => 'Alias3',
                 ), 'multiple' => true),
             ),
             array(
@@ -717,6 +745,134 @@ EOT
                     'Status2' => '<b>Alias2</b>',
                     'Status3' => '<b>Alias3</b>',
                 ), 'multiple' => true), ),
+            array(
+                <<<EOT
+<td class="sonata-ba-list-field sonata-ba-list-field-choice" objectId="12345">
+    <span
+        class="x-editable"
+        data-type="select"
+        data-value="Status1"
+        data-title="Data"
+        data-pk="12345"
+        data-url="/core/set-object-field-value?context=list&amp;field=fd_name&amp;objectId=12345&amp;code=xyz"
+        data-source="[]"
+    >
+        Status1
+    </span>
+</td>
+EOT
+                ,
+                'choice',
+                'Status1',
+                array('editable' => true),
+            ),
+            array(
+                <<<EOT
+<td class="sonata-ba-list-field sonata-ba-list-field-choice" objectId="12345">
+    <span
+        class="x-editable"
+        data-type="select"
+        data-value="Status1"
+        data-title="Data"
+        data-pk="12345"
+        data-url="/core/set-object-field-value?context=list&amp;field=fd_name&amp;objectId=12345&amp;code=xyz"
+        data-source="[{&quot;value&quot;:&quot;Status1&quot;,&quot;text&quot;:&quot;Alias1&quot;},{&quot;value&quot;:&quot;Status2&quot;,&quot;text&quot;:&quot;Alias2&quot;},{&quot;value&quot;:&quot;Status3&quot;,&quot;text&quot;:&quot;Alias3&quot;}]" >
+        Alias1 </span>
+</td>
+EOT
+                ,
+                'choice',
+                'Status1',
+                array(
+                    'editable' => true,
+                    'choices'  => array(
+                        'Status1' => 'Alias1',
+                        'Status2' => 'Alias2',
+                        'Status3' => 'Alias3',
+                    ),
+                ),
+            ),
+            array(
+                <<<EOT
+<td class="sonata-ba-list-field sonata-ba-list-field-choice" objectId="12345">
+    <span
+        class="x-editable"
+        data-type="select"
+        data-value=""
+        data-title="Data"
+        data-pk="12345"
+        data-url="/core/set-object-field-value?context=list&amp;field=fd_name&amp;objectId=12345&amp;code=xyz"
+        data-source="[{&quot;value&quot;:&quot;Status1&quot;,&quot;text&quot;:&quot;Alias1&quot;},{&quot;value&quot;:&quot;Status2&quot;,&quot;text&quot;:&quot;Alias2&quot;},{&quot;value&quot;:&quot;Status3&quot;,&quot;text&quot;:&quot;Alias3&quot;}]" >
+
+    </span>
+</td>
+EOT
+                ,
+                'choice',
+                null,
+                array(
+                    'editable' => true,
+                    'choices'  => array(
+                        'Status1' => 'Alias1',
+                        'Status2' => 'Alias2',
+                        'Status3' => 'Alias3',
+                    ),
+                ),
+            ),
+            array(
+                <<<EOT
+<td class="sonata-ba-list-field sonata-ba-list-field-choice" objectId="12345">
+    <span
+        class="x-editable"
+        data-type="select"
+        data-value="NoValidKeyInChoices"
+        data-title="Data" data-pk="12345"
+        data-url="/core/set-object-field-value?context=list&amp;field=fd_name&amp;objectId=12345&amp;code=xyz"
+        data-source="[{&quot;value&quot;:&quot;Status1&quot;,&quot;text&quot;:&quot;Alias1&quot;},{&quot;value&quot;:&quot;Status2&quot;,&quot;text&quot;:&quot;Alias2&quot;},{&quot;value&quot;:&quot;Status3&quot;,&quot;text&quot;:&quot;Alias3&quot;}]" >
+        NoValidKeyInChoices
+    </span>
+</td>
+EOT
+                ,
+                'choice',
+                'NoValidKeyInChoices',
+                array(
+                    'editable' => true,
+                    'choices'  => array(
+                        'Status1' => 'Alias1',
+                        'Status2' => 'Alias2',
+                        'Status3' => 'Alias3',
+                    ),
+                ),
+            ),
+            array(
+                <<<EOT
+<td class="sonata-ba-list-field sonata-ba-list-field-choice" objectId="12345">
+    <span
+        class="x-editable"
+        data-type="select"
+        data-value="Foo"
+        data-title="Data"
+        data-pk="12345"
+        data-url="/core/set-object-field-value?context=list&amp;field=fd_name&amp;objectId=12345&amp;code=xyz"
+        data-source="[{&quot;value&quot;:&quot;Foo&quot;,&quot;text&quot;:&quot;Delete&quot;},{&quot;value&quot;:&quot;Status2&quot;,&quot;text&quot;:&quot;Alias2&quot;},{&quot;value&quot;:&quot;Status3&quot;,&quot;text&quot;:&quot;Alias3&quot;}]" >
+         Delete
+    </span>
+</td>
+EOT
+                ,
+                'choice',
+                'Foo',
+                array(
+                    'editable'  => true,
+                    'catalogue' => 'SonataAdminBundle',
+                    'choices'   => array(
+                        'Foo'     => 'action_delete',
+                        'Status2' => 'Alias2',
+                        'Status3' => 'Alias3',
+                    ),
+                ),
+            ),
             array(
                 '<td class="sonata-ba-list-field sonata-ba-list-field-url" objectId="12345"> &nbsp; </td>',
                 'url',
@@ -1139,7 +1295,7 @@ EOT
                 '<th>Data</th> <td>Alias1</td>',
                 'choice',
                 'Status1',
-                array('safe' => false, 'choices' => array(
+                array('safe'  => false, 'choices' => array(
                     'Status1' => 'Alias1',
                     'Status2' => 'Alias2',
                     'Status3' => 'Alias3',
@@ -1149,7 +1305,7 @@ EOT
                 '<th>Data</th> <td>NoValidKeyInChoices</td>',
                 'choice',
                 'NoValidKeyInChoices',
-                array('safe' => false, 'choices' => array(
+                array('safe'  => false, 'choices' => array(
                     'Status1' => 'Alias1',
                     'Status2' => 'Alias2',
                     'Status3' => 'Alias3',
@@ -1159,7 +1315,7 @@ EOT
                 '<th>Data</th> <td>Delete</td>',
                 'choice',
                 'Foo',
-                array('safe' => false, 'catalogue' => 'SonataAdminBundle', 'choices' => array(
+                array('safe'  => false, 'catalogue' => 'SonataAdminBundle', 'choices' => array(
                     'Foo'     => 'action_delete',
                     'Status2' => 'Alias2',
                     'Status3' => 'Alias3',
@@ -1169,7 +1325,7 @@ EOT
                 '<th>Data</th> <td>NoValidKeyInChoices</td>',
                 'choice',
                 array('NoValidKeyInChoices'),
-                array('safe' => false, 'choices' => array(
+                array('safe'  => false, 'choices' => array(
                     'Status1' => 'Alias1',
                     'Status2' => 'Alias2',
                     'Status3' => 'Alias3',
@@ -1179,7 +1335,7 @@ EOT
                 '<th>Data</th> <td>NoValidKeyInChoices, Alias2</td>',
                 'choice',
                 array('NoValidKeyInChoices', 'Status2'),
-                array('safe' => false, 'choices' => array(
+                array('safe'  => false, 'choices' => array(
                     'Status1' => 'Alias1',
                     'Status2' => 'Alias2',
                     'Status3' => 'Alias3',
@@ -1189,7 +1345,7 @@ EOT
                 '<th>Data</th> <td>Alias1, Alias3</td>',
                 'choice',
                 array('Status1', 'Status3'),
-                array('safe' => false, 'choices' => array(
+                array('safe'  => false, 'choices' => array(
                     'Status1' => 'Alias1',
                     'Status2' => 'Alias2',
                     'Status3' => 'Alias3',
@@ -1208,7 +1364,7 @@ EOT
                 '<th>Data</th> <td>Delete, Alias3</td>',
                 'choice',
                 array('Foo', 'Status3'),
-                array('safe' => false, 'catalogue' => 'SonataAdminBundle', 'choices' => array(
+                array('safe'  => false, 'catalogue' => 'SonataAdminBundle', 'choices' => array(
                     'Foo'     => 'action_delete',
                     'Status2' => 'Alias2',
                     'Status3' => 'Alias3',
@@ -1218,7 +1374,7 @@ EOT
                 '<th>Data</th> <td><b>Alias1</b>, <b>Alias3</b></td>',
                 'choice',
                 array('Status1', 'Status3'),
-                array('safe' => true, 'choices' => array(
+                array('safe'  => true, 'choices' => array(
                     'Status1' => '<b>Alias1</b>',
                     'Status2' => '<b>Alias2</b>',
                     'Status3' => '<b>Alias3</b>',
@@ -1228,7 +1384,7 @@ EOT
                 '<th>Data</th> <td>&lt;b&gt;Alias1&lt;/b&gt;, &lt;b&gt;Alias3&lt;/b&gt;</td>',
                 'choice',
                 array('Status1', 'Status3'),
-                array('safe' => false, 'choices' => array(
+                array('safe'  => false, 'choices' => array(
                     'Status1' => '<b>Alias1</b>',
                     'Status2' => '<b>Alias2</b>',
                     'Status3' => '<b>Alias3</b>',
@@ -1299,7 +1455,7 @@ EOT
                 '<th>Data</th> <td><a href="http://localhost/foo">Foo</a></td>',
                 'url',
                 'Foo',
-                array('safe' => false, 'route' => array(
+                array('safe'   => false, 'route' => array(
                     'name'     => 'sonata_admin_foo',
                     'absolute' => true,
                 )),
@@ -1318,7 +1474,7 @@ EOT
                 '<th>Data</th> <td><a href="http://localhost/foo">foo/bar?a=b&amp;c=123456789</a></td>',
                 'url',
                 'http://foo/bar?a=b&c=123456789',
-                array('safe' => false, 'route' => array(
+                array('safe'   => false, 'route' => array(
                     'name'     => 'sonata_admin_foo',
                     'absolute' => true,
                 ), 'hide_protocol' => true),
@@ -1327,7 +1483,7 @@ EOT
                 '<th>Data</th> <td><a href="/foo/abcd/efgh?param3=ijkl">Foo</a></td>',
                 'url',
                 'Foo',
-                array('safe' => false, 'route' => array(
+                array('safe'     => false, 'route' => array(
                     'name'       => 'sonata_admin_foo_param',
                     'parameters' => array('param1' => 'abcd', 'param2' => 'efgh', 'param3' => 'ijkl'),
                 )),
@@ -1336,7 +1492,7 @@ EOT
                 '<th>Data</th> <td><a href="http://localhost/foo/abcd/efgh?param3=ijkl">Foo</a></td>',
                 'url',
                 'Foo',
-                array('safe' => false, 'route' => array(
+                array('safe'     => false, 'route' => array(
                     'name'       => 'sonata_admin_foo_param',
                     'absolute'   => true,
                     'parameters' => array(
@@ -1350,7 +1506,7 @@ EOT
                 '<th>Data</th> <td><a href="/foo/obj/abcd/12345/efgh?param3=ijkl">Foo</a></td>',
                 'url',
                 'Foo',
-                array('safe' => false, 'route' => array(
+                array('safe'                    => false, 'route' => array(
                     'name'                      => 'sonata_admin_foo_object',
                     'parameters'                => array(
                         'param1' => 'abcd',
@@ -1364,7 +1520,7 @@ EOT
                 '<th>Data</th> <td><a href="http://localhost/foo/obj/abcd/12345/efgh?param3=ijkl">Foo</a></td>',
                 'url',
                 'Foo',
-                array('safe' => false, 'route' => array(
+                array('safe'                    => false, 'route' => array(
                     'name'                      => 'sonata_admin_foo_object',
                     'absolute'                  => true,
                     'parameters'                => array(

+ 53 - 17
Twig/Extension/SonataAdminExtension.php

@@ -35,6 +35,11 @@ class SonataAdminExtension extends \Twig_Extension
      */
     protected $logger;
 
+    /**
+     * @var string[]
+     */
+    private $xEditableTypeMapping = array();
+
     /**
      * @param Pool            $pool
      * @param LoggerInterface $logger
@@ -87,6 +92,10 @@ class SonataAdminExtension extends \Twig_Extension
                 'sonata_xeditable_type',
                 array($this, 'getXEditableType')
             ),
+            new \Twig_SimpleFilter(
+                'sonata_xeditable_choices',
+                array($this, 'getXEditableChoices')
+            ),
         );
     }
 
@@ -390,6 +399,14 @@ EOT;
         return $admin->getUrlsafeIdentifier($model);
     }
 
+    /**
+     * @param string[] $xEditableTypeMapping
+     */
+    public function setXEditableTypeMapping($xEditableTypeMapping)
+    {
+        $this->xEditableTypeMapping = $xEditableTypeMapping;
+    }
+
     /**
      * @param $type
      *
@@ -397,23 +414,42 @@ EOT;
      */
     public function getXEditableType($type)
     {
-        $mapping = array(
-            'boolean'    => 'select',
-            'text'       => 'text',
-            'textarea'   => 'textarea',
-            'html'       => 'textarea',
-            'email'      => 'email',
-            'string'     => 'text',
-            'smallint'   => 'text',
-            'bigint'     => 'text',
-            'integer'    => 'number',
-            'decimal'    => 'number',
-            'currency'   => 'number',
-            'percent'    => 'number',
-            'url'        => 'url',
-            'date'       => 'date',
-        );
+        return isset($this->xEditableTypeMapping[$type]) ? $this->xEditableTypeMapping[$type] : false;
+    }
+
+    /**
+     * Return xEditable choices based on the field description choices options & catalogue options.
+     * With the following choice options:
+     *     ['Status1' => 'Alias1', 'Status2' => 'Alias2']
+     * The method will return:
+     *     [['value' => 'Status1', 'text' => 'Alias1'], ['value' => 'Status2', 'text' => 'Alias2']].
+     *
+     * @param FieldDescriptionInterface $fieldDescription
+     *
+     * @return array
+     */
+    public function getXEditableChoices(FieldDescriptionInterface $fieldDescription)
+    {
+        $choices   = $fieldDescription->getOption('choices', array());
+        $catalogue = $fieldDescription->getOption('catalogue');
+        $xEditableChoices = array();
+        if (!empty($choices)) {
+            reset($choices);
+            $first = current($choices);
+            // the choices are already in the right format
+            if (is_array($first) && array_key_exists('value', $first) && array_key_exists('text', $first)) {
+                $xEditableChoices = $choices;
+            } else {
+                foreach ($choices as $value => $text) {
+                    $text = $catalogue ? $fieldDescription->getAdmin()->trans($text, array(), $catalogue) : $text;
+                    $xEditableChoices[] = array(
+                        'value' => $value,
+                        'text'  => $text,
+                    );
+                }
+            }
+        }
 
-        return isset($mapping[$type]) ? $mapping[$type] : false;
+        return $xEditableChoices;
     }
 }