Entrada sobre Tcl

Formularios en Tcl/Tk

Hay varias maneras de mostrar los datos de nuestro formulario Tcl/Tk. Una de ellas es crear una etiqueta para asignarle el valor del campo entry a su atributo -text, y todo ello lo hará un procedimiento que llamaremos «Enviar». Éste configura el valor -text de la etiqueta .etiqueta obteniendo el valor del contenido escrito en el elemento del formulario.

# Formulario
entry .campo -textvariable "Introduce texto aquí"
button .boton -text Enviar -command { Enviar }
pack .boton .campo

# Etiqueta vacía
label .etiqueta
pack .etiqueta

# Procedimiento
proc Enviar { } {
set texto [.campo get]
.etiqueta configure -text $texto
}

Tcl/Tk y el Wish

Para la mayoría de ejercicios de Tcl/Tk guardamos el código en achivos de texto con la extensión .tcl  en la misma carpeta donde tengo la aplicación. Desarrollando un proyecto guardo los archivos Tcl en una carpeta del mismo nombre que el proyecto, y  creamos «versiones». Por ejemplo, la carpeta formulario_de_prueba podría contener los archivos formulario1.tcl, formluario2.tcl…. 

Para ejecutar un script Tcl prefiero utilizar Wish y el comando source. Sobre todo porque me pongo «pistas» en el código, para probar que el flujo de información es correcto; hago que el ejercicio me devuelva resultados en consola con puts, una forma sencilla de hacer pequeñas comprobaciones. Para depurar los errores es mejor usarel comando catch. Algunas funciones tienen su propio «comportamiento» para manejar los errores (como las de MySQLTcl).

Menú en Tcl/Tk de SpectTcl

Los menús son widgets a menudo imprescindibles para nuestras aplicaciones Tcl. El menú que mostramos a continuación es una simplificación de otro menú, con muchos más elementos, disponible en la librería de ejemplos de SpecTcl: una aplicación hecha en Tcl/Tk para hacer aplicaciones de un modo «visual», proporcionándonos herramientas para generar archivos Tcl muy rápidamente y ahorrándonos la tarea de escribir todo el código de los widgets. Podemos consultar los atributos de un widget y modificarlos de forma sencilla, y ver los cambios sin necesidad de cerrar la aplicación, además de muchas otras cosas.

En otras anotaciones y ejemplos Tcl de este blog hemos usado el comando pack para «dibujar» los elementos en la pantalla. Nótese que en este ejemplo se usa el comando grid.

# Menú Tcl/Tk

# Contenedor del menú
set root "."

# Elemento 1
###############
menubutton .mbOption
-indicatoron 1
-menu ".mbOption.m"
-relief raised
-textvariable mbOption_choice

# Hijo de Elemento 1
menu .mbOption.m
-background green
-tearoff 0

catch {
.mbOption.m configure
-font -*-Helvetica-Bold-R-Normal-*-*-120-*-*-*-*-*-*
}

# Hijos de Hijo de Elemento 1
.mbOption.m add radiobutton
-variable mbOption_choice
-label Artichoke
-underline 0

.mbOption.m add radiobutton
-variable mbOption_choice
-label {Brussel Sprout}
-underline 0

.mbOption.m add radiobutton
-variable mbOption_choice
-label Carrot
-underline 0

# Elemento 2
###############
menubutton .mbFruits
-menu ".mbFruits.fruitsMen"
-relief raised
-text Fruits

# Hijo de Elemento 2
menu .mbFruits.fruitsMen
-tearoff 0
catch {
.mbFruits.fruitsMen configure
-font -*-Helvetica-Bold-R-Normal-*-*-120-*-*-*-*-*-*
}

# Hijos de Hijo de Elemento 2
.mbFruits.fruitsMen add command
-command {puts Apple}
-label Apple
-underline 0

.mbFruits.fruitsMen add command
-command {puts Banana}
-label Banana
-underline 0

.mbFruits.fruitsMen add command
-command {puts Cucumber}
-label Cucumber
-underline 0

.mbFruits.fruitsMen add command
-command {puts Date}
-label Date
-underline 0

# Para representar el menu, geometry management
grid .mbOption -in $root -row 2 -column 1
grid .mbFruits -in $root -row 2 -column 2

# Comportamiento de la redimensión
grid rowconfigure $root 1 -weight 0 -minsize 30 -pad 0
grid rowconfigure $root 2 -weight 0 -minsize 30 -pad 0
grid columnconfigure $root 1 -weight 0 -minsize 30 -pad 0
grid columnconfigure $root 2 -weight 0 -minsize 30 -pad 0

# Opción seleccionada por defecto del Elemento 1
uplevel #0 set mbOption_choice Banana

Las listas en Tcl. Estructuras de datos.

Las listas son la estructura base de Tcl. Dos ejemplos de uso de listas a continuación, donde explicamos qué hace cada comando y comentamos en el código la respuesta de la consola.

Ejemplo 1: Creamos una lista con el comando list (hay más fomas de crear listas), que contiene 3 palabras. Mostramos su longitud con el comando llength. Mostramos el elemento de índice 1 de la lista con el comando lindex. Creamos una variable que contiene una palabra. Unimos esa variable a la lista como su último elemento, con el comando append, también devuelve la cadena modificada.

set Listauno [list "hola" "que" "tal"]
# devuelve hola que tal
llength $Listauno  
# devuelve 3
lindex $Listauno 1 
# devuelve que
set estas "estas"
 # devuelve estas
lappend Listauno $estas 
# devuelve hola que tal estas
llength $Listauno  
# devuelve 4

Ejemplo 2:Igual que el anterior, sólo que partimos de una lista vacía. Útil en algunos casos.

set Listados [list]
llength $Listados  
# devuelve 0
set algo "algo" 
# devuelve algo
lappend Listados $algo
 # devuelve algo
llength $Listados  
# devuelve 1
%

MySQLTcl: instalación e introducción

Instalación de MySQLTcl

Cómo instalar la aplicación MySQLTcl para poder manejar bases de datos con el lenguaje MySQL con nuestros scripts Tcl. Esta instalación es en Windows XP y XAMPP. Puede que sea necesario consultar: XAMPP, instalar un servidor local. Para saber más (o casi todo) sobre MySQL, MySQL con clase.

– Descargar MySQLTcly los archivos DLL (binarios) de la página del proyecto y descomprimir los archivo descargados en la carpeta /lib de la instalación de Tcl (si se instaló Tcl en C:/Tcl, los archivos deberán estar en C:/Tcl/lib). Todo lo necesario en la página del proyecto: http://www.xdobry.de/mysqltcl/

– Para comprobar que todo ha ido bien basta con abrir el Wish y teclear
package require mysqltcl
y nos devolverá la versión de MySQLTcl.

Introducción a MySQLTcl

Conexión

Aquí sólo les dejo el código necesario para conectarse a MySQLTcl, con alguna modificación por mi parte.

# paquete necesario
package require mysqltcl
# variable global de la conexión
global mysqlstatus
# valores de mysql
set port {3306}
set host {127.0.0.1}
set user {root}
set password {password}
set db {tcl_fact4}


# Capturamos el error (si lo hubiera) de la conexion mysql
# catch devuelte 1 si hay error en mysqlconnect
# En ese caso mostramos el comando que falló y el nº del error mysql


if [catch {mysqlconnect -host $host -port $port -user $user -password $password -db $db} mysql_handler] {
puts $mysqlstatus(command)
puts "error mysql nº"
puts $mysqlstatus(code)
}


# cerrar la conexion
mysqlclose $mysql_handler

Realizar consultas

En el enlace de más arriba también se explica cómo realizar consultas MySQL como select, update, insert… Algunos ejemplos de funciones básicas:

# hacer un select ordenado por el campo "id"
mysqlsel $mysql_handler {select campo1,campo2,...  from tabla order by id asc} -list
# insert
mysqlexec $mysql_handler {insert into tabla (campo1, campo2) values ('valor1','valor2')}
# delete
mysqlexec $mysql_handler {delete from empresas where id = 6}
# create table
set nuevatabla "CREATE TABLE nuevatabla (
ID INTEGER PRIMARY KEY,
type INT NOT NULL,
Nombre TEXT,)"
set creandonuevatabla [::mysql::exec $mysql_handler $nuevatabla]

Compilar script Tcl en un archivo .exe

Extracto y adaptación del texto titulado «How to compile a Tcl script and an icons directory into an exe (in Windows)» en el texto completo en inglés.

En el ejemplo, tenemos un archivo llamado Ejemplo.tcl con el código de nuestra aplicación y un icono mi_icono.ico situado en C:Ejemplo. No olvidar hacer las adaptaciones necesarias. Estas instrucciones funcionan en Windows.

Para este ejemplo se necesita descargar el programa freewrap.exe, y copiarlo en el directorio donde está el archivo Tcl, para tener dentro de la carpeta /Ejemplo los archivos Ejemplo.tcl, mi_icono.ico y freewrap.exe

En «Inicio/Ejecutar», escribe «cmd» y pulsa «Enter» para abrir la consola de comandos. Escribe lo siguiente:

cd Ejemplo
freewrap Ejemplo.tcl -i tu_icono.ico

Por si no tenemos icono, el fragmento -i tu_icono.ico es opcional. Los iconos deben tener las siguientes características:

– 16×16 16 colors
– 32×32 16 colors
– 32×32 2 colors

Elije la puerta correcta – Tcl y matemáticas

En un concurso de televisión se le ofrece al concursante la posibilidad de elegir una entre 3 puertas para quedarse lo que hay tras ella. El presentador le informa de que sólo una de ellas tiene un buen regalo (un apartamento en Torrevieja, un coche…), mientras que las otras dos están vacías. El concursante opta por una y el presentador (que conoce exactamente dónde está el regalo) abre lo que hay detrás de una de las otras dos puertas no elegidas por el concursante, donde no está el regalo. Luego le ofrece al concursante la opción de cambiar su decisión inicial eligiendo la otra puerta aún no abierta. ¿Qué debe hacer el concursante? Es decir, ¿debe cambiar de parecer sobre la puerta que eligió originalmente, o no?

El siguiente fragmento de código simula un concurso repetido 600 veces, donde se analiza qué pasaría sí el jugador cambiase o no de opinión.

# Ponemos unos contadores a cero
set sc 0
set nc 0
# Concursaremos i veces, en este ejemplo 600
for {set i 0} {$i < 600} {incr i} {
# Ponemos un premio detrás de una puerta, escogemos un numero entero aleatorio entre 0 y 2
set premio [expr [expr round([expr [expr rand()]*10])]%3]
# El jugador escoge su puerta, escogemos un numero entero aleatorio entre 0 y 2
set eleccion [expr [expr round([expr [expr rand()]*10])]%3]
# El presentador abre una de las otras dos puertas, donde no está el premio
# Si coincide la elección del jugador con el premio,
# aumentamos en 1 un contador inicial, y en ese caso no debería  cambiar de opinión.
# En caso contrario, aumentamos el 1 el otro contador.
if {$premio==$eleccion} {
set nc [expr $nc+1]
} else {
set sc [expr $sc+1]
}
set total [expr $sc+$nc]
}
# Porcentaje de aciertos cambiando y sin cambiar
set asc [expr [expr 100*$sc]/$total]
set anc [expr [expr $nc*100]/$total]
# Resultado
concat "La probabilidad de ganar cambiando de parecer es de aprox. un " $asc "%, y sin cambiar de parecer de un " $anc"%."

El siguiente código genera un procedimiento con dos argumentos: el número de puertas y el número de veces que se concursa. Sólo hay que hacer algunos cambios en el código anterior.

proc concurso {numeropuertas veces} {
# Ponemos unos contadores a cero
set sc 0
set nc 0
# Concursaremos i veces, establecidas como argumento del procedimiento
for {set i 0} {$i < $veces} {incr i} {
# Ponemos un premio detrás de una puerta
set premio [expr [expr round([expr [expr rand()]*10])]%$numeropuertas]
# El jugador escoge su puerta
set eleccion [expr [expr round([expr [expr rand()]*10])]%$numeropuertas]
# El presentador abre una de las otras puertas, donde no está el premio
# Si coincide la elección del jugador con el premio,
# aumentamos en 1 un contador inicial, y en ese caso no debería cambiar de opinión.
# En caso contrario, aumentamos el 1 el otro contador.
if {$premio==$eleccion} {
set nc [expr $nc+1]
} else {
set sc [expr $sc+1]
}
set total [expr $sc+$nc]
}
# Porcentaje de aciertos cambiando y sin cambiar
set asc [expr [expr 100*$sc]/$total]
set anc [expr [expr $nc*100]/$total]
# Resultado
concat La probabilidad de ganar cambiando de parecer es de aprox. un $asc%, y sin cambiar de parecer de un $anc% (con $numeropuertas puertas, repetido $veces veces).
}

Lo único que se necesita para resolver el problema es escribir un código similar al siguiente, después de crear el procedimiento:

concurso 3 100

Podemos jugar más veces para garantizar un resultado más «fiable», o incluso con más de tres puertas.

Ejercicio Javascript 1

Parte 1: Obtener la lista de elementos de etiqueta ‘p’ y mostrarlos en un alert de JavaScript.

// lista de párrafos en un array
var pes = document.getElementsByTagName('p');

// recorremos el array con un for


var texto = "";
for (i = 0 ; i < pes.length ; i++) {

  //Aquí lo mostramos en consola
  console.log(pes[i]);


  // sin el método innerHTML no podríamos ver el contenido
  texto = texto + pes[i].innerHTML; 
}
alert(texto);

Parte 2: Añadir un nuevo elemento p con el texto “Texto del nuevo párrafo” y que sea hijo del body.

// Creamos el párrafo y su texto
let p = document.createElement("p");
let pTexto = document.createTextNode("Ejemplo");

// Añadimos el texto al párrafo
p.appendChild(pTexto);

// Añadimos el párrafo como hijo del body
document.body.appendChild(p);

https://www.w3schools.com/jsref/dom_obj_htmlcollection.asp

Laravel: Actualizaciones y glosario (parte 22)

Debido a las actualizaciones recientes (a fecha de esta entrada) necesitamos actualizar nuestra base de conocimientos. Es muy útil tener un glosario a modo de resumen de ciertas definiciones. Además, los pasos para crear un proyecto desde cero han variado ligeramente.

Crear un proyecto

Cuando creamos un proyecto nuevo de Laravel 9 con los comandos habituales, elegiremos en más de una ocasión usar Jetstream. En el momento de ejecutar el comando de instalación de Laravel con Jetstream, la consola de comandos nos preguntará si queremos usar Liveware o Inertia. Al elegir Liveware, se creará el proyecto con esas opciones. Se ejecutará el comando «npm install» (instalando todas las dependencias de npm dentro del proyecto) seguido de «npm run dev», que creará un nuevo servidor de Vite.

Cuando necesitemos subir el proyecto tendremos que hacer uso del comando «npm run build» para que se cree el manifiesto donde estará compilado todo nuestro js y css. El servidor de Vite ya no será necesario, pero el proyecto esperará tener los manifiestos.

Glosario

Jetstream

Jetstream es un kit de inicio de aplicaciones diseñado para Laravel y proporciona la implementación para el inicio de sesión, el registro, la verificación de correo electrónico, la autenticación de dos factores, la gestión de sesiones, la API a través de Laravel Sanctum y las funciones opcionales de gestión de equipos de su aplicación.

A la hora de trabajar con Jetstream hayq ue escoger entre dos interfaces: Livewire e Inertia.js. La elección depende del lenguaje de los templates.

Liveware

Laravel Livewire es una librería que simplifica las interfaces de Blade y las hace más modernas, reactivas y dinámicas.

https://laravel-livewire.com/

Al usar Livewire, se puede elegir qué partes de la aplicación serán un componente de Livewire, mientras que el resto de la aplicación se puede representar como las plantillas Blade tradicionales.

Vite

Vite es un compilador que se mantendrá a la escucha de cualquier cambio en nuestro código de javascript y css, y creará los assets que incluiremos finalmente en nuestra página.

Para poder trabajar en el entorno local y cargar los estilos de nuestra aplicación, necesitamos tener el servidor encendido. Para ello ejecurtaríamos el comando «npm run dev» y ya veríamos los estilos.

Laravel ahora usa Vite: https://www.youtube.com/watch?v=4gxxWTe3pVA&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=32

Bootstrap

Cómo usar Bootstrap en Laravel 9 usando Vite: https://www.youtube.com/watch?reload=9&v=D_-i6ZiWHuQ

Bootstrap es un framework front-end utilizado para desarrollar aplicaciones web y sitios mobile first, o sea, con un layout que se adapta a la pantalla del dispositivo utilizado por el usuario. El propósito del framework es ofrecerle al usuario una experiencia más agradable cuando navega en un sitio.

Por esta razón, tiene varios recursos para configurar los estilos de los elementos de la página de una manera simple y eficiente, además de facilitar la construcción de páginas que, al mismo tiempo, están adaptadas para la web y para dispositivos móviles.

¿Cómo funciona Bootstrap?
Bootstrap está constituido por una serie de archivos CSS y JavaScript responsables de asignar características específicas a los elementos de la página.

¿Cómo configurar y usar Bootstrap?
Hay diferentes formas de configurarlo o estructurarlo para usarlo en una aplicación web. Sin embargo, para que funcione correctamente, es necesario agregar las bibliotecas JQuery y Popper.js, necesarias para la ejecución de algunos componentes de Bootstrap.

Para comenzar a usar Bootstrap en una página, es necesario agregar las referencias de los principales archivos del framework en la página principal de la aplicación.

Tailwind

Tailwind es un framework CSS. Es Brinda un conjunto único de clases de utilidad que hace que el proceso de desarrollo sea muy fácil y da como resultado un diseño único. El código y el diseño web se puede personalizar libremente.

Instalar el framework Tailwind no es tan simple como instalar un framework bootstrap, requiere un poco de configuración con laravel Mix.

Npm

https://docs.npmjs.com/cli/v6/commands/npm

npm es el administrador de paquetes para la plataforma Node JavaScript. Coloca los módulos en su lugar para que el nodo pueda encontrarlos y gestiona los conflictos de dependencia de manera inteligente.

Usualmente npm se necesita porque el proyecto requiere instalaciones.

Laravel Mix (antes de Vite)

https://laravel.com/docs/8.x/mix

Laravel Mix proporciona una API fluida para definir los pasos de compilación de Webpack utilizando varios procesadores de CSS y JavaScript comunes.

El objetivo es, entre otras cosas, procesar todo nuestro código css, minificarlo y combinarlo en un solo archivo. Lo mismo para el código javascript, minificar, ofuscar y combinar todo el código en un solo archivo. Con laravel mix nuestras páginas web son mas seguras y rápidas.

Laravel mix está optimizado para usar con Laravel, pero se puede utilizar en cualquier aplicación web, aunque no se use Laravel.

Laravel: Bootstrap con Jetstream – capítulo de transición (parte 22)

¿Qué es Jetstream?

Directamente de la documentación de Laravel:

Jetstream provides the implementation for your application’s login, registration, email verification, two-factor authentication, session management, API…

Instalación

Nuevo proyecto con Jetstream

Vamos a la consola de Gitbash, y dentro de nuestro directorio htdocs de XAMPP la línea de más abajo para un nuevo proyecto. A este lo llamaremos «bootstrap».

Tendremos que elegir entre liveware y inertia. Si estás familiarizado con blade, es más sencillo liveware. Con inertia es mejor si estás más familiarizado con javascript, por ejemplo Vue. Lo haremos con liveware.

laravel new bootstrap --jet

Base de datos

El siguiente paso es crear una base de datos llamada bootstrap (así la llamaremos) dentro de phpmyadmin.

Desde la misma consola de Gitbash, podemos ir a nuestro proyecto:

cd bootstrap

Y ejecutar las migraciones:

php artisan migrate

Iniciar el server

Inicializamos el servidor así:

php artisan serve

Se nos informará que podemos acceder mediante una url como: http://127.0.0.1:8000.

Instalar Bootstrap

Abrimos nuestro proyecto desde VSC. En la carpeta principal podemos encontrar el archivo de configuración de tailwind, pero nosotros queremos bootstrap, con lo que instalaremos un paquete llamado Jetstrap.

La siguiente línea la hemos encontrado en el repositorio de Github https://github.com/nascent-africa/jetstrap, en el readme.

composer require nascent-africa/jetstrap --dev

Ahora necesitamos ejecutar el siguiente comando para liveware:

php artisan jetstrap:swap livewire

Nos aparece el siguiente mensaje una vez terminado: Please execute the «npm install && npm run dev» command to build your assets para que los estilos se compilen. Ya estaremos viendo que el archivo de configuración de tailwind no existe, y que en la carpeta resource/sass están las variables y estilos de bootstrap, y que el archivo webpack.mix.js todo se va a compilar con sass y que buscará el archivo de la carpeta sass que se llama app.scss.

Ejecutamos por orden:

npm install
npm run dev

Al ejecutar el segundo comando nos hemos encontrado un error de la versión de node que se explica en el vídeo siguiente:

Descargamos el instalador del node más actualizado para nuestro sistema operativo y los instalamos. Con esto hemos podido ejecutar el comando, pero los estilos no se cargan y probamos a empezar todo de nuevo, esta vez con node actualizado a la última versión.

Y ahora nos encontramos con otro «problema», con una actualización Laravel ahora usa Vite, con lo que los pasos anteriores no funcionan como esperábamos.

IMPORTANTE: Para los estilos es indispensable conocer el funcionamiento de Vite, que es una mejora de Laravel con respecto a las versiones anteriores. Por lo tanto, el uso y la instalación de Tailwind o de Bootstrap va a estar condicionado por el funcionamiento de Vite.

Recursos:

Enlace de Youtube: https://www.youtuhttps://www.youtube.com/watch?v=v-R5FLGAB58&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=30

Laravel: Envío de mails con Formulario (parte 21)

Archivos:

  • resources/views/layouts/header.blade.php

Agregaremos al menú de navegación un link «contáctanos». Al pulsarlo, nos redirigirá a una vista que mostrará un formulario donde poner el nombre, el mail y un mensaje. Esa información nos será enviada en un correo electrónico.

Enlace en el menú

Abrimos la platilla de headers dentro de la carpeta partials, para añadir un link «contactanos» en nuestro menú de navegación.

            <ul>
                <li><a href="{{route('home')}}" class="{{request()->routeIs('home') ? 'active' : '' }}">Home</a>
                    {{-- @dump(request()->routeIs('home')) --}}
                </li>
                <li><a href="{{route('cursos.index')}}" class="{{request()->routeIs('cursos.*') ? 'active' : '' }}">Cursos</a>
                    {{-- @dump(request()->routeIs('cursos.index')) --}}
                </li>
                <li><a href="{{route('nosotros')}}" class="{{request()->routeIs('nosotros') ? 'active' : '' }}">Nosotros</a>
                    {{-- @dump(request()->routeIs('nosotros')) --}}
                </li>
                <li><a href="">Contactanos</a>
                </li>
</ul>

Ruta para almacenar mail

Abrimos web.php. Le damos a la ruta get que hemos creado para enviar mails el nombre «contactanos.index».

Route::get('contactanos', function() {
    $correo = new ContactanosMailable;
    Mail::to('weblikonet@gmail.com')->send($correo);
    return "mensaje enviado";
})->name('contactanos.index');

Ruta para mostrar el formulario

Hacemos que el nuevo enlace de contacto apunte a esa ruta.


<li><a href="{{route('contactanos.index')}}">Contactanos</a></li>

Controlador para la ruta y métodos

Creamos un controlador para que administre la ruta.

php make:controller ContactanosController 

Creamos dos métodos para este controlador: index() para mostrar el formulario y store() para procesarlo y enviar el mail.

class ContactanosController extends Controller
{
    //
    public function index(){
        
    }

    public function store(){
    
    }

}

Modificamos el controlador

Habíamos escrito esta función en la ruta:

        $correo = new ContactanosMailable;
        Mail::to('weblikonet@gmail.com')->send($correo);
        return "mensaje enviado";    

Lo ponemos esta vez dentro del método store().

class ContactanosController extends Controller
{
    //
    public function index(){
        
    }

    public function store(){
        $correo = new ContactanosMailable;
        Mail::to('weblikonet@gmail.com')->send($correo);
        return "mensaje enviado";    
    }

}

Como estamos creando aquí el objeto y haciendo uso de la clase, debemos importar las clases en el controlador:

use App\Mail\ContactanosMailable;
use Illuminate\Support\Facades\Mail;

Modificamos la ruta para el controlador

Modificamos la ruta para que llame al método index() del controlador ContactanosController:

Route::get('contactanos', [ContactanosController::class, 'index'])->name('contactanos.index');

Vista para el formulario

Hacemos que el método index() del controlador devuelva una vista llamada «contactanos.index»:

    public function index(){
        return view('contactanos.index');
    }

Creamos una carpeta dentro de views llamada «contactanos» y, dentro, la vista index.blade.php. Su contenido puede estar copiado de la vista home, y modificamos a nuestro antojo el title y el h1:

@extends('layouts.plantilla')

@section('title', 'Contactanos')

@section('content')
    <h1>Déjanos un mensaje</h1>
@endsection

Añadimos la clase para que el enlace quede activo cuando se visite:

<a href="{{route('contactanos.index')}}" class="{{request()->routeIs('contactanos.index') ? 'active' : '' }}">Contactanos</a>

Formulario de contacto

Creamos el formulario dentro de la vista:

<form action="">
    <label>
        Nombre:
        <br>
        <input type="text" name="name">
    </label>
    <br>


    <label>
        Correo:
        <br>
        <input type="mail" name="email">
    </label>
    <br>

    <label>
        Mensaje:
        <br>
        <textarea name="mensaje" rows="4"></textarea>    
        </label>
    <br>

    <button type="submit">Enviar mensaje</button>

</form>

Ruta post para el formulario

Creamos una ruta que procese la información del formulario. Va a ser de tipo post y llevará la url «contactanos». Le asignamos el método store() del controlador ContactanosController. Le damos el nombre «contactanos.store»:

Route::post('contactanos',[ContactanosController::class, 'store'])->name('contactanos.store');

Ponemos esta ruta en el action del formulario. Tenemos que especificar también el método del formulario, y el token csrf:

(...)
<form action="{{route('contactanos.store')}}" method="post">
    @csrf
(...)

Ejercicio

Hagamos este pequeño ejercicio antes de continuar. Si añadimos una propiedad a la clase del mailable que hemos creado para enviar el correo, como esta:

(...)
class ContactanosMailable extends Mailable
{
    use Queueable, SerializesModels;

    public $subject="Asunto del mail de contacto";

    public $propiedad="Esta es una propiedad del mailable"; 
(...)

Podemos rescatar esta propiedad dentro de la vista del mail de la siguiente forma:

<!DOCTYPE html>
(...)
<body>
    <h1>Correo Electrónico</h1>
    <p>Primer mail que mando por Laravel</p>
    {{$propiedad}}
</body>
</html>

Y comprobar que se envía correctamente

Recuperar la información del formulario

Al método store del controlador le pasamos el objeto request.

    public function store(Request $request){
        $correo = new ContactanosMailable;
        Mail::to('weblikonet@gmail.com')->send($correo);
        return "mensaje enviado";    
    }

Donde estoy creando la instancia de la clase ContactanosMailable le pasamos la información de request al constructor:

    public function store(Request $request){
        $correo = new ContactanosMailable($request->all());
        Mail::to('weblikonet@gmail.com')->send($correo);
        return "mensaje enviado";    
    }

Y recibimos la información en el constructor de la clase de la siguiente forma:

public $contacto;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($contacto)
    {
        $this->contacto = $contacto;
    }

Ahora podemos modificar la vista para imprimir la información así:

(...)
<body>
    <h1>Correo Electrónico</h1>
    <p>Primer mail que mando por Laravel</p>
    {{$propiedad}}

    <p><strong>Nombre:</strong> {{$contacto['nombre']}}</p>
    <p><strong>Nombre:</strong> {{$contacto['email']}}</p>
    <p><strong>Nombre:</strong> {{$contacto['mensaje']}}</p>
</body>
</html>

Validación

Completamos el método store() para validar el formulario. Todos los campos serán requeridos y el email debe ser un email:

 public function store(Request $request){

        $request->validate([
            'name'=>'required',
            'name'=>'required|email',
            'mensaje'=>'required',
        ]);

        $correo = new ContactanosMailable($request->all());
        Mail::to('weblikonet@gmail.com')->send($correo);
        return "mensaje enviado";    
    }

En el formulario ponemos los mensajes de error en cada campo. Abrimos la directiva error con el parámetro el nombre del campo que queramos validar, y en la directiva un párrafo con el posible error almacenado en la variable $message. Lo hacemos en cada campo:

 <form action="{{route('contactanos.store')}}" method="POST">
    @csrf
    <label>
        Nombre:
        <br>
        <input type="text" name="name">
    </label>
    <br>

    @error('name')
        <p><strong>{{$message}}</strong></p>
    @enderror

    <label>
        Correo:
        <br>
        <input type="mail" name="email">
    </label>
    <br>


    @error('email')
        <p><strong>{{$message}}</strong></p>
    @enderror

    <label>
        Mensaje:
        <br>
        <textarea name="mensaje" rows="4"></textarea>    
        </label>
    <br>
    
    @error('mensaje')
        <p><strong>{{$message}}</strong></p>
    @enderror

    <button type="submit">Enviar mensaje</button>
</form>

Redirección

Con esta redirección no mostraremos el mensaje «mensaje enviado», sino que volveremos a la vista en la que estábamos mostrando un mensaje. Le mandamos a la ruta un mensaje de sesión con el método with con dos parámetros, el nombre de la variable y su contenido.

    public function store(Request $request){

        $request->validate([
            'name'=>'required',
            'email'=>'required|email',
            'mensaje'=>'required',
        ]);

        $correo = new ContactanosMailable($request->all());
        Mail::to('weblikonet@gmail.com')->send($correo);

        return redirect()->route('contactanos.index')->with('info','mensaje enviado');    
    }

Usamos un condicional en la vista para ver si mostramos o no la variable de sesión en una alerta, en la vista del formulario.

(...)
    <button type="submit">Enviar mensaje</button>

</form>

@if (session('info'))
    <script>
        alert("{{session('info')}}")
    </script>
@endif
(...)

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=Ei3RVhdcE0A&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=29

Laravel: Envío de mails con SMTP (parte 20)

Archivos:

  • .env
  • config/mail.php
  • resources/routes/web.php

Conectar con el proveedor de correo electrónico

Laravel está configurado para conectar con ciertos proveedores que usan APIs, aunque podemos conectarnos con cualquier otro siempre que instalemos los archivos necesarios. Desde config/mail.php podemos ver toda la configuración necesaria.

La primera decisión a tomar es elegir entre API y SMTP. En el archivo mail.php está definida la variable mail_mailer y su valor por defecto es SMTP, que es el protocolo que usaremos aquí. (SMTP: Protocolo para transferencia simple de correo).

    'default' => env('MAIL_MAILER', 'smtp'

Además necesitamos las credenciales del proveedor para conectarnos por SMTP. Desde el archovo mail.php:


    'mailers' => [
        'smtp' => [
            'transport' => 'smtp',
            'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
            'port' => env('MAIL_PORT', 587),
            'encryption' => env('MAIL_ENCRYPTION', 'tls'),
            'username' => env('MAIL_USERNAME'),
            'password' => env('MAIL_PASSWORD'),
            'timeout' => null,
            'local_domain' => env('MAIL_EHLO_DOMAIN'),
        ],

Como vemos en este fragmento, estas variables se toman del archivo .env:

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

Mailtrap

Mailtrap es un servidor falso SMTP para pruebas. Una vez creada la cuenta en https://mailtrap.io/ (que aquí hemos creado con las credenciales de una de nuestras cuentas de Gmail) podemos encontrar los parámetros en el primer correo de prueba que encontraremos en la bandeja de entrada.

Necesitaremos el host, el puerto, el usuario, la contraseña para el archivo .env.

En mail.php vamos a encontrar los nombres de las variables para el remitente:


    'from' => [
        'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
        'name' => env('MAIL_FROM_NAME', 'Example'),
    ],

En el archivo .env:

MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

Creación de mailable

Desde la consola:

    php artisan make:mail ContactanosMailable

Dentro de /app se creará uan carpeta /Mail con el archivo que acabamos de crear.

Agregamos propiedades a la clase que se extiende de Mailable.

 class ContactanosMailable extends Mailable
{
    use Queueable, SerializesModels;

    public $subject="Asunto del mail de contacto";

(...)

Más abajo encontramos que la función build() llama a una vista que vamos a renombrar, y luego a crearla:

    public function build()
    {
        return $this->view('emails.contactanos');
    }

En /views creamos una carpeta /emails y dentro nuestra vista llamada contactanos.blade.php. Dentro de la vista creamos el cuerpo de un archivo HTML (recordamos: en VSC basta con escribir ! y tabular). Modificamos el HTML a nuestro antojo, e incluso agregamos un estilo:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        h1 {color:blue}
    </style>
</head>
<body>
    <h1>Correo Electrónico</h1>
    <p>Primer mail que mando por Laravel</p>
</body>
</html>

Ruta para el envío del mail

Vamos al archivo de rutas, en web.php, y creamos nueva ruta tipo get:

Route::get('contactanos', function() {
    //
})

E importamos dos clases a este archivo:


use App\Mail\ContactanosMailable;
use Illuminate\Support\Facades\Mail;

Vamos a generar una instancia de ContactanosMailable y acceder a la clase mail con el método to(), usando como parámetro un mail cualquiera y pasándole una clase send con el objeto creado:

Route::get('contactanos', function() {
    $correo = new ContactanosMailable;
    Mail::to("unmaildepruebaqueexista@gmail.com")->send($correo);
    return "mensaje enviado";
})

Al ir a la url de la ruta se enviará el mail de que hemos creado. Podemos ver los mails en nuestra bandeja de entrada de mailtrap.

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=e0ynchA_sBA&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=28

Laravel: Navegabilidad (parte 19)

Archivos:

  • resources/views/layouts/plantilla.blade.php
  • routes/web.php
  • resources/views/layouts/partials

HTML del menú de navegación

Este es el prototipo del código del menú de navegación, que escribiremos en el archivo de plantilla plantilla.blade.php:

(...)

<body>
    <!-- header -->
    <!-- nav -->
    <header>
        <nav>
            <ul>
                <li><a href="">Home</a></li>
                <li><a href="">Cursos</a></li>
                <li><a href="">Nosotros</a></li>
            </ul>
        </nav>
    </header>

(...)

Nombraremos nuestra ruta principal como «Home». En el archivo de rutas:

Route::get('/', HomeController::class)->name('Home');

Vamos a crear una ruta para «Nosotros» con el siguiente código, que utiliza un método distinto: el método view. Este método sólo lo utilizaremos para mostrar contenido estático. Admite dos parámetros, la url y el nombre de la vista. También le pondremos un nombre, la llamaremos «nosotros»:

// Route::view('url','vista')->name('nombre');
Route::view('nosotros','nosotros')->name('nosotros');

Creamos el archivo nosotros.blade.php en la carpeta vistas, le añadimos el contenido html de la vista home y la modificamos así:

@extends('layouts.plantilla')

@section('title', 'Home')

@section('content')
    <h1>Nosotros</h1>
@endsection

Ya podemos rellenar las rutas del nav de la siguiente forma:

    <header>
        <nav>
            <ul>
                <li><a href="{{route('home')}}">Home</a></li>
                <li><a href="{{route('cursos.index')}}">Cursos</a></li>
                <li><a href="{{route('nosotros')}}">Nosotros</a></li>
            </ul>
        </nav>
    </header>

Uso de la función request

Usando la función request con el método routeIs, pasando como parámetro una ruta, obtendremos true si la ruta es la actual, y false si no lo es. Podemos comprobarlo as´í:

    <header>
        <nav>
            <ul>
                <li><a href="{{route('home')}}">Home</a>
                <?php
                    dump(request()->routeIs('home'));
                ?>
</li>
                <li><a href="{{route('cursos.index')}}">Cursos</a></li>
                <li><a href="{{route('nosotros')}}">Nosotros</a></li>
            </ul>
        </nav>
    </header>

Mejor usar una directiva de blade:

@dump(request()->routeIs('home'))

Podríamos usar la misma directiva en cada ruta:

        <nav>
            <ul>
                <li><a href="{{route('home')}}">Home</a>
                    @dump(request()->routeIs('home'))
                </li>
                <li><a href="{{route('cursos.index')}}">Cursos</a>
                    @dump(request()->routeIs('cursos.index'))
                </li>
                <li><a href="{{route('nosotros')}}">Nosotros</a>
                    @dump(request()->routeIs('nosotros'))
                </li>
            </ul>
        </nav>

Añadimos una clase a cada enlace, con un condicional usando operadores ternarios. Así añadimos la clase active al enlace activo, o una clase vacía si la condición no se cumple:

            <ul>
                <li><a href="{{route('home')}}" class="{{request()->routeIs('home') ? 'active' : '' }}">Home</a>
                </li>
                <li><a href="{{route('cursos.index')}}" class="{{request()->routeIs('cursos.index') ? 'active' : '' }}">Cursos</a>
                </li>
                <li><a href="{{route('nosotros')}}" class="{{request()->routeIs('nosotros') ? 'active' : '' }}">Nosotros</a>
               </li>
            </ul>

Para que cualquier ruta que comience con cursos quede como activa (si visitamos la url de cualquier curso) usamos un asterisco:

            <ul>
                <li><a href="{{route('home')}}" class="{{request()->routeIs('home') ? 'active' : '' }}">Home</a>
                </li>
                <li><a href="{{route('cursos.index')}}" class="{{request()->routeIs('cursos.*') ? 'active' : '' }}">Cursos</a>
                </li>
                <li><a href="{{route('nosotros')}}" class="{{request()->routeIs('nosotros') ? 'active' : '' }}">Nosotros</a>
               </li>
            </ul>

Optimizamos la plantilla

Creamos una carpeta llamada partials dentro de la carpeta layouts. Dentro, un archivo de plantilla llamado header.blade.php, con el siguiente contenido (que cortamos desde la plantilla.blade.php):

<header>
        <h1>Proyecto Laravel</h1>
        <nav>
            <ul>
                <li><a href="{{route('home')}}" class="{{request()->routeIs('home') ? 'active' : '' }}">Home</a>
                    {{-- @dump(request()->routeIs('home')) --}}
                </li>
                <li><a href="{{route('cursos.index')}}" class="{{request()->routeIs('cursos.*') ? 'active' : '' }}">Cursos</a>
                    {{-- @dump(request()->routeIs('cursos.index')) --}}
                </li>
                <li><a href="{{route('nosotros')}}" class="{{request()->routeIs('nosotros') ? 'active' : '' }}">Nosotros</a>
                    {{-- @dump(request()->routeIs('nosotros')) --}}
                </li>
            </ul>
        </nav>
    </header>

Y en el lugar donde estaba todo el código del header, una directiva de blade para incluirlo:

    @include('layouts/partials/header')

Ya que hemos hecho esto, añadamos un footer. Dentro de partials, creamos footer.blade.php, lo incluimos también en la plantilla con include. El contenido del footer podría ser:

<footer>
    Esto es un footer
</footer>

Para resumir, el contenido de la plantilla.blade.php quedaría así:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title')</title>
    <!-- favicon -->
    <!-- estilos -->
    <style>
        .active {
            font-weight: bold;
            color: red
        }
    </style>

</head>
<body>
    <!-- header -->
    <!-- nav -->

    @include('layouts/partials/header')

    @yield('content')

    <!-- footer -->

    @include('layouts/partials/footer')

    <!-- script -->
</body>
</html>

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=VVY5RVdpFco&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=27

Laravel: URL amigables (parte 18)

Archivos:

  • database/migrations
  • database/factories

Crear el campo slug

Genramos un nuevo campo dentro de la migración de la tabla cursos, llamado «slug»

    public function up()
    {
        Schema::create('cursos', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug');
            $table->text('description');
            $table->text('categoria');
            $table->timestamps();
        });
    }

Editamos el factory de esta tabla:

    public function definition()
    {
        return [
            //
            'name'=>$this->faker->sentence(),
            'slug'=>Str::slug($this->faker->sentence(),'-'),
            'categoria'=>$this->faker->randomElement(['Desarrollo web','Diseño web']),
            'description'=>$this->faker->paragraph()
        ];

Hemos llamado a la clase Str y a su método slug. Cambiará todas las mayúsculas por minúsculas y sustituirá los espacios por guiones (porque hemos establecido un guión como segundo parámetro).

En este caso tendremos un slug distinto al título, y queremos que sea el mismo, con lo que lo podemos poner así:

public function definition()
    {
        $name =$this->faker->sentence();

        return [
            //
            'name'=>$name,
            'slug'=>Str::slug($name,'-'),
            'categoria'=>$this->faker->randomElement(['Desarrollo web','Diseño web']),
            'description'=>$this->faker->paragraph()
        ];
    }

No podemos olvidar llamar a la clase Str al principio del archivo, si no lo ha autorrellenado Intelephense:

use Illuminate\Support\Str;

Luego migramos para ver los cambios en la base de datos:

php artisan migrate:fresh --seed

Uso del campo slug

Abrimos la vista index de los cursos, comprobemos la url actual:

@foreach ($cursos as $curso)

        <li>
         <a href="{{route('cursos.show', $curso->id)}}">{{$curso->name}}</a> 
         </li>
          {{route('cursos.show', $curso->id))}}

        @endforeach

Recordamos que en Laravel podemos llamar a la id directamente de la siguiente manera, ya que el recurso de llamar a la id es muy utilizado y se puede hacer directamente así:

@foreach ($cursos as $curso)

        <li>
         <a href="{{route('cursos.show', $curso)}}">{{$curso->name}}</a> 
         </li>
          {{route('cursos.show', $curso))}}

        @endforeach

LA id se la pasamos al controlador mediante la variable curso. Necesitaríamos que cuando utilicemos el método route y le pasemos el objeto curso, en vez de la id, recupere el slug del objeto. Devolverá el registro cuyo slug coincida con lo que le pasemos por la url.

Vamos al modelo Curso, que vemos que se extiende de la clase Model.

Vemos que uno de los muchos métodos que tiene es getroutekeyname:

     public function getRouteKeyName()
    {
        return $this->getKeyName();
    }

Podríamos modificar este mnétodo tal que así, para lograr lo que queremos:

     public function getRouteKeyName()
    {
       // return $this->getKeyName();
 return 'slug';
    }

Pero lo mejor es hacerlo en el modelo Curso:

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=VYuthGIhrS0&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=25

Laravel: Route resource (parte 18)

Archivos:

  • routes/web.php

Uso de route resource

Desde la consola de Git Bash, navegamos hasta el directorio de nuestro proyecto y ejecutamos el siguiente comando de artisan: php artisan «route list»

php artisan r:l

Comentamos todas las rutas y vamos a definir la rutas del CRUD de la siguiente forma:

 Route::resource('cursos', CursoController::class);

Si volvemos a ejecutar el comando route list, veremos todas las 7 rutas del CRUD de nuevo en la consola.

El resultado es exactamente el mismo porque anteriormente creamos las rutas una a una siguiendo las convenciones de nombres.

Cambio de rutas creadas con route resource

En la documentación de Laravel se nos indica cómo hacerlo: https://www.oulub.com/docs/laravel/controllers#restful-localizing-resource-uris

Según se nos indica allí, vamos al archivo App/providers/appserviceprovider.php y poner en la parte superior del archivo:

use Illuminate\Support\Facades\Route;

Y dentro del método boot:

    public function boot()
    {
        Route::resourceVerbs([
            'create' => 'crear',
            'edit' => 'editar',
        ]);
    }

Y podemos hacer esto porque una cosa es la url, y otra el nombre de la ruta. Lo que estamos cambiando es la url, mientras que los formularios y lois enlaces siguen apuntando a las rutas por su nombre.

De hecho, podríamos modificar la palabra «cursos» del route resource por cualquier otra, por ejemplo, «asignaturas»; y todo seguiría funcionando como hasta ahora, pero tendríamos que usar un par de métodos más para que no falle todo:

Route::resource('asignaturas', CursoController::class)->parameters(['asignatura' => 'curso'])->names('cursos');

El método names permitirá que las rutas sigan funcionando como hasta ahora (pues están en todas las vistas, y si no, tendríamos que modificar cada vista) y el método parameters incluye un array para los nombres de las variables que usamos para generar las rutas (que, de nuevo, necesitarían ser cambiadas por «asignatura»).

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=PT6BoDQdkXk&list=PLZ2ovOgdI-kWWS9aq8mfUDkJRfYib-SvF&index=24

Laravel: Eliminar registros (parte 17)

Archivos:

  • routes/web.php
  • CursoController.php

Crearemos una ruta de tipo delete, que apunte a una url llamada curso.destroy.

Route::delete('cursos/{curso}',[CursoController::class, 'destroy'])->name('cursos.destroy');

Crearemos un método destroy() en nuestro controlador. Como se está mandando información por la url, lo rescatamos en el controlador.

    public function destroy(Curso $curso) {
        $curso->delete();
    }    

Añadiremos un botón eliminar en la vista show dentro de un formulario. El formulario tendrá la directiva method con el método del mismo (que no es post, sino delete) y el token de seguridad, además de que en el action pasaremos la nueva ruta creada con el parámetro curso. En la vista show:

<form action="{{route('cursos.destroy',$curso)}}" method="POST">
        @csrf
        @method('delete')
        <button type="submit">Eliminar</button>
    </form>

Tenemos que pedirle al método destroy que nos redirija para que el formulario no muestre una página en blanco,

    public function destroy(Curso $curso) {
        $curso->delete();
        return redirect()->route('cursos.index');
    }    

Recursos:

Enlace de Youtube: https://www.youtube.com/watch?v=3uUW1IviXns&t=3s