Browse Source

[Form] Add Arbitrary Attribute Support to Form Rendering.

Benjamin Eberlei 14 years ago
parent
commit
8d6dd2b9af
22 changed files with 164 additions and 22 deletions
  1. 9 0
      src/Symfony/Bundle/FrameworkBundle/Form/PhpEngineTheme.php
  2. 1 0
      src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/checkbox_widget.html.php
  3. 2 0
      src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget.html.php
  4. 1 0
      src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/date_widget.html.php
  5. 1 0
      src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/hidden_widget.html.php
  6. 1 0
      src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php
  7. 1 0
      src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php
  8. 1 0
      src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/radio_widget.html.php
  9. 1 0
      src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/text_widget.html.php
  10. 1 0
      src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/textarea_widget.html.php
  11. 1 0
      src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_widget.html.php
  12. 1 0
      src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget.html.php
  13. 3 4
      src/Symfony/Bundle/FrameworkBundle/Tests/Form/PhpEngineThemeTest.php
  14. 18 0
      src/Symfony/Bundle/TwigBundle/Resources/views/div_plain_layout.html.twig
  15. 1 0
      src/Symfony/Bundle/TwigBundle/Resources/views/widgets.html.twig
  16. 33 0
      src/Symfony/Bundle/TwigBundle/Tests/Form/TwigThemeTest.php
  17. 45 1
      src/Symfony/Component/Form/Renderer/FormRendererInterface.php
  18. 1 0
      src/Symfony/Component/Form/Renderer/Plugin/FieldPlugin.php
  19. 10 1
      src/Symfony/Component/Form/Renderer/Theme/PhpTheme.php
  20. 15 6
      src/Symfony/Component/Form/Renderer/ThemeRenderer.php
  21. 14 7
      tests/Symfony/Tests/Component/Form/Renderer/Theme/AbstractThemeTest.php
  22. 3 3
      tests/Symfony/Tests/Component/Form/Renderer/Theme/PhpThemeTest.php

+ 9 - 0
src/Symfony/Bundle/FrameworkBundle/Form/PhpEngineTheme.php

@@ -74,4 +74,13 @@ class PhpEngineTheme implements FormThemeInterface
 
         return $template;
     }
+
+    public function attributes(array $attribs)
+    {
+        $html = '';
+        foreach ($attribs as $k => $v) {
+            $html .= $this->engine->escape($k) . '="' . $this->engine->escape($v) .'" ';
+        }
+        return $html;
+    }
 }

+ 1 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/checkbox_widget.html.php

@@ -5,4 +5,5 @@
     <?php if ($disabled): ?>disabled="disabled"<?php endif ?>
     <?php if ($required): ?>required="required"<?php endif ?>
     <?php if ($checked): ?>checked="checked"<?php endif ?>
+    <?php if (isset($attr)): echo $renderer->getTheme()->attributes($attr); endif; ?>
 />

+ 2 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget.html.php

@@ -10,7 +10,9 @@
         <?php if ($disabled): ?> disabled="disabled"<?php endif ?>
         <?php if ($multiple): ?> multiple="multiple"<?php endif ?>
         <?php if ($class): ?> class="<?php echo $class ?>"<?php endif ?>
+        <?php if (isset($attr)): echo $renderer->getTheme()->attributes($attr); endif; ?>
     >
+        <?php if (!$required): ?><option value=""><?php echo $empty_value; ?></option><?php endif; ?>
         <?php if (count($preferred_choices) > 0): ?>
             <?php foreach ($preferred_choices as $choice => $label): ?>
                 <?php if ($choice_list->isChoiceGroup($label)): ?>

+ 1 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/date_widget.html.php

@@ -6,6 +6,7 @@
         <?php if ($disabled): ?>disabled="disabled"<?php endif ?>
         <?php if ($required): ?>required="required"<?php endif ?>
         <?php if ($class): ?>class="<?php echo $class ?>"<?php endif ?>
+        <?php if (isset($attr)): echo $renderer->getTheme()->attributes($attr); endif; ?>
     />
 <?php else: ?>
     <?php echo str_replace(array('{{ year }}', '{{ month }}', '{{ day }}'), array(

+ 1 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/hidden_widget.html.php

@@ -3,4 +3,5 @@
     name="<?php echo $name ?>"
     value="<?php echo $value ?>"
     <?php if ($disabled): ?>disabled="disabled"<?php endif ?>
+    <?php if (isset($attr)): echo $renderer->getTheme()->attributes($attr); endif; ?>
 />

+ 1 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/number_widget.html.php

@@ -7,4 +7,5 @@
     <?php if ($class): ?>class="<?php echo $class ?>"<?php endif ?>
     <?php if ($max_length && $max_length > 0): ?>maxlength="<?php echo $max_length; ?>"<?php endif; ?>
     <?php if ($size && $size > 0): ?>size="<?php echo $size; ?>"<?php endif; ?>
+    <?php if (isset($attr)): echo $renderer->getTheme()->attributes($attr); endif; ?>
 />

+ 1 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/password_widget.html.php

@@ -7,4 +7,5 @@
     <?php if ($class): ?>class="<?php echo $class ?>"<?php endif ?>
     <?php if ($max_length && $max_length > 0): ?>maxlength="<?php echo $max_length; ?>"<?php endif; ?>
     <?php if ($size && $size > 0): ?>size="<?php echo $size; ?>"<?php endif; ?>
+    <?php if (isset($attr)): echo $renderer->getTheme()->attributes($attr); endif; ?>
 />

+ 1 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/radio_widget.html.php

@@ -6,4 +6,5 @@
     <?php if ($required): ?>required="required"<?php endif ?>
     <?php if ($checked): ?>checked="checked"<?php endif ?>
     <?php if ($class): ?>class="<?php echo $class ?>"<?php endif ?>
+    <?php if (isset($attr)): echo $renderer->getTheme()->attributes($attr); endif; ?>
 />

+ 1 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/text_widget.html.php

@@ -7,4 +7,5 @@
     <?php if ($required): ?>required="required"<?php endif ?>
     <?php if ($max_length && $max_length > 0): ?>maxlength="<?php echo $max_length; ?>"<?php endif; ?>
     <?php if ($size && $size > 0): ?>size="<?php echo $size; ?>"<?php endif; ?>
+    <?php if (isset($attr)): echo $renderer->getTheme()->attributes($attr); endif; ?>
 />

+ 1 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/textarea_widget.html.php

@@ -4,6 +4,7 @@
     <?php if ($disabled): ?>disabled="disabled"<?php endif ?>
     <?php if ($required): ?>required="required"<?php endif ?>
     <?php if ($class): ?>class="<?php echo $class ?>"<?php endif ?>
+    <?php if (isset($attr)): echo $renderer->getTheme()->attributes($attr); endif; ?>
 ><?php
     echo $view->escape($value)
 ?></textarea>

+ 1 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_widget.html.php

@@ -5,4 +5,5 @@
     <?php if ($disabled): ?>disabled="disabled"<?php endif ?>
     <?php if ($required): ?>required="required"<?php endif ?>
     <?php if ($class): ?>class="<?php echo $class ?>"<?php endif ?>
+    <?php if (isset($attr)): echo $renderer->getTheme()->attributes($attr); endif; ?>
 />

+ 1 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/widget.html.php

@@ -6,4 +6,5 @@
     <?php if ($required): ?>required="required"<?php endif ?>
     <?php if ($max_length && $max_length > 0): ?>maxlength="<?php echo $max_length ?>"<?php endif; ?>
     <?php if ($size && $size > 0): ?>size="<?php echo $size ?>"<?php endif; ?>
+    <?php if (isset($attr)): echo $renderer->getTheme()->attributes($attr); endif; ?>
 />

+ 3 - 4
src/Symfony/Bundle/FrameworkBundle/Tests/Form/PhpEngineThemeTest.php

@@ -11,8 +11,7 @@
 
 namespace Symfony\Bundle\FrameworkBundle\Tests\Form;
 
-use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
-use Symfony\Bundle\FrameworkBundle\Form\PhpEngineTheme;
+use Symfony\Bundle\FrameworkBundle\Form\PhpEngineThemeFactory;
 use Symfony\Component\Templating\TemplateNameParser;
 use Symfony\Component\Templating\Loader\FilesystemLoader;
 use Symfony\Component\Templating\PhpEngine;
@@ -20,11 +19,11 @@ use Symfony\Tests\Component\Form\Renderer\Theme\AbstractThemeTest;
 
 class PhpEngineThemeTest extends AbstractThemeTest
 {
-    protected function createTheme()
+    protected function createThemeFactory()
     {
         $parser = new TemplateNameParser();
         $loader = new FilesystemLoader(__DIR__ . '/../../Resources/views/Form/%name%');
         $engine = new PhpEngine($parser, $loader, array());
-        return new PhpEngineTheme($engine);
+        return new PhpEngineThemeFactory($engine);
     }
 }

+ 18 - 0
src/Symfony/Bundle/TwigBundle/Resources/views/div_plain_layout.html.twig

@@ -0,0 +1,18 @@
+{% extends "widgets.html.twig" %}
+
+{% block row %}
+{% spaceless %}
+    <div>
+        {{ renderer.label }}
+        {{ renderer.errors }}
+        {{ renderer.widget }}
+    </div>
+{% endspaceless %}
+{% endblock row %}
+
+{% block form__widget %}
+{% spaceless %}
+    {{ block('rows') }}
+    {{ renderer.rest }}
+{% endspaceless %}
+{% endblock form__widget %}

+ 1 - 0
src/Symfony/Bundle/TwigBundle/Resources/views/widgets.html.twig

@@ -38,6 +38,7 @@
 {% block attributes %}
 {% spaceless %}
     id="{{ id }}" name="{{ name }}"{% if class %} class="{{ class }}"{% endif %}{% if disabled %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}{% if size %} size="{{ size }}"{% endif %}
+    {% for attrname,attrvalue in attr %}{{attrname}}="{{attrvalue}}" {% endfor %}
 {% endspaceless %}
 {% endblock attributes %}
 

+ 33 - 0
src/Symfony/Bundle/TwigBundle/Tests/Form/TwigThemeTest.php

@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bundle\FrameworkBundle\Tests\Form;
+
+use Symfony\Tests\Component\Form\Renderer\Theme\AbstractThemeTest;
+use Symfony\Bundle\TwigBundle\Form\TwigTheme;
+use Symfony\Bundle\TwigBundle\Extension\TransExtension;
+use Symfony\Component\Translation\MessageSelector;
+use Symfony\Component\Translation\Translator;
+
+class TwigThemeTest extends AbstractThemeTest
+{
+    protected function createTheme()
+    {
+        $loader = new \Twig_Loader_Filesystem(__DIR__ . '/../../Resources/views/');
+        $environment = new \Twig_Environment($loader, array(
+          'cache' => false,
+          'debug' => true
+        ));
+        $environment->addExtension(new TransExtension(new Translator('en', new MessageSelector())));
+
+        return new TwigTheme($environment, 'div_plain_layout.html.twig');
+    }
+}

+ 45 - 1
src/Symfony/Component/Form/Renderer/FormRendererInterface.php

@@ -20,9 +20,53 @@ interface FormRendererInterface
 
     function setChildren(array $renderers);
 
+    /**
+     * Has this renderer a specific var?
+     *
+     * @param string $name
+     * @return bool
+     */
+    function hasVar($name);
+
+    /**
+     * Set a renderer variable that is used to render a relevant part of the attached field.
+     *
+     * @param string $name
+     * @param string $value
+     * @return void
+     */
     function setVar($name, $value);
 
+    /**
+     * Get a renderer variable
+     *
+     * @param string $name
+     * @return mixed
+     */
     function getVar($name);
 
+    /**
+     * Get all variables as key value pairs
+     *
+     * @return array
+     */
+    function getVars();
+
+    /**
+     * Set an arbitrary attribute to be rendered with the primary input element of the widget.
+     *
+     * Examples could include "accesskey" or HTML5 "data-*" attributes.
+     *
+     * Warning: Do not attempt to overwrite id, name, class, size or maxlength, disabled or requried attributes with this setting.
+     * They have their own renderer variables that should be set through {@setVar()}.
+     *
+     * Important: This is a convenience method, all variables set have to accessible through {@getVar('attr')}
+     *
+     * @param string $name
+     * @param string $value
+     * @return void
+     */
+    function setAttribute($name, $value);
+
     function addPlugin(FormRendererPluginInterface $plugin);
-}
+}

+ 1 - 0
src/Symfony/Component/Form/Renderer/Plugin/FieldPlugin.php

@@ -52,5 +52,6 @@ class FieldPlugin implements FormRendererPluginInterface
         $renderer->setVar('size', null);
         $renderer->setVar('label', ucfirst(strtolower(str_replace('_', ' ', $field->getName()))));
         $renderer->setVar('multipart', false);
+        $renderer->setVar('attr', array());
     }
 }

+ 10 - 1
src/Symfony/Component/Form/Renderer/Theme/PhpTheme.php

@@ -69,9 +69,12 @@ class PhpTheme implements FormThemeInterface
                 $html .= 'multiple="multiple" ';
             }
             $html .= '>' . PHP_EOL;
+            if (!$attr['required']) {
+                $html .= '<option value="">' . $this->escape($attr['empty_value']) .'</option>';
+            }
             if (count($attr['preferred_choices']) > 0) {
                 $html .= $this->choice_list($attr['preferred_choices'], $attr['choice_list'], $attr['value']);
-                $html .= '<option disabled="disabled">' .  $attr['separator'] . '</option>' . PHP_EOL;
+                $html .= '<option disabled="disabled">' .  $this->escape($attr['separator']) . '</option>' . PHP_EOL;
             }
             $html .= $this->choice_list($attr['choices'], $attr['choice_list'], $attr['value']);
             $html .= '</select>' . PHP_EOL;
@@ -298,6 +301,12 @@ class PhpTheme implements FormThemeInterface
         if (isset($attr['max_length']) && $attr['max_length'] > 0) {
             $html .= 'maxlength="' . $this->escape($attr['max_length']) . '" ';
         }
+        if (isset($attr['attr'])) {
+            foreach ($attr['attr'] as $k => $v) {
+                $html .= $this->escape($k).'="'.$this->escape($v).'" ';
+            }
+        }
+
         return $html;
     }
 

+ 15 - 6
src/Symfony/Component/Form/Renderer/ThemeRenderer.php

@@ -130,6 +130,12 @@ class ThemeRenderer implements FormRendererInterface, \ArrayAccess
         }
     }
 
+    public function setAttribute($name, $value)
+    {
+        // handling through $this->changes not necessary
+        $this->vars['attr'][$name] = $value;
+    }
+
     public function hasVar($name)
     {
         return array_key_exists($name, $this->vars);
@@ -140,7 +146,10 @@ class ThemeRenderer implements FormRendererInterface, \ArrayAccess
         $this->initialize();
 
         // TODO exception handling
-        return $this->vars[$name];
+        if (isset($this->vars[$name])) {
+            return $this->vars[$name];
+        }
+        return null;
     }
 
     public function getVars()
@@ -206,24 +215,24 @@ class ThemeRenderer implements FormRendererInterface, \ArrayAccess
             $vars
         ));
     }
-
+ 
     public function offsetGet($name)
     {
         return $this->children[$name];
     }
-
+ 
     public function offsetExists($name)
     {
         return isset($this->children[$name]);
     }
-
+ 
     public function offsetSet($name, $value)
     {
         throw new \BadMethodCallException('Not supported');
     }
-
+ 
     public function offsetUnset($name)
     {
         throw new \BadMethodCallException('Not supported');
     }
-}
+}

+ 14 - 7
tests/Symfony/Tests/Component/Form/Renderer/Theme/AbstractThemeTest.php

@@ -15,14 +15,14 @@ use Symfony\Component\Form\Renderer\Theme\PhpTheme;
 
 abstract class AbstractThemeTest extends \PHPUnit_Framework_TestCase
 {
-    private $theme;
+    private $themeFactory;
 
     public function setUp()
     {
-        $this->theme = $this->createTheme();
+        $this->themeFactory = $this->createThemeFactory();
     }
 
-    abstract protected function createTheme();
+    abstract protected function createThemeFactory();
 
     public function testTextWidgetDefault()
     {
@@ -59,7 +59,9 @@ abstract class AbstractThemeTest extends \PHPUnit_Framework_TestCase
             'max_length' => 128,
             'disabled' => true,
             'required' => true,
-            'size' => 20
+            'size' => 20,
+            'attr' => array('accesskey' => 'G', 'title' => 'Foo'),
+            'renderer' => new \Symfony\Component\Form\Renderer\ThemeRenderer($this->themeFactory, null),
         ));
 
         $this->assertEquals('test', $input->getAttribute('value'));
@@ -68,6 +70,10 @@ abstract class AbstractThemeTest extends \PHPUnit_Framework_TestCase
         $this->assertTrue($input->hasAttribute('disabled'));
         $this->assertTrue($input->hasAttribute('required'));
         $this->assertEquals('20', $input->getAttribute('size'));
+        $this->assertTrue($input->hasAttribute('accesskey'));
+        $this->assertEquals('G', $input->getAttribute('accesskey'));
+        $this->assertTrue($input->hasAttribute('title'));
+        $this->assertEquals('Foo', $input->getAttribute('title'));
     }
 
     public function testChoiceWidgetDefaults()
@@ -85,16 +91,17 @@ abstract class AbstractThemeTest extends \PHPUnit_Framework_TestCase
             'class' => 'foo',
             'disabled' => false,
             'required' => false,
+            'empty_value' => '---',
             'expanded' => false,
             'multiple' => true,
             'preferred_choices' => $choiceList->getPreferredChoices(),
             'choices' => $choiceList->getOtherChoices(),
             'choice_list' => $choiceList,
-            'separator' => '---'
+            'separator' => '---',
         ));
 
         $this->assertXpathNodeValue($input, '//select/option[@selected="selected"]', 'Foo');
-        $this->assertXpathMatches($input, '//select/option', 4);
+        $this->assertXpathMatches($input, '//select/option', 5);
         $this->assertXpathNodeValue($input, '//select/option[@disabled="disabled"]', '---');
         $this->assertXpathMatches($input, '//select[@multiple="multiple"]', 1);
     }
@@ -116,7 +123,7 @@ abstract class AbstractThemeTest extends \PHPUnit_Framework_TestCase
 
     protected function renderAsDomElement($field, $section, $parameters)
     {
-        $html = $this->theme->render($field, $section, $parameters);
+        $html = $this->themeFactory->create()->render($field, $section, $parameters);
         $dom = new \DomDocument('UTF-8');
         $dom->loadXml($html);
         return $dom->documentElement;

+ 3 - 3
tests/Symfony/Tests/Component/Form/Renderer/Theme/PhpThemeTest.php

@@ -11,12 +11,12 @@
 
 namespace Symfony\Tests\Component\Form\Renderer\Theme;
 
-use Symfony\Component\Form\Renderer\Theme\PhpTheme;
+use Symfony\Component\Form\Renderer\Theme\PhpThemeFactory;
 
 class PhpThemeTest extends AbstractThemeTest
 {
-    protected function createTheme()
+    protected function createThemeFactory()
     {
-        return new PhpTheme();
+        return new PhpThemeFactory();
     }
 }