Ajout d'un moteur de rendu alternatif pour des affichages de données plus complexes.
This commit is contained in:
parent
3d6c973ec4
commit
2f3f5df255
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "freedatas2html",
|
"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.",
|
"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",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
68
src/build/extensions/MixedFieldsRender.js
Normal file
68
src/build/extensions/MixedFieldsRender.js
Normal 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 };
|
109
src/extensions/MixedFieldsRender.ts
Normal file
109
src/extensions/MixedFieldsRender.ts
Normal 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 };
|
96
tests/extensions/mixedFieldsRenderSpec.ts
Normal file
96
tests/extensions/mixedFieldsRenderSpec.ts
Normal 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>");
|
||||||
|
});
|
||||||
|
});
|
@ -8,6 +8,7 @@ module.exports =
|
|||||||
exampleWithCSV: "./src/demo/exampleWithCSV.ts",
|
exampleWithCSV: "./src/demo/exampleWithCSV.ts",
|
||||||
exampleWithHTML: "./src/demo/exampleWithHTML.ts",
|
exampleWithHTML: "./src/demo/exampleWithHTML.ts",
|
||||||
exampleWithJSON: "./src/demo/exampleWithJSON.ts",
|
exampleWithJSON: "./src/demo/exampleWithJSON.ts",
|
||||||
|
exampleWithMixedFields: "./src/demo/exampleWithMixedFields.ts",
|
||||||
exampleWithUL: "./src/demo/exampleWithUL.ts",
|
exampleWithUL: "./src/demo/exampleWithUL.ts",
|
||||||
exampleWithUserFile: "./src/demo/exampleWithUserFile.ts"
|
exampleWithUserFile: "./src/demo/exampleWithUserFile.ts"
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user