Repository: nteract/vdom

Issue:
Write some best practices guidance for creating components
Within `vdom`, the best way (that I can think of right now) to create new elements is to create functions that return elements:

```python
from vdom import h, p, div
def happiness(level=90):
  smiley = "😃"
  percent = level / 100.
    
  if(percent < 0.25):
    smiley = "☹️"
  elif(percent < 0.50):
    smiley = "😐"
  elif(percent < 0.75):
    smiley = "😀"

    
  return p([
      'Happiness ', smiley, h('br'), h('meter', level, min="0", low=25, high=75, optimum=90, max=100, value=level)
  ])
# Usage:

happiness()

div([
  happiness(10),
  happiness(32),
  happiness(65),
  happiness(80)
])
```

Example in nteract:

![screen shot 2017-09-25 at 11 49 15 am](https://user-images.githubusercontent.com/836375/30825369-93461922-a1e7-11e7-803c-04bd20e7fb2d.png)




Comments:

Patch:
diff --git a/README.md b/README.md
index 413c20e..b232b49 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 ## Why use VDOM?
 
 - Write **Declarative** Pythonic layouts.
-- Render the layout in Jupyter and nteract frontends.
+- Render the layout in Jupyter frontends.
 - Serialize layout for rehydration in your web app.
 
 :warning: This library is a work in progress. :warning:
diff --git a/docs/design-patterns.md b/docs/design-patterns.md
new file mode 100644
index 0000000..fcff958
--- /dev/null
+++ b/docs/design-patterns.md
@@ -0,0 +1,150 @@
+# Design Patterns
+
+The main operating principle for `vdom` (virtual DOM) is:
+
+> Write functions that return `vdom` elements
+
+In matching with `React` parlance, we'll call these functions components. This allows you to share, remix, and use components amongst everyone. You'll be able to compose together components to create greater components than you have before.
+
+## Introductory component
+
+We'll start with a component that takes a level of happiness and produces a light visualization:
+
+```python
+from vdom.helpers import div, span, p, meter
+
+def happiness(level=90):
+  smiley = "😃"
+  percent = level / 100.
+    
+  if(percent < 0.25):
+    smiley = "☹️"
+  elif(percent < 0.50):
+    smiley = "😐"
+  elif(percent < 0.75):
+    smiley = "😀"
+
+    
+  return span(
+      p('Happiness ', smiley),
+      meter(level, min=0, low=25, high=75, optimum=90, max=100, value=level)
+  )
+```
+
+The user of the component only needs to call it with a level of happiness from 0 to 100.
+
+```python
+happiness(96)
+```
+
+<span>
+<p>Happiness 😃</p>
+<meter min="0" low="25" high="75" optimum="90" max="100" value="96">
+  96
+</meter>
+</span>
+
+------------
+
+:tada: Our first component is ready! Since we can think of `happiness` as a little building block component, we can put several of these together to create whole layouts:
+
+```python
+div(
+  happiness(10),
+  happiness(32),
+  happiness(65),
+  happiness(80)
+)
+```
+
+<span>
+<p>Happiness ☹️</p>
+<meter min="0" low="25" high="75" optimum="90" max="100" value="10">
+  10
+</meter>
+</span>
+<span>
+<p>Happiness 😐</p>
+<meter min="0" low="25" high="75" optimum="90" max="100" value="32">
+  32
+</meter>
+</span>
+<span>
+<p>Happiness 😀</p>
+<meter min="0" low="25" high="75" optimum="90" max="100" value="65">
+  65
+</meter>
+</span>
+<span>
+<p>Happiness 😃</p>
+<meter min="0" low="25" high="75" optimum="90" max="100" value="80">
+  80
+</meter>
+</span>
+
+-------------------
+
+
+## Working with Python objects
+
+For this section, you'll need `ggplot` and `matplotlib` packages installed. We'll create a component, `fancy_hist` that creates a histogram which allows for displaying side by side
+
+```python
+import matplotlib.pyplot as plt
+import io, base64, urllib
+
+def fancy_hist(value, data=mpg, title="Plot", bins=12, style=None):
+    if(style is None):
+        style = {"display": "inline-block"}
+    
+    f = plt.figure()
+    plt.hist(value, bins=bins, data=data)
+
+    buf = io.BytesIO()
+    f.savefig(buf, format='png')
+    buf.seek(0)
+    string = base64.b64encode(buf.read())
+    
+    plt.close()
+        
+    return div(
+        h1(title),
+        p(str(bins), " bins"),
+        img(src='data:image/png;base64,' + urllib.parse.quote(string)),
+      style=style
+    )
+```
+
+
+```python
+from ggplot import mpg
+fancy_hist('cty', data=mpg, title="City MPG")
+```
+
+<div style="display: inline-block">
+  <h1>City MPG</h1>
+  <p>12 bins</p>
+  <img src="" />
+</div>
+
+
+```python
+div(
+  fancy_hist('hwy', data=mpg, title="Highway MPG"),
+  fancy_hist('cty', data=mpg, title="City MPG")
+)
+
+```
+
+<div>
+<div style="display: inline-block">
+  <h1>Highway MPG</h1>
+  <p>12 bins</p>
+  <img src="" />
+</div>
+<div style="display: inline-block">
+  <h1>City MPG</h1>
+  <p>12 bins</p>
+  <img src="" />
+</div>
+</div>
diff --git a/vdom/core.py b/vdom/core.py
index 23bb4e1..448fe7d 100644
--- a/vdom/core.py
+++ b/vdom/core.py
@@ -17,9 +17,11 @@ def toJSON(el, schema=None):
     If you wish to validate the JSON, pass in a schema via the schema keyword argument.
     If a schema is provided, this raises a ValidationError if JSON does not match the schema.
     """
+    if(type(el) is str):
+        return el
     if(type(el) is list):
-        json_el = list(map(toJSON, el))
-    if(type(el) is dict):
+        return list(map(toJSON, el))
+    elif(type(el) is dict):
         assert 'tagName' in el
         json_el = el.copy()
         if 'attributes' not in el:
@@ -45,7 +47,20 @@ def toJSON(el, schema=None):
 
 
 class VDOM():
-    """A basic virtual DOM class"""
+    """A basic virtual DOM class which allows you to write literal VDOM spec
+    
+    >>> VDOM({ 'tagName': 'h1', 'children': 'Hey', 'attributes': {}})
+    
+    It's probably better to use `vdom.h` or the helper functions:
+    
+    >>> from vdom import h
+    >>> h('h1', 'Hey')
+    <h1 />
+    
+    >>> from vdom.helpers import h1
+    >>> h1('Hey')
+    
+    """
     def __init__(self, obj):
         self.obj = obj
 
@@ -74,8 +89,13 @@ def _flatten_children(*children, **kwargs):
     # children as keyword argument takes precedence
     if('children' in kwargs):
         children = kwargs['children']
-    elif children is not None and len(children) > 0:
-        if isinstance(children[0], list):
+    elif children is not None:
+        if len(children) == 0:
+            children = None
+        elif len(children) == 1:
+            # Flatten by default
+            children = children[0]
+        elif isinstance(children[0], list):
             # Only one level of flattening, just to match the old API
             children = children[0]
         else:


Test patch:
diff --git a/vdom/tests/test_core.py b/vdom/tests/test_core.py
index 0f05898..ae40905 100644
--- a/vdom/tests/test_core.py
+++ b/vdom/tests/test_core.py
@@ -2,6 +2,7 @@
 # -*- coding: utf-8 -*-
 
 from ..core import _flatten_children, toJSON
+from ..helpers import div, p, img, h1, b
 
 
 def test_flatten_children():
@@ -18,25 +19,71 @@ def test_flatten_children():
     # array as first argument second highest priority
     assert _flatten_children([1], 2) == [1]
     # otherwise, positional arguments are the default
-    assert _flatten_children(2) == [2]
+    assert _flatten_children(2, 3) == [2, 3]
 
-    # with no arguments, we get an empty array
-    assert _flatten_children() == []
     # an empty array returns an empty array
     assert _flatten_children([]) == []
+    # with no arguments, we get a null
+    assert _flatten_children() == None
 
     # If the first argument is None, we assume they explicitly wanted a
     # null element for a VDOMEl (this is ok)
-    assert _flatten_children(None) == [None]
+    assert _flatten_children(None) == None
     assert _flatten_children(None, 1, None) == [None, 1, None]
 
 
 def test_toJSON():
     assert toJSON({
         'tagName': 'h1',
-        'attributes': {}
+        'attributes': { 'data-test': True },
+        'children': []
     }) == {
         'tagName': 'h1',
-        'attributes': {},
+        'attributes': { 'data-test': True},
         'children': []
     }
+
+    assert toJSON(div(h1('Our Incredibly Declarative Example'))) == {
+        'tagName': 'div',
+        'children': {
+            'tagName': 'h1',
+            'children': 'Our Incredibly Declarative Example',
+            'attributes': {}
+        },
+        'attributes': {}
+    }
+
+    assert toJSON(
+        div(
+            h1('Our Incredibly Declarative Example'),
+            p('Can you believe we wrote this ', b('in Python'), '?'),
+            img(src="https://media.giphy.com/media/xUPGcguWZHRC2HyBRS/giphy.gif"
+                ), )
+    ) == {
+        'tagName':
+        'div',
+        'children': [{
+            'tagName': 'h1',
+            'children': 'Our Incredibly Declarative Example',
+            'attributes': {}
+        }, {
+            'tagName':
+            'p',
+            'attributes': {},
+            'children': [
+                'Can you believe we wrote this ', {
+                    'tagName': 'b',
+                    'children': 'in Python',
+                    'attributes': {}
+                }, '?'
+            ]
+        }, {
+            'tagName': 'img',
+            'children': None,
+            'attributes': {
+                'src':
+                'https://media.giphy.com/media/xUPGcguWZHRC2HyBRS/giphy.gif'
+            }
+        }],
+        'attributes': {}
+    }