Forums : Xul, Xbl, JS...

Aller à la discussion :  Plus récente Plus ancienne

# Autocomplete textbox - alimentation dynamique

Envoyé par : Eric

Date : 08/04/2006 19:30

Bonjour,

J'essaye de créer un XBL qui contient une autocomplete textbox version firefox ou xulrunner.

J'ai essayé l'exemple présenté sur le site et cela fonctionne très bien.

Je suis cependant confronté au problème suivant : comment alimenter la liste des valeurs sur laquelle doit s'effectuer la recherche de manière dynamique.

L'idée est la suivante :

1) récupérer les éléments d'une datasource, les transformer en une liste de structures du style {uri, libellé}

2) réécrire les fonctions de l'objet JS qui implémente nsIAutoCompleteResult en conséquence pour ne rechercher que sur les libellés (là, c'est simple).

3) au moment de l'autocomplete (sélection dans la liste ou click), retrouver la structure dont est issu le libellé sélectionné pour retouver l'uri et donc la ressource RDF.

Si ça intéresse quelqu'un j'ai réussi à le faire dans la version autocomplete de seamonkey car le widget a une fonction addSession qui permet d'ajouter un objet qui répond à l'interface nsIAutocompleteSession.

En fait, avec xulrunner, le problème se résume à comment accéder au composant AutoCompleteSearch pour l'initialiser dynamiquement avec une datasource sur laquelle se baserait la fonction startSearch pour construire sa liste de résultats.

# Re: Autocomplete textbox - alimentation dynamique

Envoyé par : Eric

Date : 10/04/2006 15:55

Comme je ne pense pas recevoir beaucoup de réponses en l'état de la question, je vais essayer de résumer le problème autrement :

Reprenons l'exemple du Wikki http://www.xul-fr.org/wiki/RessourcesLibs/autoComplete et imaginons que la variable semaine

var semaine = ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"];

ne soit pas connue au moment de l'ouverture de l'écran, mais seulement par la suite.

Comment initialiser la liste des jours une fois l'écran ouvert ?

# Re: Autocomplete textbox - alimentation dynamique

Envoyé par : laurentj

Date : 10/04/2006 16:08

tu n'as qu'à stocker ta liste ailleurs, dans un objet global, un service. Ça peut être aussi un fichier, une base de donnée, un service web, ce que tu veux...

À toi de développer ta méthode startSearch comme il te convient. L'exemple simpliste est donné... à titre d'exemple !

# Re: Autocomplete textbox - alimentation dynamique

Envoyé par : Eric

Date : 11/04/2006 11:34

Oui Laurent, mais il se trouve que dans mon cas d'utilisation je ne vois pas trop comment même utiliser une varible globale.

J'ai des écrans avec plusieurs textbox autocomplete qui peuvent présenter les mêmes types de données, mais pas les mêmes données.

La textbox autocomplete est composant d'un XBL de type dropdown. Je l'utilise surtout pour afficher des codes.

Exemple dans un même écran j'ai deux dropdown:

Etat civil

 |--> célibataire
 |--> marié

Statut professionnel

 |--> employé
 |--> ouvrier

Sur l'XBL, j'ai un attribut kind qui indique les types de code à afficher dans la dropdown.

L'autocomplete search serait de type autocompletesearch="Codes". J'aimerais éviter de créer un XPCOM par type de codes, il y en a des centaines.

J'ai donc besoin de connaître cet attribut 'kind' de la textbox pour savoir sur base de quelle liste alimenter l'autocomplete.

A la vue du code source, j'ai le sentiment que seul le controller connaît le (et est connu du) nsIAutoCompleteInput et que c'est lui qui instancie l'autocompletesearch dans sa fonction setInput. Je ne vois donc pas le moyen, une fois l'écran chargé d'indiquer à l'autocompletesearch sur quel type de code doit s'effectuer sa sélection.

Une solution serait peut-être de réécrire le controller. Mais, avant de faire ça, je me demandais s'il n'y avait pas un autre moyen.

# Re: Autocomplete textbox - alimentation dynamique

Envoyé par : laurentj

Date : 12/04/2006 18:10

dans le fichier de ton composant, tu sais que tu déclare un constructeur, une factory, qui est chargé d'instancier le composant, en fonction du contract_id.

Tu peux donc trés bien avoir plusieurs contract_id différent, mais que cela instancie le même objet. Et il suffit que lors de cette instanciation, tu initialises ton composant avec une liste correspondant au contract_id.

Et dans ton xbl, tu map ton kind sur l'attr autocompletsearch ;-)

# Re: Autocomplete textbox - alimentation dynamique

Envoyé par : Eric

Date : 22/05/2006 14:06

Après un petit temps, je me permets de revenir sur le sujet pour spécifier les pistes que j'ai suivies :

1) travailler avec des variables globales J'ai le sentiment que les composants ne peuvent travailler avec des variables globales. Est-ce exact ?

J'ai donc utilisé des properties pour retenir temporairement le kind dont fait mention plus haut. Cela a bien fonctionné pour l'affichage.

Le problème a été la récupération des données. A nouveau, comme seul le controller connaît le composant AutoCompleteSearch, il n'y a pas moyen de connaître en dehors du controller ne fût-ce que l'index de l'élément sélectionné. Une solution pour sen sortir est que la fonction getValueAt de l'AutoCompleteResult instancie à son tour une properties qui sera récupérée ultérieurement.

De manière générale, je ne trouve pas la manière de faire très propre.

2 instancier avec un getService Une solution serait que le composant AutoCompleteSearch soit instancié au moyen d'un getService. On pourrait alors le récupérer comme on veut dans l'état où il a été laissé. Le problème c'est que je ne sais pas comment et s'il est possible d'insancier un composant javascript avec la fonction getService.

3 réécrire le controller en javascript Je l'ai fait en redirigeant la plupart des fonctions vers le nsIController.

Exemple :

startSearch : function(aSearchString){	
	this.nsIController.startSearch(aSearchString);
},

C'est en cours mais je pense que je vais me retrouver devant les mêmes problèmes qu'en 1.

4 faire pression sur la fondation Mozilla pour qu'ils rajoutent une fonction addSearchSession qui faciliterait la vie à tout le monde :)

# Re: Autocomplete textbox - alimentation dynamique

Envoyé par : laurentj

Date : 22/05/2006 14:56

J'ai donc utilisé des properties pour retenir temporairement le kind dont fait mention plus haut.

Mais pourquoi donc te compliquer la vie à ce point là ? je t'ai donné pourtant la solution ;-) Avoir plusieurs contract-id qui pointent vers un même composant. Pas besoin de faire plusieurs composants.

En js ça donnerait un truc de ce genre là :

// différentes constantes...
const MYCOMPONENT_CONTRACTID1 = '@mozilla.org/autocomplete/search;1?name=kind1';
const MYCOMPONENT_CONTRACTID2 = '@mozilla.org/autocomplete/search;1?name=kind2';
const MYCOMPONENT_CONTRACTID3 = '@mozilla.org/autocomplete/search;1?name=kind3';
const MYCOMPONENT_UUID1 = Components.ID('{xxxxx-xxxx-xxxx-xxxx....}'); // pour kind1
const MYCOMPONENT_UUID2 = Components.ID('{xxxxx-xxxx-xxxx-xxxx....}'); // pour kind2
const MYCOMPONENT_UUID3 = Components.ID('{xxxxx-xxxx-xxxx-xxxx....}'); // pour kind3

const MYCOMPONENT_IID = Components.interfaces.nsIMyInterface;

// ton composant principal, impléméntant nsIAutoCompleteSearch
// qui renvoi des listes de choses en fonction de la valeur 
// de sa propriété kind
function MyComponent(kind){
   this.kind=kind;
}

MyComponent.prototype = {
  // ici les methodes, qui agiront en fonction de this.kind, 
  // renverront les résultats en fonction de this.kind
}

/* Class Factory */
var MyComponentFactory = {

   // instancie le composant, en lui donnant un parametre kind
   // en fonction du UUID demandé 
   createInstance: function(outer, iid) {
       if (outer != null)
           throw Components.results.NS_ERROR_NO_AGGREGATION;
   
       if (!iid.equals(Components.interfaces.nsISupports))
           throw Components.results.NS_ERROR_INVALID_ARG;

       if (!iid.equals(MYCOMPONENT_UUID1))
           return new MyComponent('kind1');
       if (!iid.equals(MYCOMPONENT_UUID2))
           return new MyComponent('kind2');
       if (!iid.equals(MYCOMPONENT_UUID3))
           return new MyComponent('kind3');

       throw Components.results.NS_ERROR_INVALID_ARG;
   }
}


/* Module (for XPCOM registration) */
var MyComponentModule = {
   registerSelf: function(compMgr, fileSpec, location, type) {
       compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);

       // déclaration de plusieurs contractid (qui, en réalité
       // pointent vers la même implementation, cf factory)
       compMgr.registerFactoryLocation( MYCOMPONENT_UUID1, 
                                       "my search component type kind1", 
                                       MYCOMPONENT_CONTRACTID1,
                                       fileSpec, 
                                       location,
                                       type);
       compMgr.registerFactoryLocation( MYCOMPONENT_UUID2, 
                                       "my search component type kind2", 
                                       MYCOMPONENT_CONTRACTID2,
                                       fileSpec, 
                                       location,
                                       type);
       compMgr.registerFactoryLocation( MYCOMPONENT_UUID3, 
                                       "my search component type kind3", 
                                       MYCOMPONENT_CONTRACTID3,
                                       fileSpec, 
                                       location,
                                       type);
   },

   getClassObject: function(compMgr, cid, iid) {
       if (!cid.equals(MYCOMPONENT_UUID1) 
           || !cid.equals(MYCOMPONENT_UUID2) 
           || !cid.equals(MYCOMPONENT_UUID3))
           throw Components.results.NS_ERROR_NO_INTERFACE;

       if (!iid.equals(Components.interfaces.nsIFactory))
           throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
       return MyComponentFactory;
   },

   canUnload: function(compMgr) { return true; }
};

/* module initialisation */
function NSGetModule(comMgr, fileSpec) { return MyComponentModule; }

Aprés ça, il suffit de mettre un autocompletesearch="kind1", autocompletesearch="kind2" ou autocompletesearch="kind3" pour avoir le résultat attendu, sans avoir à développer x composants.

# Re: Autocomplete textbox - alimentation dynamique

Envoyé par : Eric

Date : 23/05/2006 14:03

J'ai en fait deux problèmes avec cette solution :

1) L'application comporte un module qui gère des codes, comme ceux décrits plus haut. L'administrateur système a le droit redéfinir ces codes et d'en compléter la hiérarchie. Le système n'a la connaissance des codes redéfinis que comme fils de codes connus. En d'autres termes, le kind n'est parfois connu qu'à l'exécution.

2) Une fois l'autocomplete terminé, il faut que je récupère l'id du code sélectionné dont le libellé est affiché dans la textbox. A nouveau, tout se passe dans le controller qui a seul connaissance de l'AutoCompleteSearch. Il est donc impossible d'appeler une fonction spécifique sur le composant pour récupérer cet ID

Pour l'instant dans la solution implémentée, j'ai fait un XBL qui hérite du binding autocomplete et redéfinit la fonction attachController pour aliementer une properties avec le kind du widget avant d'attacher le controller.

Dans la fonction startSearch du composant AutoCompleteSearch je récupère cette properties pour connaître le kind et proposer les candidats en conséquence.

Enfin, dans la fonction getValue de l'AutoCompleteSearch j'alimente une autre properties qui me permettra ultérieurement de retrouver l'id de l'élément sélectionné.

Je n'ai donc qu'un composant avec un contract_id, mais je trouve la solution peu élégante.

Je pourrais aussi reprendre le code C++ du composant autocompleteController pour ajouter la fonction qui me manque et qui ressemblerait à

NS_IMETHODIMP nsAutoCompleteController::AddAutoCompleteSearch(nsIAutoCompleteSearch *aSearch){
    mSearches->AppendElement(aSearch);
}

mais pour des raisons de maintenance (et de protabilité) j'aurais voulu éviter d'avoir à créer des composants C++.

# Re: Autocomplete textbox - alimentation dynamique

Envoyé par : chris

Date : 10/04/2007 00:17

Eric a écrit:

Comme je ne pense pas recevoir beaucoup de
réponses en l'état de la question, je vais essayer
de résumer le problème autrement :

Reprenons l'exemple du Wikki
http://www.xul-fr.org/wiki/RessourcesLibs/autoComp
lete et imaginons que la variable semaine
var semaine = ["lundi", "mardi", "mercredi",
"jeudi", "vendredi", "samedi", "dimanche"];

ne soit pas connue au moment de l'ouverture de
l'écran, mais seulement par la suite.

Comment initialiser la liste des jours une fois
l'écran ouvert ?

Je crois comprendre la question, parce que j'ai exactement la même.

Exprimée d'une autre façon (dans mon cas) c'est :

Dans cet exemple du Wiki, comment ne pas avoir le tableau semaine en dur dans le code du composant, mais être capable d'initialiser ce tableau à l'aide du code JS de mon appli ?

Je tourne en rond depuis un bon moment sans trouver la solution (jusqu'à tomber sur ce "vieux" fil ;-) ).

# Re: Autocomplete textbox - alimentation dynamique

Envoyé par : chris

Date : 10/04/2007 00:36

Bon ben je me suis finalement débrouillé avec la propriété autocompletesearchparam du textbox que je mets donc à jour dynamiquement et qui correspond au paramètre searchParam de la fonction startSearch du composant.

Ce n'est sans doute pas l'idéal, mais je n'ai pas trouvé mieux.

# Re: Autocomplete textbox - alimentation dynamique

Envoyé par : Paul Rouget

Date : 10/04/2007 03:20

Pour passer ton tableau, implémente simplement une méthode d'init. Passe par un wrapperJSObject ou alors implémente ton interface IDL.

# Re: Autocomplete textbox - alimentation dynamique

Envoyé par : chris

Date : 10/04/2007 06:17

Merci Paul, mais tout ce que tu me dis là ne me parle pas :-(

# Re: Autocomplete textbox - alimentation dynamique

Envoyé par : Eric

Date : 10/04/2007 12:10

J'ai trouvé une solution mais elle est vraiment longue à démontrer ici en terme de codes sources. Elle nécessite des améliorations car elle ralentit (légèrement) le traitement et la recherche autocomplete.

Rappel de l'objectif initial : disposer d'un composant unique pour gérer tous les types d'autocomplete de mon application.

Contexte : l'application dispose de dropdown qui sont des widgets composés très souvent de deux textbox autocomplete.

Exemple : pour saisir une localité, le widget comporte deux textbox autocomplete <zip code><libellé localité>. La recherche autocomplete peut être soit réalisée sur le zip code et le libellé s'affiche dans la colonne comment des résultats possibles, soit sur le libellé et le zip code s'affiche alors dans la colonne comment.

La dropdown utilise une datasource reprenant un liste de localités sous la forme

{ zipCode
  libelle
  id }

Il y a beaucoup d'autres exemples dans l'application de dropdown au fonctionnement similaire : <n° cadastre><libellé>, <code devise><libellé devise>, ... Les structures de données des datasources sous-jacentes sont à chaque fois différentes.

Conséquence : comme le mode de recherche diffère d'une dropdown à l'autre il y a lieu de conserver un contexte pour que le composant sache comment procéder.

Autre problème une fois l'autocomplete réalisé il y a lieu récupérer l'id associé à la valeur sélectionnée pour traitement umtérieur.

Les points principaux de la solution sont les suivants :

1) modifier le composant dropdown pour qu'il réponde également à l'interface nsIDictionary. C'est par cette interface que je passe sous forme d'attributs |key, value| les informations de contexte nécessaires pour que le composant sache comment effectuer sa recherche. En fait je passe deux informations, à savoir une référence sur la dropdown elle-même |key, pointeur sur dropdown| et un attribut classe |key, nsISupportsString|. La fonction de recherche autocomplete est implémentée dans chaque dropdown puisqu'elle est spécifique à chaque dropdown. Sa signature est cependant toujours la même. Le composant ne fait que rappeler la fonction de recherche sur la dropdown qu'il a reçu en paramètre. La classe sert à savoir sur quel type d'attribut est réalisé l'autocomplete (ex : zip code ou libellé) et est un paramètre passé à la fonction de recherche.

2) Créer un nouvel XBL autocomplete textbox sur base de celui en standard dans Firerfox et

  • modifier l'initialisation pour créer un controller spécifique
  • modifier la fonction attachController pour qu'elle attache ce controller specifique et l'initialise avec le widget courant
var c = new dropDownController();
c.dropdown = this;

3) Créer un controller spécifique, un objet javascript qui répond à l'interface nsIAutoCompleteController et qui utilise un composant nsIAutoCompleteController

function dropDownCnontroller(){
  var CC = Components.classes;
  var Ci = Components.interfaces;
           
  this.nsIController = CC["@mozilla.org/autocomplete/controller;1"];
  this.nsIController = this.nsIController.createInstance(Ci.nsIAutoCompleteController);
}
dropDownController.prototype = {...

dont toutes les méthodes sont des indirections vers celles du composant nsIController

get input(){ return this.nsIController.input; },
set input(aInput){this.nsIController.input = aInput;},        
get matchCount(){ return this.nsIController.matchCount; },
get searchStatus(){ return this.nsIController.searchStatus; },
//Methods
getCommentAt : function(aIndex){
return this.nsIController.getCommentAt(aIndex);
},
...

sauf la fonction handleEnter

handleEnter : function(){
 var indx = this.input.popup.selectedIndex;
  if(indx > -1){
   var id = this.getStyleAt(indx);
   this.dropdown.manageSelection(id);
  }
  return this.nsIController.handleEnter();
},

C'est là qu'on voit qu'il y a encore des lacunes car la seule manière que j'ai trouvée pour récupérer l'id est d'utiliser la méthode getStyleAt.

La fonction manageSelection se charge de récupérer la structure de données relative à l'élément sélectionné

     <method name="manageSelection">
       <parameter name="aId"/>
       <body><![CDATA[
         for(var i = 0; i < this.allRslt.length; ++i){
             if(this.allRslt[i].id == aId){
                 this.value = this.allRslt[i];
                 break;
             }
         }
       ]]></body>
     </method>

Il n'est plus possible de poster des messages dans ce forum.


Copyright © 2003-2013 association xulfr, 2013-2016 Laurent Jouanneau - Informations légales.

Mozilla® est une marque déposée de la fondation Mozilla.
Mozilla.org™, Firefox™, Thunderbird™, Mozilla Suite™ et XUL™ sont des marques de la fondation Mozilla.