ipyvuetify icon indicating copy to clipboard operation
ipyvuetify copied to clipboard

possible to use ipyvuetify with vuetify-jsonschema-form ?

Open jgunstone opened this issue 4 years ago • 10 comments

I recently came across this: https://github.com/koumoul-dev/vuetify-jsonschema-form which generates a UI from a JSON schema.

I was wondering whether it might be possible to use this with ipyvuetify... maybe using the JSON schema to create a vuetify template?

cheers

jgunstone avatar Sep 17 '21 13:09 jgunstone

That looks like a great library!

I tried to load it from CDN, and it works!

in try_vjsf.vue:

<template>
  <div>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@koumoul/vjsf@latest/dist/main.css">
    <div v-if="vjsf_loaded">
      <v-form v-model="valid">
        <v-jsf v-model="form_data" :schema="schema"></v-jsf>
      </v-form>
    </div>
  </div>    
</template>

<script>
module.exports = {
    async created() {
        const [VJsf] = await this.import(['https://cdn.jsdelivr.net/npm/@koumoul/vjsf@latest/dist/main.js']);
        this.$options.components['v-jsf'] = VJsf.default;
        this.vjsf_loaded = true;
    },
    methods: {
        import(deps) {
          return this.loadRequire()
              .then(() => new Promise((resolve, reject) => {
                requirejs(deps, (...modules) => resolve(modules));
              }));
        },
        loadRequire() {
          /* Needed in lab */
          if (window.requirejs) {
              console.log('require found');
              return Promise.resolve()
          }
          return new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = 'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js';
            script.onload = resolve;
            script.onerror = reject;
            document.head.appendChild(script);
          });
        }
    }
}
</script>

In a notebook:

# uncomment next lines to enable hot reloading of vue template(s). (needs the watchdog package)
# import ipyvue
# ipyvue.watch('.')

import ipyvuetify as v
import traitlets

class TryVjsf(v.VuetifyTemplate):
    template_file = "try_vjsf.vue"
    
    vjsf_loaded = traitlets.Bool(False).tag(sync=True)
    form_data = traitlets.Dict(default_value={}).tag(sync=True)
    schema = traitlets.Dict().tag(sync=True)
    valid = traitlets.Bool(False).tag(sync=True)
    

schema = {
  "type": "object",
  "properties": {
    "stringProp": { "type": "string" },
    "colorProp": { "type": "string", "x-display": "color-picker" },
  }
}
    
my_form = TryVjsf(schema=schema)
my_form
Screenshot 2021-10-04 at 13 25 44

mariobuikhuizen avatar Oct 04 '21 11:10 mariobuikhuizen

hi there,

sorry for the delay responding -

thanks so much for putting together the boiler-plate code above, it works great!

I was interested to see if it works with the schema's that are generated from pydantic - and it does!

from typing import List
from pydantic import BaseModel, Field


class PetCls:
    def __init__(self, *, name: str, species: str):
        self.name = name
        self.species = species


class PersonCls:
    def __init__(self, *, name: str, age: float = None, pets: List[PetCls]):
        self.name = name
        self.age = age
        self.pets = pets


class Pet(BaseModel):
    name: str
    species: str

    class Config:
        orm_mode = True


class Person(BaseModel):
    name: str
    age: float = Field
    pets: List[Pet]

    class Config:
        orm_mode = True


bones = PetCls(name='Bones', species='dog')
orion = PetCls(name='Orion', species='cat')
anna = PersonCls(name='Anna', age=20, pets=[bones, orion])
p_form = TryVjsf(schema=Person.schema())
p_form

image

I think that this creates a really nice workflow for rapidly creating UI's in python / jupyter:

  • The input fields are tightly defined with pydantic.
  • The out-the-box schema from pydantic automatically generates the UI.
  • The data from the form can be simply retrieved from the UI with p_form.form_data.

thanks again!

jgunstone avatar Nov 12 '21 11:11 jgunstone

Hi there, I've started to use the functionality described above and its working well.

vjsf implements a markdown editor which would be great to include.

https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/examples#markdown-editor

where they describe as follows:

You can edit markdown content using the x-display=markdown annotation.

To do this VJsf integrates EasyMDE. But to prevent creating a larger distributable it is not declared as a dependency, you need to load it yourself and make it available in global.EasyMDE (or window.EasyMDE). For example:

import EasyMDE from 'easymde/dist/easymde.min.js'
import 'easymde/dist/easymde.min.css'
global.EasyMDE = EasyMDE

I tried installing EasyMDE into the conda environment i'm using and then adding the snippet above to the try_vjsf.vue boilerplate code you put together - but that didn't work. Is there a way to simply integrate this functionality?

try_vjsf.vue

<template>
  <div>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@koumoul/vjsf@latest/dist/main.css">
    <div v-if="vjsf_loaded">
      <v-form v-model="valid">
        <v-jsf v-model="form_data" :schema="schema"></v-jsf>
      </v-form>
    </div>
  </div>    
</template>
<!-- // <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css">
// <script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script> -->
<script>
    
import EasyMDE from 'easymde/dist/easymde.min.js'
import 'easymde/dist/easymde.min.css'
global.EasyMDE = EasyMDE

module.exports = {
    async created() {
        const [VJsf] = await this.import(['https://cdn.jsdelivr.net/npm/@koumoul/vjsf@latest/dist/main.js']);
        this.$options.components['v-jsf'] = VJsf.default;
        this.vjsf_loaded = true;
    },
    methods: {
        import(deps) {
          return this.loadRequire()
              .then(() => new Promise((resolve, reject) => {
                requirejs(deps, (...modules) => resolve(modules));
              }));
        },
        loadRequire() {
          /* Needed in lab */
          if (window.requirejs) {
              console.log('require found');
              return Promise.resolve()
          }
          return new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = 'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js';
            script.onload = resolve;
            script.onerror = reject;
            document.head.appendChild(script);
          });
        }
    }
}
</script>

many thanks,

jgunstone avatar Feb 10 '22 10:02 jgunstone

I was interested to see if it works with the schema's that are generated from pydantic - and it does!

yeah, it's really cool :)

maartenbreddels avatar Feb 10 '22 11:02 maartenbreddels

I tried installing EasyMDE into the conda environmen

Did you try getting if from npm/jsdelivr like vjsf in thie example, using the import?

maartenbreddels avatar Feb 10 '22 11:02 maartenbreddels

hi - apologies - missed your response.

i think this is where my lack of understanding of javascript kicks in -

do you mean importing EasyMDE in a similar way to how it is done here:

   async created() {
       const [VJsf] = await this.import(['https://cdn.jsdelivr.net/npm/@koumoul/vjsf@latest/dist/main.js']);
       this.$options.components['v-jsf'] = VJsf.default;
       this.vjsf_loaded = true;
   },

?

cheers

jgunstone avatar Feb 15 '22 11:02 jgunstone

Stumbled on this issue while trying to get another library wrapped (AGGrid-Vue), really fantastic capability!

For @jgunstone 's question - I think you will need something in the pattern of what's shown for VJsf... I dug through the ipyvuetify and ipyvue code a bit, I think any code you put before the first { gets stripped, so all of your "import EasyMDE" isn't making it through to the final component: https://github.com/widgetti/ipyvue/blob/23e3a0fdfa6c20b8a9efbc8cef13e3a17f6b00fc/js/src/VueTemplateRenderer.js#L316-L324

That should successfully load your library (e.g. if you're watching page sources, you should see it get added), but it may/may not satisfy the dependencies... would love @maartenbreddels or @mariobuikhuizen 's input here as I'm pretty new to javascript, just feeling my way. Depending on the exact artifact you're importing from npm, they may or may not be usable it seems with the client-side dynamic import (is that true?). The "umd"/"amd" formats seem to work well, but I'm finding that plain javascript seems really to need to be wrapped. Will need to use a tool like https://browserify.org to make it importable.

Here's the code I have so far (adapted from example: https://www.ag-grid.com/vue-data-grid/vue2/)... aggrid-vue had been giving errors about no aggrid dependency, that's satisfied now, but giving error about Vue being undefined... I can import the Vue common js library, but they don't offer amd/umd, and I'd think that ipyvuetify/ipyvue must already have vue imported, not sure how to expose that to aggrid-vue...

try_aggrid.vue

<template>
  <div>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/ag-grid.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/ag-theme-alpine.css">
    <div v-if="aggrid_vue_loaded">
      <ag-grid-vue
        style="width: 500px; height: 200px"
        class="ag-theme-alpine"
        :columnDefs="columnDefs"
        :rowData="rowData"
      >
      </ag-grid-vue>
    </div>
  </div>
</template>

<script>
module.exports = {
    async created() {
        await this.import(['https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.runtime.js']);
        await this.import(['https://cdn.jsdelivr.net/npm/[email protected]/dist/ag-grid-community.amd.js']);
        const [AgGridVue] = await this.import(['https://cdn.jsdelivr.net/npm/[email protected]/dist/ag-grid-vue.umd.min.js']);
        this.$options.components['ag-grid-vue'] = AgGridVue;
        this.aggrid_vue_loaded = true;
    },
    methods: {
        import(deps) {
          return this.loadRequire()
              .then(() => new Promise((resolve, reject) => {
                requirejs(deps, (...modules) => resolve(modules));
              }));
        },
        loadRequire() {
          /* Needed in lab */
          if (window.requirejs) {
              console.log('require found');
              return Promise.resolve()
          }
          return new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = 'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js';
            script.onload = resolve;
            script.onerror = reject;
            document.head.appendChild(script);
          });
        }
    }
}
</script>
class TryAGGrid(v.VuetifyTemplate):
    template_file = "try_aggrid.vue"
    
    aggrid_loaded = traitlets.Bool(False).tag(sync=True)
    aggrid_vue_loaded = traitlets.Bool(False).tag(sync=True)

    columnDefs = traitlets.List(traitlets.Dict()).tag(sync=True)
    rowData = traitlets.List(traitlets.Dict()).tag(sync=True)

    def __init__(self, **kwargs):
        self.columnDefs = [
            {"headerName": "Make", "field": "make"},
            {"headerName": "Model", "field": "model"},
            {"headerName": "Price", "field": "price"},
        ]
        self.rowData = [
            {"make": "Toyota", "model": "Celica", "price": 35000},
            {"make": "Ford", "model": "Mondeo", "price": 32000},
            {"make": "Porsche", "model": "Boxster", "price": 72000},
        ]
        super().__init__(**kwargs)    
    
my_grid = TryAGGrid()
my_grid

javascript console error:

tslib.es6.js:25 Uncaught TypeError: Object prototype may only be an Object or null: undefined
    at setPrototypeOf (<anonymous>)
    at i (tslib.es6.js:25:5)
    at AgGridVue.ts:16:32
    at Module.fae3 (AgGridVue.ts:16:1)
    at n (bootstrap:19:22)
    at 8bbf (bootstrap:83:10)
    at ag-grid-vue.umd.min.js?v=20220916220758:1:1288
    at Object.execCb (require.js?v=d37b48bb2137faa0ab98157e240c084dd5b1b5e74911723aa1d1f04c928c2a03dedf922d049e4815f7e5a369faa2e6b6a1000aae958b7953b5cc60411154f593:1693:33)
    at Module.check (require.js?v=d37b48bb2137faa0ab98157e240c084dd5b1b5e74911723aa1d1f04c928c2a03dedf922d049e4815f7e5a369faa2e6b6a1000aae958b7953b5cc60411154f593:881:51)
    at Module.enable (require.js?v=d37b48bb2137faa0ab98157e240c084dd5b1b5e74911723aa1d1f04c928c2a03dedf922d049e4815f7e5a369faa2e6b6a1000aae958b7953b5cc60411154f593:1173:22)

ektar avatar Sep 17 '22 15:09 ektar

I think this module has a misconfiguration in its build, making it not work with requirejs. I had a similar issue with https://github.com/jbaysolutions/vue-grid-layout for which I made a custom version with this change: https://github.com/jbaysolutions/vue-grid-layout/commit/370b0159c2e7c42f954a6a84eefc3325add811aa (I should make a PR to get in the original project).

So the solution would be to make this change for ag-grid-vue too.

mariobuikhuizen avatar Sep 19 '22 16:09 mariobuikhuizen

Thanks, @mariobuikhuizen ! I looked at aggrid-vue, it looks like they are indeed missing vue as an externals definition, will work with them to see if can be added: https://github.com/ag-grid/ag-grid/blob/latest/community-modules/vue/vue.config.js

ektar avatar Sep 23 '22 14:09 ektar

I've uploaded a patched version of the library to s3 and made a few changes to the template to make it work as a POC. (If you're using Jupyter Lab, you'll need ipyvue >= 1.8.0):

<template>
  <div>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/ag-grid.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/ag-theme-alpine.css">
    <div v-if="aggrid_vue_loaded">
      <ag-grid-vue
          style="width: 500px; height: 200px"
          class="ag-theme-alpine"
          :columnDefs="columnDefs"
          :rowData="rowData"
      >
      </ag-grid-vue>
    </div>
  </div>
</template>

<script>
module.exports = {
  async created() {
    await this.import(['https://cdn.jsdelivr.net/npm/[email protected]/dist/ag-grid-community.amd.js']);
    // const [{AgGridVue}] = await this.import(['https://cdn.jsdelivr.net/npm/[email protected]/dist/ag-grid-vue.umd.js']);
    /* Load patched version of ag-grid-vue.umd.js */
    const [{AgGridVue}] = await this.import(['https://s3.us-east-2.amazonaws.com/mario.pub/ag-grid-vue.umd.js']);
    this.$options.components['ag-grid-vue'] = AgGridVue;
    this.aggrid_vue_loaded = true;
  },
  methods: {
    import(deps) {
      return this.loadRequire()
          .then(() => new Promise((resolve, reject) => {
            this.defineVue();
            requirejs(deps, (...modules) => resolve(modules));
          }));
    },
    loadRequire() {
      if (window.requirejs) {
        console.log('require found');
        return Promise.resolve()
      }
      /* Needed in lab */
      return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js';
        script.onload = resolve;
        script.onerror = reject;
        document.head.appendChild(script);
      });
    },
    defineVue() {
      if (require.defined("vue")) {
        return;
      }
      if (window.jupyterVue) {
        /* Since Lab doesn't use requirejs, jupyter-vue is not defined. Since ipyvue 1.8.0 it is added to
         * window, so we can use that to define it in requirejs */
        define('vue', [], () => window.jupyterVue.Vue);
      } else {
        define('vue', ['jupyter-vue'], jupyterVue => jupyterVue.Vue);
      }
    }
  }
}
</script>

mariobuikhuizen avatar Sep 24 '22 12:09 mariobuikhuizen