Jasonette Offline
Jasonette is an open source framework that makes it super easy to build iOS and Android native apps using just a single JSON markup.
It’s like building a website by writing HTML, but you’re building a native app instead, by writing JSON.
Whenever you open an app built with Jasonette, its JSON markup would load over HTTP and self-construct into a native app, in realtime. Basically the entire app logic exists on a remote server and gets streamed to your “app”.
What if we could load the app not just from a remote server, but directly from the device?
Introducing Jasonette Offline.
Not all apps are 100% network connected.
You may want to build an app that
- works completely offline by design.
- works online, but falls back to offline when there’s no connection.
- synchronizes with online data, but stores the app logic offline, for optimization.
- same as above, but for security reasons.
- utilizes offline user data, but keeps the app logic in sync by storing them on the cloud.
With Jasonette Offline — the latest release of Jasonette — all this is now possible. Let’s take a look:
1. file:// scheme
Local JSON
Just like Jasonette can load an entire app as JSON over https:// and turn it into an app, now you can do the same over file://
All you need to do is store the same JSON markup on your app bundle when building and use the file:// url scheme instead of http. It works exactly the same, except it loads the JSON locally from the app itself instead of a remote server, requiring no Internet connection.
Local Image
Same goes for images. You can store all the images locally and load them. Simply place the images under the same directory and refer to them using the file:// scheme.
2. Loading Screen
A “loading screen” is a screen that displays until your actual view finishes loading.
This is a perfect use case for offline JSON, since the whole purpose of a loading screen is to display while another network request is being made.
With the latest update, Jasonette has this built in — it ships with a file://loading.json
file. It’s a special URL which automatically displays while another view is loading.
It’s a full-fledged view, so you can customize whichever way you like. Here’s an example:
3. Offline Screen
Another addition that ships with the latest update is an offline screen. The offline screen is displayed when there’s no internet connection. You can find it at file://error.json
.
Just like file://loading.json
it’s a full fledged view, so you can customize it whichever way you want.
4. Offline Fallback Cache
Another way of handling offline state is using the offline fallback cache feature.
Since everything about a view is expressed in a single JSON object, we can easily cache an entire view and its app logic by simply caching the JSON object.
To implement this, simply add a single line under head
: "offline": “true"
:
{
"$jason": {
"head": {
"offline": "true",
...
}
}
}
Here’s what happens:
- Your online app will work exactly the same as before.
- However whenever it loads a view, it caches the rendered JSON.
- Next time when you visit the view, it loads the cached JSON first, which means it will load instantaneously.
- In the meantime, it will make a network request in the background.
- If it succeeds, the newly fetched JSON markup is seamlessly replaced with the old one.
- If it fails, it silently fails, and we still have the cached view.
This is very useful when we have an online-by-default app but want to allow usage during offline situations. Users will still be able to use the app, it will just be a cached version if there’s no Internet connection.
5. Global Key-Value Storage
Jasonette has a key-value persistence mechanism called $cache
. Each $cache
variable is sandboxed to its parent URL, just like how localStorage works in a browser.
But mobile apps work differently than browsers, and in many cases we need a global storage that can be accessed from ALL views. You may want to use it to store user data, small configuration attributes, or really any kind of JSON object.
This cannot be implemented with the file://
feature because files are static and immutable once you package them up on your app and ship it.
What we need is some sort of an app-wide database that we can mutate.
This is what global storage does.
How global storage works
Here’s how the global storage works:
- They work the same way as $cache except that it’s shared by all views.
- They are persisted on the device, so they stay around until you delete the app or explicitly reset them via the $global.reset API.
Setting a global variable
Here’s how you would set a global variable:
{
"type": "$global.set",
"options": {
"username": "ethan"
}
}
You can also set multiple key/value pairs at once:
{
"type": "$global.set",
"options": {
"firstname": "ethan",
"lastname": "gliechtenstein"
}
}
Getting a global variable
Once set, you can access it easily using a template expression, just like you do for cache and local variables.
{
"type": "$util.alert",
"options": {
"title": "My Name Is",
"description": "{{$global.firstname}} {{$global.lastname}}"
}
}
Resetting a global variable
If you want to remove a variable from the global namespace, you can do that too. You just need to pass an items
array, which contains the names of the variables you want to delete.
{
"type": "$util.reset",
"options": {
"items": ["firstname", "lastname"]
}
}
What does global storage enable?
The global storage enables a lot of things that were not possible before. For example, previously the only ways of implementing a picker were using the built-in $util.picker
, $util.datepicker
, $media.picker
actions.
If you wanted to create a picker for anything else, such as image picker, addressbook picker, color picker, etc. you were out of options.
With global storage, you can create custom picker views that let users select a value, sets a global variable, and returns back to the original view.
Device Environment Variables
The latest release introduces another new category of variables: $env
You can use the variable anywhere in the app using templates, just like any other variables in Jasonette.
- $env.device.width : device width
- $env.device.height : device height
- $env.device.os.name : os name (
ios
orandroid
) - $env.device.os.version : os version
- $env.device.language: device language (
"en-US"
, etc.) - $env.view.url: current view url
Distributed App Splitting
The introduction of these offline features doesn’t just mean you can build offline-only apps. It means you can:
- Split your apps into different modules via mixins.
- Store them wherever you want, depending on your requirement
- Mix and match to construct the final app, in realtime.
To understand this concept, it’s important to first understand what exactly Jasonette can express with JSON. Basically, you can describe every aspect of a functional app in JSON, including:
- Model: JSON is originally for storing data, so this one is obvious.
- View: Build components, layouts, layers, etc. in JSON.
- Controller: Describe actions (functions that actually do something) in JSON, complete with chain-able success and error callbacks.
Furthermore, Jasonette has built-in modules that let you dynamically fetch, transform, and compose JSON.
- Templates: Parse, manipulate, and transform JSON.
- Mixins: Dynamically merge multiple JSON objects.
- Require: Fetch multiple JSON objects from multiple sources in parallel.
Combining all these features with the new offline features, now you can structure your app in many different ways:
A. 100% online app
Serve all of model, view, controller over HTTP. How Jasonette works by default.
B. 100% offline app
Store all of model, view, controller locally. In case you really don’t need any online access.
C. Hybrid : online data + offline app
Keep the data in sync with the cloud, while keeping the app logic local. Possibly for security reasons, or for optimization.
D. Hybrid : online app + offline static data
Keep the app logic online for easy updates, but store the data on the device. Possibly for apps that store sensitive static data.
E. Hybrid : online app + offline dynamic data
Files are static and you can’t change them once you ship the app. Maybe you want a simple way to dynamically store user data locally, while keeping the app up-to-date by streaming from the server.
These are just some of the examples, but you can imagine how composing these different modules can help you come up with creative solutions to building different types of apps.
Conclusion
To summarize, on both iOS and Android, you can now:
- Use files to store your app.
- Utilize the app-wide key-value database.
- Break your app down into modules (like model, view, controller, etc.) and store them wherever makes sense for your purpose. (cloud/file/global storage)
I am very excited to see what people build with this. Check out the website, iOS github repository, and Android github repository. You can also learn more about the new offline features here.
Acknowledgements
This release is meaningful in that it’s the first time something was driven entirely by the community.
Actually I have to confess that the biggest hurdle was myself, I was somewhat resistant to the idea at first and didn’t spend much time thinking about offline features.
But this is an open source project and anyone can make modifications and even send pull requests if it makes sense. As I started seeing more and more pull requests from people and seeing more examples, I gradually started “getting” the value of offline.
And at some point everything just clicked. I realized we should go all out, which led us here.
Thank you
- Earl Celis for kickstarting the “offline movement” https://github.com/Jasonette/JASONETTE-iOS/pull/42
- steve21124 for pushing it even further with local mixins https://github.com/Jasonette/JASONETTE-iOS/pull/148
- Brian Hoffman for singlehandedly building out all the rest, including local button, image, and menu https://github.com/Jasonette/JASONETTE-iOS/pull/211
- Dr Washington Sanchez for throwing me tons of crazy edge cases, which helped me realize the potential of the offline first approach.