koumoul-dev / vuetify-jsonschema-form

Create beautiful and low-effort forms that output valid data. Published on npm as @koumoul/vjsf.
https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/
MIT License
538 stars 154 forks source link

Plugin creation #433

Open boutils opened 2 months ago

boutils commented 2 months ago

Hello, I am trying to create a plugin with a specific control I would like to use. Can you tell me if plugins are already supported? I followed the way markdown is done, but as soon as I tried to use my plugin, I hit some errors due to schema validation.

Screenshot 2024-04-18 at 04 12 11

The plugin is setup this way:

<template>
  <div
    class = "stoic-form"
    :id   = "identifier">
    <v-container>
      <v-row>
        <v-col>
          <v-form>
            <vjsf
              v-model  = "data"
              :schema  = "schema"
              :options = "options">
            </vjsf>
          </v-form>
        </v-col>
      </v-row>
    </v-container>
  </div>
</template>

<script>
  import Vjsf from '@koumoul/vjsf';
  import myPlugin from '../components/my-plugin/my-plugin.vue';

  export default {
    name: 'stoicForm',

    components: { Vjsf },

    data() {
      return {
        data: {},
        options: {
          nodeComponents: { myplugin: myPlugin },
        },
        schema: {
          type: 'object',
          properties: {
            str1: {
              type: 'string',
              title: 'My plugin control',
              layout: 'myplugin',
            },
          },
        },
      };
    },
  };
</script>

I can see in the json-layout repository that markdown is hardcoded in several places. Is it already ready to work with user-made plugins?

If I omit the validation in json-layout/normalize.js, I can see my plugin/component. I set as comment the 2 validation blocks in function normalizeLayoutFragment:

/* json-layout/vocabulary/src/normalize.js */

...
export function normalizeLayoutFragment (schemaFragment, schemaPath, markdown = (src) => src, optionsKeys, arrayChild) {
  optionsKeys = optionsKeys ? optionsKeys.concat(defaultOptionsKeys) : defaultOptionsKeys
  let layoutKeyword
  if (arrayChild === 'oneOf') {
    layoutKeyword = schemaFragment.oneOfLayout ?? {}
  } else {
    layoutKeyword = schemaFragment.layout ?? {}
  }
  // if (!validateLayoutKeyword(layoutKeyword)) {
  //   console.error(`layout keyword validation errors at path ${schemaPath}`, layoutKeyword, validateLayoutKeyword.errors)
  //   return {
  //     layout: getNormalizedLayout({}, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild).normalized,
  //     errors: lighterValidationErrors(validateLayoutKeyword.errors)
  //   }
  // }
  const normalizedLayout = getNormalizedLayout(layoutKeyword, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild)
  // if (!validateNormalizedLayout(normalizedLayout.normalized)) {
  //   console.error(`normalized layout validation errors at path ${schemaPath}`, normalizedLayout, validateNormalizedLayout.errors)
  //   return {
  //     layout: getNormalizedLayout({}, schemaFragment, schemaPath, markdown, optionsKeys, arrayChild).normalized,
  //     errors: lighterValidationErrors(validateNormalizedLayout.errors)
  //   }
  //   // throw new Error(`invalid layout at path ${schemaPath}`, { cause: validateNormalizedLayout.errors })
  // }
  return { layout: normalizedLayout.normalized, errors: [] }
}

@albanm Do you have any input? Can you please provide some guidance? Thank you very much !

albanm commented 2 months ago

Yes, this is a good question for which my answer is not very clear I am afraid.

The way we build and validate the vocabulary is not very extensible. We could allow for unknown values of "comp", but then what would the associated properties be ? Does this custom node component accept a label, is it focusable, etc ? So I would have to accept basically anything in the layout keyword to allow for custom behavior and validation would become very lax (along with the type checking that comes with the validation and helps writing a safe code base).

So yeah, maybe things will move on this topic, I need to think about it more. But for now the situation is:

boutils commented 2 months ago

Thank you for your answer @albanm. It makes sense. I think plugins should have the same model as standard Vuetify form components, they have label, hint, ... are focusable and accept some other various props props: {...}. The JSON definition of a custom "layout/comp" should have the same API.

Of course, depending of the plugin, we can give specific options like you did for markdown:

const vjsfOptions = {
  ...
  plugins: {
    markdown: {
      easyMDEOptions: { minHeight: '300px', maxHeight: '300px' }
    }
  }
}

My guess is we should have a register mechanism for our plugins that help go through json-layout/normalizeLayoutFragment validation.

Your suggestion to use a slot doesn't work well IMHO for "like Vuetify" components but I would be very happy to discuss it. The JSON will be very complex and not really reusable.

I hope to be able to present an example in the coming days of such a plugin which make a ton of sense for me.

albanm commented 1 month ago

I reworked the plugin mechanism both at the json-layout level (vocabulary is now extensible) and vjsf.

The markdown plugin was updated accordingly and the doc too.

boutils commented 1 month ago

Thanks a ton @albanm, you rock!