Skip to content

Commit 81c9ca6

Browse files
author
Your Name
committed
Improved documentation, removed prints in hcaptcha plugin.
Improved js and css for flash messages. Added documentation about how to change default texts in auth forms.
1 parent cb30ae3 commit 81c9ca6

5 files changed

Lines changed: 323 additions & 72 deletions

File tree

apps/_scaffold/static/css/no.css

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,3 +540,52 @@ ul.tags-list li {
540540
ul.tags-list li[data-selected=true] {
541541
opacity: 1.0;
542542
}
543+
544+
545+
flash-alerts {
546+
position: fixed;
547+
top: 20px;
548+
right: 20px;
549+
z-index: 1050;
550+
display: flex;
551+
flex-direction: column;
552+
align-items: flex-end;
553+
gap: 10px;
554+
/* Evita que los clics pasen a través del contenedor si está vacío */
555+
pointer-events: none;
556+
}
557+
/* Permite hacer clic en los toasts hijos */
558+
flash-alerts > * {
559+
pointer-events: auto;
560+
}
561+
562+
/* 2. Lógica de animación genérica.
563+
Esto funcionará para CUALQUIER elemento que coloques dentro de flash-alerts. */
564+
565+
/* Estado inicial (oculto) de cualquier notificación */
566+
flash-alerts > * {
567+
opacity: 0;
568+
transform: translateX(100%);
569+
/* Define la transición que se aplicará cuando cambien las propiedades */
570+
transition: all 0.4s ease-in-out;
571+
}
572+
573+
/* Estado final (visible) cuando se añade la clase .toast-show */
574+
flash-alerts > *.toast-show {
575+
opacity: 1;
576+
transform: translateX(0);
577+
}
578+
579+
/* 3. (Opcional) Estilo para el botón de cierre genérico, por si no usas Bulma. */
580+
.toast-close-btn {
581+
float: right;
582+
cursor: pointer;
583+
font-size: 1.5rem;
584+
font-weight: bold;
585+
line-height: 1;
586+
margin-left: 15px;
587+
opacity: 0.5;
588+
}
589+
.toast-close-btn:hover {
590+
opacity: 1;
591+
}

apps/_scaffold/static/js/utils.js

Lines changed: 80 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -534,69 +534,90 @@ Q.flash = function (detail) {
534534
// Displays flash messages
535535
Q.handle_flash = function () {
536536
const elem = Q("flash-alerts")[0];
537-
/** @type {(arg0: HTMLElement) => (event: CustomEvent) => void} */
538-
let make_handler;
539-
if ("bootstrap" in window) {
540-
make_handler = function (elem) {
541-
return function (event) {
542-
let node = document.createElement("div");
543-
const color = event.detail.class || "info";
544-
node.innerHTML = `<div
545-
class="toast fade ${color} border-${color} bg-${color}-subtle"
546-
role="alert"
547-
aria-live="assertive"
548-
aria-atomic="true"
549-
>
550-
<div class="toast-header">
551-
<strong class="me-auto">${
552-
event.detail.title || "Alert"
553-
}</strong>
554-
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
555-
</div>
556-
<div class=" toast-body">
557-
${event.detail.message}
558-
</div>
559-
</div>`;
560-
// @ts-ignore
561-
node = node.firstElementChild;
562-
elem.appendChild(node);
563-
bootstrap.Toast.getOrCreateInstance(node).show();
564-
node.addEventListener("hidden.bs.toast", () => {
565-
node.parentNode.removeChild(node);
566-
});
567-
};
568-
};
569-
} else {
570-
const make_delete_handler = function (node) {
571-
return function () {
572-
node.parentNode.removeChild(node);
573-
};
574-
};
575537

576-
make_handler = function (elem) {
577-
return function (event) {
578-
let node = document.createElement("div");
579-
node.innerHTML = `<div role="alert"><span class="close"></span>${event.detail.message}</div>`;
580-
// @ts-ignore
581-
node = Q('[role="alert"]', node)[0];
582-
node.classList.add(event.detail.class || "info");
583-
elem.appendChild(node);
584-
Q('[role="alert"] .close', node)[0].onclick =
585-
make_delete_handler(node);
586-
};
587-
};
538+
// Si no hay un elemento <flash-alerts> en la página, no hacemos nada.
539+
if (!elem) {
540+
return;
588541
}
589542

590-
if (elem) {
591-
elem.addEventListener("flash", make_handler(elem), false);
592-
/**
593-
*
594-
* @param {FlashDetails} detail
595-
*/
596-
Q.flash = function (detail) {
597-
elem.dispatchEvent(new CustomEvent("flash", { detail: detail }));
543+
const make_custom_toast_handler = function (container) {
544+
return function (event) {
545+
const detail = event.detail;
546+
547+
// Validar que tengamos los datos mínimos
548+
if (!detail.message) {
549+
detail.message = ""
550+
}
551+
if ( !detail.class) {
552+
detail.class = "solid-flash"
553+
}
554+
555+
// 1. Crear el elemento principal y aplicar TODAS las clases del backend.
556+
const toast = document.createElement("div");
557+
toast.className = detail.class; // Clave: Asigna el string completo de clases.
558+
559+
// 2. Crear el botón de cierre basado en una convención (Bulma o genérico)
560+
let closeButton;
561+
if (detail.class.includes('notification')) {
562+
// Si es una notificación de Bulma, usa su estructura.
563+
closeButton = document.createElement('button');
564+
closeButton.className = 'delete';
565+
} else {
566+
// Fallback para un sistema genérico o personalizado.
567+
closeButton = document.createElement('span');
568+
closeButton.className = 'toast-close-btn'; // Una clase genérica para darle estilo
569+
closeButton.innerHTML = '&times;'; // El símbolo 'x'
570+
}
571+
572+
// 3. Ensamblar el toast
573+
toast.appendChild(closeButton);
574+
// Añadir el mensaje como texto para evitar problemas de XSS.
575+
// El espacio inicial es para separarlo del botón.
576+
toast.appendChild(document.createTextNode(" " + detail.message));
577+
578+
// 4. Añadir el toast al contenedor
579+
container.appendChild(toast);
580+
581+
// 5. Lógica para cerrar y eliminar el toast
582+
const closeToast = () => {
583+
// Quitamos la clase de estado para iniciar la animación de salida
584+
toast.classList.remove('toast-show');
585+
586+
// Cuando la transición termine, eliminamos el elemento del DOM
587+
toast.addEventListener('transitionend', () => {
588+
if (toast.parentNode) {
589+
toast.parentNode.removeChild(toast);
590+
}
591+
}, { once: true });
592+
};
593+
594+
// 6. Asignar eventos
595+
closeButton.onclick = closeToast;
596+
setTimeout(closeToast, 5000); // Auto-cierre
597+
598+
// 7. Iniciar la animación de entrada añadiendo la clase de estado
599+
setTimeout(() => {
600+
toast.classList.add('toast-show');
601+
}, 10);
598602
};
599-
if (elem.dataset.alert) Q.flash(Q.eval(elem.dataset.alert));
603+
};
604+
605+
elem.addEventListener("flash", make_custom_toast_handler(elem), false);
606+
607+
Q.flash = function (detail) {
608+
elem.dispatchEvent(new CustomEvent("flash", { detail: detail }));
609+
};
610+
611+
if (elem.dataset.alert) {
612+
try {
613+
const alertDataStr = elem.dataset.alert.replace(/'/g, '"');
614+
const initialAlert = JSON.parse(alertDataStr);
615+
if (initialAlert && initialAlert.message) {
616+
Q.flash(initialAlert);
617+
}
618+
} catch (e) {
619+
console.error("Failed to parse initial flash data:", elem.dataset.alert, e);
620+
}
600621
}
601622
};
602623

docs/chapter-06.rst

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -338,15 +338,88 @@ The Flash helper handles the server side of them. Here is an example:
338338
@action('index')
339339
@action.uses(flash)
340340
def index():
341-
flash.set("Hello World", _class="info", sanitize=True)
341+
flash.set("Hello World", _class="solid-flash", sanitize=True)
342342
return dict()
343343
344-
and in the template:
344+
345+
Example with Bulma CSS classes for notifications.
346+
347+
.. code:: python
348+
@action('index2')
349+
@action.uses(flash)
350+
def index():
351+
flash.set("Hello World", _class="notification is-success", sanitize=True)
352+
return dict()
353+
354+
355+
To make the flash messages smooth and visually appealing, we need some CSS. This piece of CSS is included in ``no.css`` when using the default template.
345356
357+
.. code:: html
358+
359+
flash-alerts {
360+
position: fixed;
361+
top: 20px;
362+
right: 20px;
363+
z-index: 1050;
364+
display: flex;
365+
flex-direction: column;
366+
align-items: flex-end;
367+
gap: 10px;
368+
/* Prevent clicks from passing through the container if it is empty */
369+
pointer-events: none;
370+
}
371+
/* Allows clicking on child toasts */
372+
flash-alerts > * {
373+
pointer-events: auto;
374+
}
375+
376+
/* 2. Generic animation logic.
377+
This will work for ANY element you place inside flash-alerts. */
378+
379+
/* Initial state (hidden) of any notification */
380+
flash-alerts > * {
381+
opacity: 0;
382+
transform: translateX(100%);
383+
/* Defines the transition that will be applied when properties change */
384+
transition: all 0.4s ease-in-out;
385+
}
386+
387+
/* Final state (visible) when adding the .toast-show class */
388+
flash-alerts > *.toast-show {
389+
opacity: 1;
390+
transform: translateX(0);
391+
}
392+
393+
/* 3. (Optional) Style for the generic close button, in case you don't use Bulma. */
394+
.toast-close-btn {
395+
float: right;
396+
cursor: pointer;
397+
font-size: 1.5rem;
398+
font-weight: bold;
399+
line-height: 1;
400+
margin-left: 15px;
401+
opacity: 0.5;
402+
}
403+
.toast-close-btn:hover {
404+
opacity: 1;
405+
406+
}
407+
//Default backround
408+
.solid-flash {
409+
background-color: #d1d1d1; /* Light gray */
410+
padding: 5px;
411+
padding-left: 10px;
412+
border-radius: 10px;
413+
}
414+
415+
416+
And in the template:
346417
.. code:: html
347418
348419
<flash-alerts class="padded" data-alert="[[=globals().get('flash','')]]"></flash-alerts>
349420
421+
422+
350423
By setting the value of the message in the flash helper, a flash
351424
variable is returned by the action and this triggers the JS in the
352425
template to inject the message in the ``py4web-flash`` DIV which you
@@ -363,14 +436,17 @@ The client can also set/add flash messages by calling:
363436
364437
::
365438
366-
Q.flash({'message': 'hello world', 'class': 'info'});
439+
Q.flash({'message': 'hello world', 'class': 'solid-flash'});
440+
441+
442+
Remember to include ``utils.js`` in your ``layout.html``. This file is located in every new app in /static/js/utils.js, contains the logic to properly show and manage flash messages.
443+
444+
Py4web defaults to an alert class called ``solid-flash`` which is the default. Yet, there is nothing in py4web
445+
that hardcodes those names. You can use your own class names, or use any class you have available.
446+
447+
367448
368-
py4web defaults to an alert class called ``info`` and most CSS
369-
frameworks define classes for alerts called ``success``, ``error``,
370-
``warning``, ``default``, and ``info``. Yet, there is nothing in py4web
371-
that hardcodes those names. You can use your own class names.
372449
373-
You can see the basic usage of flash messages in the **examples** app.
374450
375451
The Session fixture
376452
-------------------

0 commit comments

Comments
 (0)