Xexi Tasklist · Documentación
Xexi tasklist es una SPA (aplcación de página única) de lista de tareas.
Permite crear tareas asignándoles un título, una fecha límite de completación y una descripción.
Se puede cambiar el estado de las tareas marcándolas como inactivas, importantes o completadas.
Una vez guardada, la tarea se almacena en una lista de tareas mostrando un marcador de color dependiendo del estado en el que se encuentra.
Además de los estados asignados por los marcadores, existe el estado caducada que se activa de manera automática cuando la fecha actual ha superado la fecha límite que establecimos en la tarea.
La lista de tareas queda almacenada en el navegador y las tareas pueden ser seleccionadas para modificarlas o para ser eliminarlas, tanto de manera individual como en masa.
Las áreas del interfaz
El interfaz se compone de:
Zona principal #main
En el formato de escritorio la zona principal aparece a la derecha, mientras que en móvil ocupa toda la pantalla.
Se compone de la barra de herramientas de edición de la tarea #taskToolbar y la zona del contenedor de display #display-holder donde se rendeiza la tarea que estamos editando y también las vistas cuando no existe una tarea seleccionada o cuando no existen tareas en la lista de tareas.
El código
A grandes rasgos, el tronco principal es una función llamada app(), que es la que dispara las funciones que renderizan el contenido según las condiciones de la lista de tareas taskList (si tiene contenido o no) y de si existe o no una tarea activa activeTask.
En caso de que la lista esté vacía se muestra una pantalla de bienvenida con renderWithoutTasks(), y si la lista contiene tareas comprobará si existe una tarea activa activeTask. Si no existe disparará renderWelcome() que renderiza una pantalla de bienvenida distinta y en caso de que exista renderizará la tarea con renderTask().
La clase Task, el objeto activeTask y el array de tareas taskList
La clase Task tiene como propiedades las de una tarea (contenidos y estados) y tres métodos: uno para recuperar datos getDataFrom(), otro para volcar datos putDataTo() y un tercero checkIfExpired(), que comprueba si la tarea está caducada.
activeTask es un objeto de la clase Task que actúa como intermediario entre el DOM y el array de la lista de tareas taskList.
Al crear una nueva tarea se inicializa una activeTask con los estados por defecto y los contenidos vacíos excepto por la propiedad creationDate, que más tarde servirá para identificar la tarea dentro de la lista de tareas.
Una vez editado el DOM y al guardar la tarea, activeTask recoge el contenido y los estados y se añade al array taskList que es almacenado en el localStorage.
Al renderizar la lista de tareas, a cada elemento <li> se le asigna el atributo [date-id] con su valor de creationDate para, al seleccionarlo, identificar el objeto dentro del array taskList y asignarlo a activeTask, que vuelca los datos y los estados en el renderizado de la tarea.
En dicho renderizado, los datos de texto y de fecha se vuelcan en los input mientras que los estados se establecen como atributos en el contenedor #display-holder para que luego, mediante CSS, se asignen los estilos a los elementos según esos atributos.
Alternar los botones de estado tan solo añade o elimina el atributo del contenedor displayHolder (también alterna el atributo [aria-checked] del mismo botón), es luego el método getDataFrom() de la clase Task el que se encarga de comprobar que existan esos atributos para definir las propiedades de la tarea al guardarla.
Estos mismos atributos son también añadidos a cada <li> el renderizar la lista de tareas para, de nuevo con CSS, dar estilos a los elementos de cada tarea según su estado.
Los renderizados
Con la intención de conseguir mantener de manera independiente el contenido HTML del código JS, se hace uso de las etiquetas <template> para generar las plantillas del contenido en el mismo HTML en lugar de construirlas en JS.
Esos nodos son clonados y almacenados con JS, y se renderizan cada vez que se necesitan. Para la vista de tarea, primero se incluye el template vacío en el DOM y luego se vuelca la información en él, mientras que para la lista de tareas se construye el contenido de cada elemento en un clon y se van añadiendo en un elemento HTML para, una vez recorrida la lista, incluirlo en el DOM.
***Mientras lo construía descubrí que existe route() que posiblemente sería una mejor solución (tengo que investigar más sobre esto), pero puesto que solo estaba renderizando las vistas en la zona de display y de la lista de tareas decidí seguir con mi método y aplazar el uso de router() para otro proyecto con más "páginas". Tampoco he querido utilizar ningún sistema de templates de terceros por similares razones: no creo que esta SPA sea tan compleja como para necesitarlo y quería resolverlo por mi cuenta.
El traductor
Consta de dos archivos: lang.js que contiene los namespaces y translate.js en el que se encuentran las funciones del traductor.
translate.js se inicia antes de app.js y establece un idioma por defecto en caso de no exista uno definido en las cookies.
En translate.js existen dos funciones principales: translateJs() que se dispara antes de app.js para asignar los valores a las variables que se usan para los alert() y los confirm(), y translateDom() que debe ser disparada después de cada renderizado, que se encarga de recorrer el DOM en busca de elementos que contengan el atributo que especifica si su innerHTML, [title], [placeholder] o [aria-label] debe ser traducido.
***De nuevo, no he querido utilizar ningún sistema de templating para el traductor porque quería ver los problemas con los que me encontraba. De hecho he reesctiro el traductor varias veces buscando soluciones para evitar el uso de atributos para marcar las traducciones que hay que realizar. Una idea era utilizar una formato para los textos a traducir (como por ejemplo {*key*}), recorrer el DOM como string en busca de esos formatos y reemplazarlos. Funcionaba bien, pero el problema estaba en que una vez reemplazado el DOM, perdía todos los listeners, así que hice un git reset hard y seguí con la versión de los atributos. LUEGO descubrí el Event delegation pero decidí terminar el proyecto, investigarlo de manera independiente y luego aplicarlo.
WEI-ARIA
De cara al uso de lectores de pantalla, se han aplicado atributos [title], [role] y [aria-label] a las áreas principales y a los elementos importantes y [aria-checked] a los botones de estado de las tareas.
Además se ha establecido un orden natural de tabulación con [tabindex] y se han redirigido los focus() a sus destinos según las acciones efectuadas.
Testeado con el software NVDA.
Funciones extra
- Comprobación de cambios no guardados al seleccionar o crear una nueva tarea.
- Texto animado al mouseover sobre el título de la tarea en el listado de la tarea si este es más largo que el contenedor.
- Guardar cambios al pulsar Intro mientras se edita el título de una tarea.
- Cambio del formato de la fecha mostrado según el idioma.