Menulis aplikasi pengambilan catatan JavaScript



Selamat siang teman!

Hari ini kita, sesuai namanya, akan menulis aplikasi sederhana untuk membuat dan menyimpan catatan.

Fitur aplikasi kami adalah sebagai berikut:

  1. Buat catatan.
  2. Penyimpanan catatan.
  3. Hapus catatan.
  4. Tandai tentang tugas.
  5. Informasi tentang tanggal tugas selesai.
  6. Pengingat untuk menyelesaikan tugas.

Aplikasi akan ditulis dalam JavaScript.

Catatan akan disimpan dalam basis data yang diindeks (IndexedDB). Pustaka ini akan digunakan untuk memfasilitasi bekerja dengan IndexedDB . Menurut pengembang perpustakaan ini, "sama dengan IndexedDB, tetapi dengan janji."

Diasumsikan bahwa Anda terbiasa dengan dasar-dasar IndexedDB. Jika tidak, saya sarankan membaca artikel ini sebelum melanjutkan .

Saya mengerti bahwa untuk memecahkan masalah seperti menyimpan catatan, LocalStorage sudah cukup. Namun, saya ingin menjelajahi beberapa fitur dari IndexedDB. Dengan demikian, pilihan yang mendukung yang terakhir dibuat semata-mata dari pertimbangan epistemologis. Pada akhirnya, Anda akan menemukan tautan ke aplikasi serupa di mana penyimpanan data diimplementasikan menggunakan LocalStorage.

Jadi ayo pergi.

Markup kami terlihat seperti ini:

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

Catatan:

  1. Kolom input dapat dibuat menggunakan tag โ€œgambarโ€ dan โ€œfigcaptionโ€. Akan lebih semantik, jadi untuk berbicara.
  2. Ternyata kemudian, memilih tag "input" dengan jenis "tanggal" bukanlah solusi terbaik. Tentang itu di bawah ini.
  3. Dalam satu aplikasi, pengingat (pemberitahuan) diimplementasikan menggunakan API Pemberitahuan. Namun, rasanya aneh bagi saya untuk meminta izin pengguna untuk menampilkan pemberitahuan dan menambahkan kemampuan untuk mematikannya, karena, pertama, ketika kita berbicara tentang aplikasi untuk catatan (tugas), pengingat tersirat, dan kedua, mereka dapat diimplementasikan sehingga mereka tidak mengganggu pengguna setelah penampilan berulang, yaitu diam-diam.
  4. Awalnya, aplikasi menyediakan kemampuan untuk menunjukkan tidak hanya tanggal, tetapi juga waktu pengingat. Selanjutnya, saya memutuskan bahwa tanggalnya sudah cukup. Namun, jika diinginkan, mudah untuk ditambahkan.

Kami menghubungkan gaya:
* {
    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;
}


Jangan terlalu memperhatikan mereka.

Kami lolos ke skrip.

Temukan bidang input dan buat wadah untuk catatan:

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)

Buat database dan penyimpanan:

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

    //  
    createList()
})();

Pertimbangkan fungsi menambahkan catatan untuk memahami apakah satu catatan itu atau, lebih tepatnya, apa yang dikandungnya. Ini akan membantu untuk memahami bagaimana daftar ini terbentuk:

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

Sekarang kita akan membuat daftar:

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

Array objek untuk menyimpan tanggal pengingat memiliki dua bidang: "id" untuk mengidentifikasi catatan dan "tanggal" untuk membandingkan tanggal. Menulis nilai tanggal pengingat di bidang "tanggal", kami terpaksa mengonversi nilai ini, karena inputDate.value mengembalikan data dalam format "yyyy-mm-dd", dan kami akan membandingkan data ini dengan data dalam format yang biasa kami gunakan, yaitu "Dd.mm.yyyy". Oleh karena itu, kami menggunakan metode "ganti" dan ekspresi reguler, di mana, menggunakan pengelompokan, invert blok, dan ganti tanda hubung dengan titik. Mungkin ada solusi yang lebih serbaguna atau elegan.

Selanjutnya, kami bekerja dengan catatan:

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

Fungsi untuk menghapus catatan dari daftar dan penyimpanan terlihat seperti ini:

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

Aplikasi kami tidak memiliki kemampuan untuk menghapus database, tetapi fungsi yang sesuai bisa terlihat seperti ini:

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

Fungsi pemeriksaan pengingat membandingkan tanggal saat ini dan tanggal pengingat yang dimasukkan oleh pengguna:

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

Akhirnya, tambahkan penangan kesalahan ke objek Window yang tidak diproses dalam blok kode yang sesuai:

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

Hasilnya terlihat seperti ini:



โ†’ Kode pada Github

Berikut adalah aplikasi serupa pada Penyimpanan Lokal:



โ†’ Kode aplikasi ini di Github

Saya akan dengan senang hati memberikan komentar.

Terima kasih atas perhatian Anda.

All Articles