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.
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:
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",
})
<__main__.VDOM at 0x108a58198>
If we really want, we could also create individual HTML element helpers
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,
}
h1('hey', style={ 'fontSize': '5em'})
{'attributes': {'style': {'fontSize': '5em'}}, 'children': 'hey', 'tagName': 'h1'}
VDOM(h1('This is great',
style={ 'fontSize': '5em', 'color': 'DeepPink' }))
<__main__.VDOM at 0x108a73128>
When we send updates using display(obj, display_id='x', update=True)
, the HTML gets wiped out losing any state in the frontend. This especially matters with elements like <details>
:
%%html
<details>
<summary>Click me to expand</summary>
<p>I am some hidden text</p>
</details>
I am some hidden text
An easier element to demonstrate in a tutorial notebook is the infamous (and deprecated) <marquee>
tag. Fun fact: It's GPU accelerated on Chrome.
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.
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)
<__main__.VDOM at 0x10f2f23c8>
We can use this to make a silly little game where you and your friends pick an emoji and watch as it shuffles through them.
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)
<__main__.VDOM at 0x10f2f2470>
That was good and fun, let's try something that would be useful for spark and other background jobs. We've also been making a lot of boilerplate to declare marquee
and h1
. Let's create a little wrapper to make new elements simply.
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
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!'),
])
)
<__main__.VDOM at 0x10f303a20>
details = element('details')
summary = element('summary')
progress = element('progress')
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)
<__main__.VDOM at 0x10f2bfb70>
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! 🎉