246 lines
10 KiB
TypeScript
246 lines
10 KiB
TypeScript
const { compare }=require('natural-orderby');
|
|
const errors=require("./errors.js");
|
|
|
|
import { Counter, Datas, DatasRenders, DOMElement, Paginations, Parsers, ParseErrors, RemoteSource, Selectors, SortingFields, SortingFunctions } from "./freeDatas2HTMLInterfaces";
|
|
import { Pagination} from "./freeDatas2HTMLPagination";
|
|
import { ParserForCSV} from "./freeDatas2HTMLParserForCSV";
|
|
import { ParserForHTML} from "./freeDatas2HTMLParserForHTML";
|
|
import { ParserForJSON} from "./freeDatas2HTMLParserForJSON";
|
|
import { Render} from "./freeDatas2HTMLRender";
|
|
import { Selector } from "./freeDatas2HTMLSelector";
|
|
import { SortingField } from "./freeDatas2HTMLSortingField";
|
|
|
|
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 = "";
|
|
// Le parseur :
|
|
public parser: Parsers; // public pour permettre de charger un parseur tiers après instanciation
|
|
|
|
// Données distantes :
|
|
//private _datasRemoteSource: RemoteSource|undefined=undefined;
|
|
// Ou locales :
|
|
//private _datas2Parse:string|undefined=undefined;
|
|
// Dans tous les cas, besoin d'un type :
|
|
public datasType: "CSV"|"HTML"|"JSON"|undefined;
|
|
|
|
// Le nom des champs trouvés dans les données :
|
|
public fields: string[]|undefined=undefined;
|
|
// Les données à proprement parler :
|
|
public datas: {[index: string]:any}[]=[];
|
|
// Les erreurs rencontrées durant le traitement des données reçues :
|
|
public parseErrors: ParseErrors[]|undefined;
|
|
// Doit-on tout arrêter si une erreur est rencontrée durant le traitement ?
|
|
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;
|
|
// Éventuelle 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
|
|
// Attention, si je transmets datasRemoteSource ici, il ne passera pas par un new RemoteSources()
|
|
// Il doit donc déjà avoir été testé
|
|
constructor(datasType:"CSV"|"HTML"|"JSON", datas2Parse="", datasRemoteSource?:RemoteSource)
|
|
{
|
|
this.datasRender=new Render(this);
|
|
switch (datasType)
|
|
{
|
|
case "CSV":
|
|
this.parser=new ParserForCSV(datasRemoteSource);
|
|
break;
|
|
case "HTML":
|
|
this.parser=new ParserForHTML(datasRemoteSource);
|
|
break;
|
|
case "JSON":
|
|
this.parser=new ParserForJSON(datasRemoteSource);
|
|
break;
|
|
}
|
|
if(datas2Parse !== "")
|
|
this.parser.datas2Parse=datas2Parse;
|
|
}
|
|
|
|
// 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.fields === undefined || this.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 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 checkedFunction of SortingFunctions)
|
|
{
|
|
if(! this.checkFieldExist(checkedFunction.datasFieldNb))
|
|
throw new Error(errors.converterFieldNotFound);
|
|
else
|
|
this._datasSortingFunctions.push(checkedFunction);
|
|
}
|
|
}
|
|
|
|
// 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 checkedFunction of this._datasSortingFunctions)
|
|
{
|
|
if(checkedFunction.datasFieldNb === datasFieldNb)
|
|
return checkedFunction;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// Traite les données fournies via le parseur adhoc
|
|
// Si un élément du DOM est fourni, appelle la fonction affichant les données
|
|
public async run(): Promise<any>
|
|
{
|
|
await this.parser.parse();
|
|
if(this.parser.parseResults === undefined)
|
|
throw new Error(errors.parserFail);
|
|
else
|
|
{
|
|
if(this.parser.parseResults.fields === undefined)
|
|
throw new Error(errors.parserDatasNotFound);
|
|
else if(this.stopIfParseErrors && this.parser.parseResults.errors !== undefined)
|
|
throw new Error(errors.parserMeetErrors);
|
|
else
|
|
{
|
|
// revoir l'intérêt de copier ces 3 attributs ?
|
|
this.fields=this.parser.parseResults.fields;
|
|
this.datas=this.parser.parseResults.datas;
|
|
this.parseErrors=this.parser.parseResults.errors;
|
|
if(this._datasViewElt !== undefined)
|
|
this.refreshView();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public refreshView() : void
|
|
{
|
|
if(this.fields === undefined || this._datasViewElt === undefined || this._datasViewElt.eltDOM === undefined)
|
|
throw new Error(errors.converterRefreshFail);
|
|
else
|
|
{
|
|
this.datasHTML=this.createDatas2Display(this.fields, this.datas);
|
|
this._datasViewElt.eltDOM.innerHTML=this.datasHTML;
|
|
// On réactive les éventuels champs de classement qui ont été écrasés
|
|
for(let field of this.datasSortingFields)
|
|
field.field2HTML();
|
|
}
|
|
}
|
|
|
|
private 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";
|