Nouvelle fonctionnalité permettant d'adapter le rendu HTML.

This commit is contained in:
Fabrice PENHOËT 2021-09-23 17:19:31 +02:00
parent 1b84eb8192
commit f55f897bea
7 changed files with 145 additions and 49 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "freedatas2html", "name": "freedatas2html",
"version": "0.4.6", "version": "0.5.0",
"description": "Visualization of data from various sources (CSV, API, HTML...) with filters, classification, pagination, etc.", "description": "Visualization of data from various sources (CSV, API, HTML...) with filters, classification, pagination, etc.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -25,10 +25,11 @@
</article> </article>
<p id="pages"></p> <p id="pages"></p>
<aside><p>Les données affichées viennent d'<a href="datas/elements-chimiques.csv">un fichier CSV</a> reprenant une partie des informations trouvées <a href="https://fr.wikipedia.org/wiki/%C3%89l%C3%A9ment_chimique#Caract%C3%A9ristiques_des_diff%C3%A9rents_%C3%A9l%C3%A9ments" target="_blank">sur Wikipédia</a>.</p> <aside><p>Les données affichées viennent d'<a href="datas/elements-chimiques.csv">un fichier CSV</a> reprenant une partie des informations trouvées <a href="https://fr.wikipedia.org/wiki/%C3%89l%C3%A9ment_chimique#Caract%C3%A9ristiques_des_diff%C3%A9rents_%C3%A9l%C3%A9ments" target="_blank">sur Wikipédia</a>.</p>
<p>La dernière colonne permet de montrer la possibilité d'<b>extraire des données d'une colonne ayant plusieurs valeurs par ligne</b>, ici séparées par une virgule.<br>Ce choix de la virgule comme séparateur ne pose pas de problème, tant le fichier CSV est correctement conçu.<br>Elle ne doit par contre pas être présente dans les expressions à extraire de la colonne, sachant que l'on peut désigner n'importe quel autre caractère séparateur.</p> <p>Le dernier filtre (Mots-clés) permet de montrer la possibilité d'<b>extraire des données d'un champ ayant plusieurs valeurs par ligne</b>, ici séparées par une virgule, sachant que l'on peut désigner n'importe quel autre caractère séparateur.</p>
<p>Toujours en option, certaines peuvent être désignées comme permettant de <b>classer les données</b>: un 1ᵉʳ clic lance un classement par ordre croissant, le 2ᵉ pour ordre décroissant et ainsi de suite.</p> <p>Certains champs (=colonnes) peuvent être désignées comme devant permettre de <b>classer les données</b>: un 1ᵉʳ clic lance un classement par ordre croissant, le 2ᵉ pour ordre décroissant et ainsi de suite.</p>
<p>Il est également possible d'afficher le nombre total de résultats, avec prise en compte des éventuels filtres.</p> <p>Il est également possible d'afficher le nombre total de résultats, avec prise en compte des éventuels filtres.</p>
<p>Enfin, une dernière option permet de <b>paginer les résultats</b>.</p> <p>Une autre option permet de <b>paginer les résultats</b>.</p>
<p>Enfin il est possible d'<b>afficher les données autrement que sous forme de tableau HTML</b>. Si vous affichez cette page sur un écran large de moins de 800 px, les données seront simplement listées.<br>Si vous avez un grand écran, visualiser le résultat en faisant "Alt+"Maj"+"M", puis "F5". Appuyez de nouveau sur "F5", une fois de retour sur grand écran.</p>
<p>Design basé sur <a href="https://www.getpapercss.com" target="_blank">PaperCSS</a></p></aside> <p>Design basé sur <a href="https://www.getpapercss.com" target="_blank">PaperCSS</a></p></aside>
</body> </body>
</html> </html>

View File

@ -12,6 +12,7 @@ module.exports =
parserDatasNotFound : "Aucune donnée n'a été trouvée.", parserDatasNotFound : "Aucune donnée n'a été trouvée.",
parserFail: "La lecture des données du fichier a échoué.", parserFail: "La lecture des données du fichier a échoué.",
parserNeedUrl: "Merci de fournir une url valide pour le fichier à parser.", parserNeedUrl: "Merci de fournir une url valide pour le fichier à parser.",
renderNeedDatas: "Il ne peut y avoir de pagination, si les données n'ont pas été récupérées.",
selector2HTMLFail: "Le création d'un filtre dans le DOM nécessite l'initialisation de l'élément HTML et du numéro du champs à filter.", selector2HTMLFail: "Le création d'un filtre dans le DOM nécessite l'initialisation de l'élément HTML et du numéro du champs à filter.",
selectorCheckIsOkFail: "Le test est lancé sur un filtre incorrectement initialisé ou sur un attribut absent de la donnée à tester.", selectorCheckIsOkFail: "Le test est lancé sur un filtre incorrectement initialisé ou sur un attribut absent de la donnée à tester.",
selectorFieldNotFound: "Au moins un des champs devant servir à filtrer les données n'existe pas dans le fichier.", selectorFieldNotFound: "Au moins un des champs devant servir à filtrer les données n'existe pas dans le fichier.",

View File

@ -1,4 +1,4 @@
import { FreeDatas2HTML, Pagination, Selector, SortingField } from "./freeDatas2HTML"; import { FreeDatas2HTML, Pagination, Render, Selector, SortingField } from "./freeDatas2HTML";
const initialise = async () => const initialise = async () =>
{ {
@ -24,6 +24,21 @@ const initialise = async () =>
converter.datasSourceUrl="http://localhost:8080/datas/elements-chimiques.csv"; converter.datasSourceUrl="http://localhost:8080/datas/elements-chimiques.csv";
await converter.parse(); await converter.parse();
converter.datasSortingFunctions=[{ datasFieldNb:4, sort:mySort }]; converter.datasSortingFunctions=[{ datasFieldNb:4, sort:mySort }];
// Adaptation du rendu
if(window.innerWidth < 800)
{
converter.datasRender.settings={
allBegining:"<h3>Affichage petits écrans !</h3>",
allEnding:"",
linesBegining:"<ul>",
linesEnding:"</ul>",
lineBegining:"<li><ul>",
lineEnding:"</ul></li>",
dataDisplaying:"<li><b>#FIELDNAME :</b> #VALUE</li>",
};
}
else
converter.datasRender.settings.allBegining="<table class='table-hover'>";
// Configuration de la pagination // Configuration de la pagination
const pagination=new Pagination(converter, { id:"pages" }, "Page à afficher :"); const pagination=new Pagination(converter, { id:"pages" }, "Page à afficher :");
@ -31,7 +46,7 @@ const initialise = async () =>
pagination.selectedValue=10; pagination.selectedValue=10;
converter.pagination=pagination; converter.pagination=pagination;
pagination.rend2HTML(); pagination.rend2HTML();
// Affichage initial // Affichage initial
converter.datasCounter={ id:"compteur" }; converter.datasCounter={ id:"compteur" };
await converter.run(); await converter.run();
@ -46,22 +61,21 @@ const initialise = async () =>
filtre3.selector2HTML(); filtre3.selector2HTML();
// + Injection des filtres dans le convertisseur // + Injection des filtres dans le convertisseur
converter.datasSelectors=[filtre1,filtre2,filtre3]; converter.datasSelectors=[filtre1,filtre2,filtre3];
// Ajout de champs permettant de classer les données // Ajout de champs permettant de classer les données
let sortingField1=new SortingField(converter, 0); // Uniquement avec un rendu tableau (grand écran), car entêtes de colonne nécessaires
//sortingField1.datasFieldNb=0; if(window.innerWidth >= 800)
sortingField1.field2HTML(); {
let sortingField2=new SortingField(converter, 1); let sortingField1=new SortingField(converter, 0);
// sortingField2.datasFieldNb=1; sortingField1.field2HTML();
sortingField2.field2HTML(); let sortingField2=new SortingField(converter, 1);
let sortingField3=new SortingField(converter, 2); sortingField2.field2HTML();
//sortingField3.datasFieldNb=2; let sortingField3=new SortingField(converter, 2);
sortingField3.field2HTML(); sortingField3.field2HTML();
let sortingField4=new SortingField(converter, 4); let sortingField4=new SortingField(converter, 4);
//sortingField4.datasFieldNb=4; sortingField4.field2HTML();
sortingField4.field2HTML(); converter.datasSortingFields=[sortingField1,sortingField2,sortingField3,sortingField4];
// Injection dans le convertisseur }
converter.datasSortingFields=[sortingField1,sortingField2,sortingField3,sortingField4];
} }
catch(e) catch(e)
{ {

View File

@ -2,8 +2,9 @@ const Papa = require("papaparse");
const errors = require("./errors.js"); const errors = require("./errors.js");
const { compare }= require('natural-orderby'); const { compare }= require('natural-orderby');
import { Counter, DOMElement, Paginations, Selectors, SortingFields, SortingFunctions } from "./freeDatas2HTMLInterfaces"; import { Counter, DatasRenders, DOMElement, Paginations, Selectors, SortingFields, SortingFunctions } from "./freeDatas2HTMLInterfaces";
import { Pagination} from "./freeDatas2HTMLPagination"; import { Pagination} from "./freeDatas2HTMLPagination";
import { Render} from "./freeDatas2HTMLRender";
import { Selector } from "./freeDatas2HTMLSelector"; import { Selector } from "./freeDatas2HTMLSelector";
import { SortingField } from "./freeDatas2HTMLSortingField"; import { SortingField } from "./freeDatas2HTMLSortingField";
@ -11,10 +12,12 @@ import { PapaParseDatas, PapaParseErrors, PapaParseMeta } from "./papaParseInter
export class FreeDatas2HTML export class FreeDatas2HTML
{ {
// L'élément HTML où doivent être affichées les données : // L'élément HTML où afficher les données. Laisser à undefined si non affichées :
private _datasViewElt: DOMElement = { id:"", eltDOM:undefined }; private _datasViewElt: DOMElement|undefined = undefined;
// Le code HTML résultant (utile ?) : // Le moteur de rendu pour préparer l'affichage des données
public datasHTML: string = ""; public datasRender: DatasRenders;
// Le code HTML résultant :
public datasHTML: string = "";
// L'url où accéder aux données : // L'url où accéder aux données :
private _datasSourceUrl: string = ""; private _datasSourceUrl: string = "";
@ -40,6 +43,12 @@ export class FreeDatas2HTML
// Affichage du nombre total de lignes de données (optionnel) : // Affichage du nombre total de lignes de données (optionnel) :
private _datasCounter: Counter = {}; private _datasCounter: Counter = {};
// J'initialiser avec des valeurs par défaut pouvant être surchargées par les setters
constructor()
{
this.datasRender=new Render(this);
}
// Vérifie s'il y a bien un élément dans le DOM pour l'id fourni // Vérifie s'il y a bien un élément dans le DOM pour l'id fourni
public static checkInDOMById(checkedElt: DOMElement) : DOMElement public static checkInDOMById(checkedElt: DOMElement) : DOMElement
{ {
@ -160,8 +169,6 @@ export class FreeDatas2HTML
// Lance FreeDatas2HTML suivant les données reçues : // Lance FreeDatas2HTML suivant les données reçues :
public async run(): Promise<any> public async run(): Promise<any>
{ {
if (this._datasViewElt.eltDOM === undefined)
throw new Error(errors.converterNeedDatasElt);
if(this._datasSourceUrl === "" ) if(this._datasSourceUrl === "" )
throw new Error(errors.parserNeedUrl); throw new Error(errors.parserNeedUrl);
@ -181,10 +188,14 @@ export class FreeDatas2HTML
refreshView() : void refreshView() : void
{ {
if(this.parseMetas === undefined || this.parseMetas.fields === undefined || this._datasViewElt.eltDOM === undefined) if(this.parseMetas === undefined || this.parseMetas.fields === undefined)
throw new Error(errors.converterRefreshFail); throw new Error(errors.converterRefreshFail);
this.datasHTML=this.createDatasHTML(this.parseMetas.fields, this.parseDatas);
this._datasViewElt.eltDOM.innerHTML=this.datasHTML; this.datasHTML=this.createDatas2Display(this.parseMetas.fields, this.parseDatas);
if(this._datasViewElt !== undefined && this._datasViewElt.eltDOM !== undefined)
this._datasViewElt.eltDOM.innerHTML=this.datasHTML;
// On réactive les éventuels champs de classement // On réactive les éventuels champs de classement
for(let i in this.datasSortingFields) for(let i in this.datasSortingFields)
{ {
@ -193,7 +204,7 @@ export class FreeDatas2HTML
} }
} }
createDatasHTML(fields: string[], datas: any[]) : string createDatas2Display(fields: string[], datas: any[]) : string
{ {
// Dois-je classer les données par rapport à un champ ? // Dois-je classer les données par rapport à un champ ?
if(this.datasSortedField !== undefined && this.datasSortedField.datasFieldNb !==undefined) if(this.datasSortedField !== undefined && this.datasSortedField.datasFieldNb !==undefined)
@ -216,11 +227,8 @@ export class FreeDatas2HTML
firstData=this.pagination.selectedValue*(this.pagination.pages.selectedValue-1); firstData=this.pagination.selectedValue*(this.pagination.pages.selectedValue-1);
let maxData = (this.pagination !== undefined && this.pagination.selectedValue !== undefined) ? this.pagination.selectedValue : datas.length+1; let maxData = (this.pagination !== undefined && this.pagination.selectedValue !== undefined) ? this.pagination.selectedValue : datas.length+1;
// Création du tableau de données : // Création du tableau des données à afficher :
let datasHTML="<table><thead>"; const datas2Display=[];
for (let i in fields)
datasHTML+="<th>"+fields[i]+"</th>";
datasHTML+="</thead><tbody>";
let nbVisible=0, nbTotal=0; let nbVisible=0, nbTotal=0;
for (let row in datas) for (let row in datas)
{ {
@ -236,21 +244,13 @@ export class FreeDatas2HTML
} }
if(visible && nbTotal >= firstData && nbVisible < maxData) if(visible && nbTotal >= firstData && nbVisible < maxData)
{ {
datasHTML+="<tr>"; datas2Display.push(datas[row]);
for(let field in datas[row])
{
// Attention : si les erreurs papaParse ne sont pas bloquantes, il peut y avoir des données en trop, avec comme nom de colonne : "__parsed_extra"
if(fields.indexOf(field) !== -1)
datasHTML+="<td>"+datas[row][field]+"</td>";
}
datasHTML+="</tr>";
nbVisible++; nbVisible++;
nbTotal++; nbTotal++;
} }
else if(visible) else if(visible)
nbTotal++; nbTotal++;
} }
datasHTML+="</tbody></table>";
if(this._datasCounter !== undefined && this._datasCounter.displayElement !== undefined) if(this._datasCounter !== undefined && this._datasCounter.displayElement !== undefined)
{ {
this._datasCounter.value=nbTotal; this._datasCounter.value=nbTotal;
@ -259,11 +259,12 @@ export class FreeDatas2HTML
// Tout réaffichage peut entraîner une modification du nombre de pages (évolution filtres, etc.) // Tout réaffichage peut entraîner une modification du nombre de pages (évolution filtres, etc.)
if(this.pagination !== undefined) if(this.pagination !== undefined)
this.pagination.creaPageSelector(nbTotal); this.pagination.creaPageSelector(nbTotal);
return datasHTML; return this.datasRender.rend2HTML(datas2Display);
} }
} }
// Permet l'appel des dépendances via un seul script // Permet l'appel des dépendances via un seul script
export { Pagination } from "./freeDatas2HTMLPagination"; export { Pagination } from "./freeDatas2HTMLPagination";
export { Render} from "./freeDatas2HTMLRender";
export { Selector } from "./freeDatas2HTMLSelector"; export { Selector } from "./freeDatas2HTMLSelector";
export { SortingField } from "./freeDatas2HTMLSortingField"; export { SortingField } from "./freeDatas2HTMLSortingField";

View File

@ -3,6 +3,24 @@ export interface Counter
displayElement?: DOMElement; // peut être undefined si on ne souhaite pas d'affichage automatique dans la page displayElement?: DOMElement; // peut être undefined si on ne souhaite pas d'affichage automatique dans la page
value?: number; // undefined jusqu'à recevoir sa première valeur value?: number; // undefined jusqu'à recevoir sa première valeur
} }
export interface DatasRenders
{
rend2HTML(datas: any[]): string;
settings: DatasRendersSettings;
}
export interface DatasRendersSettings
{
allBegining: string;
allEnding: string;
fieldsBegining?: string;
fieldsEnding?: string;
fieldDisplaying?: string;
linesBegining: string;
linesEnding: string;
lineBegining: string;
lineEnding: string;
dataDisplaying: string;
}
export interface DOMElement export interface DOMElement
{ {
id: string; id: string;

View File

@ -0,0 +1,61 @@
const errors = require("./errors.js");
import { DatasRenders, DatasRendersSettings } from "./freeDatas2HTMLInterfaces";
import { FreeDatas2HTML } from "./freeDatas2HTML";
export class Render implements DatasRenders
{
private _converter: FreeDatas2HTML;
public settings: DatasRendersSettings;
static readonly defaultSettings =
{
allBegining:"<table>",
allEnding:"</table>",
fieldsBegining:"<thead><tr>",
fieldsEnding:"</tr><thead>",
fieldDisplaying:"<th>#FIELDNAME</th>",
linesBegining:"<tbody>",
linesEnding:"</tbody>",
lineBegining:"<tr>",
lineEnding:"</tr>",
dataDisplaying:"<td>#VALUE</td>",
};
// Injection de la classe principale
constructor(converter: FreeDatas2HTML, settings:DatasRendersSettings=Render.defaultSettings)
{
this._converter=converter;
this.settings=settings;
}
// Reçoit les données à afficher et créer le HTML correspondant
public rend2HTML(datas: any[]) : string
{
if(this._converter.parseMetas === undefined || this._converter.parseMetas.fields === undefined)
throw new Error(errors.renderNeedDatas);
else
{
let datasHTML=this.settings.allBegining;
// On ne souhaite pas nécessairement afficher les noms de colonne
if(this.settings.fieldsBegining !== undefined && this.settings.fieldDisplaying !== undefined)
{
datasHTML+=this.settings.fieldsBegining;
for (let i in this._converter.parseMetas!.fields)
datasHTML+=this.settings.fieldDisplaying.replace("#FIELDNAME", this._converter.parseMetas.fields[Number(i)]);
datasHTML+=this.settings.fieldsEnding;
}
datasHTML+=this.settings.linesBegining;
for (let row in datas)
{
datasHTML+=this.settings.lineBegining;
for(let field in datas[row])
{
if(this._converter.parseMetas.fields.indexOf(field) !== -1)
datasHTML+=this.settings.dataDisplaying.replace("#VALUE" , datas[row][field]).replace("#FIELDNAME" , field);
}
datasHTML+=this.settings.lineEnding;
}
datasHTML+=this.settings.linesEnding+this.settings.allEnding;
return datasHTML;
}
}
}