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",
"version": "0.4.6",
"version": "0.5.0",
"description": "Visualization of data from various sources (CSV, API, HTML...) with filters, classification, pagination, etc.",
"main": "index.js",
"scripts": {

View File

@ -25,10 +25,11 @@
</article>
<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>
<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>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>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>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>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>
</body>
</html>

View File

@ -12,6 +12,7 @@ module.exports =
parserDatasNotFound : "Aucune donnée n'a été trouvée.",
parserFail: "La lecture des données du fichier a échoué.",
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.",
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.",

View File

@ -1,4 +1,4 @@
import { FreeDatas2HTML, Pagination, Selector, SortingField } from "./freeDatas2HTML";
import { FreeDatas2HTML, Pagination, Render, Selector, SortingField } from "./freeDatas2HTML";
const initialise = async () =>
{
@ -24,6 +24,21 @@ const initialise = async () =>
converter.datasSourceUrl="http://localhost:8080/datas/elements-chimiques.csv";
await converter.parse();
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
const pagination=new Pagination(converter, { id:"pages" }, "Page à afficher :");
@ -31,7 +46,7 @@ const initialise = async () =>
pagination.selectedValue=10;
converter.pagination=pagination;
pagination.rend2HTML();
// Affichage initial
converter.datasCounter={ id:"compteur" };
await converter.run();
@ -46,22 +61,21 @@ const initialise = async () =>
filtre3.selector2HTML();
// + Injection des filtres dans le convertisseur
converter.datasSelectors=[filtre1,filtre2,filtre3];
// Ajout de champs permettant de classer les données
let sortingField1=new SortingField(converter, 0);
//sortingField1.datasFieldNb=0;
sortingField1.field2HTML();
let sortingField2=new SortingField(converter, 1);
// sortingField2.datasFieldNb=1;
sortingField2.field2HTML();
let sortingField3=new SortingField(converter, 2);
//sortingField3.datasFieldNb=2;
sortingField3.field2HTML();
let sortingField4=new SortingField(converter, 4);
//sortingField4.datasFieldNb=4;
sortingField4.field2HTML();
// Injection dans le convertisseur
converter.datasSortingFields=[sortingField1,sortingField2,sortingField3,sortingField4];
// Uniquement avec un rendu tableau (grand écran), car entêtes de colonne nécessaires
if(window.innerWidth >= 800)
{
let sortingField1=new SortingField(converter, 0);
sortingField1.field2HTML();
let sortingField2=new SortingField(converter, 1);
sortingField2.field2HTML();
let sortingField3=new SortingField(converter, 2);
sortingField3.field2HTML();
let sortingField4=new SortingField(converter, 4);
sortingField4.field2HTML();
converter.datasSortingFields=[sortingField1,sortingField2,sortingField3,sortingField4];
}
}
catch(e)
{

View File

@ -2,8 +2,9 @@ const Papa = require("papaparse");
const errors = require("./errors.js");
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 { Render} from "./freeDatas2HTMLRender";
import { Selector } from "./freeDatas2HTMLSelector";
import { SortingField } from "./freeDatas2HTMLSortingField";
@ -11,10 +12,12 @@ import { PapaParseDatas, PapaParseErrors, PapaParseMeta } from "./papaParseInter
export class FreeDatas2HTML
{
// L'élément HTML où doivent être affichées les données :
private _datasViewElt: DOMElement = { id:"", eltDOM:undefined };
// Le code HTML résultant (utile ?) :
public datasHTML: string = "";
// L'élément HTML où afficher les données. Laisser à undefined si non affichées :
private _datasViewElt: DOMElement|undefined = undefined;
// Le moteur de rendu pour préparer l'affichage des données
public datasRender: DatasRenders;
// Le code HTML résultant :
public datasHTML: string = "";
// L'url où accéder aux données :
private _datasSourceUrl: string = "";
@ -40,6 +43,12 @@ export class FreeDatas2HTML
// Affichage du nombre total de lignes de données (optionnel) :
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
public static checkInDOMById(checkedElt: DOMElement) : DOMElement
{
@ -160,8 +169,6 @@ export class FreeDatas2HTML
// Lance FreeDatas2HTML suivant les données reçues :
public async run(): Promise<any>
{
if (this._datasViewElt.eltDOM === undefined)
throw new Error(errors.converterNeedDatasElt);
if(this._datasSourceUrl === "" )
throw new Error(errors.parserNeedUrl);
@ -181,10 +188,14 @@ export class FreeDatas2HTML
refreshView() : void
{
if(this.parseMetas === undefined || this.parseMetas.fields === undefined || this._datasViewElt.eltDOM === undefined)
throw new Error(errors.converterRefreshFail);
this.datasHTML=this.createDatasHTML(this.parseMetas.fields, this.parseDatas);
this._datasViewElt.eltDOM.innerHTML=this.datasHTML;
if(this.parseMetas === undefined || this.parseMetas.fields === undefined)
throw new Error(errors.converterRefreshFail);
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
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 ?
if(this.datasSortedField !== undefined && this.datasSortedField.datasFieldNb !==undefined)
@ -216,11 +227,8 @@ export class FreeDatas2HTML
firstData=this.pagination.selectedValue*(this.pagination.pages.selectedValue-1);
let maxData = (this.pagination !== undefined && this.pagination.selectedValue !== undefined) ? this.pagination.selectedValue : datas.length+1;
// Création du tableau de données :
let datasHTML="<table><thead>";
for (let i in fields)
datasHTML+="<th>"+fields[i]+"</th>";
datasHTML+="</thead><tbody>";
// Création du tableau des données à afficher :
const datas2Display=[];
let nbVisible=0, nbTotal=0;
for (let row in datas)
{
@ -236,21 +244,13 @@ export class FreeDatas2HTML
}
if(visible && nbTotal >= firstData && nbVisible < maxData)
{
datasHTML+="<tr>";
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>";
datas2Display.push(datas[row]);
nbVisible++;
nbTotal++;
}
else if(visible)
nbTotal++;
}
datasHTML+="</tbody></table>";
if(this._datasCounter !== undefined && this._datasCounter.displayElement !== undefined)
{
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.)
if(this.pagination !== undefined)
this.pagination.creaPageSelector(nbTotal);
return datasHTML;
return this.datasRender.rend2HTML(datas2Display);
}
}
// 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 { 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
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
{
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;
}
}
}