Nouvelle version application avec ajout d'une extension créant des liens de classement stand alone (+ tests et build).

This commit is contained in:
Fabrice PENHOËT 2021-12-09 16:09:46 +01:00
parent d7ae990e07
commit 38637443e2
11 changed files with 1442 additions and 27 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "freedatas2html", "name": "freedatas2html",
"version": "1.0.0", "version": "1.1.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": {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,3 @@
var compare = require('natural-orderby').compare;
var errors = require("./errors.js"); var errors = require("./errors.js");
var SortingField = (function () { var SortingField = (function () {
function SortingField(converter, datasFieldNb, fieldsDOMSelector) { function SortingField(converter, datasFieldNb, fieldsDOMSelector) {
@ -39,6 +38,9 @@ var SortingField = (function () {
get: function () { get: function () {
return this._order; return this._order;
}, },
set: function (setting) {
this._order = setting;
},
enumerable: true, enumerable: true,
configurable: true configurable: true
}); });

View File

@ -0,0 +1,83 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
import { FreeDatas2HTML, Render, SortingField } from "../FreeDatas2HTML";
import { SortingFieldsStandAlone } from "../extensions/SortingFieldsStandAlone";
var initialise = function () { return __awaiter(void 0, void 0, void 0, function () {
var converter, myRender, sortingField1, sortingField2, sortingField3, allFields, e_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
converter = new FreeDatas2HTML("CSV");
converter.parser.setRemoteSource({ url: "http://localhost:8080/datas/elements-chimiques.csv" });
return [4, converter.run()];
case 1:
_a.sent();
converter.fields2Rend = [0, 1, 2, 3];
myRender = new Render();
myRender.settings =
{
allBegining: "<h4>Liste des éléments chimiques :</h4>",
allEnding: "",
linesBegining: "<ul>",
linesEnding: "</ul>",
lineBegining: "<li><ul>",
lineEnding: "</ul></li>",
dataDisplaying: "<li><b>#FIELDNAME :</b> #VALUE</li>",
};
converter.datasRender = myRender;
sortingField1 = new SortingField(converter, 0);
sortingField2 = new SortingField(converter, 1);
sortingField3 = new SortingField(converter, 2);
allFields = new SortingFieldsStandAlone(converter, { id: "classement" });
allFields.datasSortingFields = [sortingField1, sortingField2, sortingField3];
allFields.rendSettings.allBeginning = "<h4>#LABEL</h4><ul>";
allFields.rend2HTML("Champ sur lequel classer les données :");
converter.datasViewElt = { id: "datas" };
converter.refreshView();
return [3, 3];
case 2:
e_1 = _a.sent();
console.error(e_1);
document.getElementById("datas").innerHTML = "<div class=\"alert alert-warning\">D\u00E9sol\u00E9, mais un probl\u00E8me technique emp\u00EAche l'affichage des donn\u00E9es.</div>";
return [3, 3];
case 3: return [2];
}
});
}); };
console.log("Hello, ami développeur :-)\nLe code source TypeScript utilisé pour faire fonctionner cette page est lisible ici : :\nhttps://forge.chapril.org/Fab_Blab/freeDatas2HTML/src/branch/master/src/demo/exampleWithJSON.ts\nUn bug ? Une suggestion ? => fabrice@le-fab-lab.com");
initialise();

View File

@ -0,0 +1,67 @@
import { FreeDatas2HTML } from "../FreeDatas2HTML";
var errors = require("../errors.js");
errors.needSortingFields = "Vous devez fournir au moins un champ de classement valide.";
var SortingFieldsStandAlone = (function () {
function SortingFieldsStandAlone(converter, elt, settings) {
if (settings === void 0) { settings = SortingFieldsStandAlone.defaultSettings; }
this._datasViewElt = { id: "", eltDOM: undefined };
this.datasSortingFields = [];
if (converter.fields.length === 0)
throw new Error(errors.sortingFieldNeedDatas);
this._datasViewElt = FreeDatas2HTML.checkInDOMById(elt);
this._converter = converter;
this.rendSettings = settings;
}
Object.defineProperty(SortingFieldsStandAlone.prototype, "converter", {
get: function () {
return this._converter;
},
enumerable: true,
configurable: true
});
Object.defineProperty(SortingFieldsStandAlone.prototype, "datasViewElt", {
get: function () {
return this._datasViewElt;
},
enumerable: true,
configurable: true
});
SortingFieldsStandAlone.prototype.rend2HTML = function (label) {
if (label === void 0) { label = ""; }
if (this.datasSortingFields.length === 0)
throw new Error(errors.needSortingFields);
var htmlContent = this.rendSettings.allBeginning.replace("#LABEL", label);
for (var _i = 0, _a = this.datasSortingFields; _i < _a.length; _i++) {
var field = _a[_i];
htmlContent += this.rendSettings.fieldBeginning + "<a href='#freeDatas2HTMLSorting" + field.datasFieldNb + "' id='freeDatas2HTMLSorting" + field.datasFieldNb + "'>" + this._converter.fields[field.datasFieldNb] + "</a>" + this.rendSettings.fieldEnding;
}
htmlContent += this.rendSettings.allEnding;
this._datasViewElt.eltDOM.innerHTML = htmlContent;
var _loop_1 = function (field) {
var sortingLink = document.getElementById("freeDatas2HTMLSorting" + field.datasFieldNb);
sortingLink.addEventListener("click", function (e) {
e.preventDefault();
if (field.order === undefined || field.order === "desc")
field.order = "asc";
else
field.order = "desc";
field.converter.datasSortedField = field;
field.converter.refreshView();
});
};
for (var _b = 0, _c = this.datasSortingFields; _b < _c.length; _b++) {
var field = _c[_b];
_loop_1(field);
}
};
SortingFieldsStandAlone.defaultSettings = {
allBeginning: "<span>#LABEL</span><ul>",
allEnding: "</ul>",
fieldBeginning: "<li>",
fieldEnding: "</li>",
};
return SortingFieldsStandAlone;
}());
export { SortingFieldsStandAlone };
export { FreeDatas2HTML, SortingField } from "../FreeDatas2HTML";
export { errors };

View File

@ -4,12 +4,13 @@
import { DOMElement } from "../interfaces"; import { DOMElement } from "../interfaces";
import { FreeDatas2HTML, SortingField } from "../FreeDatas2HTML"; import { FreeDatas2HTML, SortingField } from "../FreeDatas2HTML";
const errors=require("../errors.js"); const errors=require("../errors.js");
errors.needSortingFields="Vous devez fournir au moins un champ de classement valide.";
export interface SortingFieldsSettings interface SortingFieldsSettings
{ {
allBegining: string; allBeginning: string;
allEnding: string; allEnding: string;
fieldBegining: string; fieldBeginning: string;
fieldEnding: string; fieldEnding: string;
} }
@ -17,45 +18,57 @@ export class SortingFieldsStandAlone
{ {
private _converter: FreeDatas2HTML; private _converter: FreeDatas2HTML;
private _datasViewElt: DOMElement={ id: "", eltDOM: undefined }; private _datasViewElt: DOMElement={ id: "", eltDOM: undefined };
public datasSortingFields: SortingField[]=[]; public datasSortingFields: SortingField[]=[]; // SortingField refusera les champs non valides
public rendSettings: SortingFieldsSettings; public rendSettings: SortingFieldsSettings;
static readonly defaultSettings= static readonly defaultSettings=
{ {
allBegining: "<ul>", allBeginning: "<span>#LABEL</span><ul>",
allEnding: "</ul>", allEnding: "</ul>",
fieldBegining: "<li>", fieldBeginning: "<li>",
fieldEnding: "</li>", fieldEnding: "</li>",
}; };
constructor(converter: FreeDatas2HTML, elt: DOMElement, settings: SortingFieldsSettings=SortingFieldsStandAlone.defaultSettings) constructor(converter: FreeDatas2HTML, elt: DOMElement, settings: SortingFieldsSettings=SortingFieldsStandAlone.defaultSettings)
{ {
// Ne peut être appelé avant d'avoir récupéré la liste des champs :
if(converter.fields.length === 0) if(converter.fields.length === 0)
throw new Error(errors.sortingFieldNeedDatas); throw new Error(errors.sortingFieldNeedDatas);
this._converter=converter; // Test l'existence dans le DOM de l'élément devant afficher les options de classement :
this._datasViewElt=FreeDatas2HTML.checkInDOMById(elt); this._datasViewElt=FreeDatas2HTML.checkInDOMById(elt);
this._converter=converter;
this.rendSettings=settings; this.rendSettings=settings;
} }
public rend2HTML(label:string="Classer les données suivant : ") : void get converter() : FreeDatas2HTML
{ {
// Il doit y avoir au moins un champ de classement fourni : return this._converter;
}
get datasViewElt() : DOMElement
{
return this._datasViewElt;
}
public rend2HTML(label:string="") : void
{
// Au moins un champ de classement valide doit avoir été fourni :
if(this.datasSortingFields.length === 0) if(this.datasSortingFields.length === 0)
throw new Error("Vous devez fournir au moins un champ de classement valide."); throw new Error(errors.needSortingFields);
// Arrivé ici, je sais que j'ai au moins un lien de classement à injecter // Arrivé ici, il y a au moins un lien de classement à injecter :
let htmlContent="<label>"+label+"</label>"+this.rendSettings.allBegining; let htmlContent=this.rendSettings.allBeginning.replace("#LABEL", label);
for(let field of this.datasSortingFields) for(let field of this.datasSortingFields)
{ {
htmlContent+=this.rendSettings.fieldBegining+"<a href='#freeDatas2HTMLSorting"+field.datasFieldNb+"' id='freeDatas2HTMLSorting"+field.datasFieldNb+"'>"+this._converter.fields[field.datasFieldNb]+"</a>"+this.rendSettings.fieldEnding; htmlContent+=this.rendSettings.fieldBeginning+"<a href='#freeDatas2HTMLSorting"+field.datasFieldNb+"' id='freeDatas2HTMLSorting"+field.datasFieldNb+"'>"+this._converter.fields[field.datasFieldNb]+"</a>"+this.rendSettings.fieldEnding;
} }
htmlContent+=this.rendSettings.allEnding; htmlContent+=this.rendSettings.allEnding;
this._datasViewElt.eltDOM!.innerHTML=htmlContent;// "!" car existence de l'élement, testé via le constructeur. this._datasViewElt.eltDOM!.innerHTML=htmlContent;// "!" car existence de l'élement dans le DOM a été testé dans le constructeur.
// Les liens venant d'êtres injectés dans le DOM, il reste à les rendre actifs : // Les liens venant d'êtres injectés dans le DOM, il reste à les rendre actifs :
for(let field of this.datasSortingFields) for(let field of this.datasSortingFields)
{ {
let sortingLink=document.getElementById("freeDatas2HTMLSorting"+field.datasFieldNb); let sortingLink=document.getElementById("freeDatas2HTMLSorting"+field.datasFieldNb);
sortingLink!.addEventListener("click", function(e) // "!" car je sais que sortingLink existe, puisque je viens de le créer ! sortingLink!.addEventListener("click", function(e) // "!" car je sais que sortingLink existe, venant de le créer.
{ {
e.preventDefault(); e.preventDefault();
if(field.order === undefined || field.order === "desc") if(field.order === undefined || field.order === "desc")
@ -67,4 +80,8 @@ export class SortingFieldsStandAlone
}); });
} }
} }
} }
// Utile au script de tests :
export { FreeDatas2HTML, SortingField } from "../FreeDatas2HTML";
export { errors };

View File

@ -0,0 +1,108 @@
import { errors, FreeDatas2HTML, SortingField, SortingFieldsStandAlone } from "../../src/extensions/SortingFieldsStandAlone";
const fixtures=require("../fixtures.js");
describe("Test des liens de classement (hors données).", () =>
{
let converter: FreeDatas2HTML;
let sortingFields: SortingFieldsStandAlone;
beforeEach( async () =>
{
document.body.insertAdjacentHTML("afterbegin", fixtures.datasViewEltHTML);
converter=new FreeDatas2HTML("CSV");
converter.parser.setRemoteSource({ url:"http://localhost:9876/datas/datas1.csv" });
converter.datasViewElt={ id:"datas" };
await converter.run(); // parsage + 1er affichage des données
});
afterEach( () =>
{
document.body.removeChild(document.getElementById("fixture"));
});
describe("Test des données reçues pour configurer les liens de classement.", () =>
{
it("Doit générer une erreur, si initialisé avant que les champs de données ne soient connus.", () =>
{
converter=new FreeDatas2HTML("CSV");
// Pas lancé converter.run(), donc les données n'ont pas été parsées :
expect(() => { return new SortingFieldsStandAlone(converter, { id:"selector1" }); }).toThrowError(errors.sortingFieldNeedDatas);
});
it("Doit générer une erreur, si initialisé avec l'id d'un élément n'existant pas dans le DOM", () =>
{
expect(() => { return new SortingFieldsStandAlone(converter, { id:"dontExist" }); }).toThrowError(errors.converterElementNotFound+"dontExist");
});
it("Si tous les paramètres sont ok, ils doivent être acceptés.", () =>
{
expect(() => { sortingFields=new SortingFieldsStandAlone(converter, { id:"selector1" }); }).not.toThrowError();
expect(sortingFields.converter).toEqual(converter);
expect(sortingFields.datasViewElt.id).toEqual("selector1");
});
});
describe("Création et action des liens de classement", () =>
{
let sortingField1: SortingField, sortingField2: SortingField;
beforeEach(() =>
{
sortingFields=new SortingFieldsStandAlone(converter, { id:"selector1" });
sortingField1=new SortingField(converter, 0);
sortingField2=new SortingField(converter, 2);
sortingFields.datasSortingFields=[sortingField1,sortingField2];
});
it("Doit générer une erreur, si lancé avant d'avoir fourni au moins un champ de classement.", () =>
{
sortingFields=new SortingFieldsStandAlone(converter, { id:"selector1" });
expect(() => { return sortingFields.rend2HTML() }).toThrowError(errors.needSortingFields);
});
it("Doit générer une liste de liens correspondant aux champs fournis .", () =>
{
sortingFields.rend2HTML();
let expectedHTML="<span></span><ul><li>"+fixtures.sortingColumn1HTML+"</li><li>"+fixtures.sortingColumn2HTML+"</li></ul>";
expect(document.getElementById("selector1").innerHTML).toEqual(expectedHTML);
// Idem avec des paramètres d'affichage :
sortingFields.rendSettings=
{
allBeginning: "<h4>#LABEL</h4><p>",
allEnding: "</p>",
fieldBeginning: "",
fieldEnding: " | ",
};
expectedHTML="<h4>Classer les données :</h4><p>"+fixtures.sortingColumn1HTML+" | "+fixtures.sortingColumn2HTML+" | </p>";
sortingFields.rend2HTML("Classer les données :");
expect(document.getElementById("selector1").innerHTML).toEqual(expectedHTML);
});
it("Lorsqu'ils sont cliqués, les liens de classement doivent transmettre l'information au convertisseur + lui demander d'actualiser l'affichage.", () =>
{
sortingFields.rend2HTML();
let getLinks=document.querySelectorAll("ul li a") as NodeListOf<HTMLElement>;
spyOn(converter, "refreshView");
getLinks[0].click();// tri ascendant 1er champ
expect(sortingField1.converter.datasSortedField).toEqual(sortingField1);
expect(sortingField1.converter.datasSortedField.order).toEqual("asc");
expect(converter.refreshView).toHaveBeenCalledTimes(1);
getLinks[1].click();// tri ascendant mais sur le second champ
expect(sortingField2.converter.datasSortedField).toEqual(sortingField2);
expect(sortingField2.converter.datasSortedField.order).toEqual("asc");
expect(converter.refreshView).toHaveBeenCalledTimes(2);
getLinks[0].click();// tri descendant sur le 1er champ
expect(sortingField1.converter.datasSortedField).toEqual(sortingField1);
expect(sortingField1.converter.datasSortedField.order).toEqual("desc");
expect(converter.refreshView).toHaveBeenCalledTimes(3);
getLinks[0].click();// de nouveau ascendant
expect(sortingField1.converter.datasSortedField).toEqual(sortingField1);
expect(sortingField1.converter.datasSortedField.order).toEqual("asc");
expect(converter.refreshView).toHaveBeenCalledTimes(4);
});
});
});