├── 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 | = Form::open($formOptions) ?>
3 |
4 |
5 |
6 |
7 | getName(), $exclude) ) { ?>
8 | = $field->render() ?>
9 |
10 |
11 |
12 |
13 |
14 | = Form::close() ?>
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 | = Form::button($options['label'], $options['attr']) ?>
6 |
7 | <= $options['help_block']['tag'] ?> = $options['help_block']['helpBlockAttrs'] ?>>
8 | = $options['help_block']['text'] ?>
9 | = $options['help_block']['tag'] ?>>
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 | >= $options['label'] ?>
9 |
10 |
11 |
12 | <= $options['tag'] ?> = $options['elemAttrs'] ?>>= $options['value'] ?>= $options['tag'] ?>>
13 |
14 |
15 | <= $options['help_block']['tag'] ?> = $options['help_block']['helpBlockAttrs'] ?>>
16 | = $options['help_block']['text'] ?>
17 | = $options['help_block']['tag'] ?>>
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 | = $options['children']['first']->render([], true, true, false) ?>
9 | = $options['children']['second']->render([], true, true, false) ?>
10 |
11 |
12 | <= $options['help_block']['tag'] ?> = $options['help_block']['helpBlockAttrs'] ?>>
13 | = $options['help_block']['text'] ?>
14 | = $options['help_block']['tag'] ?>>
15 |
16 |
17 |
18 |
19 |
20 | = $options['children']['first']->render([], false, false, true) ?>
21 | = $options['children']['second']->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 | = Form::label($name, $options['label'], $options['label_attr']) ?>
9 |
10 |
11 |
12 | = Form::input($type, $name, $options['value'], $options['attr']) ?>
13 |
14 |
15 | <= $options['help_block']['tag'] ?> = $options['help_block']['helpBlockAttrs'] ?>>
16 | = $options['help_block']['text'] ?>
17 | = $options['help_block']['tag'] ?>>
18 |
19 |
20 |
21 |
22 |
23 | get($nameKey) as $err): ?>
24 |
>= $err ?>
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/views/textarea.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | >
4 |
5 |
6 |
7 |
8 | = Form::label($name, $options['label'], $options['label_attr']) ?>
9 |
10 |
11 |
12 | = Form::textarea($name, $options['value'], $options['attr']) ?>
13 |
14 |
15 | <= $options['help_block']['tag'] ?> = $options['help_block']['helpBlockAttrs'] ?>>
16 | = $options['help_block']['text'] ?>
17 | = $options['help_block']['tag'] ?>>
18 |
19 |
20 |
21 |
22 |
23 | get($nameKey) as $err): ?>
24 |
>= $err ?>
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/views/collection.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | >
4 |
5 |
6 |
7 |
8 | = Form::label($name, $options['label'], $options['label_attr']) ?>
9 |
10 |
11 |
12 |
13 | = $child->render() ?>
14 |
15 |
16 |
17 | <= $options['help_block']['tag'] ?> = $options['help_block']['helpBlockAttrs'] ?>>
18 | = $options['help_block']['text'] ?>
19 | = $options['help_block']['tag'] ?>>
20 |
21 |
22 |
23 |
24 |
25 | get($nameKey) as $err): ?>
26 |
>= $err ?>
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 | = Form::label($name, $options['label'], $options['label_attr']) ?>
9 |
10 |
11 |
12 |
13 | = $child->render(['selected' => $options['selected']], true, true, false) ?>
14 |
15 |
16 |
17 | <= $options['help_block']['tag'] ?> = $options['help_block']['helpBlockAttrs'] ?>>
18 | = $options['help_block']['text'] ?>
19 | = $options['help_block']['tag'] ?>>
20 |
21 |
22 |
23 |
24 |
25 | get($nameKey) as $err): ?>
26 |
>= $err ?>
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/views/select.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | >
4 |
5 |
6 |
7 |
8 | = Form::label($name, $options['label'], $options['label_attr']) ?>
9 |
10 |
11 |
12 | $options['empty_value']] : null; ?>
13 | = Form::select($name, (array)$emptyVal + $options['choices'], $options['selected'], $options['attr']) ?>
14 |
15 |
16 | <= $options['help_block']['tag'] ?> = $options['help_block']['helpBlockAttrs'] ?>>
17 | = $options['help_block']['text'] ?>
18 | = $options['help_block']['tag'] ?>>
19 |
20 |
21 |
22 |
23 |
24 | get($nameKey) as $err): ?>
25 |
>= $err ?>
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 | = Form::label($name, $options['label'], $options['label_attr']) ?>
9 |
10 |
11 |
12 |
13 | getRealName(), (array)$options['exclude']) ) { ?>
14 | = $child->render() ?>
15 |
16 |
17 |
18 |
19 | <= $options['help_block']['tag'] ?> = $options['help_block']['helpBlockAttrs'] ?>>
20 | = $options['help_block']['text'] ?>
21 | = $options['help_block']['tag'] ?>>
22 |
23 |
24 |
25 |
26 |
27 |
28 | get($nameKey) as $err): ?>
29 |
>= $err ?>
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/views/radio.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | >
4 |
5 |
6 |
7 |
8 | = Form::radio($name, $options['value'], $options['checked'], $options['attr']) ?>
9 |
10 |
11 | <= $options['help_block']['tag'] ?> = $options['help_block']['helpBlockAttrs'] ?>>
12 | = $options['help_block']['text'] ?>
13 | = $options['help_block']['tag'] ?>>
14 |
15 |
16 |
17 |
18 |
19 |
20 |
>= $options['label'] ?>
21 |
22 | = Form::label($name, $options['label'], $options['label_attr']) ?>
23 |
24 |
25 |
26 |
27 | get($nameKey) as $err): ?>
28 |
>= $err ?>
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/views/checkbox.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | >
4 |
5 |
6 |
7 |
8 | = Form::checkbox($name, $options['value'], $options['checked'], $options['attr']) ?>
9 |
10 |
11 | <= $options['help_block']['tag'] ?> = $options['help_block']['helpBlockAttrs'] ?>>
12 | = $options['help_block']['text'] ?>
13 | = $options['help_block']['tag'] ?>>
14 |
15 |
16 |
17 |
18 |
19 |
20 |
>= $options['label'] ?>
21 |
22 | = Form::label($name, $options['label'], $options['label_attr']) ?>
23 |
24 |
25 |
26 |
27 | get($nameKey) as $err): ?>
28 |
>= $err ?>
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 | [](https://travis-ci.org/kristijanhusak/laravel4-form-builder)
2 | [](https://scrutinizer-ci.com/g/kristijanhusak/laravel4-form-builder/?branch=master)
3 | [](https://packagist.org/packages/kris/laravel4-form-builder)
4 | [](https://packagist.org/packages/kris/laravel4-form-builder)
5 | [](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 |
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 and child of choice, we don't need label for it
495 | $isChildSelect = $this->type == 'select' && array_get($options, 'is_child') === true;
496 |
497 | if ($this->type == 'hidden' || $isChildSelect) {
498 | return false;
499 | }
500 |
501 | return true;
502 | }
503 |
504 | /**
505 | * Disable field
506 | *
507 | * @return $this
508 | */
509 | public function disable()
510 | {
511 | $this->setOption('attr.disabled', 'disabled');
512 |
513 | return $this;
514 | }
515 |
516 | /**
517 | * Enable field
518 | *
519 | * @return $this
520 | */
521 | public function enable()
522 | {
523 | array_forget($this->options, 'attr.disabled');
524 |
525 | return $this;
526 | }
527 | }
528 |
--------------------------------------------------------------------------------
/tests/FormTest.php:
--------------------------------------------------------------------------------
1 | plainForm
13 | ->add('name', 'text')
14 | ->add('description', 'textarea')
15 | ->add('address', 'static')
16 | ->add('remember', 'checkbox');
17 |
18 | $this->assertEquals(4, count($this->plainForm->getFields()));
19 |
20 | $this->assertTrue($this->plainForm->has('name'));
21 | $this->assertFalse($this->plainForm->has('body'));
22 |
23 | // Accessed with magic methods
24 | $this->assertEquals($this->plainForm->name, $this->plainForm->getField('name'));
25 |
26 | $this->assertInstanceOf(
27 | 'Kris\LaravelFormBuilder\Fields\InputType',
28 | $this->plainForm->getField('name')
29 | );
30 |
31 | $this->assertInstanceOf(
32 | 'Kris\LaravelFormBuilder\Fields\TextareaType',
33 | $this->plainForm->getField('description')
34 | );
35 |
36 | $this->assertInstanceOf(
37 | 'Kris\LaravelFormBuilder\Fields\CheckableType',
38 | $this->plainForm->getField('remember')
39 | );
40 |
41 | $this->assertInstanceOf(
42 | 'Kris\LaravelFormBuilder\Fields\StaticType',
43 | $this->plainForm->getField('address')
44 | );
45 | }
46 |
47 | /** @test */
48 | public function it_adds_after_some_field()
49 | {
50 | $this->plainForm
51 | ->add('name', 'text')
52 | ->add('description', 'textarea');
53 |
54 | $descIndexBefore = array_search(
55 | 'description',
56 | array_keys($this->plainForm->getFields())
57 | );
58 |
59 | $this->assertEquals(1, $descIndexBefore);
60 | $this->assertNull($this->plainForm->address);
61 |
62 | $this->plainForm->addAfter('name', 'address');
63 |
64 | $descIndexAfter = array_search(
65 | 'description',
66 | array_keys($this->plainForm->getFields())
67 | );
68 |
69 | $addressIndex = array_search(
70 | 'address',
71 | array_keys($this->plainForm->getFields())
72 | );
73 |
74 | $this->assertEquals(2, $descIndexAfter);
75 | $this->assertEquals(1, $addressIndex);
76 |
77 | $this->assertInstanceOf(
78 | 'Kris\LaravelFormBuilder\Fields\InputType',
79 | $this->plainForm->address
80 | );
81 | }
82 |
83 | /** @test */
84 | public function it_adds_before_some_field()
85 | {
86 | $this->plainForm
87 | ->add('name', 'text')
88 | ->add('description', 'textarea');
89 |
90 | $descIndexBefore = array_search(
91 | 'description',
92 | array_keys($this->plainForm->getFields())
93 | );
94 |
95 | $this->assertEquals(1, $descIndexBefore);
96 | $this->assertNull($this->plainForm->address);
97 |
98 | $this->plainForm->addBefore('name', 'address');
99 |
100 | $descIndexAfter = array_search(
101 | 'description',
102 | array_keys($this->plainForm->getFields())
103 | );
104 |
105 | $addressIndex = array_search(
106 | 'address',
107 | array_keys($this->plainForm->getFields())
108 | );
109 |
110 | $this->assertEquals(2, $descIndexAfter);
111 | $this->assertEquals(0, $addressIndex);
112 |
113 | $this->assertInstanceOf(
114 | 'Kris\LaravelFormBuilder\Fields\InputType',
115 | $this->plainForm->address
116 | );
117 | }
118 |
119 |
120 | /** @test */
121 | public function it_can_remove_existing_fields_from_form_object()
122 | {
123 | $this->plainForm
124 | ->add('name', 'text')
125 | ->add('description', 'textarea')
126 | ->add('remember', 'checkbox');
127 |
128 | $this->assertEquals(3, count($this->plainForm->getFields()));
129 |
130 | $this->assertTrue($this->plainForm->has('name'));
131 |
132 | $this->plainForm->remove('name');
133 |
134 | $this->assertEquals(2, count($this->plainForm->getFields()));
135 |
136 | $this->assertFalse($this->plainForm->has('name'));
137 | }
138 |
139 | /** @test */
140 | public function it_can_modify_existing_fields()
141 | {
142 | $this->plainForm
143 | ->add('name', 'text')
144 | ->add('description', 'textarea', [
145 | 'attr' => ['placeholder' => 'Enter text here...']
146 | ])
147 | ->add('category', 'select', [
148 | 'choices' => [ 1 => 'category-1', 2 => 'category-2']
149 | ]);
150 | // Adds new if provided name doesn't exist
151 | $this->plainForm->modify('remember', 'checkbox');
152 |
153 | // Modifies without complete ovewrite of options
154 |
155 | $this->assertEquals('textarea', $this->plainForm->description->getType());
156 | $this->assertEquals(
157 | ['placeholder' => 'Enter text here...', 'class' => 'form-control'],
158 | $this->plainForm->description->getOption('attr')
159 | );
160 |
161 | $this->plainForm->modify('description', 'text', [
162 | 'attr' => ['class' => 'modified-input']
163 | ]);
164 |
165 | $this->assertEquals('text', $this->plainForm->description->getType());
166 | $this->assertEquals(
167 | ['placeholder' => 'Enter text here...', 'class' => 'modified-input'],
168 | $this->plainForm->description->getOption('attr')
169 | );
170 |
171 | // Check if complete option ovewrite work
172 | $this->assertEquals(
173 | [ 1 => 'category-1', 2 => 'category-2'],
174 | $this->plainForm->category->getOption('choices')
175 | );
176 |
177 | $this->assertArrayNotHasKey('expanded', $this->plainForm->category->getOptions());
178 |
179 | $this->plainForm->modify('category', 'choice', [
180 | 'expanded' => true
181 | ], true);
182 |
183 | $this->assertNotEquals(
184 | [ 1 => 'category-1', 2 => 'category-2'],
185 | $this->plainForm->category->getOption('choices')
186 | );
187 |
188 | $this->assertTrue($this->plainForm->category->getOption('expanded'));
189 |
190 | }
191 |
192 | /**
193 | * @test
194 | * @expectedException \InvalidArgumentException
195 | */
196 | public function it_throws_exception_when_removing_nonexisting_field()
197 | {
198 | $this->plainForm->add('name', 'text');
199 |
200 | $this->plainForm->remove('nonexisting');
201 | }
202 |
203 | /**
204 | * @test
205 | * @expectedException \InvalidArgumentException
206 | */
207 | public function it_prevents_adding_fields_with_same_name()
208 | {
209 | $this->plainForm->add('name', 'text')->add('name', 'textarea');
210 | }
211 |
212 | /** @test */
213 | public function it_throws_InvalidArgumentException_on_non_existing_property()
214 | {
215 | $exceptionThrown = false;
216 |
217 | $this->plainForm
218 | ->add('name', 'text')
219 | ->add('description', 'textarea');
220 |
221 | try {
222 | $this->plainForm->nonexisting;
223 | } catch (\InvalidArgumentException $e) {
224 | $exceptionThrown = true;
225 | }
226 |
227 | try {
228 | $this->plainForm->getField('nonexisting');
229 | } catch (\InvalidArgumentException $e) {
230 | $exceptionThrown = true;
231 | }
232 |
233 | if ($exceptionThrown) {
234 | return;
235 | }
236 |
237 | $this->fail('Exception was not thrown for non existing field.');
238 | }
239 |
240 | /** @test */
241 | public function it_can_set_form_options_with_array_of_options()
242 | {
243 |
244 | $options = [
245 | 'method' => 'POST',
246 | 'url' => '/url/1',
247 | 'class' => 'form-container',
248 | 'model' => $this->model
249 | ];
250 |
251 | $this->plainForm->setFormOptions($options);
252 |
253 | // After the setup model is removed from options
254 | unset($options['model']);
255 |
256 | $this->assertEquals($options, $this->plainForm->getFormOptions());
257 |
258 | $this->assertEquals('POST', $this->plainForm->getMethod());
259 | $this->assertEquals('/url/1', $this->plainForm->getUrl());
260 | $this->assertInstanceOf(
261 | 'Illuminate\Database\Eloquent\Model',
262 | $this->plainForm->getModel()
263 | );
264 | }
265 |
266 | /** @test */
267 | public function it_can_set_form_options_with_setters()
268 | {
269 | $this->plainForm->setMethod('DELETE');
270 | $this->plainForm->setUrl('/posts/all');
271 | $this->plainForm->setModel($this->model);
272 | $this->plainForm->setData('some_data', ['this', 'is', 'some', 'data']);
273 | $this->plainForm->setName('test_name');
274 |
275 | $this->assertEquals(
276 | ['method' => 'DELETE', 'url' => '/posts/all'],
277 | $this->plainForm->getFormOptions()
278 | );
279 |
280 | $this->assertEquals(
281 | $this->model,
282 | $this->plainForm->getModel()
283 | );
284 |
285 | $this->assertEquals(
286 | ['this', 'is', 'some', 'data'],
287 | $this->plainForm->getData('some_data')
288 | );
289 |
290 | $this->assertEquals(
291 | $this->plainForm->getData(),
292 | ['some_data' => ['this', 'is', 'some', 'data']]
293 | );
294 |
295 | $this->assertEquals('test_name', $this->plainForm->getName());
296 | }
297 |
298 | /** @test */
299 | public function it_sets_file_option_to_true_if_file_type_added()
300 | {
301 | $this->plainForm->add('upload_file', 'file');
302 |
303 | $this->assertTrue($this->plainForm->getFormOption('files'));
304 | }
305 |
306 | /** @test */
307 | public function it_renders_the_form()
308 | {
309 | $options = [
310 | 'method' => 'POST',
311 | 'url' => '/someurl',
312 | 'class' => 'has-error'
313 | ];
314 |
315 |
316 | $this->prepareRender($options);
317 |
318 | $this->plainForm->renderForm($options, true, true, true);
319 | }
320 |
321 | /** @test */
322 | public function it_renders_rest_of_the_form()
323 | {
324 | $options = [
325 | 'method' => 'GET',
326 | 'url' => '/some/url/10'
327 | ];
328 |
329 | $this->prepareFieldRender('select');
330 | $this->prepareFieldRender('text');
331 |
332 | $fields = [
333 | new InputType('name', 'text', $this->plainForm),
334 | new InputType('email', 'email', $this->plainForm),
335 | ];
336 |
337 | $this->prepareRender($options, false, true, true, $fields);
338 |
339 | $this->plainForm->setFormOptions($options);
340 |
341 | $this->plainForm
342 | ->add('gender', 'select')
343 | ->add('name', 'text')
344 | ->add('email', 'email');
345 |
346 | $this->plainForm->gender->render();
347 |
348 | $this->plainForm->renderRest();
349 | }
350 |
351 | /** @test */
352 | public function it_renders_rest_of_the_form_until_specified_field()
353 | {
354 | $options = [
355 | 'method' => 'GET',
356 | 'url' => '/some/url/10'
357 | ];
358 |
359 | $this->prepareFieldRender('select');
360 | $this->prepareFieldRender('text');
361 |
362 | $fields = [
363 | new InputType('name', 'text', $this->plainForm),
364 | new InputType('email', 'email', $this->plainForm),
365 | ];
366 |
367 | $this->prepareRender($options, false, true, true, $fields);
368 |
369 | $this->plainForm->setFormOptions($options);
370 |
371 | $this->plainForm
372 | ->add('gender', 'select')
373 | ->add('name', 'text')
374 | ->add('email', 'email')
375 | ->add('address', 'text');
376 |
377 | $this->plainForm->gender->render();
378 |
379 | $this->plainForm->renderUntil('email');
380 | $this->assertEquals($this->plainForm->address->isRendered(), false);
381 | }
382 |
383 | /** @test */
384 | public function it_can_add_child_form_as_field()
385 | {
386 | $form = $this->setupForm(new Form());
387 | $customForm = $this->setupForm(new CustomDummyForm());
388 | $customForm->add('img', 'file');
389 | $this->request->shouldReceive('old');
390 | $model = ['song' => ['body' => 'test body'], 'title' => 'main title'];
391 | $form->setModel($model);
392 |
393 | $form
394 | ->add('title', 'text')
395 | ->add('song', 'form', [
396 | 'class' => $customForm
397 | ])
398 | ->add('songs', 'collection', [
399 | 'type' => 'form',
400 | 'data' => [
401 | ['title' => 'lorem', 'body' => 'ipsum'],
402 | new \Illuminate\Support\Collection(['title' => 'dolor', 'body' => 'sit'])
403 | ],
404 | 'options' => [
405 | 'class' => $customForm
406 | ]
407 | ])
408 | ;
409 |
410 | $this->prepareFieldRender('title');
411 | $this->prepareFieldRender('child_form');
412 | $this->prepareRender(Mockery::any(), true, true, true, Mockery::any(), $model);
413 |
414 | $this->assertEquals($form, $form->title->getParent());
415 |
416 | $form->renderForm();
417 |
418 | $this->assertEquals('songs[1]', $customForm->getName());
419 |
420 | $this->assertEquals('song[title]', $form->song->getChild('title')->getName());
421 | $this->assertCount(2, $form->songs->getChildren());
422 | $this->assertEquals('lorem', $form->songs->getChild(0)->title->getOption('value'));
423 | $this->assertEquals('test body', $form->song->body->getOption('value'));
424 | $this->assertEquals('main title', $form->title->getOption('value'));
425 | $this->assertInstanceOf(
426 | 'Kris\LaravelFormBuilder\Form',
427 | $form->song->getForm()
428 | );
429 |
430 | $this->assertTrue($form->song->getFormOption('files'));
431 |
432 | try {
433 | $form->song->badMethod();
434 | } catch (\BadMethodCallException $e) {
435 | return;
436 | }
437 | $this->fail('No exception on bad method call on child form.');
438 | }
439 |
440 | /** @test */
441 | public function it_creates_named_form()
442 | {
443 | $model = new \Illuminate\Support\Collection([
444 | 'name' => 'John Doe',
445 | 'gender' => 'f'
446 | ]);
447 |
448 | $expectModel = [
449 | 'test_name' => $model->all()
450 | ];
451 | $this->plainForm
452 | ->add('name', 'text')
453 | ->add('address', 'static');
454 | $this->assertEquals('name', $this->plainForm->getField('name')->getName());
455 | $this->assertEquals('address', $this->plainForm->getField('address')->getName());
456 | $this->plainForm->setName('test_name')->setModel($model);
457 | $this->prepareFieldRender('text');
458 | $this->prepareFieldRender('static');
459 | $this->prepareRender(Mockery::any(), true, true, true, Mockery::any(), $expectModel);
460 | $this->plainForm->renderForm();
461 |
462 | $this->assertEquals('test_name[name]', $this->plainForm->getField('name')->getName());
463 | $this->assertEquals('test_name[address]', $this->plainForm->getField('address')->getName());
464 | $this->assertEquals($expectModel, $this->plainForm->getModel());
465 | }
466 |
467 | /** @test */
468 | public function it_adds_custom_type()
469 | {
470 | $this->plainForm->addCustomField('datetime', 'Some\\Namespace\\DatetimeType');
471 |
472 | $fieldType = $this->formHelper->getFieldType('datetime');
473 |
474 | $this->assertEquals('Some\\Namespace\\DatetimeType', $fieldType);
475 | }
476 |
477 | /**
478 | * @test
479 | * @expectedException \InvalidArgumentException
480 | */
481 | public function it_throws_exception_when_adding_field_with_invalid_name()
482 | {
483 | $this->plainForm->add('', 'text');
484 | }
485 |
486 | /**
487 | * @test
488 | * @expectedException \InvalidArgumentException
489 | */
490 | public function it_throws_exception_when_adding_field_with_invalid_type()
491 | {
492 | $this->plainForm->add('name', '');
493 | }
494 |
495 | /**
496 | * @test
497 | * @expectedException \InvalidArgumentException
498 | */
499 | public function it_prevents_adding_duplicate_custom_type()
500 | {
501 | $this->plainForm->addCustomField('datetime', 'Some\\Namespace\\DatetimeType');
502 |
503 | $this->plainForm->addCustomField('datetime', 'Some\\Namespace\\DateType');
504 | }
505 |
506 |
507 | /** @test */
508 | public function it_can_compose_another_forms_fields_into_itself()
509 | {
510 | $form = $this->setupForm(new Form());
511 | $customForm = $this->setupForm(new CustomDummyForm());
512 |
513 |
514 | $form
515 | ->add('name', 'text')
516 | ->compose($customForm)
517 | ;
518 |
519 | $this->assertEquals($form, $form->name->getParent());
520 |
521 | $this->assertEquals(3, count($form->getFields()));
522 | $this->assertEquals(true, $form->has('title'));
523 | $this->assertEquals('title', $form->title->getName());
524 | $this->assertEquals('title', $form->title->getRealName());
525 |
526 | }
527 |
528 | /** @test */
529 | public function it_disables_all_fields_in_form()
530 | {
531 | $form = $this->setupForm(new Form());
532 |
533 | $form->add('name', 'text')
534 | ->add('email', 'email');
535 |
536 | $this->assertNull($form->name->getOption('attr.disabled'));
537 |
538 | $form->disableFields();
539 |
540 | $this->assertEquals('disabled', $form->name->getOption('attr.disabled'));
541 | }
542 |
543 | /** @test */
544 | public function it_enables_all_fields_in_form()
545 | {
546 | $form = $this->setupForm(new Form());
547 |
548 | $form
549 | ->add('name', 'text', [
550 | 'attr' => ['disabled' => 'disabled']
551 | ])
552 | ->add('email', 'email');
553 |
554 | $this->assertEquals('disabled', $form->name->getOption('attr.disabled'));
555 |
556 | $form->enableFields();
557 |
558 | $this->assertNull($form->name->getOption('attr.disabled'));
559 | }
560 |
561 | private function prepareRender(
562 | $formOptions = [],
563 | $showStart = true,
564 | $showFields = true,
565 | $showEnd = true,
566 | $fields = [],
567 | $model = null,
568 | $exclude = []
569 | ) {
570 | $viewRenderer = Mockery::mock('Illuminate\Contracts\View\View');
571 |
572 | $this->view->shouldReceive('make')->with('laravel4-form-builder::form')
573 | ->andReturn($viewRenderer);
574 |
575 |
576 | $viewRenderer->shouldReceive('with')->with(
577 | compact('showStart', 'showFields', 'showEnd')
578 | )->andReturnSelf();
579 |
580 | $viewRenderer->shouldReceive('with')->with(
581 | 'formOptions',
582 | $formOptions
583 | )->andReturnSelf();
584 |
585 | $viewRenderer->shouldReceive('with')->with(
586 | 'showFieldErrors',
587 | true
588 | )->andReturnSelf();
589 |
590 | $viewRenderer->shouldReceive('with')->with('fields', $fields)
591 | ->andReturnSelf();
592 |
593 | $viewRenderer->shouldReceive('with')->with('model', $model)
594 | ->andReturnSelf();
595 |
596 | $viewRenderer->shouldReceive('with')->with('exclude', $exclude)
597 | ->andReturnSelf();
598 |
599 | $viewRenderer->shouldReceive('render');
600 | }
601 |
602 | private function prepareFieldRender($view)
603 | {
604 | $viewRenderer = Mockery::mock('Illuminate\Contracts\View\View');
605 | $viewRenderer->shouldReceive('with')->andReturnSelf();
606 | $viewRenderer->shouldReceive('render');
607 |
608 | $this->view->shouldReceive('make')
609 | ->with('laravel4-form-builder::' . $view, Mockery::any())
610 | ->andReturn($viewRenderer);
611 | }
612 | }
613 |
--------------------------------------------------------------------------------
/src/Kris/LaravelFormBuilder/Form.php:
--------------------------------------------------------------------------------
1 | 'GET',
34 | 'url' => null
35 | ];
36 |
37 | /**
38 | * Additional data which can be used to build fields
39 | *
40 | * @var array
41 | */
42 | protected $data = [];
43 |
44 | /**
45 | * Should errors for each field be shown when called form($form) or form_rest($form) ?
46 | *
47 | * @var bool
48 | */
49 | protected $showFieldErrors = true;
50 |
51 | /**
52 | * Name of the parent form if any
53 | *
54 | * @var string|null
55 | */
56 | protected $name = null;
57 |
58 | /**
59 | * @var FormBuilder
60 | */
61 | protected $formBuilder;
62 |
63 | /**
64 | * List of fields to not render
65 | *
66 | * @var array
67 | **/
68 | protected $exclude = [];
69 |
70 | /**
71 | * Are form being rebuilt?
72 | *
73 | * @var bool
74 | */
75 | protected $rebuilding = false;
76 |
77 | /**
78 | * Build the form
79 | *
80 | * @return mixed
81 | */
82 | public function buildForm()
83 | {
84 | }
85 |
86 | /**
87 | * Rebuild the form from scratch
88 | *
89 | * @return $this
90 | */
91 | public function rebuildForm()
92 | {
93 | $this->rebuilding = true;
94 | // If form is plain, buildForm method is empty, so we need to take
95 | // existing fields and add them again
96 | if (get_class($this) === 'Kris\LaravelFormBuilder\Form') {
97 | foreach ($this->fields as $name => $field) {
98 | $this->add($name, $field->getType(), $field->getOptions());
99 | }
100 | } else {
101 | $this->buildForm();
102 | }
103 | $this->rebuilding = false;
104 |
105 | return $this;
106 | }
107 |
108 | /**
109 | * Create the FormField object
110 | *
111 | * @param string $name
112 | * @param string $type
113 | * @param array $options
114 | * @return FormField
115 | */
116 | protected function makeField($name, $type = 'text', array $options = [])
117 | {
118 | $this->setupFieldOptions($name, $options);
119 |
120 | $fieldName = $this->getFieldName($name);
121 |
122 | $fieldType = $this->getFieldType($type);
123 |
124 | return new $fieldType($fieldName, $type, $this, $options);
125 | }
126 |
127 | /**
128 | * Create a new field and add it to the form
129 | *
130 | * @param string $name
131 | * @param string $type
132 | * @param array $options
133 | * @param bool $modify
134 | * @return $this
135 | */
136 | public function add($name, $type = 'text', array $options = [], $modify = false)
137 | {
138 | if (!$name || trim($name) == '') {
139 | throw new \InvalidArgumentException(
140 | 'Please provide valid field name for class ['. get_class($this) .']'
141 | );
142 | }
143 |
144 | if ($this->rebuilding && !$this->has($name)) {
145 | return $this;
146 | }
147 |
148 | $this->addField($this->makeField($name, $type, $options), $modify);
149 |
150 | return $this;
151 | }
152 |
153 | /**
154 | * Add a FormField to the form's fields
155 | *
156 | * @param FormField $field
157 | * @return $this
158 | */
159 | protected function addField(FormField $field, $modify = false)
160 | {
161 | if (!$modify && !$this->rebuilding) {
162 | $this->preventDuplicate($field->getRealName());
163 | }
164 |
165 | $this->fields[$field->getRealName()] = $field;
166 |
167 | return $this;
168 | }
169 |
170 | /**
171 | * Add field before another field
172 | *
173 | * @param string $name Name of the field before which new field is added
174 | * @param string $fieldName Field name which will be added
175 | * @param string $type
176 | * @param array $options
177 | * @param boolean $modify
178 | * @return $this
179 | */
180 | public function addBefore($name, $fieldName, $type = 'text', $options = [], $modify = false)
181 | {
182 | $offset = array_search($name, array_keys($this->fields));
183 |
184 | $beforeFields = array_slice($this->fields, 0, $offset);
185 | $afterFields = array_slice($this->fields, $offset);
186 |
187 | $this->fields = $beforeFields;
188 |
189 | $this->add($fieldName, $type, $options, $modify);
190 |
191 | $this->fields += $afterFields;
192 |
193 | return $this;
194 | }
195 |
196 | /**
197 | * Add field before another field
198 | * @param string $name Name of the field after which new field is added
199 | * @param string $fieldName Field name which will be added
200 | * @param string $type
201 | * @param array $options
202 | * @param boolean $modify
203 | * @return $this
204 | */
205 | public function addAfter($name, $fieldName, $type = 'text', $options = [], $modify = false)
206 | {
207 | $offset = array_search($name, array_keys($this->fields));
208 |
209 | $beforeFields = array_slice($this->fields, 0, $offset + 1);
210 | $afterFields = array_slice($this->fields, $offset + 1);
211 |
212 | $this->fields = $beforeFields;
213 |
214 | $this->add($fieldName, $type, $options, $modify);
215 |
216 | $this->fields += $afterFields;
217 |
218 | return $this;
219 | }
220 |
221 | /**
222 | * Take another form and add it's fields directly to this form
223 | * @param mixed $class Form to merge
224 | * @param array $options
225 | * @param boolean $modify
226 | * @return $this
227 | */
228 | public function compose($class, array $options = [], $modify = false)
229 | {
230 | $options['class'] = $class;
231 |
232 | // If we pass a ready made form just extract the fields
233 | if ($class instanceof Form) {
234 | $fields = $class->getFields();
235 | } elseif ($class instanceof Fields\ChildFormType) {
236 | $fields = $class->getForm()->getFields();
237 | } elseif (is_string($class)) {
238 | // If its a string of a class make it the usual way
239 | $options['model'] = $this->model;
240 | $options['name'] = $this->name;
241 |
242 | $form = $this->formBuilder->create($class, $options);
243 | $fields = $form->getFields();
244 | } else {
245 | throw new \InvalidArgumentException(
246 | "[{$class}] is invalid. Please provide either a full class name, Form or ChildFormType"
247 | );
248 | }
249 |
250 | foreach ($fields as $field) {
251 | $this->addField($field, $modify);
252 | }
253 |
254 | return $this;
255 | }
256 |
257 | /**
258 | * Remove field with specified name from the form
259 | *
260 | * @param $name
261 | * @return $this
262 | */
263 | public function remove($name)
264 | {
265 | if ($this->has($name)) {
266 | unset($this->fields[$name]);
267 | return $this;
268 | }
269 |
270 | throw new \InvalidArgumentException('Field ['.$name.'] does not exist in '.get_class($this));
271 | }
272 |
273 | /**
274 | * Modify existing field. If it doesn't exist, it is added to form
275 | *
276 | * @param $name
277 | * @param string $type
278 | * @param array $options
279 | * @param bool $overwriteOptions
280 | * @return Form
281 | */
282 | public function modify($name, $type = 'text', array $options = [], $overwriteOptions = false)
283 | {
284 | // If we don't want to overwrite options, we merge them with old options
285 | if ($overwriteOptions === false && $this->has($name)) {
286 | $options = $this->formHelper->mergeOptions(
287 | $this->getField($name)->getOptions(),
288 | $options
289 | );
290 | }
291 |
292 | return $this->add($name, $type, $options, true);
293 | }
294 |
295 | /**
296 | * Render full form
297 | *
298 | * @param array $options
299 | * @param bool $showStart
300 | * @param bool $showFields
301 | * @param bool $showEnd
302 | * @return string
303 | */
304 | public function renderForm(array $options = [], $showStart = true, $showFields = true, $showEnd = true)
305 | {
306 | return $this->render($options, $this->fields, $showStart, $showFields, $showEnd);
307 | }
308 |
309 | /**
310 | * Render rest of the form
311 | *
312 | * @param bool $showFormEnd
313 | * @param bool $showFields
314 | * @return string
315 | */
316 | public function renderRest($showFormEnd = true, $showFields = true)
317 | {
318 | $fields = $this->getUnrenderedFields();
319 |
320 | return $this->render([], $fields, false, $showFields, $showFormEnd);
321 | }
322 |
323 | /**
324 | * Renders the rest of the form up until the specified field name
325 | *
326 | * @param string $field_name
327 | * @param bool $showFormEnd
328 | * @param bool $showFields
329 | * @return string
330 | */
331 | public function renderUntil($field_name, $showFormEnd = true, $showFields = true)
332 | {
333 | $fields = $this->getUnrenderedFields();
334 |
335 | $i = 1;
336 | foreach ($fields as $key => $value) {
337 | if ($value->getRealName() == $field_name) {
338 | break;
339 | }
340 | $i++;
341 | }
342 |
343 | $fields = array_slice($fields, 0, $i, true);
344 |
345 | return $this->render([], $fields, false, $showFields, $showFormEnd);
346 | }
347 |
348 | /**
349 | * Get single field instance from form object
350 | *
351 | * @param $name
352 | * @return FormField
353 | */
354 | public function getField($name)
355 | {
356 | if ($this->has($name)) {
357 | return $this->fields[$name];
358 | }
359 |
360 | throw new \InvalidArgumentException(
361 | 'Field with name ['. $name .'] does not exist in class '.get_class($this)
362 | );
363 | }
364 |
365 | /**
366 | * Check if form has field
367 | *
368 | * @param $name
369 | * @return bool
370 | */
371 | public function has($name)
372 | {
373 | return array_key_exists($name, $this->fields);
374 | }
375 |
376 | /**
377 | * Get all form options
378 | *
379 | * @return array
380 | */
381 | public function getFormOptions()
382 | {
383 | return $this->formOptions;
384 | }
385 |
386 | /**
387 | * Get single form option
388 | *
389 | * @param string $option
390 | * @param $default
391 | * @return mixed
392 | */
393 | public function getFormOption($option, $default = null)
394 | {
395 | return array_get($this->formOptions, $option, $default);
396 | }
397 |
398 | /**
399 | * Set single form option on form
400 | *
401 | * @param string $option
402 | * @param mixed $value
403 | *
404 | * @return $this
405 | */
406 | public function setFormOption($option, $value)
407 | {
408 | $this->formOptions[$option] = $value;
409 |
410 | return $this;
411 | }
412 |
413 | /**
414 | * Set form options
415 | *
416 | * @param array $formOptions
417 | * @return $this
418 | */
419 | public function setFormOptions($formOptions)
420 | {
421 | $this->formOptions = $this->formHelper->mergeOptions($this->formOptions, $formOptions);
422 |
423 | $this->getModelFromOptions();
424 |
425 | $this->getDataFromOptions();
426 |
427 | $this->checkIfNamedForm();
428 |
429 | return $this;
430 | }
431 |
432 | /**
433 | * Get form http method
434 | *
435 | * @return string
436 | */
437 | public function getMethod()
438 | {
439 | return $this->formOptions['method'];
440 | }
441 |
442 | /**
443 | * Set form http method
444 | *
445 | * @param string $method
446 | * @return $this
447 | */
448 | public function setMethod($method)
449 | {
450 | $this->formOptions['method'] = $method;
451 |
452 | return $this;
453 | }
454 |
455 | /**
456 | * Get form action url
457 | *
458 | * @return string
459 | */
460 | public function getUrl()
461 | {
462 | return $this->formOptions['url'];
463 | }
464 |
465 | /**
466 | * Set form action url
467 | *
468 | * @param string $url
469 | * @return $this
470 | */
471 | public function setUrl($url)
472 | {
473 | $this->formOptions['url'] = $url;
474 |
475 | return $this;
476 | }
477 |
478 | /**
479 | * @return string|null
480 | */
481 | public function getName()
482 | {
483 | return $this->name;
484 | }
485 |
486 | /**
487 | * @param string $name
488 | *
489 | * @return $this
490 | */
491 | public function setName($name)
492 | {
493 | $this->name = $name;
494 |
495 | $this->rebuildForm();
496 |
497 | return $this;
498 | }
499 |
500 | /**
501 | * Get model that is bind to form object
502 | *
503 | * @return mixed
504 | */
505 | public function getModel()
506 | {
507 | return $this->model;
508 | }
509 |
510 | /**
511 | * Set model to form object
512 | *
513 | * @param mixed $model
514 | * @return $this
515 | */
516 | public function setModel($model)
517 | {
518 | $this->model = $model;
519 |
520 | $this->setupNamedModel();
521 |
522 | return $this;
523 | }
524 |
525 | /**
526 | * Get all fields
527 | *
528 | * @return FormField[]
529 | */
530 | public function getFields()
531 | {
532 | return $this->fields;
533 | }
534 |
535 | /**
536 | * Get field dynamically
537 | *
538 | * @param $name
539 | * @return FormField
540 | */
541 | public function __get($name)
542 | {
543 | if ($this->has($name)) {
544 | return $this->getField($name);
545 | }
546 | }
547 |
548 | /**
549 | * Set the form helper only on first instantiation
550 | *
551 | * @param FormHelper $formHelper
552 | * @return $this
553 | */
554 | public function setFormHelper(FormHelper $formHelper)
555 | {
556 | $this->formHelper = $formHelper;
557 |
558 | return $this;
559 | }
560 |
561 | /**
562 | * Get form helper
563 | *
564 | * @return FormHelper
565 | */
566 | public function getFormHelper()
567 | {
568 | return $this->formHelper;
569 | }
570 |
571 | /**
572 | * Add custom field
573 | *
574 | * @param $name
575 | * @param $class
576 | */
577 | public function addCustomField($name, $class)
578 | {
579 | $this->formHelper->addCustomField($name, $class);
580 | }
581 |
582 | /**
583 | * Should form errors be shown under every field ?
584 | *
585 | * @return bool
586 | */
587 | public function haveErrorsEnabled()
588 | {
589 | return $this->showFieldErrors;
590 | }
591 |
592 | /**
593 | * Add any aditional data that field needs (ex. array of choices)
594 | *
595 | * @param string $name
596 | * @param mixed $data
597 | */
598 | public function setData($name, $data)
599 | {
600 | $this->data[$name] = $data;
601 | }
602 |
603 | /**
604 | * Get single additional data
605 | *
606 | * @param string $name
607 | * @param null $default
608 | * @return mixed
609 | */
610 | public function getData($name = null, $default = null)
611 | {
612 | if (is_null($name)) {
613 | return $this->data;
614 | }
615 |
616 | return array_get($this->data, $name, $default);
617 | }
618 |
619 | /**
620 | * Add multiple peices of data at once
621 | *
622 | * @param $data
623 | * @return $this
624 | **/
625 | public function addData(array $data)
626 | {
627 | foreach ($data as $key => $value) {
628 | $this->setData($key, $value);
629 | }
630 |
631 | return $this;
632 | }
633 |
634 | /**
635 | * Get current request
636 | *
637 | * @return \Illuminate\Http\Request
638 | */
639 | public function getRequest()
640 | {
641 | return $this->formHelper->getRequest();
642 | }
643 |
644 | /**
645 | * Render the form
646 | *
647 | * @param $options
648 | * @param $fields
649 | * @param boolean $showStart
650 | * @param boolean $showFields
651 | * @param boolean $showEnd
652 | * @return string
653 | */
654 | protected function render($options, $fields, $showStart, $showFields, $showEnd)
655 | {
656 | $formOptions = $this->formHelper->mergeOptions($this->formOptions, $options);
657 |
658 | $this->setupNamedModel();
659 |
660 | return $this->formHelper->getView()
661 | ->make($this->formHelper->getConfig('form'))
662 | ->with(compact('showStart', 'showFields', 'showEnd'))
663 | ->with('formOptions', $formOptions)
664 | ->with('fields', $fields)
665 | ->with('model', $this->getModel())
666 | ->with('exclude', $this->exclude)
667 | ->render();
668 | }
669 |
670 | /**
671 | * Get the model from the options
672 | */
673 | private function getModelFromOptions()
674 | {
675 | if (array_get($this->formOptions, 'model')) {
676 | $this->setModel(array_pull($this->formOptions, 'model'));
677 | }
678 | }
679 |
680 | /**
681 | * Get all fields that are not rendered
682 | *
683 | * @return array
684 | */
685 | protected function getUnrenderedFields()
686 | {
687 | $unrenderedFields = [];
688 |
689 | foreach ($this->fields as $field) {
690 | if (!$field->isRendered()) {
691 | $unrenderedFields[] = $field;
692 | continue;
693 | }
694 | }
695 |
696 | return $unrenderedFields;
697 | }
698 |
699 | /**
700 | * Prevent adding fields with same name
701 | *
702 | * @param string $name
703 | */
704 | protected function preventDuplicate($name)
705 | {
706 | if ($this->has($name)) {
707 | throw new \InvalidArgumentException('Field ['.$name.'] already exists in the form '.get_class($this));
708 | }
709 | }
710 |
711 | /**
712 | * @param string $type
713 | * @return string
714 | */
715 | protected function getFieldType($type)
716 | {
717 | $fieldType = $this->formHelper->getFieldType($type);
718 |
719 | if ($type == 'file') {
720 | $this->formOptions['files'] = true;
721 | }
722 |
723 | return $fieldType;
724 | }
725 |
726 | /**
727 | * Check if form is named form
728 | */
729 | protected function checkIfNamedForm()
730 | {
731 | if ($this->getFormOption('name')) {
732 | $this->name = array_pull($this->formOptions, 'name', $this->name);
733 | }
734 | }
735 |
736 | /**
737 | * Set up options on single field depending on form options
738 | *
739 | * @param string $name
740 | * @param $options
741 | */
742 | protected function setupFieldOptions($name, &$options)
743 | {
744 | if (!$this->getName()) {
745 | return;
746 | }
747 |
748 | $options['real_name'] = $name;
749 |
750 | if (!isset($options['label'])) {
751 | $options['label'] = $this->formHelper->formatLabel($name);
752 | }
753 | }
754 |
755 | /**
756 | * Get any data from options and remove it
757 | */
758 | protected function getDataFromOptions()
759 | {
760 | if (array_get($this->formOptions, 'data')) {
761 | $this->addData(array_pull($this->formOptions, 'data'));
762 | }
763 | }
764 |
765 | /**
766 | * Set namespace to model if form is named so the data is bound properly
767 | */
768 | protected function setupNamedModel()
769 | {
770 | if (!$this->getModel() || !$this->getName()) {
771 | return;
772 | }
773 |
774 | $model = $this->formHelper->convertModelToArray($this->getModel());
775 |
776 | if (!array_get($model, $this->getName())) {
777 | $this->model = [
778 | $this->getName() => $model
779 | ];
780 | }
781 | }
782 |
783 |
784 | /**
785 | * Set form builder instance on helper so we can use it later
786 | *
787 | * @param FormBuilder $formBuilder
788 | * @return $this
789 | */
790 | public function setFormBuilder(FormBuilder $formBuilder)
791 | {
792 | $this->formBuilder = $formBuilder;
793 |
794 | return $this;
795 | }
796 |
797 | /**
798 | * @return FormBuilder
799 | */
800 | public function getFormBuilder()
801 | {
802 | return $this->formBuilder;
803 | }
804 |
805 | /**
806 | * Exclude some fields from rendering
807 | *
808 | * @return $this
809 | */
810 | public function exclude(array $fields)
811 | {
812 | $this->exclude = array_merge($this->exclude, $fields);
813 |
814 | return $this;
815 | }
816 |
817 |
818 | /**
819 | * If form is named form, modify names to be contained in single key (parent[child_field_name])
820 | *
821 | * @param string $name
822 | * @return string
823 | */
824 | protected function getFieldName($name)
825 | {
826 | if ($this->getName() !== null) {
827 | return $this->getName().'['.$name.']';
828 | }
829 |
830 | return $name;
831 | }
832 |
833 | /**
834 | * Disable all fields in a form
835 | */
836 | public function disableFields()
837 | {
838 | foreach ($this->fields as $field) {
839 | $field->disable();
840 | }
841 | }
842 |
843 | /**
844 | * Enable all fields in a form
845 | */
846 | public function enableFields()
847 | {
848 | foreach ($this->fields as $field) {
849 | $field->enable();
850 | }
851 | }
852 | }
853 |
--------------------------------------------------------------------------------
/README_OLD.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/kristijanhusak/laravel4-form-builder)
2 | [](https://scrutinizer-ci.com/g/kristijanhusak/laravel4-form-builder/?branch=master)
3 | [](https://packagist.org/packages/kris/laravel4-form-builder)
4 | [](https://packagist.org/packages/kris/laravel4-form-builder)
5 | [](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 | *Note*: This package was previously used from [laravel-form-builder](http://github.com/kristijanhusak/laravel-4ofm-builder) as `laravel-4` branch.
13 |
14 | ## Laravel 5
15 | For laravel 5 version check [laravel-form-builder](https://github.com/kristijanhusak/laravel-form-builder)
16 |
17 | ## Changelog
18 | Changelog can be found [here](https://github.com/kristijanhusak/laravel4-form-builder/blob/master/CHANGELOG.md)
19 |
20 | ## Table of contents
21 | 1. [Installation](#installation)
22 | 2. [Basic usage](#usage)
23 | 1. [Usage in controllers](#usage-in-controllers)
24 | 2. [Usage in views](#usage-in-views)
25 | 3. [Plain form](#plain-form)
26 | 4. [Child Form](#child-form)
27 | 5. [Named Form](#named-form)
28 | 6. [Collection](#collection)
29 | 1. [Collection of child forms](#collection-of-child-forms)
30 | 2. [Prototype](#prototype)
31 | 7. [Field customization](#field-customization)
32 | 8. [Changing configuration and templates](#changing-configuration-and-templates)
33 | 9. [Custom fields](#custom-fields)
34 | 10. [Contributing](#contributing)
35 | 11. [Issues and bug reporting](#issues-and-bug-reporting)
36 |
37 | ###Installation
38 |
39 | ``` json
40 | {
41 | "require": {
42 | "kris/laravel4-form-builder": "1.5.*"
43 | }
44 | }
45 | ```
46 |
47 | run `composer update`
48 |
49 | Then add Service provider to `config/app.php`
50 |
51 | ``` php
52 | 'providers' => [
53 | // ...
54 | 'Kris\LaravelFormBuilder\FormBuilderServiceProvider'
55 | ]
56 | ```
57 |
58 | And Facade (also in `config/app.php`)
59 |
60 | ``` php
61 | 'aliases' => [
62 | // ...
63 | 'FormBuilder' => 'Kris\LaravelFormBuilder\Facades\FormBuilder'
64 | ]
65 |
66 | ```
67 |
68 | ### Basic usage
69 |
70 | Creating form classes is easy. Lets assume PSR-4 is set for loading namespace `Project` in `app/Project` folder. With a simple artisan command we can create form:
71 |
72 | ``` sh
73 | php artisan make:form app/Project/Forms/PostForm
74 | ```
75 |
76 | you create form class in path `app/Project/Forms/PostForm.php` that looks like this:
77 |
78 | ``` php
79 | add('name', 'text')
111 | ->add('lyrics', 'textarea')
112 | ->add('publish', 'checkbox');
113 | }
114 | }
115 | ```
116 |
117 | #### Usage in controllers
118 |
119 | Forms can be used in controller like this:
120 |
121 | ``` php
122 | formBuilder = $formBuilder;
134 | }
135 |
136 | public function create()
137 | {
138 | $form = $this->formBuilder->create('Project\Forms\SongForm', [
139 | 'method' => 'POST',
140 | 'url' => route('song.store')
141 | ]);
142 |
143 | return view('song.create', compact('form'));
144 | }
145 |
146 | public function store()
147 | {
148 | }
149 | }
150 | ```
151 |
152 | #### Usage in views
153 |
154 | From controller they can be used in views like this:
155 |
156 | ``` html
157 |
158 |
159 | @extend('layouts.master')
160 |
161 | @section('content')
162 | {{ form($form) }}
163 | @endsection
164 | ```
165 |
166 | `{{ form($form) }}` Will generate this html:
167 |
168 | ``` html
169 |
170 |
171 |
172 | name
173 |
174 |
175 |
176 | lyrics
177 |
178 |
179 |
180 | publish
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 | lyrics
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 | publish
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 | name
224 |
225 |
226 |
227 | publish
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 | name
321 |
322 |
323 |
324 | title
325 |
326 |
327 |
328 | body
329 |
330 |
331 |
332 | textarea
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 | Post title
378 |
379 |
380 |
381 | Post body
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 | Title
452 |
453 |
454 |
455 | Body
456 | dolor sit
457 |
458 |
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 | Title
511 |
512 |
513 |
514 | Body
515 | dolor sit
516 |
517 |
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 | Add to collection
555 |
556 |
567 | @endsection
568 | ```
569 |
570 | `data-prototype` will contain:
571 | ```html
572 |
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 | languages
767 |
768 | France
769 |
770 |
771 | English
772 |
773 |
774 | German
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 | = form_row($child, ['checked' => 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 |
--------------------------------------------------------------------------------