Sencha Touch 2 example of syncing localStorage store with remote JSONP proxy store

 

Please read my new post on syncing. https://lalexgraham.wordpress.com/2013/06/14/using-net-mvc-and-sencha-touch-syncing-localstorage-with-remote-storage-with-ext-ux-offlinesyncstore/

 

I have been playing around with Sencha Touch 2 for a long time now but have not used localStorage much. I recently posted about how I store data remotely and used it in Sencha Touch . But continually using remote data is not ideal and unnecessary when dealing with largely static data. So I wanted a way to store the data locally and only refresh it when something changed. To make it even simpler to begin with I just wanted to sync a localStorage store with the data from the remote JSONP proxy.

I googled around and found several posts, this being the most useful. But nothing I found demo’d how to get data from somewhere and sync it. My code at this github repository is my attempt at doing this, and below is a walk-through of that code. As always I’d welcome any feedback on my method.

EDIT. Please look at comemnts from pentool and kevin.novientis below which give some pointers on making this all work even better.

To begin with I created a boilerplate Sencha Touch 2 (ST2) app using the sencha sdk and named it “test”. I then added just one view which would list each item’s ‘title’ coming back from the store:

Ext.define('test.view.ListMarkers', {
  	extend: 'Ext.dataview.DataView',
    	xtype: 'listMarkersCard',
	config: {
        	itemTpl: '{title}'
    	}
});

I then created two stores, one for the remote data and one for the localStorage. The latter will sync with the other once it has loaded. The ‘remote’ store is just pulling data from a node.js app in turn calling a mongodb database on a local url that I have previously set-up (http:localhost:5000). More info on how to do this side here. The data is a list of markers intended for a map, but for this demo I am just listing their titles on the card I set-up previously. Once the store has loaded, the app will then use the localStorage data. The remote store was saved to app\store\Markers.js:

Ext.define('test.store.Markers', {
	extend: "Ext.data.Store",
	requires: [
        'Ext.data.proxy.JsonP'
    ],
	config: {
		storeId: 'markerStore',
		model: "test.model.Marker",
		proxy: {
			type: 'jsonp',
			callbackKey: 'callback',
        	url: 'http://localhost:5000/markers',
			reader: {
			    type: 'json',
			    rootProperty: 'data',
			    successProperty: 'success'
			}
  	 	}
	}
});

so this is calling a url to retrieve JSONP data.

The localStorage store is a lot simpler and is saved to app\store\MarkersLocalStorage.js

Ext.define('test.store.MarkersLocalStorage', {
	extend: "Ext.data.Store",
	config: {
		storeId: 'markerStoreLocalStorage',
		model: "test.model.Marker"
		//autoLoad: true
	}
});

Notice that both stores use the same Model, saved in app\model\Marker.js:

Ext.define("test.model.Marker", {
	extend: "Ext.data.Model",
	config: {
		fields: [
			{name: "markerID", type:"string"},
	     {name: "title", type: "string"},
	 	 {name: "lng", type: "string"},
	 	 {name: "lat", type: "string"},
	 	 {name: "icon", type: "string"},
	 	 {name: "description", type: "string"}
		],
		proxy: {
            type: 'localstorage',
            id  : 'proxyMarkers'
        }
     }
});

Finally, the app\controller\Main.js file that syncs the localStorage with the remote data when it shows the listMarkersCard:

Ext.define('test.controller.Main', {
    extend: 'Ext.app.Controller',
	config: {
		refs: {listMarkersCard:'listMarkersCard'},
		control: {
			listMarkersCard : {
				show: 'loadMarkers'
			}
		}
	},
	loadMarkers: function() {
		//set up refs to the two stores
		var markerStore = Ext.getStore('markerStore');
		var markerStoreLocalStorage= Ext.getStore('markerStoreLocalStorage');

		//load the localStorage store
		markerStoreLocalStorage.load();

		// check if localStorage contains data
		if ((markerStoreLocalStorage.getCount()) == 0) {
			// nothing found so  we need to load the data from external source
			console.log('localStorage data not found');
			//hand off to onMarkerStoreLoad function (below)
			markerStore.on({
			    load: 'onMarkerStoreLoad',
			    scope: this
			});
			//call load to trigger above
			markerStore.load();
		} else {
			// we are ok, just print some debug
			console.log('localStorage data found');
			console.log('localStorage count:' + markerStoreLocalStorage.getCount());
		}
		//finally set the list's store to localStorage
		this.getListMarkersCard().setStore(markerStoreLocalStorage);

	},
	onMarkerStoreLoad: function() {
		//set up refs
		var markerStoreLocalStorage= Ext.getStore('markerStoreLocalStorage');
		var markerStore = Ext.getStore('markerStore');
        	//loop through each data item and add to localStorage
		markerStore.each(function(item){
			markerStoreLocalStorage.add(item);
		});
		markerStoreLocalStorage.sync();
   	 }
});

Hopefully the comments describe what is happening but basically the data in the remote store is looped over and loaded into the localStorage. The listMarkersCard store is then set to the markerStoreLocalStorage store.

So that’s it in a nutshell. The localStorage loads up and the app uses that instead of calling on a url each time the list card loads. The functionality could be updated to refresh the localStorage only when something changes on the remote data or like with the original Sencha Touch 1 example only when the device is online. If anyone out there has some good methods or strategies on how to manage offline (localStorage) and online data using Sencha Touch then I’d love to hear them.

Advertisements

33 thoughts on “Sencha Touch 2 example of syncing localStorage store with remote JSONP proxy store

  1. Great article, I had issues with the items not being saved in localstorage. After a few hours digging around it turned out to be due to the id’s not being unique. I fixed this by changing the line: markerStoreLocalStorage.add(item); to markerStoreLocalStorage.add(item.copy()); which I presume forces the generation of a new id by creating the copy of the item instance allowing it to be saved again. This wasn’t obvious from anything I could find in the docs and I had no errors in the console but hopefully will help someone else save some time here!

    • Hi Nuthan, thanks for posting. Glad you liked it.

      The result from calling http://localhost:5000/markers?callback=callback is

      ({"success":true,"data":[{"_id":"4ff0aef861ec58f3cfb638a7","markerID":"7","title":"Centenary Square","lng":"-1.909347","lat":"52.478912","icon":"artsfest.png","description":"Centenary Square"},{"_id":"4ff0aef861ec58f3cfb638a8","markerID":"34","title":"Victoria Square","lng":"-1.902673","lat":"52.47983","icon":"artsfest.png","description":"Victoria Square"},{"_id":"4ff0aef861ec58f3cfb638a9","markerID":"11","title":"Chamberlain Square","lng":"-1.9042","lat":"52.4799","icon":"artsfest.png","description":"The centrepoint of Chamberlain Square, the Chamberlain Memorial was erected in 1880 in the presence of Joseph Chamberlan, who was a Birmingham businessmen, councillor, mayor and Member of Parliament. He died in 1914."},{"_id":"4ff0aef861ec58f3cfb638aa","markerID":"24","title":"Symphony Hall","lng":"-1.909969","lat":"52.478572","icon":"artsfest.png","description":"Symphony Hall"},{"_id":"4ff0aef861ec58f3cfb638ab","markerID":"26","title":"The Flapper Pub, Cambrian Wharf, Kingston Row","lng":"-1.912158","lat":"52.480454","icon":"artsfest.png","description":"The Flapper Pub, Cambrian Wharf, Kingston Row"},{"_id":"4ff0aef861ec58f3cfb638ac","markerID":"31","title":"Yardbird Jazz Club","lng":"-1.906171","lat":"52.479944","icon":"artsfest.png","description":"Yardbird Jazz Club"},{"_id":"4ff0aef861ec58f3cfb638ad","markerID":"30","title":"The Prince Of Wales Pub","lng":"-1.911106","lat":"52.479709","icon":"artsfest.png","description":"The Prince Of Wales Pub"},{"_id":"4ff0aef861ec58f3cfb638ae","markerID":"32","title":"Town Hall","lng":"-1.903564","lat":"52.479389","icon":"artsfest.png","description":"Town Hall"},{"_id":"4ff0aef861ec58f3cfb638af","markerID":"25","title":"The CBSO Centre","lng":"-1.909234","lat":"52.475619","icon":"artsfest.png","description":"The CBSO Centre"},{"_id":"4ff0aef861ec58f3cfb638b0","markerID":"37","title":"Birmingham Museum and Art Gallery","lng":"-1.903714","lat":"52.480173","icon":"artsfest.png","description":"The Citys Art Gallery, the home of the PRB collection amongst other fine art works."}]})
      

      note you need a callback parameter in the url as sencha will be expecting this.

      Hope this helps

      • Thanks for the reply Alex. The local-storage sync works fine. But my concern is, what if there is an update in the json from the remote server. How am i supposed to handle this?

      • Hi Nuthan. This is something I touch on at the end of the article. I haven’t put anything into practice yet but I was thinking something along the lines of using a time stamp stored separately in the database. This is updated each time the data is changed. You can then store a time stamp on the server side or on the sencha app to compare this with. If they are different you need to refresh the data.

  2. Cool man! I was wondering how to get data between jsonp proxy store and its model. Would have taken me forever to come to such a simple solution of having 2-different stores tied to the same model.

    Thanks for saving a few of my brains cells!

  3. Hi Alex, this worked 100% and thank you. When displaying the list on it own – it works. When placing it in a tabpanel however, the list no longer displays the list. The other tabs works. Everything else is identical to your app

    Ext.define(‘MyApp.view.Main’, {
    extend: ‘Ext.tab.Panel’,
    xtype: ‘maintabpanel’,

    requires: [
    ‘MyApp.view.Schedule’, // your main
    ‘MyApp.view.Settings’,
    ‘MyApp.view.About’,
    ‘MyApp.view.Welcome’
    ],
    config: {
    tabBarPosition: ‘bottom’,
    fullscreen: true,

    items: [
    {
    title: ‘Schedule’,
    iconCls: ‘bookmarks’,
    layout: ‘fit’,

    items: [
    {
    xtype: ‘titlebar’,
    docked: ‘top’,
    title: ‘Schedule’
    },
    {
    xtype: ‘listSchedulePage’ // xtype: ‘listSchedulePage’,
    }
    ]
    },
    {
    xtype: ‘settings’
    },
    {
    xtype: ‘about’
    },
    {
    xtype: ‘welcome’
    }
    ]
    }
    });

    • Hi Riyaad., glad it kind of worked for you. not sure how you are tying the store to your list (Schedule?) . I guess you are doing this in the controller. My only suggestion is that you use static data first and see if that works or view store items in the console and see if they have loaded.

  4. Thanks for this code sample! Just like the very first poster, I found that simply providing an ‘id’ for the model does not work. This way the data will not be stored in local storage and when I refresh the browser, the local storage will be empty. Not sure which version of Sencha Touch this was written in, but the release notes for v2.0.1 points out that the model needs to use the ‘uuid’ identifier strategy (Release note: http://dev.sencha.com/deploy/touch/release-notes.html; Model docs: http://docs.sencha.com/touch/2-1/#!/api/Ext.data.Model-cfg-identifier)

    So after adding an identifier config to the model with ‘uuid’, it worked! Thanks again!

    Model:
    config: {
    identifier: ‘uuid’
    }

  5. I don’t understand why you are using two stores to do this ?
    Ins’t it possible with only one store and manually changing the proxy ?
    For example, if my storage is empty THEN I manually set a proxy to retrieve data from the server and sync it..
    store = Ext.StoreMgr.get(‘YourStore’);
    store.setProxy({
    type: ‘ajax’,
    url: ‘http://www.yoururl.com?foo=bar’,
    });
    store.load();
    And next time, it’s automatically autoloaded from the localStorage..

      • Thanks for the example, it works perfect!
        I tried what kevin suggested but i couldn’t make it work. When I ask if the localStorage is empty, and if it is, i set the ajax proxy, then load the store and sync, but it never saves the data to the localStorage. Could you post an example of how to do it? Thanks

  6. I have 2 stores linked to the same model with proxy set to localstorage. The first store contains the data which I sync to the second localstore. Calling markerStore.sync() appears to copy the data into localstorage for me without looping through? I can see the items in the localstorage via the console and they remain there when I refresh. (NB Need to refresh twice to see the items in the browser ATM) (Using the latest Sencha Touch 2.2.0). Am I missing something? (This seems too easy.)

    // called when the Application is launched, remove if not needed
    launch: function(app) {

    //set up refs to the two stores
    var markerStore = Ext.getStore(‘serveritems’);
    var markerStoreLocalStorage= Ext.getStore(‘items’);

    //load the localStorage store
    markerStoreLocalStorage.load();

    // check if localStorage contains data
    if ((markerStoreLocalStorage.getCount()) == 0) {

    // no data so copy data from markerstore to localstorage
    console.log(‘no data found’);
    markerStore.sync();
    console.log(‘markerStore synced to localstorage’);

    } else {
    // we are ok, just print some debug
    console.log(‘localStorage data found’);
    console.log(‘localStorage count:’ + markerStoreLocalStorage.getCount());
    }
    //finally set the list’s store to localStorage
    this.getListView().setStore(markerStoreLocalStorage);
    console.log(‘store linked to list’);
    },

    • Thanks Heidi. This makes sense but Im missing how you are making the relationship between the two stores? Maybe send me your code to my email lalexgraham@hotmail.com …I need to rewrite this post as there are several improvements I can make and this can be one of them.

      • Both stores are linked to the same model so that on launch, if localstorage is empty, data is copied from serveritems store to the local storage model with id ‘shopping-items’.
        The items store (local store) uses the same model so when I set the listview store to this local store, it displays the items from localstorage; id ‘shopping items’.
        I’ve added quite a bit of extra, unrelated code to my project now which would probably complicate things but let me know if you still want to take a look.

        Ext.define(‘Sl.model.Item’, {
        extend: ‘Ext.data.Model’,
        requires: [‘Ext.data.identifier.Uuid’],

        config: {
        identifier : ‘uuid’,
        fields: [
        {
        name: ‘name’,
        type: ‘auto’
        }, {
        name: ‘count’,
        type: ‘auto’
        }, {
        name: ‘unit’,
        type: ‘auto’
        }, {
        name: ‘done’,
        type: ‘boolean’,
        defaultValue: false
        }],

        proxy: {
        type: ‘localstorage’,
        id: ‘shopping-items’
        }
        }
        });

  7. Hi, thank you for this tutorial, very great. I’ve seen that does not work with a NestedList. Obviously use a TreeStore like this:

    Ext.define(‘Grottaglie.store.Home’, {
    extend: ‘Ext.data.TreeStore’,

    requires: [
    ‘Grottaglie.model.Articolo’
    ],

    config: {
    autoLoad: true,
    model: ‘Grottaglie.model.Articolo’,
    storeId: ‘homeStore’,
    proxy: {
    type: ‘jsonp’,
    url: ‘http://grottaglie24.it/rss/ultime.php?callback=callback1’,
    callbackKey: ‘callback1’,
    reader: {
    type: ‘json’,
    rootProperty: ‘items’
    }
    }
    }
    });

    Can you give me any suggestions for how to apply the LocalStorage to this kind of view?
    Thaks, kind regards.

    • Hi Emanuele . Sorry Ive not really done much on TreeStores and haven’t had time to look into it. First of all are you getting anything from the jsonp proxy when loading it into the store? (loop through the store items and console.log them)

  8. Hey! thanks for your tutorial, it has gotten me very far. I have a slight issue though, I have local storage picking up the data from my remote store, but:

    1) it seems to iterate through my remote store twice, doubling my results in local storage.
    2) It doesn’t seem to be displaying the data in the view, either from my remote store or the local storage one. From what I can judge from your code it’s supposed to switch to the local storage store and display on the view but this isn’t happening.

    Do you have any idea why this may be?

    code is here: http://stackoverflow.com/questions/18577916/object-has-no-method-setstore-sencha-touch-2

    (ignore original topic of the issue, managed to get it setting the store because my view wasn’t set as a dataview. Doh!)

    • Hi james

      Thanks for the comments. Glad it helped, though sorry you are still having issues. I’ve read through your code and can’t see anything obvious that would cause a problem. As in previous replies I would just debug to test what data you are returning in the localstorage store. So …

      ticketStoreLocalStorage.each(function(item){
      console.log(item);
      });

      Is what you are getting back expected?

      With your view not displaying the data, is your ref getBuyTicketsView working I wonder? To test this just get the view to read from a store with inline data and see if it displays that? See inline data in the docs for more info: http://docs-origin.sencha.com/touch/2.2.1/#!/api/Ext.data.Store

      hth

      • Hey Alex,

        Thanks for replying. This may seem like a ridiculous resolution but after realising that everything was running successfully and the console was showing what i wanted, I had a search around and it was as simple as ensuring that your list has a height set and scrollable set as false when you render it! It all appears to be showing up nicely aside from the fact that it runs through, stores, and display my data twice. Did you have any issues with something like this?

      • Hi James. Glad you got it working. I have nothing in terms of height and scroll config on the list, no. Just tried it on the original code and the list shows. I call the list as the main view in app.js Ext.Viewport.add(Ext.create(‘test.view.ListMarkers’));. Do you do the same?

  9. Wow, very nice and easy example and working like a charm.

    I’m currently working on an auto-refresh functionality but I’m stuck on the finale part… Clearing the previous localStore and refilling it with all the new data. Instead of clearing AND refilling it he only clears it, without refilling it again.

    The steps:
    – load app for the first time with a connection => list is correctly initialised
    – (force) close the app and restart the application => previous local list is cleared but the localStore is not refilled with the new data…

    Would you mind taking a quick look at my code?

    My best thought is this is because of a delay in the clear while the list is already being refilled in the meantime?

    launch: function() {
    this.loadStores();
    },

    loadStores: function() {
    var illnessStore = Ext.getStore(‘Illnesses’);
    var illnessLocalStore= Ext.getStore(‘IllnessesLocal’);

    illnessLocalStore.load();

    illnessStore.getProxy().on(‘exception’, function (proxy, response, operation) {
    if (illnessLocalStore.getCount() == 0) {
    Ext.Msg.alert(
    ‘No connection’,
    ‘A connection is required. Put your WIFI on and try again.’,
    function() {
    Infections.app.getController(‘Application’).loadStores();
    });
    }
    });

    illnessStore.on({
    load: ‘onIllnessStoreLoad’,
    scope: this
    });

    illnessStore.load();

    this.getIllnessList().setStore(illnessLocalStore);
    },

    onIllnessStoreLoad: function() {
    var illnessStore = Ext.getStore(‘Illnesses’);
    var illnessLocalStore= Ext.getStore(‘IllnessesLocal’);

    if (illnessLocalStore.getCount() > 0) {
    illnessLocalStore.clear(); // this is where the list is cleared
    }

    illnessStore.each(function(item) {
    illnessLocalStore.add(item); // this should (re)fill the list but the second time the list os only cleared but never refilled…
    });

    illnessLocalStore.sync();
    },

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s