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.....
Some notes about what I met during my SmartGWt development.(english version at the bottom of the page)
jeudi 15 décembre 2011
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.
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 " "
for(int i=0;i<(lgReturn-lgSource);i++){
returnString += " ";
}
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 " " 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.
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 " "
for(int i=0;i<(lgReturn-lgSource);i++){
returnString += " ";
}
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 " " 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 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);
}
}
});
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();
}
});
btnAdd.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
listGrid.setCanEdit(true);
listGrid.startEditingNew();
}
});
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.
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) .
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.
Inscription à :
Commentaires (Atom)