بسیاری از رویدادها اقدامات خودبهخودی از سمت مرورگر را در پی دارند
برای نمونه:
- یک کلیک بر روی لینک – شما را به آدرس موردنظر میرساند
- یک کلیک روی دکمه ارسال فرم – تایید و ارسال فرم به سرور آغاز میشود.
- فشردن دکمه ماوس برروی متن و حرکت دادن آن – متن را انتخاب میکند
اگر بخواهیم که یک رویداد را در جاوااسکریپت مدیریت کنیم ممکن است نخواهیم تا اکشن پیشفرض مرورگر اتفاق بیفتد و بخواهیم که رفتار متفاوتی به جای آن را پیادهسازی کنیم.
جلوگیری از اکشنهای مرورگر
دو راه برای اینکه به مرورگر بگوییم نمیخواهیم تا رفتار پیشفرض را انجام دهد وجود دارد:
- راه اصلی استفاده از آبجکت
event
است. متدی به نام()event.preventDefault
وجود دارد. - اگر هندلر با استفاده از
<on<event
مشخص شده باشد (نه باaddEventListener
) آنگاه بازگرداندن مقدارfalse
به همین صورت عمل خواهد کرد.
در این فایل HTML, کلیک بر روی یک لینک منجر به تغییر آدرس مروگر نمیشود; مرورگر کاری نمیکند:
<a href="/" onclick="return false">اینجا را کلیک کنید</a>
یا
<a href="/" onclick="event.preventDefault()">اینجا</a>
در مثال بعدی، ما از این تکنیک برای ایجاد یک منو با جاوااسکریپت استفاده خواهیم کرد.
false
از یک هندلر یک استثناستمقدار بازگردانده شده توسط یک هندلر معمولا نادیده گرفته میشود.
تنها استثنا برگرداندن return false
از یک هندلر اختصاص داده شده با استفاده از <on<event
است.
در همهی موارد دیگر مقدار return
نادیده گرفته میشود. به طور خاص بازگرداندن true
هیچ معنایی ندارد.
مثال: منو
برای مثال منوی یک سایت را درنظر بگیرید:
<ul id="menu" class="menu">
<li><a href="/html">HTML</a></li>
<li><a href="/javascript">JavaScript</a></li>
<li><a href="/css">CSS</a></li>
</ul>
با مقدار CSS اینطور به نظر میرسد:
آیتمهای منو به شکل به شکل لینک با تگ <a>
پیادهسازی شدهاند نه به شکل دکمه با تگ <button>
. دلایلی مختلفی برای انجام اینکار وجود دارد. برای مثال:
- بسیاری از افراد علاقه دارند تا از "کلیک راست " استفاده کنند و گزینه “open in a new window” برای باز کردن مقصد در یک صفحه جدید استفاده کنند. اگر از
<button>
یا<span>
استفاده کنیم امکان اینکار وجود نخواهد داشت. - موتورهای جستجو تگ
<a href="...">
را هنگام ایندکس کردن دنبال میکنند.
بنابراین ما از <a>
در مارکآپ استفاده میکنیم. اما چون معمولا تمایل داریم تا کلیکها را در جاوااسکریپت هندل کنیم بنابراین باید از رفتار پیشفرض مرورگر جلوگیری کنیم.
مثل اینجا:
menu.onclick = function(event) {
if (event.target.nodeName != 'A') return;
let href = event.target.getAttribute('href');
alert( href ); // میتواند لود از سرور یا ساخت UI و مانند آن باشد...
return false; // از رفتار پیشفرض مرورگر جلوگیری میکند (به آدرس نرو)
};
اگر return false
را حذف کنیم آنگاه پس از اجرای کد ما مرورگر “رفتار پیشفرض” خود را انجام خواهد داد – مرورگر شما را به آدرس موجود در href
هدایت خواهد کرد. و ما به آن اینجا نیاز نداریم چون میخواهیم که کلیک توسط خودمان مدیریت شود.
درضمن استفاده از event delegation در اینجا منوی ما را بسیار انعطاف پذیر میکند. چون میتوانیم از لیستهای تودرتو استفاده کرده و آنرا با استفاده از CSS استایل دهیم. مثل استایل “slide down”
رویدادهای مشخص در یکدیگر جاری میشوند. اگر از اولی جلوگیری کنیم رویداد دومی وجود نخواهد داشت.
برای مثال رویداد mousedown
برروی <input>
منجر به فوکوس آن و رویداد فوکوس میشود. اگر از رویداد mousedown
جلوگیری کنیم آنگاه فوکوسی نخواهیم داشت.
تلاش کنید تا برروی اولین <input>
کلیک کنید – رویداد focus
رخ خواهد داد. اما اگر برروی دومی کلیک کنید فوکوسی وجود نخواهد داشت.
<input value="Focus works" onfocus="this.value=''">
<input onmousedown="return false" onfocus="this.value=''" value="Click me">
این بخاطر آن است که اکشن مرورگر برروی mousedown
لغو شده است. فوکوس کردن همچنان امکانپذیر است اگر ما راه دیگری برای وارد کردن اینپوت استفاده کنیم. برای مثال کلید Tab
برای انتقال از اینپوت اول به دوم اما بدون استفاده از کلیک ماوس.
<<<<<<< HEAD
امکان هندلر “passive”
آپشن اختیاری passive: true
از addEventListener
این سیگنال را به مرورگر میدهد که مرورگر هندلر ()preventDefault
را صدا نخواهد کرد.
چرا ممکن است که به این آپشن نیاز پیدا کنیم؟
ایونت هایی همچون touchmove
در دیوایسهای موبایلی وجود دارند (زمانی که یوزر انگشت خود را برروی صفحهنمایش حرکت میدهد) که به صورت پیشفرض باعث اسکرول میشوند اما این اسکرول خوردن میتواند با وجود ()preventDefault
در هندلر جلوگیری شود.
بنابراین زمانی که مرورگر چنین ایونتی را شناسایی میکند اول از همه باید همهی هندلرهارا بررسی کرده و اگر preventDefault
جایی صدا زده نشده باشد میتواند با اسکرول خوردن ادامه یابد که این میتواند سبب تاخیرها و لرزشهای غیرضروری شود.
گزینه passive: true
به مرورگر میگوید که هندلر قصد لغو اسکرول را ندارد آنگاه مرورگر بلافاصله عمل اسکرول را انجام میدهد و تجربه خوب و روانی را برای کاربر به وجود میآورد و درضمن رویداد هم هندل میشود.
در بعضی از مرورگرها (فایرفاکس و کروم) مقدار passive
به صورت پیشفرض برای رویدادهای touchstart
و touchmove
مقدار true
دارد.
bae0ef44d0208506f6e9b7f3421ee640ab41af2b
event.defaultPrevented
ویژگی event.defaultPrevented
اگر از اکشن پیشفرض جلوگیری شده باشد true
بوده و درغیراینصورت false
خواهد بود.
یک مورد استفاده جالب برای این وجود دارد.
به یاد دارید که در فصل بالارفتن و گرفتن راجع به ()event.propagation
و اینکه چرا bubbling خوب نیست صحبت کردیم؟
گاهی اوقات برای اینکه به باقی ایونت هندلرها خبر بدهیم که ایونت هندل شده میتوانیم از event.defaultPrevented
استفاده کنیم.
بیاید تا با هم یک مثال عملی را ببینیم.
به صورت پیشفرض مروگر در پاسخ به ایونت contextmenu
(کلیک راست ماوس) یک منو با آپشنهای استاندارد را نمایش میدهد. ما میتوانیم از این موضوع جلوگیری کرده و منوی خودمان را نمایش دهیم. به اینصورت:
<button>کلیک راست context menu مرورگر را نمایش میدهد</button>
<button oncontextmenu="alert('Draw our menu'); return false">
کلیک راست context menu ما را نمایش میدهد
</button>
حالا علاوه بر context menu میخواهیم تا یک منو تعریف شده در داکیومنت پیادهسازی کنیم.
با کلیکراست نزدیکترین context menu ظاهر خواهد شد.
<p>برای context menu داکیومنت اینجا کلیک راست کنید</p>
<button id="elem">برای context menu دکمه اینجا کلیک راست کنید</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Document context menu");
};
</script>
مشکل این است زمانی که برروی elem
کلیک کنیم دو منو خواهیم داشت: منوی تعریف شده برروی دکمه و (زمانی که رویداد bubble up میکند) منوی تعریف شده در داکیومنت.
چطور این مسئله را فیکس کنیم؟ یکی از راهحل ها این است: “زمانی که که میخواهیم کلیکراست را در هندلر دکمه هندل کنیم بیاید تا bubbling آن را متوقف کرده” و از ()event.stopPropagation
استفاده کنیم.
<p>برای منوی داکیومنت اینجا کلیک راست کنید</p>
<button id="elem">برای منوی دکمه (با event.stopPropagation فیکس شده)</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
event.stopPropagation();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Document context menu");
};
</script>
حالا منوی تعریف شده برروی دکمه همانطور که میخواستیم کار میکند. اما هزینه اینکار بالاست. ما برای همیشه دسترسی به اطلاعات راجع به کلیک راست را برای کدهای بیرونی قطع میکنیم که شامل شمارندههایی که آمار و امثالهم را جمع میکنند میشود. که اینکار هوشمندانه نیست.
یک راهحل جایگزین بررسی هندلر document
و اینکه آیا اکشن جلوگیری شده یا نه میباشد. اگر اینگونه است آنگاه رویداد هندل شده و نیازی به واکنش به آن نیست.
<p>کلیک راست برای منوی داکیومنت (حالتی که بررسی event.defaultPrevented اضافه شده است)</p>
<button id="elem">برای منوی دکمه کلیک راست کنید</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
if (event.defaultPrevented) return;
event.preventDefault();
alert("Document context menu");
};
</script>
حالا همه چیز به درستی کار میکند. این راه حل حتی اگر المنتهای تودرتویی داشته باشیم که هرکدام منوی خودشان را داشته باشند هم کار میکند. فقط اطمینان حاصل کنید که event.defaultPrevented
در هر یک از هندلرهای contextmenu
چک میشود.
واضح است که ()event.stopPropagation
و ()event.preventDefault
(که همچنین به عنوان return false
شناخته میشود) دو چیز متفاوت از هم هستند که به یکدیگر ربطی ندارند.
همچنین راههای جایگزینی برای پیادهسازی منوهای تودرتو وجود دارد. یکی از آنها این است که یک آبجکت گلوبال با یک هندلر برای document.oncontextmenu
داشته باشیم و همچنین متدهایی که به ما اجازه میدهند تا دیگر هندلرهایی را در آن ذخیره کنیم.
آبجت هرگونه کلیک راستی را گرفته، نگاهی به هندلرهای آن میاندازد و هندلر مناسب را اجرا میکند.
اما در اینصورت هر تکه کدی که بخواهد context menu داشته باشد باید راجع به آن آبجکت بداند و به جای هندلر contextmenu
خودش از کمک آن آبجکت گلوبال استفاده کند.
خلاصه
اکشن های پیشفرض مختلفی وجود دارند:
mousedown
– انتخاب متن را آغاز میکند (برای انتخاب ماوس را حرکت دهید)click
بر روی<input type="checkbox">
–input
را check/uncheck میکند.submit
– کلیک بر روی<input type="submit">
یا فشردن Enter درون یک فیلد فرم باعث رخ دادن این رویداد میشود و مرورگر پس از آن فرم را سابمیت میکند.keydown
– فشردن یک کلید ممکن است باعث افزودن یک کاراکتر به فیلد یا اکشنهای دیگر شود.contextmenu
– رویدادی که با کلیک راست رخ میدهد و اکشن مرتبط با آن نمایش context menu مرورگر است.- –موارد بیشتری هم وجود دارند–
اگر بخواهیم تا ایونت را به طور خاص با جاوااسکریپت هندل کنیم میتوانیم از همهی اکشنهای پیشفرض جلوگیری کنیم.
برای جلوگیری از یک اکشن پیشفرض میتوانیم از ()event.preventDefault
یا return false
استفاده کنیم. دومین متد تنها برای هندلرهای اختصاص یافته با <on<event
کار میکند.
اگر از اکشن پیشفرض جلوگیری شده باشد مقدار event.defaultPrevented
به true
تغییر میکند در غیراینصورت false
میشود.
به شکل تکنیکال با جلوگیری از اکشنهای پیشفرض مرورگر و افزودن جاوااسکریپت میتوانیم رفتار المنتها را شخصیسازی کنیم. برای مثال میتوانیم کاری کنیم تا تگ <a>
مانند یک دکمه کار کند و یک دکمه <button>
مانند یک لینک رفتار کند (یه یک آدرس دیگر ریدایرکت کند و امثالهم).
اما ما باید به طور کلی معنای سمنتیک المنتها را حفظ کنیم. برای مثال <a>
باید هدایت مرورگر به آدرسها را انجام دهد نه یک دکمه.
این موضوع علاوه بر اینکه “چیز خوبی است” کد HTML شما را از نظر دسترسیپذیری بهتر میکند.
علاوه بر این اگر مثال <a>
را درنظر بگیریم باید به این نکته توجه کنید که: یک مرورگر به ما این اجازه را میدهد تا این لینکها را در یک پنجره جدید باز کنیم (با کلیک راست برروی منوها) و کاربران این رفتار را دوست دارند. اما اگر کاری کنیم که یک دکمه مانند لینک رفتار کند و حتی با استفاده از css ظاهرش را مانند یک لینک کنیم آنگاه ویژگیهای منحصر به فرد تگ <a>
برای آن عمل نخواهد کرد.
نظرات
<code>
استفاده کنید، برای چندین خط – کد را درون تگ<pre>
قرار دهید، برای بیش از ده خط کد – از یک جعبهٔ شنی استفاده کنید. (plnkr، jsbin، codepen…)