كتابة تطبيق تدوين ملاحظات JavaScript



يوم جيد يا اصدقاء!

اليوم ، كما يوحي الاسم ، سنكتب تطبيقًا بسيطًا لإنشاء الملاحظات وتخزينها.

ستكون ميزات تطبيقنا على النحو التالي:

  1. قم بإنشاء ملاحظة.
  2. تخزين الملاحظات.
  3. حذف ملاحظة.
  4. ضع علامة على المهمة.
  5. معلومات حول تاريخ إتمام المهمة.
  6. تذكير لإكمال المهمة.

سيتم كتابة التطبيق في جافا سكريبت.

سيتم تخزين الملاحظات في قاعدة بيانات مفهرسة (IndexedDB). سيتم استخدام هذه المكتبة لتسهيل العمل مع IndexedDB . وبحسب مطوري هذه المكتبة ، فهي "مثل IndexedDB ، ولكن مع وعود".

من المفترض أنك معتاد على أساسيات قاعدة البيانات المفهرسة. إذا لم يكن الأمر كذلك ، فإنني أوصي بقراءة هذه المقالة قبل المتابعة .

أفهم أنه لحل مشكلة مثل تخزين الملاحظات ، فإن LocalStorage يكفي. ومع ذلك ، أردت استكشاف بعض ميزات IndexedDB. وهكذا ، تم اختيار الخيار الأخير فقط من الاعتبارات المعرفية. في النهاية ، ستجد روابط لتطبيق مشابه حيث يتم تنفيذ تخزين البيانات باستخدام LocalStorage.

إذن هيا بنا نذهب.

يبدو ترميزنا كما يلي:

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

ملاحظات:

  1. يمكن إنشاء حقول الإدخال باستخدام العلامتين "الشكل" و "الشكل". سيكون أكثر دلالة ، إذا جاز التعبير.
  2. كما اتضح لاحقًا ، لم يكن اختيار علامة "الإدخال" من نوع "التاريخ" هو الحل الأفضل. حول ذلك أدناه.
  3. في تطبيق واحد ، يتم تنفيذ التذكيرات (الإخطارات) باستخدام واجهة برمجة التطبيقات الخاصة بالإشعارات. ومع ذلك ، بدا غريباً أن أطلب من المستخدم إذنًا لعرض الإخطارات وإضافة القدرة على إيقاف تشغيلها ، لأنه ، أولاً ، عندما نتحدث عن تطبيق الملاحظات (المهام) ، يتم تضمين التذكيرات ، وثانيًا ، يمكن تنفيذها بحيث لا تزعج المستخدم عند الظهور المتكرر ، أي بشكل غير ملحوظ.
  4. في البداية ، وفر التطبيق القدرة على الإشارة ليس فقط إلى التاريخ ، ولكن أيضًا إلى وقت التذكير. بعد ذلك ، قررت أن التاريخ كافٍ. ومع ذلك ، إذا رغبت في ذلك ، فمن السهل إضافتها.

نحن نربط الأنماط:
* {
    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;
}


لا توليهم الكثير من الاهتمام حتى الآن.

ننتقل إلى البرنامج النصي.

ابحث عن حقول الإدخال وأنشئ حاوية للملاحظات:

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)

إنشاء قاعدة بيانات وتخزين:

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

    //  
    createList()
})();

ضع في اعتبارك وظيفة إضافة الملاحظات لفهم ماهية ملاحظة واحدة ، أو بشكل أكثر دقة ، ما تحتويه. سيساعد هذا على فهم كيفية تكوين القائمة:

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

الآن سنقوم بإنشاء قائمة:

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

تحتوي مجموعة من الكائنات لتخزين تواريخ التذكير على حقلين: "id" لتحديد الملاحظات و "date" لمقارنة التواريخ. عند كتابة قيمة تاريخ التذكير في حقل "التاريخ" ، نضطر إلى تحويل هذه القيمة ، حيث إن inputDate.value تُرجع البيانات بالتنسيق "yyyy-mm-dd" ، وسنقوم بمقارنة هذه البيانات مع البيانات بالصيغة التي اعتدنا عليها ، أي "Dd.mm.yyyy". لذلك ، نستخدم طريقة "استبدال" والتعبير العادي ، حيث باستخدام التجميع ، يتم عكس الكتل واستبدال الواصلات بنقاط. قد يكون هناك حل أكثر تنوعًا أو أناقة.

بعد ذلك ، نعمل مع الملاحظات:

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

تبدو وظيفة حذف ملاحظة من القائمة والتخزين كما يلي:

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

لا يمتلك تطبيقنا القدرة على حذف قاعدة البيانات ، ولكن قد تبدو الوظيفة المقابلة كما يلي:

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

تقارن وظيفة فحص التذكير التاريخ الحالي وتواريخ التذكير التي أدخلها المستخدم:

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

أخيرًا ، أضف معالج الأخطاء إلى كائن Window الذي لم تتم معالجته في كتل التعليمات البرمجية المقابلة:

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

تبدو النتيجة كالتالي:



→ Code على Github

هنا تطبيق مشابه على التخزين المحلي:



→ كود هذا التطبيق على Github

سأكون سعيدا بأي تعليق.

شكرآ لك على أهتمامك.

All Articles