Refreshing a data store
by Josh Trutwin

So you’ve created a nice ComboBox widget connected to dojo.data.ItemFileReadStore that pulls your company’s lengthy list of employees from the HR server via JSON and all is well. Then someone asks you to put a button/link/widget next to the ComboBox that shows the company’s departments and wants you to filter the ComboBox list of employees by department asynchronously.

No problem you think, I can just modify my ComboBox data store URL and pass the selected department id on the query string. Unfortunately when you do this the ComboBox data doesn’t change, even though you verified your server-side JSON script is sending back the right values for deparement id params. You check FireBug and notice that no new requests are made to your server even though you called dijit.byId(’my_combobox’).store.fetch(). What happened?

This is actually a very common question on dojo IRC and the forums. The above is only one use-case for needing to “refresh” a data store, by that I mean to pull different or modified data from the server.

My original use-case for this was that I had a “Add New” button next to my ComboBox that added a new employee via a simple popup form, I wanted to have the ComboBox list updated after saving the popup form to include the new employee and was stumped when it didn’t show up.

This “problem” is not a bug, it’s an intentional design of dojo.data.ItemFileReadStore. Deep in the code for the data store there is a method called _fetchItems which is called whenever the data store’s fetch() method is called.

Within this function there is a check to see if server-side data has been loaded already (assuming you are using a URL and not inline data). If data hasn’t been loaded yet it will call dojo.xhrGet to fetch the JSON from the provided URL If it has already been loaded though it fetches your data from a local cache, not hitting the server again. Most of the time this is what you want the data store to do, no need to hit the server over and over again. When using ItemFileReadStore in normal operations, one and only one fetch from the server is made, subsequent fetches are pulled from client-side memory.

This explains why the following code does nothing other than change the url property of the store object:

dijit.byId('my_combobox').store.url = '/get_employees.php?dept_id=3';
dijit.byId('my_combobox').store.fetch();

The new URL is essentially ignored.

So how do you get around this? As I see it (and I could be wrong) there are 4 possibilities.

1.) Create your own copy of dojo.data.ItemFileReadStore and hack it to allow a refetch from the server. I’ve seen some postings in forums of people who’ve done this. I tried this myself, but didn’t get far before I realized there are probably better ways than mangling the store itself and creating code I’ll have to maintain with each dojo upgrade.

2.) Use dojox.data.QueryReadStore. This data store is designed to fetch data from the server multiple times and is very useful in its own right. But many new people to dojo see “dojox” and think “experimental” or “beta”. QueryReadStore is fairly stable actually, but it still a tough sell sometimes because of that “x”. One drawback with QueryReadStore is that you will need to modify your server-side scripts to return the correct JSON based on the additional query string arguments that the data store automatically adds to your URL.

3.) Wait until dojo 1.2 - a ticket I filed adds a close() method to ItemFileReadStore that will clear the local cache and allow a user to explicitly request a new server-side fetch. (see: http://trac.dojotoolkit.org/ticket/6073)

4.) Create a new data store object and assign it to your ComboBox. This is the easiest work-around that does not require using a different data store class or modifications to your server-side code.

new_store = new dojo.data.ItemFileReadStore({url: '/get_employees.php?dept_id=3'});
dijit.byId('my_combobox').store = new_store;

This should result in a new fetch from the server using whatever URL you want. You can even refresh with the same URL if you know there is new/updated data on the server:

new_store = new dojo.data.ItemFileReadStore({url: dijit.byId('my_combobox').store.url});
dijit.byId('my_combobox').store = new_store;

This does add additional overhead and memory use as you’ll have two store objects, so you could combine this into one line to avoid a new variable:

dijit.byId('my_combobox').store = new dojo.data.ItemFileReadStore({url: '/foo.php'});

Note: this technique is not unique to the ComboBox widget but would also apply to any data store driven widget like Grids, Trees, etc.

Please let me know if you have any comments!

Tags: , ,

This entry was posted on Wednesday, June 4th, 2008 at 9:57 pm and is filed under Dojo Cookies. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

13 Responses to “Refreshing a data store”

  1. mikedef Says:

    this isnt realy working with trees. i tried it by creaing a new store for the treeModel but the tree wasn’t updated at all. any hints?

  2. trutwijd Says:

    Hmm - looking over the tree source it appears that each TreeNode maintains its own “state”. If a node has been expanded and you collapse it and re-expand it it will not query the data model on the second expand as the TreeNode considers its children as already having been loaded. It would be possible if you could easily find a way to do this (psuedocode):


    foreach myTree.getExpanededNodes as n
    n.state = "UNCHECKED"
    n.isExpanded = false
    n.expand()

    Basically for each node in the tree that is already expanded, set its state to UNCHECKED and call the code to re-expand the nodes. By having the state as UNCHECKED it will hit the updated data store instead of in-memory cache.

    You can try this, but it only works if persist=true is set on your tree:


    dojo.forEach(myTree._openedItemIds, function (item) {
    n = myTree._itemNodeMap(item);
    n.state = "UNCHECKED"
    n.isExpanded = false;
    n.expand();
    });

    Note - the above is untested. persist=true is needed because it is the only way that myTree._openedItemIds is maintained. I’m checking with the dev’s to see if there’s a better way.

    Hope that helps,

    Josh

  3. trutwijd Says:

    Sorry - better code formatting:

    foreach myTree.getExpanededNodes as n
       n.state = "UNCHECKED"
       n.isExpanded = false
       n.expand()
    

    and

    dojo.forEach(myTree._openedItemIds, function (item) {
       n = myTree._itemNodeMap(item);
       n.state = "UNCHECKED"
       n.isExpanded = false;
       n.expand();
    });
    
  4. mikedef Says:

    thanks for the advise. i will take a look at it when i got some time left

  5. mikedef Says:

    i tried the following code with my tree and persist was true, but still nothing happens. any other suggestions? i could destroy the old tree any time i need to refresh the data and substitute it with a new one, but this was i have to build all the context menus again as well.

    var new_store = new dojo.data.ItemFileReadStore({url: ‘poptarts.txt’});
    new_store.fetch();

    var myTree = dijit.byId(”tree”);

    myTree.store = new_store;

    dojo.forEach(myTree._openedItemIds, function (item) {
    n = myTree._itemNodeMap(item);
    n.state = “UNCHECKED”
    n.isExpanded = false;
    n.expand();
    });

  6. binspaul Says:

    For me, the above mentioned methods are not working. The tree is not getting updated.

    METHOD 1 :

    var _this = this;
    newStore = new dojo.data.ItemFileWriteStore({url: ‘test.html?hidValAction=getjsondata’, jsId: “datStore”});
    newStore.fetch();
    _this.tree.model.store.close();
    _this.tree.store = newStore;

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    METHOD 2 :

    var _this = this;

    _this.tree.model.store = new dojo.data.ItemFileWriteStore({url: _this.tree.model.store.url});

    _this.tree.model.store.fetch();

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    The store is getting populated. But it is not getting reflected in the tree.

    Best regards,
    Binu Paul

  7. binspaul Says:

    Why is there a difference in the store fetched earlier and the store fetched after ?

    console.info(”Tree store (before) : “);
    console.info(_this.tree.store);

    // Create a new store.
    newStore = new dojo.data.ItemFileWriteStore({url: ‘folders.html?hidValAction=getjsondata’, jsId: “newFolderStore”});
    newStore.fetch();
    _this.tree.store = newStore;

    console.info(”Tree store (after) : “);
    console.info(_this.tree.store);

    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    The output obtained is :

    Tree store (before) :
    Object _arrayOfAllItems=[18] _arrayOfTopLevelItems=[18]

    GET ………/folders.html?hidValAction=getjsondata

    Tree store (after) :
    Object _arrayOfAllItems=[0] _arrayOfTopLevelItems=[0]

    Best regards,
    Binu Paul

  8. trutwijd Says:

    Sorry I don’t have any answers for refreshing a Tree - did talk to one of the Tree dev’s and sounded like the above method (looping over _openedItemIds) should have at least worked. I’ll try to look into it some more to see if there is a solution. The main problem seems to be convincing a tree node not to check its local data cache… It might be helpful to file an enhancement request on trac. (bugs.dojotoolkit.org)

    P.S. binspaul, datastore.close() function is not yet available, need to wait till dojo 1.2 for that.

  9. clopatofsky Says:

    I had a similar problem, I´m using Dojo 1.1 cause migrating to 1.2 its a very painful issue at this moment. The way I solved the problem was using trutwijd’s suggestion, but setting the tree persistence in false.

    You must save into a variable the node’s reference, in my case was easy using a onClick event but maybe in your own apps you’ll need another method:

    itemSeleccionado = item;
    nodoSeleccionado = node;
    nodoSeleccionado.state = “UNCHECKED”;
    console.log(item);
    console.log(nodoSeleccionado);
    ……………
    ……………

    Finally when you must collapse and re-expand the corresponding node you must do it with the tree widget, if you do it directly with the node the tree will stay unmodifyed. You can place the following piece of code wherever you need to refresh the node.

    if(nodoSeleccionado.isExpanded)
    {
    console.log(”tratando de re-expandir”);
    arbolWBS._collapseNode(nodoSeleccionado);
    arbolWBS._expandNode(nodoSeleccionado);
    }

    Kind regards from Colombia an thanks to trutwijd for helping me to solve this problem. Hope it will help somebody,

    Carlos Omaña.

  10. clopatofsky Says:

    Sorry the previous post process the code I wrote:

    I had a similar problem, I´m using Dojo 1.1 cause migrating to 1.2 its a very painful issue at this moment. The way I solved the problem was using trutwijd’s suggestion, but setting the tree persistence in false.

    You must save into a variable the node’s reference, in my case was easy using a onClick event but maybe in your own apps you’ll need another method:

    itemSeleccionado = item;
    nodoSeleccionado = node;
    nodoSeleccionado.state = “UNCHECKED”;
    console.log(item);
    console.log(nodoSeleccionado);
    ……………
    ……………


    Finally when you must collapse and re-expand the corresponding node you must do it with the tree widget, if you do it directly with the node the tree will stay unmodifyed. You can place the following piece of code wherever you need to refresh the node.


    if(nodoSeleccionado.isExpanded)
    {
    console.log(”tratando de re-expandir”);
    arbolWBS._collapseNode(nodoSeleccionado);
    arbolWBS._expandNode(nodoSeleccionado);
    }

    Kind regards from Colombia an thanks to trutwijd for helping me to solve this problem. Hope it will help somebody,

    Carlos Omaña.

  11. clopatofsky Says:

    Sorry the previous post process the code I wrote:

    I had a similar problem, I´m using Dojo 1.1 cause migrating to 1.2 its a very painful issue at this moment. The way I solved the problem was using trutwijd’s suggestion, but setting the tree persistence in false.

    You must save into a variable the node’s reference, in my case was easy using a onClick event but maybe in your own apps you’ll need another method:

    itemSeleccionado = item;
    nodoSeleccionado = node;
    nodoSeleccionado.state = “UNCHECKED”;
    console.log(item);
    console.log(nodoSeleccionado);
    ……………
    ……………
    
    

    Finally when you must collapse and re-expand the corresponding node you must do it with the tree widget, if you do it directly with the node the tree will stay unmodifyed. You can place the following piece of code wherever you need to refresh the node.


    if(nodoSeleccionado.isExpanded)
    {
    console.log(”tratando de re-expandir”);
    arbolWBS._collapseNode(nodoSeleccionado);
    arbolWBS._expandNode(nodoSeleccionado);
    }

    Kind regards from Colombia an thanks to trutwijd for helping me to solve this problem. Hope it will help somebody,

    Carlos Omaña.

  12. clopatofsky Says:

    the code is note displaying correctly, any question e-mail me.

  13. trutwijd Says:

    Newer versions of dojo have better API’s for this with 1.4 having the nicest - see:

    http://dojocampus.org/content/2009/01/31/refeshing-an-itemfilereadstore/

    http://docs.dojocampus.org/dojo/data/ItemFileReadStore#reloading-refreshing-itemfilereadstore-from-a-url-dojo-toolkit-1-4

Leave a Reply

You must be logged in to post a comment.