mtt-simple
Version:
Biblioteca de componentes y helpers para desarrollo de formularios en SIMPLE digital
291 lines (259 loc) • 8.69 kB
JavaScript
const $ = require('jquery')
/**
* @typedef {Object} DatosUrlTramiteTypedef
* @property {boolean} editable - indica si el formulario actual permite la edición y actualización de datos
* @property {string} accion - ver | editar
* @property {number} idEtapa - id instancia de la etapa asociada al trámite
* @property {number} paso - número de paso dentro del flujo secuencial definido para la etapa
*/
/**
* [jquery] Obtiene el título del formulario desde el 'h1' o 'legend'
* dependiendo de la modalidad de vista/edición del formulario
* en el flujo
*/
function obtenerTituloFormulario() {
return $('fieldset legend, form > h1.title:first').text()
}
/**
* Otiene el valor del token CrossSiteRequestForgery desde los meta
*/
function obtenerTokenCSRF() {
return $('meta[name=csrf-token]').attr('content') || ''
}
/**
* Transforma el contenido del objeto a un string de modo de hacer factible
* su respaldo en los campos de texto. Como background, SIMPLE presenta
* problemas al definir el contenido de campos input con objetos JSON serializados
* @param {string | object} contenido - si es entregado un objeto, se prepara para guardar en el input
* sin el último caracter
* @returns string
* @example
* // returns "{ 'prop' : 'value' "
* deshidratar({ 'prop' : 'value' });
* @example
* // returns "[{ 'prop' : 'value' } "
* deshidratar([{ 'prop' : 'value' }]);
*/
function deshidratar(contenido) {
let c = contenido
if (typeof contenido !== 'string') {
const str = JSON.stringify(c)
c = str.substr(0, str.length - 1)
}
return c
}
/**
* Convierte el texto entregado en un objeto asumiendo que el
* contenido fue deshidratado usando la función deshidratar actual
* @param {string} contenido
* @returns {Object}
*/
function hidratar(contenido) {
if (typeof contenido !== 'string') {
throw new Error('tipo del contenido debe ser un string')
}
if (contenido === '') return null
if (!contenido.startsWith('{') && !contenido.startsWith('[')) {
return JSON.parse(contenido)
}
const c = contenido + (contenido.startsWith('{') ? '}' : ']')
return JSON.parse(c)
}
/**
* Obtener html completo del elemento incluyendo la propia etiqueta
*/
function _outerHTML(selector) {
return $('<div/>').append($(selector).clone()).html()
}
/** Transformar htmlEntities para agregarlas como contenido 'seguro' */
function _encode(str) {
return $('<div />').text(str).html()
}
/**
* Clonado de objetos usando JSON para solventar problemas de "deep copy"
* @param {Object} obj
* @returns {Object}
*/
function _clone(obj) {
return JSON.parse(JSON.stringify(obj))
}
/**
* Formato numérico para corregir problemática de locale string en formato español
* para cifras con 4 dígitos
* @param {number} num - número a ser transformado
*/
function _format(num) {
if (typeof num === 'number') {
return num.toLocaleString('en', {}).replace(',', '.')
} else {
return ''
}
}
/**
* Tranforma la notación de tipo variable lowerCamelCase de modo de hacerla legible en
* un texto tipo Title Case
* @example
* // returns 'Id Normativa'
* camelToTitleCase('idNormativa')
*/
function camelToTitleCase(str) {
return str
.replace(/[0-9]{2,}/g, match => ` ${match} `)
.replace(/[^A-Z0-9][A-Z]/g, match => `${match[0]} ${match[1]}`)
.replace(/[A-Z][A-Z][^A-Z0-9]/g, match => `${match[0]} ${match[1]}${match[2]}`)
.replace(/[ ]{2,}/g, _ => ' ')
.replace(/\s./g, match => match.toUpperCase())
.replace(/^./, match => match.toUpperCase())
.trim();
}
/**
* Obtiene la fecha indicada en formato 'yyyy-MM-dd'
* @param {Date} date
*/
function toStrDate(date) {
const y = date.getFullYear()
const m = (date.getMonth() + 1).toString().padStart(2, '0')
const d = date.getDate().toString().padStart(2, '0')
return `${y}-${m}-${d}`
}
/**
* Obtiene la fecha indicada en formato 'hh:mm:ss'
* @param {Date} date
*/
function toStrTime(date) {
const hh = date.getHours().toString().padStart(2, '0')
const mm = date.getMinutes().toString().padStart(2, '0')
const ss = date.getSeconds().toString().padStart(2, '0')
return `${hh}:${mm}:${ss}`
}
/**
* Obtiene la fecha indicada en formato 'yyyy-MM-dd hh:mm:ss'
* @param {Date} date
*/
function toStrDatetime(date) {
return `${toStrDate(date)} ${toStrTime(date)}`
}
/**
* Extrae desde la url la información de la Etapa actual
* @param {string} url
* @returns {DatosUrlTramiteTypedef}
*/
function etapaActual(url = window.location.href) {
try {
const regex = /\/etapas\/(ejecutar|ver|ejecutar_fin)\/(\d+)\/?(\d+)?/
const match = regex.exec(url)
if (!match) {
const msg = 'No fue posible identificar el id de etapa: ' + url
// throw new Error('No fue posible identificar el id de etapa: ' + url)
console.warn('etapaActual', msg)
return {}
}
const datosUrl = {
accion: match[1],
editable: match[1] === 'ejecutar',
idEtapa: match[2],
paso: match[1] !== 'ejecutar_fin' ? (match.length > 3 ? match[3] : 0) : null
}
return datosUrl
} catch (error) {
console.error('obtenerIdEtapa: ' + url, error)
return {}
}
}
/**
* @param {string} contenido - mensaje como contenido del alert
* @param {string} tipo - asociados a componente alert: primary, secondary, success, danger, warning, info, light, dark
*/
function agregarNotificacion(contenido, tipo) {
var tpl = $(`<div class="alert alert-${tipo || 'info'} alert-dismissible"><a href="#" class="close" data-dismiss="alert" aria-label="close">×</a>${contenido}</div>`)
$('.validacion').append(tpl)
$([document.documentElement, document.body]).animate({
scrollTop: $(".validacion").offset().top
}, 1000);
}
/**
* usando URI prueba si la el string entregado corresponde a uri
* válida para evitar 404 en carga de archivos usando variables
* @param {string} str
*/
function esUrlValida(str) {
try {
return new URL(str)
} catch (_) {
return null
}
}
/**
* @typedef {Object} CargarRegursoRespTypedef
* @property {number} code - 0 en caso de exito
* @property {string} message - exto descriptivo
*/
/**
* carga de forma programática de un recurso css o js
* @param {string} url - url del recurso para ser cargado
* @param {string} tipo - js, css
* @returns {Promise<CargarRegursoRespTypedef>}
*/
function cargarRecurso(url, tipo) {
return new Promise((resolve) => {
const resourceUrl = esUrlValida(url)
if (resourceUrl === null) {
resolve({ code: 1, message: `url '${url}' no válida para carga, no se inicia carga recurso` })
};
try {
const tipoArchivo = tipo || resourceUrl.pathname.split('.').pop();
console.log(`Cargar archivo '${tipoArchivo}' desde ${url}`);
let newNode = null;
switch (tipo) {
case 'js':
newNode = document.createElement('script');
newNode.setAttribute('src', url);
newNode.setAttribute('type', 'text/javascript');
break;
case 'css':
newNode = document.createElement('link');
newNode.setAttribute('href', url);
newNode.setAttribute('rel', 'stylesheet');
newNode.setAttribute('type', 'text/css');
break;
default:
return;
}
newNode.onload = function () { resolve({ code: 0, message: `recurso '${url}' cargado` }) };
newNode.onerror = function (err) { resolve({ code: 2, message: `Error al cargar recurso '${url}' : ${err.message}` }) };
document.getElementsByTagName("head")[0].appendChild(newNode);
} catch (error) {
resolve({ code: 2, message: `Error no controlado al cargar recurso '${url}' : ${error.message}` })
}
})
}
/**
* Entrega el identificador de campo SIMPLE en base al nombre del campo asignado
* @param {string} nombreCampo
* @returns 0 si el elemento no es encontrado
*/
function getIdCampo(nombreCampo) {
const $c = $(`[name=${nombreCampo}]`).first()
const $w = $c.closest('.campo.control-group')
return parseInt($w.attr('data-id') || '0', 10)
}
module.exports = {
obtenerTokenCSRF,
agregarNotificacion,
etapaActual,
obtenerTituloFormulario,
deshidratar,
hidratar,
_outerHTML,
_encode,
_clone,
_format,
camelToTitleCase,
toStrDate,
toStrTime,
toStrDatetime,
esUrlValida,
cargarRecurso,
getIdCampo
}