Ajout d'un moteur de rendu alternatif pour des affichages de données plus complexes.

This commit is contained in:
Fabrice PENHOËT 2022-02-02 15:45:27 +01:00
parent 3d6c973ec4
commit 2f3f5df255
5 changed files with 276 additions and 2 deletions

View File

@ -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": {

View File

@ -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 };

View File

@ -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 };

View File

@ -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: "<div>",
allEnding: "</div>",
linesBegining: "<ul>",
linesEnding: "</ul>",
fieldsNamesDisplaying: "<h3>##0## | ##1## | ##3## | ##2##</h3>",
datasLinesDisplaying: "<li>##0## | ##1## | ##3## | ##2##</li>"
};
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("<div><h3>Z | Élément | Famille | Symbole</h3><ul></ul></div>");
// Avec des données :
render.datas=datas;
html=render.rend2HTML();
expect(html).toEqual("<div><h3>Z | Élément | Famille | Symbole</h3><ul><li>1 | Hydrogène | Non-métal | H</li><li>2 | Hélium | Gaz noble | He</li><li>3 | Lithium | Métal alcalin | Li</li><li>4 | Béryllium | | </li></ul></div>");
});
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: "<div>",
allEnding: "</div>",
linesBegining: "<ul>",
linesEnding: "</ul>",
fieldsNamesDisplaying: "<h3>##0## | ##1## | ##3## | ##2##</h3>",
datasLinesDisplaying: "<li>##0## | ##1## (##0##) | ##3## | ##2##</li>"
};
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("<div><h3>Z | Élément | Famille | Symbole</h3><ul><li>1 | Hydrogène (1) | Non-métal | H</li><li>2 | Hélium (2) | Gaz noble | He</li><li>3 | Lithium (3) | Métal alcalin | Li</li><li>4 | Béryllium (4) | Non renseigné | </li></ul></div>");
});
});

View File

@ -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"
},