Creating Element
#Override Existing Element
In some cases you might need to add or overwrite features of some elements. To do this you can easily extend the base element on the frontend and add features as you'd like.
Let's see an example where we'd like to add the required
HTML attribute to a textarea
element if the element has a required
validation rule. This is a feature that Laraform does not provide out of the box, because it has it's built in validation for required
but let's say we still need it for some reason.
Create Element
First, let's create our custom element at resources/js/components/elements/TextareaElement.vue
:
<script>
import TextareaElement from '@laraform/laraform/src/themes/bs4/components/elements/TextareaElement'
export default {
mixins: [TextareaElement]
}
</script>
As you can see we're extending the bs4
version of textarea element assuming we are using bs4
as theme. This way we already have an element which is completely identical to the one provided by Laraform's bs4
theme.
Override Element
Let's use this element to overwrite the default TextareaElement
by setting it in resources/js/app.js
or your main JS file:
import Vue from 'vue'
import Laraform from '@laraform/laraform'
import TextareaElement from './components/elements/TextareaElement'
Laraform.element('textarea', TextareaElement)
Vue.use(Laraform)
const app = new Vue({
el: '#app'
})
Confirm
To confirm we've successfully overwritten the element add an alert()
to it when it is mounted
:
<script>
import TextareaElement from '@laraform/laraform/src/themes/bs4/components/elements/TextareaElement'
export default {
mixins: [TextareaElement],
mounted() {
alert('Custom TextareaElement is mounted')
}
}
</script>
Now if you use textarea
type within a form you should have an alert popped up when the form is loaded displaying our message.
Alternative Way To Replace Component
Instead of overwrite the textarea
element completely you can also choose to assign it only to certain elements using component
property.
First you need to register the component:
import Vue from 'vue'
import Laraform from '@laraform/laraform'
import TextareaElement from './components/elements/TextareaElement'
Vue.use(Laraform)
Vue.component('MyTextareaElement', TextareaElement)
const app = new Vue({
el: '#app'
})
Or if you are using custom frontend component you can do that locally:
<script>
import TextareaElement from './../elements/TextareaElement'
export default {
mixins: [Laraform],
components: {
MyTextareaElement: TextareaElement,
}
}
</script>
Then you'll be able to use this custom textarea element only for certain fields:
public function schema() {
return [
'headline' => [
'type' => 'textarea',
'label' => 'Headline'
],
'intro' => [
'type' => 'textarea',
'label' => 'Intro',
'component' => 'MyTextareaElement'
]
]
}
Or you can also use the component directly without even registering it:
<script>
import TextareaElement from './../elements/TextareaElement'
export default {
mixins: [Laraform],
data() {
return {
schema: {
headline: {
type: 'textarea',
label: 'Headline'
},
intro: {
type: 'textarea',
label: 'Intro',
component: TextareaElement
}
}
}
}
}
</script>
#Overriding Element Template
Great, now we can start extending our textarea element to our needs. As'd like to have the required
attribute added to the HTML of the element we'll need to override the textarea element's template.
Let's look out for the template in @laraform/laraform/src/themes/bs4/components/elements/TextareaElement
. In fact you'll find that it is extending the default @laraform/laraform/src/themes/default/components/elements/TextareaElement
template so let's copy it from there:
<template>
<component :is="theme.components.BaseElementLayout"
:el$="el$"
>
<template slot="field">
<slot name="prefix"></slot>
<component :is="theme.components.ElementLabelFloating"
v-if="floating"
:visible="!empty"
>{{ floating }}</component>
<textarea
v-model="model"
:name="name"
:id="id"
:class="theme.classes.textarea"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
:rows="rows"
@keyup="handleKeyup"
ref="textarea"
/>
<slot name="suffix"></slot>
</template>
<slot slot="label" name="label" :el$="el$"></slot>
<slot slot="before" name="before" :el$="el$"></slot>
<slot slot="between" name="between" :el$="el$"></slot>
<slot slot="error" name="error" :el$="el$"></slot>
<slot slot="after" name="after" :el$="el$"></slot>
</component>
</template>
<script>
import TextareaElement from '@laraform/laraform/src/themes/bs4/components/elements/TextareaElement'
export default {
mixins: [TextareaElement],
mounted() {
alert('Custom TextareaElement is mounted')
}
}
</script>
First this might look kinda spooky - there are so many things to render a simple element. Trust me this is still the simplest solution to provide all the features Laraform provides, so let's see one by one what is used for what.
Dynamic Components
First, you can see each <component>
is dynamic, using :is
to determine it's actual type. This is because all components in themes are interchangeable, meaning you can replace one without touching other components using it.
Element Layout
The BaseElementLayout
component as you can suspect from the name, defines the base layout of the element, rendering the wrapper HTML, label, description, etc. It also renders the field itself in a field
slot, which you can see we're using to render our textarea
element with its floating label.
Slots
Also there are slots
all over the template. They can be used to override different parts of the element when using inline elements.
That's enough for now to move forward, if you are interested more in-depth about theming and templating, check out Style & Theme and Customizing Layout chapters.
# Adding required
Attribute
From this whole template what we need to extend is the textarea
itself. Let's add a conditional required
attribute that depends on the value of required
computed property:
<template>
// ...
<textarea
v-model="model"
:name="name"
:id="id"
:class="theme.classes.textarea"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
:rows="rows"
@keyup="handleKeyup"
ref="textarea"
// We're adding :required here
:required="required"
/>
// ...
</template>
Now let's create the required
computed property, which check if the rules include required
:
<script>
import TextareaElement from '@laraform/laraform/src/themes/default/components/elements/TextareaElement'
export default {
mixins: [TextareaElement],
computed: {
required() {
// When rules are string
if (typeof this.schema.rules == 'string') {
return this.schema.rules.includes('required')
}
// When rules are array
if (typeof this.schema.rules == 'object') {
return this.schema.rules.indexOf('required') !== -1
}
}
}
}
</script>
That's it, now if you add required
to any textarea's rules
you'll have HTML validation attribute too.
#Overriding Backend
Now let's move away from extending our frontend and let's see how we can override an element on the backend.
Create Element
First, create a new textarea element at app/Elements/TextareaElement.php
extending Laraform's version:
<?php
namespace App\Elements;
use Laraform\Elements\TextareaElement as TextareaElementBase;
class TextareaElement extends TextareaElementBase
{
// ...
}
Override Element
To override the default textarea
element on the backend add this to elements
array in config/laraform.php
:
/*
|--------------------------------------------------------------------------
| Elements
|--------------------------------------------------------------------------
|
| A list of custom elements to be added to Laraform.
|
| eg. [
| 'custom' => App\Elements\CustomElement::class,
| ]
|
*/
'elements' => [
'textarea' => App\Elements\TextareaElement::class'
],
That's it! Now our textarea element is overwritten and we can customize it.
Customize
Just as an example let's see how we can remove all HTML tags from any data being set by overriding setData
method:
<?php
namespace App\Elements;
use Laraform\Elements\TextareaElement as TextareaElementBase;
class TextareaElement extends TextareaElementBase
{
public function setData($data)
{
$this->data = strip_tags($data);
}
}
Check out elements and their underlying method and properties at vendor/laraform/laraform-laravel/src/elements
folder.
#Creating New Element
Creating new elements requires a slightly different approach then extending ones. Of course if you intend to create a new element based on an existing one the process is the same as above. You can register any element types, even new ones which base on existing ones the same way as described previously.
If you want to create a completely new element, let's say a recaptcha
for example you have to start building it from the ground.
#Create Element Component
First create a component at resources/js/components/elements/RecaptchaElement.vue
and add some of Laraform's mixins:
<script>
import BaseElement from '@laraform/laraform/src/mixins/BaseElement'
import BaseValidation from '@laraform/laraform/src/mixins/BaseValidation'
export default {
name: 'RecaptchaElement',
mixins: [BaseElement, BaseValidation]
}
</script>
Laraform elements use mixins to add default properties and methods they share with other elements. Because of this, when you are creating a new element, you don't have to actually write everything from the ground. You have the basics features (methods, properties, etc.) that will take care of the main functions of each element, like handling data model, validation, converting schema to data props, etc.
The contents of these mixins are not available in the documentation, but you can always check them at @laraform/laraform/src/mixins
folder, or at element$ reference, which contains the basic features of each element.
As for this example it's enough to know, that BaseElement
and BaseValidation
mixins will add all the features to our element which eg. a text element would have.
#Create Component Template
Next we need a template for the element, so let's create a basic wrapper that most of Laraform elements use:
<template>
<component :is="theme.components.BaseElementLayout"
:el$="el$"
>
<template slot="field">
<slot name="prefix"></slot>
<!-- reCAPTCHA should come here -->
<slot name="suffix"></slot>
</template>
<slot slot="label" name="label" :el$="el$"></slot>
<slot slot="info" name="info" :el$="el$"></slot>
<slot slot="before" name="before" :el$="el$"></slot>
<slot slot="between" name="between" :el$="el$"></slot>
<slot slot="error" name="error" :el$="el$"></slot>
<slot slot="after" name="after" :el$="el$"></slot>
</component>
</template>
// ...
We're not getting into the structure of this template, as we've already discussed it before.
#Add Captcha To Template
What we'll do is to look for a reCAPTCHA implementation for Vue.js. Luckily, there's already one, so we don't have to create a Vue wrapper around Google's captcha solution. The one we are going to use is https://github.com/DanSnow/vue-recaptcha
We install it with:
$ npm install --save vue-recaptcha
Now we're ready to add that as a component and also to our template:
<template>
<component :is="theme.components.BaseElementLayout"
:el$="el$"
>
<template slot="field">
<slot name="prefix"></slot>
<vue-recaptcha />
<slot name="suffix"></slot>
</template>
<slot slot="label" name="label" :el$="el$"></slot>
<slot slot="info" name="info" :el$="el$"></slot>
<slot slot="before" name="before" :el$="el$"></slot>
<slot slot="between" name="between" :el$="el$"></slot>
<slot slot="error" name="error" :el$="el$"></slot>
<slot slot="after" name="after" :el$="el$"></slot>
</component>
</template>
<script>
import BaseElement from '@laraform/laraform/src/mixins/BaseElement'
import BaseValidation from '@laraform/laraform/src/mixins/BaseValidation'
import VueRecaptcha from 'vue-recaptcha'
export default {
name: 'RecaptchaElement',
mixins: [BaseElement, BaseValidation],
components: {
VueRecaptcha
}
}
</script>
#Adding Features
Great so we have the component in our RecaptchaElement. It requires some properties and has some events based on the documentation so we're just going to create them:
<template>
<component :is="theme.components.BaseElementLayout"
:el$="el$"
>
<template slot="field">
<slot name="prefix"></slot>
<vue-recaptcha
:sitekey="sitekey"
:loadRecaptchaScript="true"
@verify="handleVerify"
@expired="handleExpired"
@error="handleError"
/>
<slot name="suffix"></slot>
</template>
<slot slot="label" name="label" :el$="el$"></slot>
<slot slot="info" name="info" :el$="el$"></slot>
<slot slot="before" name="before" :el$="el$"></slot>
<slot slot="between" name="between" :el$="el$"></slot>
<slot slot="error" name="error" :el$="el$"></slot>
<slot slot="after" name="after" :el$="el$"></slot>
</component>
</template>
<script>
import BaseElement from '@laraform/laraform/src/mixins/BaseElement'
import BaseValidation from '@laraform/laraform/src/mixins/BaseValidation'
import VueRecaptcha from 'vue-recaptcha'
export default {
name: 'RecaptchaElement',
mixins: [BaseElement, BaseValidation],
components: {
VueRecaptcha,
},
computed: {
sitekey() {
return this.schema.sitekey
}
},
methods: {
handleVerify(response) {
this.update(response)
this.validate()
},
handleExpired() {
this.update(null)
this.validate()
},
handleError() {
this.update(null)
this.validate()
},
}
}
</script>
As you can see we added the sitekey
as a computed property which tries to look for it in the element schema. Also we set loadRecaptchaScript
to load the required Google <script>
automatically and there are events too.
If you check out the handle
methods you can see that we're using them to set the value of the field on certain events. Also we validate()
the data on state changes to prevent submitting the form if it has a required
rule and the captcha is incomplete.
#Register Frontend Element
So that's our complete element, what's left is to register it:
import Vue from 'vue'
import Laraform from '@laraform/laraform'
import RecaptchaElement from './components/elements/RecaptchaElement'
Laraform.element('recaptcha', RecaptchaElement)
Vue.use(Laraform)
const app = new Vue({
el: '#app'
})
#Create Backend Element
Also we need to create a backend element for it at app/Elements/RecaptchaElement.php
<?php
namespace App\Elements;
use Laraform\Elements\Element;
class RecaptchaElement extends Element
{
}
#Register Backend Element
Then register the backend element too at config/laraform.php
:
/*
|--------------------------------------------------------------------------
| Elements
|--------------------------------------------------------------------------
|
| A list of custom elements to be added to Laraform.
|
| eg. [
| 'custom' => App\Elements\CustomElement::class,
| ]
|
*/
'elements' => [
'recaptcha' => App\Elements\RecaptchaElement::class'
],
#Try Out
Now if we create a form we can verify that our recaptcha will appear and work as expected:
<?php
namespace App\Forms;
class RegistrationForm extends \Laraform
{
public function schema() {
return [
// ...
'captcha' => [
'type' => 'recaptcha',
'rules' => 'required',
'sitekey' => 'YOUR_SITE_KEY'
],
];
}
public function buttons() {
return [[
'label' => 'Submit'
]];
}
}