This notebook demonstrates how to use an experimental display type called application/vdom.v1+json or vdom for short.
Instead of sending HTML, you send a declarative JSON format that lists the nodes for HTML like so:
{
'tagName': 'h1',
'attributes': {
'style': {
'color': 'DeepPink'
},
},
'children': []
}
This is a bit low level, so you'll have to bear with us. The goal for an end user would be to be able to write something like this in Python:
layout = (
Div([
H1('Hello there!'),
P('''
Living the dream
'''),
])
)
(which is exactly the API that dash provides)
We'll start out a little raw and show off some of the chief benefits as we go.
In [1]:
display(
{
'application/vdom.v1+json': {
'tagName': 'h1',
'attributes': {
},
'children': 'Welcome to VDOM',
}
},
raw=True
)
We'll wrap that boilerplate up in a VDOM class, similar to the IPython.display.HTML class:
In [2]:
class VDOM():
def __init__(self, obj):
self.obj = obj
def _repr_mimebundle_(self, include, exclude, **kwargs):
return {
'application/vdom.v1+json': self.obj
}
VDOM({
'tagName': 'h1',
'attributes': {
'style': {
'textAlign': 'center'
}
},
'children': "Now you're cooking with VDOM",
})
Out[2]:
If we really want, we could also create individual HTML element helpers
In [3]:
def h1(children=None, **kwargs):
return {
'tagName': 'h1',
'attributes': {
# Fold everything else in as props
# Note that we'd _really_ want to do some validation here
**kwargs
},
'children': children,
}
In [4]:
h1('hey', style={ 'fontSize': '5em'})
Out[4]:
In [5]:
VDOM(h1('This is great',
style={ 'fontSize': '5em', 'color': 'DeepPink' }))
Out[5]:
In [6]:
%%html
<details>
<summary>Click me to expand</summary>
<p>I am some hidden text</p>
</details>
An easier element to demonstrate in a tutorial notebook is the infamous (and deprecated) <marquee> tag. Fun fact: It's GPU accelerated on Chrome.
In [7]:
from IPython.display import HTML
import time
handle = display(HTML("""<marquee>Here I am scrolling</marquee>"""), display_id='html_marquee')
time.sleep(2)
handle.display(HTML("""<marquee>RESET MUAHAHAHAHAHAH</marquee>"""), update=True)
time.sleep(2)
handle.display(HTML("""<marquee>😔</marquee>"""), update=True)
Whereas, if you do it with the VDOM, you get nice clean updates that keep state.
In [8]:
import time
def marquee(children=None, **kwargs):
return {
'tagName': 'marquee',
'attributes': {
# Fold everything else in as props
# Note that we'd _really_ want to do some validation here
**kwargs
},
'children': children,
}
h = display(VDOM(marquee('HERE WE GO VDOM')), display_id='vdom_marquee')
time.sleep(1.5)
for ii in range(12):
h.display(VDOM(marquee('😁')), update=True)
time.sleep(0.5)
h.display(VDOM(marquee('😁✌🏻')), update=True)
time.sleep(0.5)
h.display(VDOM(h1('❤️ VDOM ❤️')), update=True)
In [9]:
import secrets
import time
winner = ""
# Each player should pick an emoji that you put into this array
choices = ["🥑", "🐰", "🤷", "🚁", "🐰", "🐱"]
# "🍄", "🐱", "🚁", "☃", "🌀", "🏇", "🐼", "🦆", "🚀", "🎡"]
game = display(VDOM(h1('GAMETIME')), display_id="game")
for ii in range(40):
winner = secrets.choice(choices)
game.display(
VDOM(
marquee(winner, style={ "fontSize" : '4em' })
),
update=True
)
time.sleep(0.1)
game.display(VDOM(h1('WINNER ' + winner, style={ "fontSize" : '4em' })), update=True)
In [10]:
def element(elementType):
def elemental(children=None, **kwargs):
return {
'tagName': elementType,
'attributes': {
# Fold everything else in as props
# Note that we'd _really_ want to do some validation here
# Likely using http://bit.ly/domprops
**kwargs
},
'children': children,
}
return elemental
In [11]:
bold = element('b')
div = element('div')
p = element('p')
img = element('img')
VDOM(
div([
h1('Now Incredibly Declarative'),
p(['Can you believe we wrote ', bold('all this from scratch'), '?']),
img(src="https://media.giphy.com/media/xUPGcguWZHRC2HyBRS/giphy.gif"),
p('SO COOL!'),
])
)
Out[11]:
In [12]:
details = element('details')
summary = element('summary')
progress = element('progress')
In [13]:
progress_style= dict(width="100%", appearance="none")
job_progress = display(
VDOM(
details([
summary("Job Progress"),
progress(value=0, max=100, style=progress_style)
], open=True)
),
display_id="job_progression"
)
for value in range(10, 105, 5):
time.sleep(0.2)
job_progress.display(VDOM(
details([
summary("Job Progress - toggle me"),
progress(value=value, max=100, style=progress_style),
], open=True),
),
update=True)
You can continue to refine these basic building blocks, building even richer UIs, all in declarative Python structures.
The beauty of all this is that you can update these displays without forcing weird scrolling behavior on the users or changing the state of interactive controls on them.
🎉 Here's to building cool things! 🎉