ما نه تنها می توانیم هندلر ها را اختصاص دهیم، بلکه می توانیم رویدادها را از جاوااسکریپت نیز تولید کنیم.
از custom events می توان برای ایجاد “مولفه های گرافیکی” استفاده کرد. به عنوان مثال، یک عنصر ریشه از منوی مبتنی بر JS خودمان ممکن است رویدادهایی را ایجاد کند که میگوید چه اتفاقی در منو میافتد: باز کردن (باز کردن منو)، انتخاب (یک مورد انتخاب شده است) و غیره. کد دیگری ممکن است به event ها گوش دهد و آنچه را که در منو اتفاق می افتد مشاهده کند.
ما میتوانیم نه تنها event های کاملاً جدیدی را که برای اهداف خود اختراع میکنیم، بلکه همچنین event های داخلی مانند کلیک، پایین آوردن ماوس و غیره را ایجاد کنیم. ممکن است برای تست خودکار نرم افزار مفید باشد.
Event constructor
کلاس های event متعلق به زبان یک سلسله مراتب را تشکیل می دهند، مشابه کلاس های عنصر DOM. ریشه آن، کلاس درونساخت Event است.
ما می توانیم اشیاء event را به این صورت ایجاد کنیم:
let event = new Event(type[, options]);
پارامتر ها:
-
type: event type, یک رشته مانند
"click"
. -
options: شی ای با دو ویژگی اختیاری.
- Bubbles:اگر true بود یعنی bubbled می شود.
- cancelable (true/false): اگر ویزگی مقدار true را داشت عمل پیش فرض آن لغو میشود،
به صورت پیش فرض:
{bubbles: false, cancelable: false}
.dispatchEvent
پس از ایجاد یک event، باید آن را روی یک element با استفاده از فراخوانی elem.dispatchEvent(event)
اجرا کنیم.
سپس هندلر ها به آن واکنش نشان می دهند که گویی یک event معمولی مرورگر است. اگر event با bubble flag ها ایجاد شده باشد، bubbled می شود.
در مثال زیر رویداد click
در جاوااسکریپت آغاز می شود. هندلر به همان روشی کار می کند که اگر روی دکمه کلیک شده باشد:
<button id="elem" onclick="alert('Click!');">Autoclick</button>
<script>
let event = new Event("click");
elem.dispatchEvent(event);
</script>
راهی برای تشخیص یک user event “واقعی” از event تولید شده توسط اسکریپت وجود دارد.
ویژگی ‘event.isTrusted’ برای رویدادهایی که از اقدامات کاربر واقعی ناشی می شوند ‘true’ و برای رویدادهای تولید شده توسط اسکریپت ‘false’ است.
مثالی از Bubbling
میتوانیم یک bubbling event با نام "hello"
ایجاد کنیم و آن را در document
بگیریم.
تنها چیزی که نیاز داریم این است که bubbles
را روی true
تنظیم کنیم:
<h1 id="elem">Hello from the script!</h1>
<script>
// catch on document...
document.addEventListener("hello", function(event) { // (1)
alert("Hello from " + event.target.tagName); // Hello from H1
});
// ...dispatch on elem!
let event = new Event("hello", {bubbles: true}); // (2)
elem.dispatchEvent(event);
// the handler on document will activate and display the message.
</script>
نکات:
- باید از
addEventListener
برای شخصیسازی eventها استفاده کرد، زیراon<event>
فقط برای eventهای متعلق به زبان وجود دارد وdocument.onhello
کار نمی کند. - باید
bubbles:true
را تنظیم کنیم وگرنه رویداد bubble up نخواهد شد.
مکانیک bubbling برای event درونساخت (click
) و event شخصیسازهشده (hello
) یکسان است. هم چنین مراحلی برای capturing و bubbling نیز وجود دارد.
MouseEvent, KeyboardEvent and others
موارد زیر شامل یک لیست کوتاه از کلاس هایی برای UI Events از UI Event specification هستند:
UIEvent
FocusEvent
MouseEvent
WheelEvent
KeyboardEvent
- …
اگر میخواهید event به وجود بیاوریم، باید به جای new Event
از کلاس های بالا استفاده کنیم. برای مثال new MouseEvent("click")
.
انتخاب constructor درست این امکان را میدهد که property های استاندارد را برای هر نوع از event مشخص کنیم.
مانند clientX/clientY
برای mouse event:
let event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
clientX: 100,
clientY: 100
});
alert(event.clientX); // 100
لطفا به یاد داشته باشد: constructor های generic Event
اجازه این کار را نمیدهد.
بیایید این روش را امتحان کنیم:
let event = new Event("click", {
bubbles: true, // only bubbles and cancelable
cancelable: true, // work in the Event constructor
clientX: 100,
clientY: 100
});
alert(event.clientX); // undefined, the unknown property is ignored!
ااز نظر فنی، میتوانیم با اختصاص مستقیم event.clientX=100
پس از ایجاد، آن را حل کنیم. بنابراین این موضوع راحت و پیروی از قوانین است. event های ایجاد شده توسط مرورگر همیشه از نوع مناسبی هستند.
لیست کاملی از propertyها برای eventهای مختلف UI در مشخصات زبان وجود دارد، برای مثال، MouseEvent.
event شخصیسازیشد
برای انواع events های کاملاً جدید خودمان مانند "hello"
باید از new CustomEvent
استفاده کنیم. از نظر فنی CustomEvent با Event
یکسان است،البته با یک استثنا.
در آرگومان دوم (object) میتوانیم یک ویژگی detail
برای هر اطلاعات که میخواهیم با event ارسال کنیم، اضافه کنیم.
برای مثال:
<h1 id="elem">Hello for John!</h1>
<script>
// additional details come with the event to the handler
elem.addEventListener("hello", function(event) {
alert(event.detail.name);
});
elem.dispatchEvent(new CustomEvent("hello", {
detail: { name: "John" }
}));
</script>
ویژگی detail
می تواند هر داده ای را داشته باشد. از نظر فنی میتوانیم آن را تنظیم نکنیم، زیرا میتوانیم هر ویژگی را پس از ایجاد یک شی new Event
معمولی به آن اختصاص دهیم. اما CustomEvent
فیلد detail
ویژه ای را برای جلوگیری از conflicts با سایر property های event فراهم می کند.
علاوه بر این، کلاس event توضیح میدهد که «چه نوع رویدادی» است، و اگر event سفارشی باشد، باید از CustomEvent
استفاده کنیم تا مشخص شود که چیست.
event.preventDefault()
بسیاری از رویدادهای مرورگر دارای یک “default action” هستند، مانند پیمایش یک لینک، شروع یک انتخاب و غیره.
برای event های جدید و custom events، قطعاً هیچ اقدام پیشفرض مرورگر وجود ندارد، اما کدی که چنین رویدادی را ارسال میکند ممکن است برنامههای خاص خود را داشته باشد که پس از راهاندازی رویداد چه کاری انجام دهد.
با فراخوانی event.preventDefault()
، یک کنترل کننده رویداد ممکن است سیگنالی ارسال کند که آن اقدامات باید لغو شوند.
در آن صورت فراخوانی به elem.dispatchEvent(event)
false برمی گردد. و کدی که آن را ارسال کرده می داند که نباید ادامه یابد.
بیایید یک مثال عملی ببینیم – یک خرگوش پنهان (ممکن است منوی بسته شدن یا چیز دیگری باشد).
در زیر میتوانید یک element با آیدی #rabbit
و یک تابع hide()
را مشاهده کنید که رویداد "hide"
را روی آن ارسال میکند تا به همه علاقهمندان اطلاع دهد که خرگوش قرار است مخفی شود.
هر کنترلکنندهای میتواند با rabbit.addEventListener('hide',...)
به آن رویداد گوش دهد و در صورت نیاز، عمل را با استفاده از event.preventDefault()
لغو کند. سپس خرگوش ناپدید نمی شود:
<pre id="rabbit">
|\ /|
\|_|/
/. .\
=\_Y_/=
{>o<}
</pre>
<button onclick="hide()">Hide()</button>
<script>
function hide() {
let event = new CustomEvent("hide", {
cancelable: true // without that flag preventDefault doesn't work
});
if (!rabbit.dispatchEvent(event)) {
alert('The action was prevented by a handler');
} else {
rabbit.hidden = true;
}
}
rabbit.addEventListener('hide', function(event) {
if (confirm("Call preventDefault?")) {
event.preventDefault();
}
});
</script>
نکته: event حتما باید cancelable: true
را داشته باشد یا اینکه event.preventDefault()
فراخوانی نمی شود.
Events-in-events are synchronous
معمولا رویدادها در یک صف پردازش می شوند. یعنی: اگر مرورگر در حال پردازش onclick
باشد و یک رویداد جدید رخ دهد، به عنوان مثال: ماوس حرکت می کند، سپس handling آن در صف قرار می گیرد، پس از اتمام پردازش onclick
، کنترل کننده های مربوط به mousemove
فراخوانی می شوند.
استثنا قابل توجه زمانی است که یک رویداد از درون رویداد دیگری آغاز می شود، به عنوان مثال: با استفاده از dispatchEvent
چنین رویدادهایی بلافاصله پردازش میشوند: کنترلکنندههای رویداد جدید فراخوانی میشوند و سپس مدیریت رویداد فعلی از سر گرفته میشود.
به عنوان مثال، در کد زیر، رویداد menu-open
در حین onclick
فعال می شود.
بدون اینکه برای onclick
هندلر صبر کند تا تمام شود، شروع به پردازش می شود:
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
}));
alert(2);
};
// triggers between 1 and 2
document.addEventListener('menu-open', () => alert('nested'));
</script>
ترتیب خروجی این است: 1 → تودرتو (nested) → 2
لطفاً توجه داشته باشید که منوی رویداد تودرتو که باز است در document
وجود دارد. انتشار و مدیریت رویداد تودرتو قبل از اینکه پردازش به کد خارجی بازگردد (onclick
) به پایان می رسد.
این فقط مربوط به dispatchEvent
نیست، موارد دیگری نیز وجود دارد. اگر یک کنترل کننده event متدهایی را فراخوانی کند که رویدادهای دیگر را راهاندازی میکنند، آنها نیز به صورت همزمان و به صورت تودرتو پردازش میشوند.
بیایید بگوییم که آن را دوست نداریم. ما می خواهیم ابتدا onclick
به طور کامل پردازش شود، مستقل از menu-open
یا هر رویداد تو در تو.
سپس میتوانیم dispatchEvent
(یا یک فراخوانی event-triggering) را در انتهای onclick
قرار دهیم یا، شاید بهتر، آن را در setTimeout
با تاخیر صفر قرار دهیم:
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
setTimeout(() => menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
})));
alert(2);
};
document.addEventListener('menu-open', () => alert('nested'));
</script>
حالا dispatchEvent
یه صورت asynchronously بعد از اینکه اجرای کد فعلی به اتمام رسید، run میشود، که شامل menu.onclick
. پس event handler ها به صورت جدت و مستقل فعالیت میکنند.
خروجی به این ترتیب خواهد بود: 1 → 2 → nested.
خلاصه
برای تولید یک event code، ابتدا باید یک event object ایجاد کنیم.
سازنده Event(name, options)
یک نام رویداد دلخواه و options
object را با دو ویژگی میپذیرد:
bubbles: true
اگر رویداد باید حباب شود.cancelable: true
اگرevent.preventDefault()
باید کار کند.
سایر سازندههای native events مانند MouseEvent
, KeyboardEvent
و غیره ویژگیهای خاص آن نوع رویداد را میپذیرند. به عنوان مثال، clientX
برای رویدادهای ماوس.
برای custom events باید از سازنده CustomEvent
استفاده کنیم. این یک گزینه اضافی به نام detail
دارد، ما باید داده های رویداد خاص را به آن اختصاص دهیم. سپس همه کنترل کننده ها می توانند به عنوان event.detail
به آن دسترسی داشته باشند.
علیرغم امکان فنی ایجاد browser events مانند click
یا keydown
، ما باید با دقت زیادی از آنها استفاده کنیم.
ما نباید browser events را ایجاد کنیم زیرا این یک روش هک برای اجرای کنترلرها است. این معماری در بیشتر مواقع بد است.
هم چنین native events ممکن است ایجاد شوند:
- به عنوان یک dirty hack برای اینکه کتابخانه های شخص ثالث به روش مورد نیاز کار کنند، اگر ابزارهای تعامل دیگری را ارائه ندهند.
- برای تست خودکار، روی دکمه “click the button” در اسکریپت و ببینید که آیا رابط به درستی واکنش نشان می دهد.
همچنین eventهای سفارشی با نام خود ما اغلب برای مقاصد معماری ایجاد می شوند تا نشان دهند که چه اتفاقی در منوها، اسلایدرها و غیره می افتد.