Offline Apps

demystified by Aaron Rosenzweig

aaron@chatnbike.com

Codivus, Inc.

Follow along:

http://www.chatnbike.com/offline/

We will learn how/why:

Soup to nuts - it's all here

Holding city hall accountable

http://teamgaithersburg.org

Whatever “topic” you are viewing, if you close your web browser it “remembers” right where you left off as if by magic… even if you aren’t connected to the internet! This is perfect if you are in city hall where the internet is problematic, you can find the issues that are important to you. In a grander scheme, think of how that could be useful for other applications!

The app functions equally well on a phone versus a computer - it’s pretty cool. How does it do that? Take your computer window and make it smaller, does it now look like the interface for the phone? It does it based on “screen real estate” not by the device at hand.

The “topics” come from a central server, they aren’t hard coded. We can update them easily from a database at any time. Equally cool is that they are stored in a magical place and accessible even without your internet connection! The site always works, when you are on your farm, inside a subway, anywhere at all! Yet it will always update with the latest and greatest info when your Internet is available.

Raw database output of topics

All you need is this:

public class CommunityTopicController extends BaseRestController {

  protected ERXKeyFilter filter() {
    ERXKeyFilter filter = ERXKeyFilter.filterWithAttributes();
    filter.setUnknownKeyIgnored(true);
    return filter;
  }

  public WOActionResults indexAction() throws Throwable {
    NSArray<CommunityTopic> entries =
      CommunityTopic.fetchAllCommunityTopics(
        editingContext(), CommunityTopic.SORT_ORDER.ascs());
    return response(entries, filter());
  }
}

And this in your Properties:

ERXRest.accessControlAllowOrigin=*
ERXRest.allowWindowNameCrossDomainTransport=true
ERXRest.allowJSONP=true

To learn more about REST

Single Page Apps

Javascript?

Nice from afar but far from nice

or

Best thing since sliced bread

UNIX is user friendly

it's just picky about its friends

same holds true for Javascript

UNIX is fun - so is Javascript

$ csh
% got a light?
got: No match.

% make love
make: *** No rule to make target `love'.  Stop.

% man: why did you get a divorce?
man:: Too many arguments.

% ^How did the sex change^ operation go?
Modifier failed.

Languages don't make development enjoyable, it's the framework and IDE that do.

Java is not enjoyable. WebObjects is. If we were writing apps in JSP or J2EE we would not be having near as much fun.

Javascript is no different. It has the same learning curve. Good frameworks make javascript nice.

Enyo fluffy bunny layout

Fluffy bunny

Enyo Deployment

Enyo the juicy bits

In Hamburg, 2015, I demonstrated what it is like to develop Enyo apps using an interface builder, etc. To learn more about Enyo I'll list some links for further reading so we can continue to discuss "offline apps" today.

Enyo / Ares more info

This Enyo app is made of two reusable components

Inside Viewer.js

We place our "CommunityTopic" component inside our "Viewer" with this call:

{
        name: "communityTopicFetch",
        kind: "CommunityTopic",
        onResults: "communityTopicResults"
}

It is the last element in "components" of Viewer. It has no content of its own.

What are the three parameters?

How to search for topics

There is a method called "search()" defined in "CommunityTopic." To invoke it from our Viewer component we call it by the name we gave it and then call the method

this.$.communityTopicFetch.search();

Searching takes time, it is asynchronous, and it might fail if our Internet connection isn't available. We must handle all that.

In good times and bad

Lawnchair so we may remember

http://brian.io/lawnchair/

Lawnchair the bad parts

Lawnchair the good parts

CommunityTopic.search()

Because lawnchair is async, we must put all actions that we want to take place in its callback. This also means that we need to scope "this" and pass in the method we want to call using "enyo.bind"

CommunityTopic.search()

new enyo.Ajax({url: urlToJson, handleAs: "text"})
.response(this, "processAjaxResponse") // good times
.error(enyo.bind(this, function(inSender, inResponse) {
        // bad times
        var doResultsMethodBoundToThis =
        enyo.bind(this, "doResults");

        Lawnchair({name : 'db'}, function(store) {
          store.get("communityTopicsResults", function(obj) {
            var communityTopicsResults = obj.value;
            doResultsMethodBoundToThis(communityTopicsResults);
          });
        });
}))
.go();

We "get" results that are remembered.

CommunityTopic.processAjaxResponse()

// good times - have strong internet connection
processAjaxResponse: function(inSender, inResponse) {
  inResponse = enyo.json.parse(inResponse);
  this.doResults(inResponse);
  var db = Lawnchair({name : 'db'}, function(store) {
    store.save({key:"communityTopicsResults", value:inResponse});
  });
  return inResponse;
}

We "save" results that we fetched from REST.

Recalling the most recently viewed topic

What about sync? Your model in JS and automatically saved via REST?

Sorry. Not going to demonstrate this. There are a few ways to do this but decided this was outside the scope of this talk. Still, don't worry, I will point you in the direction to find more info about this topic.

Savvy sync client/server sync

Offline apps

Defined: an app that loads without being connected to the Internet.

How fantastic! no need to burden your server with any processing other than the actual management of data. This is scalability beyond our wildest dreams! All the work of creating HTML and managing the User-Interface happens on the client without even talking to the server.

Offline apps

So why is none of the above true?

What is so hard?

At the basic level - when do you throw away something and get a new one? It's tough to answer that question. Your dream solution is likely different than mine.

Hitting close to home

EOF is a cache. It confuses many of us until we learn the way it works. Even if we fetch data from the DB we throw the new results away! That is the default behavior. We can say "setRefreshesRefetchedObjects(true)" when we fetch and then we'll keep the new data.

It's a matter of someone not only making a caching mechanism but us actually learning what they were thinking. We can't assume anything.

Hidden war you never knew

Offline app implementations are a powder keg full of hurt feelings and conflicting views. Most of us have no idea because we have not tried to make single page apps and have not tried to cache them for the user.

Tale of two offline caches

Mozilla is pissed

The creators of Firefox have this to say:

"The previous attempt — AppCache — seemed to be a good idea because it allowed you to specify assets to cache really easily. However, it made many assumptions about what you were trying to do and then broke horribly when your app didn’t follow those assumptions exactly. Read Jake Archibald's Application Cache is a Douchebag for more details."

Douchebag?

“Hi, I’m ApplicationCache,” he said, as he reached over to shake Dev’s hand. “I turn your offline experience from sucks-ass, to success. Just one extra file, and bosh! It works. No fuss, no ‘scripting’ necessary.” Yes, he did finger-quotes while saying ‘scripting.’ I’m gritting my teeth at this point, because I know he’s greatly exaggerating his abilities and the others don’t see it. However, if I call “bullshit” on it I’ll seem like the jerk. I’m here to tell you ApplicationCache is a douchebag.

Name calling continues

Nolan Lawson calls Safari the new Internet Explorer

That's because Safari has no interest in supporting Service Workers nor IndexedDB. There is a strong difference of opinion on how things should work and what is "broken."

App Cache works

Because App Cache works, that's what we should use today. Here are some great articles to read:

Apple's App Cache Documentation

Name your cache file

Configure your webserver

cd /usr/local/etc/nginx
sudo cp mime.types mime.types.bk_April_4_2016
sudo vi mime.types
<< add the following >>
text/cache-manifest       manifest;
sudo /usr/local/etc/rc.d/nginx restart

Update your index.html

<html manifest="assets/offline.manifest">

Manifest / App Cache is asynchronous

Alerting User of Staleness

If you really want to warn the user:

if (window.applicationCache) {
  applicationCache.addEventListener('updateready', function() {
    if (confirm('An update is available. Reload now?')) {
      window.location.reload();
    }
  });
}

But what's the rush? Why get in their face?

Big Gotcha

Good habit

Every time you want to make sure that new files are downloaded, you should update the version of your manifest with a line like this at the end:

# version 14

This will catch things like images whose filename didn't change but content did.

Must have a NETWORK line

Your manifest must have a line that looks something like this:

NETWORK:
*

Without this, any file not specifically written in your manifest will not be available. Even when you are online, connected, you cannot download the file without specifying with an asterisk that all files not in the manifest can be downloaded when you are connected.

No dangling bits

If there is any file in your manifest that is not reachable, the whole App Cache falls flat. It will not cache what it can find, it will cache nothing. Make sure that if you delete or change the name of a file in the manifest then you also correct it in the manifest.

REST is the new SQL

Summary Point #1

Users want more

Summary Point #2

WO is dead, but soul still burns

Summary Point #3

We can finally build apps with the same ease and even better performance. These tools took the spirit of WO and improved upon it. A better mousetrap finally exists.

Play with Ares & Enyo

Install NodeJS

npm -d install ares-ecosystem-ide

~/node_modules/.bin/ares-ecosystem-ide -b

More Ares Ecosystem info on github

Download the example projects

One more thing!

Cluster Of Unreliable Commodity Hardware

Yet another, conservative, thing!

Javascript REST server with SQL backend

SpaceForward
Left, Down, Page DownNext slide
Right, Up, Page UpPrevious slide
POpen presenter console
HToggle this help