Over a million developers have joined DZone.

Remote Grouping in a Sencha Touch Store

· Java Zone

Discover how AppDynamics steps in to upgrade your performance game and prevent your enterprise from these top 10 Java performance problems, brought to you in partnership with AppDynamics.

In this article you are going to learn how to perform remote grouping in a Sencha Touch Store using a .Net backend. These are the topics that the tutorial covers:

  • Using a Sencha Touch Store’s grouper config
  • Using the Store Class’s remoteGroup config
  • Using the Store Class’s setGrouper method
  • Using the Store Class’s setGroupField method
  • Using the Store Class’s setGroupDir method

Creating the Sample App

The app we will use as a testbed is almost an exact copy of the one we used in the Sencha Touch Stores client-side grouping tutorial I published recently.

You can use any version of Visual Studio to create your project. Once you create a Visual Studio Project for the app, create the following directories and files:

remote grouping in a sencha touch store

Make sure to include the Sencha Touch libraries in the index.html file. My file looks like the screenshot below:

sencha-touch-stores-server-side-grouping-5

Creating the Model

We need to create a Sencha Touch Model for the records that we will place in the Store. In the Model directory, create the Product.js file and type the following model definition:

Ext.define('App.model.Product', {
    extend: 'Ext.data.Model',
    config: {
        idProperty: 'productCode',
        fields: [
            { name: 'productCode', type: 'string' },
            { name: 'productName', type: 'string' },
            { name: 'productLine', type: 'string' },
            { name: 'color', type: 'string' }
        ]
    }
});

We will declare this model in the models config of the app in the app.js file.

Ext.application({
    name: 'App',
    models: ['Product'],
    launch: function () {

    }
});

Creating the Store

We will also create the Products.js file in the Store directory. This is the definition of the Store:

Ext.define('App.store.ProductsRemote', {
    extend: 'Ext.data.Store',
    config: {
        model: 'App.model.Product',
        autoSync: false,
        proxy: {
            type: 'ajax',
            api: {
                read: '../../services/collectibles.ashx'
            },
            reader: {
                rootProperty: 'products'
            }
        },
        grouper: {
            property: 'productLine',
            direction:'ASC'
        },
        remoteGroup: true
    }
});

The grouper config defines how the Store’s records will be grouped. Grouping works by applying a first level of sorting to the Store’s records. The property config of the Grouper object is the property that will be used to group the Store’s records. The direction config is the direction used to sort the groups. If the sorters config is present, the sorters are applied after the sort defined by the grouper config. It is up to any visual components that use the Store to render visual representation of the groups.

The remoteGroup config tells the Store whether to defer grouping operations to the server. In this example we are setting it to true.

Note that when we use a buffered Store, the remoteGroup config is set to true by default, as a buffered Store holds on the client side only a subset of the dataset available on the server.

As I explained in my previous Sencha Touch Stores grouping article, other frequently used grouper configs are the groupFn and the sortProperty. We can use the groupFn to customize how the groups are defined. Commonly you would group records based on a property of the model. With groupFn we can provide a function that uses arbitrary criteria to define the groups.

The sortProperty config allows us to sort the groups by a property other than the groups themselves, whether the groups are defined by the property config or by the groupFn config.

Let’s make sure to declare the Store in the stores config of the application in the app.js file:

Ext.application({
    name: 'App',
    models: ['Product'],
    stores: ['ProductsRemote'],
    launch: function () {

    }
});

A Server-Side Handler to Send Data to a Sencha Touch Store

We will write this handler in C#. Let’s create a services directory in the app, and then add a generic handler called Collectibles.ashx to the directory. The handler should look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Newtonsoft.Json;

namespace STSort.services
{
    /// <summary>
    /// Summary description for Collectibles
    /// </summary>
    public class Collectibles : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {

        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

Note the reference to the Newtonsoft.Json namespace. This is provided be the Json.Net library, which we need to add as a reference to the Visual Studio project:

sencha-touch-stores-remote-grouping

We will leave the handler empty for the time being so we can create a few model classes that will help us send sorted data to the Sencha Touch app.

The Server-Side Product Model

We need a server-side model that we need the equivalent of the Product model in the Sencha Touch app. Let’s add a Product.cs file in the services directory, and type the following code in the file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Newtonsoft.Json;

namespace STSort.services
{
    public class Product
    {
        [JsonProperty(PropertyName = "productCode")]
        public string ProductCode { get; set; }

         [JsonProperty(PropertyName = "productName")]
        public string ProductName { get; set; }

        [JsonProperty(PropertyName = "productLine")]
        public string ProductLine { get; set; }

      [JsonProperty(PropertyName = "color")]
       public string Color { get; set; }

    }
}

Similar to the client-side Product model that we defined in the model/Product.js file, the server-side Product Class has the ProductCode, ProductName and ProductLine properties.

We decorated each property with the JsonProperty attribute provided by the Json.Net library. This attribute allows us to change the names of the Class’s properties when they are serialized to Json. We want each serialized property’s name to match the name of its equivalent property in the Product model of the Sencha Touch app. For example, ProductName becomes productName when serialized to Json.

The ProductsResult Model

When you send data to a Sencha Touch Store through its data reader, you need to use an object with at least two properties: a “success” property that tells the Store whether the request to the server was successful, and a property that contains an array of records with the data for the Store.

The next model we will create will help us do just that. It will contain a list with the records to be sent to the ProductsRemote Store, and a boolean Success property so we can tell the Store that its request was processed without issues.

In the services directory, let’s create the ProductsResult.cs file and type the following class definition:

public class ProductsResult
{
    [JsonProperty(PropertyName = "success")]
    public bool Success { get; set; }

    [JsonProperty(PropertyName = "products")]
    public List<product> Products { get; set; }
}

When serialized to Json, an instance of the ProductResult class will become a string with a format similar to the sample below, which is a format that the Store’s Reader can understand:

{"success":true,"products":[{"productCode":"S18_1097","productName":"1940 Ford Pickup Truck","productLine":"Trucks and Buses"},{"productCode":"S10_1949","productName":"1952 Alpine Renault 1300","productLine":"Classic Cars"},{"productCode":"S12_4473","productName":"1957 Chevy Pickup","productLine":"Trucks and Buses"}]}

The Group Model

The third model that we need will help us extract the group parameters sent by the ProductsRemote Store from the http request. It is critical that you understand how to do this because this is how the Sencha Touch Store tells the server endpoint how to group the records.

The group parameters sent by the Store define the data model’s properties the Store’s data need to be grouped by. Let’s create the Group.cs file in the services directory. The Group Class will look like this:

public class Group
{
    [JsonProperty(PropertyName = "property")]
    public string Property { get; set; }

    [JsonProperty(PropertyName = "direction")]
    public string Direction { get; set; }
}

When we create an instance of the Group Class, the value of the Property property is the name of a data model’s property that we want to group by. The value of the Direction property will define whether we want to sort ascending of descending. Here is an example so it is easier for you to understand:

Group grp = new Group() 
{
Property = "ProductLine",
Direction = "ASC"
};

Creating Dummy Records for the Sencha Touch Store

Back in the Collectibles.ashx handler, the first step we will take is to create a products list with a few dummy products that we will send to the Sencha Touch app:

public void ProcessRequest(HttpContext context)
{
    // Create a few dummy products.
    var products = new List<product>()
    {
        new Product() {
            ProductCode = "S10_1678",
            ProductName = "1969 Harley Davidson Ultimate Chopper",
            ProductLine = "Motorcycles"
        },
        new Product() {
            ProductCode = "S10_1949",
            ProductName = "1952 Alpine Renault 1300",
            ProductLine = "Classic Cars"
        },
        new Product() {
            ProductCode = "S10_2016",
            ProductName = "1996 Moto Guzzi 1100i",
            ProductLine = "Motorcycles"
        },
        new Product() {
            ProductCode = "S10_4698",
            ProductName = "2003 Harley-Davidson Eagle Drag Bike",
            ProductLine = "Motorcycles"
        },
        new Product() {
            ProductCode = "S12_1666",
            ProductName = "1958 Setra Bus",
            ProductLine = "Trucks and Buses"
        },
        new Product() {
            ProductCode = "S10_4757",
            ProductName = "1972 Alfa Romeo GTA",
            ProductLine = "Classic Cars"
        },
        new Product() {
            ProductCode = "S12_4473",
            ProductName = "1957 Chevy Pickup",
            ProductLine = "Trucks and Buses"
        },
        new Product() {
            ProductCode = "S18_1097",
            ProductName = "1940 Ford Pickup Truck",
            ProductLine = "Trucks and Buses"
        }
    };
}

In a real-world app you will likely retrieve these products from a database. I am hard-coding the list in order to keep this tutorial short.

Capturing the Group Parameters Sent by the Sencha Touch Store to the Server

After creating the products list, we need to find out if the ProductsRemote Store in the Sencha Touch app needs these products grouped in any particular way. We can do this by checking if the query string of the request contains the “group” key.

The group query string parameter is sent by the Store’s proxy on behalf of the Store when we define a grouper config, or when we call any of the group methods we are already familiar with. We are going to capture this parameter using the following code:

public void ProcessRequest(HttpContext context)
{
    // Create a few dummy products.
    // (Omitted for brevity)

    Group[] groups = null;
    string groupJson = context.Request.QueryString["group"];


    if (groupJson != null)
    {
        groups = JsonConvert.DeserializeObject<group []>(groupJson);
    }
}

The value of the group parameter is a Json representation of the grouper object in use by the Store. In the code above we de-serialize this object it into the groups array, which ends up containing one ore more instances of the Group Class that we created a few minutes ago.

Having captured the groups information sent by the Sencha Touch Store, we can use it to sort the data models that we will send to the Store.

Performing the Sever-Side Group for the Sencha Touch Store

Note how the concept of grouping is reduced in the server side to sorting the records in accordance with the groups defined.

public void ProcessRequest(HttpContext context)
{
    // Create a few dummy products. (Omitted for brevity)

    // Capture group parameters sent by the Sencha Touch Store (Omitted for brevity)


if (groups != null)
{
    foreach (Group group in groups)
    {
        switch (group.Property)
        {
            case "productCode":
                if (group.Direction.ToUpper() == "ASC")
                {
                    products = products.OrderBy(p => p.ProductCode).ToList();
                }
                else
                {
                    products = products.OrderByDescending(p => p.ProductCode).ToList();
                }
                break;
            case "productName":
                if (group.Direction.ToUpper() == "ASC")
                {
                    products = products.OrderBy(p => p.ProductName).ToList();
                }
                else
                {
                    products = products.OrderByDescending(p => p.ProductName).ToList();
                }
                break;
            case "productLine":
                if (group.Direction.ToUpper() == "ASC")
                {
                    products = products.OrderBy(p => p.ProductLine).ToList();
                }
                else
                {
                    products = products.OrderByDescending(p => p.ProductLine).ToList();
                }
                break;
            default:
                break;
        }
    }
}
 }

For each group in the groups array, we inspect the value of the Property and Direction properties so we can sort as desired. Sorting in .Net is a simple process using Linq’s OrderBy and OrderByDescending methods.

Sending Data to the Sencha Touch Store

We can send the sorted data to the Sencha Touch Store with a few lines of code:

public void ProcessRequest(HttpContext context)
{
    // Create a few dummy products. (Omitted for brevity)

    // Capture sort parameters sent by the Sencha Touch Store (Omitted for brevity)

    // Perform the sort (Omitted for brevity)

    var result = new ProductsResult()
    {
        Success = true,
        Products = products
    };
    context.Response.ContentType = "application/json";
    context.Response.Write(JsonConvert.SerializeObject(result));
}

Here’s where the ProducsResult Class becomes helpful. To send the sorted products list to the Sencha Touch app, we create an instance of the ProductsResult Class, set its Success property to true, and load the sorted products list into the Products property. Finally, we Json-serialize the ProductsResult instance and send it to the app using the Response.Write method.

Defining a Handler for the Store’s Load Event

With the server-side code in place, we can look at the grouping features of the store. In the app.js file, let’s first define a handler for the store’s load event The handler will allow us to perform grouping operations on the store and see the results.

Ext.application({
    name: 'App',
    models: ['Product'],
    stores: ['Products'], 
    launch: function () {

        // Use the getStore methodto get a reference to a store added via the stores config.
        var productsStore = Ext.getStore('Products');

        productsStore.on({
            load: this.onStoreLoad
        });

        productsStore.load();


    },

    onStoreLoad: function (store, records) {

    }
});   

Using a Sencha Touch Store’s Grouper Config

Let’s first explore the grouper config, which is what use at design time to define how the Store’s records will be grouped:

grouper: {
    property: 'productLine',
    direction:'ASC'
}

In order to see what happens to the store’s records when we apply this config, we will add the following code in the onStoreLoad handler:

onStoreLoad: function (store, records) {

    var grouper = store.getGrouper();

    console.log('Records grouped by ' + grouper.getProperty() + ' ' + grouper.getDirection() + ':');
    store.each(function (record) {
        console.log('- ' + record.get(grouper.getProperty()) + ' - ' + record.get('productName'));
    });
}

In the handler we first invoke the store’s getGrouper method to acquire a reference to the store’s grouper. Then, we log the grouper’s property and direction configs to the console by invoking their respective getter methods, getProperty and getDirection:

console.log('Records grouped by ' + grouper.getProperty() + ' ' + grouper.getDirection() + ':');

Last, we loop through the store’s records, logging the value of the record’s group and the value of the productName field:

store.each(function (record) {
        console.log('- ' + record.get(grouper.getProperty()) + ' - ' + record.get('productName'));
});

We can browse to the index.html file of the app in Google Chrome and see the log in the JavaScript Console:

sencha-touch-stores-server-side-grouping-2

The SetGrouper Method of a Sencha Touch Store

The Store’s setGrouper method allows us to set the Store’s grouper at run time. To see it in action we will modify the launch method of the application as follows:

launch: function () {

        // Use the getStore function to get a reference to a store added via the stores config.
        var productsStore = Ext.getStore('Products');

        productsStore.on({
            load: this.onStoreLoad
        });

        productsStore.load();


        var updateTask = Ext.create('Ext.util.DelayedTask', function () {

            // Reconfiguring grouper in a delayed task, just to wait for the previous operations  
            // to execute on the server and invoke the callbacks. 
            productsStore.setGrouper({
                property: 'color',
                direction: 'DESC'
            });

            // You have to reload for the new grouper to take effect.
            productsStore.load();

        }, this);

        updateTask.delay(2000);
},

What we are doing here is switching the grouper from productLine ASC, which is what we set through the grouper config, to color DESC. After calling setGrouper we have to reload the Store so the grouper change takes effect. We use
a delayed task just to wait for the previous operation to execute on the server and invoke the callbacks

Refreshing the index.html in Google Chrome should produce a second log that looks like the screenshot below.

sencha-touch-stores-server-side-grouping-3

Using the SetGroupField and SetGroupDir Methods of a Sencha Touch Store

A couple of methods that you might want to try are setGroupField and setGroupDir. As their names indicate, they are setter methods for the Store’s groupField and groupDir configs. Inside the Store, these configs are mapped to the Store’s grouper’s property and direction configs.

Let’s use these methods in an example so you can see how they work. Back in the app’s launch function, we will make the following changes:

launch: function () {

    // Use the getStore function to get a reference to a store added via the stores config.
    var productsStore = Ext.getStore('ProductsRemote');

    productsStore.on({
        load: this.onStoreLoad
    });

    productsStore.load();        

    var updateTask = Ext.create('Ext.util.DelayedTask', function () {

        // Reconfiguring grouper in a delayed task, just to wait for the previous operations  
        // to execute on the server and invoke the callbacks. 
        productsStore.setGrouper({
            property: 'color',
            direction: 'DESC'
        });

        // You have to reload for the new grouper to take effect.
        productsStore.load();

    }, this);

    updateTask.delay(2000);

    updateTask = Ext.create('Ext.util.DelayedTask', function () {

        // Reconfiguring grouper in a delayed task, just to wait for the previous operations  
        // to execute on the server and invoke the callbacks. 
        productsStore.setGroupField('productLine');
        productsStore.setGroupDir('DESC');
        productsStore.load();

    }, this);

    updateTask.delay(4000);

},

Note the calls to setGroupField and setGroupDir towards the end of the launch function. If we refresh index.html in Google Chrome the log should reflect the re-configured grouper’s effect on the records of the Store:

sencha-touch-stores-server-side-grouping-4

The setGroupField and setGroupDir methods might seem redundant given the fact that there Store has a setGrouper method. However, there are situations where it is convenient to re-configure the existing grouper instead of creating a new one.

If at the time that we call setGroupField or setGroupDir the Store does not have a grouper yet, the Store will instantiate and configure a grouper with the values we are passing through.

How to Set Up a Custom Grouper in a Sencha Touch Store

The last feature I wanted to talk about in this article is how to do custom grouping in a Sencha Touch Store. You can inject the grouping logic into the Store in the form of a function where you can use arbitrary criteria to define how the Store will group its records. To see how this works, let’s modify the app’s launch function so it looks like the following code:

launch: function () {

    // Use the getStore function to get a reference to a store added via the stores config.
    var productsStore = Ext.getStore('ProductsRemote');

    productsStore.on({
        load: this.onStoreLoad
    });

    productsStore.load();        

    var updateTask = Ext.create('Ext.util.DelayedTask', function () {

        // Reconfiguring grouper in a delayed task, just to wait for the previous operations  
        // to execute on the server and invoke the callbacks. 
        productsStore.setGrouper({
            property: 'color',
            direction: 'DESC'
        });

        // You have to reload for the new grouper to take effect.
        productsStore.load();

    }, this);

    updateTask.delay(2000);

    updateTask = Ext.create('Ext.util.DelayedTask', function () {

        // Reconfiguring grouper in a delayed task, just to wait for the previous operations  
        // to execute on the server and invoke the callbacks. 
        productsStore.setGroupField('productLine');
        productsStore.setGroupDir('DESC');
        productsStore.load();

    }, this);

    updateTask.delay(4000);

    updateTask = Ext.create('Ext.util.DelayedTask', function () {

        // Reconfiguring grouper in a delayed task, just to wait for the previous operations  
        // to execute on the server and invoke the callbacks. 
        productsStore.setGrouper({
            groupFn: function (record) {
                return record.get('productName').substring(0,4);
            },
            sortProperty: 'productName'
        });

        productsStore.load();

    }, this);

    updateTask.delay(6000);

},

Pay attention to the last few lines, where we are calling the setGrouper method and using the groupFn to define a custom grouping function. In this simple example I use the first four letters of the productName field to define the groups. As I said earlier, you can use arbitrary logic here basic on the specific business rules of your application.

In order to see the results of using the custom grouper we just created, we will make a small change to the onStoreLoad handler in the app.js file. First, we will define a loopsCounter variable right before the application’s definition:

var loopsCounter = 0;

Ext.application({

     // Details omitted for brevity.

});      

Then, we will modify the onStoreLoad handler like so:

onStoreLoad: function (store, records) {

    var grouper = store.getGrouper();

    if (loopsCounter < 3) {

        console.log('Records grouped by ' + grouper.getProperty() + ' ' + grouper.getDirection() + ':');
        store.each(function (record) {
            console.log('- ' + record.get(grouper.getProperty()) + ' - ' + record.get('productName'));
        });
    } else {
        console.log('Records grouped by first four chars of productName ' + grouper.getDirection() + ':');
        store.each(function (record) {
            console.log('- ' + record.get('productName'));
        });
    }

    loopsCounter++;
}

In the code above I use the loopsCounter variable to change the message that I send to the console. The reason for this change is that in the custom grouper example I do not use the Grouper’s property config and therefore I do not need to include the value of this config in the message that I send to the console.

Another refresh to the index.html file in Google Chrome should produce the results below in the developer console.

sencha-touch-stores-server-side-grouping-6

Download the Source Code

Download from GitHub: Sencha Touch Store Remote Grouping tutorial by MiamiCoder

Want to Learn More About Mobile Web Apps Development?

MiamiCoder’s Sencha Touch and jQuery Mobile books will guide you, step by step, through the process of building Sencha Touch and jQuery Mobile applications. If you like to learn by doing, these books are for you.

The Java Zone is brought to you in partnership with AppDynamics. AppDynamics helps you gain the fundamentals behind application performance, and implement best practices so you can proactively analyze and act on performance problems as they arise, and more specifically with your Java applications. Start a Free Trial.

Topics:
java,mobile,tutorial,.net & windows,tools & methods,sencha touch

Published at DZone with permission of Jorge Ramon , DZone MVB .

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}