Escribir una aplicación de toma de notas de JavaScript



¡Buen dia amigos!

Hoy, como su nombre lo indica, escribiremos una aplicación simple para crear y almacenar notas.

Las características de nuestra aplicación serán las siguientes:

  1. Crea una nota.
  2. Almacenamiento de notas.
  3. Eliminar una nota
  4. Marque sobre la tarea.
  5. Información sobre la fecha en que se completó la tarea.
  6. Recordatorio para completar una tarea.

La aplicación se escribirá en JavaScript.

Las notas se almacenarán en una base de datos indexada (IndexedDB). Esta biblioteca se utilizará para facilitar el trabajo con IndexedDB . Según los desarrolladores de esta biblioteca, es "lo mismo que IndexedDB, pero con promesas".

Se supone que está familiarizado con los conceptos básicos de IndexedDB. Si no, recomiendo leer este artículo antes de continuar .

Entiendo que para resolver un problema como el almacenamiento de notas, LocalStorage es suficiente. Sin embargo, quería explorar algunas de las características de IndexedDB. Por lo tanto, la elección a favor de este último se hizo únicamente a partir de consideraciones epistemológicas. Al final, encontrará enlaces a una aplicación similar donde el almacenamiento de datos se implementa utilizando LocalStorage.

Entonces vamos.

Nuestro marcado se ve así:

<!-- head -->
<!--  -->
<link href="https://fonts.googleapis.com/css2?family=Stylish&display=swap" rel="stylesheet">
<!--  -->
<script src="https://cdn.jsdelivr.net/npm/idb@3.0.2/build/idb.min.js"></script>

<!-- body -->
<!--   -->
<div class="box">
    <!-- - -->
    <img src="https://placeimg.com/480/240/nature" alt="#">
    <!--      -->
    <p>Note text: </p>
    <textarea></textarea>
    <!--      -->
    <p>Notification date: </p>
    <input type="date">

    <!--     -->
    <button class="add-btn">add note</button>
    <!--     -->
    <button class="clear-btn">clear storage</button>
</div>

Observaciones:

  1. Los campos de entrada podrían crearse usando las etiquetas "figura" y "figcaption". Sería más semántico, por así decirlo.
  2. Como resultó más tarde, elegir la etiqueta "input" con el tipo "date" no fue la mejor solución. Sobre esto a continuación.
  3. En una aplicación, los recordatorios (notificaciones) se implementan utilizando la API de notificaciones. Sin embargo, me pareció extraño pedirle permiso al usuario para mostrar notificaciones y agregar la capacidad de desactivarlas, porque, en primer lugar, cuando hablamos de la aplicación para notas (tareas), los recordatorios están implícitos y, en segundo lugar, se pueden implementar para que no molesten usuario tras la aparición repetida, es decir discretamente
  4. Inicialmente, la aplicación proporcionaba la capacidad de indicar no solo la fecha, sino también la hora del recordatorio. Posteriormente, decidí que la fecha es suficiente. Sin embargo, si lo desea, es fácil de agregar.

Conectamos estilos:
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    height: 100vh;
    background: radial-gradient(circle, skyblue, steelblue);
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    font-family: 'Stylish', sans-serif;
    font-size: 1.2em;
}

.box,
.list {
    margin: 0 .4em;
    width: 320px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background: linear-gradient(lightyellow, darkorange);
    border-radius: 5px;
    padding: .6em;
    box-shadow: 0 0 4px rgba(0, 0, 0, .6)
}

img {
    padding: .4em;
    width: 100%;
}

h3 {
    user-select: none;
}

p {
    margin: .2em 0;
    font-size: 1.1em;
}

textarea {
    width: 300px;
    height: 80px;
    padding: .4em;
    border-radius: 5px;
    font-size: 1em;
    resize: none;
    margin-bottom: .7em;
}

input[type="date"] {
    width: 150px;
    text-align: center;
    margin-bottom: 3em;
}

button {
    width: 140px;
    padding: .4em;
    margin: .4em 0;
    cursor: pointer;
    border: none;
    background: linear-gradient(lightgreen, darkgreen);
    border-radius: 5px;
    font-family: inherit;
    font-size: .8em;
    text-transform: uppercase;
    box-shadow: 0 2px 2px rgba(0, 0, 0, .5);
}

button:active {
    box-shadow: 0 1px 1px rgba(0, 0, 0, .7);
}

button:focus,
textarea:focus,
input:focus {
    outline: none;
}

.note {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    font-style: italic;
    user-select: none;
    word-break: break-all;
    position: relative;
}

.note p {
    width: 240px;
    font-size: 1em;
}

.note span {
    display: block;
    cursor: pointer;
    font-weight: bold;
    font-style: normal;
}

.info {
    color: blue;
}

.notify {
    color: #ddd;
    font-size: .9em;
    font-weight: normal !important;
    text-align: center;
    line-height: 25px;
    border-radius: 5px;
    width: 130px;
    height: 25px;
    position: absolute;
    top: -10px;
    left: -65px;
    background: rgba(0, 0, 0, .6);
    transition: .2s;
    opacity: 0;
}

.show {
    opacity: 1;
}

.info.null,
.notify.null {
    display: none;
}

.complete {
    padding: 0 .4em;
    color: green;
}

.delete {
    padding-left: .4em;
    color: red;
}

.line-through {
    text-decoration: line-through;
}


No les prestes mucha atención todavía.

Pasamos al guión.

Encuentre los campos de entrada y cree un contenedor para notas:

let textarea = document.querySelector('textarea')
let dateInput = document.querySelector('input[type="date"]')

let list = document.createElement('div')
list.classList.add('list')
document.body.appendChild(list)

Crear una base de datos y almacenamiento:

let db;
// IIFE
(async () => {
    //   
    // , ...
    db = await idb.openDb('db', 1, db => {
        //  
        db.createObjectStore('notes', {
            keyPath: 'id'
        })
    })

    //  
    createList()
})();

Considere la función de agregar notas para comprender qué es una sola nota o, más precisamente, qué contiene. Esto ayudará a comprender cómo se forma la lista:

//         ""
document.querySelector('.add-btn').onclick = addNote

const addNote = async () => {
    //      ,   
    if (textarea.value === '') return

    //    
    let text = textarea.value

    //     
    //    
    //    null    
    let date
    dateInput.value === '' ? date = null : date = dateInput.value

    //    
    let note = {
        id: id,
        text: text,
        //  
        createdDate: new Date().toLocaleDateString(),
        //  
        completed: '',
        //  
        notifyDate: date
    }

    //     
    try {
        await db.transaction('notes', 'readwrite')
            .objectStore('notes')
            .add(note)
        //  
        await createList()
            //   
            .then(() => {
                textarea.value = ''
                dateInput.value = ''
            })
    } catch { }
}

Ahora vamos a crear una lista:

let id

const createList = async () => {
    //  
    //     API 
    list.innerHTML = `<h3>Today is ${new Intl.DateTimeFormat('en', { year: 'numeric', month: 'long', day: 'numeric' }).format()}</h3>`

    //     
    let notes = await db.transaction('notes')
        .objectStore('notes')
        .getAll()

    //    
    let dates = []

    //     
    if (notes.length) {
        //   "id"   
        id = notes.length

        //   
        notes.map(note => {
           //    
            list.insertAdjacentHTML('beforeend',
            //    "data-id"
            `<div class = "note" data-id="${note.id}">
            //  
            <span class="notify ${note.notifyDate}">${note.notifyDate}</span>
            //  ()  
            //  ,     
            //        
            //    
            //       (CSS: .info.null, .notify.null)
            <span class="info ${note.notifyDate}">?</span>

            //  ()  
            <span class="complete">V</span>
            //         
            <p class="${note.completed}">Text: ${note.text}, <br> created: ${note.createdDate}</p>
            //  ()  
            <span class="delete">X</span>
        </div>`)
            //     
            //    
            if (note.notifyDate === null) {
                return
            //   
            } else {
                //  
                dates.push({
                    id: note.id,
                    date: note.notifyDate.replace(/(\d+)-(\d+)-(\d+)/, '$3.$2.$1')
                })
            }
        })
    //      
    } else {
        //   "id"  0
        id = 0

        //       
        list.insertAdjacentHTML('beforeend', '<p class="note">empty</p>')
    }
    // ...to be continued

Una matriz de objetos para almacenar fechas de recordatorios tiene dos campos: "id" para identificar notas y "fecha" para comparar fechas. Al escribir el valor de la fecha de recordatorio en el campo "fecha", nos vemos obligados a convertir este valor, ya que inputDate.value devuelve datos en el formato "aaaa-mm-dd", y vamos a comparar estos datos con los datos en el formato al que estamos acostumbrados, es decir "Dd.mm.aaaa". Por lo tanto, utilizamos el método de "reemplazar" y la expresión regular, donde, mediante la agrupación, invertimos bloques y reemplazamos guiones con puntos. Puede haber una solución más versátil o elegante.

A continuación, trabajamos con notas:

       // ...
       //          ""
       //       
       //     /   
       document.querySelectorAll('.note').forEach(note => note.addEventListener('click', event => {
        //        "complete" (  )
        if (event.target.classList.contains('complete')) {
            // /    ( )  "line-through",    
            event.target.nextElementSibling.classList.toggle('line-through')

            //     
            //      "complete"
            note.querySelector('p').classList.contains('line-through')
                ? notes[note.dataset.id].completed = 'line-through'
                : notes[note.dataset.id].completed = ''

            //    
            db.transaction('notes', 'readwrite')
                .objectStore('notes')
                .put(notes[note.dataset.id])

        //        "delete" (  )
        } else if (event.target.classList.contains('delete')) {
            //          
            //  ,     id  
            deleteNote(+note.dataset.id)

        //        "info" (   )
        } else if (event.target.classList.contains('info')) {
            // /    ( )  "show",   
            event.target.previousElementSibling.classList.toggle('show')
        }
    }))

    //   
    checkDeadline(dates)
}

La función para eliminar una nota de la lista y el almacenamiento se ve así:

const deleteNote = async key => {
    //        ()
    await db.transaction('notes', 'readwrite')
        .objectStore('notes')
        .delete(key)
    await createList()
}

Nuestra aplicación no tiene la capacidad de eliminar la base de datos, pero la función correspondiente podría verse así:

document.querySelector('.delete-btn').onclick = async () => {
    //   
    await idb.deleteDb('dataBase')
        //  
        .then(location.reload())
}

La función de verificación de recordatorio compara la fecha actual y las fechas de recordatorio ingresadas por el usuario:

const checkDeadline = async dates => {
    //      ".."
    let today = `${new Date().toLocaleDateString()}`

    //   
    dates.forEach(date => {
        //         
        if (date.date === today) {
            //      "?"  "!"
            document.querySelector(`div[data-id="${date.id}"] .info`).textContent = '!'
        }
    })
}

Finalmente, agregue un controlador de errores al objeto Window que no se procesó en los bloques de código correspondientes:

window.addEventListener('unhandledrejection', event => {
    console.error('error: ' + event.reason.message)
})

El resultado se ve así:



→ Código en Github

Aquí hay una aplicación similar en Almacenamiento local:



→ El código de esta aplicación en Github

me agradará cualquier comentario.

Gracias por su atención.

All Articles