بسیاری از رویدادها اقدامات خودبهخودی از سمت مرورگر را در پی دارند
برای نمونه:
- یک کلیک بر روی لینک – شما را به آدرس موردنظر میرساند
- یک کلیک روی دکمه ارسال فرم – تایید و ارسال فرم به سرور آغاز میشود.
- فشردن دکمه ماوس برروی متن و حرکت دادن آن – متن را انتخاب میکند
اگر بخواهیم که یک رویداد را در جاوااسکریپت مدیریت کنیم ممکن است نخواهیم تا اکشن پیشفرض مرورگر اتفاق بیفتد و بخواهیم که رفتار متفاوتی به جای آن را پیادهسازی کنیم.
جلوگیری از اکشنهای مرورگر
دو راه برای اینکه به مرورگر بگوییم نمیخواهیم تا رفتار پیشفرض را انجام دهد وجود دارد:
- راه اصلی استفاده از آبجکت
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…)