diff --git a/package.json b/package.json index 119ba76..41f0018 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freedatas2html", - "version": "1.2.1", + "version": "1.3.0", "description": "Conversion and display of data in different formats (CSV, JSON, HTML) with the possibility of filtering, classifying and paginating the results.", "main": "index.js", "scripts": { @@ -48,4 +48,4 @@ "natural-orderby": "^2.0.3", "papaparse": "^5.3.1" } -} +} \ No newline at end of file diff --git a/src/build/extensions/MixedFieldsRender.js b/src/build/extensions/MixedFieldsRender.js new file mode 100644 index 0000000..072b9dd --- /dev/null +++ b/src/build/extensions/MixedFieldsRender.js @@ -0,0 +1,68 @@ +var errors = require("../errors.js"); +var MixedFieldsRender = (function () { + function MixedFieldsRender(settings) { + this._fields = []; + this.fieldRenders = []; + this.datas = []; + this.settings = settings; + } + Object.defineProperty(MixedFieldsRender.prototype, "fields", { + get: function () { + return this._fields; + }, + set: function (fields) { + if (fields.length === 0) + throw new Error(errors.renderNeedFields); + else + this._fields = fields; + }, + enumerable: true, + configurable: true + }); + MixedFieldsRender.prototype.rend2HTML = function () { + if (this._fields.length === 0) + throw new Error(errors.renderNeedFields); + else { + var datasHTML = this.settings.allBegining; + if (this.settings.fieldsNamesDisplaying !== undefined) { + var fieldsNamesRend = this.settings.fieldsNamesDisplaying; + for (var i = 0; i < this._fields.length; i++) + fieldsNamesRend = fieldsNamesRend.replace("##" + i + "##", this._fields[i]); + datasHTML += fieldsNamesRend; + } + if (this.settings.linesBegining !== undefined) + datasHTML += this.settings.linesBegining; + for (var _i = 0, _a = this.datas; _i < _a.length; _i++) { + var row = _a[_i]; + var lineRend = this.settings.datasLinesDisplaying; + var _loop_1 = function (i) { + var currentField = this_1._fields[i]; + if (row[currentField] !== undefined) { + var rowRend = row[currentField]; + var render = this_1.fieldRenders.find(function (item) { + return item.name === currentField; + }); + if (render !== undefined) + rowRend = render.rend2HTML(row); + lineRend = lineRend.replace(new RegExp("##" + i + "##", "g"), rowRend); + } + else + lineRend = lineRend.replace(new RegExp("##" + i + "##", "g"), ""); + }; + var this_1 = this; + for (var i = 0; i < this._fields.length; i++) { + _loop_1(i); + } + datasHTML += lineRend; + } + if (this.settings.linesEnding !== undefined) + datasHTML += this.settings.linesEnding; + datasHTML += this.settings.allEnding; + return datasHTML; + } + }; + return MixedFieldsRender; +}()); +export { MixedFieldsRender }; +export { FreeDatas2HTML } from "../FreeDatas2HTML"; +export { errors }; diff --git a/src/extensions/MixedFieldsRender.ts b/src/extensions/MixedFieldsRender.ts new file mode 100644 index 0000000..3e47157 --- /dev/null +++ b/src/extensions/MixedFieldsRender.ts @@ -0,0 +1,109 @@ +// Alternative à Rendern pour des intégrations plus complexes. +// Le contenu des divers champs peut ici être mixé à l'affichage ou encore affiché plusieurs fois... + +import { DatasRenders } from "../interfaces"; +const errors=require("../errors.js"); + +interface DatasRendersSettings +{ + allBegining: string; + allEnding: string; + linesBegining?: string, + linesEnding?: string, + fieldsNamesDisplaying?: string; // ##n## sera remplacé par le nom du nième champ, etc. + datasLinesDisplaying: string;// ##n## sera remplacé par la valeur du nième champ, etc. +} +interface FieldRender +{ + name: string; // nom du champ concerné. + rend2HTML(lineValues: {[index: string]:string} ) : string; // fonction spécifique appelée avant d'injecter la valeur de ce champ dans datasLinesDisplaying +} + +export class MixedFieldsRender implements DatasRenders +{ + private _fields: string[]=[]; + public settings: DatasRendersSettings; + public fieldRenders: FieldRender[]=[]; + public datas: {[index: string]:string}[]=[]; + + constructor(settings: DatasRendersSettings) + { + this.settings=settings; + } + + // Les données fournies au Render peuvent être vides du fait de l'action des filtres ou encore de la pagination... + // Mais les noms des champ doivent être fournis. + set fields(fields: string[]) + { + if(fields.length === 0) + throw new Error(errors.renderNeedFields); + else + this._fields=fields; + } + + get fields() : string[] + { + return this._fields; + } + + public rend2HTML() : string + { + if(this._fields.length === 0) + throw new Error(errors.renderNeedFields); + else + { + let datasHTML=this.settings.allBegining; + + // Création d'une éventuelle entête : + if(this.settings.fieldsNamesDisplaying !== undefined) + { + // Dans le cas de champs mixés, tous les champs utilisés ne sont pas forcément listés en entête. + // Il suffit alors qu'ils soient absent de fieldsNamesDisplaying. + let fieldsNamesRend=this.settings.fieldsNamesDisplaying; + for(let i=0; i< this._fields.length; i++) + fieldsNamesRend=fieldsNamesRend.replace("##"+i+"##", this._fields[i]); + datasHTML+=fieldsNamesRend; + } + + if(this.settings.linesBegining !== undefined) + datasHTML+=this.settings.linesBegining; + + // Suivant les objets/lignes, les champs peuvent se trouver dans un ordre différent. + // Ou encore certains peuvent manquer, être en trop... + // Mais tous les champs présents dans "fields" doivent être traités en respectant leur ordre pour être cohérent avec l'éventuelle entête créée ci-dessus. + for(let row of this.datas) + { + // Idem que l'entête : tous les champs ne sont pas forcément présents dans datasLinesDisplaying. + let lineRend=this.settings.datasLinesDisplaying; + for(let i=0; i< this._fields.length; i++) + { + const currentField=this._fields[i]; + if(row[currentField] !== undefined) // champ renseigné pour cet enregistrement + { + // Par défaut, l'emplacement dans datasLinesDisplaying sera remplacé par la valeur renseignée : + let rowRend=row[currentField]; + // Sauf si une fonction de pré-traitement a été fournie pour ce champ : + const render = this.fieldRenders.find(function(item) + { + return item.name === currentField; + }); + if(render !== undefined) + rowRend=render.rend2HTML(row); + lineRend=lineRend.replace(new RegExp("##"+i+"##", "g"), rowRend); // RegExp, car bug TS avec replaceAll (parce que target = es5 ?). + } + else // si ce champ n'est pas renseigné, il faut tout de même supprimer l'indicateur de son emplacement : + lineRend=lineRend.replace(new RegExp("##"+i+"##", "g"), ""); + } + datasHTML+=lineRend; + } + if(this.settings.linesEnding !== undefined) + datasHTML+=this.settings.linesEnding; + datasHTML+=this.settings.allEnding; + return datasHTML; + } + } +} + +// Utile au script de tests : +export { FreeDatas2HTML } from "../FreeDatas2HTML"; +export { errors }; \ No newline at end of file diff --git a/tests/extensions/mixedFieldsRenderSpec.ts b/tests/extensions/mixedFieldsRenderSpec.ts new file mode 100644 index 0000000..52ad44f --- /dev/null +++ b/tests/extensions/mixedFieldsRenderSpec.ts @@ -0,0 +1,96 @@ +import { errors, FreeDatas2HTML, MixedFieldsRender } from "../../src/extensions/MixedFieldsRender"; +const fixtures=require("../fixtures.js"); + +describe("Test du moteur de rendu alternatif avec contenus mixables, etc.", () => +{ + let render: MixedFieldsRender; + const fields=[ "Z", "Élément", "Symbole", "Famille" ] ; + // Les champs des différentes lignes ne sont pas forcément dans le même ordre et certains peuvent être absents, vides ou encore en trop... + const datas=[{"Z":"1","Élément":"Hydrogène","Symbole":"H","Famille":"Non-métal"},{"Famille":"Gaz noble","Élément":"Hélium","Z":"2","Symbole":"He"},{"Champ ignoré":"Je me champ ignoré !", "Z":"3","Élément":"Lithium","Famille":"Métal alcalin","Symbole":"Li"},{"Z":"4","Élément":"Béryllium","Famille":"","Champ ignoré":"Je me champ ignoré !"}] ; + let renderSettings= + { + allBegining: "
", + allEnding: "
", + linesBegining: "", + fieldsNamesDisplaying: "

##0## | ##1## | ##3## | ##2##

", + datasLinesDisplaying: "
  • ##0## | ##1## | ##3## | ##2##
  • " + }; + + beforeEach(() => + { + render=new MixedFieldsRender(renderSettings); + }); + + it("Doit générer une erreur, si une liste de champs vide lui est fournie.", () => + { + expect(() => { return render.fields=[]; } ).toThrowError(errors.renderNeedFields); + expect(render.fields).toEqual([]); + }); + + it("Doit accepter toute liste de champs valide.", () => + { + render=new MixedFieldsRender(renderSettings); + expect(() => { return render.fields=[""]; }).not.toThrowError(); // pas normal, mais pas testé par cette classe + expect(render.fields).toEqual([""]); + expect(() => { return render.fields=["je vois double", "je vois double"]; }).not.toThrowError(); // idem + expect(render.fields).toEqual(["je vois double", "je vois double"]); + expect(() => { return render.fields=["je me sens seul"]; }).not.toThrowError(); + expect(render.fields).toEqual(["je me sens seul"]); + }); + + it("Doit générer une erreur, si lancé sans avoir fourni une liste des champs.", () => + { + expect(() => { return render.rend2HTML(); }).toThrowError(errors.renderNeedFields); + }); + + it("Ne doit pas générer d'erreur, si lancé avec une liste des champs, même s'il n'y a aucune donnée fournie.", () => + { + render.fields=fields; + expect(() => { return render.rend2HTML(); }).not.toThrowError(); + }); + + it("Doit retourner un code HTML correspondant à la configuration fournie, avec ou sans données à afficher.", () => + { + render.fields=fields; + let html=render.rend2HTML(); + expect(html).toEqual("

    Z | Élément | Famille | Symbole

    "); + // Avec des données : + render.datas=datas; + html=render.rend2HTML(); + expect(html).toEqual("

    Z | Élément | Famille | Symbole

    "); + }); + + it("Doit retourner un code HTML prenant en compte les fonctions spécifiques d'affichage, ou encore les champs affichés plusieurs fois.", () => + { + const newRenderSettings= + { + allBegining: "
    ", + allEnding: "
    ", + linesBegining: "", + fieldsNamesDisplaying: "

    ##0## | ##1## | ##3## | ##2##

    ", + datasLinesDisplaying: "
  • ##0## | ##1## (##0##) | ##3## | ##2##
  • " + }; + const newRender=new MixedFieldsRender(newRenderSettings); + const rendFamille2HTML=(values: {[index: string]:string} ) : string => + { + if(values["Famille"] !=="") + return values["Famille"]; + else + return "Non renseigné"; // doit apparaître car "Famille" est vide mais existant pour le Béryllium. + }; + const rendSymbol2HTML=(values: {[index: string]:string} ) : string => + { + if(values["Symbole"] !=="") + return values["Symbole"]; + else + return "Inconnu"; // ne doit pas apparaître, car "Symbole" n'existe pas du tout par le Béryllium, donc rendSymbol2HTML() ne doit pas être appelée. + }; + newRender.fieldRenders=[{ name:"Famille", rend2HTML: rendFamille2HTML}, { name:"Symbole", rend2HTML: rendSymbol2HTML}]; + newRender.fields=fields; + newRender.datas=datas; + let html=newRender.rend2HTML(); + expect(html).toEqual("

    Z | Élément | Famille | Symbole

    "); + }); +}); \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 242a744..67de072 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -8,6 +8,7 @@ module.exports = exampleWithCSV: "./src/demo/exampleWithCSV.ts", exampleWithHTML: "./src/demo/exampleWithHTML.ts", exampleWithJSON: "./src/demo/exampleWithJSON.ts", + exampleWithMixedFields: "./src/demo/exampleWithMixedFields.ts", exampleWithUL: "./src/demo/exampleWithUL.ts", exampleWithUserFile: "./src/demo/exampleWithUserFile.ts" },