311 lines
11 KiB
TypeScript
311 lines
11 KiB
TypeScript
const { compare }=require("natural-orderby");
|
|
const errors=require("./errors.js");
|
|
|
|
import { DatasRenders, DOMElement, Filters, Paginations, Parsers, ParseResults, RemoteSources, SortingFields, SortingFunctions } from "./interfaces";
|
|
import { Pagination} from "./Pagination";
|
|
import { ParserForCSV} from "./ParserForCSV";
|
|
import { ParserForHTML} from "./ParserForHTML";
|
|
import { ParserForJSON} from "./ParserForJSON";
|
|
import { Render} from "./Render";
|
|
import { Selector } from "./Selector";
|
|
import { SortingField } from "./SortingField";
|
|
|
|
export class FreeDatas2HTML
|
|
{
|
|
// Les paramètres de base :
|
|
private _datasViewElt: DOMElement|undefined=undefined;
|
|
public datasRender: DatasRenders;
|
|
public parser: Parsers;
|
|
public stopIfParseErrors: boolean=false;
|
|
|
|
// Les options (classement, pagination, filtres...) :
|
|
private _datasCounterElt: DOMElement|undefined=undefined;
|
|
private _datasSortingFunctions: SortingFunctions[]=[];
|
|
private _fields2Rend: number[]=[];
|
|
public datasFilters: Filters[]=[];
|
|
public datasSortingFields: SortingFields[]=[];
|
|
public datasSortedField: SortingFields|undefined;
|
|
public pagination: Paginations|undefined;
|
|
|
|
// Les résultats :
|
|
private _fields: ParseResults["fields"]=[];
|
|
private _datas: ParseResults["datas"]=[];
|
|
private _datas2Rend: {[index: string]:string}[]=[];
|
|
private _nbDatasValid: number=0;
|
|
|
|
// Le parseur, comme le render sont initialisés, mais peuvent être modifiés par des instances d'autres classes respectant leur interface.
|
|
constructor(datasFormat:"CSV"|"HTML"|"JSON", datas2Parse="", datasRemoteSource?:RemoteSources)
|
|
{
|
|
this.datasRender=new Render();
|
|
switch (datasFormat)
|
|
{
|
|
case "CSV":
|
|
this.parser=new ParserForCSV();
|
|
break;
|
|
case "HTML":
|
|
this.parser=new ParserForHTML();
|
|
break;
|
|
case "JSON":
|
|
this.parser=new ParserForJSON();
|
|
break;
|
|
}
|
|
if(datas2Parse.trim() !== "")
|
|
this.parser.datas2Parse=datas2Parse.trim();
|
|
else if(datasRemoteSource !== undefined)
|
|
this.parser.setRemoteSource(datasRemoteSource);
|
|
}
|
|
|
|
// Vérifie s'il y a bien un élément dans le DOM pour l'id fourni.
|
|
// Fonction statique également utilisée par les autres classes.
|
|
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;
|
|
}
|
|
}
|
|
|
|
set datasViewElt(elt: DOMElement)
|
|
{
|
|
this._datasViewElt=FreeDatas2HTML.checkInDOMById(elt);
|
|
}
|
|
|
|
set datasCounterElt(counterDisplayElement: DOMElement)
|
|
{
|
|
this._datasCounterElt=FreeDatas2HTML.checkInDOMById(counterDisplayElement);
|
|
}
|
|
|
|
get datas(): ParseResults["datas"]
|
|
{
|
|
return this._datas;
|
|
}
|
|
|
|
get fields(): ParseResults["fields"]
|
|
{
|
|
return this._fields;
|
|
}
|
|
|
|
get datas2Rend(): {[index: string]:string}[]
|
|
{
|
|
return this._datas2Rend;
|
|
}
|
|
|
|
get nbDatasValid(): number
|
|
{
|
|
return this._nbDatasValid;
|
|
}
|
|
|
|
get fields2Rend() : number[]
|
|
{
|
|
return this._fields2Rend;
|
|
}
|
|
|
|
// Vérifie qu'un champ existe bien dans les données parsées.
|
|
// Utilisée par les autres classes.
|
|
public checkFieldExist(nb: number) : boolean
|
|
{
|
|
if(this.parser.parseResults === undefined || this.parser.parseResults.fields[nb] === undefined)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
public realFields2Rend() : string[]
|
|
{
|
|
if(this._fields2Rend.length === 0)
|
|
return this._fields;
|
|
else
|
|
{
|
|
const realFields=[];
|
|
for(let i=0; i < this._fields.length; i++)
|
|
{
|
|
if(this._fields2Rend.indexOf(i) !== -1)
|
|
realFields.push(this._fields[i]);
|
|
}
|
|
return realFields;
|
|
}
|
|
}
|
|
|
|
// Vérifie qu'un champ faire partie de ceux à afficher.
|
|
public checkField2Rend(nb: number) : boolean
|
|
{
|
|
if(this.realFields2Rend()[nb] === undefined)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
// Vérifie que les numéros de champs pour lesquels il y a des fonctions de classement spécifiques sont cohérents.
|
|
// ! Ne peut donc être utilisé qu'après avoir parsé les données.
|
|
set datasSortingFunctions(SortingFunctions: SortingFunctions[])
|
|
{
|
|
this._datasSortingFunctions=[];
|
|
for(let checkedFunction of SortingFunctions)
|
|
{
|
|
if(! this.checkFieldExist(checkedFunction.datasFieldNb))
|
|
throw new Error(errors.converterFieldNotFound);
|
|
else
|
|
this._datasSortingFunctions.push(checkedFunction);
|
|
}
|
|
}
|
|
|
|
// Vérifie que tous les numéros de champs à afficher sont valides
|
|
// Un tableau vide signifie que tous les champs parsés seront affichés.
|
|
set fields2Rend(fields: number[])
|
|
{
|
|
if(fields.length === 0)
|
|
this._fields2Rend=fields;
|
|
else
|
|
{
|
|
for(let field of fields)
|
|
{
|
|
if(! this.checkFieldExist(field))
|
|
throw new Error(errors.converterFieldNotFound);
|
|
else
|
|
this._fields2Rend.push(field);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Retourne l'éventuelle fonction spécifique de classement associée à un champ
|
|
public getSortingFunctionForField(datasFieldNb: number): SortingFunctions|undefined
|
|
{
|
|
for(let checkedFunction of this._datasSortingFunctions)
|
|
{
|
|
if(checkedFunction.datasFieldNb === datasFieldNb)
|
|
return checkedFunction;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// Lancer le parsage des données et lance éventuellement un 1er affichage.
|
|
public async run(): Promise<any>
|
|
{
|
|
await this.parser.parse();
|
|
if(this.parser.parseResults === undefined) // mais le parseur devrait lui-même générer une erreur avant
|
|
throw new Error(errors.parserFail);
|
|
else
|
|
{
|
|
if(this.stopIfParseErrors && this.parser.parseResults.errors !== undefined)
|
|
throw new Error(errors.parserMeetErrors);
|
|
else
|
|
{
|
|
this._fields=this.parser.parseResults.fields;
|
|
this._datas=this.parser.parseResults.datas;
|
|
// Les champs ne bougeront plus, donc on peut déjà les passer au moteur de rendu.
|
|
// Mais en prenant les comptes les éventuels champs à ne pas afficher
|
|
if(this._fields2Rend.length === 0)
|
|
this.datasRender.fields=this._fields;
|
|
else
|
|
this.datasRender.fields=this.realFields2Rend();
|
|
if(this._datasViewElt !== undefined)
|
|
this.refreshView();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Actualise l'affichage des données.
|
|
// Méthode également appelée par les autres classes.
|
|
public refreshView() : void
|
|
{
|
|
if(this._fields.length === 0 || this._datasViewElt === undefined)
|
|
throw new Error(errors.converterRefreshFail);
|
|
else
|
|
{
|
|
this._datas2Rend=this.datas2HTML();
|
|
this.datasRender.datas= this._datas2Rend;
|
|
this._datasViewElt.eltDOM!.innerHTML=this.datasRender.rend2HTML(); // "!", car l'existence de "eltDOM" est testée par le setter.
|
|
|
|
// Actualisation de l'éventuel compteur :
|
|
if(this._datasCounterElt !== undefined)
|
|
this._datasCounterElt.eltDOM!.innerHTML=""+this._nbDatasValid; // même remarque pour le "!".
|
|
|
|
// Réactivation des éventuels champs de classement qui ont pu être écrasés :
|
|
for(let field of this.datasSortingFields)
|
|
field.field2HTML();
|
|
|
|
// Tout réaffichage peut entraîner une modification du nombre de pages (évolution filtres, etc.)
|
|
if(this.pagination !== undefined)
|
|
this.pagination.pages2HTML();
|
|
}
|
|
}
|
|
|
|
// Fonction sélectionnant les données à afficher en prenant en compte les éventuels filtres, la pagination, etc.
|
|
public datas2HTML() : {[index: string]:string}[]
|
|
{
|
|
// Dois-je classer les données par rapport à un champ ?
|
|
if(this.datasSortedField !== undefined)
|
|
{
|
|
const field=this._fields[this.datasSortedField.datasFieldNb];
|
|
const fieldOrder=this.datasSortedField.order;
|
|
// Une fonction spécifique de classement a-t-elle été définie pour ce champ ?
|
|
if(this.getSortingFunctionForField(this.datasSortedField.datasFieldNb) !== undefined)
|
|
{
|
|
const myFunction=this.getSortingFunctionForField(this.datasSortedField.datasFieldNb);
|
|
this._datas.sort( (a, b) => { return myFunction!.sort(a[field], b[field], fieldOrder); });
|
|
}
|
|
else
|
|
this._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 : this._datas.length;
|
|
|
|
// Création du tableau des données à afficher :
|
|
let datas2Display=[];
|
|
let nbVisible=0, nbTotal=0;
|
|
for (let row in this._datas)
|
|
{
|
|
// Pour être affichée une ligne doit valider tous les filtres connus
|
|
let valid=true, i=0;
|
|
while(this.datasFilters[i] !== undefined && valid === true)
|
|
{
|
|
valid=this.datasFilters[i].dataIsOk(this._datas[row]);
|
|
i++;
|
|
}
|
|
if(valid && nbTotal >= firstData && nbVisible < maxData)
|
|
{
|
|
datas2Display.push(this._datas[row]);
|
|
nbVisible++;
|
|
nbTotal++;
|
|
}
|
|
else if(valid)
|
|
nbTotal++;
|
|
}
|
|
this._nbDatasValid=nbTotal;
|
|
|
|
// Tous les champs doivent-ils être affichés ?
|
|
// Ne pas enlever les champs cachés plus tôt, car ils peuvent servir à filtrer les données
|
|
if(this._fields2Rend.length !== 0)
|
|
{
|
|
let newDatas2Display=[];
|
|
for(let row in datas2Display)
|
|
{
|
|
let i=0, newData: {[index: string]: string} = {};
|
|
for(let field in datas2Display[row])
|
|
{
|
|
if(this._fields2Rend.indexOf(i) !== -1)
|
|
newData[field]=datas2Display[row][field];
|
|
i++;
|
|
}
|
|
newDatas2Display.push(newData);
|
|
}
|
|
datas2Display=newDatas2Display;
|
|
}
|
|
return datas2Display;
|
|
}
|
|
}
|
|
|
|
// Permet l'appel des principales classes du module via un seul script :
|
|
export { Pagination } from "./Pagination";
|
|
export { Render} from "./Render";
|
|
export { Selector } from "./Selector";
|
|
export { SortingField } from "./SortingField";
|