# vuetify-jsonschema-form-builder

This library is a json schema form builder intended to be used alongside `vuetify-jsonschema-form` which is a form renderer. This library consists of two components, a `form-builder` which represents the drag and drop area displaying the current layout of the form, and `form-builder-tabs` which represent the available controls. Check out the [Demo](https://anovokmet.github.io/vuetify-jsonschema-form-builder/).

## Installation
```
npm install vuetify-jsonschema-form-builder
```

### Usage

```js
<template>
    <v-app>
        <v-row>
            <v-col cols="8">
                <form-builder :schema.sync="schema" :context="context"></form-builder>
            </v-col>
            <v-col cols="4">
                <form-builder-tabs :schema.sync="schema" :context="context"></form-builder-tabs>
            </v-col>
        </v-row>
    </v-app>
</template>

<script lang="ts">
import Vue from 'vue';
import { FormBuilder, FormBuilderTabs } from 'vuetify-jsonschema-form-builder';
import 'vuetify/dist/vuetify.min.css';

export default Vue.extend({
  name: "App",
  components: {
    FormBuilder, FormBuilderTabs
  },
  data() {
    return {
      schema: {
        type: 'object',
        properties: {
          stringProp: {
            type: 'string'
          },
          colorProp: {
            type: 'string',
            'x-display': 'color-picker'
          },
        }
      },
      context: {
        predefinedSelects: [
          {
            id: 'pokemon',
            label: 'Pokemon',
            url: 'https://pokeapi.co/api/v2/pokemon',
            itemsProp: 'results',
            itemTitle: 'name',
            itemKey: 'name',
          },
          {
            id: 'users',
            label: 'Users',
            url: 'https://gorest.co.in/public/v2/users',
            itemTitle: 'name',
            itemKey: 'id'
          }
        ]
      }
    };
  },
});
</script>
```

### Props

- `schema` - a jsonschema in the format of `vuetify-jsonschema-form` schemas
- `context` - an object containing additional data for generating the form
  - `predefinedSelects` - list of predefined selects that can be chosen for the dropdown component
    - `id` - unique ID of the predefined dropdown
    - `label` - display label
    - `url` - URL to fetch the values from
    - `itemsProp` - property in the response containing the dropdown options
    - `itemTitle` - property of the option to use as the display label
    - `itemKey` - property of the option to use as the display value

### How it works

`vuetify-jsonschema-form-builder` internally uses a different data structure than JSON Schema and `vuetify-jsonschema-form`. It works by converting a JSON Schema into this structure (which is easier to work with when editing form) using `SchemaParser`. Then, when the form is edited, using `SchemaBuilder`, converts it back into JSON schema that can be used with `vuetify-jsonschema-form`. This is important to know when building custom components.

Eg. a section containing two text fields:

```js
// JSON schema
{
  "type": "object",
  "allOf": [
    {
      "properties": {
        "textfield1": {
          "title": "Short text",
          "readOnly": false,
          "type": "string"
        },
        "textfield2": {
          "title": "Short text",
          "readOnly": false,
          "type": "string"
        }
      }
    }
  ]
}

// form structure
[
  {
    "type": "panel",
    "xCols": 12,
    "components": [
      {
        "type": "textfield",
        "key": "textfield1",
        "label": "Short text",
        "readOnly": false,
      },
      {
        "type": "textfield",
        "key": "textfield2",
        "label": "Short text",
        "readOnly": false,
      }
    ]
  }
]
```

To know more about the form structure check the test suites for `SchemaParser`.

### Building custom components

To start, a component definition class is needed:

*./customfield/customfield.ts*
```js
export class CustomFieldComponent extends Component {

    // default settings for the component
    // used by the form builder to create and edit the component
    // can contain any number of parameters
    static settings() {
        return {
            /** type of the component, must be unique for each component type */
            type: 'customfield',
            /** key, used by the input components as the JSON property name */
            key: 'customfield',
            label: 'Custom field',
            xCols: 12,
            required: false,
            readOnly: false,
            padding: {},
        };
    }

    // info for the builder
    static get builderInfo() {
        return {
            /** title shown in the component sidebar */
            title: 'Custom field',
            /** icon shown for the component in the builder */
            icon: 'text_fields',
            /** Vue component that displays the available options for the component (sidebar) */
            optionsTemplate: DefaultOptions,
            /** Vue component rendered in the builder body as the component */
            template: DefaultField,
            /** default settings applied to the form when dragging the component */
            settings: CustomFieldComponent.settings()
        };
    }

    constructor(component: any, options: any) {
        super(component, options);
    }

    // tells the builder how to build the JSON Schema for this component
    buildSchema(parentSchema: JsonSchema) {
        parentSchema.properties[this.component.key] = {
            ...this.buildDefaultProps(),
            type: 'string',
            'x-display': 'custom-field',
        };
        parentSchema['x-cols'] = +this.component.xCols;
        this.buildRequiredProp(parentSchema);
        this.buildPadding(parentSchema.properties[this.component.key]);
    }

}
```

This handles the building of a form with the new component.

What remains is telling `vuetify-jsonschema-form-builder` how to parse JSON schemas into the format used internally by the builder for this new component, so that existing JSON schema form definitions would display this component when opened with the form builder.

This is done by defining an extension for the `SchemaParser`:

*./components/customfield.parser.ts*
```js
 export function parseCustomField(Parser: typeof SchemaParser): typeof SchemaParser {
    return class extends Parser {
        visitStringProperty(property: JsonSchema, config: ISettings[], parent: JsonSchema, key: string): void {
            // if JSON Schema 'x-display' is 'custom-field' then it is a customfield component
            if (property["x-display"] == 'custom-field') {
                this.visitCustomField(property, config, parent, key);
            } else {
                // otherwise it is some other component
                super.visitStringProperty(property, config, parent, key);
            }
        }

        visitCustomField(property: JsonSchema, config: ISettings[], parent: JsonSchema, key: string) {
            const component = this.buildInputComponent(property, parent, key);
            component.type = 'customfield';
            config.push(component);
        }
    }
}
```

Be sure to check the source to see what each of methods used do.

All that is left to do is to register the component and the extension:

*eg. ./App.vue*
```js
import { ComponentCache, SchemaParser } from 'vuetify-jsonschema-form-builder';
import { CustomFieldComponent } from './customfield/customfield';
import { parseCustomField } from './customfield/customfield.parser';

ComponentCache.registerComponent('customfield', CustomFieldComponent);
SchemaParser.registerExtension(parseCustomField);
```

Now your custom built component for `vjsf` can be used with the form builder. Be sure to pass your custom component template to `v-jsf` when rendering the built schema:

```html
...
<v-jsf v-model="model" :schema="schema" :options="options">
  <template slot="custom-field" slot-scope="context">
    <v-custom-field v-bind="context"><v-custom-field>
  </template>
</v-jsf>
...
```
