تاروپود — خودآموز HTML و CSS

فصل ۶ از ۹

پیشرفت ترم
۰٪

ترم ۷ · پل به JavaScript

کار با فرم در JavaScript

فصل ۶پیش‌نمایش رایگان
۷ دقیقه مطالعه فصل ۶

در این فصل چه می‌سازی#

فرم‌هایی که در ترم ۱ ساختی قشنگ بودند ولی گنگ: دکمه را می‌زدی و هیچ‌کس جواب نمی‌داد. این فصل صفحه خودش جواب می‌دهد: چیزی که کاربر نوشته را با value می‌خوانی، با preventDefault جلوی عادتِ قدیمیِ مرورگر را می‌گیری، و با if/else اولین تصمیمِ واقعیِ کدت را می‌گیری. آخرش فرمِ عضویتِ یک باشگاهِ کتاب را داری که همان لحظه، بدونِ هیچ سروری، به کاربر بازخورد می‌دهد.

یک کارتِ فرم با یک فیلد و یک دکمه؛ از دکمه یک مسیر دوشاخه می‌شود: یک شاخه به علامتِ خطرِ قرمز و شاخهٔ دیگر به علامتِ تیکِ سبز می‌رسد

آخر این فصل می‌توانی:

  • نوشتهٔ داخلِ یک <input> را با value بخوانی
  • به رویدادِ submit فرم گوش بدهی و با preventDefault جلوی بارگذاریِ دوبارهٔ صفحه را بگیری
  • با if/else بر اساسِ ورودیِ کاربر دو جوابِ متفاوت بدهی
  • توضیح بدهی چرا listener را روی خودِ <form> می‌گذاریم، نه روی کلیکِ دکمه

قبل از شروع#

  • از فصل ۴: addEventListener و شیءِ event — امروز بالاخره قدرتِ واقعی‌اش را می‌بینی.
  • از فصل ۲: template literal.
  • از ترم ۱: <form>، <label>، <input> و <button>.

مثل همیشه فقط یک مرورگر لازم داری، یا فقط همین آزمایشگاه.

۱. فرمی که هنوز گنگ است#

صفحهٔ این فصل یک فرمِ آشناست — همان تگ‌های ترم ۱:

<section class="signup">
  <h1>باشگاه کتاب</h1>
  <p>اسمت را بنویس و عضو شو؛ جواب همین پایین ظاهر می‌شود.</p>
  <form class="signup-form">
    <label for="name">اسمت چیست؟</label>
    <input id="name" type="text">
    <button>عضو شو</button>
  </form>
  <p class="feedback"></p>
</section>

آن <p class="feedback"> خالی است — جای جوابِ صفحه. قرار است JS پُرش کند.

۲. رویدادِ submit و خواندنِ value#

فرم رویدادِ مخصوصِ خودش را دارد: submit، یعنی «کاربر فرم را فرستاد». و هر <input> یک value دارد: چیزی که همین الان تویش نوشته شده. اولین نسخه را بنویس:

const form = document.querySelector('.signup-form');
const nameInput = document.querySelector('#name');
const feedback = document.querySelector('.feedback');

form.addEventListener('submit', function () {
  feedback.textContent = `خوش آمدی، ${nameInput.value}!`;
});

اسمت را بنویس و «عضو شو» را بزن. در یک فایلِ واقعی — همین صفحه را روی دیسک بسازی و در مرورگر باز کنی — برای یک لحظه پیامِ خوش‌آمد را می‌بینی و بعد صفحه چشمک می‌زند، از نو بار می‌شود، input خالی می‌شود و پیام غیبش می‌زند. همه‌چیز پرید. (در آزمایشگاه این پریدن را نمی‌بینی؛ چند خط پایین‌تر می‌گویم چرا.)

🔧 اگر کار نکرد: این رفتارِ پیش‌فرضِ submit است، یادگاری از دورانِ قبل از JavaScript: مرورگر فرم را برای یک سرور می‌فرستد و صفحه را از نو می‌گیرد (در فایلِ واقعی به آدرس هم نگاه کن — یک ? تهش آمده). listener تو اجرا شد، ولی بلافاصله صفحه عوض شد و همه‌چیز از دست رفت. راهِ حل داخلِ همان بسته‌ای است که فصل ۴ شناختی: شیءِ event یک دستورِ preventDefault دارد، یعنی «کارِ پیش‌فرضت را نکن».

form.addEventListener('submit', function (event) {
  event.preventDefault();
  feedback.textContent = `خوش آمدی، ${nameInput.value}!`;
});

چک کن: حالا «عضو شو» را بزن — پیامِ خوش‌آمد می‌آید و می‌ماند؛ صفحه دیگر از نو بار نمی‌شود.

⚠️ مواظب باش: آزمایشگاهِ ما فرم را داخلِ یک iframe امن اجرا می‌کند: listener ات کامل اجرا می‌شود و preventDefault هم همان رفتارِ همیشگی را دارد. فقط یک تفاوت هست: اگر preventDefault را ننویسی، آزمایشگاه خودش پریدنِ صفحه را بی‌سروصدا مهار می‌کند — یعنی خرابی سرِ جایش است، ولی چشمک‌زدن و از‌نو‌بارشدن را آنجا نمی‌بینی. برای دیدنِ این خرابی با چشمِ خودت، همین سه فایل را مثل ترمِ ۶ روی دیسک بساز و در مرورگر باز کن. preventDefault را همیشه بنویس.

Checkpoint. قبل از ادامه، جواب بده: اگر داخلِ input باشی و Enter بزنی چه می‌شود؟ (جواب: باز هم submit اتفاق می‌افتد و همان listener اجرا می‌شود — برای همین به رویدادِ فرم گوش دادیم، نه به کلیکِ دکمه. Enter هیچ کلیکی نیست، ولی فرستادنِ فرم هست.)

۳. if/else: اولین تصمیم#

الان اگر هیچ اسمی ننویسی و «عضو شو» را بزنی، صفحه با افتخار می‌گوید «خوش آمدی، !» — جوابِ درست به ورودیِ غلط. کد باید بتواند تصمیم بگیرد: اگر input خالی است، تذکر بده؛ وگرنه خوش‌آمد بگو. این کارِ if/else است:

form.addEventListener('submit', function (event) {
  event.preventDefault();

  if (nameInput.value === '') {
    feedback.textContent = 'اسمت را ننوشته‌ای! اول اسمت را بنویس.';
    feedback.classList.add('error');
  } else {
    feedback.textContent = `خوش آمدی، ${nameInput.value}! عضویتت ثبت شد.`;
    feedback.classList.remove('error');
  }
});

if یک شرط را داخلِ پرانتز می‌سنجد؛ اگر برقرار بود block اول اجرا می‌شود، وگرنه block بعد از else. آن === یعنی «دقیقاً برابر است با» — اینجا می‌پرسد: نوشتهٔ input دقیقاً یک stringِ خالی است؟ و به classList.add('error') نگاه کن: JS فقط اسمِ حالت را می‌گذارد، قرمزیِ متن را قاعدهٔ .feedback.error در CSS می‌سازد — همان پلِ فصلِ قبل.

چک کن: یک‌بار خالی بفرست — تذکرِ قرمز؛ یک‌بار با اسم — خوش‌آمدِ عادی. یک صفحه، دو جوابِ متفاوت: کدت دارد تصمیم می‌گیرد.

Checkpoint. قبل از ادامه، جواب بده: اگر فقط یک فاصله (space) تایپ کنی و بفرستی چه می‌شود؟ (جواب: از شرط رد می‌شود و خوش‌آمد می‌گیرد، چون ' ' با '' دقیقاً برابر نیست. این سوراخ را فعلاً می‌شناسیم و می‌پذیریم؛ ابزارِ تمیزکردنِ متن جزءِ ادامهٔ مسیرِ JS است، بیرون از این پل.)

🤖 با AI: از دستیار AI ات بپرس: «preventDefault غیر از فرم کجاها به کار می‌آید؟ سه مثال بزن.» یکی از مثال‌هایش را در آزمایشگاه امتحان کن و با چشمِ خودت چک کن که واقعاً همان رفتارِ پیش‌فرض متوقف می‌شود.

کد کاملِ این فصل#

<!-- 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>
    <main>
      <section class="signup">
        <h1>باشگاه کتاب</h1>
        <p>اسمت را بنویس و عضو شو؛ جواب همین پایین ظاهر می‌شود.</p>
        <form class="signup-form">
          <label for="name">اسمت چیست؟</label>
          <input id="name" type="text">
          <button>عضو شو</button>
        </form>
        <p class="feedback"></p>
      </section>
    </main>
    <script src="script.js"></script>
  </body>
</html>
/* styles.css */
body {
  font-family: Tahoma, sans-serif;
  background-color: #f6edda;
  color: #2c2114;
  line-height: 1.7;
  padding: 24px;
}

.signup {
  background-color: #ffffff;
  border: 1px solid #d8ccae;
  border-radius: 12px;
  padding: 16px;
  max-width: 360px;
}

label {
  display: block;
  margin-block-end: 4px;
}

input {
  font-family: inherit;
  font-size: 1rem;
  padding: 8px;
  border: 1px solid #d8ccae;
  border-radius: 8px;
  margin-block-end: 8px;
  width: 100%;
  box-sizing: border-box;
}

button {
  font-family: inherit;
  font-size: 1rem;
  padding: 8px 16px;
  border: 1px solid #b98a2f;
  border-radius: 8px;
  background-color: #f3d491;
  cursor: pointer;
}

.feedback.error {
  color: #a33520;
}
// script.js
const form = document.querySelector('.signup-form');
const nameInput = document.querySelector('#name');
const feedback = document.querySelector('.feedback');

form.addEventListener('submit', function (event) {
  event.preventDefault();

  if (nameInput.value === '') {
    feedback.textContent = 'اسمت را ننوشته‌ای! اول اسمت را بنویس.';
    feedback.classList.add('error');
  } else {
    feedback.textContent = `خوش آمدی، ${nameInput.value}! عضویتت ثبت شد.`;
    feedback.classList.remove('error');
  }
});

واژه‌های تازهٔ این فصل#

کلمه تلفظ به حروف فارسی یعنی چه
value وَلیو چیزی که همین الان داخلِ یک <input> نوشته شده
submit سابمیت رویدادِ فرستادنِ فرم — با دکمه یا با Enter
preventDefault پریوِنت دیفالت به رویداد می‌گوید «کارِ پیش‌فرضت را نکن»
if / else ایف / اِلس اگر شرط برقرار بود این کار، وگرنه آن کار
=== سه‌مساوی «دقیقاً برابر است با» — مقایسهٔ سخت‌گیرانه

تمرین‌ها

اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.

در فصل بعد#

حالا صفحه‌ات می‌شنود، تصمیم می‌گیرد و جواب می‌دهد — ولی هنوز یکی‌یکی. فصل بعد با array و حلقه آشنا می‌شوی: فهرستی از داده‌ها که JS خودش برایشان عنصر می‌سازد و داخلِ صفحه می‌چیند — همان کاری که هر سایتِ واقعی با فهرستِ محصول‌ها و پیام‌هایش می‌کند.

به آخر این فصل رسیدی!

اگر ساختی و جواب داد، این دکمه مال توست.