235 lines
9.5 KiB
TypeScript
235 lines
9.5 KiB
TypeScript
const Papa = require("papaparse");
|
|
const errors = require("./errors.js");
|
|
const { compare }= require('natural-orderby');
|
|
|
|
import { DOMElement, Paginations, Selectors, SortingFields, SortingFunctions } from "./freeDatas2HTMLInterfaces";
|
|
import { Pagination} from "./freeDatas2HTMLPagination";
|
|
import { Selector } from "./freeDatas2HTMLSelector";
|
|
import { SortingField } from "./freeDatas2HTMLSortingField";
|
|
|
|
import { PapaParseDatas, PapaParseErrors, PapaParseMeta } from "./papaParseInterfaces";
|
|
|
|
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'url où accéder aux données :
|
|
private _datasSourceUrl: string = "";
|
|
|
|
// Les fonctions spécifiques de classement pour certains champs :
|
|
private _datasSortingFunctions: SortingFunctions[] = [];
|
|
|
|
// Le nom des champs (interfaces à renommer, car PapaParse = cas particulier) :
|
|
public parseMetas: PapaParseMeta|undefined = undefined;
|
|
// Les données à proprement parler :
|
|
public parseDatas: PapaParseDatas[] = [];
|
|
// Les erreurs rencontrées durant le parsage :
|
|
public parseErrors: PapaParseErrors[] = [];
|
|
// Doit-on tout arrêter si une erreur est rencontrée durant la parsage ?
|
|
public stopIfParseErrors: boolean = false;
|
|
|
|
// Les filtres possible sur certains champs
|
|
datasSelectors: Selectors[] = [];
|
|
|
|
// Les champs pouvant être classés
|
|
datasSortingFields: SortingFields[] = [];
|
|
// La dernier champ pour lequel le classement a été demandé
|
|
datasSortedField: SortingFields|undefined;
|
|
|
|
// La Pagination :
|
|
pagination: Paginations|undefined;
|
|
|
|
// Vérifie que l'élément devant afficher les données existe dans le DOM :
|
|
set datasViewElt(elt: DOMElement)
|
|
{
|
|
let checkContainerExist=document.getElementById(elt.id);
|
|
if(checkContainerExist === null)
|
|
throw new Error(errors.elementNotFound+elt.id);
|
|
else
|
|
{
|
|
this._datasViewElt.id=elt.id;
|
|
this._datasViewElt.eltDOM=checkContainerExist;
|
|
}
|
|
}
|
|
|
|
// Vérifie que l'url où chercher les données n'est pas vide : inutile si données dans page ou tranmises
|
|
set datasSourceUrl(url: string)
|
|
{
|
|
if(url.trim().length === 0)
|
|
throw new Error(errors.needUrl);
|
|
else
|
|
this._datasSourceUrl=url.trim();
|
|
}
|
|
|
|
// Vérifie que les numéros de champs pour lesquels il y a des fonctions de classement spécifiques sont cohérents
|
|
set datasSortingFunctions(SortingFunctions: SortingFunctions[])
|
|
{
|
|
this._datasSortingFunctions=[];
|
|
for(let i = 0; i < SortingFunctions.length; i++)
|
|
{
|
|
//if(FreeDatas2HTML.isNaturalNumber(SortingFunctions[i].datasFieldNb) === false) // revoir pour tester l'existence du champ
|
|
// console.error(errors.needNaturalNumber); // -> à remplacer par un test d'existence de colonne !
|
|
//else
|
|
this._datasSortingFunctions.push(SortingFunctions[i]);
|
|
}
|
|
}
|
|
|
|
// Retourne l'éventuelle fonction spécifique de classement associée à un champ
|
|
public getSortingFunctionForField(datasFieldNb: number): SortingFunctions|undefined
|
|
{
|
|
for(let i in this._datasSortingFunctions)
|
|
{
|
|
if(this._datasSortingFunctions[i].datasFieldNb === datasFieldNb)
|
|
return this._datasSortingFunctions[i];
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// Parse des données distantes (url) fournies en CSV :
|
|
public async parse(): Promise<any>
|
|
{
|
|
const converter=this;
|
|
return new Promise((resolve,reject) =>
|
|
{
|
|
if(converter._datasSourceUrl !== "" )
|
|
{
|
|
Papa.parse(converter._datasSourceUrl,
|
|
{
|
|
quoteChar: '"',
|
|
header: true,
|
|
complete: function(results :any)
|
|
{
|
|
converter.parseErrors=results.errors;
|
|
converter.parseDatas=results.data;
|
|
// Attention, papaParse peut accepter un nom de colonne vide
|
|
let realFields: string[]=[];
|
|
for(let i in results.meta.fields)
|
|
{
|
|
if(results.meta.fields[i].trim() !== "")
|
|
realFields.push(results.meta.fields[i]);
|
|
}
|
|
results.meta.fields=realFields;
|
|
converter.parseMetas=results.meta;
|
|
resolve(true);
|
|
},
|
|
error:function(error :any)
|
|
{
|
|
reject(new Error(errors.parserFail));
|
|
},
|
|
download: true,
|
|
skipEmptyLines: true,
|
|
});
|
|
}
|
|
else
|
|
reject(new Error(errors.needUrl));
|
|
});
|
|
}
|
|
|
|
// Lance FreeDatas2HTML suivant les données reçues :
|
|
public async run(): Promise<any>
|
|
{
|
|
if (this._datasViewElt.eltDOM === undefined)
|
|
throw new Error(errors.needDatasElt);
|
|
if(this._datasSourceUrl === "" )
|
|
throw new Error(errors.needUrl);
|
|
|
|
await this.parse();
|
|
|
|
if(this.parseDatas.length === 0 || this.parseMetas!.fields === undefined) // je force avec "!", car l'existence de parseMetas est certaine après parse().
|
|
throw new Error(errors.datasNotFound);
|
|
else if(this.stopIfParseErrors && this.parseErrors.length!==0)
|
|
console.error(this.parseErrors);
|
|
else
|
|
{
|
|
// Si tout est ok, affichage initial de toutes les données du fichier
|
|
this.refreshView();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
refreshView() : void
|
|
{
|
|
if(this.parseMetas === undefined || this.parseMetas.fields === undefined || this._datasViewElt.eltDOM === undefined)
|
|
throw new Error(errors.refreshFail);
|
|
this.datasHTML=this.createDatasHTML(this.parseMetas.fields, this.parseDatas);
|
|
this._datasViewElt.eltDOM.innerHTML=this.datasHTML;
|
|
// On réactive les éventuels champs de classement
|
|
for(let i in this.datasSortingFields)
|
|
{
|
|
let field=this.datasSortingFields[i];
|
|
field.field2HTML();
|
|
}
|
|
}
|
|
|
|
createDatasHTML(fields: string[], datas: any[]) : string
|
|
{
|
|
// Dois-je classer les données par rapport à un champ ?
|
|
if(this.datasSortedField !== undefined && this.datasSortedField.datasFieldNb !==undefined)
|
|
{
|
|
const field=fields[this.datasSortedField.datasFieldNb];
|
|
const fieldOrder=this.datasSortedField.order;
|
|
// Une fonction spécifique de classement a-t-elle été définie ?
|
|
if(this.getSortingFunctionForField(this.datasSortedField.datasFieldNb) !== undefined)
|
|
{
|
|
let myFunction=this.getSortingFunctionForField(this.datasSortedField.datasFieldNb);
|
|
datas.sort( (a, b) => { return myFunction!.sort(a[field], b[field], fieldOrder); });
|
|
}
|
|
else
|
|
datas.sort( (a, b) => compare( {order: fieldOrder} )(a[field], b[field]));
|
|
}
|
|
|
|
// Dois-je prendre en compte une pagination ?
|
|
let firstData=0;
|
|
if (this.pagination !== undefined && this.pagination.selectedValue !== undefined && this.pagination.pages !== undefined && this.pagination.pages.selectedValue !== undefined)
|
|
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>";
|
|
let nbVisible=0, nbTotal=0;
|
|
for (let row in datas)
|
|
{
|
|
let visible=true;
|
|
if(this.datasSelectors.length !== 0)
|
|
{
|
|
let i=0;
|
|
while(this.datasSelectors[i] !== undefined && visible===true)
|
|
{
|
|
visible=this.datasSelectors[i].dataIsOk(datas[row]);
|
|
i++;
|
|
}
|
|
}
|
|
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>";
|
|
nbVisible++;
|
|
nbTotal++;
|
|
}
|
|
else if(visible)
|
|
nbTotal++;
|
|
}
|
|
datasHTML+="</tbody></table>";
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// Permet l'appel des dépendances via un seul script
|
|
export { Pagination } from "./freeDatas2HTMLPagination";
|
|
export { Selector } from "./freeDatas2HTMLSelector";
|
|
export { SortingField } from "./freeDatas2HTMLSortingField";
|