const { compare }=require('natural-orderby'); const errors=require("./errors.js"); import { Counter, Datas, DatasRenders, DOMElement, Paginations, Parsers, ParseErrors, RemoteSources, Selectors, 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 { // 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 // Type de données à traiter 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]:string}[]=[]; // 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?:RemoteSources) { this.datasRender=new Render(); 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 { 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; // Les champs ne bougeront plus donc on peut aussi les passer au moteur de rendu this.datasRender.fields=this.fields; 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.pages2HTML(nbTotal); this.datasRender.datas=datas2Display; return this.datasRender.rend2HTML(); } } // Permet l'appel des dépendances via un seul script export { Pagination } from "./Pagination"; export { Render} from "./Render"; export { Selector } from "./Selector"; export { SortingField } from "./SortingField";