1

I'm trying to make a basic Sencha Touch app with 2.2.1 that will work offline following this example. What I have so far is more or less a direct clone of that with some different fields and it's working great in browser.

On Chrome for instance, I can see items being stored to localStorage which are then read in if I turn off my computer's internet connection. However on iOS, I try saving the app to homescreen and it works great while connected. However once I've put my device in airplane mode the localStorage appears to be empty and my list comes up empty.

I've tried having a local copy of the JSON data but that doesn't seem to get stored on iOS via the cache.manifest so that's not a fallback option either. Is there any way to get this working in the browser/homescreen on iOS? Thank you.

The key controller code that updates the caches is as follows (note it matches the example above with the exception of some different model/store names):

    init: function () {
        var onlineStore = Ext.getStore('Tables'),
        localStore = Ext.create('Ext.data.Store', {
            model: "MyApp.model.OfflineTable"
        }),
        me = this;

        localStore.load();

        /*
        * When app is online, store all the records to HTML5 local storage.
        * This will be used as a fallback if app is offline more
        */
        onlineStore.on('refresh', function (store, records) {
            // Get rid of old records, so store can be repopulated with latest details
            localStore.getProxy().clear();

            store.each(function(record) {
                var rec = {
                    id: record.data.id,
                    text: record.data.text,
                    content: record.data.content,
                    group: record.data.group
                };
                localStore.add(rec);
                localStore.sync();
            });
            console.log("Setting local store of size " + localStore.getCount());
            console.log("Item 0 is " + localStore.getAt(0).data.text);
        });

        /*
        * If app is offline a Proxy exception will be thrown. If that happens then use
        * the fallback / local stoage store instead
        */
        onlineStore.getProxy().on('exception', function () {
            me.getTables().setStore(localStore); //rebind the view to the local store
            console.log("Getting local store of size " + localStore.getCount());
            console.log("Item 0 is " + localStore.getAt(0).data.text);
            localStore.each(function(record) {
                console.log(record);
            });
            localStore.load(); // This causes the "loading" mask to disappear
            Ext.Msg.alert('Notice', 'You are in offline mode', Ext.emptyFn); //alert the user that they are in offline mode
        });
    }

It's from the console.log calls above that I can see my localStorage is looking great on browser and while connected, but empty once disconnected on iOS (though still fine offline on desktop).

My offline model is:

Ext.define('MyApp.model.OfflineTable', {
    extend: 'Ext.data.Model',
    config: {
        fields: [
            "id",
            "text",
            "content",
            "group"
        ],
        identifier:'uuid', // needed to avoid console warnings!
        proxy: {
            type: 'localstorage',
            id: 'offlineTableData'
        }
    }
});

And my online model is simply

Ext.define('MyApp.model.Table', {
    extend: 'Ext.data.Model',
    config: {
        fields: [
            "id",
            "text",
            "content",
            "group"
        ]
    }
});

Lastly my store is:

Ext.define('MyApp.store.Tables', {
    extend: 'Ext.data.Store',

    config: {
        model: 'MyApp.model.Table',
        proxy: {
            timeout: 3000,
            type: 'ajax',
            url: /* DATA URL */
        },
        autoLoad: true
    }
});
Primus202
  • 648
  • 7
  • 24

2 Answers2

1

Try creating both stores from the Ext.Application instead of: Ext.create('Ext.data.Store', {model: "MyApp.model.OfflineTable"}) using the same model and replicate the contents on the online-store (TablesRemote) load callback:

TablesRemote:

Ext.define('MyApp.store.TablesRemote', {
    extend: 'Ext.data.Store',

    config: {
        model: 'MyApp.model.Table',
        proxy: {
            timeout: 3000,
            type: 'ajax',
            url: /* DATA URL */
        },
        autoLoad: true,

        listeners: {
            load: function(st, g, s, o, opts) {
                var localStore = Ext.getStore('TablesLocal');
                st.each(function(record) {
                    localStore.add(record);
                    localStore.sync();
                });
            }
        }
    }
});

TablesLocal:

Ext.define('MyApp.store.TablesLocal', {
    extend: 'Ext.data.Store',
    config: {
        model: 'MyApp.model.Table',
        proxy: {
            type: 'localstorage'
        },
        autoLoad: true
    }
});

Then you should always access data using MyApp.store.TablesLocal.

Hope it helps-

Nico Grunfeld
  • 1,133
  • 1
  • 8
  • 19
  • So I need two stores for local/online? The example I followed in my original question used two models with an online store, and then created the local store in the controller file as above. – Primus202 Sep 11 '13 at 18:03
  • Yes I know. IMO opinion is better and is a more MVC way. I use this approach a lot and never had any problem, it's a very controlled solution. – Nico Grunfeld Sep 11 '13 at 18:09
  • Tried it but that's just a simple structure. I think I tracked down my problem: the updates I was making to my localStorage store were never actually kept. Sencha only syncs "dirty" records on `sync`. I now see my data stored in `localStorage` via the inspector but I have to figure out how to read it back out. – Primus202 Sep 11 '13 at 19:00
  • Tracked down the issue to how Sencha syncs, see my answer on this question for a fix all. – Primus202 Sep 11 '13 at 19:40
1

Several hours of debugging later I finally found a valid solution. There's a completely (as far as I can see) undocumented nuance to how Sencha Touch handles localStorage. The idea of the original sample I followed is the online data is synced to a local store for later use in the following manner:

store.each(function(record) {
    var rec = {
        id: record.data.id,
        text: record.data.text,
        content: record.data.content,
        group: record.data.group
    };
    localStore.add(rec);
    localStore.sync();
});

However in practice, this only stores the data locally in the current instance which defeats the point. Sencha only actually updates the browser's local storage for "dirty" records. Since we're creating brand new records they squeaky "clean." So in order for a the localStore.sync() call to actually stick we need to dirty up our records in the copying loop above as follows:

store.each(function(record) {
    var myRecord = Ext.create("MyApp.model.Table", {
        id: record.data.id,
        text: record.data.text,
        group: record.data.group,
        content: record.data.content
    });
    myRecord.setDirty();
    theStore.add(myRecord);
    theStore.sync();
});

In essence we're creating a "dirty" record based on our global table model and adding it to the local store which will then properly sync and store it since it's now "dirty." This stores each record as a separate localStorage property with the original offlineTableData (as declared in our model's proxy ID) apparently serving as ID index for all entries. Therefore you'll see a whole bunch of offlineTableData entries in the localStorage but fret not; I'm not sure if there's a way to condense this but it appears to work on an iOS device in Airplane mode after an initial sync.

Hope that makes sense, happy coding.

Primus202
  • 648
  • 7
  • 24