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:

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:

_images/template-hot-reload.gif

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))
)