9. Paged Collections

 Documentation Summary
 Page Summary

Using the PagedQuery Component

The PagedQuery component provides a client view on a remote component executing queries. It extends ListCollectionView, and thus can be used as a data provider for any Flex UI component.

The collection is completely paged and keeps in memory only the data needed for the current display. In fact, it has to keep two complete pages to avoid too many server calls.

It supports automatic remote sorting and filtering. The server-side part of the paging depends on the server technology and is described in the next paragraphs.

On the client-side, you first need to register the client component with:

<mx:Script>
  <![CDATA[
    import org.granite.tide.collections.PagedQuery;

    Tide.getInstance().addComponent("people", PagedQuery);
  ]]>
</mx:Script>

This registers a client component with a page size defined by the server. It's also possible to define the page size on the client with :

<mx:Script>
  <![CDATA[
    Tide.getInstance().addComponentWithFactory("people", PagedQuery, 
        { maxResults: 36 });
  ]]>
</mx:Script>

When using the Tide client framework, that can be done in a Tide module initializer as any other component declaration.

It is important that this registration is done in a static block initializer of the main MXML class, because it has to be defined before the first reference in a component, and in particular before any data binding on context.people is initialized by Flex.

That's all. Just bind the component as a data provider for any component and it should work as expected:

<mx:DataGrid id="people" dataProvider="{ctx.people}" width="100%" height="100%"
    liveScrolling="false">
    <mx:columns>
        <mx:DataGridColumn dataField="firstName" headerText="First name"/>
        <mx:DataGridColumn dataField="lastName" headerText="Last name"/>
    </mx:columns>
</mx:DataGrid>

The DataGrid sorting triggers a remote refresh of the collection, and the changes on the data filter are maintained during the remote refresh, so the filtering is also done remotely.

It is important to disable liveScrolling to avoid excessive remote traffic.
Flex 4 Spark controls do not handle ItemPendingError by themselves and need a special wrapper AsyncListView
<s:List>
    <mx:AsyncListView list="{people}"/>
</s:List>

See here for more details.

Just like the paged collections of LCDS, everything works only if there is a correct uid property on the entities. It is thus recommended to use the Tide templates for the Gas3 generator, which provide a default implementation of uid if there is no uid property on the entities.

Server-Side Implementation

You will then have to write a server component that handles the querying of the data.

Untyped filter

The server component should implement the following method:

public Map find(Map filter, int first, int max, String order, boolean desc);

first, max, order and desc are straightforward. filter is a map containing the parameter values of the query. These values can be set on the client by:

tideContext.queryBean.filter.<parameter1> = <value1>;
tideContext.queryBean.filter.<parameter2> = <value2>;
...

The return object must be a map containing four properties:

  • firstResult: Should be exactly the same as the argument passed in (int first).
  • maxResults: Should be exactly the same as the argument passed in (int max), except when its value is 0, meaning that the client component is initializing and needs a max value. In this case, you have to set the page value, which must absolutely be greater than the maximum expected number of elements displayed simultaneously in a table.
  • resultCount: Number of results.
  • resultList: List of results.

The following code snippet is a quick and dirty implementation and can be used as a base for other implementations (here this is a Spring service but the Seam, EJB3, CDI would be extremely similar) :

PeopleServiceImpl.java
@Service("people")
@Transactional(readOnly=true)
public class PeopleServiceImpl implements PeopleService {

    @PersistenceContext
    protected EntityManager manager;

    public Map find(Map filter, int first, int max, String order, boolean desc) {
        Map result = new HashMap(4);

        String from = "from Person e ";
        String where = "where lower(e.lastName) like '%' || lower(:lastName) || '%' ";
        String orderBy = (
            order != null ? "order by e." + order + (desc ? " desc" : "") : ""
        );
        String lastName = (
            filter.containsKey("lastName") ? (String)filter.get("lastName") : ""
        );

        Query qc = manager.createQuery("select count(e) " + from + where);
        qc.setParameter("lastName", lastName);
        long resultCount = (Long)qc.getSingleResult();

        if (max == 0)
            max = 36;

        Query ql = manager.createQuery("select e " + from + where + orderBy);
        ql.setFirstResult(first);
        ql.setMaxResults(max);
        ql.setParameter("lastName", lastName);
        List resultList = ql.getResultList();

        result.put("firstResult", first);
        result.put("maxResults", max);
        result.put("resultCount", resultCount);
        result.put("resultList", resultList);

        return result;
    }
}

It is also possible to define on the Flex size an alternative remote component name and method name that will implement the querying :

<mx:Script>
  <![CDATA[
    Spring.getInstance().addComponentWithFactory("people", PagedQuery, 
        { maxResults: 36, remoteComponentName: "peopleService", methodName: "list" });
  ]]>
</mx:Script>

In this case, the previous component would be :

PeopleServiceImpl.java
@Service("peopleService")
@Transactional(readOnly=true)
public class PeopleServiceImpl implements PeopleService {

    @PersistenceContext
    protected EntityManager manager;

    public Map list(Map filter, int first, int max, String order, boolean desc) {
        Map result = new HashMap();

        String from = "from Person e ";
        String where = "where lower(e.lastName) like '%' || lower(:lastName) || '%' ";
        String orderBy = (
            order != null ? "order by e." + order + (desc ? " desc" : "") : ""
        );
        String lastName = (
            filter.containsKey("lastName") ? (String)filter.get("lastName") : ""
        );

        Query qc = manager.createQuery("select count(e) " + from + where);
        qc.setParameter("lastName", lastName);
        long resultCount = (Long)qc.getSingleResult();

        if (max == 0)
            max = 36;

        Query ql = manager.createQuery("select e " + from + where + orderBy);
        ql.setFirstResult(first);
        ql.setMaxResults(max);
        ql.setParameter("lastName", lastName);
        List resultList = ql.getResultList();

        result.put("resultCount", resultCount);
        result.put("resultList", resultList);

        return result;
    }
}

Note that this time we did not have to return firstResult and maxResults because the page size is defined on the client.

Type-safe filter

It is finally possible to use a type-safe filter object instead of the Map. The server implementation will then be something like :

PeopleServiceImpl.java
@Service("peopleService")
@Transactional(readOnly=true)
public class PeopleServiceImpl implements PeopleService {

    @PersistenceContext
    protected EntityManager manager;

    public Map list(Person examplePerson, int first, int max, String order, boolean desc) {
        Map result = new HashMap();

        String from = "from Person e ";
        String where = "where lower(e.lastName) like '%' || lower(:lastName) || '%' ";
        String orderBy = (
            order != null ? "order by e." + order + (desc ? " desc" : "") : ""
        );
        String lastName = (
            examplePerson.getLastName() != null ? examplePerson.getLastName() : ""
        );

        Query qc = manager.createQuery("select count(e) " + from + where);
        qc.setParameter("lastName", lastName);
        long resultCount = (Long)qc.getSingleResult();

        if (max == 0)
            max = 36;

        Query ql = manager.createQuery("select e " + from + where + orderBy);
        ql.setFirstResult(first);
        ql.setMaxResults(max);
        ql.setParameter("lastName", lastName);
        List resultList = ql.getResultList();

        result.put("resultCount", resultCount);
        result.put("resultList", resultList);

        return result;
    }
}

In this case, you also have to define the filter class on the client component :

<mx:Script>
  <![CDATA[
    Spring.getInstance().addComponentWithFactory("people", PagedQuery, 
        { maxResults: 36, filterClass: Person }
    );
  ]]>
</mx:Script>

If the filter class is bindable, then people.filter will be an instance of the provided filter class. If not, the PagedQuery will create an ObjectProxy around the filter instance to track changes on it.
You can also directly provide your own filter instance instead of letting the component instantiate the class itself :

<mx:Script>
  <![CDATA[
    Spring.getInstance().addComponentWithFactory("people", PagedQuery, 
        { maxResults: 36, filter: new Person() }
    );
  ]]>
</mx:Script>

 


Browse Space

- Pages
- Blog
- Labels
- Attachments
- Bookmarks
- Mail
- Advanced

Explore Confluence

- Popular Labels
- Notation Guide

Your Account

Log In

Other Features

Add Content


Copyright © 2011 Granite Data Services S.A.S. All Rights Reserved.