mercredi 21 mars 2012

Paginer une FlexTable en GWT





Nous allons voir dans ce premier article comment rendre une FlexTable Paginable, mais surtout au travers de cet exemple montrer pourquoi l'open source c'est bon,et qu'il faut en manger.



Pourquoi paginer une FlexTable ?
En GWT, le premier composant auquel on pense lorsque l'on souhaite afficher des données par pages est le Datagrid.
Le principe est simple: on définit des colonnes via des types de cellules, ainsi que la donnée à afficher dans chaque colonne et on obtient une grille de données paginées grâce à un Pager.
Cependant, il se peut que vous vouliez afficher vos données non en grille, mais organisées différemment, dans un Widget:


Par exemple comme ceci si vous avez des compétences en design controversées.




Le datagrid pose problème dans le cas où vous souhaiteriez afficher un widget comme celui-ci, comprenant plusieurs images cliquables. En effet le datagrid prend la priorité sur le click et il est peu aisé de différencier l'origine du click si l'on décide de faire un datagrid à 1 colonne, contenant le widget.


 aucun soucis avec les hyperlink cependant


Nous allons donc nous tourner vers l'utilisation d'une FlexTable, une table que l'on peut remplir avec des widgets

Afin d'éviter de re-développer tout un système de pagination, le mieux serait d'utiliser le SimplePager utilisable avec un Datagrid, et qui prend un objet de type HasRows en vue à paginer.
Tant qu'a faire, nous souhaitons aussi réutiliser le mécanisme de sélection multipage: Nous allons donc créer un objet qui étend FlexTable, et qui implemente HasData (une sous interface de HasRow ).

Plutôt Flexible la FlexTable...


La FlexTable est une sous-classe de HTMLTable, tandis que le DataGrid est un composant plus complexe héritant de AbstractHasData et comprenant un presenter.

 En allant fouiller dans les sources, nous voyons ce qui va nous poser problème: l'implémentation dans ces classes de la fonction getRowCount().
- Dans le datagrid, cette fonction fait appel au presenter, contenant la liste des données:
  public int getRowCount() {
    return presenter.getRowCount();
  }

- Dans l'HtmlTable, la fonction renvoie le nombre des élements du DOM  (autrement dit des éléments html générés):

  @Override
  public int getRowCount() {
    return getDOMRowCount();
  }

Le pager utilisant cette fonction pour savoir le nombre d'éléments au total toute pages confondues, il nous faut le surcharger dans la FlexTable :

  @Override
  public int getRowCount() {
     return count; // setted with the setRowCount function
  }
 


Derniers coup de clef à molette...


Cette surcharge entraîne des dysfonctionnements de la FlexTable (index out of bound exception,...).
En effet en retournant voir un peu les sources de la HtmlTable, nous voyons qu'elle utilise getRowCount pour connaitre réellement le nombre de lignes dans la page courante dans certaines fonctions, pour par exemple vider la table. Il faut donc surcharger les 3 fonctions suivante pour y remplacer le getRowCount() par getDomRowCount() :

 /**
  * Remove all rows in this table.
  */
 @Override
 public void removeAllRows() {
  int numRows = getDOMRowCount();
  for (int i = 0; i < numRows; i++) {
   removeRow(0);
  }
 }

 /**
  * Checks that the row is within the correct bounds.
  * 
  * @param row
  *            row index to check
  * @throws IndexOutOfBoundsException
  */
 @Override
 protected void checkRowBounds(int row) {
  int rowSize = getDOMRowCount();
  if ((row > rowSize) || (row < 0)) {
   throw new IndexOutOfBoundsException("Row index: " + row
     + ", Row size: " + rowSize);
  }
 }

 @Override
 protected void prepareRow(int row) {
  if (row < 0) {
   throw new IndexOutOfBoundsException(
     "Cannot create a row with a negative index: " + row);
  }

  // Ensure that the requested row exists.
  int rowCount = getDOMRowCount();
  for (int i = rowCount; i <= row; i++) {
   insertRow(i);
  }
 }

Une fois ces fonctions surchargée, votre FlexTable est paginable.


Le mot de la fin


Nous avons donc rendu une FlexTable paginable en quelques petites surcharges.
Bien qu'utile, ce n'est pas vraiment le fait de rendre une FlexTable paginé qui est intéressant mais cette caractéristique des technologies Open-source : Lorsque l'on a un problème, on se ballade dans le code source du framework utilisé pour savoir comment il fonctionne, pour ensuite pouvoir l'utiliser au mieux.

GWT reste un ToolKit et ne fournit pas de composants aussi évolués qu'en Flex ou Silverlight. Cependant Tout les comportements sont définits par des interfaces, et la compréhension de la tuyauterie en arrière plan permet de faire exactement ce que l'on veut avec quelques surcharges effectuées à bon escient.
C'est cette transparence que j'aime avant tout dans le monde de la JVM.
L'open source c'est bien mangez en.

Sources
Le projet complet est disponible ici.

La FlexTable

package com.cafetux.table.example.client.table;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.cafetux.table.example.client.widgets.LoadingTableWidget;
import com.cafetux.table.example.shared.DTO;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.Range;
import com.google.gwt.view.client.RangeChangeEvent;
import com.google.gwt.view.client.RangeChangeEvent.Handler;
import com.google.gwt.view.client.RowCountChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionModel;

/**
 * the flex table that have pager and Key Provider.
 */
public abstract class FlexTableHasDatas extends FlexTable implements
  HasDatasForFlexTable {

 /**
  * interface of the presenter for the flexTable has row. permit lazzy
  * loading.
  * 
  * @author cafetux
  * 
  */
 public interface TablePresenter {
  /**
   * load next pool of results.
   */
  void loadMoreResults();

  void loadPage();

  HasDatasForFlexTable getView();

  int getDataSize();

  T getData(int i);
 }

 private Range range = new Range(0, 5);

 public static final int BUFFER_SIZE = 5;
 public static final int MAX_RESULT_RETRIEVED = 10;

 private boolean isExact = true;

 private int count;
 private SelectionModel selectionModel;

 private final Set handlers = new HashSet();
 private final TablePresenter presenter;

 private Widget emptyWidget = new Label("empty table");
 private Widget loadingWidget = new Label("Loading...");

 public FlexTableHasDatas(TablePresenter presenter,
   com.google.gwt.view.client.SelectionChangeEvent.Handler... handlers) {
  super();
  this.presenter = presenter;
  initSelectionChangeHandlerSet(handlers);
 }

 /**
  * insert the view rendition on the table.
  * 
  * @param index
  *            the ndex to insert
  * @param item
  *            the item to insert.
  */
 protected abstract void insertItem(int index, T item);

 /**
  * @param handlers
  */
 private void initSelectionChangeHandlerSet(
   com.google.gwt.view.client.SelectionChangeEvent.Handler... handlers) {
  for (com.google.gwt.view.client.SelectionChangeEvent.Handler handler : handlers) {
   this.handlers.add(handler);
  }
  this.handlers.add(new SelectionChangeEvent.Handler() {

   @Override
   public void onSelectionChange(SelectionChangeEvent event) {
    refresh();
   }
  });
 }

 /**
  * check if the specified item was selcted.
  * 
  * @param identifier
  *            the object to check.
  * @return true if is selectionned.
  */
 public boolean isSelected(T identifier) {
  return getSelectionModel().isSelected(identifier);
 }

 @Override
 public HandlerRegistration addRangeChangeHandler(Handler handler) {
  return addHandler(handler, RangeChangeEvent.getType());
 }

 @Override
 public HandlerRegistration addRowCountChangeHandler(
   com.google.gwt.view.client.RowCountChangeEvent.Handler handler) {
  return addHandler(handler, RowCountChangeEvent.getType());
 }

 @Override
 public Range getVisibleRange() {
  return range;
 }

 // / redefinition for replace getRowCount()
 /**
  * Remove all rows in this table.
  */
 @Override
 public void removeAllRows() {
  int numRows = getDOMRowCount();
  for (int i = 0; i < numRows; i++) {
   removeRow(0);
  }
 }

 /**
  * Checks that the row is within the correct bounds.
  * 
  * @param row
  *            row index to check
  * @throws IndexOutOfBoundsException
  */
 @Override
 protected void checkRowBounds(int row) {
  int rowSize = getDOMRowCount();
  if ((row > rowSize) || (row < 0)) {
   throw new IndexOutOfBoundsException("Row index: " + row
     + ", Row size: " + rowSize);
  }
 }

 @Override
 protected void prepareRow(int row) {
  if (row < 0) {
   throw new IndexOutOfBoundsException(
     "Cannot create a row with a negative index: " + row);
  }

  // Ensure that the requested row exists.
  int rowCount = getDOMRowCount();
  for (int i = rowCount; i <= row; i++) {
   insertRow(i);
  }
 }

 // /////////////////////////////////////////////////////////////////
 @Override
 public boolean isRowCountExact() {
  return isExact;
 }

 @Override
 public void setRowCount(int count) {
  this.count = count;
  RowCountChangeEvent.fire(this, count, isExact);
  showIsEmpty();

 }

 @Override
 public int getRowCount() {
  return count;
 }

 @Override
 public void setRowCount(int count, boolean isExact) {
  GWT.log("setRowCount(" + count + "," + isExact + ")");
  this.isExact = isExact;
  setRowCount(count);
 }

 @Override
 public void setVisibleRange(int start, int length) {
  setVisibleRange(new Range(start, length));
 }

 @Override
 public void setVisibleRange(Range range) {

  GWT.log("setVisibleRange(" + range + ")");
  this.range = range;
  if ((range.getStart() + range.getLength() >= presenter.getDataSize()))
   presenter.loadMoreResults();
  RangeChangeEvent.fire(this, getVisibleRange());
  refresh();
 }

 @Override
 public HandlerRegistration addCellPreviewHandler(
   com.google.gwt.view.client.CellPreviewEvent.Handler handler) {
  System.out.println("addCellPreviewHandler");
  return null;
 }

 @Override
 public SelectionModel getSelectionModel() {
  return this.selectionModel;
 }

 @Override
 public T getVisibleItem(int indexOnPage) {
  GWT.log("getVisibleItem " + indexOnPage);

  return null;
 }

 @Override
 public int getVisibleItemCount() {
  return range.getLength();
 }

 @Override
 public Iterable getVisibleItems() {
  return getVisibleItemsList();
 }

 private List getVisibleItemsList() {

  List res = new ArrayList();
  int end = range.getStart() + range.getLength();

  if (end > this.getRowCount()) {
   end = this.getRowCount();
  }

  for (int i = range.getStart(); i < end; i++) {
   res.add(presenter.getData(i));
  }
  return res;
 }

 @Override
 public void setVisibleRangeAndClearData(Range range,
   boolean forceRangeChangeEvent) {
  GWT.log("setVisibleRangeAndClearData");

 }

 private void refresh() {
  removeAllRows();
  int end = range.getStart() + range.getLength();
  if (end > presenter.getDataSize())
   end = presenter.getDataSize();
  int row = 0;
  for (int i = range.getStart(); i < end; i++) {
   if (i < presenter.getDataSize()) {
    insertProductRow(row, presenter.getData(i));
    row++;
   }
  }
 }

 @Override
 public void setRowData(int start, List values) {

  if (presenter.getDataSize() == count)
   isExact = true;
  RowCountChangeEvent.fire(this, presenter.getDataSize(), isExact);

 }

 /**
  * @param index
  * @param productLightT
  */
 private void insertProductRow(int index, final T data) {
  insertRow(index);
  insertSelectCheckBox(index, data);
  insertItem(index, data);
 }

 /**
  * @param index
  * @param productLightDTO
  */
 private void insertSelectCheckBox(int index, final T productLightDTO) {
  CheckBox selected = new CheckBox();
  selected.setValue(getSelectionModel().isSelected(productLightDTO));
  selected.addValueChangeHandler(new ValueChangeHandler() {

   @Override
   public void onValueChange(ValueChangeEvent event) {
    if (event.getValue()) {
     getSelectionModel().setSelected(productLightDTO, true);
    }
   }
  });
  setWidget(index, 0, selected);
 }

 @Override
 public void setSelectionModel(SelectionModel selectionModel) {
  this.selectionModel = selectionModel;
  refreshSelectionChangedHandlers();
 }

 private void refreshSelectionChangedHandlers() {
  for (SelectionChangeEvent.Handler handler : handlers) {
   selectionModel.addSelectionChangeHandler(handler);
  }
 }

 @Override
 public int getTotalDataCount() {
  return presenter.getDataSize();
 }

 /** **/
 public void setEmptyWidget(Widget empty) {
  this.emptyWidget = empty;
 }

 public void setLoadingWidget(Widget loading) {
  this.loadingWidget = loading;
 }

 @Override
 public void showLoading() {
  removeAllRows();
  insertRow(0);
  setWidget(0, 3, loadingWidget);
 }

 @Override
 public void showIsEmpty() {
  removeAllRows();
  insertRow(0);
  setWidget(0, 0, emptyWidget);
 }
}
package com.cafetux.table.example.client.table;

import com.cafetux.table.example.client.view.DtoView;
import com.cafetux.table.example.client.widgets.EmptyTableWidget;
import com.cafetux.table.example.client.widgets.LoadingTableWidget;
import com.cafetux.table.example.shared.DTO;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.cellview.client.AbstractPager;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.cellview.client.SimplePager.Resources;
import com.google.gwt.user.cellview.client.SimplePager.TextLocation;
import com.google.gwt.view.client.MultiSelectionModel;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.SelectionModel;

public class MyFlexTable extends FlexTableHasDatas {
 
 /** loading and empty table widgets **/
 private final EmptyTableWidget empty = new EmptyTableWidget();
 private final LoadingTableWidget loading = new LoadingTableWidget();
 private final AbstractPager pager = new SimplePager(TextLocation.CENTER,
   (Resources) GWT.create(SimplePager.Resources.class), false, 0, true);

 /** the key Provider **/
 private final HasIdentifierProvider KEY_PROVIDER = new HasIdentifierProvider();
 private final SelectionModel selectionModel = new MultiSelectionModel(
   KEY_PROVIDER);

 /**
  * the provider to give the identifier of the row object.
  */
 private static final class HasIdentifierProvider implements
   ProvidesKey {
  /**
   * {@inheritDoc}
   */
  @Override
  public Object getKey(DTO item) {
   return item.getIdentifier();
  }
 }

 /**
  * the constructor that set the presenter,and add handlers for
  * selectionChangedEvent
  * 
  * @param presenter
  *            the presenter to load datas.
  * @param handlers
  *            the handlers called when selection change.
  */
 public MyFlexTable(TablePresenter presenter,
   com.google.gwt.view.client.SelectionChangeEvent.Handler... handlers) {
  super(presenter, handlers);
  setEmptyWidget(empty);
  setLoadingWidget(loading);
  setSelectionModel(selectionModel);
  pager.setDisplay(this);
 }

 public AbstractPager getPager() {
  return pager;
 }

 @Override
 protected void insertItem(int index, DTO item) {

  DtoView view = new DtoView();
  view.fill(item);
  setWidget(index, 1, view);

 }

}


Les icones viennent de http://www.iconfinder.com/

Aucun commentaire:

Enregistrer un commentaire

Crédits

Thème dérivé du GUI Set Retro-pixel.