json-schema-form / json-schema-form-core

Core library
MIT License
65 stars 13 forks source link

Design suggestion #3

Open mmc41 opened 7 years ago

mmc41 commented 7 years ago

@Anthropic Hi Anthropic I just completed a schema based form editor for angular 2 for a future project and I would like to discuss my solution with you - if you are interested? - since I think some of my ideas might be useful for this project and I am interested in co-operation.

My proposed solution consist of: 1) A generic gui model in typescript that can be easily consumed and used to draw a form. The gui model only contains representational data + mappings back to the schema. No validation stuff. 2) A typescript translator that reads a JSON schema file and translates it into a gui model. 3) A web form in angular 2 that process the gui model, shows the corresponding form and calls into AJV (https://github.com/epoberezkin/ajv) at roughly each keystroke to validate and show validation errors.

This solution is very simple yet works fine for my yet-to-be announced OS project. See example of an Angular2 form below using this approach. The code could be extended to work with any web framework with modifications to step 3. The main idea, which I think could affect this core project is to introduce a gui model - or a IR (intermediate representation) in other words - as a translation target for the core AND to simplify things by not putting validations stuff into it but still requiring a fast schema validator to be used in the gui rather than standard validation api's for form elements.

Example of dynamic from produced from a json schema:

screen shot 2016-10-25 at 08 32 09

An example of how an instance of the corresponding gui model could look like is shown below:

const complex_gui_model1: SettingsGuiModel = {
  kind: 'group',
  name: '',
  controlType: 'group',
  label: '',
  tooltip: '',
  settingsObjectPath: '',
  required: true,
  elements: [
    { kind: 'group', name: 'authentication', controlType: 'group', label: 'Authentication', tooltip: 'an authentication description here', settingsObjectPath: 'authentication', isRoot: false, required: true,
      elements: [ { kind: 'field', name: 'user', controlType: 'input', label: 'User', tooltip: 'a username', settingsObjectPath: 'authentication.user', defaultValue: '', values: undefined, required: true, type: 'string', subType: 'none' },
                  { kind: 'field', name: 'password', controlType: 'input', label: 'Password', tooltip: 'a password', settingsObjectPath: 'authentication.password', defaultValue: '', values: undefined, required: true, type: 'string', subType: 'none' },
                  { kind: 'field', name: 'scheme', controlType: 'input', label: 'scheme',  tooltip: '', settingsObjectPath: 'authentication.scheme', defaultValue: 'basic', values: undefined, required: true, type: 'string', subType: 'none' },
                  { kind: 'field', name: 'preemptive', controlType: 'yesno', label: 'preemptive',  tooltip: '', settingsObjectPath: 'authentication.preemptive', defaultValue: true, values: undefined, required: true, type: 'boolean', subType: 'none'}
                ]
    },
    { kind: 'group', name: 'server', controlType: 'group', label: 'Server', tooltip: '', settingsObjectPath: 'server', isRoot: false, required: true,
      elements: [ { kind: 'field', name: 'host', controlType: 'input', label: 'host', tooltip: '', settingsObjectPath: 'server.host', defaultValue: '', values: undefined, required: true, type: 'string', subType: 'none' },
                  { kind: 'field', name: 'port', controlType: 'input', label: 'port', tooltip: '', settingsObjectPath: 'server.port', defaultValue: 80, values: undefined, required: true, type: 'integer', subType: 'none' },
                  { kind: 'field', name: 'protocol', controlType: 'dropdown', label: 'protocol', tooltip: '', settingsObjectPath: 'server.protocol', defaultValue: 'http', values: ['http', 'ftp'], required: true, type: 'string', subType: 'none' }
                ]
    }
  ],
  errors: [],
  isRoot: true
};

And the corresponding source schema looks like is shown here:

{
  '$schema': 'http://json-schema.org/draft-04/schema#',
  'type': 'object',
  'properties': {
    'authentication': {
      'type': 'object',
      'title': 'Authentication',
      'description': 'an authentication description here',
      'properties': {
        'user': {
          'type': 'string',
          'minLength': 1,
          'default': '',
          'title' : 'User',
          'description': 'a username',
        },
        'password': {
          'type': 'string',
          'minLength': 1,
          'default': '',
          'title' : 'Password',
          'description': 'a password',
        },
        'scheme': {
          'type': 'string',
          'default': 'basic'
        },
        'preemptive': {
          'type': 'boolean',
          'default': true
        }
      },
      'required': [
        'user',
        'password',
        'scheme',
        'preemptive'
      ]
    },
    'server': {
      'type': 'object',
      'title': 'Server',
      'properties': {
        'host': {
          'type': 'string',
          'default': ''
        },
        'port': {
          'type': 'integer',
          'multipleOf': 1,
          'maximum': 65535,
          'minimum': 0,
          'exclusiveMaximum': false,
          'exclusiveMinimum': false,
          'default': 80
        },
        'protocol': {
          'type': 'string',
          'default': 'http',
          'enum' : ['http', 'ftp']
        }
      },
      'required': [
        'host',
        'port',
        'protocol'
      ]
    }
  },
  'required': [
    'authentication',
    'server'
  ],
  'additionalProperties': false
}

Let me know if you think this sounds interesting?. If so, we could co-operate on a "standardised" gui model and a translator from json schema to that model. As noted, I have an initial version working.

/Morten

severinkehding commented 7 years ago

Hey Morten,

i am currently looking for a solution to do exactly what you already did. I stumbled upon some repos all with more or less exciting results i am almost ready to implement it myself. If you dont mind i would like to take a look at your working version we migth find a mutual agreement to further advance this dynamic rendering.

Cheers!

mmc41 commented 7 years ago

@severinkehding Thanks for your interest. The code is currently a part of a closed-source project but I can be persuaded to work on extracting it and releasing it as OS on github and npm if someone (you?) will commit to help with docs and maintenance. Otherwise I do not have time. The code is high-quality with tests but currently no docs apart from some comments.

Anthropic commented 7 years ago

@mmc41 send me a PM in Gitter and we can discuss, although I am in Australia so being 1am I am off to sleep now so I will follow up tomorrow :)

Frankly I have been looking at two half started implementations and haven't decided yet which way to go, so I am open to understanding more solutions further to try to find the best end solution for users.

mmc41 commented 7 years ago

@Anthropic Will send you a msg tomorrow. What is your Gitter channel / username ?

Anthropic commented 7 years ago

@Anthropic, so https://gitter.im/Anthropic :)

mmc41 commented 7 years ago

@severinkehding @Anthropic I took a little time to repackage the code for the proposal as a independent project which has been released on npm as "json-schema-js-gui-model". See the source on github.

I am still interested in combining efforts, in particular on the json schema ui extensions.

Anthropic commented 7 years ago

@mmc41 had a quick look on the weekend, I like what I see, I need to find more time to wrap my head around TypeScript. Can you export a TS lib and then still using TS es6 syntax import it into another lib? Ran into issues with the core and importing it into ASF in es6 format and everything breaking due to babel changes in v6, just wondering if you know how well TS would handle making a bundle you can bundle in another bundle :)

mmc41 commented 7 years ago

Note sure about your particular use case, but yes you can export typescript from one module and import it in other modules. Generally, you just add an index.d.ts file in addition to the index.js file and an types entry to the package.json file. You can see how it is done in the json-schema-js-gui-model project. BTW: I am working on a v2.0 of my libraries that transpiles into es5 to work better in browsers. The prerelease code is committed to GitHub but not released as npm yet.

P.S. The approach I suggest of compiling json schema to a gui model which in turn is used to construct a UI form, is similar to a Intermediate Language (IR) of a classic compiler and is a well-known way to decouple things. It makes it much easier to innovate and contribute.

nicklasb commented 7 years ago

I import typescript left and right, and runtime-compiles in my solution, IMO, basically all ways work.

@mmc41 One could call the XHTML representation of the DOM model the intermediate language. That is why it is so easy to extend ASF?

mmc41 commented 7 years ago

@nicklasb Yes, in fact there are many intermediate representations/languages at different abstraction levels. For instance, microcode, assembler, and java byte code are all representations of essentially "the same thing" at different abstraction levels. The point is that a good IR is well suited for a particular use at a particular abstraction level. The DOM is for a different use and for a different abstraction level, than the proposed IR for UI forms.

Anthropic commented 7 years ago

@mmc41 if you run the ASF demo and in chrome dev tools search the source for var merged and watcch the line after it, you can see the merge that we currently use. Still uses schema data, but combined with the form data and we modify the key to an object path for traversal of the model.