19: Final Workshop

ฝึกสร้าง Web Application ด้วยโครงสร้าง Semantic HTML ที่ถูกต้อง (Header, Main, Aside, Footer)

แอปพลิเคชัน "My Reading List" 📚

โจทย์: สร้างหน้าเว็บสำหรับอ่านบทความ โดยจะดึงข้อมูลบทความจำลองจาก Server มาแสดงในพื้นที่หลัก (<main>) เมื่อผู้ใช้อ่านชื่อบทความแล้วสนใจ สามารถกดปุ่ม "บันทึกไว้อ่านทีหลัง" บทความนั้นจะถูกเก็บลง LocalStorage และไปแสดงผลอยู่ในแถบด้านข้าง (<aside>)


API ที่ใช้: JSONPlaceholder (Posts)

จะดึงบทความมาแค่ 6 อัน ข้อมูลที่ได้จะเป็น Array ของ Object (1 บทความ = 1 Object)

JS
const url = "https://jsonplaceholder.typicode.com/posts?_limit=6";

// โครงสร้างข้อมูลแต่ละบทความ:
// 1. id: รหัสบทความ (เช่น 1, 2, 3...)
// 2. title: หัวข้อบทความ
// 3. body: เนื้อหาบทความ

ขั้นตอนที่ 1: เตรียม UI จาก HTML และ CSS ทีละ Component

สร้างไฟล์ reading-list.html ขึ้นมาใหม่ อย่าลืมใส่แท็กของ Bootstrap, FontAwesome ไว้ที่ส่วน <head> และ import JavaScript ของ BoostStrap ที่ส่วนท้ายของ <body> ให้เรียบร้อย จากนั้นให้นำโค้ดแต่ละส่วนไปประกอบร่างตามลำดับ

1. แถบเมนูด้านบน (Header & Navbar)

ใช้แท็ก <header> ควบคู่กับคอมโพเนนต์ Navbar ของ Bootstrap เพื่อสร้างแถบเมนูนำทาง (เมนูอื่นๆ ใส่ href เป็น # ไว้แทน)

HTML CSS
<header>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark shadow-sm">
        <div class="container">
            <a class="navbar-brand fw-bold" href="#">📚 Dev Article Hub</a>
            <div class="collapse navbar-collapse">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item"><a class="nav-link active" href="#">หน้าแรก</a></li>
                    <li class="nav-item"><a class="nav-link" href="#">บทความทั้งหมด</a></li>
                    <li class="nav-item"><a class="nav-link" href="#">ติดต่อเรา</a></li>
                </ul>
            </div>
        </div>
    </nav>
</header>
2. พื้นที่เนื้อหาหลัก (Main Content)

เปิดแท็ก <div class="container"> แล้วแบ่งฝั่งซ้ายด้วย <main class="col-md-8"> สำหรับแสดงบทความ โดยมีกล่อง id="articleContainer" ที่จะใช้ JavaScript เอาข้อมูลมาใส่เพิ่มในกล่อง

HTML CSS
<!-- พื้นที่เนื้อหาทั้งหมด -->
<div class="container my-5">
    <div class="row g-4">
        
        <!-- ฝั่งซ้าย (Main) -->
        <main class="col-md-8">
            <h4 class="fw-bold mb-4">บทความล่าสุด</h4>
            
            <!-- กล่องเปล่าที่รอรับบทความจาก JavaScript -->
            <div id="articleContainer" class="row g-3">
                <div class="text-center text-muted">กำลังโหลดข้อมูล..</div>
            </div>
        </main>

        <!-- (เว้นที่ไว้ใส่ฝั่งขวา) -->

    </div> <!-- ปิด div.row -->
</div> <!-- ปิด div.container -->
3. แถบด้านข้าง (Aside & Sidebar)

นำโค้ดนี้ไปต่อจากฝั่งซ้าย (ภายใน .row เดียวกัน) โดยจะใช้แท็ก <aside class="col-md-4"> เพื่อสร้างแถบด้านข้าง และเตรียม id="savedList" ไว้สำหรับโชว์บทความที่บันทึกจาก LocalStorage

HTML CSS
        <!-- ฝั่งขวา (Aside) -->
        <aside class="col-md-4">
            <div class="card border-0 shadow-sm bg-white sticky-top" style="top: 20px;">
                <div class="card-header bg-primary text-white fw-bold">
                    📌 บทความที่บันทึก
                </div>
                <div class="card-body">
                    
                    <!-- กล่องเปล่าที่รอรับบทความที่บันทึกไว้จาก JavaScript -->
                    <div id="savedList" class="list-group list-group-flush mb-3">
                        <div class="text-muted small text-center p-3 border rounded bg-light">ยังไม่มีบทความที่บันทึกไว้</div>
                    </div>
                    
                    <button id="btnClear" class="btn btn-outline-danger btn-sm w-100 fw-bold">ลบรายการทั้งหมด</button>
                </div>
            </div>
        </aside>
4. ส่วนท้าย (Footer)

ปิดท้ายหน้าเว็บด้วยแท็ก <footer> เพื่อแสดงลิขสิทธิ์
ใส่ fixed-bottom เพื่อให้ Footer ลอยอยู่ด้านล่างของหน้าเว็บเสมอ

HTML CSS
<footer class="bg-dark text-white text-center py-4 fixed-bottom">
    <small>&copy; 2026 My Reading List. All rights reserved.</small>
</footer>

ขั้นตอนที่ 2: ทยอยประกอบร่าง JavaScript

1. การดึงข้อมูลจาก API และสร้าง Elements ของบทความ

อธิบายการทำงาน: สร้างฟังก์ชันสำหรับนำข้อมูลจาก JSONPlaceholder API มาแปลงเป็น Json
จากนั้นให้สร้าง HTML Card ของแต่ละบทความ พร้อมฝังคำสั่งภาษา JavaScript ไว้ที่ปุ่มเพื่อรอรับการกดปุ่มบันทึก
สำหรับเนื้อหาของบทความที่ยาวเกิน 80 ตัวอักษร จะถูกตัดที่ 80 ตัวอักษร โดยใช้ substring(0, 80)
จากนั้นให้เรียกใช้ฟังก์ชัน เพื่อสั่งให้แสดงผลบทความตั้งแต่โหลดหน้าเว็บ

JS
// ประกาศตัวแปรสำหรับเก็บ Element ที่จะต้องไปจัดการบ่อย ๆ

// ฟังก์ชัน 1: ไปขอดึงบทความ
// 1.1ดึงข้อมูลจาก API แล้วแปลงผลลัพธ์เป็น JSON
// 1.2 วนลูปสร้าง HTML ของแต่ละบทความ
// 1.3 นำไปแปะบนหน้าจอ

// เรียกใช้ฟังก์ชัน สั่งให้โหลดบทความทันทีที่เปิดเว็บ

2. การบันทึกและอ่านข้อมูลจาก LocalStorage

อธิบายการทำงาน: เมื่อกดปุ่มบันทึก จะนำ id และ title สร้างเป็น Object แล้วยัดใส่ Array แล้วเซฟลง LocalStorage (อย่าลืมเช็คซ้ำก่อน)
จากนั้นเรียกฟังก์ชัน loadSavedArticles() ที่จะอ่านข้อมูลจาก LocalStorage แล้วสร้าง HTML Card เพื่อแสดงผลในแถบด้านข้าง (Aside)

JS
// ฟังก์ชัน 2: บันทึกบทความ
// 2.1 อ่านข้อมูลเก่าออกมาก่อน (ถ้าไม่มีให้เป็น Array ว่าง [])
// 2.2 ตรวจสอบข้อมูล ถ้ารหัสบทความนี้เคยถูกบันทึกไปแล้ว ให้เด้งเตือนและหยุดทำงาน
// 2.3 เอาของใหม่ใส่เข้า Array แล้ว Save ทับลงใน localStorage
// 2.4 สั่งอัปเดตหน้าจอฝั่งขวา

// ฟังก์ชัน 3: ดึงบทความที่เซฟไว้ มาแสดงในฝั่งขวา
// 3.1 อ่านข้อมูลจาก localStorage ออกมา (ถ้าไม่มีให้เป็น Array ว่าง [])
// 3.2 ถ้าไม่มีข้อมูลเลย ให้แสดงข้อความ "ยังไม่มีบทความที่บันทึกไว้"
// 3.3 ถ้ามีข้อมูล ให้สร้าง HTML List (ใช้ List Group ของ Bootstrap) เพื่อแสดงผลบทความที่บันทึกไว้
// 3.4 นำ HTML ที่สร้างขึ้นไปแปะบนหน้าจอฝั่งขวา

// สั่งให้โหลดรายการโปรดทันทีที่เปิดเว็บ (เผื่อมีของเก่าค้างอยู่)

3. การลบข้อมูลใน LocalStorage

อธิบายการทำงาน: หากผู้ใช้กด X ท้ายบทความที่บันทึกไว้ จะคัดแยกบทความชิ้นนั้นออกจาก Array แล้ว Save ทับลงไปใหม่ จากนั้นอัปเดตหน้าจอ
หากผู้ใช้กดปุ่ม "ล้างรายการทั้งหมด" จะลบข้อมูลทั้งหมดใน localStorage แล้วอัปเดตหน้าจอ

JS
// ฟังก์ชัน 4: ลบทีละอัน
// 4.1 สร้างฟังก์ชันที่รับ id ของบทความที่ต้องการลบ
// 4.2 อ่านข้อมูลจาก localStorage ออกมา (ถ้าไม่มีให้เป็น Array ว่าง [])
// 4.3 ใช้ filter กรองเอา "เฉพาะ" ตัวที่ id ไม่ตรงกับที่กดลบ (เก็บตัวอื่นไว้)
// 4.4 Save กลับลงไปใหม่ แล้วอัปเดตหน้าจอ

// ฟังก์ชัน 5: ปุ่มล้างรายการทั้งหมด
// 5.1 แสดง confirm ถามผู้ใช้ก่อน
// 5.2 ถ้าตกลงให้ลบข้อมูลใน localStorage ทิ้งทั้งหมด แล้วอัปเดตหน้าจอให้กลายเป็นค่าว่าง

ตัวอย่างผลลัพธ์ที่เสร็จสมบูรณ์

บทความล่าสุด
กำลังโหลดข้อมูล..
© 2026 My Reading List Workshop. All rights reserved.