Forms
Our Forms package is designed to give developers to freedom to build forms via classes and set the fields in those classes comfortably. You can easily pass a form to a view and it will render the whole form, reducing your need to type out divs, labels, inputs and more.
Forms by default is set to Bootstrap's classes, but you can change these in the config.
Artisan Commands
Generate a form for a specific model using this make command. It will add the ModelForm to a Forms directory in the app/Html/Forms
namespace.
artisan make:model-form {model}
Generate a generic form with a specific name using this command. It will add the BaseForm to a Forms directory in the app/Html/Forms
namespace.
artisan make:base-form {name}
Other form make commands are as follows:
artisan make:modal-form {name}
artisan make:livewire-form {name}
artisan make:wizard-form {name}
If you made a ModelForm you likely want a factory for your model, since we have the fields within the ModelForm class we can generate basic factories.
artisan make:form-factory {form}
Set Alternate Connections
app(UserForm::class)->setConnection('alternate');
Or in the UserForm
itself:
$connection = 'alternate';
Blade Directives
Forms only has one blade directive and that is for handling any assets you may add to a field. In the case of the Quill
field we load the CDN assets (javascript and css), and we inject a snippet of JavaScript. These assets need to be rendered in your view. Ideally this is done close to the closing body tag of your main template.
@forms
Or, if you wish to split the resources (which is smarter) you can use:
@formStyles
@formScripts
Just place that below any of your JavaScript file references and you can easily load the forms field assets when the Form is being rendered on screen.
Blade Components
If you like to keep your blade files looking a little nicer you can also use Blade Components. This lets you reduce the use of curly braces everywhere.
<x-f-action
route="delete.user"
method="delete"
payload="$user"
></x-f-action>
<x-f :content="$form"></x-f>
<x-f-search
route="search"
></x-f-search>
Available Form Components
x-f
x-f-base
x-f-model
x-f-action
x-f-modal
x-f-delete
x-f-search
Helpers
form() // access the `Form` class
Chained Methods on Forms
All forms have some chained methods for configuration available:
payload([]): sets the payload for the form
setProperty($key, $value): set a specific value for the form
asModal($triggerContent = null, $triggerClass = null, $message = null, $modalTitle = null): Set a form as a modal popup
triggerContent($content): Set the button content for the modal
triggerClass($cssClass): Set the button css classes for the modal
modalTitle($content): Set the modal header title
id($content): Set the modal ID property
onChange: Perform a submit on form change
onKeydown: Perform a submit on keydown in form
viaAjax: Submit a form via the global ajax method in the config "forms.form.global-ajax-method"
asLivewire(): Enables forms to use "wire:submit.prevent" for submission
confirmAsModal($message, $buttonText, $buttonClass = 'btn btn-primary'): Enable a modal to popup for confirmation
render(): String output of a form
getFormId(): Gets the ID of a form either autogenerated or set in advance
renderForLivewire(): String output of a form for livewire
Non chained Methods on Forms
These methods are available within a form to be called. They are not chainable on the Form object.
setUp(): This method is available in the cases where you need to perform data adjustments or form customizations before the fields are generated
config(): This method is available in the cases where you need to override default configs for specific forms
Fields
Fields are PHP objects which define types, options, attributes etc for the Form Object. This lets you design forms and update them easily via a single object rather than having to update HTML files for multiple roles in your application.
FIELD_OPTIONS
type: A type string such as text or file
options: Options for <select> type
format: Format for DateTime objects
legend: A label for the legend of horizontal checkboxes
null_value: False by default, but allows empty values to be placed at the front of selects
null_label: Text which is placed as the first option in a select with a blank value
value: If you set the value of a field it will fill it in, or select it in the case of selects
label: A string or false (if you want the label blank - useful for `legend` uses)
model: Model class for the HasOne and HasMany Fields
model_options: Model options
label: The label attribute on the model
value: The value attribute on the model
method: A custom method to run on the model
params: Parameters to send to the custom method
before: Text or HTML you wish to put before an input
after: Text or HTML you wish to put after the input
view: A view path to a custom template
template: A custom template with some basic key value swaps
attributes: HTML attributes for your input field
factory: The type of input faker to use for test generating
assets: The js, styles, scripts, and stylesheets values
There are a large collection of Fields available out of the box and a make:field {name}
command in case you need custom ones. Fields generate a config array which is then processed by the FieldMaker
, AttributeBuilder
and FieldBuilder
to create forms for easy use.
Available Fields
Any fields that include JS should have zero dependencies. Those that require jQuery obviously require jQuery.
Bootstrap/Country (includes JS, requires jQuery)
Bootstrap/DateTimePicker (includes JS)
Bootstrap/Day (includes JS, requires jQuery)
Bootstrap/HasMany (includes JS, requires jQuery)
Bootstrap/HasOne (includes JS, requires jQuery)
Bootstrap/Month (includes JS, requires jQuery)
Bootstrap/Select (includes JS, requires jQuery)
Bootstrap/TomSelect (includes JS, requires jQuery)
Bootstrap/Suggest (includes JS, requires jQuery)
Bootstrap/Timezone (includes JS, requires jQuery)
Bootstrap/Toggle (includes JS, requires jQuery)
Attachments (includes JS)
AutoSuggest (includes JS)
AutoSuggestSelect (includes JS)
AutosizeTextArea (includes JS)
Checkbox
CheckboxInline
Code (includes JS)
Color
Country (includes JS)
CustomFile
Datalist
Date
Datepicker
DatetimeLocal
Decimal
Dropzone (includes JS)
Editor (includes JS)
Email
File
FilePond (includes JS)
FileWithPreview (includes JS)
GrapesJs
HasMany
HasOne
hCaptcha
Hidden
Image
Month
Name
Number
Password
PasswordWithReveal (includes JS)
Quill (includes JS)
Radio
RadioInline
Range
Search
Select
Slug (includes JS)
Summernote (includes JS)
SwitchToggle
Tags (includes JS)
Telephone
Text
TextArea
Time
Timezone (includes JS)
Toggled (includes JS)
Trix
Typeahead (includes JS, requires jQuery)
Url
Week
Field Methods Available
In general you can set field options as an array in the make
method of the Field.
Text::make('name', [
'required' => true,
]);
However you can also use any of the methods below as in a chaining style in order to set the Field configuration details.
Example:
Text::make('name')->required();
Methods
required()
placeholder('foo')
attribute('foo', 'bar')
attributes(['foo' => 'bar'])
value('foo')
label('foo')
name('foo')
cssClass('foo') // set the class of the input field
labelClass('foo') // set the field's label's class
accept(['json/application'])
before('foo') // places Foo before the input field
after('foo') // places Foo after the input field
legend('foo')
readonly() // set a field as readonly
disabled() // set a field as disabled
maxlength(5) // set a field's maxlength attribute
size(5) // set a field's size attribute
min(5) // set a field's min attribute
max(5) // set a field's max attribute
step(.5) // set a field's step attribute
pattern('foo') // set the pattern attribute of the field
multiple() // set if the field can accept multiple values
autofocus() // set a field's autofocus attribute
autocomplete() // set a field's autofocus attribute
id('foo') // set a field's id attribute
style('foo') // set a field's style attribute
title('foo') // set a field's title attribute
data('foo', 'bar') // add a data attribute to a field
selectOptions(['foo' => 'bar']) // set the options for a select type field
instance($foo) // set a Field's instance
option('key', 'value') // set a field's option
options(['key' => 'value']) // set a field's option as an array
view('foo.bar') // set the view for the field template
template('{field}') // set the template for the field
model(User::class)
modelMethod('getUsersWithRole') // default: all()
modelParams(['role' => 'member']) // default: null
modelValue('id') // default: id
modelLabel('name') // default: name
groupClass('foo') // class in a div around the label and input
ungrouped() // remove the div around the label and input
withoutLabel() // remove the label from the Field
onlyField() // remove the label and wrapping div from the Field
sortable() // make a field sortable in the rendered index
canSelectNone() // give a select Field a null option
noneLabel('foo') // set the label for the null option in a select Field
visible() // make a Field visible in the rendered index
hidden() // make a field hidden in the rendered index
tableClass('foo') // set the class for the rendered index column
submitOnChange() // enable changing a field to trigger a submit
submitOnKeyUp() // enable key up on a field to trigger a submit
Special Field Options
Some fields have extra options which pertain to their configs. Below are more custom options you can apply to these special fields.
Boostrap/Toggle
See https://gitbrent.github.io/bootstrap4-toggle/ for more options.
theme: "light|dark"
on: "On"
off: "Off"
size: "sm"
Boostrap/Suggest
btn: "btn-primary"
Boostrap/Select
btn: "btn-primary"
Boostrap/HasOne
btn: "btn-primary"
Boostrap/HasMany
btn: "btn-primary"
Code
See https://codemirror.net/ for more options.
mode: "htmlmixed"
theme: "default"
Datepicker
See https://github.com/qodesmith/datepicker#readme for more options
theme: "light"
background-color: "#FFF"
color: "#FFF"
number-color: "#111"
highlight: "var(--primary, "#EEE")"
header: "var(--primary, "#EEE")"
start-day: 1
format: "YYYY-MM-DD"
Dropzone
theme: "light"
queue-complete: "function () { window.location.reload() }"
upload-multiple: true
route: ""
Filepond
file_size: "25MB"
process_url: null
submit_button: "button[type="submit"]"
upload_result_field: null
FileWithPreview
preview_identifier: "" // class or ID for an avatar img or div
preview_as_background_image: false
hCaptcha
Add this to your services.php
config file.
'hcaptcha' => [
'sitekey' => env('HCAPTCHA_SITEKEY'),
'secret' => env('HCAPTCHA_SECRET'),
],
Editor
Uses the latest EditorJS as its WYSIWYG editor.
theme: "light"
upload_route: "upload.image"
Special
If you want to use images inside EditorJS you need to have a route which can handle the image uploads. The following is an example of a Controller invoke method which can handle the file uploads, below is the Quill example using the same controller.
$validatedData = $request->validate([
'file' => 'image|required',
]);
$path = collect($validatedData)->first()->store('public/uploads');
return response()->json([
'success' => true,
'file' => [
'url' => Storage::url($path),
],
]);
Editor also has a parser available at Grafite\Forms\Parsers\Editor
. This lets you do something like:
app(Grafite\Forms\Parsers\Editor::class)->parse(auth()->user()->bio)->render();
Quill
theme: "light"
quill_theme: "snow"
toolbars: [
"basic",
"extra",
"lists",
"super_sub",
"indents",
"headers",
"colors",
"image",
"video"
]
upload_route: "upload.image"
Special
Interaction
mention_ats: users you want to show in a popup when the user enters "@"
mention_hashes: hashes you want to show in a popup when the user enters "#"
mention_links: links you want to show in a popup when the user enters "^"
mention_link_path: A path for when the user clicks on the link path
mention_at_path: A path for when the user clicks on the user mention
mention_hash_path: A path for when the user clicks on the mention hash
Images
If you want to use images inside Quill you need to have a route which can handle the image uploads. Storing images as data-urls (Quill default method) is never wise. The following is an example of a Controller invoke method which can handle the file uploads.
$validatedData = $request->validate([
'file' => 'image|required',
]);
$path = collect($validatedData)->first()->store('public/uploads');
return response()->json([
'success' => true,
'file' => [
'url' => Storage::url($path),
],
]);
Tags
default-border: "#EEE"
focus-border: "#EEE"
Toggled
color: "blue"
Typeahead
matches: []
Field Assets
Out of the box Forms comes with a few fields which contain field assets. You can add assets to any field, and the Forms package will collect them and put links to them where you use the blade directive forms
. We suggest below your app.js
link on your master blade file.
In order to add assets to a field you need to use the following protected methods: stylesheets
, scripts
, styles
, js
. The following is an example from the Quill
field.
protected static function stylesheets($options)
{
return [
"//cdn.quilljs.com/1.3.6/quill.bubble.css",
"//cdn.quilljs.com/1.3.6/quill.snow.css",
];
}
protected static function scripts($options)
{
return ['//cdn.quilljs.com/1.3.6/quill.js'];
}
// This was added for demo purposes
protected static function styles($id, $options)
{
return <<<EOT
#$id {
color: #FFF;
}
EOT;
}
protected static function js($id, $options)
{
$theme = $options['theme'] ?? 'snow';
$placeholder = $options['placeholder'] ?? '';
return <<<EOT
new Quill('#$id', {
theme: '$theme',
placeholder: '$placeholder'
});
EOT;
}
Minification
By default for any form assets all JS and CSS is minified in production using https://github.com/matthiasmullie/minify. We keep in unminified locally and in testing for easier debugging.
JavaScript Repetition
Overall the last thing we want is to have a page filled with forms which perform the same functions but add repetition to our JS scripts payload. As such we have a system for handling how we render and perform actions on fields when we load them to the DOM.
public static function onLoadJs($id, $options)
{
return '_formsjs_editorField';
}
This method added to a field tells the forms methods which method to run initially. If we wish to send a config to that method we do do that as such:
public static function onLoadJsData($id, $options)
{
if (is_null($options['upload_route'])) {
throw new \Exception('You need to set an `upload_route` for handling image uploads to EditorJs.', 1);
}
return json_encode([
'route' => route($options['upload_route']),
'placeholder' => $options['placeholder'] ?? 'Let`s write an awesome story!',
]);
}
Then within our js
method as described above we can get more general for a field so when we have pages with numerous instances we don't create a massive payload for the browser.
public static function js($id, $options)
{
return <<<JS
_formsjs_editorField = function (element) {
if (! element.getAttribute('data-formsjs-rendered')) {
let _config = JSON.parse(element.getAttribute('data-formsjs-onload-data'));
// Do magic here
}
}
JS;
}
Default JS Validation
In general based on form submissions - we return a nice looking form with the error labels in place. The annoying part would be writing JavaScript validation per input to clean up the UI from the form return. If you set php-inline public $withJsValidation = true;
then you'll get a handy vanilla JS injection which removes the invalid classes on keyup or focus out.
Single Field Usage
If you want to use the Forms builder for just a single field you can quite easily with the form
helper.
{!! form()->makeField(\Grafite\Forms\Fields\Text::class, 'name', [
'id' => 'nameField',
]) !!}
This is extra useful when you want to use JS driven form fields
Sections
The various Form Objects allow you to set Sections. For example, you may have a BlogForm
and you may want one row to have three columns while the next row has two, this can be achieved with the setSections
method.
$columns = 1;
public function fields()
{
return [
Text::make('title', [
'required' => true,
]),
Text::make('url', [
'required' => true
]),
TextArea::make('entry')->required(),
Date::make('published_at'),
];
}
By default this will build a form with single column content. If you wish to set these fields to specific layouts you need to set the columns
to sections
$columns = 'sections';
public function fields()
{
return [
Text::make('title', [
'required' => true,
]),
Text::make('url', [
'required' => true
]),
TextArea::make('entry', []),
Date::make('published_at', []),
];
}
public function setSections()
{
return [
[
'title',
'url',
],
[
'entry'
],
[
'published_at'
]
];
}
The above will produce a form that is two columns, one, and one. You can add a key
to the sections to add a horizontal line and a header.
public function setSections()
{
return [
[
'title',
'url',
],
'Content' => [
'entry'
],
[
'published_at'
]
];
}
HTML Snippets
Within the fields section of the Form Object you can also place some HTML snippets. These can help with spacing and UI layouts. The Snippets included in Forms are:
OpenDiv
CloseDiv
Div
Heading
HrTag
Span
Button
All of these snippets can have nearly any attribute set. Div
, Heading
, Button
, Span
can also have content
set in them, and in most cases require the content
value be set for them.
Card Wrapper
Sometimes you may want to place a form inside a card component. If you do, you may wish to set the $isCardForm
property to true. This will place the form buttons in the card footer and wrap the form in a .card-body
div. This lets you control the use of card-headers while giving your form a clean component like style.
Disable on Submit
When a user clicks the submit button we can end up having issues if they click it repeatedly. Setting the $disableOnSubmit
property to true, will in turn disable the submit button onclick and insert a fontawesome spinning icon.
Form
The Form class lets us generate simple forms with minimal code.
form()->action('method', 'route', 'button text', $html_attributes);
Generates a form using the method and route with a button, for easier addition of delete buttons and more.
You can also provide a payload to action forms similar to the confirm below:
form()->payload(['user' => $user->id])->action(...);
form()->confirm('Are you sure?')->action(...);
Adds a confirmation popup to the button.
confirm is only supported in the delete method of ModelForms.
If you wish to handle the confirm using a modal or other JS integration you can pass a method
name into the confirm method which will trigger that JS method:
form()->confirm('Are you sure you want to delete this?', 'confirmation')->action(...);
This will add the following to the submit button in the form:
data-formsjs-onclick="confirmation(event, 'Are you sure you want to delete this?')"
You will require a global confirmation
method to be defined somewhere such as in app.js
. Your confirmation method could look something like this, which uses Bootstrap's modal:
window.confirmation = (_event, _message) => {
_event.preventDefault();
$('#appModalMessage').html(_message);
$('#appModal').modal({
show: true
});
$('#appModalConfirmBtn').click(() => {
_event.target.form.submit();
});
return false;
}
There is also support for a submitMethod
attribute on all forms. This custom submit allows you to override the standard submit and perform actions against your form. This can be useful if you want to do an ajax submission of your form.
Info
All method set should be global or chianed global like app.forms.ajax
etc. Internally the system handles for the form submission bindings and looks for these methdods in order to comply with CSP policies.
Info
There is a also a submitMethods
array property that can be set for any ModelForm
. This lets you set update or create forms to ajax driven or standard.
<?php
namespace App\Http\Forms;
use Grafite\Forms\Fields\Password;
use Grafite\Forms\Forms\BaseForm;
class UserSecurityForm extends BaseForm
{
public $route = 'user.security';
public $method = 'put';
public $orientation = 'horizontal';
public $submitMethod = 'ajax';
public $buttons = [
'submit' => 'Save',
];
public $columns = 1;
public function fields()
{
return [
Password::make('old_password', [
'required' => true,
'label' => 'Old Password'
]),
Password::make('new_password', [
'required' => true,
'label' => 'New Password'
]),
Password::make('new_password_confirmation', [
'required' => true,
'label' => 'Confirm New Password'
]),
];
}
}
window.ajax = (_event) => {
_event.preventDefault();
let _form = _event.target.form;
let _method = _form.method.toLowerCase();
let _data = new FormData(_form);
window.axios[_method](_form.action, _data, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then((response) => {
window.notify.success(response.data.message);
})
.catch((error) => {
window.notify.warning(error.response.data.message);
for (var key in error.response.data.errors) {
document.querySelector('input[name="'+key+'"]').classList.add('border-danger');
window.notify.error(error.response.data.errors[key][0]);
}
});
}
form()->open($options);
Opens a form allowing you to specify options: action, method, attributes etc.
form()->model($model, $options);
Open a form based on a model
Form Types
There are 6 main form types available:
Form
This is mostly used as the action form for simple action based forms with single buttons etc.
BaseForm
The full control of rendered fields with script injection options. Generall not bound to a model.
ModelForm
The full rendered fields and script injections with a model bound to the actions.
ModalForm
The full rendered fields and script injections inside a Bootstrap based modal. They do require a triggerContent
and triggerClass
which are the content and class for the button which opens the Modal.
LivewireForm
Livewire form is more basic, ignoring buttons, allowing for public $onKeydown
as well as the ease of passing the Component $data
into the form via the make()
method. If you wish to use the whole form notion, then set the $method
to whatever you wish, by default its set to submit.
WizardForm
The wizard form enables developers to create a wizard like experience for their form submission. It collects the specified fields and sets them into steps defined in the Form class. The Next and Previous buttons can be modified and they run basic HTML5 validation as you move forward.
Config
In general all classes are defined in the config, which means you can avoid Boostrap if you want to. You can also set the form class directly on the form itself.
public $formClass = 'form';
Any classes set on the form, or field itself will override the default configs. The following are default configs:
'buttons' => [
'submit' => 'btn btn-primary',
'delete' => 'btn btn-danger',
'cancel' => 'btn btn-secondary',
],
'form' => [
'class' => 'form',
'delete-class' => 'form-inline',
'inline-class' => 'form d-inline',
'group-class' => 'form-group',
'input-class' => 'form-control',
'label-class' => 'control-label',
'label-check-class' => 'form-check-label',
'before_after_input_wrapper' => 'input-group',
'text-error' => 'text-danger',
'error-class' => 'has-error',
'check-class' => 'form-check',
'check-input-class' => 'form-check-input',
'check-inline-class' => 'form-check form-check-inline',
'custom-file-label' => 'custom-file-label',
'custom-file-input-class' => 'custom-file-input',
'custom-file-wrapper-class' => 'custom-file',
'sections' => [
'column-base' => 'col-md-',
'row-class' => 'row',
'full-size-column' => 'col-md-12',
'header-spacing' => 'mt-2 mb-2',
'row-alignment-between' => 'd-flex justify-content-between',
'row-alignment-end' => 'd-flex justify-content-end',
],
'orientation' => 'vertical',
'horizontal-class' => 'form-horizontal',
'label-column' => 'col-md-2 col-form-label pt-0',
'input-column' => 'col-md-10',
]
Form Scripts
Similar to Field assets you can inject JavaScript directly into your form to contain logic etc. This lets you avoid having random scripts in your JavaScript assets for random parts of your application. To add scripts simply create a scripts method and return what you wish.
public function js()
{
return <<<EOT
console.log("hello world");
EOT;
}
ModelForm
Using the make:model-form {model}
command you can quickly generate forms for Models. This will let you generate forms based on the model.
app(UserForm::class)->create();
app(UserForm::class)->edit($user);
app(UserForm::class)->delete($user);
Example
<?php
namespace App\Http\Forms;
use App\Models\Role;
use App\Models\User;
use Grafite\Forms\Fields\File;
use Grafite\Forms\Fields\Text;
use Grafite\Forms\Fields\Email;
use Grafite\Forms\Fields\Checkbox;
use Grafite\Forms\Forms\ModelForm;
class UserForm extends ModelForm
{
public $model = User::class;
public $routePrefix = 'user';
public $routeParameters = ['id'];
public $buttons = [
'submit' => 'Save',
];
public $columns = 1;
public $orientation = 'horizontal';
public $hasFiles = true;
public function fields()
{
return [
Text::make('name', [
'required' => true,
]),
Email::make('email', [
'required' => true
]),
Checkbox::make('dark_mode', [
'legend' => 'Dark Mode'
]),
File::make('avatar', []),
];
}
}
Within this UserForm
class you can set the fields in in the fields
method:
public function fields()
{
return [
Text::make('name', [
'required' => true,
]),
Email::make('email', [
'required' => true
]),
];
}
This will generate a form with these fields only. You can also set the orientation
if you wish to use labels on the left side instead of above and columns
if you wish to generate a form in which the fields are split into more columns.
$routePrefix
is required as it defines the routes using route names which match the standards route naming of Laravel.
$routeParameters
is required if you need to pass more than the default ID parameter for a form. This is also needed if you wish to use uuid
etc.
$hasFiles
enables the file submission of the form.
$buttons
enables you to customize the button values for submit and cancel
$buttonClasses
enables you to customize the button class values for submit, cancel, and delete which can also be done in the config as form default
$confirmMessage
lets you set the confirm message for the delete button
$confirmMethod
sets the method name for the onclick
event when clicking on the delete button
$confirmSubmission
sets a string value for the confirmation popup on a form submission
With the delete()
form you can also add the confirmation method like so:
{!! form()
->confirm('Are you sure you want to delete your avatar?', 'confirmation')
->action('delete', 'user.destroy.avatar', 'delete', ['class' => 'btn btn-sm btn-outline-secondary'])
!!}
Accessing the Model Instance
You can access the model instance in a model form, it can give you the freedom to collect data from the model in the case of setting select values etc.
public $instance;
You can also check if its been set by running:
$this->hasInstance()
Custom View for Fields
You can create a custom view that the Forms will use for your fields. Just have you view file follow this pattern:
<div class="row">
<div class="form-group">
{!! $label !!}
<div class="row">
{!! $field !!}
</div>
</div>
{!! $errors !!}
{!! $options !!}
</div>
The values passed to the view are label
, field
, errors
, options
. The options is how you can pass more abstract configs through, but in all honesty we dont really see many use cases of that.
Custom Template for Fields
You can create a custom template that the Forms will use for your fields. This way you do not need a view file. Templates have a VERY basic templating component to them.
You have the following variables that are passed to the template:
Template Key | What it Is |
---|---|
{id} | The field ID |
{name} | The label |
{errors} | An HTML string of error messages |
{label} | The label as an HTML string |
{field} | The field as an HTML string |
{rowClass} | The CSS class which wraps around the label and field |
{labelClass} | The CSS class which is on the label |
{fieldClass} | The CSS class which is on the field |
This has value when using horizontal
forms vs vertical
forms.
<div class="{rowClass}">
<label for="{id}" class="{labelClass}">{name}</label>
<div class="{fieldClass}">
{field}
</div>
{errors}
</div>
Relationships
Forms also has Fields called HasOne
and HasMany
. These enable you to select one or multiple from a relationship binding in your model.
For example you may have a User
who has a role
which is belongsToMany
.
You can set the Field for something like 'Roles' as below.
HasOne::make('role', [
'model' => Role::class,
'model_options' => [
'label' => 'name',
'value' => 'id',
'method' => 'all',
'params' => null,
]
]);
You can also customize how you access the method and with what parameters. By default it will try to collect the options by getting Role::all()
however you can limit that with something like Role::custom(params)
.
Index and Search
With a ModelForm
you can also utilize the index and search methods. This lets you render full index tables of models and search forms for those models.
There are also some special properties you can set on your ModelForm
in order to optimize the index and allow some fields to be sortable.
$with
is used as a portion of the model query run in index listing.
$paginate
allows you to set a number for how many items to load in the index.
app(UserForm::class)->index()
In case you wish to output the index to an API call.
app(UserForm::class)->index()->toJson()
Renders a search form for the model index
app(UserForm::class)->index()->search('route.for.search', 'placeholder', 'Button Text', 'form-method');
Field Options
Each field by default will be listed in the index. You can customize this by setting the visible
option to false
.
If you wish to make a field sortable (asc|desc) then you can set the sortable
option to true
.
If you wish to adjust the table class for the index list you can set the table_class
option.
Custom Query on Index
You can also pass a query into the index
method if you wish to set some details.
$query = User::whereIn('in', [1, 2, 3]);
app(UserForm::class)->index($query);
ModalForm
A modal form is a form inside a modal popup. It requires a Trigger (a button) to have the modal popped open.
This form type contains all the similar attributes to BaseForm
and ModelForm
. It also has the trigger attributes.
$triggerContent
Content that goes inside the modal trigger button.
$triggerClass
The classes for the button that triggers the modal.
These attributes can also be set for ModelForm
classes and utilize the asModal()
method. This can also be used for delete buttons with ModelForm
objects. With the asModal you're able to allow a simple Confirmation modal.
WizardForm
A form as a set of steps with next and previous buttons.
The standard properties such as route
, method
are to be set as well as the optional progressBarColor
. The fields are defined in the standard way and similar to the setSections
method you can need to set the steps in the steps
method.
Livewire
You have to first ensure your app is set up with all basic Livewire setup.
You can use the forms inside Livewire and handle a varity of options. First you need to set that the Form is using Livewire using the following property.
public $withLivewire = true;
If you wish to have the changes propogate on keydown you can enable that as well with the following:
public $livewireOnKeydown = true;
Within your Livewire component you have a handful of options. You need to set the $data
property in the mount
method, then create a submit
method and lastly set the form within the render method. Below is an example:
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use App\Http\Forms\UserForm;
class UserSettings extends Component
{
public $data;
public function mount()
{
$user = request()->user();
$this->data['name'] = $user->name;
$this->data['email'] = $user->email;
$this->data['billing_email'] = $user->billing_email;
$this->data['state'] = $user->state;
$this->data['country'] = $user->country;
}
protected $rules = [
'data.billing_email' => 'required|email',
];
public function submit()
{
$this->validate();
request()->user()->update($this->data);
}
public function render()
{
$user = request()->user();
$form = app(UserForm::class)
->setErrorBag($this->getErrorBag())
->edit($user);
return view('livewire.user-settings')->withForm($form);
}
}
Render Method
Within the render method you will need to place your Form code. Specifically you will need to pass in the ErrorBag
from the component in order to correctly render field errors. This lets you handle real time error reporting on form entry quite easily.
app(UserForm::class)->setErrorBag($this->getErrorBag())->create()
The above example is in our Scaffold project. It provides an example of using the Forms inside a Livewire component.
LivewireForm Example
<?php
namespace App\Http\Forms;
use Grafite\Forms\Html\Button;
use Grafite\Forms\Fields\Number;
use Grafite\Forms\Forms\LivewireForm;
class CartForm extends LivewireForm
{
public $columns = 2;
/**
* Set the desired fields for the form.
*
* @return array
*/
public function fields()
{
return [
Number::make('count', [
'required' => true,
'label' => false,
'wrapper' => false
]),
Button::make([
'wire:click.prevent' => 'setNumber',
'content' => '<span class="fas fa-fw fa-plus pr-4"></span> Add to Cart'
], 'add')
];
}
}
LivewireForm Component Example
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use App\Http\Forms\CartForm;
class Cart extends Component
{
public $data;
protected $rules = [
'data.count' => 'lte:1000',
];
public function mount()
{
$this->data['count'] = 88;
}
public function setNumber()
{
$this->validate();
$this->data['count'] = 987987897;
}
public function render()
{
$form = app(CartForm::class)
->setErrorBag($this->getErrorBag())
->make($this->data);
return view('livewire.cart')->withForm($form);
}
}
Livewire and Forms JavaScript
If you have some fields which have JavaScript running elements of them you can easily solve some problems by adding wire:ignore
to the div surrounding your $form
in the component view.
<div wire:ignore>
{!! $form !!}
</div>
This tells Livewire to NOT perform the DOM diff on the child Form elements. This means your form doesn't try to reload its components etc.
One more thing
If you do autoloading of forms via Livewires content swapping system, these forms will NOT have any of the event bindings set. You will need to rerun window.FormsJS()
. This will not interupt any previously rendered forms as they are marked as processed.
Parsers
Some fields require parsers since the data they store isn't very consumable by default. Editor
is a great example of this. The Editor
field stores a JSON object of blocks of HTML. This can then use the Editor
Parser to convert it to proper HTML for end user consumption.
Parsers should have parse
, handler
, and render
methods generally to handle accepting data, processing it and rendering it as HTML or other content.
Content-Security-Policy
Forms generally works by adding data attributes to field and form elements and then having a script on DOMContentLoaded run which handles all the bindings etc. You can add a nonce value to the @forms()
directive if you need to. If there are any issues with it please report them accordingly.