فصل 18HTTP و فرمها
ارتباط باید مستقل از وضعیت باشد [...] اینگونه که هر درخواست از سمت سرویسگیرنده به سرویسدهنده باید شامل تمامی اطلاعات مورد نیاز برای درک درخواست باشد، و نباید بتوان از زمینهای در ارتباط که روی سرویسدهنده از پیش ذخیره شده است، استفاده نمود.
پروتوکل انتقال ابرمتن (http) که پیش از این در فصل 13 آمد، مکانیزمی است که از طریق آن دادهها در شبکهی گستردهی وب، درخواست و فراهم می شوند. این فصل به توصیف این پروتوکل با جزئیات بیشتری می پردازد و چگونگی دسترسی جاوااسکریپت در مرورگر را به آن شرح میدهد.
پروتوکل
اگر آدرس eloquentjavascript.net/18_http.html را در نوار آدرس مرورگرتان تایپ کنید، مرورگر ابتدا به دنبال آدرس سرویسدهندهی (server) مرتبط با دامنهی eloquentjavascript.net میگردد و تلاش می کند تا یک ارتباط TCP روی پورت 80 ، پورت پیشفرض برای ترافیک HTTP، به آن بگشاید. اگر سرویس دهنده وجود داشته باشد و این ارتباط را پذیرش کند، مرورگر ممکن است اطلاعاتی شبیه به زیر را به آن ارسال نماید:
GET /18_http.html HTTP/1.1 Host: eloquentjavascript.net User-Agent: Your browser's name
سپس سرویسدهنده از طریق همان اتصال پاسخ می دهد.
HTTP/1.1 200 OK Content-Length: 65585 Content-Type: text/html Last-Modified: Mon, 08 Jan 2018 10:29:45 GMT <!doctype html> ... the rest of the document
مرورگر بخشی از پاسخ را که بعد از خط خالی می آید را میگیرد، بخش بدنه یا body (نباید با برچسب <body>
در HTML اشتباه گرفته شود)، و آن را به عنوان یک سند HTML نشان میدهد.
اطلاعاتی که توسط کاربر ارسال می شود را درخواست (request) می نامند. یک درخواست با این خط شروع می شود:
GET /18_http.html HTTP/1.1
اولین کلمه معرف روش یا متد (method) درخواست است. GET به این معناست که ما قصد گرفتن منبع مشخص شده را داریم. دیگر متدهای رایج شامل DELETE برای حذف یک منبع، PUT برای جایگزین کردن آن، و POST برای ارسال اطلاعات به آن می شوند. توجه داشته باشید که سرویس دهنده مجبور نیست هر درخواستی را که دریافت می کند اجرا کند. اگر به یک وب سایت تصادفی درخواستی از نوع DELETE برای حذف صفحهی اصلی آن ارسال نمایید، احتمالا رد خواهد شد.
قسمت بعد از نام متد، معرف مسیر منبعی است که درخواست برای آن ارسال می شود. در ساده ترین شکل، یک منبع، فایلی روی سرویسدهنده میباشد اما اجباری برای آن در پروتوکل وجود ندارد. یک منبع می تواند هرچیزی که بتوان آن را انتقال داد باشد همان طور که یک فایل اینگونه میباشد. خیلی از سرویسدهندهها پاسخهای تولیدی را به صورت پویا و درحین اجرا ایجاد می کنند. به عنوان مثال، اگر آدرس https://github.com/marijnh را باز کنید، سرویس دهنده در بانک اطلاعاتیش به دنبال کاربری با نام “marijnh” می گردد و اگر پیدا کرد، یک صفحهی پروفایل برای این کاربر ایجاد و برمی گرداند.
بعد از مسیر منبع، اولین خط از درخواست شامل 1.1/HTTP میباشد که بیانگر نسخهی پروتوکل HTTP در حال استفاده میباشد.
در عمل، خیلی از سایتها از HTTP نسخه 2 استفاده می کنند که از همان مفاهیم نسخه 1.1 پشتبانی می کند اما بسیار پیشرفتهتر است که در نتیجه سریع تر عمل می کند. مرورگرها در هنگام ارتباط با سرویسدهندهی داده شده، به صورت خودکار به نسخهی پروتوکل مناسب رجوع می کنند و نتیجهی درخواست فارغ از نسخهی پروتوکل یکسان است. به دلیل اینکه نسخهی 1.1 سرراست تر و آسان تر است، ما به همان می پردازیم.
پاسخ سرویسدهنده با یک شمارهی نسخه شروع می شود که بعد از آن وضعیت پاسخ درج می شود. ابتدا یک عدد سه رقمی وضعیت و سپس یک رشتهی متنی قابل خواندن برای انسان برگردانده میشود.
HTTP/1.1 200 OK
کدهای وضعیتی که با عدد 2 شروع می شوند بیانگر موفقیت درخواست ارسال شده می باشند. کدهایی که با 4 شروع می شوند به این معنا هستند که در درخواست مشکلی وجود داشته است. 404 احتمالا معروف ترین کد وضعیت HTTP میباشد – به معنای این که منبع درخواستی پیدا نشد. کدهایی که با 5 شروع می شوند به معنای وجود خطا در سرویس دهنده می باشند و اشکال در درخواست نیست.
اولین خط درخواست یا پاسخ ممکن است شامل تعدادی header یا سرپیام باشد. این خطوط به شکل name: value هستند و اطلاعات بیشتری دربارهی درخواست یا پاسخ فراهم می نمایند. سرپیامهای زیر بخشی از پاسخ مثال بودند:
Content-Length: 65585 Content-Type: text/html Last-Modified: Thu, 04 Jan 2018 14:05:30 GMT
این پیام اندازه و نوع سند پاسخ را مشخص می کند. در این مثال، این پاسخ، سندی HTML به اندازهی 65585 بایت میباشد. همچنین زمان آخرین تغییر ایجاد شده در سند نیز آمده است.
استفاده از بیشتر این سرپیامها برای سرویسدهنده و سرویسگیرنده اختیاری است. اما تعداد کمی از آن ها اجباری می باشند. به عنوان مثال، سرپیام Host که بیانگر نام میزبان (hostname) است باید در درخواست وجود داشته باشد چرا که یک سرویس دهنده ممکن است به چندین نام میزبان رو یک آدرس IP سرویسدهی کند، که در صورت مشخص نبودن آن سرپیام، سرویسدهنده نمی تواند تشخیص دهد که درخواست سرویسگیرنده مربوط به کدام نام میزبان میباشد.
بعد از قسمت سرپیامها، در هر دوی درخواست و پاسخ، یک خط خالی قرار می گیرد که بعد از آن بدنه خواهد آمد که حاوی دادههایی است که ارسال می شوند. در درخواستهای GET و DELETE هیچ دادهای ارسال نمی شود اما در PUT و POST این گونه نیست. به طور مشابه، بعضی از انواع پاسخ، مثل پاسخهای خطا، دارای قسمت بدنه نمی باشند.
مرورگرها و HTTP
همانطور که در مثال دیدیم، یک مرورگر وقتی که ما یک URL را در نوار آدرسش وارد می کنیم، یک درخواست می سازد. زمانی که صفحهی HTML دریافتی، به فایلهای دیگری ارجاع دارد مثل تصاویر و فایلهای جاوااسکریپت ، آن فایلها نیز بازیابی می شوند.
یک وبسایت نسبتا بزرگ به آسانی می تواند دارای چیزی بین 10 تا 200 منبع باشد. برای اینکه بتوان این منابع را به سرعت بارگیری کرد، مرورگر چندین درخواست GET را همزمان ارسال می کند به جای اینکه برای تک تک پاسخها منتظر بماند.
صفحات HTML ممکن است حاوی فرمها باشند که این امکان را به کاربر می دهند تا اطلاعاتی را در آن ها نوشته و به سرویسدهنده ارسال کنند. اینجا مثالی از یک فرم را مشاهده می کنید:
<form method="GET" action="example/message.html"> <p>Name: <input type="text" name="name"></p> <p>Message:<br><textarea name="message"></textarea></p> <p><button type="submit">Send</button></p> </form>
این کد فرمی با دو فیلد را تولید می کند: یک فیلد کوچک که نام را درخواست می کند و فیلدی بزرگتر که امکان نوشتن پیام را فراهم میسازد. زمانی که روی دکمهی Send کلیک می کنید، فرم ثبت می شود (submit) به این معنا که محتوای فیلدهای آن به صورت یک درخواست HTTP در آمده و ارسال می شود و مرورگر به محل نتیجهی درخواست منتقل می شود.
زمانی که خاصیت method
مربوط به عنصر <form>
برابر با GET
باشد ( یا اینکه اصلا ذکر نشود)، اطلاعات فرم به انتهای URL مشخص شده در خاصیت action
به عنوان یک رشتهی پرسجو (query string) اضافه می شود. مرورگر ممکن است درخواستی به این URL به شکل زیر ایجاد کند:
GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1
علامت سوال بیانگر انتهای قسمت مسیر مربوط به URL و شروع رشتهی پرس و جو میباشد. بعد از آن جفتهای نام و مقدار که متناظر با خاصیت name در فیلدهای فرم و محتوای آن عناصر میباشند به ترتیب می آیند. کاراکتر آمپرسند(&
) برای جداسازی جفتها استفاده می شود.
پیام اصلی که در URL منتقل می شود “?Yes” است اما علامت سوال با یک کد رمزی جایگزین شده است. بعضی از کاراکترهای موجود در رشتههای پرس و جو باید گریز داده شوند. علامت سوال که به صورت %3F
در آمده است یکی از آن ها است. به نظر یک قانون نانوشته وجود دارد که هر فرمتی روش گریزدهی کاراکتر مخصوص به خود را لازم دارد. در این مورد، که کدگذاری URL نامیده می شود (URL Encoding) از یک علامت درصد و دو رقم هگزادسیمال (مبنای 16) برای کدگذاری کد کاراکتر استفاده می شود. در این مورد، 3F که در سیستم دهدهی معادل 63 میباشد، کد کاراکتر علامت سوال است. جاوااسکریپت دو تابع encodeURIComponent
و decodeURIComponent
را برای کدگذاری و کدگشایی از این فرمت فراهم نموده است.
console.log(encodeURIComponent("Yes?")); // → Yes%3F console.log(decodeURIComponent("Yes%3F")); // → Yes?
اگر خاصیت method
را در فرم HTML مثالی که دیدیم به POST
تغییر دهیم، درخواست HTTP ساخته شده از روش POST
استفاده می کند و رشتهی پرسوجو را در قسمت بدنهی درخواست قرار می دهد تا اینکه در URL اضافه کند.
POST /example/message.html HTTP/1.1 Content-length: 24 Content-type: application/x-www-form-urlencoded name=Jean&message=Yes%3F
درخواستهای GET بهتر است برای درخواستهایی استفاده شوند که اثرات جانبی ندارند و فقط برای درخواست اطلاعات استفاده می شوند. درخواستهایی که قرار است چیزی را روی سرویس دهنده تغییر دهند ، مثلا حساب کاربری جدیدی ایجاد کنند یا پیامی را ارسال کنند، باید با دیگر روش ها مثل POST
ارسال شوند. یک نرم افزار سمت کاربر (client-side) مثل مرورگر می داند که نباید به صورت کورکورانه درخواستهای POST
ارسال می کند اما به صورت ضمنی درخواستهای GET
ایجاد کند – به عنوان مثال برای پیشواکشی یک منبع که کاربر به زودی به آن نیاز خواهد داشت.
به فرمها و نحوهی تعامل با آن ها به وسیلهی جاوااسکریپت در همین فصل باز خواهیم گشت.
Fetch
رابطی را که از طریق آن، جاوااسکریپت مرورگر می تواند درخواستهای HTTP بسازد، fetch
می نامند. به دلیل اینکه این رابط نسبتا جدید است، به خوبی از promise ها استفاده می کند ( که در رابط های مرورگر چیز نادری است).
fetch("example/data.txt").then(response => { console.log(response.status); // → 200 console.log(response.headers.get("Content-Type")); // → text/plain });
فراخوانی fetch
یک promise را برمی گرداند که به یک شیء Response
منجر می شود که حاوی اطلاعاتی دربارهی پاسخ سرویسدهنده است، مثل کد وضعیت آن و سرپیامهایش. سرپیامها درون یک شیء شبیه Map
قرار می گیرند که نسبت به بزرگی و کوچکی حروف کلیدهایش (نام سرپیامها) حساس نیست چرا که نام سرپیام ها لزومی ندارد به حروف حساس باشد. به این معنا که headers.
و headers.
مقدار مشابهی را برمی گردانند.
توجه داشته باشید که promise برگشتی از fetch
به صورت موفقیتآمیز حلوفصل میشود حتی اگر سرویس دهنده با کد خطا پاسخ داده باشد. همچنین ممکن است لغو شود، اگر خطایی در شبکه رخ بدهد یا سرویسدهندهای که درخواست به آن ارسال می شود پیدا نشود.
اولین آرگومانی که fetch دریافت می کند URLای است که مورد درخواست است. زمانی که این URL با نام یک پروتوکل شروع نمی شود (مثل http:)، به صورت نسبی در نظر گرفته می شود، به این معنا که آدرس نسبت به موقعیت سند فعلی تفسیر می شود. اگر این URL با یک کاراکتر اسلش (/) شروع شود، جایگزین مسیر فعلی خواهد شد منظور بخشی است که پس از نام سرویسدهنده در URL میآید. در صورت نبود اسلش در ابتدا، مسیر فعلی تا آخرین کاراکتر اسلش، پیش از URL نسبی داده شده قرار خواهد گرفت.
برای دریافت محتوای اصلی یک پاسخ، می توانید از متد text
آن استفاده کنید. به دلیل اینکه promise اولیه، به محض اینکه سرپیامهای پاسخ دریافت شوند، حل و فصل می شود، و خواندن بدنهی پاسخ ممکن است زمان بیشتری بطلبد، دوباره به شکل یک promise برگردانده می شود.
fetch("example/data.txt") .then(resp => resp.text()) .then(text => console.log(text)); // → This is the content of data.txt
متد مشابهی به نام json
وجود دارد که promiseای برمی گرداند که زمانی موفق به تولید مقدار می شود که بدنه را بتوان به عنوان JSON تفسیر کرد، و اگر محتوای بدنه یک JSON معتبر نباشد، promise لغو می شود.
به صورت پیشفرض، fetch
از روش GET
برای ساختن درخواستهایش استفاده می کند و بدنهای برای درخواست درنظر نمی گیرد. می توانید به صورت دیگری آن را تنظیم کنید؛ به وسیلهی ارسال آرگومان دوم که حاوی شیء گزینههای بیشتر میباشد. به عنوان مثال، این درخواست تلاش می کند تا example/data.txt
را حذف کند.
fetch("example/data.txt", {method: "DELETE"}).then(resp => { console.log(resp.status); // → 405 });
کد وضعیت 405 به این معنا است که “این متد مجاز نیست”. روش یک سرویسدهندهی HTTP برای گفتن “من نمی توانم این کار رو بکنم” است.
برای افزودن یک بدنهی درخواست، می توانید گزینهی body
را بیفزایید. برای تنظیم سرپیامها، گزینهای به نام headers
وجود دارد. به عنوان مثال، این درخواست یک سرپیام Range
را اضافه میکند که به سرویسدهنده دستور می دهد که فقط بخشی از پاسخ را برگرداند.
fetch("example/data.txt", {headers: {Range: "bytes=8-19"}}) .then(resp => resp.text()) .then(console.log); // → the content
مرورگر به صورت خودکار چند سرپیام به درخواست اضافه می کند مثل “Host” و آن دسته از سرپیامها که برای سرویسدهنده لازم هستند تا بتواند اندازهی بدنه را بداند. اما اضافه کردن سرپیامهای خودتان معمولا برای افزودن چیزهایی مثل اطلاعات هویتسنجی یا اعلام نوع فرمت فایل درخواستی به سرویسدهنده، استفاده می شود.
سازوکار حفاظتی - HTTP sandboxing
ساختن درخواستهای HTTP درون اسکریپتهای صفحهی وب، نگرانیهایی در مورد امنیت به وجود میآورد. فردی که اسکریپت را کنترل می کند ممکن است نیتی مشابه کسی که اسکریپت در کامپیوترش اجرا می شود، نداشته باشد. به صورت واضحتر، اگر من از سایت themafia.org بازدید کنم دوست ندارم که اسکریپتهای این سایت بتوانند درخواستی به سایت بانک من (mybank.com) ارسال کنند و از اطلاعات شناسایی من در مرورگرم استفاده کنند و دستوراتی برای انتقال پولهای من به حسابهای ناشناس اجرا کنند.
به همین دلیل، مرورگرها با غیرفعال کردن اجازهی ارسال درخواست HTTP از درون اسکریپتها به دیگر دامنهها( نام هایی مثل themefia.org یا mybank.com) از ما محافظت می کنند.
ممکن است این محدودیت در زمان ساختن سیستمهایی که لازم است برای اهداف قانونی به چند دامنه دسترسی داشته باشد آزار دهنده باشد. خوشبختانه، سرویسدهندهها می توانند یک سرپیام شبیه مثال زیر را در پاسخ خودشان قرار دهند تا صراحتا به مرورگر اعلام کنند که مشکلی نیست که درخواست از دامنهی دیگری بیاید.
Access-Control-Allow-Origin: *
بهرهمندی از HTTP
در زمان ساخت سیستمی که لازم است در آن بین یک برنامهی جاوااسکریپت که در مرورگر اجرا می شود (سمت کاربر) و برنامهای که روی سرویسدهنده است (سمت سرور) ارتباط برقرار شود، می توان از روشهای متفاوتی برای مدلسازی این ارتباط استفاده کرد.
یکی از مدلهای رایج، استفاده از فراخوانیهای رویه از راه دور (remote procedure calls) است. دراین مدل، ارتباط از الگوی فراخوانی نرمال توابع پیروی می کند با این استثناء که تابع در واقع روی کامپیوتر دیگر اجرا می شود. فراخوانی آن شامل ساخت یک درخواست به سرویسدهنده و مشخص کردن نام تابع و آرگومانهای آن میباشد. پاسخ به آن درخواست حاوی مقدار بازگشتی است.
در مدل فراخوانی از راه دور، HTTP فقط نقش یک وسیله ارتباطی را بازی می کند و شما به احتمال زیاد آن را با یک لایهی تجرید مخفی می کنید.
روشی دیگر ساختن سیستم ارتباط برپایه مفهوم منابع و متدهای HTTP است. به جای اینکه یک رویهی از راه دور به نام addUser
فراخوانی شود، از درخواست PUT
به /users/larry
استفاده می کنید. به جای قرار دادن مشخصات آن کاربر در آرگومانهای تابع، یک فرمت سند JSON تعریف می کنید ( یا از فرمتی موجود استفاده می کنید) که نمایانگر یک کاربر باشد. بدنهی درخواست PUT
برای ایجاد یک منبع جدید، سندی به این شکل خواهد بود. یک منبع را می توان با ساختن یک درخواست GET
به URL منبع (به عنوان مثال /users/larry
) واکشی کرد که دوباره سندی که نمایانگر منبع مورد نظر است را برمیگرداند.
استفاده از روش دوم باعث می شود بتوان از بعضی امکاناتی که HTTP فراهم می کند آسان تر استفاده کرد مثل پشتیبانی از کش کردن منابع (نگه داری یک کپی روی سیستم کاربر برای دسترسی سریع). مفاهیم استفاده شده در HTTP که خوبی طراحی شده اند، می توانند مجموعهی مفیدی از قواعد را برای طراحی رابط سرویسدهنده فراهم نمایند.
امنیت و HTTPS
سفر دادهها در اینترنت معمولا در مسیری بلند و پرمخاطره رخ می دهد. برای اینکه دادهها به مقصدشان برسند، باید از چیزهای مختلفی مثل Wifi یک کافی شاپ تا شبکههایی که توسط شرکتهای متنوع و دولتها کنترل می شوند عبور کنند. در هر نقطهای در طول مسیرش می توانند مورد بازرسی یا حتی دستکاری قرار بگیرند.
بعضی اطلاعات باید مخفی بمانند مانند رمز عبور حساب ایمیلتان، یا اطلاعاتی هستند که باید هنگامی که به مقصد میرسند دستکاری نشده باشند مانند شمارهی حسابی که در سایت بانکتان به آن پول واریز می کنید. HTTP ساده زیاد مناسب این این موارد نیست.
پروتوکل امن HTTP که URLها در آن با https:// شروع می شوند، ترافیک HTTP را به شکلی محافظت می کند که ساختن و دستکاری آن خیلی سخت بشود. قبل از تبادل دادهها ، سرویسگیرنده تحقیق می کند که سرویسدهنده همانی باشد که ادعا می کند و این کار با درخواست از سرور برای فراهم ساختن گواهینامهی رمزی که توسط یک مرجع معتبر صدور گواهینامه صادر شده است و مورد شناسایی مرورگر میباشد، انجام می شود. بعد تمامی دادههایی که از طریق این ارتباط جابجا می شوند به رمز در می آیند به شکلی که از استراق سمع و مداخله ایمن بمانند.
بنابراین، وقتی این پروتوکل به درستی کار می کند، HTTPS از جعل وب سایت مورد ارتباط توسط شخص ثالث و جاسوسی در ارتباطات شما جلوگیری میکند. این پروتوکل کامل و بی نقص نیست، و موارد متنوعی وجود داشته که HTTPS موفقیت آمیز نبودهاست؛ به دلیل لو رفتن گواهینامه یا دزدیده شدن آن یا نرمافزار معیوب، اما به هر حال این پروتوکل بسیار از HTTP ساده امن تر میباشد.
فیلدهای فرم
فرم ها اساسا برای وب قبل از جاوااسکریپت طراحی شده بوند، تا به وبسایتها امکان ارسال اطلاعات ثبت شده توسط کاربر را به صورت یک درخواست HTTP بدهند. این طراحی فرض را بر این می گذارد که تعامل با سرویسدهنده همیشه با انتقال به یک صفحهی دیگر رخ می دهد.
اما عناصر فرم هم شبیه به دیگر قسمتهای صفحه، بخشی از DOM محسوب می شوند و عناصر DOM که فیلدهای فرم را نشان می دهند دارای تعدادی خاصیت و رخداد هستند که در دیگر عناصر موجود نیست. این باعث می شود که بتوان با برنامههای جاوااسکریپت این گونه فیلدها را کنترل و رسیدگی کرد و بتوان کارهایی مثل اضافه کردن کارکردهای جدید به یک فرم یا استفاده از فرمها و فیلدها به عنوان بلاکهای سازنده در اپلیکیشنهای جاوااسکریپت استفاده کرد.
یک فرم وب شامل هر تعداد از فیلدهای ورودی که توسط برچسب <form>
محصور می شوند است. HTML امکان داشتن سبکهای متفاوت و متعددی از فیلدها را فراهم می سازد، از چکباکسهای on/off تا منوهای بازشدنی و فیلدهایی برای ورودی متنی. این کتاب به صورت جامع در مورد همهی انواع فیلدها صحبت نمی کند، اما به صورت کلی نگاهی به آن ها می اندازیم.
خیلی از انواع فیلدها از برچسب <input>
استفاده می کنند. خصوصیت type
این برچسب برای انتخاب سبک فیلد استفاده می شود. اینجا بعضی از انواع <input>
رایج آورده شده است:
text | یک فیلد یک خطی برای دریافت متن |
password | فیلدی شبیه به text با این تفاوت که متن تایپشده قابل شناسایی نیست |
checkbox | فیلدی دارای دو حالت فعال و غیرفعال |
radio | بخشی از یک فیلد چند گزینهای |
file | این فیلد به کاربر اجازهی انتخاب یک فایل از کامپیوترش را می دهد |
فیلدهای فرم نیازی نیست حتما درون یک برچسب <form>
قرار گیرند. می توانید آن ها در هر جای صفحه استفاده کنید. استفاده بدون برچسب form از فیلدها باعث می شود که نتوان آن ها را به صورت نرمال ثبت کرد ( فقط یک فرم به شکل کامل قابلیت ثبت را دارد)، اما در مواقع کار با فیلدهای ورودی در جاوااسکریپت ، اغلب قصد ثبت فیلدهایمان به صورت نرمال نداریم.
<p><input type="text" value="abc"> (text)</p> <p><input type="password" value="abc"> (password)</p> <p><input type="checkbox" checked> (checkbox)</p> <p><input type="radio" value="A" name="choice"> <input type="radio" value="B" name="choice" checked> <input type="radio" value="C" name="choice"> (radio)</p> <p><input type="file"> (file)</p>
رابط جاوااسکریپت برای این گونه عناصر بسته به نوع عنصر متفاوت است.
فیلدهای متنی چند خطی برچسب مخصوص خودشان را دارند، <textarea>
، بیشتر به دلیل اینکه قرار دادن یک مقدار چند خطی در قسمت خصوصیتها (attribute) مناسب نیست. برچسب <textarea>
نیاز به برچسب پایانی </
دارد و از متن بین این دو برچسب به جای خاصیت value
استفاده می کند.
<textarea> one two three </textarea>
و در نهایت، برچسب <select>
برای ایجاد فیلدی که امکان انتخاب از بین تعدادی گزینهی از پیش تعریف شده را به کاربر می دهد استفاده می شود.
<select> <option>Pancakes</option> <option>Pudding</option> <option>Ice cream</option> </select>
هر زمان که مقدار یک فیلد فرم تغییر کند، یک رخداد "change"
ایجاد می شود.
Focus
برخلاف بیشتر عناصر موجود در اسناد HTML، فیلدهای فرم را می توان با صفحهی کلید فعال (focus) کرد. زمانی که با کلیک موس یا روشی دیگر این عناصر فعال می شوند در دسترس ورودی صفحهی کلید قرار می گیرند.
بنابراین زمانی می توانید درون یک فیلد متنی چیزی تایپ کنید که در مرورگر فعال (مورد تمرکز) باشد. دیگر فیلدها به رخدادهای صفحهکلید به شکلی متفاوت پاسخ می دهند. به عنوان مثال در <select>
با تایپ متن توسط کاربر این فیلد تلاش می کند تا گزینهای که محتوای تایپ شده را دارد انتخاب شود و با کلیدهای جهت دار می توان گزینهی انتخابی را تغییر داد.
می توانیم فعال بودن یک فیلد را با جاوااسکریپت به وسیلهی متدهای focus
و blur
کنترل کنیم. اولین متد عنصری که روی آن فراخوانی شده را فعال می کند و دومی از حالت فعال آن را خارج می کند. مقداری که در document.
قرار دارد متناظر با عنصری است که در حال حاضر فعال میباشد.
<input type="text"> <script> document.querySelector("input").focus(); console.log(document.activeElement.tagName); // → INPUT document.querySelector("input").blur(); console.log(document.activeElement.tagName); // → BODY </script>
برای بعضی صفحات، انتظار می رود که کاربر بلافاصله به سراغ یک فیلد فرم برود. جاوااسکریپت می تواند برای فعال سازی این فیلد بعد از بارگیری سند استفاده شود، HTML نیز خاصیتی به نام autofocus
فراهم می کند که همین اثر را دارد و به مرورگر اعلام می کند که قصد داریم کدام فیلد فعال باشد. این گزینه این امکان را برای مرورگر فراهم می سازد که این رفتار را در مواقعی که مناسب نیست غیرفعال کند، مثل زمانی که کاربر به سراغ فیلد دیگری رفته است.
مرورگرها به شکلی سنتی به کاربر اجازه می دهند تا فیلدهای دیگر را با استفاده از کلید tab فعال کند. می توانیم با استفاده از خاصیت tabindex
، به ترتیب این فعالسازی اثر بگذاریم . در مثال پیش رو، در سند مشخص می شود که حالت فعال فیلد بعد از فیلد متنی به جای لینک help، به دکمهی OK منتقل شود.
<input type="text" tabindex=1> <a href=".">(help)</a> <button onclick="console.log('ok')" tabindex=2>OK</button>
به صورت پیشفرض، بیشتر انواع عناصر HTML نمی توانند فعال شوند (focus). اما می توانید از tabindex
در همهی عناصر استفاده کنید که باعث می شود بتوان به آن ها قابلیت فعال بودن داد. قرار دادن -1 برای tabindex
باعث می شود که از آن عنصر صرف نظر شود حتی اگر به صورت نرمال امکان فعال بودن داشته باشد.
فیلدهای خاموش (disabled)
می توان تمامی فیلدهای فرم را به وسیلهی خاصیت disabled
خاموش کرد. این خاصیت را می توان بدون مشخص کردن مقدار به کار برد- همین که وجود داشته باشد باعث خاموش شدن فیلد می شود.
<button>I'm all right</button> <button disabled>I'm out</button>
فیلدهای خاموش را نمی توان تغییر داد یا مورد تمرکز (focus) قرار داد. همچنین مرورگرها ظاهر آن ها خاکستری و کم رنگ می کنند.
زمانی که یک برنامه در حال رسیدگی به کاری است که از فشردن یک دکمه یا یک فیلد کنترلی دیگر ایجاد شده، و اینکار نیازمند ارتباط با سرویسدهنده است که در نتیجه نیاز به کمی زمان دارد، بهتر است تا پایان انجام آن کار، فیلد مورد نظر را خاموش کنیم. با این کار ، اگر کاربر صبر نکند و دوباره روی دکمه کلیک کند، به صورت ناخواسته آن کار تکرار نمی شود.
فرم به عنوان یک کل
زمانی که یک فیلد درون عنصر <form>
قرار می گیرد، عنصر DOM آن خاصیتی به نام form
خواهد داشت که به عنصر DOM فرم اشاره می کند. عنصر <form>
در عوض، خاصیتی به نام elements
دارد که حاوی یک مجموعهی آرایهشکل از فیلدهای قرار گرفته در آن میباشد.
خاصیت name
یک فیلد در فرم، راه شناسایی مقدار آن در زمان ثبت فرم میباشد. این خاصیت را همچنین می توان به عنوان نام در زمان دسترسی به خاصیت elements
مربوط به فرم استفاده کرد که هم به صورت شیء آرایهشکل ( قابل دسترسی با عدد) و هم به صورت یک map (دسترسی با نام).
<form action="example/submit.html"> Name: <input type="text" name="name"><br> Password: <input type="password" name="password"><br> <button type="submit">Log in</button> </form> <script> let form = document.querySelector("form"); console.log(form.elements[1].type); // → password console.log(form.elements.password.type); // → password console.log(form.elements.name.form == form); // → true </script>
یک دکمه که خاصیت type
آن که برابر با submit
تنظیم شده، در هنگام فشرده شدن، باعث می شود که فرم ثبت شود. فشردن کلید enter روی صفحه کلید وقتی که یک فیلد فرم فعال است نیز همین اثر را به دنبال خواهد داشت.
ثبت یک فرم به صورت نرمال به این صورت است که مرورگر کاربر را به صفحهای که در خاصیت action
فرم مشخص شده است، منتقل میکند چه درخواست به روش GET
باشد چه POST
. اما قبل از این اتفاق، یک رخداد "submit"
ایجاد می شود. این رخداد را می توان با جاوااسکریپت مدیریت کرد و گرداننده می تواند با فراخوانی preventDefault
روی شی رخداد مانع از این رفتار پیشفرض شود.
<form action="example/submit.html"> Value: <input type="text" name="value"> <button type="submit">Save</button> </form> <script> let form = document.querySelector("form"); form.addEventListener("submit", event => { console.log("Saving value", form.elements.value.value); event.preventDefault(); }); </script>
متوقف کردن رخدادهای "submit"
در جاوااسکریپت کاربردهای متنوعی دارد. می توانیم کدی بنویسیم که صحت مقادیری که کاربر وارد می کند را بررسی کند و در صورت وجود اشتباه به جای ثبت فرم بلافاصله خطایی نشان دهد. یا می توانیم روش معمول ثبت کردن فرم را غیرفعال کنیم ، به عنوان مثال، و بدون نیاز به بارگیری مجدد صفحه، اطلاعات ورودی را با استفاده از fetch
به سرویسدهنده ارسال کنیم.
فیلدهای متنی
فیلدهایی که به وسیلهی برچسبهای <input>
ایجاد می شوند و نوع text
یا password
را دارند به همراه برچسبهای <textarea>
، از رابط یکسانی استفاده می کنند. عناصر متناظرشان در DOM دارای خاصیتی به نام value
میباشد که محتوای کنونی آن ها را به شکل رشتهای نگهداری می کند. تغییر این خاصیت و استفاده از رشتهای دیگر برای آن محتوای فیلد را تغییر می دهد.
دو خاصیت selectionStart
و selectionEnd
مربوط به فیلدهای متنی اطلاعاتی در رابطه با مکاننما و ناحیهی انتخاب شده در متن فراهم می سازند. زمانی که چیزی انتخاب نشده است، این دو خاصیت مقدار عددی یکسانی را نگهداری می کنند که موقعیت مکان نما می باشد. به عنوان مثال، 0 به معنای شروع متن و 10 بیانگر این است که مکاننما بعد از کاراکتر دهم همین کاراکتر قرار دارد. زمانی که بخشی از فیلد انتخاب شده باشد، این دو خاصیت مقدار متفاوتی خواهند داشت و بیانگر شروع و پایان متن انتخاب شده می باشند. مثل value
این خاصیتها را نیز می توان تغییر داد.
فرض کنید که در حال نوشتن مقالهای در مورد Khasekhemwy (پادشاهی در مصر باستان) می باشید و در نوشتن نام او دچار مشکل هستید. در کد پیش رو یک برچسب <textarea>
و یک گردانندهی رخداد با هم ترکیب می شوند و با فشردن کلید F2 کلمهی “Khasekhemwy” برای شما در فیلد وارد می شود.
<textarea></textarea> <script> let textarea = document.querySelector("textarea"); textarea.addEventListener("keydown", event => { // The key code for F2 happens to be 113 if (event.keyCode == 113) { replaceSelection(textarea, "Khasekhemwy"); event.preventDefault(); } }); function replaceSelection(field, word) { let from = field.selectionStart, to = field.selectionEnd; field.value = field.value.slice(0, from) + word + field.value.slice(to); // Put the cursor after the word field.selectionStart = from + word.length; field.selectionEnd = from + word.length; } </script>
تابع replaceSelection
بخش انتخاب شده در فیلد متنی را با کلمهی داده شده جایگزین می نماید و بعد مکاننما را به بعد از آن کلمه منتقل می کند تا کاربر بتواند به تایپ خود ادامه دهد.
رخداد "change"
برای یک فیلد متنی با هر بار نوشتن چیزی ایجاد نمی شود. بلکه زمانی ایجاد می شود که فیلد مذکور بعد از اینکه محتوایش تغییر کرد از حالت focus خارج می شود. برای اینکه بتوان به سرعت به تغییرات در یک فیلد متنی پاسخ داد، باید یک گرداننده برای رخداد "input"
ثبت کنید که با تایپ هر کاراکتر توسط کاربر یا حذف آن یا ایجاد هر تغییری در محتوای فیلد ایجاد می شود.
در مثال پیش رو یک فیلد متنی نشان داده می شود و یک شمارنده که طول متن موجود در فیلد را نشان می دهد.
<input type="text"> length: <span id="length">0</span> <script> let text = document.querySelector("input"); let output = document.querySelector("#length"); text.addEventListener("input", () => { output.textContent = text.value.length; }); </script>
فیلد بررسی (checkbox) و دکمههای رادیویی
یک فیلد چکباکس، یک فیلد دو حالته است. مقدار آن را می توان به وسیلهی خاصیت checked
آن تغییر داد یا بهدست آورد که مقداری بولی میباشد.
<label> <input type="checkbox" id="purple"> Make this page purple </label> <script> let checkbox = document.querySelector("#purple"); checkbox.addEventListener("change", () => { document.body.style.background = checkbox.checked ? "mediumpurple" : ""; }); </script>
برچسب <label>
یک بخش از سند را به یک فیلد ورودی مرتبط میسازد. با کلیک روی label، فیلد مرتبط با آن فعال می شود که باعث می شود مورد تمرکز مرورگر قرار بگیرد یا در مواقعی که یک چکباکس یا دکمهی رادیویی باشد مقدارش را تغییر دهد (انتخاب یا از حالت انتخاب خارج می کند)
یک دکمهی رادیویی شبیه به یک چکباکس است با این تفاوت که به صورت ضمنی و به وسیلهی خاصیت name
مشترک به دیگر دکمههای رادیویی پیوند می خورد در نتیجه فقط یکی از این دکمهها را می توان در آن واحد فعال داشت.
Color: <label> <input type="radio" name="color" value="orange"> Orange </label> <label> <input type="radio" name="color" value="lightgreen"> Green </label> <label> <input type="radio" name="color" value="lightblue"> Blue </label> <script> let buttons = document.querySelectorAll("[name=color]"); for (let button of Array.from(buttons)) { button.addEventListener("change", () => { document.body.style.background = button.value; }); } </script>
در مثال بالا، استفاده از براکتها در پرس و جوی CSS که به متد querySelectorAll
ارسال می شوند برای انتخاب و نشانهگرفتن خصوصیتهای عناصر (attributes)، استفاده می شوند. این پرس و جو عناصری را نتخاب می کند که مقدار خاصیت name
شان برابر با “color”
باشد.
فیلدهای select (انتخاب گزینه)
فیلدهای select به طور مفهومی مشابه دکمههای رادیویی هستند- این فیلدها هم به کاربر، امکان انتخاب از یک مجموعه گزینهها را می دهند. در حالیکه در یک دکمهی رادیویی چیدمان گزینهها تحت کنترل ما میباشد، ظاهر یک برچسب <select>
توسط مرورگر تعیین می شود.
فیلدهای select همچنین در یک حالت خاص، بیشتر شبیه لیستی از checkboxها میباشد تا دکمههای رادیویی. اگر خاصیت multiple
را به یک برچسب <select>
اضافه کنیم، کاربر این امکان را خواهد داشت که به جای یک گزینه بتواند چندین گزینه انتخاب نماید. این شکل از فیلد در بیشتر مرورگرها به صورت متفاوتی نسبت به حالت نرمال یک گزینهای نشان داده می شود. در حالت عادی، این فیلد به صورت یک منوی کشویی (بازشونده) نمایش داده می شود که گزینههایش زمانی دیده میشوند که آن را باز نمایید.
هر برچسب <option>
دارای یک مقدار میباشد. این مقدار را می توان با خاصیت value
تعریف کرد. اگر این خاصیت را مشخص نکنید، متن درون گزینه به عنوان مقدار در نظر گرفته میشود. خاصیت value
متعلق به عنصر <select>
نشاندهندهی گزینهی انتخاب شده میباشد. برای یک فیلد چندگزینهای (multiple) این خاصیت معنای خاصی نخواهد داشت چرا که فقط به یک مقدار از مقادیر انتخاب شده اشاره می کند.
برچسبهای <option>
متعلق به فیلد <select>
را می توان از طریق یک شیء آرایهگونه و به وسیلهی خاصیت options
فیلد مورد دسترسی قرار داد. هر گزینه دارای یک خاصیت به نام selected
میباشد که بیانگر این است که آیا انتخاب شده است یا خیر. این خاصیت را همچنین می توان برای انتخاب کردن یا از انتخاب خارج نمودن یک گزینه نیز استفاده کرد.
این مثال مقادیر انتخاب شده را از یک فیلد انتخاب چندگزینهای (multiple
) استخراج می کند و از آن ها برای نوشتن یک عدد دودویی از بیتهای جداگانه استفاده می کند. برای انتخاب چندین گزینه کلید control (یا command در مک) را نگه دارید.
<select multiple> <option value="1">0001</option> <option value="2">0010</option> <option value="4">0100</option> <option value="8">1000</option> </select> = <span id="output">0</span> <script> let select = document.querySelector("select"); let output = document.querySelector("#output"); select.addEventListener("change", () => { let number = 0; for (let option of Array.from(select.options)) { if (option.selected) { number += Number(option.value); } } output.textContent = number; }); </script>
فیلدهای انتخاب فایل
فیلدهای فایل در ابتدا به عنوان راهی برای بارگذاری فایلها از کامپیوتر کاربر به وسیلهی یک فرم طراحی شدند. در مرورگرهای مدرن، همچنین این فیلدها راهی برای خواندن این فایلها به وسیلهی برنامههای جاوااسکریپت فراهم میکنند. فیلد انتخاب فایل به عنوان نوعی دربان عمل می کند؛ اسکریپت نمی تواند فایلهای خصوصی را از کامپیوتر کاربر بخواند، اما اگر کاربر فایلی را به وسیلهی این فیلد انتخاب کند، تفسیر مرورگر این خواهد بود که اسکریپت ممکن است فایل انتخاب شده را بخواند.
ظاهر یک فیلد فایل معمولا شبیه به یک دکمه که عنوانی شبیه به “choose a file” یا “browse” را دارد میباشد و معمولا اطلاعاتی در بارهی فایل انتخاب شده در کنار آن نمایش داده می شود.
<input type="file"> <script> let input = document.querySelector("input"); input.addEventListener("change", () => { if (input.files.length > 0) { let file = input.files[0]; console.log("You chose", file.name); if (file.type) console.log("It has type", file.type); } }); </script>
خاصیت files
مربوط به عنصر فیلد فایل، یک شیء آرایهگونه است ( نه یک آرایهی واقعی) که حاوی فایلهایی است که در فیلد انتخاب شده اند. در ابتدا این خاصیت خالی است. علت اینکه نام آن file
نیست این است که خصوصیتی(attribute) در فیلد فایل وجود دارد به نام multiple
که باعث می شود بتوان چندین فایل را به صورت همزمان انتخاب کرد.
اشیائی که در شیء files
قرار دارند دارای خاصیتهایی مثل name
(نام فایل)، size
(اندازه فایل به بایت که معادل 8 بیت است) و type
(نوع فایل مثل text/plain
یا image/jpeg
) می باشند.
چیزی که در آن وجود ندارد خاصیتی است که محتوای فایل را نگهداری کند. بدست آوردن محتوای فایل کمی پیچیده تر است. به دلیل اینکه خواندن فایل از روی دیسک سخت ممکن است طول بکشد، رابط آن باید به صورت ناهمگام (asynchronous) باشد تا موجب قفل شدن شدن صفحه نشود.
<input type="file" multiple> <script> let input = document.querySelector("input"); input.addEventListener("change", () => { for (let file of Array.from(input.files)) { let reader = new FileReader(); reader.addEventListener("load", () => { console.log("File", file.name, "starts with", reader.result.slice(0, 20)); }); reader.readAsText(file); } }); </script>
خواندن یک فایل به وسیلهی ایجاد شیئی به نام FileReader
انجام می شود، که گردانندهی رخداد load
برای آن ثبت می شود و متد readAsText
آن فراخوانی می شود و فایلی که قصد خواندن آن را داریم به آن داده شود. به محض اینکه بارگیری فایل به اتمام برسد، خاصیت result
شیء reader فایل، حاوی محتوای فایل خواهد بود.
اشیاء FileReader
زمانی که به هر دلیل خواندن فایل با مشکل روبرو شود، یک رخداد “error”
ایجاد می کنند . شیء خطا در نهایت در خاصیت error
شیء reader قرار می گیرد. این رابط قبل از اضافه شدن promiseها به زبان جاوااسکریپت طراحی شده است .می توانید این کار را در قالب promise به صورت زیر انجام دهید.
function readFileText(file) { return new Promise((resolve, reject) => { let reader = new FileReader(); reader.addEventListener( "load", () => resolve(reader.result)); reader.addEventListener( "error", () => reject(reader.error)); reader.readAsText(file); }); }
ذخیرهی دادهها در سمت کاربر (سرویسگیرنده)
صفحات سادهی HTML با کمی جاوااسکریپت می توانند فرمت خوبی برای اپلیکیشنهای کوچک باشند – برنامههای کمکی کوچکی که بعضی کارها را خودکار می کنند. با ترکیب چند فیلد فرم با گردانندههای رخداد، می توانید هر کاری از تبدیل سانتی متر به اینچ گرفته تا محاسبهی رمزهای عبور از روی یک رمزعبور مادر و نام یک وبسایت را انجام دهید.
زمانی که این گونه اپلیکیشنها نیاز به ذخیره و به خاطرسپاری چیزی به صورت پایدار دارند، نمی توانید از متغیرهای جاوااسکریپت استفاده کنید- متغیرها با بسته شدن صفحهی مرورگر از بین می روند. می توانید یک سرویسدهنده راه اندازی کنید، آنرا به اینترنت متصل کنید و اطلاعات اپلیکیشن تان را در آنجا ذخیره کنید. در فصل 20 به چگونگی این کار می پردازیم. اما این روش، کار و پیچیدگی زیادتری می طلبد. گاهی اوقات نگهداشتن دادهها در خود مرورگر برای کار ما کافی است.
شیء localStorage
را می توان برای ذخیرهی دادهها به صورتی که با بارگیری مجدد صفحه حفظ شوند استفاده کرد. این شیء به شما این امکان را می دهد که مقادیر رشتهای را تحت نامهایی دسته بندی و ذخیره کنید.
localStorage.setItem("username", "marijn"); console.log(localStorage.getItem("username")); // → marijn localStorage.removeItem("username");
یک مقدار در localStorage
تا زمانی که دوباره بازنویسی شود باقی می ماند، می توان آن را با removeItem
حذف کرد ؛ همچنین کاربر میتواند دادههای محلی مرورگر خود را به صورت دستی حذف کند.
سایتهایی که از دامنههای متفاوتی هستند ناحیههای ذخیرهی مجزایی دریافت می کنند. این یعنی دادههایی که در localStorage
توسط یک وبسایت داده شده ذخیره می شوند، علی القاعده، فقط توسط اسکریپتهایی که روی همان سایت قرار دارند قابل خواندن و بازنویسی می باشند.
مرورگرها محدودیتی در رابطه با اندازهی دادههایی که یک وب سایت می تواند در localStorage
ذخیره کند اعمال می کنند. این محدودیت، در کنار این واقعیت که پر کردن دیسک سخت کاربر با اطلاعات بهدردنخور فایدهای ندارد، باعث می شود که این ویژگی موجب گرفته شدن فضای زیادی نشود.
در کد پیش رو یک اپلیکیشن ابتدایی برای یادداشت برداری پیاده سازی شده است. این برنامه مجموعهای از یادداشت ها دارای نام را نگهداری می کند و به کاربر این امکان را می دهد که این یادداشتها را ویرایش کرده و موارد جدید را نیز بتواند اضافه کند.
Notes: <select></select> <button>Add</button><br> <textarea style="width: 100%"></textarea> <script> let list = document.querySelector("select"); let note = document.querySelector("textarea"); let state; function setState(newState) { list.textContent = ""; for (let name of Object.keys(newState.notes)) { let option = document.createElement("option"); option.textContent = name; if (newState.selected == name) option.selected = true; list.appendChild(option); } note.value = newState.notes[newState.selected]; localStorage.setItem("Notes", JSON.stringify(newState)); state = newState; } setState(JSON.parse(localStorage.getItem("Notes")) || { notes: {"shopping list": "Carrots\nRaisins"}, selected: "shopping list" }); list.addEventListener("change", () => { setState({notes: state.notes, selected: list.value}); }); note.addEventListener("change", () => { setState({ notes: Object.assign({}, state.notes, {[state.selected]: note.value}), selected: state.selected }); }); document.querySelector("button") .addEventListener("click", () => { let name = prompt("Note name"); if (name) setState({ notes: Object.assign({}, state.notes, {[name]: ""}), selected: name }); }); </script>
اسکریپت بالا وضعیت ابتدایی خودش را از مقدار "Notes"
که در localStorage
ذخیره شده است میگیرد یا در صورت نبود آن، یک وضعیت نمونه که یک لیست خرید فرضی در آن قرار دارد را ایجاد می کند. خواندن یک فیلد که وجود خارجی ندارد از localStorage
موجب می شود تا مقدار null
برگردانده شود. ارسال null
به JSON.parse
موجب می شود که رشتهی "null"
به وجود بیاید و درنتیجه خروجی null
تولید شود. بنابراین، عملگر ||
را می توان برای در نظر گرفتن مقدار پیشفرض در شرایطی مثل این استفاده کرد.
متد setState
موجب می شود اطمینان حاصل کنیم که DOM وضعیت مشخصی را نمایش می دهد و وضعیت جدید را در localStorage
ذخیره می کند. گردانندههای رخداد این تابع را برای انتقال به یک وضعیت جدید فراخوانی می کنند.
استفاده از Object.assign
در مثال بالا به این خاطر بود که یک کپی از شیء state.notes
قدیمی ایجاد شود که حاوی خاصیتی باشد که اضافه یا بازنویسی شده است. Object.assign
اولین آرگومانش را گرفته و تمامی خاصیتهایی که در آرگومانهای بعدی می آید را به آن اضافه می کند. بنابراین، روش استفاده از براکتها در آرگومان سوم برای ایجاد یک خاصیت که نام آن بر اساس یک مقدار پویا مشخص می شود استفاده شده است.
شیء دیگری وجود دارد شبیه به localStorage
به نام sessionStorage
. تفاوت بین این دو این است که محتوای sessionStorage
با به پایان رسیدن هر جلسه (session) فراموش می شود که در بیشتر مرورگرها با بستن مرورگر این اتفاق می افتد.
خلاصه
در این فصل به نحوهی عملکرد پروتوکل HTTP پرداختیم. یک کلاینت درخواستی را ارسال می کند که حاوی یک متد (معمولا GET
) و یک مسیر که معرف یک منبع است میباشد. سرویس دهنده سپس تصمیم می گیرد که با این درخواست چه کند و با یک کد وضعیت و یک بدنهی پاسخ، به درخواست جواب می دهد. هر دوی درخواست و پاسخ می توانند حاوی سرپیامها (headers) باشند که اطلاعات بیشتری را فراهم می کنند.
رابطی که از طریق آن جاوااسکریپت مرورگر می تواند درخواستهای HTTP را بسازد fetch
نامیده می شود. ساخت یک درخواست به شکل زیر می ماند:
fetch("/18_http.html").then(r => r.text()).then(text => { console.log(`The page starts with ${text.slice(0, 15)}`); });
مرورگرها از درخواستهای نوع GET
برای بازیابی منابع مورد نیاز جهت نمایش یک صفحهی وب استفاده می کنند. یک صفحه وب ممکن است حاوی فرمها نیز باشد که این امکان را فراهم می کنند که با ثبت فرم، اطلاعات ورودی کاربر برای ارسال درخواست برای یک صفحهی جدید ارسال شود.
HTML اشکال متنوعی از فیلدهای فرم را پشتیبانی می کند مانند فیلدهای متنی، چکباکسها یا، فیلدهای چندگزینهای و فیلدهای انتخاب فایل.
این فیلدها را می تواند توسط جاوااسکریپت کنترل و تغییر داد. رخداد "change"
با ایجاد تغییر در یک فیلد تولید می شود، رخداد "input"
زمانی ایجاد می شود که متنی در فیلد تایپ شود و رخدادهای مربوط به صفحهکلید هنگامی که فیلد توسط صفحهکلید در دسترس و فعال است دریافت می شوند. خاصیتهایی مثل value
(در فیلدهای text و select) یا checked
(در چکباکسها و دکمههای رادیویی) برای خواندن یا تنظیم محتوای فیلد استفاده می شوند.
زمانی که یک فرم ثبت می شود، یک رخداد "submit"
روی آن ایجاد می شود. یک گردانندهی جاوااسکریپت می تواند با فراخوانی preventDefault
مانع از ثبت فرم شود. فیلدهای فرم را نیز می تواند بیرون از برچسب form استفاده کرد.
زمانی که کاربر فایلی را از سیستم محلی فایل خود به وسیلهی فیلد انتخاب فایل گزینش می کند، رابط FileReader
را می تواند برای دسترسی به محتوای آن فایل از درون یک برنامه جاوااسکریپت استفاده کرد.
اشیاء localStorage
و sessionStorage
را می توان برای ذخیره اطلاعات به صورت مانا که علی رغم بارگیری مجدد صفحه باقی میمانند استفاده کرد. اولین شیء، دادهها را به صورت همیشگی ذخیره می کند( یا تا هنگامی که کاربر تصمیم به حذف آنها بگیرد) و دومی تا زمانی که مرورگر بسته نشده داده ها را نگه می دارد.
تمرینها
مذاکره محتوا
یکی از کارهایی که HTTP می تواند انجام دهد اصطلاحا مذاکرهی محتوا – content negotiation خوانده می شود. از سرپیام درخواست Accept
برای اعلام نوع سندی که سرویسگیرنده درخواست دارد به سرویسدهنده استفاده می شود.خیلی از سرویسدهندهها این سرپیام را نادیده می گیرند، اما زمانی که یک سرویسدهنده از راههای متنوع کدگذاری یک منبع آگاه است، می تواند به این سرپیام نگاه کرده و چیزی که سرویسگیرنده ترجیح می دهد را برایش ارسال کند.
آدرس https://eloquentjavascript.net/author تنظیم شده است که بسته به درخواست سرویسگیرنده با متن عادی، HTML یا JSON پاسخ میدهد. این فرمتها توسط انواع استاندارد شدهی رسانه، application/json
و text/plain
، text/html
شناسایی میشود.
درخواستهایی برای بازیابی هر سه نوع فرمت گفته شده را به منبع ارسال کنید. از خاصیت headers
در شیء مربوط به گزینههای که به fetch
داده می شود برای تنظیم سرپیام Accept
به نوع رسانهی مورد نظر استفاده کنید.
درآخر، تلاش کنید که نوع رسانهی application/
را درخواست کنید و ببینید چه اتفاقی خواهد افتاد.
// Your code here.
کد خود را بر اساس مثالهای مربوط به fetch
بنویسید که پیشتر در این فصل آمد.
درخواست برای یک نوع رسانهی نامعتبر موجب میشود که پاسخی با کد 406 برگردانده شود، “Not acceptable”، که کدی است که سرویسدهنده باید در زمانی که قادر نیست به سرپیام Accept
پاسخ معتبری بدهد، ارسال کند.
یک میز کار جاوااسکریپت
رابطی بسازید که به کاربران این امکان را بدهد که بتوانند کدهای جاوااسکریپت بنویسند و آنها را اجرا کنند.
یک دکمهی کنار فیلد <textarea>
قرار دهید که در صورت فشرده شدن ، از سازندهی Function
که در فصل 10 دیدیم استفاده کند تا محتوای فیلد را درون یک تابع قرار داده و فراخوانی کند. مقدار بازگشتی از تابع را ، یا هر خطایی که به وجود می آید، به یک رشته تبدیل کنید و آن را پایین فیلد متنی نمایش دهید.
<textarea id="code">return "hi";</textarea> <button id="button">Run</button> <pre id="output"></pre> <script> // Your code here. </script>
از document.
یا document.
برای دسترسی به عناصر موجود در صفحهی HTMLتان استفاده کنید. یک گردانندهی رخداد برای "click"
یا "mousedown"
روی دکمه می می تواند خاصیت value
مربوط به فیلد متنی را گرفته و Function
را روی آن فراخوانی کند.
اطمینان حاصل کنید که هم فراخوانی به Function
و هم فراخوانی نتیجهی آن را در یک بلاک try
محصور کنید تا بتوانید استثناءهایی که تولید می شود را پوشش دهید. در این مورد، واقعا نمیدانیم که چه نوع استثنائی را باید انتظار داشته باشیم، پس همه را پوشش می دهیم.
خاصیت textContent
مربوط به عنصر محل نمایش را می توان برای قرار دادن متن رشتهای در آن استفاده کرد. یا، اگر قصد دارید که محتوای قبلی آن را نگهدارید، یک گرهی متنی جدید با استفاده از document.
بسازید و آن را به عنصر مورد نظر الحاق کنید. به خاطر داشته باشید که به پایان آن یک کاراکتر خط جدید اضافه کنید تا از ظاهر شدن تمام خروجی در یک خط جلوگیری کنید.
بازی زندگی کانوی ( Conway’s Game of Life)
بازی زندگی کانوی یک تشبیه ساده است که یک “زندگی” مصنوعی را روی یک جدول ایجاد می کند. هر خانه جدول می تواند زنده یا مرده باشد. در هر نسل(دوره) قوانین زیر اعمال می شوند:
-
هر خانه(سلول) زنده ای که کمتر از دو یا بیشتر از سه همسایهی زنده داشته باشد میمیرد.
-
هر سلول زنده که دو یا سه همسایهی زنده داشته باشد به زندگی تا دورهی بعد ادامه خواهد داد.
-
هر سلول مرده که دقیقا سه همسایهی زنده داشته باشد به یک سلول زنده تبدیل می شود.
هر خانهای که مجاور محسوب شود به عنوان همسایه در نظر گرفته می شود حتی خانههایی که به صورت قطری مجاور هستند.
توجه داشته باشید که این قوانین به کل جدول (grid) اعمال می شوند نه به یک چهار ضلعی محدود. به این معنا که شمارش همسایهها بر اساس وضعیتی خواهد بود که ابتدای هر نسل وجود دارد و تغییرات اعمالی به همسایهها در طول این نسل نباید به وضعیت جدید یک سلول مشخص اثر بگذارد.
این بازی را به وسیلهی جاوااسکریپت و با استفاده هر ساختار دادهای که مناسب می دانید پیاده سازی کنید. از Math.random
برای پر کردن جدول با الگوی تصادفی برای شروع استفاده کنید. از فیلدهای checkbox به همراه دکمهای در کنارشان برای انتقال به نسل بعد در جدول استفاده کنید. زمانی که کاربر checkbox را فعال یا غیر فعال می کند تغییرات آن ها باید در هنگام محاسبهی نسل بعد اعمال شوند.
<div id="grid"></div> <button id="next">Next generation</button> <script> // Your code here. </script>
برای حل مسالهی اتفاق افتادن تغییرات در یک زمان واحد به صورت مفهومی، سعی کنید که محاسبهی یک نسل را به صورت یک تابع ناب (pure) ببینید، که یک grid را گرفته و یک grid جدید تولید می کند که نمایندهی دورهی بعد میباشد.
نمایش ماتریکس را میتوان از راهی که در فصل 6 نشان داده شد، میسر کرد. میتوانید تعداد همسایههای زنده را به وسیلهی دو حلقهی تودرتو بشمارید، با پیمایش مختصات مجاور در هر دو بعد. مواظب باشید که سلولهای خارج از فیلد را نشمارید و سلول مرکز را هم به حساب نیاورید، سلولی که همسایههایش را قصد داریم بشماریم.
از دو راه می توان اطمینان حاصل کرد که تغییر checkbox ها روی نسل بعد تاثیر میگذارد. یک گردانندهی رخداد می تواند این تغییرات را متوجه شود و grid فعلی را برای بازتاب آن بهروز کند، یا می توانید یک grid تازه از مقادیر موجود در checkbox ها قبل از محاسبهی نسل بعد برای تولید یک grid تازه استفاده کنید.
اگر انتخاب شما استفاده از گردانندههای رخداد باشد، ممکن است بخواهید که خاصیتهایی برای مشخص نمودن موقعیت هر checkbox الحاق کنید تا بتوان به آسانی سلولی که باید تغییر کند را پیدا کنید.
برای رسم جدول checkbox ها، میتوانید یا از یک <table>
( به Chapter 14 رجوع کنید ) استفاده کنید یا به سادگی همهی آن ها را در یک عنصر قرار داده و از <br>
بین ردیفها استفاده کنید.