├── Balloon └── Bundle │ └── FormBuilderBundle │ ├── Tests │ ├── Controller │ │ ├── FormFieldControllerTest.php │ │ └── FormControllerTest.php │ ├── bootstrap.php │ ├── Form │ │ └── BuilderTest.php │ └── DependencyInjection │ │ └── BalloonFormBuilderExtensionTest.php │ ├── Resources │ ├── doc │ │ ├── edit.png │ │ ├── list.png │ │ └── answer.png │ ├── config │ │ ├── twig.yml │ │ ├── routing.yml │ │ └── form.yml │ ├── views │ │ ├── FormField │ │ │ └── create.html.twig │ │ ├── Form │ │ │ ├── create.html.twig │ │ │ ├── list.html.twig │ │ │ ├── answer.html.twig │ │ │ └── edit.html.twig │ │ └── layout.html.twig │ └── public │ │ └── bootstrap.min.css │ ├── BalloonFormBuilderBundle.php │ ├── phpunit.xml.dist │ ├── Model │ ├── FormAnswerInterface.php │ ├── FormFieldAnswerInterface.php │ ├── FormInterface.php │ └── FormFieldInterface.php │ ├── Form │ ├── Factory.php │ ├── Manager.php │ ├── Promise.php │ ├── Respond.php │ ├── Renderer.php │ ├── Storage.php │ └── Builder.php │ ├── Twig │ └── FormExtension.php │ ├── DependencyInjection │ ├── BalloonFormBuilderExtension.php │ └── Configuration.php │ ├── Entity │ ├── FormAnswer.php │ ├── Form.php │ ├── FormFieldAnswer.php │ └── FormField.php │ └── Controller │ ├── FormFieldController.php │ └── FormController.php ├── .gitmodules ├── composer.json ├── LICENSE └── README.md /Balloon/Bundle/FormBuilderBundle/Tests/Controller/FormFieldControllerTest.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/doc/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wisembly/FormBuilderBundle/HEAD/Balloon/Bundle/FormBuilderBundle/Resources/doc/edit.png -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/doc/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wisembly/FormBuilderBundle/HEAD/Balloon/Bundle/FormBuilderBundle/Resources/doc/list.png -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/doc/answer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wisembly/FormBuilderBundle/HEAD/Balloon/Bundle/FormBuilderBundle/Resources/doc/answer.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/symfony"] 2 | path = vendor/symfony 3 | url = https://github.com/symfony/symfony.git 4 | [submodule "vendor/Twig"] 5 | path = vendor/Twig 6 | url = https://github.com/fabpot/Twig.git 7 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/config/twig.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | balloon_form_twig_extension.class: Balloon\Bundle\FormBuilderBundle\Twig\FormExtension 3 | 4 | services: 5 | balloon_form_twig_extension: 6 | class: %balloon_form_twig_extension.class% 7 | arguments: [%balloon_form_config%] 8 | tags: [{ name: 'twig.extension' }] 9 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | register(); 10 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/views/FormField/create.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "BalloonFormBuilderBundle::layout.html.twig" %} 2 | 3 | {% block content %} 4 |
5 |
6 | {{ type }} 7 | {{ form_widget(form) }} 8 |
9 | 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "balloon/formbuilderbundle", 3 | "type": "Bundle", 4 | "description": "Store your custom forms as Doctrine entities", 5 | "keywords": ["Symfony2", "Bundle", "Forms", "FormBuilder"], 6 | "license": "GPL", 7 | "authors": [ 8 | { 9 | "name": "Jules Boussekeyt" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.3.0" 14 | }, 15 | "autoload": { 16 | "psr-0": { "Balloon\\Bundle\\FormBuilderBundle": "" } 17 | } 18 | } -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/BalloonFormBuilderBundle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle; 13 | 14 | use Symfony\Component\HttpKernel\Bundle\Bundle; 15 | 16 | /*u* 17 | * BalloonFormBuilderBundle 18 | * 19 | * @author Jules Boussekeyt 20 | */ 21 | class BalloonFormBuilderBundle extends Bundle 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/views/Form/create.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "BalloonFormBuilderBundle::layout.html.twig" %} 2 | 3 | {% block content %} 4 |

Create a form

5 |
6 |

Your form has no field for the moment.

7 |
8 |

Start by choosing a field to add:

9 |
10 | {% for type in form_types() %} 11 | {{ type }} 12 | {% endfor %} 13 |
14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ./Tests 10 | 11 | 12 | 13 | 14 | ./ 15 | 16 | ./Resources 17 | ./Tests 18 | ./vendor 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/views/Form/list.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "BalloonFormBuilderBundle::layout.html.twig" %} 2 | 3 | {% block content %} 4 |

List of forms

5 |
6 | create a form 7 |
8 | 9 | {% for form in forms %} 10 |
11 | {{ form.name }} 12 | delete 13 | answer 14 | edit 15 |
16 | {% else %} 17 | no form yet.. 18 | {% endfor %} 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Model/FormAnswerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Model; 13 | 14 | /** 15 | * FormAnswerInterface 16 | * 17 | * @author Jules Boussekeyt 18 | */ 19 | interface FormAnswerInterface 20 | { 21 | /** 22 | * @param FormInterface $form 23 | */ 24 | public function setForm(FormInterface $form); 25 | 26 | /** 27 | * @return ArrayCollection 28 | */ 29 | public function getFieldAnswers(); 30 | 31 | /** 32 | * @param FormFieldInterface $field 33 | */ 34 | public function addFieldAnswer(FormFieldAnswerInterface $fieldAnswer); 35 | } 36 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Model/FormFieldAnswerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Model; 13 | 14 | /** 15 | * FormFieldAnswerInterface 16 | * 17 | * @author Jules Boussekeyt 18 | */ 19 | interface FormFieldAnswerInterface 20 | { 21 | /** 22 | * @param FormAnswerInterface $answer 23 | */ 24 | public function setAnswer(FormAnswerInterface $answer); 25 | 26 | /** 27 | * @param FormFieldInterface $field 28 | */ 29 | public function setField(FormFieldInterface $field); 30 | 31 | /** 32 | * @param mixed $value 33 | */ 34 | public function setValue($value); 35 | 36 | /** 37 | * @return string 38 | */ 39 | public function getScalarValue(); 40 | } 41 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/config/routing.yml: -------------------------------------------------------------------------------- 1 | form_list: 2 | pattern: / 3 | defaults: { _controller: BalloonFormBuilderBundle:Form:list } 4 | form_create: 5 | pattern: /create 6 | defaults: { _controller: BalloonFormBuilderBundle:Form:create } 7 | form_edit: 8 | pattern: /edit/{formid} 9 | defaults: { _controller: BalloonFormBuilderBundle:Form:edit } 10 | form_answer: 11 | pattern: /answer/{formid} 12 | defaults: { _controller: BalloonFormBuilderBundle:Form:answer } 13 | form_delete: 14 | pattern: /delete/{formid} 15 | defaults: { _controller: BalloonFormBuilderBundle:Form:delete } 16 | 17 | form_field_create: 18 | pattern: /field/create/{type}/{formid} 19 | defaults: { _controller: BalloonFormBuilderBundle:FormField:create } 20 | form_field_edit: 21 | pattern: /field/edit/{index}/{formid} 22 | defaults: { _controller: BalloonFormBuilderBundle:FormField:edit } 23 | form_field_delete: 24 | pattern: /field/delete/{index}/{formid} 25 | defaults: { _controller: BalloonFormBuilderBundle:FormField:delete } 26 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Form/Factory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Form; 13 | 14 | use Balloon\Bundle\FormBuilderBundle\Model\FormFieldInterface; 15 | use Balloon\Bundle\FormBuilderBundle\Model\FormInterface; 16 | 17 | /** 18 | * Factory 19 | * 20 | * @author Jules Boussekeyt 21 | */ 22 | class Factory 23 | { 24 | private $formClass; 25 | private $fieldClass; 26 | 27 | public function __construct($formClass, $fieldClass) 28 | { 29 | $this->formClass = $formClass; 30 | $this->fieldClass = $fieldClass; 31 | } 32 | 33 | public function formInstance() 34 | { 35 | return new $this->formClass; 36 | } 37 | 38 | public function fieldInstance() 39 | { 40 | return new $this->fieldClass; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Balloon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Model/FormInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Model; 13 | 14 | /** 15 | * FormInterface 16 | * 17 | * @author Jules Boussekeyt 18 | */ 19 | interface FormInterface 20 | { 21 | /** 22 | * @return integer 23 | */ 24 | public function getId(); 25 | 26 | /** 27 | * @param string $name 28 | */ 29 | public function setName($name); 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function getName(); 35 | 36 | /** 37 | * @return array 38 | */ 39 | public function getFields(); 40 | 41 | /** 42 | * @param FormFieldInterface $field 43 | */ 44 | public function addField(FormFieldInterface $field); 45 | 46 | /** 47 | * @param FormAnswerInterface $answer 48 | */ 49 | public function addAnswer(FormAnswerInterface $answer); 50 | } 51 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Twig/FormExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Twig; 13 | 14 | /** 15 | * FormExtension 16 | * 17 | * @author Jules Boussekeyt 18 | */ 19 | class FormExtension extends \Twig_Extension 20 | { 21 | private $fieldConfig; 22 | 23 | public function __construct(array $fieldConfig) 24 | { 25 | $this->fieldConfig = $fieldConfig; 26 | } 27 | 28 | public function getFunctions() 29 | { 30 | // @codeCoverageIgnoreStart 31 | return array( 32 | 'form_types' => new \Twig_Function_Method($this, 'getFormTypes'), 33 | ); 34 | // @codeCoverageIgnoreEnd 35 | } 36 | 37 | public function getFormTypes() 38 | { 39 | return array_keys($this->fieldConfig); 40 | } 41 | 42 | public function getName() 43 | { 44 | return 'balloon_form_builder'; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Form/Manager.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Form; 13 | 14 | use Doctrine\Common\Persistence\ObjectManager; 15 | 16 | /** 17 | * Manager 18 | * 19 | * @author Jules Boussekeyt 20 | */ 21 | class Manager 22 | { 23 | protected $objectManager; 24 | protected $repository; 25 | 26 | public function __construct(ObjectManager $objectManager, $repositoryName) 27 | { 28 | $this->objectManager = $objectManager; 29 | $this->repository = $this->objectManager->getRepository($repositoryName); 30 | } 31 | 32 | public function getRepository() 33 | { 34 | // @codeCoverageIgnoreStart 35 | return $this->repository; 36 | // @codeCoverageIgnoreEnd 37 | } 38 | 39 | public function find($id) 40 | { 41 | return $this->repository->find($id); 42 | } 43 | 44 | public function findAll() 45 | { 46 | return $this->repository->findAll(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/views/Form/answer.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "BalloonFormBuilderBundle::layout.html.twig" %} 2 | 3 | {% block content %} 4 | {% if not answered %} 5 |
6 |
7 | {{ form.name | title }} 8 | {{ form_widget(fields) }} 9 |
10 | 11 |
12 | {% else %} 13 | {% if answers|length > 0 %} 14 |

Answers

15 | 16 | 17 | {% for i,field in form.fields %} 18 | 19 | {% endfor %} 20 | 21 | 22 | {% for i,answer in answers %} 23 | 24 | {% for i,field in answer.fieldAnswers %} 25 | 26 | {% endfor %} 27 | 28 | {% endfor %} 29 | 30 |
{{ field.options.label }}
{{ field.scalarValue }}
31 | {% endif %} 32 | {% endif %} 33 | 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Model/FormFieldInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Model; 13 | 14 | /** 15 | * FormFieldInterface 16 | * 17 | * @author Jules Boussekeyt 18 | */ 19 | interface FormFieldInterface 20 | { 21 | /** 22 | * @return string 23 | */ 24 | public function getType(); 25 | 26 | /** 27 | * @param string $type 28 | */ 29 | public function setType($type); 30 | 31 | /** 32 | * @param array $options 33 | */ 34 | public function setOptions(array $options); 35 | 36 | /** 37 | * @return array 38 | */ 39 | public function getOptions(); 40 | 41 | /** 42 | * @param FormInterface $form 43 | */ 44 | public function setForm(FormInterface $form); 45 | 46 | /** 47 | * @param FormFieldAnswerInterface $fieldAnswer 48 | */ 49 | public function addFieldAnswer(FormFieldAnswerInterface $fieldAnswer); 50 | 51 | /** 52 | * toArray 53 | * 54 | * @param FormFieldInterface $field 55 | */ 56 | public static function toArray(FormFieldInterface $field); 57 | 58 | /** 59 | * fromArray 60 | * 61 | * @param FormFieldInterface $field 62 | * @param array $data 63 | */ 64 | public static function fromArray(array $data, FormFieldInterface $field = null); 65 | } 66 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/config/form.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | balloon_form_respond.class: Balloon\Bundle\FormBuilderBundle\Form\Respond 3 | balloon_form_builder.class: Balloon\Bundle\FormBuilderBundle\Form\Builder 4 | balloon_form_factory.class: Balloon\Bundle\FormBuilderBundle\Form\Factory 5 | balloon_form_manager.class: Balloon\Bundle\FormBuilderBundle\Form\Manager 6 | balloon_form_storage.class: Balloon\Bundle\FormBuilderBundle\Form\Storage 7 | balloon_form_promise.class: Balloon\Bundle\FormBuilderBundle\Form\Promise 8 | balloon_form_renderer.class: Balloon\Bundle\FormBuilderBundle\Form\Renderer 9 | 10 | services: 11 | balloon_form_respond: 12 | class: %balloon_form_respond.class% 13 | arguments: [@doctrine.orm.entity_manager, %balloon_answer_entity%, %balloon_field_answer_entity%] 14 | 15 | balloon_form_builder: 16 | class: %balloon_form_builder.class% 17 | arguments: [@form.factory, @balloon_form_promise, %balloon_form_config%] 18 | 19 | balloon_form_factory: 20 | class: %balloon_form_factory.class% 21 | arguments: [%balloon_form_entity%, %balloon_field_entity%] 22 | 23 | balloon_form_promise: 24 | class: %balloon_form_promise.class% 25 | arguments: [@balloon_form_manager, @balloon_form_storage, @balloon_form_factory] 26 | 27 | balloon_form_manager: 28 | class: %balloon_form_manager.class% 29 | arguments: [@doctrine.orm.entity_manager, %balloon_form_entity%] 30 | 31 | balloon_form_storage: 32 | class: %balloon_form_storage.class% 33 | arguments: [@session] 34 | 35 | balloon_form_renderer: 36 | class: %balloon_form_renderer.class% 37 | arguments: [@balloon_form_builder, @form.factory, @twig.extension.form, @twig, %balloon_form_resources%] 38 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/DependencyInjection/BalloonFormBuilderExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\DependencyInjection; 13 | 14 | use Symfony\Component\DependencyInjection\ContainerBuilder; 15 | use Symfony\Component\Config\FileLocator; 16 | use Symfony\Component\HttpKernel\DependencyInjection\Extension; 17 | use Symfony\Component\DependencyInjection\Loader; 18 | 19 | /** 20 | * BalloonFormBuilderExtension 21 | * 22 | * @author Jules Boussekeyt 23 | */ 24 | class BalloonFormBuilderExtension extends Extension 25 | { 26 | /** 27 | * {@inheritDoc} 28 | */ 29 | public function load(array $configs, ContainerBuilder $container) 30 | { 31 | $configuration = new Configuration(); 32 | $config = $this->processConfiguration($configuration, $configs); 33 | 34 | $container->setParameter('balloon_form_resources', $config['resources']); 35 | $container->setParameter('balloon_form_config', $config['fields']); 36 | $container->setParameter('balloon_form_entity', $config['form_entity']); 37 | $container->setParameter('balloon_field_entity', $config['field_entity']); 38 | $container->setParameter('balloon_answer_entity', $config['answer_entity']); 39 | $container->setParameter('balloon_field_answer_entity', $config['field_answer_entity']); 40 | 41 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 42 | $loader->load('form.yml'); 43 | $loader->load('twig.yml'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Form/Promise.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Form; 13 | 14 | /** 15 | * Promise 16 | * 17 | * @author Jules Boussekeyt 18 | */ 19 | class Promise 20 | { 21 | private $manager; 22 | private $storage; 23 | private $factory; 24 | 25 | public function __construct(Manager $manager, Storage $storage, Factory $factory) 26 | { 27 | $this->manager = $manager; 28 | $this->storage = $storage; 29 | $this->factory = $factory; 30 | } 31 | 32 | /** 33 | * Search in the session if we have a form corresponding 34 | * 35 | * If not fetch it in the db and return an array fields 36 | * 37 | * @param integer $formid 38 | * @return array An array of fields 39 | */ 40 | public function findFields($formid) 41 | { 42 | if (null !== ($fields = $this->storage->all($formid))) { 43 | return array_map(array($this->factory->fieldInstance(), 'fromArray'), $fields); 44 | } 45 | 46 | if (null !== ($form = $this->manager->find($formid))) { 47 | if ($form->getFields()->count() > 0) { 48 | $fields = array_map(array($form->getFields()->first(), 'toArray'), $form->getFields()->toArray()); 49 | } else { 50 | $fields = array(); 51 | } 52 | 53 | // TODO remove me 54 | $this->storage->init($formid, $fields); 55 | 56 | return $form->getFields()->toArray(); 57 | } 58 | 59 | // @codeCoverageIgnoreStart 60 | throw new \ErrorException("should not happen, formid : $formid"); 61 | // @codeCoverageIgnoreEnd 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Form/Respond.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Form; 13 | 14 | use Balloon\Bundle\FormBuilderBundle\Model\FormInterface; 15 | use Doctrine\ORM\EntityManager; 16 | 17 | /** 18 | * Respond 19 | * 20 | * @author Jules Boussekeyt 21 | */ 22 | class Respond 23 | { 24 | private $entityManager; 25 | private $answerClass; 26 | private $fieldAnswerClass; 27 | 28 | public function __construct(EntityManager $entityManager, $answerClass, $fieldAnswerClass) 29 | { 30 | $this->repository = $entityManager->getRepository($answerClass); 31 | $this->answerClass = $answerClass; 32 | $this->fieldAnswerClass = $fieldAnswerClass; 33 | } 34 | 35 | public function findAll($formid) 36 | { 37 | return $this->repository->findByForm($formid); 38 | } 39 | 40 | public function answer(FormInterface $form, array $data) 41 | { 42 | if ($form->getFields()->count() !== count($data)) { 43 | // @codeCoverageIgnoreStart 44 | throw new \ErrorException('size of fields should be equals to the size of data'); 45 | // @codeCoverageIgnoreEnd 46 | } 47 | 48 | $index = 0; 49 | $fields = $form->getFields(); 50 | 51 | $answer = new $this->answerClass; 52 | $form->addAnswer($answer); 53 | 54 | foreach ($data as $key => $value) { 55 | $fieldAnswer = new $this->fieldAnswerClass; 56 | 57 | $fields->get($index)->addFieldAnswer($fieldAnswer); 58 | $fieldAnswer->setValue($value); 59 | 60 | $answer->addFieldAnswer($fieldAnswer); 61 | 62 | $index++; 63 | } 64 | 65 | return $answer; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/views/Form/edit.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "BalloonFormBuilderBundle::layout.html.twig" %} 2 | 3 | {% block content %} 4 |

Edit form

5 |
6 | 7 |
8 |
9 | Form settings 10 | {{ form_widget(form) }} 11 |
12 | 13 |
14 | 15 |
16 |
Add a field: 17 |
18 | {% for type in form_types() %} 19 | {{ type }} 20 | {% endfor %} 21 |
22 |
23 | 24 | {% form_theme fields _self %} 25 |
26 |
27 | Form preview 28 | {{ form_widget(fields, {"formid": formid}) }} 29 |
30 |
31 | 32 | {% block field_rows %}{% spaceless %} 33 | {{ form_errors(form) }} 34 | {% for child in form %} 35 | {{ form_row(child, {"formid": formid}) }} 36 | {% endfor %} 37 | {% endspaceless %}{% endblock field_rows %} 38 | 39 | {% block field_row %}{% spaceless %} 40 | {% if form.get("label") != "Form" %} 41 |
42 | {{ form_label(form, label|default(null)) }} 43 | {{ form_errors(form) }} 44 | {{ form_widget(form) }} 45 |

46 | {{ "edit" | trans }} | 47 | {{ "delete" | trans }} 48 |
49 | {% endif %} 50 | {% endspaceless %}{% endblock field_row %} 51 | 52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Entity/FormAnswer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Entity; 13 | 14 | use Doctrine\ORM\Mapping as ORM; 15 | use Doctrine\Common\Collections\ArrayCollection; 16 | use Balloon\Bundle\FormBuilderBundle\Model\FormInterface; 17 | use Balloon\Bundle\FormBuilderBundle\Model\FormFieldInterface; 18 | use Balloon\Bundle\FormBuilderBundle\Model\FormFieldAnswerInterface; 19 | use Balloon\Bundle\FormBuilderBundle\Model\FormAnswerInterface; 20 | 21 | /** 22 | * FormAnswer 23 | * 24 | * @ORM\Table() 25 | * @ORM\Entity 26 | * 27 | * @author Jules Boussekeyt 28 | */ 29 | class FormAnswer implements FormAnswerInterface 30 | { 31 | /** 32 | * @var integer $id 33 | * 34 | * @ORM\Column(name="id", type="integer") 35 | * @ORM\Id 36 | * @ORM\GeneratedValue(strategy="AUTO") 37 | */ 38 | private $id; 39 | 40 | /** 41 | * @var Form $form 42 | * 43 | * @ORM\ManyToOne(targetEntity="Form", inversedBy="answers") 44 | */ 45 | private $form; 46 | 47 | /** 48 | * @var ArrayCollection $fieldAnswers 49 | * 50 | * @ORM\OneToMany(targetEntity="FormFieldAnswer", mappedBy="answer", cascade={"persist", "remove", "merge"}) 51 | */ 52 | private $fieldAnswers; 53 | 54 | public function __construct() 55 | { 56 | $this->fieldAnswers = new ArrayCollection(); 57 | } 58 | 59 | public function setForm(FormInterface $form) 60 | { 61 | $this->form = $form; 62 | } 63 | 64 | public function getFieldAnswers() 65 | { 66 | return $this->fieldAnswers; 67 | } 68 | 69 | public function addFieldAnswer(FormFieldAnswerInterface $fieldAnswer) 70 | { 71 | $fieldAnswer->setAnswer($this); 72 | 73 | $this->fieldAnswers->add($fieldAnswer); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Form/Renderer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Form; 13 | 14 | use Symfony\Component\Form\FormFactory; 15 | use Symfony\Component\Form\FormView; 16 | use Symfony\Bridge\Twig\Extension\FormExtension; 17 | use Twig_Environment; 18 | 19 | /** 20 | * Renderer 21 | * 22 | * This class can be used if you want to render frontend/backend templates for a field 23 | * 24 | * @author Jules Boussekeyt 25 | */ 26 | class Renderer 27 | { 28 | private $builder; 29 | private $formFactory; 30 | private $formExtension; 31 | private $theme; 32 | 33 | public function __construct(Builder $builder, FormFactory $formFactory, FormExtension $formExtension, Twig_Environment $twig, array $themes = array()) 34 | { 35 | $this->builder = $builder; 36 | $this->formFactory = $formFactory; 37 | $this->formExtension = $formExtension; 38 | $this->themes = $themes; 39 | 40 | $this->formExtension->initRuntime($twig); 41 | } 42 | 43 | public function renderFront($type, array $options = array()) 44 | { 45 | $formView = $this->formFactory 46 | ->createBuilder($type, null, $options) 47 | ->getForm() 48 | ->createView(); 49 | 50 | if (!empty($this->themes)) { 51 | $this->formExtension->setTheme($formView, $this->themes); 52 | } 53 | 54 | return $this->formExtension->renderRow($formView); 55 | } 56 | 57 | public function renderBack($type, array $options = array()) 58 | { 59 | $formType = $this->formFactory->getType($type); 60 | $typeOptions = $this->builder->getTypeOptions($formType, $options); 61 | $formView = $this->builder->buildType($formType, $typeOptions)->createView(); 62 | 63 | if (!empty($this->themes)) { 64 | $this->formExtension->setTheme($formView, $this->themes); 65 | } 66 | 67 | return $this->formExtension->renderRow($formView); 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Form/Storage.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Form; 13 | 14 | use Symfony\Component\HttpFoundation\Session\Session; 15 | 16 | /** 17 | * Storage 18 | * 19 | * @author Jules Boussekeyt 20 | */ 21 | class Storage 22 | { 23 | private $session; 24 | 25 | public function __construct(Session $session) 26 | { 27 | $this->session = $session; 28 | } 29 | 30 | public function generateId() 31 | { 32 | // we don't take the session_id 33 | // cuz we can create many forms at the same time 34 | return rand(0, 1000); 35 | } 36 | 37 | public function all($formid) 38 | { 39 | return $this->_get($formid); 40 | } 41 | 42 | public function init($formid, array $fields) 43 | { 44 | $this->_set($formid, $fields); 45 | } 46 | 47 | public function add($formid, $type, $data) 48 | { 49 | $fields = $this->_get($formid); 50 | 51 | $fields[] = array('type' => $type, 'options' => $data); 52 | 53 | $this->_set($formid, $fields); 54 | } 55 | 56 | public function get($formid, $index) 57 | { 58 | $fields = $this->_get($formid); 59 | 60 | return $fields[$index]; 61 | } 62 | 63 | public function set($formid, $index, $type, array $data) 64 | { 65 | $fields = $this->_get($formid); 66 | 67 | $fields[$index] = array('type' => $type, 'options' => $data); 68 | 69 | $this->_set($formid, $fields); 70 | } 71 | 72 | public function remove($formid, $index) 73 | { 74 | $fields = $this->_get($formid); 75 | 76 | unset($fields[$index]); 77 | 78 | $this->_set($formid, $fields); 79 | } 80 | 81 | protected function _get($formid) 82 | { 83 | return $this->session->get('forms_'.$formid); 84 | } 85 | 86 | protected function _set($formid, array $fields) 87 | { 88 | return $this->session->set('forms_'.$formid, $fields); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\DependencyInjection; 13 | 14 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 15 | use Symfony\Component\Config\Definition\ConfigurationInterface; 16 | 17 | /** 18 | * Configuration 19 | * 20 | * @author Jules Boussekeyt 21 | */ 22 | class Configuration implements ConfigurationInterface 23 | { 24 | /** 25 | * {@inheritDoc} 26 | */ 27 | public function getConfigTreeBuilder() 28 | { 29 | $treeBuilder = new TreeBuilder(); 30 | 31 | $rootNode = $treeBuilder->root('balloon_form_builder'); 32 | 33 | $rootNode 34 | ->children() 35 | ->arrayNode('resources') 36 | ->defaultValue(array()) 37 | ->validate() 38 | ->ifTrue(function($v) { return empty($v); }) 39 | ->then(function($v){ 40 | return array(); 41 | }) 42 | ->end() 43 | ->prototype('scalar')->end() 44 | ->end() 45 | ->scalarNode('form_entity') 46 | ->defaultValue('Balloon\Bundle\FormBuilderBundle\Entity\Form') 47 | ->end() 48 | ->scalarNode('field_entity') 49 | ->defaultValue('Balloon\Bundle\FormBuilderBundle\Entity\FormField') 50 | ->end() 51 | ->scalarNode('answer_entity') 52 | ->defaultValue('Balloon\Bundle\FormBuilderBundle\Entity\FormAnswer') 53 | ->end() 54 | ->scalarNode('field_answer_entity') 55 | ->defaultValue('Balloon\Bundle\FormBuilderBundle\Entity\FormFieldAnswer') 56 | ->end() 57 | ->arrayNode('fields') 58 | ->isRequired() 59 | ->useAttributeAsKey('name') 60 | ->prototype('array') 61 | ->useAttributeAsKey('name') 62 | ->prototype('variable') 63 | ->end() 64 | ->end() 65 | ->end() 66 | ; 67 | 68 | return $treeBuilder; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Tests/Form/BuilderTest.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class BuilderTest extends \PHPUnit_Framework_TestCase 14 | { 15 | public function testGetTypeOptions() 16 | { 17 | $builder = $this->getBuilder(); 18 | $typeMock = $this->getTypeMock(); 19 | $options = $builder->getTypeOptions($typeMock, array( 20 | 'is_required' => 'true', 21 | 'choices' => array('foo', 'bar', 'baz'), 22 | )); 23 | 24 | $this->assertSame(true, $options['is_required']); 25 | $this->assertEquals(8, count($options['choices'])); 26 | $this->assertEquals(1, count($options['type'])); 27 | } 28 | 29 | public function testBuildType() 30 | { 31 | $builder = $this->getBuilder(); 32 | $form = $builder->buildType(new ChoiceType()); 33 | 34 | $this->assertInstanceof('Symfony\Component\Form\Form', $form); 35 | $this->assertInstanceof('Symfony\Component\Form\Form', $form->get('choices')); 36 | $this->assertInstanceof('Symfony\Component\Form\Form', $form->get('expanded')); 37 | $this->assertInstanceof('Symfony\Component\Form\Form', $form->get('choices')); 38 | } 39 | 40 | public function testGetType() 41 | { 42 | $builder = $this->getBuilder(); 43 | 44 | try { 45 | $builder->getType('unknown'); 46 | $this->fail('->getType() should throw an exception if type do not exists'); 47 | } catch (\InvalidArgumentException $e) {} 48 | 49 | $this->assertInstanceof('Symfony\Component\Form\Extension\Core\Type\FieldType', $builder->getType('field')); 50 | } 51 | 52 | 53 | public function getBuilder() 54 | { 55 | $formFactory = new FormFactory(array(new CoreExtension())); 56 | 57 | $promise= \Mockery::mock('\Balloon\Bundle\FormBuilderBundle\Form\Promise', array( 58 | )); 59 | 60 | $config = array(); 61 | 62 | return new Builder($formFactory, $promise, $config); 63 | } 64 | 65 | public function getTypeMock() 66 | { 67 | return \Mockery::mock('\Symfony\Component\Form\Extension\Core\Type\FieldType', array( 68 | 'getDefaultOptions' => array( 69 | 'choices' => array(), 70 | 'type' => array('text', 'number', 'object'), 71 | 'is_required' => false, 72 | ), 73 | 'getName' => null, 74 | )); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/views/layout.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "::base.html.twig" %} 2 | 3 | {% block body %} 4 |
5 |
6 | 9 |
10 |
11 | {% block content %}{% endblock %} 12 |
13 |
14 |

Secondary content

15 |
16 |
17 |
18 |
19 | 20 |
21 |
22 | {% endblock %} 23 | 24 | {% block stylesheets %} 25 | 26 | 88 | {% endblock %} 89 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Entity/Form.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Entity; 13 | 14 | use Balloon\Bundle\FormBuilderBundle\Model\FormAnswerInterface; 15 | use Balloon\Bundle\FormBuilderBundle\Model\FormFieldInterface; 16 | use Balloon\Bundle\FormBuilderBundle\Model\FormInterface; 17 | use Doctrine\Common\Collections\ArrayCollection; 18 | use Doctrine\ORM\Mapping as ORM; 19 | 20 | /** 21 | * Form 22 | * 23 | * @ORM\Table() 24 | * @ORM\Entity 25 | * 26 | * @author Jules Boussekeyt 27 | */ 28 | class Form implements FormInterface 29 | { 30 | /** 31 | * @var integer $id 32 | * 33 | * @ORM\Column(name="id", type="integer") 34 | * @ORM\Id 35 | * @ORM\GeneratedValue(strategy="AUTO") 36 | */ 37 | protected $id; 38 | 39 | /** 40 | * @var string $name 41 | * 42 | * @ORM\Column(name="name", type="string", length=255) 43 | */ 44 | protected $name; 45 | 46 | /** 47 | * @var string $name 48 | * 49 | * @ORM\OneToMany(targetEntity="FormField", mappedBy="form", cascade={"persist", "remove", "merge"}) 50 | */ 51 | protected $fields; 52 | 53 | /** 54 | * @var Form $form 55 | * 56 | * @ORM\OneToMany(targetEntity="FormAnswer", mappedBy="form", cascade={"persist", "remove", "merge"}) 57 | */ 58 | protected $answers; 59 | 60 | public function __construct() 61 | { 62 | $this->fields = new ArrayCollection(); 63 | $this->answers = new ArrayCollection(); 64 | } 65 | 66 | /** 67 | * Get id 68 | * 69 | * @return integer 70 | */ 71 | public function getId() 72 | { 73 | return $this->id; 74 | } 75 | 76 | /** 77 | * Set name 78 | * 79 | * @param string $name 80 | * @return Form 81 | */ 82 | public function setName($name) 83 | { 84 | $this->name = $name; 85 | return $this; 86 | } 87 | 88 | /** 89 | * Get name 90 | * 91 | * @return string 92 | */ 93 | public function getName() 94 | { 95 | return $this->name; 96 | } 97 | 98 | public function getFields() 99 | { 100 | return $this->fields; 101 | } 102 | 103 | public function addField(FormFieldInterface $field) 104 | { 105 | $field->setForm($this); 106 | 107 | $this->fields->add($field); 108 | } 109 | 110 | public function addAnswer(FormAnswerInterface $answer) 111 | { 112 | $answer->setForm($this); 113 | 114 | $this->answers->add($answer); 115 | } 116 | 117 | public function getAnswers() 118 | { 119 | return $this->answers; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Controller/FormFieldController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Controller; 13 | 14 | use Balloon\Bundle\FormBuilderBundle\Entity\Field; 15 | use Balloon\Bundle\FormBuilderBundle\Entity\Form; 16 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; 17 | use Symfony\Component\HttpFoundation\Request; 18 | 19 | /** 20 | * FormFieldController 21 | * 22 | * @author Jules Boussekeyt 23 | */ 24 | class FormFieldController extends Controller 25 | { 26 | public function createAction(Request $request, $type, $formid) 27 | { 28 | $formType = $this->get('balloon_form_builder')->getType($type); 29 | $form = $this->get('balloon_form_builder')->buildType($formType); 30 | 31 | if ('POST' === $request->getMethod()) { 32 | $form->bindRequest($request); 33 | if ($form->isValid()) { 34 | $this->get('balloon_form_storage')->add($formid, $type, $form->getData()); 35 | 36 | return $this->redirect($this->generateUrl('form_edit', array('formid' => $formid))); 37 | // @codeCoverageIgnoreStart 38 | } 39 | } 40 | // @codeCoverageIgnoreEnd 41 | 42 | return $this->render('BalloonFormBuilderBundle:FormField:create.html.twig', array( 43 | 'type' => $type, 44 | 'form' => $form->createView() 45 | )); 46 | } 47 | 48 | public function editAction(Request $request, $index, $formid) 49 | { 50 | $fieldArr = $this->get('balloon_form_storage')->get($formid, $index); 51 | $field = $this->get('balloon_form_factory')->fieldInstance()->fromArray($fieldArr); 52 | $formType = $this->get('balloon_form_builder')->getType($field->getType()); 53 | $form = $this->get('balloon_form_builder')->buildType($formType, $field->getOptions()); 54 | 55 | if ('POST' === $request->getMethod()) { 56 | $form->bindRequest($request); 57 | if ($form->isValid()) { 58 | $this->get('balloon_form_storage')->set($formid, $index, $field->getType(), $form->getData()); 59 | 60 | return $this->redirect($this->generateUrl('form_edit', array('formid' => $formid))); 61 | // @codeCoverageIgnoreStart 62 | } 63 | } 64 | // @codeCoverageIgnoreEnd 65 | 66 | return $this->render('BalloonFormBuilderBundle:FormField:create.html.twig', array( 67 | 'type' => $field->getType(), 68 | 'form' => $form->createView(), 69 | )); 70 | } 71 | 72 | public function deleteAction(Request $request, $index, $formid) 73 | { 74 | $this->get('balloon_form_storage')->remove($formid, $index); 75 | 76 | return $this->redirect($this->generateUrl('form_edit', array('formid' => $formid))); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### /!\This bundle has not been updated to Symfony 2.1-RC2+ and is no longer maintained by Balloon/!\ 2 | 3 | 4 | Let your users create dynamic forms and store them in the database. 5 | 6 | Features: 7 | 8 | - Create forms 9 | - Add fields (register custom fields) 10 | - Answer to a form 11 | - View form results 12 | 13 | ## Requirements : 14 | For Symfony 2.1-RC1, use 1.1 tag 15 | For a Symfony 2.0 compliant version, use 1.0 tag 16 | 17 | ## Installation (for 2.0 and 2.1-RC1 use, without composer) 18 | 19 | Clone the project: 20 | 21 | ```bash 22 | $ git submodule add -f git://github.com/Balloon/FormBuilderBundle.git vendor/bundles/Balloon/Bundle/FormBuilderBundle 23 | ``` 24 | 25 | Register the bundle in `app/AppKernel.php`: 26 | 27 | ```php 28 | registerNamespaces(array( 46 | // ... 47 | 'Balloon\\Bundle\\FormBuilderBundle' => __DIR__.'/../vendor/bundles', 48 | // ... 49 | )); 50 | ``` 51 | 52 | Add this section to your `app/config/config.yml` file: 53 | 54 | ```yaml 55 | balloon_form_builder: 56 | fields: 57 | field: 58 | label: ~ 59 | required: false 60 | max_length: ~ 61 | choice: 62 | label: ~ 63 | multiple: false 64 | expanded: false 65 | choices: {} 66 | country: 67 | label: ~ 68 | language: 69 | label: ~ 70 | timezone: 71 | label: ~ 72 | datetime: 73 | label: ~ 74 | date: 75 | label: ~ 76 | time: 77 | label: ~ 78 | checkbox: 79 | label: ~ 80 | ``` 81 | 82 | Add this section to your `app/config/routing.yml` file: 83 | 84 | ```yaml 85 | balloon_form_builder: 86 | resource: "@BalloonFormBuilderBundle/Resources/config/routing.yml" 87 | prefix: /form 88 | ``` 89 | 90 | Init assets: 91 | 92 | ```bash 93 | $ ./app/console assets:install web --symlink 94 | ``` 95 | 96 | And if you haven't a dedicated virtual host, add this to `app/config/config.yml`: 97 | 98 | ```yaml 99 | framwork: 100 | templating: 101 | assets_base_urls: "/balloon-form" 102 | ``` 103 | 104 | Your done now go to `/app_dev.php/form` 105 | 106 | ## Screenshots 107 | 108 | ![list form](https://github.com/Balloon/FormBuilderBundle/raw/master/Resources/doc/list.png) 109 |

List forms

110 | 111 | 112 | ![edit form](https://github.com/Balloon/FormBuilderBundle/raw/master/Resources/doc/edit.png) 113 |

Edit a form

114 | 115 | ![answer form](https://github.com/Balloon/FormBuilderBundle/raw/master/Resources/doc/answer.png) 116 |

Answer to a form

117 | 118 | ## Contributors 119 | 120 | creator [gordonslondon](http://github.com/gordonslondon) 121 | maintener [guillaumepotier] 122 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Entity/FormFieldAnswer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Entity; 13 | 14 | use Balloon\Bundle\FormBuilderBundle\Model\FormAnswerInterface; 15 | use Balloon\Bundle\FormBuilderBundle\Model\FormFieldAnswerInterface; 16 | use Balloon\Bundle\FormBuilderBundle\Model\FormFieldInterface; 17 | use Doctrine\ORM\Mapping as ORM; 18 | 19 | /** 20 | * FormFieldAnswer 21 | * 22 | * @ORM\Table() 23 | * @ORM\Entity 24 | * 25 | * @author Jules Boussekeyt 26 | */ 27 | class FormFieldAnswer implements FormFieldAnswerInterface 28 | { 29 | /** 30 | * @var integer $id 31 | * 32 | * @ORM\Column(name="id", type="integer") 33 | * @ORM\Id 34 | * @ORM\GeneratedValue(strategy="AUTO") 35 | */ 36 | private $id; 37 | 38 | /** 39 | * @ORM\Column(name="value", type="array") 40 | */ 41 | private $value; 42 | 43 | /** 44 | * @var FormItem $field 45 | * 46 | * @ORM\ManyToOne(targetEntity="FormField", inversedBy="form") 47 | */ 48 | private $field; 49 | 50 | /** 51 | * @var FormItem $field 52 | * 53 | * @ORM\ManyToOne(targetEntity="FormAnswer", inversedBy="fieldAnswers") 54 | */ 55 | private $answer; 56 | 57 | public function setField(FormFieldInterface $field) 58 | { 59 | $this->field = $field; 60 | } 61 | 62 | public function setValue($value) 63 | { 64 | $this->value = $value; 65 | } 66 | 67 | public function getScalarValue() 68 | { 69 | switch (gettype($this->value)) { 70 | case 'object': 71 | if ($this->value instanceof \DateTime) { 72 | $dateFormatter = \IntlDateFormatter::create( 73 | \Locale::getDefault(), 74 | \IntlDateFormatter::SHORT, 75 | \IntlDateFormatter::SHORT, 76 | date_default_timezone_get(), 77 | \IntlDateFormatter::GREGORIAN 78 | ); 79 | 80 | return $dateFormatter->format($this->value); 81 | } 82 | 83 | return $this->value; 84 | case 'array': 85 | $choices = array(); 86 | 87 | $options = $this->field->getOptions(); 88 | foreach ($options['choices'] as $k => $v) { 89 | if (false !== array_search($k, $this->value)) { 90 | $choices[] = $v; 91 | } 92 | } 93 | 94 | return implode(', ', $choices); 95 | case 'boolean': 96 | return $this->value ? '~' : ''; 97 | default: 98 | if ($this->field->getType() == 'choice') { 99 | $options = $this->field->getOptions(); 100 | $choices = $options['choices']; 101 | 102 | return $choices[$this->value]; 103 | } 104 | 105 | return $this->value; 106 | } 107 | } 108 | 109 | public function setAnswer(FormAnswerInterface $answer) 110 | { 111 | $this->answer = $answer; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Controller/FormController.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Controller; 13 | 14 | use Balloon\Bundle\FormBuilderBundle\Entity\Field; 15 | use Balloon\Bundle\FormBuilderBundle\Entity\Form; 16 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; 17 | use Symfony\Component\HttpFoundation\Request; 18 | 19 | /** 20 | * FormController 21 | * 22 | * @author Jules Boussekeyt 23 | */ 24 | class FormController extends Controller 25 | { 26 | public function listAction() 27 | { 28 | $forms = $this->get('balloon_form_manager')->findAll(); 29 | 30 | return $this->render('BalloonFormBuilderBundle:Form:list.html.twig', array('forms' => $forms)); 31 | } 32 | 33 | public function createAction() 34 | { 35 | $formid = $this->get('balloon_form_storage')->generateId(); 36 | 37 | return $this->render('BalloonFormBuilderBundle:Form:create.html.twig', array( 38 | 'formid' => $formid, 39 | )); 40 | } 41 | 42 | public function editAction($formid) 43 | { 44 | $form = $this->get('balloon_form_manager')->find($formid); 45 | 46 | if (null === $form) { 47 | $form = $this->get('balloon_form_factory')->formInstance(); 48 | } 49 | 50 | $formForm = $this->createFormBuilder($form)->add('name')->getForm(); 51 | 52 | $fields = $this->get('balloon_form_promise')->findFields($formid); 53 | $fieldsForm = $this->get('balloon_form_builder')->buildFields($fields, false); 54 | 55 | if ('POST' === $this->getRequest()->getMethod()) { 56 | $formForm->bindRequest($this->getRequest()); 57 | 58 | if ($formForm->isValid()) { 59 | foreach ($fields as $field) { 60 | $form->addField($field); 61 | } 62 | 63 | $this->getDoctrine()->getEntityManager()->persist($form); 64 | $this->getDoctrine()->getEntityManager()->flush(); 65 | 66 | return $this->redirect($this->generateUrl('form_list')); 67 | // @codeCoverageIgnoreStart 68 | } 69 | } 70 | // @codeCoverageIgnoreEnd 71 | 72 | return $this->render('BalloonFormBuilderBundle:Form:edit.html.twig', array( 73 | 'formid' => $formid, 74 | 'fields' => $fieldsForm->createView(), 75 | 'form' => $formForm->createView(), 76 | )); 77 | } 78 | 79 | public function deleteAction($formid) 80 | { 81 | $form = $this->get('balloon_form_manager')->find($formid); 82 | 83 | $em = $this->getDoctrine()->getEntityManager(); 84 | $em->remove($form); 85 | $em->flush(); 86 | 87 | return $this->redirect($this->generateUrl('form_list')); 88 | } 89 | 90 | public function answerAction(Request $request, $formid) 91 | { 92 | $form = $this->get('balloon_form_manager')->find($formid); 93 | $fields = $this->get('balloon_form_builder')->build($formid); 94 | 95 | if ('POST' === $request->getMethod()) { 96 | $fields->bindRequest($request); 97 | 98 | if ($fields->isValid()) { 99 | $answer = $this->get('balloon_form_respond')->answer($form, $fields->getData()); 100 | $this->getDoctrine()->getEntityManager()->persist($answer); 101 | $this->getDoctrine()->getEntityManager()->flush(); 102 | 103 | $request->getSession()->set('answered_'.$formid, true); 104 | } 105 | } 106 | 107 | $answers = $this->get('balloon_form_respond')->findAll($formid); 108 | $answered = $request->getSession()->get('answered_'.$formid, false); 109 | 110 | return $this->render('BalloonFormBuilderBundle:Form:answer.html.twig', array( 111 | 'form' => $form, 112 | 'answers' => $answers, 113 | 'answered'=> $answered, 114 | 'fields' => $fields->createView(), 115 | )); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Tests/DependencyInjection/BalloonFormBuilderExtensionTest.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class BalloonFormBuilderExtensionTest extends \PHPUnit_Framework_TestCase 14 | { 15 | private $configuration; 16 | 17 | /** 18 | * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException 19 | */ 20 | public function testFormLoadThrowsExceptionUnlessFieldsSet() 21 | { 22 | $loader = new BalloonFormBuilderExtension(); 23 | $config = $this->getEmptyConfig(); 24 | unset($config['fields']); 25 | $loader->load(array($config), new ContainerBuilder()); 26 | } 27 | 28 | public function testFormLoadWithCustomClasses() 29 | { 30 | $loader = new BalloonFormBuilderExtension(); 31 | $config = $this->createFullConfiguration(); 32 | $this->assertEquals('FormEntity', $this->configuration->getParameter('balloon_form_entity')); 33 | $this->assertEquals('FieldEntity', $this->configuration->getParameter('balloon_field_entity')); 34 | $this->assertEquals('AnswerEntity', $this->configuration->getParameter('balloon_answer_entity')); 35 | $this->assertEquals('FieldAnswerEntity', $this->configuration->getParameter('balloon_field_answer_entity')); 36 | } 37 | 38 | public function testFormLoadWithFields() 39 | { 40 | $loader = new BalloonFormBuilderExtension(); 41 | $config = $this->createFullConfiguration(); 42 | $configuration = $this->configuration->getParameter('balloon_form_config'); 43 | $this->assertArrayHasKey('field', $configuration); 44 | $this->assertArrayHasKey('label', $configuration['field']); 45 | $this->assertEquals(null, $configuration['field']['label']); 46 | } 47 | 48 | public function testFormLoadWithResources() 49 | { 50 | $loader = new BalloonFormBuilderExtension(); 51 | $config = $this->createFullConfiguration(); 52 | $configuration = $this->configuration->getParameter('balloon_form_resources'); 53 | $this->assertEquals(array('my_template'), $configuration); 54 | } 55 | 56 | public function testFormLoadWithEmptyResources() 57 | { 58 | $loader = new BalloonFormBuilderExtension(); 59 | $config = $this->createEmptyConfiguration(); 60 | $configuration = $this->configuration->getParameter('balloon_form_resources'); 61 | $this->assertEquals(array(), $configuration); 62 | } 63 | 64 | protected function createEmptyConfiguration() 65 | { 66 | $this->configuration = new ContainerBuilder(); 67 | $loader = new BalloonFormBuilderExtension(); 68 | $config = $this->getEmptyConfig(); 69 | $loader->load(array($config), $this->configuration); 70 | $this->assertTrue($this->configuration instanceof ContainerBuilder); 71 | 72 | return $config; 73 | } 74 | 75 | protected function createFullConfiguration() 76 | { 77 | $this->configuration = new ContainerBuilder(); 78 | $loader = new BalloonFormBuilderExtension(); 79 | $config = $this->getFullConfig(); 80 | $loader->load(array($config), $this->configuration); 81 | $this->assertTrue($this->configuration instanceof ContainerBuilder); 82 | 83 | return $config; 84 | } 85 | 86 | public function getEmptyConfig() 87 | { 88 | $yaml = <<parse($yaml); 96 | } 97 | 98 | public function getFullConfig() 99 | { 100 | $yaml = <<parse($yaml); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Entity/FormField.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Entity; 13 | 14 | use Balloon\Bundle\FormBuilderBundle\Model\FormFieldAnswerInterface; 15 | use Balloon\Bundle\FormBuilderBundle\Model\FormFieldInterface; 16 | use Balloon\Bundle\FormBuilderBundle\Model\FormInterface; 17 | use Doctrine\Common\Collections\ArrayCollection; 18 | use Doctrine\ORM\Mapping as ORM; 19 | 20 | /** 21 | * Balloon\Bundle\FormBuilderBundle\Entity\FormField 22 | * 23 | * @ORM\Table() 24 | * @ORM\Entity 25 | * 26 | * @author Jules Boussekeyt 27 | */ 28 | class FormField implements FormFieldInterface 29 | { 30 | /** 31 | * @var integer $id 32 | * 33 | * @ORM\Column(name="id", type="integer") 34 | * @ORM\Id 35 | * @ORM\GeneratedValue(strategy="AUTO") 36 | */ 37 | private $id; 38 | 39 | /** 40 | * @var string $type 41 | * 42 | * @ORM\Column(name="type", type="string", length=255) 43 | */ 44 | private $type; 45 | 46 | /** 47 | * @var array $options 48 | * 49 | * @ORM\Column(name="options", type="array") 50 | */ 51 | private $options = array(); 52 | 53 | /** 54 | * @var string $form 55 | * 56 | * @ORM\ManyToOne(targetEntity="Form", inversedBy="fields") 57 | */ 58 | private $form; 59 | 60 | /** 61 | * @ORM\OneToMany(targetEntity="FormFieldAnswer", mappedBy="field", cascade={"remove"}) 62 | */ 63 | private $fieldAnswer; 64 | 65 | public function __construct() 66 | { 67 | $this->fieldAnswer = new ArrayCollection(); 68 | } 69 | 70 | public static function toArray(FormFieldInterface $field) 71 | { 72 | return array( 73 | 'type' => $field->getType(), 74 | 'options' => $field->getOptions(), 75 | ); 76 | } 77 | 78 | public static function fromArray(array $data, FormFieldInterface $field = null) 79 | { 80 | $field = null === $field ? new static() : $field; 81 | 82 | if (isset($data['type'])) { 83 | $field->setType($data['type']); 84 | } 85 | 86 | if (isset($data['options'])) { 87 | // fix boolean casting 88 | foreach ($data['options'] as $key => $val) { 89 | if ('false' === $val) $data['options'][$key] = false; 90 | if ('true' === $val) $data['options'][$key] = true; 91 | } 92 | 93 | // remove empty choices 94 | if (isset($data['options']['choices'])) { 95 | $data['options']['choices'] = array_filter($data['options']['choices'], 'strlen'); 96 | } 97 | 98 | $field->setOptions($data['options']); 99 | } 100 | 101 | return $field; 102 | } 103 | 104 | /** 105 | * Set type 106 | * 107 | * @param string $type 108 | * @return FormField 109 | */ 110 | public function setType($type) 111 | { 112 | $this->type = $type; 113 | return $this; 114 | } 115 | 116 | /** 117 | * Get type 118 | * 119 | * @return string 120 | */ 121 | public function getType() 122 | { 123 | return $this->type; 124 | } 125 | 126 | /** 127 | * Set options 128 | * 129 | * @param array $options 130 | * @return FormField 131 | */ 132 | public function setOptions(array $options) 133 | { 134 | if (isset($options['choices'])) { 135 | $choices = $options['choices']; 136 | unset($options['choices']); 137 | 138 | foreach ($choices as $key => $val) { 139 | if (!empty($val)) { 140 | $options['choices'][$key] = $val; 141 | } 142 | } 143 | 144 | } 145 | 146 | $this->options = $options; 147 | 148 | return $this; 149 | } 150 | 151 | /** 152 | * Get options 153 | * 154 | * @return array 155 | */ 156 | public function getOptions() 157 | { 158 | return $this->options; 159 | } 160 | 161 | /** 162 | * Set form 163 | * 164 | * @param string $form 165 | * @return FormField 166 | */ 167 | public function setForm(FormInterface $form) 168 | { 169 | $this->form = $form; 170 | return $this; 171 | } 172 | 173 | public function addFieldAnswer(FormFieldAnswerInterface $fieldAnswer) 174 | { 175 | $fieldAnswer->setField($this); 176 | 177 | $this->fieldAnswer->add($fieldAnswer); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Form/Builder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE. 10 | */ 11 | 12 | namespace Balloon\Bundle\FormBuilderBundle\Form; 13 | 14 | use Balloon\Bundle\FormBuilderBundle\Model\FormFieldInterface; 15 | use Symfony\Component\Form\Extension\Core\Type\CheckboxType; 16 | use Symfony\Component\Form\Extension\Core\Type\ChoiceType; 17 | use Symfony\Component\Form\Extension\Core\Type\CollectionType; 18 | use Symfony\Component\Form\Extension\Core\Type\FieldType; 19 | use Symfony\Component\Form\FormFactory; 20 | use Symfony\Component\Form\FormTypeInterface; 21 | 22 | /** 23 | * Builder 24 | * 25 | * @author Jules Boussekeyt 26 | */ 27 | class Builder 28 | { 29 | protected $formFactory; 30 | protected $promise; 31 | protected $fieldConfig; 32 | 33 | public function __construct(FormFactory $formFactory, Promise $promise, $fieldConfig) 34 | { 35 | $this->formFactory = $formFactory; 36 | $this->promise = $promise; 37 | $this->fieldConfig = $fieldConfig; 38 | } 39 | 40 | public function build($formid, $bindData = false) 41 | { 42 | return $this->buildFields($this->promise->findFields($formid), $bindData); 43 | } 44 | 45 | public function buildFields(array $fields, $bindData = false) 46 | { 47 | $builder = $this->formFactory->createBuilder('form', $bindData ? $fields : array()); 48 | 49 | foreach ($fields as $i => $field) { 50 | $builder->add((string)$i, $field->getType(), $field->getOptions()); 51 | } 52 | 53 | return $builder->getForm(); 54 | } 55 | 56 | /** 57 | * get a FormType 58 | * 59 | * @param mixed $type 60 | */ 61 | public function getType($type) 62 | { 63 | if (!$this->formFactory->hasType($type)) { 64 | throw new \InvalidArgumentException("type '$type' do not exists"); 65 | } 66 | 67 | return $this->formFactory->getType($type); 68 | } 69 | 70 | /** 71 | * Build the Form of a given FormType 72 | * 73 | * @param FormTypeInterface $type 74 | * @param array $data 75 | */ 76 | public function buildType(FormTypeInterface $type, array $values = array()) 77 | { 78 | $data = $this->getTypeOptions($type, $values); 79 | $configOptions = $type->getDefaultOptions(array()); 80 | $configOptions = isset($this->fieldConfig[$type->getName()]) ? $this->fieldConfig[$type->getName()] : $configOptions; 81 | $builder = $this->formFactory->createBuilder('form', $data); 82 | 83 | foreach ($data as $key => $val) { 84 | $val = $configOptions[$key]; 85 | switch(gettype($val)) { 86 | case 'boolean': 87 | $builder->add($key, new CheckboxType(), array( 88 | 'required' => false, 89 | )); 90 | break; 91 | case 'NULL': 92 | case 'integer': 93 | case 'double': 94 | case 'string': 95 | $builder->add($key, new FieldType(), array( 96 | 'required' => false, 97 | )); 98 | break; 99 | case 'array': 100 | // if there's no values, it's some blank fields 101 | if (empty($val)) { 102 | $builder->add($key, new CollectionType(), array( 103 | 'required' => false, 104 | 'allow_add' => true, 105 | 'allow_delete' => true, 106 | )); 107 | // else a select 108 | } else { 109 | $builder->add($key, new ChoiceType(), array( 110 | 'choices' => $configOptions[$key], 111 | 'expanded' => false 112 | )); 113 | } 114 | break; 115 | default: 116 | throw new \InvalidArgumentException('unsupported type '. gettype($val)); 117 | } 118 | } 119 | 120 | return $builder->getForm(); 121 | } 122 | 123 | /** 124 | * For a given FormType we get all options {@see FormTypeInterface::getDefaultOptions()} 125 | * 126 | * @param FormTypeInterface $type The FormType 127 | * @param array $values Values for FormType options 128 | */ 129 | public function getTypeOptions(FormTypeInterface $type, array $values = array(), $tag = null) 130 | { 131 | 132 | if (isset($values['multiple'])) { 133 | $this->fieldConfig['choice']['expanded'] = true; 134 | } 135 | 136 | $options = isset($this->fieldConfig[$type->getName()]) 137 | ? $this->fieldConfig[$type->getName()] 138 | : $type->getDefaultOptions(array()); 139 | 140 | if ($tag != null) { 141 | $this->fieldConfig['html']['tag'] = $tag; 142 | } 143 | 144 | foreach ($options as $option => $val) { 145 | 146 | // if field as an array value 147 | if (is_array($val)) { 148 | // if value is an empty array create some fields 149 | if (empty($val)) { 150 | $val = array_fill(0, 4, ''); 151 | } 152 | // else we set the default value to the first element 153 | else { 154 | $val = reset($val); 155 | } 156 | } 157 | 158 | // if a value is provided, bind it 159 | if (!isset($values[$option])) { 160 | $values[$option] = $val; 161 | } 162 | 163 | // fix boolean casting 164 | if (is_bool($val) && !is_bool($values[$option])) { 165 | $values[$option] = (bool) $values[$option]; 166 | } 167 | } 168 | 169 | // if the field name is 'choices' we add some fields 170 | if (isset($values['choices']) && false === array_search('', $values['choices'])) { 171 | $values['choices'] += array_fill(count($values['choices']), count($values['choices']) + 2, ''); 172 | } 173 | 174 | return $values; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Tests/Controller/FormControllerTest.php: -------------------------------------------------------------------------------- 1 | getContainer()->get('doctrine.orm.entity_manager'); 16 | $em->getConnection()->exec('INSERT INTO Form (name) VALUES (\'toto\')'); 17 | 18 | self::$repository = $em->getRepository('BalloonFormBuilderBundle:Form'); 19 | self::$formId = self::$repository->findOneByName('toto')->getId(); 20 | 21 | $options = serialize(array('label' => 'field label', 'required' => true, 'max_length' => 50)); 22 | $em->getConnection()->exec("INSERT INTO FormField (form_id, type, options) VALUES (".self::$formId.", 'field', '$options')"); 23 | } 24 | 25 | public static function tearDownAfterClass() 26 | { 27 | static::createClient()->getContainer()->get('database_connection') 28 | ->exec("DELETE FROM FormFieldAnswer"); 29 | 30 | static::createClient()->getContainer()->get('database_connection') 31 | ->exec("DELETE FROM FormAnswer"); 32 | 33 | static::createClient()->getContainer()->get('database_connection') 34 | ->exec("DELETE FROM FormField"); 35 | 36 | static::createClient()->getContainer()->get('database_connection') 37 | ->exec("DELETE FROM Form"); 38 | } 39 | 40 | public function testList() 41 | { 42 | $client = static::createClient(); 43 | 44 | $crawler = $client->request('GET', '/form/'); 45 | $this->assertTrue($crawler->filter('html:contains("toto")')->count() > 0, $client->getResponse()->getContent()); 46 | } 47 | 48 | public function testCreateForm() 49 | { 50 | $client = static::createClient(); 51 | 52 | $crawler = $client->request('GET', '/form/'); 53 | $crawler = $client->click($crawler->selectLink('create a form')->link()); 54 | 55 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 56 | } 57 | 58 | public function testAddField() 59 | { 60 | $client = static::createClient(); 61 | 62 | $crawler = $client->request('GET', '/form/create'); 63 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 64 | 65 | $crawler = $client->click($crawler->selectLink('field')->link()); 66 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 67 | 68 | $form = $crawler->selectButton('save')->form(array('form[label]' => 'field label')); 69 | $crawler = $client->submit($form); 70 | $this->assertEquals(302, $client->getResponse()->getStatusCode()); 71 | $crawler = $client->followRedirect(); 72 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 73 | 74 | $this->assertEquals(1, $crawler->filter('html:contains("field label")')->count()); 75 | 76 | $session = $client->getContainer()->get('session'); 77 | $formid = str_replace('/form/edit/', '', $client->getRequest()->getRequestUri()); 78 | $this->assertTrue($session->has('forms_'.$formid)); 79 | $field = $session->get('forms_'.$formid); 80 | $this->assertArrayHasKey(0, $field); 81 | $this->assertEquals('field', $field[0]['type']); 82 | $this->assertEquals('field label', $field[0]['options']['label']); 83 | } 84 | 85 | public function testEditForm() 86 | { 87 | $client = self::createClient(); 88 | 89 | $crawler = $client->request('GET', '/form/edit/'.self::$formId); 90 | $this->assertEquals(200, $client->getResponse()->getStatusCode(), $client->getResponse()->getContent()); 91 | 92 | $form = $crawler->selectButton('save')->form(array('form[name]' => 'A form')); 93 | $crawler = $client->submit($form); 94 | $this->assertTrue($client->getResponse()->isRedirect(), $client->getResponse()->getContent()); 95 | 96 | $crawler = $client->followRedirect(); 97 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 98 | 99 | $this->assertTrue($crawler->filter('html:contains("A form")')->count() > 0); 100 | } 101 | 102 | public function testEditField() 103 | { 104 | $client = static::createClient(); 105 | 106 | $crawler = $client->request('GET', '/form/edit/'.self::$formId); 107 | $this->assertEquals(200, $client->getResponse()->getStatusCode(), $client->getResponse()->getContent()); 108 | 109 | $crawler = $client->click($crawler->selectLink('edit')->link()); 110 | $this->assertEquals(200, $client->getResponse()->getStatusCode(), $client->getResponse()->getContent()); 111 | $form = $crawler->selectButton('save')->form(array('form[label]' => 'field label edited')); 112 | $crawler = $client->submit($form); 113 | 114 | $crawler = $client->followRedirect(); 115 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 116 | 117 | $this->assertTrue($crawler->filter('html:contains("field label")')->count() > 0, $client->getResponse()->getContent()); 118 | 119 | $session = $client->getContainer()->get('session'); 120 | $this->assertTrue($session->has('forms_'.self::$formId)); 121 | $field = $session->get('forms_'.self::$formId); 122 | $this->assertArrayHasKey(0, $field); 123 | $this->assertEquals('field', $field[0]['type']); 124 | $this->assertEquals('field label edited', $field[0]['options']['label']); 125 | } 126 | 127 | public function testDeleteField() 128 | { 129 | $client = self::createClient(); 130 | 131 | // delete a field 132 | $crawler = $client->request('GET', '/form/edit/'.self::$formId); 133 | $crawler = $client->click($crawler->selectLink('delete')->link()); 134 | $crawler = $client->followRedirect(); 135 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 136 | } 137 | 138 | public function testAnswer() 139 | { 140 | $client = $this->createClient(); 141 | } 142 | 143 | public function testAnswerForm() 144 | { 145 | $client = self::createClient(); 146 | 147 | // go to answer a form 148 | $crawler = $client->request('GET', '/form/'); 149 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 150 | $this->assertEquals(1, $crawler->filter('html:contains("A form")')->count()); 151 | $crawler = $client->click($crawler->selectLink('answer')->link()); 152 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 153 | 154 | // answer the form 155 | $form = $crawler->selectButton('answer it!')->form(array( 156 | 'form[0]' => 'field1', 157 | 'form[1]' => 'field2', 158 | )); 159 | $crawler = $client->submit($form); 160 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 161 | 162 | $this->assertEquals(1, $crawler->filter('html:contains("field1")')->count()); 163 | $this->assertEquals(1, $crawler->filter('html:contains("field2")')->count()); 164 | } 165 | 166 | public function testDeleteForm() 167 | { 168 | $client = self::createClient(); 169 | 170 | // delete a form 171 | $crawler = $client->request('GET', '/form/'); 172 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 173 | $this->assertEquals(1, $crawler->filter('html:contains("A form")')->count()); 174 | $crawler = $client->click($crawler->selectLink('delete')->link()); 175 | $crawler = $client->followRedirect(); 176 | $this->assertEquals(200, $client->getResponse()->getStatusCode()); 177 | $this->assertEquals(0, $crawler->filter('html:contains("A form")')->count(), $client->getResponse()->getContent()); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Balloon/Bundle/FormBuilderBundle/Resources/public/bootstrap.min.css: -------------------------------------------------------------------------------- 1 | html,body{margin:0;padding:0;} 2 | h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite,code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var,dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;} 3 | table{border-collapse:collapse;border-spacing:0;} 4 | ol,ul{list-style:none;} 5 | q:before,q:after,blockquote:before,blockquote:after{content:"";} 6 | html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} 7 | a:focus{outline:thin dotted;} 8 | a:hover,a:active{outline:0;} 9 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} 10 | audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} 11 | audio:not([controls]){display:none;} 12 | sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;} 13 | sup{top:-0.5em;} 14 | sub{bottom:-0.25em;} 15 | img{border:0;-ms-interpolation-mode:bicubic;} 16 | button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;} 17 | button,input{line-height:normal;*overflow:visible;} 18 | button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 19 | button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} 20 | input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;} 21 | input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;} 22 | textarea{overflow:auto;vertical-align:top;} 23 | body{background-color:#ffffff;margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;color:#404040;} 24 | .container{width:940px;margin-left:auto;margin-right:auto;zoom:1;}.container:before,.container:after{display:table;content:"";zoom:1;} 25 | .container:after{clear:both;} 26 | .container-fluid{position:relative;min-width:940px;padding-left:20px;padding-right:20px;zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";zoom:1;} 27 | .container-fluid:after{clear:both;} 28 | .container-fluid>.sidebar{position:absolute;top:0;left:20px;width:220px;} 29 | .container-fluid>.content{margin-left:240px;} 30 | a{color:#0069d6;text-decoration:none;line-height:inherit;font-weight:inherit;}a:hover{color:#00438a;text-decoration:underline;} 31 | .pull-right{float:right;} 32 | .pull-left{float:left;} 33 | .hide{display:none;} 34 | .show{display:block;} 35 | .row{zoom:1;margin-left:-20px;}.row:before,.row:after{display:table;content:"";zoom:1;} 36 | .row:after{clear:both;} 37 | .row>[class*="span"]{display:inline;float:left;margin-left:20px;} 38 | .span1{width:40px;} 39 | .span2{width:100px;} 40 | .span3{width:160px;} 41 | .span4{width:220px;} 42 | .span5{width:280px;} 43 | .span6{width:340px;} 44 | .span7{width:400px;} 45 | .span8{width:460px;} 46 | .span9{width:520px;} 47 | .span10{width:580px;} 48 | .span11{width:640px;} 49 | .span12{width:700px;} 50 | .span13{width:760px;} 51 | .span14{width:820px;} 52 | .span15{width:880px;} 53 | .span16{width:940px;} 54 | .span17{width:1000px;} 55 | .span18{width:1060px;} 56 | .span19{width:1120px;} 57 | .span20{width:1180px;} 58 | .span21{width:1240px;} 59 | .span22{width:1300px;} 60 | .span23{width:1360px;} 61 | .span24{width:1420px;} 62 | .row>.offset1{margin-left:80px;} 63 | .row>.offset2{margin-left:140px;} 64 | .row>.offset3{margin-left:200px;} 65 | .row>.offset4{margin-left:260px;} 66 | .row>.offset5{margin-left:320px;} 67 | .row>.offset6{margin-left:380px;} 68 | .row>.offset7{margin-left:440px;} 69 | .row>.offset8{margin-left:500px;} 70 | .row>.offset9{margin-left:560px;} 71 | .row>.offset10{margin-left:620px;} 72 | .row>.offset11{margin-left:680px;} 73 | .row>.offset12{margin-left:740px;} 74 | .span-one-third{width:300px;} 75 | .span-two-thirds{width:620px;} 76 | .row>.offset-one-third{margin-left:340px;} 77 | .row>.offset-two-thirds{margin-left:660px;} 78 | p{font-size:13px;font-weight:normal;line-height:18px;margin-bottom:9px;}p small{font-size:11px;color:#bfbfbf;} 79 | h1,h2,h3,h4,h5,h6{font-weight:bold;color:#404040;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#bfbfbf;} 80 | h1{margin-bottom:18px;font-size:30px;line-height:36px;}h1 small{font-size:18px;} 81 | h2{font-size:24px;line-height:36px;}h2 small{font-size:14px;} 82 | h3,h4,h5,h6{line-height:36px;} 83 | h3{font-size:18px;}h3 small{font-size:14px;} 84 | h4{font-size:16px;}h4 small{font-size:12px;} 85 | h5{font-size:14px;} 86 | h6{font-size:13px;color:#bfbfbf;text-transform:uppercase;} 87 | ul,ol{margin:0 0 18px 25px;} 88 | ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} 89 | ul{list-style:disc;} 90 | ol{list-style:decimal;} 91 | li{line-height:18px;color:#808080;} 92 | ul.unstyled{list-style:none;margin-left:0;} 93 | dl{margin-bottom:18px;}dl dt,dl dd{line-height:18px;} 94 | dl dt{font-weight:bold;} 95 | dl dd{margin-left:9px;} 96 | hr{margin:20px 0 19px;border:0;border-bottom:1px solid #eee;} 97 | strong{font-style:inherit;font-weight:bold;} 98 | em{font-style:italic;font-weight:inherit;line-height:inherit;} 99 | .muted{color:#bfbfbf;} 100 | blockquote{margin-bottom:18px;border-left:5px solid #eee;padding-left:15px;}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;} 101 | blockquote small{display:block;font-size:12px;font-weight:300;line-height:18px;color:#bfbfbf;}blockquote small:before{content:'\2014 \00A0';} 102 | address{display:block;line-height:18px;margin-bottom:18px;} 103 | code,pre{padding:0 3px 2px;font-family:Monaco, Andale Mono, Courier New, monospace;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 104 | code{background-color:#fee9cc;color:rgba(0, 0, 0, 0.75);padding:1px 3px;} 105 | pre{background-color:#f5f5f5;display:block;padding:8.5px;margin:0 0 18px;line-height:18px;font-size:12px;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;white-space:pre;white-space:pre-wrap;word-wrap:break-word;} 106 | form{margin-bottom:18px;} 107 | fieldset{margin-bottom:18px;padding-top:18px;}fieldset legend{display:block;padding-left:150px;font-size:19.5px;line-height:1;color:#404040;*padding:0 0 5px 145px;*line-height:1.5;} 108 | form .clearfix{margin-bottom:18px;zoom:1;}form .clearfix:before,form .clearfix:after{display:table;content:"";zoom:1;} 109 | form .clearfix:after{clear:both;} 110 | label,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:normal;} 111 | label{padding-top:6px;font-size:13px;line-height:18px;float:left;width:130px;text-align:right;color:#404040;} 112 | form .input{margin-left:150px;} 113 | input[type=checkbox],input[type=radio]{cursor:pointer;} 114 | input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;font-size:13px;line-height:18px;color:#808080;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} 115 | select{padding:initial;} 116 | input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;} 117 | input[type=file]{background-color:#ffffff;padding:initial;border:initial;line-height:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} 118 | input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;} 119 | select,input[type=file]{height:27px;*height:auto;line-height:27px;*margin-top:4px;} 120 | select[multiple]{height:inherit;background-color:#ffffff;} 121 | textarea{height:auto;} 122 | .uneditable-input{background-color:#ffffff;display:block;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;} 123 | :-moz-placeholder{color:#bfbfbf;} 124 | ::-webkit-input-placeholder{color:#bfbfbf;} 125 | input,textarea{-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);} 126 | input:focus,textarea:focus{outline:0;border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);} 127 | input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;} 128 | form .clearfix.error>label,form .clearfix.error .help-block,form .clearfix.error .help-inline{color:#b94a48;} 129 | form .clearfix.error input,form .clearfix.error textarea{color:#b94a48;border-color:#ee5f5b;}form .clearfix.error input:focus,form .clearfix.error textarea:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;} 130 | form .clearfix.error .input-prepend .add-on,form .clearfix.error .input-append .add-on{color:#b94a48;background-color:#fce6e6;border-color:#b94a48;} 131 | form .clearfix.warning>label,form .clearfix.warning .help-block,form .clearfix.warning .help-inline{color:#c09853;} 132 | form .clearfix.warning input,form .clearfix.warning textarea{color:#c09853;border-color:#ccae64;}form .clearfix.warning input:focus,form .clearfix.warning textarea:focus{border-color:#be9a3f;-webkit-box-shadow:0 0 6px #e5d6b1;-moz-box-shadow:0 0 6px #e5d6b1;box-shadow:0 0 6px #e5d6b1;} 133 | form .clearfix.warning .input-prepend .add-on,form .clearfix.warning .input-append .add-on{color:#c09853;background-color:#d2b877;border-color:#c09853;} 134 | form .clearfix.success>label,form .clearfix.success .help-block,form .clearfix.success .help-inline{color:#468847;} 135 | form .clearfix.success input,form .clearfix.success textarea{color:#468847;border-color:#57a957;}form .clearfix.success input:focus,form .clearfix.success textarea:focus{border-color:#458845;-webkit-box-shadow:0 0 6px #9acc9a;-moz-box-shadow:0 0 6px #9acc9a;box-shadow:0 0 6px #9acc9a;} 136 | form .clearfix.success .input-prepend .add-on,form .clearfix.success .input-append .add-on{color:#468847;background-color:#bcddbc;border-color:#468847;} 137 | .input-mini,input.mini,textarea.mini,select.mini{width:60px;} 138 | .input-small,input.small,textarea.small,select.small{width:90px;} 139 | .input-medium,input.medium,textarea.medium,select.medium{width:150px;} 140 | .input-large,input.large,textarea.large,select.large{width:210px;} 141 | .input-xlarge,input.xlarge,textarea.xlarge,select.xlarge{width:270px;} 142 | .input-xxlarge,input.xxlarge,textarea.xxlarge,select.xxlarge{width:530px;} 143 | textarea.xxlarge{overflow-y:auto;} 144 | input.span1,textarea.span1{display:inline-block;float:none;width:30px;margin-left:0;} 145 | input.span2,textarea.span2{display:inline-block;float:none;width:90px;margin-left:0;} 146 | input.span3,textarea.span3{display:inline-block;float:none;width:150px;margin-left:0;} 147 | input.span4,textarea.span4{display:inline-block;float:none;width:210px;margin-left:0;} 148 | input.span5,textarea.span5{display:inline-block;float:none;width:270px;margin-left:0;} 149 | input.span6,textarea.span6{display:inline-block;float:none;width:330px;margin-left:0;} 150 | input.span7,textarea.span7{display:inline-block;float:none;width:390px;margin-left:0;} 151 | input.span8,textarea.span8{display:inline-block;float:none;width:450px;margin-left:0;} 152 | input.span9,textarea.span9{display:inline-block;float:none;width:510px;margin-left:0;} 153 | input.span10,textarea.span10{display:inline-block;float:none;width:570px;margin-left:0;} 154 | input.span11,textarea.span11{display:inline-block;float:none;width:630px;margin-left:0;} 155 | input.span12,textarea.span12{display:inline-block;float:none;width:690px;margin-left:0;} 156 | input.span13,textarea.span13{display:inline-block;float:none;width:750px;margin-left:0;} 157 | input.span14,textarea.span14{display:inline-block;float:none;width:810px;margin-left:0;} 158 | input.span15,textarea.span15{display:inline-block;float:none;width:870px;margin-left:0;} 159 | input.span16,textarea.span16{display:inline-block;float:none;width:930px;margin-left:0;} 160 | input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;} 161 | .actions{background:#f5f5f5;margin-top:18px;margin-bottom:18px;padding:17px 20px 18px 150px;border-top:1px solid #ddd;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;}.actions .secondary-action{float:right;}.actions .secondary-action a{line-height:30px;}.actions .secondary-action a:hover{text-decoration:underline;} 162 | .help-inline,.help-block{font-size:13px;line-height:18px;color:#bfbfbf;} 163 | .help-inline{padding-left:5px;*position:relative;*top:-5px;} 164 | .help-block{display:block;max-width:600px;} 165 | .inline-inputs{color:#808080;}.inline-inputs span{padding:0 2px 0 1px;} 166 | .input-prepend input,.input-append input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} 167 | .input-prepend .add-on,.input-append .add-on{position:relative;background:#f5f5f5;border:1px solid #ccc;z-index:2;float:left;display:block;width:auto;min-width:16px;height:18px;padding:4px 4px 4px 5px;margin-right:-1px;font-weight:normal;line-height:18px;color:#bfbfbf;text-align:center;text-shadow:0 1px 0 #ffffff;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} 168 | .input-prepend .active,.input-append .active{background:#a9dba9;border-color:#46a546;} 169 | .input-prepend .add-on{*margin-top:1px;} 170 | .input-append input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} 171 | .input-append .add-on{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;margin-right:0;margin-left:-1px;} 172 | .inputs-list{margin:0 0 5px;width:100%;}.inputs-list li{display:block;padding:0;width:100%;} 173 | .inputs-list label{display:block;float:none;width:auto;padding:0;margin-left:20px;line-height:18px;text-align:left;white-space:normal;}.inputs-list label strong{color:#808080;} 174 | .inputs-list label small{font-size:11px;font-weight:normal;} 175 | .inputs-list .inputs-list{margin-left:25px;margin-bottom:10px;padding-top:0;} 176 | .inputs-list:first-child{padding-top:6px;} 177 | .inputs-list li+li{padding-top:2px;} 178 | .inputs-list input[type=radio],.inputs-list input[type=checkbox]{margin-bottom:0;margin-left:-20px;float:left;} 179 | .form-stacked{padding-left:20px;}.form-stacked fieldset{padding-top:9px;} 180 | .form-stacked legend{padding-left:0;} 181 | .form-stacked label{display:block;float:none;width:auto;font-weight:bold;text-align:left;line-height:20px;padding-top:0;} 182 | .form-stacked .clearfix{margin-bottom:9px;}.form-stacked .clearfix div.input{margin-left:0;} 183 | .form-stacked .inputs-list{margin-bottom:0;}.form-stacked .inputs-list li{padding-top:0;}.form-stacked .inputs-list li label{font-weight:normal;padding-top:0;} 184 | .form-stacked div.clearfix.error{padding-top:10px;padding-bottom:10px;padding-left:10px;margin-top:0;margin-left:-10px;} 185 | .form-stacked .actions{margin-left:-20px;padding-left:20px;} 186 | table{width:100%;margin-bottom:18px;padding:0;font-size:13px;border-collapse:collapse;}table th,table td{padding:10px 10px 9px;line-height:18px;text-align:left;} 187 | table th{padding-top:9px;font-weight:bold;vertical-align:middle;} 188 | table td{vertical-align:top;border-top:1px solid #ddd;} 189 | table tbody th{border-top:1px solid #ddd;vertical-align:top;} 190 | .condensed-table th,.condensed-table td{padding:5px 5px 4px;} 191 | .bordered-table{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.bordered-table th+th,.bordered-table td+td,.bordered-table th+td{border-left:1px solid #ddd;} 192 | .bordered-table thead tr:first-child th:first-child,.bordered-table tbody tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;} 193 | .bordered-table thead tr:first-child th:last-child,.bordered-table tbody tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;} 194 | .bordered-table tbody tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;} 195 | .bordered-table tbody tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;} 196 | table .span1{width:20px;} 197 | table .span2{width:60px;} 198 | table .span3{width:100px;} 199 | table .span4{width:140px;} 200 | table .span5{width:180px;} 201 | table .span6{width:220px;} 202 | table .span7{width:260px;} 203 | table .span8{width:300px;} 204 | table .span9{width:340px;} 205 | table .span10{width:380px;} 206 | table .span11{width:420px;} 207 | table .span12{width:460px;} 208 | table .span13{width:500px;} 209 | table .span14{width:540px;} 210 | table .span15{width:580px;} 211 | table .span16{width:620px;} 212 | .zebra-striped tbody tr:nth-child(odd) td,.zebra-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;} 213 | .zebra-striped tbody tr:hover td,.zebra-striped tbody tr:hover th{background-color:#f5f5f5;} 214 | table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:hidden;} 215 | table .headerSortUp,table .headerSortDown{background-color:rgba(141, 192, 219, 0.25);text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);} 216 | table .header:hover:after{visibility:visible;} 217 | table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} 218 | table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;} 219 | table .blue{color:#049cdb;border-bottom-color:#049cdb;} 220 | table .headerSortUp.blue,table .headerSortDown.blue{background-color:#ade6fe;} 221 | table .green{color:#46a546;border-bottom-color:#46a546;} 222 | table .headerSortUp.green,table .headerSortDown.green{background-color:#cdeacd;} 223 | table .red{color:#9d261d;border-bottom-color:#9d261d;} 224 | table .headerSortUp.red,table .headerSortDown.red{background-color:#f4c8c5;} 225 | table .yellow{color:#ffc40d;border-bottom-color:#ffc40d;} 226 | table .headerSortUp.yellow,table .headerSortDown.yellow{background-color:#fff6d9;} 227 | table .orange{color:#f89406;border-bottom-color:#f89406;} 228 | table .headerSortUp.orange,table .headerSortDown.orange{background-color:#fee9cc;} 229 | table .purple{color:#7a43b6;border-bottom-color:#7a43b6;} 230 | table .headerSortUp.purple,table .headerSortDown.purple{background-color:#e2d5f0;} 231 | .topbar{height:40px;position:fixed;top:0;left:0;right:0;z-index:10000;overflow:visible;}.topbar a{color:#bfbfbf;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} 232 | .topbar h3 a:hover,.topbar .brand:hover,.topbar ul .active>a{background-color:#333;background-color:rgba(255, 255, 255, 0.05);color:#ffffff;text-decoration:none;} 233 | .topbar h3{position:relative;} 234 | .topbar h3 a,.topbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;color:#ffffff;font-size:20px;font-weight:200;line-height:1;} 235 | .topbar p{margin:0;line-height:40px;}.topbar p a:hover{background-color:transparent;color:#ffffff;} 236 | .topbar form{float:left;margin:5px 0 0 0;position:relative;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;} 237 | .topbar form.pull-right{float:right;} 238 | .topbar input{background-color:#444;background-color:rgba(255, 255, 255, 0.3);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:normal;font-weight:13px;line-height:1;padding:4px 9px;color:#ffffff;color:rgba(255, 255, 255, 0.75);border:1px solid #111;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.topbar input:-moz-placeholder{color:#e6e6e6;} 239 | .topbar input::-webkit-input-placeholder{color:#e6e6e6;} 240 | .topbar input:hover{background-color:#bfbfbf;background-color:rgba(255, 255, 255, 0.5);color:#ffffff;} 241 | .topbar input:focus,.topbar input.focused{outline:0;background-color:#ffffff;color:#404040;text-shadow:0 1px 0 #ffffff;border:0;padding:5px 10px;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);} 242 | .topbar-inner,.topbar .fill{background-color:#222;background-color:#222222;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222));background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);} 243 | .topbar div>ul,.nav{display:block;float:left;margin:0 10px 0 0;position:relative;left:0;}.topbar div>ul>li,.nav>li{display:block;float:left;} 244 | .topbar div>ul a,.nav a{display:block;float:none;padding:10px 10px 11px;line-height:19px;text-decoration:none;}.topbar div>ul a:hover,.nav a:hover{color:#ffffff;text-decoration:none;} 245 | .topbar div>ul .active>a,.nav .active>a{background-color:#222;background-color:rgba(0, 0, 0, 0.5);} 246 | .topbar div>ul.secondary-nav,.nav.secondary-nav{float:right;margin-left:10px;margin-right:0;}.topbar div>ul.secondary-nav .menu-dropdown,.nav.secondary-nav .menu-dropdown,.topbar div>ul.secondary-nav .dropdown-menu,.nav.secondary-nav .dropdown-menu{right:0;border:0;} 247 | .topbar div>ul a.menu:hover,.nav a.menu:hover,.topbar div>ul li.open .menu,.nav li.open .menu,.topbar div>ul .dropdown-toggle:hover,.nav .dropdown-toggle:hover,.topbar div>ul .dropdown.open .dropdown-toggle,.nav .dropdown.open .dropdown-toggle{background:#444;background:rgba(255, 255, 255, 0.05);} 248 | .topbar div>ul .menu-dropdown,.nav .menu-dropdown,.topbar div>ul .dropdown-menu,.nav .dropdown-menu{background-color:#333;}.topbar div>ul .menu-dropdown a.menu,.nav .menu-dropdown a.menu,.topbar div>ul .dropdown-menu a.menu,.nav .dropdown-menu a.menu,.topbar div>ul .menu-dropdown .dropdown-toggle,.nav .menu-dropdown .dropdown-toggle,.topbar div>ul .dropdown-menu .dropdown-toggle,.nav .dropdown-menu .dropdown-toggle{color:#ffffff;}.topbar div>ul .menu-dropdown a.menu.open,.nav .menu-dropdown a.menu.open,.topbar div>ul .dropdown-menu a.menu.open,.nav .dropdown-menu a.menu.open,.topbar div>ul .menu-dropdown .dropdown-toggle.open,.nav .menu-dropdown .dropdown-toggle.open,.topbar div>ul .dropdown-menu .dropdown-toggle.open,.nav .dropdown-menu .dropdown-toggle.open{background:#444;background:rgba(255, 255, 255, 0.05);} 249 | .topbar div>ul .menu-dropdown li a,.nav .menu-dropdown li a,.topbar div>ul .dropdown-menu li a,.nav .dropdown-menu li a{color:#999;text-shadow:0 1px 0 rgba(0, 0, 0, 0.5);}.topbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.topbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{background-color:#191919;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#292929), to(#191919));background-image:-moz-linear-gradient(top, #292929, #191919);background-image:-ms-linear-gradient(top, #292929, #191919);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #292929), color-stop(100%, #191919));background-image:-webkit-linear-gradient(top, #292929, #191919);background-image:-o-linear-gradient(top, #292929, #191919);background-image:linear-gradient(top, #292929, #191919);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#292929', endColorstr='#191919', GradientType=0);color:#ffffff;} 250 | .topbar div>ul .menu-dropdown .active a,.nav .menu-dropdown .active a,.topbar div>ul .dropdown-menu .active a,.nav .dropdown-menu .active a{color:#ffffff;} 251 | .topbar div>ul .menu-dropdown .divider,.nav .menu-dropdown .divider,.topbar div>ul .dropdown-menu .divider,.nav .dropdown-menu .divider{background-color:#222;border-color:#444;} 252 | .topbar ul .menu-dropdown li a,.topbar ul .dropdown-menu li a{padding:4px 15px;} 253 | li.menu,.dropdown{position:relative;} 254 | a.menu:after,.dropdown-toggle:after{width:0;height:0;display:inline-block;content:"↓";text-indent:-99999px;vertical-align:top;margin-top:8px;margin-left:4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #ffffff;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;} 255 | .menu-dropdown,.dropdown-menu{background-color:#ffffff;float:left;display:none;position:absolute;top:40px;z-index:900;min-width:160px;max-width:220px;_width:160px;margin-left:0;margin-right:0;padding:6px 0;zoom:1;border-color:#999;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:0 1px 1px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.menu-dropdown li,.dropdown-menu li{float:none;display:block;background-color:none;} 256 | .menu-dropdown .divider,.dropdown-menu .divider{height:1px;margin:5px 0;overflow:hidden;background-color:#eee;border-bottom:1px solid #ffffff;} 257 | .topbar .dropdown-menu a,.dropdown-menu a{display:block;padding:4px 15px;clear:both;font-weight:normal;line-height:18px;color:#808080;text-shadow:0 1px 0 #ffffff;}.topbar .dropdown-menu a:hover,.dropdown-menu a:hover,.topbar .dropdown-menu a.hover,.dropdown-menu a.hover{background-color:#dddddd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));background-image:-moz-linear-gradient(top, #eeeeee, #dddddd);background-image:-ms-linear-gradient(top, #eeeeee, #dddddd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #dddddd));background-image:-webkit-linear-gradient(top, #eeeeee, #dddddd);background-image:-o-linear-gradient(top, #eeeeee, #dddddd);background-image:linear-gradient(top, #eeeeee, #dddddd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);color:#404040;text-decoration:none;-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);} 258 | .open .menu,.dropdown.open .menu,.open .dropdown-toggle,.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);} 259 | .open .menu-dropdown,.dropdown.open .menu-dropdown,.open .dropdown-menu,.dropdown.open .dropdown-menu{display:block;} 260 | .tabs,.pills{margin:0 0 18px;padding:0;list-style:none;zoom:1;}.tabs:before,.pills:before,.tabs:after,.pills:after{display:table;content:"";zoom:1;} 261 | .tabs:after,.pills:after{clear:both;} 262 | .tabs>li,.pills>li{float:left;}.tabs>li>a,.pills>li>a{display:block;} 263 | .tabs{border-color:#ddd;border-style:solid;border-width:0 0 1px;}.tabs>li{position:relative;margin-bottom:-1px;}.tabs>li>a{padding:0 15px;margin-right:2px;line-height:34px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.tabs>li>a:hover{text-decoration:none;background-color:#eee;border-color:#eee #eee #ddd;} 264 | .tabs .active>a,.tabs .active>a:hover{color:#808080;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;} 265 | .tabs .menu-dropdown,.tabs .dropdown-menu{top:35px;border-width:1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;} 266 | .tabs a.menu:after,.tabs .dropdown-toggle:after{border-top-color:#999;margin-top:15px;margin-left:5px;} 267 | .tabs li.open.menu .menu,.tabs .open.dropdown .dropdown-toggle{border-color:#999;} 268 | .tabs li.open a.menu:after,.tabs .dropdown.open .dropdown-toggle:after{border-top-color:#555;} 269 | .pills a{margin:5px 3px 5px 0;padding:0 15px;line-height:30px;text-shadow:0 1px 1px #ffffff;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}.pills a:hover{color:#ffffff;text-decoration:none;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#00438a;} 270 | .pills .active a{color:#ffffff;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#0069d6;} 271 | .pills-vertical>li{float:none;} 272 | .tab-content>.tab-pane,.pill-content>.pill-pane,.tab-content>div,.pill-content>div{display:none;} 273 | .tab-content>.active,.pill-content>.active{display:block;} 274 | .breadcrumb{padding:7px 14px;margin:0 0 18px;background-color:#f5f5f5;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5));background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;} 275 | .breadcrumb .divider{padding:0 5px;color:#bfbfbf;} 276 | .breadcrumb .active a{color:#404040;} 277 | .hero-unit{background-color:#f5f5f5;margin-bottom:30px;padding:60px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;} 278 | .hero-unit p{font-size:18px;font-weight:200;line-height:27px;} 279 | footer{margin-top:17px;padding-top:17px;border-top:1px solid #eee;} 280 | .page-header{margin-bottom:17px;border-bottom:1px solid #ddd;-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}.page-header h1{margin-bottom:8px;} 281 | .btn.danger,.alert-message.danger,.btn.danger:hover,.alert-message.danger:hover,.btn.error,.alert-message.error,.btn.error:hover,.alert-message.error:hover,.btn.success,.alert-message.success,.btn.success:hover,.alert-message.success:hover,.btn.info,.alert-message.info,.btn.info:hover,.alert-message.info:hover{color:#ffffff;} 282 | .btn .close,.alert-message .close{font-family:Arial,sans-serif;line-height:18px;} 283 | .btn.danger,.alert-message.danger,.btn.error,.alert-message.error{background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} 284 | .btn.success,.alert-message.success{background-color:#57a957;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} 285 | .btn.info,.alert-message.info{background-color:#339bb9;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} 286 | .btn{cursor:pointer;display:inline-block;background-color:#e6e6e6;background-repeat:no-repeat;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);padding:5px 14px 6px;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);color:#333;font-size:13px;line-height:normal;border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{background-position:0 -15px;color:#333;text-decoration:none;} 287 | .btn:focus{outline:1px dotted #666;} 288 | .btn.primary{color:#ffffff;background-color:#0064cd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} 289 | .btn.active,.btn:active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);} 290 | .btn.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} 291 | .btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} 292 | .btn.large{font-size:15px;line-height:normal;padding:9px 14px 9px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} 293 | .btn.small{padding:7px 9px 7px;font-size:11px;} 294 | :root .alert-message,:root .btn{border-radius:0 \0;} 295 | button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;} 296 | .close{float:right;color:#000000;font-size:20px;font-weight:bold;line-height:13.5px;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=25);-khtml-opacity:0.25;-moz-opacity:0.25;opacity:0.25;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-khtml-opacity:0.4;-moz-opacity:0.4;opacity:0.4;} 297 | .alert-message{position:relative;padding:7px 15px;margin-bottom:18px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);}.alert-message .close{margin-top:1px;*margin-top:0;} 298 | .alert-message a{font-weight:bold;color:#404040;} 299 | .alert-message.danger p a,.alert-message.error p a,.alert-message.success p a,.alert-message.info p a{color:#ffffff;} 300 | .alert-message h5{line-height:18px;} 301 | .alert-message p{margin-bottom:0;} 302 | .alert-message div{margin-top:5px;margin-bottom:2px;line-height:28px;} 303 | .alert-message .btn{-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);} 304 | .alert-message.block-message{background-image:none;background-color:#fdf5d9;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);padding:14px;border-color:#fceec1;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.alert-message.block-message ul,.alert-message.block-message p{margin-right:30px;} 305 | .alert-message.block-message ul{margin-bottom:0;} 306 | .alert-message.block-message li{color:#404040;} 307 | .alert-message.block-message .alert-actions{margin-top:5px;} 308 | .alert-message.block-message.error,.alert-message.block-message.success,.alert-message.block-message.info{color:#404040;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} 309 | .alert-message.block-message.error{background-color:#fddfde;border-color:#fbc7c6;} 310 | .alert-message.block-message.success{background-color:#d1eed1;border-color:#bfe7bf;} 311 | .alert-message.block-message.info{background-color:#ddf4fb;border-color:#c6edf9;} 312 | .alert-message.block-message.danger p a,.alert-message.block-message.error p a,.alert-message.block-message.success p a,.alert-message.block-message.info p a{color:#404040;} 313 | .pagination{height:36px;margin:18px 0;}.pagination ul{float:left;margin:0;border:1px solid #ddd;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} 314 | .pagination li{display:inline;} 315 | .pagination a{float:left;padding:0 14px;line-height:34px;border-right:1px solid;border-right-color:#ddd;border-right-color:rgba(0, 0, 0, 0.15);*border-right-color:#ddd;text-decoration:none;} 316 | .pagination a:hover,.pagination .active a{background-color:#c7eefe;} 317 | .pagination .disabled a,.pagination .disabled a:hover{background-color:transparent;color:#bfbfbf;} 318 | .pagination .next a{border:0;} 319 | .well{background-color:#f5f5f5;margin-bottom:20px;padding:19px;min-height:20px;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} 320 | .modal-backdrop{background-color:#000000;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;}.modal-backdrop.fade{opacity:0;} 321 | .modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} 322 | .modal{position:fixed;top:50%;left:50%;z-index:11000;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal .close{margin-top:7px;} 323 | .modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;} 324 | .modal.fade.in{top:50%;} 325 | .modal-header{border-bottom:1px solid #eee;padding:5px 15px;} 326 | .modal-body{padding:15px;} 327 | .modal-body form{margin-bottom:0;} 328 | .modal-footer{background-color:#f5f5f5;padding:14px 15px 15px;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;zoom:1;margin-bottom:0;}.modal-footer:before,.modal-footer:after{display:table;content:"";zoom:1;} 329 | .modal-footer:after{clear:both;} 330 | .modal-footer .btn{float:right;margin-left:5px;} 331 | .modal .popover,.modal .twipsy{z-index:12000;} 332 | .twipsy{display:block;position:absolute;visibility:visible;padding:5px;font-size:11px;z-index:1000;filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}.twipsy.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;} 333 | .twipsy.above .twipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} 334 | .twipsy.left .twipsy-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} 335 | .twipsy.below .twipsy-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} 336 | .twipsy.right .twipsy-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} 337 | .twipsy-inner{padding:3px 8px;background-color:#000000;color:white;text-align:center;max-width:200px;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} 338 | .twipsy-arrow{position:absolute;width:0;height:0;} 339 | .popover{position:absolute;top:0;left:0;z-index:1000;padding:5px;display:none;}.popover.above .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} 340 | .popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} 341 | .popover.below .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} 342 | .popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} 343 | .popover .arrow{position:absolute;width:0;height:0;} 344 | .popover .inner{background:#000000;background:rgba(0, 0, 0, 0.8);padding:3px;overflow:hidden;width:280px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);} 345 | .popover .title{background-color:#f5f5f5;padding:9px 15px;line-height:1;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;border-bottom:1px solid #eee;} 346 | .popover .content{background-color:#ffffff;padding:14px;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover .content p,.popover .content ul,.popover .content ol{margin-bottom:0;} 347 | .fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;} 348 | .label{padding:1px 3px 2px;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;white-space:nowrap;background-color:#bfbfbf;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;} 349 | .label.warning{background-color:#f89406;} 350 | .label.success{background-color:#46a546;} 351 | .label.notice{background-color:#62cffc;} 352 | .media-grid{margin-left:-20px;margin-bottom:0;zoom:1;}.media-grid:before,.media-grid:after{display:table;content:"";zoom:1;} 353 | .media-grid:after{clear:both;} 354 | .media-grid li{display:inline;} 355 | .media-grid a{float:left;padding:4px;margin:0 0 18px 20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}.media-grid a img{display:block;} 356 | .media-grid a:hover{border-color:#0069d6;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);} 357 | --------------------------------------------------------------------------------