Submitting Data
- Submitting Form
- Using Hooks
- Sending Response
- Handling Response
- Custom Endpoint
- Processing Traits
- Custom Submission
#Submitting Form
To demonstrate the submission process of Laraform let's create a simple newsletter subscription form at app\Forms\NewsletterForm.php
:
<?php
namespace App\Forms;
class NewsletterForm extends \Laraform
{
public function schema() {
return [
'email' => [
'type' => 'text',
'placeholder' => 'Email address'
]
];
}
public function buttons() {
return [[
'label' => 'Submit'
]];
}
}
We are going to build up this form through the chapter to become a fully functioning newsletter subscription form.
Now, assign this form to your view using app('App\Forms\NewsletterForm')
and render it with ->render()
method as we've learned in the previous chapter. You should see a simple form with an Email address field and Submit button.
#Auto-Processing
What happens if you press Submit? It seems like nothing but in fact the form is being submitted to /laraform/process
endpoint (open the browser's Network tab to see it).
Laraform offers out of the box data processing at this /laraform/process
endpoint, which enables you to assign an Eloquent model to a form and have everything validated, inserted and updated automatically. This saves you a lot of time and you can also add hooks to implement custom logic, as we'll learn later this chapter.
#Assigning Eloquent Model
Now that we have our basic newsletter subscription form, let's assign an Eloquent model:
class NewsletterForm extends \Laraform
{
public $model = \App\Subscriber::class;
// ...
}
As we don't have the Subscriber
model yet, let's create it along with the migration.
Migration
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateSubscribersTable extends Migration
{
public function up()
{
Schema::create('subscribers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('email');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('subscribers');
}
}
Model
Let's place the model at app/Subscriber.php
:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Subscriber extends Model
{
protected $fillable = ['email'];
}
After you run the migration you have everything set up to receive new subscribers. If you submit the form now, you should see the new email address added to subscribers
table with timestamps.
#Using Hooks
What if we want to save the user's IP address along with the email or if we want to forward this data to a 3rd party email service? Let's see how we can do those with hooks.
#Before Hook
Let's extend our form with a meta
field, called ip
:
public function schema() {
return [
'email' => [
'type' => 'text',
'placeholder' => 'Email address'
],
'ip' => [
'type' => 'meta'
],
];
}
meta
field is used to store date invisible to users but important to us.Once we have a field for the IP address we need to fill in its value before the data is saved by Laraform. This can be done using before
hook:
class NewsletterForm extends \Laraform
{
public $model = \App\Subscriber::class;
public function schema() {
return [
'email' => [
'type' => 'text',
'placeholder' => 'Email address'
],
'ip' => [
'type' => 'meta'
]
];
}
public function buttons() {
// ...
}
public function before() {
$this->data['ip'] = \Request::ip();
}
}
schema
, so everything added to the submitted data will be ignored, unless you define an element for it.To finish this example let's update our migration and model.
Migration
public function up()
{
Schema::create('subscribers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('email');
$table->string('ip');
$table->timestamps();
});
}
Model
class Subscriber extends Model
{
protected $fillable = ['email', 'ip'];
}
After you run the migration and submit a new email address, you should see the IP address being saved to the database as well.
#After Hook
What if we want to do something after the entity is saved? As you probably guessed, we can use the after
hook. Let's save the user's IP address this time using after
hook.
class NewsletterForm extends \Laraform
{
public $model = \App\Subscriber::class;
public function schema() {
return [
'email' => [
'type' => 'text',
'placeholder' => 'Email address'
]
];
}
public function buttons() {
// ...
}
public function after() {
// Retrieving Subscriber model
$subscriber = $this->database->getEntity();
// Set entity 'ip' value
$subscriber->ip = \Request::ip();
// Save the model
$subscriber->save();
}
}
As you can see in this case we don't have to define a meta
element for ip
as we are directly updating the Subscriber
model after the data is saved.
Let's try this example in your browser and see that the IP is getting saved in this case too.
#Processing Without Model
It's also possible to process the data without assigning an Eloquent model. The most straightforward is to use after
hook for this purpose:
class NewsletterForm extends \Laraform
{
public function schema() {
return [
'email' => [
'type' => 'text',
'placeholder' => 'Email address'
]
];
}
public function buttons() {
// ...
}
public function after() {
$subscriber = new \App\Subscriber;
$subscriber->email = $this->data['email'];
$subscriber->ip = \Request::ip();
$subscriber->save();
}
}
#Other Hooks & API
The before
and after
hooks are not the only ones. Find more at Events & hooks section.
Also, if you are using hooks there's a chance you want rely on some methods of Laraform itself. To learn more about them check out its Laraform reference.
#Sending Response
Once the form is submitted and successfully processed by the backend you'll receive the following response:
{
status: 'success',
messages: [],
payload: []
}
You can add custom messages
or payload
as well as setting the status
in the before
and after
hook using fail()
and success()
methods. Let's see a complete example staying with the first version of our newsletter form:
class NewsletterForm extends \Laraform
{
public $model = \App\Subscriber::class;
public function schema() {
return [
'email' => [
'type' => 'text',
'placeholder' => 'Email address'
]
];
}
public function buttons() {
// ...
}
public function before() {
if ($this->exists()) {
return $this->fail('Already subscribed');
}
}
public function after() {
return $this->success('Successfully subscribed');
}
protected function extists() {
return !!\App\Subscriber::where('email', $this->data['email'])->count();
}
}
Now if you try to add an email address that already exists in the database you'll receive the following response:
{
status: 'fail',
messages: ['Already subscribed'],
payload: []
}
While if you successfully add a new email you'll get this:
{
status: 'success',
messages: ['Successfully subscribed'],
payload: []
}
before
or after
hook have a return value that form's response will be instantly returned and no further processing will take place.#Handling Response
The response sent by the backend does not do anything by itself. This is because displaying messages to users can be implemented in many different ways and Laraform does not want to force a solution. Instead it provides events on the frontend that you can subscribe to and deal with the response yourself.
To see that in action first create a frontend form and attach it to our existing NewsletterForm
.
Create NewsletterForm.vue
Let's create a new frontend form at resources/js/components/forms/NewsletterForm.vue
:
<script>
export default {
mixins: [Laraform],
created() {
alert('Form is created')
}
}
</script>
Attach To Backend
Keeping our form from the beginning of this section, let's add the newsletter-form
component to our backend:
class NewsletterForm extends \Laraform
{
public $component = 'newsletter-form';
// ...
}
Register Component
Open up the app.js
(or main JS file) and add the NewsletterForm
as a component.
import Vue from 'vue'
import NewsletterForm from './components/forms/NewsletterForm'
Vue.component('newsletter-form', NewsletterForm)
// ...
There you go - if you refresh the page now an alert should appear saying Form is created which means you've successfully connected the frontend form.
Subscribe To response
Event
The next is to replace the content of created()
hook with a subscription for response
event:
export default {
mixins: [Laraform],
created() {
this.on('response', (response) => {
alert(response.data.messages[0])
})
}
}
If you compile your assets, refresh the page and submit the form now you'll see the response message sent by the backend in the alert window.
#The `success`, `fail` and `error` Events
The response
event is a general one to process every successful response with 200
like HTTP statuses. If you want to differentiatate between fail
and success
responses based on the value of status
you can use fail
and success
events:
export default {
mixins: [Laraform],
created() {
this.on('fail', (response) => {
alert('Failed: ' + response.data.messages[0])
})
this.on('success', (response) => {
alert('Successful: ' + response.data.messages[0])
// Resetting the form
this.reset()
})
}
}
If the server is responding with an error (HTTP status of 500
for example) you catch that by subscribing to error
event:
export default {
mixins: [Laraform],
created() {
this.on('error', (error) => {
console.log(error)
})
}
}
#Custom Endpoint
Now that we know the basics of submitting forms, let's go one step further and see how you can replace auto-processing.
To provide a custom endpoint, where the form should submit, just add endpoint
property to the form:
class Form extends \Laraform
{
public $endpoint = '/my/custom/endpoint'
// ...
}
You can also do this globally in by changing the endpoint
property in configuration:
/*
|--------------------------------------------------------------------------
| Endpoint
|--------------------------------------------------------------------------
|
| Default endpoint where the form should submit.
|
*/
'endpoint' => '/my/custom/endpoint',
#Defining Endpoint On Frontend
If you are not using Laravel or you are not rendering a form from the backend you might define the endpoint in the frontend form component:
export default {
mixins: [Laraform],
data() {
return {
endpoint: '/my/custom/endpoint',
// ...
}
}
}
Or in the frontend configuration as well:
import Vue from 'vue'
import Laraform from '@laraform/laraform'
Laraform.config({
endpoints: {
process: '/my/custom/endpoint'
}
})
// ...
#Processing Traits
If you don't want to use Laraform's FormController@process
action to process the form, but still rely on the auto-processing feature, you can simply add ProcessesForm
trait to your controller:
<?php
namespace App\Http\Controllers;
use Laraform\Traits\ProcessesForm;
class FormController extends Controller
{
use ProcessesForm;
// ...
}
This is useful if you want to implement Authorization for example.
In this case you need to set up your own route to deal with the endpoint:
Route::post('/process-form', 'FormController@process');
Also you need to set the /process-form
endpoint globally or on a form level, as we've discussed.
#Using Resource Controllers
If you are using Resource Controllers and want to add auto-processing feature to those, you can add StoresForm
and UpdatesForm
traits along with form
method, which should return the related form class name. This will add store
and update
methods to your resource controller, which will auto-process the form:
<?php
namespace App\Http\Controllers;
use App\Subscriber;
use Laraform\Traits\StoresForm;
use Laraform\Traits\UpdatesForm;
use App\Forms\NewsletterForm;
class SubscriberController extends Controller
{
use StoresForm, UpdatesForm;
public function form() {
return app(NewsletterForm::class);
}
public function index() {
// ...
}
public function create() {
// ...
return view('subscriber.create', [
'form' => $this->form()
]);
}
public function show() {
// ...
}
public function edit(Subscriber $subscriber) {
// ...
$form = $this->form();
return view('subscriber.edit', [
'form' => $form->setEndpoint($form->getEndpoint() . '/' . $subscriber->id)
->load($subscriber->id)
]);
}
public function destroy() {
// ...
}
}
Notice On Using Resource Controller With Laraform
A minor fix must be added to resource routes when using with Laraform. As PUT
and PATCH
methods are expected to send data similar to GET
(as query params) we need to change this, because sending whole form data, including even files eg. wouldn't make sense in our case.
When creating routes for the resource just exclude update
and add it manually as a POST
request:
Route::resource('subscribers', 'SubscriberController')->except(['update']);
Route::post('subscribers/{subscriber}', 'SubscriberController@update');
#Using Auto-Processing Manually
If you'd like to use Laraform's auto-processing feature with different method than process
or the aforementioned resource actions, you can use the Laraform\Process\AutoProcess
class directly:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Laraform\Process\AutoProcess;
class CustomController extends Controller
{
public function action(Request $request) {
return (new AutoProcess())->process($request);
}
}
By default Laraform will try to parse the name of the backend form class from key
property. You might optionally pass a Laraform\Laraform
instance as a second parameter if you want to override this with one exact form:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Laraform\Process\AutoProcess;
use App\Forms\PostForm;
class CustomController extends Controller
{
public $form = PostForm::class;
public function action(Request $request) {
return (new AutoProcess())->process($request, app($this->form));
}
}
#Custom Submission
If you want to implement the submission logic all by yourself, you can absolutely do that. Let's see how.
#Overriding Frontend Submission
On the frontend just replace send()
method of main Laraform component to overwrite submission logic.
Let's create a new component at resources/js/components/forms/Laraform.vue
:
<script>
export default {
mixins: [Laraform],
methods: {
send() {
// implement your logic here
}
}
}
</script>
Then use this component as a mixin for each of your forms instead of the global Laraform
mixin:
<script>
import Laraform from './Laraform'
export default {
mixins: [Laraform]
}
</script>
If you are interested in the default implementation of send by Laraform go to: node_modules/@laraform/laraform/src/components/Laraform.vue
and search for send()
method.
#Custom Backend Processing
If you want to completely or partially replace the auto-processing logic of Laraform just create your own action as a processor as described in Custom endpoint section.
For this it might be useful to check out the Laraform backend Reference to see what methods and properties you can rely on. You can also check out the default implementation at vendor/laraform/laraform-laravel/src/Controllers/FormController.php@process
.