Ajout possibilité de fournir des fonctions spécifiques pour classer les données de certaines colonnes.

This commit is contained in:
Fabrice PENHOËT 2021-09-06 17:25:30 +02:00
parent 75c9efc3b1
commit 66ea27b0fb
6 changed files with 85 additions and 16 deletions

View File

@ -18,9 +18,9 @@ De même l'idée est de rester libre du rendu des données en n'imposant pas de
La première version se contente de récupérer et parser des données présentes dans un fichier CSV via un appel Ajax. La première version se contente de récupérer et parser des données présentes dans un fichier CSV via un appel Ajax.
Les données trouvées sont affichées dans un tableau. En option, des colonnes peuvent être indiquées par filtrer les données et/ou les classer. Les données trouvées sont affichées dans un tableau. En option, des colonnes peuvent être indiquées par filtrer les données et/ou les classer.
Il est possible de fournir des fonctions spécifiques pour classer les données de certaines colonnes.
Il reste à ajouter : Il reste à ajouter :
- la possibilité de fournir des fonctions spécifiques pour classer les données de certaines colonnes.
- la possibilité de paginer les données. - la possibilité de paginer les données.
- la possibilité d'utiliser des sources/formats différents qu'un fichier CSV pour extraire les données. - la possibilité d'utiliser des sources/formats différents qu'un fichier CSV pour extraire les données.
- la possibilité de spécifier un code HTML autre qu'un tableau pour lister les données. - la possibilité de spécifier un code HTML autre qu'un tableau pour lister les données.

View File

@ -1,6 +1,6 @@
{ {
"name": "freedatas2html", "name": "freedatas2html",
"version": "0.3.5", "version": "0.3.6",
"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

@ -7,7 +7,20 @@ const initialise = async () =>
let converter=new freeDatas2HTML(); let converter=new freeDatas2HTML();
converter.datasViewElt={ id:"datas" }; converter.datasViewElt={ id:"datas" };
converter.datasSelectors=[{ datasFieldNb:3, id:"filtre1"}, { datasFieldNb:4, id:"filtre2"},{ datasFieldNb:5, id:"filtre3", separator:"," }]; converter.datasSelectors=[{ datasFieldNb:3, id:"filtre1"}, { datasFieldNb:4, id:"filtre2"},{ datasFieldNb:5, id:"filtre3", separator:"," }];
converter.datasSortingColumns=[{ datasFieldNb:0 }, { datasFieldNb:1 },{ datasFieldNb:2 }]; const mySort = (a: any, b: any, order: "asc"|"desc" = "asc") =>
{
const values = [ "> 100000", "> 1 et < 100 000", "≤ 1", "Traces", "Inexistant"];
if(order === "desc")
values.reverse();
if(values.indexOf(a) > values.indexOf(b))
return -1;
else if(values.indexOf(a) < values.indexOf(b))
return 1;
else
return 0;
};
converter.datasSortingColumns=[{ datasFieldNb:0 }, { datasFieldNb:1 },{ datasFieldNb:2 }, { datasFieldNb:4 }];
converter.datasSortingFunctions= [{ datasFieldNb:4, sort:mySort}];
converter.datasSourceUrl="http://localhost:8080/datas/elements-chimiques.csv"; converter.datasSourceUrl="http://localhost:8080/datas/elements-chimiques.csv";
await converter.run(); await converter.run();
} }

View File

@ -8,18 +8,20 @@ import { domElement, selectors, sortingColumns, sortingFunctions } from "./free
export class freeDatas2HTML export class freeDatas2HTML
{ {
private _datasViewElt: domElement = { id:"", eltDOM:undefined }; private _datasViewElt: domElement = { id:"", eltDOM:undefined };
private _datasSourceUrl: string = ""; public datasHTML: string = "";
// Revoir car tous les attributs suivants sont liés aux colonnes/fields des données (créer une classe ?)
private _datasSelectors: selectors[] = []; private _datasSelectors: selectors[] = [];
private _datasSortingColumns: sortingColumns[] = []; private _datasSortingColumns: sortingColumns[] = [];
private _datasSortedColumn: sortingColumns|undefined; private _datasSortedColumn: sortingColumns|undefined;
private _datasSortingFunctions: sortingFunctions[] = [];
// Parseur fichier :
private _datasSourceUrl: string = "";
public parseMeta: papaParseMeta|undefined = undefined; public parseMeta: papaParseMeta|undefined = undefined;
public parseDatas: papaParseDatas[] = []; public parseDatas: papaParseDatas[] = [];
public parseErrors: papaParseErrors[] = []; public parseErrors: papaParseErrors[] = [];
public datasHTML: string = "";
public stopIfParseErrors: boolean = false; public stopIfParseErrors: boolean = false;
public static isNaturalNumber(nb) public static isNaturalNumber(nb: number)
{ {
return (Number.isInteger(nb) === false || nb < 0) ? false : true; return (Number.isInteger(nb) === false || nb < 0) ? false : true;
} }
@ -84,12 +86,36 @@ export class freeDatas2HTML
} }
} }
} }
get datasSortingColumns() : sortingColumns[] get datasSortingColumns() : sortingColumns[]
{ {
return this._datasSortingColumns; return this._datasSortingColumns;
} }
// Attention : une fonction de classement peut aussi bien servir à une colonne triable, qu'à une colonne servant à filtrer les données
set datasSortingFunctions(sortingFunctions: sortingFunctions[])
{
this._datasSortingFunctions=[];
for(let i = 0; i < sortingFunctions.length; i++)
{
if(freeDatas2HTML.isNaturalNumber(sortingFunctions[i].datasFieldNb) === false)
console.error(errors.needNaturalNumber);
else
this._datasSortingFunctions.push(sortingFunctions[i]);
}
}
// Retourne la fonction spécifique de classement associée à une colonne
public getSortingFunctionForField(datasFieldNb: number) : sortingFunctions|undefined
{
for(let i in this._datasSortingFunctions)
{
if(this._datasSortingFunctions[i].datasFieldNb === datasFieldNb)
return this._datasSortingFunctions[i];
}
return undefined;
}
public async parse(): Promise<any> public async parse(): Promise<any>
{ {
const converter=this; const converter=this;
@ -172,7 +198,6 @@ export class freeDatas2HTML
let checkedValues=this.parseDatas[row][colName].split(this._datasSelectors[i].separator as string); let checkedValues=this.parseDatas[row][colName].split(this._datasSelectors[i].separator as string);
for(let i in checkedValues) for(let i in checkedValues)
{ {
let checkedValue=checkedValues[i].trim(); let checkedValue=checkedValues[i].trim();
if(checkedValue !== "" && values.indexOf(checkedValue) === -1) if(checkedValue !== "" && values.indexOf(checkedValue) === -1)
values.push(checkedValue); values.push(checkedValue);
@ -181,7 +206,10 @@ export class freeDatas2HTML
} }
if(values.length > 0) if(values.length > 0)
{ {
values.sort(compare()); if(this.getSortingFunctionForField(this._datasSelectors[i].datasFieldNb) !== undefined)
values.sort(this.getSortingFunctionForField(this._datasSelectors[i].datasFieldNb)!.sort);
else
values.sort(compare());
this._datasSelectors[i].name=colName; this._datasSelectors[i].name=colName;
this._datasSelectors[i].values=values; this._datasSelectors[i].values=values;
selectorsHTML[i]="<label for='freeDatas2HTMLSelector"+i+"'>"+colName+" : </label><select name='freeDatas2HTMLSelector"+i+"' id='freeDatas2HTMLSelector"+i+"'><option value='0'>----</option>"; selectorsHTML[i]="<label for='freeDatas2HTMLSelector"+i+"'>"+colName+" : </label><select name='freeDatas2HTMLSelector"+i+"' id='freeDatas2HTMLSelector"+i+"'><option value='0'>----</option>";
@ -220,7 +248,7 @@ export class freeDatas2HTML
{ {
const converter=this; const converter=this;
this._datasViewElt.eltDOM.innerHTML=this.datasHTML; this._datasViewElt.eltDOM.innerHTML=this.datasHTML;
// Ici car il faut que la tableau soit déjà dans le DOM pour écouter les clics // Ici, car il faut que le tableau soit déjà dans le DOM pour "mettre sous écoute" les clics
if(this._datasSortingColumns.length > 0) if(this._datasSortingColumns.length > 0)
{ {
let getTableTh=document.querySelectorAll("table th"); let getTableTh=document.querySelectorAll("table th");
@ -233,7 +261,7 @@ export class freeDatas2HTML
htmlContent="<a href='#freeDatas2HTMLSorting"+datasFieldNb+"' id='freeDatas2HTMLSorting"+datasFieldNb+"'>"+htmlContent+"</a>"; htmlContent="<a href='#freeDatas2HTMLSorting"+datasFieldNb+"' id='freeDatas2HTMLSorting"+datasFieldNb+"'>"+htmlContent+"</a>";
getTableTh[datasFieldNb].innerHTML=htmlContent; getTableTh[datasFieldNb].innerHTML=htmlContent;
let sortingElement=document.getElementById("freeDatas2HTMLSorting"+datasFieldNb); let sortingElement=document.getElementById("freeDatas2HTMLSorting"+datasFieldNb);
sortingElement!.addEventListener('click', function(e) sortingElement!.addEventListener("click", function(e)
{ {
e.preventDefault(); e.preventDefault();
let order=converter.datasSortingColumns[i].order ; let order=converter.datasSortingColumns[i].order ;
@ -268,7 +296,14 @@ export class freeDatas2HTML
{ {
const col=fields[this._datasSortedColumn.datasFieldNb]; const col=fields[this._datasSortedColumn.datasFieldNb];
const colOrder=this._datasSortedColumn.order; const colOrder=this._datasSortedColumn.order;
datas.sort( (a, b) => compare( {order: colOrder} )(a[col], b[col])); // Une fonction spécifique de classement a-t-elle été définie ?
if(this.getSortingFunctionForField(this._datasSortedColumn.datasFieldNb) !== undefined)
{
let myFunction=this.getSortingFunctionForField(this._datasSortedColumn.datasFieldNb);
datas.sort( (a, b) => { return myFunction!.sort(a[col], b[col], colOrder); });
}
else
datas.sort( (a, b) => compare( {order: colOrder} )(a[col], b[col]));
} }
// Création du tableau de données : // Création du tableau de données :

View File

@ -14,4 +14,9 @@ export interface sortingColumns
{ {
datasFieldNb: number; datasFieldNb: number;
order?: "asc"|"desc"|undefined; order?: "asc"|"desc"|undefined;
}
export interface sortingFunctions
{
datasFieldNb: number;
sort(a: any,b: any, order?: "asc"|"desc"): number; // cf. https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
} }

View File

@ -41,7 +41,6 @@ describe("freeDatas2HTML", () =>
expect(converter.datasSelectors[0].id).toEqual("selector2"); expect(converter.datasSelectors[0].id).toEqual("selector2");
}); });
// Utile pour tester les valeurs fournies pour les numéros de colonne
it("Doit retourner un booléen indiquant si un nombre est naturel ou non.", () => it("Doit retourner un booléen indiquant si un nombre est naturel ou non.", () =>
{ {
expect(freeDatas2HTML.isNaturalNumber(-1)).toBeFalse(); expect(freeDatas2HTML.isNaturalNumber(-1)).toBeFalse();
@ -66,6 +65,23 @@ describe("freeDatas2HTML", () =>
{ {
expect(() => { return converter.datasSourceUrl=" "; }).toThrowError(errors.needUrl); expect(() => { return converter.datasSourceUrl=" "; }).toThrowError(errors.needUrl);
}); });
it("Doit me retourner la fonction associée à une colonne, de manière à ce qu'elle soit utilisable pour comparer deux valeurs.", () =>
{
// Fonction volontairement basique, car ce n'est pas la fonction que l'on teste ici, mais le fait que l'on puisse l'utiliser !
const simpleSort = (a: any, b: any) =>
{
if(a < b)
return 1;
else if(a > b)
return -1;
else
return 0;
};
converter.datasSortingFunctions=[{ datasFieldNb:0, sort:simpleSort }];
expect(converter.getSortingFunctionForField(0)).toBeDefined();
expect([7,9,3,5].sort(converter.getSortingFunctionForField(0).sort)).toEqual([9,7,5,3]);
});
}); });
describe("Parsage du fichier et création du tableau de données", () => describe("Parsage du fichier et création du tableau de données", () =>