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

Selects as group buttons #313

Closed rguliev closed 2 years ago

rguliev commented 2 years ago

Hi,

First of all, thank you for this cool package. I really like it!

My situation is that I would like to have a possibility to render an input with choices, like radio buttons or selects as group buttons. So, for example, I would like to be able to add something like:

        "btnGroupProp": {
            "type": 'string',
            "title": 'Button group select',
            "description": 'This is button group select',
            'oneOf': [
              {"const": "-1", "title": "Not know"},
              {"const": "0", "title": "No"},
              {"const": "1", "title": "Yes"}
            ],
            'x-display': 'button-group',
        }

I am trying to create a custom component using slots but it does not work for some reason. I would appreciate your help:)

Here are my components:

// This is the form component
<template>
  <v-container>
      <v-form ref="form" v-model="valid">
        <v-jsf v-model="model" :schema="schema">
          <template slot="button-group" slot-scope="context">
            <v-jsf-button-group v-bind="context"/>
          </template>
        </v-jsf>
      </v-form>
      <p>data={{model}}</p>
  </v-container>
</template>

<script>
import VJsf from '@koumoul/vjsf'
import '@koumoul/vjsf/lib/VJsf.css'
import VJsfButtonGroup from './v-jsf-button-group.vue';

export default {
  name: 'App',
  components: { VJsf, VJsfButtonGroup },
  data: () => ({
    valid: false,
    model: {},
    schema: {
      "type": "object",
      "properties": {
        "btnGroupProp": {
            "type": 'string',
            "title": 'Button group select',
            "description": 'This is button group select',
            'x-display': 'button-group',
            'oneOf': [
              {"const": "-1", "title": "Not know"},
              {"const": "0", "title": "No"},
              {"const": "1", "title": "Yes"}
            ]
        }
      }
    }
  }),
};
</script>
// v-jsf-button-group.vue
<template>
  <v-input :value="value" :name="fullKey" :label="label" :disabled="disabled" :rules="rules" :required="required">
    <v-btn-toggle tile color="deep-purple accent-3" group :value="value">
        <v-btn v-for="choice in fullSchema.oneOf" :key="choice.const" :disabled="disabled" :value="choice.const">
          {{ choice.title }}
        </v-btn>
    </v-btn-toggle>
  </v-input>
</template>

<script>
export default {
  props: {
    value: { type: String, default: '' },
    options: { type: Object, required: true },
    schema: { type: Object, required: true },
    fullSchema: { type: Object, required: true },
    fullKey: { type: String, required: true },
    label: { type: String, default: '' },
    htmlDescription: { type: String, default: '' },
    disabled: { type: Boolean, default: false },
    required: { type: Boolean, default: false },
    rules: { type: Array, required: true },
    on: { type: Object, required: true }
  },
  data: () => ({}),
  beforeMount () { console.log("HERE")  },
  beforeCreate () { console.log("HERE")  },
}
</script>
albanm commented 2 years ago

Hi. The documentation is not clear about this, but for a custom component to be used on a property you have to have a prefix "custom-" on the slot name and x-display value.

rguliev commented 2 years ago

Hi, It worked. Thanks a lot. Another issue now is that model data is not connected to value of my custom input. Could you please give a hint on this as well?

albanm commented 2 years ago

You need to call on.input with the modified value, see https://github.com/koumoul-dev/vuetify-jsonschema-form/blob/master/doc/components/wrappers/v-jsf-tiptap.vue#L62 for example

rguliev commented 2 years ago

Worked! Thank you!

Here is my code in case someone will have the same issue. The code is not perfect and does not cover all options that the package provides (i.e. enum, array of anyOf, etc.). But it gives the idea of how to create such a component.

// MyForm.vue
<template>
  <v-container>
      <v-form ref="form" v-model="valid">
        <v-jsf v-model="model" :schema="schema">
          <template slot="custom-button-group" slot-scope="context">
            <v-jsf-button-group v-bind="context"/>
          </template>
        </v-jsf>
      </v-form>
      <p>data={{model}}</p>
  </v-container>
</template>

<script>
import VJsf from '@koumoul/vjsf'
import '@koumoul/vjsf/lib/VJsf.css'
import VJsfButtonGroup from './v-jsf-button-group.vue';

export default {
  name: 'App',
  components: { VJsf, VJsfButtonGroup },
  data: () => ({
    valid: false,
    model: {
      "btnGroupProp": "1"
    },
    schema: {
      "type": "object",
      "properties": {
        "btnGroupProp": {
            "type": 'string',
            "title": 'Button group select',
            "description": 'This is button group select',
            'x-display': 'custom-button-group',
            'oneOf': [
              {"const": "-1", "title": "Not know"},
              {"const": "0", "title": "No"},
              {"const": "1", "title": "Yes"}
            ]
        }
      }
    }
  }),
};
</script>
// v-jsf-button-group.vue
<template>
  <v-input
    :value="value"
    :name="fullKey"
    :label="label"
    :disabled="disabled"
    :rules="rules"
    :required="required"
    class="vjsf-button-group"
  >
    <v-btn-toggle color="primary" tile :value="value" @change="updateModelValue">
        <v-btn v-for="opt in fullSchema.oneOf" :key="opt.const" :disabled="disabled" :value="opt.const">
          {{ opt.title }}
        </v-btn>
    </v-btn-toggle>
  </v-input>
</template>

<script>
export default {
  props: {
    value: { type: String, default: '' },
    options: { type: Object, required: true },
    schema: { type: Object, required: true },
    fullSchema: { type: Object, required: true },
    fullKey: { type: String, required: true },
    label: { type: String, default: '' },
    htmlDescription: { type: String, default: '' },
    disabled: { type: Boolean, default: false },
    required: { type: Boolean, default: false },
    rules: { type: Array, required: true },
    on: { type: Object, required: true }
  },
  methods: {
    updateModelValue(value) {
      this.on.input(value)
    }
  },
}
</script>

<style lang="css">
.vjsf-button-group .v-input__slot,
.vjsf-button-group .v-btn-toggle {
  display: block;
}
</style>