audezu
Novembre 29, 2025, 5:07
1
Pré-requis : avoir une table avec une colonne formule qui crée du code html dynamique, cf Générer des PDF personnalisés / Publipostage / Widget Markdown ou Publipostage avec données de plusieurs tables .
Enregistrer un PDF pour chaque vue, en indiquant le nom du fichier
Le custom widget ouvre la boîte de dialogue d’impression pour enregistrer en PDF les documents générés grâce à du publipostage, en les nommant de la manière souhaitée.
Utilisation
Dans votre table qui contient le html :
ajoutez une colonne de type booléen, pour sélectionner les lignes que vous souhaitez imprimer
Dans votre page :
Ajoutez une vue personnalisée basée sur la table qui contient le html, choisir la vue « URL personnalisée » avec le lien suivant : https://maluhialoha.github.io/grist-cw-html-to-pdf/
Dans le panneau de création, autorisez l’accès au document, et indiquez la colonne de votre table qui contient le html, ainsi que la colonne de sélection créée précédemment
(optionnel) Dans le custom widget, indiquez le nom que vous souhaitez donner aux documents. Vous pouvez utiliser des valeurs dynamiques, en utilisant les identifiants de colonnes entourés d’accolades (ex : Facture de {Nom} - {Ville})
Cliquez sur « Enregistrer ».
La boîte de dialogue d’impression va s’ouvrir pour chaque document à télécharger. Il faut choisir « Enregistrer en PDF » et valider l’enregistrement dans le dossier souhaité.
Document d’exemple ici :
PS : au départ je voulais faire un téléchargement direct, sans passer par la boîte de dialogue : j’ai testé les librairies htmltopdf et htmltocanva mais le html est très mal rendu (problèmes d’espaces notamment)…
Enregistrer toutes les vues dans un même PDF
Vous pouvez utiliser ce custom widget : Imprimer en masse - publipostage
En choisissant « Enregistrer en PDF » quand la boîte de dialogue s’ouvre.
audezu
Décembre 1, 2025, 5:11
2
Code du widget
Le code est disponible ici : GitHub - maluhialoha/grist-cw-html-to-pdf
Il est servi à cette adresse : https://maluhialoha.github.io/grist-cw-html-to-pdf/ , pour être utilisé directement comme custom widget (cf section précédente).
Pour information ci-dessous la v0 du code, sans la partie de mappage des colonnes :
html :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://docs.getgrist.com/grist-plugin-api.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
padding: 10px;
background: #f5f5f5;
line-height: 1.4;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 15px;
border-radius: 6px;
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
}
h1 {
color: #333;
margin-bottom: 15px;
font-size: 18px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 8px;
color: #555;
font-weight: 500;
font-size: 14px;
}
input[type="text"] {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 3px;
font-size: 13px;
display: block;
margin-bottom: 10px;
}
input[type="text"]:focus {
outline: none;
border-color: #667eea;
}
button {
background: #667eea;
color: white;
border: none;
padding: 8px 20px;
border-radius: 3px;
font-size: 14px;
cursor: pointer;
font-weight: 500;
transition: background 0.2s;
}
button:hover {
background: #5568d3;
}
details {
margin-top: 12px;
border: 1px solid #e0e0e0;
border-radius: 3px;
padding: 8px 10px;
background: #fafafa;
}
summary {
cursor: pointer;
font-weight: 600;
color: #333;
font-size: 13px;
user-select: none;
}
summary:hover {
color: #667eea;
}
details[open] summary {
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px solid #e0e0e0;
}
ol, ul {
padding-left: 20px;
color: #555;
font-size: 13px;
}
li {
margin-bottom: 4px;
}
code {
background: #f0f0f0;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 13px;
}
pre {
display: none;
}
</style>
</head>
<body>
<div class="container">
<h1>📄 Téléchargement PDFs</h1>
<div class="form-group">
<label for="namePattern">Nom que vous souhaitez donner aux documents :</label>
<input type="text" id="namePattern" placeholder="Ex: Dossier {Nom} {Prenom}">
<button id="printBtn">Télécharger</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
js - il faut remplacer formule_avec_style par l’id de la colonne qui contient votre html :
grist.ready({ requiredAccess: 'full' });
const output = document.querySelector("#output");
const printBtn = document.querySelector("#printBtn");
const namePattern = document.querySelector("#namePattern");
let tableData = [];
// Récupérer la table
grist.onRecords(table => {
tableData = table
});
// Bouton d'impression
printBtn.addEventListener('click', async () => {
if (tableData.length === 0) {
alert("Aucune donnée à télécharger");
return;
}
// Vérifier que la colonne "Télécharger" existe
if (tableData.length > 0 && !('Telecharger' in tableData[0])) {
alert("La colonne 'Télécharger' n'existe pas dans la table");
return;
}
const rowsToDownload = tableData.filter(row => row.Telecharger === true);
if (rowsToDownload.length === 0) {
alert("Aucune ligne avec Télécharger = True");
return;
}
const pattern = namePattern.value.trim();
// Générer le nom du fichier
function generateFileName(row, index) {
if (!pattern) {
return `${index + 1}`;
}
const variables = pattern.match(/\{([^}]+)\}/g);
if (!variables) {
return `${pattern} - ${index + 1}`;
}
let fileName = pattern;
for (let variable of variables) {
const columnId = variable.slice(1, -1).trim();
if (!(columnId in row)) {
throw new Error(`La colonne "${columnId}" n'existe pas`);
}
const value = (row[columnId] || '').toString().trim();
fileName = fileName.replace(variable, value);
}
fileName = fileName.replace(/\s+/g, ' ').trim();
return fileName;
}
// Ouvrir les fenêtres d'impression
try {
for (let i = 0; i < rowsToDownload.length; i++) {
const row = rowsToDownload[i];
const fileName = generateFileName(row, i);
const printWindow = window.open('', '_blank');
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${fileName}</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
margin: 0;
}
@media print {
body {
padding: 10mm;
}
}
</style>
</head>
<body>
${row.formule_avec_style || ''}
<script>
window.onload = function() {
setTimeout(function() {
window.print();
}, 250);
};
<\/script>
</body>
</html>
`;
printWindow.document.write(htmlContent);
printWindow.document.close();
}
} catch (error) {
alert(error.message);
}
});