En este artículo compartiré tips y notas, códigos importantes y explicaciones en qué situaciones emplear al momento de desarrollar frontend y backend.
APIs en PHP
Para hacer que nuestra APIs permita conexiones de otros sitios y aplicaciones terceras, es necesario configurar los CORS, el siguiente es un ejemplo básico:
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Authorization, X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Allow: GET, POST, OPTIONS");
if ($_SERVER["REQUEST_METHOD"] == "OPTIONS") {
http_response_code(200);
exit;
}
Allow-Origin permite todos los origenes con el comodín *, en casos específicos donde solo un determinado sitio deba conectarse solo colocamos la IP ahí, el Allow-Headers del ejemplo lo utilizo para autenticaciones JWT, en Allow-Methods y Allow los métodos básicos GET y POST, me sucedió que OPTIONS lo requerí cuando quería conectar apps móviles con mi backend, adicionada de un IF porque el cliente al intentar conectarse con el backend realiza una petición de prueba de manera automática para validar OPTIONS.
Envío de archivos
Para el frontend es requerido añadir en el form donde irá el campo archivo, el atributo enctype=”multipart/form-data”, nuestro formulario debe tener el método POST, comparto un extracto HTML, en el ejemplo se utiliza Boostrap por ello verán clases relacionadas, un contenedor de la imagen y el control para subir el archivo.
<div class="mb-1">
<img src="img/producto-default.png" alt="Producto" id="imgProducto" class="img-fluid" style="cursor: pointer;">
</div>
<div class="mb-1">
<label for="filFPImagen">Imagen</label>
<input class="form-control" type="file" id="filFPImagen" name="filFPImagen" accept="image/jpeg, image/jpg, image/webp, image/png">
</div>
Con el atributo accept validamos el tipo de archivo, aunque no es garantía de que nadie podrá subir algo diferente, otros ejemplos para emplear en accept image/*, video/*, audio/*, .pdf, .docx. También podemos recibir varios archivos con el atributo multiple, en adición este mismo atributo puede ser empleado en los select.
Para enviar esta información recomiendo AJAX, comparto un ejemplo con JQuery https://code.jquery.com/jquery-4.0.0.min.js que puede ser añadido en nuestro JavaScript.
const formdata = new FormData()
const imagenes = $("#filFPImagen").get(0).files
for (let i = 0; i < imagenes.length; i++) {
formdata.append("filFPImagen[]", imagenes[i])
}
$.ajax({
url: API + ENDPOINT,
type: "post",
data: formdata,
contentType: false,
processData: false,
success: function (respuesta) {
}
})
Con JQuery para tener una vista previa de la imagen al momento de cargarla podemos añadir este código a nuestro JavaScript.
$("#filFPImagen").change(function (event) {
const imgProducto = $(this).get(0).files[0]
if (imgProducto) {
$("#imgProducto").attr("src", URL.createObjectURL(imgProducto))
}
})
Para el frontend muestro un ejemplo de PHP de como sería la recepción del formulario, este es un ejemplo cuando no es multiple, recomiendo que si se va a hacer con imágenes varias hacer debugs y prints para observar el comportamiento.
<?php
if (isset($_FILES["filFPImagen"])) {
$allowed_image_extension = array("image/jpeg", "image/jpg", "image/webp", "image/png");
$imagen = $_FILES["filFPImagen"];
$name = basename($imagen["name"][0]);
$type = $imagen["type"][0];
$size = $imagen["size"][0];
if (in_array($type, $allowed_image_extension)) {
$tmp_name = $imagen["tmp_name"][0];
$name_explode = explode(".", $name);
$extension = $name_explode[count($name_explode) - 1];
move_uploaded_file($tmp_name, "img/$name");
}
}
En el ejemplo suponemos tenemos una carpeta img dentro de la api, es mejor opción por tema optimización no guardar archivos en nuestra base de datos, si te preocupa la privacidad puedes colocar en la carpeta upload el siguiente htaccess.
Order Deny,Allow
Deny from all
Para bases de datos yo utilizo.
<FilesMatch "\.(db|sqlite|sqlite3|sql)$">
Require all denied
</FilesMatch>
Para la lectura por imagen, en el frontend usamos:
$.ajax({
url: API + ENDPOINT,
method: "GET",
xhrFields: {
responseType: "blob"
},
cache: false,
success: function (data) {
try {
let url = URL.createObjectURL(data)
$("#imgProducto").attr("src", url)
}
catch (error) {
console.error(error)
}
}
})
Y en el backend:
<?php
$ruta = "img/test.png";
if (!file_exists($ruta)) {
exit;
}
$mime = mime_content_type($ruta);
header("Content-Type: $mime");
header("Content-Length: " . filesize($ruta));
readfile($ruta);
Para la lectura por varias imagenes en el backend.
<?php
foreach ($rutas as $x => $ruta) {
if (!file_exists($ruta)) {
continue;
}
$rutas[$x]["mime"] = mime_content_type($ruta);
$contenido = file_get_contents($ruta);
$rutas[$x]["base64"] = base64_encode($contenido);
}
Y el array puede ser impreso en JSON e impreso en imagenes del frontend de la siguiente forma:
`<img src="data:${ruta.mime};base64,${ruta.base64}">`
Para otra edición queda pendiente plasmar un ejemplo con la imagen en base de datos.
Media Queries en CSS
Aunque Bootstrap provee varias clases que nos pueden ayudar a diseñar el responsive siempre requeriremos aplicar reglas específicas en nuestros elementos HTML, este es un ejemplo de una Media Query:
/** reglas por default */
@media only screen and (max-width: 768px) {
/** reglas si el ancho está abajo de 768px */
}
Envio de fechas y horas / Flatpickr
HTML nos brinda un input date útil, la compatibilidad y experiencia en diferentes navegadores puede variar, entonces busquemos estandarizar con el uso de la librería Flatpickr, que además dara soporte al manejo de experiencia frontend y backend, te dejo las CDNs para que las descargues:
- https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.13/themes/dark.min.css
- https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.13/flatpickr.min.js
- https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.13/l10n/es.min.js
Para el frontend en nuestro HTML hacemos nuestro input así:
<input type="text" class="form-control" id="dpFPFechaRegistro" name="dpFPFechaRegistro">
En nuestro JavaScript:
const configFechaHora = {
locale: "es",
weekNumbers: true,
enableTime: true,
minuteIncrement: 5,
altInput: true,
altFormat: "d/M/Y G:i K",
dateFormat: "Y-m-d H:i:s",
time_24hr: false
}
let flatpickrFechaRegistro = $("#dpFPFechaRegistro").flatpickr(configFechaHora)
Para colocar una determinada fecha y hora:
flatpickrFechaRegistro.setDate("2026-05-14 00:21:00")
El backend sigue con normalidad con la propiedad altFormat especificamos como se va a enviar la fecha al servidor.
Cajas de texto con autocompletado / JQuery UI
JQuery cuenta con una librería para Interfaces de Usuario llamada JQuery UI, que dispone de variedad de widgets entre ellos el autcompletado también conocido como combo editable, para añadir esta librería agregamos las siguientes CDNs.
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.14.1/themes/base/jquery-ui.min.css" integrity="sha512-TFee0335YRJoyiqz8hA8KV3P0tXa5CpRBSoM0Wnkn7JoJx1kaq1yXL/rb8YFpWXkMOjRcv5txv+C6UluttluCQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.14.1/jquery-ui.min.js" integrity="sha512-MSOo1aY+3pXCOCdGAYoBZ6YGI0aragoQsg1mKKBHXCYPIWxamwOE7Drh+N5CPgGI5SA9IEKJiPjdfqWFWmZtRA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
En nuestro código JavaScript solo añadimos el siguiente código.
function searchAutocompleteProductos() {
$("#cboProductos").autocomplete("search", $("#cboProductos").val())
}
function autocompleteProductos() {
if ($("#cboProductos").val().length < 4) {
return
}
$.get("api/?autocompleteProductos", {
text: $("#cboProductos").val()
}, function (productos) {
$("#cboProductos").autocomplete("option", "source", productos)
searchAutocompleteProductos()
})
}
$("#cboProductos")
.on("input", autocompleteProductos)
.focus(searchAutocompleteProductos)
$("#cboProductos").autocomplete({
source: [],
minLength: 0,
_renderItem: function (ul, item) {
return $("<li>")
.attr("data-value", item.value)
.attr("data-label", item.label)
.append(item.label)
.appendTo(ul);
},
select: function (event, ui) {
const item = ui.item
event.preventDefault()
},
focus: function (event, ui) {
const item = ui.item
event.preventDefault()
}
})
En el backend solo debemos regresar un arreglo de objetos con las propiedades value y label, podemos añadir más propiedades, solamente en el script en el método renderItem agregamos esas propiedades en los data atributos, los método select y focus nos permitirán obtener los items con las proiedades que hayamos incluido.
Validaciones / JQuery Validate
JQuery validate es un plugin de JQuery el cual nos permite validar nuestros formularios, aplicando reglas y manejar mensajes de error, para añadirlo colocamos las siguientes CDNs.
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.5/jquery.validate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.5/additional-methods.min.js"></script>
Podemos personalizar globalmente nuestros mensajes de error por defecto de la siguiente forma.
$.extend($.validator.messages, {
required: "Llena este campo",
number: "Solo números",
digits: "Solo números enteros",
min: $.validator.format("No valores menores a {0}"),
max: $.validator.format("No valores mayores a {0}"),
minlength: $.validator.format("Mínimo {0} caracteres"),
maxlength: $.validator.format("Máximo {0} caracteres"),
rangelength: $.validator.format("Solo {0} caracteres"),
equalTo: "El texto de este campo no coincide con el anterior",
date: "Ingresa fechas validas",
email: "Ingresa un correo electrónico valido"
})
Para aplicar las validaciones solamente usamos el metodo validate en el formulario de nuestro interes para validar, en el código de ejemplo te he añadido errorPlacement una propiedad util para configurar el lanzamiento del mensaje, en casos de contraseñas donde se confirman y están en la misma fila en algunos diseños saturan la fila y luce bastante mal, por lo que es mejor sacar el mensaje del elemento padre e insertarlo por fuera.
const validatorForm = $("#form").validate({
errorClass: "is-invalid",
validClass: "is-valid",
rules: {
idCampo: {rule: value}
},
errorPlacement: function(error, element) {
if (element.get(0).type == "password") {
error.insertAfter(element.get(0).parentNode)
}
else {
error.insertAfter(element)
}
}
})
Para llamar la validación y revisar el estado de validación puedes utilizar el método valid ya sea en el formulario o bien en la instancia de validate.
$("#form").valid()
if (!validatorForm.valid()) {
return
}
Para consultar todas las validaciones disponibles puedes acceder a la documentación oficial https://jqueryvalidation.org/.
Función debounce
Las funciones debounce resultan utiles para aplicar una espera hasta encontrar la última función que respeta un retraso definido, imaginemos una búsqueda automática cada que un usuario escribe en una caja de texto, esto perfectamente se implementa con el evento input, pero sucede que input se llamará cada que alguien escriba un caracter, por ejemplo si alguien busca “Hola”, se harán 4 peticiones de búsqueda H, Ho, Hol, Hola, al usar debounce se podría agregar un delay de 100 milisegundos, de esta manera no se ejecuta H, ni Ho, ni Hol, solo Hola o lo anterior si la espera de 100 milisegundos se cumple.
function debounce(fun, delay) {
let timer
return function (...args) {
clearTimeout(timer)
timer = setTimeout(function () {
fun.apply(this, args)
}, delay)
}
}
function imprimir(texto) {
console.log(texto)
}
const imprimirConDebounce = debounce(imprimir, 100)
imprimirConDebounce("H")
imprimirConDebounce("Ho")
imprimirConDebounce("Hol")
imprimirConDebounce("Hola")
AngularJS
Para integrar AngularJS (La versión simple de Angular, descontinuada) en nuestro frontend, recomiendo trabajar con las siguientes CDNs para el tema de uso básico de funciones y rutas.
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js" integrity="sha512-KZmyTq3PLx9EZl0RHShHQuXtrvdJ+m35tuOiwlcZfs/rE7NZv29ygNA8SFCkMXTnYZQK2OX0Gm2qKGfvWEtRXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-route/1.8.3/angular-route.min.js" integrity="sha512-y1qD3hz/IAf8W4+/UMLZ+CN6LIoUGi7srWJ3r1R17Hid8x0yXe+1B5ZelkaL1Mjzedzu0Cg3HBvDG02SAgSzBw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Aparte de agregar las CDNs en nuestro HTML, también debemo indicar la app con la directiva ng-app y al mismo tiempo la vista con la directiva ng-view.
<html lang="es" ng-app="angularjsApp">
<div ng-view></div>
En nuestro JavaScript va esta simple estructura para comenzar cualquier proyecto.
const app = angular.module("angularjsApp", ["ngRoute"])
app.config(function ($routeProvider, $locationProvider) {
$locationProvider.hashPrefix("")
$routeProvider
.when("/", {
templateUrl: "views/modulo.html",
controller: "moduloCtrl"
})
.otherwise({
redirectTo: "/"
})
})
app.run(["$rootScope", "$location", function($rootScope, $location) {
$rootScope.$on("$routeChangeSuccess", function (event, current, previous) {
const path = current.$$route.originalPath
$rootScope.path = path
})
$rootScope.$on("$routeChangeError", function () {
})
$rootScope.$on("$routeChangeStart", function (event, next, current) {
})
}])
app.controller("moduloCtrl", function ($scope) {
})
Para trabajar con parametros a traves de las rutas, incluimos $routeParams en nuestro controlador, en la ruta definimos parametros con /:param .