FreeDatas2HTML/src/freeDatas2HTML.ts

270 lines
11 KiB
TypeScript

const Papa = require("papaparse");
const errors = require("./errors.js");
const { compare }= require('natural-orderby');
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";
import { PapaParseDatas, PapaParseErrors, PapaParseMeta } from "./papaParseInterfaces";
export class FreeDatas2HTML
{
// 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 = "";
// 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 fonctions spécifiques de classement pour certains champs :
private _datasSortingFunctions: SortingFunctions[] = [];
// 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;
// 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
{
let searchEltInDOM=document.getElementById(checkedElt.id);
if(searchEltInDOM === null)
throw new Error(errors.converterElementNotFound+checkedElt.id);
else
{
checkedElt.eltDOM=searchEltInDOM;
return checkedElt;
}
}
// Vérifie qu'un champ existe bien dans les données
public checkFieldExist(nb: number) : boolean
{
if(this.parseMetas === undefined || this.parseMetas.fields === undefined || this.parseMetas.fields[nb] === undefined)
return false;
else
return true;
}
// Vérifie que l'élément devant afficher les données existe dans le DOM :
set datasViewElt(elt: DOMElement)
{
this._datasViewElt=FreeDatas2HTML.checkInDOMById(elt);
}
// 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.parserNeedUrl);
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
// ! Ne peut être testé qu'après avoir reçu les données
set datasSortingFunctions(SortingFunctions: SortingFunctions[])
{
this._datasSortingFunctions=[];
for(let i = 0; i < SortingFunctions.length; i++)
{
if(! this.checkFieldExist(SortingFunctions[i].datasFieldNb))
throw new Error(errors.converterFieldNotFound);
else
this._datasSortingFunctions.push(SortingFunctions[i]);
}
}
// On teste l'id de l'élément du DOM où afficher le compteur s'il est fourni
set datasCounter(counterDisplayElement: DOMElement)
{
this._datasCounter={ displayElement: FreeDatas2HTML.checkInDOMById(counterDisplayElement), value: undefined };
}
// Retourne la valeur du compteur de lignes (sans l'élément DOM)
public getDatasCounterValue(): number|undefined
{
if(this._datasCounter !== undefined && this._datasCounter.value != undefined)
return this._datasCounter.value;
else
return undefined;
}
// 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.parserNeedUrl));
});
}
// Lance FreeDatas2HTML suivant les données reçues :
public async run(): Promise<any>
{
if(this._datasSourceUrl === "" )
throw new Error(errors.parserNeedUrl);
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.parserDatasNotFound);
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)
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)
{
let field=this.datasSortingFields[i];
field.field2HTML();
}
}
createDatas2Display(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 des données à afficher :
const datas2Display=[];
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)
{
datas2Display.push(datas[row]);
nbVisible++;
nbTotal++;
}
else if(visible)
nbTotal++;
}
if(this._datasCounter !== undefined && this._datasCounter.displayElement !== undefined)
{
this._datasCounter.value=nbTotal;
this._datasCounter.displayElement.eltDOM!.innerHTML=""+nbTotal; // eltDOM ne peut être undefined (cf setter)
}
// Tout réaffichage peut entraîner une modification du nombre de pages (évolution filtres, etc.)
if(this.pagination !== undefined)
this.pagination.creaPageSelector(nbTotal);
return this.datasRender.rend2HTML(datas2Display);
}
}
// Permet l'appel des dépendances via un seul script
export { Pagination } from "./freeDatas2HTMLPagination";
export { Render} from "./freeDatas2HTMLRender";
export { Selector } from "./freeDatas2HTMLSelector";
export { SortingField } from "./freeDatas2HTMLSortingField";