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);
                }
            }
        });

mercredi 12 octobre 2011

Window / basics

  • Utiliser un header si on a besoin de title, icon, buttons
  • On ajoute des items et non pas des members
  • setAutoSize() pour re-dimmensionner automatiquement
  • setKeepInParentRect() pour empêcher de ne plus pouvoir repositionner la fenêtre lorsque le header n'est plus visible.
  • setDismissOnOutsideClick(true);  pour fermer la fenêtre dès que l'on clicque en dehors
  • setShowMinimizeButton(false); pour ne pas inclure l'icone de réduction (idem pour close et maximize)
     



  • We need the header if we want title, icon, buttons
  • We don’t add members but items
  • setAutoSize() to resize automatically
  • setKeepInParentRect() to avoid to get the header by which the window is draggable unreachable.
  • setDismissOnOutsideClick(true);  to close the window as soon as we click outside of it.
  • setShowMinimizeButton(false); to remove the minimize icon (same for close and maximize)

mardi 27 septembre 2011

DynamicForm + CanvasItem (2 IButton)

Même si on peut jouer avec le setStartRow et setEndRow il n'est pas toujours très pratique de positionner des boutons dans un formulaire comme on le désire.
 Dans certains cas je n'utilise pas de ButtonItem mais plutôt un CanvasItem qui va inclure un HLayout contenant lui même des boutons et des LayoutSpacer pour assurer leur centrage, on peut éventuellement fixer la largeur de ceux-ci :

public class ValiderAnnulerItem extends CanvasItem{

    private MyDialogButton btnValidate ;
    private MyDialogButton btnCancel ;
   
    public MyDialogButton getBtnValidate() {
        return btnValidate;
    }
    public void setBtnValider(MyDialogButton btnValidate) {
        this.btnValidate = btnValidate;
    }
    public MyDialogButton getBtnAnnuler() {
        return btnCancel;
    }
    public void setBtnAnnuler(MyDialogButton btnCancel) {
        this.btnCancel = btnCancel;
    }
   
    HLayout lesBoutons = new HLayout();
    public ValiderAnnulerItem(){
        this.setTitle("");
    }
   
    public void initialize(){
        lesBoutons.setHeight(btnValidate.getHeight() +5);
        lesBoutons.addMember(new LayoutSpacer());
        lesBoutons.addMember(btnValidate);
        lesBoutons.addMember(new LayoutSpacer());
        lesBoutons.addMember(btnCancel);
        lesBoutons.addMember(new LayoutSpacer());
        this.setCanvas(lesBoutons);
    }
}

A l'utilisation:
ValiderAnnulerItem canvasButtons = new ValiderAnnulerItem();
        canvasButtons.setBtnValider(btnValidate);
        canvasButtons.setBtnAnnuler(btnCancel);
        canvasButtons.initialize();
..............       
          
        f.setItems(...........,canvasButtons);


Even if we can position ButtonItem in a DynamicForm with the setStartRow and setEndRow, it's not always very practical to get the appearance we like to obtain.
So sometime I use in place a CanvasItem subclass with  IButton subclass (which fire business events via the SimpleEventBus). In this case we can use a normal Hlayout (we could use a Vlayout depending of a property...) to setup the arrangement we need. I use LayoutSpacer with or without setting their width to center the buttons.

vendredi 9 septembre 2011

Un objet Session / Session Object to share objects

Avant d'implémenter le SimpleBusEvent j'ai eu besoin d'échanger des objets entre différents composants de mon application (Par exemple des attributs d'un record de listgrid lors de sa sélection ou le record en entier dont je peux avoir besoin dans un autre panel) .
 Pour cela j'ai définit une classe utilitaire Session qui me permet de travailler comme avec une Session en Asp.net ou en Php mais coté client. Pour éviter les problèmes usuels de faute de frappe sur la clé j'ai créé des méthodes put et get spécifiques (des accesseurs de Session) pour certains objets couramment utilisés. J'ai tout de même conservé la possibilité d'utiliser la Session de manière générique (<String>, <Object>). Je ne sais pas d'ailleurs si c'est très malin mais pour le développement c'est plus rapide pour faire un essai que d'aller tout de suite ajouter deux accesseurs. Cela fait pas mal de méthodes à écrire mais au final c'est pratique.

Une partie du code
public class Session {private static HashMap<String, Object> map = new HashMap<String,Object>();
   
    public static void put(String cle, Object objet){
        map.put(cle, objet);
    }
   
    public static Object get(String cle){
        return  map.get(cle);
    }  
   
    public static void putNomRubrique(String chaine){
        map.put("NomRubrique", chaine);
    }
   
    public static String getNomRubrique(){
        return (String) map.get("NomRubrique");
    }
  
  
   
    public static RubriqueDTO getRubrique(){
        return (RubriqueDTO) map.get("Rubrique");
    }
   
    public static void putVariable(VariableDTO variable){

  .....
Before I could use the SimpleEventBus I was in the need of using some object in differents place of my application, the component need some way of using common data (for example if I need a record data after its selection in a ListGrid in a panel A and which  I need sometime later in a panel B) . So I wrote a simple Session class acting like Asp.net or Php Session but for client side.
To avoid the problems of typing error on the key string I use typed get and put method for the common object I use. I kept anyway the normal way of using the HashMap <String>, <Object> with two methods, I don't know if it's a good idea but it's practical during development or test something without be obliged to create the two methods. It make a lot of method to write but eventually it's really practise. I know that it is not so good writing manner because if you don't put the value you are interested in in the right place it's difficult to find errors.

mercredi 31 août 2011

DynamicForm brief

Un formulaire de type DynamicForm gère une collection d’objets de type FormItem qui seront rendus en tant qu’éléments de saisie utilisateur.
L’objet DynamicForm gère tous les aspects du formulaire :
  • La disposition
  • Les valeurs
  • La validation.
  • La liaison de données.
Les principaux FormItem sont :
CanvasItem, CheckboxItem, ColorPickerItem, DateItem, FloatItem, HiddenItem, IntegerItem, RadioGroupItem, SelectItem, SpacerItem, StaticTextItem, TextAreaItem, TextItem

Et pour tous les autres cas CanvasItem est très pratique car il permet "d'intégrer " toute sorte de Widget n'héritant pas de FormItem. on peut ainsi personnaliser un formulaire en mettant une zone avec plusieurs boutons dans une seule colonne par exemple ou mettre un ListGrid etc.. (voir aussi ici)

 
Une DynamicForm  est composée de colonnes dans lesquelles sont disposés les différents contrôles de saisie utilisateurs dérivant tous de FormItem. Par défaut il y a deux colonnes, la plupart des contrôles occupent deux colonnes, une pour le titre et une pour la zone de saisie. Mais rien n'empêche de disposer d'un nombre quelquonque de colonnes(pair ou impair).
On peut disposer les FormItems comme on le désire en utilisant setStartRow, setEndRow et des objets de type SpacerItem ou RowSpacerItem pour créer des « trous » d'une cellule ou d'une rangée complète dans la grille.
En ce qui concerne le style, les FormItems peuvent être personnalisés séparément  pour leur titre ou pour la « boite de saisie ».
Le titre par défaut effectue automatiquement un retour à la ligne, propriété que l’on peut modifier à l’aide de  setWrapTitle (false supprime le retour automatique).
Quand à la boite de saisie on peut utiliser soit  setTextBoxStyle et pour les DateItem  en mode "représentation texte" définir un TextItem stylé comme on le désire et l'attribuer à ce DateItem par
setTextFieldProperties(TextItem textfieldproperties).
La saisie peut être rendue obligatoire (apparition d’une signalisation lors de la validation) par setRequired (le message à afficher est indiquer avec la méthode setRequiredMessage).
Lors de la création de formulaire en mode lecture on peut inhiber la saisie  d’un champ en utilisant setDisabled(true). Dans un tel cas si l’on désire que l’apparence du champ ne soit pas modifiée il faut utiliser setShowDisabled(false) et la valeur du champ ne sera plus grisée et donc plus lisible.
Une sorte de fieldSet peut être inséré à l'aide des méthodes setIsGroup et setGroupTitle

 A DynamicForm  is a set of columns where your different form input elements are arranged. By default there are two column, most of the formItem use two columns (one for the title and the other for the input element) but you can set any number of column and an input control can span several column.By using setStartRow and setEndRow you can setup the controls as you want. "holes" can be introduced by adding SpacerItem (a cell "hole") or RowSpacerItem(a row "hole") too.
For the cases where you don't find what you are looking for in the FormItems, CanvasItem is really practise when you want to use a Control(widget) which doesn't extend FormItem whith it you can add anything you want in your form as a ListGrid for example. (you can have a look here )
The title of the formItem wrap automatically by default and we can use setWrapTitle(false) to have a one line only title.
Some sort of fieldSet can be put around the form by using the methods setIsGroup and setGroupTitle.  

mardi 30 août 2011

List2List transfer from one list to another / first steps

    public static void testListeAListe(){
        String[] laListeSource = new String[5];
        laListeSource[0] = "Pharmacie";
        laListeSource[1] = "Médecine";
        laListeSource[2] = "Osthéopathie";
        laListeSource[3] = "Dentiste";
        laListeSource[4] = "Prothèse";
        ListeAListe lesListes = new ListeAListe(laListeSource,null,"Rubriques","Panier",300);
        lesListes.draw();
    }







public class ListeAListe extends HLayout {
    private final ListGrid source;
    private final ListGrid destination;
    private RecordList sourceList;
    private RecordList destinationList;
    private String titreSource;
    private String titreDestination;
    private ListeAListe moimeme;
    private DataSource sourceDS;
    private DataSource destinationDS;

    public ListeAListe(String[] sourceRecords, String[] destinationRecords,
        String titreSource, String titreDestination, int hauteur) {
        super();
        this.moimeme = this;
        sourceDS = new DataSource();
        DataSourceField valeurSource = new DataSourceField("valeur", FieldType.TEXT); 
        valeurSource.setPrimaryKey(true);
        valeurSource.setTitle(titreSource);
        sourceDS.setFields(valeurSource);
        sourceDS.setClientOnly(true);
        destinationDS = new DataSource();
        DataSourceField valeurDestination = new DataSourceField("valeur", FieldType.TEXT); 
        valeurDestination.setPrimaryKey(true);
        valeurDestination.setTitle(titreDestination);
        destinationDS.setFields(valeurDestination);
        destinationDS.setClientOnly(true);
        this.setWidth("500px");
        this.setHeight(hauteur);
        source = new ListGrid();       
        source.setWidth("35%");
        sourceList = new RecordList();
        for (String s: sourceRecords)
        {
            ListGridRecord r = new ListGridRecord();
            r.setAttribute("valeur",s);
            sourceList.add(r);
        }

        sourceDS.setTestData(sourceList.toArray());
        source.setDataSource(sourceDS);
        source.setAutoFetchData(true);
        source.setEmptyMessage("Liste vide");

        destination = new ListGrid();
        destination.setWidth("35%");
        destinationList = new RecordList();
        destinationDS.setTestData(destinationList.toArray());
        destination.setDataSource(destinationDS);
        destination.setAutoFetchData(true);
        destination.setEmptyMessage("Liste vide");


        VStack transferStack = new VStack(3); 
        transferStack.setWidth("30%"); 
        transferStack.setAlign(VerticalAlignment.CENTER);

        TransferImgButton right = new TransferImgButton(TransferImgButton.RIGHT);
        right.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                ListGridRecord selection = source.getSelectedRecord();
                if(selection != null){
                    if(!recordExistant(selection,destinationList)) {
                        sourceDS.removeData(selection);
                        destinationDS.addData(selection);    
                        destination.selectRecord(selection, false);
                    }
                }

            }
        });

        TransferImgButton left = new TransferImgButton(TransferImgButton.LEFT);
        left.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                ListGridRecord selection = destination.getSelectedRecord();
                if(selection != null){
                    if(!recordExistant(selection,sourceList)) {
                        destinationDS.removeData(selection);
                        sourceDS.addData(selection);   
                        source.selectRecord(selection, false);
                    }
                }

            }
        });

        TransferImgButton rightAll = new TransferImgButton(TransferImgButton.RIGHT_ALL);
        rightAll.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                source.selectAllRecords();
                ListGridRecord[] records = source.getSelection();
                for(Record record : records){
                    sourceDS.removeData(record);
                    destinationDS.addData(record); 
                    destination.selectRecord(record, false);
                }

            }
        });

        TransferImgButton leftAll = new TransferImgButton(TransferImgButton.LEFT_ALL);
        leftAll.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                destination.selectAllRecords();
                ListGridRecord[] records = destination.getSelection();
                for(Record record : records){
                    destinationDS.removeData(record);
                    sourceDS.addData(record); 
                    source.selectRecord(record, false);
                }

            }
        });

        transferStack.addMember(right);
        transferStack.addMember(left);
        transferStack.addMember(rightAll);
        transferStack.addMember(leftAll);

        this.addMember(source);
        this.addMember(transferStack);
        this.addMember(destination); 

    }

    protected boolean recordExistant(ListGridRecord selectedRecord, RecordList liste) {
        if(liste.contains(selectedRecord)){
            return true;
        }else{
            return false;
        }
    }
}

vendredi 26 août 2011

TreeGrid Empty message

Un truc un peu vicieux:
J'avais utilisé setEmptyMessage pour indiquer que les données étaient en cours de chargement (pas de datasource, juste un appel RPC avec une boucle dans le onSuccess de la fonction de callBack): pas de problème l'affichage se faisait .
Mais j'ai du ensuite montrer la racine mais surtout afficher quelquechose d'autre que le folder, donc j'ai créer un TreeNode avec le titre qui allait bien et un icone adequat........... mais la plus de empty message car il y a dès le début ce TreeNode (enfin c'est ce que je pense).
Donc utilisation d'une fenêtre "Patientez"  sur laquelle j'invoque show() au départ  de ma requete et hide() une fois que le TreeGrid a reçu ses données du Tree. J'ai utilisé le eventBus et ma fenêtre est instanciée à l'ouverture de l'application pour être utilisée quand j'en ai besoin.

A nasty thing:
 I was using setEmptyMessage to prompt the user when loading data (no dataSource, only RPC with a loop in the onSucess of the callBack method. No problem the mesage was there.
Later I've beeen asked to add a root with a name and icon. So iI create a TreeNode and set the root of the Tree. But now no more message (I still have to investigate this...)
Anyway I add a "wait please" window with an image and a variable message. This window is instanciated at the beginning of the app. I used eventBus event to hide it or show when I need to ask the user to be patient.


mercredi 24 août 2011

Eclipse: Problèmes de développement - Development troubles

Ayant des soucis depuis le début avec ma configuration Eclipse en mode no-server avec Tomcat 7 (difficultés lors du premier run au redémarrage du système) j'ai découvert bien tard (voir le lien ci dessous) l'ongler Overview (double clic sur le serveur dans la fenêtre Server). Dans la rubrique Server Locations j'ai modifié la sélection pour Use Tomcat Installation et dans Server Options coché Publish module context to separate XML files. Et depuis je n'ai plus de problème.....

lien qui m'a déniaisé:
http://stackoverflow.com/questions/5492955/trying-to-get-tomcat-7-working-with-eclipse-3-6-1


From the beginning I got trouble at the first run of my app in Dev mode with -noserver option with Tomcat7 server. I discover with this link the Server config tab of Eclipse. So I modify to give to Eclipse the control on Tomcat in place of running it in worspace/metadata/plugin...... and since No problems


vendredi 5 août 2011

EventBus code

If something need to be clarified(and I supposed it should be) don't hesistate to write comments

(Update on 2012 October the 2nd)
MainPanel which implements the interface ActionChangedEventHandler to handle all the business events which are related to new action taken by the user.:
public class MainPanel extends VLayout implements ActionChangedEventHandler {

 ------
   

    public MainPanel(){
       
  //My MainPanel "subscribe" to the business event ACTION_CHANGED
 MainPanel.eventBus.addHandler(ActionChangedEvent.TYPE, this);     

 .........
ActionChanged event handler implementation in MainPanel: Everytime this business event will be triggered, my MainPanel will excute this bit of code.
I have several "subevent categories" defined by the property action of the event and which are defeined in the enum BusEventsAction .
@Override
    public void onActionChanged(ActionChangedEvent event) {
        switch(event.getAction()){
        case CATALOG:
            ContentPanelType1 type1= new ContentPanelType1 ();
            chargePanel(type1);
            break;
        case CATEGORY:
           ContentPanelType2 type2= new ContentPanelType2 ();
            chargePanel(type2);
            break;
        case VARIABLES:
........

            break;

ContentPanel:
public class ContentPanelType1 extends MyAppContentPanel implements Type1EventEventHandler
public ContentPanelType1 (){
MainPanel.eventBus.addHandler(Type1Event.TYPE, this);            initializeComponent()
    }

Business event handler (in this case the interface define one method called processRequest):
@Override
    public void processRequest(Type1Event event) {
        SC.say("Coucou");
       
    }

ActionChanged Event Handler interface:
public interface ActionChangedEventHandler  extends  EventHandler {
    void onActionChanged(ActionChangedEvent event);

}
ActionChanged  Event class:
public class ActionChangedEvent extends  GwtEvent<ActionChangedEventHandler> {
    public static final Type<ActionChangedEventHandler> TYPE = new Type<ActionChangedEventHandler>();
   
    private BusEventsAction action;

    public BusEventsAction getAction() {
        return action;
    }

    public void setAction(BusEventsAction action) {
        this.action = action;
    }

    @Override
    public Type getAssociatedType() {       
        return TYPE;
    }

   
    @Override
    protected void dispatch(ActionChangedEventHandler handler) {
        handler.onActionChanged(this);
       
    }

}

Bus events   Enum
public enum BusEvents { 
  ACTION_CHANGED(0),
  CATEGORY_CHANGED(1),
  VARIABLE_CHANGED(2),
  BASES_CHANGED(3),
  USERS_CHANGED(4),
  NEWS_CHANGED(5),
.....
 final private int  value;
   BusEvents( int value) {        
        this.value = value;
    }
   
    public int getValue(){
        return value;
    }
}
......
Bus events actions  Enum
public enum BusEventsAction { 
  CATALOGUE_VARIABLES(0),
  CATALOGUE_CATEGORIES(1),
  USERS(2),
      
.....
   

Business event Triggering:
Suppose I have a button which need to bring the variables catalog panel as the content I will do the following:
btnCatalog.addClickHandler(new ClickHandler() {
@Override
  public void onClick(ClickEvent event) {
//Instantiate an event
    ActionChangedEvent e = new ActionChangedEvent();
//set the sub event type
    e.setAction(BusEventsAction. CATALOGUE_VARIABLES);
//get the EventBus instance and fire the event    
    MainControl.getEventBus().fireEvent(e);
 }
});

Any panel implementing the ActionCHangeEventHandler will  process this event in its onActionChanged(ActionChangedEvent event) method and decide depending of the type of action if it will react to the event or not.

EventBus

If something need to be clarified(and I supposed it should be) don't hesistate to write comments

(mise à jour le 02/10/2012)
Architecture de mes Ihm:
Après passage dans une phase d'authentification on a accès au panel Principal: MainPanel.
Celui-ci contient une zone de contenu ContentPanelHolder qui recoit des panels ayant comme classe mère une classe abstraite MyAppContentPanel.
 Chacun de ces panels de contenu possède un jeu de boutons de commandes donnant accès aux fonctionnalités correspondantes aux droits de l'utilisateur.
Chaque bouton de commande va générer un événement métier de type ActionChangedEvent, cette classe d'événement possède un accesseur permettant de préciser la fonctionalités désirée ce qui correspondra donc à différents panels à charger dans le ContentPanelHolder.
public void setAction(BusEventsAction action) {
        this.action = action;
    }   
    @Override
    public Type getAssociatedType() {       
        return TYPE;
    } 

Bien sur les ContentPanels sont eux intéressés à recevoir les événements concernant la mise à jour des données .
L'EventBus permet de transformer les événements "utilisateurs" en "événements métier", de les déclencher et d'éviter de coupler la source des événements aux "abonnés"  qui vont les traiter.  On délègue la distribution à l'EventBus. Lorsque l'on a besoin de rajouter dans un composants (le plus souvent un panel) un comportement déclenché par tel ou tel événement, il suffit de faire implémenter l'interface du gestionnaire correspondant (handler) et d'inscrire le handler auprès de l'EventBus et d'écrire le code à exécuter en réaction à l'événement.
(update on 2012 October the 2nd )
UI Architecture:
After passing through an authentification stage the MainPanel is displayed.
This one cinlude a content place  ContentPanelHolder which is loaded with panels extending an abstract class MyAppContentPanel.
Each of this content panels has a set of command buttons corresponding to the actions authorized by the user's right.
Each button will trigger a business event of  ActionChangedEvent, this event class get  accessors to set and obtain the action required which in turn correspond to different panels to load in the ContentPanelHolder.
public void setAction(BusEventsAction action) {
        this.action = action;
    }   
    @Override
    public Type getAssociatedType() {       
        return TYPE;
    }

Besides the ContentPanels will subscribe to data related business events...
L'EventBus allow to transform the "user" events into "business events", to trigger them and to avoid to couple the events source with the "listenners" which will process them.  We delegate the dispatching to the EventBus. When we need to add in a component (mainly panels) a behavior triggered by an event we just have to do the necessary in the panel to implemnts the handler interface, declare the handler to the EventBus and write the code to execute in response to the event.

lundi 1 août 2011

Passage en 100% SmartGwt : Une zone Bouton

Suite à l'impossibilité de gérer le mélange des Composants Smart avec ceux de Gwt, toute l'application est modifiée pour n'utiliser que des composants SmartGwt.

1ère modification une zone cliquable (sorte de maxi bouton ) composé d'une image et d'un texte
public class MyButton extends Canvas {
public MyButton(String image,String libelle){
        super();
        this.setHeight(128);
        this.setWidth(128);
        this.setStyleName("plBoutonAction");
        VLayout v = new VLayout();
        HLayout h = new HLayout();        
        h.setAlign(Alignment.CENTER);
        h.setCursor(Cursor.HAND);
        v.setWidth(128);
        v.setHeight(128);
        v.setAlign(Alignment.CENTER);
                    
        
        img.setID("img_du_Bouton");
        img.setWidth(48);
        img.setHeight(48);
        img.setSrc(image);
        img.setCursor(Cursor.HAND);
        

        label.setHeight(48);        
        label.setAlign(Alignment.CENTER);
        label.setContents(libelle);
        label.setCursor(Cursor.HAND);
        
        h.addMember(img);
        v.addMember(h);
        v.addMember(label);        
        this.addChild(v);
        //Css style pour changer la bordure lors du Hover
        this.setStyleName("plBoutonAction");
        
    }

 Classe css:
.plBoutonAction {
    border: 1px solid #F1F1F1;
}

.plBoutonAction:hover {
    border: 1px solid #898989;
}

  
    J'ai essayé d'utiliser une classe plPointer qui ferait cursor: pointer; mais les styles des composants inclus ne semblent pas être hérités.
    Donc finalement j'utilise la méthode setCursor sur chaque sous-composant.
Cette classe possède plusieurs constructeurs avec différents paramètres width,height, container, rang du bouton dans un panneau de n boutons etc...


Further to many troubles due to a mix of Gwt/Smartgwt, I switch the application to 100% Smartgwt (except for upload).
I had some sort of Button zone which include an image and a label with a grey border appearing with the mouse hover..
I got trouble using a Css class which would do: cursor: pointer.
Style don't seem to be inherited by the member of a layout.
So at the end I use the setCursor method on each sub component.
This class get several constructors with different parameters such as width, height, container panel, button rank in a xx buttons panel etc...

vendredi 1 juillet 2011

Smart & Gwt Deux styles pour un mariage quelquefois explosif


Petit problème avec un ComboBoxItem qui n'affiche rien si il n'y qu'une seule valeur dans la ValueMap.
La FAQ du forum indique plusieurs fois: css externe ou thème de Gwt.
Donc
1°) On extrait la css du jar
2°) On modifie le descripteur xml
retirer l'inherits concernant le theme
<!-- <inherits name='com.google.gwt.user.theme.standard.Standard' /> -->
rajouter
<inherits name="com.google.gwt.user.theme.standard.StandardResources" />
3°) On injecte la feuille css par la page html de démarrage après modification personellement de ce qui concerne les éléments html standard.
<link type="text/css" rel="stylesheet" href="standard.css">

Les styles ne sont pas conservés et les menus de gwt sont tout de même modifiés (il faudra que je paufine).

En tout cas le problème de la COMBO est résolu.
Prochaine étape migration vers une version Full SmartGwt





Further to a problem with a ComboBoxItem which didn't display anything when there was only one item in the ValueMap.
The Isomorphic forum Faq indicate the trouble coming from outside Css or Gwt Theme.
I extracted the css from the jar and removed the style concerning normal Html tags.
Modify the xml file and the Html.
Some Gwt element s are little bit different now and I will need to tweak the Css, but the Combo is OK.
Next Step migrate to Full SmartGwt.