در این فصل چه میسازی#
این فصل هیچ مفهومِ تازهای ندارد — و این بهترین خبرِ ممکن است: یعنی برای ساختنِ همهچیزِ این پروژه، همین حالا همهچیز را بلدی. یک صفحهٔ نمونهکار — از همان جنسی که در ترمهای قبل ساختی — تحویل میگیری و سه قابلیتِ واقعی به آن اضافه میکنی: یک کلیدِ تمِ روشن/تیره، فهرستِ پروژههایی که فیلتر میشود، و فرمی که به کاربرش جوابِ درست میدهد. آخرِ این فصل، آخرِ تاروپود است.

آخر این فصل میتوانی:
- با
classList.toggleو custom property ها تمِ کلِ صفحه را عوض کنی - یک فهرست را از روی چند آرایه، بسته به انتخابِ کاربر، از نو بسازی
- به فرم بازخوردِ خطا و موفقیت بدهی
- پروژه را با یک چکلیستِ تحویل، مثل یک حرفهای ببندی
قبل از شروع#
- از فصل ۵:
classList.toggle— و از ترم ۲: custom property ها. - از فصل ۷: آرایه،
for...of،createElement/appendو الگوی «اول خالی کن، بعد از نو بساز». - از فصل ۶:
value،submit،preventDefaultوif/else.
مثل همیشه فقط یک مرورگر لازم داری، یا فقط همین آزمایشگاه.
۱. صفحهای که تحویل میگیری#
صفحه یک نمونهکارِ کوچک است: سرصفحهای با اسم و دکمهٔ «تغییر تم» که هنوز کاری نمیکند، بخشِ پروژهها با سه دکمهٔ فیلتر، فرمِ تماس، و پاصفحه:
<!-- index.html -->
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>نمونهکارهای پروانه</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header class="site-header">
<h1>نمونهکارهای پروانه</h1>
<button id="theme-btn" type="button">تغییر تم</button>
</header>
<main>
<section class="projects">
<h2>پروژههای من</h2>
<button id="all-btn" class="filter" type="button">همه</button>
<button id="web-btn" class="filter" type="button">سایتها</button>
<button id="fun-btn" class="filter" type="button">تمرینهای سرگرمی</button>
<ul id="project-list">
<li>کارت پروفایل</li>
<li>وبلاگ شخصی</li>
<li>صفحهٔ فرود محصول</li>
<li>گالری عکس</li>
<li>کارت تبریک</li>
</ul>
</section>
<section class="contact">
<h2>پیام بده</h2>
<form id="contact-form">
<label for="name-input">اسمت:</label>
<input type="text" id="name-input">
<button type="submit">بفرست</button>
</form>
<p id="form-note"></p>
</section>
</main>
<footer class="site-footer">
<p>ساختهٔ پروانه — پایانِ تاروپود</p>
</footer>
<script src="script.js"></script>
</body>
</html>
به یک تصمیم دقت کن: فهرستِ پروژهها را عمداً در HTML هم نوشتهایم تا صفحه حتی بدونِ JavaScript هم کامل دیده شود — همان درسِ فصلِ قبل: JS تزئین است، نه ستون.
CSS صفحه با custom property ها نوشته شده و یک قاعدهٔ آماده هم منتظرِ توست:
body.dark {
--bg: #201a12;
--surface: #2c2114;
--ink: #f6edda;
--accent: #7fb4d6;
}
یعنی هر وقت body یک class به اسمِ dark بگیرد، همهٔ رنگهای صفحه با هم عوض میشوند. CSS اش هست؛ فقط مانده کسی آن class را روشن و خاموش کند — کارِ تو.
۲. کلیدِ تم#
با دانشِ فصل ۵، اولین تلاشِ خیلیها این است:
const themeBtn = document.querySelector('#theme-btn');
themeBtn.addEventListener('click', function () {
themeBtn.classList.toggle('dark');
});
دکمه را بزن. هیچ اتفاقی نمیافتد. نه خطایی در console، نه تغییری روی صفحه — از همان خرابیهای بیصدا که از ترمهای CSS خوب میشناسی.
🔧 اگر کار نکرد: در DevTools به عنصرها نگاه کن: class
darkدارد اضافه و کم میشود، ولی روی دکمه. selector تو در CSSbody.darkاست — قاعده منتظرِdarkرویbodyاست، نه روی دکمه. class را همانجایی toggle کن که CSS منتظرش است — وdocument.bodyمیانبرِ همیشگیِ JS به خودِ<body>است، بدونِ نیاز بهquerySelector:
themeBtn.addEventListener('click', function () {
document.body.classList.toggle('dark');
});
✅ چک کن: با هر کلیک، کلِ صفحه بینِ روشن و تیره میرود — و آن transition ی که از ترم ۵ میشناسی، جابهجایی را نرم کرده است.
۳. فیلترِ پروژهها#
داده را مثل فصل ۷ نگه میداریم: هر دسته یک آرایه، و «همه» را هم با همان push و حلقه میسازیم:
const webProjects = ['کارت پروفایل', 'وبلاگ شخصی', 'صفحهٔ فرود محصول'];
const funProjects = ['گالری عکس', 'کارت تبریک'];
const allProjects = [];
for (const project of webProjects) {
allProjects.push(project);
}
for (const project of funProjects) {
allProjects.push(project);
}
بعد سه دکمه، سه listener — هرکدام همان الگوی آشنا: اول خالی کن، بعد بساز:
webBtn.addEventListener('click', function () {
projectList.textContent = '';
for (const project of webProjects) {
const item = document.createElement('li');
item.textContent = project;
projectList.append(item);
}
});
دو listener دیگر همیناند، فقط با آرایهٔ خودشان (کدِ کامل پایین است). بله — سه تکهٔ تقریباً یکسان نوشتیم و این تکرار آزاردهنده است. قرار هم هست باشد: کوتاهکردنِ این تکرار یعنی تعریفِ functionِ خودت، و آن دقیقاً اولین درسِ مسیرِ JavaScript بعد از این پل است. امروز، تکرارِ صادقانه بهتر از ابزاری است که هنوز یاد نگرفتهای.
✅ چک کن: «سایتها» سه پروژه نشان میدهد، «تمرینهای سرگرمی» دو تا، و «همه» هر پنج تا — بدونِ هیچ دوقلوشدنی، چون قبل از هر ساختن خالی میکنی.
۴. بازخوردِ فرم#
فرمِ بیجواب، دیوارِ بیدر است. با ابزارِ فصل ۶ (سه عنصرِ فرم را هم با querySelector گرفتهایم — کدِ کامل پایین):
form.addEventListener('submit', function (event) {
event.preventDefault();
if (nameInput.value === '') {
formNote.textContent = 'اسمت را ننوشتهای — فرمِ خالی جایی نمیرود.';
formNote.classList.add('error');
} else {
formNote.textContent = `ممنون ${nameInput.value}! فرمت بینقص پر شده.`;
formNote.classList.remove('error');
}
});
✅ چک کن: ارسالِ خالی پیامِ قرمز میگیرد؛ با اسم، پیامِ عادی و مؤدبانه. (فرستادنِ واقعیِ پیام سرور میخواهد — کارِ ما اینجا بازخوردِ صادقانه به کاربر است.)
۵. چکلیستِ تحویل#
حرفهایها پروژه را با حس تمام نمیکنند؛ با چکلیست تمام میکنند. یکییکی، واقعاً امتحان کن:
- کلیدِ تم کار میکند و بعد از ده کلیکِ پشتِ هم هم گیج نمیشود.
- در هر دو تم، همهٔ متنها — حتی متنِ روی دکمهها — خوانا هستند.
- هر سه فیلتر فهرستِ درستِ خودشان را نشان میدهند و فهرست هرگز دوقلو نمیشود.
- ارسالِ فرمِ خالی پیامِ خطا میگیرد — نه سکوت.
- با کلیدِ
Tabبه همهٔ دکمهها و فرم میرسی، چون همه<button>و<input>واقعیاند (ترم ۱). - با JavaScript خاموش هم صفحه محتوایش را نشان میدهد — فهرست در HTML هست.
- صفحه در عرضِ موبایل هم سالم است (ترم ۴).
اگر همهٔ تیکها خورد، پروژهات تحویلدادنی است.
و یک قدمِ آخرِ حرفهای:
🤖 با AI: حالا که پروژه تمام است، کلِ
script.jsات را به دستیار AI ات بده و بپرس: «این کد چه میکند و کجاهایش تکراری است؟» جایی که تکرار را نشانت میدهد، همان جایی است که این فصل عمداً ساده نگه داشت. پیشنهادهایش را فقط بخوان و بفهم — با چیزی که در آزمایشگاه میبینی مقایسه کن، ولی امروز چیزی را عوض نکن: کدِ کارکردهٔ تحویلدادنی، از کدِ «قشنگترِ» نصفهکاره باارزشتر است.
کدِ کاملِ پروژه#
// script.js
/* --- 1) kelid-e theme --- */
const themeBtn = document.querySelector('#theme-btn');
themeBtn.addEventListener('click', function () {
document.body.classList.toggle('dark');
});
/* --- 2) filter-e prozhe-ha --- */
const webProjects = ['کارت پروفایل', 'وبلاگ شخصی', 'صفحهٔ فرود محصول'];
const funProjects = ['گالری عکس', 'کارت تبریک'];
const allProjects = [];
for (const project of webProjects) {
allProjects.push(project);
}
for (const project of funProjects) {
allProjects.push(project);
}
const projectList = document.querySelector('#project-list');
const allBtn = document.querySelector('#all-btn');
const webBtn = document.querySelector('#web-btn');
const funBtn = document.querySelector('#fun-btn');
allBtn.addEventListener('click', function () {
projectList.textContent = '';
for (const project of allProjects) {
const item = document.createElement('li');
item.textContent = project;
projectList.append(item);
}
});
webBtn.addEventListener('click', function () {
projectList.textContent = '';
for (const project of webProjects) {
const item = document.createElement('li');
item.textContent = project;
projectList.append(item);
}
});
funBtn.addEventListener('click', function () {
projectList.textContent = '';
for (const project of funProjects) {
const item = document.createElement('li');
item.textContent = project;
projectList.append(item);
}
});
/* --- 3) bazkhord-e form --- */
const form = document.querySelector('#contact-form');
const nameInput = document.querySelector('#name-input');
const formNote = document.querySelector('#form-note');
form.addEventListener('submit', function (event) {
event.preventDefault();
if (nameInput.value === '') {
formNote.textContent = 'اسمت را ننوشتهای — فرمِ خالی جایی نمیرود.';
formNote.classList.add('error');
} else {
formNote.textContent = `ممنون ${nameInput.value}! فرمت بینقص پر شده.`;
formNote.classList.remove('error');
}
});
index.html را بالا دیدی؛ styles.css کامل هم در آزمایشگاه است — تماماً چیزهایی که خودت در هفت ترم ساختهای.
واژههای تازهٔ این فصل#
| کلمه | تلفظ به حروف فارسی | یعنی چه |
|---|---|---|
| capstone | کَپاستون | پروژهٔ پایانی که همهٔ آموختهها را یکجا بهکار میگیرد |
| ship | شیپ | تحویلدادنِ کار، وقتی چکلیستش واقعاً پاس شده |
تمرینها
اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.
در فصل بعد#
فصلِ بعدی وجود ندارد. اینجا پایانِ تاروپود است.
هفت ترم پیش با یک فایلِ خالی شروع کردی. حالا پشتِ سرت: صفحههایی با اسکلتِ semantic، استایلی که cascade اش را میفهمی، layout هایی با flexbox و grid که در هر عرضی سالم میمانند، حالت و حرکت، کدِ تمیزِ component محور — و حالا صفحهای که به کاربرش جواب میدهد. تار HTML بود، پود CSS، و این ترمِ آخر نخِ جانی بود که پارچه را تکان داد.
و یک حرفِ صادقانه دربارهٔ ادامهٔ راه: این ترم یک پل بود، نه یک دورهٔ JavaScript. آنطرفِ پل دنیای بزرگی است — function هایی که خودت تعریف میکنی، object ها، آرایههای عمیقتر، و بعدها framework ها — و یادگیریِ عمیقش یک خودآموزِ جداست برای وقتی که خواستی. ولی مهمترین ابزار را همین حالا داری، چون هفت ترم است که داری تمرینش میکنی: بساز، خرابش کن، بفهم چرا خراب شد، درستش کن.
وب همیشه همین بوده: آدمهایی که چیزی میسازند و نشانش میدهند. از امروز تو یکی از آنهایی. چیزی بساز.
به آخر این فصل رسیدی!
اگر ساختی و جواب داد، این دکمه مال توست.