JSON web container

What if you could take the web, turn it into a native app component, and plug it into native layout/scrollview along with other native components? Like magic!


Meet JSON Web Container. Here's how it works:

Step 1. Write HTML: Write HTML just like you would make a website, using HTML, Javascript, CSS, or whatever. As long as it works in a browser, it will work as a web container.

Step 2. Write JSON: Write JSON to describe how it blends into native app layout. You can even style it just like any other component!

Step 3. Enjoy!

Here's an example:


Check out this video and you’ll realize how much more you can do with this:



This is a simple update but simultaneously a huge deal for Jasonette, because it opens door to all kinds of new possibilities.


How this came to be

It all started with Brad.

Just like any developer, I believed web would never work in a native app. We did kind of have a half-assed 'html' component with the first version of Jasonette, but it was very limited and was just a hack to display a small subset of HTML elements.

Most importantly, it didn't have Android support (I never implemented an Android counterpart because I really didn't see much utility from the iOS version so didn't want to invest time).

Then one day I see a new pull request on the Android repository:

Obviously I was super skeptical at first ("Everyone knows web views suck!").

But then I actually tried it out..... I was shocked to find how smooth it was. Whole bunch of web views were scrolling within a list view just fine!

So eventually I got around to converting the iOS HTML component to use the same approach.

Also on top of that I made some improvements and design decisions to make sure that these "web containers" will feel completely native inside our apps. I also added support for Javascript.

Adding Javascript opened doors to all kinds of crazy things:

Performance

Once I had a prototype running I posted it on the forum and I got lots of great feedback. One was something I hadn't thought of before:

This comment made me realize that all remote assets (like javascript libraries or css) were being loaded separately for each web container, which resulted in tons of duplicate downloads AND slowdown.

So I went back and added caching mechanism to improve this. So now, all the static assets are cached so they don't get redownloaded multiple times.

After all this was done, the web container was performing flawlessly on both iOS and Android.

In fact, sometimes you can't even tell if it's a native component or a web container!

To sum up: Peformance is great! Of course it won't be 100% the performance of native, but the whole point is to mix and match this into your native app, not to power the entire app using HTML (we're building a native app here!). You have never been able to do something like this before (Seamlessly integrate web views into native scroll views, native layouts, native background, and use them as native components) and that makes tons of difference.


Conclusion

The moral of this story is, "Always push the boundary". With Jasonette we're doing something that most people will say is crazy ("it will never work!") anyway, so why not go all out!

Also I think the way this feature came together is really awesome because this could only happen because the project was open for contribution. Thanks Brad!

I encourage you to feel free to send pull requests no matter how crazy it is.

And that's it! I'm sure this will take your native app to the next level. The initial release for web container is intentionally minimal, and we'll probably add more powerful features going forward.

Would appreciate feedback!

TODO:

Self Mix-in

In the last post, I've talked about mix-ins for Jasonette, which lets you mix in remote JSON objects to generate a new JSON object. (If you haven't read that one yet, please read it first and come back, you'll need to understand the basics first)

Well, today I have a powerful piece of dark magic which takes this to a whole new level.

Meet Self-Mixin -- A mix-in that mixes a JSON object into itself.

self mixin

Syntax

There's only one thing you need to know: $document.

$document refers to the the currently loaded JSON tree.

For example, let's imagine we've loaded the following JSON on Jasonette.

{
  "items": [{
    "type": "label",
    "text": "Label 1"
  }, {
    "type": "label",
    "text": "Label 2"
  }, {
    "type": "label",
    "text": "Label 3"
  }],
  "$jason": {
    "body": {
      "sections": [{
        "@": "$document.items"
      }]
    }
  }
}

Notice the $document.items part. This is a self mixin in action.

When resolving, Jasonette looks at the loaded JSON tree itself ($document) and searches for an attribute called items, then it replaces the mixin expression with the object, which results in:

{
  "items": [{
    "type": "label",
    "text": "Label 1"
  }, {
    "type": "label",
    "text": "Label 2"
  }, {
    "type": "label",
    "text": "Label 3"
  }],
  "$jason": {
    "body": {
      "sections": [{
        "items": [{
          "type": "label",
          "text": "Label 1"
        }, {
          "type": "label",
          "text": "Label 2"
        }, {
          "type": "label",
          "text": "Label 3"
        }]
      }]
    }
  }
}


$document is contextual

$document is a contextual concept. The same $document reference can point to a different object depending on how the JSON object was loaded (Was it loaded as the main JSON markup? vs. Was it loaded from the main markup as a mix-in?)

Let's take a look at some examples:

1. Loaded as the main JSON markup

Here's a basic case:

https://blah.blah/child.json

{
  "$jason": {
    "head": {
      "title": "Test",
      "templates": {
        "body": {
          "sections": [{
            "items": [{
              "@": "$document.model"
            }]
          }]
        }
      }
    }
  },
  "model": {
    "type": "label",
    "text": "Bye World"
  }
}

When Jasonette loads and resolves the "@": "$document.model", it takes the current JSON tree's model attribute and replaces it into the mixin expression, and we end up with:

{
  "$jason": {
    "head": {
      "title": "Test",
      "templates": {
        "body": {
          "sections": [{
            "items": [{
              "type": "label",
              "text": "Bye World"
            }]
          }]
        }
      }
    }
  },
  "model": {
    "type": "label",
    "text": "Bye World"
  }
}

But we already knew that. Nothing special here.


2. Loaded as a mix-in

This time, let's try taking the same child.json and mix it into another JSON called parent.json like this:

https://blah.blah/parent.json

{
  "@": "https://blah.blah/child.json",
  "model": {
    "type": "label",
    "text": "Hello World"
  }
}

Here's what will happen:

Step 1. Jasonette loads parent.json and discovers that there's a mixin to resolve.

Step 2. So it mixes in the contents of https://blah.blah/child.json. To refresh your memory, child.json looks like this:

{
  "$jason": {
    "head": {
      "title": "Test",
      "templates": {
        "body": {
          "sections": [{
            "items": [{
              "@": "$document.model"
            }]
          }]
        }
      }
    }
  },
  "model": {
    "type": "label",
    "text": "Bye World"
  }
}

Step 3. Notice how parent.json and child.json both have the model attribute, but with different values. Whenever there's a conflict, the remotely mixed-in attribute (model from child.json) gets overridden by the main one (model attribute from parent.json) like this:

{
  "$jason": {
    "head": {
      "title": "Test",
      "templates": {
        "body": {
          "sections": [{
            "items": [{
              "@": "$document.model"
            }]
          }]
        }
      }
    }
  },
  "model": {
    "type": "label",
    "text": "Hello World"
  }
}

Step 4. Now Jasonette finds that there's still another mixin to resolve: $document.model. So it mixes in the model attribute.

The final result is:

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

Important distinction: In the previous example the $document.model inside child.json was interpreted as child.json itself, but this time the same $document.model was interpreted as parent.json, since its interpretation was carried out after it was mixed into parent.json.


Impossible is Nothing

Let's step back and think about what all this means.

This means now you can practically manipulate ANY part of a JSON tree at your will.

1. Organize the JSON any way you want

Previously Jasonette had some conventions you needed to follow. For example, you place templates under $jason.head.templates, data under $jason.head.data, actions under $jason.head.actions, and so forth.

{
  "$jason": {
    "head": {
      "data": {
        "dwarfs": [{
          "username": "Doc"
        }, {
          "username": "Dopey"
        }, {
          "username": "Bashful"
        }, {
          "username": "Grumpy"
        }, {
          "username": "Sneezy"
        }, {
          "username": "Sleepy"
        }, {
          "username": "Happy"
        }]
      },
      "templates": {
        "body": {
          "sections": [{
            "items": {
              "{{#each model.dwarfs}}": {
                "type": "label",
                "text": "{{username}}"
              }
            }
          }]
        }
      }
    }
  }
}

This still applies, but you are no longer locked into writing your markup strictly this way since you now have access to ANY part of the entire JSON tree.

Here's an example that does the same thing as above:

{
  "$jason": {
    "head": {
      "data": {
        "@": "$document.model"
      },
      "templates": {
        "@": "$document.view.body"
      }
    }
  },
  "view": {
    "body": {
      "sections": [{
        "items": {
          "{{#each model.dwarfs}}": {
            "type": "label",
            "text": "{{username}}"
          }
        }
      }]
    }
  },
  "model": {
    "dwarfs": [{
      "username": "Doc"
    }, {
      "username": "Dopey"
    }, {
      "username": "Bashful"
    }, {
      "username": "Grumpy"
    }, {
      "username": "Sneezy"
    }, {
      "username": "Sleepy"
    }, {
      "username": "Happy"
    }]
  }
}

Combine this with remote mix-ins and you have something like:

{
  "$jason": {
    "head": {
      "data": {
        "@": "$document.model"
      },
      "templates": {
        "@": "$document.view.body"
      }
    }
  },
  "view": {
    "@": "https://blah.blah/body.json"
  },
  "model": {
    "@": "https://blah.blah/dwarfs.json"
  }
}

You can even mix-in the $jason object from remote:

{
  "@": "https://blah.blah/jason_skeleton.json",
  "view": {
    "@": "https://blah.blah/body.json"
  },
  "model": {
    "@": "https://blah.blah/dwarfs.json"
  }
}

where jason_skeleton.json looks like this:

https://blah.blah/jason_skeleton.json

{
  "$jason": {
    "head": {
      "data": {
        "@": "$document.model"
      },
      "templates": {
        "@": "$document.view.body"
      }
    }
  }
}


2. Modularize and reuse

Once you get the hang of it, you'll come up with all kinds of creative ways of utilizing this feature. Let me explain one.

Let's go back to the dwarfs app example. What if we wanted to take the same app and just switch out the data to build a new app? Here's the original view:

https://blah.blah/view.json

{
  "body": {
    "sections": [{
      "items": {
        "{{#each model.dwarfs}}": {
          "type": "label",
          "text": "{{username}}"
        }
      }
    }]
  }
}

As you can see, the template makes two assumptions:

  1. $jason.head.data.model has an array called dwarfs
  2. The dwarfs array contains objects with the attribute username.

So we immediately run into a problem. This view is not really reusable outside of this dwarfs app context. For example we may have an API that returns the following:

{
  "response": {
    "movies": [{
      "title": "American Psycho"
    }, {
      "title": "Fight Club"
    }, {
      "title": "Matrix"
    }, {
      "title": "The Shining"
    }]
  }
}

First, let's fix the model.dwarfs part. Let's switch it out with something that's more generic: model.items

{
  "body": {
    "sections": [{
      "items": {
        "{{#each model.items}}": {
          "type": "label",
          "text": "{{username}}"
        }
      }
    }]
  }
}

Now let's go back to the main JSON. Currently it looks like this:

{
  "@": "https://blah.blah/jason_skeleton.json",
  "view": {
    "@": "https://blah.blah/body.json"
  },
  "model": {
    "@": "https://blah.blah/dwarfs.json"
  }
}

Let's change this since it wouldn't work anymore now that the view is expecting model.items. We need to create a mapping that transforms model.dwarfs into model.items, like this:

{
  "@": "https://blah.blah/jason_skeleton.json",
  "view": {
    "@": "https://blah.blah/body.json"
  },
  "model": {
    "items": {
      "@": "[email protected]://blah.blah/dwarfs.json"
    }
  }
}

Here we have used partial mixin (introduced in the last article on mixins) to select dwarfs and assign it to $document.model.items.

This solves the first problem.


Now, we still have one more problem to solve. We want to make the "text": "{{username}}" part generic as well so it can be reused for any other cases. How can we create a mapping for this?

Let's go back to view.json:

{
  "body": {
    "sections": [{
      "items": {
        "{{#each model.items}}": {
          "type": "label",
          "text": "{{username}}"
        }
      }
    }]
  }
}

Instead of "text": "{{username}}" we will use a self-mixin.

{
  "body": {
    "sections": [{
      "items": {
        "{{#each model.items}}": {
          "type": "label",
          "@": "$document.adapter.label"
        }
      }
    }]
  }
}

Of course, this assumes that we have an adapter.label object at root level, so let's go back and create it in the main JSON:

{
  "@": "https://blah.blah/jason_skeleton.json",
  "view": {
    "@": "https://blah.blah/body.json"
  },
  "model": {
    "items": {
      "@": "[email protected]://blah.blah/dwarfs.json"
    }
  },
  "adapter": {
    "label": {
      "text": "{{username}}"
    }
  }
}

Finally we have everything we need!


Now we can easily turn this into a Movies app which consumes the following API:

{
  "response": {
    "movies": [{
      "title": "American Psycho"
    }, {
      "title": "Fight Club"
    }, {
      "title": "Matrix"
    }, {
      "title": "The Shining"
    }]
  }
}

To do this we:

  1. Switch out model.items.
  2. Update the adapter.label to "text": "{{title}}".

Here's the result:

{
  "@": "https://blah.blah/jason_skeleton.json",
  "view": {
    "@": "https://blah.blah/body.json"
  },
  "model": {
    "items": {
      "@": "[email protected]://blah.blah/movies.json"
    }
  },
  "adapter": {
    "label": {
      "text": "{{title}}"
    }
  }
}

Finale

Lambda

This concludes the series on the lambda branch rollout. To summarize, here's the list:

  • $labmda: Full functional programming in JSON. Including arguments, call stacks, return values, etc.
  • $require: Parallel JSON requests
  • mixins: Powerful JSON manipulation

Apps as toys

During the span of time it took me writing this single article, I could have probably made 100 apps with Jasonette. Jasonette makes it THAT easy to make apps.

With lambda, require, and mix-in we take a huge step forward.

But we're still not there yet. More to come!


Making an app should be like putting together toy parts


Mix~in!

Have you met Frank, the ice cream truck guy? He takes a cone, adds a scoop of ice cream, maybe one more, adds some syrup, adds some sprinkles, and there you go, you have an ice cream cone!

He probably didn't make the sprinkles himself, he probably didn't make the syrup, and he probably didn't make the cone, but he sure can magically put them all together and make a really good ice cream cone, all in just ten seconds, and that's why we love him!

Same with Jasonette: You should not have to waste time reinventing the ice cream cone.

Ideally, you shouldn't even have to write a single line of JSON logic, but simply put together what already exist, and still perform your magic!


What is a Mix-in?

Jasonette Mix-ins let you compose multiple JSON objects into one. Since everything on Jasonette is written in JSON, this means you can mix in anything from actions to styles to templates!

You can use it to:

  1. Separate your JSON markup into multiple manageable modules
  2. Write reusable JSON modules to use in different projects
  3. Make your JSON module reusable and share it with the world!


Usage

You can implement a mixin simply by adding a key named "@" and setting a URL as its value. Every time a Jasonette view loads, it:

  1. Looks at all occurrences of "@": URL pairs.
  2. Fetches all the URL content, in parallel.
  3. Lastly, replaces each mixin expression with returned objects.


1. Basic

Here's a mix-in expression:

{
    "item": {
        "@": "https://jasonbase.com/things/h3f.json"
    }
}

Let's say https://jasonbase.com/things/h3f.json contains a JSON object that looks like this:

{
    "type": "label",
    "text": "Hello",
    "style": {
        "font": "HelveticaNeue",
        "color": "#ff0000"
    }
}

When the mix-in is resolved, it fetches and merges this object into the original, becoming:

{
    "item": {
        "type": "label",
        "text": "Hello",
        "style": {
            "font": "HelveticaNeue",
            "color": "#ff0000"
        }
    }
}


2. Array

You can also mix-in an array of URLs (example: "@": [URL, URL,.., URL]).

Here's an example where we mix-in the same JSON object 3 times:

{
    "items": {
        "@": [
            "https://jasonbase.com/things/h3f.json",
            "https://jasonbase.com/things/h3f.json",
            "https://jasonbase.com/things/h3f.json"
        ]
    }
}

When resolved, this becomes:

{
    "items": [
        {
            "type": "label",
            "text": "Hello",
            "style": {
                "font": "HelveticaNeue",
                "color": "#ff0000"
            }
        },
        {
            "type": "label",
            "text": "Hello",
            "style": {
                "font": "HelveticaNeue",
                "color": "#ff0000"
            }
        },
        {
            "type": "label",
            "text": "Hello",
            "style": {
                "font": "HelveticaNeue",
                "color": "#ff0000"
            }
        }
    ]
}


How is Mix-in different from $require?

We've talked about the $require action in the last post. Let me explain how mixins are different before you get confused.


1. Mix-ins modify the JSON markup itself.

$require is an action, which means all JSON objects you fetch using $require stay on memory only while you're executing the next action. If you don't save it or use it in the succeeding action, the required JSON objects immediately go away. This is because $require fetches JSON objects straight to memory, independent from the main JSON markup tree.

However, Mix-ins are designed for modifying the very JSON markup itself.

When I say JSON markup, I mean the main JSON that Jasonette loads for the current view, with a format that looks like this:

{
  "$jason": {
    "head": { },
    "body": { }
  }
}

In fact, with mix-ins, your initial markup doesn't even have to contain the $jason key initially--You can use mix-ins to dynamically construct the final markup.

Which means something like the following is possible with mix-ins:

{
  "$jason": {
    "head": {
      "@": "https://jasonbase.com/things/333.json"
    },
    "body": {
      "@": "https://jasonbase.com/things/enf.json"
    }
  }
}

as well as

{
  "@": "https://jasonbase.com/things/111.json"
}


2. Mix-ins get resolved before interpretation

You already know how Jasonette works:

  1. Fetches a JSON markup from a URL
  2. Interprets the fetched JSON to construct a view
  3. Bootstraps an actions controller which takes care of handling actions

$require starts its lifecycle after step 3, since it's an action.

On the other hand, mix-ins get resolved on step 1 even before any interpretation is made. Here's the complete sequence:

  1. Fetches a JSON markup from a URL
  2. The mix-in engine looks for any "@" references
  3. If any exist, the mix-in engine fetches all the referenced JSON objects and mixes them in to construct the final JSON markup.
  4. Interprets the fetched JSON to construct a view
  5. Bootstraps an actions controller which takes care of handling actions


Features

Due to the recursive/composable nature of JSON, Mix-ins are infinitely flexible.

Let's take a look at some basic things you can do:

Multiple Mix-ins

There is NO limit to the number of times you can mix-in a remote JSON object into your main JSON. They all get fetched and mixed into the main JSON object, in parallel.

{
    "model": {
        "@": "https://jasonbase.com/things/3nf.json"
    },
    "view": {
        "@": "https://jasonbase.com/things/13f.json"
    },
    "controller": {
        "@": "https://jasonbase.com/things/ina.json"
    }
}


Mix-ins Everywhere

You can use mix-ins anywhere in the JSON tree, as many times as you want.

Just remember that the mix-in resolution happens before any interpretation. The bootstrapping/rendering of the view comes after all this has completed.

{
    "$jason": {
        "head": {
            "styles": {
                "@": "https://jasonbase.com/things/3nf.json"
            },
            "templates": {
                "@": "https://jasonbase.com/things/a33.json"
            },
            "actions": {
                "oauth": {
                    "@": "https://jasonbase.com/things/4nf.json"
                },
                "$load": {
                    "trigger": "oauth",
                    "success": {
                        "type": "$render"
                    }
                }
            }
        }
    }
}


Inheritance

Going extreme, you can even mix-in an entire JSON object to inherit from it.

{
    "@": "https://jasonbase.com/things/3nf.json"
}

You may be asking "Why would I do that?". Which brings us to....


Overriding

In many cases you probably want to:

  1. Mix-in some generic JSON module
  2. Then override some of the JSON attributes with your own custom values.

Above example of mixing in an entire JSON is such a case. It becomes really powerful when you can mix in a JSON object AND override on top of that--it effectively becomes inheritance.

Let's say https://jasonbase.com/things/3nf.json contained the following JSON:

{
    "lastname": "Simpson",
    "address": "742 Evergreen Terrace",
    "city": "Springfield"
}

Here's a mix-in expression that references this:

[{
    "firstname": "Bart",
    "@": "https://jasonbase.com/things/3nf.json"
}, {
    "firstname": "Homer",
    "@": "https://jasonbase.com/things/3nf.json"
}, {
    "firstname": "Lisa",
    "@": "https://jasonbase.com/things/3nf.json"
}, {
    "firstname": "Maggie",
    "@": "https://jasonbase.com/things/3nf.json"
}, {
    "firstname": "Marge",
    "@": "https://jasonbase.com/things/3nf.json"
}]

When resolved, it merges in the remote JSON object for each, and also sets the local custom attribute "firstname" on top of that, becoming:

[{
    "firstname": "Bart",
    "lastname": "Simpson",
    "address": "742 Evergreen Terrace",
    "city": "Springfield"
}, {
    "firstname": "Homer",
    "lastname": "Simpson",
    "address": "742 Evergreen Terrace",
    "city": "Springfield"
}, {
    "firstname": "Lisa",
    "lastname": "Simpson",
    "address": "742 Evergreen Terrace",
    "city": "Springfield"
}, {
    "firstname": "Maggie",
    "lastname": "Simpson",
    "address": "742 Evergreen Terrace",
    "city": "Springfield"
}, {
    "firstname": "Marge",
    "lastname": "Simpson",
    "address": "742 Evergreen Terrace",
    "city": "Springfield"
}]


Partial Mix-in

Sometimes you only want to import ONLY a part of a remote JSON object. Let's look at an example:

{
  "posts": [{
    "title": "Hello World",
    "content": "Lorem Ipsum blah blah",
    "comments": [{
      "username": "Kat",
      "message": "Great job!"
    }, {
      "username": "Sophie",
      "message": "You suck!"
    }]
  }]
}

We can mix-in the entire object with:

{
  "@": "https://jasonbase.com/things/n3f.json"
}

But what if we only wanted the comments array from the first post? Simple! Just prepend the URL with the path:

{
  "@": "posts[0].[email protected]://jasonbase.com/things/n3f.json"
}


Performance? No Problem!

As mentioned in the $require article, the performance for parallel loading is not much different from loading a single JSON object, you will fail to notice any difference!

The biggest concern I had when first working on this feature was performance. For some reason I had this idea that on mobile, only a single network request should be made at a time.

Turns out, this was absolutely a non-issue! Upon actually trying it out on my iPhone and Android, I found that there is no noticeable difference!

I thought about why this is so, and in retrospect it kind of makes sense. Here's the reasoning:

Think about a typical app where the view displays multiple items, each with an image and some text. When you load this view, it's going out there and concurrently fetching EVERY image and displaying it on the screen. Which means, this type of concurrent requests have existed on a low level, and have already been working fine without me realizing. And in most cases JSON files are significantly smaller than images. If concurrent image downloads work fine, concurrent JSON downloads should have no problem!

(from the $require.json article)


One more thing...

My original goal was to make this the final post of the series.

But there's one more type of Mix-in which takes all I've explained here to a whole new level.

This requires its own post, so I am going to write another article on this soon. Stay tuned!

require JSON;

The $network.request action is used to dynamically fetch a single remote JSON file. But what if you want to fetch as many JSON files as you want, simultaneously?

Welcome to the world of $require.


parallel


Why would I want to do that?

Maybe you need to compose multiple sets of data to display to the view.

Maybe you need to fetch JSON objects from multiple different servers, each for a different purpose.

Or just maybe, you're building a completely decentralized networking app which needs to hit up every node individually!

Just remember that EVERYTHING on Jasonette (models, actions, views, templates, styles, or whatever) is expressed in JSON, which means you can dynamically require any part of your app.


In any case, this requires a powerful parallel networking feature.


Problem

Let's say we wanted to fetch a remote JSON object and assign it to a local variable:

{
  "type": "$network.request",
  "options": {
    "url": "https://jsonplaceholder.typicode.com/posts"
  },
  "success": {
    "type": "$set",
    "options": {
      "posts": "{{$jason}}"
    }
  }
}
  1. We use $network.request to fetch some posts as JSON.
  2. Then we assign the result to a local variable posts, using the $set action.


Now, what if we wanted to fetch from 6 different resources and do the same thing? Here's the code:

{
  "type": "$network.request",
  "options": {
    "url": "https://jsonplaceholder.typicode.com/posts"
  },
  "success": {
    "type": "$set",
    "options": {
      "posts": "{{$jason}}"
    },
    "success": {
      "type": "$network.request",
      "options": {
        "url": "https://jsonplaceholder.typicode.com/comments"
      },
      "success": {
        "type": "$set",
        "options": {
          "comments": "{{$jason}}"
        },
        "success": {
          "type": "$network.request",
          "options": {
            "url": "https://jsonplaceholder.typicode.com/albums"
          },
          "success": {
            "type": "$set",
            "options": {
              "albums": "{{$jason}}"
            },
            "success": {
              "type": "$network.request",
              "options": {
                "url": "https://jsonplaceholder.typicode.com/photos"
              },
              "success": {
                "type": "$set",
                "options": {
                  "photos": "{{$jason}}"
                },
                "success": {
                  "type": "$network.request",
                  "options": {
                    "url": "https://jsonplaceholder.typicode.com/todos"
                  },
                  "success": {
                    "type": "$set",
                    "options": {
                      "todos": "{{$jason}}"
                    },
                    "success": {
                      "type": "$network.request",
                      "options": {
                        "url": "https://jsonplaceholder.typicode.com/users"
                      },
                      "success": {
                        "type": "$set",
                        "options": {
                          "users": "{{$jason}}"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

There are two problems:

  1. The code is too long
  2. It will be 6 times slow because it runs these network requests one after one, serially.


Meet $require.

Now comes $require to the rescue:

{
  "type": "$require",
  "options": {
    "posts": "https://jsonplaceholder.typicode.com/posts",
    "comments": "https://jsonplaceholder.typicode.com/comments",
    "albums": "https://jsonplaceholder.typicode.com/albums",
    "photos": "https://jsonplaceholder.typicode.com/photos",
    "todos": "https://jsonplaceholder.typicode.com/todos",
    "users": "https://jsonplaceholder.typicode.com/users"
  },
  "success": {
    "type": "$set",
    "options": {
      "posts": "{{$jason.posts}}",
      "comments": "{{$jason.comments}}",
      "albums": "{{$jason.albums}}",
      "photos":  "{{$jason.photos}}",
      "todos": "{{$jason.todos}}",
      "users": "{{$jason.users}}"
    }
  }
}

No kidding, this is it! This code does exactly the same thing as above!

Furthermore it's 6 times faster since the requests are made concurrently.

Basically, $require waits until all of the requests have returned (either success or failure), and then calls its success callback with the returned JSON objects.

To reiterate:

  1. The code is much shorter
  2. Will be 6 times faster since the network requests are run in parallel


Syntax

Let's get into the actual syntax.

Just like any other actions, the $require action takes an options object. There are two patterns:

  1. Key: URL String
  2. Key: URL Array


1. Key: URL String

The same syntax we saw above. Basically, the $require action looks at all key: value pairs in the options object, fetches all the URL strings, and then assigns each result to its corresponding key of the return value.

{
  "type": "$require",
  "options": {
    "posts": "https://jsonplaceholder.typicode.com/posts",
    "comments": "https://jsonplaceholder.typicode.com/comments"
  },
  "success": {
    "type": "$set",
    "options": {
      "jasonplaceholder_posts": "{{$jason.posts}}",
      "jasonplaceholder_comments": "{{$jason.comments}}"
    }
  }
}

In this example, the keys are posts and comments, so you can access the results in the succeeding action ($set) using {{$jason.posts}} and {{$jason.comments}}.


2. Key: URL Array

You can also have an array of URLs (instead of a single URL string) as the value.

{
  "type": "$require",
  "options": {
    "result": ["https://jsonplaceholder.typicode.com/posts", "https://jsonplaceholder.typicode.com/comments"]
  },
  "success": {
    "type": "$set",
    "options": {
      "jasonplaceholder_posts": "{{$jason.result[0]}}",
      "jasonplaceholder_comments": "{{$jason.result[1]}}"
    }
  }
}

In this example, we only have a single key (result) and the value is an array of URLs. So when we fetch these remote JSON content, we push them into the result array. Once all JSON responses have returned, we go on to the next action $set, where we can access them using {{$jason.result[0]}} and {{$jason.result[1]}}.


Performance

The biggest concern I had when first working on this feature was performance. For some reason I had this idea that on mobile, only a single network request should be made at a time.

Turns out, this was absolutely a non-issue! Upon actually trying it out on my iPhone and Android, I found that there is no noticeable difference!

I thought about why this is so, and in retrospect it kind of makes sense. Here's the reasoning:

Think about a typical app where the view displays multiple items, each with an image and some text. When you load this view, it's going out there and concurrently fetching EVERY image and displaying it on the screen. Which means, this type of concurrent requests have existed on a low level, and have already been working fine without me realizing. And in most cases JSON files are significantly smaller than images. If concurrent image downloads work fine, concurrent JSON downloads should have no problem!


Note

$require has one job--fetch multiple JSON files in parallel--and it doesn't try to be more than that.

1. Only GET request

Since $require is more about requiring multiple JSON files and not about actually making requests that will update things on the server, every $require is actually a GET request under the hood.

2. Only JSON type

$require is strictly for fetching JSON.

If you want to fetch HTML, RSS, or any raw content, these are not meant to be done in parallel and you need to use $network.request.


Conclusion

Parallel network requests can be extremely powerful, especially considering how literally EVERYTHING on Jasonette is expressed in JSON.

This includes all of model, view, controller logic.

All you need to do is:

  1. Require some JSON object
  2. Assign them to a local variable
  3. Use it anywhere!

What's possible is only bound by your imagination.

I've been personally experimenting with $require and can see there's all kinds of potentially interesting use cases. If you find a clever use case, please share!


Designing a Functional Programming Language in JSON

Today, let me show you something you have never seen--a programming language written in JSON.

And not just any programming, but functional programming.

lambda

Here's the summary of this article:

  1. Anatomy of a function
  2. How to write a function in JSON
  3. Expressing function call stacks and return values in JSON

Note: This post discusses the technology behind Jasonette, an open source framework that lets you make cross-platform mobile apps with just a JSON markup.

Expressing all of Model-View-Controller logic in pure JSON is not a simple task, and each deserves its own article. I have already written an article about the view-side logic.

But in this article, it's all about the controller logic. More specifically, functions.



1. Anatomy of a function

Programming languages are supposed to carry out useful tasks. Normally these exist in the form of functions. Let's take a look at Javascript for example.


1. A function has a name and arguments.

Names let you reference functions in a unique manner.

You pass Arguments to functions so they can be processed.

function


2. Callback is when you pass a function as an argument to another function.

Callbacks are important in any application that involves asynchronous tasks such as network requests, processing user interactions, etc. because you have to delay the callback function's execution until it's time.

function


So the question is: How can we use a JSON object to describe things like this?


2. Writing a function in JSON

Jasonette has something called actions. Actions describe tasks. Each action consists of up to four attributes:

  1. type: name of a native function to run
  2. options: arguments to pass to the function
  3. success: success callback to run when the function returns success
  4. error: error callback to run when the function returns error

Using just these four attributes, you can build any kind of fully functional app.


Arguments

Functions are not so meaningful without arguments. We want a function to perform different actions based on the arguments we pass to it.

Here we pass title and description attributes to $util.alert action to create a custom alert dialog.

{
  "type": "$util.alert",
  "options": {
    "title": "Basic Alert",
    "description": "I'm a basic alert. I simply display an alert that needs to be dismissed before moving forward"
  }
}

When you run this action on Jasonette, It displays an alert that looks like the following:

iOS Android


Callbacks

Since most things that happen on mobile devices are asynchronous in nature, callbacks are first-class citizens and built into actions on Jasonette.

There are two types of callbacks: success and error. success gets called when the action succeeds. error when it fails.

Here's an example of a success callback:

{
  "fetch": {
    "type": "$network.request",
    "options": {
      "url": "https://jsonplaceholder.typicode.com/posts"
    },
    "success": {
      "type": "$render"
    }
  }
}

Here's what it does:

  1. Makes a network request ($network.request) to jsonplaceholder server
  2. Passes that result to its success callback action--which is another action called $render--which renders the result using whichever template you define (also in JSON).
  3. We name the action fetch by wrapping it with the key.


Evaluation

Since the native language of JSON is Javascript (JSON is short for JavaScript Object Notation), Jasonette implements evaluation using a built-in Javascript engine. If you want to evaluate something, simply wrap it inside a template expression ({{ }}).

Inline Javascript means we can do some crazy stuff like this:

{
  "fetch": {
    "type": "$network.request",
    "options": {
      "url": "https://.....",
      "method": "POST",
      "data": {
        "guid": "{{var str=$cache.guid; var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; for ( var block, charCode, idx = 0, map = chars, output = ''; str.charAt(idx | 0) || (map = '=', idx % 1); output += map.charAt(63 & block >> 8 - idx % 1 * 8)) { charCode = str.charCodeAt(idx += 3/4); if (charCode > 0xFF) { throw new InvalidCharacterError('btoa failed'); } block = block << 8 | charCode; } return output;}}"
      }
    },
    "success": {
      "type": "$render"
    }
  }
}

Here's what's going on:

  1. We try to make a network request using the data specified inside options.data.
  2. But the value of guid is a template expression, so the interpreter first executes the Javascript inside the template expression to determine the final value.
  3. If you look closely at the Javascript code, it takes the $cache.guid ($cache is a global variable for persisting data on the device) and generates a hash from it. It's basically a manual implementation of javascript's native btoa function.
  4. After all that's done, it finally makes the network request with the final value.
  5. Then it calls $render via its success callback.



Intermission

Everything below this point is completely NEW to Jasonette with the latest release.



Transition from Message dispatch to Function calls

So far I made it sound like actions on Jasonette are like functions. But it was only half true.

How it used to work

In the previous versions of Jasonette, every action would terminate immediately after calling its success or error callback. Here's what a typical action call chain looked like:

  1. Action 1 calls Action 2 and terminates.
  2. Action 2 does its job, and calls Action 3, then terminates.
  3. Action 3 does its job, and calls Action 4, then terminates. etc.

While not perfect, this model works fine because each action callback passes its result onto the next action (packaged as a variable called $jason), and as long as each action keeps doing its job correctly and propagates the correct value, the last action in the call chain would end up with expected results.

Problem

The probelm is, you can't create subroutines with this architecture. It's impossible to do something as straight-forward as:

  1. Action 1 calls Action 2 and waits.
  2. Action 2 finishes running, and sends a return value back to Action 1.
  3. Action 1 picks up where it left off and carries on with the return value from Action 2.

It's impossible because every action immediately terminates after triggering another action--it can't wait for its subroutine to return.

In this sense, calling an action was more like a message dispatch than a function.

Functions have call stacks, their own local states, and most importantly, return values. And there are no such things when you're simply dispatching messages.

Without a way to implement subroutines:

  1. It's impossible to write modular code
  2. Writing recursive algorithms is not trivial


This was not a serious problem initially, when most people just used Jasonette to write small apps and prototypes. But as people started to realize they can actually write production apps this way, this became problematic because they started writing thousands of lines. Imagine reading through thousands of lines of JSON code with absolutely no modularity.


Expressing function call stacks & return values in JSON

To address this we needed a way for an action to:

  1. Invoke another action
  2. Wait for the callee action to return, and continue on with the return value once it returns.

Also it would be best if we didn't invent a new concept just for this, but instead simply come up with just another new action to achieve this.

So $lambda and $return were born.


$lambda

$lambda is a special purpose action whose sole purpose is to trigger another action. Unlike other actions which carry out some task, it only functions as a mediator that

  1. Executes another action
  2. Waits for it to return
  3. Resumes and starts executing the next action with the return value

The $lambda action takes two arguments as its options:

  1. name: The name of the function to trigger
  2. options: An options object to pass to the triggering action.

Let's take a look at an example usage of $lambda:

{
  "fetch": {
    "type": "$lambda",
    "options": {
      "name": "btoa",
      "options": {
        "guid": "{{$cache.guid}}"
      }
    },
    "success": {
      "type": "$network.request",
      "options": {
        "url": "https://.....",
        "data": {
          "guid": "{{$jason.hashed}}"
        }
      },
      "success": {
        "type": "$render"
      }
    }
  }
}

In this example,

  1. We invoke an action called btoa (which we will come back to in the next section) using $lambda.
  2. Then the $lambda action waits until the btoa action completes its task and returns.
  3. Finally when that happens, the $lambda action's success callback gets executed.
  4. Note that the success action is a $network.request action that uses the return value from btoa in the form of the variable $jason.


$return

$return is actually not a single action but a category of actions. There are two:

  1. $return.success : Used for returning values on success. This will backtrack to the current action's caller action and execute its success callback.
  2. $return.error : Used for returning an error. This will backtrack to the current action's caller action and execute its error callback.

There are two ways of returning--with return value and without return value

  • Without return value: If the action doesn't need to return any value, simply doing "type": "$return.success" is enough to return the control back to the caller action.
  • With return value: However if you wish to send a return value back to the caller action, you will need to set the options.

For example, when you $return.success this way:

{
  "myname": {
    "type": "$return.success",
    "options": {
      "firstname": "Bart",
      "lastname": "Simpson"
    }
  }
}

You will be able to access the options object as $jason in the caller action's success callback. Here's an example:

{
  "type": "$lambda",
  "options": {
    "name": "myname"
  },
  "success": {
    "type": "$util.toast",
    "options": {
      "text": "My name is {{$jason.firstname}} {{$jason.lastname}}"
    }
  }
}

Notice how the $util.toast is accessing the $jason.firstname and $jason.lastname values after the myname action returns.


Circling back to our btoa action, here's what it looks like when we extract the btoa action out as a standalone function. All it does is compute the hash and return. (It's basically a manual implementation of the native btoa function.

{
  "btoa": {
    "type": "$return.success",
    "options": {
      "hashed": "{{var str=$jason.guid; var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; for ( var block, charCode, idx = 0, map = chars, output = ''; str.charAt(idx | 0) || (map = '=', idx % 1); output += map.charAt(63 & block >> 8 - idx % 1 * 8)) { charCode = str.charCodeAt(idx += 3/4); if (charCode > 0xFF) { throw new InvalidCharacterError('btoa failed'); } block = block << 8 | charCode; } return output;}}"
    }
  }
}

Notice that the function uses var str=$jason.guid; (previously it was var str=$cache.guid;).

$jason is the argument passed in from the caller action, which means it only exists within this local scope. The function doesn't use global variables like $cache anymore.


Simplifying $lambda with 'trigger'

$lambda actions can be a bit verbose, so there's a syntactic sugar for calling $lambda actions.

Instead of using the $lambda action directly, you can use the trigger keyword to shorten it down.

Instead of the following $lambda action:

<Action> ::= {
  "type": "$lambda",
  "options": {
    "name": <Action Name>,
    "options": <Arguments object>
  },
  "success": <Action>,
  "error": <Action>
}

We can use the trigger notation like this:

<Action> ::= {
  "trigger": <Action Name>,
  "options": <Arguments object>,
  "success": <Action>,
  "error": <Action>
}

For example, instead of this:

{
  "type": "$lambda",
  "options": {
    "name": "some_action",
    "options": {
      "key1": "value1",
      "key2": "value2"
    }
  },
  "success": {
    "type": "$render"
  },
  "error": {
    "type": "$render"
  }
}

You can use:

{
  "trigger": "some_action",
  "options": {
    "key1": "value1",
    "key2": "value2"
  },
  "success": {
    "type": "$render"
  },
  "error": {
    "type": "$render"
  }
}


Stay Tuned for Part 2 and 3

If you read this far, you're either thinking:

  1. "This is the awesomest thing since Jasonette!" (probably like 5% of you)
  2. or, "Yeah cool story bro, now you can do some hipster functional stuff, so what?" (the rest 95%)

I think those of you who have already written long lines of JSON code may immediately get the benefit since it directly deals with your pain point. But even if you're in the group #2, don't feel bad!

Because this is the first piece of the puzzle towards introducing total modularity to Jasonette, and I have two more posts coming up where I will talk about how all this fits into the picture.

Stay tuned for the next one very soon.