Skip to content
Browse files

Initial commit

  • Loading branch information...
0 parents commit 3239dd82eda4b62e75ba3cb9d2a2367fcbd238ac @Abhoryo committed Oct 25, 2011
Showing with 1,488 additions and 0 deletions.
  1. +18 −0 APYJsFormValidationBundle.php
  2. +199 −0 CacheWarmer/JsFormValidationCacheWarmer.php
  3. +85 −0 DependencyInjection/APYJsFormValidationExtension.php
  4. +32 −0 Form/Extension/FieldTypeExtension.php
  5. +65 −0 README.md
  6. +19 −0 Resources/config/services.yml
  7. +73 −0 Resources/doc/assets_warmer.md
  8. +34 −0 Resources/doc/configuration.md
  9. +26 −0 Resources/doc/constraints_warning.md
  10. +1 −0 Resources/doc/installation.md
  11. +25 −0 Resources/doc/overriding_the_bundle.md
  12. +149 −0 Resources/doc/simple_example.md
  13. +30 −0 Resources/doc/twig_function.md
  14. +19 −0 Resources/meta/LICENSE
  15. +4 −0 Resources/views/Constraints/AllValidator.js.twig
  16. +8 −0 Resources/views/Constraints/BlankValidator.js.twig
  17. +4 −0 Resources/views/Constraints/CallbackValidator.js.twig
  18. +4 −0 Resources/views/Constraints/ChoiceValidator.js.twig
  19. +4 −0 Resources/views/Constraints/CollectionValidator.js.twig
  20. +18 −0 Resources/views/Constraints/CountryValidator.js.twig
  21. +16 −0 Resources/views/Constraints/DateTimeValidator.js.twig
  22. +16 −0 Resources/views/Constraints/DateValidator.js.twig
  23. +16 −0 Resources/views/Constraints/EmailValidator.js.twig
  24. +12 −0 Resources/views/Constraints/FalseValidator.js.twig
  25. +4 −0 Resources/views/Constraints/FileValidator.js.twig
  26. +4 −0 Resources/views/Constraints/ImageValidator.js.twig
  27. +150 −0 Resources/views/Constraints/IpValidator.js.twig
  28. +18 −0 Resources/views/Constraints/LanguageValidator.js.twig
  29. +18 −0 Resources/views/Constraints/LocaleValidator.js.twig
  30. +15 −0 Resources/views/Constraints/MaxLengthValidator.js.twig
  31. +16 −0 Resources/views/Constraints/MaxValidator.js.twig
  32. +15 −0 Resources/views/Constraints/MinLengthValidator.js.twig
  33. +16 −0 Resources/views/Constraints/MinValidator.js.twig
  34. +8 −0 Resources/views/Constraints/NotBlankValidator.js.twig
  35. +8 −0 Resources/views/Constraints/NotNullValidator.js.twig
  36. +8 −0 Resources/views/Constraints/NullValidator.js.twig
  37. +15 −0 Resources/views/Constraints/RegexValidator.js.twig
  38. +23 −0 Resources/views/Constraints/SizeLengthValidator.js.twig
  39. +20 −0 Resources/views/Constraints/SizeValidator.js.twig
  40. +16 −0 Resources/views/Constraints/TimeValidator.js.twig
  41. +12 −0 Resources/views/Constraints/TrueValidator.js.twig
  42. +4 −0 Resources/views/Constraints/TypeValidator.js.twig
  43. +4 −0 Resources/views/Constraints/UniqueEntityValidator.js.twig
  44. +19 −0 Resources/views/Constraints/UrlValidator.js.twig
  45. +107 −0 Resources/views/JsFormValidation.js.twig
  46. +111 −0 Twig/Extension/JsFormValidationTwigExtension.php
18 APYJsFormValidationBundle.php
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * This file is part of the JsFormValidationBundle.
+ *
+ * (c) Abhoryo <abhoryo@free.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace APY\JsFormValidationBundle;
+
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+class APYJsFormValidationBundle extends Bundle
+{
+}
199 CacheWarmer/JsFormValidationCacheWarmer.php
@@ -0,0 +1,199 @@
+<?php
+
+/*
+ * This file is part of the JsFormValidationBundle.
+ *
+ * (c) Abhoryo <abhoryo@free.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace APY\JsFormValidationBundle\CacheWarmer;
+
+use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+use Symfony\Component\Validator\Mapping\ClassMetadata;
+use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
+use Doctrine\Common\Annotations\AnnotationReader;
+use Symfony\Component\Validator\Constraints;
+
+use Assetic\AssetWriter;
+use Assetic\AssetManager;
+use Assetic\Factory\AssetFactory;
+use Assetic\Factory\LazyAssetManager;
+use Assetic\Filter\Yui\JsCompressorFilter;
+use Assetic\FilterManager;
+use Assetic\Asset\AssetCollection;
+use Symfony\Component\Form\FormView;
+use Symfony\Component\Form\FormError;
+
+class JsFormValidationCacheWarmer implements CacheWarmerInterface
+{
+ private $container;
+
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ }
+
+ public function warmUp($cacheDir)
+ {
+ $enabled = $this->container->getParameter('apy_js_form_validation.enabled');
+ if ($enabled == true) {
+ $assets_warmer = $this->container->getParameter('apy_js_form_validation.assets_warmer');
+
+ foreach ($assets_warmer as $asset_warmer) {
+
+ if ($asset_warmer['form_fields'] == array('ALL')) {
+ $metadata = new ClassMetadata($asset_warmer['entity_class']);
+ $reflClass = $metadata->getReflectionClass();
+ $className = $reflClass->getName();
+
+ $form_fields = array();
+ foreach ($reflClass->getProperties() as $property) {
+ if ($property->getDeclaringClass()->getName() == $className) {
+ $form_fields[] = $property->getName();
+ }
+ }
+ $asset_warmer['form_fields'] = $form_fields;
+ }
+
+ $this->generateFormValidationScript($asset_warmer['entity_class'], $asset_warmer['form_name'], $asset_warmer['form_fields'], $asset_warmer['validation_groups'], true);
+ }
+ }
+ }
+
+ public function generateFormValidationScript($entityName, $formName = 'form', $formFields = array(), $formValidationGroups = array('Default'), $overwrite = false)
+ {
+ $asseticPath = $this->container->getParameter('assetic.write_to');
+ $scriptPath = $this->container->getParameter('apy_js_form_validation.script_directory');
+ $scriptFile = strtolower(str_replace(chr(92),'',$entityName)."_".$formName.'_'.implode("+",$formValidationGroups).".js");
+ $scriptRealPath = $asseticPath.$scriptPath;
+
+ if ( ! is_dir($scriptRealPath) ) {
+ mkdir($scriptRealPath, 0777, true);
+ }
+
+ if ( $overwrite || (false === file_exists($scriptRealPath.$scriptFile)) ) {
+
+ $metadata = new ClassMetadata($entityName);
+
+ // annotations constraints
+ $annotationloader = new AnnotationLoader(new AnnotationReader());
+ $annotationloader->loadClassMetadata($metadata);
+
+ // php constraints
+ // $entity = new $entityName();
+ // $entity->loadValidatorMetadata($metadata);
+
+ // yml constraints
+
+ // xml constraints
+
+ $librairyCalls = array();
+ $javascriptCalls = array();
+ $constraints = array();
+
+ foreach ($metadata->properties as $propertyName => $property) {
+ // Property presents in the form ?
+ if (in_array($propertyName, $formFields)) {
+ /* @var $property \PropertyMetadata */
+ foreach ($property->getConstraints() as $contraint) {
+ /* @var $contraint \ElementMetadata */
+
+ $contraintName = end((explode(chr(92), get_class($contraint))));
+ $contraintParameters = get_object_vars($contraint);
+
+ // Check validation groups
+ foreach ($contraintParameters['groups'] as $validationGroup) {
+ if (in_array($validationGroup, $formValidationGroups)) {
+ // Groups are no longer needed
+ unset($contraintParameters['groups']);
+
+ $librairies = "APYJsFormValidationBundle:Constraints:{$contraintName}Validator.js.twig";
+
+ if (!isset($librairyCalls[$contraintName])) {
+ $librairyCalls[$contraintName] = $librairies;
+ }
+
+ $javascriptConstraintParameters = array();
+ foreach ($contraintParameters as $variable => $value) {
+ if (is_array($value)) {
+ $value = json_encode($value);
+ }
+ else {
+ // regex
+ if (stristr('pattern', $variable) === FALSE) {
+ $value = "'".$value."'";
+ }
+ }
+
+ $javascriptConstraintParameters[] = "$variable:$value";
+ }
+
+ $javascriptConstraintParameters = '{'.join(', ',$javascriptConstraintParameters).'}';
+
+ $constraints[$formName."_".$propertyName][] = array(
+ 'name' => $contraintName,
+ 'parameters' => $javascriptConstraintParameters
+ );
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Retrieve validation mode from configuration
+ $check_modes = array('submit' => false, 'blur' => false);
+ switch($this->container->getParameter('apy_js_form_validation.check_mode')) {
+ default:
+ case 'submit':
+ $check_modes['submit'] = true;
+ break;
+ case 'blur':
+ $check_modes['blur'] = true;
+ break;
+ case 'both':
+ $check_modes = array('submit' => true, 'blur' => true);
+ break;
+ }
+
+ // Render the validation script
+ $validation_bundle = $this->container->getParameter('apy_js_form_validation.validation_bundle');
+ $template = $this->container->get('templating')->render($validation_bundle.'::JsFormValidation.js.twig',
+ array(
+ 'formName'=>$formName,
+ 'fieldConstraints'=>$constraints,
+ 'librairyCalls'=>$librairyCalls,
+ 'check_modes'=>$check_modes
+ )
+ );
+
+ // Create asset and compress it
+ $asset = new AssetCollection();
+ $asset->setContent($template);
+ $asset->setTargetPath($scriptRealPath.$scriptFile);
+
+ // Js compression
+ if ($this->container->getParameter('apy_js_form_validation.yui_js')) {
+ $yui = new JsCompressorFilter($this->container->getParameter('assetic.filter.yui_js.jar'), $this->container->getParameter('assetic.java.bin'));
+ $yui->filterDump($asset);
+ }
+
+ if (false === @file_put_contents($asset->getTargetPath(), $asset->getContent())) {
+ throw new \RuntimeException('Unable to write file '.$asset->getTargetPath());
+ }
+ }
+
+ return $scriptPath.$scriptFile;
+ }
+
+ public function isOptional()
+ {
+ return true;
+ }
+}
85 DependencyInjection/APYJsFormValidationExtension.php
@@ -0,0 +1,85 @@
+<?php
+
+/*
+ * This file is part of the JsFormValidationBundle.
+ *
+ * (c) Abhoryo <abhoryo@free.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace APY\JsFormValidationBundle\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
+use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+use Symfony\Component\Config\Definition\Processor;
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+use Symfony\Component\Config\FileLocator;
+
+class APYJsFormValidationExtension extends Extension
+{
+ public function load(array $configs, ContainerBuilder $container)
+ {
+ $processor = new Processor();
+ $config = $processor->process($this->getConfigTree(), $configs);
+
+ $container->setParameter('apy_js_form_validation.enabled', $config['enabled']);
+ $container->setParameter('apy_js_form_validation.yui_js', $config['yui_js']);
+ $container->setParameter('apy_js_form_validation.check_mode', $config['check_mode']);
+ $container->setParameter('apy_js_form_validation.script_directory', $config['script_directory']);
+ $container->setParameter('apy_js_form_validation.validation_bundle', $config['validation_bundle']);
+ $container->setParameter('apy_js_form_validation.assets_warmer', $config['assets_warmer']);
+
+ $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
+ $loader->load('services.yml');
+ }
+
+ private function getConfigTree()
+ {
+ $tb = new TreeBuilder();
+ return $tb
+ ->root('apy_js_form_validation')
+ ->children()
+ ->booleanNode('enabled')->defaultValue(true)->end()
+ ->booleanNode('yui_js')->defaultValue(false)->end()
+ ->scalarNode('check_mode')
+ ->defaultValue('both')
+ ->validate()
+ ->ifNotInArray(array('submit', 'blur', 'both'))
+ ->thenInvalid('The %s mode is not supported')
+ ->end()
+ ->end()
+ ->scalarNode('validation_bundle')->defaultValue('APYJsFormValidationBundle')->end()
+ ->scalarNode('script_directory')->defaultValue('/bundles/jsformvalidation/js/')->end()
+ ->arrayNode('assets_warmer')
+ ->canBeUnset()
+ ->prototype('array')
+ ->children()
+ ->scalarNode('entity_class')->isRequired()->end()
+ ->scalarNode('form_name')->defaultValue('form')->end()
+ ->arrayNode('form_fields')
+ ->defaultValue(array('ALL'))
+ ->beforeNormalization()
+ ->ifTrue(function($v){ return !is_array($v); })
+ ->then(function($v){ return array($v); })
+ ->end()
+ ->prototype('scalar')->end()
+ ->end()
+ ->arrayNode('validation_groups')
+ ->defaultValue(array('Default'))
+ ->beforeNormalization()
+ ->ifTrue(function($v){ return !is_array($v); })
+ ->then(function($v){ return array($v); })
+ ->end()
+ ->prototype('scalar')->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->buildTree();
+ }
+}
32 Form/Extension/FieldTypeExtension.php
@@ -0,0 +1,32 @@
+<?php
+
+/*
+ * This file is part of the JsFormValidationBundle.
+ *
+ * (c) Abhoryo <abhoryo@free.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace APY\JsFormValidationBundle\Form\Extension;
+
+use Symfony\Component\Form\AbstractTypeExtension;
+use Symfony\Component\Form\FormInterface;
+use Symfony\Component\Form\FormView;
+
+class FieldTypeExtension extends AbstractTypeExtension
+{
+ public function getExtendedType()
+ {
+ return 'field';
+ }
+
+ public function buildView(FormView $view, FormInterface $form)
+ {
+ // Add validation groups to the view
+ if ($form->hasAttribute('validation_groups')) {
+ $view->set('validation_groups' , $form->getAttribute('validation_groups'));
+ }
+ }
+}
65 README.md
@@ -0,0 +1,65 @@
+Getting Started With JsFormValidationBundle
+===========================================
+
+This bundle generate automatically a script to perform validations of a form in javascript.
+
+It use the same constraints defined with annotations for your entity or your document.
+
+This bundle is `g11n` compatible.(i18n + L10n)
+
+**Compatibility**: Symfony 2.0+
+
+## Prerequisite
+
+* The JavaScript framework [jQuery](http://jquery.com/) is recommended.
+* BazingaExposeTranslationBundle is mandatory. This bundle compute and translate messages in javascript.
+
+## Installation
+
+Please follow the steps given [here](https://github.com/APY/APYJsFormValidationBundle/blob/master/Resources/doc/installation.md) to install this bundle.
+
+## Usage
+
+This bundle is really easy to use. All you need is to call a twig function in your template.
+
+`{{ JSFV(form) }}`
+
+Template of a simple form:
+
+```xml
+<!-- MyProjectMyBundle:Default:index.html.twig -->
+
+<!-- Include prerequisite librairies and bundles -->
+<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
+<script type="text/javascript" src="{{ asset('bundles/bazingaexposetranslation/js/translation.js') }}"></script>
+<script type="text/javascript" src="{{ url('bazinga_exposetranslation_js', { 'domain_name': 'validators' }) }}"></script>
+
+<!-- Call JsFormValidationBundle -->
+{{ JSFV(form) }}
+
+<!-- Display the form -->
+<form action="{{ path('myform') }}" method="post" {{ form_enctype(form) }}>
+ {{ form_widget(form) }}
+ <input type="submit" />
+</form>
+
+```
+
+See a complet example [here](https://github.com/APY/APYJsFormValidationBundle/blob/master/Resources/doc/simple_example.md).
+
+The following documents are available:
+
+1. [Installation](https://github.com/APY/APYJsFormValidationBundle/blob/master/Resources/doc/installation.md)
+2. [Simple Exemple](https://github.com/APY/APYJsFormValidationBundle/blob/master/Resources/doc/simple_example.md)
+3. [Twig Function](https://github.com/APY/APYJsFormValidationBundle/blob/master/Resources/doc/twig_function.md)
+4. [Configuration](https://github.com/APY/APYJsFormValidationBundle/blob/master/Resources/doc/configuration.md)
+5. [Assets warmer](https://github.com/APY/APYJsFormValidationBundle/blob/master/Resources/doc/assets_warmer.md)
+6. [Overriding the bundle](https://github.com/APY/APYJsFormValidationBundle/blob/master/Resources/doc/overriding_the_bundle.md)
+7. [Constraints warning](https://github.com/APY/APYJsFormValidationBundle/blob/master/Resources/doc/constraints_warning.md)
+
+## TODO
+
+* Script all possible constraints
+* Manage php, yml and xml defined constraints
+* Minify script with other compressor ?
+* Implement validation script with other javascript framework ?
19 Resources/config/services.yml
@@ -0,0 +1,19 @@
+services:
+ twig.extension.jsformvalidation:
+ class: APY\JsFormValidationBundle\Twig\Extension\JsFormValidationTwigExtension
+ tags:
+ - { name: twig.extension }
+ arguments:
+ - @service_container
+
+ kernel.cache_warmer.jsformvalidation:
+ class: APY\JsFormValidationBundle\CacheWarmer\JsFormValidationCacheWarmer
+ tags:
+ - { name: kernel.cache_warmer }
+ arguments:
+ - @service_container
+
+ form.type_extension.validation_groups:
+ class: APY\JsFormValidationBundle\Form\Extension\FieldTypeExtension
+ tags:
+ - { name: form.type_extension, alias: field }
73 Resources/doc/assets_warmer.md
@@ -0,0 +1,73 @@
+Assets warmer
+=============
+
+During the cache warming, you can warm the assets of your forms and then use them directly in your templates.
+
+To warm assets, you have to define them in the configuration file of your application.
+
+```yml
+# app/config.yml
+
+apy_js_form_validation:
+ script_directory: /bundle/jsformvalidation/js/
+ assets_warmer:
+ - { entity_class: Acme\StoreBundle\Entity\Product, form_name: my_form, validation_groups: group1, form_fields: ['my_field1', 'my_field2'] }
+ - { entity_class: Acme\StoreBundle\Entity\Product, validation_groups: { group1, group2 } }
+ - { entity_class: Acme\StoreBundle\Entity\Product, form_name: my_form }
+```
+
+script_directory is optional, default value is `/bundle/jsformvalidation/js/`
+
+entity_class argument is required.
+form_name argument is optional, default value is `form`
+validation_groups is optional, default value is `Default`. It can be a string (one group) or an array of groups.
+form_fields is optional, default value is `ALL`. It can be a string (one field) or an array of fields. Set this array as your list of fields in your form.
+
+In controller: `$this->createFormBuilder($product)->add('name', 'text')->add('price', 'money', array('currency' => 'USD'))`
+
+In configuration: `form_fields: ['name', 'price']`
+
+------
+
+Here is the pattern of a generated file:
+
+`script_directory.entityClass_formName_validationGroup1+validationGroup2.js`
+
+With these parameters:
+
+script_directory: /bundle/jsformvalidation/js/
+entityClass: MyProject\MyBundle\Entity\Product
+formName: my_form
+validationGroups: group1, group2
+
+will turn into:
+
+`/bundle/jsformvalidation/js/MyProjectMyBundleEntityProduct_my_form_group1+group2.js`
+
+If there aren't validation groups defined, the `Default` group is used.
+
+It will turn into:
+
+`/bundle/jsformvalidation/js/MyProjectMyBundleEntityProduct_my_form_Default.js`
+
+When scripts are generated, you can use them is your templates.
+
+```xml
+<!-- src/MyProject/MyBundle/Resources/views/Default/index.html.twig -->
+<!-- MyProjectMyBundle:Default:index.html.twig -->
+
+<!-- Include prerequisite librairies and bundles -->
+<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
+<script type="text/javascript" src="{{ asset('bundles/bazingaexposetranslation/js/translation.js') }}"></script>
+<script type="text/javascript" src="{{ url('bazinga_exposetranslation_js', { 'domain_name': 'validators' }) }}"></script>
+<script type="text/javascript" src="/bundle/jsformvalidation/js/MyProjectMyBundleEntityProduct_my_form_Default.js"></script>
+
+<!-- Display the form -->
+<form action="{{ path('storeform') }}" method="post" {{ form_enctype(form) }}>
+ {{ form_widget(form) }}
+ <input type="submit" />
+</form>
+
+```
+
+**Note:** If you use directly the path of your scripts, the enabled option in configuration has no effect.
34 Resources/doc/configuration.md
@@ -0,0 +1,34 @@
+Reference
+=========
+
+```yml
+# app/config.yml
+
+apy_js_form_validation:
+ enabled: true
+ yui_js: false
+ check_mode: both
+ validation_bundle: APYJsFormValidationBundle
+ script_directory: /bundle/jsformvalidation/js/
+ assets_warmer:
+ - { entity_class: MyProject\MyBundle\Entity\Product, validation_groups: {group1, group2}, form_name: my_form, form_fields: ['my_field1', 'my_field2'] }
+```
+
+* `enabled` is optional (Default: `true`). Set to `false` disable all javascript form validations if you use the Twig function.
+
+* `yui_js` is optional (Default: `false`). Set to `true` enable yui compressor. `yui_js` assetic filter have to be defined.
+
+* `check_mode` is optional (Default: `both`). Mode of the validation.
+Set to `submit` enable a validation of a form on the submit action.
+Set to `blur` enable a validation of a field of a form when the field lost the focus.
+Set to `both` enable both validations of a form.
+
+* `bundle` is optional (Default: `APYJsFormValidationBundle`).
+You can override the default implementation of the validation script in your bundle.
+
+Here is the default template of the script for validation.
+APYJsFormValidationBundle::JsFormValidation.js.twig`
+
+* `script_directory` is optional (Default: `/bundle/jsformvalidation/js/`). Define where the script will be generated.
+
+* `assets_warmer` is optional (Default: `~`). See [Assets warmer](https://github.com/APY/APYJsFormValidationBundle/blob/master/Resources/doc/assets_warmer.md)
26 Resources/doc/constraints_warning.md
@@ -0,0 +1,26 @@
+# Email
+checkMX is not perfomed
+
+# Url
+JsFormValidation constraint checks for real IPv4
+
+# File + Image
+Javascript can't manipulate remote file directly
+
+# Callback
+Not implemented yet
+
+# Valid
+Not implemented yet
+
+# UniqueEntity
+Not implemented yet
+
+# All
+Not implemented yet
+
+# Collection
+Not implemented yet
+
+# Choice
+Not implemented yet
1 Resources/doc/installation.md
@@ -0,0 +1 @@
+Installation
25 Resources/doc/overriding_the_bundle.md
@@ -0,0 +1,25 @@
+Overriding the bundle
+=====================
+
+## Validation
+
+You can define the bundle of the validation script in configuration.
+
+```yml
+# app/config.yml
+
+apy_js_form_validation:
+ validation_bundle: MyProjectMyBundle
+```
+
+Validation script is defined in a template file so you can also overriding it like a normal template.
+
+`app/Resources/JsFormValidationBundle/views/JsFormValidation.js.twig`
+
+## Constraints
+
+The contraints are pure javascript but you can overriding them.
+Constraints script are defined in template files so you can overriding them like a normal template.
+
+`app/Resources/JsFormValidationBundle/views/Constraints/MinValidator.js.twig`
+`app/Resources/JsFormValidationBundle/views/Constraints/NotBlankValidator.js.twig`
149 Resources/doc/simple_example.md
@@ -0,0 +1,149 @@
+A Complet Simple Example
+========================
+
+## Step 1 : Create a bundle
+
+In this example, we create a bundle `MyBundle` in the directory `src/MyProject`
+
+```bash
+$ php app/console generate:bundle --namespace=MyProject/MyBundle --format=yml
+```
+
+## Step 2: Create Routes
+
+Add these two routes in the routing file of the bundle.
+
+```yml
+# src/MyProject/MyBundle/Resources/config/routing.yml
+myform:
+ pattern: /myform
+ defaults: { _controller: MyProjectMyBundle:Default:form }
+
+myformsuccess:
+ pattern: /myformsuccess
+ defaults: { _controller: MyProjectMyBundle:Default:success }
+```
+
+## Step 3: Create an product entity with constraints
+
+```php
+<?php
+// src/MyProject/MyBundle/Entity/Product.php
+namespace MyProject\MyBundle\Entity;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class Product
+{
+ /**
+ * @Assert\NotBlank()
+ */
+ protected $name;
+
+ /**
+ * @Assert\NotBlank()
+ * @Assert\Min(limit=20)
+ */
+ protected $price;
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function setPrice($price)
+ {
+ $this->price = $price;
+ }
+
+ public function getPrice()
+ {
+ return $this->price;
+ }
+}
+```
+
+## Step 4: Create a template
+
+```xml
+<!-- src/MyProject/MyBundle/Resources/views/Default/index.html.twig -->
+<!-- MyProjectMyBundle:Default:index.html.twig -->
+
+<!-- Include prerequisite librairies and bundles -->
+<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
+<script type="text/javascript" src="{{ asset('bundles/bazingaexposetranslation/js/translation.js') }}"></script>
+<script type="text/javascript" src="{{ url('bazinga_exposetranslation_js', { 'domain_name': 'validators' }) }}"></script>
+
+<!-- Call JsFormValidationBundle -->
+
+{{ JSFV(form) }}
+
+<!-- Display the form -->
+<form action="{{ path('storeform') }}" method="post" {{ form_enctype(form) }}>
+ {{ form_widget(form) }}
+ <input type="submit" />
+</form>
+
+```
+
+## Step 5: Create the Controller
+
+```php
+<?php
+// src/MyProject/MyBundle/Controller/Default.php
+namespace MyProject\MyBundle\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Component\HttpFoundation\Response;
+use MyProject\MyBundle\Entity\Product;
+
+class DefaultController extends Controller
+{
+ public function formAction()
+ {
+ $product = new Product();
+ $form = $this->createFormBuilder($product)
+ ->add('name', 'text')
+ ->add('price', 'money', array('currency' => 'USD'))
+ ->getForm();
+
+ $request = $this->get('request');
+ if ($request->getMethod() == 'POST') {
+ $form->bindRequest($request);
+
+ if ($form->isValid()) {
+ return $this->redirect($this->generateUrl('myformsuccess'));
+ }
+ }
+
+ return $this->render('MyProjectMyBundle:Default:index.html.twig', array( 'form' => $form->createView() ));
+ }
+
+ public function successAction()
+ {
+ return new Response('Product posted');
+ }
+}
+```
+
+## Step 6: Clear the cache
+
+Execute this command to clear the cache:
+
+```bash
+$ php app/console cache:clear
+```
+
+## Step 7: Usage
+
+Go to your form page. ( ie : http://localhost/app_dev.php/myform )
+
+* Click in and out of a field, validation messages appear.
+* Put some text in the name field and the number 10 in the price field.
+* Submit the form.
+* A validation message for the price field appears. The submit action is prevented by the bundle when all fields are not correctly filled.
30 Resources/doc/twig_function.md
@@ -0,0 +1,30 @@
+Twig function
+==========================
+
+The twig function JSFV generate the script and returns the path of the script inside a script balise.
+
+`{{ JSFV(form) }}`
+
+will turn into:
+
+`<script type="text/javascript" src="/bundle/jsformvalidation/js/MyProjectMyBundleEntityProduct_my_form_Default.js"></script>`
+
+The script isn't regenerate if the script already exists.
+
+**Note: If the bundle is disabled, this Twig function returns nothing.
+
+---------
+
+JSFV function accepts a boolean argument. Sets to true, the Twig function displays only the path of the script.
+
+`{{ JSFV(form, true) }}`
+
+will turn into:
+
+`/bundle/jsformvalidation/js/MyProjectMyBundleEntityProduct_my_form_Default.js`
+
+So you can use this too:
+
+`<script type="text/javascript" src="{{ asset( JSFV(form, true) ) }}"></script>`
+
+**Note: If the bundle is disabled, this Twig function returns `/bundle/jsformvalidation/js/no_jsfv_script.js`.
19 Resources/meta/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011-2012 Abhoryo
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
4 Resources/views/Constraints/AllValidator.js.twig
@@ -0,0 +1,4 @@
+function All(value, params)
+{
+ return true;
+}
8 Resources/views/Constraints/BlankValidator.js.twig
@@ -0,0 +1,8 @@
+function Blank(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ return getComputeMessage(params.message);
+}
4 Resources/views/Constraints/CallbackValidator.js.twig
@@ -0,0 +1,4 @@
+function Callback(value, params)
+{
+ return true;
+}
4 Resources/views/Constraints/ChoiceValidator.js.twig
@@ -0,0 +1,4 @@
+Choice: function(value, params)
+{
+ return true;
+}
4 Resources/views/Constraints/CollectionValidator.js.twig
@@ -0,0 +1,4 @@
+function Collection(value, params)
+{
+ return true;
+}
18 Resources/views/Constraints/CountryValidator.js.twig
@@ -0,0 +1,18 @@
+function Country(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ value = String(value);
+
+ var countries = {{ getCountries()|raw }};
+
+ for (key in countries) {
+ if (countries[key] == value) {
+ return true;
+ }
+ }
+
+ return getComputeMessage(params.message);
+}
16 Resources/views/Constraints/DateTimeValidator.js.twig
@@ -0,0 +1,16 @@
+function DateTime(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ value = String(value);
+
+ var pattern = /^(\d{4})-(\d{2})-(\d{2}) (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/;
+
+ if ( pattern.test(value) ) {
+ return true;
+ }
+
+ return getComputeMessage(params.message);
+}
16 Resources/views/Constraints/DateValidator.js.twig
@@ -0,0 +1,16 @@
+function Date(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ value = String(value);
+
+ var pattern = /^(\d{4})-(\d{2})-(\d{2})$/;
+
+ if ( pattern.test(value) ) {
+ return true;
+ }
+
+ return getComputeMessage(params.message);
+}
16 Resources/views/Constraints/EmailValidator.js.twig
@@ -0,0 +1,16 @@
+function Email(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ value = String(value);
+
+ var pattern = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})$/;
+
+ if ( pattern.test(value) ) {
+ return true;
+ }
+
+ return getComputeMessage(params.message);
+}
12 Resources/views/Constraints/FalseValidator.js.twig
@@ -0,0 +1,12 @@
+function False(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ if (false === value || 0 === value || '0' === value) {
+ return true;
+ }
+
+ return getComputeMessage(params.message);
+}
4 Resources/views/Constraints/FileValidator.js.twig
@@ -0,0 +1,4 @@
+function File(value, params)
+{
+ return true;
+}
4 Resources/views/Constraints/ImageValidator.js.twig
@@ -0,0 +1,4 @@
+function Image(value, params)
+{
+ return true;
+}
150 Resources/views/Constraints/IpValidator.js.twig
@@ -0,0 +1,150 @@
+function Ip(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ /*
+ * Private functions
+ * Inspired by php_filter_validate_ip function
+ * Copyright (c) 1997-2011 The PHP Group
+ */
+ var isIPv4 = function (value) {
+ var ip = value.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
+ return ip != null && ip[1] <= 255 && ip[2] <= 255 && ip[3] <= 255 && ip[4] <= 255;
+ };
+
+ var isIPv4_no_priv = function (value) {
+ var ip = value.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
+ if ( (ip[0] == 10)
+ || (ip[0] == 172 && (ip[1] >= 16 && ip[1] <= 31))
+ || (ip[0] == 192 && ip[1] == 168) )
+ {
+ return false;
+ }
+ else {
+ return true;
+ }
+ };
+
+ var isIPv4_no_res = function (value) {
+ var ip = value.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
+ if ( (ip[0] == 0)
+ || (ip[0] == 128 && ip[1] == 0)
+ || (ip[0] == 191 && ip[1] == 255)
+ || (ip[0] == 169 && ip[1] == 254)
+ || (ip[0] == 192 && ip[1] == 0 && ip[2] == 2)
+ || (ip[0] == 127 && ip[1] == 0 && ip[2] == 0 && ip[3] == 1)
+ || (ip[0] >= 224 && ip[0] <= 255) )
+ {
+ return false;
+ }
+ else {
+ return true;
+ }
+ };
+
+ var isIPv6 = function (ip) {
+{% raw %}
+ var pattern = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/
+{% endraw %}
+ return pattern.test(ip);
+ };
+
+ var isIPv6_no_priv = function (value) {
+ var vallow = value.toLowerCase();
+ if ( value.length >=2 && ( 'fc' == vallow.substr(0, 2) || 'fd' == vallow.substr(0, 2) ) ) {
+ return false;
+ }
+ else {
+ return true;
+ }
+ };
+
+ var isIPv6_no_res = function (value) {
+ var vallow = value.toLowerCase();
+ var vallen = value.length;
+ switch (vallen) {
+ case 0:
+ case 1: break;
+ case 2:
+ if ( '::' == vallow ) {
+ return false;
+ }
+ break;
+ case 3:
+ if ( '::1' == vallow || '5f:' == vallow ) {
+ return false;
+ }
+ break;
+ default:
+ if (vallen >= 5) {
+ if ( "fe8" == vallow.substr(0, 3)
+ || "fe9" == vallow.substr(0, 3)
+ || "fea" == vallow.substr(0, 3)
+ || "feb" == vallow.substr(0, 3) )
+ {
+ return false;
+ }
+ }
+ if ( (vallen >= 9 && "2001:0db8" == vallow.substr(0, 9))
+ || (vallen >= 2 && "5f" == vallow.substr(0, 2))
+ || (vallen >= 4 && "3ff3" == vallow.substr(0, 4))
+ || (vallen >= 8 && "2001:001" == vallow.substr(0, 8)) )
+ {
+ return false;
+ }
+ break;
+ }
+ };
+
+ value = String(value);
+
+ var result = false;
+ switch (params.version) {
+ case '4':
+ result = isIPv4(value);
+ break;
+ case '6':
+ result = isIPv6(value);
+ break;
+ case 'all':
+ result = isIPv4(value) || isIPv6(value);
+ break;
+ case '4_no_priv':
+ result = isIPv4(value) && isIPv4_no_priv(value);
+ break;
+ case '6_no_priv':
+ result = isIPv6(value) && isIPv6_no_priv(value);
+ break;
+ case 'all_no_priv':
+ result = ( (isIPv4(value) && isIPv4_no_priv(value))
+ || (isIPv6(value) && isIPv6_no_priv(value)) );
+ break;
+ case '4_no_res':
+ result = isIPv4(value) && isIPv4_no_res(value);
+ break;
+ case '6_no_res':
+ result = isIPv6(value) && isIPv6_no_res(value);
+ break;
+ case 'all_no_res':
+ result = ( (isIPv4(value) && isIPv4_no_res(value))
+ || (isIPv6(value) && isIPv6_no_res(value)) );
+ break;
+ case '4_public':
+ result = isIPv4(value) && isIPv4_no_priv(value) && isIPv4_no_res(value);
+ break;
+ case '6_public':
+ result = isIPv6(value) && isIPv6_no_priv(value) && isIPv6_no_res(value);
+ break;
+ case 'all_public':
+ result = ( (isIPv4(value) && isIPv4_no_priv(value) && isIPv4_no_res(value))
+ || (isIPv6(value) && isIPv6_no_priv(value) && isIPv6_no_res(value)) );
+ break;
+ default:
+ result = false
+ break;
+ }
+
+ return result ? result : getComputeMessage(params.message);
+}
18 Resources/views/Constraints/LanguageValidator.js.twig
@@ -0,0 +1,18 @@
+function Language(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ value = String(value);
+
+ var languages = {{ getLanguages()|raw }};
+
+ for (key in languages) {
+ if (languages[key] == value) {
+ return true;
+ }
+ }
+
+ return getComputeMessage(params.message);
+}
18 Resources/views/Constraints/LocaleValidator.js.twig
@@ -0,0 +1,18 @@
+function Locale(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ value = String(value);
+
+ var locales = {{ getLocales()|raw }};
+
+ for (key in locales) {
+ if (locales[key] == value) {
+ return true;
+ }
+ }
+
+ return getComputeMessage(params.message);
+}
15 Resources/views/Constraints/MaxLengthValidator.js.twig
@@ -0,0 +1,15 @@
+function MaxLength(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ value = String(value);
+
+ // Charset ?
+ if (value.length > parseInt(params.limit)) {
+ return getComputeMessage(params.message, { 'limit' : params.limit } );
+ }
+
+ return true;
+}
16 Resources/views/Constraints/MaxValidator.js.twig
@@ -0,0 +1,16 @@
+function Max(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ if (!((typeof(value) === 'number' || typeof(value) === 'string') && !isNaN(value))) {
+ return getComputeMessage(params.invalidMessage, { 'limit' : params.limit } );
+ }
+
+ if (parseInt(value) > parseInt(params.limit)) {
+ return getComputeMessage(params.message, { 'limit' : params.limit } );
+ }
+
+ return true;
+}
15 Resources/views/Constraints/MinLengthValidator.js.twig
@@ -0,0 +1,15 @@
+function MinLength(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ value = String(value);
+
+ // Charset ?
+ if (value.length < parseInt(params.limit)) {
+ return getComputeMessage(params.message, { 'limit' : params.limit } );
+ }
+
+ return true;
+}
16 Resources/views/Constraints/MinValidator.js.twig
@@ -0,0 +1,16 @@
+function Min(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ if (!((typeof(value) === 'number' || typeof(value) === 'string') && !isNaN(value))) {
+ return getComputeMessage(params.invalidMessage, { 'limit' : params.limit } );
+ }
+
+ if (parseInt(value) < parseInt(params.limit)) {
+ return getComputeMessage(params.message, { 'limit' : params.limit } );
+ }
+
+ return true;
+}
8 Resources/views/Constraints/NotBlankValidator.js.twig
@@ -0,0 +1,8 @@
+function NotBlank(value, params)
+{
+ if (isDefined(value)) {
+ return getComputeMessage(params.message);
+ }
+
+ return true;
+}
8 Resources/views/Constraints/NotNullValidator.js.twig
@@ -0,0 +1,8 @@
+function NotNull(value, params)
+{
+ if (typeof(value) == 'undefined' || value === null) {
+ return getComputeMessage(params.message);
+ }
+
+ return true;
+}
8 Resources/views/Constraints/NullValidator.js.twig
@@ -0,0 +1,8 @@
+function Null(value, params)
+{
+ if (typeof(value) == 'undefined' || value === null) {
+ return true;
+ }
+
+ return getComputeMessage(params.message);
+}
15 Resources/views/Constraints/RegexValidator.js.twig
@@ -0,0 +1,15 @@
+function Regex(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ value = String(value);
+
+ // xor with converted boolean values
+ if ( !params.match != !params.pattern.test(value) ) {
+ return getComputeMessage(params.message);
+ }
+
+ return true;
+}
23 Resources/views/Constraints/SizeLengthValidator.js.twig
@@ -0,0 +1,23 @@
+function SizeLength(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ value = String(value);
+
+ // Charset ?
+ if (parseInt(params.min) == parseInt(params.max) && value.length != parseInt(params.max)) {
+ return getComputeMessage(params.exactMessage, { 'limit' : params.max } );
+ }
+
+ if (value.length > parseInt(params.max)) {
+ return getComputeMessage(params.maxMessage, { 'limit' : params.max } );
+ }
+
+ if (value.length < parseInt(params.min)) {
+ return getComputeMessage(params.minMessage, { 'limit' : params.min } );
+ }
+
+ return true;
+}
20 Resources/views/Constraints/SizeValidator.js.twig
@@ -0,0 +1,20 @@
+function Size(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ if (!(typeof(value) === 'number' || typeof(value) === 'string') && !isNaN(value)) {
+ return getComputeMessage(params.invalidMessage, { 'limit' : params.limit } );
+ }
+
+ if (parseInt(value) > parseInt(params.max)) {
+ return getComputeMessage(params.maxMessage, { 'limit' : params.max } );
+ }
+
+ if (parseInt(value) < parseInt(params.min)) {
+ return getComputeMessage(params.minMessage, { 'limit' : params.min } );
+ }
+
+ return true;
+}
16 Resources/views/Constraints/TimeValidator.js.twig
@@ -0,0 +1,16 @@
+function Time(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ value = String(value);
+
+ var pattern = /^([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/;
+
+ if ( pattern.test(value) ) {
+ return true;
+ }
+
+ return getComputeMessage(params.message);
+}
12 Resources/views/Constraints/TrueValidator.js.twig
@@ -0,0 +1,12 @@
+function True(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ if (true === value || 1 === value || '1' === value) {
+ return true;
+ }
+
+ return getComputeMessage(params.message);
+}
4 Resources/views/Constraints/TypeValidator.js.twig
@@ -0,0 +1,4 @@
+Type: function(value, params)
+{
+ return true;
+}
4 Resources/views/Constraints/UniqueEntityValidator.js.twig
@@ -0,0 +1,4 @@
+UniqueEntity: function(value, params)
+{
+ return true;
+}
19 Resources/views/Constraints/UrlValidator.js.twig
@@ -0,0 +1,19 @@
+function Url(value, params)
+{
+ if (isDefined(value)) {
+ return true;
+ }
+
+ value = String(value);
+
+ // A domain name or a IPv4 or a IPv6 + a port(optional) and a /, nothing or a / with something
+ var protocols = params.protocols.join('|');
+{% raw %}
+ var pattern = new RegExp("^("+protocols+")://((([a-zA-Z0-9\\-])+\\.)+([a-zA-Z0-9]{2,4})|(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|\\[((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|(([0-9A-Fa-f]{1,4}:){0,5}:((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|(::([0-9A-Fa-f]{1,4}:){0,5}((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))\\])(:[0-9]+)?(/?|/\\S+)$", "gi");
+{% endraw %}
+ if ( pattern.test(value) ) {
+ return true;
+ }
+
+ return getComputeMessage(params.message);
+}
107 Resources/views/JsFormValidation.js.twig
@@ -0,0 +1,107 @@
+/*
+ * This file is part of the JsFormValidationBundle.
+ *
+ * (c) Abhoryo <abhoryo@free.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+var JsFormValidation = JsFormValidation || {};
+
+(function(JSFV, $) {
+ $.extend(JSFV, (function() {
+ function getComputeMessage(key, placeholders, number) {
+ {% raw %}
+ ExposeTranslation.placeHolderPrefix = '{{ ';
+ ExposeTranslation.placeHolderSuffix = ' }}';
+ {% endraw %}
+ // Default translations
+ if (!ExposeTranslation.has('validators:'+key)) {
+ ExposeTranslation.add('validators:'+key, key);
+ }
+
+ return ExposeTranslation.get('validators:'+key, placeholders, number);
+ }
+
+ function isDefined(value) {
+ return (typeof(value) == 'undefined' || null === value || '' === value);
+ }
+ function checkError(field, checkFunction, parameters) {
+ // Remove old errors on the field
+ field.parent().find('.error_list').remove();
+
+ errorMessage = checkFunction(field.val(), parameters);
+ if (errorMessage != true) {
+ addError(field, errorMessage);
+
+ return false;
+ }
+
+ return true;
+ }
+ function addError(field, errorMessage) {
+ // Add errors block
+ if (field.prev('ul').length==0) {
+ field.before("<ul class='error_list'></ul>");
+ }
+
+ // Add error
+ field.prev('ul').append("<li>"+errorMessage+"</li>");
+ }
+{% for librairyCall in librairyCalls %}
+{% include librairyCall %}
+
+{% endfor %}
+
+ return {
+{% for fieldName, constraints in fieldConstraints %}
+ check_{{ fieldName }}: function() {
+{% if constraints|length == 1 %}
+{% for constraint in constraints %}
+ return checkError($('#{{ fieldName }}'), {{ constraint.name }}, {{ constraint.parameters|raw }} );
+{% endfor %}
+{% else %}
+ result = true;
+{% for constraint in constraints %}
+ result = result && checkError($('#{{ fieldName }}'), {{ constraint.name }}, {{ constraint.parameters|raw }} );
+{% endfor %}
+ return result;
+{% endif %}
+ },
+{% endfor %}
+ };
+ })());
+})(JsFormValidation, jQuery);
+
+
+$(function() {
+
+{% if check_modes.submit %}
+ // On submit checks
+ var form_div = $('div#{{ formName }}');
+
+ if (form_div.length==1) {
+ var form = form_div.parent();
+
+ form.submit(function() {
+ var submitForm = true;
+
+{% for fieldName, constraints in fieldConstraints %}
+ submitForm = submitForm && JsFormValidation.check_{{ fieldName }}();
+{% endfor %}
+ return submitForm;
+ });
+ }
+{% endif %}
+
+{% if check_modes.blur %}
+ // On blur checks
+{% for fieldName, constraints in fieldConstraints %}
+ $('#{{ fieldName }}').blur(function() {
+ JsFormValidation.check_{{ fieldName }}();
+ });
+
+{% endfor %}
+{% endif %}
+});
111 Twig/Extension/JsFormValidationTwigExtension.php
@@ -0,0 +1,111 @@
+<?php
+
+/*
+ * This file is part of the JsFormValidationBundle.
+ *
+ * (c) Abhoryo <abhoryo@free.fr>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace APY\JsFormValidationBundle\Twig\Extension;
+
+use Symfony\Component\Form\FormView;
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use APY\JsFormValidationBundle\CacheWarmer\JsFormValidationCacheWarmer;
+
+class JsFormValidationTwigExtension extends \Twig_Extension
+{
+
+ private $container;
+
+ public function __construct(ContainerInterface $container)
+ {
+ $this->container = $container;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFunctions()
+ {
+ return array(
+ 'JSFV' => new \Twig_Function_Method($this, 'JsFormValidationFunction', array('is_safe' => array('all'))),
+ 'getCountries' => new \Twig_Function_Method($this, 'getCountries'),
+ 'getLanguages' => new \Twig_Function_Method($this, 'getLanguages'),
+ 'getLocales' => new \Twig_Function_Method($this, 'getLocales'),
+ );
+ }
+
+ public function JsFormValidationFunction(FormView $view, $getScriptPath = false)
+ {
+ $enabled = $this->container->getParameter('apy_js_form_validation.enabled');
+
+ if ($enabled == true) {
+ // retrieve parameters from the form
+ $entityName = get_class($view->get('value'));
+ $formName = $view->get('name');
+ $validationGroups = $view->get('validation_groups', array('Default'));
+ $formFields = array_keys($view->getChildren());
+
+ // Generate the script
+ $jsfvCacheWarmer = new JsFormValidationCacheWarmer($this->container);
+ $scriptFile = $jsfvCacheWarmer->generateFormValidationScript($entityName, $formName, $formFields, $validationGroups);
+
+ if ($getScriptPath) {
+ return $scriptFile;
+ }
+ else {
+ return '<script type="text/javascript" src="'.$scriptFile.'"></script>';
+ }
+ }
+ else {
+ // If the bundle is disabled and $getScriptPath is set to true an empty script is generated
+ if ($getScriptPath) {
+ $asseticPath = $this->container->getParameter('assetic.write_to');
+ $scriptPath = $this->container->getParameter('apy_js_form_validation.script_directory');
+ $scriptFile = 'no_jsfv_script.js';
+ $scriptRealPath = $asseticPath.$scriptPath;
+
+ if ( ! is_dir($scriptRealPath) ) {
+ mkdir($scriptRealPath, 0777, true);
+ }
+
+ $filePath = $scriptRealPath.$scriptFile;
+ if (false === file_exists($filePath)) {
+ if (false === @file_put_contents($filePath, '// JsFormValidation bundle is disabled')) {
+ throw new \RuntimeException('Unable to write file '.$filePath);
+ }
+ }
+ return $scriptPath.$scriptFile;
+ }
+ }
+ }
+
+ public function getCountries()
+ {
+ return json_encode(\Symfony\Component\Locale\Locale::getCountries());
+ }
+
+ public function getLanguages()
+ {
+ return json_encode(\Symfony\Component\Locale\Locale::getLanguages());
+ }
+
+ public function getLocales()
+ {
+ return json_encode(\Symfony\Component\Locale\Locale::getLocales());
+ }
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ public function getName()
+ {
+ return 'JsFormValidation';
+ }
+}

0 comments on commit 3239dd8

Please sign in to comment.
Something went wrong with that request. Please try again.