├── src ├── Exceptions │ ├── InvalidHtml.php │ ├── MissingTag.php │ └── InvalidChild.php ├── Elements │ ├── I.php │ ├── P.php │ ├── Div.php │ ├── Span.php │ ├── Legend.php │ ├── Button.php │ ├── Attributes │ │ ├── Name.php │ │ ├── Type.php │ │ ├── Target.php │ │ ├── Value.php │ │ ├── Placeholder.php │ │ ├── Required.php │ │ ├── Autofocus.php │ │ ├── Disabled.php │ │ ├── ReadonlyTrait.php │ │ ├── Autocomplete.php │ │ └── MinMaxLength.php │ ├── Label.php │ ├── Element.php │ ├── Optgroup.php │ ├── Fieldset.php │ ├── A.php │ ├── Option.php │ ├── Textarea.php │ ├── Input.php │ ├── Img.php │ ├── File.php │ ├── Form.php │ └── Select.php ├── HtmlElement.php ├── helpers.php ├── HtmlServiceProvider.php ├── Selectable.php ├── Facades │ └── Html.php ├── Helpers │ └── Arr.php ├── Attributes.php ├── BaseElement.php └── Html.php ├── LICENSE.md ├── composer.json ├── README.md └── CHANGELOG.md /src/Exceptions/InvalidHtml.php: -------------------------------------------------------------------------------- 1 | app->singleton(Html::class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Exceptions/MissingTag.php: -------------------------------------------------------------------------------- 1 | attribute('name', $name); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Elements/Attributes/Type.php: -------------------------------------------------------------------------------- 1 | attribute('type', $type); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Elements/Attributes/Target.php: -------------------------------------------------------------------------------- 1 | attribute('target', $target); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Elements/Attributes/Value.php: -------------------------------------------------------------------------------- 1 | attribute('value', $value); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Elements/Label.php: -------------------------------------------------------------------------------- 1 | attribute('for', $for); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Elements/Attributes/Placeholder.php: -------------------------------------------------------------------------------- 1 | attribute('placeholder', $placeholder); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Elements/Attributes/Required.php: -------------------------------------------------------------------------------- 1 | attribute('required') 21 | : $this->forgetAttribute('required'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Elements/Attributes/Autofocus.php: -------------------------------------------------------------------------------- 1 | attribute('autofocus') 21 | : $this->forgetAttribute('autofocus'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Elements/Attributes/Disabled.php: -------------------------------------------------------------------------------- 1 | attribute('disabled', 'disabled') 21 | : $this->forgetAttribute('disabled'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Elements/Attributes/ReadonlyTrait.php: -------------------------------------------------------------------------------- 1 | attribute('readonly') 21 | : $this->forgetAttribute('readonly'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Elements/Element.php: -------------------------------------------------------------------------------- 1 | newInstanceWithoutConstructor(); 20 | 21 | $element->tag = $tag; 22 | $element->attributes = new Attributes(); 23 | $element->children = new Collection(); 24 | 25 | return $element; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Elements/Optgroup.php: -------------------------------------------------------------------------------- 1 | attribute('label', $label); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Elements/Attributes/Autocomplete.php: -------------------------------------------------------------------------------- 1 | attribute('autocomplete', $value); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Elements/Fieldset.php: -------------------------------------------------------------------------------- 1 | prependChild( 27 | Legend::create()->text($contents) 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Helpers/Arr.php: -------------------------------------------------------------------------------- 1 | true, 'bar' => false, 'baz']) 17 | * // => ['foo', 'baz'] 18 | * 19 | * @param mixed $map 20 | * 21 | * @return array 22 | */ 23 | public static function getToggledValues($map) 24 | { 25 | return Collection::make($map)->map(function ($condition, $value) { 26 | if (is_numeric($value)) { 27 | return $condition; 28 | } 29 | 30 | return $condition ? $value : null; 31 | })->filter()->toArray(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Elements/Attributes/MinMaxLength.php: -------------------------------------------------------------------------------- 1 | attribute('minlength', $minlength); 23 | } 24 | 25 | /** 26 | * @param int $maxlength 27 | * 28 | * @return static 29 | */ 30 | public function maxlength(int $maxlength) 31 | { 32 | return $this->attribute('maxlength', $maxlength); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Elements/A.php: -------------------------------------------------------------------------------- 1 | attribute('href', $href); 30 | } 31 | 32 | /** 33 | * @param string|null $route 34 | * @param mixed $params 35 | * 36 | * @return static 37 | */ 38 | public function route($route, ...$params) 39 | { 40 | return $this->href(route($route, ...$params)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Elements/Option.php: -------------------------------------------------------------------------------- 1 | attribute('selected', 'selected'); 31 | } 32 | 33 | /** 34 | * @param bool $condition 35 | * 36 | * @return static 37 | */ 38 | public function selectedIf($condition) 39 | { 40 | return $condition ? 41 | $this->selected() : 42 | $this->unselected(); 43 | } 44 | 45 | /** 46 | * @return static 47 | */ 48 | public function unselected() 49 | { 50 | return $this->forgetAttribute('selected'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-html", 3 | "description": "A fluent html builder", 4 | "keywords": [ 5 | "spatie", 6 | "html" 7 | ], 8 | "homepage": "https://github.com/spatie/laravel-html", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Sebastian De Deyne", 13 | "email": "sebastian@spatie.be", 14 | "homepage": "https://spatie.be", 15 | "role": "Developer" 16 | }, 17 | { 18 | "name": "Freek Van der Herten", 19 | "email": "freek@spatie.be", 20 | "homepage": "https://spatie.be", 21 | "role": "Developer" 22 | } 23 | ], 24 | "require": { 25 | "php": "^8.2", 26 | "illuminate/http": "^10.0|^11.0|^12.0", 27 | "illuminate/support": "^10.0|^11.0|^12.0" 28 | }, 29 | "require-dev": { 30 | "mockery/mockery": "^1.3", 31 | "orchestra/testbench": "^8.0|^9.0|^10.0", 32 | "pestphp/pest": "^2.34|^3.7" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Spatie\\Html\\": "src" 37 | }, 38 | "files": [ 39 | "src/helpers.php" 40 | ] 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "Spatie\\Html\\Test\\": "tests" 45 | } 46 | }, 47 | "scripts": { 48 | "test": "vendor/bin/pest" 49 | }, 50 | "config": { 51 | "sort-packages": true, 52 | "allow-plugins": { 53 | "pestphp/pest-plugin": true 54 | } 55 | }, 56 | "extra": { 57 | "laravel": { 58 | "providers": [ 59 | "Spatie\\Html\\HtmlServiceProvider" 60 | ], 61 | "aliases": { 62 | "Html": "Spatie\\Html\\Facades\\Html" 63 | } 64 | } 65 | }, 66 | "minimum-stability": "dev", 67 | "prefer-stable": true 68 | } 69 | -------------------------------------------------------------------------------- /src/Elements/Textarea.php: -------------------------------------------------------------------------------- 1 | html($value); 48 | } 49 | 50 | /** 51 | * @param int $rows 52 | * 53 | * @return static 54 | */ 55 | public function rows(int $rows) 56 | { 57 | return $this->attribute('rows', $rows); 58 | } 59 | 60 | /** 61 | * @param int $cols 62 | * 63 | * @return static 64 | */ 65 | public function cols(int $cols) 66 | { 67 | return $this->attribute('cols', $cols); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Elements/Input.php: -------------------------------------------------------------------------------- 1 | checked(false); 49 | } 50 | 51 | /** 52 | * @param bool $checked 53 | * 54 | * @return static 55 | */ 56 | public function checked($checked = true) 57 | { 58 | return $checked 59 | ? $this->attribute('checked', 'checked') 60 | : $this->forgetAttribute('checked'); 61 | } 62 | 63 | /** 64 | * @param string|null $size 65 | * 66 | * @return static 67 | */ 68 | public function size($size) 69 | { 70 | return $this->attribute('size', $size); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Elements/Img.php: -------------------------------------------------------------------------------- 1 | attribute('alt', $alt); 36 | } 37 | 38 | /** 39 | * @param string|null $src 40 | * 41 | * @return static 42 | */ 43 | public function src($src) 44 | { 45 | return $this->attribute('src', $src); 46 | } 47 | 48 | /** 49 | * @param string|null $srcset 50 | * 51 | * @return static 52 | */ 53 | public function srcset($srcset) 54 | { 55 | return $this->attribute('srcset', $srcset); 56 | } 57 | 58 | /** 59 | * @param string|null $loading 60 | * 61 | * @return static 62 | */ 63 | public function loading($loading) 64 | { 65 | return $this->attribute('loading', $loading); 66 | } 67 | 68 | /** 69 | * @param string|null $crossorigin 70 | * 71 | * @return static 72 | */ 73 | public function crossorigin($crossorigin) 74 | { 75 | return $this->attribute('crossorigin', $crossorigin); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Elements/File.php: -------------------------------------------------------------------------------- 1 | attributes->setAttribute('type', 'file'); 46 | } 47 | 48 | /** 49 | * @param string|null $type 50 | * 51 | * @return static 52 | */ 53 | public function accept($type) 54 | { 55 | return $this->attribute('accept', $type); 56 | } 57 | 58 | /** 59 | * @return static 60 | */ 61 | public function acceptAudio() 62 | { 63 | return $this->attribute('accept', self::ACCEPT_AUDIO); 64 | } 65 | 66 | /** 67 | * @return static 68 | */ 69 | public function acceptVideo() 70 | { 71 | return $this->attribute('accept', self::ACCEPT_VIDEO); 72 | } 73 | 74 | /** 75 | * @return static 76 | */ 77 | public function acceptImage() 78 | { 79 | return $this->attribute('accept', self::ACCEPT_IMAGE); 80 | } 81 | 82 | /** 83 | * @return static 84 | */ 85 | public function multiple() 86 | { 87 | return $this->attribute('multiple'); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Elements/Form.php: -------------------------------------------------------------------------------- 1 | attribute('action', $action); 43 | } 44 | 45 | /** 46 | * @param string|null $route 47 | * @param mixed $params 48 | * 49 | * @return static 50 | */ 51 | public function route($route, ...$params) 52 | { 53 | return $this->action(route($route, ...$params)); 54 | } 55 | 56 | /** 57 | * @param string|null $method 58 | * 59 | * @return static 60 | */ 61 | public function method($method) 62 | { 63 | return $this->attribute('method', $method); 64 | } 65 | 66 | /** 67 | * @param bool $novalidate 68 | * 69 | * @return static 70 | */ 71 | public function novalidate($novalidate = true) 72 | { 73 | return $novalidate 74 | ? $this->attribute('novalidate') 75 | : $this->forgetAttribute('novalidate'); 76 | } 77 | 78 | /** 79 | * @return static 80 | */ 81 | public function acceptsFiles() 82 | { 83 | return $this->attribute('enctype', 'multipart/form-data'); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Attributes.php: -------------------------------------------------------------------------------- 1 | $value) { 23 | if ($attribute === 'class') { 24 | $this->addClass($value); 25 | 26 | continue; 27 | } 28 | 29 | if (is_int($attribute)) { 30 | $attribute = $value; 31 | $value = ''; 32 | } 33 | 34 | $this->setAttribute($attribute, (string) $value); 35 | } 36 | 37 | return $this; 38 | } 39 | 40 | /** 41 | * @param string $attribute 42 | * @param string|null $value 43 | * 44 | * @return $this 45 | */ 46 | public function setAttribute($attribute, $value = null) 47 | { 48 | if ($attribute === 'class') { 49 | $this->addClass($value); 50 | 51 | return $this; 52 | } 53 | 54 | $this->attributes[$attribute] = $value; 55 | 56 | return $this; 57 | } 58 | 59 | /** 60 | * @param string $attribute 61 | * 62 | * @return $this 63 | */ 64 | public function forgetAttribute($attribute) 65 | { 66 | if ($attribute === 'class') { 67 | $this->classes = []; 68 | 69 | return $this; 70 | } 71 | 72 | if (isset($this->attributes[$attribute])) { 73 | unset($this->attributes[$attribute]); 74 | } 75 | 76 | return $this; 77 | } 78 | 79 | /** 80 | * @param string $attribute 81 | * @param mixed $fallback 82 | * 83 | * @return mixed 84 | */ 85 | public function getAttribute($attribute, $fallback = null) 86 | { 87 | if ($attribute === 'class') { 88 | return implode(' ', $this->classes); 89 | } 90 | 91 | return $this->attributes[$attribute] ?? $fallback; 92 | } 93 | 94 | public function hasAttribute(string $attribute): bool 95 | { 96 | return array_key_exists($attribute, $this->attributes); 97 | } 98 | 99 | /** 100 | * @param string|iterable $class 101 | */ 102 | public function addClass($class) 103 | { 104 | if (is_string($class)) { 105 | $class = explode(' ', $class); 106 | } 107 | 108 | $class = Arr::getToggledValues($class); 109 | 110 | $this->classes = array_unique( 111 | array_merge($this->classes, $class) 112 | ); 113 | } 114 | 115 | /** 116 | * @return bool 117 | */ 118 | public function isEmpty() 119 | { 120 | return empty($this->attributes) && empty($this->classes); 121 | } 122 | 123 | /** 124 | * @return array 125 | */ 126 | public function toArray() 127 | { 128 | if (empty($this->classes)) { 129 | return $this->attributes; 130 | } 131 | 132 | return array_merge(['class' => implode(' ', $this->classes)], $this->attributes); 133 | } 134 | 135 | /** 136 | * @return string 137 | */ 138 | public function render() 139 | { 140 | if ($this->isEmpty()) { 141 | return ''; 142 | } 143 | 144 | $attributeStrings = []; 145 | 146 | foreach ($this->toArray() as $attribute => $value) { 147 | if ($value === '') { 148 | $attributeStrings[] = $attribute; 149 | 150 | continue; 151 | } 152 | 153 | $value = htmlentities($value, ENT_QUOTES, 'UTF-8', false); 154 | 155 | $attributeStrings[] = "{$attribute}=\"{$value}\""; 156 | } 157 | 158 | return implode(' ', $attributeStrings); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Elements/Select.php: -------------------------------------------------------------------------------- 1 | attribute('multiple'); 59 | 60 | $name = $element->getAttribute('name'); 61 | 62 | if ($name && ! Str::endsWith($name, '[]')) { 63 | $element = $element->name($name.'[]'); 64 | } 65 | 66 | $element->applyValueToOptions(); 67 | 68 | return $element; 69 | } 70 | 71 | /** 72 | * @param iterable $options 73 | * 74 | * @return static 75 | */ 76 | public function options($options) 77 | { 78 | return $this->addChildren($options, function ($text, $value) { 79 | if (is_array($text) || $text instanceof Collection) { 80 | return $this->optgroup($value, $text); 81 | } 82 | 83 | return Option::create() 84 | ->value($value) 85 | ->text($text) 86 | ->selectedIf($value === $this->value); 87 | }); 88 | } 89 | 90 | /** 91 | * @param string $label 92 | * @param iterable $options 93 | * 94 | * @return static 95 | */ 96 | public function optgroup($label, $options) 97 | { 98 | return Optgroup::create() 99 | ->label($label) 100 | ->addChildren($options, function ($text, $value) { 101 | return Option::create() 102 | ->value($value) 103 | ->text($text) 104 | ->selectedIf($value === $this->value); 105 | }); 106 | } 107 | 108 | /** 109 | * @param string|null $text 110 | * 111 | * @return static 112 | */ 113 | public function placeholder($text) 114 | { 115 | return $this->prependChild( 116 | Option::create() 117 | ->value(null) 118 | ->text($text) 119 | ->selectedIf(! $this->hasSelection()) 120 | ); 121 | } 122 | 123 | /** 124 | * @param string|iterable $value 125 | * 126 | * @return static 127 | */ 128 | public function value($value = null) 129 | { 130 | $element = clone $this; 131 | 132 | $element->value = $value; 133 | 134 | $element->applyValueToOptions(); 135 | 136 | return $element; 137 | } 138 | 139 | protected function hasSelection() 140 | { 141 | return $this->children->contains->hasAttribute('selected'); 142 | } 143 | 144 | protected function applyValueToOptions() 145 | { 146 | $value = $this->value instanceof \Illuminate\Support\Collection 147 | ? $this->value 148 | : Collection::make($this->value); 149 | 150 | if (! $this->hasAttribute('multiple')) { 151 | $value = $value->take(1); 152 | } 153 | 154 | $this->children = $this->applyValueToElements($value, $this->children); 155 | } 156 | 157 | protected function applyValueToElements($value, Collection $children) 158 | { 159 | return $children->map(function ($child) use ($value) { 160 | if ($child instanceof Optgroup) { 161 | return $child->setChildren($this->applyValueToElements($value, $child->children)); 162 | } 163 | 164 | if ($child instanceof Selectable) { 165 | return $child->selectedIf($value->contains($child->getAttribute('value'))); 166 | } 167 | 168 | return $child; 169 | }); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | Logo for laravel-html 6 | 7 | 8 | 9 |

Painless HTML generation

10 | 11 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-html.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-html) 12 | [![MIT Licensed](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 13 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-html.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-html) 14 | 15 |
16 | 17 | This package helps you generate HTML using a clean, simple and easy to read API. All elements can be dynamically generated and put together. The HTML builder helps you generate dynamically assigned form elements based on your selected model, the session or a default value. 18 | 19 | ## Support us 20 | 21 | [](https://spatie.be/github-ad-click/laravel-html) 22 | 23 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 24 | 25 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 26 | 27 | ## Postcardware 28 | 29 | You're free to use this package (it's [MIT-licensed](LICENSE.md)), but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. 30 | 31 | Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium. 32 | 33 | All postcards are published [on our website](https://spatie.be/en/opensource/postcards). 34 | 35 | ## Installation 36 | 37 | You can install the package via composer: 38 | 39 | ``` bash 40 | composer require spatie/laravel-html 41 | ``` 42 | 43 | And optionally register an alias for the facade. 44 | 45 | ```php 46 | // config/app.php 47 | 'aliases' => [ 48 | ... 49 | 'Html' => Spatie\Html\Facades\Html::class, 50 | ]; 51 | ``` 52 | 53 | ## Usage 54 | 55 | ### Concepts 56 | 57 | Elements—classes under the `Spatie\Html\Elements` namespace—are generally created via a `Spatie\Html\Html` builder instance. 58 | 59 | ```php 60 | html()->span()->text('Hello world!'); 61 | ``` 62 | 63 | Element attributes and contents are modified via with fluent methods which return a new instance. This means element instances are immutable. 64 | 65 | ```php 66 | $icon = html()->span()->class('fa'); 67 | 68 | $icon->class('fa-eye'); // '' 69 | $icon->class('fa-eye-slash'); // '' 70 | ``` 71 | 72 | Element classes don't have any knowledge of the outside world. Any coupling to other concepts, like requests and sessions, should happen in the builder class, not on the element classes. 73 | 74 | By convention, we assume that builder methods will modify values to our advantage (like pulling old values from the session on a failed form request), and element methods will be deterministic. 75 | 76 | ```php 77 | // This will try to resolve an initial value, and fall back to 'hello@example.com' 78 | $email = html()->email('email', 'hello@example.com'); 79 | 80 | // This will always have 'hello@example.com' as it's value 81 | $email = html()->email('email')->value('hello@example.com'); 82 | ``` 83 | 84 | ## Upgrading 85 | 86 | ### From v1 to v2 87 | 88 | Version 2 was created because the typehints in version 1 was holding the package back in some cases (like multiple select which requires an array of values instead of a string which was assumed). 89 | 90 | Luckily, bumping the version number in `composer.json` and running `composer update` should be non-breaking. Here are some caveats to look out for: 91 | 92 | - The package now ships with a `html()` function by default, which returns an instance of the `Html` builder class. If you've defined your own method, you'll need to remove it. 93 | - Various type hints have been removed throughout the package, if you've extended a class to override its methods, you'll need to update them accordingly (everything still behaves the same!) 94 | 95 | ## Changelog 96 | 97 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 98 | 99 | ## Testing 100 | 101 | ```bash 102 | $ composer test 103 | ``` 104 | 105 | ## Contributing 106 | 107 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 108 | 109 | ## Security 110 | 111 | If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. 112 | 113 | ## Credits 114 | 115 | - [Sebastian De Deyne](https://github.com/sebastiandedeyne) 116 | - [Freek Van der Herten](https://github.com/freekmurze) 117 | - [All Contributors](../../contributors) 118 | 119 | ## About Spatie 120 | Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). 121 | 122 | ## License 123 | 124 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 125 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-html` will be documented in this file. 4 | 5 | ## 3.12.3 - 2025-12-22 6 | 7 | ### What's Changed 8 | 9 | * make BaseElement::unless() compatible with Conditionable::unless() by @miken32 in https://github.com/spatie/laravel-html/pull/260 10 | 11 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.12.2...3.12.3 12 | 13 | ## 3.12.2 - 2025-12-22 14 | 15 | ### What's Changed 16 | 17 | * Document conditional methods by @miken32 in https://github.com/spatie/laravel-html/pull/261 18 | 19 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.12.1...3.12.2 20 | 21 | ## 3.12.1 - 2025-10-02 22 | 23 | ### What's Changed 24 | 25 | * Update issue template by @AlexVanderbist in https://github.com/spatie/laravel-html/pull/257 26 | * Add support for multiples requests in the same process by @gtg-bantonio in https://github.com/spatie/laravel-html/pull/258 27 | 28 | ### New Contributors 29 | 30 | * @gtg-bantonio made their first contribution in https://github.com/spatie/laravel-html/pull/258 31 | 32 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.12.0...3.12.1 33 | 34 | ## 3.12.0 - 2025-03-21 35 | 36 | ### What's Changed 37 | 38 | * add disabled option to file input by @it-can in https://github.com/spatie/laravel-html/pull/253 39 | 40 | ### New Contributors 41 | 42 | * @it-can made their first contribution in https://github.com/spatie/laravel-html/pull/253 43 | 44 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.11.3...3.12.0 45 | 46 | ## 3.11.3 - 2025-02-17 47 | 48 | ### What's Changed 49 | 50 | * Laravel 12.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-html/pull/251 51 | 52 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.11.2...3.11.3 53 | 54 | ## 3.11.2 - 2025-02-05 55 | 56 | ### What's Changed 57 | 58 | * Radio buttons with value 0 are incorrectly marked as checked by @acarpio89 in https://github.com/spatie/laravel-html/pull/249 59 | 60 | ### New Contributors 61 | 62 | * @acarpio89 made their first contribution in https://github.com/spatie/laravel-html/pull/249 63 | 64 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.11.1...3.11.2 65 | 66 | ## 3.11.1 - 2024-10-18 67 | 68 | ### What's Changed 69 | 70 | * fix: Passing null to parameter #1 ($string) of type string is deprecated by @francoism90 in https://github.com/spatie/laravel-html/pull/244 71 | 72 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.11.0...3.11.1 73 | 74 | ## 3.11.0 - 2024-07-16 75 | 76 | ### What's Changed 77 | 78 | * Add some attributes by @francoism90 in https://github.com/spatie/laravel-html/pull/239 79 | 80 | ### New Contributors 81 | 82 | * @francoism90 made their first contribution in https://github.com/spatie/laravel-html/pull/239 83 | 84 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.10.1...3.11.0 85 | 86 | ## 3.10.1 - 2024-07-15 87 | 88 | ### What's Changed 89 | 90 | * Fix value omitted when input created with no name by @raveren in https://github.com/spatie/laravel-html/pull/235 91 | * Fix value omitted when input created with no name by @raveren in https://github.com/spatie/laravel-html/pull/236 92 | * Fix for Select and model's relationships by @nikosv in https://github.com/spatie/laravel-html/pull/237 93 | 94 | ### New Contributors 95 | 96 | * @nikosv made their first contribution in https://github.com/spatie/laravel-html/pull/237 97 | 98 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.10.0...3.10.1 99 | 100 | ## 3.10.0 - 2024-07-03 101 | 102 | ### What's Changed 103 | 104 | * Update docs for name attribute by @bskl in https://github.com/spatie/laravel-html/pull/225 105 | * Add Conditionable trait: now `->when()` helper is available on all elements by @raveren in https://github.com/spatie/laravel-html/pull/234 106 | 107 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.9.0...3.10.0 108 | 109 | ## 3.9.0 - 2024-04-25 110 | 111 | ### What's Changed 112 | 113 | * Add use statement by @bskl in https://github.com/spatie/laravel-html/pull/222 114 | * Add aria helper method by @bskl in https://github.com/spatie/laravel-html/pull/226 115 | 116 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.8.0...3.9.0 117 | 118 | ## 3.8.0 - 2024-04-24 119 | 120 | ### What's Changed 121 | 122 | * Add autocomplete attribute helper to the form element by @raveren in https://github.com/spatie/laravel-html/pull/221 123 | * Added support for Htmlable contents in BaseElement by @hemmesdev in https://github.com/spatie/laravel-html/pull/215 124 | * Register Service Provider in Laravel 11 by @gqrdev in https://github.com/spatie/laravel-html/pull/224 125 | * Add name attribute to form element by @bskl in https://github.com/spatie/laravel-html/pull/223 126 | 127 | ### New Contributors 128 | 129 | * @hemmesdev made their first contribution in https://github.com/spatie/laravel-html/pull/215 130 | * @gqrdev made their first contribution in https://github.com/spatie/laravel-html/pull/224 131 | 132 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.7.0...3.8.0 133 | 134 | ## 3.7.0 - 2024-03-23 135 | 136 | ### What's Changed 137 | 138 | * Fix return value in docs in element-methods.md by @raveren in https://github.com/spatie/laravel-html/pull/218 139 | * Add autocomplete attribute helper to input, select and textarea by @raveren in https://github.com/spatie/laravel-html/pull/219 140 | * Fix link with version in documentation by @fey in https://github.com/spatie/laravel-html/pull/217 141 | 142 | ### New Contributors 143 | 144 | * @raveren made their first contribution in https://github.com/spatie/laravel-html/pull/218 145 | * @fey made their first contribution in https://github.com/spatie/laravel-html/pull/217 146 | 147 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.6.0...3.7.0 148 | 149 | ## 3.6.0 - 2024-03-08 150 | 151 | ### What's Changed 152 | 153 | * Laravel 11.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-html/pull/214 154 | 155 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.5.0...3.6.0 156 | 157 | ## 3.4.0 - 2024-01-05 158 | 159 | ### What's Changed 160 | 161 | * Fix docblock to solve phpstan errors when passing an array to html()->div() by @SanderMuller in https://github.com/spatie/laravel-html/pull/210 162 | * Documentation on how to extend the package by @azamtav in https://github.com/spatie/laravel-html/pull/204 163 | 164 | ### New Contributors 165 | 166 | * @SanderMuller made their first contribution in https://github.com/spatie/laravel-html/pull/210 167 | * @azamtav made their first contribution in https://github.com/spatie/laravel-html/pull/204 168 | 169 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.3.0...3.4.0 170 | 171 | ## 3.3.0 - 2023-10-24 172 | 173 | ### What's Changed 174 | 175 | - Add documentation for new FormElement::route() method by @miken32 in https://github.com/spatie/laravel-html/pull/190 176 | - Update `.gitattributes` by @totoprayogo1916 in https://github.com/spatie/laravel-html/pull/194 177 | - Correction to docs re: readonly vs isReadonly by @sgilberg in https://github.com/spatie/laravel-html/pull/195 178 | - Get value from model with casts php native enum by @bskl in https://github.com/spatie/laravel-html/pull/203 179 | 180 | ### New Contributors 181 | 182 | - @totoprayogo1916 made their first contribution in https://github.com/spatie/laravel-html/pull/194 183 | - @sgilberg made their first contribution in https://github.com/spatie/laravel-html/pull/195 184 | - @bskl made their first contribution in https://github.com/spatie/laravel-html/pull/203 185 | 186 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.2.2...3.3.0 187 | 188 | ## 3.2.2 - 2023-07-20 189 | 190 | ### What's Changed 191 | 192 | - Allow setting a form action to a route by @miken32 in https://github.com/spatie/laravel-html/pull/189 193 | 194 | ### New Contributors 195 | 196 | - @miken32 made their first contribution in https://github.com/spatie/laravel-html/pull/189 197 | 198 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.2.1...3.2.2 199 | 200 | ## 3.2.1 - 2023-01-24 201 | 202 | ### What's Changed 203 | 204 | - Convert all tests to Pest by @alexmanase in https://github.com/spatie/laravel-html/pull/183 205 | - Laravel 10.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-html/pull/184 206 | 207 | ### New Contributors 208 | 209 | - @alexmanase made their first contribution in https://github.com/spatie/laravel-html/pull/183 210 | - @laravel-shift made their first contribution in https://github.com/spatie/laravel-html/pull/184 211 | 212 | **Full Changelog**: https://github.com/spatie/laravel-html/compare/3.2.0...3.2.1 213 | 214 | ## 3.2.0 - 2022-12-27 215 | 216 | - Add `P` class to render paragraphs 217 | 218 | ## 3.1.0 - 2022-01-14 219 | 220 | - Allow Laravel 9 221 | 222 | ## 3.0.0 - 2021-11-17 223 | 224 | - Add compatiblity with PHP 8.1. The only breaking change with v2 is that `readonly` has been renamed to `isReadonly`. 225 | 226 | ## 2.30.0 - 2022-07-09 227 | 228 | - Better support for numeric values in attributes 229 | 230 | ## 2.29.0 - 2021-02-09 231 | 232 | - Add `target` attribute method to links and button 233 | 234 | ## 2.28.1 - 2020-11-30 235 | 236 | - add support for PHP 8 237 | 238 | ## 2.28.0 - 2020-09-30 239 | 240 | - add the disabled method to all elements that support the attribute (#165) 241 | 242 | ## 2.27.0 - 2020-09-09 243 | 244 | - Add support for Laravel 8 245 | 246 | ## 2.26.0 - 2020-04-20 247 | 248 | - Internal refactor to normalize availabel attribute methods 249 | 250 | ## 2.25.0 - 2020-03-02 251 | 252 | - add Laravel 7 support 253 | 254 | ## 2.24.0 - 2019-09-04 255 | 256 | - Added number input 257 | 258 | ## 2.23.0 - 2019-09-04 259 | 260 | - Laravel 6 support 261 | - Better handling for `0` values in inputs 262 | - Add `range` for range inputs 263 | - Format date and time values 264 | 265 | ## 2.22.1 - 2019-07-16 266 | 267 | - Prevent password fields to be filled 268 | 269 | ## 2.22.0 - 2019-04-26 270 | 271 | - Changed the `value` parameter in `data` to an optional parameter 272 | 273 | ## 2.21.0 - 2019-02-27 274 | 275 | - Added Laravel 5.8 support 276 | - Dropped PHP 7.0 support 277 | - Dropped Laravel 5.4 support 278 | - Dropped PHPUnit 6 support 279 | 280 | ## 2.20.1 - 2019-02-01 281 | 282 | - use `Arr::` and `Str::` functions 283 | 284 | ## 2.20.0 - 2019-01-18 285 | 286 | - Added `unless` method and magic `__call` handler (e.g. `$input->valueUnless(false, 5)`) 287 | - Added `size` attribute method to `Input` 288 | - Added `name` attribute method to `Button` 289 | - Fixed checkbox value repopulation after request 290 | 291 | ## 2.19.9 - 2019-01-10 292 | 293 | - Improve default of `tel` link 294 | 295 | ## 2.19.8 - 2018-09-04 296 | 297 | - Add support for Laravel 5.7 298 | 299 | ## 2.19.7 - 2018-04-30 300 | 301 | - Allow radio input check "0" value 302 | 303 | ## 2.19.6 - 2018-04-30 304 | 305 | - Correctly prefill form array attributes from the model 306 | 307 | ## 2.19.5 - 2018-04-04 308 | 309 | - Allow `null` children 310 | 311 | ## 2.19.4 - 2018-03-28 312 | 313 | - Revert comparison function change in `2.19.2` 314 | 315 | ## 2.19.2 - 2018-03-26 316 | 317 | - Fixed comparison function for selected options in `Select` 318 | 319 | ## 2.19.1 - 2018-03-23 320 | 321 | - Fixed `Html::radio` auto-generated id's & checked behaviour 322 | 323 | ## 2.19.0 - 2018-03-09 324 | 325 | - Changed `Input::require` to accept a boolean value 326 | 327 | ## 2.18.0 - 2018-03-02 328 | 329 | - Added `I` element class and `Html::i` factory method 330 | 331 | ## 2.17.0 - 2018-02-28 332 | 333 | - Added `Html::value` function that's a public method for `old` 334 | 335 | ## 2.16.0 - 2018-02-26 336 | 337 | - Added `Img` element class and `Html::img` factory method 338 | 339 | ## 2.15.1 - 2018-02-26 340 | 341 | - Removed `id` from CSRF fields 342 | 343 | ## 2.15.0 - 2018-02-23 344 | 345 | - Added `Input::date` and `Input::time` 346 | 347 | ## 2.14.0 - 2018-02-22 348 | 349 | - Added `Input::disabled` 350 | 351 | ## 2.13.1 - 2018-02-20 352 | 353 | - Added `Form::novalidate` 354 | 355 | ## 2.12.1 - 2018-02-08 356 | 357 | - Fixed Laravel 5.6 compatibility 358 | 359 | ## 2.12.0 - 2018-02-08 360 | 361 | - Added Laravel 5.6 compatibility 362 | - Fixed an issue with checkbox values 363 | 364 | ## 2.11.0 - 2018-02-02 365 | 366 | - Add `readonly` method to input 367 | 368 | ## 2.10.3 - 2018-01-09 369 | 370 | - Fix `__call` when using macros 371 | 372 | ## 2.10.2 - 2017-12-28 373 | 374 | - `Htmlable` elements can now be used in the `html()` method 375 | - Array notation is now implicitly converted to dot notation in `old` (e.g. `foo[1] -> foo.1`) 376 | 377 | ## 2.10.1 - 2017-12-18 378 | 379 | - Fixed old values containing `0` 380 | 381 | ## 2.10.0 - 2017-11-08 382 | 383 | - Added `required` method to `Select` 384 | 385 | ## 2.9.0 - 2017-10-20 386 | 387 | - Added `required` method to `Textarea` 388 | 389 | ## 2.8.2 - 2017-10-13 390 | 391 | - Fixed a bug with values that are a `"0"` string 392 | 393 | ## 2.8.1 - 2017-10-12 394 | 395 | - Fixed a bug with values that are a `"0"` string 396 | 397 | ## 2.8.0 - 2017-10-12 398 | 399 | - Added a magic `__call` method that responds to methods ending with `If`, so any method can be called with a condition as it's first argument. The method will only be applied if the condition is truthy. 400 | 401 | ## 2.7.0 - 2017-10-11 402 | 403 | - Added `BaseElement::data` for data attributes 404 | 405 | ## 2.6.0 - 2017-10-11 406 | 407 | - Added `BaseElement::setChildren` to replace all children 408 | - Fixed a bug that didn't select options in optgroups when applying a value 409 | 410 | ## 2.5.0 - 2017-10-11 411 | 412 | - Added `BaseElement::style` for setting the style attribute (with a string or an associative array) 413 | - Added `Html::reset` for form reset buttons 414 | 415 | ## 2.4.1 - 2017-09-07 416 | 417 | - Nothing changed, but `2.2.0` was accidentally tagged as `2.4.0`. This release contains the actual latest version at the time of writing. 418 | 419 | ## 2.3.0 - 2017-09-04 420 | 421 | - Added `checked` and `unchecked` methods to `Input` 422 | 423 | ## 2.2.0 - 2017-08-29 424 | 425 | - Added `Optgroup` element 426 | - Added the ability to create optgroups in `Options` by passing an array of groups with options 427 | 428 | ## 2.1.0 - 2017-08-24 429 | 430 | - Added `Html::file` and a `File` element for file inputs 431 | 432 | ## 2.0.2 - 2017-07-14 433 | 434 | - Fixed an issue that stripped square brackets from element attributes 435 | 436 | ## 2.0.1 - 2017-06-28 437 | 438 | - Fixed the `Html` facade 439 | 440 | ## 2.0.0 - 2017-06-13 441 | 442 | - Minimum requirements have been reduced to PHP 7.0 443 | - Added a `html()` helper function that returns an instance of `Html` 444 | - Added `Macroable` to all elements and `Html` 445 | - Loosened type hints in method signatures for flexibility 446 | - Added `Html::multiselect` method 447 | - Added `Select::multiple` method 448 | 449 | ## 1.5.0 - 2017-05-19 450 | 451 | - Added `class` method to `Html` 452 | 453 | ## 1.4.0 - 2017-05-16 454 | 455 | - Added a `placeholder` method to `Textarea` 456 | 457 | ## 1.3.1 - 2017-05-09 458 | 459 | - Added an empty `value` to `Select::placeholder` 460 | 461 | ## 1.3.0 - 2017-05-08 462 | 463 | - Added a `placeholder` method to `Select` for default empty values 464 | 465 | ## 1.2.0 - 2017-04-28 466 | 467 | - Added a `Html` facade 468 | 469 | ## 1.1.1 - 2017-04-27 470 | 471 | - Fixed an issue where html was escaped when you didn't want it to do that, like in buttons and links 472 | 473 | ## 1.1.0 - 2017-04-19 474 | 475 | - Added `Html::radio` 476 | - Fixed an issue that set the wrong `value` for a checkbox created with `Html::checkbox` 477 | - Fixed a case sensitivity issue with the `Textarea` class 478 | 479 | ## 1.0.0 - 2017-03-31 480 | 481 | - Initial release 482 | -------------------------------------------------------------------------------- /src/BaseElement.php: -------------------------------------------------------------------------------- 1 | $attributes) 21 | * @method static attributesIfNotNull(bool $condition, iterable $attributes) 22 | * @method static attributesUnless(bool $condition, iterable $attributes) 23 | * @method static forgetAttributeIf(bool $condition, string $attribute) 24 | * @method static forgetAttributeIfNotNull(bool $condition, string $attribute) 25 | * @method static forgetAttributeUnless(bool $condition, string $attribute) 26 | * @method static classIf(bool $condition, string|iterable $class) 27 | * @method static classIfNotNull(bool $condition, string|iterable $class) 28 | * @method static classUnless(bool $condition, string|iterable $class) 29 | * @method static addClassIf(bool $condition, string|iterable $class) 30 | * @method static addClassIfNotNull(bool $condition, string|iterable $class) 31 | * @method static addClassUnless(bool $condition, string|iterable $class) 32 | * @method static idIf(bool $condition, string $id) 33 | * @method static idIfNotNull(bool $condition, string $id) 34 | * @method static idUnless(bool $condition, string $id) 35 | * @method static styleIf(bool $condition, array|string|null $style) 36 | * @method static styleIfNotNull(bool $condition, array|string|null $style) 37 | * @method static styleUnless(bool $condition, array|string|null $style) 38 | * @method static dataIf(bool $condition, string $name, string|null $value) 39 | * @method static dataIfNotNull(bool $condition, string $name, string|null $value) 40 | * @method static dataUnless(bool $condition, string $name, string|null $value) 41 | * @method static ariaIf(bool $condition, string $name, string|null $value) 42 | * @method static ariaIfNotNull(bool $condition, string $name, string|null $value) 43 | * @method static ariaUnless(bool $condition, string $name, string|null $value) 44 | * @method static addChildrenIf(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $children, callable|null $mapper) 45 | * @method static addChildrenIfNotNull(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $children, callable|null $mapper) 46 | * @method static addChildrenUnless(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $children, callable|null $mapper) 47 | * @method static addChildIf(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $child, callable|null $mapper) 48 | * @method static addChildIfNotNull(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $child, callable|null $mapper) 49 | * @method static addChildUnless(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $child, callable|null $mapper) 50 | * @method static childIf(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $child, callable|null $mapper) 51 | * @method static childIfNotNull(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $child, callable|null $mapper) 52 | * @method static childUnless(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $child, callable|null $mapper) 53 | * @method static childrenIf(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $children, callable|null $mapper) 54 | * @method static childrenIfNotNull(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $children, callable|null $mapper) 55 | * @method static childrenUnless(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $children, callable|null $mapper) 56 | * @method static setChildrenIf(bool $condition, \Spatie\Html\HtmlElement[] $children, callable|null $mapper) 57 | * @method static setChildrenIfNotNull(bool $condition, \Spatie\Html\HtmlElement[] $children, callable|null $mapper) 58 | * @method static setChildrenUnless(bool $condition, \Spatie\Html\HtmlElement[] $children, callable|null $mapper) 59 | * @method static prependChildrenIf(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $children, callable|null $mapper) 60 | * @method static prependChildrenIfNotNull(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $children, callable|null $mapper) 61 | * @method static prependChildrenUnless(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $children, callable|null $mapper) 62 | * @method static prependChildIf(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $children, callable|null $mapper) 63 | * @method static prependChildIfNotNull(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $children, callable|null $mapper) 64 | * @method static prependChildUnless(bool $condition, \Spatie\Html\HtmlElement|string|iterable|int|float|null $children, callable|null $mapper) 65 | * @method static textIf(bool $condition, string|null $text) 66 | * @method static textIfNotNull(bool $condition, string|null $text) 67 | * @method static textUnless(bool $condition, string|null $text) 68 | * @method static htmlIf(bool $condition, string|null $html) 69 | * @method static htmlIfNotNull(bool $condition, string|null $html) 70 | * @method static htmlUnless(bool $condition, string|null $html) 71 | */ 72 | abstract class BaseElement implements Htmlable, HtmlElement 73 | { 74 | use Conditionable { 75 | unless as trait_unless; 76 | } 77 | 78 | use Macroable { 79 | __call as __macro_call; 80 | } 81 | 82 | /** @var string */ 83 | protected $tag; 84 | 85 | /** @var \Spatie\Html\Attributes */ 86 | protected $attributes; 87 | 88 | /** @var \Illuminate\Support\Collection */ 89 | protected $children; 90 | 91 | public function __construct() 92 | { 93 | if (empty($this->tag)) { 94 | throw MissingTag::onClass(static::class); 95 | } 96 | 97 | $this->attributes = new Attributes(); 98 | $this->children = new Collection(); 99 | } 100 | 101 | public static function create() 102 | { 103 | return new static(); 104 | } 105 | 106 | /** 107 | * @param string $attribute 108 | * @param string|null $value 109 | * 110 | * @return static 111 | */ 112 | public function attribute($attribute, $value = null) 113 | { 114 | $element = clone $this; 115 | 116 | $element->attributes->setAttribute($attribute, (string) $value); 117 | 118 | return $element; 119 | } 120 | 121 | /** 122 | * @param iterable $attributes 123 | * 124 | * @return static 125 | */ 126 | public function attributes($attributes) 127 | { 128 | $element = clone $this; 129 | 130 | $element->attributes->setAttributes($attributes); 131 | 132 | return $element; 133 | } 134 | 135 | /** 136 | * @param string $attribute 137 | * 138 | * @return static 139 | */ 140 | public function forgetAttribute($attribute) 141 | { 142 | $element = clone $this; 143 | 144 | $element->attributes->forgetAttribute($attribute); 145 | 146 | return $element; 147 | } 148 | 149 | /** 150 | * @param string $attribute 151 | * @param mixed $fallback 152 | * 153 | * @return mixed 154 | */ 155 | public function getAttribute($attribute, $fallback = null) 156 | { 157 | return $this->attributes->getAttribute($attribute, $fallback); 158 | } 159 | 160 | /** 161 | * @param string $attribute 162 | * 163 | * @return bool 164 | */ 165 | public function hasAttribute($attribute) 166 | { 167 | return $this->attributes->hasAttribute($attribute); 168 | } 169 | 170 | /** 171 | * @param iterable|string $class 172 | * 173 | * @return static 174 | */ 175 | public function class($class) 176 | { 177 | return $this->addClass($class); 178 | } 179 | 180 | /** 181 | * Alias for `class`. 182 | * 183 | * @param iterable|string $class 184 | * 185 | * @return static 186 | */ 187 | public function addClass($class) 188 | { 189 | $element = clone $this; 190 | 191 | $element->attributes->addClass($class); 192 | 193 | return $element; 194 | } 195 | 196 | /** 197 | * @param string $id 198 | * 199 | * @return static 200 | */ 201 | public function id($id) 202 | { 203 | return $this->attribute('id', $id); 204 | } 205 | 206 | /** 207 | * @param array|string|null $style 208 | * 209 | * @return static 210 | */ 211 | public function style($style) 212 | { 213 | if (is_array($style)) { 214 | $style = implode('; ', array_map(function ($value, $attribute) { 215 | return "{$attribute}: {$value}"; 216 | }, $style, array_keys($style))); 217 | } 218 | 219 | return $this->attribute('style', $style); 220 | } 221 | 222 | /** 223 | * @param string $name 224 | * @param string $value 225 | * 226 | * @return static 227 | */ 228 | public function data($name, $value = null) 229 | { 230 | return $this->attribute("data-{$name}", $value); 231 | } 232 | 233 | /** 234 | * @param string $attribute 235 | * @param string|null $value 236 | * 237 | * @return static 238 | */ 239 | public function aria($attribute, $value = null) 240 | { 241 | return $this->attribute("aria-{$attribute}", $value); 242 | } 243 | 244 | /** 245 | * @param \Spatie\Html\HtmlElement|string|iterable|int|float|null $children 246 | * @param callable|null $mapper 247 | * 248 | * @return static 249 | */ 250 | public function addChildren($children, $mapper = null) 251 | { 252 | if (is_null($children)) { 253 | return $this; 254 | } 255 | 256 | $children = $this->parseChildren($children, $mapper); 257 | 258 | $element = clone $this; 259 | 260 | $element->children = $element->children->merge($children); 261 | 262 | return $element; 263 | } 264 | 265 | /** 266 | * Alias for `addChildren`. 267 | * 268 | * @param \Spatie\Html\HtmlElement|string|iterable|int|float|null $child 269 | * @param callable|null $mapper 270 | * 271 | * @return static 272 | */ 273 | public function addChild($child, $mapper = null) 274 | { 275 | return $this->addChildren($child, $mapper); 276 | } 277 | 278 | /** 279 | * Alias for `addChildren`. 280 | * 281 | * @param \Spatie\Html\HtmlElement|string|iterable|int|float|null $child 282 | * @param callable|null $mapper 283 | * 284 | * @return static 285 | */ 286 | public function child($child, $mapper = null) 287 | { 288 | return $this->addChildren($child, $mapper); 289 | } 290 | 291 | /** 292 | * Alias for `addChildren`. 293 | * 294 | * @param \Spatie\Html\HtmlElement|string|iterable|int|float|null $children 295 | * @param callable|null $mapper 296 | * 297 | * @return static 298 | */ 299 | public function children($children, $mapper = null) 300 | { 301 | return $this->addChildren($children, $mapper); 302 | } 303 | 304 | /** 305 | * Replace all children with an array of elements. 306 | * 307 | * @param \Spatie\Html\HtmlElement[] $children 308 | * @param callable|null $mapper 309 | * 310 | * @return static 311 | */ 312 | public function setChildren($children, $mapper = null) 313 | { 314 | $element = clone $this; 315 | 316 | $element->children = new Collection(); 317 | 318 | return $element->addChildren($children, $mapper); 319 | } 320 | 321 | /** 322 | * @param \Spatie\Html\HtmlElement|string|iterable|int|float|null $children 323 | * @param callable|null $mapper 324 | * 325 | * @return static 326 | */ 327 | public function prependChildren($children, $mapper = null) 328 | { 329 | $children = $this->parseChildren($children, $mapper); 330 | 331 | $element = clone $this; 332 | 333 | $element->children = $children->merge($element->children); 334 | 335 | return $element; 336 | } 337 | 338 | /** 339 | * Alias for `prependChildren`. 340 | * 341 | * @param \Spatie\Html\HtmlElement|string|iterable|int|float|null $children 342 | * @param callable|null $mapper 343 | * 344 | * @return static 345 | */ 346 | public function prependChild($children, $mapper = null) 347 | { 348 | return $this->prependChildren($children, $mapper); 349 | } 350 | 351 | /** 352 | * @param string|null $text 353 | * 354 | * @return static 355 | */ 356 | public function text($text) 357 | { 358 | return $this->html(htmlentities($text ?? '', ENT_QUOTES, 'UTF-8', false)); 359 | } 360 | 361 | /** 362 | * @param string|null $html 363 | * 364 | * @return static 365 | */ 366 | public function html($html) 367 | { 368 | if ($this->isVoidElement()) { 369 | throw new InvalidHtml("Can't set inner contents on `{$this->tag}` because it's a void element"); 370 | } 371 | 372 | return $this->setChildren($html); 373 | } 374 | 375 | /** 376 | * Conditionally transform the element. Note that since elements are 377 | * immutable, you'll need to return a new instance from the callback. 378 | * 379 | * @param bool $condition 380 | * @param \Closure $callback 381 | * 382 | * @return mixed 383 | */ 384 | public function if(bool $condition, \Closure $callback) 385 | { 386 | return $condition ? $callback($this) : $this; 387 | } 388 | 389 | /** 390 | * Conditionally transform the element. Note that since elements are 391 | * immutable, you'll need to return a new instance from the callback. 392 | * 393 | * @param mixed $condition 394 | * @param callable $callback 395 | * @param callable|null $default 396 | * 397 | * @return mixed 398 | */ 399 | public function unless($condition, callable $callback, callable|null $default = null) 400 | { 401 | if (! is_bool($condition) || ! $callback instanceof \Closure || ! is_null($default)) { 402 | return $this->trait_unless($condition, $callback, $default); 403 | } 404 | 405 | return $this->if(! $condition, $callback); 406 | } 407 | 408 | /** 409 | * Conditionally transform the element. Note that since elements are 410 | * immutable, you'll need to return a new instance from the callback. 411 | * 412 | * @param mixed $value 413 | * @param \Closure $callback 414 | * 415 | * @return mixed 416 | */ 417 | public function ifNotNull($value, \Closure $callback) 418 | { 419 | return ! is_null($value) ? $callback($this) : $this; 420 | } 421 | 422 | /** 423 | * @return \Illuminate\Contracts\Support\Htmlable 424 | */ 425 | public function open() 426 | { 427 | $tag = $this->attributes->isEmpty() 428 | ? '<' . $this->tag . '>' 429 | : "<{$this->tag} {$this->attributes->render()}>"; 430 | 431 | $children = $this->children->map(function ($child): string { 432 | if ($child instanceof HtmlElement) { 433 | return $child->render(); 434 | } 435 | 436 | if (is_null($child)) { 437 | return ''; 438 | } 439 | 440 | if (is_string($child) || is_numeric($child)) { 441 | return $child; 442 | } 443 | 444 | throw InvalidChild::childMustBeAnHtmlElementOrAString(); 445 | })->implode(''); 446 | 447 | return new HtmlString($tag . $children); 448 | } 449 | 450 | /** 451 | * @return \Illuminate\Contracts\Support\Htmlable 452 | */ 453 | public function close() 454 | { 455 | return new HtmlString( 456 | $this->isVoidElement() 457 | ? '' 458 | : "tag}>" 459 | ); 460 | } 461 | 462 | /** 463 | * @return \Illuminate\Contracts\Support\Htmlable 464 | */ 465 | public function render() 466 | { 467 | return new HtmlString( 468 | $this->open() . $this->close() 469 | ); 470 | } 471 | 472 | public function isVoidElement(): bool 473 | { 474 | return in_array($this->tag, [ 475 | 'area', 'base', 'br', 'col', 'embed', 'hr', 476 | 'img', 'input', 'keygen', 'link', 'menuitem', 477 | 'meta', 'param', 'source', 'track', 'wbr', 478 | ]); 479 | } 480 | 481 | /** 482 | * Dynamically handle calls to the class. 483 | * Check for methods finishing by If or fallback to Macroable. 484 | * 485 | * @param string $name 486 | * @param array $arguments 487 | * @return mixed 488 | * 489 | * @throws BadMethodCallException 490 | */ 491 | public function __call($name, $arguments) 492 | { 493 | if (Str::endsWith($name, $conditions = ['If', 'Unless', 'IfNotNull'])) { 494 | foreach ($conditions as $condition) { 495 | if (! method_exists($this, $method = str_replace($condition, '', $name))) { 496 | continue; 497 | } 498 | 499 | return $this->callConditionalMethod($condition, $method, $arguments); 500 | } 501 | } 502 | 503 | return $this->__macro_call($name, $arguments); 504 | } 505 | 506 | protected function callConditionalMethod($type, $method, array $arguments) 507 | { 508 | $value = array_shift($arguments); 509 | $callback = function () use ($method, $arguments) { 510 | return $this->{$method}(...$arguments); 511 | }; 512 | 513 | switch ($type) { 514 | case 'If': 515 | return $this->if((bool) $value, $callback); 516 | case 'Unless': 517 | return $this->unless((bool) $value, $callback); 518 | case 'IfNotNull': 519 | return $this->ifNotNull($value, $callback); 520 | default: 521 | return $this; 522 | } 523 | } 524 | 525 | public function __clone() 526 | { 527 | $this->attributes = clone $this->attributes; 528 | $this->children = clone $this->children; 529 | } 530 | 531 | public function __toString(): string 532 | { 533 | return $this->render(); 534 | } 535 | 536 | public function toHtml(): string 537 | { 538 | return $this->render(); 539 | } 540 | 541 | protected function parseChildren($children, $mapper = null): Collection 542 | { 543 | if ($children instanceof HtmlElement) { 544 | $children = [$children]; 545 | } elseif ($children instanceof Htmlable) { 546 | $children = $children->toHtml(); 547 | } 548 | 549 | $children = Collection::make($children); 550 | 551 | if ($mapper) { 552 | $children = $children->map($mapper); 553 | } 554 | 555 | $this->guardAgainstInvalidChildren($children); 556 | 557 | return $children; 558 | } 559 | 560 | protected function guardAgainstInvalidChildren(Collection $children) 561 | { 562 | foreach ($children as $child) { 563 | if ($child instanceof HtmlElement || is_null($child) || is_string($child) || is_numeric($child)) { 564 | continue; 565 | } 566 | 567 | throw InvalidChild::childMustBeAnHtmlElementOrAString(); 568 | } 569 | } 570 | } 571 | -------------------------------------------------------------------------------- /src/Html.php: -------------------------------------------------------------------------------- 1 | attributeIf($href, 'href', $href) 56 | ->html($contents); 57 | } 58 | 59 | /** 60 | * @param string|null $href 61 | * @param string|null $text 62 | * 63 | * @return \Spatie\Html\Elements\I 64 | */ 65 | public function i($contents = null) 66 | { 67 | return I::create() 68 | ->html($contents); 69 | } 70 | 71 | /** 72 | * @param \Spatie\Html\HtmlElement|string|null $contents 73 | * 74 | * @return \Spatie\Html\Elements\P 75 | */ 76 | public function p($contents = null) 77 | { 78 | return P::create() 79 | ->html($contents); 80 | } 81 | 82 | /** 83 | * @param string|null $type 84 | * @param string|null $text 85 | * 86 | * @return \Spatie\Html\Elements\Button 87 | */ 88 | public function button($contents = null, $type = null, $name = null) 89 | { 90 | return Button::create() 91 | ->attributeIf($type, 'type', $type) 92 | ->attributeIf($name, 'name', $this->fieldName($name)) 93 | ->html($contents); 94 | } 95 | 96 | /** 97 | * @param \Illuminate\Support\Collection|iterable|string $classes 98 | * 99 | * @return \Illuminate\Contracts\Support\Htmlable 100 | */ 101 | public function class($classes): Htmlable 102 | { 103 | if ($classes instanceof Collection) { 104 | $classes = $classes->toArray(); 105 | } 106 | 107 | $attributes = new Attributes(); 108 | $attributes->addClass($classes); 109 | 110 | return new HtmlString( 111 | $attributes->render() 112 | ); 113 | } 114 | 115 | /** 116 | * @param string|null $name 117 | * @param bool $checked 118 | * @param string|null $value 119 | * 120 | * @return \Spatie\Html\Elements\Input 121 | */ 122 | public function checkbox($name = null, $checked = null, $value = '1') 123 | { 124 | return $this->input('checkbox', $name, $value) 125 | ->attributeIf(! is_null($value), 'value', $value) 126 | ->attributeIf((bool) $this->old($name, $checked), 'checked'); 127 | } 128 | 129 | /** 130 | * @param \Spatie\Html\HtmlElement|string|iterable|int|float|null $contents 131 | * 132 | * @return \Spatie\Html\Elements\Div 133 | */ 134 | public function div($contents = null) 135 | { 136 | return Div::create()->children($contents); 137 | } 138 | 139 | /** 140 | * @param string|null $name 141 | * @param string|null $value 142 | * 143 | * @return \Spatie\Html\Elements\Input 144 | */ 145 | public function email($name = null, $value = null) 146 | { 147 | return $this->input('email', $name, $value); 148 | } 149 | 150 | /** 151 | * @param string|null $name 152 | * @param string|null $value 153 | * 154 | * @return \Spatie\Html\Elements\Input 155 | */ 156 | public function search($name = null, $value = null) 157 | { 158 | return $this->input('search', $name, $value); 159 | } 160 | 161 | /** 162 | * @param string|null $name 163 | * @param string|null $value 164 | * @param bool $format 165 | * 166 | * @return \Spatie\Html\Elements\Input 167 | */ 168 | public function date($name = '', $value = null, $format = true) 169 | { 170 | $element = $this->input('date', $name, $value); 171 | 172 | if (! $format || empty($element->getAttribute('value'))) { 173 | return $element; 174 | } 175 | 176 | return $element->value($this->formatDateTime($element->getAttribute('value'), self::HTML_DATE_FORMAT)); 177 | } 178 | 179 | /** 180 | * @param string|null $name 181 | * @param string|null $value 182 | * @param bool $format 183 | * 184 | * @return \Spatie\Html\Elements\Input 185 | */ 186 | public function datetime($name = '', $value = null, $format = true) 187 | { 188 | $element = $this->input('datetime-local', $name, $value); 189 | 190 | if (! $format || empty($element->getAttribute('value'))) { 191 | return $element; 192 | } 193 | 194 | return $element->value($this->formatDateTime( 195 | $element->getAttribute('value'), 196 | self::HTML_DATE_FORMAT.'\T'.self::HTML_TIME_FORMAT 197 | )); 198 | } 199 | 200 | /** 201 | * @param string|null $name 202 | * @param string|null $value 203 | * @param string|null $min 204 | * @param string|null $max 205 | * @param string|null $step 206 | * 207 | * @return \Spatie\Html\Elements\Input 208 | */ 209 | public function range($name = '', $value = null, $min = null, $max = null, $step = null) 210 | { 211 | return $this->input('range', $name, $value) 212 | ->attributeIfNotNull($min, 'min', $min) 213 | ->attributeIfNotNull($max, 'max', $max) 214 | ->attributeIfNotNull($step, 'step', $step); 215 | } 216 | 217 | /** 218 | * @param string|null $name 219 | * @param string|null $value 220 | * @param bool $format 221 | * 222 | * @return \Spatie\Html\Elements\Input 223 | */ 224 | public function time($name = '', $value = null, $format = true) 225 | { 226 | $element = $this->input('time', $name, $value); 227 | 228 | if (! $format || empty($element->getAttribute('value'))) { 229 | return $element; 230 | } 231 | 232 | return $element->value($this->formatDateTime($element->getAttribute('value'), self::HTML_TIME_FORMAT)); 233 | } 234 | 235 | /** 236 | * @param string $tag 237 | * 238 | * @return \Spatie\Html\Elements\Element 239 | */ 240 | public function element($tag) 241 | { 242 | return Element::withTag($tag); 243 | } 244 | 245 | /** 246 | * @param string|null $type 247 | * @param string|null $name 248 | * @param string|null $value 249 | * 250 | * @return \Spatie\Html\Elements\Input 251 | */ 252 | public function input($type = null, $name = null, $value = null) 253 | { 254 | $hasValue = ! is_null($value) || ($type !== 'password' && ! is_null($this->old($name, $value))); 255 | 256 | return Input::create() 257 | ->attributeIf($type, 'type', $type) 258 | ->attributeIf($name, 'name', $this->fieldName($name)) 259 | ->attributeIf($name, 'id', $this->fieldName($name)) 260 | ->attributeIf($hasValue, 'value', $this->old($name, $value)); 261 | } 262 | 263 | /** 264 | * @param \Spatie\Html\HtmlElement|string|null $legend 265 | * 266 | * @return \Spatie\Html\Elements\Fieldset 267 | */ 268 | public function fieldset($legend = null) 269 | { 270 | return $legend 271 | ? Fieldset::create()->legend($legend) 272 | : Fieldset::create(); 273 | } 274 | 275 | /** 276 | * @param string $method 277 | * @param string|null $action 278 | * 279 | * @return \Spatie\Html\Elements\Form 280 | */ 281 | public function form($method = 'POST', $action = null) 282 | { 283 | $method = strtoupper($method); 284 | $form = Form::create(); 285 | 286 | // If Laravel needs to spoof the form's method, we'll append a hidden 287 | // field containing the actual method 288 | if (in_array($method, ['DELETE', 'PATCH', 'PUT'])) { 289 | $form = $form->addChild($this->hidden('_method')->value($method)); 290 | } 291 | 292 | // On any other method that get, the form needs a CSRF token 293 | if ($method !== 'GET') { 294 | $form = $form->addChild($this->token()); 295 | } 296 | 297 | return $form 298 | ->method($method === 'GET' ? 'GET' : 'POST') 299 | ->attributeIf($action, 'action', $action); 300 | } 301 | 302 | /** 303 | * @param string|null $name 304 | * @param string|null $value 305 | * 306 | * @return \Spatie\Html\Elements\Input 307 | */ 308 | public function hidden($name = null, $value = null) 309 | { 310 | return $this->input('hidden', $name, $value); 311 | } 312 | 313 | /** 314 | * @param string|null $src 315 | * @param string|null $alt 316 | * 317 | * @return \Spatie\Html\Elements\Img 318 | */ 319 | public function img($src = null, $alt = null) 320 | { 321 | return Img::create() 322 | ->attributeIf($src, 'src', $src) 323 | ->attributeIf($alt, 'alt', $alt); 324 | } 325 | 326 | /** 327 | * @param \Spatie\Html\HtmlElement|iterable|string|null $contents 328 | * @param string|null $for 329 | * 330 | * @return \Spatie\Html\Elements\Label 331 | */ 332 | public function label($contents = null, $for = null) 333 | { 334 | return Label::create() 335 | ->attributeIf($for, 'for', $this->fieldName($for)) 336 | ->children($contents); 337 | } 338 | 339 | /** 340 | * @param \Spatie\Html\HtmlElement|string|null $contents 341 | * 342 | * @return \Spatie\Html\Elements\Legend 343 | */ 344 | public function legend($contents = null) 345 | { 346 | return Legend::create()->html($contents); 347 | } 348 | 349 | /** 350 | * @param string $email 351 | * @param string|null $text 352 | * 353 | * @return \Spatie\Html\Elements\A 354 | */ 355 | public function mailto($email, $text = null) 356 | { 357 | return $this->a('mailto:'.$email, $text ?: $email); 358 | } 359 | 360 | /** 361 | * @param string|null $name 362 | * @param iterable $options 363 | * @param string|iterable|null $value 364 | * 365 | * @return \Spatie\Html\Elements\Select 366 | */ 367 | public function multiselect($name = null, $options = [], $value = null) 368 | { 369 | return Select::create() 370 | ->attributeIf($name, 'name', $this->fieldName($name)) 371 | ->attributeIf($name, 'id', $this->fieldName($name)) 372 | ->options($options) 373 | ->value($name ? $this->old($name, $value) : $value) 374 | ->multiple(); 375 | } 376 | 377 | /** 378 | * @param string|null $name 379 | * @param string|null $value 380 | * @param string|null $min 381 | * @param string|null $max 382 | * @param string|null $step 383 | * 384 | * @return \Spatie\Html\Elements\Input 385 | */ 386 | public function number($name = null, $value = null, $min = null, $max = null, $step = null) 387 | { 388 | return $this->input('number', $name, $value) 389 | ->attributeIfNotNull($min, 'min', $min) 390 | ->attributeIfNotNull($max, 'max', $max) 391 | ->attributeIfNotNull($step, 'step', $step); 392 | } 393 | 394 | /** 395 | * @param string|null $text 396 | * @param string|null $value 397 | * @param bool $selected 398 | * 399 | * @return \Spatie\Html\Elements\Option 400 | */ 401 | public function option($text = null, $value = null, $selected = false) 402 | { 403 | return Option::create() 404 | ->text($text) 405 | ->value($value) 406 | ->selectedIf($selected); 407 | } 408 | 409 | /** 410 | * @param string|null $value 411 | * 412 | * @return \Spatie\Html\Elements\Input 413 | */ 414 | public function password($name = null) 415 | { 416 | return $this->input('password', $name); 417 | } 418 | 419 | /** 420 | * @param string|null $name 421 | * @param bool $checked 422 | * @param string|null $value 423 | * 424 | * @return \Spatie\Html\Elements\Input 425 | */ 426 | public function radio($name = null, $checked = null, $value = null) 427 | { 428 | return $this->input('radio', $name, $value) 429 | ->attributeIf($name, 'id', $value === null ? $name : ($name.'_'.Str::slug($value))) 430 | ->attributeIf(! is_null($value), 'value', $value) 431 | ->attributeIf((! is_null($value) && $this->old($name) === $value) || $checked, 'checked'); 432 | } 433 | 434 | /** 435 | * @param string|null $name 436 | * @param iterable $options 437 | * @param string|iterable|null $value 438 | * 439 | * @return \Spatie\Html\Elements\Select 440 | */ 441 | public function select($name = null, $options = [], $value = null) 442 | { 443 | return Select::create() 444 | ->attributeIf($name, 'name', $this->fieldName($name)) 445 | ->attributeIf($name, 'id', $this->fieldName($name)) 446 | ->options($options) 447 | ->value($name ? $this->old($name, $value) : $value); 448 | } 449 | 450 | /** 451 | * @param \Spatie\Html\HtmlElement|string|null $contents 452 | * 453 | * @return \Spatie\Html\Elements\Span 454 | */ 455 | public function span($contents = null) 456 | { 457 | return Span::create()->children($contents); 458 | } 459 | 460 | /** 461 | * @param string|null $text 462 | * 463 | * @return \Spatie\Html\Elements\Button 464 | */ 465 | public function submit($text = null) 466 | { 467 | return $this->button($text, 'submit'); 468 | } 469 | 470 | /** 471 | * @param string|null $text 472 | * 473 | * @return \Spatie\Html\Elements\Button 474 | */ 475 | public function reset($text = null) 476 | { 477 | return $this->button($text, 'reset'); 478 | } 479 | 480 | /** 481 | * @param string $number 482 | * @param string|null $text 483 | * 484 | * @return \Spatie\Html\Elements\A 485 | */ 486 | public function tel($number, $text = null) 487 | { 488 | return $this->a('tel:'.$number, $text ?: $number); 489 | } 490 | 491 | /** 492 | * @param string|null $name 493 | * @param string|null $value 494 | * 495 | * @return \Spatie\Html\Elements\Input 496 | */ 497 | public function text($name = null, $value = null) 498 | { 499 | return $this->input('text', $name, $value); 500 | } 501 | 502 | /** 503 | * @param string|null $name 504 | * 505 | * @return \Spatie\Html\Elements\File 506 | */ 507 | public function file($name = null) 508 | { 509 | return File::create() 510 | ->attributeIf($name, 'name', $this->fieldName($name)) 511 | ->attributeIf($name, 'id', $this->fieldName($name)); 512 | } 513 | 514 | /** 515 | * @param string|null $name 516 | * @param string|null $value 517 | * 518 | * @return \Spatie\Html\Elements\Textarea 519 | */ 520 | public function textarea($name = null, $value = null) 521 | { 522 | return Textarea::create() 523 | ->attributeIf($name, 'name', $this->fieldName($name)) 524 | ->attributeIf($name, 'id', $this->fieldName($name)) 525 | ->value($this->old($name, $value)); 526 | } 527 | 528 | /** 529 | * @return \Spatie\Html\Elements\Input 530 | */ 531 | public function token() 532 | { 533 | return $this 534 | ->hidden() 535 | ->name('_token') 536 | ->value(session()->token()); 537 | } 538 | 539 | /** 540 | * @param \ArrayAccess|array $model 541 | * 542 | * @return $this 543 | */ 544 | public function model($model) 545 | { 546 | $this->model = $model; 547 | 548 | return $this; 549 | } 550 | 551 | /** 552 | * @param \ArrayAccess|array $model 553 | * @param string|null $method 554 | * @param string|null $action 555 | * 556 | * @return \Spatie\Html\Elements\Form 557 | */ 558 | public function modelForm($model, $method = 'POST', $action = null): Form 559 | { 560 | $this->model($model); 561 | 562 | return $this->form($method, $action); 563 | } 564 | 565 | /** 566 | * @return $this 567 | */ 568 | public function endModel() 569 | { 570 | $this->model = null; 571 | 572 | return $this; 573 | } 574 | 575 | /** 576 | * @return \Illuminate\Contracts\Support\Htmlable 577 | */ 578 | public function closeModelForm(): Htmlable 579 | { 580 | $this->endModel(); 581 | 582 | return $this->form()->close(); 583 | } 584 | 585 | /** 586 | * @param string $name 587 | * @param mixed $value 588 | * 589 | * @return mixed 590 | */ 591 | protected function old($name, $value = null) 592 | { 593 | if (empty($name)) { 594 | return $value; 595 | } 596 | 597 | // Convert array format (sth[1]) to dot notation (sth.1) 598 | $name = preg_replace('/\[(.+)\]/U', '.$1', $name); 599 | 600 | // If there's no default value provided, the html builder currently 601 | // has a model assigned and there aren't old input items, 602 | // try to retrieve a value from the model. 603 | if (is_null($value) && $this->model && empty(request()->old())) { 604 | $value = ($value = data_get($this->model, $name)) instanceof UnitEnum 605 | ? $this->getEnumValue($value) 606 | : $value; 607 | } 608 | 609 | return request()->old($name, $value); 610 | } 611 | 612 | /** 613 | * Retrieve the value from the current session or assigned model. This is 614 | * a public alias for `old`. 615 | * 616 | * @param string $name 617 | * @param mixed $value 618 | * 619 | * @return mixed 620 | */ 621 | public function value($name, $default = null) 622 | { 623 | return $this->old($name, $default); 624 | } 625 | 626 | /** 627 | * @param string $name 628 | * 629 | * @return string 630 | */ 631 | protected function fieldName($name) 632 | { 633 | return $name; 634 | } 635 | 636 | protected function ensureModelIsAvailable() 637 | { 638 | if (empty($this->model)) { 639 | throw new Exception('Method requires a model to be set on the html builder'); 640 | } 641 | } 642 | 643 | /** 644 | * @param string $value 645 | * @param string $format DateTime formatting string supported by date_format() 646 | * @return string 647 | */ 648 | protected function formatDateTime($value, $format) 649 | { 650 | if (empty($value)) { 651 | return $value; 652 | } 653 | 654 | try { 655 | $date = new DateTimeImmutable($value); 656 | 657 | return $date->format($format); 658 | } catch (Exception $e) { 659 | return $value; 660 | } 661 | } 662 | 663 | /** 664 | * Get the value from the given enum. 665 | * 666 | * @param \UnitEnum|\BackedEnum $value 667 | * @return string|int 668 | */ 669 | protected function getEnumValue($value) 670 | { 671 | return $value instanceof BackedEnum 672 | ? $value->value 673 | : $value->name; 674 | } 675 | } 676 | --------------------------------------------------------------------------------