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!