Template Usage
First template
When building larger UI’s it may be convenient to write composite widgets using the Vue template syntax. Lets start with a basic example:
import ipyvuetify as v
import traitlets
class FruitSelector(v.VuetifyTemplate):
fruits = traitlets.List(traitlets.Unicode(), default_value=['Apple', 'Pear']).tag(sync=True)
selected = traitlets.Unicode(default_value=None, allow_none=True).tag(sync=True)
@traitlets.default('template')
def _template(self):
return '''
<template>
<div>
<v-select label="Fruits" :items="fruits" v-model="selected"/>
</div>
</template>
'''
fruits = FruitSelector()
fruits
- The general pattern is to:
Inherit from
VuetifyTemplate
Define traits which will be available as
data
in your Vue template.Return a Vue template string as default value for the
template
trait.
Advanced template
While the first template is actually more code than simply creating a
v.Select
widgets, this next example will demonstrate a Vue feature not
available using the normal wrapped Vuetify widgets:
import ipyvuetify as v
import traitlets
class FruitSelector(v.VuetifyTemplate):
fruits = traitlets.List(traitlets.Unicode(), default_value=['Apple', 'Pear']).tag(sync=True)
selected = traitlets.Unicode(default_value=None, allow_none=True).tag(sync=True)
@traitlets.default('template')
def _template(self):
return '''
<template>
<div>
<v-select label="Fruits" :items="fruits" v-model="selected"/>
Available fruits
<table class="fruit-selector">
<tr v-for="(fruit, index) in fruits" :key="index" @click="selected = fruit">
<td>{{index}}</td>
<td>{{fruit}}</td>
<td>{{fruit == selected ? "selected" : ""}}</td>
</tr>
</table>
</div>
</template>
<style id="fruit-selector-style">
.fruit-selector td {
border: 1px solid black;
}
</style>
'''
fruits = FruitSelector(fruits=['Banana', 'Pear', 'Apple'])
fruits
In this example we make use of the list rendering feature of Vue so that we only have to synchronize a list of strings, and not create a widget for every fruit we want to display.
Note
By giving the <style>
tag an id attribute, we can remove the old css when the notebook cell gets executed multiple times, this is useful when developing in the Jupyter notebook.
Template in vue files
Although a powerful feature, programming Vue templates in a string is quite
cumbersome, since you lose syntax highlighting and other advanced editor
features. Instead, we can put out Vue template into a .vue
file, and point
our VuetifyTemplate to it.
Vuetify template fruit-selector.vue
:
<template>
<div>
<v-select label="Fruits" :items="fruits" v-model="selected" />
<div>
<v-btn @click="addBanana" :disabled="hasBanana">Add banana</v-btn>
<v-btn @click="removeBanana" :disabled="!hasBanana">Remove banana</v-btn>
<v-btn @click="add_fruit_python()" :disabled="!can_add_from_python"
>Add from Pyton</v-btn
>
</div>
Available fruits
<table class="fruit-selector-file">
<tr
v-for="(fruit, index) in fruits"
:key="index"
@click="selected = fruit"
>
<td>{{ index }}</td>
<td>{{ fruit }}</td>
<td class="selected">
<v-icon v-if="fruit == selected" left color="green">
mdi-check
</v-icon>
<v-icon v-if="fruit != selected" left color="grey">
mdi-check
</v-icon>
</td>
</tr>
</table>
</div>
</template>
<style id="fruit-selector-style-file">
.fruit-selector-file td {
border: 1px solid #999;
padding: 5px 10px;
}
.fruit-selector-file {
border-collapse: collapse;
}
.fruit-selector-file td.selected {
width: 50px;
}
</style>
<script>
module.exports = {
methods: {
addBanana() {
this.fruits.push("Banana");
},
removeBanana() {
this.fruits.pop("Banana");
if (this.selected == "Banana") {
this.selected = null;
}
},
},
computed: {
hasBanana() {
return this.fruits.includes("Banana");
},
},
watch: {
fruits() {
console.log("Current fruits", this.fruits);
},
},
};
</script>
Python:
import ipyvuetify as v
import traitlets
import random
other_fruits = ['Pineapple', 'Kiwi', 'Cherry']
class FruitSelector(v.VuetifyTemplate):
template_file = 'fruit-selector.vue'
fruits = traitlets.List(traitlets.Unicode(), default_value=['Apple', 'Pear']).tag(sync=True)
selected = traitlets.Unicode(default_value=None, allow_none=True).tag(sync=True)
can_add_from_python = traitlets.Bool(default_value=True).tag(sync=True)
def vue_add_fruit_python(self, data=None):
if other_fruits:
fruit = other_fruits.pop()
self.fruits = self.fruits + [fruit]
if not other_fruits:
self.can_add_from_python = False
In this example we demonstrate a few new features:
vue_add_fruit_python
can be used as an event handler from the Vue template (exposed without thevue_
prefix)Vue Methods.
Hot reloading
Now that your Vue templates are in a file, we can add a file watch to support hot reloading:
import ipyvue
ipyvue.watch()
A demonstration in a screen capture:
Note
For this feature we require the watchdog packages. Install it using pip install "watchdog>=2.0"
or conda install -c conda-forge "watchdog>=2.0"
Embed ipywidgets
Any ipywidget can be embedded by setting them in a trait and adding widget_serialization, accessing them in the template with the jupyter-widget
tag:
import ipyvuetify as v
import ipywidgets as widgets
import traitlets
slider1 = widgets.IntSlider(description='Slider 1', value=20)
slider2 = v.Slider(label='Slider 2', v_model=8)
class MyComponent(v.VuetifyTemplate):
items = traitlets.List().tag(sync=True, **widgets.widget_serialization)
single_widget = traitlets.Any().tag(sync=True, **widgets.widget_serialization)
@traitlets.default('template')
def _template(self):
return '''
<template>
<div>
<div v-for="item in items" :key="item.title + item.content">
{{ item.title }}: <jupyter-widget :widget="item.content" />
</div>
Single widget: <jupyter-widget :widget="single_widget" />
</div>
</template>
'''
MyComponent(
items=[{
'title': 'Title 1',
'content': slider1
}, {
'title': 'Title 2',
'content': slider2
}],
single_widget=widgets.IntSlider(description='Single slider', value=40))
)