diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 3ab047c494..4f3856914b 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -218,6 +218,36 @@ {% endspaceless %} {% endblock email_widget %} +{% block button_widget %} +{% spaceless %} + {% if type is not defined or type is empty %} + {% set type = "button" %} + {% endif %} + {% if label is empty %} + {% set label = name|humanize %} + {% endif %} + +{% endspaceless %} +{% endblock button_widget %} + +{% block submit_widget %} +{% spaceless %} + {% if type is not defined %} + {% set type = "submit" %} + {% endif %} + {{ block('button_widget') }} +{% endspaceless %} +{% endblock submit_widget %} + +{% block reset_widget %} +{% spaceless %} + {% if type is not defined %} + {% set type = "reset" %} + {% endif %} + {{ block('button_widget') }} +{% endspaceless %} +{% endblock reset_widget %} + {# Labels #} {% block form_label %} @@ -237,6 +267,8 @@ {% endspaceless %} {% endblock form_label %} +{% block button_label %}{% endblock %} + {# Rows #} {% block repeated_row %} @@ -259,6 +291,14 @@ {% endspaceless %} {% endblock form_row %} +{% block button_row %} +{% spaceless %} +
+ {{ form_widget(form) }} +
+{% endspaceless %} +{% endblock button_row %} + {% block hidden_row %} {{ form_widget(form) }} {% endblock hidden_row %} @@ -317,6 +357,13 @@ {% endspaceless %} {% endblock widget_container_attributes %} +{% block button_attributes %} +{% spaceless %} + id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif %} + {% for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %} +{% endspaceless %} +{% endblock button_attributes %} + {# Deprecated in Symfony 2.1, to be removed in 2.3 #} {% block generic_label %}{{ block('form_label') }}{% endblock %} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig index 63bd7d2787..1f339763c7 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig @@ -14,6 +14,17 @@ {% endspaceless %} {% endblock form_row %} +{% block button_row %} +{% spaceless %} + + + + {{ form_widget(form) }} + + +{% endspaceless %} +{% endblock button_row %} + {% block hidden_row %} {% spaceless %} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml index d614e4dc50..a70e06becb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form.xml @@ -140,6 +140,15 @@ + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_attributes.html.php new file mode 100644 index 0000000000..63d16bd357 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_attributes.html.php @@ -0,0 +1,6 @@ +id="escape($id) ?>" +name="escape($full_name) ?>" +disabled="disabled" + $v): ?> + escape($k), $view->escape($v)) ?> + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_label.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_label.html.php new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_row.html.php new file mode 100644 index 0000000000..b52e929845 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_row.html.php @@ -0,0 +1,3 @@ +
+ widget($form) ?> +
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php new file mode 100644 index 0000000000..4f78927382 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/button_widget.html.php @@ -0,0 +1,4 @@ +humanize($name); } ?> + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/reset_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/reset_widget.html.php new file mode 100644 index 0000000000..1575e82928 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/reset_widget.html.php @@ -0,0 +1 @@ +block($form, 'button_widget', array('type' => isset($type) ? $type : 'reset')) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/submit_widget.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/submit_widget.html.php new file mode 100644 index 0000000000..d42bb2a78f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/submit_widget.html.php @@ -0,0 +1 @@ +block($form, 'button_widget', array('type' => isset($type) ? $type : 'submit')) ?> diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/button_row.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/button_row.html.php new file mode 100644 index 0000000000..ef4d22b975 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/FormTable/button_row.html.php @@ -0,0 +1,6 @@ + + + + widget($form) ?> + + diff --git a/src/Symfony/Component/Form/Button.php b/src/Symfony/Component/Form/Button.php new file mode 100644 index 0000000000..b1b0b00d65 --- /dev/null +++ b/src/Symfony/Component/Form/Button.php @@ -0,0 +1,412 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Exception\AlreadyBoundException; + +/** + * Default implementation of {@link ButtonInterface}. + * + * @author Bernhard Schussek + */ +class Button implements \IteratorAggregate, FormInterface +{ + /** + * @var FormInterface + */ + private $parent; + + /** + * @var FormConfigInterface + */ + private $config; + + /** + * @var Boolean + */ + private $bound = false; + + /** + * Creates a new button from a form configuration. + * + * @param FormConfigInterface $config The button's configuration. + */ + public function __construct(FormConfigInterface $config) + { + $this->config = $config; + } + + /** + * Unsupported method. + * + * @param mixed $offset + * + * @return Boolean Always returns false. + */ + public function offsetExists($offset) + { + return false; + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param mixed $offset + * + * @throws \BadMethodCallException + */ + public function offsetGet($offset) + { + throw new \BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param mixed $offset + * @param mixed $value + * + * @throws \BadMethodCallException + */ + public function offsetSet($offset, $value) + { + throw new \BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param mixed $offset + * + * @throws \BadMethodCallException + */ + public function offsetUnset($offset) + { + throw new \BadMethodCallException('Buttons cannot have children.'); + } + + /** + * {@inheritdoc} + */ + public function setParent(FormInterface $parent = null) + { + $this->parent = $parent; + } + + /** + * {@inheritdoc} + */ + public function getParent() + { + return $this->parent; + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param int|string|FormInterface $child + * @param null $type + * @param array $options + * + * @throws \BadMethodCallException + */ + public function add($child, $type = null, array $options = array()) + { + throw new \BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param string $name + * + * @throws \BadMethodCallException + */ + public function get($name) + { + throw new \BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * @param string $name + * + * @return Boolean Always returns false. + */ + public function has($name) + { + return false; + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param string $name + * + * @throws \BadMethodCallException + */ + public function remove($name) + { + throw new \BadMethodCallException('Buttons cannot have children.'); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getErrors() + { + return array(); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param string $modelData + * + * @throws \BadMethodCallException + */ + public function setData($modelData) + { + throw new \BadMethodCallException('Buttons cannot have data.'); + } + + /** + * Unsupported method. + * + * @return null Always returns null. + */ + public function getData() + { + return null; + } + + /** + * Unsupported method. + * + * @return null Always returns null. + */ + public function getNormData() + { + return null; + } + + /** + * Unsupported method. + * + * @return null Always returns null. + */ + public function getViewData() + { + return null; + } + + /** + * Unsupported method. + * + * @return array Always returns an empty array. + */ + public function getExtraData() + { + return array(); + } + + /** + * Returns the button's configuration. + * + * @return FormConfigInterface The configuration. + */ + public function getConfig() + { + return $this->config; + } + + /** + * Returns whether the button is submitted. + * + * @return Boolean true if the button was submitted. + */ + public function isBound() + { + return $this->bound; + } + + /** + * Returns the name by which the button is identified in forms. + * + * @return string The name of the button. + */ + public function getName() + { + return $this->config->getName(); + } + + /** + * Unsupported method. + * + * @return null Always returns null. + */ + public function getPropertyPath() + { + return null; + } + + /** + * Unsupported method. + * + * @param FormError $error + * + * @throws \BadMethodCallException + */ + public function addError(FormError $error) + { + throw new \BadMethodCallException('Buttons cannot have errors.'); + } + + /** + * Unsupported method. + * + * @return Boolean Always returns true. + */ + public function isValid() + { + return true; + } + + /** + * Unsupported method. + * + * @return Boolean Always returns false. + */ + public function isRequired() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isDisabled() + { + return $this->config->getDisabled(); + } + + /** + * Unsupported method. + * + * @return Boolean Always returns true. + */ + public function isEmpty() + { + return true; + } + + /** + * Unsupported method. + * + * @return Boolean Always returns true. + */ + public function isSynchronized() + { + return true; + } + + /** + * Binds data to the button. + * + * @param null|string $submittedData The data + * + * @return Button The button instance + * + * @throws Exception\AlreadyBoundException If the form has already been bound. + */ + public function bind($submittedData) + { + if ($this->bound) { + throw new AlreadyBoundException('A form can only be bound once'); + } + + $this->bound = true; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getRoot() + { + return $this->parent ? $this->parent->getRoot() : $this; + } + + /** + * {@inheritdoc} + */ + public function isRoot() + { + return null === $this->parent; + } + + /** + * {@inheritdoc} + */ + public function createView(FormView $parent = null) + { + if (null === $parent && $this->parent) { + $parent = $this->parent->createView(); + } + + return $this->config->getType()->createView($this, $parent); + } + + /** + * Unsupported method. + * + * @return integer Always returns 0. + */ + public function count() + { + return 0; + } + + /** + * Unsupported method. + * + * @return \EmptyIterator Always returns an empty iterator. + */ + public function getIterator() + { + return new \EmptyIterator(); + } +} diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php new file mode 100644 index 0000000000..ce30b0ab26 --- /dev/null +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -0,0 +1,823 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Exception\FormException; + +/** + * A builder for {@link Button} instances. + * + * @author Bernhard Schussek + */ +class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface +{ + /** + * @var Boolean + */ + protected $locked = false; + + /** + * @var Boolean + */ + private $disabled; + + /** + * @var ResolvedFormTypeInterface + */ + private $type; + + /** + * @var string + */ + private $name; + + /** + * @var array + */ + private $attributes = array(); + + /** + * @var array + */ + private $options; + + /** + * Creates a new button builder. + * + * @param string $name The name of the button. + * @param array $options The button's options. + * + * @throws FormException If the name is empty. + */ + public function __construct($name, array $options) + { + if (empty($name) && 0 != $name) { + throw new FormException('Buttons cannot have empty names.'); + } + + $this->name = (string) $name; + $this->options = $options; + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param string|integer|FormBuilderInterface $child + * @param string|FormTypeInterface $type + * @param array $options + * + * @throws \BadMethodCallException + */ + public function add($child, $type = null, array $options = array()) + { + throw new \BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param string $name + * @param string|FormTypeInterface $type + * @param array $options + * + * @throws \BadMethodCallException + */ + public function create($name, $type = null, array $options = array()) + { + throw new \BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param string $name + * + * @throws \BadMethodCallException + */ + public function get($name) + { + throw new \BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param string $name + * + * @throws \BadMethodCallException + */ + public function remove($name) + { + throw new \BadMethodCallException('Buttons cannot have children.'); + } + + /** + * Unsupported method. + * + * @param string $name + * + * @return Boolean Always returns false. + */ + public function has($name) + { + return false; + } + + /** + * Returns the children. + * + * @return array Always returns an empty array. + */ + public function all() + { + return array(); + } + + /** + * Creates the button. + * + * @return Button The button + */ + public function getForm() + { + return new Button($this->getFormConfig()); + } + + /** + * Unsupported method. + * + * Does nothing. + * + * @param FormBuilderInterface $parent The parent builder + * + * @deprecated Deprecated since version 2.2, to be removed in 2.3. You + * should not rely on the parent of a builder, because it is + * likely that the parent is only set after turning the builder + * into a form. + */ + public function setParent(FormBuilderInterface $parent = null) + { + } + + /** + * Unsupported method. + * + * @return null Always returns null. + * + * @deprecated Deprecated since version 2.2, to be removed in 2.3. You + * should not rely on the parent of a builder, because it is + * likely that the parent is only set after turning the builder + * into a form. + */ + public function getParent() + { + return null; + } + + /** + * Unsupported method. + * + * @return Boolean Always returns false. + * + * @deprecated Deprecated since version 2.2, to be removed in 2.3. You + * should not rely on the parent of a builder, because it is + * likely that the parent is only set after turning the builder + * into a form. + */ + public function hasParent() + { + return false; + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param string $eventName + * @param callable $listener + * @param integer $priority + * + * @throws \BadMethodCallException + */ + public function addEventListener($eventName, $listener, $priority = 0) + { + throw new \BadMethodCallException('Buttons do not support event listeners.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param EventSubscriberInterface $subscriber + * + * @throws \BadMethodCallException + */ + public function addEventSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Buttons do not support event subscribers.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param FormValidatorInterface $validator + * + * @throws \BadMethodCallException + * + * @deprecated Deprecated since version 2.1, to be removed in 2.3. + */ + public function addValidator(FormValidatorInterface $validator) + { + throw new \BadMethodCallException('Buttons do not support validators.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param DataTransformerInterface $viewTransformer + * @param Boolean $forcePrepend + * + * @throws \BadMethodCallException + */ + public function addViewTransformer(DataTransformerInterface $viewTransformer, $forcePrepend = false) + { + throw new \BadMethodCallException('Buttons do not support data transformers.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @throws \BadMethodCallException + */ + public function resetViewTransformers() + { + throw new \BadMethodCallException('Buttons do not support data transformers.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param DataTransformerInterface $modelTransformer + * @param Boolean $forceAppend + * + * @throws \BadMethodCallException + */ + public function addModelTransformer(DataTransformerInterface $modelTransformer, $forceAppend = false) + { + throw new \BadMethodCallException('Buttons do not support data transformers.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @throws \BadMethodCallException + */ + public function resetModelTransformers() + { + throw new \BadMethodCallException('Buttons do not support data transformers.'); + } + + /** + * {@inheritdoc} + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function setAttributes(array $attributes) + { + $this->attributes = $attributes; + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param DataMapperInterface $dataMapper + * + * @throws \BadMethodCallException + */ + public function setDataMapper(DataMapperInterface $dataMapper = null) + { + throw new \BadMethodCallException('Buttons do not support data mappers.'); + } + + /** + * Set whether the button is disabled. + * + * @param Boolean $disabled Whether the button is disabled + * + * @return ButtonBuilder The button builder. + */ + public function setDisabled($disabled) + { + $this->disabled = $disabled; + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param mixed $emptyData + * + * @throws \BadMethodCallException + */ + public function setEmptyData($emptyData) + { + throw new \BadMethodCallException('Buttons do not support empty data.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param Boolean $errorBubbling + * + * @throws \BadMethodCallException + */ + public function setErrorBubbling($errorBubbling) + { + throw new \BadMethodCallException('Buttons do not support error bubbling.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param Boolean $required + * + * @throws \BadMethodCallException + */ + public function setRequired($required) + { + throw new \BadMethodCallException('Buttons cannot be required.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param null $propertyPath + * + * @throws \BadMethodCallException + */ + public function setPropertyPath($propertyPath) + { + throw new \BadMethodCallException('Buttons do not support property paths.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param Boolean $mapped + * + * @throws \BadMethodCallException + */ + public function setMapped($mapped) + { + throw new \BadMethodCallException('Buttons do not support data mapping.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param Boolean $byReference + * + * @throws \BadMethodCallException + */ + public function setByReference($byReference) + { + throw new \BadMethodCallException('Buttons do not support data mapping.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param Boolean $virtual + * + * @throws \BadMethodCallException + */ + public function setVirtual($virtual) + { + throw new \BadMethodCallException('Buttons cannot be virtual.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param Boolean $compound + * + * @throws \BadMethodCallException + */ + public function setCompound($compound) + { + throw new \BadMethodCallException('Buttons cannot be compound.'); + } + + /** + * Sets the type of the button. + * + * @param ResolvedFormTypeInterface $type The type of the button. + * + * @return ButtonBuilder The button builder. + */ + public function setType(ResolvedFormTypeInterface $type) + { + $this->type = $type; + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param array $data + * + * @throws \BadMethodCallException + */ + public function setData($data) + { + throw new \BadMethodCallException('Buttons do not support data.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param Boolean $locked + * + * @throws \BadMethodCallException + */ + public function setDataLocked($locked) + { + throw new \BadMethodCallException('Buttons do not support data locking.'); + } + + /** + * Unsupported method. + * + * This method should not be invoked. + * + * @param FormFactoryInterface $formFactory + * + * @return void + * + * @throws \BadMethodCallException + */ + public function setFormFactory(FormFactoryInterface $formFactory) + { + throw new \BadMethodCallException('Buttons do not support form factories.'); + } + + /** + * Builds and returns the button configuration. + * + * @return FormConfigInterface + */ + public function getFormConfig() + { + // This method should be idempotent, so clone the builder + $config = clone $this; + $config->locked = true; + + return $config; + } + + /** + * Unsupported method. + * + * @return null Always returns null. + */ + public function getEventDispatcher() + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * Unsupported method. + * + * @return null Always returns null. + */ + public function getPropertyPath() + { + return null; + } + + /** + * Unsupported method. + * + * @return Boolean Always returns false. + */ + public function getMapped() + { + return false; + } + + /** + * Unsupported method. + * + * @return Boolean Always returns false. + */ + public function getByReference() + { + return false; + } + + /** + * Unsupported method. + * + * @return Boolean Always returns false. + */ + public function getVirtual() + { + return false; + } + + /** + * Unsupported method. + * + * @return Boolean Always returns false. + */ + public function getCompound() + { + return false; + } + + /** + * Returns the form type used to construct the button. + * + * @return ResolvedFormTypeInterface The button's type. + */ + public function getType() + { + return $this->type; + } + + /** + * Unsupported method. + * + * @return array Always returns an empty array. + */ + public function getViewTransformers() + { + return array(); + } + + /** + * Unsupported method. + * + * @return array Always returns an empty array. + */ + public function getModelTransformers() + { + return array(); + } + + /** + * Unsupported method. + * + * @return null Always returns null. + */ + public function getDataMapper() + { + return null; + } + + /** + * Unsupported method. + * + * @return array Always returns null. + * + * @deprecated Deprecated since version 2.1, to be removed in 2.3. + */ + public function getValidators() + { + return array(); + } + + /** + * Unsupported method. + * + * @return Boolean Always returns false. + */ + public function getRequired() + { + return false; + } + + /** + * Returns whether the button is disabled. + * + * @return Boolean Whether the button is disabled. + */ + public function getDisabled() + { + return $this->disabled; + } + + /** + * Unsupported method. + * + * @return Boolean Always returns false. + */ + public function getErrorBubbling() + { + return false; + } + + /** + * Unsupported method. + * + * @return null Always returns null. + */ + public function getEmptyData() + { + return null; + } + + /** + * Returns additional attributes of the button. + * + * @return array An array of key-value combinations. + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Returns whether the attribute with the given name exists. + * + * @param string $name The attribute name. + * + * @return Boolean Whether the attribute exists. + */ + public function hasAttribute($name) + { + return isset($this->attributes[$name]); + } + + /** + * Returns the value of the given attribute. + * + * @param string $name The attribute name. + * @param mixed $default The value returned if the attribute does not exist. + * + * @return mixed The attribute value. + */ + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + /** + * Unsupported method. + * + * @return null Always returns null. + */ + public function getData() + { + return null; + } + + /** + * Unsupported method. + * + * @return null Always returns null. + */ + public function getDataClass() + { + return null; + } + + /** + * Unsupported method. + * + * @return Boolean Always returns false. + */ + public function getDataLocked() + { + return false; + } + + /** + * Unsupported method. + * + * @return null Always returns null. + */ + public function getFormFactory() + { + return null; + } + + /** + * Returns all options passed during the construction of the button. + * + * @return array The passed options. + */ + public function getOptions() + { + return $this->options; + } + + /** + * Returns whether a specific option exists. + * + * @param string $name The option name, + * + * @return Boolean Whether the option exists. + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * Returns the value of a specific option. + * + * @param string $name The option name. + * @param mixed $default The value returned if the option does not exist. + * + * @return mixed The option value. + */ + public function getOption($name, $default = null) + { + return isset($this->options[$name]) ? $this->options[$name] : $default; + } + + /** + * Unsupported method. + * + * @return integer Always returns 0. + */ + public function count() + { + return 0; + } + + /** + * Unsupported method. + * + * @return \EmptyIterator Always returns an empty iterator. + */ + public function getIterator() + { + return new \EmptyIterator(); + } +} diff --git a/src/Symfony/Component/Form/ButtonTypeInterface.php b/src/Symfony/Component/Form/ButtonTypeInterface.php new file mode 100644 index 0000000000..dd5117c4da --- /dev/null +++ b/src/Symfony/Component/Form/ButtonTypeInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * A type that should be converted into a {@link Button} instance. + * + * @author Bernhard Schussek + */ +interface ButtonTypeInterface extends FormTypeInterface +{ +} diff --git a/src/Symfony/Component/Form/ClickableInterface.php b/src/Symfony/Component/Form/ClickableInterface.php new file mode 100644 index 0000000000..893f02df0a --- /dev/null +++ b/src/Symfony/Component/Form/ClickableInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * A clickable form element. + * + * @author Bernhard Schussek + */ +interface ClickableInterface +{ + /** + * Returns whether this element was clicked. + * + * @return Boolean Whether this element was clicked. + */ + public function isClicked(); +} diff --git a/src/Symfony/Component/Form/Extension/Core/CoreExtension.php b/src/Symfony/Component/Form/Extension/Core/CoreExtension.php index ff7a1a3e0e..3934d38261 100644 --- a/src/Symfony/Component/Form/Extension/Core/CoreExtension.php +++ b/src/Symfony/Component/Form/Extension/Core/CoreExtension.php @@ -50,6 +50,9 @@ class CoreExtension extends AbstractExtension new Type\TimezoneType(), new Type\UrlType(), new Type\FileType(), + new Type\ButtonType(), + new Type\SubmitType(), + new Type\ResetType(), ); } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php b/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php new file mode 100644 index 0000000000..ca47a7bc2f --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/Type/BaseType.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolverInterface; + +/** + * Encapsulates common logic of {@link FormType} and {@link ButtonType}. + * + * This type does not appear in the form's type inheritance chain and as such + * cannot be extended (via {@link FormTypeExtension}s) nor themed. + * + * @author Bernhard Schussek + */ +abstract class BaseType extends AbstractType +{ + /** + * {@inheritdoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->setDisabled($options['disabled']); + } + + /** + * {@inheritdoc} + */ + public function buildView(FormView $view, FormInterface $form, array $options) + { + /* @var \Symfony\Component\Form\ClickableInterface $form */ + + $name = $form->getName(); + $blockName = $options['block_name'] ?: $form->getName(); + $translationDomain = $options['translation_domain']; + + if ($view->parent) { + if ('' !== ($parentFullName = $view->parent->vars['full_name'])) { + $id = sprintf('%s_%s', $view->parent->vars['id'], $name); + $fullName = sprintf('%s[%s]', $parentFullName, $name); + $uniqueBlockPrefix = sprintf('%s_%s', $view->parent->vars['unique_block_prefix'], $blockName); + } else { + $id = $name; + $fullName = $name; + $uniqueBlockPrefix = '_'.$blockName; + } + + if (!$translationDomain) { + $translationDomain = $view->parent->vars['translation_domain']; + } + } else { + $id = $name; + $fullName = $name; + $uniqueBlockPrefix = '_'.$blockName; + + // Strip leading underscores and digits. These are allowed in + // form names, but not in HTML4 ID attributes. + // http://www.w3.org/TR/html401/struct/global.html#adef-id + $id = ltrim($id, '_0123456789'); + } + + $blockPrefixes = array(); + for ($type = $form->getConfig()->getType(); null !== $type; $type = $type->getParent()) { + array_unshift($blockPrefixes, $type->getName()); + } + $blockPrefixes[] = $uniqueBlockPrefix; + + if (!$translationDomain) { + $translationDomain = 'messages'; + } + + $view->vars = array_replace($view->vars, array( + 'form' => $view, + 'id' => $id, + 'name' => $name, + 'full_name' => $fullName, + 'disabled' => $form->isDisabled(), + 'label' => $options['label'], + 'multipart' => false, + 'attr' => $options['attr'], + 'block_prefixes' => $blockPrefixes, + 'unique_block_prefix' => $uniqueBlockPrefix, + 'translation_domain' => $translationDomain, + // Using the block name here speeds up performance in collection + // forms, where each entry has the same full block name. + // Including the type is important too, because if rows of a + // collection form have different types (dynamically), they should + // be rendered differently. + // https://github.com/symfony/symfony/issues/5038 + 'cache_key' => $uniqueBlockPrefix.'_'.$form->getConfig()->getType()->getName(), + )); + } + + /** + * {@inheritdoc} + */ + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'block_name' => null, + 'disabled' => false, + 'label' => null, + 'attr' => array(), + 'translation_domain' => null, + )); + + $resolver->setAllowedTypes(array( + 'attr' => 'array', + )); + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php b/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php new file mode 100644 index 0000000000..3569963bc4 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/Type/ButtonType.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\ButtonTypeInterface; + +/** + * A form button. + * + * @author Bernhard Schussek + */ +class ButtonType extends BaseType implements ButtonTypeInterface +{ + /** + * {@inheritdoc} + */ + public function getParent() + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'button'; + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index 16e7bbc7a0..bd01f8f040 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Form\Extension\Core\Type; -use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; @@ -23,7 +22,7 @@ use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; -class FormType extends AbstractType +class FormType extends BaseType { /** * @var PropertyAccessorInterface @@ -40,9 +39,10 @@ class FormType extends AbstractType */ public function buildForm(FormBuilderInterface $builder, array $options) { + parent::buildForm($builder, $options); + $builder ->setRequired($options['required']) - ->setDisabled($options['disabled']) ->setErrorBubbling($options['error_bubbling']) ->setEmptyData($options['empty_data']) ->setPropertyPath($options['property_path']) @@ -65,85 +65,34 @@ class FormType extends AbstractType */ public function buildView(FormView $view, FormInterface $form, array $options) { + parent::buildView($view, $form, $options); + $name = $form->getName(); - $blockName = $options['block_name'] ?: $form->getName(); $readOnly = $options['read_only']; - $translationDomain = $options['translation_domain']; if ($view->parent) { if ('' === $name) { throw new Exception('Form node with empty name can be used only as root form node.'); } - if ('' !== ($parentFullName = $view->parent->vars['full_name'])) { - $id = sprintf('%s_%s', $view->parent->vars['id'], $name); - $fullName = sprintf('%s[%s]', $parentFullName, $name); - $uniqueBlockPrefix = sprintf('%s_%s', $view->parent->vars['unique_block_prefix'], $blockName); - } else { - $id = $name; - $fullName = $name; - $uniqueBlockPrefix = '_'.$blockName; - } - // Complex fields are read-only if they themselves or their parents are. if (!$readOnly) { $readOnly = $view->parent->vars['read_only']; } - - if (!$translationDomain) { - $translationDomain = $view->parent->vars['translation_domain']; - } - } else { - $id = $name; - $fullName = $name; - $uniqueBlockPrefix = '_'.$blockName; - - // Strip leading underscores and digits. These are allowed in - // form names, but not in HTML4 ID attributes. - // http://www.w3.org/TR/html401/struct/global.html#adef-id - $id = ltrim($id, '_0123456789'); - } - - $blockPrefixes = array(); - for ($type = $form->getConfig()->getType(); null !== $type; $type = $type->getParent()) { - array_unshift($blockPrefixes, $type->getName()); - } - $blockPrefixes[] = $uniqueBlockPrefix; - - if (!$translationDomain) { - $translationDomain = 'messages'; } $view->vars = array_replace($view->vars, array( - 'form' => $view, - 'id' => $id, - 'name' => $name, - 'full_name' => $fullName, - 'read_only' => $readOnly, - 'errors' => $form->getErrors(), - 'valid' => $form->isBound() ? $form->isValid() : true, - 'value' => $form->getViewData(), - 'data' => $form->getNormData(), - 'disabled' => $form->isDisabled(), - 'required' => $form->isRequired(), - 'max_length' => $options['max_length'], - 'pattern' => $options['pattern'], - 'size' => null, - 'label' => $options['label'], - 'multipart' => false, - 'attr' => $options['attr'], - 'label_attr' => $options['label_attr'], - 'compound' => $form->getConfig()->getCompound(), - 'block_prefixes' => $blockPrefixes, - 'unique_block_prefix' => $uniqueBlockPrefix, - 'translation_domain' => $translationDomain, - // Using the block name here speeds up performance in collection - // forms, where each entry has the same full block name. - // Including the type is important too, because if rows of a - // collection form have different types (dynamically), they should - // be rendered differently. - // https://github.com/symfony/symfony/issues/5038 - 'cache_key' => $uniqueBlockPrefix.'_'.$form->getConfig()->getType()->getName(), + 'read_only' => $readOnly, + 'errors' => $form->getErrors(), + 'valid' => $form->isBound() ? $form->isValid() : true, + 'value' => $form->getViewData(), + 'data' => $form->getNormData(), + 'required' => $form->isRequired(), + 'max_length' => $options['max_length'], + 'pattern' => $options['pattern'], + 'size' => null, + 'label_attr' => $options['label_attr'], + 'compound' => $form->getConfig()->getCompound(), )); } @@ -169,6 +118,8 @@ class FormType extends AbstractType */ public function setDefaultOptions(OptionsResolverInterface $resolver) { + parent::setDefaultOptions($resolver); + // Derive "data_class" option from passed "data" object $dataClass = function (Options $options) { return isset($options['data']) && is_object($options['data']) ? get_class($options['data']) : null; @@ -202,29 +153,23 @@ class FormType extends AbstractType )); $resolver->setDefaults(array( - 'block_name' => null, 'data_class' => $dataClass, 'empty_data' => $emptyData, 'trim' => true, 'required' => true, 'read_only' => false, - 'disabled' => false, 'max_length' => null, 'pattern' => null, 'property_path' => null, 'mapped' => true, 'by_reference' => true, 'error_bubbling' => $errorBubbling, - 'label' => null, - 'attr' => array(), 'label_attr' => array(), 'virtual' => false, 'compound' => true, - 'translation_domain' => null, )); $resolver->setAllowedTypes(array( - 'attr' => 'array', 'label_attr' => 'array', )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ResetType.php b/src/Symfony/Component/Form/Extension/Core/Type/ResetType.php new file mode 100644 index 0000000000..cf55f7c591 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/Type/ResetType.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\ButtonTypeInterface; + +/** + * A reset button. + * + * @author Bernhard Schussek + */ +class ResetType extends AbstractType implements ButtonTypeInterface +{ + /** + * {@inheritdoc} + */ + public function getParent() + { + return 'button'; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'reset'; + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php b/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php new file mode 100644 index 0000000000..6d160b9692 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/Type/SubmitType.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\SubmitButtonTypeInterface; + +/** + * A submit button. + * + * @author Bernhard Schussek + */ +class SubmitType extends AbstractType implements SubmitButtonTypeInterface +{ + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['clicked'] = $form->isClicked(); + } + + /** + * {@inheritdoc} + */ + public function getParent() + { + return 'button'; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'submit'; + } +} diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php index 6b9ac4e4f0..19c888e200 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php @@ -53,17 +53,9 @@ class FormTypeValidatorExtension extends AbstractTypeExtension */ public function setDefaultOptions(OptionsResolverInterface $resolver) { - // Make sure that validation groups end up as null, closure or array - $validationGroupsNormalizer = function (Options $options, $groups) { - if (empty($groups)) { - return null; - } - - if (is_callable($groups)) { - return $groups; - } - - return (array) $groups; + // BC clause + $constraints = function (Options $options) { + return $options['validation_constraint']; }; // Constraint should always be converted to an array @@ -73,8 +65,9 @@ class FormTypeValidatorExtension extends AbstractTypeExtension $resolver->setDefaults(array( 'error_mapping' => array(), - 'validation_groups' => null, - 'constraints' => null, + // "validation_constraint" is deprecated. Use "constraints". + 'validation_constraint' => null, + 'constraints' => $constraints, 'cascade_validation' => false, 'invalid_message' => 'This value is not valid.', 'invalid_message_parameters' => array(), @@ -83,7 +76,6 @@ class FormTypeValidatorExtension extends AbstractTypeExtension )); $resolver->setNormalizers(array( - 'validation_groups' => $validationGroupsNormalizer, 'constraints' => $constraintsNormalizer, )); } diff --git a/src/Symfony/Component/Form/FormInterface.php b/src/Symfony/Component/Form/FormInterface.php index 94f6b3e29d..3e8fe525ec 100644 --- a/src/Symfony/Component/Form/FormInterface.php +++ b/src/Symfony/Component/Form/FormInterface.php @@ -150,9 +150,9 @@ interface FormInterface extends \ArrayAccess, \Traversable, \Countable public function getConfig(); /** - * Returns whether the field is bound. + * Returns whether the form is submitted. * - * @return Boolean true if the form is bound to input values, false otherwise + * @return Boolean true if the form is submitted, false otherwise */ public function isBound(); diff --git a/src/Symfony/Component/Form/ResolvedFormType.php b/src/Symfony/Component/Form/ResolvedFormType.php index f2ef06c8b8..7203817b94 100644 --- a/src/Symfony/Component/Form/ResolvedFormType.php +++ b/src/Symfony/Component/Form/ResolvedFormType.php @@ -111,7 +111,7 @@ class ResolvedFormType implements ResolvedFormTypeInterface // Should be decoupled from the specific option at some point $dataClass = isset($options['data_class']) ? $options['data_class'] : null; - $builder = new FormBuilder($name, $dataClass, new EventDispatcher(), $factory, $options); + $builder = $this->newBuilder($name, $dataClass, $factory, $options); $builder->setType($this); $builder->setParent($parent); @@ -127,7 +127,7 @@ class ResolvedFormType implements ResolvedFormTypeInterface { $options = $form->getConfig()->getOptions(); - $view = new FormView($parent); + $view = $this->newView($parent); $this->buildView($view, $form, $options); @@ -243,4 +243,43 @@ class ResolvedFormType implements ResolvedFormTypeInterface return $this->optionsResolver; } + + /** + * Creates a new builder instance. + * + * Override this method if you want to customize the builder class. + * + * @param string $name The name of the builder. + * @param string $dataClass The data class. + * @param FormFactoryInterface $factory The current form factory. + * @param array $options The builder options. + * + * @return FormBuilderInterface The new builder instance. + */ + protected function newBuilder($name, $dataClass, FormFactoryInterface $factory, array $options) + { + if ($this->innerType instanceof ButtonTypeInterface) { + return new ButtonBuilder($name, $options); + } + + if ($this->innerType instanceof SubmitButtonTypeInterface) { + return new SubmitButtonBuilder($name, $options); + } + + return new FormBuilder($name, $dataClass, new EventDispatcher(), $factory, $options); + } + + /** + * Creates a new view instance. + * + * Override this method if you want to customize the view class. + * + * @param FormView|null $parent The parent view, if available. + * + * @return FormView A new view instance. + */ + protected function newView(FormView $parent = null) + { + return new FormView($parent); + } } diff --git a/src/Symfony/Component/Form/SubmitButton.php b/src/Symfony/Component/Form/SubmitButton.php new file mode 100644 index 0000000000..01af5c2e72 --- /dev/null +++ b/src/Symfony/Component/Form/SubmitButton.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * A button that submits the form. + * + * @author Bernhard Schussek + */ +class SubmitButton extends Button implements ClickableInterface +{ + /** + * @var Boolean + */ + private $clicked = false; + + /** + * {@inheritdoc} + */ + public function isClicked() + { + return $this->clicked; + } + + /** + * Binds data to the button. + * + * @param null|string $submittedData The data + * + * @return SubmitButton The button instance + * + * @throws Exception\AlreadyBoundException If the form has already been bound. + */ + public function bind($submittedData) + { + parent::bind($submittedData); + + $this->clicked = null !== $submittedData; + + return $this; + } +} diff --git a/src/Symfony/Component/Form/SubmitButtonBuilder.php b/src/Symfony/Component/Form/SubmitButtonBuilder.php new file mode 100644 index 0000000000..088fb74819 --- /dev/null +++ b/src/Symfony/Component/Form/SubmitButtonBuilder.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * A builder for {@link SubmitButton} instances. + * + * @author Bernhard Schussek + */ +class SubmitButtonBuilder extends ButtonBuilder +{ + /** + * Creates the button. + * + * @return Button The button + */ + public function getForm() + { + return new SubmitButton($this->getFormConfig()); + } +} diff --git a/src/Symfony/Component/Form/SubmitButtonTypeInterface.php b/src/Symfony/Component/Form/SubmitButtonTypeInterface.php new file mode 100644 index 0000000000..f7ac13f7ec --- /dev/null +++ b/src/Symfony/Component/Form/SubmitButtonTypeInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * A type that should be converted into a {@link SubmitButton} instance. + * + * @author Bernhard Schussek + */ +interface SubmitButtonTypeInterface extends FormTypeInterface +{ +} diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php index 7fd743006e..391e81173e 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -82,6 +82,22 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest ); } + public function testButtonRow() + { + $form = $this->factory->createNamed('name', 'button'); + $view = $form->createView(); + $html = $this->renderRow($view); + + $this->assertMatchesXpath($html, +'/div + [ + ./button[@type="button"][@name="name"] + ] + [count(//label)=0] +' + ); + } + public function testRest() { $view = $this->factory->createNamedBuilder('name', 'form') diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index bf2c29e8e1..bad30f0679 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -1762,4 +1762,38 @@ abstract class AbstractLayoutTest extends FormIntegrationTestCase //input[@type="text"][@id="child"][@name="child"]' , 2); } + + public function testButton() + { + $form = $this->factory->createNamed('name', 'button'); + + $this->assertWidgetMatchesXpath($form->createView(), array(), + '/button[@type="button"][@name="name"]' + ); + } + + public function testButtonLabelIsEmpty() + { + $form = $this->factory->createNamed('name', 'button'); + + $this->assertSame('', $this->renderLabel($form->createView())); + } + + public function testSubmit() + { + $form = $this->factory->createNamed('name', 'submit'); + + $this->assertWidgetMatchesXpath($form->createView(), array(), + '/button[@type="submit"][@name="name"]' + ); + } + + public function testReset() + { + $form = $this->factory->createNamed('name', 'reset'); + + $this->assertWidgetMatchesXpath($form->createView(), array(), + '/button[@type="reset"][@name="name"]' + ); + } } diff --git a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php index e0f62c4ab1..28fdba2dda 100644 --- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php @@ -125,6 +125,25 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest ); } + public function testButtonRow() + { + $form = $this->factory->createNamed('name', 'button'); + $view = $form->createView(); + $html = $this->renderRow($view); + + $this->assertMatchesXpath($html, +'/tr + [ + ./td + [.=""] + /following-sibling::td + [./button[@type="button"][@name="name"]] + ] + [count(//label)=0] +' + ); + } + public function testRest() { $view = $this->factory->createNamedBuilder('name', 'form') diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/BaseTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/BaseTypeTest.php new file mode 100644 index 0000000000..d56355c644 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/BaseTypeTest.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\Type; + +/** + * @author Bernhard Schussek + */ +abstract class BaseTypeTest extends TypeTestCase +{ + public function testPassDisabledAsOption() + { + $form = $this->factory->create($this->getTestedType(), null, array('disabled' => true)); + + $this->assertTrue($form->isDisabled()); + } + + public function testPassIdAndNameToView() + { + $form = $this->factory->createNamed('name', $this->getTestedType()); + $view = $form->createView(); + + $this->assertEquals('name', $view->vars['id']); + $this->assertEquals('name', $view->vars['name']); + $this->assertEquals('name', $view->vars['full_name']); + } + + public function testStripLeadingUnderscoresAndDigitsFromId() + { + $form = $this->factory->createNamed('_09name', $this->getTestedType()); + $view = $form->createView(); + + $this->assertEquals('name', $view->vars['id']); + $this->assertEquals('_09name', $view->vars['name']); + $this->assertEquals('_09name', $view->vars['full_name']); + } + + public function testPassIdAndNameToViewWithParent() + { + $parent = $this->factory->createNamed('parent', 'form'); + $parent->add($this->factory->createNamed('child', $this->getTestedType())); + $view = $parent->createView(); + + $this->assertEquals('parent_child', $view['child']->vars['id']); + $this->assertEquals('child', $view['child']->vars['name']); + $this->assertEquals('parent[child]', $view['child']->vars['full_name']); + } + + public function testPassIdAndNameToViewWithGrandParent() + { + $parent = $this->factory->createNamed('parent', 'form'); + $parent->add($this->factory->createNamed('child', 'form')); + $parent['child']->add($this->factory->createNamed('grand_child', $this->getTestedType())); + $view = $parent->createView(); + + $this->assertEquals('parent_child_grand_child', $view['child']['grand_child']->vars['id']); + $this->assertEquals('grand_child', $view['child']['grand_child']->vars['name']); + $this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->vars['full_name']); + } + + public function testPassTranslationDomainToView() + { + $form = $this->factory->create($this->getTestedType(), null, array( + 'translation_domain' => 'domain', + )); + $view = $form->createView(); + + $this->assertSame('domain', $view->vars['translation_domain']); + } + + public function testInheritTranslationDomainFromParent() + { + $parent = $this->factory->createNamed('parent', 'form', null, array( + 'translation_domain' => 'domain', + )); + $child = $this->factory->createNamed('child', $this->getTestedType()); + $view = $parent->add($child)->createView(); + + $this->assertEquals('domain', $view['child']->vars['translation_domain']); + } + + public function testPreferOwnTranslationDomain() + { + $parent = $this->factory->createNamed('parent', 'form', null, array( + 'translation_domain' => 'parent_domain', + )); + $child = $this->factory->createNamed('child', $this->getTestedType(), null, array( + 'translation_domain' => 'domain', + )); + $view = $parent->add($child)->createView(); + + $this->assertEquals('domain', $view['child']->vars['translation_domain']); + } + + public function testDefaultTranslationDomain() + { + $parent = $this->factory->createNamed('parent', 'form'); + $child = $this->factory->createNamed('child', $this->getTestedType()); + $view = $parent->add($child)->createView(); + + $this->assertEquals('messages', $view['child']->vars['translation_domain']); + } + + public function testPassLabelToView() + { + $form = $this->factory->createNamed('__test___field', $this->getTestedType(), null, array('label' => 'My label')); + $view = $form->createView(); + + $this->assertSame('My label', $view->vars['label']); + } + + public function testPassMultipartFalseToView() + { + $form = $this->factory->create($this->getTestedType()); + $view = $form->createView(); + + $this->assertFalse($view->vars['multipart']); + } + + abstract protected function getTestedType(); +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ButtonTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ButtonTypeTest.php new file mode 100644 index 0000000000..55835e77fe --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ButtonTypeTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\Type; + +/** + * @author Bernhard Schussek + */ +class ButtonTypeTest extends BaseTypeTest +{ + public function testCreateButtonInstances() + { + $this->assertInstanceOf('Symfony\Component\Form\Button', $this->factory->create('button')); + } + + protected function getTestedType() + { + return 'button'; + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php index 6b906ea4f3..f6663d6b65 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php @@ -50,8 +50,13 @@ class FormTest_AuthorWithoutRefSetter } } -class FormTypeTest extends TypeTestCase +class FormTypeTest extends BaseTypeTest { + public function testCreateFormInstances() + { + $this->assertInstanceOf('Symfony\Component\Form\Form', $this->factory->create('form')); + } + public function testPassRequiredAsOption() { $form = $this->factory->create('form', null, array('required' => false)); @@ -63,13 +68,6 @@ class FormTypeTest extends TypeTestCase $this->assertTrue($form->isRequired()); } - public function testPassDisabledAsOption() - { - $form = $this->factory->create('form', null, array('disabled' => true)); - - $this->assertTrue($form->isDisabled()); - } - public function testBoundDataIsTrimmedBeforeTransforming() { $form = $this->factory->createBuilder('form') @@ -102,49 +100,6 @@ class FormTypeTest extends TypeTestCase $this->assertEquals('reverse[ a ]', $form->getData()); } - public function testPassIdAndNameToView() - { - $form = $this->factory->createNamed('name', 'form'); - $view = $form->createView(); - - $this->assertEquals('name', $view->vars['id']); - $this->assertEquals('name', $view->vars['name']); - $this->assertEquals('name', $view->vars['full_name']); - } - - public function testStripLeadingUnderscoresAndDigitsFromId() - { - $form = $this->factory->createNamed('_09name', 'form'); - $view = $form->createView(); - - $this->assertEquals('name', $view->vars['id']); - $this->assertEquals('_09name', $view->vars['name']); - $this->assertEquals('_09name', $view->vars['full_name']); - } - - public function testPassIdAndNameToViewWithParent() - { - $parent = $this->factory->createNamed('parent', 'form'); - $parent->add($this->factory->createNamed('child', 'form')); - $view = $parent->createView(); - - $this->assertEquals('parent_child', $view['child']->vars['id']); - $this->assertEquals('child', $view['child']->vars['name']); - $this->assertEquals('parent[child]', $view['child']->vars['full_name']); - } - - public function testPassIdAndNameToViewWithGrandParent() - { - $parent = $this->factory->createNamed('parent', 'form'); - $parent->add($this->factory->createNamed('child', 'form')); - $parent['child']->add($this->factory->createNamed('grand_child', 'form')); - $view = $parent->createView(); - - $this->assertEquals('parent_child_grand_child', $view['child']['grand_child']->vars['id']); - $this->assertEquals('grand_child', $view['child']['grand_child']->vars['name']); - $this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->vars['full_name']); - } - public function testNonReadOnlyFormWithReadOnlyParentBeingReadOnly() { $parent = $this->factory->createNamed('parent', 'form', null, array('read_only' => true)); @@ -180,57 +135,6 @@ class FormTypeTest extends TypeTestCase $this->assertSame(10, $view->vars['max_length']); } - public function testPassTranslationDomainToView() - { - $form = $this->factory->create('form', null, array('translation_domain' => 'test')); - $view = $form->createView(); - - $this->assertSame('test', $view->vars['translation_domain']); - } - - public function testNonTranslationDomainFormWithTranslationDomainParentBeingTranslationDomain() - { - $parent = $this->factory->createNamed('parent', 'form', null, array('translation_domain' => 'test')); - $child = $this->factory->createNamed('child', 'form'); - $view = $parent->add($child)->createView(); - - $this->assertEquals('test', $view['child']->vars['translation_domain']); - } - - public function testTranslationDomainFormWithNonTranslationDomainParentBeingTranslationDomain() - { - $parent = $this->factory->createNamed('parent', 'form'); - $child = $this->factory->createNamed('child', 'form', null, array('translation_domain' => 'test')); - $view = $parent->add($child)->createView(); - - $this->assertEquals('test', $view['child']->vars['translation_domain']); - } - - public function testNonTranslationDomainFormWithNonTranslationDomainParentBeingTranslationDomainDefault() - { - $parent = $this->factory->createNamed('parent', 'form'); - $child = $this->factory->createNamed('child', 'form'); - $view = $parent->add($child)->createView(); - - $this->assertEquals('messages', $view['child']->vars['translation_domain']); - } - - public function testPassLabelToView() - { - $form = $this->factory->createNamed('__test___field', 'form', null, array('label' => 'My label')); - $view = $form->createView(); - - $this->assertSame('My label', $view->vars['label']); - } - - public function testDefaultTranslationDomain() - { - $form = $this->factory->create('form'); - $view = $form->createView(); - - $this->assertSame('messages', $view->vars['translation_domain']); - } - public function testBindWithEmptyDataCreatesObjectIfClassAvailable() { $form = $this->factory->create('form', null, array( @@ -404,6 +308,7 @@ class FormTypeTest extends TypeTestCase $this->assertEquals('', $form->getName()); } + public function testSubformDoesntCallSetters() { $author = new FormTest_AuthorWithoutRefSetter(new Author()); @@ -523,14 +428,6 @@ class FormTypeTest extends TypeTestCase $this->assertSame($ref2, $author['referenceCopy']); } - public function testPassMultipartFalseToView() - { - $form = $this->factory->create('form'); - $view = $form->createView(); - - $this->assertFalse($view->vars['multipart']); - } - public function testPassMultipartTrueIfAnyChildIsMultipartToView() { $form = $this->factory->create('form'); @@ -661,4 +558,9 @@ class FormTypeTest extends TypeTestCase $this->assertSame('0', $view->vars['label']); } + + protected function getTestedType() + { + return 'form'; + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/SubmitTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/SubmitTypeTest.php new file mode 100644 index 0000000000..b85d72f568 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/SubmitTypeTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\Type; + +/** + * @author Bernhard Schussek + */ +class SubmitTypeTest extends TypeTestCase +{ + public function testCreateSubmitButtonInstances() + { + $this->assertInstanceOf('Symfony\Component\Form\SubmitButton', $this->factory->create('submit')); + } + + public function testNotClickedByDefault() + { + $button = $this->factory->create('submit'); + + $this->assertFalse($button->isClicked()); + } + + public function testNotClickedIfBoundWithNull() + { + $button = $this->factory->create('submit'); + $button->bind(null); + + $this->assertFalse($button->isClicked()); + } + + public function testClickedIfBoundWithEmptyString() + { + $button = $this->factory->create('submit'); + $button->bind(''); + + $this->assertTrue($button->isClicked()); + } + + public function testClickedIfBoundWithUnemptyString() + { + $button = $this->factory->create('submit'); + $button->bind('foo'); + + $this->assertTrue($button->isClicked()); + } +}