Skip to content
This repository has been archived by the owner on May 13, 2024. It is now read-only.

Extend a new Control

Phat Tran edited this page Oct 26, 2019 · 2 revisions

Introduction

For a good reason, sometimes we must extend a new control for our special cases. This has to be done in order to reach the requirement from the end-users, customers,...

So "Extensible" is very important. That's why I found a solution to deal with.

Requirements to extend a new control

  • Vue knowledge
  • JS knowledge

Concept

To extend a new control, we need to prepare 3 Vue Components:

  • Template component - How the control will look like in the Configuration/Template page
  • Sidebar component - To handle your own data/config for your custom control
  • GUI Component - To show up for the end-users.

After that, we will provide it so Vue-Form-Builder can inject your components into its lifecycle.

Note: You don't need to fork my project and build your own library to deal with this. You can create the Vue Component in your current project. Let me show you more below.

For example, I will create a new control using this library http://www.bootstraptoggle.com/ instead of using a normal checkbox.

Provide Custom-Control for Vue-Form-Builder

Options is an object that you need to pass into the FormBuilder component. Basically:

// template
<form-builder type="template" :options="myOption"></form-builder>

// your data:
{
    data: {
        myOption: {}, // object
    }
}

For the options, you need a property named moreControls inside. It's an object too and the structure should look like this: https://github.com/sethsandaru/vue-form-builder/blob/master/src/config/control_constant.js

In the end for the toggle, I supposed to have 3 components already. So let me define the custom-control for Vue-FormBuilder:

<form-builder type="template" :options="myOption"></form-builder>

// data: 
<script>
[...]

// import stuff here
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck'; // re-use like checkbox :(

// Import Components - REMEMBER TO CHANGE YOUR PATH TO YOUR COMPONENTS :D
import GUI_ToggleControl from '@mypath/gui/ToggleControl';
import TEMPLATE_ToggleControl from '@mypath/template/ToggleControl';
import SIDEBAR_ToggleControl from '@mypath/sidebar_items/ToggleConfigControl';

export default {
    data() {
        return {
            myOption: {
                moreControls: {
                    toggle: {
                        label: "Bootstrap Toggle",
                        icon: faCheck, // please change to another icon if you want, remember import.
                        source: {
                            gui: GUI_ToggleControl, // register component here
                            template: TEMPLATE_ToggleControl,
                            config: SIDEBAR_ToggleControl,
                        }
                    },
                    [... more custom controls??]
                }
            }
        }
    }
}
</script>

** This must be the last step that you do. Therefore the Form should be running **   
** Please remember not to use the duplicated key from constant [CONSTANT_FILE](https://github.com/sethsandaru/vue-form-builder/blob/master/src/config/control_constant.js). Otherwise your control will not be injected**

Create a Template/Configuration Control

Because of the toggle is based on the checkbox concept. So I will copy the Checkbox Template Control which already created in the Vue Form Builder.

Normal code: https://github.com/sethsandaru/vue-form-builder/blob/master/src/template/ui/common/controls/CheckboxControl.vue

You can see that we have 2 props and we can use the control object and labelPosition string.

For the "labelPosition" concept, please check it out at the Documentation for the Vue Form Template. Typically, we got 2 position is top and left. So that's why you will see my v-if v-if="labelPosition === 'left'"

For the control object. Here is the sample data, it's just an object to contains the configuration of the control.

{
              "type": "text",
              "name": "control_text_336499",
              "fieldName": "DocumentID",
              "label": "Document Number",
              "order": 0,
              "defaultValue": "",
              "value": "",
              "className": "col-md-5",
              "readonly": false,
              "labelBold": false,
              "labelItalic": false,
              "labelUnderline": false,
              "required": true,
              "isMultiLine": false,
              "isInteger": false,
              "decimalPlace": 0,
              "isTodayValue": false,
              "dateFormat": "dd/mm/yy",
              "isNowTimeValue": false,
              "timeFormat": "HH:mm",
              "isMultiple": false,
              "isAjax": false,
              "dataOptions": [],
              "ajaxDataUrl": "",
              "isChecked": false
}

Because of the Toggle basically a CheckBox => I'll re-use the isChecked property.

So I'm creating a new Vue Component: "TemplateToggleControl.vue" in my own project.

First, we'll change the template part of Vue Component:

<template>
    <div class="controlItemWrapper" :class="control.className" :data-control-name="control.name">
        <div class="controlItem row" :id="control.name" v-if="labelPosition === 'left'">
            <div class="col-md-4">
                <label :class="{'bold': control.labelBold, 'italic': control.labelItalic, 'underline': control.labelUnderline}">
                    {{control.label}}
                </label>
            </div>
            <div class="col-md-8 input-group">
                <div class="text-center w-100 toggle-container">
                    <input type="checkbox" :name="control.fieldName" :checked="control.isChecked">
                </div>
            </div>
        </div>
        <div class="controlItem row" :id="control.name" v-else>
            <div class="form-group col-md-12">
                <label :class="{'bold': control.labelBold, 'italic': control.labelItalic, 'underline': control.labelUnderline}">
                    {{control.label}}
                </label>
                <div class="input-group">
                    <div class="text-center w-100 toggle-container">
                        <input type="checkbox" :name="control.fieldName" :checked="control.isChecked">
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

Secondly, Update the script of the component.

Basically we just init the control in mounted hook and destroy control in beforeDestroy hook

<script>
    // REMEMBER: To import your special stuff here :D
    require('path/to/my/stuff/bootstrap-toggle.min.js');
    require('path/to/my/stuff/bootstrap-toggle.min.css');

    export default {
        name: "ToggleControl", // Change Name
        props: ['control', 'labelPosition'], // core-basic data, don't change that if you don't know anything about it.
        mounted() {
            $(this.$el).find(".toggle-container > input") // look for the input-checkbox
                .bootstrapToggle(); // run the init
        },
        beforeDestroy() {
            // have to destroy the control to reduce ram, dom,... https://vuejs.org/v2/cookbook/avoiding-memory-leaks.html
            $(this.$el).find(".toggle-container > input")           
                .bootstrapToggle('destroy'); // destroy it
        }
    }
</script>

Create Sidebar Component to Config Data

Normal code: https://github.com/sethsandaru/vue-form-builder/blob/master/src/template/ui/sidebar_items/CheckboxConfigComponent.vue

Basically I don't need to change much. But if your control have some specific things to config. Please define it here including the template itself.

The control object is the same like above.

Also, please don't change directly the data to control in the hook (created, mounted) or methods. Vue will show an error. V-model should be enough. If you got some specific case, use the computed instead.

GUI Component

I think you should get familiar with the custom flow right now. So we will do quickly for the last step.

Normal code: https://github.com/sethsandaru/vue-form-builder/blob/master/src/gui/ui/controls/CheckboxControl.vue

Code for Toggle:

<template>
    <div>
        <div class="row checkBoxControl" v-if="labelPosition === 'left'">
            <div class="col-md-4">
                <label :for="control.name + '_gui_control'"
                       :class="{'bold': control.labelBold, 'italic': control.labelItalic, 'underline': control.labelUnderline}">
                    {{control.label}}
                </label>
            </div>
            <div class="col-md-8 text-center">
                <input type="checkbox"
                       :readonly="this.control.readonly"
                       :name="control.fieldName"
                       v-model="control.value" />
            </div>
        </div>
        <div class="form-group" v-else>
            <label :for="control.name + '_gui_control'"
                   :class="{'bold': control.labelBold, 'italic': control.labelItalic, 'underline': control.labelUnderline}">
                {{control.label}}
            </label>

            <div class="text-center">
                <input type="checkbox"
                       :readonly="this.control.readonly"
                       :name="control.fieldName"
                       v-model="control.value" />
            </div>
        </div>
    </div>
</template>

<script>
    // REMEMBER: To import your special stuff here :D
    require('path/to/my/stuff/bootstrap-toggle.min.js');
    require('path/to/my/stuff/bootstrap-toggle.min.css');

    export default {
        name: "ToggleControl",
        props: ['control', 'labelPosition'],
        mounted() {
            let toggle_state = 'off';

            // because the final data for Checkbox/Toggle is true/false also it got default value => set it.
            if (this.control.isChecked) {
                this.control.value = true;
                toggle_state = 'on';
            }

            $(this.$el).find("input").bootstrapToggle(toggle_state);
        },
        beforeDestroy() {
            // avoid mem leak - destroy it before
            $(this.$el).find("input").bootstrapToggle('destroy');
        }
    }
</script>

Last thing to do

Back to this step to see how to define/inject your stuff for the Vue-Form-Builder

TLDR or Source to look over

Those code below are your friend, look over at it:

Thank you. Sorry for the late documentation.