NAV

Watch This first

Intro

Jason is a JSON browser.

It works similar to how web browsers work, but the difference is:

  1. Jason deals with JSON APIs instead of HTML documents.
  2. And then renders out native mobile interface instead of web pages.
Web page Jason view
content source HTML file JSON file
example url http://www.website.com/index.html http://www.openjason.org/index.json
output Visual web page Visual native mobile interface
opens in Web browsers (Chrome, firefox, etc.) Jason browser
Syntax derived from XML JSON
How to build one Create an HTML file following the HTML spec, and serve it from your server Create a JSON file following the Jason markup spec, and serve it from your server

Before we begin

Before we begin, note that this document is a work in progress. There are many parts I haven’t covered yet. The document will keep updating.

Also, you can try out the code on Jason app whenever you see this button:

run_this.png

Step 1. Build a view

Overview

{
  "$jason": {
    "head": {
      "title": "Hello World",
      "description": "Just a simple view"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "text": "Row 1"
        }, {
          "type": "label",
          "text": "Row 2"
        }]
      }]
    }
  }
}

What an actual Jason document looks like. The top level nodes are head and body.

Let’s start from how a Jason markup is structured.

  1. It’s just a JSON file that describes how it should be rendered.

  2. The root element is $jason.

  3. $jason element has head and body as its children.

In this example, we have an actual functional Jason markup that contains a single section. And that section contains two rows with labels.

{
  "$jason": {
    "head": {
      "title": "Hello World",
      "icon": "http://...jpg",
      "description": "This is a hello world app",
      "website": {
        "text": "Website",
        "url": "http://www.openjason.org"
      },
      "styles": {...},
      "actions": {...},
      "templates": {...}
    },
    "body": {...}
  }
}

Head contains metadata about the document such as title, description, and icon.

Head also contains more advanced features such as stylesheets, actions, and templates declarations (Advanced)

Attribute Description
title Describes the document title. It will be displayed on the navbar.
description Document description. Not immediately visible on the page but shows up on intro pages and on the homescreen.
icon Icon to represent the page. Shows up on the homescreen when it’s favorited.
website Used to specify an external website associated with this Jason. Has two child attributes text (Text to describe the url), and url (The url to open in a web browser)
styles For declaring styles (similar to HTML <style> tag in HTML)
actions For declaring actions (similar to <script> tag in HTML)
templates For delcaring a body template in case we need to dynamically render data

Body

{
  "$jason": {
    "head": {...},
    "body": {
      "nav": { ... },
      "sections": [SECTION, SECTION, ..., SECTION]
    }
  }
}

If head was all about metadata, body is the actual part that gets displayed on the screen.

Nothing inside head is displayed, while everything inside body is displayed.

Body consists of Nav and Sections.

{
  "$jason": {
    "head": {
      "title": "Hello"
    },
    "body": {
      "nav": {
        "items": [Nav Items]
      }
    }
  }
}

nav

nav is the top navigation bar and everything attached to it, such as search bar, menu button, and tabs.

It takes items as an argument.

1. Search item

{
  "$jason": {
    "head": {
      "title": "Hello"
    },
    "body": {
      "nav": {
        "items": [{
          "type": "search",
          "name": "query_text",
          "action": {
            "type": "$widget.alert",
            "options": {
              "title": "You've entered:",
              "description": "{{$get.query_text}}"
            }
          }
        }]
      }
    }
  }
}

In this case we will be able to access the text inside the search box using {{$get.query_text}}

nav

Search does two things when user submits input:

A. Takes the search text and sets it to local variable whose name you specify using name attribute, which then can be accessed using $get. (Learn more about $get in Local variables)

B. Then runs the action under action attribute. In most cases you probably want to utilize the local variable you set from step A.

Option Description Required
type “search” Yes
name variable name used to access the value (via $get action) Yes
placeholder Placeholder to display intead of ‘search’ Yes
action The action to be triggered when user submits the text Yes

2. Tabs item

{
  "$jason": {
    "head": {
      "title": "Tabs demo"
    },
    "body": {
      "nav": {
        "items": [{
          "type": "tabs",
          "items": [{
            "text": "Tab 1",
            "url": "http://openjason.org/1.json"
          }, {
            "text": "Tab 2",
            "url": "http://openjason.org/2.json"
          }, {
            "text": "Tab 3",
            "url": "http://openjason.org/3.json"
          }]
        }]
      }
    }
  }
}

nav

Option Description Required
type “tabs” Yes
items Array of tab elements, each of which has a text and a url. Selecting a tab loads the corresponding url in the current view. Yes

3. Menu item

{
  "$jason": {
    "head": {
      "title": "Menu demo"
    },
    "body": {
      "nav": {
        "items": [{
          "type": "menu",
          "text": "Menu",
          "action": {
            "type": "$widget.alert",
            "options": {
              "title": "Hello",
              "description": "You've pressed a menu button"
            }
          }
        }]
      }
    }
    }
}

Tapping the menu element triggers an action

nav

Menu reponds to a tap and triggers an action.

There is only one menu item (The top right button) since the top left spot is reserved for back button.

Menu can trigger any action. We will go through actions later.

Option Description Required
type “menu” Yes
text Text for the menu element No (choose between text/image)
image Image url for the menu element No (choose between text/image)
action Action to be triggered when tapped No (choose between action/items)

Sections

Items are the basic units for displaying content on Jason. And sections are containers for these items.

sections

Items

When you look at mobile apps, most of them display collections of repeating items.

For example in a Facebook app, each post has the same template (avatar, username, status message, like button, etc.), but just filled in with different content. We call this unit an item.

These similar looking items make up a section.

Sometimes we may need multiple sections, where each section has its own distinct type of repeating items.

In some cases we may want to have a single element that describes the section itself (and is not part of the repeating items). We use header to do that. You’ll see them at the top of each section. They stick to top nav bar as you scroll.

Note that a header is just another item, but separate from rest of the repeating items in the section, and doesn’t react to touch events since they’re meant to be readonly.

Example

{
  "$jason": {
    "head": {
      "title": "Basic Tableview"
    },
    "body": {
      "sections": [
        {
          "header": {
            "type": "label",
            "text": "This is the first section header"
          },
          "items": [
            {
              "type": "label",
              "text": "This is the only item in the first section"
            }
          ]
        }, 
        {
          "header": {
            "type": "label",
            "text": "This is a header item"
          },
          "items": [
            {
              "type": "label",
              "text": "This is the first item inside items array"
            }, {
              "type": "label",
              "text": "This is the second item inside items array"
            }, {
              "type": "label",
              "text": "This is the third item inside items array"
            }
          ]
        }
      ]
    }
  }
}

Here we have two sections. The first section contains 1 header and 1 item. The second section contains 1 header and 3 items.

Here’s how these all fit together.

  1. Body can have 1 or more sections.

  2. A section can have 0 or 1 header.

  3. A section can contain 1 or more repeating items.

  4. header is actually also an item, but just used as a standalone to act as divider.

    Description
    header Single Item
    items Array of items
    type if “horizontal”, the section flows horizontally instead of the default vertical flow

Vertical vs. Horizontal section

Vertical section Horizontal section
This is the default type, and what most apps look like. In a vertical section, items flow vertically from top to bottom (default) like a list. In a horizontal section, Items flow horizontally from left to right, like a carousel.
vertical section horizontal section

Vertical section

vertical section

Horizontal section

horizontal section

How to specify horizontal?

As mentioned above, add {"type": "horizontal"} option on the section you want.

Components

Now let’s fill in the item with different types of content. There are many UI components we can use.

Let’s take a look at each type and how we can describe them.

1. Label

  {
    "$jason": {
      "head": {
        "title": "Test"
      },
      "body": {
        "sections": [{
          "items": [{
            "type": "label",
            "text": "Hello world"
          }]
        }]
      }
    }
  }

Read-only text.

Description Required
type “label” Yes
text Text to display Yes

2. Image

  {
    "$jason": {
      "head": {
        "title": "Test"
      },
      "body": {
        "sections": [{
          "items": [{
            "type": "image",
            "url": "http://vignette3.wikia.nocookie.net/simpsons/images/b/b0/HomerSimpson5.gif/revision/latest?cb=20141025153213"
          }]
        }]
      }
    }
  }

Display remote image by specifying its url.

Description Required
type “image” Yes
url image url to load Yes

3. Button

  {
    "$jason": {
      "head": {
        "title": "Test"
      },
      "body": {
        "sections": [{
          "items": [{
            "type": "button",
            "text": "Push me",
            "action": {
              "type": "$widget.banner",
              "options": {
                "title": "Hello World",
                "description": "I am a banner"
              }
            }
          }]
        }]
      }
    }
  }

Buttons can trigger actions. More on actions later.

Description Required
type “image” Yes
text text to display Yes
action Action Optional

5. Textfield

  {
    "$jason": {
      "head": {
        "title": "Test"
      },
      "body": {
        "sections": [{
          "items": [{
            "type": "textfield",
            "name": "username",
            "placeholder": "Enter a username",
            "value": "ethan"
          }, {
            "type": "label",
            "action": {
              "type": "$widget.banner",
              "options": {
                "title": "You entered",
                "description": "{{$get.username}}"
              }
            }
          }]
        }]
      }
    }
  }

You can access the text inside the textfield by using {{$get.username}}

Description Required
type “textfield” Yes
name local variable name to bind the value to. Optional
placeholder Placeholder text Optional
value String to pre-fill the field with Optional

Single line input field. Equivalent to HTML’s <input type = 'text' />

You need to specify its name attribute in order to reference it throughout the document using $get notation.

6. Textarea

  {
    "$jason": {
      "head": {
        "title": "Test"
      },
      "body": {
        "sections": [{
          "items": [{
            "type": "textarea",
            "name": "content",
            "placeholder": "Write a blog post"
          }, {
            "type": "label",
            "action": {
              "type": "$widget.alert",
              "options": {
                "title": "Your post",
                "description": "{{$get.content}}"
              }
            }
          }]
        }]
      }
    }
  }

Same as textfield, you can access the text inside by using {{$get.content}}

Multi-line text input. Equivalent to HTML’s <textarea />

Like textfield, you need to specify its name attribute in order to reference it throughout the document using $get notation.

Description Required
type “textarea” Yes
name local variable name to bind the value to. Optional
value String to pre-fill the field with Optional

Layouts

We can build simple user interfaces with just the above components, but in most cases that’s not enough.

Just like how a single Facebook post or a Tweet consists of an avatar, username label, timestamp label, text label, like buttons, etc., we need to mix and match these components to create more sophisticated items.

Meet layout. Layout describes how an item should contain its child components.

  1. To stack multiple components vertically, we use vertical layout.

  2. To stack multiple components horizontally, we use horizontal layout.

  3. Of course, we can do this recursively, by stacking layouts and components within layouts.

1. Vertical layout

{
  "$jason": {
    "head": {
      "title": "Test"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "vertical",
          "components": [
            {
              "type": "label",
              "text": "Ethan"
            }, {
              "type": "label",
              "text": "Just setting up my Ethan"
            }
          ]
        }]
      }]
    }
  }
}

Vertical layout automatically lays out its child components in a vertical manner, from top to bottom.

Description Required
type “vertical” Yes
components any UI component, such as label, button, etc. Yes

2. Horizontal layout

{
  "$jason": {
    "head": {
      "title": "Test"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "horizontal",
          "components": [
            {
              "type": "label",
              "text": "Ethan"
            }, {
              "type": "label",
              "text": "Gliechtenstein"
            }
          ]
        }]
      }]
    }
  }
}

Horizontal layout automatically lays out its child components in a horizontal manner, from left to right.

Description Required
type “vertical” Yes
components any UI component, such as label, button, etc. Yes

3. Nested layouts

{
  "$jason": {
    "head": {
      "title": "Test"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "horizontal",
          "components": [
            {
              "type": "image",
              "url": "https://media.giphy.com/media/xT1XH3Ya5s5w5e3oJi/giphy.gif"
            }, 
            {
              "type": "vertical",
              "components": [
                {
                  "type": "label",
                  "text": "Homer"
                }, {
                  "type": "label",
                  "text": "Just setting up my Homer"
                }
              ]
            }
          ]
        }]
      }]
    }
  }
}

Here we have a horizontal layout at top level, which has an image on the left side and another vertical layout on the right side. The vertical layout contains two labels, and they’re stacked vertically.

We can nest layouts inside layouts. This is how you can create very sophisticated user interfaces just by combining the basic components.

Step 2. Style the view

So far we’ve looked at how to structure all the building blocks and their containers.

Now let’s style them up by attaching style attribute.

Basic rules

1. Style attribute

{
  "type": "label",
  "text": "Hello"
}

Before styling

{
  "type": "label",
  "text": "Hello",
  "style": {
    "font": "HelveticaNeue-Bold",
    "align": "right",
    "size": "20",
    "background": "rgba(200,0,0,0.2)",
    "color": "#00000"
  }
}

After styling

You can style any UI element simply by adding a style attribute, like the label example here.

Of course, each element is different so the actual attributes inside will vary depending on element type. We will go through them one by one.

2. String value only

{
  "size": "20"
}

Even though 20 is a number we treat everything as strings for the sake of simplicity.

All attribute values are strings for the sake of simplicity, even the ones that are numbers. Put them all in quotes.

3. To set to false, exclude it

{
  "options": {
    "key": "a3jfn3kflnfk3lsf",
    "secret": "enfkd-dmefjslejf_dl4",
    "token_in_body": "true"
  }
}

token_in_body will be true

{
  "options": {
    "key": "a3jfn3kflnfk3lsf",
    "secret": "enfkd-dmefjslejf_dl4"
  }
}

token_in_body will be false (obviously, since it’s not included)

{
  "options": {
    "key": "a3jfn3kflnfk3lsf",
    "secret": "enfkd-dmefjslejf_dl4",
    "token_in_body": "false"
  }
}

WARNING: token_in_body will be true! We need to exclude the attribute altogether to set it to false.

To set an attribute false, simply don’t include it.

In the following example, if you want token_in_body to be false, simply don’t include it.

Since every attribute is treated as string, including any value for token_in_body in this case will be evaluated to true.

Styling components

Let’s take a look at what style attributes are available for each component.

Label

{
  "$jason": {
    "head": {
      "title": "Label Styling Example",
      "description": "Try tweaking the values from the original code and re-open to see what happens"
    },
    "body": {
      "sections": [{
        "items": [
          {
            "type": "label",
            "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla sapien ante, aliquet quis enim vel, egestas varius massa. Duis ornare, lacus nec interdum rutrum",
            "style": {
              "font": "HelveticaNeue-CondensedBold",
              "background": "rgba(200,0,0,0.2)",
              "color": "#00000",
              "size": "20",
              "width": "200",
              "height": "100",
              "align": "center"
            }
          }
        ]
      }]
    }
  }
}

Supported attributes

Attribute Description
font Specify the font (Refer to this list)
background Background color of the label
color Text color of the label
size Font size in integer
width In case you wish to fix the label width
height In case you wish to fix the label height
align Alignment within the frame. Can be left, right, center, justified, natural,top, bottom . Default is left.

Image

{
  "$jason": {
    "head": {
      "title": "Image Styling Example",
      "description": "Try tweaking the values from the original code and re-open to see what happens"
    },
    "body": {
      "sections": [{
        "items": [
          {
            "type": "image",
            "url": "http://s2.quickmeme.com/img/00/00e404752b38ff463dd89dc7261de7e1cc435c7e3e2bb17ea9cadd88c3216a75.jpg",
            "style": {
              "width": "300",
              "padding": "10",
              "corner_radius": "160"
            }
          }
        ]
      }]
    }
  }
}
Attribute Description
width width of an image
height height of an image, defaults to the same value as width if not specified.
corner_radius Makes the corner round by specifying the radius. Similar to 'border-radius’ css property in html.

Button

{
  "$jason": {
    "head": {
      "title": "Button Styling Example",
      "description": "Try tweaking the values from the original code and re-open to see what happens"
    },
    "body": {
      "sections": [{
        "items": [
          {
            "type": "horizontal",
            "components": [
              {
                "type": "button",
                "text": "Press me",
                "style": {
                  "width": "100",
                  "height": "100",
                  "corner_radius": "50",
                  "align": "right",
                  "background": "#00ff00",
                  "color": "#ffffff"
                }
              },
              {
                "type": "button",
                "text": "Press me",
                "style": {
                  "background": "#ff0000",
                  "align": "center",
                  "color": "#ffffff"
                }
              },
              {
                "type": "button",
                "text": "Press me",
                "style": {
                  "width": "100",
                  "height": "100",
                  "corner_radius": "50",
                  "align": "left",
                  "background": "#0000ff",
                  "color": "#ffffff"
                }
              }
            ]
          }
        ]
      }]
    }
  }
}
Attribute Description
width width of the button
height height of the button, defaults to the same value as width if not specified.
corner_radius Makes the corner round by specifying the radius. Similar to 'border-radius’ css property in html.
background Background color of the button
color Text color

Textarea

{
  "$jason": {
    "head": {
      "title": "Textarea Styling Example",
      "description": "Try tweaking the values from the original code and re-open to see what happens"
    },
    "body": {
      "style": {
        "background": "#82A11A"
      },
      "sections": [{
        "items": [
          {
            "type": "vertical",
            "components": [{
              "type": "textarea",
              "name": "editor",
              "placeholder": "Type something...",
              "style": {
                "width": "100%",
                "placeholder_color": "#cecece",
                "height": "300",
                "color": "#ebebeb",
                "font": "HelveticaNeue-Bold",
                "size": "30",
                "align": "right",
                "autocorrect": "true",
                "autocapitalize": "true",
                "spellcheck": "true"
              }
            }]
          }
        ]
      }]
    }
  }
}
Attribute Description
width width of the textarea. Can be pixel value in integer or percentage (with device width being 100%)
height height of the textarea. Can be pixel value in integer or percentage (with device height being 100%)
color Text color
font Specify the font (Refer to http://iosfonts.com/)
size Font size in integer
align Alignment within the frame. Can be left, right, center, justified, natural, top, bottom. Default is left
autocorrect Autocorrect settings on if 'true’. Otherwise leave this attribute out.
autocapitalize Autocapitalize settings on if 'true’. Otherwise leave this attribute out.
spellcheck Spellcheck settings on if 'true’. Otherwise leave this attribute out.

Textfield

{
  "$jason": {
    "head": {
      "title": "Single line textfield Styling Example",
      "description": "Try tweaking the values from the original code and re-open to see what happens"
    },
    "body": {
      "style": {
        "background": "#FDD42A"
      },
      "sections": [{
        "items": [
          {
            "type": "vertical",
            "style": {
              "padding_top": "100"
            },
            "components": [{
              "type": "textfield",
              "placeholder": "Password",
              "name": "password_input",
              "style": {
                "secure": "true",
                "placeholder_color": "#ebebeb",
                "width": "100%",
                "color": "#000000",
                "font": "HelveticaNeue-Bold",
                "size": "30",
                "align": "center",
                "autocorrect": "true",
                "autocapitalize": "true",
                "spellcheck": "true"
              }
            }]
          }
        ]
      }]
    }
  }
}
Attribute Description
secure To support secure input entry such as password field, set this to 'true’
width width of the textarea. Can be pixel value in integer or percentage (with device width being 100%)
color Text color
font Specify the font (Refer to http://iosfonts.com/)
size Font size in integer
align Alignment within the frame. Can be left, right, center, justified, natural, top, bottom. Default is left
autocorrect Autocorrect settings on if 'true’. Otherwise leave this attribute out.
autocapitalize Autocapitalize settings on if 'true’. Otherwise leave this attribute out.
autocapitalize Autocapitalize settings on if 'true’. Otherwise leave this attribute out.
spellcheck Spellcheck settings on if 'true’. Otherwise leave this attribute out.

Styling layouts

{
  "type": "horizontal",
  "style": {
    "padding": "10",
    "spacing": "5",
    "distribution": "fill",
    "align": "top"
  },
  "components": [
    {
      "type": "image",
      "url": "http://...."
    },
    {
      "type": "vertical",
      "style": {
        "spacing": "5",
        "distribution": "fill",
        "align": "left"
      },
      "components": [
        {
          "type": "label",
          "text": "Name"
        },
        {
          "type": "label",
          "text": "Homer Simpson"
        }
      ]
    }
  ]
}

Layouts do not have size on their own. A layout is used to lay out its child components therefore only expresses how these children will be laid out.

However layouts have padding, which individual components don’t have. So if you want to add padding to anything, make sure to wrap it around with a layout.

Attribute Description
padding The padding value that surrounds the layout.
spacing Specifies spaces between each child component. For example to make sure each label is 5 pixels away from each other you would set spacing to '5’.
background Background color for the layout
z_index z-index of the layout. Default value is “0”. Specifying “-1” for example will bring that layout one level below the rest. (Google CSS z-index if you don’t know what this means)
distribution Distribution property (More below)
align Align property (More below)

Distribution property

value description
fill (Default)
equalsize
proportional
equalspace
equalcentertocenter

Align property

value description
fill (Default for Vertical layout)
firstbaseline
lastbaseline
left
top (Default for Horizontal layout)
right
bottom
center

Styling items

Style Items

{
  "$jason": {
    "head": {
      "title": "Item styling example",
      "description": "Try tweaking the values from the original code and re-open to see what happens"
    },
    "body": {
      "sections": [{
        "header": {
          "type": "vertical",
          "style": {
            "z_index": "-1"
          },
          "components": [{
            "type": "label",
            "style": {
              "color": "#000000",
              "size": "40",
              "font": "HelveticaNeue-CondensedBold"
            },
            "text": "Header"
          }]
        },
        "items": [
          {
            "type": "vertical",
            "style": {
              "background": "rgba(0,0,255,0.7)",
              "padding": "30",
              "color": "#00ff00"
            },
            "href": {
              "url": "http://www.google.com",
              "view": "Web"
            },
            "components": [{
              "type": "label",
              "text": "Regular item",
              "style": {
                "size": "40",
                "color": "#ff0000"
              }
            }]
          }
        ]
      }]
    }
  }
}
Description Required
z_index z-index for the item No
color tint color for the item. Also sets the tint color for disclosure indicator in case the item is linked to another view via href No
background background color for the item. No

Styling nav

Style the Nav

{
  "$jason": {
    "head": {
      "title": "Nav styling example",
      "description": "Try tweaking the values from the original code and re-open to see what happens"
    },
    "body": {
      "nav": {
        "style": {
          "background": "#85188F",
          "color": "#ffffff"
        }
      },
      "sections": [{
        "items": [{
          "type": "label",
          "style": {
            "padding": "20",
            "size": "30"
          },
          "text": "Row 1"
        }, {
          "type": "label",
          "style": {
            "padding": "20",
            "size": "30"
          },
          "text": "Row 2"
        }, {
          "type": "label",
          "style": {
            "padding": "20",
            "size": "30"
          },
          "text": "Row 3"
        }, {
          "type": "label",
          "style": {
            "padding": "20",
            "size": "30"
          },
          "text": "Row 4"
        }, {
          "type": "label",
          "style": {
            "padding": "20",
            "size": "30"
          },
          "text": "Row 5"
        }, {
          "type": "label",
          "style": {
            "padding": "20",
            "size": "30"
          },
          "text": "Row 6"
        }, {
          "type": "label",
          "style": {
            "padding": "20",
            "size": "30"
          },
          "text": "Row 7"
        }, {
          "type": "label",
          "style": {
            "padding": "20",
            "size": "30"
          },
          "text": "Row 8"
        }, {
          "type": "label",
          "style": {
            "padding": "20",
            "size": "30"
          },
          "text": "Row 9"
        }, {
          "type": "label",
          "style": {
            "padding": "20",
            "size": "30"
          },
          "text": "Row 10"
        }, {
          "type": "label",
          "style": {
            "padding": "20",
            "size": "30"
          },
          "text": "Row 11"
        }, {
          "type": "label",
          "style": {
            "padding": "20",
            "size": "30"
          },
          "text": "Row 12"
        }]
      }]
    }
  }
}
Description Required
color tint color for nav and the footer. default is #00000 Optional
background background color for the nav and the footer. default is #fffff. Optional

Styling the view background

{
  "$jason": {
    "head": {
      "title": "Background styling example",
      "description": "Try tweaking the values from the original code and re-open to see what happens"
    },
    "body": {
      "style": {
        "background": "#85188F"
      },
      "sections": [{
        "items": [{
          "type": "image",
          "url": "https://upload.wikimedia.org/wikipedia/en/7/7f/WillyWonkaMoviePoster.jpg"
        }]
      }]
    }
  }
}

To set the background color for the entire view, add style attribute to body itself and specify the background color by using background.

Description Required
background background color for the entire view. default is #fffff. Optional
border border color. 'none’ if you want to remove border Optional

Step 3. Interact with the view

When you touch an item or swipe on it, Jason can trigger actions or send you to another view.

There are 3 types of ways Jason can handle user events.

1. href

href sends you to another Jason view. It’s exactly like <a href = 'some link'>link</a> in HTML.

Just attach it to any UI item that can be touched.

Description Required
url Another url to visit Yes
view “Jason”, “Web”, “App” (default is 'Jason’) No
transition “modal”, “replace”, “push”, (default is 'push’) No

Types of views

There are 3 types of views that can be opened through href.

Description
Jason This is the default. A JSON url to any Jason view
Web Opens a Safari View from the app
App In case the url is an app url scheme, it can open an external app

1. Jason view example

  {
    "$jason": {
      "head": {
        "title": "Link to another Jason view"
      },
      "body": {
        "sections": [{
          "items": [{
            "type": "label",
            "text": "Touch me",
            "href": {
              "url": "https://plasticfm.herokuapp.com/things.json",
              "view": "Jason"
            }
          }]
        }]
      }
    }
  }

2. Web view example

  {
    "$jason": {
      "head": {
        "title": "Link to a web view"
      },
      "body": {
        "sections": [{
          "items": [{
            "type": "label",
            "text": "Let's google!",
            "href": {
              "url": "https://www.google.com/search?q=willy+wonka",
              "view": "Web"
            }
          }]
        }]
      }
    }
  }

3. App example

  {
    "$jason": {
      "head": {
        "title": "Link to a web view"
      },
      "body": {
        "sections": [{
          "items": [{
            "type": "label",
            "text": "Text me",
            "href": {
              "url": "sms:+12345678901",
              "view": "App"
            }
          }, {
            "type": "label",
            "text": "Mail me",
            "href": {
              "url": "mailto:test@example.com",
              "view": "App"
            }
          }, {
            "type": "label",
            "text": "Play some song",
            "href": {
              "url": "music:",
              "view": "App"
            }
          }, {
            "type": "label",
            "text": "Download an app",
            "href": {
              "url": "https://itunes.apple.com/us/app/ethan/id907037398?mt=8",
              "view": "App"
            }
          }]
        }]
      }
    }
  }

Types of transitions

By default a new view will be pushed in from the right side, just like most apps.

However you can specify transition modes to open the new view in different manners.

Description
“push” This is default. It pushes a new view from the right side.
“modal” Opens as a modal. The new modal works the same as regular views, except it comes with an “X” button to close it.
“replace” Replaces the current view with the URL instead of pushing a new view in.

2. action

  {
    "$jason": {
      "head": {
        "title": "Test"
      },
      "body": {
        "sections": [{
          "items": [{
            "type": "label",
            "text": "Touch me",
            "action": {
              "type": "$widget.toast",
              "options": {
                "text": "How dare you touch me!"
              }
            }
          }]
        }]
      }
    }
  }

We will discuss more about actions later, but it’s basically like functions in javascript, except that it executes your phone device features instead of web browser features (which a web browser does). The built-in actions can do anything from displaying a toast message to getting user geolocation to making network requests.

Attach action attribute to any UI item that can be touchd, and it will trigger the action when touched. See actions for more.

3. menu

  {
    "$jason": {
      "head": {
        "title": "Test"
      },
      "body": {
        "sections": [{
          "items": [{
            "type": "label",
            "text": "Swipe me to left",
            "menu": {
              "items": [{
                "text": " Alert ",
                "style": {
                  "background":"#ff0000"
                },
                "action": {
                  "type": "$widget.alert",
                  "options": {
                    "title": "Hello",
                    "description": "This is an alert"
                  }
                }
              }, {
                "text": " Banner ",
                "action": {
                  "type": "$widget.banner",
                  "options": {
                    "title": "Hello",
                    "description": "This is a banner"
                  }
                }
              }]
            }
          }]
        }]
      }
    }
  }

You may want to give users multiple action options for each item.

Menu shows up when you swipe left on a section item (be it an image, label, or whatever), just like what happens when you swipe left on the iOS mail app.

Menu is has an items attribute, which is an array of Buttons. Which means each buton has its own action and text.

Description Required
type “menu” Yes
items Array of Buttons Yes

Step 4. Run actions

What is an action?

Actions are like javascript in HTML. It lets you execute functions not directly related to the view.

Actions let you access all the powerful device features such as

Actions in Jason are basically equivalent to Javascript in HTML.

Basic syntax

Actions are like functions.

In most programming languages, functions have arguments and a return value.

1. Arguments

{
  "type": "$widget.alert",
  "options": {
    "title": "Proceed?",
    "description": "Tap OK to display a banner"
  },
  "success": {
    "type": "$widget.banner",
    "options": {
      "title": "Banner",
      "description": "This is a banner"
    }
  },
  "error": {
    "type": "$widget.toast",
    "options": {
      "text": "Ooops, something went wrong"
    }
  }
}

An action takes 4 arguments:

Options Description
type Name of a built-in action.
options Parameters to pass to the action (optional)
success Set this attribute if you want this action to call another action after it finishes. It’s like a callback function. (optional)
error Set this attribute if you want to handle exceptions. If anything goes wrong, this will be called instead of success. (optional)

Let’s take a look at the example action.

  1. [type] It first executes $widget.alert
  2. [options] When it does so, it passes {'title': 'Proceed?', 'description': "Tap OK to display a banner"} as argument
  3. [success] When it succeeds (when user presses 'ok’), it executes $widget.banner
  4. [error] If it fails for some reason, it executes $widget.toast

Only type is mandatory, and the rest options vary depending on the action type.

2. Return value

{
  "type": "$geo.get",
  "success": {
    "type": "$widget.banner",
    "options": {
      "title": "Your current coordinate",
      "description": "{{$jason.coords}}"
    }
  }
}

In this example, $geo.get action returns a value that looks like {"$jason": {"coords": "12.321,32.534"}} so the next action ($widget.banner) utilizes the value.

Actions can pass its return value to the next action in the call chain (through success or error)

Actions don’t always have a return value, but when it does, it returns them wrapped inside $jason object.

How to trigger an action?

Action can be triggered in three ways.

1. User interaction triggered

{
  "$jason": {
    "head": {
      "title": "I dare you to push",
      "description": "Push the item to see what happens"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "text": "Push me",
          "action": {
            "type": "$widget.toast",
            "options": {
              "text": "Don't push me bro"
            }
          }
        }]
      }]
    }
  }
}

You can attach an action to any visible UI component. Here’s an example of attaching an action to a card to trigger a toast notification when tapped:

2. Triggered by action success

{
  "$jason": {
    "head": {
      "title": "Watch this Alert triggering Banner",
      "description": "Tap the item to trigger an alert, and when you press OK, it will display a Banner"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "text": "Push me to show alert",
          "action": {
            "type": "$widget.alert",
            "options": {
              "title": "Show banner?",
              "description": "Push OK to show banner"
            },
            "success": {
              "type": "$widget.banner",
              "options": {
                "title": "Yo",
                "description": "I told you not to push me bro"
              }
            }
          }
        }]
      }]
    }
  }
}

You can attach an action as a success attribute of another action. Here’s an example of attaching a $render action after a $network.request has completed to notify users it was a success:

3. Triggered by action error

{
  "$jason": {
    "head": {
      "title": "Error triggered action",
      "description": "Tap the item to trigger a network request to an unreachable url, which will trigger an error banner"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "text": "Push me to load some unreachable url",
          "action": {
            "type": "$network.request",
            "options": {
              "url": "http://dfhdfnwenfwef.this.url.doesn't.work"
            },
            "error": {
              "type": "$widget.banner",
              "options": {
                "title": "Error",
                "description": "That url doesn't work man"
              }
            }
          }
        }]
      }]
    }
  }
}

Sometimes your action fails and you need to handle it.

For example, when you try to make a network request and your session has expired, it should send you to a login screen. You can use error attribute to do that.

4. Auto-triggered by Jason

There are certain built events that automatically get triggered in certain situations, which include:

A. $load

{
  "$jason": {
    "head": {
      "title": "$load example",
      "description": "Show alert on load",
      "actions": {
        "$load": {
          "type": "$widget.alert",
          "options": {
            "title": "This alert",
            "description": "will show up automatically when the view loads"
          }
        }
      }
    }
  }
}

This will display an alert when you open the view

Triggered automatically when a view loads

B. $pull

{
  "$jason": {
    "head": {
      "title": "$pull example",
      "description": "Try pull to refresh to listen to the sound",
      "actions": {
        "$pull": {
          "type": "$audio.play",
          "options": {
            "url": "https://s3.amazonaws.com/www.textcast.co/icons/yo.mp3",
            "inline": "true"
          }
        }
      }
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "vertical",
          "style": {
            "align": "center",
            "padding": "20",
            "z_index": "-1"
          },
          "components": [{
            "type": "image",
            "url": "https://d30y9cdsu7xlg0.cloudfront.net/png/126349-200.png",
            "style": {
              "z_index": "-1",
              "width": "100"
            }
          }]
        }]
      }]
    }
  }
}

This will play an audio when you pull to refresh.

Triggered automatically when a user does a pull to refresh gesture

See Actions registry to learn more.

API Reference

Basic

1. $reload

Refreshes the view by re-fetching content from the current URL.

{
  "items": [{
    "type": "label",
    "action": {
      "type": "$reload"
    }
  }]
}

This will reload the page when a user taps on the item.

2. $menu

{
  "nav": {
    "items": [{
      "type": "menu",
      "options": {
        "image": "http://openjason.org/menu.png",
        "items": [{
          "text": "Menu 1",
          "action": {...}
        }, {
          "text": "Menu 2",
          "action": {...}
        }, {
          "text": "Menu 3",
          "action": {...}
        }]
      }
    }]
  }
}

In this case the nav menu item triggers $menu action. But $menu action can be triggered by any other events!

Opens a dropdown menu from the nav.

Description Required
image Image to display Optional
text Text to display Optional
items Array of menu items (Each has its own text and action attributes) YES

Native widgets

Native interfaces that are not part of the main view

1. $widget.banner

Displays a banner notification with title and description.

2. $widget.toast

Displays a toast notification with a simple text

Option Description
text Text to display
type “success”, “error”, “info”, “warning”

3. $widget.alert

Displays an alert. The alert can also have inputs which users can fill in, and will return the key/value pairs wrapped with $jason.

Option Description
title title of the alert
description description
form takes an array of form fields, each of which has name and value attribute.
{
    "form": [{
      "name": "username_attribute",
      "value": "Enter username here"
    }, {
      "name": "password_attribute",
      "type": "secure"
    }]
}
Option Description
type Specify {"type": "secure"} to make it a secure field
name attribute key
value to preset the field, just add the value attribute.
{
  "$jason": {
    "username_attribute": "ethan",
    "password_attribute": "password"
  }
}

Above result will be returned by $widget.alert. You can chain another action as a success callback and use the attributes by referencing $jason.username_attribute or $jason.password_attribute for example. See below for example:

{
  "type": "$widget.alert",
  "options": {
    "title": "Sign in",
    "description": "Please enter username and password",
    "form": [{
      "type": "text",
      "name": "username",
      "value": ""
    }, {
      "type": "secure",
      "name": "password",
      "value": ""
    }]
  },
  "success": {
    "type": "$network.request",
    "options": {
      "url": (SIGN_IN_URL),
      "method": "post",
      "data": {
        "username": "{{$jason.username}}",
        "password": "{{$jason.password}}"
      }
    }
  }
}

An example network request made after user enters username and password from $widget.alert

4. $widget.share

Opens a share sheet with any text passed to as options.text

{
  "type": "$widget.share",
  "options": {
    "text": "{{$jason.url}}"
  }
}

5. $widget.picker

Opens an picker menu with each item linking to an Action or an Href.**

Options description
items Array of items. Each item can have a text and action attribute, or text and href attribute.
Returns
None

Note

The picker itself doesn’t have any return value but its items might. For example, if a user selects an item that executes a $geo.get, the $geo.get action will have its own return value.

Example

{
  "type": "$widget.picker",
  "options": {
    "items": [
      {
        "text": "Get current location",
        "action": {
          "type": "$geo.get",
          "success": {
            "type": "$widget.alert",
            "options": {
              "title": "Your current location",
              "description": "{{$jason.coords}}"
            }
          }
        }
      },
      {
        "text": "Display a toast",
        "action": {
          "type": "$widget.toast",
          "options": {
            "text": "Oh hello there"
          }
        }
      },
      {
        "text": "Doh!",
        "action": {
          "type": "$audio.play",
          "options": {
            "url": "http://www.thesoundarchive.com/simpsons/homer/doh1.mp3",
            "inline": "true"
          }
        }
      }
    ]
  }
}

Try!

{
  "$jason": {
    "head": {
      "title": "$pull example",
      "description": "Try pull to refresh to listen to the sound"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "text": "banner",
          "action": {
            "type": "$widget.banner",
            "options": {
              "title": "Title goes here",
              "description": "Description goes here"
            }
          }
        }, {
          "type": "label",
          "text": "toast",
          "action": {
            "type": "$widget.toast",
            "options": {
              "text": "Text goes here"
            }
          }
        }, {
          "type": "label",
          "text": "alert",
          "action": {
            "type": "$widget.alert",
            "options": {
              "title": "Title goes here",
              "description": "Description goes here",
              "form": [{
                "name": "username_attribute",
                "value": "Enter username here"
              }, {
                "name": "password_attribute",
                "type": "secure"
              }]
            },
            "success": {
              "type": "$widget.banner",
              "options": {
                "title": "Username: {{$jason.username_attribute}}",
                "description": "Password: {{$jason.password_attribute}}"
              }
            }
          }
        }, {
          "type": "label",
          "text": "share",
          "action": {
            "type": "$widget.share",
            "options": {
              "text": "Any text you want to share"
            }
          }
        }, {
          "type": "label",
          "text": "picker",
          "action": {
            "type": "$widget.picker",
            "options": {
              "items": [{
                "text": "trigger banner",
                "action": {
                  "type": "$widget.banner",
                  "options": {
                    "title": "Banner triggered!",
                    "description": "This banner was triggered from a picker"
                  }
                }
              }, {
                "text": "Open google",
                "action": {
                  "type": "$href",
                  "options": {
                    "url": "https://www.google.com",
                    "view": "Web"
                  }
                }
              }]
            }
          }
        }]
      }]
    }
  }
}

DEVICE ACCESS

1. $media.camera

Capture a video, photo, or a animated gif**

{
  "$jason": {
    "head": {
      "title": "Camera",
      "description": "Tap to open up camera"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "text": "Take a pic",
          "action": {
            "type": "$media.camera",
            "options": {
              "edit": "true",
              "type": "photo"
            },
            "success": {
              "type": "$widget.alert",
              "options": {
                "title": "Stored {{$jason['content-type']}} at",
                "description": "{{$jason.url}}"
              }
            }
          }
        }]
      }]
    }
  }
}

Normally you will want to do with the photo or video you took. You can upload your media to cloud storage such as s3. See $network.upload for more.

Return value

{
  "$jason": {
    "url": (FILE_URL),
    "data": (NSDATA),
    "content-type": ("video/mp4"|"image/png")
  }
}

$media.camera returns an object that looks like above. You can chain the action to another action and utilize the $jason object.

2. $media.play

Plays a video from remote url.

Options description
url Remote url to stream from.
inline 'true’ if video is embedded in a component via video type. When played inline, the video will have the frame value of its parent item.
muted 'true’ to mute the sound.
Return
None
{
  "$jason": {
    "head": {
      "title": "Video",
      "description": "Tap to play the video"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "image",
          "url": "https://vjs.zencdn.net/v/oceans.png",
          "action": {
            "type": "$media.play",
            "options": {
              "url": "https://vjs.zencdn.net/v/oceans.mp4",
              "inline": "true"
            }
          }
        }]
      }]
    }
  }
}

An example snippet to trigger $media.play action.

3. $media.picker

Opens a media picker (camera roll).**

Options description
edit true (optional. provides an option to edit media)
type photo / gif / video

Returns value

{
  "$jason": {
    "url": (FILE_URL),
    "data": (RAW_DATA),
    "content-type": ("video/mp4"|"image/png")
  }
}

Example

{
  "$jason": {
    "head": {
      "title": "Media picker",
      "description": "Tap to select media"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "text": "Select media",
          "action": {
            "type": "$media.picker",
            "options": {
              "edit": "true",
              "type": "photo"
            },
            "success": {
              "type": "$widget.alert",
              "options": {
                "title": "Selected {{$jason['content-type']}} at",
                "description": "{{$jason.url}}"
              }
            }
          }
        }]
      }]
    }
  }
}

Note

To learn more about how to upload/send the picked/created media, see here.

4. $audio.play

Play audio from remote url

Options Description
url A remote url to stream audio from.
inline set 'true’ to play a short audio clip without opening the player interface.
Return value
None

Example

{
  "$jason": {
    "head": {
      "title": "Play audio"
    }, 
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "text": "Yo",
          "action": {
            "type": "$audio.play",
            "options": {
              "url": "https://s3.amazonaws.com/www.textcast.co/icons/yo.mp3",
              "inline": "true"
            }
          }
        }]
      }]
    }
  }
}

5. $audio.record

Record audio**

Options
None
Returns
url
content-type

Example

{
  "$jason": {
    "head": {
      "title": "Play audio"
    }, 
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "text": "Record Now",
          "action": {
            "type": "$audio.record",
            "success": {
              "type": "$widget.alert",
              "options": {
                "title": "Audio stored at",
                "description": "{{$jason.url}}"
              }
            }
          }
        }]
      }]
    }
  }
}

1. $geo.get

Get user’s location**

Options
none
Returns
coord
{
  "$jason": {
    "head": {
      "title": "Right Here",
      "description": "Searching anything nearby, links to yelp, google streetview and foursquare"
    },
    "body": {
      "sections": [
        {
          "items": [
            {
              "type": "label",
              "text": "Street View",
              "style": {
                "size": "40",
                "font": "HelveticaNeue-CondensedBold",
                "color": "#000000"
              },
              "action": {
                "type": "$geo.get",
                "success": {
                  "type": "$href",
                  "options": {
                    "url": "http://maps.google.com/maps?q=&layer=c&cbll={{$jason.coord}}&cbp=11,0,0,0,0",
                    "view": "App"
                  }
                }
              }
            },
            {
              "type": "label",
              "text": "Yelp",
              "style": {
                "size": "40",
                "font": "HelveticaNeue-CondensedBold",
                "color": "#000000"
              },
              "action": {
                "type": "$geo.get",
                "success": {
                  "type": "$href",
                  "options": {
                    "url": "http://www.yelp.com/search?find_desc=food&cll={{$jason.coord}}&ns=1",
                    "view": "App"
                  }
                }
              }
            },
            {
              "type": "label",
              "text": "Foursquare",
              "style": {
                "size": "40",
                "font": "HelveticaNeue-CondensedBold",
                "color": "#000000"
              },
              "action": {
                "type": "$geo.get",
                "success": {
                  "type": "$href",
                  "options": {
                    "url": "https://foursquare.com/explore?ll={{$jason.coord}}&mode=url&q=Food",
                    "view": "App"
                  }
                }
              }
            }
          ]
        }
      ]
    }
  }
}

Local variable

Use $set and $get to set and get local variables**

1. $set

$set action is used to set local variable.

Here’s how to set ethan and gliechtenstein to variables named firstname and lastname:

{
  "type": "$set",
  "options": {
    "firstname": "ethan",
    "lastname": "gliechtenstein"
  }
}

This is how you set a variable. We are setting local variables firstname and lastname to ethan and gliechtenstein respectively.

Note

2. $get

You can access the local variables you set by referring to {{$get.VARIABLE_NAME}}

[
  {
    "type": "label",
    "type": "{{$get.firstname}}"
  },
  {
    "type": "label",
    "type": "{{$get.lastname}}"
  }
]

Assuming that we have already set the local variables firstname and lastname from above, we can utilize them using the $get notation. Again, this won’t automatically update, so we will need to run a $render

[View tap to change color example]

Key storage

Use $keys to access secure local key storage powered by keychain

Note

Here’s an example code for requesting a user’s keys for oauth usage:

{
  "type": "$oauth.request",
  "options": {
    "key": "{{$keys.client_id}}",
    "secret": "{{$keys.client_secret}}",
    "path": "/v1/comments",
    "scheme": "https",
    "host": "api.producthunt.com"
  },
  "error": {
    "trigger": "login"
  },
  "success": {
    "type": "$render"
  }
}

Why do we need this?

Sometimes you need to have a way for users to fill out a field so your Jason can behave in a custom manner. One of those cases is OAuth (3rd party cloud access).

For example, let’s say you wrote a Jason that functions as a Twitter client that fetches tweets from any user’s account.

To access Twitter API you need to provide API keys. If you hardcoded that API key into your Jason, everyone who uses your Jason will be using your API quota. Which means you may reach your API limit very quickly.

To solve this issue we take a Bring Your Own Keys approach.

You can build a Jason that only provides a template (which describes how the view will behave, what data it will fetch, etc.). But as for accessing data, every user fills in their own API key to make API requests. That way you don’t have to worry about your API limites being used up by other people.

How does it work?

When you include $keys reference anywhere in your Jason markup, it will automatically detect it and present an intro view that looks like this when a user first tries to use your Jason:

Here’s a Giphy example

giphy_intro.png

Here’s a Twitter example

twitter_intro.png

Once presented with this view, users will need to fill in the blanks using their own keys to proceed.

When they tap the empty item, it will open up a modal that looks like this (actual keys masked in this screenshot for security reasons):

addakey.png

This is where users can manage their keys. They can add key/value pairs like this:

addakey.png

Adding keys to the key manager doesn’t mean anything yet. The user has to explicitly allow your Jason to access the key you selected everytime they start using a new Jason.

Note

Just having the $keys code in there doesn’t mean anything. They are just a way to declare that the user will need to fill them using a variable in their key storage.

Whenever there’s a code that references $keys, users will be shown a intro page which requests the values.

Only after they explicitly fill out the keys from the key storage view will they land on the actual page.

Here’s what it looks like:

Whenever you need a user Once you set a key, you can access them

{
  "type": "$cache.set",
  "options": {
    "firstname": "ethan",
    "lastname": "gliechtenstein"
  }
}

Cache

Use $cache to persist and reaccess content**

Note

$cache is sandboxed per url. Therefore anything you store to $cache is stored just for that document.

1. Storing value to $cache

$cache.set action is used to store value to any key localstorage.

Options

Return value

Example

The following code will store the oauth.request result to cache before rendering it.

First it fetches content from API_URL, and sets the result ($jason) as $cache["tweets"]. Then it renders $cache ($cache.set always passes on the entire $cache to the next action as $jason).

{
  "type": "$oauth.request",
  "options": {
    "url": API_URL
  },
  "success": {
    "type": "$cache.set",
    "options": {
      "tweets": "{{$jason}}"
    },
    "success": {
      "type": "$render"
    }
  }
}

Without caching it would have looked like this:

{
    "type": "$oauth.request",
    "options": {
      "url": API_URL
    },
    "success": {
      "type": "$render"
    }
 }

2. Retrieving from cache

Directly access $cache variable to access cache.

Example

Once you set cache, you can easily access them just like you would access a variable. For example, you could store a tracking_keyword value locally and automatically perform a search whenever the document loads.

{
  "type": "$oauth.request",
  "options": {
    "url": SEARCH_URL,
    "data": {
      "search_query": "{{$cache.tracking_keyword}}"
    }
  }
}       

[Advanced] Client-side render

Client-side rendering means the Jason markup is generated dynamically on the client-side.

Instead of a full Jason markup, the server can return:

  1. Data: Pure data without any of Jason markup.
  2. Template: A template to parse the data into a Jason markup.

Without templates

{
  "$jason": {
    "head": {
      "title": "The simpsons"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "text": "Homer"
        }, { 
          "type": "label",
          "text": "Marge"
        }, { 
          "type": "label",
          "text": "Bart"
        }, { 
          "type": "label",
          "text": "Lisa"
        }, { 
          "type": "label",
          "text": "Maggie"
        }]
      }]
    }
  }
}

For example, instead of returning a full Jason markup like this example, we can use a template/data approach below.

With templates

{
  "$jason": {
    "head": {
      "data": {
        "family": "Simpson",
        "members": [ { "name": "Homer" }, { "name": "Marge" }, { "name": "Bart" }, { "name": "Lisa" }, { "name": "Maggie" } ]
      },
      "templates": {
        "body": {
          "sections": [{
            "items": {
              "{{#each members}}": {
                "type": "label",
                "text": "{{name}}"
              }
            }
          }]
        }
      }
    }
  }
}

The {{ … }} notation is the templating part. The words inside curly braces are variables. Variables are filled in with data from the data object.

We can use a template to dynamically render the data. Here we have separated data from its presentation (Template).

Note that:

  1. We have two new attributes–data and templates–under head.
  2. There is no body

Since there is no body, nothing gets rendered by default.

However Jason checks the head at load time to see if there are any templates and data to be rendered, then renders them if they exist.

Here’s the parsing process:

  1. Jason loads the initial JSON file.
  2. Does it contain body?
    • YES => Render the body
    • NO => Go to step 3.
  3. Does it contain templates and data?
    • YES => Use templates.body to render data.
    • NO => Don’t render anything.

Why is this useful?

Client-side rendering is useful because we can separate data from its representation. This means we can:

  1. Instantly Jasonify my existing webapp/API

    • If you already have a web app that serves json data, you can simply write a single Jason template to parse the data instead of rewriting the entire API.
  2. Take a template and render different source data.

    • Render data fetched from 3rd party API with my own template (For example, write a template that functions as a “Twitter app”–the data is fetched from Twitter API, but the way tweets are presented is decided by my own template)
    • Render local data (The data doesn’t even need to be from remote sources, you can even render locally stored data fetched from $cache. (Read Executing actions)
  3. Take a data and render using different templates.

    • You can take a piece of data and render it using various templates, and use the one you like the best.
  4. Take a data and take all kinds of action on it.

    • Templates are not just visual. They translate into a fully functional Jason markup, which means you can even dynamically generate actions.

Template reference

1. {{ }} notation

{
  "$jason": {
    "head": {
      "title": "Display location",
      "data": {
        "coords": "12.1234,23.2345"
      },
      "templates": {
        "body": {
          "sections": [{
            "items": [
              {
                "type": "label",
                "text": "Latitude: {{coords.split(',')[0]}}"
              },
              {
                "type": "label",
                "text": "Longitude: {{coords.split(',')[1]}}"
              }           
            ]
          }]
        }
      }
    }
  }
}

Full JSON file. Its head contains data and templates, so we will be rendering client-side. We will use the body template to render the data.

{
  "data": {
    "coords": "12.1234,23.2345"
  }
}

Here’s the data part. coords is a variable whose value is “12.1234,23.2345”.

{
  "items": [
    {
      "type": "label",
      "text": "Latitude: {{coords.split(',')[0]}}"
    },
    {
      "type": "label",
      "text": "Longitude: {{coords.split(',')[1]}}"
    }           
  ]
}

We will parse the coords variable using this template. The expressions inside the curly brackets are pure javascript expressions.

{
  "$jason": {
    "head": {
      "title": "Display location",
      "data": {
        "coords": "12.1234,23.2345"
      },
      "templates": {
        "body": {
          "sections": [{
            "items": [
              {
                "type": "label",
                "text": "Latitude: 12.1234"
              },
              {
                "type": "label",
                "text": "Longitude: 23.2345"
              }           
            ]
          }]
        }
      }
    }
  }
}

Here’s the resulting Jason

2. Loop (#each)

{
  "data": {
    "family": "Simpson",
    "members": [
      {
        "name": "Homer"
      },
      {
        "name": "Marge"
      },
      {
        "name": "Lisa"
      },
      {
        "name": "Bart"
      },
      {
        "name": "Maggie"
      }
    ]
  }
}

We want to iterate through each member and render labels.

{
  "items": {
    "{{#each members}}": {
      "type": "label",
      "text": "{{name}}"
    }
  }
}

This is the template. #each keyword is used to iterate through a variable that comes after it.

{
  "items": [
    {
      "type": "label",
      "text": "Homer"
    },
    {
      "type": "label",
      "text": "Marge"
    },
    {
      "type": "label",
      "text": "Lisa"
    },
    {
      "type": "label",
      "text": "Bart"
    },
    {
      "type": "label",
      "text": "Maggie"
    }
  ]
}

We will end up with the following result

In many cases we need to iterate through a large array of items.

3. Conditionals (#if/#elseif/#else)

[
    {
        "{{#if (EXPRESSION A)}}": (Jason markup A)
    },
    {
        "{{#elseif (EXPRESSION B)}}": (Jason markup B)
    }
    {
        "{{#else (EXPRESSION C)}}": (Jason markup C)
    }
]

Here, the template will go through the items in the array sequentially until it encounters an expression that’s true. Then it will only render the corresponding markup.

Example

{
  "data": {
    "name": "Homer"
  }
}

Let’s say we have this data

{
    "type": "label",
    "text": [
        {
            "{{#if name=='Bart'}}": "Ay Caramba!"
        },
        {
            "{{#elseif name=='Homer'}}": "Donuts..."
        }
    ]
}

And have a template

{
    "type": "label",
    "text": "Donuts..."
}

This would be the rendered result.

4. “this”

Being able to use full fledged javascript expression means we can use things like this. This is useful in cases where we need to access the current context. For example when we are trying to iterate through memebers in the following json:

{
  "family": "Simpson",
  "members": ["Homer", "Marge", "Lisa", "Bart", "Maggie"]
}

Here we can iterate through members, but the items are not objects and therefore cannot be referred to with a key. We need a way to refer to the item itself. In this case we use this.

{
  "{{#each members}}": {
    "type": "label",
    "text": "{{this}}"
  }
}

[Advanced] Actions registry

How it works

This is like a <script> tag in javascript.

You register action names under actions under head, and then call it from anywhere with:

"trigger": "ACTION_NAME"

Here’s how:

{
    "head": {
        "actions": {
            "signout_action": {
                "type": "$network.request",
                "options": {
                    "url": "http://simpspsonssocialnetwork.com/sign_out.json",
                    "method": "delete"
                },
                "success": {
                    "type": "$reload"
                }
            }
        }
    },
    "body": { ... }
}

Step 1. Register our action under actions. Here it’s called signout_action.

{
    "head": { ... },
    "body": {
    "nav": {
      "items": [{
        "type": "menu",
        "text": "Sign out",
        "action": {
          "trigger": "lets_signout"
        }
      }]
    }
  }
}

Step 2. Call it from anywhere by using trigger keyword

{
  "$jason": {
    "head": {
      "actions": {
        "signout_action": {
          "type": "$network.request",
          "options": {
            "url": "http://simpspsonssocialnetwork.com/sign_out.json",
            "method": "delete"
          },
          "success": {
            "type": "$reload"
          }
        }
      }
    },
    "body": {
      "nav": {
        "items": [{
          "type": "menu",
          "text": "Sign out",
          "action": {
            "trigger": "lets_signout"
          }
        }]
      }
    }
  }
}

Here’s the full code

Automatically triggered actions

These are automatically triggered built-in events that you can attach your custom actions to.

$load

{
  "$jason": {
    "head": {
      "title": "Do something when the view loads",
      "actions": {
        "$load": {
          "trigger": "show_welcome"
        }, 
        "show_welcome": {
          "type": "$widget.banner",
          "options": {
            "title": "Welcome",
            "description": "The view has loaded"
          }
        }
      }
    }
  }
}

This triggers the action show_welcome, which displays $widget.banner when the view loads

$load is triggered automatically when a view loads.

This is especially useful for $network.request calls or $oauth.request calls because it can dynamically fetch content on load event and render them using templates (See AJAX and Client-side render section)

$pull

{
  "$jason": {
    "head": {
      "title": "Play audio when pulled to refresh",
      "actions": {
        "$load": {
          "trigger": "yo"
        }, 
        "yo": {
          "type": "$audio.play",
          "options": {
            "url": "https://s3.amazonaws.com/www.textcast.co/icons/yo.mp3",
            "inline": "true"
          }
        }
      }
    }
  }
}

This will play an audio file when pulled to refresh. Of course you can connect any other kinds of action to this event.

$pull is triggered automatically when a user does a pull to refresh gesture.

[Advanced] Stylesheet

You can style any component this way, but sometimes you want to keep all style related expressions in one place.

Redundant inline styles

Here’s an example to demonstrate when this is especially true. Basically, when we have a huge number of repeating elements, we end up with mostly redundant style attributes like this: Notice how each item has the same style.

{
  "$jason": {
    "head": {
      "title": "Ordinary styling"
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "style": {
            "font": "HelveticaNeue-CondensedBold",
            "size": "30",
            "padding": "10",
            "color": "#ff0000"
          },
          "text": "Bart"
        }, {
          "type": "label",
          "style": {
            "font": "HelveticaNeue-CondensedBold",
            "size": "30",
            "padding": "10",
            "color": "#ff0000"
          },
          "text": "Homer"
        }, {
          "type": "label",
          "style": {
            "font": "HelveticaNeue-CondensedBold",
            "size": "30",
            "padding": "10",
            "color": "#ff0000"
          },
          "text": "Marge"
        }, {
          "type": "label",
          "style": {
            "font": "HelveticaNeue-CondensedBold",
            "size": "30",
            "padding": "10",
            "color": "#ff0000"
          },
          "text": "Lisa"
        }, {
          "type": "label",
          "style": {
            "font": "HelveticaNeue-CondensedBold",
            "size": "30",
            "padding": "10",
            "color": "#ff0000"
          },
          "text": "Maggie"
        }]
      }]
    }
  }
}

Stylesheet based styling

{
  "$jason": {
    "head": {
      "title": "Stylsheet based styling",
      "styles": {
        "firstname": {
          "font": "HelveticaNeue-CondensedBold",
          "size": "30",
          "padding": "10",
          "color": "#ff0000"
        }
      }
    },
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "class": "firstname",
          "text": "Bart"
        }, {
          "type": "label",
          "class": "firstname",
          "text": "Homer"
        }, {
          "type": "label",
          "class": "firstname",
          "text": "Marge"
        }, {
          "type": "label",
          "class": "firstname",
          "text": "Lisa"
        }, {
          "type": "label",
          "class": "firstname",
          "text": "Maggie"
        }]
      }]
    }
  }
}

To solve this problem you can use a stylesheet. Here’s how to do it:

  1. Instead of style object, specify a class.
  2. Then, add the class under the stylesheet (styles attribute of head).

In this example, we’ve named the class firstname.

Without stylesheet appraoch we would have had to specify the the same style for every single item, making it look something like this:

[Advanced] AJAX

Sorry about the naming, it’s not really Ajax but couln’t think of a better term to describe this yet.

If you don’t know what an AJAX is, it’s a web browser technology that allows a page to dynamically update without rereshing the entire page.

Try Demo first

First try the available demos in the app. [Link]

Render with AJAX fetched data

Previously we saw that a template inside templates will automatically render the data inside data on load time and render it.

We can take one step further and dynamically fetch the data after the page loads, instead of including all the data in one JSON.

  1. Only fetch the template once and repopulate incoming data by just fetching the data

    • Most cases the template won’t change wheenver you reload stuff, so why not just load data once the template has loaded?
  2. Build a Jason view without even touching your existing API.

    • You can just write a template that will parse your existing data APIs

This is where the AJAX concept comes in. Here’s how it works:

  1. Fetch the initial JSON and see if it contains $load handler
  2. If $load handler

Example

First, check out the API we will be parsing http://imagejason.herokuapp.com/db

Example API Data

It returns a JSON that looks like this:

{
  "response": {
    "db": [
      {
        "_id": "571a4ebe44096303002092f4",
        "url": "https://s3-us-west-2.amazonaws.com/fm.ethan.jason/CD8D15B0-978A-4823-A687-F048EA728257.jpeg",
        "__v": 0
      },
      {
        "_id": "571a4e9144096303002092f3",
        "url": "https://s3-us-west-2.amazonaws.com/fm.ethan.jason/45C8A405-FD8E-4574-B155-3B84BD902B5E.jpeg",
        "__v": 0
      },
      {
        "_id": "5719a2cd6bb4cc03002f5bbf",
        "url": "https://s3-us-west-2.amazonaws.com/fm.ethan.jason/6693C2F6-8830-41ED-8B7D-49CB22FE1E01.jpeg",
        "__v": 0
      },
      {
        "_id": "5719907f1aa0f80300c55277",
        "url": "https://s3-us-west-2.amazonaws.com/fm.ethan.jason/5F6CD831-3F6A-4F81-90B1-4BE744EC73EA.jpeg",
        "__v": 0
      },
      {
        "_id": "571982bd0eb36403003c681f",
        "url": "https://s3-us-west-2.amazonaws.com/fm.ethan.jason/C4B3C0D9-1D7A-4F76-9F13-C4E549A8179E.jpeg",
        "__v": 0
      },
      ...
    ]
  }
}

It’s a pure data JSON and is not a Jason markup.

Let’s try to render this API data by using AJAX approach.

Rendering the API data using AJAX

Things to keep in mind:

  1. We first call $network.request to fetch data from the url
  2. When $network.request returns with data, it goes to the next step by looking at success callback.
  3. In this case $render is the success callback, and gets executed with the network data.
  4. All actions always return data wrapped inside $jason key. $network.request is no exception.

At this point, the data we have will loook like this:

{
  "$jason": {
    "response": {
      "db": [
        {
          "_id": "571a4ebe44096303002092f4",
          "url": "https://s3-us-west-2.amazonaws.com/fm.ethan.jason/CD8D15B0-978A-4823-A687-F048EA728257.jpeg",
          "__v": 0
        },
        {
          "_id": "571a4e9144096303002092f3",
          "url": "https://s3-us-west-2.amazonaws.com/fm.ethan.jason/45C8A405-FD8E-4574-B155-3B84BD902B5E.jpeg",
          "__v": 0
        },
        ..
      ]
    }
  }
}

Result Jason

Therefore we need a template that looks like this:

{
  "$jason": {
    "head": {
      "actions": {
        "$load": {
          "type": "$network.request",
          "options": {
            "url": "http://imagejason.herokuapp.com/db"
          },
          "success": {
            "type": "$render"
          }
        }
      },
      "templates": {
        "body": {
          "sections": [{
            "items": {
              "{{#each $jason.response.db}}": {
                "type": "image",
                "url": "{{url}}"
              }
            }
          }]
        }
      }
    }
  }
}

Notice the $load event is triggering $network.request action. This is the AJAX action that fetches and passes the data. When it succeeds, it calls $render action with the fetched data wrapped inside $jason.

AJAX Actions

1. $render

In the client side render section we saw that we can return a single JSON file that contains both a data and a template and automatically render them at load time.

However there are cases where this isn’t enough. For example you may want to render:

  1. data you have no control over, such as 3rd party cloud APIs.
  2. data you generate just by using the app.
  3. data stored locally on user device.

Meet $render.

$render is an action that can be called anytime throughout the view lifecycle to trigger redrawing of the view based on any template and any data passed to it.

{
  "type": "$network.request",
  "options": {
    "url": "SOME API URL"
  },
  "success": {
    "type": "$render"
  }
}

An example $render snippet. A network request triggers $render when it succeeds. The $network.request action automatically passes the data it fetched to $render.

Options description
template To specify a template to render. By default it’s body and normally you don’t need to specify this.
data (optional) To specify data to render. Normally render is called at the last stage of an action call chain to render the result passed in from its previous action. However sometimes you can explicitly set data attribute to render any data.
Returns
None

$render vs. $reload

There are two ways to redraw views:

  1. $reload

    • Full refresh of the current view by re-fetching content from the url.
  2. $render

    • Takes a data and parses it with a template, and then render it in the view.
    • Requires a template under head
    • Requires data. The data can be:
    • embedded in the original Jason
    • dynamically generated AFTER the page load as a result of any action.

$render requires two things:

  1. data: Of course we need something to render. This should have been generated by the action that triggered $render. This can be anything from $network.request to $geo.get.

  2. template: Once we have the data, we want to present the data in a way we desire. We use template to do that.

2. $network.request

{
  "type": "$network.request",
  "options": {
    "url": (URL),
    "method": (METHOD),
    "data": (PARAMETER),
    "headers": (HEADER)
  },
  "success": (ACTION)
}

$network.request takes the following format:

Options Description
url The url to access.
method can be get, post, put, delete.
data Parameters to send along with the url (optional)
headers Headers to attach to every request if any (optional)

Here’s an example:

{
  "type": "$network.request",
  "options": {
    "url": "http://plasticfm.herokuapp.com/things/3.json",
    "type": "get"
  },
  "success": {
    "type": "$render"
  }
}

3. $network.auth

{
  "type": "$network.auth",
  "options": {
    "url": (URL),
    "method": (METHOD),
    "data": (PARAMETER),
    "headers": (HEADER)
  },
  "success": (ACTION)
}

Typical $network.auth format

Authentication to a server.

Options Description
url The url to access.
method can be get, post, put, delete.
data Parameters to send along with the url (optional)
headers Headers to attach to every request if any (optional)

Similar to $network.request, except for a couple of things:

{
  "$session": {
    "headers": (SESSION TO APPEND TO EVERY REQUEST HEADER),
    "body": (SESSION TO APPEND TO EVERY REQUEST BODY)
  }
}

The server must return a response with this format to a $network.auth request. Then Jason will automatically store the $session object for later usage. See below for an actual example.

{
  "$session": {
    "headers": {
      "username": "ethan",
      "auth_token": "39fj3lsf9djfjs"
    }
  }
}

In this case, the server returns these session objects to notify Jason that it should attach these parameters to headers whenever the client makes request to this domain.

Jason will store the session object for the domain you’re accessing, and it will automatically attach the session object to $network.request calls to that domain in the future.

4. $network.unauth

{
  "type": "$network.unauth",
  "options": {
    "url": (URL),
    "domain": (DOMAIN)
  },
  "success": (ACTION)
}

This clears session for a domain created by $network.auth

[Advanced] OAUTH

Try Demo first

First try the available demos in the app. [Link]

CLOUD API ACCESS (OAUTH)

Access cloud APIs via Oauth.**

Supports OAuth1, OAuth2, and iOS Social Framework**

1. $oauth.auth

$oauth.auth takes different format depending on Oauth version.

OAuth2**

The default value for version is 2. Which means we don’t need to specify version whent it’s OAuth2.

Oauth2 requires authorize and access configurations. It is best to explain the specification by looking at an example:

{
  "type": "$oauth.auth",
  "options": {
    "authorize": {
      "key": "{{$keys.client_id}}",
      "secret": "{{$keys.client_secret}}",
      "scheme": "https",
      "host": "api.producthunt.com",
      "path": "/v1/oauth/authorize",
      "data": {
        "redirect_uri": "jason://oauth",
        "response_type": "code",
        "scope": "public"
      }
    },
    "access": {
      "key": "{{$keys.client_id}}",
      "secret": "{{$keys.client_secret}}",
      "scheme": "https",
      "host": "api.producthunt.com",
      "path": "/v1/oauth/token",
      "token_in_body": "true",
      "data": {
        "grant_type": "authorization_code",
        "redirect_uri": "jason://oauth"
      }
    }
  },
  "success": {
    "trigger": "fetch"
  }
}

OAuth1**

Oauth1 requires request, authorize and access configurations. Here’s a working example of Tumblr Oauth authentication action:

{
  "type": "$oauth.auth",
  "options": {
    "version": "1",
    "request": {
      "key": "{{$keys.key}}",
      "secret": "{{$keys.secret}}",
      "scheme": "https",
      "host": "www.tumblr.com",
      "path": "/oauth/request_token",
      "data": {
        "oauth_callback": "json://oauth"
      }
    },
    "authorize": {
      "key": "{{$keys.key}}",
      "secret": "{{$keys.secret}}",
      "scheme": "https",
      "host": "www.tumblr.com",
      "path": "/oauth/authorize"
    },
    "access": {
      "key": "{{$keys.key}}",
      "secret": "{{$keys.secret}}",
      "scheme": "https",
      "host": "www.tumblr.com",
      "path": "/oauth/access_token"
    }
  },
  "success": {
    "trigger": "reload"
  }
}

Note that version “1” has been specified since it’s Oauth1.

Most configuration attributes are straight-forward if you know the Oauth spec (or read the API provider’s API document), so I will explain some notable attributes

options description
key OAuth client_id
secret OAuth client_secret if it exists

To get either the key or the secret, you need to visit the API provider’s API website and create an app. Then it will give you a key, and a secret (if they require one).

Here are some urls where you can get API keys for each service. However most modern apps/services provide an API so you can just google (APP NAME) api key or (APP NAME) create app and will find the page where you can get API keys.

Service Where to generate the key
Twitter https://apps.twitter.com/
Reddit https://ssl.reddit.com/prefs/apps/
Foursquare https://foursquare.com/developers/apps
New York Times http://developer.nytimes.com/apps/register
Instagram https://www.instagram.com/developer/register/
Github https://github.com/settings/applications/new
Slack https://api.slack.com/apps/new
Mailchimp https://us8.admin.mailchimp.com/account/api/
Dark Sky https://developer.forecast.io/

How to secure your keys

You can quickly try out OAuth calls by simply hard-code your API keys into your Jason to start using it like this:

{
  "type": "$oauth.auth",
  "options": {
    "authorize": {
      "key": "84gfh2ldks23jfsdkj",
      "secret": "a9djf_dn3kd9dnfkefKdlefje",
      "scheme": "https",
      "host": "api.producthunt.com",
      "path": "/v1/oauth/authorize",
      "data": {
        "redirect_uri": "jason://oauth",
        "response_type": "code",
        "scope": "public"
      }
    }
  }
}

The problem with this is, if you share this Jason so others can try it out, they will be using up your API quota. This is not secure and also not good for your own usage either.

To solve this issue we use $keys, which lets users manage their keys securely and let Jason vies use them only when they approve. See Key storage section to learn how to do this.

Note

Most configuration attributes are straight-forward if you know the Oauth spec (or read the API provider’s API document), so I will explain some of the notable attributes

key | client_id (The oauth client id you get by creating an app from the API provider’s API console)

secret | client_secret (The oauth client secret you get by creating an app from the API provider’s API console)

Since these are sensitive data, we normally use the built-in secure key storage to let users fill out these fields. That’s why you see the {{$keys.client_id}} and {{$keys.client_secret}} notation. You can learn more about how $keys works here.

token_in_body | This attribute determines whether authentication token will be sent over header or body. If true, it means the token is sent over body. Otherwise it’s sent over header.

headers | Not shown in the example but just like $network.request you can send data over header simply by adding headers attribute.

2. $oauth.request

$oauth.request takes different format depending on Oauth version.

Once authenticated, $oauth.request works pretty much the same across all oauth versions.

Just make sure to specify the oauth version if it’s not Oauth2.

Example Oauth2

{
  "type": "$oauth.request",
  "options": {
    "key": "{{$keys.client_id}}",
    "secret": "{{$keys.client_secret}}",
    "path": "/v1/posts",
    "scheme": "https",
    "host": "api.producthunt.com"
  },
  "error": {
    "trigger": "login"
  },
  "success": {
    "type": "$render"
  }
}

Example Oauth1

{
  "type": "$oauth.request",
  "options": {
    "key": "{{$keys.key}}",
    "secret": "{{$keys.secret}}",
    "version": "1",
    "path": "/v2/user/dashboard",
    "scheme": "https",
    "host": "api.tumblr.com"
  },
  "success": {
    "type": "$render"
  },
  "error": {
    "trigger": "login"
  }
}

Example iOS social framework

{
  "type": "$oauth.request",
  "options": {
    "version": "0",
    "path": "1.1/statuses/home_timeline.json",
    "scheme": "https",
    "host": "api.twitter.com",
    "client_id": "{{$keys.client_id}}"
  },
  "success": {...}
}

Note

3. $oauth.unauth

$oauth.unauth signs you out of the APIs

Simply call $oauth.unauth with the key that was used for $oauth.auth.

It will clear all tokens associated with that key.

{
  "type": "$oauth.unauth",
  "options": {
    "key": "{{$keys.client_id}}"
  },
  "success": {
    "type": "$back"
  }
}

Twitter Client via Jason

{
  "$jason": {
    "head": {
      "title": "Custom Twitter",
      "description": "Try tweaking the code to build your own Twitter client",
      "icon": "https://cdn1.iconfinder.com/data/icons/logotypes/32/twitter-128.png",
      "actions": {
        "$pull": {
          "trigger": "reload"
        },
        "$load": {
          "trigger": "reload"
        },
        "reload": {
          "trigger": "twitter"
        },
        "twitter": {
          "type": "$oauth.request",
          "options": {
            "version": "0",
            "path": "1.1/statuses/home_timeline.json",
            "scheme": "https",
            "host": "api.twitter.com",
            "client_id": "{{$keys.client_id}}"
          },
          "success": [
            {
              "{{#if Array.isArray($jason)}}": {
                "type": "$render"
              }
            },
            {
              "{{#else}}": {
                "type": "$oauth.unauth",
                "options": {
                  "key": "{{$keys.client_id}}",
                  "version": "0"
                },
                "success": {
                  "type": "$kill"
                }
              }
            }
          ],
          "error": {
            "trigger": "error"
          }
        },
        "error": {
          "type": "$oauth.unauth",
          "options": {
            "key": "{{$keys.client_id}}",
            "version": "0"
          },
          "success": {
            "type": "$kill"
          }
        }
      },
      "templates": {
        "body": {
          "nav": {
            "style": {
              "background": "#1da1f2",
              "status": "dark",
              "color": "#ffffff"
            },
            "items": [
              {
                "type": "menu",
                "image": "https://s3.amazonaws.com/www.textcast.co/icons/settings%402x.png",
                "style": {
                  "color": "#ffffff"
                },
                "action": {
                  "type": "$oauth.unauth",
                  "options": {
                    "version": "0",
                    "client_id": "{{$keys.client_id}}"
                  },
                  "success": {
                    "type": "$kill"
                  }
                }
              }
            ]
          },
          "sections": [
            {
              "items": {
                "{{#each $jason}}": [
                  {
                    "{{#if /https/.test(text)}}": {
                      "type": "horizontal",
                      "href": {
                        "url": "{{text.replace(/.*(https:\\/\\/[/.a-zA-Z0-9]+).*/, '$1')}}",
                        "view": "Web"
                      },
                      "style": {
                        "padding": "10",
                        "spacing": "10"
                      },
                      "components": [
                        {
                          "type": "image",
                          "url": "{{user.profile_image_url_https}}",
                          "style": {
                            "width": "40",
                            "corner_radius": "20"
                          }
                        },
                        {
                          "type": "vertical",
                          "components": [
                            {
                              "type": "label",
                              "style": {
                                "font": "HelveticaNeue-CondensedBold"
                              },
                              "text": "{{user.screen_name}}"
                            },
                            {
                              "type": "label",
                              "style": {
                                "font": "HelveticaNeue-Bold",
                                "color": "#cacaca"
                              },
                              "text": "{{user.name}}"
                            },
                            {
                              "type": "label",
                              "text": "{{text.replace(/https:\\/\\/[/.a-zA-Z0-9]+/g, '')}}",
                              "style": {
                                "font": "HelveticaNeue"
                              }
                            }
                          ]
                        }
                      ]
                    }
                  },
                  {
                    "{{#else}}": {
                      "type": "horizontal",
                      "style": {
                        "padding": "10",
                        "spacing": "10"
                      },
                      "components": [
                        {
                          "type": "image",
                          "url": "{{user.profile_image_url_https}}",
                          "style": {
                            "width": "40",
                            "corner_radius": "20"
                          }
                        },
                        {
                          "type": "vertical",
                          "components": [
                            {
                              "type": "label",
                              "style": {
                                "font": "HelveticaNeue-CondensedBold"
                              },
                              "text": "{{user.screen_name}}"
                            },
                            {
                              "type": "label",
                              "style": {
                                "font": "HelveticaNeue-Bold",
                                "color": "#cacaca"
                              },
                              "text": "{{user.name}}"
                            },
                            {
                              "type": "label",
                              "text": "{{text.replace(/https:\\/\\/[/.a-zA-Z0-9]+/g, '>>')}}",
                              "style": {
                                "font": "HelveticaNeue"
                              }
                            }
                          ]
                        }
                      ]
                    }
                  }
                ]
              }
            }
          ]
        }
      }
    }
  }
}