jeudi 3 mai 2012

BreadCrumbs / Fil d'ariane

 Exemple de rendu:
Tous mes panels de contenu étant chargé dans un Layout du panel principal HomePanel celui ci offre la possibilité d'afficher une sorte de fil d'ariane.
La classe abstraite dont hérite tous ces panels de contenus implémente les deux méthode addAriane et clearAriane et déclare la méthode abstraite majAriane qu'implémente  les panels de contenu.
La méthode addAriane a pour paramètre le libelle de ce qui va apparaitre dans la section du fil d'ariane et  une référence vers le panel à charger si l'utilisateur clique sur cette section. Si le deuxième paramètre est null la section exposera juste le texte qui correspond en fait au panel ou on est rendu dans la navigation.
Le fil d'ariane est rendu à l'aide d'un ToolStrip composé de bouton (un par section).
La méthode addAriane implémentée dans HomePanel permet d'ajouter des boutons séparé par un ToolStripSeparator.

public void addAriane(String lien,final ContentPanel p){
 Canvas[] lesBoutons =  this.ariane.getMembers();
 int nbrButtons = lesBoutons.length;
 if(nbrButtons >0){
  this.ariane.addSeparator();
 }
 if(p!=null){
   ToolStripButton btnLien = new ToolStripButton(lien);
   btnLien.setTitleStyle("plUnderline");
   btnLien.addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() {
 @Override
 public void onClick(ClickEvent event) {
       chargePanel(p); //charge le panel dans HomePanel              
}
});
this.ariane.addButton(btnLien);
}else{
  ToolStripButton btnLien = new ToolStripButton(lien);
  btnLien.setDisabled(true);
  btnLien.setShowDisabled(false);
  this.ariane.addButton(btnLien);
}
}
   
/**
 * Nettoie le fil d'ariane.
*/
public void clearAriane(){
   Canvas[] lesBoutons =  this.ariane.getMembers();
   int nbrButtonss = lesBoutons.length;
   if(nbrButtonss != 0){
   this.ariane.removeMembers(lesBoutons);   
   if(nbrButtonss !=0){
    for (int i =0 ;i < nbrButtonss;i++){
    lesBoutons[i].destroy();
    }
   }   
}
}

@Override
public void majAriane() {
   clearAriane();
   addAriane("Home", MyAppContext.getHomePanel());       addAriane("The panel where I come from",
MyAppContext.getThePanelWhereIComeFrom());
addAriane("The panel where I am", null);
}


All my content panels are loaded in a Layout of the main panel HomePanel and this one offer a some sort of breadcrumbs.
The abstract class from which are derived all the content panels implement the two methods  addAriane and clearAriane and declare the abstract method majAriane implémented by the content panels.
The addAriane method get some parameters: the label of the section , a reference to the panel which be loaded if the user click on the section, if this parameter is null only text will be displayed as it is the last eleemnt of the breadcrumb, where we are.
The breadcrumb is rendered usin a Toolstrip composed of a buttons set (one per section).
 addAriane method  implemented in HomePanel allow to add buttons separated by a ToolStripSeparator.

mardi 27 mars 2012

ListGrid Cell click handler and column number (numéro de colonne d'un champ)

Très simple, mais il y a tellement de propriétés et de méthodes que quelquefois je suis tenté d'écrire du code pas très propre pour éviter de chercher.
J'ai un ListGrid avec un champ de type icone, lorsque je click sur un champ si il s'agit de cette icone j'effectue une action.
ListGridField oeilField = new ListGridField("oeil");
        oeilField.setWidth(40);
        oeilField.setTitle("Voir");
        oeilField.setType(ListGridFieldType.ICON);
        oeilField.setCellIcon("16/oeil.png");
Jusqu'à présent je comparait le numéro de la colonne de la source de l'événement avec celui de l’icône:
 if(event.getColNum()== 3)
Aujourdh'ui j'ai rajouté un champ donc j'ai cherché un peu plus et il suffit de faire:

demandesGrid.addCellClickHandler(new CellClickHandler() {
 @Override
 public void onCellClick(CellClickEvent event) {
   if(event.getColNum()== demandesGrid.getFieldNum("oeil")){
       traiteDemandeClick();
     }

   }
});

Really easy, but there are so many properties and methods that I'm often tempted to write dirty code.
I got a listgrid with an icon ListGridField. When I click on this icon I want to take some action.
Since the beginning I compare the event source column number to the hard coded index of the field, but as today I added some fields  I went a little bit further in the javadoc and found that I just need to use the getFieldNum method which use the ListGridField name to give the index of the column.....

samedi 4 février 2012

TreeGrid without DataSource with Hilites

J’utilise un TreeGrid pour afficher un catalogue de données. Les TreeNode appartiennent à plusieurs types ; Catégorie, Variables et Marqueurs. Ce type est indiqué à l'aide d'un attribut.
J’ai dans mon panel de consultation de catalogue une boite de recherche qui permet d’indiquer par des cases à cocher le type de node recherché et une liste de mots à rechercher dans leur libellé.
Pour afficher le résultat j’utilise, d’une part les Hilites et d’autre part l’ouvertures des nodes parents qui permettent d’accéder aux éléments trouvés.
Afin de remettre à blanc le résultat de la recherche j’ai un bouton de reset que je désire utiliser également pour retirer les hilites. J’ai essayé disableHilite() mais cela n'a pas marché. Après de nombreux autres essais j'ai fini par modifier le tableau de Hilites et fournir un nouveau tableau vierge au TreGrid.

Note du 09/03/2012: 
J'ai du  mettre à jour les Hilites avant les données car j'avais des problèmes de non affichage des hilites dans certains cas.
    leTreeGrid.setHilites(HILITES);
    leTreeGrid.setData(leTree);

Reset:
TreeGrid leTreeGrid = Session.getTreeGridFromSession();           
final Hilite[] HILITES = new Hilite[]{
};               
leTreeGrid.setHilites(HILITES);
Search:
final Hilite[] HILITES = new Hilite[]{
new Hilite() {{ 
   setCriteria(new Criterion("hilite", OperatorId.EQUALS, "oui"));
   setCssText("color:#202020;background-color:#EDF0A5;"); 
   setId("0");
   }},
};               
leTreeGrid.setHilites(HILITES);



I use a TreeGrid to show a catalog data. Some nodes are Category, other are Variable (they are leaves) and some are marker which are internally related to variables set.
I have in my CatalogConsultPanel a search box where I can select on which type of node I Search and which word set I’m looking for.
To display the result I use the hilite property of the TreeGrid.
So I need to Hilite every node which “hilite” attribute is set to yes.

As I have a clear button to reset the search result I wanted to remove the hilites.
I tried to disable the hilites but with no success, so eventually I set the TreeGrid with a new blank Hilite array.

03/09/2012 Note:
I had to re- order the hilite and data set operations because I had some hilite display trouble.
    leTreeGrid.setHilites(HILITES);
    leTreeGrid.setData(leTree);



jeudi 15 décembre 2011

Web app UI design and framework are time consuming

Aujourd’hui je suis morose
Après avoir passé plusieurs mois à développer une application de taille moyenne (environ 60 panels, une trentaine de fenêtres modales, une douzaine de service de 10 à 20 méthodes)  sur la partie frontale avec un collègue travaillant sur le back end.
  Mes problèmes majeurs:
 Je passe trop de temps à fouiller pour trouver les détails dans le framework, le copier-coller n'est pas satisfaisant pour que ma courbe d'apprentissage soit correcte.
 Pas d'ouvrage disponible (et ceci est vrai pour beaucoup de framework) ce qui me met mal à l'aise dans mon travail et me donne l'impression d'être seul et sans aide.
Les forums : trop souvent les intervenant pensent que l'on poste une question sans avoir cherché dans un premier temps, c'est très frustrant.
Tous ces détails font que je passe plus de temps à me prendre la tête pour faire fonctionner tel ou tel détail qu'à me pencher sur les aspects métier, au final ma connaissance de l'application s'en ressent et du coup j'ai plus de mal à comprendre les besoins des utilisateurs.
Quelque fois je me demande comment je pourrait travailler une semaine sans internet.
Si il s'agit de manière moderne de travailler alors je me fais vieux....

A gloomy day
Just some words after spending some month on developping a medium size application (about 60 panels, 30 modal windows, 12 services of 10 to 20 methods)
I'm Working alone on the frond end and having another person working on the backend.
All the use case have been written clearly and I'm giving an increment once a week to the users.

Anyway, my main trouble were and still are:
Two much time spent to dig into the framwork to find how to do details.
Cut and paste is not so good to give us a good learning curve because we don't know every time why such a bit of code is working or not.
No book available (true for a good part of the framework) and even it is not so important because sometime they are really bad , it give me a bad opinion of my manner of working.
Forum: good to learn if I take the time to try to find answer to others questions. Generally not so good because other or administrator often think we didn't spend any time on seraching before posting a problem.


All these details took me away too much from the business rules and requirement of the application and I spend more time on technical programming side than thinking to the model, rules, testing etc...
Sometimes I'm just thinking what will happen if I were obliged to work on my app one week without Internet access
If that's the modern way of working I'm getting very old.....

lundi 7 novembre 2011

ListGrid without datasource CRUD

Comme j'ai rencontré (voir post précédant) des problèmes avec la suppression des enregistrement dans le ListGrid, j'ai décidé de tout remettre à plat et de rajouter deux colonnes à mon ListGrid, celles ci sont cachées(par exemple status.setHidden(false);) :
un booléen pour indiquer qu'il s'agit d'un nouvel enregistrement(et donc si il est marqué comme "delete" il ne faut pas chercher à le supprimer de la base puisqu'il n'y est pas encore) et une string pour indiquer le statut de la rangée. Lors de la validation je parcourrai mes rangées en faisant le nécessaire en fonction des valeurs des deux colonnes.
J'utilise pour le statut une énumération:
public enum GridStatusTypes {
    ORIGIN("o"), 
    CREATE("c"),
    RETRIEVE("r"),
    UPDATE("u"),
    DELETE("d");
    final private String value;
    GridStatusTypes(String  value) {
        this.value = value;
    }

    public String getValue(){
        return value;
    }
}

Pour traiter les suppressions (y compris celle de nouvelles rangées juste crées) je les laisse dans la ListGrid jusqu'à la validation mais les indique en rouge en utilisant la redéfinition de la méthode getCellCSSText de la ListGrid:
listGrid = new ListGrid(){
  protected String getCellCSSText(ListGridRecord record, int rowNum, int colNum){
   if(record == null){
       return null;
   }else{
    Record rec = listGrid.getEditedRecord(rowNum);
    String status = rec.getAttributeAsString("status");
    String statusDelete = GridStatusTypes.DELETE.getValue();
    if (status.equals(statusDelete)) {
       return "font-weight:bold; color:red;";
    }else{
       String css = super.getCellCSSText(record, rowNum, colNum);
       return css;
    }
   }        
  }
 };

Le code peut surement être simplifié.
Le test du record == null a été rajouté pour les cas ou je crée une nouvelle rangée sinon cela part en erreur.
Je dois utiliser getEditedRecord, car en fait à chaque modification du statut la rangée est modifiée et les nouvelels valeurs ne sont accessibles que par cette méthode.
Ensuite le bouton de suppression modifie la colonne statut:
public void onClick(ClickEvent event) {
  ListGridRecord currentRecord = listGrid.getSelectedRecord();               
    if(currentRecord != null){       
   listGrid.setEditValue(rangeeEnCours, "status", GridStatusTypes.DELETE.getValue());   
   listGrid.refreshRow(rangeeEnCours);                           
     }  


As I got trouble with my ListGrid without DataSource, I decided to restart from square one and add two more column to it . These two column are hidden(for ex status.setHidden(false);) :
One with a boolean value indicating that a row has just been created and one with a string value to indicate its status using an enum for it.
When the List is first displayed with the database data all the row status column values are "ORIGIN".
I had to use getEditedRecord because As my editing button (create, update delete) modify a field the new value are only available through this method.
To indicate than the deleted record had been correctly notified I changed the color of the deleted row in red by overriding the  getCellCSSText mehod of the ListGrid. This code need probably to be looked after because I did some comparision test to avoid errors during creation of new records.
So I can create, modify delete and at the end during the validation I will iterate through my grid and do which is necessary depending of the value of the two added column. For example I will not delete from the database a row marked with DELETE but which has also its boolean to True.

vendredi 28 octobre 2011

DynamicForm, simple details on which I spent too much time

Pas très compliqué, mais j'ai passé plus de temps que j'aurais voulu sur certains détails:

1°) Les FormItem ne peuvent pas être dimensionnés à l'aide de pourcentage du genre setWidth("80%");
Le problème c'est que dans mon source j'avis des endroits ou cela marchait...... Oui mais avec setWidth("100%"). Rien dans le javadoc par contre dans le source:
  public void setWidth(String width) {
  if("100%".equals(width)) width = "*";
  assert width.indexOf("%") == -1 : "FormItems do not support percent sizing.";
  setAttribute("width", width)

   }
2°) J'avais plusieurs RadioGroupItem horizonatux dans un formulaire certains avec des options courtes (Oui, Non ) et d'autres beaucoup plus longues. Ces optons ne se trouvaient donc pas alignées
Seule solution que j'ai trouvée: utilisée une méthode pour mettre toutes les chaines de caractères des options à la même longueur. Par contre on ne peux pas ajouter des espaces il faut mettre des "&nbsp"
for(int i=0;i<(lgReturn-lgSource);i++){
            returnString += "&nbsp";
}


3°) J'avais besoin de mettre une image dans le formulaire
CanvasItem imageFromTo = new CanvasItem();   
 Img img = new Img();
 img.setSrc("64/deplacer.png");
 img.setVisible(true);
 img.setParentElement(image);
 imageFromTo.setCanvas(img);
 imageFromTo.setWidth(48);
 imageFromTo.setHeight(48);
 imageFromTo.setTitle("");
Au début mon image ne s'affichait pas car j'étais passé par une Canvas intermédiaire et c'est celui ci que j'attribuait au CanvasItem.

    Canvas image = new Canvas();
        image.setWidth(48);
        image.setHeight(48);
        Img img = new Img();
        img.setSrc("64/deplacer.png");
        img.setVisible(true);
        img.setParentElement(image);

4°) TextAreaItem: pour les scrollBar et supprimer la poignée de redimmensionnement il faut passer par les Css.
                                    
                                                      ------------------------------------------------


1°) FormItem cannot be sized using percentage but setWidth("100%") works nothing in the javadoc but in the source .....

2°) Alignment problem of different length option text with  Horizontal RadioGroup, only solution I found was to  concatenate the text with a variable "&nbsp" to get constant length option text.


3°) I need to add an image in the dynamicForm. The first time I use an intermediate Canvs which I set to the CanvasItem. The image didn't show up. Using directly the image is working right.


4°) TextAreaItem: to get the scrollBars and remove the resize handle we need to use css.

ListGrid without datasource

I have several existing RPC services so I don'use actually Datasources.

I have a ListGrid which I want to use to display data coming from one of these services. I wanted also to be able to create, modify and delete  new records.

So:
1 ListGrid + 3 buttons for create, modify and delete + 1 button to validate.
.When the edit is finished I use the validate button to trigger EditComplete event with the method
listGrid.saveAllEdits();
In the EditCompletHandler :
I detect the creation with event.getOldRecord which in this case is null.
If not null  I'm in the modify case.
To read the values, as the edited values are separated from the listgrid data, we need to use the  event.getNewValues(). So after reading the new value and knowing if I need to create or modify I instanciate the right serializable object and I set its attribute with the new values

A problem arise  to process the delete case, since the new created record can't be selected(even if the selected record is ok), so at the end as I don't know yet how to do it I added a context menu  to avoid erasing the wrong record.

Note(31/10) : I was wrong to think it was ok, I had to  use the Delete column with:
    listGrid.setCanRemoveRecords(true);       
   listGrid.setRemoveIcon("16/supprimer.png");

It's the only way I found to delete a new fresh record.

Note(03/11/2011): The problem is: there is noway to be notified when the RemoveICon is clicked and one record deleted. So I think I will ask the user to either discard all the edits or save first and delete after... not really satisfying.... Widget working hand to hand with datasource everything is ok but without databinding there is a lot of work around to find.

listGrid.addEditCompleteHandler(new EditCompleteHandler() {
 @Override
public void onEditComplete(EditCompleteEvent event) {
  int rangee = event.getRowNum();   
  Record rec = event.getOldRecord();
  if(rec == null){
    SC.say("ADDING NEW RECORD");
  }else{
  SC.say("MODIFY THE SELECTED RECORD");
  }
  Map lesValeurs  = event.getNewValues();               
  String newLibelle = (String) lesValeurs.get("libelle");
  String newcode =(String) lesValeurs.get( "code");
  Date newDebut =(Date) lesValeurs.get( "dateDeDebut");
  Date newFin = (Date) lesValeurs.get( "dateDeFin");

  MyObjectDTO myObject = new MyObjectDTO();
  myObject.setLibelle(newLibelle);
............


ButtonItem btnAdd = new ButtonItem();
btnAdd.addClickHandler(new ClickHandler() {
 @Override
public void onClick(ClickEvent event) {
  listGrid.setCanEdit(true);
  listGrid.startEditingNew();
  }
});

ButtonItem btnModify = new ButtonItem();
btnModify.addClickHandler(new ClickHandler() {
 @Override
public void onClick(ClickEvent event) {
   ListGridRecord currentRecord = listGrid.getSelectedRecord();
   if(currentRecord != null){
      listGrid.startEditing(listGrid.getRecordIndex (currentRecord),1,true);
                }
            }
        });