Skip to content

Commit e973d99

Browse files
committed
Initial commit
0 parents  commit e973d99

22 files changed

+738
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea/

Readme.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Laravel Forms
2+
3+
By default the views are setup for beagle
4+
Javascript not included
5+
6+
## Directives
7+
8+
9+
### Form Open
10+
```
11+
@form(['update' => route('some-model.store', [$model]), 'model' => $model, 'files' => true, 'extra' => [...]])
12+
@form(['store' => route('some-model.store'), 'files' => true, 'extra' => [...]])
13+
@form(['delete' => route('some-model.delete'), 'files' => true, 'extra' => [...]])
14+
@form(['get' => route('search.route'), 'extra' => [...]])
15+
```
16+
17+
#### Notes
18+
* Using any of these methods will automatically append the **csrf \_token** and correct **route type \_method** fields.
19+
* If a model is passed via the `'model'` key, We will use it's data to populate the form fields value.
20+
* Anything passed via the `'extra'` array will be appended as attributes to the form.
21+
22+
### Form Close
23+
```
24+
@endform
25+
```
26+
27+
#### Notes
28+
* The form is actually a component, which means you need to close it after you're done.
29+
* This will also unbind the `'model'` so future forms on the same page are not affected.
30+
31+
32+
### Form Field
33+
Now we get into the fun stuff. The minimum required for a form field is a name
34+
```
35+
@formField(['name' => 'first_name'])
36+
```
37+
This will render a `type="text"` form field wrapped in a form group with the label of `First Name` and an appropriate value, Additionally this will handle displaying server validation errors.
38+
39+
```
40+
@formField(['name' => 'name', 'label' => 'Your Name'])
41+
```
42+
This will render as above, however the label will be `Your Name`
43+
44+
```
45+
@formField(['name' => 'name', 'label' => false])
46+
```
47+
This will render as above, however there will be no label
48+
49+
```
50+
@formField(['name' => 'work_email', 'type' => 'email' ])
51+
```
52+
This will render a `type="email"` form field following the previous rules
53+
54+
```
55+
@formField(['name' => 'role', 'options' => ['k' => 'v', 'k2' => 'v2']])
56+
```
57+
This will render a `select` with the given options.
58+
If no value is found for this field we will also prepend an empty `<option>` tag
59+
60+
```
61+
@formField(['name' => 'roles', 'options' => ['k' => 'v', 'k2' => 'v2'], 'extra' => ['multiple']])
62+
```
63+
This will render a `select` with the given options, additionally the name on the select will be `roles[]`
64+
65+
```
66+
@formField(['name' => 'first_name', 'extra' => ['required', 'data-boolean', 'data-something' => 'value']])
67+
```
68+
This will render a field with the following additional attributes `required data-boolean data-something="value"`
69+
70+
## Calculating Values
71+
The following order of precedence is used for calculating values for a form
72+
0. Input matching the `name` via Laravels `old($fieldName)` helper.
73+
0. Attributes from the model provided to `@form`
74+
0. Values passed in directly e.g. `@formField(['name' => 'name', 'value' => 'John'])`.
75+
76+
### Note on _old_ input and multiple forms...
77+
Unfortunately, there's no way to scope Laravels `old($fieldName)` helper, meaning that if one form on a page has old input,
78+
all fields on the page with the same name will be populated with the old input.
79+
This is not a limitation of this package, rather it is a limitation of Laravel's old input handling.
80+
81+
## A note about templates, backward compatibility and support
82+
We've open sourced this package so you can use it too, however it is primarily designed for
83+
our inhouse usage, meaning some of the templates have specific rules that require javascript from our templates to run,
84+
additionally we'll be releasing often to add new features and fixes we need, but it may break your work.
85+
86+
Rather than using this package directly, we suggest you fork it and maintain a copy for your organisation.
87+
88+
## IDE Helper
89+
90+
If you don't have them already, add the following to your projects `.idea/blade.xml` file so PHPstorm knows about the directives
91+
92+
```xml
93+
<data directive="@endform" />
94+
<data directive="@form" injection="true" prefix="&lt;?php function x(array $options) {}; x(" suffix="); ?&gt;" />
95+
<data directive="@formField" injection="true" prefix="&lt;?php function x(array $options) {}; x(" suffix="); ?&gt;" />
96+
```

composer.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "webfox/laravel-forms",
3+
"description": "A Laravel form package",
4+
"require": {
5+
"php": "^7.2.0",
6+
"laravel/framework": "~5.7"
7+
},
8+
"license": "MIT",
9+
"authors": [
10+
{
11+
"name": "Matthew Hailwood",
12+
"email": "[email protected]"
13+
},
14+
{
15+
"name": "Derek Kaijser",
16+
"email": "[email protected]"
17+
}
18+
],
19+
"autoload": {
20+
"psr-4": {
21+
"Webfox\\LaravelForms\\": "src/"
22+
}
23+
},
24+
"extra": {
25+
"laravel": {
26+
"providers": [
27+
"Webfox\\LaravelForms\\ServiceProvider"
28+
]
29+
}
30+
}
31+
}

config/config.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
return [
4+
'container_path' => 'forms::container',
5+
'field_path' => 'forms::fields',
6+
];

src/AttributeManager.php

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
3+
namespace Webfox\LaravelForms;
4+
5+
use Illuminate\Database\Eloquent\Collection;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Support\Arr;
8+
use Illuminate\View\View;
9+
10+
class AttributeManager
11+
{
12+
13+
/** @var \Illuminate\Database\Eloquent\Model|null */
14+
protected $formModel;
15+
16+
public function __construct(FormModelStack $modelStack)
17+
{
18+
if ($modelStack->isNotEmpty()) {
19+
$this->setModel($modelStack->last());
20+
}
21+
}
22+
23+
public function setModel(Model $model = null)
24+
{
25+
$this->formModel = $model;
26+
return $this;
27+
}
28+
29+
public function getFieldActualName(View $view)
30+
{
31+
$extra = $view->offsetExists('extra') ? $view->offsetGet('extra') : [];
32+
$name = $view->offsetGet('name');
33+
34+
foreach ($extra as $key => $value) {
35+
if ($key === 'multiple' && $value === true) {
36+
return "{$name}[]";
37+
}
38+
39+
if ($value === 'multiple' && (is_int($key) || is_string($key) && ctype_digit($key))) {
40+
return "{$name}[]";
41+
}
42+
}
43+
44+
return $name;
45+
}
46+
47+
public function getExtraAttributes(View $view)
48+
{
49+
50+
$extra = $view->offsetExists('extra') ? $view->offsetGet('extra') : [];
51+
if (($extra['data-allow-clear'] ?? false) || in_array('data-allow-clear', $extra)) {
52+
$view->offsetSet('allowClear', true);
53+
}
54+
55+
if ($view->offsetExists('placeholder')) {
56+
$extra['placeholder'] = $view->offsetGet('placeholder');
57+
}
58+
59+
// Extra property mappings for textarea
60+
if ($view->offsetExists('rows')) {
61+
$extra['rows'] = $view->offsetGet('rows');
62+
}
63+
64+
$valueRequiredBooleans = ['data-allow-clear'];
65+
66+
// Convert extra properties to key="value" string
67+
$attributes = array_map(function ($key, $value) use ($valueRequiredBooleans) {
68+
// ['required']
69+
if (is_int($key) || is_string($key) && ctype_digit($key)) {
70+
return in_array($value, $valueRequiredBooleans) ? "{$value}=\"1\"" : $value;
71+
}
72+
73+
// ['required' => true]
74+
if (is_bool($value) && $value) {
75+
return in_array($key, $valueRequiredBooleans) ? "{$key}=\"1\"" : $key;
76+
}
77+
78+
// ['required' => false]
79+
if (is_bool($value) && !$value) {
80+
return null;
81+
}
82+
83+
return sprintf('%s="%s"', $key, htmlspecialchars($value));
84+
}, array_keys($extra), $extra);
85+
86+
// Remove empty (boolean false) attributes
87+
$attributes = array_filter($attributes);
88+
89+
return implode(' ', $attributes);
90+
}
91+
92+
public function getFieldTemplate(View $view)
93+
{
94+
// Check the template
95+
$fieldType = $this->getFieldType($view);
96+
97+
$templates = [
98+
config('forms.field_path') . ".{$fieldType}",
99+
config('forms.field_path') . ".simple",
100+
];
101+
102+
$template = Arr::first($templates, function ($view) {
103+
return view()->exists($view);
104+
});
105+
106+
if (!$template) {
107+
throw new \Exception("Unable to locate formField " . implode(', ', $templates));
108+
}
109+
110+
111+
return $template;
112+
}
113+
114+
public function getFieldType(View $view)
115+
{
116+
if ($view->offsetExists('type')) {
117+
return $view->offsetGet('type');
118+
}
119+
120+
if ($view->offsetExists('options')) {
121+
return 'select';
122+
}
123+
124+
return 'text';
125+
}
126+
127+
public function getFieldValue(View $view)
128+
{
129+
$fieldName = $view->offsetGet('name');
130+
$fieldType = $this->getFieldType($view);
131+
132+
// Find the expected value
133+
if (in_array($fieldType, ['file', 'password'])) {
134+
return $view->offsetExists('value') ? $view->offsetGet('value') : null;
135+
}
136+
137+
$value = null;
138+
if ($this->formModel && !in_array($fieldName, $this->formModel->getHidden())) {
139+
$value = $this->formModel->getAttribute($fieldName);
140+
}
141+
if ($value === null && $view->offsetExists('value')) {
142+
$value = $view->offsetGet('value');
143+
}
144+
145+
$value = old($fieldName, $value);
146+
147+
if ($value instanceof Collection) {
148+
$value = $value->modelKeys();
149+
}
150+
151+
return is_bool($value) ? intval($value) : $value;
152+
}
153+
}

src/FieldViewComposer.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
4+
namespace Webfox\LaravelForms;
5+
6+
use Illuminate\Http\Request;
7+
use Illuminate\View\View;
8+
9+
class FieldViewComposer
10+
{
11+
12+
/**
13+
* @var \Illuminate\Http\Request
14+
*/
15+
protected $request;
16+
17+
/** @var \Webfox\LaravelForms\AttributeManager */
18+
protected $attributeManager;
19+
20+
public function __construct(Request $request, AttributeManager $attributeManager)
21+
{
22+
$this->request = $request;
23+
$this->attributeManager = $attributeManager;
24+
}
25+
26+
public function compose(View $view)
27+
{
28+
// Calculate the field type
29+
$fieldType = $this->attributeManager->getFieldType($view);
30+
31+
// Repopulate the template
32+
$view->offsetSet('extraAttributes', $this->attributeManager->getExtraAttributes($view));
33+
$view->offsetSet('fieldTemplate', $this->attributeManager->getFieldTemplate($view));
34+
$view->offsetSet('value', $this->attributeManager->getFieldValue($view));
35+
$view->offsetSet('type', $this->attributeManager->getFieldType($view));
36+
$view->offsetSet('actualName', $this->attributeManager->getFieldActualName($view));
37+
$view->offsetSet('onlyTemplate', in_array($fieldType, ['checkbox']));
38+
$view->offsetSet('model', app(FormModelStack::class)->current());
39+
}
40+
41+
42+
}

0 commit comments

Comments
 (0)