#!/usr/bin/env python # coding: utf-8 # # Declarative layouts # # 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: # # ```js # { # '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: # # ```python # layout = ( # Div([ # H1('Hello there!'), # P(''' # Living the dream # '''), # ]) # ) # ``` # # (which is exactly the API that [dash](https://plot.ly/dash) provides) # # We'll start out a little raw and show off some of the chief benefits as we go. # # In[2]: 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", }) # 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'}) # In[5]: VDOM(h1('This is great', style={ 'fontSize': '5em', 'color': 'DeepPink' })) # # Why not just use HTML? # # 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 `
`: # In[6]: get_ipython().run_cell_magic('html', '', '
\n Click me to expand\n

I am some hidden text

\n
\n') # An easier element to demonstrate in a tutorial notebook is the infamous (and deprecated) `` tag. Fun fact: It's GPU accelerated on Chrome. # In[7]: from IPython.display import HTML import time handle = display(HTML("""Here I am scrolling"""), display_id='html_marquee') time.sleep(2) handle.display(HTML("""RESET MUAHAHAHAHAHAH"""), update=True) time.sleep(2) handle.display(HTML("""😔"""), 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) # # Game time # # 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. # 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) # # Collapsible job progress views # # 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. # 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!'), ]) ) # 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! 🎉