Dans l’article précédent, nous avons montré la page Création pour la base de données NoSql Questionnaire. Nous allons maintenant créer la page Recherche.
La page Recherche doit permettre de retrouver une ou plusieurs fiches correspondant à des critères de recherche. Ce que nous attendons de Questionnaire, c’est un moyen simple de définir :
- des types de recherche regroupant un ou plusieurs champs de filtrage
- un format de recherche pour chaque type de champs
A l’issue de cet article nous aurons une page Recherche pour une base NoSql Questionnaire.
Un champ de recherche pour une base de données NoSql Questionnaire
Les données rangées dans une base de données NoSql sont très diverses. Nous avons aussi bien du texte, que des nombres, des dates, des listes, des code barres, des images…
Pour la Recherche sur un champ, nous pouvons définir des méthodes spécifiques. Par exemple :
Date : nous pouvons chercher avec une date de début et une date de fin.
Nombre : nous avons une valeur minimale et une valeur maximale
Texte : nous pouvons utiliser des expressions régulières.
Liste : nous pouvons avoir une sélection de valeur à choisir par des cases à cocher placées devant chacune des valeurs. Parfois le choix dans la liste doit être exclusif, parfois il peut être multiple.
La présentation de la recherche associée à ces types de champ est prédéfini au niveau de la base NoSql Questionnaire. A chaque type de champ est associé son format de recherche à afficher dans la page.
Utilisation du modèle Questionnaire
Le modèle Questionnaire défini le type de champ : nombre, texte, liste, date, password… A chacun de ces types de champ correspond une présentation de la recherche. Aussi, nous n’avons pas besoin de redéfinir à chaque fois comment s’effectue la recherche sur la date ou un nombre…
La recherche pleine Fiche
Dans certain cas, nous pouvons faire une recherche sur toute la fiche sans tenir compte de la particularité des champs. Cette recherche consiste à utiliser une recherche de type RegEx (Regular Expression) sur tout le contenu de la fiche. Nous ne prenons plus en compte les champs de la fiche.
Cela s’apparente à ce que pratique les moteurs de recherche sur internet.
Des groupes de Recherche dans la base de données NoSql Questionnaire
Un groupe de recherche est un ensemble de champs associés pour définir une recherche. Nous pouvons avoir des recherches que sur un champs ou au contraire cumuler le filtrage en utilisant plusieurs champs pour une sélection plus fine de la recherche.
Chaque groupe de recherche possède un nom et un libellé. Les groupes sont à créer dans le modèle du Questionnaire.
Évolution du modèle de la base de donnée NoSql Questionnaire
Pour créer les groupes de recherche, nous devons faire évoluer notre modèle Questionnaire pour inclure la définition des groupes de recherche.
Un groupe est une liste de champ, avec un nom et un libellé pour le groupe.
Reprenons le Questionnaire Ouvrage défini dans l’article présentant la base NoSql pour application Android. Nous ajoutons la gestion des clés et la gestion de la page Recherche.
Le Questionnaire Ouvrage avec la gestion des Clés et de la Recherche :
public class Ouvrage extends QuestionnaireModel { public Ouvrage(Context context) { super(context); } public PlugsetMessage build(PlugsetMessage message) { QuestionnaireLibrary q = new QuestionnaireLibrary(this); q.newPage("Ouvrage"); q.design("standardBox",QuestionnaireWord.ALPHANUM,30); q.design("dateBox",QuestionnaireWord.DATE,8); q.design("barcodeBox",QuestionnaireWord.EAN13,13); q.design("imageBox",QuestionnaireWord.IMAGE,2560,1600);//pixel q.design("currencyBox",QuestionnaireWord.CURRENCY,8); q.design("numberBox",QuestionnaireWord.INTEGER,4); q.design("referenceBox",QuestionnaireWord.CARD_ID,25);//10 pour le nom du Questionnaire et 15 pour le CardId q.design("pathBox",QuestionnaireWord.PATH,60);//chemin dans la hierarchie q.defineItem("Titre","Titre","standardBox"); q.defineItem("Auteur","Auteur","standardBox"); q.defineItem("Editeur","Editeur","standardBox"); q.defineItem("DateEdition","Paru le","dateBox"); q.defineItem("CodeBarre","EAN13","barcodeBox"); q.defineItem("Couverture","Couverture","imageBox"); q.defineItem("Prix","Prix","currencyBox"); q.defineItem("NbPages","NbPages","numberBox"); q.defineReference("Proprietaire","AnnuaireQuestionnaire","referenceBox"); q.defineHierarchy("Localisation","LocalisationHierarchy","pathBox"); q.defineReference("Emprunteur","AnnuaireQuestionnaire","referenceBox"); q.defineReference("EditionPourOuvrage","OuvrageQuestionnaire","referenceBox"); q.defineReference("OeuvrePourOuvrage","OuvrageQuestionnaire","referenceBox"); q.theme("Oeuvre"); q.askQuestion("Titre","Titre"); q.askQuestion("Auteur","Auteur"); q.endTheme(); q.theme("Edition"); q.askReference("Oeuvre","OeuvrePourOuvrage"); q.askQuestion("Editeur","Editeur"); q.askQuestion("DateEdition","DateEdition"); q.askQuestion("CodeBarre","CodeBarre"); q.askCapture("CouvertureRecto","Couverture"); q.askCapture("CouvertureVerso","Couverture","4ème de couverture"); q.askQuestion("Prix","Prix"); q.askQuestion("NbPages","NbPages"); q.endTheme(); q.theme("Livre"); q.askReference("Edition","EditionPourOuvrage"); q.askReference("Proprietaire","Proprietaire"); q.askHierarchy("Localisation","Localisation"); q.askReference("Emprunteur","Emprunteur"); q.endTheme(); q.keyList(QuestionnaireWord.KEY_LIST); q.keyListItem("Oeuvre.Auteur", ""); q.keyListItem("Oeuvre.Titre", ""); q.keyListItem("Edition.Editeur", ""); q.keyListItem("Edition.DateEdition", ""); q.keyListItem("Edition.CodeBarre", ""); q.keyListItem("Livre.Proprietaire", ""); q.endKeyList(); q.viewSearch("researchByAuteur"); q.searchAsk("Oeuvre.Auteur", "Auteur"); q.endViewSearch(); q.viewSearch("researchByTitre"); q.searchAsk("Oeuvre.Titre", "Titre"); q.endViewSearch(); q.viewSearch("researchByOeuvre"); q.searchAsk("Oeuvre.Auteur", "Auteur"); q.searchAsk("Oeuvre.Titre", "Titre"); q.endViewSearch(); q.viewSearch("researchByEditeur"); q.searchAsk("Edition.Editeur", "Editeur"); q.endViewSearch(); q.viewSearch("researchByCodeBarre"); q.searchAsk("Edition.CodeBarre", "CodeBarre"); q.endViewSearch(); q.endPage(); Repository questionnaireRepository=q.get(); System.out.println("AppLog.Ouvrage.build : questionnaireRepository="+questionnaireRepository.getXmlString()); //on doit construire la réponse et la mettre dans message message.put("page","xml",getRepository()); return message; } }
Le Modèle proposé permet de créer plusieurs type de recherche dans un même Questionnaire.
Nous complétons QuestionnaireLibrary avec keyList, keyListItem, searchAsk, viewSearch :
public final void keyList(String name) { XmlTagItem<QuestionnaireWord> tag = new XmlTagItem<QuestionnaireWord>(); tag.setTagStart(); tag.setModel(QuestionnaireWord.KEY_LIST); tag.addProperty(KeyWord.NAME, name); get().addTag(tag); } public final void endKeyList() { XmlTagItem<QuestionnaireWord> tag = new XmlTagItem<QuestionnaireWord>(); tag.setTagEnd(); tag.setModel(QuestionnaireWord.KEY_LIST); get().addTag(tag); } public final void keyListItem(String name, String value) { XmlTagItem<QuestionnaireWord> tag = new XmlTagItem<QuestionnaireWord>(); tag.setTagSingle(); tag.setModel(QuestionnaireWord.KEY_LIST_ITEM); tag.addProperty(KeyWord.NAME, name); tag.addProperty(KeyWord.VALUE, value); get().addTag(tag); } public final void searchAsk(String name, String value) { XmlTagItem<QuestionnaireWord> tag = new XmlTagItem<QuestionnaireWord>(); tag.setTagSingle(); tag.setModel(QuestionnaireWord.SEARCH_ASK); tag.addProperty(KeyWord.NAME, name); tag.addProperty(KeyWord.VALUE, value); get().addTag(tag); } public final void viewSearch(String name) { XmlTagItem<QuestionnaireWord> tag = new XmlTagItem<QuestionnaireWord>(); tag.setTagStart(); tag.setModel(QuestionnaireWord.VIEW_SEARCH); tag.addProperty(KeyWord.NAME, name); get().addTag(tag); } public final void endViewSearch() { XmlTagItem<QuestionnaireWord> tag = new XmlTagItem<QuestionnaireWord>(); tag.setTagEnd(); tag.setModel(QuestionnaireWord.VIEW_SEARCH); get().addTag(tag); }
Nous complétons QuestionnaireWord avec KEY_LIST, KEY_LIST_ITEM, SEARCH_ASK, VIEW_SEARCH :
public enum QuestionnaireWord implements ModelWord { ALPHANUM("Alphanum"), ASK_CAPTURE("AskCapture"), ASK_QUESTION("AskQuestion"), ASK_HIERARCHY("AskHierarchy"), ASK_REFERENCE("AskReference"), CARD_ID("CardId"), CURRENCY("Currency"), DATE("Date"), DEFINE_HIERARCHY("DefineHierarchy"), DEFINE_ITEM("DefineItem"), DEFINE_LIST("DefineList"), DEFINE_REFERENCE("DefineReference"), DESIGN("Design"), EAN13("Ean13"), IMAGE("Image"), INTEGER("Integer"), KEY_LIST("KeyList"), KEY_LIST_ITEM("KeyListItem"), LIST_ITEM("ListItem"), PATH("Path"), QUESTIONNAIRE("Questionnaire"), SEARCH_ASK("SearchAsk"), THEME("Theme"), VIEW_SEARCH("ViewSearch"); private final String value; private QuestionnaireWord(String value) { this.value = value; } @Override public String getValue() { return value; } @Override public String toString() { return value; } public static QuestionnaireWord parse(String value){ return ModelWord.parse(QuestionnaireWord.class, value); } }
Visualisation de la page Recherche pour le Questionnaire Ouvrage
Nous allons reprendre notre Questionnaire Ouvrage et définir la mise en page de la recherche.
La page OuvrageSearch :
public class OuvrageSearch extends ScreenModel { Context context; public OuvrageSearch(Context context) { super(context); } public PlugsetMessage build(PlugsetMessage message) { ScreenLibrary s = new ScreenLibrary(this); s.newPage("ouvrageSearch", "A1", "B15","0.5"); s.label("titrePage", null,"Recherche par Ouvrage", "A1", "B1"); String ouvrageAuteur=getOuvrageAuteur(message); String ouvrageTitre=getOuvrageTitre(message); String listeSize=getListeSize(message); s.label("labelAuteur","auteur", "Auteur", "A2", "A2"); s.text("auteur", ouvrageAuteur, 1,25,"A3", "B3", false); s.label("labelTitre", "titre", "Titre", "A5", "A5"); s.text("titre", ouvrageTitre, 1,25,"A6", "B6", false); s.button("btnSearch", "Lancer la recherche", "A8", "B8", "callScript", "doModify", true);s.endPage(); s.label("labelCompteur","compteur", "Nombre d'ouvrages identifiés", "A10", "A10"); s.text("compteur", listeSize, 1,25,"B10", "B10", false); s.button("btnList", "Voir la liste des ouvrages", "A12", "B12", "callScreen", "menu.asp", true); s.button("btnAnnuler", "Annuler", "A15", "B15", "callScreen", "menu.asp", true); s.endPage(); Repository screenRepository=s.get(); System.out.println("AppLog.MonCompteDisplay.build : screenRepository="+screenRepository.getXmlString()); //on doit construire la réponse et la mettre dans message message.put("page","xml",getRepository()); return message; } private String getOuvrageAuteur(PlugsetMessage message){ return ""; } private String getOuvrageTitre(PlugsetMessage message){ return ""; } private String getListeSize(PlugsetMessage message){ return ""; } }
Nous prenons dans le modèle Questionnaire la recherche ResearchByOeuvre qui contient 2 champs de filtrage : le titre de l’œuvre et l’auteur.
Le résultat dans le simulateur est le suivant :
Lorsque nous lançons la recherhce, le champs Nombre d’ouvrages se met à jour pour indiquer le nombre de fiches trouvées.
Table inverse et Clé de recherche dans le Questionnaire
pour accélérer la recherche, la base NoSql Questionnaire défini des tables inverses. Ces tables inverses reprennent toutes les valeurs d’un champ et associent pour chaque valeur la liste des CardId, c’est à dire l’identifiant de la fiche pour laquelle le champ contient la valeur demandée.
Ainsi, la recherche consiste à trouver la table inverse et à récupérer la liste des CardId correspondant à la valeur fournit dans la zone recherche. Lorsqu’il y a plusieurs champ dans le groupe de recherhce, nous recherchons les CardId présent dans les 2 tables inverses.
Le modèle Questionnaire permet de définir la liste des clés avec KeyList.
Gestion de l’espace dans l’écran
Nous constatons que la grille a besoin d’être ajustée page par page afin d’avoir un équilibre dans la page en fonction du nombre d’informations présentes.
La propriété Html utilisée est grid-gap. Cette propriété est défini dans le tag Screen.
Nous définissons GridGap dans KeyWord ligne 10 :
public enum KeyWord { ACTION_NAME("actionName"), ACTION_PARAM("actionParam"), AREA_START("areaStart"), AREA_END("areaEnd"), CAPTION("caption"), DESIGN("design"), EDITABLE("editable"), GRID_GAP("GridGap"), LABEL_FOR("labelFor"), HEIGHT("height"), LENGTH("length"), LENGTH_MIN("lengthMin"), LENGTH_MAX("lengthMax"), NAME("name"), QUESTIONNAIRE_NAME("questionnaireName"), TYPE("type"), VALUE("value"), WIDTH("width"); private String value; private static final Map<String, KeyWord> BY_LABEL = new HashMap(); static{ for(KeyWord word:values()){ BY_LABEL.put(word.value,word); } } private KeyWord(String value) { this.value = value; } public String getValue() { return value; } @Override public String toString() { return getValue(); } public static KeyWord parse(String value) { return BY_LABEL.get(value); } }
Lorsque dans un Tag, une propriété n’est pas définie, la méthode getProperty retourne null. Nous avons parfois besoin de remplacer cette valeur null par une valeur spécifique. Dans le cas présent nous souhaitons avoir la valeur « 1 ». Nous ajoutons dans RepositoryItem la méthode getProperty(attributName, defaultValue)
RepositoryItem modification de getProperty :
public Object getProperty(KeyWord att){ return properties.get(att); } public Object getProperty(KeyWord att, Object defaultValue){ if(hasProperty(att)) { return properties.get(att); } return defaultValue; }
Et nous ajoutons cette option dans ScreenLibrary newPage
public void newPage(String name, String areaStart, String areaEnd) { newPage( name, areaStart, areaEnd,null); } public void newPage(String name, String areaStart, String areaEnd, String gridGap) { model.setRepository(new Repository(name)); model.setModelMemory(new SquareMap<String, Integer, Object>()); XmlTagItem<ScreenWord> tag = new XmlTagItem<ScreenWord>(); tag.setTagStart(); tag.setModel(ScreenWord.SCREEN); tag.addProperty(KeyWord.NAME, name); tag.addProperty(KeyWord.AREA_START,areaStart); tag.addProperty(KeyWord.AREA_END, areaEnd); tag.addProperty(KeyWord.GRID_GAP, gridGap); get().addTag(tag); } public void endPage() { XmlTagItem<ScreenWord> tag = new XmlTagItem<ScreenWord>(); tag.setTagEnd(); tag.setModel(ScreenWord.SCREEN); get().addTag(tag); }
La prise en compte de la propriété GridGap se fait dans HtmlEngine ligne 22.
public boolean screen_start(RepositoryItem tag) { try { System.out.println("AppLog "+this.getClass().getName()+".screen_start"+" tag=" + tag); screenName = tag.getName();//le nom de l'écran String areaStart = (String) tag.getProperty(KeyWord.AREA_START); String areaEnd = (String) tag.getProperty(KeyWord.AREA_END); String gridGap = (String) tag.getProperty(KeyWord.GRID_GAP,"1"); System.out.println("AppLog "+this.getClass().getName()+".screen_start"+" screenName=" + screenName+" areaStart=" + areaStart+" areaEnd=" + areaEnd); areaGrid =new AreaGrid(areaStart,areaEnd); title=screenName; //pas besoin de code html pour l'instant //StringBuilder screenHtml=new StringBuilder(); //htmlBody.append(screenHtml).append(Ascii.CRLF); StringBuilder screenCss=new StringBuilder(); screenCss.append("*"+"{").append(Ascii.CRLF); screenCss.append(" box-sizing:border-box;").append(Ascii.CRLF); screenCss.append("}").append(Ascii.CRLF); screenCss.append("body"+"{").append(Ascii.CRLF); screenCss.append(" background-color:#AAA;").append(Ascii.CRLF); //screenCss.append(" justify-content:space-evenly;").append(Ascii.CRLF); //screenCss.append(" align-content:end;").append(Ascii.CRLF); screenCss.append(" grid-gap:").append(gridGap).append("rem;").append(Ascii.CRLF); screenCss.append("}").append(Ascii.CRLF); css.append(screenCss); } catch (Exception e) { System.out.println("AppLog "+this.getClass().getName()+".screen_start"+" ERROR :" + e); } return false; }