Skip to main content

DisForm.vue


DisForm.vue

Developer page

For developers

A deep dive into the code behind the Template-Manager, which comprises the mDIS interactive form designer.

single-tile

Purpose of DisForm

DisForm is a feature-rich Vue component that is used to filter, list, and update the data records of a data model (table). Using the Template-Manager, mDIS Admins design new tables/models and new data input forms that are typically based on DisForm. The newly generated "artifacts" - a set of .vue files, .json files, and others (PHP files) - can then be customized. In Legacy DIS, a similar process (of customizing data models and input forms) was also known as specialization.

Note

Files src/components/DisForm.vue and its container src/components/DisSmartForm.vue should not be changed by any user.

The <template> section of DisForm typically contains two other Form components which should not be changed either:

  • DisFilterForm.vue: Implements the Filter Bar visible at the top of each form. DisFilterForm is used to render the filter components and filter-by-value form. This component updates the current URL query parameters corresponding to the selected filter component's value. It also offers a modal Filter-by-Values dialog box that processes the new filter expressions entered by the user. The filter settings then "trickle through" the other components embedded in the form, and the filter is applied to the record listing at the bottom of the page.
    After receiving the new value of a filter expression, all affected components update themselves. This process is triggered by events emitted by changes in DisFilterForm.
  • DisDataTable.vue, which lists and paginates the linked table records. The listing depends on the current filter settings, visible as URL parameters in the browser's address bar on the current page. Alternatively, the listing may also depend on the current filter-by-value settings obtained from the DisFilterForm.vue component (in case this dialog box was used).

The <script> section of DisForm contains a rich data array and a lot of props and methods. There are too many to discuss them here. Most of them are either click handlers for the many buttons inside the navigation bar and filter bar, or other types of event listeners.

Tutorial: customizing a .vue file

Form Specialization: Data Fields

Forms generated by mDIS admins are typically based on DisForm. The new forms can be customized or specialized. The remainder of this document is a tutorial on how to perform various customizations on a file SampleForm.vue. (You might use a different name for your form, and that's okay.)

About this Tutorial

  • This tutorial demonstrates how to customize the forms with a text editor, by editing the generated source code.
  • Therefore, the Templates-Manager GUI plays only a minor role in this tutorial, mainly during initial form generation.
  • If you follow along in your IDE, copy the sections in green font from the code blocks below. After pasting them into your SampleForm.vue file (or equivalent), remove the plus signs ( + ) from the beginning of each line!
  • If you work in development mode (npm run serve), after saving any .vue file, the changes should be immediately visible in the browser. In production mode, you need to run npm run build.

To make form specialization easier to understand, we will start from this example:

form specialization example

A simple form with four fields. Here are the model templates and the form templates, both in JSON format:

Model Template

Generate this model with the Template-Manager. Alternatively, save this code into a .json file, then upload it with the "Upload a model template (*.json)" feature of the Template-Manager. "Save & Generate" the model.

On lines 2, 3, and 4 of the following code, you can change the "module", "name", and "table" entries to something you prefer. ("module" means "TableSet", "name" is the Model Name as it appears on the web page, and "table" is the name of the database table. Avoid blanks ``.):

{
    "module": "Sa",
    "name": "Sample",
    "table": "sa_sample",
    "importTable": null,
    "parentModel": "",
    "columns": {
        "id": {
            "name": "id",
            "importSource": "",
            "type": "integer",
            "size": 11,
            "required": false,
            "primaryKey": true,
            "autoInc": true,
            "label": "ID",
            "description": "",
            "validator": "",
            "validatorMessage": "",
            "unit": "",
            "selectListName": "",
            "calculate": "",
            "defaultValue": ""
        },
        "title": {
            "name": "title",
            "importSource": "",
            "type": "string",
            "size": 50,
            "required": true,
            "primaryKey": false,
            "autoInc": false,
            "label": "Title",
            "description": "The title of this record",
            "validator": "",
            "validatorMessage": "",
            "unit": "",
            "selectListName": "",
            "calculate": "",
            "defaultValue": ""
        },
        "slug": {
            "name": "slug",
            "importSource": "",
            "type": "string",
            "size": 100,
            "required": false,
            "primaryKey": false,
            "autoInc": false,
            "label": "Slug",
            "description": "A unique kebab-case title",
            "validator": "",
            "validatorMessage": "",
            "unit": "",
            "selectListName": "",
            "calculate": "",
            "defaultValue": ""
        },
        "depth": {
            "name": "depth",
            "importSource": "",
            "type": "double",
            "size": null,
            "required": true,
            "primaryKey": false,
            "autoInc": false,
            "label": "Depth",
            "description": "Depth in meters",
            "validator": "",
            "validatorMessage": "",
            "unit": "",
            "selectListName": "",
            "calculate": "",
            "defaultValue": ""
        },
        "depth_mm": {
            "name": "depth_mm",
            "importSource": "",
            "type": "double",
            "size": null,
            "required": true,
            "primaryKey": false,
            "autoInc": false,
            "label": "Depth Mm",
            "description": "Depth in millimeters",
            "validator": "",
            "validatorMessage": "",
            "unit": "",
            "selectListName": "",
            "calculate": "",
            "defaultValue": ""
        }
    },
    "indices": {
        "pk_id": {
            "name": "pk_id",
            "type": "PRIMARY",
            "columns": [
                "id"
            ]
        },
        "unique_slug": {
            "name": "unique_slug",
            "type": "UNIQUE",
            "columns": [
                "slug"
            ]
        }
    },
    "foreignkeys": [],
    "createdAt": 1556631283,
    "modifiedAt": 1556631295,
    "generatedAt": 1556631298,
    "fullName": "SaSample"
}

Form Template

Generate this form with the Forms-Manager, or upload it with the "Upload a form template (*.json)" feature of the Template-Manager. "Save & Generate" the form. Note the file names that are being generated.

{
    "name": "sample",
    "dataModel": "SaSample",
    "fields": [
        {
            "name": "title",
            "label": "Title",
            "description": "The title of this record",
            "validators": [
                {
                    "type": "required"
                },
                {
                    "type": "string"
                }
            ],
            "formInput": {
                "type": "text",
                "disabled": false,
                "calculate": "",
                "jsCalculate": ""
            },
            "group": "-group1",
            "order": 0
        },
        {
            "name": "slug",
            "label": "Slug",
            "description": "A unique kebab-case title",
            "validators": [
                {
                    "type": "string"
                }
            ],
            "formInput": {
                "type": "text",
                "disabled": false,
                "calculate": "",
                "jsCalculate": ""
            },
            "group": "-group1",
            "order": 1
        },
        {
            "name": "depth",
            "label": "Depth",
            "description": "Depth in meters",
            "validators": [
                {
                    "type": "required"
                },
                {
                    "type": "number"
                }
            ],
            "formInput": {
                "type": "text",
                "disabled": false,
                "calculate": "",
                "jsCalculate": ""
            },
            "group": "Meta Data",
            "order": 0
        },
        {
            "name": "depth_mm",
            "label": "Depth Mm",
            "description": "Depth in millimeters",
            "validators": [
                {
                    "type": "required"
                },
                {
                    "type": "number",
                    "min": null,
                    "max": null
                }
            ],
            "formInput": {
                "type": "text",
                "disabled": false,
                "calculate": "[depth] * 100",
                "jsCalculate": "this.formModel['depth'] * 100"
            },
            "group": "Meta Data",
            "order": 1
        }
    ],
    "filterDataModels": [],
    "requiredFilters": [],
    "subForms": [],
    "supForms": [],
    "createdAt": 1556631418,
    "modifiedAt": 1556631472,
    "generatedAt": 1556631490
}

With this example, we will demonstrate a typical form specialization case.

The first step is to remove the .generated extension from the generated form file. In directory src/forms, rename the file SampleForm.vue.generated to SampleForm.vue.

In the TemplatesManager/FormsManager GUI, a warning appears:

renamed-warning

Be careful during later runs of the form generator for that model; you might overwrite your own changes. Put the files you customized under version control (git). For this tutorial, this is not really necessary.

Toggle editable/uneditable

In SampleForm.vue, find the text field you want to edit, and simply change the value of the disabled property from false to true:

<DisTextInput
    :class="{'c-dis-form__input': true, 'c-dis-form__input--modified': formScenario === 'edit' && selectedItem['title'] !== formModel['title']}"
-    :disabled="false"
+    :disabled="true"
    :validators="validators['title']"
    name="title"
    label="Title"
    :serverValidationErrors="serverValidationErrors"
    hint="The title of this record"
    v-model="formModel['title']"
    :readonly="formScenario === 'view'"/>

This edit can also be done via the Template manager. But for demonstration purposes, we change the file src/forms/SampleForm.vue.

Hide/unhide

This edit is similar to the previous edit above. But this time, add the attribute v-showopen in new window to control the field visibility:

<DisTextInput
    :class="{'c-dis-form__input': true, 'c-dis-form__input--modified': formScenario === 'edit' && selectedItem['title'] !== formModel['title']}"
    :disabled="false"
+    v-show="false"
    :validators="validators['title']"
    name="title"
    label="Title"
    :serverValidationErrors="serverValidationErrors"
    hint="The title of this record"
    v-model="formModel['title']"
    :readonly="formScenario === 'view'"/>

If the line with v-show is not there, you must add it.

Now the form should look like this (details can differ):

invisible

Change field widths

The fields are arranged using a Vuetify layout scheme called "Grid System"open in new window. You are free to modify the grid, and thus to rearrange the input fields, as you wish. As an example, we will change the layout from a 2x2 grid to a 4x1 grid, thus rendering every field on its own full row. We accomplish this by removing the four "md3 sm6" snippets from the SampleForm.vue file.

<v-layout wrap mb-3>
    <v-flex xs12 pl-2 pt-2>
        -group1
    </v-flex>
-    <v-flex lg2 md3 sm6 xs12 pr-2 pl-2>
+    <v-flex lg2         xs12 pr-2 pl-2>
        <DisTextInput name="title"/>
    </v-flex>
-    <v-flex lg2 md3 sm6 xs12 pr-2 pl-2>
+    <v-flex lg2         xs12 pr-2 pl-2>
        <DisTextInput name="slug"/>
    </v-flex>
</v-layout>
<v-layout wrap mb-3>
    <v-flex xs12 pl-2 pt-2>
        Meta Data
    </v-flex>
-    <v-flex lg2 md3 sm6 xs12 pr-2 pl-2>
+    <v-flex lg2         xs12 pr-2 pl-2>
        <DisTextInput name="depth"/>
    </v-flex>
-    <v-flex lg2 md3 sm6 xs12 pr-2 pl-2>
+    <v-flex lg2         xs12 pr-2 pl-2>
        <DisTextInput name="depth_mm"/>
    </v-flex>
</v-layout>

These changes effectively remove the table rendering rules for medium (md) screens and small (sm) screens, treating all screens as extra small. This forces a full-width rendering on most screens, regardless of size. (The terms md3, sm6, etc. are CSS classes from the Vuetify component library which itself is inspired by Bootstrap.)

Result:

Note the larger widths of the input fields after changing the 2x2 grid to a 4x1 grid.

example for changing form layout

Change field order

Changing the order of the fields is also possible, but it's not shown here. Simply switch the positions of any DisTextInput elements inside the Sample.vue file.

This feature is available via the Template manager, too. Drag-and-drop the field positions.

Set label/hint

This change can be demonstrated with another couple of very simple one-liners:

<DisTextInput
    :class="{'c-dis-form__input': true, 'c-dis-form__input--modified': formScenario === 'edit' && selectedItem['depth'] !== formModel['depth']}"
    :disabled="false"
    :validators="validators['depth']"
    name="depth"
-    label="Depth"
+    label="Driller Depth"
    :serverValidationErrors="serverValidationErrors"
-    hint="Depth in meters"
+    hint="Driller Depth in meters"
    v-model.number="formModel['depth']"
    :readonly="formScenario === 'view'"/>

(Result not shown)

Available via the Template manager, too.

Calculated fields

Let's try an advanced example: Add a new field error_estimate that is calculated on the fly.

In principle, available via Templates/Forms Manager GUI, too. Here we accomplish this in code, with more fine-grained control.

In the <script> element of the Sample.vue file, after the this.simpleFields = ... line, insert the following JavaScript code.

This JS snippet adds a text field definition error_estimate that does not exist in the model. Nevertheless, it will be displayed on the form:

this.simpleFields = JSON.parse('[{"name":"title","label":"Title","description":"The title of this record","group":"-group1","order":0,"inputType":"text"},{"name":"slug","label":"Slug","description":"A unique kebab-case title","group":"-group1","order":1,"inputType":"text"},{"name":"depth","label":"Depth","description":"Depth in meters","group":"Meta Data","order":0,"inputType":"text"},{"name":"depth_mm","label":"Depth Mm","description":"Depth in millimeters","group":"Meta Data","order":1,"inputType":"text"}]')
+    this.simpleFields.push({
+      name: 'error_estimate',
+      label: 'Error Estimate',
+      description: 'Displays an error estimate of the Driller Depth',
+      group: 'Meta Data',
+      order: 2,
+      inputType: 'text'
+    })
    this.requiredFilters = JSON.parse('[]')
    this.subForms = JSON.parse('[]')

Previously, in Section "Hide/unhide", you have changed a <DisTextInput> element by setting v-show= "false". In the <template> element of the Sample.vue file, find that <DisTextInput> element. Immediately before the opening <DisTextInput>, add the following HTML/Vue code (without +s). The green snippet defines an input field for the new field error_estimate:

    <v-flex xs12 pr-2 pl-2>
        <DisTextInput
            :class="{'c-dis-form__input': true, 'c-dis-form__input--modified': formScenario === 'edit' && selectedItem['depth_mm'] !== formModel['depth_mm']}"
            :disabled="true"
            :validators="validators['depth_mm']"
            name="depth_mm"
            label="Depth Mm"
            :serverValidationErrors="serverValidationErrors"
            hint="Depth in millimeters"
            v-model.number="formModel['depth_mm']"
            :readonly="formScenario === 'view'"/>
    </v-flex>
+    <v-flex xs12 pr-2 pl-2>
+        <DisTextInput
+            :class="{'c-dis-form__input': true, 'c-dis-form__input--modified': formScenario === 'edit' && selectedItem['error_estimate'] !== formModel['error_estimate']}"
+            :disabled="true"
+            :validators="validators['error_estimate']"
+            name="error_estimate"
+            label="Error Estimate"
+            :serverValidationErrors="serverValidationErrors"
+            hint="Displays an error estimate of the Driller Depth"
+            v-model.number="formModel['error_estimate']"
+            :readonly="formScenario === 'view'"/>
+    </v-flex>
</v-layout>

Developers: It is important to make the field reactive. We do this by binding formModel['error_estimate'] object to v-model, see the third green line from below.

At this time, we have a new text field error_estimate, but the field is empty. This is because the form does not do any estimations yet.

In the <script> element of the Sample.vue file, add this JavaScript code. This code defines a custom anonymous function (){ } with a primitive "error estimation algorithm".

Add the this.calculatedFields['error_estimate'] object property:

this.calculatedFields = {}
    this.calculatedFields['depth_mm'] = function () {
      return this.formModel['depth'] * 100
    }
+    this.calculatedFields['error_estimate'] = function () {
+      if (this.formModel['depth'] > 1000) {
+        return 'About 1 m'
+      }
+      if (this.formModel['depth'] > 500) {
+        return 'About 0.5 m'
+      }
+      if (this.formModel['depth'] > 250) {
+        return 'About 0.10 m'
+      }
+      if (this.formModel['depth'] > 100) {
+        return 'About 0.01 m'
+      }
+      return 'Not Enough Data!'
+    }

    this.simpleFields = JSON.parse('[{"name":"title","label":"Title","description":"The title of this record","group":"-group1","order":0,"inputType":"text"},{"name":"slug","label":"Slug","description":"A unique kebab-case title","group":"-group1","order":1,"inputType":"text"},{"name":"depth","label":"Depth","description":"Depth in meters","group":"Meta Data","order":0,"inputType":"text"},{"name":"depth_mm","label":"Depth Mm","description":"Depth in millimeters","group":"Meta Data","order":1,"inputType":"text"}]')

Result (Note the new "Error Estimate" text field):

advanced calculation example

Exercises

  1. Test the form reactivity: Update any value of any record. Are the calculated fields updated immediately? Are they displayed correctly?
  2. Add a similar calculation rule for the "Depth mm" field. It should take the value of Depth (m) and multiply it with 100.

Vue 2 Slots

Vue slots allow you to inject custom content, such as instructions or extra components, into forms and other Vue components. In mDIS, slots are used for form specialization and customization. For a detailed guide and examples, see the Vue 2 Slots in mDIS documentation.