npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@zdwh/vue-json-schema-form

v1.0.19

Published

表单从来没有这么简单,通过一份 json-schema,你就拥有了一套交互完整,校验完善的表单。

Downloads

22

Readme

@zdwh/vue-json-schema-form

表单从来没有这么简单,通过一份 json-schema,你就拥有了一套交互完整,校验完善的表单。

Version License Coverage Build

demo 演示

USAGE

npm i @zdwh/vue-json-schema-form -S

然后

import JsonSchemaForm from '@zdwh/vue-json-schema-form'
import JsonSchemaFormThemeElement from '@zdwh/vue-json-schema-form/dist/theme-element/index.common.js'

vue.use(JsonSchemaForm)
vue.use(JsonSchemaFormThemeElement)

<JsonSchemaForm :schema="schema" v-model="value" :formProps="...props pass to form" :plugins="plugins" locale="" />

theme 是必须的,真正的表单组件都是由 theme 提供的,将 theme 拆分出来的目的很明显,未来对于不同的使用场景,可以无缝迁移,同时可以在移动端使用。

props

schema

json schema 对象

v-model

绑定结果值

formProps

传递给表单组件的props,这里面的值根据表单的最终实现来定,比如theme-elementformProps可以是任意 element-ui 中的 form 组件的props

plugins

插件

uiSchema

具体参考下面的vjsf

locale

默认zh中文,支持值参考ajv-i18n

ajvInstanceOptions

参考ajv options

vjsf

vjsf是我们用来在 json schema 基础上帮助我们更好得渲染表单的工具参数,我们可以通过在每个 schema 节点上带上来传递:

const schema = {
  type: 'string',
  vjsf: {
    component: 'your-custom-component',
    additionProps: {
      ...props, // 对于表单组件你想传递的其他参数
    },
    title: '名称',
    description: '描述',
    withFormItem: true, // 对于自定义组件,是否使用formItem,默认为true
  },
}

如果你不想把这些属性放在schema上面,那么你可以通过给JsonSchemaForm传递uiSchema参数来进行定制,只要保持uiSchema的结构和schema一致就行 比如:

const schema = {
  type: 'object',
  properties: {
    name: {
      type: string,
    },
    pets: {
      type: 'array',
      items: {
        type: 'string',
      },
    },
  },
}

const uiSchema = {
  title: '我和我的宠物',
  properties: {
    name: {
      title: '我的名字',
      component: 'input',
    },
    pets: {
      title: '我的宠物们',
      items: {
        title: '名字',
      },
    },
  },
}

custom event

如果你有自定义组件,并且你的自定义组件想要跟JsonSchemaForm的 owner 进行沟通,你的组件可以inject: ['fireEvent'],然后通过this.fireEvent(name, data)来向外触发一个事件,在 owner 处,我们可以进行监听:

<json-schema-form @youEventName="yourHandler" />

propertiesOrder

对象 key 的排序,尤其在有dependencies的时候非常有用,在声明 schema 的时候,在typeobject的 schema 中可以增加propertiesOrder属性, 他的值是一个数组,properties里面的变量会根据这里声明的顺序进行渲染,没有出现在这个数组里面的变量名则放在最后面

插件

插件能够帮助我们非常方便地扩展功能,目前支持情况如下:

  • [x] customFormat
  • [x] customKeywords

如何创建一个自定义 format 插件

接口定义如下:

interface AjvFormat {
  name: string
  definition: FormatValidator | FormatDefinition
}

interface CustomFormat extends AjvFormat {
  component: String // jsf-text-input || your-custom-component
}

interface JsonSchemFormPlugin {
  customFormats: CustomFormat[]
}

比如我们增加一个图片上传的插件,这个组件返回的结果最终的图片链接,我们需要校验的自然也是这个链接,所以我们的 format 定义如下:

const format = {
  name: 'image',
  definition: /reg-to-valid-image-url/,
}

对于这个 format 我们需要使用特定的组件来进行渲染,毕竟他要实现上传图片并返回图片路径的功能,这并不是标准的 json schema 功能,并且上传的操作是业务相关操作直接写死在 vjsf 库内肯定不合适。

这时候我们可以创建一个ImageUploader组件,并且注册到 vue 上,这时候我们的插件就成型了:

const plugin = {
  customFormats: [
    {
      name: 'image',
      definition: /reg-to-valid-image-url/,
      component: 'image-uploader',
    },
  ],
}

在我们定义如下 schema 之后:

{
  type: 'string',
  format: 'image'
}

我们的表单就是如下:

<image-uploader v-model="value" />

快速替换 format 使用的组件

默认的 format 组件映射关系如下:

export const stringFormatComponentMap: StringMap = {
  default: 'jsf-text-input',
  color: 'jsf-color-picker',
  date: 'jsf-date-picker',
  'date-time': 'jsf-date-time-picker',
  time: 'jsf-time-picker',
}

export const numberFormatComponentMap: StringMap = {
  default: 'jsf-number-input',
  date: 'jsf-date-picker',
  'date-time': 'jsf-date-time-picker',
  time: 'jsf-time-picker',
}

我们可以通过formatMaps来定义 format 默认使用的组件,比如:

const formats = {
  string: {
    date: 'jsf-my-date-picker'
  }
}

<JsonSchemaForm :formatMaps="formats" />

之后字符串类型的日期format就会使用jsf-my-date-picker作为表单组件

重要:该方式建议只对 json schema 默认支持的 format 使用,对于你自定义的 format,你仍然需要使用插件的方式,因为你需要制定该 format 的校验方式。

如何自定义关键字插件

接口如下:

interface CustomKeyword {
  name: string
  definition: KeywordDefinition
  transformSchema?: (originSchema: Schema) => Schema
}

关于自定义 Ajv 关键字,请看Ajv 文档,此处的definition就是这个作用,而name则是你的关键字的名字。

自定义关键字和customFormats最大的区别是,我们不需要指定组件(毕竟我们不可能到每个类型里面判断关键字该怎么渲染)。 在这里我们通过transformSchema来转换 schema,也就是我们真正渲染的 schema 是通过transformSchema转换的结果, 这个方法会收到原始的 schema,你需要返回一个新的 schema,注意:不要 originSchema 上做改动

表单渲染会根据你返回的新的 schema 来进行。

示例
const plugin: JsonSchemFormPlugin = {
  customKeywords: [
    {
      name: 'test',
      definition: {
        // validate(schema: any, data: any) {
        //   return typeof data === 'object' && data.x === 1
        // },
        macro(schema: any) {
          return {
            ...schema,
            type: 'object',
            properties: {
              x: {
                type: 'number',
                minimum: 5,
              },
            },
          }
        },
        errors: true,
      },
      transformSchema(schema: any) {
        return {
          ...schema,
          type: 'object',
          properties: {
            x: {
              type: 'number',
              vjsf: {
                title: '测试数字',
              },
            },
          },
        }
      },
    },
  ],
}

在使用关键字的时候,我们只需要:

const schema = {
  test: true,
}

实际等于的效果如下:

const schema = {
  type: 'object',
  properties: {
    x: {
      type: 'number',
      vjsf: {
        title: '测试数字',
      },
    },
  },
}

我们建议通过macro来声明该关键字的校验,因为这能够完全契合 vjsf 的错误显示。如果你通过validation来进行校验,最终校验结果只针对于当前路径,而并不会有针对 transform 之后的 schema 的校验,可能就需要你自行显示错误信息了。

校验

你可以通过给JsonSchemaForm组件指定一个ref,然后通过ref.doValidate()来进行校验,会返回{ errors, valid },其中errors是错误信息的对象。组件会把错误信息显示到每个表单项,你可以根据自己的需求以另外的方式提醒错误。

TODO:

  • [ ] 输入时对每个表单项独立进行校验

错误信息

我们通过ajv-errors来提供错误信息的定制,在你的每一项 schema 里面你可以:

const schema = {
  type: 'string',
  pattern: '/^abc$/',
  errorMessage: {
    pattern: '请填写正确的内容',
  },
}

如果你不写errorMessage,那么在用户输入的内容不匹配正则的时候,显示的错误信息是:应当匹配模式 "/^abc&/",这对于非技术人员显然不够友好,在增加了errorMessage之后,对于关键字pattern的错误信息就会显示你指定的错误信息。

Note:你可以对每个关键字设定错误信息

errorMessage: {
  pattern: '请填写正确的内容',
  maxLength: '最长不超过xxx',
  //...others
}

主题

主题就是一组规范命名的组件,这一组组件都注册到全局 vue 上之后,vjsf 渲染表单就会使用这些组件,组件列表:

{
  JsfColorPicker: '颜色选择器' // 非必须,会回滚到text-input
  JsfDatePicker: '日期选择'
  JsfDateTimePicker: '日期时间选择'
  JsfDateTimeRangePicker: '日期时间区间选择'
  JsfForm: '表单组件'
  JsfFormItem: '表单项组件'
  JsfNumberInput: '数字输入'
  JsfSelection: '下拉选择'
  JsfSwitch: 'boolean开关'
  JsfTextInput: '字符串输入'
  JsfTimePicker: '时间选择'
  JsfSingleTypeArrayWrapper: '单类型数组区块控制器'
  JsfAlert: '提示框'
}

你要实现一个主题则需要实现这些组件并逐一注册到vue

使用默认主题

vjsf 的默认主题现在打包在一起,需要通过:

import JsonSchemaFormThemeElement from '@zdwh/vue-json-schema-form/dist/theme-element/index.common.js'
import JsonSchemaFormThemeElement from '@zdwh/vue-json-schema-form/dist/theme-element/index.css'

来引入。

在自定义组件的时候,你需要给你的表单组件套上FormItem来进行布局和错误显示:

<FormItem v-bind="formItemProps">
  <YourContent>
</FormItem>

import { FormItem, CommonBase } from '@zdwh/vue-json-schema-form/dist/theme-element/index.common.js'

export default class YourComponent extends CommonBase

为了方便获取formItemProps,我们提供了CommonBase作为你的组件可继承的基类

当然如果你只需要一个 mixin,你可以import { FormItemPropsMixin } from '@zdwh/vue-json-schema-form/dist/theme-element/index.common.js'

帮助方法

import { ThemeBaseClass, ThemeBaseMixin } from '@zdwh/vue-json-schema-form'

如果你用class开发可以继承前者,或者你可以使用后面的 mixin

表单依赖

demo

export default {
  name: '依赖关系',
  schema: {
    type: 'object',
    properties: {
      selected: {
        type: 'number',
        title: '是否选中',
        enum: [1, 2, 3],
      },
    },
    dependencies: {
      selected: {
        oneOf: [
          {
            properties: {
              selected: {
                // const: 1,

                const: 1,
              },
              name1: {
                type: 'string',
                title: '名字1',
              },
            },
            required: ['name1'],
          },
          {
            properties: {
              selected: {
                const: 2,
              },
              name2: {
                type: 'string',
                title: '名字2',
              },
            },
            required: ['name2'],
          },
          {
            properties: {
              selected: {
                const: 3,
              },
              name3: {
                type: 'string',
                title: '名字3',
              },
            },
            required: ['name3'],
          },
        ],
      },
    },
  },
}

解释

通过dependencies声明依赖关系,dependencieskey是依赖选项,比如在这里selected是依赖项,在selected有值的情况下才会展示和执行他包含的内容。

selected的值是一个oneOf则对应我们对于selected不同的结果会现实其中某个结果,比如在这里如果:

  • selected1,则我们必须填写name1
  • selected2,则我们必须填写name2
  • selected3,则我们必须填写name3

注意这里我们在每一项中都声明了一个selected,他的类型是const也就是固定值,以此我们来强制区分不同的结果,符合selected1的结果必定不会符合其他的选项,就完全符合oneOf的逻辑。