widgetti / ipyvuetify

Jupyter widgets based on vuetify UI components
MIT License
343 stars 59 forks source link

v-select does not work as expected when used as Child Component with VuetifyTemplate #149

Open gioxc88 opened 3 years ago

gioxc88 commented 3 years ago

I am not sure the problem is related only to v-select but it might be more general. Below an example:

  1. I define a very simple Select component (WORKS)
import ipywidgets as widgets
import ipyvue as vue
from traitlets import Dict, Unicode, List
import ipyvuetify as v

select_template= '''
    <template>
    <v-row align="center">
        <v-col cols="4">
            <v-select :items="items" label="Test"></v-select>
        </v-col>
    </v-row>
    </template>
    <script>
    module.exports = {
        props: ["items"]
    }
    </script>
'''

class Select(v.VuetifyTemplate):
    items = List([]).tag(sync=True)
    template = Unicode(select_template).tag(sync=True)

Select(items=['a', 'b', 'c'])

image

  1. I put this component in a Main component using the Select class directly (DOESN'T WORK as expected) Clicking on the arrow to show the menu items is not working.
class Main(v.VuetifyTemplate):
    items = List([]).tag(sync=True)
    template = Unicode('''
        <template>
            <v-card>
                <v-card-title>Showing SubComponents</v-card-title>
                <my-select :items="items"/>
            </v-card>
        </template>
    ''').tag(sync=True)

    components=Dict({
        'my-select': Select,
    }).tag(sync=True, **v.VuetifyTemplate.class_component_serialization)

Main(items=['a', 'b', 'c'])

image

  1. I do the same thing but I specify the component as a string using template instead of using the Select class (WORKS)
class Main(v.VuetifyTemplate):
    items = List([]).tag(sync=True)
    template = Unicode('''
        <template>
            <v-card>
                <v-card-title>Showing SubComponents</v-card-title>
                <my-select :items="items"/>
            </v-card>
        </template>
    ''').tag(sync=True)

    components=Dict({
        'my-select': select_template,
    }).tag(sync=True, **v.VuetifyTemplate.class_component_serialization)

Main(items=['a', 'b', 'c'])

image

  1. Finally if I use the class as a component instead of the template string I am also experiencing the same problem of this issue on ipyvue about the events emitted by the Child not been sent to the Parent

Any help on this would be appreciated. Many thanks Gio

etienne-monier commented 3 years ago

I produced the same error ... Help !

etienne-monier commented 3 years ago

I'll be more explicit.

I defined a template vue based ont the following file:

<v-select v-model="selected" :items="items" :label="label" multiple>
    <template v-slot:prepend-item>
        <v-list-item ripple @click="toggle">
            <v-list-item-action>
                <v-icon :color="selected.length > 0 ? 'indigo darken-4' : ''">
                    {{ icon }}
                </v-icon>
            </v-list-item-action>
            <v-list-item-content>
                <v-list-item-title>
                    Select All
                </v-list-item-title>
            </v-list-item-content>
        </v-list-item>
        <v-divider class="mt-2"></v-divider>
    </template>
</v-select>
<script>
    export default {

        computed: {

            allSelected() {
                return this.selected.length === this.items.length
            },
            someSelected() {
                return this.selected.length > 0 && !this.allSelected
            },
            icon() {
                if (this.allSelected) return 'mdi-close-box'
                if (this.someSelected) return 'mdi-minus-box'
                return 'mdi-checkbox-blank-outline'
            },
        },

        methods: {
            toggle() {
                this.$nextTick(() => {
                    if (this.allSelected) {
                        this.selected = []
                    } else {
                        this.selected = this.items.slice()
                    }
                })
            },
        },
    }
    <script>
    module.exports = {
        props: ["items", "selected", "label"]
    }
    </script>
</script>

I created the following template class.

class SelectIntVue(v.VuetifyTemplate):
    template_file = str(VUE_ROOT / "select.vue")
    items = traitlets.List(traitlets.Int(), default_value=None).tag(sync=True)
    selected = traitlets.List(traitlets.Int(), default_value=None).tag(
        sync=True
    )
    label = traitlets.Unicode(default_value="").tag(sync=True)

I'd like to incorporate it in a basic form, but I got inspired from the @gioxc88 method. In fact, incorporating a widget trait is not perfect as I should need to create the slider before incorporating it to the form. That's correct for a simple form, but that's not for a complex application.

I then tried the 3rd point solution from @gioxc88 and produced something like this

select = """
<template>
<v-select v-model="selected" :items="items" :label="label" multiple>
    <template v-slot:prepend-item>
        <v-list-item ripple @click="toggle">
            <v-list-item-action>
                <v-icon :color="selected.length > 0 ? 'indigo darken-4' : ''">
                    {{ icon }}
                </v-icon>
            </v-list-item-action>
            <v-list-item-content>
                <v-list-item-title>
                    Select All
                </v-list-item-title>
            </v-list-item-content>
        </v-list-item>
        <v-divider class="mt-2"></v-divider>
    </template>
</v-select>
</template>
<script>
    module.exports = {
        props: ["items", "selected", "label"],
    },
    export default {
        props: ["items", "selected", "label"],
        computed: {

            allSelected() {
                return this.selected.length === this.items.length
            },
            someSelected() {
                return this.selected.length > 0 && !this.allSelected
            },
            icon() {
                if (this.allSelected) return 'mdi-close-box'
                if (this.someSelected) return 'mdi-minus-box'
                return 'mdi-checkbox-blank-outline'
            },
        },

        methods: {
            toggle() {
                this.$nextTick(() => {
                    if (this.allSelected) {
                        this.selected = []
                    } else {
                        this.selected = this.items.slice()
                    }
                })
            },
        },
    }
</script>
"""

but when I use it in this example

class CreateForm(v.VuetifyTemplate):

    id_items = traitlets.List(traitlets.Int(), default_value=None).tag(sync=True)
    category_items = traitlets.List(traitlets.Unicode(), default_value=None).tag(sync=True)
    selectedCategory = traitlets.Unicode("").tag(sync=True)

    components = traitlets.Dict({
        'my-select': select,
    }).tag(sync=True, **v.VuetifyTemplate.class_component_serialization)

    @traitlets.default('template')
    def _template(self):
        return '''
<v-card>
    <v-card-title>
        <span class="text-h5">A test</span>
    </v-card-title>
    <v-card-text>
        <v-container fluid>
            <v-row>
                <v-col cols="12" sm="6">
                    <my-select :items="id_items" label="Sat. id" />
                </v-col>
                <v-col cols="12" sm="6">
                    <v-select v-model="selectedCategory" :items="category_items" label="Category" required></v-select>
                </v-col>
            </v-row>
        </v-container>
    </v-card-text>
    <v-card-actions>
        <v-spacer></v-spacer>
        <v-btn color="blue darken-1" text @click="close_form">
            Close
        </v-btn>
        <v-btn color="blue darken-1" text @click="send_form">
            Create
        </v-btn>
    </v-card-actions>
</v-card>
<script>
    export default {
        methods: {
            close_form() {
                this.$emit('closeForm')
            },
            send_form() {
                this.$emit('closeForm', selectedCategory)
            }
        }
    }
</script>
'''

id_items=[0, 1, 2, 3, 4, 5]
category_list = ['a', 'b']
form = CreateForm(id_items=id_items, category_items=category_list)
form

This does not display the slider.

bug

(by the way, is there a log or a debug mode specific to vuetify? This would help to know why something does not display)