├── tests ├── .gitkeep ├── Fields │ ├── StaticTypeTest.php │ ├── RepeatedTypeTest.php │ ├── InputTypeTest.php │ ├── ChoiceTypeTest.php │ ├── ButtonTypeTest.php │ ├── EntityTypeTest.php │ └── CollectionTypeTest.php ├── Console │ └── FormGeneratorTest.php ├── FormBuilderTestCase.php ├── FormBuilderTest.php ├── FormHelperTest.php └── FormTest.php ├── .gitignore ├── src ├── Kris │ └── LaravelFormBuilder │ │ ├── Console │ │ ├── stubs │ │ │ └── form-class-template.stub │ │ ├── FormGenerator.php │ │ └── FormMakeCommand.php │ │ ├── Fields │ │ ├── InputType.php │ │ ├── TextareaType.php │ │ ├── SelectType.php │ │ ├── ButtonType.php │ │ ├── CheckableType.php │ │ ├── StaticType.php │ │ ├── RepeatedType.php │ │ ├── EntityType.php │ │ ├── ChildFormType.php │ │ ├── ParentType.php │ │ ├── ChoiceType.php │ │ ├── CollectionType.php │ │ └── FormField.php │ │ ├── Facades │ │ └── FormBuilder.php │ │ ├── FormBuilderTrait.php │ │ ├── FormBuilderServiceProvider.php │ │ ├── FormBuilder.php │ │ ├── FormHelper.php │ │ └── Form.php ├── views │ ├── form.php │ ├── button.php │ ├── static.php │ ├── repeated.php │ ├── text.php │ ├── textarea.php │ ├── collection.php │ ├── choice.php │ ├── select.php │ ├── child_form.php │ ├── radio.php │ └── checkbox.php ├── config │ └── config.php └── helpers.php ├── .travis.yml ├── composer.json ├── .scrutinizer.yml ├── LICENSE ├── phpunit.xml ├── README.md ├── CHANGELOG.md └── README_OLD.md /tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | .idea 6 | coverage 7 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Console/stubs/form-class-template.stub: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | getName(), $exclude) ) { ?> 8 | render() ?> 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/SelectType.php: -------------------------------------------------------------------------------- 1 | [], 17 | 'empty_value' => null, 18 | 'selected' => null 19 | ]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/ButtonType.php: -------------------------------------------------------------------------------- 1 | false, 20 | 'attr' => ['type' => $this->type] 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/views/button.php: -------------------------------------------------------------------------------- 1 | 2 |
> 3 | 4 | 5 | 6 | 7 | < > 8 | 9 | > 10 | 11 | 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | 9 | before_script: 10 | - travis_retry composer self-update 11 | - travis_retry composer install --prefer-source --no-interaction 12 | 13 | script: 14 | - phpunit --coverage-text --coverage-clover=coverage.clover 15 | 16 | after_script: 17 | - wget https://scrutinizer-ci.com/ocular.phar 18 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover 19 | 20 | notifications: 21 | email: 22 | recipients: 23 | - husakkristijan@gmail.com 24 | on_success: never 25 | on_failure: always 26 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/CheckableType.php: -------------------------------------------------------------------------------- 1 | type; 17 | } 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | public function getDefaults() 23 | { 24 | return [ 25 | 'attr' => ['class' => null, 'id' => $this->getName()], 26 | 'value' => 1, 27 | 'checked' => null 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kris/laravel4-form-builder", 3 | "description": "Laravel form builder - symfony like", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Kristijan Husak", 8 | "email": "husakkristijan@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.4.0", 13 | "illuminate/html": "4.2.*", 14 | "illuminate/database": "4.2.*" 15 | }, 16 | "require-dev": { 17 | "phpunit/phpunit": "~4.0", 18 | "mockery/mockery": "0.9.*" 19 | }, 20 | "extra": { 21 | "branch-alias": { 22 | "dev-master": "1.6.x-dev" 23 | } 24 | }, 25 | "autoload": { 26 | "psr-0": { 27 | "Kris\\LaravelFormBuilder": "src/" 28 | }, 29 | "classmap": [ 30 | "tests/FormBuilderTestCase.php" 31 | ], 32 | "files": [ 33 | "src/helpers.php" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/views/static.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | < >> 13 | 14 | 15 | < > 16 | 17 | > 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/FormBuilderTrait.php: -------------------------------------------------------------------------------- 1 | create($name, $options, $data); 18 | } 19 | 20 | /** 21 | * Create a plain Form instance 22 | * 23 | * @param array $options Options to pass to the form 24 | * @param array $data additional data to pass to the form 25 | * 26 | * @return \Kris\LaravelFormBuilder\Form 27 | */ 28 | protected function plain(array $options = [], array $data = []) 29 | { 30 | return \App::make('laravel-form-builder')->plain($options, $data); 31 | } 32 | } -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | php: 3 | code_rating: true 4 | remove_extra_empty_lines: true 5 | remove_php_closing_tag: true 6 | remove_trailing_whitespace: true 7 | fix_use_statements: 8 | remove_unused: true 9 | preserve_multiple: false 10 | preserve_blanklines: true 11 | order_alphabetically: true 12 | fix_php_opening_tag: true 13 | fix_linefeed: true 14 | fix_line_ending: true 15 | fix_identation_4spaces: true 16 | fix_doc_comments: true 17 | filter: 18 | excluded_paths: [tests/*, vendor/*, src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php] 19 | tools: 20 | external_code_coverage: 21 | timeout: 600 22 | php_code_coverage: false 23 | php_code_sniffer: 24 | config: 25 | standard: PSR2 26 | filter: 27 | paths: ['src'] 28 | php_loc: 29 | enabled: true 30 | excluded_dirs: [vendor, tests] 31 | php_cpd: 32 | enabled: true 33 | excluded_dirs: [vendor, tests] 34 | -------------------------------------------------------------------------------- /src/views/repeated.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
> 4 | 5 | 6 | 7 | 8 | render([], true, true, false) ?> 9 | render([], true, true, false) ?> 10 | 11 | 12 | < > 13 | 14 | > 15 | 16 | 17 | 18 | 19 | 20 | render([], false, false, true) ?> 21 | render([], false, false, true) ?> 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kristijan Husak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/StaticType.php: -------------------------------------------------------------------------------- 1 | setupStaticOptions($options); 8 | return parent::render($options, $showLabel, $showField, $showError); 9 | } 10 | 11 | /** 12 | * Setup static field options 13 | */ 14 | private function setupStaticOptions(&$options) 15 | { 16 | $options['elemAttrs'] = $this->formHelper->prepareAttributes($this->getOption('attr')); 17 | $options['labelAttrs'] = $this->formHelper->prepareAttributes($this->getOption('label_attr')); 18 | } 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | protected function getTemplate() 24 | { 25 | return 'static'; 26 | } 27 | 28 | /** 29 | * @inheritdoc 30 | */ 31 | protected function getDefaults() 32 | { 33 | return [ 34 | 'tag' => 'div', 35 | 'attr' => ['class' => 'form-control-static', 'id' => $this->getName()] 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | 21 | ./src/Kris 22 | 23 | ./src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php 24 | ./src/Kris/LaravelFormBuilder/Facades/FormBuilder.php 25 | ./src/Kris/LaravelFormBuilder/Console/FormMakeCommand.php 26 | ./src/Kris/LaravelFormBuilder/FormBuilderTrait.php 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/views/text.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | < > 16 | 17 | > 18 | 19 | 20 | 21 | 22 | 23 | get($nameKey) as $err): ?> 24 |
>
25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /src/views/textarea.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | < > 16 | 17 | > 18 | 19 | 20 | 21 | 22 | 23 | get($nameKey) as $err): ?> 24 |
>
25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /src/views/collection.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | render() ?> 14 | 15 | 16 | 17 | < > 18 | 19 | > 20 | 21 | 22 | 23 | 24 | 25 | get($nameKey) as $err): ?> 26 |
>
27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /src/config/config.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'wrapper_class' => 'form-group', 6 | 'wrapper_error_class' => 'has-error', 7 | 'label_class' => 'control-label', 8 | 'field_class' => 'form-control', 9 | 'help_block_class' => 'help-block', 10 | 'error_class' => 'text-danger', 11 | 'required_class' => 'required' 12 | ], 13 | // Templates 14 | 'form' => 'laravel4-form-builder::form', 15 | 'text' => 'laravel4-form-builder::text', 16 | 'textarea' => 'laravel4-form-builder::textarea', 17 | 'button' => 'laravel4-form-builder::button', 18 | 'radio' => 'laravel4-form-builder::radio', 19 | 'checkbox' => 'laravel4-form-builder::checkbox', 20 | 'select' => 'laravel4-form-builder::select', 21 | 'choice' => 'laravel4-form-builder::choice', 22 | 'repeated' => 'laravel4-form-builder::repeated', 23 | 'child_form' => 'laravel4-form-builder::child_form', 24 | 'collection' => 'laravel4-form-builder::collection', 25 | 'static' => 'laravel4-form-builder::static', 26 | 27 | 'default_namespace' => '', 28 | 29 | 'custom_fields' => [ 30 | // 'datetime' => 'App\Forms\Fields\Datetime' 31 | ] 32 | ]; 33 | -------------------------------------------------------------------------------- /src/views/choice.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | render(['selected' => $options['selected']], true, true, false) ?> 14 | 15 | 16 | 17 | < > 18 | 19 | > 20 | 21 | 22 | 23 | 24 | 25 | get($nameKey) as $err): ?> 26 |
>
27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /src/views/select.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | $options['empty_value']] : null; ?> 13 | 14 | 15 | 16 | < > 17 | 18 | > 19 | 20 | 21 | 22 | 23 | 24 | get($nameKey) as $err): ?> 25 |
>
26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/FormBuilderServiceProvider.php: -------------------------------------------------------------------------------- 1 | commands('Kris\LaravelFormBuilder\Console\FormMakeCommand'); 16 | 17 | $this->registerFormHelper(); 18 | 19 | $this->app->bindShared('laravel-form-builder', function ($app) { 20 | 21 | return new FormBuilder($app, $app['laravel-form-helper']); 22 | }); 23 | } 24 | 25 | protected function registerFormHelper() 26 | { 27 | $this->app->bindShared('laravel-form-helper', function ($app) { 28 | 29 | $configuration = $app['config']->get('laravel4-form-builder::config'); 30 | 31 | return new FormHelper($app['view'], $app['request'], $configuration); 32 | }); 33 | 34 | $this->app->alias('laravel-form-helper', 'Kris\LaravelFormBuilder\FormHelper'); 35 | } 36 | 37 | public function boot() 38 | { 39 | $this->package('kris/laravel4-form-builder'); 40 | } 41 | 42 | /** 43 | * @return string[] 44 | */ 45 | public function provides() 46 | { 47 | return ['laravel-form-builder']; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/views/child_form.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | getRealName(), (array)$options['exclude']) ) { ?> 14 | render() ?> 15 | 16 | 17 | 18 | 19 | < > 20 | 21 | > 22 | 23 | 24 | 25 | 26 | 27 | 28 | get($nameKey) as $err): ?> 29 |
>
30 | 31 | 32 | 33 | 34 | 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /src/views/radio.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | < > 12 | 13 | > 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | get($nameKey) as $err): ?> 28 |
>
29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/views/checkbox.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | < > 12 | 13 | > 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | get($nameKey) as $err): ?> 28 |
>
29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/RepeatedType.php: -------------------------------------------------------------------------------- 1 | 'password', 20 | 'second_name' => null, 21 | 'first_options' => ['label' => 'Password'], 22 | 'second_options' => ['label' => 'Password confirmation'] 23 | ]; 24 | } 25 | 26 | protected function createChildren() 27 | { 28 | $firstName = $this->getRealName(); 29 | $secondName = $this->getOption('second_name'); 30 | 31 | if (is_null($secondName)) { 32 | $secondName = $firstName.'_confirmation'; 33 | } 34 | 35 | $form = $this->parent->getFormBuilder()->plain([ 36 | 'name' => $this->parent->getName(), 37 | 'model' => $this->parent->getModel() 38 | ]) 39 | ->add($firstName, $this->getOption('type'), $this->getOption('first_options')) 40 | ->add($secondName, $this->getOption('type'), $this->getOption('second_options')); 41 | 42 | $this->children['first'] = $form->getField($firstName); 43 | $this->children['second'] = $form->getField($secondName); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Fields/StaticTypeTest.php: -------------------------------------------------------------------------------- 1 | ['class' => 'static-class', 'id' => 'some_static'] 14 | ]; 15 | 16 | $this->plainForm->setModel(['some_static' => 'static text']); 17 | 18 | $expectedOptions = $this->getDefaults( 19 | ['class' => 'static-class', 'id' => 'some_static'], 20 | 'some_static', 21 | 'Some Static', 22 | 'static text' 23 | ); 24 | 25 | $expectedOptions['tag'] = 'div'; 26 | 27 | $expectedOptions['elemAttrs'] = $this->formHelper->prepareAttributes( 28 | ['class' => 'static-class', 'id' => 'some_static'] 29 | ); 30 | $expectedOptions['labelAttrs'] = $this->formHelper->prepareAttributes( 31 | $expectedOptions['label_attr'] 32 | ); 33 | 34 | $expectedViewData = [ 35 | 'name' => 'some_static', 36 | 'nameKey' => 'some_static', 37 | 'type' => 'static', 38 | 'options' => $expectedOptions, 39 | 'showLabel' => true, 40 | 'showField' => true, 41 | 'showError' => false 42 | ]; 43 | 44 | $this->fieldExpetations('static', $expectedViewData); 45 | 46 | $static = new StaticType('some_static', 'static', $this->plainForm, $options); 47 | 48 | $static->render(); 49 | 50 | $this->assertEquals('static text', $static->getOption('value')); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Console/FormGeneratorTest.php: -------------------------------------------------------------------------------- 1 | formGenerator = new FormGenerator(); 16 | } 17 | 18 | /** @test */ 19 | public function it_returns_comment_when_no_fields_passed() 20 | { 21 | $parsedFields = $this->formGenerator->getFieldsVariable(); 22 | 23 | $this->assertEquals('// Add fields here...', $parsedFields); 24 | } 25 | 26 | /** @test */ 27 | public function it_parses_fields_from_options_to_methods() 28 | { 29 | $fields = 'first_name:text, last_name:text, user_email:email, user_password:password'; 30 | 31 | $expected = join('', [ 32 | "\$this\n", 33 | " ->add('first_name', 'text')\n", 34 | " ->add('last_name', 'text')\n", 35 | " ->add('user_email', 'email')\n", 36 | " ->add('user_password', 'password');", 37 | ]); 38 | 39 | $parsedFields = $this->formGenerator->getFieldsVariable($fields); 40 | 41 | $this->assertSame($expected, $parsedFields); 42 | } 43 | 44 | /** @test */ 45 | public function it_gets_class_info_for_given_full_class_name() 46 | { 47 | // Parsed in this format from Laravels GeneratorCommand 48 | $className = 'app/ProjectNamespace/Forms/PostForm'; 49 | 50 | $expected = (object) [ 51 | 'namespace' => 'ProjectNamespace\\Forms', 52 | 'className' => 'PostForm' 53 | ]; 54 | 55 | $classInfo = $this->formGenerator->getClassInfo($className); 56 | 57 | $this->assertEquals($expected, $classInfo); 58 | } 59 | } -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | renderForm($options); 11 | } 12 | 13 | } 14 | 15 | if (!function_exists('form_start')) { 16 | 17 | function form_start(Form $form, array $options = []) 18 | { 19 | return $form->renderForm($options, true, false, false); 20 | } 21 | 22 | } 23 | 24 | if (!function_exists('form_end')) { 25 | 26 | function form_end(Form $form, $showFields = true) 27 | { 28 | return $form->renderRest(true, $showFields); 29 | } 30 | 31 | } 32 | 33 | if (!function_exists('form_rest')) { 34 | 35 | function form_rest(Form $form) 36 | { 37 | return $form->renderRest(false); 38 | } 39 | 40 | } 41 | 42 | if (!function_exists('form_until')) { 43 | 44 | function form_until(Form $form, $field_name) 45 | { 46 | return $form->renderUntil($field_name, false); 47 | } 48 | 49 | } 50 | 51 | if (!function_exists('form_row')) { 52 | 53 | function form_row(FormField $formField, array $options = []) 54 | { 55 | return $formField->render($options); 56 | } 57 | 58 | } 59 | 60 | if (!function_exists('form_label')) { 61 | 62 | function form_label(FormField $formField, array $options = []) 63 | { 64 | return $formField->render($options, true, false, false); 65 | } 66 | 67 | } 68 | 69 | if (!function_exists('form_widget')) { 70 | 71 | function form_widget(FormField $formField, array $options = []) 72 | { 73 | return $formField->render($options, false, true, false); 74 | } 75 | 76 | } 77 | 78 | if (!function_exists('form_errors')) { 79 | 80 | function form_errors(FormField $formField, array $options = []) 81 | { 82 | return $formField->render($options, false, false, true); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/EntityType.php: -------------------------------------------------------------------------------- 1 | null, 15 | 'query_builder' => null, 16 | 'property' => 'name', 17 | 'property_key' => 'id', 18 | ]; 19 | 20 | return array_merge(parent::getDefaults(), $defaults); 21 | } 22 | 23 | /** 24 | * @inheritdoc 25 | */ 26 | protected function createChildren() 27 | { 28 | if ($this->getOption('choices')) { 29 | return parent::createChildren(); 30 | } 31 | 32 | $entity = $this->getOption('class'); 33 | $queryBuilder = $this->getOption('query_builder'); 34 | $key = $this->getOption('property_key'); 35 | $value = $this->getOption('property'); 36 | 37 | if (!$entity || !class_exists($entity)) { 38 | throw new \InvalidArgumentException(sprintf( 39 | 'Please provide valid "class" option for entity field [%s] in form class [%s]', 40 | $this->getRealName(), 41 | get_class($this->parent) 42 | )); 43 | } 44 | 45 | $entity = new $entity(); 46 | 47 | if ($queryBuilder instanceof \Closure) { 48 | $data = $queryBuilder($entity); 49 | if (is_object($data) && method_exists($data, 'lists')) { 50 | $data = $data->lists($value, $key); 51 | } 52 | } else { 53 | $data = $entity->lists($value, $key); 54 | } 55 | 56 | if ($data instanceof Collection) { 57 | $data = $data->all(); 58 | } 59 | 60 | 61 | $this->options['choices'] = $data; 62 | 63 | return parent::createChildren(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Fields/RepeatedTypeTest.php: -------------------------------------------------------------------------------- 1 | fieldExpetations('text', Mockery::any()); 12 | $this->fieldExpetations('text', Mockery::any()); 13 | $this->fieldExpetations('repeated', Mockery::any()); 14 | $repeatedForm = $this->setupForm(new Form()); 15 | // Child fields are initialized when constructing a repeated type 16 | // from plain form 17 | $this->container->shouldReceive('make')->once()->andReturn($this->plainForm); 18 | 19 | $repeated = new RepeatedType('password', 'repeated', $this->plainForm, []); 20 | 21 | $repeated->render(); 22 | 23 | $this->assertEquals(2, count($repeated->getChildren())); 24 | 25 | $this->assertInstanceOf('Kris\LaravelFormBuilder\Fields\InputType', $repeated->first); 26 | $this->assertInstanceOf('Kris\LaravelFormBuilder\Fields\InputType', $repeated->second); 27 | $this->assertNull($repeated->third); 28 | } 29 | 30 | /** @test */ 31 | public function it_checks_if_field_rendered_by_children() 32 | { 33 | $this->fieldExpetations('text', Mockery::any()); 34 | $this->fieldExpetations('text', Mockery::any()); 35 | $this->fieldExpetations('repeated', Mockery::any()); 36 | $this->container->shouldReceive('make')->andReturn($this->plainForm); 37 | 38 | $repeated = new RepeatedType('password', 'repeated', $this->plainForm, [ 39 | 'type' => 'file' 40 | ]); 41 | 42 | $this->assertFalse($repeated->isRendered()); 43 | 44 | $repeated->first->render(); 45 | 46 | $this->assertTrue($repeated->isRendered()); 47 | 48 | $this->assertTrue($this->plainForm->getFormOption('files')); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Console/FormGenerator.php: -------------------------------------------------------------------------------- 1 | parseFields($fields); 16 | } 17 | 18 | return '// Add fields here...'; 19 | } 20 | 21 | /** 22 | * @param string $name 23 | * @return object 24 | */ 25 | public function getClassInfo($name) 26 | { 27 | $fullNamespacedPath = explode('/', $name); 28 | array_shift($fullNamespacedPath); 29 | $className = array_pop($fullNamespacedPath); 30 | 31 | return (object)[ 32 | 'namespace' => join('\\', $fullNamespacedPath), 33 | 'className' => $className 34 | ]; 35 | } 36 | 37 | /** 38 | * Parse fields from string 39 | * 40 | * @param $fields 41 | * @return string 42 | */ 43 | protected function parseFields($fields) 44 | { 45 | $fieldsArray = explode(',', $fields); 46 | $text = '$this'."\n"; 47 | 48 | foreach ($fieldsArray as $field) { 49 | $text .= $this->prepareAdd($field, end($fieldsArray) == $field); 50 | } 51 | 52 | return $text.';'; 53 | } 54 | 55 | /** 56 | * Prepare template for single add field 57 | * 58 | * @param $field 59 | * @param bool $isLast 60 | * @return string 61 | */ 62 | protected function prepareAdd($field, $isLast = false) 63 | { 64 | $field = trim($field); 65 | list($name, $type) = explode(':', $field); 66 | $textArr = [ 67 | " ->add('", 68 | $name, 69 | "', '", 70 | $type, 71 | "')", 72 | ($isLast) ? "" : "\n" 73 | ]; 74 | 75 | return join('', $textArr); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/FormBuilder.php: -------------------------------------------------------------------------------- 1 | container = $container; 25 | $this->formHelper = $formHelper; 26 | } 27 | 28 | /** 29 | * @param $formClass 30 | * @param $options 31 | * @param $data 32 | * @return Form 33 | */ 34 | public function create($formClass, array $options = [], array $data = []) 35 | { 36 | $class = $this->getNamespaceFromConfig() . $formClass; 37 | 38 | if (!class_exists($class)) { 39 | throw new \InvalidArgumentException( 40 | 'Form class with name ' . $class . ' does not exist.' 41 | ); 42 | } 43 | 44 | $form = $this->container 45 | ->make($class) 46 | ->setFormHelper($this->formHelper) 47 | ->setFormBuilder($this) 48 | ->setFormOptions($options) 49 | ->addData($data); 50 | 51 | $form->buildForm(); 52 | 53 | return $form; 54 | } 55 | 56 | /** 57 | * Get the namespace from the config 58 | * 59 | * @return string 60 | */ 61 | protected function getNamespaceFromConfig() 62 | { 63 | $namespace = $this->formHelper->getConfig('default_namespace'); 64 | 65 | if (!$namespace) { 66 | return ''; 67 | } 68 | 69 | return $namespace . '\\'; 70 | } 71 | 72 | /** 73 | * Get instance of the empty form which can be modified 74 | * 75 | * @param array $options 76 | * @param array $data 77 | * @return Form 78 | */ 79 | public function plain(array $options = [], array $data = []) 80 | { 81 | return $this->container 82 | ->make('Kris\LaravelFormBuilder\Form') 83 | ->setFormHelper($this->formHelper) 84 | ->setFormBuilder($this) 85 | ->setFormOptions($options) 86 | ->addData($data); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/Fields/InputTypeTest.php: -------------------------------------------------------------------------------- 1 | 12, 14 | 'required' => true, 15 | 'help_block' => [ 16 | 'text' => 'this is help' 17 | ] 18 | ]; 19 | 20 | $expectedOptions = $this->getDefaults( 21 | ['required' => 'required'], 22 | 'hidden_id', 23 | 'Hidden Id', 24 | 13, 25 | 'this is help' 26 | ); 27 | 28 | $expectedOptions['help_block']['helpBlockAttrs'] = 'class="help-block" '; 29 | $expectedOptions['required'] = true; 30 | $expectedOptions['label_attr']['class'] .= ' required'; 31 | 32 | $expectedViewData = [ 33 | 'name' => 'hidden_id', 34 | 'nameKey' => 'hidden_id', 35 | 'type' => 'hidden', 36 | 'options' => $expectedOptions, 37 | 'showLabel' => false, 38 | 'showField' => true, 39 | 'showError' => true 40 | ]; 41 | 42 | $this->fieldExpetations('text', $expectedViewData); 43 | 44 | $hidden = new InputType('hidden_id', 'hidden', $this->plainForm, $options); 45 | 46 | $hidden->render(['value' => 13]); 47 | } 48 | 49 | 50 | /** @test */ 51 | public function it_handles_default_values() 52 | { 53 | $options = [ 54 | 'default_value' => 100 55 | ]; 56 | $this->plainForm->setModel(null); 57 | $input = new InputType('test', 'text', $this->plainForm, $options); 58 | 59 | $this->assertEquals(100, $input->getOption('value')); 60 | } 61 | 62 | /** @test */ 63 | public function model_value_overrides_default_value() 64 | { 65 | $options = [ 66 | 'default_value' => 100 67 | ]; 68 | $this->plainForm->setModel(['test' => 5]); 69 | $input = new InputType('test', 'text', $this->plainForm, $options); 70 | 71 | $this->assertEquals(5, $input->getOption('value')); 72 | } 73 | 74 | /** @test */ 75 | public function explicit_value_overrides_default_values() 76 | { 77 | $options = [ 78 | 'default_value' => 100, 79 | 'value' => 500 80 | ]; 81 | 82 | $input = new InputType('test', 'text', $this->plainForm, $options); 83 | 84 | $this->assertEquals(500, $input->getOption('value')); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /tests/Fields/ChoiceTypeTest.php: -------------------------------------------------------------------------------- 1 | ['class' => 'choice-class'], 14 | 'choices' => ['yes' => 'Yes', 'no' => 'No'], 15 | 'selected' => 'yes' 16 | ]; 17 | 18 | $this->fieldExpetations('select', Mockery::any()); 19 | 20 | $this->fieldExpetations('choice', Mockery::any()); 21 | 22 | $choice = new ChoiceType('some_choice', 'choice', $this->plainForm, $options); 23 | 24 | $choice->render(); 25 | 26 | $this->assertEquals(1, count($choice->getChildren())); 27 | 28 | $this->assertEquals('yes', $choice->getOption('selected')); 29 | } 30 | 31 | /** @test */ 32 | public function it_creates_choice_as_checkbox_list() 33 | { 34 | $options = [ 35 | 'attr' => ['class' => 'choice-class-something'], 36 | 'choices' => [1 => 'monday', 2 => 'tuesday'], 37 | 'selected' => 'tuesday', 38 | 'multiple' => true, 39 | 'expanded' => true 40 | ]; 41 | 42 | $this->fieldExpetations('checkbox', Mockery::any()); 43 | 44 | $this->fieldExpetations('checkbox', Mockery::any()); 45 | 46 | $this->fieldExpetations('choice', Mockery::any()); 47 | 48 | $choice = new ChoiceType('some_choice', 'choice', $this->plainForm, $options); 49 | 50 | $choice->render(); 51 | 52 | $this->assertEquals(2, count($choice->getChildren())); 53 | 54 | $this->assertEquals('tuesday', $choice->getOption('selected')); 55 | 56 | $this->assertContainsOnlyInstancesOf('Kris\LaravelFormBuilder\Fields\CheckableType', $choice->getChildren()); 57 | } 58 | 59 | /** @test */ 60 | public function it_creates_choice_as_radio_buttons() 61 | { 62 | $options = [ 63 | 'attr' => ['class' => 'choice-class-something'], 64 | 'choices' => [1 => 'yes', 2 => 'no'], 65 | 'selected' => 'no', 66 | 'expanded' => true 67 | ]; 68 | 69 | $this->fieldExpetations('radio', Mockery::any()); 70 | 71 | $this->fieldExpetations('radio', Mockery::any()); 72 | 73 | $this->fieldExpetations('choice', Mockery::any()); 74 | 75 | $choice = new ChoiceType('some_choice', 'choice', $this->plainForm, $options); 76 | 77 | $choice->render(); 78 | 79 | $this->assertEquals(2, count($choice->getChildren())); 80 | 81 | $this->assertInstanceOf( 82 | 'Kris\LaravelFormBuilder\Fields\CheckableType', 83 | $choice->getChild(1) 84 | ); 85 | 86 | $this->assertEquals('no', $choice->getOption('selected')); 87 | 88 | $this->assertContainsOnlyInstancesOf('Kris\LaravelFormBuilder\Fields\CheckableType', $choice->getChildren()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/ChildFormType.php: -------------------------------------------------------------------------------- 1 | form; 27 | } 28 | 29 | /** 30 | * @inheritdoc 31 | */ 32 | protected function getDefaults() 33 | { 34 | return [ 35 | 'class' => null, 36 | 'value' => null, 37 | 'formOptions' => [], 38 | 'data' => [], 39 | 'exclude' => [] 40 | ]; 41 | } 42 | 43 | /** 44 | * @return mixed|void 45 | */ 46 | protected function createChildren() 47 | { 48 | $this->form = $this->getClassFromOptions(); 49 | 50 | if ($this->form->getFormOption('files')) { 51 | $this->parent->setFormOption('files', true); 52 | } 53 | $model = $this->getOption($this->valueProperty); 54 | if ($model !== null) { 55 | foreach ($this->form->getFields() as $name => $field) { 56 | $field->setValue($this->getModelValueAttribute($model, $name)); 57 | } 58 | } 59 | 60 | $this->children = $this->form->getFields(); 61 | } 62 | 63 | /** 64 | * @return Form 65 | * @throws \Exception 66 | */ 67 | protected function getClassFromOptions() 68 | { 69 | if ($this->form instanceof Form) { 70 | return $this->form->setName($this->name); 71 | } 72 | 73 | $class = $this->getOption('class'); 74 | 75 | if (!$class) { 76 | throw new \InvalidArgumentException( 77 | 'Please provide full name or instance of Form class.' 78 | ); 79 | } 80 | 81 | if (is_string($class)) { 82 | $formOptions = array_merge( 83 | ['model' => $this->parent->getModel(), 'name' => $this->name], 84 | $this->getOption('formOptions') 85 | ); 86 | 87 | $data = array_merge($this->parent->getData(), $this->getOption('data')); 88 | 89 | return $this->parent->getFormBuilder()->create( 90 | $class, 91 | $formOptions, 92 | $data 93 | ); 94 | } 95 | 96 | if ($class instanceof Form) { 97 | if (!$class->getModel()) { 98 | $class->setModel($this->parent->getModel()); 99 | } 100 | 101 | if (!$class->getData()) { 102 | $class->addData($this->parent->getData()); 103 | } 104 | 105 | return $class->setName($this->name); 106 | } 107 | 108 | throw new \InvalidArgumentException( 109 | 'Class provided does not exist or it passed in wrong format.' 110 | ); 111 | } 112 | 113 | /** 114 | * @param $method 115 | * @param $arguments 116 | * 117 | * @return Form|null 118 | */ 119 | public function __call($method, $arguments) 120 | { 121 | if (method_exists($this->form, $method)) { 122 | return call_user_func_array([$this->form, $method], $arguments); 123 | } 124 | 125 | throw new \BadMethodCallException( 126 | 'Method ['.$method.'] does not exist on form ['.get_class($this->form).']' 127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /tests/Fields/ButtonTypeTest.php: -------------------------------------------------------------------------------- 1 | ['class' => 'form-group has-error'], 14 | 'attr' => ['class' => 'btn-class', 'disabled' => 'disabled'] 15 | ]; 16 | 17 | $expectedOptions = $this->getDefaults( 18 | ['class' => 'btn-class', 'type' => 'button', 'disabled' => 'disabled'], 19 | 'some_button', 20 | 'Some Button' 21 | ); 22 | 23 | $expectedViewData = [ 24 | 'name' => 'some_button', 25 | 'nameKey' => 'some_button', 26 | 'type' => 'button', 27 | 'options' => $expectedOptions, 28 | 'showLabel' => true, 29 | 'showField' => true, 30 | 'showError' => true 31 | ]; 32 | 33 | $this->fieldExpetations('button', $expectedViewData); 34 | 35 | $button = new ButtonType('some_button', 'button', $this->plainForm, $options); 36 | 37 | $button->render(); 38 | } 39 | 40 | /** @test */ 41 | public function it_can_handle_object_with_getters_and_setters() 42 | { 43 | $expectedOptions = $this->getDefaults(['type' => 'submit'], 'save', 'Save'); 44 | $expectedOptions['wrapperAttrs'] = null; 45 | $expectedOptions['wrapper'] = false; 46 | /* $expectedOptions['wrapperAttrs'] = null; */ 47 | 48 | $this->fieldExpetations('button', Mockery::any()); 49 | 50 | $button = new ButtonType('save', 'submit', $this->plainForm); 51 | 52 | $this->assertEquals('save', $button->getName()); 53 | $this->assertEquals('submit', $button->getType()); 54 | $this->assertEquals($expectedOptions, $button->getOptions()); 55 | $this->assertFalse($button->isRendered()); 56 | 57 | $button->setName('cancel'); 58 | $button->setType('reset'); 59 | $button->setOptions(['attr' => ['id' => 'button-id'], 'label' => 'Cancel it']); 60 | 61 | $expectedOptions = $this->getDefaults(['type' => 'submit', 'id' => 'button-id'], 'save', 'Cancel it'); 62 | $expectedOptions['wrapperAttrs'] = null; 63 | $expectedOptions['wrapper'] = false; 64 | 65 | $this->assertEquals('cancel', $button->getName()); 66 | $this->assertEquals('reset', $button->getType()); 67 | $this->assertEquals($expectedOptions, $button->getOptions()); 68 | 69 | $button->render(); 70 | 71 | $this->assertTrue($button->isRendered()); 72 | } 73 | 74 | /** @test */ 75 | public function it_can_change_template_with_options() 76 | { 77 | $expectedOptions = $this->getDefaults( 78 | ['type' => 'submit'], 79 | 'some_submit', 80 | 'Some Submit' 81 | ); 82 | 83 | $expectedOptions['wrapper'] = false; 84 | $expectedOptions['wrapperAttrs'] = null; 85 | 86 | $expectedViewData = [ 87 | 'name' => 'some_submit', 88 | 'nameKey' => 'some_submit', 89 | 'type' => 'submit', 90 | 'options' => $expectedOptions, 91 | 'showLabel' => true, 92 | 'showField' => true, 93 | 'showError' => true 94 | ]; 95 | 96 | $this->fieldExpetations('button', $expectedViewData, 'custom-template'); 97 | 98 | $button = new ButtonType('some_submit', 'submit', $this->plainForm, ['template' => 'custom-template']); 99 | 100 | $button->render(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/ParentType.php: -------------------------------------------------------------------------------- 1 | hasDefault) { 32 | $this->createChildren(); 33 | } 34 | $this->checkIfFileType(); 35 | } 36 | 37 | /** 38 | * @param mixed $val 39 | * 40 | * @return ChildFormType 41 | */ 42 | protected function setValue($val) 43 | { 44 | parent::setValue($val); 45 | $this->createChildren(); 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * @param array $options 52 | * @param bool $showLabel 53 | * @param bool $showField 54 | * @param bool $showError 55 | * @return string 56 | */ 57 | public function render(array $options = [], $showLabel = true, $showField = true, $showError = true) 58 | { 59 | $options['children'] = $this->children; 60 | return parent::render($options, $showLabel, $showField, $showError); 61 | } 62 | 63 | /** 64 | * Get all children of the choice field 65 | * 66 | * @return mixed 67 | */ 68 | public function getChildren() 69 | { 70 | return $this->children; 71 | } 72 | 73 | /** 74 | * Get a child of the choice field 75 | * 76 | * @return mixed 77 | */ 78 | public function getChild($key) 79 | { 80 | return array_get($this->children, $key); 81 | } 82 | 83 | /** 84 | * @inheritdoc 85 | */ 86 | public function isRendered() 87 | { 88 | foreach ((array) $this->children as $key => $child) { 89 | if ($child->isRendered()) { 90 | return true; 91 | } 92 | } 93 | 94 | return parent::isRendered(); 95 | } 96 | 97 | /** 98 | * Get child dynamically 99 | * 100 | * @param $name 101 | * @return FormField 102 | */ 103 | public function __get($name) 104 | { 105 | return $this->getChild($name); 106 | } 107 | 108 | /** 109 | * Check if field has type property and if it's file add enctype/multipart to form 110 | */ 111 | protected function checkIfFileType() 112 | { 113 | if ($this->getOption('type') === 'file') { 114 | $this->parent->setFormOption('files', true); 115 | } 116 | } 117 | 118 | public function __clone() 119 | { 120 | foreach ((array) $this->children as $key => $child) { 121 | $this->children[$key] = clone $child; 122 | } 123 | } 124 | 125 | /** 126 | * @inheritdoc 127 | */ 128 | public function disable() 129 | { 130 | foreach ($this->children as $field) { 131 | $field->disable(); 132 | } 133 | } 134 | 135 | /** 136 | * @inheritdoc 137 | */ 138 | public function enable() 139 | { 140 | foreach ($this->children as $field) { 141 | $field->enable(); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tests/Fields/EntityTypeTest.php: -------------------------------------------------------------------------------- 1 | 'Yes', 'no' => 'No']; 12 | $options = [ 13 | 'attr' => ['class' => 'choice-class'], 14 | 'choices' => $choices, 15 | 'selected' => 'yes' 16 | ]; 17 | 18 | $this->fieldExpetations('select', Mockery::any()); 19 | $this->fieldExpetations('choice', Mockery::any()); 20 | 21 | $choice = new EntityType('some_entity', 'entity', $this->plainForm, $options); 22 | $choice->render(); 23 | 24 | $this->assertEquals(1, count($choice->getChildren())); 25 | $this->assertEquals($choices, $choice->getOption('choices')); 26 | $this->assertEquals('yes', $choice->getOption('selected')); 27 | } 28 | 29 | /** @test */ 30 | public function it_uses_passed_class_model_to_fetch_all() 31 | { 32 | $mdl = new DummyModel(); 33 | $options = [ 34 | 'class' => 'DummyModel' 35 | ]; 36 | 37 | $this->fieldExpetations('select', Mockery::any()); 38 | $this->fieldExpetations('choice', Mockery::any()); 39 | 40 | $choice = new EntityType('entity_choice', 'entity', $this->plainForm, $options); 41 | $choice->render(); 42 | 43 | $expected = [ 44 | 1 => 'English', 45 | 2 => 'French', 46 | 3 => 'Serbian' 47 | ];; 48 | 49 | $this->assertEquals($expected, $choice->getOption('choices')); 50 | } 51 | 52 | /** 53 | * @test 54 | * @expectedException \InvalidArgumentException 55 | */ 56 | public function it_throws_an_exception_if_model_class_not_provided() 57 | { 58 | $options = []; 59 | 60 | $this->fieldExpetations('select', Mockery::any()); 61 | $this->fieldExpetations('choice', Mockery::any()); 62 | 63 | $choice = new EntityType('entity_choice', 'entity', $this->plainForm, $options); 64 | } 65 | 66 | /** @test */ 67 | public function it_uses_query_builder_to_filter_choices() 68 | { 69 | $mdl = new DummyModel(); 70 | $options = [ 71 | 'class' => 'DummyModel', 72 | 'property' => 'short_name', 73 | 'query_builder' => function (DummyModel $model) { 74 | return $model->getData()->filter(function($val) { 75 | return $val['id'] > 1; 76 | }); 77 | } 78 | ]; 79 | 80 | $this->fieldExpetations('select', Mockery::any()); 81 | $this->fieldExpetations('choice', Mockery::any()); 82 | 83 | $choice = new EntityType('entity_choice', 'entity', $this->plainForm, $options); 84 | $choice->render(); 85 | 86 | $expected = [ 87 | 2 => 'Fr', 88 | 3 => 'Rs' 89 | ]; 90 | 91 | $this->assertEquals($expected, $choice->getOption('choices')); 92 | } 93 | } 94 | 95 | class DummyModel { 96 | 97 | protected $data = [ 98 | ['id' => 1, 'name' => 'English', 'short_name' => 'En'], 99 | ['id' => 2, 'name' => 'French', 'short_name' => 'Fr'], 100 | ['id' => 3, 'name' => 'Serbian', 'short_name' => 'Rs'] 101 | ]; 102 | 103 | public function __construct($data = []) 104 | { 105 | $this->data = new Illuminate\Support\Collection($data ?: $this->data); 106 | } 107 | 108 | public function lists($val, $key) 109 | { 110 | return $this->data->lists($val, $key); 111 | } 112 | 113 | public function getData() 114 | { 115 | return $this->data; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/ChoiceType.php: -------------------------------------------------------------------------------- 1 | options['expanded']; 31 | $multiple = $this->options['multiple']; 32 | 33 | if ($multiple) { 34 | $this->options['attr']['multiple'] = true; 35 | } 36 | 37 | if ($expanded && !$multiple) { 38 | return $this->choiceType = 'radio'; 39 | } 40 | 41 | if ($expanded && $multiple) { 42 | return $this->choiceType = 'checkbox'; 43 | } 44 | } 45 | 46 | /** 47 | * @inheritdoc 48 | */ 49 | protected function getDefaults() 50 | { 51 | return [ 52 | 'choices' => null, 53 | 'selected' => null, 54 | 'expanded' => false, 55 | 'multiple' => false, 56 | 'choice_options' => [ 57 | 'wrapper' => false, 58 | 'is_child' => true 59 | ] 60 | ]; 61 | } 62 | 63 | /** 64 | * Create children depending on choice type 65 | */ 66 | protected function createChildren() 67 | { 68 | $this->children = []; 69 | $this->determineChoiceField(); 70 | 71 | $fieldType = $this->formHelper->getFieldType($this->choiceType); 72 | 73 | switch ($this->choiceType) { 74 | case 'radio': 75 | case 'checkbox': 76 | $this->buildCheckableChildren($fieldType); 77 | break; 78 | default: 79 | $this->buildSelect($fieldType); 80 | break; 81 | } 82 | } 83 | 84 | /** 85 | * Build checkable children fields from choice type 86 | * 87 | * @param string $fieldType 88 | */ 89 | protected function buildCheckableChildren($fieldType) 90 | { 91 | $multiple = $this->getOption('multiple') ? '[]' : ''; 92 | 93 | foreach ((array)$this->options['choices'] as $key => $choice) { 94 | $id = str_replace('.', '_', $this->getNameKey()) . '_' . $key; 95 | $options = $this->formHelper->mergeOptions( 96 | $this->getOption('choice_options'), 97 | [ 98 | 'attr' => ['id' => $id], 99 | 'label_attr' => ['for' => $id], 100 | 'label' => $this->formHelper->formatLabel($choice), 101 | 'checked' => in_array($key, (array)$this->options[$this->valueProperty]), 102 | 'value' => $key 103 | ] 104 | ); 105 | $this->children[] = new $fieldType( 106 | $this->name . $multiple, 107 | $this->choiceType, 108 | $this->parent, 109 | $options 110 | ); 111 | } 112 | } 113 | 114 | /** 115 | * Build select field from choice 116 | * 117 | * @param string $fieldType 118 | */ 119 | protected function buildSelect($fieldType) 120 | { 121 | $this->children[] = new $fieldType( 122 | $this->name, 123 | $this->choiceType, 124 | $this->parent, 125 | $this->formHelper->mergeOptions($this->options, ['is_child' => true]) 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/FormBuilderTestCase.php: -------------------------------------------------------------------------------- 1 | view = Mockery::mock('Illuminate\View\Factory'); 53 | $this->request = Mockery::mock('Illuminate\Http\Request'); 54 | $this->container = Mockery::mock('Illuminate\Foundation\Application'); 55 | $this->model = Mockery::mock('Illuminate\Database\Eloquent\Model'); 56 | $this->config = include __DIR__.'/../src/config/config.php'; 57 | 58 | $session = Mockery::mock('Symfony\Component\HttpFoundation\Session\SessionInterface'); 59 | $session->shouldReceive('get')->zeroOrMoreTimes()->andReturnSelf(); 60 | $session->shouldReceive('has')->zeroOrMoreTimes()->andReturn(true); 61 | 62 | $this->request->shouldReceive('getSession')->zeroOrMoreTimes()->andReturn($session); 63 | 64 | $this->formHelper = new FormHelper($this->view, $this->request, $this->config); 65 | $this->formBuilder = new FormBuilder($this->container, $this->formHelper); 66 | 67 | $this->plainForm = $this->setupForm(new Form()); 68 | } 69 | 70 | public function tearDown() 71 | { 72 | Mockery::close(); 73 | $this->view = null; 74 | $this->request = null; 75 | $this->container = null; 76 | $this->model = null; 77 | $this->config = null; 78 | $this->formHelper = null; 79 | $this->formBuilder = null; 80 | $this->plainForm = null; 81 | } 82 | 83 | protected function fieldExpetations($name, $expectedViewData, $templatePrefix = null, $renderReturn = null) { 84 | $viewRenderer = Mockery::mock('Illuminate\Contracts\View\View'); 85 | 86 | if ($renderReturn) { 87 | $viewRenderer->shouldReceive('render')->andReturn($renderReturn); 88 | } else { 89 | $viewRenderer->shouldReceive('render'); 90 | } 91 | 92 | if (!$templatePrefix) { 93 | $templatePrefix = 'laravel4-form-builder::'.$name; 94 | } 95 | 96 | $this->view->shouldReceive('make') 97 | ->with($templatePrefix, $expectedViewData) 98 | ->andReturn($viewRenderer); 99 | } 100 | 101 | protected function getDefaults($attr = [], $id = '', $label = '', $defaultValue = null, $helpText = null) 102 | { 103 | return [ 104 | 'wrapper' => ['class' => 'form-group has-error'], 105 | 'attr' => array_merge(['class' => 'form-control'], $attr), 106 | 'help_block' => ['text' => $helpText, 'tag' => 'p', 'attr' => [ 107 | 'class' => 'help-block' 108 | ]], 109 | 'value' => $defaultValue, 110 | 'default_value' => null, 111 | 'label' => $label, 112 | 'is_child' => false, 113 | 'label_attr' => ['class' => 'control-label'], 114 | 'errors' => ['class' => 'text-danger'], 115 | 'wrapperAttrs' => 'class="form-group has-error" ', 116 | 'errorAttrs' => 'class="text-danger" ' 117 | ]; 118 | } 119 | 120 | protected function setupForm(Form $form) 121 | { 122 | $form->setFormHelper($this->formHelper) 123 | ->setFormBuilder($this->formBuilder); 124 | 125 | $form->buildForm(); 126 | return $form; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Console/FormMakeCommand.php: -------------------------------------------------------------------------------- 1 | files = $files; 41 | $this->formGenerator = $formGenerator; 42 | } 43 | 44 | /** 45 | * Execute the console command. 46 | * 47 | * @return void 48 | */ 49 | public function fire() 50 | { 51 | $path = $this->getNameInput(); 52 | 53 | if ($this->files->exists($path)) { 54 | return $this->error('Form already exists!'); 55 | } 56 | 57 | $this->makeDirectory($path); 58 | 59 | $this->files->put($path.'.php', $this->buildClass($path)); 60 | 61 | $this->info('Form created successfully.'); 62 | } 63 | 64 | /** 65 | * Build the directory for the class if necessary. 66 | * 67 | * @param string $path 68 | * @return string 69 | */ 70 | protected function makeDirectory($path) 71 | { 72 | if (!$this->files->isDirectory(dirname($path))) { 73 | $this->files->makeDirectory(dirname($path), 0777, true, true); 74 | } 75 | } 76 | 77 | /** 78 | * Build the controller class with the given name. 79 | * 80 | * @param string $name 81 | * @return string 82 | */ 83 | protected function buildClass($name) 84 | { 85 | $stub = $this->files->get($this->getStub()); 86 | 87 | return $this->replaceNamespace($stub, $name)->replaceClass($stub, $name); 88 | } 89 | 90 | /** 91 | * Get the console command arguments. 92 | * 93 | * @return array 94 | */ 95 | protected function getArguments() 96 | { 97 | return array( 98 | array('name', InputArgument::REQUIRED, 'Full path for Form class.'), 99 | ); 100 | } 101 | 102 | /** 103 | * Get the console command options. 104 | * 105 | * @return array 106 | */ 107 | protected function getOptions() 108 | { 109 | return array( 110 | array('fields', null, InputOption::VALUE_OPTIONAL, 'Fields for the form'), 111 | ); 112 | } 113 | 114 | /** 115 | * Replace the class name for the given stub. 116 | * 117 | * @param string $stub 118 | * @param string $name 119 | * @return string 120 | */ 121 | protected function replaceClass($stub, $name) 122 | { 123 | $formGenerator = $this->formGenerator; 124 | 125 | $stub = str_replace( 126 | '{{class}}', 127 | $formGenerator->getClassInfo($name)->className, 128 | $stub 129 | ); 130 | 131 | return str_replace( 132 | '{{fields}}', 133 | $formGenerator->getFieldsVariable($this->option('fields')), 134 | $stub 135 | ); 136 | } 137 | 138 | /** 139 | * Replace the namespace for the given stub. 140 | * 141 | * @param string $stub 142 | * @param string $name 143 | * @return $this 144 | */ 145 | protected function replaceNamespace(&$stub, $name) 146 | { 147 | $stub = str_replace( 148 | '{{namespace}}', 149 | $this->formGenerator->getClassInfo($name)->namespace, 150 | $stub 151 | ); 152 | 153 | return $this; 154 | } 155 | 156 | /** 157 | * Get the desired class name from the input. 158 | * 159 | * @return string 160 | */ 161 | protected function getNameInput() 162 | { 163 | return $this->argument('name'); 164 | } 165 | 166 | /** 167 | * Get the stub file for the generator. 168 | * 169 | * @return string 170 | */ 171 | protected function getStub() 172 | { 173 | return __DIR__ . '/stubs/form-class-template.stub'; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /tests/FormBuilderTest.php: -------------------------------------------------------------------------------- 1 | container->shouldReceive('make') 15 | ->with('Kris\LaravelFormBuilder\Form') 16 | ->andReturn($this->plainForm); 17 | 18 | $options = [ 19 | 'method' => 'PUT', 20 | 'url' => '/some/url/1', 21 | 'model' => $this->model 22 | ]; 23 | 24 | $plainForm = $this->formBuilder->plain($options); 25 | 26 | $this->assertEquals('PUT', $plainForm->getMethod()); 27 | $this->assertEquals('/some/url/1', $plainForm->getUrl()); 28 | $this->assertEquals($this->model, $plainForm->getModel()); 29 | $this->assertNull($plainForm->buildForm()); 30 | } 31 | 32 | /** @test */ 33 | public function it_creates_custom_form_and_sets_options_on_it() 34 | { 35 | $options = [ 36 | 'method' => 'POST', 37 | 'url' => '/posts', 38 | 'data' => ['dummy_choices' => [1 => 'choice_1', 2 => 'choice_2']] 39 | ]; 40 | 41 | $customForm = new CustomDummyForm(); 42 | 43 | $this->container->shouldReceive('make') 44 | ->with('CustomDummyForm') 45 | ->andReturn($customForm); 46 | 47 | $customFormInstance = $this->formBuilder->create('CustomDummyForm', $options); 48 | 49 | $this->assertEquals('POST', $customFormInstance->getMethod()); 50 | $this->assertEquals($this->request, $customFormInstance->getRequest()); 51 | $this->assertEquals('/posts', $customFormInstance->getUrl()); 52 | $this->assertEquals([1 => 'choice_1', 2 => 'choice_2'], $customFormInstance->getData('dummy_choices')); 53 | $this->assertInstanceOf('Kris\\LaravelFormBuilder\\Form', $customFormInstance); 54 | $this->assertArrayHasKey('title', $customForm->getFields()); 55 | $this->assertArrayHasKey('body', $customForm->getFields()); 56 | } 57 | 58 | /** 59 | * @test 60 | * @expectedException \InvalidArgumentException 61 | */ 62 | public function it_throws_exception_if_child_form_is_not_valid_class() 63 | { 64 | $this->plainForm->add('song', 'form', [ 65 | 'class' => 'nonvalid' 66 | ]); 67 | } 68 | 69 | /** 70 | * @test 71 | * @expectedException \InvalidArgumentException 72 | */ 73 | public function it_throws_exception_if_child_form_class_is_not_passed() 74 | { 75 | $this->plainForm->add('song', 'form', [ 76 | 'class' => null 77 | ]); 78 | } 79 | 80 | /** 81 | * @test 82 | * @expectedException \InvalidArgumentException 83 | */ 84 | public function it_throws_exception_if_child_form_class_is_not_valid_format() 85 | { 86 | $this->plainForm->add('song', 'form', [ 87 | 'class' => 1 88 | ]); 89 | } 90 | 91 | /** @test */ 92 | public function it_can_set_form_helper_once_and_call_build_form() 93 | { 94 | $form = $this->setupForm(new CustomDummyForm()); 95 | 96 | $this->assertEquals($this->formHelper, $form->getFormHelper()); 97 | $this->assertEquals($this->formBuilder, $form->getFormBuilder()); 98 | $this->assertArrayHasKey('title', $form->getFields()); 99 | $this->assertArrayHasKey('body', $form->getFields()); 100 | } 101 | 102 | /** @test */ 103 | public function it_appends_default_namespace_from_config_on_building() 104 | { 105 | $form = new LaravelFormBuilderTest\Forms\NamespacedDummyForm(); 106 | $config = $this->config; 107 | $config['default_namespace'] = 'LaravelFormBuilderTest\Forms'; 108 | $formHelper = new FormHelper($this->view, $this->request, $config); 109 | $formBuilder = new FormBuilder($this->container, $formHelper); 110 | 111 | $this->container->shouldReceive('make') 112 | ->with('LaravelFormBuilderTest\Forms\NamespacedDummyForm') 113 | ->andReturn($form); 114 | 115 | $formBuilder->create('NamespacedDummyForm'); 116 | } 117 | 118 | } 119 | 120 | class CustomDummyForm extends Form 121 | { 122 | 123 | public function buildForm() 124 | { 125 | $this->add('title', 'text') 126 | ->add('body', 'textarea'); 127 | } 128 | } 129 | } 130 | 131 | namespace LaravelFormBuilderTest\Forms { 132 | 133 | use Kris\LaravelFormBuilder\Form; 134 | 135 | class NamespacedDummyForm extends Form 136 | { 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tests/Fields/CollectionTypeTest.php: -------------------------------------------------------------------------------- 1 | 'select', 15 | 'data' => [['id' => 1], ['id' => 2], ['id' => 3]], 16 | 'options' => [ 17 | 'choices' => ['m' => 'male', 'f' => 'female'], 18 | 'label' => false 19 | ] 20 | ]; 21 | 22 | $this->fieldExpetations('text', Mockery::any(), null); 23 | $this->fieldExpetations('collection', Mockery::any()); 24 | $this->request->shouldReceive('old')->andReturn([['id' => 1]]); 25 | 26 | $emailsCollection = new CollectionType('emails', 'collection', $this->plainForm, $options); 27 | 28 | $this->assertEquals(3, count($emailsCollection->getChildren())); 29 | $this->assertInstanceOf('Kris\LaravelFormBuilder\Fields\SelectType', $emailsCollection->prototype()); 30 | } 31 | 32 | /** @test */ 33 | public function it_uses_old_input_if_available() 34 | { 35 | $options = [ 36 | 'type' => 'select', 37 | 'data' => [['id' => 4], ['id' => 5], ['id' => 6]], 38 | 'options' => [ 39 | 'choices' => ['m' => 'male', 'f' => 'female'], 40 | 'label' => false 41 | ] 42 | ]; 43 | 44 | $this->fieldExpetations('text', Mockery::any(), null); 45 | $this->fieldExpetations('collection', Mockery::any()); 46 | $this->request->shouldReceive('old')->andReturn([['id' => 1], ['id' => 2]]); 47 | 48 | $emailsCollection = new CollectionType('emails', 'collection', $this->plainForm, $options); 49 | 50 | $this->assertEquals(2, count($emailsCollection->getChildren())); 51 | $this->assertInstanceOf('Kris\LaravelFormBuilder\Fields\SelectType', $emailsCollection->prototype()); 52 | } 53 | 54 | /** @test */ 55 | public function it_creates_collection_with_child_form() 56 | { 57 | $form = clone $this->plainForm; 58 | $this->request->shouldReceive('old'); 59 | 60 | $form->add('name', 'text') 61 | ->add('gender', 'choice', [ 62 | 'choices' => ['m' => 'male', 'f' => 'female'] 63 | ]) 64 | ->add('published', 'checkbox'); 65 | 66 | $data = new \Illuminate\Support\Collection([ 67 | ['name' => 'john doe', 'gender' => 'm'], 68 | ['name' => 'jane doe', 'gender' => 'f'] 69 | ]); 70 | 71 | $options = [ 72 | 'type' => 'form', 73 | 'data' => $data, 74 | 'options' => [ 75 | 'class' => $form 76 | ] 77 | ]; 78 | 79 | $this->fieldExpetations('collection', Mockery::any()); 80 | 81 | $childFormCollection = new CollectionType('emails', 'collection', $this->plainForm, $options); 82 | 83 | $childFormCollection->render(); 84 | 85 | $this->assertEquals(2, count($childFormCollection->getChildren())); 86 | } 87 | 88 | /** 89 | * @test 90 | */ 91 | public function it_throws_exception_when_requesting_prototype_while_it_is_disabled() 92 | { 93 | $options = [ 94 | 'type' => 'text', 95 | 'prototype' => false 96 | ]; 97 | 98 | $this->fieldExpetations('collection', Mockery::any()); 99 | $this->request->shouldReceive('old'); 100 | 101 | $childFormCollection = new CollectionType('emails', 'collection', $this->plainForm, $options); 102 | 103 | $childFormCollection->render(); 104 | 105 | try { 106 | $childFormCollection->prototype(); 107 | } catch (\Exception $e) { 108 | return; 109 | } 110 | 111 | $this->fail('Exception was not thrown when asked for prototype when disabled.'); 112 | } 113 | 114 | /** 115 | * @test 116 | */ 117 | public function it_throws_exception_when_creating_nonexisting_type() 118 | { 119 | $options = [ 120 | 'type' => 'nonexisting' 121 | ]; 122 | 123 | $this->request->shouldReceive('old'); 124 | $this->fieldExpetations('collection', Mockery::any()); 125 | 126 | try { 127 | $childFormCollection = new CollectionType('emails', 'collection', $this->plainForm, $options); 128 | } catch (\Exception $e) { 129 | return; 130 | } 131 | 132 | $this->fail('Exception was not thrown when creating non existing type in collection.'); 133 | } 134 | 135 | /** 136 | * @test 137 | */ 138 | public function it_throws_exception_when_data_is_not_iterable() 139 | { 140 | $options = [ 141 | 'type' => 'text', 142 | 'data' => 'invalid' 143 | ]; 144 | 145 | $this->request->shouldReceive('old'); 146 | $this->fieldExpetations('collection', Mockery::any()); 147 | 148 | try { 149 | $childFormCollection = new CollectionType('emails', 'collection', $this->plainForm, $options); 150 | } catch (\Exception $e) { 151 | return; 152 | } 153 | 154 | $this->fail('Exception was not thrown for non iterable collection data'); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/travis/kristijanhusak/laravel4-form-builder/master.svg?style=flat)](https://travis-ci.org/kristijanhusak/laravel4-form-builder) 2 | [![Coverage Status](http://img.shields.io/scrutinizer/coverage/g/kristijanhusak/laravel4-form-builder.svg?style=flat)](https://scrutinizer-ci.com/g/kristijanhusak/laravel4-form-builder/?branch=master) 3 | [![Total Downloads](https://img.shields.io/packagist/dt/kris/laravel4-form-builder.svg?style=flat)](https://packagist.org/packages/kris/laravel4-form-builder) 4 | [![Latest Stable Version](https://img.shields.io/packagist/v/kris/laravel4-form-builder.svg?style=flat)](https://packagist.org/packages/kris/laravel4-form-builder) 5 | [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](LICENSE) 6 | 7 | # Laravel 4 form builder 8 | 9 | Form builder for **Laravel 4** inspired by Symfony's form builder. With help of Laravels FormBuilder class creates forms that can be easy modified and reused. 10 | By default it supports Bootstrap 3. 11 | 12 | ## Laravel 5 13 | For laravel 5 version check [laravel-form-builder](https://github.com/kristijanhusak/laravel-form-builder) 14 | 15 | ## Documentation 16 | For detailed documentation refer to [http://kristijanhusak.github.io/laravel-form-builder/](http://kristijanhusak.github.io/laravel-form-builder/). 17 | 18 | **Note** There are small differences between version 4 and 5 (Configuration path, etc.), so please be aware of those. 19 | 20 | ## Changelog 21 | Changelog can be found [here](https://github.com/kristijanhusak/laravel4-form-builder/blob/master/CHANGELOG.md) 22 | 23 | ###Installation 24 | 25 | ``` json 26 | { 27 | "require": { 28 | "kris/laravel4-form-builder": "1.6.*" 29 | } 30 | } 31 | ``` 32 | 33 | run `composer update` 34 | 35 | Then add Service provider to `config/app.php` 36 | 37 | ``` php 38 | 'providers' => [ 39 | // ... 40 | 'Kris\LaravelFormBuilder\FormBuilderServiceProvider' 41 | ] 42 | ``` 43 | 44 | And Facade (also in `config/app.php`) 45 | 46 | ``` php 47 | 'aliases' => [ 48 | // ... 49 | 'FormBuilder' => 'Kris\LaravelFormBuilder\Facades\FormBuilder' 50 | ] 51 | 52 | ``` 53 | 54 | ### Quick start 55 | 56 | Creating form classes is easy. Lets assume PSR-4 is set for loading namespace `App` in `app/Project` folder. With a simple artisan command we can create form: 57 | 58 | ```sh 59 | php artisan make:form app/Project/Forms/SongForm --fields="name:text, lyrics:textarea, publish:checkbox" 60 | ``` 61 | 62 | Form is created in path `app/Project/Forms/SongForm.php` with content: 63 | 64 | ```php 65 | add('name', 'text') 75 | ->add('lyrics', 'textarea') 76 | ->add('publish', 'checkbox'); 77 | } 78 | } 79 | ``` 80 | 81 | If you want to instantiate empty form without any fields, just skip passing `--fields` parameter: 82 | 83 | ```sh 84 | php artisan make:form app/Project/Forms/PostForm 85 | ``` 86 | 87 | Gives: 88 | 89 | ```php 90 | 'POST', 117 | 'url' => route('song.store') 118 | ]); 119 | 120 | return view('song.create', compact('form')); 121 | } 122 | } 123 | ``` 124 | 125 | Print the form in view with `form()` helper function: 126 | 127 | ```html 128 | 129 | 130 | @extend('layouts.master') 131 | 132 | @section('content') 133 | {{{ form($form) }}} 134 | @endsection 135 | ``` 136 | 137 | Above code will generate this html: 138 | 139 | ```html 140 |
141 | 142 |
143 | 144 | 145 |
146 |
147 | 148 | 149 |
150 |
151 | 152 | 153 |
154 |
155 | ``` 156 | 157 | ### Contributing 158 | Project follows [PSR-2](http://www.php-fig.org/psr/psr-2/) standard and it's covered with PHPUnit tests. 159 | Pull requests should include tests and pass [Travis CI](https://travis-ci.org/kristijanhusak/laravel4-form-builder) build. 160 | 161 | To run tests first install dependencies with `composer install`. 162 | 163 | After that tests can be run with `vendor/bin/phpunit` 164 | -------------------------------------------------------------------------------- /tests/FormHelperTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($this->view, $this->formHelper->getView()); 13 | $this->assertEquals($this->request, $this->formHelper->getRequest()); 14 | } 15 | 16 | /** @test */ 17 | public function it_merges_options_properly() 18 | { 19 | $initial = [ 20 | 'attr' => ['class' => 'form-control'], 21 | 'label_attr' => ['class' => 'test'], 22 | 'selected' => null 23 | ]; 24 | 25 | $options = [ 26 | 'attr' => ['id' => 'form-id'], 27 | 'label_attr' => ['class' => 'new-class'], 28 | ]; 29 | 30 | $expected = [ 31 | 'attr' => ['class' => 'form-control', 'id' => 'form-id'], 32 | 'label_attr' => ['class' => 'new-class'], 33 | 'selected' => null 34 | ]; 35 | 36 | $mergedOptions = $this->formHelper->mergeOptions($initial, $options); 37 | 38 | $this->assertEquals($expected, $mergedOptions); 39 | } 40 | 41 | /** @test */ 42 | public function it_gets_proper_class_for_specific_field_type() 43 | { 44 | $input = $this->formHelper->getFieldType('text'); 45 | $select = $this->formHelper->getFieldType('select'); 46 | $textarea = $this->formHelper->getFieldType('textarea'); 47 | $submit = $this->formHelper->getFieldType('submit'); 48 | $reset = $this->formHelper->getFieldType('reset'); 49 | $button = $this->formHelper->getFieldType('button'); 50 | $radio = $this->formHelper->getFieldType('radio'); 51 | $checkbox = $this->formHelper->getFieldType('checkbox'); 52 | $choice = $this->formHelper->getFieldType('choice'); 53 | $repeated = $this->formHelper->getFieldType('repeated'); 54 | $collection = $this->formHelper->getFieldType('collection'); 55 | $static = $this->formHelper->getFieldType('static'); 56 | $entity = $this->formHelper->getFieldType('entity'); 57 | 58 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\InputType', $input); 59 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\SelectType', $select); 60 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\TextareaType', $textarea); 61 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\ButtonType', $submit); 62 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\ButtonType', $reset); 63 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\ButtonType', $button); 64 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\CheckableType', $radio); 65 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\CheckableType', $checkbox); 66 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\ChoiceType', $choice); 67 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\RepeatedType', $repeated); 68 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\CollectionType', $collection); 69 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\StaticType', $static); 70 | $this->assertEquals('Kris\\LaravelFormBuilder\\Fields\\EntityType', $entity); 71 | } 72 | 73 | /** 74 | * @test 75 | * @expectedException \InvalidArgumentException 76 | */ 77 | public function it_throws_InvalidArgumentException_for_non_existing_field_type() 78 | { 79 | $this->formHelper->getFieldType('nonexisting'); 80 | } 81 | 82 | /** @test */ 83 | public function it_creates_html_attributes_from_array_of_options() 84 | { 85 | $options = ['class' => 'form-control', 'data-id' => 1, 'id' => 'post']; 86 | 87 | $attributes = $this->formHelper->prepareAttributes($options); 88 | 89 | $this->assertEquals( 90 | 'class="form-control" data-id="1" id="post" ', 91 | $attributes 92 | ); 93 | } 94 | 95 | /** @test */ 96 | public function it_load_custom_field_types_from_config() 97 | { 98 | $config = $this->config; 99 | 100 | $config['custom_fields']['datetime'] = 'App\Forms\DatetimeType'; 101 | 102 | $formHelper = new FormHelper($this->view, $this->request, $config); 103 | 104 | $this->assertEquals( 105 | 'App\Forms\DatetimeType', 106 | $formHelper->getFieldType('datetime') 107 | ); 108 | } 109 | 110 | /** @test */ 111 | public function it_formats_the_label() 112 | { 113 | $this->assertEquals( 114 | 'Some Name', 115 | $this->formHelper->formatLabel('some_name') 116 | ); 117 | 118 | $this->assertEquals( 119 | 'Song', 120 | $this->formHelper->formatLabel('song') 121 | ); 122 | 123 | $this->assertNull($this->formHelper->formatLabel(false)); 124 | } 125 | 126 | /** @test */ 127 | public function it_converts_model_to_array() 128 | { 129 | $model = ['m' => 'male', 'f' => 'female']; 130 | $collection = new Collection($model); 131 | 132 | $collection = $this->formHelper->convertModelToArray($collection); 133 | $sameModel = $this->formHelper->convertModelToArray($model); 134 | $this->assertEquals($model, $collection); 135 | $this->assertEquals($model, $sameModel); 136 | $this->assertNull($this->formHelper->convertModelToArray([])); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/CollectionType.php: -------------------------------------------------------------------------------- 1 | null, 34 | 'options' => ['is_child' => true], 35 | 'prototype' => true, 36 | 'data' => null, 37 | 'property' => 'id', 38 | 'prototype_name' => '__NAME__' 39 | ]; 40 | } 41 | 42 | /** 43 | * Get the prototype object 44 | * 45 | * @return FormField 46 | * @throws \Exception 47 | */ 48 | public function prototype() 49 | { 50 | 51 | if ($this->getOption('prototype') === false) { 52 | throw new \Exception( 53 | 'Prototype for collection field [' . $this->name .'] is disabled.' 54 | ); 55 | } 56 | 57 | return $this->proto; 58 | } 59 | 60 | /** 61 | * @inheritdoc 62 | */ 63 | protected function createChildren() 64 | { 65 | $this->children = []; 66 | $type = $this->getOption('type'); 67 | $oldInput = $this->parent->getRequest()->old($this->getNameKey()); 68 | 69 | try { 70 | $fieldType = $this->formHelper->getFieldType($type); 71 | } catch (\Exception $e) { 72 | throw new \Exception( 73 | 'Collection field ['.$this->name.'] requires [type] option'. "\n\n". 74 | $e->getMessage() 75 | ); 76 | } 77 | 78 | $data = $this->getOption($this->valueProperty, []); 79 | 80 | // Needs to have more than 1 item because 1 is rendered by default 81 | if (count($oldInput) > 1) { 82 | $data = $oldInput; 83 | } 84 | 85 | if ($data instanceof Collection) { 86 | $data = $data->all(); 87 | } 88 | 89 | $field = new $fieldType($this->name, $type, $this->parent, $this->getOption('options')); 90 | 91 | if ($this->getOption('prototype')) { 92 | $this->generatePrototype(clone $field); 93 | } 94 | 95 | if (!$data || empty($data)) { 96 | return $this->children[] = $this->setupChild(clone $field, '[0]'); 97 | } 98 | 99 | if (!is_array($data) && !$data instanceof \Traversable) { 100 | throw new \Exception( 101 | 'Data for collection field ['.$this->name.'] must be iterable.' 102 | ); 103 | } 104 | 105 | foreach ($data as $key => $val) { 106 | $this->children[] = $this->setupChild(clone $field, '['.$key.']', $val); 107 | } 108 | } 109 | 110 | /** 111 | * Set up a single child element for a collection 112 | * 113 | * @param FormField $field 114 | * @param $name 115 | * @param null $value 116 | * @return FormField 117 | */ 118 | protected function setupChild(FormField $field, $name, $value = null) 119 | { 120 | $newFieldName = $field->getName().$name; 121 | 122 | $firstFieldOptions = $this->formHelper->mergeOptions( 123 | $this->getOption('options'), 124 | ['attr' => ['id' => $newFieldName]] 125 | ); 126 | 127 | $field->setName($newFieldName); 128 | $field->setOptions($firstFieldOptions); 129 | 130 | if ($value && !$field instanceof ChildFormType) { 131 | $value = $this->getModelValueAttribute( 132 | $value, 133 | $this->getOption('property') 134 | ); 135 | } 136 | 137 | $field->setValue($value); 138 | 139 | return $field; 140 | } 141 | 142 | /** 143 | * Generate prototype for regular form field 144 | * 145 | * @param FormField $field 146 | */ 147 | protected function generatePrototype(FormField $field) 148 | { 149 | $field->setOption('is_prototype', true); 150 | $field = $this->setupChild($field, $this->getPrototypeName()); 151 | 152 | if ($field instanceof ChildFormType) { 153 | foreach ($field->getChildren() as $child) { 154 | if ($child instanceof CollectionType) { 155 | $child->preparePrototype($child->prototype()); 156 | } 157 | } 158 | } 159 | 160 | $this->proto = $field; 161 | } 162 | 163 | /** 164 | * Generate array like prototype name 165 | * 166 | * @return string 167 | */ 168 | protected function getPrototypeName() 169 | { 170 | return '[' . $this->getOption('prototype_name') . ']'; 171 | } 172 | 173 | /** 174 | * Prepare collection for prototype by adding prototype as child 175 | * @param FormField $field 176 | */ 177 | public function preparePrototype(FormField $field) 178 | { 179 | if (!$field->getOption('is_prototype')) { 180 | throw new \InvalidArgumentException( 181 | 'Field ['.$field->getRealName().'] is not a valid prototype object.' 182 | ); 183 | } 184 | 185 | $this->children = []; 186 | $this->children[] = $field; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/FormHelper.php: -------------------------------------------------------------------------------- 1 | 'InputType', 38 | 'email' => 'InputType', 39 | 'url' => 'InputType', 40 | 'tel' => 'InputType', 41 | 'search' => 'InputType', 42 | 'password' => 'InputType', 43 | 'hidden' => 'InputType', 44 | 'number' => 'InputType', 45 | 'date' => 'InputType', 46 | 'file' => 'InputType', 47 | 'image' => 'InputType', 48 | 'color' => 'InputType', 49 | 'datetime-local' => 'InputType', 50 | 'month' => 'InputType', 51 | 'range' => 'InputType', 52 | 'time' => 'InputType', 53 | 'week' => 'InputType', 54 | 'select' => 'SelectType', 55 | 'textarea' => 'TextareaType', 56 | 'button' => 'ButtonType', 57 | 'submit' => 'ButtonType', 58 | 'reset' => 'ButtonType', 59 | 'radio' => 'CheckableType', 60 | 'checkbox' => 'CheckableType', 61 | 'choice' => 'ChoiceType', 62 | 'form' => 'ChildFormType', 63 | 'entity' => 'EntityType', 64 | 'collection' => 'CollectionType', 65 | 'repeated' => 'RepeatedType', 66 | 'static' => 'StaticType' 67 | ]; 68 | 69 | /** 70 | * Custom types 71 | * 72 | * @var array 73 | */ 74 | private $customTypes = []; 75 | 76 | /** 77 | * @param View $view 78 | * @param Request $request 79 | * @param array $config 80 | */ 81 | public function __construct(View $view, Request $request, array $config = []) 82 | { 83 | $this->view = $view; 84 | $this->config = $config; 85 | $this->request = $request; 86 | $this->loadCustomTypes(); 87 | } 88 | 89 | /** 90 | * @param string $key 91 | * @param string $default 92 | * @return mixed 93 | */ 94 | public function getConfig($key, $default = null) 95 | { 96 | return array_get($this->config, $key, $default); 97 | } 98 | 99 | /** 100 | * @return View 101 | */ 102 | public function getView() 103 | { 104 | return $this->view; 105 | } 106 | 107 | /** 108 | * @return Request 109 | */ 110 | public function getRequest() 111 | { 112 | return $this->request; 113 | } 114 | 115 | /** 116 | * Merge options array 117 | * 118 | * @param array $first 119 | * @param array $second 120 | * @return array 121 | */ 122 | public function mergeOptions(array $first, array $second) 123 | { 124 | return array_replace_recursive($first, $second); 125 | } 126 | 127 | /** 128 | * Get proper class for field type 129 | * 130 | * @param $type 131 | * @return string 132 | */ 133 | public function getFieldType($type) 134 | { 135 | $types = array_keys(static::$availableFieldTypes); 136 | 137 | if (!$type || trim($type) == '') { 138 | throw new \InvalidArgumentException('Field type must be provided.'); 139 | } 140 | 141 | if (array_key_exists($type, $this->customTypes)) { 142 | return $this->customTypes[$type]; 143 | } 144 | 145 | if (!in_array($type, $types)) { 146 | throw new \InvalidArgumentException( 147 | sprintf( 148 | 'Unsupported field type [%s]. Available types are: %s', 149 | $type, 150 | join(', ', array_merge($types, array_keys($this->customTypes))) 151 | ) 152 | ); 153 | } 154 | 155 | $namespace = __NAMESPACE__.'\\Fields\\'; 156 | 157 | return $namespace . static::$availableFieldTypes[$type]; 158 | } 159 | 160 | /** 161 | * Convert array of attributes to html attributes 162 | * 163 | * @param $options 164 | * @return string 165 | */ 166 | public function prepareAttributes($options) 167 | { 168 | if (!$options) { 169 | return null; 170 | } 171 | 172 | $attributes = []; 173 | 174 | foreach ($options as $name => $option) { 175 | if ($option !== null) { 176 | $name = is_numeric($name) ? $option : $name; 177 | $attributes[] = $name.'="'.$option.'" '; 178 | } 179 | } 180 | 181 | return join('', $attributes); 182 | } 183 | 184 | /** 185 | * Add custom field 186 | * 187 | * @param $name 188 | * @param $class 189 | */ 190 | public function addCustomField($name, $class) 191 | { 192 | if (!array_key_exists($name, $this->customTypes)) { 193 | return $this->customTypes[$name] = $class; 194 | } 195 | 196 | throw new \InvalidArgumentException('Custom field ['.$name.'] already exists on this form object.'); 197 | } 198 | 199 | /** 200 | * Load custom field types from config file 201 | */ 202 | private function loadCustomTypes() 203 | { 204 | $customFields = (array) $this->getConfig('custom_fields'); 205 | 206 | if (!empty($customFields)) { 207 | foreach ($customFields as $fieldName => $fieldClass) { 208 | $this->addCustomField($fieldName, $fieldClass); 209 | } 210 | } 211 | } 212 | 213 | public function convertModelToArray($model) 214 | { 215 | if (!$model) { 216 | return null; 217 | } 218 | 219 | if ($model instanceof Model) { 220 | return $model->toArray(); 221 | } 222 | 223 | if ($model instanceof Collection) { 224 | return $model->all(); 225 | } 226 | 227 | return $model; 228 | } 229 | 230 | /** 231 | * Format the label to the proper format 232 | * 233 | * @param $name 234 | * @return string 235 | */ 236 | public function formatLabel($name) 237 | { 238 | if (!$name) { 239 | return null; 240 | } 241 | 242 | return ucwords(str_replace('_', ' ', $name)); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.6.11 2 | - Fix checkables checked state not working with model 3 | - Fix adding `for` attribute twice to label 4 | - Add some tests 5 | 6 | ## 1.6.10 7 | - add `disableFields` and `enableFields` functions to Form class to allow disabling all fields in the form 8 | - Fix collection type model value binding 9 | 10 | ## 1.6.0 11 | - **Minor BC Break** - Rename `default_value` to `value`, and use `default_value` as fallback value if no `value` or model data available 12 | 13 | If You published views update all templates and set `$options['default_value']` to `$options['value']` 14 | 15 | - Add form composition (Add fields from another form with `compose()` method) - Thanks to [@theshaunwalker](https://github.com/theshaunwalker) 16 | - Add trait for controller that allows shorter sintax (`$this->form()` and `$this->plain()`) 17 | - Fix `renderUntil` to check the name by real name instead of namespaced name 18 | - Fix collection of child forms not rendering when there is no data 19 | - Fix collection prototype to return proper `prototype_name` for nested collections 20 | - Return `$this` from `rebuildForm()` method to allow regenerating form in loops 21 | 22 | ## 1.5.10 23 | - Fix collection of forms not rendering when there is no model or any data passed to collection. 24 | 25 | ## 1.5.1 26 | - Add `entity` field type that allows fetching specific Model data 27 | 28 | # 1.5.0 29 | - Bind all fields values manually without Laravel's form builder `Form::model` (Check note below for possible BC break) 30 | - Add possibility to use Closure as default value for fields which solves issues like in [#98](https://github.com/kristijanhusak/laravel-form-builder/issues/98#issuecomment-103893235) 31 | - Add `addBefore` and `addAfter` methods to Form class to allow adding fields at specific location 32 | - Add `required` option for all field types, that adds class `required` (configurable in config) to label, and `required` attribute to field. 33 | - Rename command from `form:make` to `make:form` 34 | - Fix passing model to child forms 35 | - Add `wrapper` option for button type, defaults to false 36 | - Fix `help_block` rendering twice on repeated field type 37 | - Fix choice field type adding additional `[]` to the name in child forms/collections 38 | - Fix choice field type not adding `[]` on regular forms 39 | - Fix expanded/multiple choice fields id by prefixing it with properly formatted name 40 | - Set FormBuilder class properties to protected to allow extending 41 | - Optmization and other minor fixes 42 | 43 | **Note**: If You published views before, they need to be updated to prevent possible breaking. 44 | Since value binding is now done in package, and `Form::model` is removed, views needs to be republished (or updated) to remove `Form::model` from [form.php](https://github.com/kristijanhusak/laravel-form-builder/blob/master/src/views/form.php). Also [choice.php](https://github.com/kristijanhusak/laravel-form-builder/blob/master/src/views/choice.php) needs to be updated to pass `selected` value. 45 | 46 | ## 1.4.20 47 | - Add `help_block` option for fields which renders note under the field (http://getbootstrap.com/css/#forms) 48 | - Fix repeated type not closing tags properly 49 | - Fix default_value for child forms ([#77](https://github.com/kristijanhusak/laravel-form-builder/issues/80)) 50 | - Pass form data to child forms. 51 | - Fix issue with showing validation errors for nested forms ([#78](https://github.com/kristijanhusak/laravel-form-builder/issues/78). Thanks to [@theshaunwalker](https://github.com/theshaunwalker)) 52 | 53 | ## 1.4.11 54 | - Add ability to exclude some fields from rendering ([PR-77](https://github.com/kristijanhusak/laravel-form-builder/pull/77). Thanks to [@theshaunwalker](https://github.com/theshaunwalker)) 55 | 56 | ## 1.4.10 57 | - Use old input for collection field type. (Example: after failed validation, redirect back withInput). 58 | - Add static type field. 59 | - Add `form_until` function that renders fields until specified field(Thanks to [@hackel](https://github.com/hackel)) 60 | - using `getData` without param returns all data 61 | - Bind data to child form every time to allow setting null. 62 | 63 | ## 1.4.05 64 | - Fix id and name collision. 65 | 66 | ## 1.4.04 67 | - Fix collection type form model binding 68 | 69 | ## 1.4.03 70 | - Fix custom template per field. 71 | 72 | ## 1.4.02 73 | - Fix adding enctype multipart/form-data to form when field is of type file. 74 | 75 | ## 1.4.01 76 | - Fix setting field id on child forms. 77 | 78 | ## 1.4.0 79 | - Allow calling form methods directly from child form 80 | - Update views to print all errors for single field 81 | - Fix custom field template for child forms 82 | - Fix disabling errors for fields in form class 83 | 84 | ## 1.3.70 85 | - Fix form model binding for named forms 86 | - Collection type now pulls data from model if data not passed in `data` option 87 | 88 | ## 1.3.61 89 | - Fix choices to show only one error message 90 | 91 | ## 1.3.6 92 | - Update `repeated` type to work with child forms 93 | - Add possibility to create named forms (fields get name[fieldname]) 94 | 95 | ## 1.3.5 96 | - Add support for some HTML5 input types(By @bishopb) 97 | 98 | ## 1.3.4 99 | - Add `choice_options` property to `choice` field type that handles each radio/checkbox options when `expanded` is true. 100 | - Allow disabling wrapper for fields by setting it to `false` 101 | 102 | ## 1.3.3 103 | - Fix child form rebuild bug. 104 | 105 | ## 1.3.2 106 | - Allow passing any type of model to form class (array, object, eloquent model, Collection). 107 | 108 | ## 1.3.1 109 | - Fix bug where wrapper was not showing if label was false. 110 | 111 | ## 1.3.0 112 | - Add [Collection](https://github.com/kristijanhusak/laravel4-form-builder#collection) type 113 | - Minor fixes 114 | 115 | ## 1.2.0 116 | - Change package namespace in service provider (was broken when config or views were published) 117 | - Allow adding child forms with full class name which will be instantiated automatically 118 | - Add FormBuilder class instance to the Form class 119 | - Setting label explicitly to false in the field options disables printing label for that field 120 | - Minor fixes 121 | 122 | ## 1.1.8 123 | - Dynamically access children in parent form types. 124 | 125 | ## 1.1.7 126 | - Fix empty select value to be empty string instead of `0` 127 | 128 | ## 1.1.6 129 | - Append `[]` to field name automatically if multiple attribute is set to true. 130 | 131 | ## 1.1.5 132 | - Fix `child form validation errors not showing` bug. 133 | 134 | ## 1.1.4 135 | - Added `repeated` type field (https://github.com/kristijanhusak/laravel-form-builder#field-customization) 136 | - Minor fixes 137 | 138 | ## 1.1.3 139 | - Added [Child form](https://github.com/kristijanhusak/laravel-form-builder#child-form) type 140 | - Added `rebuildForm` method 141 | - Added `getRequest` method 142 | - Added `setData` and `getData` options to form 143 | - Minor fixes 144 | 145 | ## 1.1.2 146 | - `form_end()` function now also renders rest of the form fields - can be changed with 2nd parameter as false(`form_end($form, false)`) 147 | - Minor fixes 148 | 149 | ## 1.1.1 150 | - Added `remove()` and `modify()` methods to Form class 151 | - Added 'empty_value' option for select 152 | - `choice` and `select` types now needs `selected` option to pass key instead of value of choice 153 | 154 | ## 1.1 155 | - Added [Laravel 4](https://github.com/kristijanhusak/laravel-form-builder/tree/laravel-4) support (Tags 0.*) 156 | - Config loaded outside of class and injected as array 157 | - Changed command from `laravel-form-builder:make` to `form:make` 158 | 159 | ## 1.0 160 | - Initial version 161 | -------------------------------------------------------------------------------- /src/Kris/LaravelFormBuilder/Fields/FormField.php: -------------------------------------------------------------------------------- 1 | name = $name; 92 | $this->type = $type; 93 | $this->parent = $parent; 94 | $this->formHelper = $this->parent->getFormHelper(); 95 | $this->setTemplate(); 96 | $this->setDefaultOptions($options); 97 | $this->setupValue(); 98 | } 99 | 100 | protected function setupValue() 101 | { 102 | $value = $this->getOption($this->valueProperty); 103 | $isChild = $this->getOption('is_child'); 104 | 105 | if ($value instanceof \Closure) { 106 | $this->valueClosure = $value; 107 | } 108 | 109 | if (($value === null || $value instanceof \Closure) && !$isChild) { 110 | $this->setValue($this->getModelValueAttribute($this->parent->getModel(), $this->name)); 111 | } elseif (!$isChild) { 112 | $this->hasDefault = true; 113 | } 114 | } 115 | 116 | /** 117 | * Get the template, can be config variable or view path 118 | * 119 | * @return string 120 | */ 121 | abstract protected function getTemplate(); 122 | 123 | /** 124 | * @param array $options 125 | * @param bool $showLabel 126 | * @param bool $showField 127 | * @param bool $showError 128 | * @return string 129 | */ 130 | public function render(array $options = [], $showLabel = true, $showField = true, $showError = true) 131 | { 132 | $val = null; 133 | $value = array_get($options, $this->valueProperty); 134 | $defaultValue = array_get($options, $this->defaultValueProperty); 135 | 136 | if ($showField) { 137 | $this->rendered = true; 138 | } 139 | 140 | // Check if default value is passed to render function from view. 141 | // If it is, we save it to a variable and then override it before 142 | // rendering the view 143 | if ($value) { 144 | $val = $value; 145 | } elseif ($defaultValue && !$this->getOption($this->valueProperty)) { 146 | $val = $defaultValue; 147 | } 148 | 149 | $options = $this->prepareOptions($options); 150 | 151 | if ($val) { 152 | $options[$this->valueProperty] = $val; 153 | } 154 | 155 | if (!$this->needsLabel($options)) { 156 | $showLabel = false; 157 | } 158 | 159 | if ($showError) { 160 | $showError = $this->parent->haveErrorsEnabled(); 161 | } 162 | 163 | return $this->formHelper->getView()->make( 164 | $this->template, 165 | [ 166 | 'name' => $this->name, 167 | 'nameKey' => $this->getNameKey(), 168 | 'type' => $this->type, 169 | 'options' => $options, 170 | 'showLabel' => $showLabel, 171 | 'showField' => $showField, 172 | 'showError' => $showError 173 | ] 174 | )->render(); 175 | } 176 | 177 | /** 178 | * Get the attribute value from the model by name 179 | * 180 | * @param mixed $model 181 | * @param string $name 182 | * @return mixed 183 | */ 184 | protected function getModelValueAttribute($model, $name) 185 | { 186 | $transformedName = $this->transformKey($name); 187 | if (is_string($model)) { 188 | return $model; 189 | } elseif (is_object($model)) { 190 | return object_get($model, $transformedName); 191 | } elseif (is_array($model)) { 192 | return array_get($model, $transformedName); 193 | } 194 | } 195 | 196 | /** 197 | * Transform array like syntax to dot syntax 198 | * 199 | * @param $key 200 | * @return mixed 201 | */ 202 | protected function transformKey($key) 203 | { 204 | return str_replace(['.', '[]', '[', ']'], ['_', '', '.', ''], $key); 205 | } 206 | 207 | /** 208 | * Prepare options for rendering 209 | * 210 | * @param array $options 211 | * @return array 212 | */ 213 | protected function prepareOptions(array $options = []) 214 | { 215 | $helper = $this->formHelper; 216 | 217 | if (array_get($this->options, 'template') !== null) { 218 | $this->template = array_pull($this->options, 'template'); 219 | } 220 | 221 | $options = $helper->mergeOptions($this->options, $options); 222 | 223 | if ($this->parent->haveErrorsEnabled()) { 224 | $this->addErrorClass($options); 225 | } 226 | 227 | if ($this->getOption('attr.multiple')) { 228 | $this->name = $this->name.'[]'; 229 | } 230 | 231 | if ($this->getOption('required') === true) { 232 | $options['label_attr']['class'] .= ' ' . $this->formHelper 233 | ->getConfig('defaults.required_class', 'required'); 234 | $options['attr']['required'] = 'required'; 235 | } 236 | 237 | $options['wrapperAttrs'] = $helper->prepareAttributes($options['wrapper']); 238 | $options['errorAttrs'] = $helper->prepareAttributes($options['errors']); 239 | 240 | if ($options['is_child']) { 241 | $options['labelAttrs'] = $helper->prepareAttributes($options['label_attr']); 242 | } 243 | 244 | if ($options['help_block']['text']) { 245 | $options['help_block']['helpBlockAttrs'] = $helper->prepareAttributes( 246 | $options['help_block']['attr'] 247 | ); 248 | } 249 | 250 | return $options; 251 | } 252 | 253 | /** 254 | * Get name of the field 255 | * 256 | * @return string 257 | */ 258 | public function getName() 259 | { 260 | return $this->name; 261 | } 262 | 263 | /** 264 | * Set name of the field 265 | * 266 | * @param string $name 267 | * @return $this 268 | */ 269 | public function setName($name) 270 | { 271 | $this->name = $name; 272 | 273 | return $this; 274 | } 275 | 276 | /** 277 | * Get dot notation key for fields 278 | * 279 | * @return string 280 | **/ 281 | public function getNameKey() 282 | { 283 | return $this->transformKey($this->name); 284 | } 285 | 286 | /** 287 | * Get field options 288 | * 289 | * @return array 290 | */ 291 | public function getOptions() 292 | { 293 | return $this->options; 294 | } 295 | 296 | /** 297 | * Get single option from options array. Can be used with dot notation ('attr.class') 298 | * 299 | * @param $option 300 | * @param string $default 301 | * 302 | * @return mixed 303 | */ 304 | public function getOption($option, $default = null) 305 | { 306 | return array_get($this->options, $option, $default); 307 | } 308 | 309 | /** 310 | * Set field options 311 | * 312 | * @param array $options 313 | * @return $this 314 | */ 315 | public function setOptions($options) 316 | { 317 | $this->options = $this->prepareOptions($options); 318 | 319 | return $this; 320 | } 321 | 322 | /** 323 | * Set single option on the field 324 | * 325 | * @param string $name 326 | * @param mixed $value 327 | * @return $this 328 | */ 329 | public function setOption($name, $value) 330 | { 331 | array_set($this->options, $name, $value); 332 | 333 | return $this; 334 | } 335 | 336 | /** 337 | * Get the type of the field 338 | * 339 | * @return string 340 | */ 341 | public function getType() 342 | { 343 | return $this->type; 344 | } 345 | 346 | /** 347 | * Set type of the field 348 | * 349 | * @param mixed $type 350 | * @return $this 351 | */ 352 | public function setType($type) 353 | { 354 | if ($this->formHelper->getFieldType($type)) { 355 | $this->type = $type; 356 | } 357 | 358 | return $this; 359 | } 360 | 361 | /** 362 | * @return Form 363 | */ 364 | public function getParent() 365 | { 366 | return $this->parent; 367 | } 368 | 369 | /** 370 | * Check if the field is rendered 371 | * 372 | * @return bool 373 | */ 374 | public function isRendered() 375 | { 376 | return $this->rendered; 377 | } 378 | 379 | /** 380 | * Default options for field 381 | * 382 | * @return array 383 | */ 384 | protected function getDefaults() 385 | { 386 | return []; 387 | } 388 | 389 | /** 390 | * Defaults used across all fields 391 | * 392 | * @return array 393 | */ 394 | private function allDefaults() 395 | { 396 | return [ 397 | 'wrapper' => ['class' => $this->formHelper->getConfig('defaults.wrapper_class')], 398 | 'attr' => ['class' => $this->formHelper->getConfig('defaults.field_class')], 399 | 'help_block' => ['text' => null, 'tag' => 'p', 'attr' => [ 400 | 'class' => $this->formHelper->getConfig('defaults.help_block_class') 401 | ]], 402 | 'value' => null, 403 | 'default_value' => null, 404 | 'label' => $this->formHelper->formatLabel($this->getRealName()), 405 | 'is_child' => false, 406 | 'label_attr' => ['class' => $this->formHelper->getConfig('defaults.label_class')], 407 | 'errors' => ['class' => $this->formHelper->getConfig('defaults.error_class')] 408 | ]; 409 | } 410 | 411 | /** 412 | * Get real name of the field without form namespace 413 | * 414 | * @return string 415 | */ 416 | public function getRealName() 417 | { 418 | return $this->getOption('real_name', $this->name); 419 | } 420 | 421 | /** 422 | * @param $value 423 | * @return $this 424 | */ 425 | protected function setValue($value) 426 | { 427 | if ($this->hasDefault) { 428 | return $this; 429 | } 430 | 431 | $closure = $this->valueClosure; 432 | 433 | if ($closure instanceof \Closure) { 434 | $value = $closure($value ?: null); 435 | } 436 | 437 | if ($value === null || $value === false) { 438 | $value = $this->getOption($this->defaultValueProperty); 439 | } 440 | 441 | $this->options[$this->valueProperty] = $value; 442 | 443 | return $this; 444 | } 445 | 446 | /** 447 | * Set the template property on the object 448 | */ 449 | private function setTemplate() 450 | { 451 | $this->template = $this->formHelper->getConfig($this->getTemplate(), $this->getTemplate()); 452 | } 453 | 454 | /** 455 | * Add error class to wrapper if validation errors exist 456 | * 457 | * @param $options 458 | */ 459 | protected function addErrorClass(&$options) 460 | { 461 | $errors = $this->formHelper->getRequest()->getSession()->get('errors'); 462 | 463 | if ($errors && $errors->has($this->getNameKey())) { 464 | $errorClass = $this->formHelper->getConfig('defaults.wrapper_error_class'); 465 | 466 | if ($options['wrapper'] && !str_contains($options['wrapper']['class'], $errorClass)) { 467 | $options['wrapper']['class'] .= ' '.$errorClass; 468 | } 469 | } 470 | 471 | return $options; 472 | } 473 | 474 | 475 | /** 476 | * Merge all defaults with field specific defaults and set template if passed 477 | * 478 | * @param array $options 479 | */ 480 | protected function setDefaultOptions(array $options = []) 481 | { 482 | $this->options = $this->formHelper->mergeOptions($this->allDefaults(), $this->getDefaults()); 483 | $this->options = $this->prepareOptions($options); 484 | } 485 | 486 | /** 487 | * Check if fields needs label 488 | * 489 | * @param array $options 490 | * @return bool 491 | */ 492 | protected function needsLabel(array $options = []) 493 | { 494 | // If field is 171 |
172 | 173 | 174 |
175 |
176 | 177 | 178 |
179 |
180 | 181 | 182 |
183 | 184 | ``` 185 | 186 | There are several helper methods that can help you customize your rendering: 187 | 188 | 189 | ``` html 190 | 191 | 192 | {{ form_row($form->lyrics, ['attr' => ['class' => 'big-textarea']]) }} 193 | 194 | 195 | 196 |
197 | 198 | 199 |
200 | ``` 201 | 202 | You can also split it even more: 203 | ``` html 204 | {{ form_start($form) }} 205 |
206 | 207 | {{ form_label($form->publish) }} 208 | 209 | 210 | {{ form_widget($form->publish, ['checked' => true]) }} 211 | 212 | 213 | {{ form_errors($form->publish) }} 214 |
This field is required.
215 | 216 | {{ form_rest($form) }} 217 | 222 |
223 | 224 | 225 |
226 |
227 | 228 | 229 |
230 | 231 | 241 | {{ form_end($form) }} 242 |
243 | 244 | ``` 245 | ### Plain form 246 | 247 | If you need to quick create a small form that does not to be reused, you can use `plain` method: 248 | 249 | ``` php 250 | 'POST', 260 | 'url' => route('login') 261 | ])->add('username', 'text')->add('password', 'password')->add('login', 'submit'); 262 | 263 | return view('auth.login', compact('form')); 264 | } 265 | 266 | public function postLogin() 267 | { 268 | } 269 | } 270 | ``` 271 | 272 | ### Child form 273 | You can add one form as a child in another form. This will render all fields from that child form and wrap them in name provided: 274 | 275 | ``` php 276 | 277 | class PostForm 278 | { 279 | public function buildForm() 280 | { 281 | $this 282 | ->add('title', 'text') 283 | ->add('body', 'textarea'); 284 | } 285 | } 286 | 287 | class GenderForm 288 | { 289 | public function buildForm() 290 | { 291 | $this 292 | ->add('gender', 'select', [ 293 | 'choices' => $this->getData('genders') 294 | ]); 295 | } 296 | } 297 | 298 | class SongForm extends Form 299 | { 300 | public function buildForm() 301 | { 302 | $this 303 | ->add('name', 'text') 304 | ->add('gender', 'form', [ 305 | 'class' => 'App\Forms\GenderForm', 306 | // Passed to gender form as data (same as calling addData($data) method), 307 | // works only if class is passed as string 308 | 'data' => ['genders' => ['m' => 'Male', 'f' => 'Female']] 309 | ]) 310 | ->add('song', 'form', [ 311 | 'class' => $this->formBuilder->create('App\Forms\PostForm') 312 | ]) 313 | ->add('lyrics', 'textarea'); 314 | } 315 | } 316 | ``` 317 | So now song form will render this: 318 | ```html 319 |
320 | 321 | 322 |
323 |
324 | 325 | 326 |
327 |
328 | 329 | 330 |
331 |
332 | 333 | 334 |
335 | ``` 336 | 337 | ### Named form 338 | Named forms are very similar to child forms, only difference is that they are used as standalone forms. 339 | 340 | ```php 341 | class PostForm 342 | { 343 | // Can be changed when creating a form 344 | protected $name = 'post'; 345 | 346 | public function buildForm() 347 | { 348 | $this 349 | ->add('title', 'text', [ 350 | 'label' => 'Post title' 351 | ]) 352 | ->add('body', 'textarea', [ 353 | 'label' => 'Post body' 354 | ]); 355 | } 356 | } 357 | 358 | class PostController { 359 | public function createAction() 360 | { 361 | $form = FormBuilder::create('App\Forms\PostForm'); 362 | 363 | // Can be set from here in 2 ways: 364 | // This allows flexibility to use only when needed 365 | // 1. way: 366 | $form = FormBuilder::create('App\Forms\PostForm', [ 367 | 'name' => 'post' 368 | ]); 369 | 370 | // 2. way; 371 | $form = FormBuilder::create('App\Forms\PostForm')->setName('post'); 372 | } 373 | } 374 | 375 | // View 376 |
377 | 378 | 379 |
380 |
381 | 382 | 383 |
384 | ``` 385 | 386 | 387 | ### Collection 388 | Collections are used for working with array of data, mostly used for relationships (OneToMany, ManyToMany). 389 | 390 | It can be any type that is available in the package. Here are some examples: 391 | 392 | ``` php 393 | add('title', 'text') 402 | ->add('body', 'textarea') 403 | ->add('tags', 'collection', [ 404 | 'type' => 'text', 405 | 'property' => 'name', // Which property to use on the tags model for value, defualts to id 406 | 'data' => [], // Data is automatically bound from model, here we can override it 407 | 'options' => [ // these are options for a single type 408 | 'label' => false, 409 | 'attr' => ['class' => 'tag'] 410 | ] 411 | ]); 412 | } 413 | } 414 | ``` 415 | And in controller: 416 | ```php 417 | 1, 427 | // 'title' => 'lorem ipsum', 428 | // 'body' => 'dolor sit' 429 | // 'tags' => [ 430 | // ['id' => 1, 'name' => 'work', 'desc' => 'For work'], 431 | // ['id' => 2, 'name' => 'personal', 'desc' => 'For personal usage'] 432 | // ] 433 | // ] 434 | 435 | // Collection field type will automatically pull tags data from the model, 436 | // If we want to override the data, we can pass `data` option to the field 437 | 438 | $form = $formBuilder->create('App\Forms\PostForm', [ 439 | 'model' => $post 440 | ]); 441 | 442 | return view('posts.edit', compact('form')); 443 | } 444 | } 445 | ``` 446 | 447 | Then the view will contain: 448 | ```html 449 |
450 |
451 | 452 | 453 |
454 |
455 | 456 | 457 |
458 |
459 | 460 |
461 | 462 |
463 |
464 | 465 |
466 |
467 |
468 | ``` 469 | 470 | #### Collection of child forms 471 | 472 | [Child form](#child-form) also can be used as a collection. 473 | 474 | ```php 475 | 476 | add('name', 'text') 485 | ->add('desc', 'textarea'); 486 | } 487 | } 488 | 489 | class PostForm extends Form 490 | { 491 | public function buildForm() 492 | { 493 | $this 494 | ->add('title', 'text') 495 | ->add('body', 'textarea') 496 | ->add('tags', 'collection', [ 497 | 'type' => 'form', 498 | 'options' => [ // these are options for a single type 499 | 'class' => 'App\Forms\TagsForm' 500 | 'label' => false, 501 | ] 502 | ]); 503 | } 504 | } 505 | ``` 506 | And with same controller setup as above, we get this: 507 | ```html 508 |
509 |
510 | 511 | 512 |
513 |
514 | 515 | 516 |
517 |
518 | 519 |
520 |
521 | 522 | 523 |
524 |
525 | 526 | 527 |
528 |
529 |
530 |
531 | 532 | 533 |
534 |
535 | 536 | 537 |
538 |
539 |
540 | ``` 541 | 542 | #### Prototype 543 | 544 | If you need to dynamically generate HTML for additional elements in the collection, you can use `prototype()` method on the form field. Let's use example above: 545 | ```html 546 | @extends('app') 547 | 548 | @section('content') 549 | {{ form_start($form) }} 550 |
// Use {{{ }}} here to escape html 551 | {{ form_row($form->tags) }} 552 |
553 | {{ form_end($form) }} 554 | 555 | 556 | 567 | @endsection 568 | ``` 569 | 570 | `data-prototype` will contain: 571 | ```html 572 |
573 |
574 | 575 | 576 |
577 |
578 | 579 | 580 |
581 |
582 | ``` 583 | 584 | And clicking on the button `.add-to-collection` will automatically generate proper html from the prototype. 585 | 586 | Prototype can be configured in the form class: 587 | ```php 588 | use Kris\LaravelFormBuilder\Form; 589 | 590 | class PostForm extends Form 591 | { 592 | public function buildForm() 593 | { 594 | $this 595 | ->add('title', 'text') 596 | ->add('body', 'textarea') 597 | ->add('tags', 'collection', [ 598 | 'type' => 'text', 599 | 'property' => 'name', 600 | 'prototype' => true, // Should prototype be generated. Default: true 601 | 'prototype_name' => '__NAME__' // Value used for replacing when generating new elements from prototype, default: __NAME__ 602 | 'options' => [ 603 | 'label' => false, 604 | 'attr' => ['class' => 'tag'] 605 | ] 606 | ]); 607 | } 608 | } 609 | ``` 610 | 611 | ### Field Customization 612 | Fields can be easily customized within the class or view: 613 | 614 | ``` php 615 | add('name', 'text', [ 632 | 'wrapper' => 'name-input-container', 633 | 'help_block' => [ 634 | 'text' => 'I am help text', // If text is set, automatically adds help text under the field. Default: null 635 | 'tag' => 'p' // this is default, 636 | 'attr' => ['class' => 'help-block'] // Default, class pulled from config file 637 | ], 638 | 'attr' => ['class' => 'input-name', 'placeholder' => 'Enter name here...'], 639 | 'label' => 'Full name' 640 | ]) 641 | ->add('bio', 'textarea', [ 642 | 'wrapper' => false // This disables the wrapper for this field 643 | ]) 644 | // This creates a select field 645 | ->add('subscription', 'choice', [ 646 | 'choices' => ['monthly' => 'Monthly', 'yearly' => 'Yearly'], 647 | 'empty_value' => '==== Select subscription ===', 648 | 'multiple' => false // This is default. If set to true, it creates select with multiple select posibility 649 | ]) 650 | ->add('categories', 'entity', [ 651 | 'class' => 'App\Category', // Entity that holds data 652 | 'property' => 'name', // Value that will be used as a label for each choice option, default: name 653 | 'property_key' => 'id', // Value that will be used as a value for each choice option, default: id 654 | 'query_builder' => function(App\Category $category) { // If provided, gets data from this closure and lists it 655 | return $category->where('active', 1); 656 | } 657 | ]) 658 | // This creates radio buttons 659 | ->add('gender', 'choice', [ 660 | 'choices' => ['m' => 'Male', 'f' => 'Female'], 661 | 'label' => false, // This forces hiding label, even when calling form_row 662 | 'selected' => 'm', 663 | 'expanded' => true 664 | 'choice_options' => [ // Handles options when expanded is true and/or multiple is true 665 | 'wrapper' => ['class' => 'choice-wrapper'] // Shows the wrapper for each radio or checkbox, default is false 666 | ] 667 | 668 | ]) 669 | // Automatically adds enctype="multipart/form-data" to form 670 | ->add('image', 'file', [ 671 | 'label' => 'Upload your image' 672 | ]) 673 | // This creates a checkbox list 674 | ->add('languages', 'choice', [ 675 | 'choices' => [['id' => 1, 'en' => 'English'], ['id' => 2, 'de' => 'German'], ['id' => 3, 'fr' => 'France']], 676 | 'selected' => function ($data) { // Allows handling data before passed to view for setting default values. Useful for related models 677 | return array_pluck($data, 'id'); 678 | } 679 | 'expanded' => true, 680 | 'multiple' => true 681 | ]) 682 | // Renders all fieds from song form and wraps names for better handling 683 | // becomes 684 | ->add('song', 'form', [ 685 | 'class' => \FormBuilder::create('App\Forms\SongForm') 686 | ]) 687 | ->add('policy-agree', 'checkbox', [ 688 | 'default_value' => 1, // 689 | 'label' => 'I agree to policy', 690 | 'checked' => false // This is the default. 691 | ]) 692 | // Creates 2 inputs. These are the defaults 693 | ->add('password', 'repeated', [ 694 | 'type' => 'password' // can be anything that fits 695 | 'second_name' => 'password_confirmation', // defaults to name_confirmation 696 | 'first_options' => [], // Same options available as for text type 697 | 'second_options' => [], // Same options available as for text type 698 | ]) 699 | ->add('save', 'submit', [ 700 | 'attr' = ['class' => 'btn btn-primary'] 701 | ]) 702 | ->add('clear', 'reset', [ 703 | 'label' => 'Clear the form', 704 | 'attr' => ['class' => 'btn btn-danger'] 705 | ]); 706 | } 707 | } 708 | ``` 709 | 710 | You can also remove fields from the form when neccessary. For example you don't want to show `clear` button and `subscription` fields on the example above on edit page: 711 | 712 | ``` php 713 | 'PUT', 724 | 'url' => route('posts.update', $id), 725 | 'model' => $post 726 | ]) 727 | ->remove('clear') 728 | ->remove('subscription'); 729 | 730 | return view('posts.edit', compact('form')); 731 | } 732 | 733 | public function update($id) 734 | { 735 | } 736 | } 737 | ``` 738 | 739 | Or you can modify it in the similar way (options passed will be merged with options from old field, 740 | if you want to overwrite it pass 4th parameter as `true`) 741 | 742 | ``` php 743 | // ... 744 | public function edit($id) 745 | { 746 | $post = Post::findOrFail($id); 747 | $form = \FormBuilder::create(PostForm::class, [ 748 | 'method' => 'PUT', 749 | 'url' => route('posts.update', $id), 750 | 'model' => $post, 751 | ]) 752 | // If passed name does not exist, add() method will be called with provided params 753 | ->modify('gender', 'select', [ 754 | 'attr' => ['class' => 'form-select'] 755 | ], false) // If this is set to true, options will be overwritten - default: false 756 | 757 | return view('posts.edit', compact('form')); 758 | } 759 | ``` 760 | 761 | In a case when `choice` type has `expanded` set to `true` and/or `multiple` also set to true, you get a list of 762 | radios/checkboxes: 763 | 764 | ``` html 765 |
766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 |
777 | ``` 778 | 779 | If you maybe want to customize how each radio/checkbox is rendered, maybe wrap it in some container, you can loop over children on `languages` choice field: 780 | 781 | ``` php 782 | // ... 783 | 784 | languages->getChildren() as $child): ?> 785 |
786 | true]) ?> 787 |
788 | 789 | // ... 790 | ``` 791 | 792 | Here is a categorized list of all available field types: 793 | * Simple 794 | * text 795 | * textarea 796 | * select 797 | * choice 798 | * checkbox 799 | * radio 800 | * password 801 | * hidden 802 | * file 803 | * Date and Time 804 | * date 805 | * datetime-local 806 | * month 807 | * time 808 | * week 809 | * Special Purpose 810 | * color 811 | * search 812 | * image 813 | * email 814 | * url 815 | * tel 816 | * number 817 | * range 818 | * Buttons 819 | * submit 820 | * reset 821 | * button 822 | * Form Builder Extensions 823 | * repeated 824 | * [form](#child-form) 825 | * [collection](#collection) 826 | 827 | You can also bind the model to the class and add other options with setters 828 | 829 | ``` php 830 | setMethod('PUT') 842 | ->setUrl(route('post.update')) 843 | ->setModel($model); // This will automatically do Form::model($model) in the form 844 | ->setData('post_choices', [ 'y' => 'yes', 'n' => 'no']); // This can be used in form like $this->getData('post_choices') 845 | ->addData([ // Add multiple data values at once 846 | 'name' => 'some_name', 847 | 'some_other_data' => 'some other data' 848 | ]); 849 | 850 | 851 | // Code above is similar to this: 852 | 853 | $form = \FormBuilder::create('App\Forms\PostForm', [ 854 | 'method' => 'PUT', 855 | 'url' => route('post.update'), 856 | 'model' => $model, 857 | 'data' => [ 'post_choices' => [ 'y' => 'yes', 'n' => 'no'] ] 858 | ]); 859 | 860 | or this: 861 | 862 | $form = \FormBuilder::create('App\Forms\PostForm')->setFormOptions([ 863 | 'method' => 'PUT', 864 | 'url' => route('post.update'), 865 | 'model' => $model, 866 | 'data' => [ 'post_choices' => [ 'y' => 'yes', 'n' => 'no'] ] 867 | ]); 868 | 869 | // Any options passed like this except 'model' and 'data' will be passed to the view for form options 870 | // So if you need to pass any data to form class, and use it only there, use setData() method or 'data' key 871 | // and pass what you need 872 | 873 | return view('posts.edit', compact('form')); 874 | } 875 | 876 | public function update() 877 | { 878 | } 879 | } 880 | ``` 881 | 882 | And in form, you can use that model to populate some fields like this 883 | 884 | ``` php 885 | getRequest()->all(); 896 | 897 | $this 898 | ->add('title', 'text') 899 | ->add('body', 'textearea') 900 | ->add('some_choices', 'choices', [ 901 | 'choices' => $this->getData('post_choices') // When form is created passed as ->setData('post_choices', ['some' => 'array']) 902 | ]) 903 | ->add('category', 'select', [ 904 | 'choices' => $this->model->categories()->lists('id', 'name') 905 | ]); 906 | } 907 | } 908 | ``` 909 | 910 | ### Changing configuration and templates 911 | 912 | As mentioned above, bootstrap 3 form classes are used. If you want to change the defaults you need to publish the config like this: 913 | ``` sh 914 | php artisan config:publish kris/laravel4-form-builder 915 | ``` 916 | This will create folder `kris` in `app/config/packages` folder which will contain 917 | [config.php](https://github.com/kristijanhusak/laravel4-form-builder/blob/master/src/config/config.php) file. 918 | 919 | change values in `defaults` key as you wish. 920 | 921 | If you would like to avoid typing in full namespace of the form class when creating, you can add default namespace to the config that was just published, and it will prepend it every time you want to create form: 922 | 923 | ``` php 924 | 'App\Forms' 930 | ] 931 | 932 | // app/Http/Controllers/HomeController 933 | 934 | public function indexAction() 935 | { 936 | \FormBuilder::create('SongForm'); 937 | } 938 | ``` 939 | 940 | If you want to customize the views for fields and forms you can publish the views like this: 941 | ``` sh 942 | php artisan view:publish kris/laravel4-form-builder 943 | ``` 944 | 945 | This will create folder with all files in `app/views/packages/kris/laravel4-form-builder` 946 | 947 | Other way is to change path to the templates in the 948 | [config.php](https://github.com/kristijanhusak/laravel4-form-builder4/blob/master/src/config/config.php) file. 949 | 950 | ``` php 951 | return [ 952 | // ... 953 | 'checkbox' => 'posts.my-custom-checkbox' // resources/views/posts/my-custom-checkbox.blade.php 954 | ]; 955 | ``` 956 | 957 | 958 | One more way to change template is directly from Form class: 959 | 960 | ``` php 961 | add('title', 'text') 971 | ->add('body', 'textearea', [ 972 | 'template' => 'posts.textarea' // resources/views/posts/textarea.blade.php 973 | ]); 974 | } 975 | } 976 | ``` 977 | 978 | **When you are adding custom templates make sure they inherit functionality from defaults to prevent breaking.** 979 | 980 | ### Custom fields 981 | 982 | If you want to create your own custom field, you can do it like this: 983 | 984 | ``` php 985 | 1014 | 1015 | // ... 1016 | ``` 1017 | 1018 | **Notice:** Package templates uses plain PHP for printing because of plans for supporting version 4 (prevent conflict with tags), but you can use blade for custom fields, just make sure to use tags that are not escaping html (`{{ }}`) 1019 | 1020 | And then add it to published config file(`app/config/packages/kris/laravel4-form-builder/config.php`) in key `custom-fields` key this: 1021 | 1022 | ``` php 1023 | // ... 1024 | 'custom_fields' => [ 1025 | 'datetime' => 'Project\Forms\Fields\DatetimeType' 1026 | ] 1027 | // ... 1028 | ``` 1029 | 1030 | Or if you want to load it only for a single form, you can do it directly in BuildForm method: 1031 | 1032 | ``` php 1033 | addCustomField('datetime', 'Project\Forms\Fields\DatetimeType'); 1042 | 1043 | $this 1044 | ->add('title', 'text') 1045 | ->add('created_at', 'datetime') 1046 | } 1047 | } 1048 | ``` 1049 | 1050 | ### Contributing 1051 | Project follows [PSR-2](http://www.php-fig.org/psr/psr-2/) standard and it's covered with PHPUnit tests. 1052 | Pull requests should include tests and pass [Travis CI](https://travis-ci.org/kristijanhusak/laravel4-form-builder) build. 1053 | 1054 | To run tests first install dependencies with `composer install`. 1055 | 1056 | After that tests can be run with `vendor/bin/phpunit` 1057 | 1058 | ### Todo 1059 | * Add possibility to disable showing validation errors under fields - **DONE** 1060 | * Add event dispatcher ? 1061 | --------------------------------------------------------------------------------