El objetivo de esta práctica es conocer el sistema de eventos que ofrecen los navegadores web y que son accesibles para los programas en JavaScript.
El uso de eventos nos permitirá dotar de interactividad a la aplicación que estamos desarrollando: el usuario podrá interactuar con dicha aplicación para introducir datos y realizar modificaciones en su estado.
El repositorio base de la práctica está disponible en: https://github.com/pedroprieto/practica_dwec_gestor_presupuesto
Se supone que ya está configurado el repositorio personal y el remoto secundario (profesor
). Si no es así, revisa las instrucciones de las prácticas anteriores. En el apartado de Preparación se indica cómo proceder.
Para poder realizar esta práctica y que funcione adecuadamente el entorno de test será necesario instalar el siguiente software en el equipo:
Para realizar la práctica se seguirán los principios del Desarrollo Guiado por Test. Para ello se proporciona en el repositorio una serie de ficheros que permiten ejecutar tests. Dichos tests comprobarán que el programa cumple con algunos de los requisitos de la práctica.
El repositorio se ha configurado para que se ejecuten los tests automáticamente en la nube de GitHub cuando se realice un push o una pull request. Para ello se hará uso del servicio GitHub Actions.
- Instalar los requisitos de software indicados
- Abrir un terminal
- Situarse en la carpeta del repositorio personal de la práctica
- Incorporar a tu repositorio personal los cambios realizados por el profesor correspondientes a los archivos de esta práctica. Para ello hay que ejecutar:
git pull profesor master
- Este comando descarga los cambios que ha realizado el profesor en el repositorio base y los integra en tu repositorio personal. Tras realizar este paso, seguramente git abra el editor configurado por defecto para que introduzcas un mensaje para crear un nuevo commit que integre tus cambios y los cambios del profesor. Debes introducir el texto y guardar los cambios.
- En principio no deben producirse conflictos. En caso de que se produzcan (por ejemplo, porque has editado el fichero
.gitignore
y yo también porque lo exigía la práctica), resuélvelos y notifícamelo a través de un Issue. - Ejecuta el comando
git push
para subir los cambios a tu repositorio personal (el remoto principal) en GitHub y que queden guardados ahí también. - Ejecutar el comando
npm install
. Este comando instalará todas las librerías de Node necesarias para ejecutar los tests. Dichas librerías se guardarán en una carpeta con nombrenode_modules
dentro del repositorio. Nótese que dicha carpeta está excluida del repositorio en el archivo.gitignore
. - Ejecutar el comando
npm run test5
para lanzar los tests de esta práctica. Este comando podrá ejecutarse tantas veces como sea necesario. Por pantalla se mostrarán los tests que se pasan y los que no, de manera que se tendrá información sobre las acciones que hay que realizar. Los tests también se ejecutarán automáticamente en GitHub Actions al subir los cambios al repositorio y al realizar la pull request. - Opcionalmente (recomendable), ejecutar el comando
npm test
para lanzar todos los tests presentes en el repositorio. Se deberá comprobar que se pasan los tests de las prácticas anteriores a la que se esté realizando. Lógicamente, si el repositorio incluye los tests de prácticas posteriores a la que se esté realizando, dichos tests no se pasarán (ya que el trabajo está todavía por hacer). Este último caso puede darse si la persona no está realizando la práctica propuesta en la semana actual (va con “retraso”, por así decirlo). En GitHub Actions se ejecutarán todos los tests en tareas independientes: así se podrá comprobar si el test de la práctica que se está realizando se ha pasado.¡IMPORTANTE! Esta práctica utiliza la suite de test Cypress. Puedes ejecutar el test en consola de la manera habitual (
npm run test5
) o bien ejecutar el modo gráfico mediante el comandonpx cypress open
. Recuerda ejecutarnpm install
antes para instalar el paquetecypress
.Recuerda que puedes utilizar el navegador para visualizar el trabajo que vas haciendo. Como la carga de scripts la realizamos a través de módulos, no basta con hacer doble clic en el archivo
HTML
, sino que es necesario visualizar la página desde un servidor. Para ello puedes utilizar la extensión Live Server de Vísual Studio Code.
Lee atentamente los siguientes artículos y sus correspondientes subsecciones en caso de que las tengan:
Vamos a utilizar los ficheros de la práctica anterior realizando modificaciones sobre el archivo js/gestionPresupuestoWeb.js
.
Utilizaremos de nuevo el fichero interaccionHTML.html
para mostrar los datos e interactuar con la aplicación a través del navegador.
La aplicación funcionará de la siguiente manera:
- El usuario abrirá el archivo
interaccionHTML.html
en el navegador (a través de un servidor web, tal como se ha comentado en la sección de Preparación). - El archivo
interaccionHTML.html
cargará el programajs/generarDatosEstaticos.js
. Dicho programa hará uso de dos programas (que se utilizarán como librerías):- Librería
js/gestionPresupuestoWeb.js
, que definirá una serie de funciones para interactuar con el DOM de la página y mostrar los datos en HTML. En esta práctica realizaremos modificaciones en este fichero para añadir soporte de eventos. - Librería
js/gestionPresupuesto.js
, que contiene la lógica de negocio de la aplicación (funciones para crear, editar, borrar y mostrar gastos).
- Librería
El archivo js/generarDatosEstaticos.js
se utilizará para crear unos gastos iniciales para poder hacer pruebas durante el desarrollo (para que no aparezca la aplicación vacía). Por tanto, en una aplicación en producción no sería necesario: el archivo HTML funcionaría cargando el archivo /js/gestionPresupuestoWeb.js
directamente.
Deberemos añadir el siguiente código HTML como primer hijo de la capa <div id="aplicacion">
:
<div id="controlesprincipales">
<button type="button" id="actualizarpresupuesto">
Actualizar presupuesto
</button>
<button type="button" id="anyadirgasto">
Añadir gasto
</button>
</div>
Vamos a hacer una serie de modificaciones sobre este fichero.
El fichero js/gestionPresupuestoWeb.js
tiene que tener acceso a las funciones de lógica de negocio de la aplicación, ya que crearemos funciones que procesen las acciones y los datos que realice e introduzca el usuario.
Realizaremos la importación mediante import * as para utilizar un nombre de módulo que agrupe las funciones exportadas por dicho fichero.
Estamos desarrollando una aplicación JavaScript controlada por datos. Cada vez que se añade, modifica o borra un gasto, debemos mostrar el resultado en la página HTML. Recordemos que la aplicación debe mostrar:
- El presupuesto
- El total de gastos
- El balance actual
- El listado con los gastos y sus datos
- Otra información (agrupaciones de gastos, etc.)
Las funciones de lógica de negocio (fichero js/gestionPresupuesto.js
) guardan la información en objetos independientes del navegador: si cambiamos el valor de un gasto, por ejemplo, el contenido que se muestra en la página HTML no se actualiza a no ser que lo hagamos explícitamente.
Por tanto, es necesario disponer de una función que vuelva a crear toda la estructura HTML que refleje los cambios realizados en el modelo de datos. Esta función se denominará repintar
, y realizará las siguientes tareas:
- Mostrar el presupuesto en
div#presupuesto
(funcionesmostrarPresupuesto
ymostrarDatoEnId
) - Mostrar los gastos totales en
div#gastos-totales
(funcionescalcularTotalGastos
ymostrarDatoEnId
) - Mostrar el balance total en
div#balance-total
(funcionescalcularBalance
ymostrarDatoEnId
) - Borrar el contenido de
div#listado-gastos-completo
, para que el paso siguiente no duplique la información. Puedes utilizarinnerHTML
para borrar el contenido de dicha capa. - Mostrar el listado completo de gastos en
div#listado-gastos-completo
(funcioneslistarGastos
ymostrarGastoWeb
)
La función repintar
no actualizará el resto de capas (filtrados y agrupaciones) de la práctica anterior (lo haremos así por simplicidad).
Esta función se utilizará como manejadora de eventos del botón actualizarpresupuesto
del código HTML. Realizará las siguientes tareas:
- Pedir al usuario que introduzca un presupuesto mediante un prompt.
- Convertir el valor a número (recuerda que
prompt
siempre devuelve unstring
). - Actualicar el presupuesto (función
actualizarPresupuesto
) - Llamar a la función
repintar
para que se muestre la información actualizada en el archivo HTML. Recuerda que actualizar el presupuesto provoca cambios en el balance, por lo que al ejecutarrepintar
se actualizarán ambos campos.
Una vez definida la función, se añadirá como manejadora del evento click
del botón actualizarpresupuesto
mediante addEventListener
. Para ello habrá que obtener el elemento botón correspondiente previamente.
Esta función se utilizará como manejadora de eventos del botón anyadirgasto
del código HTML. Realizará las siguientes tareas:
- Pedir al usuario la información necesaria para crear un nuevo gasto mediante sucesivas preguntas con prompt (por orden: descripción, valor, fecha y etiquetas). Por simplicidad, de momento no se comprobará la validez de dichos datos. La fecha vendrá dada en formato internacional (
yyyy-mm-dd
) y las etiquetas se introducirán en un único cuadro de texto como una lista separada por comas (por ejemplo,etiqueta1,etiqueta2,etiqueta3
). - Convertir el valor a número (recuerda que
prompt
siempre devuelve unstring
). - Convertir la cadena de texto de etiquetas devuelta por
prompt
a un array. - Crear un nuevo gasto (función
crearGasto
). ¡Ojo con la manera de pasar el parámetro ~etiquetas~! - Añadir el gasto a la lista (función
anyadirGasto
). - Llamar a la función
repintar
para que se muestre la lista con el nuevo gasto.
Una vez definida la función, se añadirá como manejadora del evento click
del botón anyadirgasto
mediante addEventListener
. Para ello habrá que obtener el elemento botón correspondiente previamente.
Esta función se utilizará como objeto manejador de eventos para editar un gasto.
La razón de utilizar esta función es la siguiente: queremos añadir un botón para editar cada gasto de la lista. Por tanto, necesitamos que haya una conexión entre dicho botón y el gasto asociado a él. Recordemos que si tenemos 4 gastos, tendremos 4 botones de Editar.
Hay muchas soluciones para realizar esta asociación: una podría ser añadir un id
al botón Editar
de dicho gasto que sea el mismo que el id
del gasto asociado. Así, si se hace clic en dicho botón, inspeccionando su atributo id
podremos saber el gasto al que hace referencia. No obstante, esta solución no es demasiado buena: la lógica de negocio de nuestra aplicación solo almacena el array de gastos, por lo que tendríamos que añadir una función para buscar dicho gasto dado su id
.
En lugar de ello optaremos por una solución un poco más elaborada aprovechando que addEventListener
nos permite utilizar un objeto como manejador de eventos.
La función EditarHandle
será una función constructora que definirá exclusivamente un método llamado handleEvent
. Cuando creemos un objeto basado en su prototipo, asignaremos a dicho objeto una propiedad llamada gasto
, que será una referencia al gasto que estemos editando. El código de la función handleEvent
podrá hacer referencia a dicho gasto a través de this.gasto
, ya que es una propiedad del objeto. Esta función realizará las siguientes tareas:
- Pedir al usuario la información necesaria para editar el gasto mediante sucesivas preguntas con prompt. Por simplicidad, de momento no se comprobará la validez de dichos datos. La fecha vendrá dada en formato internacional (
yyyy-mm-dd
) y las etiquetas se introducirán en un único cuadro de texto como una lista separada por comas (por ejemplo,etiqueta1,etiqueta2,etiqueta3
). Recuerda queprompt
admite como segundo parámetro el valor por defecto del cuadro de diálogo, por lo que puedes proporcionar el valor actual de cada propiedad del gasto. - Convertir el valor a número (recuerda que
prompt
siempre devuelve unstring
). - Convertir la cadena de texto de etiquetas devuelta por
prompt
a un array. - Actualizar las propiedades del gasto (disponible mediante
this.gasto
), mediante las funcionesactualizarValor
,actualizarDescripcion
,actualizarFecha
yanyadirEtiquetas
. - Llamar a la función
repintar
para que se muestre la lista de gastos con los datos actualizados de la edición.
Esta función se utilizará como objeto manejador de eventos para borrar un gasto.
El funcionamiento de esta función es muy parecido a la anterior, con la excepción de su funcionamiento interno.
La función BorrarHandle
será una función constructora que definirá exclusivamente un método llamado handleEvent
. Cuando creemos un objeto basado en su prototipo, asignaremos a dicho objeto una propiedad llamada gasto
, que será una referencia al gasto que estemos editando. El código de la función handleEvent
podrá hacer referencia a dicho gasto a través de this.gasto
, ya que es una propiedad del objeto. Esta función realizará las siguientes tareas:
- Borrar el gasto asociado. Para ello utilizará la función
borrarGasto
y como parámetro utilizará elid
del gasto seleccionado, disponible enthis.gasto
. - Llamar a la función
repintar
para que se muestre la lista actualizada de gastos.
Esta función se utilizará como objeto manejador de eventos para borrar etiquetas de un gasto.
El funcionamiento de esta función es muy parecido a la anterior, con la excepción de su funcionamiento interno.
La función BorrarEtiquetasHandle
será una función constructora que definirá exclusivamente un método llamado handleEvent
. Cuando creemos un objeto basado en su prototipo, asignaremos a dicho objeto una propiedad llamada gasto
, que será una referencia al gasto que estemos editando y una propiedad llamada etiqueta
, que hará referencia a la etiqueta que se pretenda eliminar. El código de la función handleEvent
podrá hacer referencia a dicho gasto a través de this.gasto
y a this.etiqueta
, ya que son propiedades del objeto. Esta función realizará las siguientes tareas:
- Borrar la etiqueta seleccionada del gasto asociado. Para ello utilizará la función
borrarEtiquetas
del gasto asociado (this.gasto
) y como parámetro utilizará la etiqueta seleccionada, disponible enthis.etiqueta
. - Llamar a la función
repintar
para que se muestre la lista actualizada de gastos.
Una vez definidos los manejadores de eventos para editar y borrar un gasto y borrar etiquetas, es turno de modificar la función mostrarGastoWeb
para que, además de la estructura HTML definida en la práctica anterior, cree dos botones para editar y borrar el gasto y añada los manejadores de eventos necesarios para realizar las acciones de edición y borrado de gastos y borrado de etiquetas.
Estas modificaciones son:
- Botón editar:
- Crear un botón con texto
Editar
de tipobutton
(<button type="button">
) con clasegasto-editar
. - Crear un nuevo objeto a partir de la función constructora
EditarHandle
. - Establecer la propiedad
gasto
del objeto creado al objetogasto
(recuerda que el objetogasto
es un parámetro pasado a la funciónmostrarGastoWeb
). - Añadir el objeto recién creado como objeto manejador del evento
click
al botónEditar
recién creado. - Añadir el botón al DOM a continuación de las etiquetas
- Crear un botón con texto
- Botón borrar:
- Crear un botón con texto
Borrar
de tipobutton
(<button type="button">
) con clasegasto-borrar
. - Crear un nuevo objeto a partir de la función constructora
BorrarHandle
. - Establecer la propiedad
gasto
del objeto creado al objetogasto
(recuerda que el objetogasto
es un parámetro pasado a la funciónmostrarGastoWeb
). - Añadir el objeto recién creado como objeto manejador del evento
click
al botónBorrar
recién creado. - Añadir el botón al DOM a continuación del botón
Editar
.
- Crear un botón con texto
- Eventos para los
span
de etiquetas (no crearemos botón de borrar: el borrado se producirá si el usuario hace clic encima de una etiqueta):- Crear un nuevo objeto a partir de la función constructora
BorrarEtiquetasHandle
. - Establecer la propiedad
gasto
del objeto creado al objetogasto
(recuerda que el objetogasto
es un parámetro pasado a la funciónmostrarGastoWeb
). - Establecer la propiedad
etiqueta
del objeto creado al texto de la etiqueta que se esté procesando (seguramente este valor lo tendrás disponible dentro del bucle que se encarga de pintar un elementospan
para cada etiqueta). - Añadir el objeto recién creado como objeto manejador del evento
click
alspan
de la etiqueta.
- Crear un nuevo objeto a partir de la función constructora
La estructura HTML final que debe quedar para cada gasto es la siguiente:
<div class="gasto">
<div class="gasto-descripcion">DESCRIPCIÓN DEL GASTO</div>
<div class="gasto-fecha">FECHA DEL GASTO</div>
<div class="gasto-valor">VALOR DEL GASTO</div>
<div class="gasto-etiquetas">
<!-- Este elemento span tendrá un manejador de eventos -->
<span class="gasto-etiquetas-etiqueta">
ETIQUETA 1
</span>
<!-- Este elemento span tendrá un manejador de eventos -->
<span class="gasto-etiquetas-etiqueta">
ETIQUETA 2
</span>
<!-- Etcétera -->
</div>
<!-- Este botón tendrá un manejador de eventos -->
<button class="gasto-editar" type="button">Editar</button>
<!-- Este botón tendrá un manejador de eventos -->
<button class="gasto-borrar" type="button">Borrar</button>
</div>
- Cada persona trabajará en su repositorio personal que habrá creado tras realizar el fork del repositorio base.
- Todos los archivos de la práctica se guardarán en el repositorio y se subirán a GitHub periódicamente. Es conveniente ir subiendo los cambios aunque no sean definitivos. No se admitirán entregas de tareas que tengan un solo commit.
- Como mínimo se debe realizar un commit por cada elemento de la lista de tareas a realizar (si es que estas exigen crear código, claro está).
- Para cualquier tipo de duda o consulta se pueden abrir
Issues
haciendo referencia al profesor mediante el texto@pedroprieto
dentro del texto delIssue
. Losissues
deben crearse en tu repositorio: si no se muestra la pestaña deIssues
puedes activarla en losSettings
de tu repositorio. - Una vez finalizada la tarea se debe realizar una
Pull Request
al repositorio base indicando tu nombre y apellidos en el mensaje.