Schreiben einer JavaScript-Notizanwendung



Guten Tag, Freunde!

Wie der Name schon sagt, werden wir heute eine einfache Anwendung zum Erstellen und Speichern von Notizen schreiben.

Die Funktionen unserer Anwendung sind wie folgt:

  1. Erstellen Sie eine Notiz.
  2. Aufbewahrung von Notizen.
  3. Notiz löschen.
  4. Markieren Sie über die Aufgabe.
  5. Informationen zum Datum, an dem die Aufgabe abgeschlossen wurde.
  6. Erinnerung, um eine Aufgabe abzuschließen.

Die Anwendung wird in JavaScript geschrieben.

Notizen werden in einer indizierten Datenbank (IndexedDB) gespeichert. Diese Bibliothek wird verwendet, um die Arbeit mit IndexedDB zu erleichtern . Laut den Entwicklern dieser Bibliothek ist es "dasselbe wie IndexedDB, aber mit Versprechungen".

Es wird davon ausgegangen, dass Sie mit den Grundlagen von IndexedDB vertraut sind. Wenn nicht, empfehle ich, diesen Artikel zu lesen, bevor Sie fortfahren .

Ich verstehe, dass LocalStorage ausreicht, um ein Problem wie das Speichern von Notizen zu lösen. Ich wollte jedoch einige der Funktionen von IndexedDB untersuchen. Die Entscheidung für Letzteres wurde daher ausschließlich aus erkenntnistheoretischen Gründen getroffen. Am Ende finden Sie Links zu einer ähnlichen Anwendung, in der die Datenspeicherung mit LocalStorage implementiert wird.

So lass uns gehen.

Unser Markup sieht folgendermaßen aus:

<!-- 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>

Bemerkungen:

  1. Eingabefelder können mit den Tags "figure" und "figcaption" erstellt werden. Es wäre sozusagen semantischer.
  2. Wie sich später herausstellte, war die Auswahl des Tags "Eingabe" mit dem Typ "Datum" nicht die beste Lösung. Darüber unten.
  3. In einer Anwendung werden Erinnerungen (Benachrichtigungen) mithilfe der Benachrichtigungs-API implementiert. Es erschien mir jedoch seltsam, den Benutzer um Erlaubnis zu bitten, Benachrichtigungen anzuzeigen und diese zu deaktivieren, da erstens, wenn wir über die Anwendung für Notizen (Aufgaben) sprechen, Erinnerungen impliziert werden und zweitens sie implementiert werden können, damit sie nicht stören Benutzer bei wiederholtem Erscheinen, d.h. unauffällig.
  4. Anfänglich bot die Anwendung die Möglichkeit, nicht nur das Datum, sondern auch die Uhrzeit der Erinnerung anzugeben. Anschließend habe ich entschieden, dass das Datum ausreicht. Falls gewünscht, ist es jedoch einfach hinzuzufügen.

Wir verbinden Stile:
* {
    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;
}


Achten Sie noch nicht besonders auf sie.

Wir gehen zum Drehbuch über.

Suchen Sie die Eingabefelder und erstellen Sie einen Container für Notizen:

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)

Erstellen Sie eine Datenbank und einen Speicher:

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

    //  
    createList()
})();

Betrachten Sie die Funktion des Hinzufügens von Notizen, um zu verstehen, was eine einzelne Notiz ist oder genauer gesagt, was sie enthält. Dies hilft zu verstehen, wie die Liste gebildet wird:

//         ""
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 { }
}

Jetzt erstellen wir eine Liste:

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

Ein Array von Objekten zum Speichern von Erinnerungsdaten enthält zwei Felder: "ID" zum Identifizieren von Notizen und "Datum" zum Vergleichen von Daten. Wenn wir den Wert des Erinnerungsdatums in das Feld "Datum" schreiben, müssen wir diesen Wert konvertieren, da inputDate.value Daten im Format "JJJJ-MM-TT" zurückgibt und wir diese Daten mit den Daten in dem gewohnten Format vergleichen "DD / MM / JJJJ". Daher verwenden wir die Methode "Ersetzen" und den regulären Ausdruck, wobei wir mithilfe der Gruppierung Blöcke invertieren und Bindestriche durch Punkte ersetzen. Möglicherweise gibt es eine vielseitigere oder elegantere Lösung.

Als nächstes arbeiten wir mit Notizen:

       // ...
       //          ""
       //       
       //     /   
       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)
}

Die Funktion zum Löschen einer Notiz aus der Liste und dem Speicher sieht folgendermaßen aus:

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

Unsere Anwendung kann die Datenbank nicht löschen, aber die entsprechende Funktion könnte folgendermaßen aussehen:

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

Die Erinnerungsprüfungsfunktion vergleicht das aktuelle Datum und die vom Benutzer eingegebenen Erinnerungsdaten:

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 = '!'
        }
    })
}

Fügen Sie abschließend dem Window-Objekt einen Fehlerhandler hinzu, der nicht in den entsprechenden Codeblöcken verarbeitet wurde:

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

Das Ergebnis sieht folgendermaßen aus:



→ Code auf Github

Hier ist eine ähnliche Anwendung auf Local Storage:



→ Den Code dieser Anwendung auf Github

freue ich mich über Kommentare.

Vielen Dank für Ihre Aufmerksamkeit.

All Articles