DataFilterLib – Pagination de données filtrées (ArrayCollection avec filterFunction)
On m'a posé une question très intéressante sur la DataFilterLib:
Cedric
Bonjour moi j'aurais une petite question :
Comment intégrer les fonctionnalités de filtrage sur une dataGrid dont les données sont paginées (à l'aide du paginateur disponible sur ce site)?
En effet, la DataFilterLib permet de filtrer des données côté client selon plusieurs critères, de manière déclarative (MXML) ou dynamique (AS). Elle permet, pour simplifier, de donner plusieurs filterFunction à un ArrayCollection.
Et pour la pagination (côté client), le plus simple et le plus effectif est d'utiliser aussi une filterFunction sur une ICollectionView, un ArrayCollection par exemple. La question est alors, comment combiner les deux?
Application de test
Voilà le code utilisé pour tester l'application. Le dataProvider se trouve dans un fichier à part pour faciliter la lecture:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%"
paddingRight="20" xmlns:filter="com.fnicollet.datafilter.filter.*"
xmlns:pagination="pagination.*">
<mx:Script>
<![CDATA[
import pagination.event.PaginateEvent;
import com.fnicollet.datafilter.filter.DataFilterType;
import com.fnicollet.datafilter.filter.DataFilterSingleValueOperator;
[Bindable]
private var _startIdx:int = 0;
[Bindable]
private var _endIdx:int = 999999999;
private function pageChangeHandler(e:PaginateEvent):void {
var page:int = e.index;
_startIdx = (page * e.itemsPerPage) + 1;
_endIdx = Math.min((_startIdx + e.itemsPerPage - 1), e.itemsTotal);
//txt.text = "Results: " + String(_startIdx) + " - " + String(_endIdx) + " of " + e.itemsTotal;
}
]]>
</mx:Script>
<mx:Script source="/data/StateData.as"/>
<filter:DataFilterSet id="filterSet" data="{statesData}">
<filter:dataFilterParameters>
<filter:DataFilterParameters id="simpleParam" filterType="{DataFilterType.SINGLE_VALUE}"
filterKeys="state"
filterOperator="{DataFilterSingleValueOperator.LIKE}"
filterValues="{stateInput.text}"/>
</filter:dataFilterParameters>
</filter:DataFilterSet>
<mx:Label text="Filter By State Name (Contains)" fontSize="14" fontWeight="bold"/>
<mx:TextInput id="stateInput"/>
<mx:Label text="Pagination over the filtered elements" fontSize="14" fontWeight="bold"/>
<pagination:PageSelector id="paginator" itemsPerPage="6" rangeCount="6"
itemsTotal="{statesData.length}" selectedIndex="0"
pageChange="pageChangeHandler(event)"/>
<mx:Label text="Filtered Data" fontSize="14" fontWeight="bold"/>
<mx:DataGrid rowCount="7" dataProvider="{statesData}" width="100%">
<mx:columns>
<mx:DataGridColumn dataField="state" headerText="State Name"/>
<mx:DataGridColumn dataField="sales" headerText="Sales"/>
<mx:DataGridColumn dataField="employees" headerText="Number of Employees"/>
<mx:DataGridColumn dataField="population" headerText="Population"/>
</mx:columns>
</mx:DataGrid>
<mx:Label text="Unfiltered Data" fontSize="14" fontWeight="bold"/>
<mx:DataGrid rowCount="8" dataProvider="{statesData.source}" width="100%">
<mx:columns>
<mx:DataGridColumn dataField="state" headerText="State Name"/>
<mx:DataGridColumn dataField="sales" headerText="Sales"/>
<mx:DataGridColumn dataField="employees" headerText="Number of Employees"/>
<mx:DataGridColumn dataField="population" headerText="Population"/>
</mx:columns>
</mx:DataGrid>
</mx:Application>
Un composant générique de pagination est intégré et met seulement à jour 2 variables Bindable. Il n'effectue encore aucun filtrage:
Composant Flex – Interface de Pagination générique
Premier test: Ajouter un nouveau filtre pour la pagination
Dans un premier temps, la première idée que j'ai eu fut de simplement ajouter un nouveau filtre, se comportant comme un filtre de type Interval, dont l'intervalle irait entre l'index de mon premier élément de page et le dernier élément.
Pour créer ce nouveau filtre, qui n'existe pas par défaut, il suffit de créer une classe qui hérite de "DataFilterInterval": DataFilterIntervalPagination. Pour l'ajouter en tant que filtre, il suffit d'utiliser la technique que j'expose dans cet article:
DataFilterLib – Utilisation de filtres personnalisés
Pour une utilisation dans une application, il suffit ensuite de donner:
<filter:DataFilterParameters filterClass="{DataFilterIntervalPagination}"
id="intervalPagination" filterValues="{[_startIdx, _endIdx]}"/>
Et déjà, là, premier obstacle, la méthode de filtrage de base. Pour simplifier, la DataFilterLib boucle sur la méthode "apply" de chaque filtre dont la signature est la suiavnte:
public function apply(item:Object):Boolean {
On prend ensuite chaque résultat et si au moins un d'entre eux est false, l'objet est filtré. Pour les filtres sur valeur et sur intervalle, on cherche à comparer une certaine propriété de l'item (donnée par le paramètre "filterKeys"). En pseudo-code, on a :
public function apply(item:Object):Boolean {
var value:String = item[filterKey];
if (value == filterValue){
return true;
}
return false;
}
On fait ici la distinction par rapport à une propriété de l'objet. Seulement dans notre cas, on ne veut pas comparer une des propriétés de l'objet mais bien la position de l'objet dans le dataProvider. Si son index est dans la page courante on garde l'objet, sinon on le filtre.
Composant Flex – Interface de Pagination générique (exemple)
Il y a quelques semaines, je présentais sur Adobe Flex Tutorial un composant Flex nommé Paginator qui était une interface de pagination générique pour vos applications. Pour rappel, la pagination sert à présenter la donnée par bouts (pages) afin de ne pas noyer l'utilisateur. Dans un commentaire, CapoeiraDance demandait à voir un exemple d'application de ce composant. Voici donc un exemple que j'ai concocté moi-même.
Dans cet exemple, on a une donnée sur les états américains avec quelques valeurs. J'ai choisi de n'afficher que 8 éléments pas page pour montrer le fonctionnement de l'interface de pagination. La vraie pagination (au niveau de la data) se fait par une "filterFunction" sur le dataProvider (ArrayCollection). Ainsi, on va regarder l'index de l'item dans la donnée pour savoir s'il est dans la page courante. Le code n'est sûrement pas optimal mais cela donne une idée.
Attention, pour indiquer le nombre total d'éléments au paginator, on utilise bien la propriété "length" de la "source" de l'ArrayCollection et pas la "length" de l'ArrayCollection. En effet, si on utilise directement statesData.length, les résultats seront erronés car la fonction de filtre va modifier cette longueur.
J'aurai pu être chauvin et utiliser la DataFilterLib pour créer cette pagination mais gardons les choses simples pour l'instant
.
Un peu de code pour commencer
[Bindable]
private var statesData:ArrayCollection = new ArrayCollection([...]);
...
// variables utilisées par la fonction de filtre
private var _startIdx:int = 0;
private var _endIdx:int = 1000;
private function init():void{
// on donne une fonction de filtre à la donnée pour n'afficher que les résultats courants
statesData.filterFunction = paginateFilterFunction;
statesData.refresh();
}
private function pageChangeHandler(e:PaginateEvent):void{
// affichage du nombre de résultats courant
var page:int = e.index;
var startIndex:int = (page * e.itemsPerPage) + 1;
var endIndex:int = Math.min((startIndex + e.itemsPerPage -1), e.itemsTotal);
_startIdx = startIndex - 1;
_endIdx = endIndex;
txt.text = "Results: " + String(startIndex) + " - " + String(endIndex) + " of " + e.itemsTotal;
// filtrage de la donnée par page
statesData.refresh();
}
private function paginateFilterFunction(item:Object):Boolean{
var itemIdx:int = statesData.getItemIndex(item);
return (itemIdx >= _startIdx && itemIdx < _endIdx) || itemIdx == statesData.source.length;
}
...
<local:PageSelector id="paginator"
itemsPerPage="{8}"
rangeCount="6"
itemsTotal="{statesData.source.length}"
selectedIndex="0"
pageChange="pageChangeHandler(event)" />
L'application en ligne
Flex Source Code Download: Télécharger le code source complet de l'application
Flex Modules – Communication Module vers Application
Dans l'article précédent, on a vu comment communiquer entre une application Flex et un module. Voyons maintenant comment créer une communication entre un module et l'application qui le charge.
Les modules peuvent accéder aux propriétés et aux méthodes de l'application parente en utilisant une référence vers la propriété "parentApplication".
L'exemple suivant accède à la propriété "expenses" de l'application parente au chargement du module. Le module utilise ensuite cette propriété, un ArrayCollection, comme source pour la donnée du graphique. Quand l'utilisateur clique sur le bouton, le module appelle la méthode getNewData() de l'application parente qui renvoie un nouvel ArrayCollection pour le Chart:
Le code du module
<?xml version="1.0"?>
<!-- modules/ChartChildModule.mxml -->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml"
width="100%"
height="100%"
creationComplete="getDataFromParent()">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var expenses:ArrayCollection;
// Access properties of the parent application.
private function getDataFromParent():void {
expenses = parentApplication.expenses;
}
]]>
</mx:Script>
<mx:ColumnChart id="myChart"
dataProvider="{expenses}">
<mx:horizontalAxis>
<mx:CategoryAxis dataProvider="{expenses}"
categoryField="Month"/>
</mx:horizontalAxis>
<mx:series>
<mx:ColumnSeries xField="Month"
yField="Profit"
displayName="Profit"/>
<mx:ColumnSeries xField="Month"
yField="Expenses"
displayName="Expenses"/>
</mx:series>
</mx:ColumnChart>
<mx:Legend dataProvider="{myChart}"/>
<mx:Button id="b1"
click="expenses = parentApplication.getNewData();"
label="Get New Data"/>
</mx:Module>








