{ "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "source": [ "# Declarative layouts\n", "\n", "This notebook demonstrates how to use an _experimental_ display type called `application/vdom.v1+json` or `vdom` for short.\n", "\n", "Instead of sending HTML, you send a declarative JSON format that lists the nodes for HTML like so:\n", "\n", "```js\n", "{\n", " 'tagName': 'h1',\n", " 'attributes': {\n", " 'style': {\n", " 'color': 'DeepPink'\n", " },\n", " },\n", " 'children': []\n", "}\n", "```\n", "\n", "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:\n", "\n", "```python\n", "layout = (\n", " Div([\n", " H1('Hello there!'),\n", " P('''\n", " Living the dream\n", " '''),\n", " ])\n", ")\n", "```\n", "\n", "(which is exactly the API that [dash](https://plot.ly/dash) provides)\n", "\n", "We'll start out a little raw and show off some of the chief benefits as we go.\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "outputs": [ { "data": { "application/vdom.v1+json": { "attributes": {}, "children": "Welcome to VDOM", "tagName": "h1" } }, "metadata": {}, "output_type": "display_data" } ], "source": [ "display(\n", " {\n", " 'application/vdom.v1+json': {\n", " 'tagName': 'h1',\n", " 'attributes': {\n", " },\n", " 'children': 'Welcome to VDOM',\n", " }\n", " },\n", " raw=True\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll wrap that boilerplate up in a `VDOM` class, similar to the `IPython.display.HTML` class:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "outputs": [ { "data": { "application/vdom.v1+json": { "attributes": { "style": { "textAlign": "center" } }, "children": "Now you're cooking with VDOM", "tagName": "h1" }, "text/plain": [ "<__main__.VDOM at 0x108a58198>" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class VDOM():\n", " def __init__(self, obj):\n", " self.obj = obj\n", " \n", " def _repr_mimebundle_(self, include, exclude, **kwargs):\n", " return {\n", " 'application/vdom.v1+json': self.obj\n", " }\n", "\n", "VDOM({\n", " 'tagName': 'h1',\n", " 'attributes': {\n", " 'style': {\n", " 'textAlign': 'center'\n", " }\n", " },\n", " 'children': \"Now you're cooking with VDOM\",\n", "})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we really want, we could also create individual HTML element helpers" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "outputs": [], "source": [ "def h1(children=None, **kwargs):\n", " return {\n", " 'tagName': 'h1',\n", " 'attributes': {\n", "\n", " # Fold everything else in as props\n", " # Note that we'd _really_ want to do some validation here\n", " **kwargs\n", " },\n", " 'children': children,\n", "\n }" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "outputs": [ { "data": { "text/plain": [ "{'attributes': {'style': {'fontSize': '5em'}},\n", " 'children': 'hey',\n", " 'tagName': 'h1'}" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h1('hey', style={ 'fontSize': '5em'})" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "outputs": [ { "data": { "application/vdom.v1+json": { "attributes": { "style": { "color": "DeepPink", "fontSize": "5em" } }, "children": "This is great", "tagName": "h1" }, "text/plain": [ "<__main__.VDOM at 0x108a73128>" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "VDOM(h1('This is great',\n", " style={ 'fontSize': '5em', 'color': 'DeepPink' }))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Why not just use HTML?\n", "\nWhen 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 `
`:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "outputs": [ { "data": { "text/html": [ "
\n", " Click me to expand\n", "

I am some hidden text

\n", "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%html\n", "
\n", " Click me to expand\n", "

I am some hidden text

\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An easier element to demonstrate in a tutorial notebook is the infamous (and deprecated) `` tag. Fun fact: It's GPU accelerated on Chrome." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "outputs": [ { "data": { "text/html": [ "Here I am scrolling" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from IPython.display import HTML\n", "import time\n", "\n", "handle = display(HTML(\"\"\"Here I am scrolling\"\"\"), display_id='html_marquee')\n", "time.sleep(2)\n", "handle.display(HTML(\"\"\"RESET MUAHAHAHAHAHAH\"\"\"), update=True)\n", "time.sleep(2)\n", "handle.display(HTML(\"\"\"šŸ˜”\"\"\"), update=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Whereas, if you do it with the VDOM, you get nice clean updates that keep state.\n", "\n" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "outputs": [ { "data": { "application/vdom.v1+json": { "attributes": {}, "children": "ā¤ļø VDOM ā¤ļø", "tagName": "h1" }, "text/plain": [ "<__main__.VDOM at 0x10f2f23c8>" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import time\n", "\n", "def marquee(children=None, **kwargs):\n", " return {\n", " 'tagName': 'marquee',\n", " 'attributes': {\n", " # Fold everything else in as props\n", " # Note that we'd _really_ want to do some validation here\n", " **kwargs\n", " },\n", " 'children': children,\n", "\n", " }\n", "\n", "h = display(VDOM(marquee('HERE WE GO VDOM')), display_id='vdom_marquee')\n", "time.sleep(1.5)\n", "\n", "for ii in range(12):\n", " h.display(VDOM(marquee('😁')), update=True)\n", " time.sleep(0.5)\n", " h.display(VDOM(marquee('šŸ˜āœŒšŸ»')), update=True)\n", " time.sleep(0.5)\n", "\nh.display(VDOM(h1('ā¤ļø VDOM ā¤ļø')), update=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Game time\n", "\nWe can use this to make a silly little game where you and your friends pick an emoji and watch as it shuffles through them." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false, "outputExpanded": false }, "outputs": [ { "data": { "application/vdom.v1+json": { "attributes": { "style": { "fontSize": "4em" } }, "children": "WINNER 🐱", "tagName": "h1" }, "text/plain": [ "<__main__.VDOM at 0x10f2f2470>" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import secrets\n", "import time\n", "\n", "winner = \"\"\n", "\n", "# Each player should pick an emoji that you put into this array\n", "choices = [\"šŸ„‘\", \"🐰\", \"🤷\", \"🚁\", \"🐰\", \"🐱\"]\n", " # \"šŸ„\", \"🐱\", \"🚁\", \"☃\", \"šŸŒ€\", \"šŸ‡\", \"🐼\", \"šŸ¦†\", \"šŸš€\", \"šŸŽ”\"]\n", "\n\n", "game = display(VDOM(h1('GAMETIME')), display_id=\"game\")\n", "\n", "for ii in range(40):\n", " winner = secrets.choice(choices)\n", " game.display(\n", " VDOM(\n", " marquee(winner, style={ \"fontSize\" : '4em' })\n", " ),\n", " update=True\n", " )\n", " time.sleep(0.1)\n", "\ngame.display(VDOM(h1('WINNER ' + winner, style={ \"fontSize\" : '4em' })), update=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Collapsible job progress views\n", "\nThat 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." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "outputs": [], "source": [ "def element(elementType):\n", " def elemental(children=None, **kwargs):\n", " return {\n", " 'tagName': elementType,\n", " 'attributes': {\n", " # Fold everything else in as props\n", " # Note that we'd _really_ want to do some validation here\n", " # Likely using http://bit.ly/domprops\n", " **kwargs\n", " },\n", " 'children': children,\n", "\n", " }\n", " return elemental" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "outputs": [ { "data": { "application/vdom.v1+json": { "attributes": {}, "children": [ { "attributes": {}, "children": "Now Incredibly Declarative", "tagName": "h1" }, { "attributes": {}, "children": [ "Can you believe we wrote ", { "attributes": {}, "children": "all this from scratch", "tagName": "b" }, "?" ], "tagName": "p" }, { "attributes": { "src": "https://media.giphy.com/media/xUPGcguWZHRC2HyBRS/giphy.gif" }, "children": null, "tagName": "img" }, { "attributes": {}, "children": "SO COOL!", "tagName": "p" } ], "tagName": "div" }, "text/plain": [ "<__main__.VDOM at 0x10f303a20>" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bold = element('b')\n", "div = element('div')\n", "p = element('p')\n", "img = element('img')\n", "\n\n", "VDOM(\n", " div([\n", " h1('Now Incredibly Declarative'),\n", " p(['Can you believe we wrote ', bold('all this from scratch'), '?']),\n", " img(src=\"https://media.giphy.com/media/xUPGcguWZHRC2HyBRS/giphy.gif\"),\n", " p('SO COOL!'),\n", " ])\n", ")" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "outputs": [], "source": [ "details = element('details')\n", "summary = element('summary')\n", "progress = element('progress')" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false, "inputHidden": false, "outputHidden": false }, "outputs": [ { "data": { "application/vdom.v1+json": { "attributes": { "open": true }, "children": [ { "attributes": {}, "children": "Job Progress - toggle me", "tagName": "summary" }, { "attributes": { "max": 100, "style": { "appearance": "none", "width": "100%" }, "value": 100 }, "children": null, "tagName": "progress" } ], "tagName": "details" }, "text/plain": [ "<__main__.VDOM at 0x10f2bfb70>" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "progress_style= dict(width=\"100%\", appearance=\"none\")\n", "\n", "job_progress = display(\n", " VDOM(\n", " details([\n", " summary(\"Job Progress\"),\n", " progress(value=0, max=100, style=progress_style)\n", " ], open=True)\n", " ),\n", " display_id=\"job_progression\"\n", ")\n", "\n", "for value in range(10, 105, 5):\n", " time.sleep(0.2)\n", "\n", " job_progress.display(VDOM(\n", " details([\n", " summary(\"Job Progress - toggle me\"),\n", " progress(value=value, max=100, style=progress_style),\n", " \n", " ], open=True),\n", " \n", " ),\n", " update=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can continue to refine these basic building blocks, building even richer UIs, all in declarative Python structures.\n", "\n", "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.\n", "\nšŸŽ‰ Here's to building cool things! šŸŽ‰" ] } ], "metadata": { "kernel_info": { "name": "python3" }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.2" }, "nteract": { "version": "nteract-on-jupyter@2.0.4" }, "title": "VDOMmable Updates with Emojis" }, "nbformat": 4, "nbformat_minor": 0 }