gamutable/js/gamutable.es6.js

559 lines
14 KiB
JavaScript

jQuery(function () {
// pour les #URL_ACTION_AUTEUR
// il faut ajouter une class : url_action
$('#app').on('click', '.url_action', function (e) {
e.preventDefault();
e.stopPropagation();
let confirmation = $(this).data('confirm');
if (confirmation !== undefined) {
if (!confirm(confirmation)) {
return;
}
}
let url = $(this).attr('href');
let id = $(this).data('id');
let nomBlocAjaxReload = $(this).data('ajaxreload');
console.time('Chargement de VueJs APRES Ajax');
$.ajax({
url: url,
async: true,
}).done(function () {
if (parseInt(id) > 0) {
app.rechargerJson(id);
} else {
app.rechargerJson();
}
if (nomBlocAjaxReload !== undefined) {
ajaxReload(nomBlocAjaxReload, {
args: { id },
callback: function () {},
});
}
});
});
// lancement d'une modalbox
$('#app').on('click', '.modalbox', function (e) {
e.stopPropagation();
e.preventDefault();
let confirmation = $(this).data('confirm');
if (confirmation !== undefined) {
if (!confirm(confirmation)) {
return;
}
}
let url = $(this).attr('href');
url += '&var_zajax=content';
let data = {};
let minHeight = $(this).data('minheight');
if (minHeight !== undefined) {
data.minHeight = minHeight;
}
let minWidth = $(this).data('minwidth');
if (minWidth !== undefined) {
data.minWidth = minWidth;
}
let width = $(this).data('width');
if (width !== undefined) {
data.width = width;
}
$.modalbox(url, data);
});
// lancement d'une médiabox
$('#app').on('click', '.mediabox', function (e) {
e.preventDefault();
let confirmation = $(this).data('confirm');
if (confirmation !== undefined) {
if (!confirm(confirmation)) {
return;
}
}
let href = $(this).attr('href');
$.fn.mediabox({ href });
});
});
function recupJson(d) {
try {
return JSON.parse(d);
} catch (e) {
return [];
}
}
const orderBy = (arr, props, orders, champ) =>
arr.sort((a, b) =>
props.reduce((acc, prop, i) => {
if (acc === 0) {
let [p1, p2] =
orders && orders[i] === 'desc'
? [b[champ][prop], a[champ][prop]]
: [a[champ][prop], b[champ][prop]];
// passe en lowercase les String
p1 = typeof p1 === 'string' ? p1.toLowerCase() : p1;
p2 = typeof p2 === 'string' ? p2.toLowerCase() : p2;
// Gestion du format de date
// transforme 03/11/2000 en 20001103
let re = /^(\d{2})\/(\d{2})\/(\d{2,4})$/;
if (typeof p1 !== 'number') {
let r1 = p1.match(re);
if (Array.isArray(r1)) {
p1 = r1[3] + r1[2] + r1[1];
}
let r2 = p2.match(re);
if (Array.isArray(r2)) {
p2 = r2[3] + r2[2] + r2[1];
}
}
acc = p1 > p2 ? 1 : p1 < p2 ? -1 : 0;
}
return acc;
}, 0)
);
console.time('Chargement de VueJS AVANT Ajax');
console.time('Chargement de VueJs APRES Ajax');
Vue.nextTick(function () {
console.timeEnd('Chargement de VueJS AVANT Ajax');
});
function exporterCSV(json, delimitercsv, name) {
let csv = '';
if (delimitercsv) {
csv = Papa.unparse(json, { delimiter: delimitercsv });
} else {
csv = Papa.unparse(json);
}
//Download the file as CSV
let link = document.createElement('a');
link.setAttribute('href', 'data:text/csv;charset=utf-8,%EF%BB%BF' + encodeURIComponent(csv));
link.setAttribute('download', name);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function trouver_index(table, id) {
let i = -1;
table.forEach((ligne, index) => {
if (ligne.html.id === parseInt(id)) {
i = index;
}
});
return i;
}
let monTableau = {
props: {
tparpage: {
type: Array,
default: function () {
return [10, 20, 50, 'Tous'];
},
},
apiuri: {
type: String,
required: true,
},
champcsv: {
type: String,
},
delimitercsv: {
type: String,
},
namecsv: {
type: String,
},
url_sort_asc: {
type: String,
},
url_sort_desc: {
type: String,
},
urlvuebloc: {
type: String,
},
vueblocdefaut: {
type: String,
default: 'tableau',
},
},
data: function () {
return {
table: [],
header: [],
crayons: [],
classes: [],
filtreCol: [],
filtreColSelected: [],
filtreColModif: 0,
filtreColVal: {},
search: '',
page: 1,
parPage: sessionStorage.getItem('nbItems') ? sessionStorage.getItem('nbItems') : this.tparpage[0],
parPageSelect: sessionStorage.getItem('nbItems') ? sessionStorage.getItem('nbItems') : this.tparpage[0],
pages: [],
triOrders: [],
triProps: [],
selectTr: [],
champ_search: 'html',
chargement: true,
nameLocalStorage: this.calculer_nameLocalStorage(),
quelleVue: this.vueblocdefaut,
vuebloc: '',
};
},
mounted() {
this.chargerJson();
if (this.urlvuebloc) {
fetch(this.urlvuebloc)
.then((response) => response.text())
.then((data) => {
this.vuebloc = data;
});
}
},
computed: {
tableau: function () {
this.setPages();
//if (!this.search) {
if (!this.search && !this.filtreColModif) {
return this.pagination(this.table);
}
return this.pagination(
this.table.filter((ligne) => {
let rsearch =
Object.values(ligne[this.champ_search])
.toString()
.toLowerCase()
.indexOf(this.search.toLowerCase()) < 0
? false
: true;
if (!rsearch) {
return false;
}
Object.keys(this.filtreColSelected).forEach((colName) => {
if (rsearch) {
let colValue = this.filtreColSelected[colName];
if (colValue) {
if (ligne.search[colName] !== colValue) {
rsearch = false;
}
}
}
});
return rsearch;
})
);
},
},
watch: {
parPageSelect(e) {
if (!parseInt(e)) {
this.parPage = this.table.length;
} else {
this.parPage = e;
}
sessionStorage.setItem('nbItems', this.parPage);
},
table() {
let $table = [];
$table = [
...[
{
header: this.header,
crayons: this.crayons,
classes: this.classes,
filtreCol: this.filtreCol,
},
],
...this.table,
];
localStorage.setItem(this.nameLocalStorage, JSON.stringify($table));
},
},
methods: {
selectValCol() {
this.filtreColModif++;
},
calculer_nameLocalStorage() {
if (this.apiuri) {
return this.apiuri.match(/.*page=(.*)/)[1];
}
return '';
},
chargerJson(id) {
let url = this.apiuri;
if (parseInt(id) > 0) {
url += '&id=' + id;
} else {
let data = localStorage.getItem(this.nameLocalStorage);
data = recupJson(data);
if (data && data.length) {
let config = data.shift();
this.header = config.header;
if (config.crayons !== undefined) {
this.crayons = config.crayons;
}
if (config.classes !== undefined) {
this.classes = config.classes;
}
this.table = data;
if (data.length && data[0].search) {
this.champ_search = 'search';
}
console.log('fin chargement localStorage');
}
}
fetch(url)
.then((response) => response.json())
.then((data) => {
let config = data.shift();
this.header = config.header;
if (config.crayons !== undefined) {
this.crayons = config.crayons;
}
if (config.classes !== undefined) {
this.classes = config.classes;
}
if (parseInt(id) > 0) {
if (data.length > 0) {
let i = trouver_index(this.table, id);
this.table[i] = data[0];
} else {
let i = trouver_index(this.table, id);
this.table.splice(i, 1);
}
} else {
this.table = data;
if (data[0] && data[0].search) {
this.champ_search = 'search';
}
}
if (config.filtreCol !== undefined) {
this.filtreCol = config.filtreCol;
this.filtreCol.forEach((col) => {
let Tval = [''];
this.table.forEach((t) => {
let valCol = t.search[col];
if (Tval.indexOf(valCol) === -1) {
Tval.push(valCol);
}
});
this.filtreColVal[col] = Tval;
});
}
Vue.nextTick(() => {
this.chargement = false;
if ($_id > 0) {
this.selectLigne($_id, 'id');
let url = new URL(window.location);
url = url.href.replace('&_id=' + $_id, '');
history.pushState({}, null, url);
}
console.timeEnd('Chargement de VueJs APRES Ajax');
});
})
.catch((error) => console.log(error));
},
setPages() {
let nombreDePages = Math.ceil(this.table.length / this.parPage);
this.pages = [];
for (let index = 1; index <= nombreDePages; index++) {
this.pages.push(index);
}
},
pagination(tableau) {
let page = this.page;
let parPage = this.parPage;
let from = page * parPage - parPage;
let to = page * parPage;
return tableau.slice(from, to);
},
afficher_crayons(name, id) {
if (Object.keys(this.crayons).indexOf(name) !== -1) {
return `crayon ${this.crayons[name]}-${name}-${id}`;
}
},
tri(col, sens = false) {
const i = this.triProps.indexOf(col);
if (i !== -1) {
if (!sens) {
sens = 'asc';
if (this.triOrders[i] === 'asc') {
sens = 'desc';
}
}
this.triOrders[i] = sens;
} else {
if (!sens) {
sens = 'asc';
}
this.triProps.push(col);
this.triOrders.push(sens);
}
this.table = orderBy(this.table, this.triProps, this.triOrders, this.champ_search);
},
ordreActif(col, sens) {
const i = this.triProps.indexOf(col);
if (i !== -1) {
if (this.triOrders[i] === sens) {
return 'active';
}
}
},
resetTri() {
this.table = orderBy(this.table, ['id'], '', this.champ_search);
this.triOrders = [];
this.triProps = [];
},
selectLigne(id, col) {
if (col === 'id' && parseInt(id)) {
let i = this.selectTr.indexOf(id);
if (i !== -1) {
this.selectTr.splice(i, 1);
} else {
this.selectTr.push(id);
}
}
},
exportCSV() {
let $csv = [];
let $header = [];
let $tableau = [];
Object.keys(this.header).forEach((k) => $header.push(k));
$tableau = this.tableau.reduce((acc, ligne) => {
let $uneLigne = [];
Object.values(ligne[this.champcsv]).forEach((l) => $uneLigne.push(l));
return [...acc, [...$uneLigne]];
}, []);
$csv = [[...$header], ...$tableau];
exporterCSV($csv, this.delimitercsv, this.namecsv);
},
replaceBloc(ligne) {
let html = this.vuebloc;
Object.keys(ligne).forEach((key) => {
html = html.replace(`@@${key}@@`, ligne[key]);
});
return html;
},
changerVue(vue) {
this.quelleVue = vue;
},
},
template: `
<div class="gamutable">
<div class="gamutable--surTable">
<select id="parPage" v-model="parPageSelect">
<option v-for="v in tparpage" :key="v">{{v}}</option>
</select>
<input class="gamutable--rechercher" type="text" v-model="search" placeholder="Rechercher">
<button class="btn gamutable--resetOrderBy" type="button" @click.stop="resetTri()"
title="Réinitialiser les tris des colonnes">
<i class="fa fa-repeat fas fa-redo">
</button>
<button class="btn gamutable--vueTable" title="Switcher en Vue tableau"
@click.stop="changerVue('tableau')"
v-if="this.vuebloc"
v-show="this.quelleVue === 'bloc'">
<i class="fas fa fa-table">
</button>
<button class="btn gamutable--vueBloc" title="Switcher en Vue Bloc"
@click.stop="changerVue('bloc')"
v-if="this.vuebloc"
v-show="this.quelleVue === 'tableau'">
<i class="fas fa fa-th-large">
</button>
<button class="btn gamutable--exportCSV" type="button" @click.stop="exportCSV()"
v-show="this.namecsv"
title="Exporter le tableau affiché en csv"
>
<i class="fa fa-file-excel-o fas fa-file-csv" aria-hidden="true"></i>
</button>
<span v-show="chargement" class="rouge">
<i class="fa fa-refresh fa-spin fa-fw rouge fas fa-sync fa-spin"></i>
<span class="texteMajBDD">
Mise à jour de la base de donnée
</span>
</span>
<span v-show="!chargement" class="btn verte" style="cursor:auto;"
title="Base de donnée synchronisée !"
>
<i class="fa fas fa-database"></i>
</span>
</div>
<div class="vueBlocs" v-if="quelleVue === 'bloc'">
<div class="vueBlocs-unbloc"
v-for="(ligne) in tableau"
:key=ligne.id
v-html=replaceBloc(ligne.html)
>
</div>
</div>
<table class="table table--zebra" v-if="quelleVue === 'tableau'">
<thead>
<tr>
<th v-for="(label,head,i) in header" :key="'head_'+i" :class="[head,classes[head]]">
<span v-html="label"></span>
<span class="iconeTri">
<i class="fa fa-sort-asc fa-sort-up" :class="ordreActif(head, 'asc')" aria-hidden="true" @click.stop="tri(head,'asc')"></i>
<i class="fa fa-sort-desc fa-sort-down":class="ordreActif(head, 'desc')" aria-hidden="true" @click.stop="tri(head,'desc')" ></i>
</span>
</th>
</tr>
<tr v-if="filtreCol.length" class="filtreColonne">
<th v-for="(label,head,i) in header" :key="'filtreCol_'+i">
<div v-if="filtreCol.indexOf(head) !== -1" :id="'filtreCol_'+head">
<select name="'col_'+head" class="filtrerCol" v-model="filtreColSelected[head]">
<option
v-for="option in filtreColVal[head]"
:value="option"
@click="selectValCol"
>{{option}}</option>
</select>
</div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="l in tableau" :key="l.html.id" :class="selectTr.indexOf(l.html.id) !== -1 ? 'select' : ''" >
<td v-for="(td,name, i) in l.html" :key="'td_'+i" :class="[afficher_crayons(name,l.html.id), name, classes[name]]" v-html="td" @click="selectLigne(l.html.id,name)">
</td>
</tr>
</tbody>
</table>
<div class="gamutable--sousTable">
<div class="gamutable-nbrMax">{{tableau.length}} / {{table.length}} éléments</div>
<div class="gamutable--pagination">
<div class="page-item">
<button type="button" class="page-link" v-if="page != 1" @click="page=1"> Start </button>
<button type="button" class="page-link" v-if="page != 1" @click="page--"> Previous </button>
</div>
<div class="page-item">
<button type="button" class="page-link" v-for="pageNumber in pages.slice(page-1, page+5)" @click="page = pageNumber"> {{pageNumber}} </button>
</div>
<div class="page-item">
<button type="button" @click="page++" v-if="page < pages.length" class="page-link"> Next </button>
<button type="button" @click="page=pages.length" v-if="page < pages.length" class="page-link"> Last </button>
</div>
</div>
</div>
</div>`,
};
const gamuTable = {
components: { monTableau },
methods: {
rechargerJson(id) {
this.$refs.montableau.chargerJson(id);
},
},
};
let app = Vue.createApp(gamuTable).mount('#app');