Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

possible to use ipyvuetify with vuetify-jsonschema-form ? #182

Open
jgunstone opened this issue Sep 17, 2021 · 10 comments
Open

possible to use ipyvuetify with vuetify-jsonschema-form ? #182

jgunstone opened this issue Sep 17, 2021 · 10 comments

Comments

@jgunstone
Copy link

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

@mariobuikhuizen
Copy link
Collaborator

mariobuikhuizen commented Oct 4, 2021

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

@jgunstone
Copy link
Author

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
Copy link
Author

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 jgunstone reopened this Feb 10, 2022
@maartenbreddels
Copy link
Collaborator

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
Copy link
Collaborator

I tried installing EasyMDE into the conda environmen

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

@jgunstone
Copy link
Author

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

@ektar
Copy link

ektar commented Sep 17, 2022

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/ag-grid-community@28.1.1/styles/ag-grid.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ag-grid-community@28.1.1/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/vue@2.6.10/dist/vue.runtime.js']);
        await this.import(['https://cdn.jsdelivr.net/npm/ag-grid-community@28.1.1/dist/ag-grid-community.amd.js']);
        const [AgGridVue] = await this.import(['https://cdn.jsdelivr.net/npm/ag-grid-vue@28.1.1/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)

@mariobuikhuizen
Copy link
Collaborator

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: jbaysolutions/vue-grid-layout@370b015 (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.

@ektar
Copy link

ektar commented Sep 23, 2022

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

@mariobuikhuizen
Copy link
Collaborator

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/ag-grid-community@28.1.1/styles/ag-grid.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ag-grid-community@28.1.1/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/ag-grid-community@28.1.1/dist/ag-grid-community.amd.js']);
    // const [{AgGridVue}] = await this.import(['https://cdn.jsdelivr.net/npm/ag-grid-vue@28.1.1/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>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants