Friday, June 11, 2010

ItemFileReadStore and Custom Queries/Filters

By all accounts the ItemFileReadStore is great ... however, the out-of-the-box query syntax is very limited. (Follow the link to see what it can do) In this post, we'll explore a simple way to alter the ItemFileReadStore that allows highly customizable queries with the fetch method.

Let's keep it simple: We are working with a data store that has a list of people and their ages. We need to fetch all of the people that are between the ages of 21 and 40. Given the default query syntax, this would be impossible (well, I'm sure a regular expression guru could probably do it, but that isn't practical. Plus, it may not work when a more complex criteria is needed. ie: comparing two attributes from a given item) Intuitively, this would make a lot of sense:


//myStore is an instance of ItemFileReadStore
myStore.fetch(
{
    query:
    {
        age_filter: function(store, item)
        {
            var age = parseFloat(store.getValue(item, 'age'));
            return age >= 21 && age <= 40;
        }
    },
    onItem: function(item)
    {
        //do whatever with/to your returned items
    }
});


Instead of a query parameter, a function that returns a boolean value would be passed to the fetch method. The fetch method would then execute the function against every item in the ItemFileReadStore passing an instance of the data-store and the current item as arguments. If the function returns false for an item, it would not be included in the results.

This opens up the door to unlimited query possibilities ... however the default ItemFileReadStore does NOT work in this manner. Why not? If I were to guess, it's because it doesn't fit into the specifications of the Read API. For example, this would not work on a QueryReadStore because the server can't interpret a javascript function and therefore wouldn't know how to filter it's results.

You may also be asking why we just don't check the age in the onItem callback and handle this logic there. We'll see a good reason why when we use the DataGrid's filter method.

Now the fun part, lets make a small change to the ItemFileReadStore to implement this feature. I am using Dojo 1.4.3, and, of course, you need the source files (not the "compressed" files) Open up dojo/data/ItemFileReadStore.js and look for the _fetchItems method (should be around line 264) More specifically, we're going to look at a for loop in this function (line 288) This loop basically loops through all of the items in the data-store and determines whether or not they should be returned in the fetch results. Here is what it should look like once we're finished with it (notice the comments for the added lines of code)


for(i = 0; i < arrayOfItems.length; ++i){
    var match = true;
    var candidateItem = arrayOfItems[i];
    if(candidateItem === null){
        match = false;
    }else{
        for(key in requestArgs.query){
            value = requestArgs.query[key];
            // this is the beginning of what we add
            if(dojo.isFunction(value))
            {
                if(!value(self, candidateItem))
                    match = false;
            }
            // and this is the end ... notice the "else" in the next line as well
            else if(!self._containsValue(candidateItem, key, value, regexpList[key])){
                match = false;
            }
        }
    }
    if(match){
        items.push(candidateItem);
    }
}

Pretty simple. We just check each value of the query object to see if it is a function. If it is, we call the function passing this store and the current item as parameters. If it returns false, then we set the match to false and it doesn't get added in the fetch results.


Now we can pass in whatever function we want as a query criteria in the query object. Also notice that the keys in the query object don't matter (under the default query they do) In this example we used "age_filter," which doesn't exist as an attribute on any of the items in the data-store. And the existing functionality still works!


Where this really comes in handy is the DataGrid. With a data-grid that is backed by an ItemFileReadStore, we can now do this:

dataGrid.filter(
{
    age_filter: function(store, item)
    {
        var age = parseFloat(store.getValue(item, 'age'));
        return age >= 21 && age <= 40;
    }
});

So there you have it! 100% custom filtering criteria for a ItemFileReadStore and DataGrid. As an added bonus, since the ItemFileWriteStore inherits from the ItemFileReadStore, this functionality is already included ... if you make the small and simple change.

No comments:

Post a Comment