Feedback Laraform is now in maintenance mode. Read more on our GitHub.

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'
    ]];
  }
}