اذهب إلى المحتوى

مكون الرسائل المنبثقة Modal في بوتستراب


حسام برهان

سنتعامل في هذا الدرس مع مكوّن جديد ومفيد من بوتستراب، ألا وهو مكوّن الرسائل المنبثقة Modal. يُستخدم هذا المكوّن في إضفاء ميزة جمالية عندما نريد التواصل مع المستخدم، كأن نُظهر به رسالة تفيد بالانتهاء من تنفيذ إجراء معيّن، أو الإبلاغ عن حدوث خطأ ما، أو حتى عندما نريد أن نُخبر المستخدم بمعلومة معيّنة بحيث يمكنه أن يتخذ قرارًا ما بناء على ذلك. وكما جرت العادة، سنطبّق ما سنتعلّمه في هذا الدرس على موقع" نبيه" الذي بنيناه في مقالات سابقة من هذه السلسلة.

سنعمل في هذا الدرس على:

  • التعرّف على مكوّن الرسائل المنبثقة Modal في بوتستراب.
  • إضافة المنتجات إلى سلة المشتريات في موقع نبيه.
  • إنشاء صفحة جديدة في موقع نبيه لعرض محتويات سلة المشتريات.

هذا الفصل جزء من سلسلة فصول عن بوتستراب 5، وإليك كامل فهرس السلسلة:

التعرف على مكون الرسائل المنبثقة Modal في بوتستراب

يمكن إنشاء أشكال متنوّعة وغنيّة من مكوّن الرسائل المنبثقة. سنستعرض فيما يلي الشكل المبسّط الأساسي التالي:

<div class="modal" tabindex="-1" id="exampleModal"> <!-- الموضع 1 -->
  <div class="modal-dialog"> <!-- الموضع 2 -->
    <div class="modal-content"> <!-- الموضع 3 -->
      <div class="modal-header"><!-- الموضع 4 -->
        <h5 class="modal-title">العنوان هنا</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body"> <!-- الموضع 5 -->
        <p>محتويات النافذة المنبثقة هنا</p>
      </div>
      <div class="modal-footer"> <!-- الموضع 6 -->
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إغلاق</button>
        <button type="button" class="btn btn-primary">حفظ التغييرات</button>
      </div>
    </div>
  </div>
</div>

من الشيفرة السابقة يبدأ تعريف النافذة المنبثقة في "الموضع 1" من خلال الصنف modal لعنصر div عادي. أما في "الموضع 2" نستخدم الصنف modal-dialog الضروري أيضًا لكي تعمل النافذة المنبثقة بشكل صحيح. تُوضع محتويات النافذة المنبثقة بشكل كامل ضمن عنصر div يحمل الصنف modal-content ويمثِّل "الموضع 3" الحاضن لمحتويات النافذة المنبثقة.

يمكن أن يحتوي العنصر div.modal-content بدوره على ثلاثة أقسام:

  • ترويسة النافذة المنبثق"الموضع 4" وهي عنصر div له الصنف modal-header.
  • جسم النافذة المنبثقة "الموضع 5" وهي عنصر div له الصنف modal-body.
  • تذييل النافذة المنبثقة "الموضع 6" وهي عنصر div له الصنف modal-footer.

بالنسبة للترويسة، فيمكن أن نضع فيها عنوان النافذة المنبثقة (عن طريق عنصر div له التنسيق modal-title) أو بمعنى آخر عنوان الرسالة التي نريد عرضها للمستخدم، بالإضافة إلى إمكانية وضع زر صغير (له التنسيق btn-close) لنسمح للمستخدم بإغلاق هذه النافذة. أمّا جسم النافذة، فمكن أن نضع فيه أي محتوى ملائم نريد عرضه للمستخدم. وبالنسبة للتذييل، فيوضع فيه عادةً أزرار التحكّم التي يمكن من خلالها الاستجابة للرسالة أو المعلومة التي يعرضها التطبيق.

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

<!-- Button trigger modal -->
    <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
        أظهر النافذة
    </button>

يمكن وضع هذا الزر قبل أو بعد الشيفرة السابقة ضمن القسم body من الصفحة. لاحظ أنّ السمة data-bs-target من الزر السابق لها القيمة ‎#exampleModal وهي نفسها معرّف عنصر div الرئيسي "الموضع 1" الذي يحضن النافذة المنبثقة.

يجب عليك الانتباه أيضًا أنّه لن يمكنك تجربة الشيفرة السابقة بشكل ناجح دون استيراد مكتبات بوتستراب بشكل صحيح، كما تعلّمنا ذلك مسبقًا.

بعد أن تعمل الشيفرة السابقة بشكل صحيح، ستحصل على شكل شبيه بما يلي:

1.png

ستلاحظ عند الضغط على الزر "إغلاق" اختفاء هذه النافذة وسبب ذلك هو وجود السمة data-bs-dismiss مع القيمة modal لها، ويمكنك أيضًا الخروج من هذه النافذة بالنقر في أي مكان خارجها، وهو السلوك الافتراضي. يمكنك منع هذا السلوك، أي أنّك تجعل إخفاء هذه النافذة يكون حصرًا بالنقر على الزر "إغلاق" بأن تضع السمة data-bs-backdrop مع القيمة static لها وذلك ضمن عنصر div الرئيسي "الموضع 1".

يمكنك أيضًا الحصول على تأثير لطيف عند إظهار النافذة وإخفائها وذلك بأني تضيف التنسيق fade إلى الصنف modal ضمن عنصر div الرئيسي "الموضع 1".

سيصبح عنصر div الرئيسي بعد التعديلين المقترحين على الشكل التالي:

<div class="modal fade" tabindex="-1" id="exampleModal" data-bs-backdrop="static">

في الحقيقة، توجد العديد من الإضافات والتنسيقات المختلفة التي يمكنك الإطلاع عليها من خلال صفحة التوثيق الرسمية لهذا المكوّن.

إضافة المنتجات إلى سلة المشتريات في موقع نبيه

سنعمل في هذه الفقرة على محاكاة عملية إضافة المنتجات إلى سلّة المشتريات الخاصة بموقع نبيه (يمكنك تنزيل شيفرة الدرس الخامس السابق من الملف المرفق في الأسفل).

في الحقيقة هناك العديد من الملاحظات حول هذا الموضوع، من أهمّها، أّنه من المنطقي إنشاء حسابات للمستخدمين كي يستطيع المستخدم إضافة أي منتج يرغبه إلى سلّة المشتريات الخاصة به، وأيضًا أنّنا في هذا المشروع سنعمل على حفظ المنتجات المضافة للسلة إلى التخزين المحلّي الخاص بالمتصفّخ وليس إلى قاعدة بيانات موجودة على الإنترنت. سنتغاضى في الحقيقة عن هذين المتطلّبين المهمّين والبديهيين في التطبيقات العمليّة، وذلك بسبب أنّ الهدف من هذا المشروع هو شرح مفاهيم بوتستراب 5 بصورة مبسّطة وميسرة.

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

  • إضافة الشيفرة اللازمة للإظهار نافذة منبثقة عندما يريد المستخدم أن يضيف منتج ما إلى سلّة المشتريات.
  • إضافة قسم لشيفرة جافاسكريبت تتمثّل مهمتها في إدارة عملية إضافة المنتجات التي يرغبها المستخدم إلى التخزين الداخلي ضمن المتصفّح وأيضًا تحديث أيقونة سلّة المشتريات الموجودة في أعلى الصفحة بالعدد الحالي للمنتجات التي اختارها المستخدم.

في الحقيقة ستعمل شيفرة جافاسكربت السابقة على تضمين وحدة برمجية module اسمها main الموجودة ضمن الملف main.js، حيث تحتوي هذه الوحدة على بعض التوابع المفيدة التي تدير العمليات البرمجية الكاملة التي نحتاجها في هذا الدرس (سنتحدّث عن هذه الوحدة بعد قليل).

ستصبح الشيفرة الخاصة بالملف index.html على الشكل التالي (الملف مرفق في نهاية المقال):

<!doctype html>
<html lang="ar" dir="rtl">

<head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <!-- Bootstrap CSS -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.rtl.min.css"
    integrity="sha384-gXt9imSW0VcJVHezoNQsP+TNrjYXoGcrqBZJpry9zJt8PCQjobwmhMGaDHTASo9N" crossorigin="anonymous">
  <link href="css/styles.css" rel="stylesheet" />

  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link
    href="https://fonts.googleapis.com/css2?family=Almarai:wght@300;400;700;800&family=Tajawal:wght@200;300;400;500;700;800;900&display=swap"
    rel="stylesheet">

  <!-- Boostrap 5 icons - web font -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.0/font/bootstrap-icons.css">

  <title>دورات نبيه</title>
</head>

<body>
  <!-- نافذة منبثقة -->
  <div class="modal fade" id="addToCartConfirmation" tabindex="-1" aria-labelledby="addToCartConfirmation"
    aria-hidden="true">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="modalLabel">نبيه</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          تمت الإضافة بنجاح
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إغلاق</button>
          <button type="button" class="btn btn-primary" onclick="window.location='shopping-cart.html'">الذهاب إلى السلة</button>
        </div>
      </div>
    </div>
  </div>

  <section id="header">
    <!-- Nav Bar -->
    <nav class="navbar navbar-expand-lg navbar-light">
      <div class="container-fluid">
        <a class="navbar-brand" href="#">
          <img src="images/nabih-logo.png" style="margin-left: 8px;" alt="" height="32">
          نبيه
        </a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav me-2 mb-2 mb-lg-0 ms-3">
            <li class="nav-item"><a class="nav-link" href="#">التصنيفات</a></li>
          </ul>
          <form class="d-flex me-auto w-100">
            <input class="form-control" type="search" placeholder="ابحث عن أي موضوع">
            <div class="nav-link" style="position: relative;" onclick="goToShoppingCart()">
              <div class="counter zero"></div>

              <a href="shopping-cart.html"><img src="images/icons8-shopping-cart-32.png" /></a> 
            </div>

            <button type="button" class="btn btn-dark nabih-buttons">دخول</button>
            <button type="button" class="btn btn-outline-dark nabih-buttons">تسجيل جديد</button>
          </form>
        </div>
      </div>
    </nav>

    <!--Main View-->
    <div class="mainview">
      <div class="row">
        <div class="col-lg-6">
          <h1 class="mainview-heading">دورات غنية بجودة عالية</h1>
          <h3 class="mainview-subheading"> طور نفسك لتنافس في سوق العمل</h3>
        </div>
        <div class="col-lg-6">
          <img class="mainview-image" src="images/show-case.jpg" />
        </div>
      </div>
    </div>
  </section>

  <section id="best-selling">
    <div class="section-title">الدورات الأكثر مبيعًا</div>
    <div id="bestSellingCourses" class="carousel carousel-dark slide" data-bs-ride="carousel">
      <div class="carousel-inner">
        <div class="carousel-item active">

          <div class="row">

            <div class="col-lg-3">
              <div class="card" name="productCard" id="P1">
                <img src="images/python-product.jpg" class="card-img-top product-image" alt="python-course">
                <div class="card-body">
                  <h5 class="product-title card-title">دورة أساسيات البرمجة باستخدام بايثون</h5>
                  <p class="product-text card-text">في هذه الدورة سنتعلّم مبادئ البرمجة في لغة بايثون</p>
                  <a href="#" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addToCartConfirmation">أضف
                    إلى السلة</a>
                  <div class="price-tag">$10</div>
                </div>
              </div>
            </div>

            <div class="col-lg-3">
              <div class="card" name="productCard" id="P2">
                <img src="images/github-product.jpg" class="card-img-top product-image" alt="github-course">
                <div class="card-body">
                  <h5 class="product-title card-title">دورة التعامل مع GitHub</h5>
                  <p class="product-text card-text">تعلّم كيف تدير مشاريعك البرمجية من حيث إدارة الإصدار والمساهة في
                    مشاريع برمجية
                    أخرى.</p>
                  <a href="#" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addToCartConfirmation">أضف
                    إلى السلة</a>
                  <div class="price-tag">$15</div>
                </div>
              </div>
            </div>

            <div class="col-lg-3">
              <div class="card" name="productCard" id="P3">
                <img src="images/python-django-product.jpg" class="card-img-top product-image" alt="django-course">
                <div class="card-body">
                  <h5 class="product-title card-title">دورة تطوير تطبيقات ويب باستخدام بايثون مع Django</h5>
                  <p class="product-text card-text">تعلّم كيف تبني تطبيقات ويب باستخدام لغة بايثون مع Django </p>
                  <a href="#" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addToCartConfirmation">أضف
                    إلى السلة</a>
                  <div class="price-tag">$20</div>
                </div>
              </div>
            </div>

            <div class="col-lg-3">
              <div class="card" name="productCard" id="P4">
                <img src="images/javascript-product.jpg" class="card-img-top product-image" alt="javascript-course">
                <div class="card-body">
                  <h5 class="product-title card-title">دورة أساسيات البرمجة باستخدام جافاسكريبت</h5>
                  <p class="product-text card-text"> سنتعلّم في هذه الدورة كيفية تطوير تطبيقات الواجهة الأمامية باستخدام
                    جافاسكريبت
                  </p>
                  <a href="#" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addToCartConfirmation">أضف
                    إلى السلة</a>
                  <div class="price-tag">$25</div>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="carousel-item">
          <div class="row">
            <div class="col-lg-3">
              <div class="card" name="productCard" id="P5">
                <img src="images/php-product.jpg" class="card-img-top product-image" alt="php-course">
                <div class="card-body">
                  <h5 class="product-title card-title">دورة أساسيات البرمجة باستخدام PHP</h5>
                  <p class="product-text card-text">في هذه الدورة سنتعلّم مبادئ البرمجة في لغة PHP</p>
                  <a href="#" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addToCartConfirmation">أضف
                    إلى السلة</a>
                  <div class="price-tag">$13</div>
                </div>
              </div>
            </div>

            <div class="col-lg-3">
              <div class="card" name="productCard" id="P6">
                <img src="images/arduino-product.jpg" class="card-img-top product-image" alt="arduino-course">
                <div class="card-body">
                  <h5 class="product-title card-title">دورة التعامل مع أساسيات Arduino</h5>
                  <p class="product-text card-text">تعلّم كيف تبني أنظمة مضمنه تتضمّن مشاريع بسيطة باستخدام Arduino</p>
                  <a href="#" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addToCartConfirmation">أضف
                    إلى السلة</a>
                  <div class="price-tag">$30</div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <button class="carousel-control-prev" type="button" data-bs-target="#bestSellingCourses" data-bs-slide="prev">
        <span class="carousel-control-prev-icon" aria-hidden="true"></span>
      </button>
      <button class="carousel-control-next" type="button" data-bs-target="#bestSellingCourses" data-bs-slide="next">
        <span class="carousel-control-next-icon" aria-hidden="true"></span>
      </button>
    </div>
  </section>

  <section id="course-categories">
    <div class="section-title">الأقسام المتاحة</div>
    <div class="row">
      <div class="col-lg-4">
        <a class="category-link" href="#">
          <div class="card category-card">
            <img src="images/programming-category.png" class="card-img-top category-image"
              alt="certifications-category">
            <div class="card-body">
              <p class="card-text category-text">البرمجة</p>
            </div>
          </div>
        </a>
      </div>

      <div class="col-lg-4">
        <a class="category-link" href="#">
          <div class="card category-card">
            <img src="images/marketing-category.png" class="card-img-top category-image" alt="certifications-category">
            <div class="card-body">
              <p class="card-text category-text">التسويق</p>
            </div>
          </div>
        </a>
      </div>

      <div class="col-lg-4">
        <a class="category-link" href="#">
          <div class="card category-card">
            <img src="images/freelance-category.png" class="card-img-top category-image" alt="certifications-category">
            <div class="card-body">
              <p class="card-text category-text">العمل الحر</p>
            </div>
          </div>
        </a>
      </div>
    </div>

    <div class="row">
      <div class="col-lg-4">
        <a class="category-link" href="#">
          <div class="card category-card">
            <img src="images/cloud-computing-category.png" class="card-img-top category-image"
              alt="certifications-category">
            <div class="card-body">
              <p class="card-text category-text">التطبيقات السحابية</p>
            </div>
          </div>
        </a>
      </div>

      <div class="col-lg-4">
        <a class="category-link" href="#">
          <div class="card category-card">
            <img src="images/certificates-category.png" class="card-img-top category-image"
              alt="certifications-category">
            <div class="card-body">
              <p class="card-text category-text">الشهادات العالمية</p>
            </div>
          </div>
        </a>
      </div>

      <div class="col-lg-4">
        <a class="category-link" href="#">
          <div class="card category-card">
            <img src="images/apps-category.png" class="card-img-top category-image" alt="certifications-category">
            <div class="card-body">
              <p class="card-text category-text">البرامج والتطبيقات</p>
            </div>
          </div>
        </a>
      </div>
    </div>
  </section>

  <section id="footer">
    <footer>
      <div class="container-fluid">
        <i class="bi bi-facebook social-icon"></i>
        <i class="bi bi-twitter social-icon"></i>
        <i class="bi bi-instagram social-icon"></i>
        <i class="bi bi-envelope social-icon"></i>
        <p>© دورات نبيه 2021</p>
      </div>
    </footer>
  </section>

  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
    crossorigin="anonymous"></script>

  <script type="module">
    import {updateCartLabel, saveItem} from './js/main.js';

    let products = document.getElementsByName('productCard');

    for (let product of products) {
      const product_id = product.attributes['id'].value;
      const product_title = product.getElementsByClassName('product-title')[0].innerHTML;
      const product_price = product.getElementsByClassName('price-tag')[0].innerHTML;
      const addToCartButton = product.getElementsByClassName('btn-primary')[0];

      addToCartButton.addEventListener('click', (e) => {
        const item_to_post = { id: product_id, course_name: product_title, price: product_price.substring(1) };
        saveItem(item_to_post);
        updateCartLabel();
      });
    }
  </script>

</body>

</html>

ستكون هناك أيضًا تعديلات طفيفة على ملف التنسيقات styles.css لكي يدعم صفحة سلة المشتريات. المحتوى الجديد للملف سيكون على النحو التالي:

body{
    font-family: 'Tajawal', sans-serif;
    font-size: 1em;
}

.nabih-buttons{
    margin: 0 4px; 
    padding: 6px 8px;
    width: 7rem;
    white-space: nowrap; 
}

.container-fluid{
    padding:0 5%;
}

.section-title{
    text-align: center;
    font-size: xx-large;
    padding: 3rem 0;
}

/*Header Section*/

.counter{
    color: white;
    margin: 0;
    position: absolute;
    font-size: 15px;
    text-align: center;
    right: 6px;
    top: 0px;
    width: 20px;
    height: 20px;
    background: red;
    border-radius: 50%;
}

.zero{
    opacity: 0;
}

#header{
    background-color: #eee;
}

.navbar-brand{
    font-family: 'Almarai';
    color:#393e46;
    font-size: 2.5rem;
    font-weight:900;
    margin: 0;
}

.mainview-heading {
    color: white;
    font-size: 4em;
    text-align: right;
}

.mainview-subheading{
    color: white;
    margin: 64px 0;
    text-align: right;
}

.mainview{
    background-color: #233e8b;
    padding: 3% 5%;
}

.mainview-image{
    border-radius: 2%;
    width: 500px;
    float: left;    
}

/*Best Selling Section*/

.carousel-inner{
    padding: 0 5%;
}
.carousel-control-prev {
    background: linear-gradient(to right, rgba(200,200,200,0), rgba(200,200,200,1));
}

.carousel-control-next {
    background: linear-gradient(to left, rgba(200,200,200,0), rgba(200,200,200,1));
}

.product-image{
    height: 12rem;
}

.product-title{
    height: 4rem;    
}

.product-text{
    height: 6rem;
}

.price-tag{
    float: left; 
    border-width: 1px; 
    border-style: solid; 
    width: 3rem; height: 2rem;
    text-align: center;
    padding:2px;
    margin-top:2px;
}

/*Course Categories Section*/

#course-categories{
    background-color: #eee;
    padding: 0 10% 8px;
}

#course-categories .row{
    margin-bottom: 72px;
}

.category-card{
    margin-left: 24px;
    margin-right:24px;
    padding: 48px 0px 32px;
}

.category-image{
    width: 50%;
    height: auto;
    margin: 0 auto;
}

.category-text{
    text-align: center;
    font-size: 1.3rem;
}

.category-link{
    text-decoration: none;
    color:inherit;
}

/*Footer Section*/

footer{
    height: 200px;
}

.social-icon {
    margin: 20px 10px;
  }

#footer .container-fluid{
    padding: 7% 15%;
    text-align: center;
}

/*Shopping Cart*/
.shopping-cart-title{
    padding: 3% 5%;
}
.shopping-cart-content{
    padding: 0 5%;
}

.shopping-cart-total{
    padding:0 5% 3%;
    font-weight: bold;
}

.shopping-cart-item{
    padding-top: 8px;
    padding-bottom: 8px;
    font-size: 1.1rem;
}

ستحصل على نافذة منبثقة شبيهة بما يلي:

3.png

إضافة الوحدة البرمجية main

إذا لم تكن قد أنشأت المجلّد js ضمن المجلّد الرئيسي للمشروع فافعل ذلك الآن، بعد ذلك أضف الملف main.js لهذا المجلّد. انسخ الشيفرة البرمجية التالية لهذا الملف:

export function saveItem(item) {
    let key = Object.keys(localStorage)
                .find(e => e === String(item.id));

    if(key){
        let tmp = JSON.parse(localStorage.getItem(key));
        tmp.push(item);
        localStorage.setItem(item.id, JSON.stringify(tmp));
    }
    else{
        localStorage.setItem(item.id, JSON.stringify([item]));
    }
}

export function getItem(id) {
    return window.localStorage.getItem(item.id);
}

export function getAllItems(){
    const keys = Object.keys(window.localStorage);
    let entries = [];

    for(let key of keys){
        entries.push(JSON.parse(localStorage.getItem(key)))
    }

    return entries;
}

export function updateShoppingCartList(){
    let shoppingCartContent = document.getElementById('shoppingCartContent');
    let counter = 1;
    let total_products = 0;
    let amount_due = 0;

    for (let group of getAllItems()) {
        const div = document.createElement('div');

        div.className = 'row shopping-cart-item';

        div.innerHTML = `
            <div class="col-1">
                <span>${counter}</span>
            </div>
            <div class="col-6">
                <span>${group[0].course_name}</span>
            </div>
            <div class="col-1">
                <span>${group.length}</span>
            </div>
            <div class="col-1">
                <span>$${group[0].price}</span>    
            </div>
            <div class="col-1">
                <span>$${group.length * group[0].price}</span>    
            </div>
            `;

        shoppingCartContent.appendChild(div);

        amount_due += group.length * group[0].price;
        counter++;
        total_products += group.length;
    }

    document.getElementById('amountDue').innerHTML = '$' + amount_due;
}

export function updateCartLabel(){
    const counterElement = document.getElementsByClassName('counter')[0];
    let count = 0;

    count = getAllItems().reduce( (acc, group) => acc + group.length, 0)

    if(count > 0){
        counterElement.innerHTML = count;
        counterElement.classList.remove('zero');
    }
}

يحتوي هذا الملف على خمسة توابع، وهي:

  • التابع saveItem ويقبل وسيطًا وحيدًا وهو العنصر "المنتج" الذي اختاره المستخدم، حيث يحفظه ضمن التخزين الداخلي للمتصفّح.
  • التابع getItem ويقبل وسيطًا واحيدًا هو معرّف المنتج الذي نود الحصول على بياناته من التخزين الداخلي.
  • التابع getAllItems وهو لا يحتاج إلى أية وسائط، ويُرجع مصفوفة تحتوي على جميع المنتجات المحفوظة ضمن التخزين الداخلي.
  • التابع updateShoppingCartList ومهمّته تحديث الصفحة الخاصة بسلّة المشتريات، بالمنتجات الموجودة ضمن التخزين الداخلي، حيث يعرضها بطريقة جدولية بسيطة، موضّحا اسم المنتج وعدد النسخ المطلوب شرائها منه، وسعر النسخة، وإجمالي المبلغ الواجب دفعه لهذا المنتج تحديدًا. وفي نهاية الجدول سيعرض أيضًا المجموع النهائي الواجب دفعه من قبل المستخدم (سنرى توضيحًا لبنية هذا الجدول بعد قليل).
  • التابع updateCartLabel ووظيفته تحديث أيقونة سلّة المشتريات الموجودة أعلى الصفحة بعدد نسخ المنتجات المطلوب شراؤها.
اقتباس

ملاحظة: ربما يبدو من غير المنطقي أن يشتري أحد المستخدمين أكثر من نسخة واحدة من نفس الدورة التدريبية "المنتج" وهذا صحيح بالطبع. في الحقيقة دعمت هذا التوجّه ضمن هذا المشروع بهدف إضافة مزية قد تفيدك في مشروع آخر.

عرض محتويات سلة المشتريات

أضف الملف shopping-cart.html إلى المشروع، وانسخ إليه المحتويات التالية:

<!DOCTYPE html>
<html lang="ar" dir="rtl">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.rtl.min.css"
        integrity="sha384-gXt9imSW0VcJVHezoNQsP+TNrjYXoGcrqBZJpry9zJt8PCQjobwmhMGaDHTASo9N" crossorigin="anonymous">
    <link href="css/styles.css" rel="stylesheet" />

    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link
        href="https://fonts.googleapis.com/css2?family=Almarai:wght@300;400;700;800&family=Tajawal:wght@200;300;400;500;700;800;900&display=swap"
        rel="stylesheet">

    <!-- Boostrap 5 icons - web font -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.0/font/bootstrap-icons.css">

    <title>دورات نبيه | سلة المشتريات</title>
</head>

<body>

    <section id="header">
        <!-- Nav Bar -->
        <nav class="navbar navbar-expand-lg navbar-light">
            <div class="container-fluid">
                <a class="navbar-brand" href="#">
                    <img src="images/nabih-logo.png" style="margin-left: 8px;" alt="" height="32">
                    نبيه
                </a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse"
                    data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
                    aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav me-2 mb-2 mb-lg-0 ms-3">
                        <li class="nav-item"><a class="nav-link" href="#">التصنيفات</a></li>
                    </ul>
                    <form class="d-flex me-auto w-100">
                        <input class="form-control" type="search" placeholder="ابحث عن أي موضوع">
                        <div class="nav-link" style="position: relative;">
                            <div class="counter zero"></div>

                            <img src="images/icons8-shopping-cart-32.png" />
                        </div>

                        <button type="button" class="btn btn-dark nabih-buttons">دخول</button>
                        <button type="button" class="btn btn-outline-dark nabih-buttons">تسجيل جديد</button>
                    </form>
                </div>
            </div>
        </nav>
    </section>

    <!--Main View-->
    <div class="shopping-cart-title"><h2>سلة المشتريات</h2></div>
    <div id="shoppingCartContent" class="shopping-cart-content">
        <div class="row">
            <div class="col-1">

            </div>
            <div class="col-6">
                <h5>الدورة</h5>
            </div>
            <div class="col-1">
                <h5>العدد</h5>
            </div>
            <div class="col-1">
                <h5>السعر</h5>
            </div>
            <div class="col-1">
                <h5>الإجمالي</h5>
            </div>
        </div>
    </div>
    <div class="row shopping-cart-total">
        <div id="amountDue" class="offset-9 col-1" style="border-top-style: solid;"></div>
    </div>
    <div class="row">
        <div class="col-4 shopping-cart-content"><a href="checkout.html" class="btn btn-primary">الشراء الآن</a></div>
    </div>
    <section id="footer">
        <footer>
            <div class="container-fluid">
                <i class="bi bi-facebook social-icon"></i>
                <i class="bi bi-twitter social-icon"></i>
                <i class="bi bi-instagram social-icon"></i>
                <i class="bi bi-envelope social-icon"></i>
                <p>© دورات نبيه 2021</p>
            </div>
        </footer>
    </section>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
        crossorigin="anonymous"></script>

    <script type="module">
        import {updateShoppingCartList, updateCartLabel} from './js/main.js';

        updateShoppingCartList();
        updateCartLabel();
    </script>
</body>
</html>

القسم الأول من الشيفرة السابقة مألوف بالنسبة لك، فهو مسؤول عن تعريف شريط التنقّل كما في الصفحة الرئيسية بالضبط. القسم الثاني من الشيفرة بدءًا من "الموضع 1" يعرّف بنية بسيطة تشبه الجدول مهمتها عرض محتويات سلة المشتريات. الشيفرة البرمجية المسؤولة عن تعبئة هذه البنية موجودة ضمن "الموضع 2". تستورد هذه الشيفرة المكتوبة بلغة جافا سكريبت، تابعين من الملف main.js (أضفناه قبل قليل)، ثم تعمل على استدعائهما تباعًا.

في حال وجود منتجات في السلة ستحصل على شكل شبيه بما يلي:

2.png

يمكنك الوصول إلى الشيفرة الكاملة لهذا الدرس من الملف bootstrap5-tutorial.zip، كما ويمكنك الإطلاع على نسخة حيّة من هذه الصفحة من هنا.

خاتمة

كان الدرس طويلًا بعض الشيء إلّا أنّنا أنجزنا الكثير! الميزة الجديدة التي تعرّفنا عليها في هذا الدرس هي مكوّن الرسائل المنبثقة Modal الذي يسمح بالتفاعل الفعّال مع المستخدم من خلال توفير وسيلة لعرض المعلومات للمستخدم، مع إمكانية توفير إمكانية التفاعل من خلال تلقي رد من المستخدم حول سؤال محّدد نرغب بالحصول على الإجابة عليه. سنتابع عملنا في الدرس القادم في توفير صفحة خاصّة للدفع، لشراء المنتجات التي اختارها المستخدم.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...