@fmcoldays/post-action
v0.4.24
Published
Ejecuta peticiones HTTP declarativas al hacer click en elementos con data-action-url (confirmación, loading, targets, notificaciones, descarga, redirección).
Maintainers
Readme
@fmcoldays/post-action
Ejecuta peticiones HTTP desde cualquier elemento HTML con data-action="true". Soporta confirmación, loading state, actualización de targets en el DOM, notificaciones, descarga de archivos y redirección. Sin dependencias externas.
npm install @fmcoldays/post-actionLas notificaciones y la confirmación las da
@fmcoldays/notify(dependencia interna, se instala sola).
Uso
Importar una vez al arrancar la app; el listener se registra solo:
import '@fmcoldays/post-action'Control manual (opcional):
import { registerPostAction } from '@fmcoldays/post-action'
const unregister = registerPostAction()
// unregister() para quitar el listenerFunciona en cualquier elemento (<button>, <a>, <form>, <select>, <div>…) mediante event delegation.
Atributos
Requerido
| Atributo | Tipo | Descripción |
|---|---|---|
| data-action | "true" | Activa todos los listeners en el elemento. Requerido en cualquier elemento que use atributos data-action-*. Sin él, data-action-submit, data-action-bind y cualquier otro atributo data-action-* son ignorados. |
Petición
| Atributo | Tipo | Default | Descripción |
|---|---|---|---|
| data-action-url | string (URL) | — | URL de la petición. Si se omite: en <form> usa el atributo action; en <a> usa el href. |
| data-action-method | string | "POST" | Método HTTP. Si se omite: en <form> usa el atributo method (default GET del browser); en <a> usa GET; en otros elementos usa POST. |
| data-action-body | string (JSON) | — | Body como JSON literal. En GET se añade al query string. |
| data-action-body-form | string (selector) | — | Selector de un <form> cuyo contenido se envía como FormData (POST) o query string (GET). |
| data-action-pick | string (JSON) | — | Mapa { "campo": "#selector" } resuelto al momento del click. GET → query string. POST → form-encoded si algún selector apunta a un <select multiple>, JSON en caso contrario. |
| data-action-csrf | "true" | — | Incluye el token CSRF en el header RequestVerificationToken. |
Forms:
<form data-action="true">intercepta el submit y envía los campos comoFormData. El token CSRF de ASP.NET (campo oculto__RequestVerificationToken) va incluido automáticamente.
Confirmación
| Atributo | Tipo | Default | Descripción |
|---|---|---|---|
| data-action-confirm | "true" | — | Muestra diálogo de confirmación antes de ejecutar. |
| data-action-confirm-title | string | "¿Estás seguro?" | Título del diálogo. |
| data-action-confirm-description | string | "Esta acción no se puede deshacer." | Mensaje del diálogo. |
Select autónomo (select[data-action="true"])
Un <select> con data-action="true" y data-action-url ejecuta una petición GET cada vez que su valor cambia, añadiendo automáticamente name=value a la URL. No necesita estar dentro de un <form>.
| Atributo | Default | Notas |
|---|---|---|
| data-action-method | "GET" | Diferente al default POST de otros elementos. |
| data-action-dismiss | false | Nunca cierra el modal padre, salvo que se ponga explícitamente. |
<!-- Al cambiar empresa → GET /ComandoSensor/VehiculosOptions?id_empresa=5 → llena #sel-vehiculo -->
<select name="id_empresa"
data-action="true"
data-action-url="/ComandoSensor/VehiculosOptions"
data-action-target="#sel-vehiculo"
data-action-silent="true">
<option value="1">Empresa A</option>
<option value="2">Empresa B</option>
</select>Select2: si el
<select>está inicializado con Select2, el bridge jQuery (select2:select/select2:unselect) actúa automáticamente cuando jQuery está disponible enwindow.$. No requiere configuración adicional ni jQuery como dependencia del paquete.
Submit automático (data-action-submit)
Dispara requestSubmit() en un form cuando el elemento cambia o recibe input. Útil para filtros que se envían solos sin botón.
| Atributo | Tipo | Default | Descripción |
|---|---|---|---|
| data-action-submit | string (selector) o vacío | — | Selector del form a enviar. Si está vacío usa el form padre. |
| data-action-submit-delay | number (ms) | 400 para texto, 0 para el resto | Debounce antes de disparar el submit. Aplica a cualquier tipo de elemento. |
<!-- Select dentro del form: dispara submit al cambiar -->
<select name="estado" data-action-submit>…</select>
<!-- Input de texto fuera del form: debounce 400ms por defecto -->
<input name="buscar" data-action-submit="#mi-form" />
<!-- Select fuera del form con delay personalizado -->
<select name="pagSize" data-action-submit="#mi-form" data-action-submit-delay="300">…</select>Para inputs con
min/max, el valor se clampea automáticamente antes de enviar y mientras el usuario escribe.
Clonar valor en un form (data-action-bind)
Crea un <input type="hidden"> dentro del form con el valor del elemento original. Se mantiene sincronizado en tiempo real. Útil cuando el control está fuera del <form>.
| Atributo | Tipo | Default | Descripción |
|---|---|---|---|
| data-action-bind | string (selector) | — | Selector del form donde se inyecta el clon. |
| data-action-bind-visible | "true" | — | Hace visible el clon (útil para debug). |
<!-- Select fuera del form: su valor viaja al form como hidden input -->
<select name="pageSize"
data-action-bind="#filter-form"
data-action-submit="#filter-form">…</select>
<!-- Input de texto fuera del form -->
<input name="pageNumber" type="text"
min="1" max="@pgTotal"
data-action-bind="#filter-form"
data-action-submit="#filter-form" />Si el form es reemplazado por AJAX (
data-action-target), el clon se recrea automáticamente.
Limpiar form antes de la petición
| Atributo | Tipo | Default | Descripción |
|---|---|---|---|
| data-action-clear-form | string (selector) | — | Llama a form.reset() en el form indicado antes de hacer la petición (después de la confirmación). Útil en botones que limpian filtros: primero resetea el form y luego hace la petición a la URL limpia. |
<button type="button"
data-action="true"
data-action-url="/Empleados"
data-action-target="#ajaxresult"
data-action-skeleton="#sk-table"
data-action-clear-form="#filter-form">
<span>filter_alt_off</span>
Limpiar
</button>Loading
| Atributo | Tipo | Default | Descripción |
|---|---|---|---|
| data-action-disable | "true" | — | Deshabilita el elemento (o el botón de submit) durante la petición. |
| data-action-loading-class | string | — | Clases CSS a añadir al trigger durante la petición (ej. "loading loading-spinner"). |
Resultado
| Atributo | Tipo | Default | Descripción |
|---|---|---|---|
| data-action-target | string (selector) | — | Selector del elemento donde se inyecta la respuesta HTML. |
| data-action-target-prop | "html" \| "text" \| "value" | "html" | Propiedad del target a actualizar. |
| data-action-download | "true" | — | Trata la respuesta como archivo descargable. El nombre se toma del header Content-Disposition. Silencioso por defecto. |
| data-action-silent | "true" | "false" | — | "true" suprime la notificación de éxito. "false" la fuerza aunque haya data-action-target. Sin poner: notifica solo si no hay target. Los errores se muestran siempre. |
| data-action-success-msg | string | "La operación se completó exitosamente." | Mensaje de éxito. |
| data-action-error-msg | string | "Ocurrió un error al realizar la operación." | Mensaje de error. |
Post-éxito
| Atributo | Tipo | Descripción |
|---|---|---|
| data-action-reload | "true" | Recarga la página tras éxito. |
| data-action-redirect | string (URL) | Redirige a esta URL tras éxito. |
| data-action-then | string (selector) | Hace click en este elemento tras éxito. |
Ejemplos
DELETE con confirmación y recarga
<button type="button"
data-action="true"
data-action-url="/api/items/42"
data-action-method="DELETE"
data-action-confirm="true"
data-action-confirm-title="¿Eliminar item?"
data-action-confirm-description="Esta acción no se puede deshacer."
data-action-reload="true">
Eliminar
</button>Form — guardar y refrescar tabla
<!-- action y method del form se usan como fallback, no hace falta data-action-url -->
<form action="/Clientes/Crear"
method="post"
data-action="true"
data-action-disable="true"
data-action-then="#filtro-form">
<input name="Nombre" />
<input name="Email" />
<button type="submit">Guardar</button>
</form>Form — confirmar antes de enviar
<form action="/Pedidos/Confirmar"
method="post"
data-action="true"
data-action-confirm="true"
data-action-confirm-title="Confirmar pedido"
data-action-confirm-description="¿Enviar este pedido? No se puede modificar después."
data-action-redirect="/Pedidos">
<!-- campos -->
<button type="submit">Confirmar</button>
</form>Enlace que inyecta HTML (GET desde href)
<!-- href se usa como URL y GET como method por defecto -->
<a href="/Clientes/42/Detalle"
data-action="true"
data-action-target="#panel-detalle"
data-action-silent="true">
Ver detalle
</a>GET que inyecta HTML en un target
<button type="button"
data-action="true"
data-action-url="/api/preview/42"
data-action-method="GET"
data-action-target="#preview-container"
data-action-silent="true">
Ver preview
</button>GET con parámetros dinámicos de inputs
<button type="button"
data-action="true"
data-action-url="/Reportes/Filtrar"
data-action-method="GET"
data-action-pick='{"fecha": "#fecha", "id": "#id_caja"}'
data-action-target="#resultado"
data-action-silent="true">
Filtrar
</button>POST con <select multiple> (multi-select)
Cuando alguno de los selectores en data-action-pick apunta a un <select multiple>, el body cambia automáticamente a application/x-www-form-urlencoded con claves repetidas, compatible con el model binding de ASP.NET MVC (List<string>, IEnumerable<int>, etc.).
<!-- Guarda asignaciones de un vehículo (operadores, tarjetas, estaciones son multi-select) -->
<button type="button"
data-action="true"
data-action-url="/Vehiculos/SaveDatosVehiculo"
data-action-method="POST"
data-action-pick='{"id_vehiculo":"#id_v_42","fld_litrosPermitidos":"#litros-42","id_ruta":"#ruta-42","fld_tarjetas":"#tarjetas-42","fld_empleados":"#operadores-42","fld_estaciones":"#estaciones-42"}'>
Guardar
</button>// ASP.NET MVC — bindea correctamente sin [FromBody]
[HttpPost]
public ActionResult SaveDatosVehiculo(
int id_vehiculo,
decimal fld_litrosPermitidos,
List<string> id_ruta,
List<string> fld_tarjetas,
List<string> fld_empleados,
List<string> fld_estaciones) { … }Descargar archivo Excel
<button type="button"
data-action="true"
data-action-url="/SensorData/GenerarReporte"
data-action-method="GET"
data-action-pick='{"id": "#id_caja", "fecha": "#fecha"}'
data-action-download="true">
Descargar Excel
</button>Toggle silencioso que actualiza el DOM
<button type="button"
data-action="true"
data-action-url="/api/toggle/42"
data-action-target="#status-badge"
data-action-silent="true"
data-action-loading-class="loading loading-spinner">
Activar
</button>Respuesta JSON esperada del backend
El flujo de éxito o error se determina en este orden:
- HTTP status — 4xx/5xx siempre es error.
- Campo
successbooleano (cualquier capitalización) —true= éxito,false= error. successRules/errorRulesconfiguradas globalmente — primera que aplique gana.- Sin ninguno de los anteriores — se confía en el HTTP status.
El único campo de mensaje reconocido es message (en cualquier capitalización: message, Message, MESSAGE…).
{ "message": "Guardado correctamente." }
{ "success": true, "message": "Guardado correctamente." }
{ "success": false, "message": "El correo ya existe." }En ASP.NET MVC:
// Éxito HTTP
return Ok(new { message = "Guardado correctamente." });
// Error HTTP
return BadRequest(new { message = "El correo ya existe." });
// Éxito/error en el body (HTTP 200 siempre)
return Json(new { success = true, message = "Guardado correctamente." });
return Json(new { success = false, message = "El correo ya existe." });Configuración global de reglas
Si tu backend usa una convención propia (ej. id == 200 como éxito), configúralo una vez al arrancar la app:
import { PostAction, fieldEquals, fieldNotEquals } from '@fmcoldays/post-action'
PostAction.configure({
successRules: [fieldEquals('id', 200)],
errorRules: [fieldNotEquals('id', 200)],
})Con @fmcoldays/all:
import { configure, fieldEquals, fieldNotEquals } from '@fmcoldays/all'
configure({
postAction: {
successRules: [fieldEquals('id', 200)],
errorRules: [fieldNotEquals('id', 200)],
},
})| Helper | Descripción |
|---|---|
| fieldEquals(field, value) | Éxito/error si data[field] === value. |
| fieldNotEquals(field, value) | Éxito/error si data[field] !== value. |
También puedes pasar funciones custom:
PostAction.configure({
successRules: [
(data) => 'code' in data ? data.code === 1 : undefined
],
})Cada función devuelve true, false, o undefined (no aplica, pasa a la siguiente regla).
Notas
- Para
<input type="checkbox">o<input type="radio">: el estado se togglea tras el éxito. data-action-reloadydata-action-redirectson mutuamente excluyentes;redirecttiene prioridad.data-action-downloadydata-action-targetson mutuamente excluyentes; con download no se inyecta HTML.data-action-clear-formaplica solo a elementos que disparan la petición directamente (data-action="true"+data-action-url). No aplica adata-action-submitnidata-action-bind.
API programática
| Export | Descripción |
|---|---|
| registerPostAction(root?) | Registra los listeners de click, submit, change e input. Devuelve una función para desregistrar. |
| handlePostActionClick(e) | Handler de click crudo. |
| handlePostActionSubmit(e) | Handler de submit crudo. |
| handlePostActionSelectChange(select) | Handler de change para select[data-action="true"]. Usado internamente; útil si integras tu propio bridge de eventos. |
| handleActionSubmitTrigger(e) | Handler de data-action-submit (change/input). |
| bindElements(root?) | Procesa data-action-bind en el root dado. Idempotente. |
| executeRequest(opts) | Ejecuta la petición HTTP a partir de un ActionOptions. |
| parseOptions(el) | Lee los data-action-* de un elemento a ActionOptions. |
| PostAction.configure(opts) | Configura reglas globales de éxito/error. |
| fieldEquals(field, value) | Helper — regla que evalúa data[field] === value. |
| fieldNotEquals(field, value) | Helper — regla que evalúa data[field] !== value. |
| Tipos | ActionOptions, ActionResult, PostActionConfig, SuccessRule, ErrorRule. |
Estructura
src/
├── index.ts # barril + auto-registro
├── register.ts # listeners (click, submit, change, input, post-action:updated)
├── handle-post-action-click.ts # orquestador para elementos clickeables
├── handle-post-action-submit.ts # orquestador para forms
├── handle-post-action-select-change.ts # orquestador para select[data-action="true"] + bridge Select2
├── handle-action-submit-trigger.ts # data-action-submit (debounce, clamp min/max)
├── bind-elements.ts # data-action-bind (clon hidden + re-bind tras AJAX)
├── types/ # ActionOptions, ActionResult
├── helpers/ # options, dom, notify, download, server-message
└── services/ # request.service (fetch + shared.buildHttpPayload)