├── .gitignore ├── tests ├── Dummy │ ├── DummyMacros.php │ ├── DummyToArray.php │ ├── DummyButton.php │ └── DummyEloquent.php ├── TestCases │ └── MacroableContainer.php ├── Framework │ ├── FrameworkTest.php │ ├── NudeFrameworkTest.php │ ├── TwitterBootstrapTest.php │ ├── ZurbFrameworkTest.php │ ├── ZurbFramework5Test.php │ ├── ZurbFramework4Test.php │ ├── TwitterBootstrap5Test.php │ ├── TwitterBootstrap3Test.php │ └── TwitterBootstrap4Test.php ├── Fields │ ├── HiddenTest.php │ ├── TextareaTest.php │ ├── UneditableTest.php │ ├── ButtonTest.php │ ├── FileTest.php │ ├── FloatingLabelTest.php │ └── PlainTextTest.php ├── MethodDispatcherTest.php ├── HelpersTest.php ├── ActionsTest.php ├── FormerTest.php └── Traits │ └── FieldTest.php ├── src ├── Former │ ├── Form │ │ ├── CheckableGroup.php │ │ ├── Fields │ │ │ ├── Textarea.php │ │ │ ├── Switchbox.php │ │ │ ├── Radio.php │ │ │ ├── Uneditable.php │ │ │ ├── Checkbox.php │ │ │ ├── Plaintext.php │ │ │ ├── Hidden.php │ │ │ ├── Button.php │ │ │ ├── File.php │ │ │ └── Input.php │ │ ├── Elements.php │ │ └── Actions.php │ ├── Interfaces │ │ ├── FieldInterface.php │ │ └── FrameworkInterface.php │ ├── Facades │ │ └── Former.php │ ├── Exceptions │ │ └── InvalidFrameworkException.php │ ├── Traits │ │ ├── FormerObject.php │ │ └── Framework.php │ ├── Framework │ │ ├── Nude.php │ │ ├── ZurbFoundation.php │ │ ├── ZurbFoundation4.php │ │ └── ZurbFoundation5.php │ ├── Populator.php │ ├── FormerServiceProvider.php │ ├── Helpers.php │ ├── MethodDispatcher.php │ └── LiveValidation.php ├── Laravel │ └── File.php └── config │ └── former.php ├── CONTRIBUTING.md ├── phpunit.xml ├── .github └── workflows │ └── tests.yml ├── composer.json ├── README.md └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Development 2 | .idea 3 | .phpunit.result.cache 4 | 5 | # Dependencies 6 | composer.lock 7 | vendor 8 | -------------------------------------------------------------------------------- /tests/Dummy/DummyMacros.php: -------------------------------------------------------------------------------- 1 | values = $values; 11 | } 12 | 13 | public function toArray() 14 | { 15 | return $this->values; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Dummy/DummyButton.php: -------------------------------------------------------------------------------- 1 | text = $text; 11 | } 12 | 13 | public function __toString() 14 | { 15 | return ''.$this->text.''; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | Thank you for contributing to Former! Please target all changes to the master branch. 4 | 5 | Don't forget to add a test in the `tests` folder : 6 | - If it's a feature you're adding, add a test proving it works. 7 | - If it's a bug you're fixing, write a test proving it doesn't occur anymore. 8 | 9 | Thanks for you input and thanks for using Former! 10 | -------------------------------------------------------------------------------- /src/Former/Form/Fields/Textarea.php: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | tests 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Former/Facades/Former.php: -------------------------------------------------------------------------------- 1 | isGrouped()) { 16 | // Remove any possible items added by the Populator. 17 | $this->items = array(); 18 | } 19 | $this->items(func_get_args()); 20 | 21 | return $this; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Former/Form/Fields/Radio.php: -------------------------------------------------------------------------------- 1 | items(func_get_args()); 28 | 29 | return $this; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Former/Exceptions/InvalidFrameworkException.php: -------------------------------------------------------------------------------- 1 | framework = $framework; 21 | $this->message = "Framework was not found [{$this->framework}]"; 22 | 23 | return $this; 24 | } 25 | /** 26 | * Gets the errors object. 27 | * 28 | * @return string 29 | */ 30 | public function getFramework() 31 | { 32 | return $this->framework; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Former/Form/Fields/Uneditable.php: -------------------------------------------------------------------------------- 1 | addClass($this->app['former.framework']->getUneditableClasses()); 24 | 25 | $this->setId(); 26 | 27 | return $this->app['former.framework']->createDisabledField($this); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Former/Form/Fields/Checkbox.php: -------------------------------------------------------------------------------- 1 | isGrouped()) { 28 | // Remove any possible items added by the Populator. 29 | $this->items = array(); 30 | } 31 | $this->items(func_get_args()); 32 | 33 | return $this; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Framework/FrameworkTest.php: -------------------------------------------------------------------------------- 1 | former->framework('ZurbFoundation'); 11 | 12 | $this->assertEquals('ZurbFoundation', $this->former->framework()); 13 | } 14 | 15 | public function testCanCheckWhatTheFrameworkIs() 16 | { 17 | $current = $this->app['former.framework']->current(); 18 | $isCurrent = $this->app['former.framework']->is($current); 19 | 20 | $this->assertTrue($isCurrent); 21 | } 22 | 23 | public function testCanCreateFieldOutsideOfForm() 24 | { 25 | $this->former->closeGroup(); 26 | $this->former->close(); 27 | 28 | $text = $this->former->text('foobar')->__toString(); 29 | 30 | $this->assertEquals('Foobar', $text); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | fail-fast: true 9 | matrix: 10 | os: [ ubuntu-latest, windows-latest ] 11 | php: [ "8.4", "8.3", "8.2", "8.1", "8.0", "7.4", "7.3", "7.2" ] 12 | 13 | name: PHP ${{ matrix.php }} - ${{ matrix.os }} 14 | runs-on: ${{ matrix.os }} 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | 20 | - name: Cache dependencies 21 | uses: actions/cache@v4 22 | with: 23 | path: ~/.composer/cache/files 24 | key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 25 | 26 | - name: Setup PHP 27 | uses: shivammathur/setup-php@v2 28 | with: 29 | php-version: ${{ matrix.php }} 30 | coverage: none 31 | 32 | - name: Install dependencies 33 | run: composer update --prefer-stable --prefer-dist --no-interaction --no-suggest 34 | 35 | - name: Execute tests 36 | run: vendor/bin/phpunit 37 | -------------------------------------------------------------------------------- /tests/Fields/HiddenTest.php: -------------------------------------------------------------------------------- 1 | former->hidden('foo')->value('bar')->__toString(); 13 | $matcher = $this->matchField(array(), 'hidden'); 14 | $field = Arr::except($matcher, 'id'); 15 | 16 | $this->assertHTML($field, $input); 17 | } 18 | 19 | public function testCanPopulateHiddenFields() 20 | { 21 | $this->former->populate(array('foo' => 'bar')); 22 | 23 | $input = $this->former->hidden('foo')->value('bis')->__toString(); 24 | $matcher = $this->matchField(array(), 'hidden'); 25 | $field = Arr::except($matcher, 'id'); 26 | $field['attributes']['value'] = 'bar'; 27 | 28 | $this->assertHTML($field, $input); 29 | } 30 | 31 | public function testEncodedValue() 32 | { 33 | $input = $this->former->hidden('foo')->value('bar')->__toString(); 34 | $this->assertStringContainsString('value="<a>bar</a>"', $input); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Former/Form/Fields/Plaintext.php: -------------------------------------------------------------------------------- 1 | addClass($this->app['former.framework']->getPlainTextClasses()); 24 | $this->setId(); 25 | if ($this->app['former']->getOption('escape_plaintext_value', true)) { 26 | $this->escapeValue(); 27 | } 28 | 29 | return $this->app['former.framework']->createPlainTextField($this); 30 | } 31 | 32 | protected function escapeValue() 33 | { 34 | $valueToEscape = $this->getValue(); 35 | $value = is_string($valueToEscape) || $valueToEscape instanceof HtmlString ? e($valueToEscape) : $valueToEscape; 36 | 37 | return $this->forceValue($value); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Dummy/DummyEloquent.php: -------------------------------------------------------------------------------- 1 | shouldReceive('getResults')->andReturn(array( 21 | new DummyEloquent(array('id' => 1, 'name' => 'foo')), 22 | new DummyEloquent(array('id' => 3, 'name' => 'bar')), 23 | )) 24 | ->mock(); 25 | } 26 | 27 | public function rolesAsCollection() 28 | { 29 | return Mockery::mock('Illuminate\Database\Eloquent\Relations\HasMany') 30 | ->shouldReceive('getResults')->andReturn(new Collection(array( 31 | new DummyEloquent(array('id' => 1, 'name' => 'foo')), 32 | new DummyEloquent(array('id' => 3, 'name' => 'bar')), 33 | ))) 34 | ->mock(); 35 | } 36 | 37 | public function getCustomAttribute() 38 | { 39 | return 'custom'; 40 | } 41 | 42 | public function __toString() 43 | { 44 | return $this->name; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Former/Form/Fields/Hidden.php: -------------------------------------------------------------------------------- 1 | 37 | */ 38 | public function render() 39 | { 40 | return HtmlInput::create('hidden', $this->name, Helpers::encode($this->value), $this->attributes)->render(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anahkiasen/former", 3 | "description": "A powerful form builder", 4 | "homepage": "http://formers.github.io/former/", 5 | "license": "MIT", 6 | "keywords": [ 7 | "bootstrap", 8 | "form", 9 | "foundation", 10 | "laravel" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "Maxime Fabre", 15 | "email": "ehtnam6@gmail.com" 16 | } 17 | ], 18 | "require": { 19 | "php": "^7.2|^8.0", 20 | "illuminate/config": "^5.1.3|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 21 | "illuminate/container": "^5.1.3|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 22 | "illuminate/contracts": "^5.1.3|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 23 | "illuminate/http": "^5.1.3|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 24 | "illuminate/routing": "^5.1.3|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 25 | "illuminate/session": "^5.1.3|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 26 | "illuminate/translation": "^5.1.3|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 27 | "illuminate/support": "^5.1.3|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 28 | "kylekatarnls/html-object": "^1.5" 29 | }, 30 | "require-dev": { 31 | "phpunit/phpunit": "^8.5", 32 | "mockery/mockery": "^1.3", 33 | "illuminate/database": "^5.1.3|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Former\\": [ 38 | "src/Former", 39 | "tests" 40 | ], 41 | "Laravel\\": "src/Laravel" 42 | } 43 | }, 44 | "extra": { 45 | "laravel": { 46 | "providers": [ 47 | "Former\\FormerServiceProvider" 48 | ], 49 | "aliases": { 50 | "Former": "Former\\Facades\\Former" 51 | } 52 | } 53 | }, 54 | "minimum-stability": "dev", 55 | "prefer-stable": true 56 | } 57 | -------------------------------------------------------------------------------- /tests/MethodDispatcherTest.php: -------------------------------------------------------------------------------- 1 | app, array()); 13 | 14 | $reflector = new \ReflectionClass($dispatcher); 15 | $attribute = $reflector->getProperty('repositories'); 16 | $attribute->setAccessible(true); 17 | 18 | $this->assertCount(0, $attribute->getValue($dispatcher)); 19 | 20 | $dispatcher->addRepository('A\Namespace\\'); 21 | $this->assertCount(1, $attribute->getValue($dispatcher)); 22 | $this->assertContains('A\Namespace\\', $attribute->getValue($dispatcher)); 23 | } 24 | 25 | /** 26 | * Test that camel-cased names like Former::fakeField get translated to 27 | * a titlecased class of A\Fakefield. This is the original Former approach. 28 | */ 29 | public function testSupportsTitleCasedFields() 30 | { 31 | $dispatcher = new MethodDispatcher($this->app, array('A\\')); 32 | $method = new ReflectionMethod($dispatcher, 'getClassFromMethod'); 33 | $method->setAccessible(true); 34 | 35 | $mock = Mockery::mock('A\Fakefield'); 36 | 37 | $this->assertEquals('A\Fakefield', $method->invoke($dispatcher, 'fakefield')); 38 | } 39 | 40 | /** 41 | * Test that camel-cased names (Former::fakeField) or snake-cased names 42 | * (Fomer::fake_field) get translated to a study cased class name (FakeField) 43 | */ 44 | public function testSupportsCamelCasedAndSnakeCasedFields() 45 | { 46 | $dispatcher = new MethodDispatcher($this->app, array('A\\')); 47 | $method = new ReflectionMethod($dispatcher, 'getClassFromMethod'); 48 | $method->setAccessible(true); 49 | 50 | $mock = Mockery::mock('A\FakeField'); 51 | 52 | $this->assertEquals('A\FakeField', $method->invoke($dispatcher, 'fakeField')); 53 | $this->assertEquals('A\FakeField', $method->invoke($dispatcher, 'fake_field')); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Framework/NudeFrameworkTest.php: -------------------------------------------------------------------------------- 1 | former->framework('Nude'); 14 | } 15 | 16 | //////////////////////////////////////////////////////////////////// 17 | //////////////////////////////// TESTS ///////////////////////////// 18 | //////////////////////////////////////////////////////////////////// 19 | 20 | public function testCanDisplayErrorMessages() 21 | { 22 | // Create field 23 | $this->former->withErrors($this->validator); 24 | $required = $this->former->text('required')->wrapAndRender(); 25 | 26 | // Matcher 27 | $matcher = 28 | 'Required'. 29 | ''. 30 | 'The required field is required.'; 31 | 32 | $this->assertEquals($matcher, $required); 33 | } 34 | 35 | public function testCanDisplayNestedErrorMessages() 36 | { 37 | // Use special one-time-use validator 38 | $this->former->withErrors($this->mockValidator("foo.bar", "Foo", null)); 39 | 40 | // Create field 41 | $required = $this->former->text("foo[bar]")->id('foo')->label('Foo')->wrapAndRender(); 42 | 43 | // Matcher 44 | $matcher = 45 | 'Foo'. 46 | ''. 47 | 'The Foo field is required.'; 48 | 49 | $this->assertEquals($matcher, $required); 50 | } 51 | 52 | public function testGroupOpenHasNoElement() 53 | { 54 | $group = $this->former->group('foo')->__toString(); 55 | $matcher = 'Foo'; 56 | 57 | $this->assertEquals($matcher, $group); 58 | } 59 | 60 | public function testGroupCloseHasNoElement() 61 | { 62 | $this->former->group('foo'); 63 | $text = $this->former->closeGroup(); 64 | 65 | $this->assertEmpty($text); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/Framework/TwitterBootstrapTest.php: -------------------------------------------------------------------------------- 1 | former->framework('TwitterBootstrap'); 15 | } 16 | 17 | //////////////////////////////////////////////////////////////////// 18 | ////////////////////////////// MATCHERS //////////////////////////// 19 | //////////////////////////////////////////////////////////////////// 20 | 21 | public function createPrependAppendMatcher($prepend = array(), $append = array()) 22 | { 23 | foreach ($prepend as $k => $p) { 24 | if (!Str::startsWith($p, ''.$p.''; 26 | } 27 | } 28 | foreach ($append as $k => $a) { 29 | if (!Str::startsWith($a, ''.$a.''; 31 | } 32 | } 33 | 34 | $class = array(); 35 | if ($prepend) { 36 | $class[] = "input-prepend"; 37 | } 38 | if ($append) { 39 | $class[] = "input-append"; 40 | } 41 | 42 | return 43 | ''. 44 | 'Foo'. 45 | ''. 46 | ''. 47 | implode('', $prepend). 48 | ''. 49 | implode('', $append). 50 | ''. 51 | ''. 52 | ''; 53 | } 54 | 55 | //////////////////////////////////////////////////////////////////// 56 | //////////////////////////////// TESTS ///////////////////////////// 57 | //////////////////////////////////////////////////////////////////// 58 | 59 | public function testAppendWhiteIcon() 60 | { 61 | $control = $this->former->text('foo')->appendIcon('white-something')->__toString(); 62 | $matcher = $this->createPrependAppendMatcher(array(), array('')); 63 | 64 | $this->assertEquals($matcher, $control); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/HelpersTest.php: -------------------------------------------------------------------------------- 1 | former->text('pagination')->__toString(); 12 | $matcher = $this->matchField(array(), 'text', 'pagination'); 13 | 14 | $this->assertHTML($matcher, $former); 15 | } 16 | 17 | public function testTranslateFieldNameUnderScoreToSpace() 18 | { 19 | $input = $this->former->text('field_name_with_underscore')->__toString(); 20 | $matcher = $this->matchLabel('Field name with underscore', 'field_name_with_underscore'); 21 | 22 | $this->assertHTML($matcher, $input); 23 | } 24 | 25 | public function testCanDisableTranslationCapitalization() 26 | { 27 | $this->app['config'] = Mockery::mock('Config') 28 | ->shouldReceive('get')->with('former.live_validation', '')->andReturn(true) 29 | ->shouldReceive('get')->with('former.translate_from', '')->andReturn(true) 30 | ->shouldReceive('get')->with('former.automatic_label', '')->andReturn(true) 31 | ->shouldReceive('get')->with('former.capitalize_translations', '')->andReturn(false) 32 | ->mock(); 33 | Helpers::setApp($this->app); 34 | 35 | $this->assertEquals('field', Helpers::translate('field')); 36 | } 37 | 38 | public function testNestedTranslationFieldNames() 39 | { 40 | $matcher = $this->matchLabel('City', 'address.city'); 41 | $input = $this->former->text('address.city')->__toString(); 42 | $this->assertHTML($matcher, $input); 43 | 44 | $matcher = $this->matchLabel('City', 'address[city]'); 45 | $input = $this->former->text('address[city]')->__toString(); 46 | $this->assertHTML($matcher, $input); 47 | } 48 | 49 | public function testDoesntTryInvalidKeys() 50 | { 51 | $input = $this->former->text('Invalid Label?')->__toString(); 52 | $matcher = $this->matchLabel('Invalid Label?', 'Invalid Label?'); 53 | 54 | $this->assertHTML($matcher, $input); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Fields/TextareaTest.php: -------------------------------------------------------------------------------- 1 | 'textarea', 22 | 'content' => 'bar', 23 | 'attributes' => array( 24 | 'class' => 'foo', 25 | 'cols' => '50', 26 | 'data-foo' => 'bar', 27 | 'id' => 'foo', 28 | 'name' => 'foo', 29 | 'rows' => '10', 30 | ), 31 | ); 32 | } 33 | 34 | //////////////////////////////////////////////////////////////////// 35 | //////////////////////////////// TESTS ///////////////////////////// 36 | //////////////////////////////////////////////////////////////////// 37 | 38 | public function testCanCreateArrayFields() 39 | { 40 | $this->former->populate(array('foo' => array('fr' => 'bar'))); 41 | $textarea = $this->former->textarea('foo[fr]')->__toString(); 42 | $matcher = 'Foo[fr]bar'; 43 | 44 | $this->assertEquals($matcher, $textarea); 45 | } 46 | 47 | public function testCanCreateTextareas() 48 | { 49 | $attributes = $this->matchTextarea(); 50 | $textarea = $this->former->textarea('foo')->setAttributes($attributes['attributes'])->value('bar')->__toString(); 51 | $matcher = $this->matchTextarea(); 52 | 53 | $this->assertControlGroup($textarea); 54 | $this->assertHTML($matcher, $textarea); 55 | } 56 | 57 | public function testTextareaContentIsProperlyEncoded() 58 | { 59 | $value = 'foo'; 60 | $attributes = $this->matchTextarea(); 61 | $textarea = $this->former 62 | ->textarea('foo') 63 | ->setAttributes($attributes['attributes']) 64 | ->value($value) 65 | ->__toString(); 66 | $matcher = $this->matchTextarea(); 67 | $matcher['content'] = $value; 68 | 69 | $this->assertControlGroup($textarea); 70 | $this->assertHTML($matcher, $textarea); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/Fields/UneditableTest.php: -------------------------------------------------------------------------------- 1 | 'label', 22 | 'attributes' => array('for' => 'foo'), 23 | ); 24 | } 25 | 26 | /** 27 | * Matches an uneditable input 28 | * 29 | * @return array 30 | */ 31 | public function matchUneditableInput() 32 | { 33 | return array( 34 | 'tag' => 'input', 35 | 'attributes' => array( 36 | 'disabled' => 'disabled', 37 | 'type' => 'text', 38 | 'name' => 'foo', 39 | 'value' => 'bar', 40 | 'id' => 'foo', 41 | ), 42 | ); 43 | } 44 | 45 | /** 46 | * Matches an uneditable input as a span 47 | * 48 | * @return [type] [description] 49 | */ 50 | public function matchUneditableSpan() 51 | { 52 | return array( 53 | 'tag' => 'span', 54 | 'content' => 'bar', 55 | 'attributes' => array( 56 | 'class' => 'uneditable-input', 57 | ), 58 | ); 59 | } 60 | 61 | //////////////////////////////////////////////////////////////////// 62 | //////////////////////////////// TESTS ///////////////////////////// 63 | //////////////////////////////////////////////////////////////////// 64 | 65 | public function testCanCreateClassicDisabledFields() 66 | { 67 | $this->former->framework('Nude'); 68 | $nude = $this->former->uneditable('foo')->value('bar')->__toString(); 69 | 70 | $this->assertHTML($this->matchPlainLabel(), $nude); 71 | $this->assertHTML($this->matchUneditableInput(), $nude); 72 | 73 | $this->resetLabels(); 74 | $this->former->framework('ZurbFoundation'); 75 | $zurb = $this->former->uneditable('foo')->value('bar')->__toString(); 76 | 77 | $this->assertHTML($this->matchPlainLabel(), $zurb); 78 | $this->assertHTML($this->matchUneditableInput(), $zurb); 79 | } 80 | 81 | public function testCanCreateUneditableFieldsWithBootstrap() 82 | { 83 | $input = $this->former->uneditable('foo')->value('bar')->__toString(); 84 | 85 | $this->assertControlGroup($input); 86 | $this->assertHTML($this->matchUneditableSpan(), $input); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Former 2 | ## A Laravelish way to create and format forms 3 | 4 | [](https://packagist.org/packages/anahkiasen/former) 5 | [](https://packagist.org/packages/anahkiasen/former) 6 | 7 | Former outputs form elements in HTML compatible with your favorite CSS framework (Bootstrap and Foundation are currently supported). Former also handles repopulation after validation errors, including automatically rendering error text with affected fields. 8 | 9 | ### Introduction 10 | 11 | Former provides a fluent method of form creation, allowing you to do: 12 | 13 | ```php 14 | Former::framework('TwitterBootstrap3'); 15 | 16 | Former::horizontal_open() 17 | ->id('MyForm') 18 | ->rules(['name' => 'required']) 19 | ->method('GET'); 20 | 21 | Former::xlarge_text('name') # Bootstrap sizing 22 | ->class('myclass') # arbitrary attribute support 23 | ->label('Full name') 24 | ->value('Joseph') 25 | ->required() # HTML5 validation 26 | ->help('Please enter your full name'); 27 | 28 | Former::textarea('comments') 29 | ->rows(10) 30 | ->columns(20) 31 | ->autofocus(); 32 | 33 | Former::actions() 34 | ->large_primary_submit('Submit') # Combine Bootstrap directives like "lg and btn-primary" 35 | ->large_inverse_reset('Reset'); 36 | 37 | Former::close(); 38 | ``` 39 | 40 | Every time you call a method that doesn't actually exist, Former assumes you're trying to set an attribute and creates it magically. That's why you can do in the above example `->rows(10)` ; in case you want to set attributes that contain dashes, just replace them by underscores : `->data_foo('bar')` equals `data-foo="bar"`. 41 | Now of course in case you want to set an attribute that actually contains an underscore you can always use the fallback method `setAttribute('data_foo', 'bar')`. You're welcome. 42 | 43 | This is the core of it, but Former offers a lot more. I invite you to consult the wiki to see the extent of what Former does. 44 | 45 | ----- 46 | 47 | ### Installation 48 | Require Former package using Composer: 49 | 50 | composer require anahkiasen/former 51 | 52 | Publish config files with artisan: 53 | 54 | php artisan vendor:publish --provider="Former\FormerServiceProvider" 55 | 56 | #### App.php config for Laravel 5.4 and below 57 | 58 | For Laravel 5.4 and below, you must modify your `config/app.php`. 59 | 60 | In the `providers` array add : 61 | 62 | Former\FormerServiceProvider::class 63 | 64 | Add then alias Former's main class by adding its facade to the `aliases` array in the same file : 65 | 66 | 'Former' => 'Former\Facades\Former', 67 | 68 | ----- 69 | 70 | ### Documentation 71 | 72 | Please refer to the [wiki](https://github.com/formers/former/wiki) for the full documentation. 73 | -------------------------------------------------------------------------------- /src/Former/Form/Elements.php: -------------------------------------------------------------------------------- 1 | app = $app; 37 | $this->session = $session; 38 | } 39 | 40 | /** 41 | * Generate a hidden field containing the current CSRF token. 42 | * 43 | * @return string 44 | */ 45 | public function token() 46 | { 47 | $csrf = method_exists($this->session, 'getToken') ? $this->session->getToken() : $this->session->token(); 48 | 49 | return (string) $this->app['former']->hidden('_token', $csrf); 50 | } 51 | 52 | /** 53 | * Creates a label tag 54 | * 55 | * @param string $label The label content 56 | * @param string $for The field the label's for 57 | * @param array $attributes The label's attributes 58 | * 59 | * @return Element A tag 60 | */ 61 | public function label($label, $for = null, $attributes = array()) 62 | { 63 | if (!$label instanceof Htmlable) { 64 | $oldLabel = (string) $label; 65 | $label = Helpers::translate($oldLabel, ''); 66 | 67 | // If there was no change to the label, 68 | // then a Laravel translation did not occur 69 | if (lcfirst($label) == $oldLabel) { 70 | $label = str_replace('_', ' ', $label); 71 | } 72 | } else { 73 | $label = $label->toHtml(); 74 | } 75 | 76 | $attributes['for'] = $for; 77 | $this->app['former']->labels[] = $for; 78 | 79 | return Element::create('label', $label, $attributes); 80 | } 81 | 82 | /** 83 | * Creates a form legend 84 | * 85 | * @param string $legend The text 86 | * @param array $attributes Its attributes 87 | * 88 | * @return Element A tag 89 | */ 90 | public function legend($legend, $attributes = array()) 91 | { 92 | $legend = Helpers::translate($legend); 93 | 94 | return Element::create('legend', $legend, $attributes); 95 | } 96 | 97 | /** 98 | * Close a field group 99 | * 100 | * @return string 101 | */ 102 | public function closeGroup() 103 | { 104 | $closing = ''; 105 | if (Group::$opened && isset(Group::$openGroup)) { 106 | $closing = Group::$openGroup->close(); 107 | } 108 | 109 | // Close custom group 110 | Group::$opened = false; 111 | 112 | // Reset custom group reference 113 | Group::$openGroup = null; 114 | 115 | return $closing; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Former/Form/Fields/Button.php: -------------------------------------------------------------------------------- 1 | app = $app; 54 | $this->attributes = (array) $attributes; 55 | $this->type = $type; 56 | $this->value($value); 57 | 58 | // Set correct element for the various button patterns 59 | switch ($type) { 60 | case 'button': 61 | $this->element = 'button'; 62 | $this->isSelfClosing = false; 63 | break; 64 | case 'link': 65 | $this->type = null; 66 | $this->element = 'a'; 67 | $this->attributes['href'] = $link; 68 | $this->isSelfClosing = false; 69 | break; 70 | } 71 | } 72 | 73 | //////////////////////////////////////////////////////////////////// 74 | ////////////////////////// FIELD METHODS /////////////////////////// 75 | //////////////////////////////////////////////////////////////////// 76 | 77 | /** 78 | * Check if the field is a button 79 | * 80 | * @return boolean 81 | */ 82 | public function isButton() 83 | { 84 | return true; 85 | } 86 | 87 | /** 88 | * Prepend an icon to the button 89 | * 90 | * @param string $icon 91 | * @param array $attributes 92 | * 93 | * @return self 94 | */ 95 | public function icon($icon, $attributes = array()) 96 | { 97 | $icon = $this->app['former.framework']->createIcon($icon, $attributes); 98 | $this->value = $icon.' '.$this->value; 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * Hijack Former's Object model value method 105 | * 106 | * @param string $value The new button text 107 | */ 108 | public function value($value) 109 | { 110 | $this->value = Helpers::translate($value); 111 | 112 | return $this; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/Framework/ZurbFrameworkTest.php: -------------------------------------------------------------------------------- 1 | former->framework('ZurbFoundation'); 14 | } 15 | 16 | //////////////////////////////////////////////////////////////////// 17 | ////////////////////////////// MATCHERS //////////////////////////// 18 | //////////////////////////////////////////////////////////////////// 19 | 20 | public function matchLabel($name = 'foo', $field = 'foo', $required = false) 21 | { 22 | return array( 23 | 'tag' => 'label', 24 | 'content' => ucfirst($name), 25 | 'attributes' => array( 26 | 'for' => $field, 27 | ), 28 | ); 29 | } 30 | 31 | //////////////////////////////////////////////////////////////////// 32 | //////////////////////////////// TESTS ///////////////////////////// 33 | //////////////////////////////////////////////////////////////////// 34 | 35 | public function testCanUseMagicMethods() 36 | { 37 | $input = $this->former->three_text('foo')->__toString(); 38 | 39 | $this->assertLabel($input); 40 | $this->assertHTML($this->matchField(), $input); 41 | } 42 | 43 | public function testCanSetAnErrorStateOnAField() 44 | { 45 | $input = $this->former->text('foo')->state('error')->__toString(); 46 | $matcher = array('tag' => 'div', 'attributes' => array('class' => 'error')); 47 | 48 | $this->assertLabel($input); 49 | $this->assertHTML($this->matchField(), $input); 50 | $this->assertHTML($matcher, $input); 51 | } 52 | 53 | public function testCanAppendHelpTexts() 54 | { 55 | $input = $this->former->text('foo')->inlineHelp('bar')->__toString(); 56 | $matcher = array('tag' => 'span', 'content' => 'Bar'); 57 | 58 | $this->assertLabel($input); 59 | $this->assertHTML($this->matchField(), $input); 60 | $this->assertHTML($matcher, $input); 61 | } 62 | 63 | public function testCantUseBootstrapReservedMethods() 64 | { 65 | $this->expectException('BadMethodCallException'); 66 | 67 | $this->former->text('foo')->blockHelp('bar')->__toString(); 68 | } 69 | 70 | public function testVerticalFormInputField() 71 | { 72 | $this->former->vertical_open(); 73 | $field = $this->former->text('foo')->__toString(); 74 | 75 | $match = ''. 76 | 'Foo'. 77 | ''. 78 | ''; 79 | 80 | $this->assertEquals($match, $field); 81 | } 82 | 83 | public function testHorizontalFormInputField() 84 | { 85 | $field = $this->former->text('foo')->__toString(); 86 | 87 | $match = ''. 88 | ''. 89 | 'Foo'. 90 | ''. 91 | ''. 92 | ''. 93 | ''. 94 | ''; 95 | 96 | $this->assertEquals($match, $field); 97 | } 98 | 99 | public function testHelpTextHasCorrectClasses() 100 | { 101 | 102 | $input = $this->former->text('foo')->inlineHelp('bar')->__toString(); 103 | $matcher = array('tag' => 'span', 'attributes' => array('class' => 'alert-box alert error'), 'content' => 'Bar'); 104 | $this->assertHTML($matcher, $input); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/Fields/ButtonTest.php: -------------------------------------------------------------------------------- 1 | former->button('Save')->__toString(); 12 | $matcher = $this->matchButton('btn', 'Save'); 13 | 14 | $this->assertHTML($matcher, $button); 15 | } 16 | 17 | public function testCanChainMethodsToAButton() 18 | { 19 | $button = $this->former->button('Save')->class('btn btn-primary')->value('Cancel')->__toString(); 20 | $matcher = $this->matchButton('btn btn-primary', 'Cancel'); 21 | 22 | $this->assertHTML($matcher, $button); 23 | } 24 | 25 | public function testCanCreateASubmitButton() 26 | { 27 | $button = $this->former->submit('Save')->class('btn btn-primary')->__toString(); 28 | $matcher = $this->matchInputButton('btn btn-primary', 'Save'); 29 | 30 | $this->assertHTML($matcher, $button); 31 | } 32 | 33 | public function testCanUseFormerObjectMethods() 34 | { 35 | $button = $this->former->button('pagination.next')->setAttributes($this->testAttributes)->__toString(); 36 | $matcher = $this->matchButton('foo', 'Next', array('data-foo' => 'bar')); 37 | 38 | $this->assertHtml($matcher, $button); 39 | } 40 | 41 | public function testCanDynamicallyCreateButtons() 42 | { 43 | $button = $this->former->large_block_primary_submit('Save')->__toString(); 44 | $matcher = $this->matchInputButton('btn-large btn-block btn-primary btn', 'Save'); 45 | 46 | $this->assertHTML($matcher, $button); 47 | } 48 | 49 | public function testCanCreateAResetButton() 50 | { 51 | $button = $this->former->large_block_inverse_reset('Reset')->__toString(); 52 | $matcher = $this->matchInputButton('btn-large btn-block btn-inverse btn', 'Reset', 'reset'); 53 | 54 | $this->assertHTML($matcher, $button); 55 | } 56 | 57 | public function testCanHaveMultipleInstancesOfAButton() 58 | { 59 | $multiple = array($this->former->submit('submit'), $this->former->reset('reset')); 60 | $multiple = implode(' ', $multiple); 61 | 62 | $matcher1 = $this->matchInputButton('btn', 'Submit', 'submit'); 63 | $matcher2 = $this->matchInputButton('btn', 'Reset', 'reset'); 64 | 65 | $this->assertHTML($matcher1, $multiple); 66 | $this->assertHTML($matcher2, $multiple); 67 | } 68 | 69 | public function testButtonsAreHtmlObjects() 70 | { 71 | $button = $this->former->submit('submit'); 72 | $button->name('foo'); 73 | $matcher = $this->matchInputButton('btn', 'Submit', 'submit'); 74 | $matcher['attributes']['name'] = 'foo'; 75 | 76 | $this->assertHTML($matcher, $button->render()); 77 | } 78 | 79 | public function testLinksDontHaveTypeAttribute() 80 | { 81 | $this->former->horizontal_open(); 82 | $link = $this->former->link('#', 'foo')->__toString(); 83 | $this->former->close(); 84 | 85 | $this->assertEquals('#', $link); 86 | } 87 | 88 | public function testCanCreateButtonWithIcon() 89 | { 90 | $this->markTestSkipped('Fatals with `Could not load XML from object`'); 91 | 92 | $button = $this->former->button('Save')->icon('save'); 93 | $matcher = $this->matchButton('btn', 'Save'); 94 | $matcher['child'] = array( 95 | 'tag' => 'i', 96 | 'attributes' => array('class' => 'icon-save'), 97 | ); 98 | 99 | $this->assertHTML($matcher, $button); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Former/Form/Actions.php: -------------------------------------------------------------------------------- 1 | app = $app; 42 | $this->value = $value; 43 | 44 | // Add specific actions classes to the actions block 45 | $this->addClass($this->app['former.framework']->getActionClasses()); 46 | } 47 | 48 | /** 49 | * Get the content of the Actions block 50 | * 51 | * @return string 52 | */ 53 | public function getContent() 54 | { 55 | $content = array_map(function ($content) { 56 | return method_exists($content, '__toString') ? (string) $content : $content; 57 | }, $this->value); 58 | 59 | return $this->app['former.framework']->wrapActions(implode(' ', $content)); 60 | } 61 | 62 | /** 63 | * Dynamically append actions to the block 64 | * 65 | * @param string $method The method 66 | * @param array $parameters Its parameters 67 | * 68 | * @return Actions 69 | */ 70 | public function __call($method, $parameters) 71 | { 72 | // Dynamically add buttons to an actions block 73 | if ($this->isButtonMethod($method)) { 74 | $text = Arr::get($parameters, 0); 75 | $link = Arr::get($parameters, 1); 76 | $attributes = Arr::get($parameters, 2); 77 | if (!$attributes and is_array($link)) { 78 | $attributes = $link; 79 | } 80 | 81 | return $this->createButtonOfType($method, $text, $link, $attributes); 82 | } 83 | 84 | return parent::__call($method, $parameters); 85 | } 86 | 87 | //////////////////////////////////////////////////////////////////// 88 | ////////////////////////////// HELPERS ///////////////////////////// 89 | //////////////////////////////////////////////////////////////////// 90 | 91 | /** 92 | * Create a new Button and add it to the actions 93 | * 94 | * @param string $type The button type 95 | * @param string $name Its name 96 | * @param string $link A link to point to 97 | * @param array $attributes Its attributes 98 | * 99 | * @return Actions 100 | */ 101 | private function createButtonOfType($type, $name, $link, $attributes) 102 | { 103 | $this->value[] = $this->app['former']->$type($name, $link, $attributes)->__toString(); 104 | 105 | return $this; 106 | } 107 | 108 | /** 109 | * Check if a given method calls a button or not 110 | * 111 | * @param string $method The method to check 112 | * 113 | * @return boolean 114 | */ 115 | private function isButtonMethod($method) 116 | { 117 | $buttons = array('button', 'submit', 'link', 'reset'); 118 | 119 | return (bool) Str::contains($method, $buttons); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Former/Interfaces/FrameworkInterface.php: -------------------------------------------------------------------------------- 1 | 'div', 16 | 'content' => $content, 17 | 'attributes' => array('class' => 'form-actions'), 18 | ); 19 | } 20 | 21 | //////////////////////////////////////////////////////////////////// 22 | //////////////////////////////// TESTS ///////////////////////////// 23 | //////////////////////////////////////////////////////////////////// 24 | 25 | public function testCanCreateAnActionBlock() 26 | { 27 | $action = $this->former->actions('foo')->__toString(); 28 | 29 | $this->assertHTML($this->matchActions(), $action); 30 | } 31 | 32 | public function testCanCreateAnActionsBlock() 33 | { 34 | $actions = $this->former->actions('foo', 'bar')->__toString(); 35 | 36 | $this->assertHTML($this->matchActions('foo bar'), $actions); 37 | } 38 | 39 | public function testCanCreateAnActionsBlockWithTags() 40 | { 41 | $actions = $this->former->actions('Submit', 'Reset')->__toString(); 42 | $matcher = $this->matchActions(); 43 | unset($matcher['content']); 44 | $matcher['children'] = array( 45 | 'count' => 2, 46 | 'only' => array( 47 | 'tag' => 'button', 48 | ), 49 | ); 50 | 51 | $this->assertHTML($matcher, $actions); 52 | } 53 | 54 | public function testCanUseObjectsAsActions() 55 | { 56 | $actions = $this->former->actions($this->former->submit('submit'), $this->former->reset('reset'))->__toString(); 57 | 58 | $matcher = array('tag' => 'div', 'attributes' => array('class' => 'form-actions')); 59 | $matcher['child'] = $this->matchInputButton('btn', 'Submit', 'submit'); 60 | $this->assertHTML($matcher, $actions); 61 | 62 | $matcher['child'] = $this->matchInputButton('btn', 'Reset', 'reset'); 63 | $this->assertHTML($matcher, $actions); 64 | } 65 | 66 | public function testCanChainMethodsToActionsBlock() 67 | { 68 | $actions = $this->former->actions('content')->id('foo')->addClass('bar')->data_foo('bar')->__toString(); 69 | $matcher = $this->matchActions('content'); 70 | $matcher['attributes'] = array( 71 | 'id' => 'foo', 72 | 'class' => 'bar form-actions', 73 | 'data-foo' => 'bar', 74 | ); 75 | 76 | $this->assertHTML($matcher, $actions); 77 | } 78 | 79 | public function testCanChainActionsToActionsBlock() 80 | { 81 | $actions = $this->former->actions() 82 | ->data_submit('foo') 83 | ->large_primary_submit('submit') 84 | ->small_block_inverse_reset('reset') 85 | ->__toString(); 86 | 87 | // Match block 88 | $matcher = $this->matchActions(); 89 | unset($matcher['content']); 90 | $this->assertHTML($matcher, $actions); 91 | 92 | // Match buttons 93 | $matcher['child'] = $this->matchInputButton('btn-large btn-primary btn', 'Submit', 'submit'); 94 | $this->assertHTML($matcher, $actions); 95 | 96 | $matcher['child'] = $this->matchInputButton('btn-small btn-block btn-inverse btn', 'Reset', 'reset'); 97 | $this->assertHTML($matcher, $actions); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/FormerTest.php: -------------------------------------------------------------------------------- 1 | 'legend', 17 | 'content' => 'Test', 18 | 'attributes' => $this->testAttributes, 19 | ); 20 | } 21 | 22 | public function matchToken() 23 | { 24 | return array( 25 | 'tag' => 'input', 26 | 'attributes' => array( 27 | 'type' => 'hidden', 28 | 'name' => '_token', 29 | 'value' => 'csrf_token', 30 | ), 31 | ); 32 | } 33 | 34 | public function matchLabel($name = 'foo', $field = 'foo', $required = false) 35 | { 36 | return array( 37 | 'tag' => 'label', 38 | 'content' => 'Foo', 39 | 'attributes' => array('for' => ''), 40 | ); 41 | } 42 | 43 | //////////////////////////////////////////////////////////////////// 44 | //////////////////////////////// TESTS ///////////////////////////// 45 | //////////////////////////////////////////////////////////////////// 46 | 47 | public function testCanCreateFormLegends() 48 | { 49 | $this->markTestSkipped('Fatals with `Could not load XML from object`'); 50 | 51 | $legend = $this->former->legend('test', $this->testAttributes); 52 | 53 | $this->assertHTML($this->matchLegend(), $legend); 54 | } 55 | 56 | public function testCanCreateFormLabels() 57 | { 58 | $this->markTestSkipped('Fatals with `Could not load XML from object`'); 59 | 60 | $label = $this->former->label('foo'); 61 | 62 | $this->assertLabel($label); 63 | } 64 | 65 | public function testCanCreateCsrfTokens() 66 | { 67 | $token = $this->former->token(); 68 | 69 | $this->assertHTML($this->matchToken(), $token); 70 | } 71 | 72 | public function testCanCreateFormMacros() 73 | { 74 | $this->markTestSkipped('Fatals with `Could not load XML from object`'); 75 | 76 | $former = $this->former; 77 | $this->former->macro('captcha', function ($name = null) use ($former) { 78 | return $former->text($name)->raw(); 79 | }); 80 | 81 | $this->assertEquals($this->former->text('foo')->raw(), $this->former->captcha('foo')); 82 | $this->assertHTML($this->matchField(), $this->former->captcha('foo')); 83 | } 84 | 85 | public function testCanUseClassesAsMacros() 86 | { 87 | $this->former->macro('loltext', 'Former\Dummy\DummyMacros@loltext'); 88 | 89 | $this->assertEquals('lolfoobar', $this->former->loltext('foobar')); 90 | } 91 | 92 | public function testMacrosDontTakePrecedenceOverNativeFields() 93 | { 94 | $former = $this->former; 95 | $this->former->macro('label', function () use ($former) { 96 | return 'NOPE'; 97 | }); 98 | 99 | $this->assertNotEquals('NOPE', $this->former->label('foo')); 100 | } 101 | 102 | public function testCloseCorrectlyRemoveInstances() 103 | { 104 | $this->former->close(); 105 | 106 | $this->assertFalse($this->app->bound('former.form')); 107 | } 108 | 109 | public function testCanUseFacadeWithoutContainer() 110 | { 111 | $this->markTestSkipped("Test currently failing, but I'm not sure how to fix or if it matters. " . 112 | 'If using Facade without a container is broken for you, please file an issue or PR with details.'); 113 | 114 | $text = Former::text('foo')->render(); 115 | 116 | $this->assertEquals('', $text); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Former/Form/Fields/File.php: -------------------------------------------------------------------------------- 1 | maxSize 71 | ? HtmlInput::hidden('MAX_FILE_SIZE', $this->maxSize) 72 | : null; 73 | 74 | return $hidden.parent::render(); 75 | } 76 | 77 | //////////////////////////////////////////////////////////////////// 78 | ////////////////////////// FIELD METHODS /////////////////////////// 79 | //////////////////////////////////////////////////////////////////// 80 | 81 | /** 82 | * Set which types of files are accepted by the file input 83 | 84 | */ 85 | public function accept() 86 | { 87 | $mimes = array(); 88 | 89 | // Transform all extensions/groups to mime types 90 | foreach (func_get_args() as $mime) { 91 | 92 | // Shortcuts and extensions 93 | if (in_array($mime, $this->mimeGroups)) { 94 | $mime .= '/*'; 95 | } 96 | $mime = LaravelFile::mime($mime, $mime); 97 | 98 | $mimes[] = $mime; 99 | } 100 | 101 | // Add accept attribute by concatenating the mimes 102 | $this->attributes['accept'] = implode(',', $mimes); 103 | 104 | return $this; 105 | } 106 | 107 | /** 108 | * Set a maximum size for files 109 | * 110 | * @param integer $size A maximum size 111 | * @param string $units The size's unit 112 | */ 113 | public function max($size, $units = 'KB') 114 | { 115 | // Bytes or bits ? 116 | $unit = substr($units, -1); 117 | $base = 1024; 118 | if ($unit == 'b') { 119 | $size = $size / 8; 120 | } 121 | 122 | // Convert 123 | switch ($units[0]) { 124 | case 'K': 125 | $size = $size * $base; 126 | break; 127 | case 'M': 128 | $size = $size * pow($base, 2); 129 | break; 130 | case 'G': 131 | $size = $size * pow($base, 3); 132 | break; 133 | } 134 | 135 | $this->maxSize = (int) $size; 136 | 137 | return $this; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/Fields/FileTest.php: -------------------------------------------------------------------------------- 1 | 'input', 17 | 'id' => 'foo', 18 | 'attributes' => array( 19 | 'type' => 'file', 20 | 'name' => 'foo', 21 | ), 22 | ); 23 | 24 | if ($accept) { 25 | $file['attributes']['accept'] = $accept; 26 | } 27 | 28 | return $file; 29 | } 30 | 31 | public function matchFiles() 32 | { 33 | return array( 34 | 'tag' => 'input', 35 | 'id' => 'foo[]', 36 | 'attributes' => array( 37 | 'type' => 'file', 38 | 'multiple' => true, 39 | 'name' => 'foo[]', 40 | ), 41 | ); 42 | } 43 | 44 | public function matchHidden($size) 45 | { 46 | return array( 47 | 'tag' => 'input', 48 | 'attributes' => array( 49 | 'type' => 'hidden', 50 | 'name' => 'MAX_FILE_SIZE', 51 | 'value' => $size, 52 | ), 53 | ); 54 | } 55 | 56 | //////////////////////////////////////////////////////////////////// 57 | //////////////////////////////// TESTS ///////////////////////////// 58 | //////////////////////////////////////////////////////////////////// 59 | 60 | public function testCanCreateAFileField() 61 | { 62 | $file = $this->former->file('foo')->__toString(); 63 | 64 | $this->assertControlGroup($file); 65 | $this->assertHTML($this->matchFile(), $file); 66 | } 67 | 68 | public function testCanCreateAMultipleFilesField() 69 | { 70 | $file = $this->former->files('foo')->__toString(); 71 | 72 | $this->assertLabel($file, 'foo[]'); 73 | $this->assertHTML($this->matchControlGroup(), $file); 74 | $this->assertHTML($this->matchFiles(), $file); 75 | } 76 | 77 | public function testCanCustomizeAcceptedFormats() 78 | { 79 | $file = $this->former->file('foo')->accept('video', 'image', 'audio', 'jpeg', 'image/gif')->__toString(); 80 | 81 | $this->assertControlGroup($file); 82 | $this->assertHTML($this->matchFile('video/*,image/*,audio/*,image/jpeg,image/gif'), $file); 83 | } 84 | 85 | public function testCanSetMaxSizeInKilobytes() 86 | { 87 | $file = $this->former->file('foo')->max(1, 'KB')->__toString(); 88 | 89 | $this->assertControlGroup($file); 90 | $this->assertHTML($this->matchFile(), $file); 91 | $this->assertHTML($this->matchHidden('1024'), $file); 92 | } 93 | 94 | public function testCanSetMaxSizeInMegabytes() 95 | { 96 | $file = $this->former->file('foo')->max(2, 'MB')->__toString(); 97 | 98 | $this->assertControlGroup($file); 99 | $this->assertHTML($this->matchFile(), $file); 100 | $this->assertHTML($this->matchHidden('2097152'), $file); 101 | } 102 | 103 | public function testCanSetMaxSizeInGigabytes() 104 | { 105 | $file = $this->former->file('foo')->max(1, 'GB')->__toString(); 106 | 107 | $this->assertControlGroup($file); 108 | $this->assertHTML($this->matchFile(), $file); 109 | $this->assertHTML($this->matchHidden('1073741824'), $file); 110 | } 111 | 112 | public function testCanSetMaxSizeInBits() 113 | { 114 | $file = $this->former->file('foo')->max(1, 'Mb')->__toString(); 115 | 116 | $this->assertControlGroup($file); 117 | $this->assertHTML($this->matchFile(), $file); 118 | $this->assertHTML($this->matchHidden('131072'), $file); 119 | } 120 | 121 | public function testCanSetMaxSizeInOctets() 122 | { 123 | $file = $this->former->file('foo')->max(2, 'Mo')->__toString(); 124 | 125 | $this->assertControlGroup($file); 126 | $this->assertHTML($this->matchFile(), $file); 127 | $this->assertHTML($this->matchHidden('2097152'), $file); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Former/Form/Fields/Input.php: -------------------------------------------------------------------------------- 1 | value)) { 47 | $values = array(); 48 | foreach ($this->value as $value) { 49 | $values[] = is_object($value) ? $value->__toString() : $value; 50 | } 51 | if (isset($values)) { 52 | $this->value = implode(', ', $values); 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * Prints out the current tag 59 | * 60 | * @return string An input tag 61 | */ 62 | public function render() 63 | { 64 | // Particular case of the search element 65 | if ($this->isOfType('search')) { 66 | $this->asSearch(); 67 | } 68 | 69 | $this->setId(); 70 | 71 | // Render main input 72 | $input = parent::render(); 73 | 74 | // If we have a datalist to append, print it out 75 | if ($this->datalist) { 76 | $input .= $this->createDatalist($this->list, $this->datalist); 77 | } 78 | 79 | return $input; 80 | } 81 | 82 | //////////////////////////////////////////////////////////////////// 83 | ////////////////////////// FIELD METHODS /////////////////////////// 84 | //////////////////////////////////////////////////////////////////// 85 | 86 | /** 87 | * Adds a datalist to the current field 88 | * 89 | * @param array $datalist An array to use a source 90 | * @param string $value The field to use as value 91 | * @param string $key The field to use as key 92 | */ 93 | public function useDatalist($datalist, $value = null, $key = null) 94 | { 95 | $datalist = Helpers::queryToArray($datalist, $value, $key); 96 | 97 | $list = $this->list ?: 'datalist_'.$this->name; 98 | 99 | // Create the link to the datalist 100 | $this->list = $list; 101 | $this->datalist = $datalist; 102 | 103 | return $this; 104 | } 105 | 106 | /** 107 | * Add range to the input 108 | * 109 | * @param integer $min 110 | * @param integer $max 111 | * 112 | * @return self 113 | */ 114 | public function range($min, $max) 115 | { 116 | $this->min($min); 117 | $this->max($max); 118 | 119 | return $this; 120 | } 121 | 122 | //////////////////////////////////////////////////////////////////// 123 | /////////////////////////////// HELPERS //////////////////////////// 124 | //////////////////////////////////////////////////////////////////// 125 | 126 | /** 127 | * Render a text element as a search element 128 | */ 129 | private function asSearch() 130 | { 131 | $this->type = 'text'; 132 | $this->addClass('search-query'); 133 | 134 | return $this; 135 | } 136 | 137 | /** 138 | * Renders a datalist 139 | * 140 | * @param string $id The datalist's id attribute 141 | * @param array $values Its values 142 | * 143 | * @return string A tag 144 | */ 145 | private function createDatalist($id, $values) 146 | { 147 | $datalist = ''; 148 | foreach ($values as $key => $value) { 149 | $datalist .= ''.$key.''; 150 | } 151 | $datalist .= ''; 152 | 153 | return $datalist; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /tests/Framework/ZurbFramework5Test.php: -------------------------------------------------------------------------------- 1 | former->framework('ZurbFoundation5'); 14 | $this->former->horizontal_open()->__toString(); 15 | } 16 | 17 | //////////////////////////////////////////////////////////////////// 18 | ////////////////////////////// MATCHERS //////////////////////////// 19 | //////////////////////////////////////////////////////////////////// 20 | 21 | public function matchLabel($name = 'foo', $field = 'foo', $required = false) 22 | { 23 | return array( 24 | 'tag' => 'label', 25 | 'content' => ucfirst($name), 26 | 'attributes' => array( 27 | 'for' => $field, 28 | ), 29 | ); 30 | } 31 | 32 | //////////////////////////////////////////////////////////////////// 33 | //////////////////////////////// TESTS ///////////////////////////// 34 | //////////////////////////////////////////////////////////////////// 35 | 36 | public function testCanUseMagicMethods() 37 | { 38 | $input = $this->former->large_submit('Save')->__toString(); 39 | $matcher = $this->matchInputButton('large button', 'Save'); 40 | 41 | $this->assertHTML($matcher, $input); 42 | } 43 | 44 | public function testCanSetAnErrorStateOnAField() 45 | { 46 | $input = $this->former->text('foo')->state('error')->__toString(); 47 | $matcher = array('tag' => 'div', 'attributes' => array('class' => 'error')); 48 | 49 | $this->assertLabel($input); 50 | $this->assertHTML($this->matchField(), $input); 51 | $this->assertHTML($matcher, $input); 52 | } 53 | 54 | public function testCanAppendHelpTexts() 55 | { 56 | $input = $this->former->text('foo')->inlineHelp('bar')->__toString(); 57 | $matcher = array('tag' => 'small', 'content' => 'Bar'); 58 | 59 | $this->assertLabel($input); 60 | $this->assertHTML($this->matchField(), $input); 61 | $this->assertHTML($matcher, $input); 62 | } 63 | 64 | public function testCantUseBootstrapReservedMethods() 65 | { 66 | $this->expectException('BadMethodCallException'); 67 | 68 | $this->former->text('foo')->blockHelp('bar')->__toString(); 69 | } 70 | 71 | public function testCreateIconWithFrameworkSpecificIcon() 72 | { 73 | $icon = $this->app['former.framework']->createIcon('star')->__toString(); 74 | $match = ''; 75 | 76 | $this->assertEquals($match, $icon); 77 | } 78 | 79 | public function testCanAppendIcon() 80 | { 81 | $this->former->vertical_open(); 82 | $input = $this->former->text('foo')->appendIcon('star')->__toString(); 83 | $match = ''. 84 | 'Foo'. 85 | ''. 86 | ''. 87 | ''. 88 | ''. 89 | ''. 90 | ''. 91 | ''. 92 | ''. 93 | ''. 94 | ''. 95 | ''; 96 | 97 | $this->assertEquals($match, $input); 98 | } 99 | 100 | public function testCreateOverideIconSettingsWithFrameworkSpecificIcon() 101 | { 102 | $icon = $this->app['former.framework']->createIcon('star')->__toString(); 103 | $match = ''; 104 | 105 | $this->assertEquals($match, $icon); 106 | } 107 | 108 | public function testVerticalFormInputField() 109 | { 110 | $this->former->vertical_open(); 111 | $field = $this->former->text('foo')->__toString(); 112 | 113 | $match = ''. 114 | 'Foo'. 115 | ''. 116 | ''; 117 | 118 | $this->assertEquals($match, $field); 119 | } 120 | 121 | public function testHorizontalFormInputField() 122 | { 123 | $field = $this->former->text('foo')->__toString(); 124 | 125 | $match = ''. 126 | ''. 127 | 'Foo'. 128 | ''. 129 | ''. 130 | ''. 131 | ''. 132 | ''; 133 | 134 | $this->assertEquals($match, $field); 135 | } 136 | 137 | public function testHelpTextHasCorrectClasses() 138 | { 139 | 140 | $input = $this->former->text('foo')->inlineHelp('bar')->__toString(); 141 | $matcher = array('tag' => 'small', 'attributes' => array('class' => 'error'), 'content' => 'Bar'); 142 | $this->assertHTML($matcher, $input); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tests/Framework/ZurbFramework4Test.php: -------------------------------------------------------------------------------- 1 | former->framework('ZurbFoundation4'); 14 | $this->former->horizontal_open()->__toString(); 15 | } 16 | 17 | //////////////////////////////////////////////////////////////////// 18 | ////////////////////////////// MATCHERS //////////////////////////// 19 | //////////////////////////////////////////////////////////////////// 20 | 21 | public function matchLabel($name = 'foo', $field = 'foo', $required = false) 22 | { 23 | return array( 24 | 'tag' => 'label', 25 | 'content' => ucfirst($name), 26 | 'attributes' => array( 27 | 'for' => $field, 28 | ), 29 | ); 30 | } 31 | 32 | //////////////////////////////////////////////////////////////////// 33 | //////////////////////////////// TESTS ///////////////////////////// 34 | //////////////////////////////////////////////////////////////////// 35 | 36 | public function testCanUseMagicMethods() 37 | { 38 | $input = $this->former->large_submit('Save')->__toString(); 39 | $matcher = $this->matchInputButton('large button', 'Save'); 40 | 41 | $this->assertHTML($matcher, $input); 42 | } 43 | 44 | public function testCanSetAnErrorStateOnAField() 45 | { 46 | $input = $this->former->text('foo')->state('error')->__toString(); 47 | $matcher = array('tag' => 'div', 'attributes' => array('class' => 'error')); 48 | 49 | $this->assertLabel($input); 50 | $this->assertHTML($this->matchField(), $input); 51 | $this->assertHTML($matcher, $input); 52 | } 53 | 54 | public function testCanAppendHelpTexts() 55 | { 56 | $input = $this->former->text('foo')->inlineHelp('bar')->__toString(); 57 | $matcher = array('tag' => 'span', 'content' => 'Bar'); 58 | 59 | $this->assertLabel($input); 60 | $this->assertHTML($this->matchField(), $input); 61 | $this->assertHTML($matcher, $input); 62 | } 63 | 64 | public function testCantUseBootstrapReservedMethods() 65 | { 66 | $this->expectException('BadMethodCallException'); 67 | 68 | $this->former->text('foo')->blockHelp('bar')->__toString(); 69 | } 70 | 71 | public function testCreateIconWithFrameworkSpecificIcon() 72 | { 73 | $icon = $this->app['former.framework']->createIcon('smiley')->__toString(); 74 | $match = ''; 75 | 76 | $this->assertEquals($match, $icon); 77 | } 78 | 79 | public function testCanAppendIcon() 80 | { 81 | $this->former->vertical_open(); 82 | $input = $this->former->text('foo')->appendIcon('ok')->__toString(); 83 | $match = ''. 84 | 'Foo'. 85 | ''. 86 | ''. 87 | ''. 88 | ''. 89 | ''. 90 | ''. 91 | ''. 92 | ''. 93 | ''; 94 | 95 | $this->assertEquals($match, $input); 96 | } 97 | 98 | public function testCreateOverideIconSettingsWithFrameworkSpecificIcon() 99 | { 100 | $icon = $this->app['former.framework']->createIcon('smiley')->__toString(); 101 | $match = ''; 102 | 103 | $this->assertEquals($match, $icon); 104 | } 105 | 106 | public function testVerticalFormInputField() 107 | { 108 | $this->former->vertical_open(); 109 | $field = $this->former->text('foo')->__toString(); 110 | 111 | $match = ''. 112 | 'Foo'. 113 | ''. 114 | ''; 115 | 116 | $this->assertEquals($match, $field); 117 | } 118 | 119 | public function testHorizontalFormInputField() 120 | { 121 | $field = $this->former->text('foo')->__toString(); 122 | 123 | $match = ''. 124 | ''. 125 | 'Foo'. 126 | ''. 127 | ''. 128 | ''. 129 | ''. 130 | ''; 131 | 132 | $this->assertEquals($match, $field); 133 | } 134 | 135 | public function testHelpTextHasCorrectClasses() 136 | { 137 | 138 | $input = $this->former->text('foo')->inlineHelp('bar')->__toString(); 139 | $matcher = array('tag' => 'span', 'attributes' => array('class' => 'alert-box radius warning'), 'content' => 'Bar'); 140 | $this->assertHTML($matcher, $input); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Former/Traits/FormerObject.php: -------------------------------------------------------------------------------- 1 | setId(); 53 | 54 | return $this->attributes['id']; 55 | } 56 | 57 | /** 58 | * Set the matching ID on a field if possible 59 | */ 60 | protected function setId() 61 | { 62 | if (!array_key_exists('id', $this->attributes) and 63 | in_array($this->name, $this->app['former']->labels) 64 | ) { 65 | // Set and save the field's ID 66 | $id = $this->getUniqueId($this->name); 67 | $this->attributes['id'] = $id; 68 | $this->app['former']->ids[] = $id; 69 | } 70 | } 71 | 72 | /** 73 | * Get an unique ID for a field from its name 74 | * 75 | * @param string $name 76 | * 77 | * @return string 78 | */ 79 | protected function getUniqueId($name) 80 | { 81 | $names = &$this->app['former']->names; 82 | 83 | if (array_key_exists($name, $names)) { 84 | $count = $names[$name] + 1; 85 | $names[$name] = $count; 86 | return $name . '-' . $count; 87 | } 88 | 89 | $names[$name] = 1; 90 | return $name; 91 | } 92 | 93 | /** 94 | * Runs the frameworks getFieldClasses method on this 95 | * 96 | * @return $this 97 | */ 98 | protected function setFieldClasses() 99 | { 100 | $framework = isset($this->app['former.form.framework']) ? $this->app['former.form.framework'] : $this->app['former.framework']; 101 | $framework->getFieldClasses($this, $this->modifiers); 102 | 103 | return $this; 104 | } 105 | 106 | /** 107 | * Render the FormerObject and set its id 108 | * 109 | * @return string 110 | */ 111 | public function render() 112 | { 113 | // Set the proper ID according to the label 114 | $this->setId(); 115 | 116 | if($this instanceof Field) { 117 | $this->setFieldClasses(); 118 | } 119 | 120 | // Encode HTML value 121 | $isButton = ($this instanceof Field) ? $this->isButton() : false; 122 | if (!$isButton and is_string($this->value)) { 123 | $this->value = Helpers::encode($this->value); 124 | } 125 | 126 | return parent::render(); 127 | } 128 | 129 | //////////////////////////////////////////////////////////////////// 130 | ////////////////////////////// GETTERS ///////////////////////////// 131 | //////////////////////////////////////////////////////////////////// 132 | 133 | /** 134 | * Get the object's name 135 | * 136 | * @return string 137 | */ 138 | public function getName() 139 | { 140 | return $this->name; 141 | } 142 | 143 | //////////////////////////////////////////////////////////////////// 144 | //////////////////////////// OBJECT TYPE /////////////////////////// 145 | //////////////////////////////////////////////////////////////////// 146 | 147 | /** 148 | * Get the object's type 149 | * 150 | * @return string 151 | */ 152 | public function getType() 153 | { 154 | return $this->type; 155 | } 156 | 157 | /** 158 | * Change a object's type 159 | * 160 | * @param string $type 161 | */ 162 | public function setType($type) 163 | { 164 | $this->type = $type; 165 | 166 | return $this; 167 | } 168 | 169 | /** 170 | * Check if an object is of a certain type 171 | * 172 | * @return boolean 173 | */ 174 | public function isOfType() 175 | { 176 | $types = func_get_args(); 177 | 178 | return in_array($this->type, $types); 179 | } 180 | 181 | /** 182 | * Set the modifiers from initial method call 183 | * 184 | * @return $this 185 | */ 186 | public function getModifiers() 187 | { 188 | return $this->modifiers; 189 | } 190 | 191 | /** 192 | * Set the modifiers from initial method call 193 | * 194 | * @return $this 195 | */ 196 | public function setModifiers($modifiers) 197 | { 198 | $this->modifiers = $modifiers; 199 | return $this; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/Former/Framework/Nude.php: -------------------------------------------------------------------------------- 1 | app = $app; 34 | $this->setFrameworkDefaults(); 35 | } 36 | 37 | //////////////////////////////////////////////////////////////////// 38 | /////////////////////////// FILTER ARRAYS ////////////////////////// 39 | //////////////////////////////////////////////////////////////////// 40 | 41 | public function filterButtonClasses($classes) 42 | { 43 | return $classes; 44 | } 45 | 46 | public function filterFieldClasses($classes) 47 | { 48 | return $classes; 49 | } 50 | 51 | //////////////////////////////////////////////////////////////////// 52 | ///////////////////////////// ADD CLASSES ////////////////////////// 53 | //////////////////////////////////////////////////////////////////// 54 | 55 | public function getFieldClasses(Field $field, $classes = array()) 56 | { 57 | $classes = $this->filterFieldClasses($classes); 58 | 59 | // If we found any class, add them 60 | if ($classes) { 61 | $field->class(implode(' ', $classes)); 62 | } 63 | 64 | return $field; 65 | } 66 | 67 | public function getGroupClasses() 68 | { 69 | return null; 70 | } 71 | 72 | public function getLabelClasses() 73 | { 74 | return null; 75 | } 76 | 77 | public function getUneditableClasses() 78 | { 79 | return null; 80 | } 81 | 82 | public function getPlainTextClasses() 83 | { 84 | return null; 85 | } 86 | 87 | public function getFormClasses($type) 88 | { 89 | return null; 90 | } 91 | 92 | public function getActionClasses() 93 | { 94 | return null; 95 | } 96 | 97 | //////////////////////////////////////////////////////////////////// 98 | //////////////////////////// RENDER BLOCKS ///////////////////////// 99 | //////////////////////////////////////////////////////////////////// 100 | 101 | /** 102 | * Create an help text 103 | */ 104 | public function createHelp($text, $attributes = array()) 105 | { 106 | return Element::create('span', $text, $attributes)->addClass('help'); 107 | } 108 | 109 | /** 110 | * Render a disabled field 111 | * 112 | * @param Field $field 113 | * 114 | * @return Input 115 | */ 116 | public function createDisabledField(Field $field) 117 | { 118 | $field->disabled(); 119 | 120 | return Input::create('text', $field->getName(), $field->getValue(), $field->getAttributes()); 121 | } 122 | 123 | /** 124 | * Render a plain text field 125 | * Which fallback to a disabled field 126 | * 127 | * @param Field $field 128 | * 129 | * @return Element 130 | */ 131 | public function createPlainTextField(Field $field) 132 | { 133 | return $this->createDisabledField($field); 134 | } 135 | 136 | //////////////////////////////////////////////////////////////////// 137 | //////////////////////////// WRAP BLOCKS /////////////////////////// 138 | //////////////////////////////////////////////////////////////////// 139 | 140 | /** 141 | * Wrap an item to be prepended or appended to the current field 142 | * 143 | * @param string $item 144 | * 145 | * @return Element A wrapped item 146 | */ 147 | public function placeAround($item) 148 | { 149 | return Element::create('span', $item); 150 | } 151 | 152 | /** 153 | * Wrap a field with prepended and appended items 154 | * 155 | * @param Field $field 156 | * @param array $prepend 157 | * @param array $append 158 | * 159 | * @return string A field concatented with prepended and/or appended items 160 | */ 161 | public function prependAppend($field, $prepend, $append) 162 | { 163 | $return = ''; 164 | $return .= implode('', $prepend); 165 | $return .= $field->render(); 166 | $return .= implode('', $append); 167 | $return .= ''; 168 | 169 | return $return; 170 | } 171 | 172 | /** 173 | * Wraps all field contents with potential additional tags. 174 | * 175 | * @param Field $field 176 | * 177 | * @return Field A wrapped field 178 | */ 179 | public function wrapField($field) 180 | { 181 | return $field; 182 | } 183 | 184 | /** 185 | * Wrap actions block with potential additional tags 186 | * 187 | * @param Actions $actions 188 | * 189 | * @return string A wrapped actions block 190 | */ 191 | public function wrapActions($actions) 192 | { 193 | return $actions; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/Former/Populator.php: -------------------------------------------------------------------------------- 1 | items = $items; 24 | } 25 | 26 | //////////////////////////////////////////////////////////////////// 27 | ///////////////////////// INDIVIDUAL VALUES //////////////////////// 28 | //////////////////////////////////////////////////////////////////// 29 | 30 | /** 31 | * Get the value of a field 32 | * 33 | * @param string $field The field's name 34 | * 35 | * @return mixed 36 | */ 37 | public function get($field, $fallback = null) 38 | { 39 | // Anonymous fields should not return any value 40 | if ($field == null) { 41 | return null; 42 | } 43 | 44 | // Plain array 45 | if (is_array($this->items) and !Str::contains($field, '[')) { 46 | return parent::get($field, $fallback); 47 | } 48 | 49 | // Transform the name into an array 50 | $value = $this->items; 51 | $field = $this->parseFieldAsArray($field); 52 | 53 | // Dive into the model 54 | foreach ($field as $relationship) { 55 | 56 | // Get attribute from model 57 | if (!is_array($value)) { 58 | $value = $this->getAttributeFromModel($value, $relationship, $fallback); 59 | 60 | continue; 61 | } 62 | 63 | // Get attribute from model 64 | if (array_key_exists($relationship, $value)) { 65 | $value = $value[$relationship]; 66 | } else { 67 | // Check array for submodels that may contain the relationship 68 | $inSubmodel = false; 69 | 70 | foreach ($value as $key => $submodel) { 71 | $value[$key] = $this->getAttributeFromModel($submodel, $relationship, $fallback); 72 | 73 | if ($value[$key] !== $fallback) { 74 | $inSubmodel = true; 75 | } 76 | } 77 | 78 | // If no submodels contained the relationship, return the fallback, not an array of fallbacks 79 | if (!$inSubmodel) { 80 | $value = $fallback; 81 | break; 82 | } 83 | } 84 | } 85 | 86 | return $value; 87 | } 88 | 89 | //////////////////////////////////////////////////////////////////// 90 | ///////////////////////////// SWAPPERS ///////////////////////////// 91 | //////////////////////////////////////////////////////////////////// 92 | 93 | /** 94 | * Replace the items 95 | * 96 | * @param mixed $items 97 | * 98 | * @return void 99 | */ 100 | public function replace($items) 101 | { 102 | $this->items = $items; 103 | } 104 | 105 | /** 106 | * Reset the current values array 107 | * 108 | * @return void 109 | */ 110 | public function reset() 111 | { 112 | $this->items = array(); 113 | } 114 | 115 | //////////////////////////////////////////////////////////////////// 116 | ////////////////////////////// HELPERS ///////////////////////////// 117 | //////////////////////////////////////////////////////////////////// 118 | 119 | /** 120 | * Parses the name of a field to a tree of fields 121 | * 122 | * @param string $field The field's name 123 | * 124 | * @return array A tree of field 125 | */ 126 | protected function parseFieldAsArray($field) 127 | { 128 | if (Str::contains($field, '[]')) { 129 | return (array) $field; 130 | } 131 | 132 | // Transform array notation to dot notation 133 | if (Str::contains($field, '[')) { 134 | $field = preg_replace("/[\[\]]/", '.', $field); 135 | $field = str_replace('..', '.', $field); 136 | $field = trim($field, '.'); 137 | } 138 | 139 | // Parse dot notation 140 | if (Str::contains($field, '.')) { 141 | $field = explode('.', $field); 142 | } else { 143 | $field = (array) $field; 144 | } 145 | 146 | return $field; 147 | } 148 | 149 | /** 150 | * Get an attribute from a model 151 | * 152 | * @param object $model The model 153 | * @param string $attribute The attribute's name 154 | * @param string $fallback Fallback value 155 | * 156 | * @return mixed 157 | */ 158 | public function getAttributeFromModel($model, $attribute, $fallback) 159 | { 160 | if ($model instanceof Model) { 161 | // Return fallback if attribute is null 162 | $value = $model->getAttribute($attribute); 163 | return is_null($value) ? $fallback : $value; 164 | } 165 | 166 | if ($model instanceof Collection) { 167 | return $model->get($attribute, $fallback); 168 | } 169 | 170 | if (is_object($model) && method_exists($model, 'toArray')) { 171 | $model = $model->toArray(); 172 | } else { 173 | $model = (array) $model; 174 | } 175 | if (array_key_exists($attribute, $model)) { 176 | return $model[$attribute]; 177 | } 178 | 179 | return $fallback; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /tests/Fields/FloatingLabelTest.php: -------------------------------------------------------------------------------- 1 | former->framework('TwitterBootstrap5'); 19 | $this->former->vertical_open()->__toString(); 20 | } 21 | 22 | //////////////////////////////////////////////////////////////////// 23 | ////////////////////////////// MATCHERS //////////////////////////// 24 | //////////////////////////////////////////////////////////////////// 25 | 26 | /** 27 | * Matches a floating label 28 | * 29 | * @return array 30 | */ 31 | public function matchFloatingLabel() 32 | { 33 | return array( 34 | 'tag' => 'label', 35 | ); 36 | } 37 | 38 | /** 39 | * Matches an input text tag 40 | * 41 | * @return array 42 | */ 43 | public function matchFloatingInputText() 44 | { 45 | return array( 46 | 'tag' => 'input', 47 | 'attributes' => array( 48 | 'id' => 'foo', 49 | 'class' => 'form-control', 50 | 'placeholder' => 'Dummy placeholder', 51 | 'type' => 'text', 52 | 'name' => 'foo', 53 | ), 54 | ); 55 | } 56 | 57 | /** 58 | * Matches a textarea tag 59 | * 60 | * @return array 61 | */ 62 | public function matchFloatingTextarea() 63 | { 64 | return array( 65 | 'tag' => 'textarea', 66 | 'attributes' => array( 67 | 'id' => 'foo', 68 | 'class' => 'form-control', 69 | 'placeholder' => 'Dummy placeholder', 70 | 'name' => 'foo', 71 | ), 72 | ); 73 | } 74 | 75 | /** 76 | * Matches a select tag 77 | * 78 | * @return array 79 | */ 80 | public function matchFloatingSelect() 81 | { 82 | return array( 83 | 'tag' => 'select', 84 | 'children' => array( 85 | 'count' => 4, 86 | 'only' => array('tag' => 'option'), 87 | ), 88 | 'attributes' => array( 89 | 'id' => 'foo', 90 | 'class' => 'form-select', 91 | 'name' => 'foo', 92 | ), 93 | ); 94 | } 95 | 96 | //////////////////////////////////////////////////////////////////// 97 | ////////////////////////////// ASSERTIONS ////////////////////////// 98 | //////////////////////////////////////////////////////////////////// 99 | 100 | /** 101 | * Matches a Form Floating Label Group 102 | * 103 | * @param string $input 104 | * @param string $label 105 | * 106 | * @return boolean 107 | */ 108 | protected function formFloatingLabelGroup( 109 | $input = '', 110 | $label = 'Foo' 111 | ) { 112 | return ''.$input.$label.''; 113 | } 114 | 115 | //////////////////////////////////////////////////////////////////// 116 | //////////////////////////////// TESTS ///////////////////////////// 117 | //////////////////////////////////////////////////////////////////// 118 | 119 | public function testCanCreateFloatingLabelInputText() 120 | { 121 | $input = $this->former 122 | ->text('foo') 123 | ->placeholder('Dummy placeholder') 124 | ->floatingLabel() 125 | ->__toString(); 126 | 127 | $this->assertHTML($this->matchFloatingLabel(), $input); 128 | $this->assertHTML($this->matchFloatingInputText(), $input); 129 | 130 | $matcher = $this->formFloatingLabelGroup(); 131 | $this->assertEquals($matcher, $input); 132 | } 133 | 134 | public function testCanCreateFloatingLabelTextarea() 135 | { 136 | $textarea = $this->former 137 | ->textarea('foo') 138 | ->placeholder('Dummy placeholder') 139 | ->floatingLabel() 140 | ->__toString(); 141 | 142 | $this->assertHTML($this->matchFloatingLabel(), $textarea); 143 | $this->assertHTML($this->matchFloatingTextarea(), $textarea); 144 | 145 | $matcher = $this->formFloatingLabelGroup(''); 146 | $this->assertEquals($matcher, $textarea); 147 | } 148 | 149 | public function testCanCreateFloatingLabelSelect() 150 | { 151 | $selectElement = $this->former 152 | ->select('foo') 153 | ->options($this->options) 154 | ->placeholder('Choose an option') 155 | ->floatingLabel(); 156 | 157 | $select = $selectElement->__toString(); 158 | 159 | $this->assertHTML($this->matchFloatingLabel(), $select); 160 | $this->assertHTML($this->matchFloatingSelect(), $select); 161 | 162 | $placeholderOption = Element::create('option', 'Choose an option', array('value' => '', 'disabled' => 'disabled', 'selected' => 'selected')); 163 | 164 | $options = array($placeholderOption); 165 | foreach ($this->options as $key => $option) { 166 | $options[] = Element::create('option', $option, array('value' => $key)); 167 | } 168 | $this->assertEquals($selectElement->getOptions(), $options); 169 | 170 | $matcher = $this->formFloatingLabelGroup(''.implode('', $options).''); 171 | $this->assertEquals($matcher, $select); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/Laravel/File.php: -------------------------------------------------------------------------------- 1 | 'application/postscript', 12 | 'aif' => 'audio/x-aiff', 13 | 'aifc' => 'audio/x-aiff', 14 | 'aiff' => 'audio/x-aiff', 15 | 'avi' => 'video/x-msvideo', 16 | 'bin' => 'application/macbinary', 17 | 'bmp' => 'image/bmp', 18 | 'class' => 'application/octet-stream', 19 | 'cpt' => 'application/mac-compactpro', 20 | 'css' => 'text/css', 21 | 'csv' => array('text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream'), 22 | 'dcr' => 'application/x-director', 23 | 'dir' => 'application/x-director', 24 | 'dll' => 'application/octet-stream', 25 | 'dms' => 'application/octet-stream', 26 | 'doc' => 'application/msword', 27 | 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 28 | 'dvi' => 'application/x-dvi', 29 | 'dxr' => 'application/x-director', 30 | 'eml' => 'message/rfc822', 31 | 'eps' => 'application/postscript', 32 | 'exe' => array('application/octet-stream', 'application/x-msdownload'), 33 | 'gif' => 'image/gif', 34 | 'gtar' => 'application/x-gtar', 35 | 'gz' => 'application/x-gzip', 36 | 'hqx' => 'application/mac-binhex40', 37 | 'htm' => 'text/html', 38 | 'html' => 'text/html', 39 | 'jpe' => array('image/jpeg', 'image/pjpeg'), 40 | 'jpeg' => array('image/jpeg', 'image/pjpeg'), 41 | 'jpg' => array('image/jpeg', 'image/pjpeg'), 42 | 'js' => 'application/x-javascript', 43 | 'json' => array('application/json', 'text/json'), 44 | 'lha' => 'application/octet-stream', 45 | 'log' => array('text/plain', 'text/x-log'), 46 | 'lzh' => 'application/octet-stream', 47 | 'mid' => 'audio/midi', 48 | 'midi' => 'audio/midi', 49 | 'mif' => 'application/vnd.mif', 50 | 'mov' => 'video/quicktime', 51 | 'movie' => 'video/x-sgi-movie', 52 | 'mp2' => 'audio/mpeg', 53 | 'mp3' => array('audio/mpeg', 'audio/mpg', 'audio/mpeg3', 'audio/mp3'), 54 | 'mpe' => 'video/mpeg', 55 | 'mpeg' => 'video/mpeg', 56 | 'mpg' => 'video/mpeg', 57 | 'mpga' => 'audio/mpeg', 58 | 'oda' => 'application/oda', 59 | 'odp' => 'application/vnd.oasis.opendocument.presentation', 60 | 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', 61 | 'odt' => 'application/vnd.oasis.opendocument.text', 62 | 'pdf' => array('application/pdf', 'application/x-download'), 63 | 'php' => array('application/x-httpd-php', 'text/x-php'), 64 | 'php3' => 'application/x-httpd-php', 65 | 'php4' => 'application/x-httpd-php', 66 | 'phps' => 'application/x-httpd-php-source', 67 | 'phtml' => 'application/x-httpd-php', 68 | 'png' => 'image/png', 69 | 'pps' => array('application/mspowerpoint', 'application/vnd.ms-powerpoint'), 70 | 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 71 | 'ppt' => array('application/vnd.ms-powerpoint', 'application/powerpoint'), 72 | 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 73 | 'ps' => 'application/postscript', 74 | 'psd' => 'application/x-photoshop', 75 | 'qt' => 'video/quicktime', 76 | 'ra' => 'audio/x-realaudio', 77 | 'ram' => 'audio/x-pn-realaudio', 78 | 'rm' => 'audio/x-pn-realaudio', 79 | 'rpm' => 'audio/x-pn-realaudio-plugin', 80 | 'rtf' => array('application/rtf', 'text/rtf'), 81 | 'rtx' => 'text/richtext', 82 | 'rv' => 'video/vnd.rn-realvideo', 83 | 'sea' => 'application/octet-stream', 84 | 'shtml' => 'text/html', 85 | 'sit' => 'application/x-stuffit', 86 | 'smi' => 'application/smil', 87 | 'smil' => 'application/smil', 88 | 'so' => 'application/octet-stream', 89 | 'swf' => 'application/x-shockwave-flash', 90 | 'tar' => 'application/x-tar', 91 | 'text' => 'text/plain', 92 | 'tgz' => array('application/x-tar', 'application/x-gzip-compressed'), 93 | 'tif' => 'image/tiff', 94 | 'tiff' => 'image/tiff', 95 | 'txt' => 'text/plain', 96 | 'wav' => 'audio/x-wav', 97 | 'wbxml' => 'application/wbxml', 98 | 'wmlc' => 'application/wmlc', 99 | 'word' => array('application/msword', 'application/octet-stream'), 100 | 'xht' => 'application/xhtml+xml', 101 | 'xhtml' => 'application/xhtml+xml', 102 | 'xl' => 'application/excel', 103 | 'xls' => array('application/vnd.ms-excel', 'application/excel', 'application/msexcel'), 104 | 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 105 | 'xml' => 'text/xml', 106 | 'xsl' => 'text/xml', 107 | 'zip' => array('application/x-zip', 'application/zip', 'application/x-zip-compressed'), 108 | ); 109 | 110 | /** 111 | * Get a file MIME type by extension. 112 | * 113 | * // Determine the MIME type for the .tar extension 114 | * $mime = File::mime('tar'); 115 | * // Return a default value if the MIME can't be determined 116 | * $mime = File::mime('ext', 'application/octet-stream'); 117 | * 118 | * 119 | * @param string $extension 120 | * @param string $default 121 | * 122 | * @return string 123 | */ 124 | public static function mime($extension, $default = 'application/octet-stream') 125 | { 126 | $mimes = self::$mimes; 127 | 128 | if (!array_key_exists($extension, $mimes)) { 129 | return $default; 130 | } 131 | 132 | return (is_array($mimes[$extension])) ? $mimes[$extension][0] : $mimes[$extension]; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Former/FormerServiceProvider.php: -------------------------------------------------------------------------------- 1 | app = static::make($this->app); 33 | } 34 | 35 | /** 36 | * Get the services provided by the provider. 37 | * 38 | * @return string[] 39 | */ 40 | public function provides() 41 | { 42 | return array('former', 'Former\Former'); 43 | } 44 | 45 | //////////////////////////////////////////////////////////////////// 46 | /////////////////////////// CLASS BINDINGS ///////////////////////// 47 | //////////////////////////////////////////////////////////////////// 48 | 49 | /** 50 | * Create a Former container 51 | * 52 | * @param Container $app 53 | * 54 | * @return Container 55 | */ 56 | public static function make($app = null) 57 | { 58 | if (!$app) { 59 | $app = new Container(); 60 | } 61 | 62 | // Bind classes to container 63 | $provider = new static($app); 64 | $app = $provider->bindCoreClasses($app); 65 | $app = $provider->bindFormer($app); 66 | 67 | return $app; 68 | } 69 | 70 | /** 71 | * Bind the core classes to the Container 72 | * 73 | * @param Container $app 74 | * 75 | * @return Container 76 | */ 77 | public function bindCoreClasses(Container $app) 78 | { 79 | // Cancel if in the scope of a Laravel application 80 | if ($app->bound('events')) { 81 | return $app; 82 | } 83 | 84 | // Core classes 85 | ////////////////////////////////////////////////////////////////// 86 | 87 | $app->bindIf('files', 'Illuminate\Filesystem\Filesystem'); 88 | $app->bindIf('url', 'Illuminate\Routing\UrlGenerator'); 89 | 90 | // Session and request 91 | ////////////////////////////////////////////////////////////////// 92 | 93 | $app->bindIf('session.manager', function ($app) { 94 | return new SessionManager($app); 95 | }); 96 | 97 | $app->bindIf('session', function ($app) { 98 | return $app['session.manager']->driver('array'); 99 | }, true); 100 | 101 | $app->bindIf('request', function ($app) { 102 | $request = Request::createFromGlobals(); 103 | if (method_exists($request, 'setSessionStore')) { 104 | $request->setSessionStore($app['session']); 105 | } else if (method_exists($request, 'setLaravelSession')) { 106 | $request->setLaravelSession($app['session']); 107 | } else { 108 | $request->setSession($app['session']); 109 | } 110 | 111 | return $request; 112 | }, true); 113 | 114 | // Config 115 | ////////////////////////////////////////////////////////////////// 116 | 117 | $app->bindIf('path.config', function ($app) { 118 | return __DIR__ . '/../config/'; 119 | }, true); 120 | 121 | $app->bindIf('config', function ($app) { 122 | $config = new Repository; 123 | $this->loadConfigurationFiles($app, $config); 124 | return $config; 125 | }, true); 126 | 127 | // Localization 128 | ////////////////////////////////////////////////////////////////// 129 | 130 | $app->bindIf('translation.loader', function ($app) { 131 | return new FileLoader($app['files'], 'src/config'); 132 | }); 133 | 134 | $app->bindIf('translator', function ($app) { 135 | $loader = new FileLoader($app['files'], 'lang'); 136 | 137 | return new Translator($loader, 'fr'); 138 | }); 139 | 140 | return $app; 141 | } 142 | 143 | /** 144 | * Load the configuration items from all of the files. 145 | * 146 | * @param Container $app 147 | * @param Repository $config 148 | * @return void 149 | */ 150 | protected function loadConfigurationFiles($app, Repository $config) 151 | { 152 | foreach ($this->getConfigurationFiles($app) as $key => $path) 153 | { 154 | $config->set($key, require $path); 155 | } 156 | } 157 | 158 | /** 159 | * Get all of the configuration files for the application. 160 | * 161 | * @param $app 162 | * @return array 163 | */ 164 | protected function getConfigurationFiles($app) 165 | { 166 | $files = array(); 167 | 168 | foreach (Finder::create()->files()->name('*.php')->in($app['path.config']) as $file) 169 | { 170 | $files[basename($file->getRealPath(), '.php')] = $file->getRealPath(); 171 | } 172 | 173 | return $files; 174 | } 175 | 176 | /** 177 | * Bind Former classes to the container 178 | * 179 | * @param Container $app 180 | * 181 | * @return Container 182 | */ 183 | public function bindFormer(Container $app) 184 | { 185 | // Add config namespace 186 | $configPath = __DIR__ . '/../config/former.php'; 187 | $this->mergeConfigFrom($configPath, 'former'); 188 | $this->publishes([$configPath => $app['path.config'] . '/former.php']); 189 | 190 | $framework = $app['config']->get('former.framework'); 191 | 192 | $app->bind('former.framework', function ($app) { 193 | return $app['former']->getFrameworkInstance($app['config']->get('former.framework')); 194 | }); 195 | 196 | $app->singleton('former.populator', function ($app) { 197 | return new Populator(); 198 | }); 199 | 200 | $app->singleton('former.dispatcher', function ($app) { 201 | return new MethodDispatcher($app, Former::FIELDSPACE); 202 | }); 203 | 204 | $app->singleton('former', function ($app) { 205 | return new Former($app, $app->make('former.dispatcher')); 206 | }); 207 | $app->alias('former', 'Former\Former'); 208 | 209 | Helpers::setApp($app); 210 | 211 | return $app; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/Former/Helpers.php: -------------------------------------------------------------------------------- 1 | get(); 76 | } 77 | 78 | $translation = null; 79 | $translateFrom = static::$app['former']->getOption('translate_from'); 80 | if (substr($translateFrom, -1) !== '/') { 81 | $translateFrom .= '.'; 82 | } 83 | $translateFrom .= $key; 84 | 85 | // Convert a[b[c]] to a.b.c for nested translations [a => [b => 'B!']] 86 | if (strpos($translateFrom, ']') !== false) { 87 | $translateFrom = str_replace(array(']', '['), array('', '.'), $translateFrom); 88 | } 89 | 90 | // Search for the key itself, if it is valid 91 | $validKey = preg_match("/^[a-z0-9_-]+([.][a-z0-9 _-]+)+$/i", $key) === 1; 92 | if ($validKey && static::$app['translator']->has($key)) { 93 | $translation = static::$app['translator']->get($key); 94 | } elseif (static::$app['translator']->has($translateFrom)) { 95 | $translation = static::$app['translator']->get($translateFrom); 96 | } 97 | 98 | // Replace by fallback if invalid 99 | if (!$translation or is_array($translation)) { 100 | $translation = $fallback; 101 | } 102 | 103 | // Capitalize 104 | $capitalize = static::$app['former']->getOption('capitalize_translations'); 105 | 106 | return $capitalize ? ucfirst($translation) : $translation; 107 | } 108 | 109 | //////////////////////////////////////////////////////////////////// 110 | ////////////////////////// DATABASE HELPERS //////////////////////// 111 | //////////////////////////////////////////////////////////////////// 112 | 113 | /** 114 | * Transforms an array of models into an associative array 115 | * 116 | * @param array|object $query The array of results 117 | * @param string|function $text The value to use as text 118 | * @param string|array $attributes The data to use as attributes 119 | * 120 | * @return array A data array 121 | */ 122 | public static function queryToArray($query, $text = null, $attributes = null) 123 | { 124 | // Automatically fetch Lang objects for people who store translated options lists 125 | // Same of unfetched queries 126 | if (!$query instanceof Collection) { 127 | if ((is_object($query) || is_string($query)) && method_exists($query, 'get')) { 128 | $query = $query->get(); 129 | } 130 | if (!is_array($query)) { 131 | $query = (array) $query; 132 | } 133 | } 134 | 135 | //Convert parametrs of old format to new format 136 | if (!is_callable($text)) { 137 | $optionTextValue = $text; 138 | $text = function ($model) use ($optionTextValue) { 139 | if ($optionTextValue and isset($model->$optionTextValue)) { 140 | return $model->$optionTextValue; 141 | } elseif ((is_object($model) || is_string($model)) && method_exists($model, '__toString')) { 142 | return $model->__toString(); 143 | } else { 144 | return null; 145 | } 146 | }; 147 | } 148 | 149 | if (!is_array($attributes)) { 150 | if (is_string($attributes)) { 151 | $attributes = ['value' => $attributes]; 152 | } else { 153 | $attributes = ['value' => null]; 154 | } 155 | } 156 | 157 | if (!isset($attributes['value'])) { 158 | $attributes['value'] = null; 159 | } 160 | //------------------------------------------------- 161 | 162 | // Populates the new options 163 | foreach ($query as $model) { 164 | // If it's an array, convert to object 165 | if (is_array($model)) { 166 | $model = (object) $model; 167 | } 168 | 169 | // Calculate option text 170 | $optionText = $text($model); 171 | 172 | // Skip if no text value found 173 | if (!$optionText) { 174 | continue; 175 | } 176 | 177 | //Collect option attributes 178 | foreach ($attributes as $optionAttributeName => $modelAttributeName) { 179 | if (is_callable($modelAttributeName)) { 180 | $optionAttributeValue = $modelAttributeName($model); 181 | } elseif ($modelAttributeName and isset($model->$modelAttributeName)) { 182 | $optionAttributeValue = $model->$modelAttributeName; 183 | } elseif($optionAttributeName === 'value') { 184 | //For backward compatibility 185 | if (method_exists($model, 'getKey')) { 186 | $optionAttributeValue = $model->getKey(); 187 | } elseif (isset($model->id)) { 188 | $optionAttributeValue = $model->id; 189 | } else { 190 | $optionAttributeValue = $optionText; 191 | } 192 | } else { 193 | $optionAttributeValue = ''; 194 | } 195 | 196 | //For backward compatibility 197 | if (count($attributes) === 1) { 198 | $array[$optionAttributeValue] = (string) $optionText; 199 | } else { 200 | $array[$optionText][$optionAttributeName] = (string) $optionAttributeValue; 201 | } 202 | } 203 | } 204 | 205 | return !empty($array) ? $array : $query; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/Former/MethodDispatcher.php: -------------------------------------------------------------------------------- 1 | app = $app; 38 | $this->repositories = (array) $repositories; 39 | } 40 | 41 | //////////////////////////////////////////////////////////////////// 42 | ///////////////////////////// REPOSITORIES ///////////////////////// 43 | //////////////////////////////////////////////////////////////////// 44 | 45 | /** 46 | * Add a fields repository 47 | * 48 | * @param string $repository 49 | * 50 | * @return $this 51 | */ 52 | public function addRepository($repository) 53 | { 54 | array_unshift($this->repositories, $repository); 55 | 56 | return $this; 57 | } 58 | 59 | //////////////////////////////////////////////////////////////////// 60 | ///////////////////////////// DISPATCHERS ////////////////////////// 61 | //////////////////////////////////////////////////////////////////// 62 | 63 | /** 64 | * Dispatch a call to a registered macro 65 | * 66 | * @param string $method The macro's name 67 | * @param array $parameters The macro's arguments 68 | * 69 | * @return mixed 70 | */ 71 | public function toMacros($method, $parameters) 72 | { 73 | if (!$this->app['former']->hasMacro($method)) { 74 | return false; 75 | } 76 | 77 | // Get and format macro 78 | $callback = $this->app['former']->getMacro($method); 79 | if ($callback instanceof Closure) { 80 | return call_user_func_array($callback, $parameters); 81 | } // Cancel if the macro is invalid 82 | elseif (!is_string($callback)) { 83 | return false; 84 | } 85 | 86 | // Get class and method 87 | list($class, $method) = explode('@', $callback); 88 | $this->app->instance('Illuminate\Container\Container', $this->app); 89 | 90 | return call_user_func_array(array($this->app->make($class), $method), $parameters); 91 | } 92 | 93 | /** 94 | * Dispatch a call over to Elements 95 | * 96 | * @param string $method The method called 97 | * @param array $parameters Its parameters 98 | * 99 | * @return string 100 | */ 101 | public function toElements($method, $parameters) 102 | { 103 | // Disregards if the method isn't an element 104 | if (!method_exists($elements = new Form\Elements($this->app, $this->app['session']), $method)) { 105 | return false; 106 | } 107 | 108 | return call_user_func_array(array($elements, $method), $parameters); 109 | } 110 | 111 | /** 112 | * Dispatch a call over to Form 113 | * 114 | * @param string $method The method called 115 | * @param array $parameters Its parameters 116 | * 117 | * @return Form 118 | */ 119 | public function toForm($method, $parameters) 120 | { 121 | // Disregards if the method doesn't contain 'open' 122 | if (!Str::contains($method, 'open') and !Str::contains($method, 'Open')) { 123 | return false; 124 | } 125 | 126 | $form = new Form\Form($this->app, $this->app['url'], $this->app['former.populator']); 127 | 128 | return $form->openForm($method, $parameters); 129 | } 130 | 131 | /** 132 | * Dispatch a call over to Group 133 | * 134 | * @param string $method The method called 135 | * @param array $parameters Its parameters 136 | * 137 | * @return Group 138 | */ 139 | public function toGroup($method, $parameters) 140 | { 141 | // Disregards if the method isn't "group" 142 | if ($method != 'group') { 143 | return false; 144 | } 145 | 146 | // Create opener 147 | $group = new Form\Group( 148 | $this->app, 149 | Arr::get($parameters, 0, null), 150 | Arr::get($parameters, 1, null) 151 | ); 152 | 153 | // Set custom group as true 154 | Form\Group::$opened = true; 155 | 156 | // Set custom group reference 157 | Form\Group::$openGroup = $group; 158 | 159 | return $group; 160 | } 161 | 162 | /** 163 | * Dispatch a call over to Actions 164 | * 165 | * @param string $method The method called 166 | * @param array $parameters Its parameters 167 | * 168 | * @return Actions 169 | */ 170 | public function toActions($method, $parameters) 171 | { 172 | if ($method != 'actions') { 173 | return false; 174 | } 175 | 176 | return new Form\Actions($this->app, $parameters); 177 | } 178 | 179 | /** 180 | * Dispatch a call over to the Fields 181 | * 182 | * @param string $method The method called 183 | * @param array $parameters Its parameters 184 | * 185 | * @return Field 186 | */ 187 | public function toFields($method, $parameters) 188 | { 189 | // Listing parameters 190 | $class = $this->getClassFromMethod($method); 191 | $field = new $class( 192 | $this->app, 193 | $method, 194 | Arr::get($parameters, 0), 195 | Arr::get($parameters, 1), 196 | Arr::get($parameters, 2), 197 | Arr::get($parameters, 3), 198 | Arr::get($parameters, 4), 199 | Arr::get($parameters, 5) 200 | ); 201 | 202 | return $field; 203 | } 204 | 205 | //////////////////////////////////////////////////////////////////// 206 | ///////////////////////////// HELPERS ////////////////////////////// 207 | //////////////////////////////////////////////////////////////////// 208 | 209 | /** 210 | * Get the correct class to call according to the created field 211 | * 212 | * @param string $method The field created 213 | * 214 | * @return string The correct class 215 | */ 216 | protected function getClassFromMethod($method) 217 | { 218 | // If the field's name directly match a class, call it 219 | $class = Str::singular(Str::title($method)); 220 | $studly_class = Str::singular(Str::studly($method)); 221 | foreach ($this->repositories as $repository) { 222 | if (class_exists($repository.$studly_class)) { 223 | return $repository.$studly_class; 224 | } else { 225 | if (class_exists($repository.$class)) { 226 | return $repository.$class; 227 | } 228 | } 229 | } 230 | 231 | // Else convert known fields to their classes 232 | switch ($method) { 233 | case 'switch': 234 | case 'switches': 235 | $class = Former::FIELDSPACE.'Switchbox'; 236 | break; 237 | case 'submit': 238 | case 'link': 239 | case 'reset': 240 | $class = Former::FIELDSPACE.'Button'; 241 | break; 242 | 243 | case 'multiselect': 244 | $class = Former::FIELDSPACE.'Select'; 245 | break; 246 | 247 | default: 248 | $class = Former::FIELDSPACE.'Input'; 249 | break; 250 | } 251 | 252 | return $class; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /tests/Framework/TwitterBootstrap5Test.php: -------------------------------------------------------------------------------- 1 | former->framework('TwitterBootstrap5'); 14 | $this->former->horizontal_open()->__toString(); 15 | } 16 | 17 | //////////////////////////////////////////////////////////////////// 18 | ////////////////////////////// MATCHERS //////////////////////////// 19 | //////////////////////////////////////////////////////////////////// 20 | 21 | public function hmatch($label, $field) 22 | { 23 | return ''.$label.''.$field.''; 24 | } 25 | 26 | public function vmatch($label, $field) 27 | { 28 | return ''.$label.$field.''; 29 | } 30 | 31 | //////////////////////////////////////////////////////////////////// 32 | //////////////////////////////// TESTS ///////////////////////////// 33 | //////////////////////////////////////////////////////////////////// 34 | 35 | public function testFrameworkIsRecognized() 36 | { 37 | $this->assertNotEquals('TwitterBootstrap', $this->former->framework()); 38 | $this->assertEquals('TwitterBootstrap5', $this->former->framework()); 39 | } 40 | 41 | public function testVerticalFormFieldsDontInheritHorizontalMarkup() 42 | { 43 | $this->former->open_vertical(); 44 | $field = $this->former->text('foo')->__toString(); 45 | $this->former->close(); 46 | 47 | $match = $this->vmatch('Foo', 48 | ''); 49 | 50 | $this->assertEquals($match, $field); 51 | } 52 | 53 | public function testHorizontalFormWithDefaultLabelWidths() 54 | { 55 | $field = $this->former->text('foo')->__toString(); 56 | $match = $this->hmatch('Foo', 57 | ''); 58 | 59 | $this->assertEquals($match, $field); 60 | } 61 | 62 | public function testPrependIcon() 63 | { 64 | $this->former->open_vertical(); 65 | $icon = $this->former->text('foo')->prependIcon('thumbs-up')->__toString(); 66 | $match = $this->vmatch('Foo', 67 | ''. 68 | ''. 69 | ''. 70 | ''); 71 | 72 | $this->assertEquals($match, $icon); 73 | } 74 | 75 | public function testAppendIcon() 76 | { 77 | $this->former->open_vertical(); 78 | $icon = $this->former->text('foo')->appendIcon('thumbs-up')->__toString(); 79 | $match = $this->vmatch('Foo', 80 | ''. 81 | ''. 82 | ''. 83 | ''); 84 | $this->assertEquals($match, $icon); 85 | } 86 | 87 | public function testTextFieldsGetControlClass() 88 | { 89 | $this->former->open_vertical(); 90 | $field = $this->former->text('foo')->__toString(); 91 | $match = $this->vmatch('Foo', 92 | ''); 93 | 94 | $this->assertEquals($match, $field); 95 | } 96 | 97 | public function testButtonSizes() 98 | { 99 | $this->former->open_vertical(); 100 | $buttons = $this->former->actions()->lg_submit('Submit')->submit('Submit')->sm_submit('Submit')->xs_submit('Submit')->__toString(); 101 | $match = ''. 102 | ''. 103 | ' '. 104 | ' '. 105 | ' '. 106 | ''; 107 | 108 | $this->assertEquals($match, $buttons); 109 | } 110 | 111 | public function testCanOverrideFrameworkIconSettings() 112 | { 113 | // e.g. using other Glyphicon sets 114 | $icon1 = $this->app['former.framework']->createIcon('facebook', null, array( 115 | 'tag' => 'span', 116 | 'set' => 'social', 117 | 'prefix' => 'glyphicon', 118 | ))->__toString(); 119 | $match1 = ''; 120 | 121 | $this->assertEquals($match1, $icon1); 122 | 123 | // e.g using Font-Awesome circ v3.2.1 124 | $icon2 = $this->app['former.framework']->createIcon('flag', null, array( 125 | 'tag' => 'i', 126 | 'set' => '', 127 | 'prefix' => 'icon', 128 | ))->__toString(); 129 | $match2 = ''; 130 | 131 | $this->assertEquals($match2, $icon2); 132 | } 133 | 134 | public function testCanCreateWithErrors() 135 | { 136 | $this->former->open_vertical(); 137 | $this->former->withErrors($this->validator); 138 | 139 | $required = $this->former->text('required')->__toString(); 140 | $matcher = 141 | ''. 142 | 'Required'. 143 | ''. 144 | 'The required field is required.'. 145 | ''; 146 | 147 | $this->assertEquals($matcher, $required); 148 | } 149 | 150 | public function testAddScreenReaderClassToInlineFormLabels() 151 | { 152 | $this->former->open_inline(); 153 | 154 | $field = $this->former->text('foo')->__toString(); 155 | 156 | $match = 157 | ''. 158 | 'Foo'. 159 | ''. 160 | ''; 161 | 162 | $this->assertEquals($match, $field); 163 | $this->assertEquals($match, $field); 164 | 165 | $this->former->close(); 166 | } 167 | 168 | public function testHeightSettingForFields() 169 | { 170 | $this->former->open_vertical(); 171 | 172 | $field = $this->former->lg_text('foo')->__toString(); 173 | $match = 174 | ''. 175 | 'Foo'. 176 | ''. 177 | ''; 178 | $this->assertEquals($match, $field); 179 | 180 | $this->resetLabels(); 181 | $field = $this->former->sm_select('foo')->__toString(); 182 | $match = 183 | ''. 184 | 'Foo'. 185 | ''. 186 | ''; 187 | $this->assertEquals($match, $field); 188 | 189 | $this->former->close(); 190 | } 191 | 192 | public function testAddFormControlClassToInlineActionsBlock() 193 | { 194 | $this->former->open_inline(); 195 | $buttons = $this->former->actions()->submit('Foo')->__toString(); 196 | $match = ''. 197 | ''. 198 | ''; 199 | 200 | $this->assertEquals($match, $buttons); 201 | 202 | $this->former->close(); 203 | } 204 | 205 | public function testButtonsAreNotWrapped() 206 | { 207 | $button = $this->former->text('foo')->append($this->former->button('Search'))->wrapAndRender(); 208 | $matcher = 'Search'; 209 | 210 | $this->assertStringContainsString($matcher, $button); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /tests/Framework/TwitterBootstrap3Test.php: -------------------------------------------------------------------------------- 1 | former->framework('TwitterBootstrap3'); 14 | $this->former->horizontal_open()->__toString(); 15 | } 16 | 17 | //////////////////////////////////////////////////////////////////// 18 | ////////////////////////////// MATCHERS //////////////////////////// 19 | //////////////////////////////////////////////////////////////////// 20 | 21 | public function hmatch($label, $field) 22 | { 23 | return ''.$label.''.$field.''; 24 | } 25 | 26 | public function vmatch($label, $field) 27 | { 28 | return ''.$label.$field.''; 29 | } 30 | 31 | //////////////////////////////////////////////////////////////////// 32 | //////////////////////////////// TESTS ///////////////////////////// 33 | //////////////////////////////////////////////////////////////////// 34 | 35 | public function testFrameworkIsRecognized() 36 | { 37 | $this->assertNotEquals('TwitterBootstrap', $this->former->framework()); 38 | $this->assertEquals('TwitterBootstrap3', $this->former->framework()); 39 | } 40 | 41 | public function testVerticalFormFieldsDontInheritHorizontalMarkup() 42 | { 43 | $this->former->open_vertical(); 44 | $field = $this->former->text('foo')->__toString(); 45 | $this->former->close(); 46 | 47 | $match = $this->vmatch('Foo', 48 | ''); 49 | 50 | $this->assertEquals($match, $field); 51 | } 52 | 53 | public function testHorizontalFormWithDefaultLabelWidths() 54 | { 55 | $field = $this->former->text('foo')->__toString(); 56 | $match = $this->hmatch('Foo', 57 | ''); 58 | 59 | $this->assertEquals($match, $field); 60 | } 61 | 62 | public function testPrependIcon() 63 | { 64 | $this->former->open_vertical(); 65 | $icon = $this->former->text('foo')->prependIcon('ok')->__toString(); 66 | $match = $this->vmatch('Foo', 67 | ''. 68 | ''. 69 | ''. 70 | ''); 71 | 72 | $this->assertEquals($match, $icon); 73 | } 74 | 75 | public function testAppendIcon() 76 | { 77 | $this->former->open_vertical(); 78 | $icon = $this->former->text('foo')->appendIcon('ok')->__toString(); 79 | $match = $this->vmatch('Foo', 80 | ''. 81 | ''. 82 | ''. 83 | ''); 84 | $this->assertEquals($match, $icon); 85 | } 86 | 87 | public function testTextFieldsGetControlClass() 88 | { 89 | $this->former->open_vertical(); 90 | $field = $this->former->text('foo')->__toString(); 91 | $match = $this->vmatch('Foo', 92 | ''); 93 | 94 | $this->assertEquals($match, $field); 95 | } 96 | 97 | public function testButtonSizes() 98 | { 99 | $this->former->open_vertical(); 100 | $buttons = $this->former->actions()->lg_submit('Submit')->submit('Submit')->sm_submit('Submit')->xs_submit('Submit')->__toString(); 101 | $match = ''. 102 | ''. 103 | ' '. 104 | ' '. 105 | ' '. 106 | ''; 107 | 108 | $this->assertEquals($match, $buttons); 109 | } 110 | 111 | public function testCanOverrideFrameworkIconSettings() 112 | { 113 | // e.g. using other Glyphicon sets 114 | $icon1 = $this->app['former.framework']->createIcon('facebook', null, array( 115 | 'set' => 'social', 116 | 'prefix' => 'glyphicon', 117 | ))->__toString(); 118 | $match1 = ''; 119 | 120 | $this->assertEquals($match1, $icon1); 121 | 122 | // e.g using Font-Awesome circ v3.2.1 123 | $icon2 = $this->app['former.framework']->createIcon('flag', null, array( 124 | 'tag' => 'i', 125 | 'set' => '', 126 | 'prefix' => 'icon', 127 | ))->__toString(); 128 | $match2 = ''; 129 | 130 | $this->assertEquals($match2, $icon2); 131 | } 132 | 133 | public function testCanCreateWithErrors() 134 | { 135 | $this->former->open_vertical(); 136 | $this->former->withErrors($this->validator); 137 | 138 | $required = $this->former->text('required')->__toString(); 139 | $matcher = 140 | ''. 141 | 'Required'. 142 | ''. 143 | 'The required field is required.'. 144 | ''; 145 | 146 | $this->assertEquals($matcher, $required); 147 | } 148 | 149 | public function testAddScreenReaderClassToInlineFormLabels() 150 | { 151 | $this->former->open_inline(); 152 | 153 | $field = $this->former->text('foo')->__toString(); 154 | 155 | $match = 156 | ''. 157 | 'Foo'. 158 | ''. 159 | ''; 160 | 161 | $this->assertEquals($match, $field); 162 | $this->assertEquals($match, $field); 163 | 164 | $this->former->close(); 165 | } 166 | 167 | public function testHeightSettingForFields() 168 | { 169 | $this->former->open_vertical(); 170 | 171 | $field = $this->former->lg_text('foo')->__toString(); 172 | $match = 173 | ''. 174 | 'Foo'. 175 | ''. 176 | ''; 177 | $this->assertEquals($match, $field); 178 | 179 | $this->resetLabels(); 180 | $field = $this->former->sm_select('foo')->__toString(); 181 | $match = 182 | ''. 183 | 'Foo'. 184 | ''. 185 | ''; 186 | $this->assertEquals($match, $field); 187 | 188 | $this->former->close(); 189 | } 190 | 191 | public function testAddFormControlClassToInlineActionsBlock() 192 | { 193 | $this->former->open_inline(); 194 | $buttons = $this->former->actions()->submit('Foo')->__toString(); 195 | $match = ''. 196 | ''. 197 | ''; 198 | 199 | $this->assertEquals($match, $buttons); 200 | 201 | $this->former->close(); 202 | } 203 | 204 | public function testButtonsAreWrappedInSpecialClass() 205 | { 206 | $button = $this->former->text('foo')->append($this->former->button('Search'))->wrapAndRender(); 207 | $matcher = 'Search'; 208 | 209 | $this->assertStringContainsString($matcher, $button); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /tests/Framework/TwitterBootstrap4Test.php: -------------------------------------------------------------------------------- 1 | former->framework('TwitterBootstrap4'); 14 | $this->former->horizontal_open()->__toString(); 15 | } 16 | 17 | //////////////////////////////////////////////////////////////////// 18 | ////////////////////////////// MATCHERS //////////////////////////// 19 | //////////////////////////////////////////////////////////////////// 20 | 21 | public function hmatch($label, $field) 22 | { 23 | return ''.$label.''.$field.''; 24 | } 25 | 26 | public function vmatch($label, $field) 27 | { 28 | return ''.$label.$field.''; 29 | } 30 | 31 | //////////////////////////////////////////////////////////////////// 32 | //////////////////////////////// TESTS ///////////////////////////// 33 | //////////////////////////////////////////////////////////////////// 34 | 35 | public function testFrameworkIsRecognized() 36 | { 37 | $this->assertNotEquals('TwitterBootstrap', $this->former->framework()); 38 | $this->assertEquals('TwitterBootstrap4', $this->former->framework()); 39 | } 40 | 41 | public function testVerticalFormFieldsDontInheritHorizontalMarkup() 42 | { 43 | $this->former->open_vertical(); 44 | $field = $this->former->text('foo')->__toString(); 45 | $this->former->close(); 46 | 47 | $match = $this->vmatch('Foo', 48 | ''); 49 | 50 | $this->assertEquals($match, $field); 51 | } 52 | 53 | public function testHorizontalFormWithDefaultLabelWidths() 54 | { 55 | $field = $this->former->text('foo')->__toString(); 56 | $match = $this->hmatch('Foo', 57 | ''); 58 | 59 | $this->assertEquals($match, $field); 60 | } 61 | 62 | public function testPrependIcon() 63 | { 64 | $this->former->open_vertical(); 65 | $icon = $this->former->text('foo')->prependIcon('thumbs-up')->__toString(); 66 | $match = $this->vmatch('Foo', 67 | ''. 68 | ''. 69 | ''. 70 | ''); 71 | 72 | $this->assertEquals($match, $icon); 73 | } 74 | 75 | public function testAppendIcon() 76 | { 77 | $this->former->open_vertical(); 78 | $icon = $this->former->text('foo')->appendIcon('thumbs-up')->__toString(); 79 | $match = $this->vmatch('Foo', 80 | ''. 81 | ''. 82 | ''. 83 | ''); 84 | $this->assertEquals($match, $icon); 85 | } 86 | 87 | public function testTextFieldsGetControlClass() 88 | { 89 | $this->former->open_vertical(); 90 | $field = $this->former->text('foo')->__toString(); 91 | $match = $this->vmatch('Foo', 92 | ''); 93 | 94 | $this->assertEquals($match, $field); 95 | } 96 | 97 | public function testButtonSizes() 98 | { 99 | $this->former->open_vertical(); 100 | $buttons = $this->former->actions()->lg_submit('Submit')->submit('Submit')->sm_submit('Submit')->xs_submit('Submit')->__toString(); 101 | $match = ''. 102 | ''. 103 | ' '. 104 | ' '. 105 | ' '. 106 | ''; 107 | 108 | $this->assertEquals($match, $buttons); 109 | } 110 | 111 | public function testCanOverrideFrameworkIconSettings() 112 | { 113 | // e.g. using other Glyphicon sets 114 | $icon1 = $this->app['former.framework']->createIcon('facebook', null, array( 115 | 'tag' => 'span', 116 | 'set' => 'social', 117 | 'prefix' => 'glyphicon', 118 | ))->__toString(); 119 | $match1 = ''; 120 | 121 | $this->assertEquals($match1, $icon1); 122 | 123 | // e.g using Font-Awesome circ v3.2.1 124 | $icon2 = $this->app['former.framework']->createIcon('flag', null, array( 125 | 'tag' => 'i', 126 | 'set' => '', 127 | 'prefix' => 'icon', 128 | ))->__toString(); 129 | $match2 = ''; 130 | 131 | $this->assertEquals($match2, $icon2); 132 | } 133 | 134 | public function testCanCreateWithErrors() 135 | { 136 | $this->former->open_vertical(); 137 | $this->former->withErrors($this->validator); 138 | 139 | $required = $this->former->text('required')->__toString(); 140 | $matcher = 141 | ''. 142 | 'Required'. 143 | ''. 144 | 'The required field is required.'. 145 | ''; 146 | 147 | $this->assertEquals($matcher, $required); 148 | } 149 | 150 | public function testAddScreenReaderClassToInlineFormLabels() 151 | { 152 | $this->former->open_inline(); 153 | 154 | $field = $this->former->text('foo')->__toString(); 155 | 156 | $match = 157 | ''. 158 | 'Foo'. 159 | ''. 160 | ''; 161 | 162 | $this->assertEquals($match, $field); 163 | $this->assertEquals($match, $field); 164 | 165 | $this->former->close(); 166 | } 167 | 168 | public function testHeightSettingForFields() 169 | { 170 | $this->former->open_vertical(); 171 | 172 | $field = $this->former->lg_text('foo')->__toString(); 173 | $match = 174 | ''. 175 | 'Foo'. 176 | ''. 177 | ''; 178 | $this->assertEquals($match, $field); 179 | 180 | $this->resetLabels(); 181 | $field = $this->former->sm_select('foo')->__toString(); 182 | $match = 183 | ''. 184 | 'Foo'. 185 | ''. 186 | ''; 187 | $this->assertEquals($match, $field); 188 | 189 | $this->former->close(); 190 | } 191 | 192 | public function testAddFormControlClassToInlineActionsBlock() 193 | { 194 | $this->former->open_inline(); 195 | $buttons = $this->former->actions()->submit('Foo')->__toString(); 196 | $match = ''. 197 | ''. 198 | ''; 199 | 200 | $this->assertEquals($match, $buttons); 201 | 202 | $this->former->close(); 203 | } 204 | 205 | public function testButtonsAreNotWrapped() 206 | { 207 | $button = $this->former->text('foo')->append($this->former->button('Search'))->wrapAndRender(); 208 | $matcher = 'Search'; 209 | 210 | $this->assertStringContainsString($matcher, $button); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/config/former.php: -------------------------------------------------------------------------------- 1 | true, 8 | 9 | // The default form type 10 | 'default_form_type' => 'horizontal', 11 | 12 | // Whether Former should escape HTML tags of 'plaintext' fields value 13 | // Enabled by default 14 | // 15 | // Instead of disabled this option, you should use the 'HtmlString' class: 16 | // Former::plaintext('text') 17 | // ->forceValue( 18 | // new Illuminate\Support\HtmlString('your HTML data') 19 | // ) 20 | 'escape_plaintext_value' => true, 21 | 22 | // Validation 23 | //////////////////////////////////////////////////////////////////// 24 | 25 | // Whether Former should fetch errors from Session 26 | 'fetch_errors' => true, 27 | 28 | // Whether Former should try to apply Validator rules as attributes 29 | 'live_validation' => true, 30 | 31 | // Whether Former should automatically fetch error messages and 32 | // display them next to the matching fields 33 | 'error_messages' => true, 34 | 35 | // Checkables 36 | //////////////////////////////////////////////////////////////////// 37 | 38 | // Whether checkboxes should always be present in the POST data, 39 | // no matter if you checked them or not 40 | 'push_checkboxes' => false, 41 | 42 | // The value a checkbox will have in the POST array if unchecked 43 | 'unchecked_value' => 0, 44 | 45 | // Required fields 46 | //////////////////////////////////////////////////////////////////// 47 | 48 | // The class to be added to required fields 49 | 'required_class' => 'required', 50 | 51 | // A facultative text to append to the labels of required fields 52 | 'required_text' => '*', 53 | 54 | // Translations 55 | //////////////////////////////////////////////////////////////////// 56 | 57 | // Where Former should look for translations 58 | 'translate_from' => 'validation.attributes', 59 | 60 | // Whether text that comes out of the translated 61 | // should be capitalized (ex: email => Email) automatically 62 | 'capitalize_translations' => true, 63 | 64 | // An array of attributes to automatically translate 65 | 'translatable' => array( 66 | 'help', 67 | 'inlineHelp', 68 | 'blockHelp', 69 | 'placeholder', 70 | 'data_placeholder', 71 | 'label', 72 | ), 73 | 74 | // Framework 75 | //////////////////////////////////////////////////////////////////// 76 | 77 | // The framework to be used by Former 78 | 'framework' => 'TwitterBootstrap3', 79 | 80 | 'TwitterBootstrap5' => array( 81 | 82 | // Map Former-supported viewports to Bootstrap 5 equivalents 83 | 'viewports' => array( 84 | 'large' => 'lg', 85 | 'medium' => 'md', 86 | 'small' => 'sm', 87 | 'mini' => 'xs', 88 | ), 89 | // Width of labels for horizontal forms expressed as viewport => grid columns 90 | 'labelWidths' => array( 91 | 'large' => 2, 92 | 'small' => 4, 93 | ), 94 | // HTML markup and classes used by Bootstrap 5 for icons 95 | 'icon' => array( 96 | 'tag' => 'i', 97 | 'set' => 'fa', 98 | 'prefix' => 'fa', 99 | ), 100 | 101 | ), 102 | 103 | 'TwitterBootstrap4' => array( 104 | 105 | // Map Former-supported viewports to Bootstrap 4 equivalents 106 | 'viewports' => array( 107 | 'large' => 'lg', 108 | 'medium' => 'md', 109 | 'small' => 'sm', 110 | 'mini' => 'xs', 111 | ), 112 | // Width of labels for horizontal forms expressed as viewport => grid columns 113 | 'labelWidths' => array( 114 | 'large' => 2, 115 | 'small' => 4, 116 | ), 117 | // HTML markup and classes used by Bootstrap 4 for icons 118 | 'icon' => array( 119 | 'tag' => 'i', 120 | 'set' => 'fa', 121 | 'prefix' => 'fa', 122 | ), 123 | 124 | ), 125 | 126 | 'TwitterBootstrap3' => array( 127 | 128 | // Map Former-supported viewports to Bootstrap 3 equivalents 129 | 'viewports' => array( 130 | 'large' => 'lg', 131 | 'medium' => 'md', 132 | 'small' => 'sm', 133 | 'mini' => 'xs', 134 | ), 135 | // Width of labels for horizontal forms expressed as viewport => grid columns 136 | 'labelWidths' => array( 137 | 'large' => 2, 138 | 'small' => 4, 139 | ), 140 | // HTML markup and classes used by Bootstrap 3 for icons 141 | 'icon' => array( 142 | 'tag' => 'span', 143 | 'set' => 'glyphicon', 144 | 'prefix' => 'glyphicon', 145 | ), 146 | 147 | ), 148 | 149 | 'Nude' => array( // No-framework markup 150 | 'icon' => array( 151 | 'tag' => 'i', 152 | 'set' => null, 153 | 'prefix' => 'icon', 154 | ), 155 | ), 156 | 157 | 'TwitterBootstrap' => array( // Twitter Bootstrap version 2 158 | 'icon' => array( 159 | 'tag' => 'i', 160 | 'set' => null, 161 | 'prefix' => 'icon', 162 | ), 163 | ), 164 | 165 | 'ZurbFoundation5' => array( 166 | // Map Former-supported viewports to Foundation 5 equivalents 167 | 'viewports' => array( 168 | 'large' => 'large', 169 | 'medium' => null, 170 | 'small' => 'small', 171 | 'mini' => null, 172 | ), 173 | // Width of labels for horizontal forms expressed as viewport => grid columns 174 | 'labelWidths' => array( 175 | 'small' => 3, 176 | ), 177 | // Classes to be applied to wrapped labels in horizontal forms 178 | 'wrappedLabelClasses' => array('right', 'inline'), 179 | // HTML markup and classes used by Foundation 5 for icons 180 | 'icon' => array( 181 | 'tag' => 'i', 182 | 'set' => null, 183 | 'prefix' => 'fi', 184 | ), 185 | // CSS for inline validation errors 186 | 'error_classes' => array('class' => 'error'), 187 | ), 188 | 189 | 'ZurbFoundation4' => array( 190 | // Foundation 4 also has an experimental "medium" breakpoint 191 | // explained at http://foundation.zurb.com/docs/components/grid.html 192 | 'viewports' => array( 193 | 'large' => 'large', 194 | 'medium' => null, 195 | 'small' => 'small', 196 | 'mini' => null, 197 | ), 198 | // Width of labels for horizontal forms expressed as viewport => grid columns 199 | 'labelWidths' => array( 200 | 'small' => 3, 201 | ), 202 | // Classes to be applied to wrapped labels in horizontal forms 203 | 'wrappedLabelClasses' => array('right', 'inline'), 204 | // HTML markup and classes used by Foundation 4 for icons 205 | 'icon' => array( 206 | 'tag' => 'i', 207 | 'set' => 'general', 208 | 'prefix' => 'foundicon', 209 | ), 210 | // CSS for inline validation errors 211 | 'error_classes' => array('class' => 'alert-box radius warning'), 212 | ), 213 | 214 | 'ZurbFoundation' => array( // Foundation 3 215 | 'viewports' => array( 216 | 'large' => '', 217 | 'medium' => null, 218 | 'small' => 'mobile-', 219 | 'mini' => null, 220 | ), 221 | // Width of labels for horizontal forms expressed as viewport => grid columns 222 | 'labelWidths' => array( 223 | 'large' => 2, 224 | 'small' => 4, 225 | ), 226 | // Classes to be applied to wrapped labels in horizontal forms 227 | 'wrappedLabelClasses' => array('right', 'inline'), 228 | // HTML markup and classes used by Foundation 3 for icons 229 | 'icon' => array( 230 | 'tag' => 'i', 231 | 'set' => null, 232 | 'prefix' => 'fi', 233 | ), 234 | // CSS for inline validation errors 235 | // should work for Zurb 2 and 3 236 | 'error_classes' => array('class' => 'alert-box alert error'), 237 | ), 238 | 239 | 240 | ); 241 | -------------------------------------------------------------------------------- /tests/Fields/PlainTextTest.php: -------------------------------------------------------------------------------- 1 | 'label', 23 | 'attributes' => array('for' => 'foo'), 24 | ); 25 | } 26 | 27 | /** 28 | * Matches a plain label without 'for' attribute 29 | * 30 | * @return array 31 | */ 32 | public function matchPlainLabelWithBS3() 33 | { 34 | return array( 35 | 'tag' => 'label', 36 | ); 37 | } 38 | 39 | /** 40 | * Matches an plain text fallback input 41 | * Which is a disabled input 42 | * 43 | * @return array 44 | */ 45 | public function matchPlainTextFallbackInput() 46 | { 47 | return array( 48 | 'tag' => 'input', 49 | 'attributes' => array( 50 | 'disabled' => 'disabled', 51 | 'type' => 'text', 52 | 'name' => 'foo', 53 | 'value' => 'bar', 54 | 'id' => 'foo', 55 | ), 56 | ); 57 | } 58 | 59 | /** 60 | * Matches an plain text input as a div tag 61 | * 62 | * @return array 63 | */ 64 | public function matchPlainTextInput() 65 | { 66 | return array( 67 | 'tag' => 'div', 68 | 'content' => 'bar', 69 | 'attributes' => array( 70 | 'class' => 'form-control-static', 71 | ), 72 | ); 73 | } 74 | 75 | /** 76 | * Matches an plain text input as a p tag 77 | * 78 | * @return array 79 | */ 80 | public function matchPlainTextInputWithBS4() 81 | { 82 | return array( 83 | 'tag' => 'div', 84 | 'content' => 'bar', 85 | 'attributes' => array( 86 | 'class' => 'form-control-plaintext', 87 | ), 88 | ); 89 | } 90 | 91 | /** 92 | * Matches an plain text input as a div tag 93 | * 94 | * @return array 95 | */ 96 | public function matchPlainTextInputWithHtmlValueEscaped() 97 | { 98 | return array( 99 | 'tag' => 'div', 100 | 'content' => '', 101 | 'attributes' => array( 102 | 'class' => 'form-control-static', 103 | ), 104 | ); 105 | } 106 | 107 | /** 108 | * Matches an plain text input as a div tag 109 | * 110 | * @return array 111 | */ 112 | public function matchPlainTextInputWithHtmlValue() 113 | { 114 | return array( 115 | 'tag' => 'div', 116 | 'child' => array( 117 | 'tag' => 'strong', 118 | 'content' => 'bar', 119 | ), 120 | 'attributes' => array( 121 | 'class' => 'form-control-static', 122 | ), 123 | ); 124 | } 125 | 126 | //////////////////////////////////////////////////////////////////// 127 | ////////////////////////////// ASSERTIONS ////////////////////////// 128 | //////////////////////////////////////////////////////////////////// 129 | 130 | /** 131 | * Matches a Form Static Group 132 | * 133 | * @param string $input 134 | * @param string $label 135 | * 136 | * @return boolean 137 | */ 138 | protected function formStaticGroup( 139 | $input = 'bar', 140 | $label = 'Foo' 141 | ) { 142 | return $this->formGroup($input, $label); 143 | } 144 | 145 | /** 146 | * Matches a Form Static Group 147 | * 148 | * @param string $input 149 | * @param string $label 150 | * 151 | * @return boolean 152 | */ 153 | protected function formStaticGroupForBS4( 154 | $input = 'bar', 155 | $label = 'Foo' 156 | ) { 157 | return $this->formGroupWithBS4($input, $label); 158 | } 159 | 160 | /** 161 | * Matches a Form Static Group with HTML value escaped 162 | * 163 | * @param string $input 164 | * @param string $label 165 | * 166 | * @return boolean 167 | */ 168 | protected function formStaticGroupWithHtmlValueEscaped( 169 | $input = '<script>alert(1);</script>', 170 | $label = 'Foo' 171 | ) { 172 | return $this->formGroup($input, $label); 173 | } 174 | 175 | /** 176 | * Matches a Form Static Group with HTML value 177 | * 178 | * @param string $input 179 | * @param string $label 180 | * 181 | * @return boolean 182 | */ 183 | protected function formStaticGroupWithHtmlValue( 184 | $input = 'bar', 185 | $label = 'Foo' 186 | ) { 187 | return $this->formGroup($input, $label); 188 | } 189 | 190 | //////////////////////////////////////////////////////////////////// 191 | //////////////////////////////// TESTS ///////////////////////////// 192 | //////////////////////////////////////////////////////////////////// 193 | 194 | public function testCanCreatePlainTextFallbackInputFields() 195 | { 196 | $this->former->framework('Nude'); 197 | $nude = $this->former->plaintext('foo')->value('bar')->__toString(); 198 | 199 | $this->assertHTML($this->matchPlainLabel(), $nude); 200 | $this->assertHTML($this->matchPlainTextFallbackInput(), $nude); 201 | 202 | $this->resetLabels(); 203 | $this->former->framework('ZurbFoundation'); 204 | $zurb = $this->former->plaintext('foo')->value('bar')->__toString(); 205 | 206 | $this->assertHTML($this->matchPlainLabel(), $zurb); 207 | $this->assertHTML($this->matchPlainTextFallbackInput(), $zurb); 208 | } 209 | 210 | public function testCanCreatePlainTextFieldsWithBS3() 211 | { 212 | $this->former->framework('TwitterBootstrap3'); 213 | $input = $this->former->plaintext('foo')->value('bar')->__toString(); 214 | 215 | $this->assertHTML($this->matchPlainLabelWithBS3(), $input); 216 | $this->assertHTML($this->matchPlainTextInput(), $input); 217 | 218 | $matcher = $this->formStaticGroup(); 219 | $this->assertEquals($matcher, $input); 220 | } 221 | 222 | public function testCanCreatePlainTextFieldsWithBS4() 223 | { 224 | $this->former->framework('TwitterBootstrap4'); 225 | $input = $this->former->plaintext('foo')->value('bar')->__toString(); 226 | 227 | $this->assertHTML($this->matchPlainLabelWithBS3(), $input); 228 | $this->assertHTML($this->matchPlainTextInputWithBS4(), $input); 229 | 230 | $matcher = $this->formStaticGroupForBS4(); 231 | $this->assertEquals($matcher, $input); 232 | } 233 | 234 | public function testCanCreatePlainTextFieldsWithHtmlValueEscaped() 235 | { 236 | $this->former->framework('TwitterBootstrap3'); 237 | $htmlValue = ''; 238 | $input = $this->former->plaintext('foo')->value($htmlValue)->__toString(); 239 | 240 | $this->assertHTML($this->matchPlainLabelWithBS3(), $input); 241 | $this->assertHTML($this->matchPlainTextInputWithHtmlValueEscaped(), $input); 242 | 243 | $matcher = $this->formStaticGroupWithHtmlValueEscaped(); 244 | $this->assertEquals($matcher, $input); 245 | } 246 | 247 | public function testCanCreatePlainTextFieldsWithHtmlValue() 248 | { 249 | $this->former->framework('TwitterBootstrap3'); 250 | $htmlValue = new HtmlString('bar'); 251 | $input = $this->former->plaintext('foo')->value($htmlValue)->__toString(); 252 | 253 | $this->assertHTML($this->matchPlainLabelWithBS3(), $input); 254 | $this->assertHTML($this->matchPlainTextInputWithHtmlValue(), $input); 255 | 256 | $matcher = $this->formStaticGroupWithHtmlValue(); 257 | $this->assertEquals($matcher, $input); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/Former/LiveValidation.php: -------------------------------------------------------------------------------- 1 | field = $field; 27 | } 28 | 29 | /** 30 | * Apply live validation rules to a field 31 | * 32 | * @param array $rules The rules to apply 33 | */ 34 | public function apply($rules) 35 | { 36 | // If no rules to apply, cancel 37 | if (!$rules) { 38 | return false; 39 | } 40 | 41 | foreach ($rules as $rule => $parameters) { 42 | 43 | // If the rule is unsupported yet, skip it 44 | if (!method_exists($this, $rule)) { 45 | continue; 46 | } 47 | 48 | $this->$rule($parameters); 49 | } 50 | } 51 | 52 | //////////////////////////////////////////////////////////////////// 53 | //////////////////////////////// RULES ///////////////////////////// 54 | //////////////////////////////////////////////////////////////////// 55 | 56 | // Field types 57 | //////////////////////////////////////////////////////////////////// 58 | 59 | /** 60 | * Email field 61 | */ 62 | public function email() 63 | { 64 | $this->field->setType('email'); 65 | } 66 | 67 | /** 68 | * URL field 69 | */ 70 | public function url() 71 | { 72 | $this->field->setType('url'); 73 | } 74 | 75 | /** 76 | * Required field 77 | */ 78 | public function required() 79 | { 80 | $this->field->required(); 81 | } 82 | 83 | // Patterns 84 | //////////////////////////////////////////////////////////////////// 85 | 86 | /** 87 | * Integer field 88 | */ 89 | public function integer() 90 | { 91 | $this->field->pattern('\d+'); 92 | } 93 | 94 | /** 95 | * Numeric field 96 | */ 97 | public function numeric() 98 | { 99 | if ($this->field->isOfType('number')) { 100 | $this->field->step('any'); 101 | } else { 102 | $this->field->pattern('[+-]?\d*\.?\d+'); 103 | } 104 | } 105 | 106 | /** 107 | * Not numeric field 108 | */ 109 | public function not_numeric() 110 | { 111 | $this->field->pattern('\D+'); 112 | } 113 | 114 | /** 115 | * Only alphanumerical 116 | */ 117 | public function alpha() 118 | { 119 | $this->field->pattern('[a-zA-Z]+'); 120 | } 121 | 122 | /** 123 | * Only alphanumerical and numbers 124 | */ 125 | public function alpha_num() 126 | { 127 | $this->field->pattern('[a-zA-Z0-9]+'); 128 | } 129 | 130 | /** 131 | * Alphanumerical, numbers and dashes 132 | */ 133 | public function alpha_dash() 134 | { 135 | $this->field->pattern('[a-zA-Z0-9_\-]+'); 136 | } 137 | 138 | /** 139 | * In [] 140 | */ 141 | public function in($possible) 142 | { 143 | // Create the corresponding regex 144 | $possible = (sizeof($possible) == 1) ? $possible[0] : '('.join('|', $possible).')'; 145 | 146 | $this->field->pattern('^'.$possible.'$'); 147 | } 148 | 149 | /** 150 | * Not in [] 151 | */ 152 | public function not_in($impossible) 153 | { 154 | $this->field->pattern('(?:(?!^'.join('$|^', $impossible).'$).)*'); 155 | } 156 | 157 | /** 158 | * Matches a pattern 159 | */ 160 | public function match($pattern) 161 | { 162 | // Remove delimiters from existing regex 163 | $pattern = substr($pattern[0], 1, -1); 164 | 165 | $this->field->pattern($pattern); 166 | } 167 | 168 | /** 169 | * Alias for match 170 | */ 171 | public function regex($pattern) 172 | { 173 | return $this->match($pattern); 174 | } 175 | 176 | // Boundaries 177 | //////////////////////////////////////////////////////////////////// 178 | 179 | /** 180 | * Max value 181 | */ 182 | public function max($max) 183 | { 184 | if ($this->field->isOfType('file')) { 185 | $this->size($max); 186 | } else { 187 | $this->setMax($max[0]); 188 | } 189 | } 190 | 191 | /** 192 | * Max size 193 | */ 194 | public function size($size) 195 | { 196 | $this->field->max($size[0]); 197 | } 198 | 199 | /** 200 | * Min value 201 | */ 202 | public function min($min) 203 | { 204 | $this->setMin($min[0]); 205 | } 206 | 207 | /** 208 | * Set boundaries 209 | */ 210 | public function between($between) 211 | { 212 | list($min, $max) = $between; 213 | 214 | $this->setBetween($min, $max); 215 | } 216 | 217 | /** 218 | * Set accepted mime types 219 | * 220 | * @param string[] $mimes 221 | */ 222 | public function mimes($mimes) 223 | { 224 | // Only useful on file fields 225 | if (!$this->field->isOfType('file')) { 226 | return false; 227 | } 228 | 229 | $this->field->accept($this->setAccepted($mimes)); 230 | } 231 | 232 | /** 233 | * Set accept only images 234 | */ 235 | public function image() 236 | { 237 | $this->mimes(array('jpg', 'png', 'gif', 'bmp')); 238 | } 239 | 240 | // Dates 241 | //////////////////////////////////////////////////////////////////// 242 | 243 | /** 244 | * Before a date 245 | */ 246 | public function before($date) 247 | { 248 | list($format, $date) = $this->formatDate($date[0]); 249 | 250 | $this->field->max(date($format, $date)); 251 | } 252 | 253 | /** 254 | * After a date 255 | */ 256 | public function after($date) 257 | { 258 | list($format, $date) = $this->formatDate($date[0]); 259 | 260 | $this->field->min(date($format, $date)); 261 | } 262 | 263 | //////////////////////////////////////////////////////////////////// 264 | ////////////////////////////// HELPERS ///////////////////////////// 265 | //////////////////////////////////////////////////////////////////// 266 | 267 | /** 268 | * Transform extensions and mime groups into a list of mime types 269 | * 270 | * @param array $mimes An array of mimes 271 | * 272 | * @return string A concatenated list of mimes 273 | */ 274 | private function setAccepted($mimes) 275 | { 276 | // Transform extensions or mime groups into mime types 277 | $mimes = array_map(array('\Laravel\File', 'mime'), $mimes); 278 | 279 | return implode(',', $mimes); 280 | } 281 | 282 | /** 283 | * Format a date to a pattern 284 | * 285 | * @param string $date The date 286 | * 287 | * @return string The pattern 288 | */ 289 | private function formatDate($date) 290 | { 291 | $format = 'Y-m-d'; 292 | 293 | // Add hour for datetime fields 294 | if ($this->field->isOfType('datetime', 'datetime-local')) { 295 | $format .= '\TH:i:s'; 296 | } 297 | 298 | return array($format, strtotime($date)); 299 | } 300 | 301 | /** 302 | * Set a maximum value to a field 303 | * 304 | * @param integer $max 305 | */ 306 | private function setMax($max) 307 | { 308 | $attribute = $this->field->isOfType('number') ? 'max' : 'maxlength'; 309 | 310 | $this->field->$attribute($max); 311 | } 312 | 313 | /** 314 | * Set a minimum value to a field 315 | * 316 | * @param integer $min 317 | */ 318 | private function setMin($min) 319 | { 320 | if ($this->field->isOfType('number') == 'min') { 321 | $this->field->min($min); 322 | } else { 323 | $this->field->pattern(".{".$min.",}"); 324 | } 325 | } 326 | 327 | /** 328 | * Set a minimum and maximum value to a field 329 | * 330 | * @param $min 331 | * @param $max 332 | */ 333 | public function setBetween($min, $max) 334 | { 335 | if ($this->field->isOfType('number') == 'min') { 336 | // min, max values for generation of the pattern 337 | $this->field->min($min); 338 | $this->field->max($max); 339 | } else { 340 | $this->field->pattern('.{'.$min.','.$max.'}'); 341 | 342 | // still let the browser limit text input after reaching the max 343 | $this->field->maxlength($max); 344 | } 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/Former/Framework/ZurbFoundation.php: -------------------------------------------------------------------------------- 1 | 'one', 30 | 2 => 'two', 31 | 3 => 'three', 32 | 4 => 'four', 33 | 5 => 'five', 34 | 6 => 'six', 35 | 7 => 'seven', 36 | 8 => 'eight', 37 | 9 => 'nine', 38 | 10 => 'ten', 39 | 11 => 'eleven', 40 | 12 => 'twelve', 41 | ); 42 | 43 | /** 44 | * The field states available 45 | * 46 | * @var array 47 | */ 48 | protected $states = array( 49 | 'error', 50 | ); 51 | 52 | /** 53 | * Create a new ZurbFoundation instance 54 | * 55 | * @param Container $app 56 | */ 57 | public function __construct(Container $app) 58 | { 59 | $this->app = $app; 60 | $this->setFrameworkDefaults(); 61 | } 62 | 63 | //////////////////////////////////////////////////////////////////// 64 | /////////////////////////// FILTER ARRAYS ////////////////////////// 65 | //////////////////////////////////////////////////////////////////// 66 | 67 | public function filterButtonClasses($classes) 68 | { 69 | return $classes; 70 | } 71 | 72 | public function filterFieldClasses($classes) 73 | { 74 | // Filter classes 75 | $classes = array_intersect($classes, $this->fields); 76 | 77 | return $classes; 78 | } 79 | 80 | //////////////////////////////////////////////////////////////////// 81 | ///////////////////// EXPOSE FRAMEWORK SPECIFICS /////////////////// 82 | //////////////////////////////////////////////////////////////////// 83 | 84 | protected function setFieldWidths($labelWidths) 85 | { 86 | $labelWidthClass = $fieldWidthClass = $fieldOffsetClass = ''; 87 | 88 | $viewports = $this->getFrameworkOption('viewports'); 89 | 90 | foreach ($labelWidths as $viewport => $columns) { 91 | if ($viewport) { 92 | $labelWidthClass .= $viewports[$viewport].$this->fields[$columns].' '; 93 | $fieldWidthClass .= $viewports[$viewport].$this->fields[12 - $columns].' '; 94 | $fieldOffsetClass .= $viewports[$viewport].'offset-by-'.$this->fields[$columns].' '; 95 | } 96 | } 97 | 98 | $this->labelWidth = $labelWidthClass.'columns'; 99 | $this->fieldWidth = $fieldWidthClass.'columns'; 100 | $this->fieldOffset = $fieldOffsetClass.'columns'; 101 | } 102 | 103 | //////////////////////////////////////////////////////////////////// 104 | ///////////////////////////// ADD CLASSES ////////////////////////// 105 | //////////////////////////////////////////////////////////////////// 106 | 107 | public function getFieldClasses(Field $field, $classes = array()) 108 | { 109 | $classes = $this->filterFieldClasses($classes); 110 | 111 | return $this->addClassesToField($field, $classes); 112 | } 113 | 114 | public function getGroupClasses() 115 | { 116 | if ($this->app['former.form']->isOfType('horizontal')) { 117 | return 'row'; 118 | } else { 119 | return null; 120 | } 121 | } 122 | 123 | /** 124 | * Add label classes 125 | * 126 | * @return string|null An array of attributes with the label class 127 | */ 128 | public function getLabelClasses() 129 | { 130 | if ($this->app['former.form']->isOfType('horizontal')) { 131 | return $this->getFrameworkOption('wrappedLabelClasses'); 132 | } else { 133 | return null; 134 | } 135 | } 136 | 137 | public function getUneditableClasses() 138 | { 139 | return null; 140 | } 141 | 142 | public function getPlainTextClasses() 143 | { 144 | return null; 145 | } 146 | 147 | public function getFormClasses($type) 148 | { 149 | return null; 150 | } 151 | 152 | public function getActionClasses() 153 | { 154 | return null; 155 | } 156 | 157 | //////////////////////////////////////////////////////////////////// 158 | //////////////////////////// RENDER BLOCKS ///////////////////////// 159 | //////////////////////////////////////////////////////////////////// 160 | 161 | public function createHelp($text, $attributes = null) 162 | { 163 | if (is_null($attributes) or empty($attributes)) { 164 | $attributes = $this->getFrameworkOption('error_classes'); 165 | } 166 | 167 | return Element::create('span', $text, $attributes); 168 | } 169 | 170 | /** 171 | * Render a disabled field 172 | * 173 | * @param Field $field 174 | * 175 | * @return Input 176 | */ 177 | public function createDisabledField(Field $field) 178 | { 179 | $field->disabled(); 180 | 181 | return Input::create('text', $field->getName(), $field->getValue(), $field->getAttributes()); 182 | } 183 | 184 | /** 185 | * Render a plain text field 186 | * Which fallback to a disabled field 187 | * 188 | * @param Field $field 189 | * 190 | * @return Element 191 | */ 192 | public function createPlainTextField(Field $field) 193 | { 194 | return $this->createDisabledField($field); 195 | } 196 | 197 | //////////////////////////////////////////////////////////////////// 198 | //////////////////////////// WRAP BLOCKS /////////////////////////// 199 | //////////////////////////////////////////////////////////////////// 200 | 201 | /** 202 | * Wrap an item to be prepended or appended to the current field. 203 | * For Zurb we return the item and handle the wrapping in prependAppend 204 | * as wrapping is dependent on whether we're prepending or appending. 205 | * 206 | * @return string A wrapped item 207 | */ 208 | public function placeAround($item) 209 | { 210 | return $item; 211 | } 212 | 213 | /** 214 | * Wrap a field with prepended and appended items 215 | * 216 | * @param Field $field 217 | * @param array $prepend 218 | * @param array $append 219 | * 220 | * @return string A field concatented with prepended and/or appended items 221 | */ 222 | public function prependAppend($field, $prepend, $append) 223 | { 224 | $return = ''; 225 | 226 | foreach ($prepend as $item) { 227 | $return .= ''.$item.''; 228 | } 229 | 230 | $return .= ''.$field->render().''; 231 | 232 | foreach ($append as $item) { 233 | $return .= ''.$item.''; 234 | } 235 | 236 | return $return; 237 | } 238 | 239 | /** 240 | * Wraps all label contents with potential additional tags. 241 | * 242 | * @param string $label 243 | * 244 | * @return string A wrapped label 245 | */ 246 | public function wrapLabel($label) 247 | { 248 | if ($this->app['former.form']->isOfType('horizontal')) { 249 | return Element::create('div', $label)->addClass($this->labelWidth); 250 | } else { 251 | return $label; 252 | } 253 | } 254 | 255 | /** 256 | * Wraps all field contents with potential additional tags. 257 | * 258 | * @param Field $field 259 | * 260 | * @return Element A wrapped field 261 | */ 262 | public function wrapField($field) 263 | { 264 | if ($this->app['former.form']->isOfType('horizontal')) { 265 | return Element::create('div', $field)->addClass($this->fieldWidth); 266 | } else { 267 | return $field; 268 | } 269 | } 270 | 271 | /** 272 | * Wrap actions block with potential additional tags 273 | * 274 | * @param Actions $actions 275 | * 276 | * @return string A wrapped actions block 277 | */ 278 | public function wrapActions($actions) 279 | { 280 | if ($this->app['former.form']->isOfType('horizontal')) { 281 | return Element::create('div', $actions)->addClass(array($this->fieldOffset, $this->fieldWidth)); 282 | } else { 283 | return $actions; 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/Former/Framework/ZurbFoundation4.php: -------------------------------------------------------------------------------- 1 | app = $app; 66 | $this->setFrameworkDefaults(); 67 | } 68 | 69 | //////////////////////////////////////////////////////////////////// 70 | /////////////////////////// FILTER ARRAYS ////////////////////////// 71 | //////////////////////////////////////////////////////////////////// 72 | 73 | public function filterButtonClasses($classes) 74 | { 75 | // Filter classes 76 | $classes = array_intersect($classes, $this->buttons); 77 | $classes[] = 'button'; 78 | 79 | return $classes; 80 | } 81 | 82 | public function filterFieldClasses($classes) 83 | { 84 | return null; 85 | } 86 | 87 | //////////////////////////////////////////////////////////////////// 88 | ///////////////////// EXPOSE FRAMEWORK SPECIFICS /////////////////// 89 | //////////////////////////////////////////////////////////////////// 90 | 91 | protected function setFieldWidths($labelWidths) 92 | { 93 | $labelWidthClass = $fieldWidthClass = $fieldOffsetClass = ''; 94 | 95 | $viewports = $this->getFrameworkOption('viewports'); 96 | 97 | foreach ($labelWidths as $viewport => $columns) { 98 | if ($viewport) { 99 | $labelWidthClass .= $viewports[$viewport].'-'.$columns.' '; 100 | $fieldWidthClass .= $viewports[$viewport].'-'.(12 - $columns).' '; 101 | $fieldOffsetClass .= $viewports[$viewport].'-offset-'.$columns.' '; 102 | } 103 | } 104 | 105 | $this->labelWidth = $labelWidthClass.'columns'; 106 | $this->fieldWidth = $fieldWidthClass.'columns'; 107 | $this->fieldOffset = $fieldOffsetClass.'columns'; 108 | } 109 | 110 | //////////////////////////////////////////////////////////////////// 111 | ///////////////////////////// ADD CLASSES ////////////////////////// 112 | //////////////////////////////////////////////////////////////////// 113 | 114 | public function getFieldClasses(Field $field, $classes = array()) 115 | { 116 | if ($field->isButton()) { 117 | $classes = $this->filterButtonClasses($classes); 118 | } else { 119 | $classes = $this->filterFieldClasses($classes); 120 | } 121 | 122 | return $this->addClassesToField($field, $classes); 123 | } 124 | 125 | public function getGroupClasses() 126 | { 127 | if ($this->app['former.form']->isOfType('horizontal')) { 128 | return 'row'; 129 | } else { 130 | return null; 131 | } 132 | } 133 | 134 | /** 135 | * Add label classes 136 | * 137 | * @return string|null An array of attributes with the label class 138 | */ 139 | public function getLabelClasses() 140 | { 141 | if ($this->app['former.form']->isOfType('horizontal')) { 142 | return $this->getFrameworkOption('wrappedLabelClasses'); 143 | } else { 144 | return null; 145 | } 146 | } 147 | 148 | public function getUneditableClasses() 149 | { 150 | return null; 151 | } 152 | 153 | public function getPlainTextClasses() 154 | { 155 | return null; 156 | } 157 | 158 | public function getFormClasses($type) 159 | { 160 | return null; 161 | } 162 | 163 | public function getActionClasses() 164 | { 165 | return null; 166 | } 167 | 168 | //////////////////////////////////////////////////////////////////// 169 | //////////////////////////// RENDER BLOCKS ///////////////////////// 170 | //////////////////////////////////////////////////////////////////// 171 | 172 | public function createHelp($text, $attributes = null) 173 | { 174 | if (is_null($attributes) or empty($attributes)) { 175 | $attributes = $this->getFrameworkOption('error_classes'); 176 | } 177 | 178 | return Element::create('span', $text, $attributes); 179 | } 180 | 181 | /** 182 | * Render a disabled field 183 | * 184 | * @param Field $field 185 | * 186 | * @return Input 187 | */ 188 | public function createDisabledField(Field $field) 189 | { 190 | $field->disabled(); 191 | 192 | return Input::create('text', $field->getName(), $field->getValue(), $field->getAttributes()); 193 | } 194 | 195 | /** 196 | * Render a plain text field 197 | * Which fallback to a disabled field 198 | * 199 | * @param Field $field 200 | * 201 | * @return Element 202 | */ 203 | public function createPlainTextField(Field $field) 204 | { 205 | return $this->createDisabledField($field); 206 | } 207 | 208 | //////////////////////////////////////////////////////////////////// 209 | //////////////////////////// WRAP BLOCKS /////////////////////////// 210 | //////////////////////////////////////////////////////////////////// 211 | 212 | /** 213 | * Wrap an item to be prepended or appended to the current field. 214 | * For Zurb we return the item and handle the wrapping in prependAppend 215 | * as wrapping is dependent on whether we're prepending or appending. 216 | * 217 | * @return string A wrapped item 218 | */ 219 | public function placeAround($item) 220 | { 221 | return $item; 222 | } 223 | 224 | /** 225 | * Wrap a field with prepended and appended items 226 | * 227 | * @param Field $field 228 | * @param array $prepend 229 | * @param array $append 230 | * 231 | * @return string A field concatented with prepended and/or appended items 232 | */ 233 | public function prependAppend($field, $prepend, $append) 234 | { 235 | $return = ''; 236 | 237 | foreach ($prepend as $item) { 238 | $return .= ''.$item.''; 239 | } 240 | 241 | $return .= ''.$field->render().''; 242 | 243 | foreach ($append as $item) { 244 | $return .= ''.$item.''; 245 | } 246 | 247 | return $return; 248 | } 249 | 250 | /** 251 | * Wraps all label contents with potential additional tags. 252 | * 253 | * @param string $label 254 | * 255 | * @return string A wrapped label 256 | */ 257 | public function wrapLabel($label) 258 | { 259 | if ($this->app['former.form']->isOfType('horizontal')) { 260 | return Element::create('div', $label)->addClass($this->labelWidth); 261 | } else { 262 | return $label; 263 | } 264 | } 265 | 266 | /** 267 | * Wraps all field contents with potential additional tags. 268 | * 269 | * @param Field $field 270 | * 271 | * @return Element A wrapped field 272 | */ 273 | public function wrapField($field) 274 | { 275 | if ($this->app['former.form']->isOfType('horizontal')) { 276 | return Element::create('div', $field)->addClass($this->fieldWidth); 277 | } else { 278 | return $field; 279 | } 280 | } 281 | 282 | /** 283 | * Wrap actions block with potential additional tags 284 | * 285 | * @param Actions $actions 286 | * 287 | * @return string A wrapped actions block 288 | */ 289 | public function wrapActions($actions) 290 | { 291 | return $actions; 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /tests/Traits/FieldTest.php: -------------------------------------------------------------------------------- 1 | former->close(); 30 | $field = $this->former->text('foo')->raw(); 31 | 32 | $this->assertEquals('', $field->__toString()); 33 | } 34 | 35 | public function testCanChangeTheFieldIdAndKeepLabelInSync() 36 | { 37 | $field = $this->former->text('foo'); 38 | $field->id('bar'); 39 | 40 | $matcher = 'Foo'; 41 | 42 | $this->assertEquals($matcher, $field->__toString()); 43 | } 44 | 45 | public function testCanChangeTypeMidCourse() 46 | { 47 | $field = $this->former->text('foo')->setType('email'); 48 | 49 | $this->assertEquals('email', $field->getType()); 50 | } 51 | 52 | public function testCanRenameField() 53 | { 54 | $input = $this->former->text('foo')->name('bar')->__toString(); 55 | $matcher = $this->controlGroup( 56 | '', 57 | 'Bar'); 58 | 59 | $this->assertEquals($matcher, $input); 60 | } 61 | 62 | public function testCanSetValueOnFields() 63 | { 64 | $matcher = $this->controlGroup(''); 65 | $static = $this->former->text('foo')->value('bar')->__toString(); 66 | $this->assertHTML($this->matchField(), $static); 67 | $this->assertHTML($this->matchControlGroup(), $static); 68 | 69 | $this->resetLabels(); 70 | $input = $this->former->text('foo', null, 'bar')->__toString(); 71 | $this->assertHTML($this->matchField(), $input); 72 | $this->assertHTML($this->matchControlGroup(), $input); 73 | } 74 | 75 | public function testCanForceValueOnFields() 76 | { 77 | $this->former->populate(array('foo' => 'unbar')); 78 | $static = $this->former->text('foo')->forceValue('bar')->__toString(); 79 | $matcher = $this->controlGroup(''); 80 | 81 | $this->assertEquals($matcher, $static); 82 | } 83 | 84 | public function testCanCreateViaMagicAttribute() 85 | { 86 | $static = $this->former->text('foo')->class('foo')->data_bar('bar')->__toString(); 87 | $this->assertHTML($this->matchField(), $static); 88 | $this->assertHTML($this->matchControlGroup(), $static); 89 | 90 | $this->resetLabels(); 91 | $input = $this->former->text('foo', null, null, array('class' => 'foo', 'data-bar' => 'bar'))->__toString(); 92 | $this->assertHTML($this->matchField(), $input); 93 | $this->assertHTML($this->matchControlGroup(), $input); 94 | } 95 | 96 | public function testCanCreateViaMagicAttributeUnvalue() 97 | { 98 | $static = $this->former->text('foo')->require()->__toString(); 99 | $matcher = $this->controlGroup(''); 100 | 101 | $this->assertHTML($this->matchField(), $static); 102 | $this->assertHTML($this->matchControlGroup(), $static); 103 | } 104 | 105 | public function testCanSetAttributes() 106 | { 107 | $attributes = array('class' => 'foo', 'data-foo' => 'bar'); 108 | 109 | $static = $this->former->text('foo')->require()->setAttributes($attributes)->__toString(); 110 | 111 | $field = $this->matchField(); 112 | $field['attributes']['require'] = null; 113 | $field['attributes']['class'] = 'foo'; 114 | $field['attributes']['data-foo'] = 'bar'; 115 | $this->assertHTML($field, $static); 116 | $this->assertHTML($this->matchControlGroup(), $static); 117 | } 118 | 119 | public function testCanReplaceAttributes() 120 | { 121 | $attributes = array('class' => 'foo', 'data-foo' => 'bar'); 122 | 123 | $static = $this->former->text('foo')->require()->replaceAttributes($attributes)->__toString(); 124 | $matcher = $this->controlGroup(''); 125 | 126 | $field = $this->matchField(); 127 | $field['attributes']['class'] = 'foo'; 128 | $field['attributes']['data-foo'] = 'bar'; 129 | $this->assertHTML($field, $static); 130 | $this->assertHTML($this->matchControlGroup(), $static); 131 | } 132 | 133 | public function testCanGetAttribute() 134 | { 135 | $former = $this->former->text('name')->addClass('span1')->foo('bar'); 136 | 137 | $this->assertEquals('span1', $former->class); 138 | $this->assertEquals('bar', $former->foo); 139 | } 140 | 141 | public function testCanAddClass() 142 | { 143 | $matcher = $this->controlGroup(''); 144 | $static = $this->former->text('foo')->class('foo')->addClass('bar')->__toString(); 145 | $this->assertHTML($this->matchControlGroup(), $static); 146 | $this->assertHTML($this->matchField(), $static); 147 | 148 | $this->resetLabels(); 149 | $input = $this->former->text('foo', null, null, array('class' => 'foo'))->addClass('bar')->__toString(); 150 | $this->assertHTML($this->matchControlGroup(), $input); 151 | $this->assertHTML($this->matchField(), $input); 152 | } 153 | 154 | /** 155 | * @dataProvider provideSizes 156 | */ 157 | public function testCanUseMagicMethods($size) 158 | { 159 | $method = $size.'_text'; 160 | $class = Str::startsWith($size, 'span') ? $size.' ' : 'input-'.$size.' '; 161 | $static = $this->former->$method('foo')->addClass('bar')->__toString(); 162 | if ($class == 'input-foo ') { 163 | $class = null; 164 | } 165 | 166 | $field = $this->matchField(); 167 | $field['attributes']['class'] = $class.'bar'; 168 | $this->assertHTML($this->matchControlGroup(), $static); 169 | $this->assertHTML($field, $static); 170 | } 171 | 172 | public function testAutomaticLabelsForSingleSelectField() 173 | { 174 | $field = $this->former->select('foo'); 175 | 176 | $matcher = 'Foo'; 177 | 178 | $this->assertEquals($matcher, $field->__toString()); 179 | } 180 | 181 | public function testAutomaticLabelsForMultiSelectField() 182 | { 183 | $field = $this->former->select('foo[]'); 184 | 185 | $matcher = 'Foo'; 186 | 187 | $this->assertEquals($matcher, $field->__toString()); 188 | } 189 | 190 | public function testCantHaveDuplicateIdsForFields() 191 | { 192 | $field = $this->former->text('name')->render(); 193 | $fieldTwo = $this->former->text('name')->render(); 194 | $fieldThree = $this->former->text('name')->render(); 195 | 196 | $this->assertEquals('', $field); 197 | $this->assertEquals('', $fieldTwo); 198 | $this->assertEquals('', $fieldThree); 199 | } 200 | 201 | public function testCanChangeBindingOfField() 202 | { 203 | $this->former->populate(array('bar' => 'unbar')); 204 | $static = $this->former->text('foo')->bind('bar')->__toString(); 205 | $matcher = $this->controlGroup(''); 206 | 207 | $this->assertEquals($matcher, $static); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/Former/Framework/ZurbFoundation5.php: -------------------------------------------------------------------------------- 1 | app = $app; 66 | $this->setFrameworkDefaults(); 67 | } 68 | 69 | //////////////////////////////////////////////////////////////////// 70 | /////////////////////////// FILTER ARRAYS ////////////////////////// 71 | //////////////////////////////////////////////////////////////////// 72 | 73 | public function filterButtonClasses($classes) 74 | { 75 | // Filter classes 76 | $classes = array_intersect($classes, $this->buttons); 77 | $classes[] = 'button'; 78 | 79 | return $classes; 80 | } 81 | 82 | public function filterFieldClasses($classes) 83 | { 84 | return null; 85 | } 86 | 87 | //////////////////////////////////////////////////////////////////// 88 | ///////////////////// EXPOSE FRAMEWORK SPECIFICS /////////////////// 89 | //////////////////////////////////////////////////////////////////// 90 | 91 | protected function setFieldWidths($labelWidths) 92 | { 93 | $labelWidthClass = $fieldWidthClass = $fieldOffsetClass = ''; 94 | 95 | $viewports = $this->getFrameworkOption('viewports'); 96 | 97 | foreach ($labelWidths as $viewport => $columns) { 98 | if ($viewport) { 99 | $labelWidthClass .= $viewports[$viewport].'-'.$columns.' '; 100 | $fieldWidthClass .= $viewports[$viewport].'-'.(12 - $columns).' '; 101 | $fieldOffsetClass .= $viewports[$viewport].'-offset-'.$columns.' '; 102 | } 103 | } 104 | 105 | $this->labelWidth = $labelWidthClass.'columns'; 106 | $this->fieldWidth = $fieldWidthClass.'columns'; 107 | $this->fieldOffset = $fieldOffsetClass.'columns'; 108 | } 109 | 110 | //////////////////////////////////////////////////////////////////// 111 | ///////////////////////////// ADD CLASSES ////////////////////////// 112 | //////////////////////////////////////////////////////////////////// 113 | 114 | public function getFieldClasses(Field $field, $classes = array()) 115 | { 116 | if ($field->isButton()) { 117 | $classes = $this->filterButtonClasses($classes); 118 | } else { 119 | $classes = $this->filterFieldClasses($classes); 120 | } 121 | 122 | return $this->addClassesToField($field, $classes); 123 | } 124 | 125 | public function getGroupClasses() 126 | { 127 | if ($this->app['former.form']->isOfType('horizontal')) { 128 | return 'row'; 129 | } else { 130 | return null; 131 | } 132 | } 133 | 134 | /** 135 | * Add label classes 136 | * 137 | * @return string|null An array of attributes with the label class 138 | */ 139 | public function getLabelClasses() 140 | { 141 | if ($this->app['former.form']->isOfType('horizontal')) { 142 | return $this->getFrameworkOption('wrappedLabelClasses'); 143 | } else { 144 | return null; 145 | } 146 | } 147 | 148 | public function getUneditableClasses() 149 | { 150 | return null; 151 | } 152 | 153 | public function getPlainTextClasses() 154 | { 155 | return null; 156 | } 157 | 158 | public function getFormClasses($type) 159 | { 160 | return null; 161 | } 162 | 163 | public function getActionClasses() 164 | { 165 | return null; 166 | } 167 | 168 | //////////////////////////////////////////////////////////////////// 169 | //////////////////////////// RENDER BLOCKS ///////////////////////// 170 | //////////////////////////////////////////////////////////////////// 171 | 172 | public function createHelp($text, $attributes = null) 173 | { 174 | if (is_null($attributes) or empty($attributes)) { 175 | $attributes = $this->getFrameworkOption('error_classes'); 176 | } 177 | 178 | return Element::create('small', $text, $attributes); 179 | } 180 | 181 | /** 182 | * Render a disabled field 183 | * 184 | * @param Field $field 185 | * 186 | * @return Input 187 | */ 188 | public function createDisabledField(Field $field) 189 | { 190 | $field->disabled(); 191 | 192 | return Input::create('text', $field->getName(), $field->getValue(), $field->getAttributes()); 193 | } 194 | 195 | /** 196 | * Render a plain text field 197 | * Which fallback to a disabled field 198 | * 199 | * @param Field $field 200 | * 201 | * @return Element 202 | */ 203 | public function createPlainTextField(Field $field) 204 | { 205 | return $this->createDisabledField($field); 206 | } 207 | 208 | //////////////////////////////////////////////////////////////////// 209 | //////////////////////////// WRAP BLOCKS /////////////////////////// 210 | //////////////////////////////////////////////////////////////////// 211 | 212 | /** 213 | * Wrap an item to be prepended or appended to the current field. 214 | * For Zurb we return the item and handle the wrapping in prependAppend 215 | * as wrapping is dependent on whether we're prepending or appending. 216 | * 217 | * @return string A wrapped item 218 | */ 219 | public function placeAround($item) 220 | { 221 | return $item; 222 | } 223 | 224 | /** 225 | * Wrap a field with prepended and appended items 226 | * 227 | * @param Field $field 228 | * @param array $prepend 229 | * @param array $append 230 | * 231 | * @return string A field concatented with prepended and/or appended items 232 | */ 233 | public function prependAppend($field, $prepend, $append) 234 | { 235 | $return = ''; 236 | 237 | foreach ($prepend as $item) { 238 | $return .= ''.$item.''; 239 | } 240 | 241 | $return .= ''.$field->render().''; 242 | 243 | foreach ($append as $item) { 244 | $return .= ''.$item.''; 245 | } 246 | 247 | $return .= ''; 248 | 249 | return $return; 250 | } 251 | 252 | /** 253 | * Wraps all label contents with potential additional tags. 254 | * 255 | * @param string $label 256 | * 257 | * @return string A wrapped label 258 | */ 259 | public function wrapLabel($label) 260 | { 261 | if ($this->app['former.form']->isOfType('horizontal')) { 262 | return Element::create('div', $label)->addClass($this->labelWidth); 263 | } else { 264 | return $label; 265 | } 266 | } 267 | 268 | /** 269 | * Wraps all field contents with potential additional tags. 270 | * 271 | * @param Field $field 272 | * 273 | * @return Element A wrapped field 274 | */ 275 | public function wrapField($field) 276 | { 277 | if ($this->app['former.form']->isOfType('horizontal')) { 278 | return Element::create('div', $field)->addClass($this->fieldWidth); 279 | } else { 280 | return $field; 281 | } 282 | } 283 | 284 | /** 285 | * Wrap actions block with potential additional tags 286 | * 287 | * @param Actions $actions 288 | * 289 | * @return string A wrapped actions block 290 | */ 291 | public function wrapActions($actions) 292 | { 293 | return $actions; 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/Former/Traits/Framework.php: -------------------------------------------------------------------------------- 1 | current(); 101 | } 102 | 103 | /** 104 | * Check if the current framework doesn't match something 105 | * 106 | * @param string $framework 107 | * 108 | * @return boolean 109 | */ 110 | public function isnt($framework) 111 | { 112 | return $framework != $this->current(); 113 | } 114 | 115 | //////////////////////////////////////////////////////////////////// 116 | /////////////////////////// COMMON METHODS ///////////////////////// 117 | //////////////////////////////////////////////////////////////////// 118 | 119 | /** 120 | * List form types triggered special styling form current framework 121 | * 122 | * @return array 123 | */ 124 | public function availableTypes() 125 | { 126 | return $this->availableTypes; 127 | } 128 | 129 | /** 130 | * Filter a field state 131 | * 132 | * @param string $state 133 | * 134 | * @return string 135 | */ 136 | public function filterState($state) 137 | { 138 | // Filter out wrong states 139 | return in_array($state, $this->states) ? $state : null; 140 | } 141 | 142 | /** 143 | * Framework error state 144 | * 145 | * @return string 146 | */ 147 | public function errorState() 148 | { 149 | return 'error'; 150 | } 151 | 152 | /** 153 | * Returns corresponding inline class of a field 154 | * 155 | * @param Field $field 156 | * 157 | * @return string 158 | */ 159 | public function getInlineLabelClass($field) 160 | { 161 | return 'inline'; 162 | } 163 | 164 | /** 165 | * Set framework defaults from its config file 166 | */ 167 | protected function setFrameworkDefaults() 168 | { 169 | $this->setFieldWidths($this->getFrameworkOption('labelWidths')); 170 | $this->setIconDefaults(); 171 | } 172 | 173 | /** 174 | * @param string $widths 175 | */ 176 | protected function setFieldWidths($widths) 177 | { 178 | } 179 | 180 | /** 181 | * Override framework defaults for icons with config values where set 182 | */ 183 | protected function setIconDefaults() 184 | { 185 | $this->iconTag = $this->getFrameworkOption('icon.tag'); 186 | $this->iconSet = $this->getFrameworkOption('icon.set'); 187 | $this->iconPrefix = $this->getFrameworkOption('icon.prefix'); 188 | } 189 | 190 | /** 191 | * Render an icon 192 | * 193 | * @param array $attributes Its general attributes 194 | * 195 | * @return string 196 | */ 197 | public function createIcon($iconType, $attributes = array(), $iconSettings = array()) 198 | { 199 | // Check for empty icons 200 | if (!$iconType) { 201 | return false; 202 | } 203 | 204 | // icon settings can be overridden for a specific icon 205 | $tag = Arr::get($iconSettings, 'tag', $this->iconTag); 206 | $set = Arr::get($iconSettings, 'set', $this->iconSet); 207 | $prefix = Arr::get($iconSettings, 'prefix', $this->iconPrefix); 208 | 209 | return Element::create($tag, null, $attributes)->addClass("$set $prefix-$iconType"); 210 | } 211 | 212 | //////////////////////////////////////////////////////////////////// 213 | ///////////////////////////// HELPERS ////////////////////////////// 214 | //////////////////////////////////////////////////////////////////// 215 | 216 | /** 217 | * Add classes to a field 218 | * 219 | * @param Field $field 220 | * @param array $classes 221 | * 222 | * @return \Former\Traits\Field 223 | */ 224 | protected function addClassesToField($field, $classes) 225 | { 226 | // If we found any class, add them 227 | if ($classes) { 228 | $field->addClass(implode(' ', $classes)); 229 | } 230 | 231 | return $field; 232 | } 233 | 234 | /** 235 | * Prepend an array of classes with a string 236 | * 237 | * @param array $classes The classes to prepend 238 | * @param string $with The string to prepend them with 239 | * 240 | * @return array A prepended array 241 | */ 242 | protected function prependWith($classes, $with) 243 | { 244 | return array_map(function ($class) use ($with) { 245 | return $with.$class; 246 | }, $classes); 247 | } 248 | 249 | /** 250 | * Create a label for a field 251 | * 252 | * @param Field $field 253 | * @param Element $label The field label if non provided 254 | * 255 | * @return string A label 256 | */ 257 | public function createLabelOf(Field $field, ?Element $label = null) 258 | { 259 | // Get the label and its informations 260 | if (!$label) { 261 | $label = $field->getLabel(); 262 | } 263 | 264 | // Get label "for" 265 | $for = $field->id ?: $field->getName(); 266 | 267 | // Get label text 268 | $text = $label->getValue(); 269 | if (!$text) { 270 | return false; 271 | } 272 | 273 | // Append required text 274 | if ($field->isRequired()) { 275 | $text .= $this->app['former']->getOption('required_text'); 276 | } 277 | 278 | // Render plain label if checkable, else a classic one 279 | $label->setValue($text); 280 | if (!$field->isCheckable()) { 281 | $label->for($for); 282 | } 283 | 284 | return $label; 285 | } 286 | 287 | /** 288 | * Get an option for the current framework 289 | * 290 | * @param string $option 291 | * 292 | * @return string 293 | */ 294 | protected function getFrameworkOption($option) 295 | { 296 | return $this->app['config']->get("former.{$this->current()}.$option"); 297 | } 298 | 299 | //////////////////////////////////////////////////////////////////// 300 | //////////////////////////// WRAP BLOCKS /////////////////////////// 301 | //////////////////////////////////////////////////////////////////// 302 | 303 | /** 304 | * Wraps all label contents with potential additional tags. 305 | * 306 | * @param string $label 307 | * 308 | * @return string A wrapped label 309 | */ 310 | public function wrapLabel($label) 311 | { 312 | return $label; 313 | } 314 | 315 | //////////////////////////////////////////////////////////////////// 316 | //////////////////////////// RENDER BLOCKS ///////////////////////// 317 | //////////////////////////////////////////////////////////////////// 318 | 319 | /** 320 | * Render an validation error text 321 | * 322 | * @param string $text 323 | * @param array $attributes 324 | * 325 | * @return string 326 | */ 327 | public function createValidationError($text, $attributes = array()) 328 | { 329 | return $this->createHelp($text, $attributes); 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 4.0.0 4 | - Laravel 5+ branch 5 | 6 | ## 3.5.3 7 | 8 | ### Added 9 | - Allow repopulating multiselects from Laravel relationships 10 | - Support checkbox repopulation from models 11 | 12 | ### Fixed 13 | - Handle dot notation in live validation 14 | - Don't repopulate _token fields 15 | - Encode values of hidden fields 16 | 17 | ## 3.5.2 18 | 19 | ### Fixed 20 | - Fix the `bind` function for checkables 21 | - Fixed an error when manually creating checkables 22 | 23 | ## 3.5.1 24 | 25 | ### Added 26 | - Added support for Foundation 5 27 | - Added PlainText field type 28 | - Added `->bind()` method to fields to change which binding to use for repopulation 29 | 30 | ### Changed 31 | - Peformance tweaks (framework caching) 32 | 33 | ### Fixed 34 | - Fixed position of MAX_FILE_SIZE field 35 | - Fixed Laravel 5 compatibility 36 | - Fixed wrong class for inline checkables on Bootstrap 3 37 | 38 | ## 3.5.0 39 | 40 | ### Changed 41 | - Bumped minimum requirement to 5.4 42 | 43 | ## 3.4.4 44 | 45 | ### Deprecated 46 | - Last release for PHP 5.3 47 | 48 | ## 3.4.3 49 | 50 | ### Added 51 | - Added step parameter to `Select::range()` method 52 | - Allow individual checkboxes to override the global push-checkbox setting 53 | 54 | ### Changed 55 | - Add some additional options for Foundation 4 56 | - Allow translation method to fetch nested keys via dot or brackets 57 | 58 | ### Fixed 59 | - Fix some repopulation issues 60 | 61 | ## 3.4.2 62 | 63 | ### Added 64 | - Added the ability to specify multiple namespaces to look for fields in the MethodDispatcher 65 | - Appended buttons are now properly wrapped in input-group-btn in Bootstrap 3 66 | 67 | ### Fixed 68 | - Fixed a bug where wrong items would get selected in optgroups 69 | - Fixed some bug when fetching data from the request 70 | - Fixed spaces in validation rules causing errors 71 | 72 | ## 3.4.1 73 | 74 | ### Added 75 | - Added support for passing MessageBag instances to `withErrors` 76 | 77 | ### Changed 78 | - MethodDispatcher can now look for field classes in multiple namespaces 79 | - Use objects' `toArray` instead of array casting when possible 80 | 81 | ### Fixed 82 | - Fix framework classes overwriting classes predefined on Field classes 83 | - Fix stability problems that prevented Former form being installed 84 | 85 | ## 3.4.0 86 | 87 | ### Added 88 | - Added `Former::rawOpen` to open a temporary no-framework/no-label form 89 | - Added support for camelCase form openers (ie. `verticalOpen` and `vertical_open`) 90 | - Added possibility to disable automatic capitalization of translations 91 | 92 | ### Fixed 93 | - Fixed a bug where two fields with the same name would get the same ID 94 | - Various bugfixes related to repopulation 95 | - Fix various memory and 4.1 compatibility issues 96 | 97 | ## 3.3.0 98 | 99 | ### Added 100 | - Add ability to pass attributes to a select's options 101 | - Add support for PATCH method 102 | - Add ability to create range number fields (`Former::number('foo')->range(1, 5)` sets the `min` to 1 and `max` to 5) 103 | - Added Form->route and Form->controller to set a form's action to a route/controller path, and the corresponding method 104 | 105 | ### Changed 106 | - Allow switching to alternate icon fonts 107 | - Form classes are now framework-dependant 108 | - More work on the Bootstrap 3 integration 109 | - Prevent custom groups from responding to errors from non-grouped fields 110 | 111 | ### Fixed 112 | - Fix bug in selection false values in Selects 113 | - Fix bug where selects with optgroups weren't populated correctly 114 | 115 | ## 3.2.0 116 | 117 | ### Changed 118 | - Updated TwitterBootstrap3 to the latest release 119 | - Former now handles camelCase attributes (ie. `dataPlaceholder` for `data-placeholder`) 120 | - `$group->getErrors()` is now public 121 | 122 | ## 3.1.0 123 | 124 | ### Added 125 | - You can now configure which attributes are translated by default 126 | - Added the `TwitterBootstrap3` framework 127 | - Add a second argument to `Former::group` that allows specifying which errors should be displayed 128 | - Add ability to interact with the Group's method by using `onGroup{method}` (ex: `onGroupAddClass`) 129 | 130 | ### Changed 131 | - All fields are now displayed as "raw" by default in custom groups 132 | 133 | ### Fixed 134 | - Fix some checkable bugs 135 | 136 | ## 3.0.0 137 | 138 | ### Added 139 | - Refactor of Former – the project is now framework agnostic, see installation details 140 | - You can now chain methods to actions blocks `Former::actions('Hello', 'Mr Bond')->id('foo')->addClass('bar')` 141 | - You can now chain buttons to actions blocks `Former::actions()->large_primary_submit('Submit')` 142 | - You can now chain live validation rules to fields (example: `Former::text('foo')->alpha_dash()`) 143 | - You can now display a single field without control group in any kind of form (`Former::text('foo')->raw()`) 144 | - Frameworks names renamed from `bootstrap` to `TwitterBootstrap`, `zurb` to `ZurbFoundation` and `null` to `Nude` 145 | - You can now manually open groups via `Former::group('label')` 146 | - You can also create a group that contains raw content (not a field) with `Former::group('label')->contents('YourContent')`. This will wrap the content in a control class so that your content is aligned with the fields. 147 | - Checkables now handle being populated from relations 148 | - You can now add classes to the group via the `->addGroupClass` method 149 | - Former::withRules() now also take a Laravel-formatted string of rules (ie. "rule|rule:parameter|rule") 150 | - You can now populate on a form-basis with the chained method `->populate` on a form opener 151 | - Add support for macros with Former::macro($name, $macro()) 152 | - Add Select->range() method 153 | - Former now automatically inserts a token in the form 154 | - Support for select groups 155 | 156 | ## 2.6.0 157 | 158 | ### Added 159 | - 'required_text' to append a text to required fields's labels 160 | - Former::open()->rules([]) as alias to Former::withRules 161 | 162 | ### Fixed 163 | - Fix a bug where labels for radios would fail because of identical ids 164 | - Fixed a bug where ->populateField would fail if the form was populated with an object 165 | 166 | ## 2.5.0 167 | 168 | ### Added 169 | - Add basic button class that allow Bootstrappy submit/buttons 170 | - ControlGroup->prependIcon and appendIcon methods 171 | - Ability to pass an array of classes to add to Field->addClass 172 | 173 | ### Fixed 174 | - Fix instantiated classes bug in PHP 5.3.2 175 | - Fix multiple buttons instances overwriting themselves 176 | 177 | ## 2.4.0 178 | 179 | ### Added 180 | - Form openers are now objects too and accept chained methods 181 | - Add `unchecked_value` option to decide what value unchecked checkboxes have in the POST array 182 | - Allow booleans to be passed to Checkable->check() on single items 183 | 184 | ### Changed 185 | - Disable `push_checkbox` option by default 186 | 187 | ### Fixed 188 | - Automatically fetch Lang objects passed to `->options` 189 | 190 | ## 2.3.0 191 | 192 | ### Added 193 | - Add `push_checkboxes` option which forces the submiting of unchecked fields to the POST array 194 | 195 | ## 2.2.0 196 | 197 | ### Added 198 | - Add `Former::file()` and `Former::files()` with methods `->max` and `->accept` 199 | - Add ability to set a placeholder option for select fields 200 | - Add ability to set attributes for a label 201 | 202 | ## 2.1.0 203 | 204 | ### Added 205 | - Add ability to populate field with a model's relationships 206 | - Added `->check()` method on radios and checkboxes 207 | 208 | ## 2.0.0 209 | 210 | ### Added 211 | - Former now uses Laravel's Config class to manage settings, letting the user create a `former.php` file in `application/config` to override default options 212 | - Add option to disable automatic labeling of fields 213 | 214 | ### Fixed 215 | - Fix translation of empty strings 216 | 217 | ## 1.2.1 218 | 219 | ### Added 220 | - Fetch automatically key and value from models through `get_key` and `__toString` 221 | - Add `Former::populateField` to populate a specific field 222 | 223 | ### Fixed 224 | - Fixed a bug preventing from using one Former call to output several times 225 | 226 | ## 1.2.0 227 | 228 | ### Added 229 | - Add suport for Zurb's Foundation framework 230 | - Allow the passing of Query/Eloquent objects to text fields through `->useDatalist` 231 | - Add option to desactivate live validation 232 | 233 | ### Changed 234 | - Allow public use of `Former::getErrors()` 235 | - Let user specify a custom id for generated datalists 236 | 237 | ### Fixed 238 | - Don't create a label tag around checkboxes if the label is empty 239 | - Fix custom arguments of `open()` not working as desired 240 | 241 | ## 1.1.0 242 | 243 | ### Added 244 | - Allow the passing of Query/Eloquent objets to select fields through `->fromQuery` 245 | 246 | ### Fixed 247 | - Disable form population on password fields 248 | - Fix uneditable inputs outputing as text fields 249 | 250 | ## 1.0.0 251 | 252 | ### Added 253 | - Initial release of Former on [Laravel Bundles](http://bundles.laravel.com/bundle/former/) 254 | --------------------------------------------------------------------------------
113 | * // Determine the MIME type for the .tar extension 114 | * $mime = File::mime('tar'); 115 | * // Return a default value if the MIME can't be determined 116 | * $mime = File::mime('ext', 'application/octet-stream'); 117 | *