Skip to content
Snippets Groups Projects
Commit 50b3c817 authored by Laurent Lecluse's avatar Laurent Lecluse
Browse files

Nouveau composant UTableAjax permettant de créer un tableau avec chargement...

Nouveau composant UTableAjax permettant de créer un tableau avec chargement des données en Ajax et affichage avec Vue, avec son côté serveur.
parent d5d01112
No related branches found
No related tags found
No related merge requests found
Pipeline #27362 passed
CHANGELOG
=========
6.2.1 (29/03/2024)
------------------
- Nouveau composant UTableAjax permettant de créer un tableau avec chargement des données en Ajax et affichage avec Vue, avec son côté serveur.
- Suppression de la fonction de formatage de date
6.2.0 (26/03/2024)
------------------
......
<template>
<div class="dt-bootstrap5">
<b-row>
<b-col>
Afficher <label>
<select v-model="dSize" class="form-select form-select-sm">
<option v-for="ps in pageSizes" :key="ps" :value="ps">{{ ps }}</option>
</select>
</label> éléments
</b-col>
<b-col>
<div class="float-end">
Rechercher : <label><input v-model="dSearch"
class="form-control form-inline form-control-sm"/></label>
</div>
</b-col>
</b-row>
<table class="table table-bordered dataTable mb-2" ref="tableRef">
<slot></slot>
</table>
<b-row>
<b-col>Affichage de l'élément {{ elStart }} à {{ elEnd }} sur {{ dCount }} éléments</b-col>
<b-col>
<div class="dataTables_paginate paging_simple_numbers">
<b-pagination align="end" :page="page" v-model="page" :total-rows="dCount" :per-page="cSize"
last-number="true"
firstNumber="true"
prev-text="Précédent"
next-text="Suivant"
/>
</div>
</b-col>
</b-row>
</div>
</template>
<script>
export default {
name: "UTableAjax",
props: {
id: {required: false, type: String},
size: {required: false, default: 10},
count: {required: false},
search: {required: false},
dataUrl: {required: true, type: String},
},
data()
{
return {
page: 1,
pageSizes: [10, 25, 50, 100, 'Tous'],
defaultSize: 10,
dSize: this.size,
dCount: this.count,
dSearch: this.search,
searchTimer: null,
columns: {},
loading: false,
orderCol: undefined,
orderDir: 'asc',
};
},
computed: {
cSize()
{
if (isNaN(this.dSize)) {
return 9999999999999;
}
return this.dSize;
},
storageIdentifier()
{
return 'UTableAjax-' + this.id + '-' + window.location.href;
},
elStart()
{
if (isNaN(this.dSize)) {
return 1;
}
return ((this.page - 1) * this.dSize) + 1;
},
elEnd()
{
if (isNaN(this.dCount)) {
return (this.page - 1) * this.dSize + this.dSize;
}
if (isNaN(this.dSize)) {
return this.dCount;
}
return Math.min(this.dCount, (this.page - 1) * this.dSize + this.dSize);
},
},
watch: {
dSize(newSize)
{
localStorage.setItem(this.storageIdentifier, newSize);
this.getData();
},
dSearch(newSearch)
{
const that = this;
clearTimeout(this.searchTimer);
this.searchTimer = setTimeout(() => {
if (this.page > 1) {
this.page = 1;
}else {
that.getData();
}
}, 500);
},
page(newPage)
{
this.getData();
},
},
methods: {
getData()
{
unicaenVue.axios.post(this.dataUrl, {
page: this.page,
size: this.dSize,
elStart: this.elStart,
elEnd: this.elEnd,
search: this.dSearch,
orderCol: this.orderCol,
orderDir: this.orderDir,
}).then(response => {
let data = response.data;
this.dCount = data.count;
this.$emit('data', data.data);
});
},
orderBy(column)
{
const element = this.columns[column];
for(let col in this.columns){
if (col != column){
if (this.columns[col].classList.contains('sorting_asc')){
this.columns[col].classList.remove('sorting_asc');
}
if (this.columns[col].classList.contains('sorting_desc')){
this.columns[col].classList.remove('sorting_desc');
}
console.log(col)
}
}
let newOrder = 'asc';
if (element.classList.contains('sorting_asc')){
newOrder = 'desc';
element.classList.remove('sorting_asc');
element.classList.add('sorting_desc');
}else if (element.classList.contains('sorting_desc')){
element.classList.remove('sorting_desc');
element.classList.add('sorting_asc');
}else{
element.classList.add('sorting_asc');
}
this.orderCol = column;
this.orderDir = newOrder;
this.getData();
},
},
mounted()
{
this.dSize = parseInt(localStorage.getItem(this.storageIdentifier)) || this.defaultSize;
this.page = 1;
this.dSize = this.size;
this.dCount = this.count;
const headEl = this.$refs.tableRef;
const that = this;
if (headEl) {
// Récupération des éléments <th> qui ont l'attribut 'column'
const thElements = headEl.querySelectorAll('th[column]');
thElements.forEach(th => {
Array.from(th.attributes).forEach(attr => {
if (attr.name == 'column') {
this.columns[attr.value] = th;
th.dataset.column = attr.value;
th.onclick = function () {
that.orderBy(this.dataset.column);
}
th.removeAttribute(attr.name);
th.classList.add('sorting');
}
});
});
}
this.getData();
},
}
</script>
<style scoped>
</style>
\ No newline at end of file
# Composants proposés en standard
- [UCalendar]() Calendrier pour la saisie d'informations par mois (à documenter)
- [UTableAjax](composants/UTableAjax.md) Permet d'afficher des tableaux de données chargés en Ajax et formatés avec Vue.js
# UTableAjax
Permet de dessiner un pseudo DataTable avec un chargement des données en Ajax et un affichage en VueJS.
Exemple d'utilisation côté client, dans un composant Vue :
```vue
<template>
<u-table-ajax :data-url="this.dataUrl" @data="maj">
<thead>
<tr>
<th column="ID">Id</th><!-- l'attribut column doit être renseigné pour pouvoir la rendre triable -->
<th column="LIBELLE">Libellé</th>
<th column="FORMULE">Formule</th>
<th column="ANNEE">Année</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<!-- On liste toute les lignes et on les affiche ici -->
<tr v-for="(line,i) in lines" :key="i">
<td>{{ line['ID'] }}</td>
<td>{{ line['LIBELLE'] }}</td>
<td>{{ line['FORMULE'] }}</td>
<td>{{ line['ANNEE'] }}</td>
<td><a :href="editUrl(line['ID'])">Modifier</a></td>
</tr>
</tbody>
</u-table-ajax>
</template>
<script>
export default {
name: 'Test',
data()
{
return {
dataUrl: unicaenVue.url('.../data'),
lines: [],
};
},
methods: {
maj(lines)
{
this.lines = lines;
},
editUrl(id)
{
return unicaenVue.url('mon-url/:id', {id: id});
},
},
}
</script>
<style scoped>
</style>
```
Exemple de génération de données côté serveur, dans un contrôleur :
```php
<?php
namespace MonModule\Controller;
use Laminas\Mvc\Controller\AbstractActionController;
use UnicaenVue\Util;
class TestController extends AbstractActionController
{
public function dataAction()
{
// Requête SQL
// Elle doit avoir un paramètre :search pour traiter les recherches
// Elle ne doit pas avoir d'OrderBY : il sera ajouté ensuite selon le besoin
$sql = "
SELECT
fti.id id,
fti.libelle libelle,
f.libelle formule,
a.libelle annee
FROM
formule_test_intervenant fti
JOIN formule f ON f.id = fti.formule_id
JOIN annee a ON a.id = fti.annee_id
WHERE
lower(fti.libelle || ' ' || f.libelle || ' ' || a.libelle) like :search
";
// renvoie un axiosModel avec les données issues du requêtage
$em = /* Récupération de l'entityManager de Doctrine */;
return Util::tableAjaxData($em, $this->axios()->fromPOst(), $sql);
}
}
```
Résultat :
![UTableAjax](UTableAjax.png)
\ No newline at end of file
......@@ -8,6 +8,8 @@
[Partie serveur PHP](serveur.md)
[Composants proposés en standard](composants.md)
Pour faire vos premiers pas avec Vue :
- [Doc d'introduction à VueJS de Stéphane](https://git.unicaen.fr/bouvry/presentation-dev/-/blob/master/src/vuejs.md)
......
......@@ -2,13 +2,7 @@
namespace UnicaenVue\Controller\Plugin;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Query;
use Laminas\Mvc\Controller\Plugin\AbstractPlugin;
use Laminas\Mvc\Plugin\FlashMessenger\FlashMessenger;
use Laminas\View\Model\JsonModel;
use UnicaenVue\Axios\AxiosExtractor;
use UnicaenVue\Axios\AxiosExtractorInterface;
/**
* Description of Axios
......
<?php
namespace UnicaenVue;
use Doctrine\ORM\EntityManager;
use UnicaenVue\View\Model\AxiosModel;
class Util
{
static public function tableAjaxData(EntityManager $em, array $post, string $sql): AxiosModel
{
$search = $post['search'] ?? '';
$elStart = ($post['elStart'] ?? 1) - 1;
$size = $post['size'] ?? 10;
$orderCol = $post['orderCol'] ?? null;
$orderDir = ($post['orderDir'] ?? 'asc') == 'asc' ? 'asc' : 'desc';
if ($orderCol && (str_contains($orderCol, '"') || str_contains($orderCol, "'"))){
$orderCol = null; // protection contre les injections
}
$limitSql = "OFFSET :elStart ROWS FETCH FIRST :size ROWS ONLY";
if ($orderCol) {
$orderSql = "ORDER BY $orderCol $orderDir";
} else {
$orderSql = '';
}
$params = [
'elStart' => $elStart,
'size' => $size,
'search' => strtolower('%' . $search . '%'),
];
$count = (int)$em->getConnection()->fetchOne('SELECT count(*) cc FROM (' . $sql . ') t', $params);
$data = $em->getConnection()->fetchAllAssociative($sql . "\n" . $orderSql . "\n" . $limitSql, $params);
$result = [
'count' => $count,
'data' => $data,
];
return new AxiosModel($result);
}
}
\ No newline at end of file
......@@ -2,7 +2,6 @@
namespace UnicaenVue\View\Model;
use Exception;
use Laminas\View\Model\ModelInterface;
use Traversable;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment