Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event delegation #319

Merged
merged 3 commits into from
Feb 8, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 75 additions & 75 deletions 2-ui/2-events/03-event-delegation/article.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@

# Event delegation
# پترن Event delegation

Capturing and bubbling allow us to implement one of the most powerful event handling patterns called *event delegation*.
گرفتن (capture) و bubbling ایونت ها به ما این توانایی را میدهد که از یکی از قویترین الگوهای ایونت هندلینگ یعنی *event delegation* استفاده کنیم.

The idea is that if we have a lot of elements handled in a similar way, then instead of assigning a handler to each of them -- we put a single handler on their common ancestor.
ایده این است که اگر تعداد زیادی المنت داریم و میخواهیم به یک شکل آنها رو هندل کنیم به جای اینکه به تک تک آنها هندلر مجزا اختصاص دهیم، یک هندلر را برای المنت والد مشترک آنها اختصاص میدهیم.

In the handler we get `event.target` to see where the event actually happened and handle it.
در هندلری که اختصاص میدهیم با استفاده از `event.target` محل وقوع رویداد را متوجه میشویم و بنابراین میتوانیم آنرا هندل کنیم.

Let's see an example -- the [Ba-Gua diagram](http://en.wikipedia.org/wiki/Ba_gua) reflecting the ancient Chinese philosophy.
بیاید تا با هم یک مثال رو بررسی کنیم -- [دیاگرام Ba-Gua] (http://en.wikipedia.org/wiki/Ba_gua) که یک فلسفه چینی باستانی رو نشون میده

Here it is:
به این شکل :

[iframe height=350 src="bagua" edit link]

The HTML is like this:
فایل HTML به اینصورت خواهد بود:

```html
<table>
Expand All @@ -30,45 +30,45 @@ The HTML is like this:
</table>
```

The table has 9 cells, but there could be 99 or 9999, doesn't matter.
جدول 9 سلول دارد اما این عدد امکان دارد 99 یا 9999 باشد. مهم نیست.

**Our task is to highlight a cell `<td>` on click.**
**ماموریت ما این است که سلول `<td>` که روی آن کلیک شد را هایلایت کنیم**

Instead of assign an `onclick` handler to each `<td>` (can be many) -- we'll setup the "catch-all" handler on `<table>` element.
به جای آنکه هندلر `onclick` را به هریک از تگ های `<td>` اساین کنیم (که ممکن است تعداد زیادی از آنها داشته باشیم)، هندلر "catch-all" را برروی المنت `<table>` اساین میکنیم.

It will use `event.target` to get the clicked element and highlight it.
این عمل از `event.target` برای پیدا کردن المنت کلیک شده و هایلایت آن استفاده می‌کند.

The code:
کد:

```js
let selectedTd;

*!*
table.onclick = function(event) {
let target = event.target; // where was the click?
let target = event.target; // کلیک کجا اتفاق افتاد؟

if (target.tagName != 'TD') return; // not on TD? Then we're not interested
if (target.tagName != 'TD') return; // المنت TD نیست؟ پس المنت موردنظر ما نیست

highlight(target); // highlight it
highlight(target); // هایلایتش کن
};
*/!*

function highlight(td) {
if (selectedTd) { // remove the existing highlight if any
if (selectedTd) { // اگر هایلایتی وجود دارد آنرا حذف کن
selectedTd.classList.remove('highlight');
}
selectedTd = td;
selectedTd.classList.add('highlight'); // highlight the new td
selectedTd.classList.add('highlight'); // TD جدید را هایلایت کن
}
```

Such a code doesn't care how many cells there are in the table. We can add/remove `<td>` dynamically at any time and the highlighting will still work.
این کد به اینکه چند سلول داخل جدول قرار دارد اهمیتی نمیدهد. میتوانیم المنت‌های `<td>` رو به شکل دینامیکی هر زمان که خواستیم اضافه/کم کنیم و همچنان هایلایت کار خواهد کرد.

Still, there's a drawback.
اما همچنان یک اشکال وجود دارد.

The click may occur not on the `<td>`, but inside it.
کلیک ممکن است نه روی `<td>` بلکه درون آن اتفاق بیفتد.

In our case if we take a look inside the HTML, we can see nested tags inside `<td>`, like `<strong>`:
در اینصورت اگر به داخل HTML نگاهی بندازیم میتوانیم تگ های درون `<td>` را ببینمی. مثل المنت `<strong>`

```html
<td>
Expand All @@ -79,13 +79,13 @@ In our case if we take a look inside the HTML, we can see nested tags inside `<t
</td>
```

Naturally, if a click happens on that `<strong>` then it becomes the value of `event.target`.
طبیعتا زمانی که یک کلیک بر روی `<strong>` انجام میشود آنگاه مقدار `event.target` برابر آن خواهد شد.

![](bagua-bubble.svg)

In the handler `table.onclick` we should take such `event.target` and find out whether the click was inside `<td>` or not.
در هندلر `table.onclick` ما باید مقدار `event.target` را گرفته و از این طریق مشخص کنیم که آیا کلیک درون `<td>` اتفاق افتاده یا نه.

Here's the improved code:
کد بهبود یافته شده:

```js
table.onclick = function(event) {
Expand All @@ -99,27 +99,27 @@ table.onclick = function(event) {
};
```

Explanations:
1. The method `elem.closest(selector)` returns the nearest ancestor that matches the selector. In our case we look for `<td>` on the way up from the source element.
2. If `event.target` is not inside any `<td>`, then the call returns immediately, as there's nothing to do.
3. In case of nested tables, `event.target` may be a `<td>`, but lying outside of the current table. So we check if that's actually *our table's* `<td>`.
4. And, if it's so, then highlight it.
توضیحات:
1. متد `elem.closest(selector)` نزدیک‌ترین المنت به سلکتور را برمیگرداند. در این مسئله ما در مسیر از سورس المنت به بالا به دنبال المنت `<td>` هستیم.
2. اگر `event.target` درون هیچ `<td>` نباشد آنگاه فراخوانی تابع بلافاصله برمیگردد. درست مانند آنکه چیزی برای انجام دادن وجود ندارد.
3. در مسئله جدول‌های تودرتو `event.target` ممکن است یک المنت `<td>` باشد که خارج از جدول مورد نظر ما قرار گرفته است بنابراین نیاز است بررسی کنیم که المنت آیا درون جدول موردنظر ما قرار دارد یا نه.
4. و اگر چنین است آنگاه هایلایتش کن.

As the result, we have a fast, efficient highlighting code, that doesn't care about the total number of `<td>` in the table.
در نتیحه ما یک کد هایلایتر سریع و کارا داریم که عملکرد آن به تعداد سلول‌های `<td>` در جدول ارتباطی ندارد.

## Delegation example: actions in markup
## مثالی از الگوی delegation: اکشن‌های مارک‌آپ

There are other uses for event delegation.
استفاده‌های دیگری هم برای event delegation وجود دارد

Let's say, we want to make a menu with buttons "Save", "Load", "Search" and so on. And there's an object with methods `save`, `load`, `search`... How to match them?
در نظر بگیرید که میخواهیم یک منو با دکمه‌های "Save", "Load"، "Search" و مانند آن بسازیم آبجکتی با متدهای `save`, `load`, `search` و ... وجود دارد. چطور میتوانیم این متدهارا به دکمه‌های مربوطه وصل کنیم؟

The first idea may be to assign a separate handler to each button. But there's a more elegant solution. We can add a handler for the whole menu and `data-action` attributes for buttons that has the method to call:
اولین ایده‌ای که به ذهن میرسد ممکن است این باشد که برای هریک از دکمه‌ها هندلر مجزا اساین کنیم. اما راه‌حل بهتری هم وجود دارد. می‌توانیم یک هندلر به کل منو و اتریبیوت‌های `data-action` دکمه‌ها اضافه کنیم.

```html
<button *!*data-action="save"*/!*>Click to Save</button>
```

The handler reads the attribute and executes the method. Take a look at the working example:
هندلر اتریبیوت را خوانده و متد را اجرا میکند. نگاهی به این مثال واقعی بیندازید:

```html autorun height=60 run untrusted
<div id="menu">
Expand Down Expand Up @@ -161,28 +161,28 @@ The handler reads the attribute and executes the method. Take a look at the work
</script>
```

Please note that `this.onClick` is bound to `this` in `(*)`. That's important, because otherwise `this` inside it would reference the DOM element (`elem`), not the `Menu` object, and `this[action]` would not be what we need.
توجه کنید که در `(*)` متد `this.onClick` به `this` متصل شده است. این مهم است زیرا در غیراینصورت `this` درون آن به المنت DOM رفرنس میدهد (`elem`) نه به آبجکت `Menu` و `this[action]` چیزی که ما میخواهیم نخواهد بود.

So, what advantages does delegation give us here?
خب، استفاده از الگوی delegation اینجا برای ما چه فایده‌ای دارد؟

```compare
+ We don't need to write the code to assign a handler to each button. Just make a method and put it in the markup.
+ The HTML structure is flexible, we can add/remove buttons at any time.
```مقایسه
+ نیازی به نوشتن کد برای اختصاص دادن یک هندلر به هریک از دکمه‌ها نداریم. فقط یک متد مینویسیم و در مارک‌آپ قرار می‌دهیم.
+ ساختار HTML منعطف خواهد بود، می‌توانیم دکمه‌ها را هر زمان که خواستیم اضافه/کم کنیم.
```

We could also use classes `.action-save`, `.action-load`, but an attribute `data-action` is better semantically. And we can use it in CSS rules too.
همچنین میتوانیم از کلاس‌های `action-save`، `action-load` استفاده کنیم. اما از لحاط سمنتیک اتریبیوت `data-action` بهتر است. به علاوه می‌توانیم از آن در CSS rule ها هم استفاده کنیم.

## The "behavior" pattern
## الگوی "behavior"

We can also use event delegation to add "behaviors" to elements *declaratively*, with special attributes and classes.
همچنین ما میتوانیم با استفاده از الگوی delegation با استفاده از اتریبیوت‌ها و کلاس‌های خاص رفتارهایی را به صورت *declaratively* به المنت‌ها اضافه کنیم.

The pattern has two parts:
1. We add a custom attribute to an element that describes its behavior.
2. A document-wide handler tracks events, and if an event happens on an attributed element -- performs the action.
الگو دو قسمت دارم:
1. یک اتریبیوت شخصی‌سازی شده که بیانگر رفتار آن باشد را به المنت اضافه میکنیم.
2. یک هندلر به وسعت داکیومنت ایونت‌ها را ردیابی میکند و اگر یک رویداد بر روی المنتی بااتریبوت موردنظر اتفاق آنگاه عمل خواهد کرد.

### Behavior: Counter
### رفتار شمارشگر

For instance, here the attribute `data-counter` adds a behavior: "increase value on click" to buttons:
برای مثال اینجا اتریبیوت `data-counter` رفتار "مقدار را با کلیک افزایش بده" را به دکمه‌ها اضافه میکند.

```html run autorun height=60
Counter: <input type="button" value="1" data-counter>
Expand All @@ -191,27 +191,27 @@ One more counter: <input type="button" value="2" data-counter>
<script>
document.addEventListener('click', function(event) {

if (event.target.dataset.counter != undefined) { // if the attribute exists...
if (event.target.dataset.counter != undefined) { // در صورتی که اتریبیوت وجود داشته باشد..
event.target.value++;
}

});
</script>
```

If we click a button -- its value is increased. Not buttons, but the general approach is important here.
اگر بر روی یک دکمه کلیک کنیم مقدار آن افزایش می‌یابد. اینجا روش کلی مهم است نه دکمه ها.

There can be as many attributes with `data-counter` as we want. We can add new ones to HTML at any moment. Using the event delegation we "extended" HTML, added an attribute that describes a new behavior.
ممکن است هر تعداد اتریبیوت با مقدار `data-counter` که بخواهیم داشته باشیم. هر زمان که بخواهیم میتوانیم یکی دیگه اضافه کنیم. با استفاده از الگوی event delegation با افزودن یک اتریبیوت که بیانگر رفتاری جدید است HTML را گسترش داده‌ایم.

```warn header="For document-level handlers -- always `addEventListener`"
When we assign an event handler to the `document` object, we should always use `addEventListener`, not `document.on<event>`, because the latter will cause conflicts: new handlers overwrite old ones.
زمانی که یک ایونت هندلر را به آبجکت `document` اختصاص میدهیم، همواره باید از `addEventListener` استفاده کنیم، نه `document.on<event>` چون دومی موجب کانفلیکت خواهد شد: هندلرهای جدید جایگزین قدیمی‌ها خواهند شد.

For real projects it's normal that there are many handlers on `document` set by different parts of the code.
برای پروژه‌های واقعی طبیعتا ممکن است هندلرهای زیادی روی `document` در قسمت‌های مختلف کد تعریف شده باشد.
```

### Behavior: Toggler
### رفتار: تغییر وضعیت

One more example of behavior. A click on an element with the attribute `data-toggle-id` will show/hide the element with the given `id`:
یک مثال دیگر از این رفتار. یک کلیک بر روی المنتی با اتریبیوت `data-toggle-id` المنتی با `id` داده شده را نمایش/پنهان می‌کند.

```html autorun run height=60
<button *!*data-toggle-id="subscribe-mail"*/!*>
Expand All @@ -236,37 +236,37 @@ One more example of behavior. A click on an element with the attribute `data-tog
</script>
```

Let's note once again what we did. Now, to add toggling functionality to an element -- there's no need to know JavaScript, just use the attribute `data-toggle-id`.
بیاید تا یک بار دیگه به کاری که انجام دادیم توجه کنیم. حالا برای افزودن کارکرد تغییر وضعیت به یک المنت نیازی به دانستن جاوااسکریپت نداریم، تنها با استفاده از اتریبیوت `data-toggle-id` میتوان اینکار را انجام داد.

That may become really convenient -- no need to write JavaScript for every such element. Just use the behavior. The document-level handler makes it work for any element of the page.
این ممکن است خیلی کار مارا راحت کند. نیازی به نوشتن جاوااسکریپت برای هر المنت نیست. تنها با استفاده از رفتار. هندلری که به وسعت داکیومنت تعریف کردیم باعث میشود راه حل ما برای همه‌ی المنت‌های داخل صفحه کار کند.

We can combine multiple behaviors on a single element as well.
همچنین میتوانیم چندین رفتار را برای یک المنت ترکیب کنیم.

The "behavior" pattern can be an alternative to mini-fragments of JavaScript.
الگوی "رفتار" میتواند یک جایگزین برای mini fragment های جاوااسکریپت باشد.

## Summary
## خلاصه

Event delegation is really cool! It's one of the most helpful patterns for DOM events.
الگوی Event delegation واقعا باحال است! این الگو یکی از مفیدترین الگوها برای ایونت‌های DOM است.

It's often used to add the same handling for many similar elements, but not only for that.
این الگو معمولا برای کنترل تعداد زیادی از المان‌های مشابه استفاده می‌شود اما این تنها کاربرد آن نیست.

The algorithm:
الگوریت:

1. Put a single handler on the container.
2. In the handler -- check the source element `event.target`.
3. If the event happened inside an element that interests us, then handle the event.
1. یک هندلر برای کل کانتینر قرار دهید.
2. در هندلر المنت سورس را از طریق `event.target` بیابید.
4. اگر ایونت درون المنتی که موردنظر ماست اتفاق افتاده است آنگاه ایونت را هندل کنید.

Benefits:
فواید:

```compare
+ Simplifies initialization and saves memory: no need to add many handlers.
+ Less code: when adding or removing elements, no need to add/remove handlers.
+ DOM modifications: we can mass add/remove elements with `innerHTML` and the like.
```مقایسه
+ عدم نیاز به اضافه کردن تعداد زیادی هندلر باعث ذخیره حافظه و ساده‌سازی شروع میشود
+ کد کمتر: زمان اضافه و کم کردن المان‌ها نیازی به تغییر در هندلرها نیست.
+ تغییرات DOM: میتواینم با استفاده از `innerHTML` و ابزارهای مشابه آن تعداد زیادی المان را کم/زیاد کنیم.
```

The delegation has its limitations of course:
بدون شک این الگو هم محدودیت‌های خودش را دارد:

```compare
- First, the event must be bubbling. Some events do not bubble. Also, low-level handlers should not use `event.stopPropagation()`.
- Second, the delegation may add CPU load, because the container-level handler reacts on events in any place of the container, no matter whether they interest us or not. But usually the load is negligible, so we don't take it into account.
```مقایسه
- نخست ایونت باید bubble کند. بعضی از ایونت‌ها bubble نمی‌کنند. همچنین هندلرهای سطح پایین نباید از `event.stopPropagation()` استفاده کنند.
- دوم اینکه ممکن است به بار CPU اضافه کند چون هندلری که در سطح کانتینر تعریف شده است، درهرکجای کانتینر، بدون توجه به اینکه آیا مدنظر ما هستند یا نه به رویدادها واکنش نشان خواهد داد. اما این بار CPU معمولا قابل چشم‌پوشی است بنابراین ما آنرا به حساب نمی‌آوریم.
```