בשורה מרעישה לכל מי שסובל מבעיות UX הנובעות מבלבול בין לחיצות כפולות לבודדות
-
כידוע חלק גדול מהסבל של האנושות (אחרי רעב מחלות ומלחמות) נובע מכך שיוזרים לוחצים פעמיים על כפתורים, כשצריך ללחוץ פעם אחת, והלחיצה הכפולה נספרת גם כ 2 לחיצות בודדות, ואז דברים מתחילים להתחרבש, שלא לדבר על שליחת API פעמיים לשרת, חיובים כפולים בכרטיסי אשראי, הזמנות כפולות, ושיגור של 2 פצצות אטום במקום אחת (אם מדובר למשל בדונלד טראמפ או משהו כזה).... אבל מילא פצצות אטום וכדומה, במקרה היותר גרוע מדובר בדיאלוג שנפתח באמצע המסך, ואז נסגר כי היוזר לחץ פעמיים על הכפתור שפותח את הדיאלוג, והלחיצה השניה בעצם סוגרת את הדיאלוג, כי זו לחיצה מחוץ לשטח הדיאלוג, כאן זה כבר הרבה יותר נורא מפצצת אטום, כי היוזר מתקשר אליך לנייד "דחוף דחוף" תודו.
מצד שני אתה לא יכול להכריח את כולם ללחוץ פעמיים כי חלק מהיוזרים מצפים ללחיצה בודדת, הרי מדובר בכפתור אחרי הכל...
אז הפתרון שלנו הוא דיירקטיב לאנגולר שמאפשר לך לשלוט ביד רמה על אירועי ההקלקה באופן הבא:אפשר להגדיר לחיצה בודדת בלבד והלחיצה השניה של ההקלקה הכפולה לא יורה אירוע בכלל (זו התנהגות ברירת המחדל)
אפשר להגדיר שהלחיצה הבודדת לא יורה בכלל כאשר ישנה לחיצה כפולה.
אפשר גם להגדיר את אותו אירוע ללחיצה בודדת או כפולה, כך שאם הוא לחץ לחיצה כפולה האירוע יירה בזכות הלחיצה הכפולה, והלחיצה הבודדת לא תשפיע בכלל.
לאחרונה הבעיה הזו גרמה לי הרבה תלונות UXיות, ולכן הפשלתי את השרוולים והכנסתי את הידיים לבוץ אחת ולתמיד.
נא לא להעלות את הקוד הזה לאינטרנט בשלב זה. לשימוש חברי הפורום בלבד.
תהנו.import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core'; @Directive({ selector: '[mtrSingleClick]' }) export class SingleClickDirective { private singleClickWasClickedRecently = false; private isdblClick = false; @Input() public debounceTime = 500; @Input() public ignoreSingleClickWhenDblClick = true; @Input('mtrSingleClickStopPropagation') public stopPropagation = false; @Output() singleClick = new EventEmitter<any>(); @Output() singleOrDblclick = new EventEmitter<any>(); @HostListener('click', ['$event']) onClick($event): void { if (this.stopPropagation) { $event.stopPropagation(); } if (this.singleClickWasClickedRecently) { return; } this.singleClickWasClickedRecently = true; if (this.ignoreSingleClickWhenDblClick) { // check if not dblclick const mainTimer = setTimeout(() => { if (this.isdblClick) { this.singleClickWasClickedRecently = false; clearTimeout(mainTimer); return; } // if is not dblclick this.emitSingleClick($event); clearTimeout(mainTimer); }, this.debounceTime); } else { this.emitSingleClick($event); } } emitSingleClick($event): void { this.singleClick.emit($event); this.singleOrDblclick.emit($event); const timer = setTimeout(() => { this.singleClickWasClickedRecently = false; clearTimeout(timer); }, this.debounceTime); } @HostListener('dblclick', ['$event']) onDblclick($event): void { if (this.stopPropagation) { $event.stopPropagation(); } this.isdblClick = true; const timer = setTimeout(() => { this.isdblClick = false; clearTimeout(timer); }, this.debounceTime); this.singleOrDblclick.emit($event); } constructor() { } }
למימוש:
<div mtrSingleClick (singleOrDblclick)="openForm()" [mtrSingleClickStopPropagation]="true" > </div>
-
@ארכיטקט במקום השטות שכתבתי למעלה, אנסה עכשיו לכתוב משהו מועיל.
אני יודע שאתה מהותיקים פה, ואני יחסית חדש פה ובתכנות בכללי, אז קח את הדברים בפרופורציה. אבל נהנתי לגזור את הקוד שלך לגזרים מקוה שתסלח לי.
יש לי כמה הערות על הקוד שלך ואנסה לסדר אותם.
א) מטה הערה, האם זה חסרון בי שלקח לי הרבה זמן (יחסית) להבין את הלוגיקה של הקוד? משהו פה מרגיש לי מסובך מדי. (לענ"ד זה מה שגרם לחלק מהבעיות שבאות ג', גם הערת @Shaya היה מונע אות א' וג')
ב) שאלה צדדית:
למה אתה נוהג לנקות את ה-timeOut אחרי הרצת הפונקציה של הטיימאאוט? זה לא קורה ממילא?ג) עכשיו למנה העיקרית, הבשר: אם ננתח את ההתנהגות שאנחנו רוצים, לכאורה נגיע למסקנה שיש שיעור זמן מסויים שכל אירוע קליק שמגיע תוך שיעור זמן זה מאז הקליק האחרון אנחנו רוצים לשייך אותו לאירוע מתמשך ולבנות ממנו אירוע דאבל/טריפל/קוואדריפל קליק.
אני טוען שהקוד שלך לא מספק את ההתנהגות הרצויה. חוץ מזה הוא לוקה בחסר שהוא לא נצמד להגדרת מערכת ההפעלה של מה נקרא דאבל קליק.
אנסה להסביר את דברי. (תוכל לשים לב שגם אני קצת מסתבך לכתוב בצורה ברורה מספיק. מקווה שיהיה מובן.)לב הבעיה היא שיש חוסר עקביות שמצד אחד אתה משתמש בשיעור זמן שרירותי של 500 ms ומצד שני אתה משתמש גם בהגדרת המערכת כאשר אתה מזהה לפי אם קיבלת אירוע דאבל קליק. הגדרת המערכת יכולה להיות יותר או פחות מ-500 ms. (אגב, במחשב שלי לפי בדיקה שלי נראה שזה מוגדר על 500ms)
בשורה 11 אתה מגדיר את שיעור זמן של 500 ms.
בשורה 25 אתה זורק לפח כל קליק שמגיע תוך 500 ms מהקליק הראשון. כמו"כ אתה זורק לפח כל קליק שמגיע תוך שיעור 500 ms מאירוע הדאבל קליק האחרון.
בשורה 30 יש IF -- צומת דרכים.- אם המתכנת רצה להתעלם מקליק יחיד כאשר מזוהה דאבל קליק אז אתה משהה את פליטת האירוע של הקליק עד שאתה מקבל אישור אם מדובר בדאבל קליק או לא. אתה מחכה שיעור זמן של 500, ואתה בודק אם לפי הגדרת המערכת יש דאבל קליק.
אם לא מזוהה דאבל לפי הגדרת המערכת אתה פולט קליק יחיד ומחכה עוד 500 ms עד שאתה מנקה את הדגל של singleClickWasClickedRecently, ז.א. אתה זורק לפח כל קליק שמגיע תוך שנייה שלימה של הקליק הראשון אם המערכת לא זיהתה קליק כפול.
מה שיוצא הוא ששני קליקים בתוך שיעור הזמן של מערכת ההפעלה מזוהים כדאבל קליק, וביותר משיעור הזמן הזה ועד לשיעור שנייה שלימה הם מזוהים כקליק אחד. האם זו התנהגות נכונה? לכאורה לא. - אם המתכנת לא רוצה להתעלם מקליק יחיד כאשר מזוהה קליק כפול, אז אתה מיד יורה אירוע של קליק יחיד ואז מחכה עד סוף ה-500 ms לנקות את הדגל. זה בסדר. אבל אז, אם מזוהה דאבל קליק אתה מאריך את הזמן שבו אתה מתעלם מקליקים נוספים בעוד 500 ms, שזה טוב, אבל אם לא מזוהה קליק כפול בתוך ה-500 ms הראשונים, אז אתה כבר לא מתעלם מקליקים נוספים.
בקיצור, חוסר עקביות.
אני מתנצל על חוסר הבהירות בטענות שלי. זה פשוט נושא מסובך... מקווה שאתה מבין את הטענות. אם יהיה לי זמן וחשק אנסה להביא פתרונות גם.
- אם המתכנת רצה להתעלם מקליק יחיד כאשר מזוהה דאבל קליק אז אתה משהה את פליטת האירוע של הקליק עד שאתה מקבל אישור אם מדובר בדאבל קליק או לא. אתה מחכה שיעור זמן של 500, ואתה בודק אם לפי הגדרת המערכת יש דאבל קליק.
-
@yossiz אמר בבשורה מרעישה לכל מי שסובל מבעיות UX הנובעות מבלבול בין לחיצות כפולות לבודדות:
מטה הערה, האם זה חסרון בי שלקח לי הרבה זמן (יחסית) להבין את הלוגיקה של הקוד? משהו פה מרגיש לי מסובך מדי. (לענ"ד זה מה שגרם לחלק מהבעיות שבאות ג', גם הערת @Shaya היה מונע אות א' וג')
לדעתי הקוד כתוב היטיב, אבל לא מתועד מספיק, אולי אפשר לעשות שם עוד כמה הפרדות כדי להבהיר את ההתנהגות.
הוא נכתב בערב שבת בין השמשות, אז אכן לא הייתי צריך לפרסם אותו בשלב זה. אבל אי"ה אשלים את התיעוד ואפרסמהו.@yossiz אמר בבשורה מרעישה לכל מי שסובל מבעיות UX הנובעות מבלבול בין לחיצות כפולות לבודדות:
שאלה צדדית:
למה אתה נוהג לנקות את ה-timeOut אחרי הרצת הפונקציה של הטיימאאוט? זה לא קורה ממילא?אכן אין חובה, זה הרגל - הואיל ואנו משתמשים כבדים ב rxjs - לנקות כל הזמן את ההרשמות.
עכשיו בעקבות ההערה שלך ביררתי וזה נראה שהוא מפסיק להאזין אחרי האירוע.
עדכון: כעת דיברתי עם חבר, הוא אומר שפעם בוודאי זה היה חובה, ייתכן שהיום דפדפנים מודרניים מנקים את זה לבד, אבל כדאי לשמור על ההרגל הזה בשביל מי שמתעקש לעבוד עם אקספלורר 1.@yossiz אמר בבשורה מרעישה לכל מי שסובל מבעיות UX הנובעות מבלבול בין לחיצות כפולות לבודדות:
נגיע למסקנה שיש שיעור זמן מסויים שכל אירוע קליק שמגיע תוך שיעור זמן זה מאז הקליק האחרון אנחנו רוצים לשייך אותו לאירוע מתמשך ולבנות ממנו אירוע דאבל/טריפל/קוואדריפל קליק.
לא ממש, אני לא "בונה" דאבל קליק, שים לב שאני מאזין לאירועים קיימים של קליק ודאבל קליק, ורק מחליט אם אני יורה או לא. ההחלטה אם לירות אירוע ואיזה אירוע, זהו לב לבו של הקוד הזה.
@yossiz אמר בבשורה מרעישה לכל מי שסובל מבעיות UX הנובעות מבלבול בין לחיצות כפולות לבודדות:
אתה משתמש בשיעור זמן שרירותי של 500 ms
זו אכן חולשה של הקוד, אבל למרבה הצער JS לא יודע להגיד לנו מהו פרק הזמן של דאבל קליק, הלכתי לפי הפרסומים של מייקרוסופט שברירת מחדל של מערכת ההפעלה היא 500ms, אם תמצא דרך לדעת את ההגדרות אשמח לשיתוף. ולהזכירך המאפיין הזה הוא אינפוט כך שניתן גם לשחק איתו לחובבי דאבל קליקים מהירים במיוחד.
@yossiz אמר בבשורה מרעישה לכל מי שסובל מבעיות UX הנובעות מבלבול בין לחיצות כפולות לבודדות:
אם לא מזוהה דאבל לפי הגדרת המערכת אתה פולט קליק יחיד ומחכה עוד 500 ms עד שאתה מנקה את הדגל של singleClickWasClickedRecently, ז.א. אתה זורק לפח כל קליק שמגיע תוך שנייה שלימה של הקליק הראשון אם המערכת לא זיהתה קליק כפול.
אכן זה ראוי לתיקון.
@yossiz אמר בבשורה מרעישה לכל מי שסובל מבעיות UX הנובעות מבלבול בין לחיצות כפולות לבודדות:
אם המתכנת לא רוצה להתעלם מקליק יחיד כאשר מזוהה קליק כפול, אז אתה מיד יורה אירוע של קליק יחיד ואז מחכה עד סוף ה-500 ms לנקות את הדגל. זה בסדר. אבל אז, אם מזוהה דאבל קליק אתה מאריך את הזמן שבו אתה מתעלם מקליקים נוספים בעוד 500 ms, שזה טוב, אבל אם לא מזוהה קליק כפול בתוך ה-500 ms הראשונים, אז אתה כבר לא מתעלם מקליקים נוספים.
זה נכלל בתיקון הקודם, בקיצור בשורה 25 הסרתי את התנאי של isDblClick לא צריך אותו.
את זה תיקנתי כבר עכשיו.
בהמשך אנסה להפוך את הקוד לברור יותר ואפרסם גירסה חדשה.תודה על ההשקעה.
-
@yossiz שיפרתי את הקוד, ההערות שלך תוקנו. כעת אם יהיו שיפורים אעדכן את התגובה הזו כל הזמן.
import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core'; /** * This directive help us to manage user single click and double click * In regular DOM events, when a user click double click - twoo events of "click" will be fired, and one event of "dblclick" * This behevior make troubles. becose if we need one event, and the user click twice, twoo events will be fired. * So: * the basic behvior of this directive, is to emit a new event called "singleClick". this event is fired only once, * and is not affected by the second click of user. * * ==================================================================================================================== * * In addition can you use an event called "singleOrDblclick" so that only one event between them will be triggered, * and they will not contradict each other. * So if you have problems with such a scenario * for example opening dialogs they may close on the second click if they are opened on the first click * you can solve the problem by listening to the "singleOrDblclick" event. * * More beheviors documented bellow in the code */ @Directive({ selector: '[mtrSingleClick]' }) export class SingleClickDirective { /** * a flag that tell us if was there a single click before less than the time allotted in the variable "doubleClickSpeed" */ private singleClickWasClickedRecently_FLAG = false; /** * a flag that tell us if we now after a dblclick event */ private isdblClick_FLAG = false; /** * the maximum time (in ms) between clicks, that the DOM fired a dblclick event. */ @Input() public doubleClickSpeed = 500; /** * if you wand that not fired an single click event when the user click double click. */ @Input() public ignoreSingleClickWhenDblClick = true; /** * use $event.stopPropagation()? */ @Input('mtrSingleClickStopPropagation') public stopPropagation = false; /** * an event that emmitted once for single click only and not emmitted twice when the user click double click. * if you choise "ignoreSingleClickWhenDblClick" then this event will not be fired at all - if the user make a double click. */ @Output() singleClick = new EventEmitter<any>(); /** * this event will fired once when the user make a single click or a dblclick * if the user make a dblclick then the event will be fired only by the dblclick DOM event. */ @Output() singleOrDblclick = new EventEmitter<any>(); @HostListener('click', ['$event']) onClick($event): void { if (this.stopPropagation) { $event.stopPropagation(); } // if singleClickWasClickedRecently we do nothing if (this.singleClickWasClickedRecently_FLAG) { return; } // Raise a flag this.singleClickWasClickedRecently_FLAG = true; // If we ignoreSingleClickWhenDblClick we cannot fire event before checking that the user not click twice if (this.ignoreSingleClickWhenDblClick) { // So we have to wait until we make sure we are not in a double click mode const mainTimer = setTimeout(() => { // Either way we have to lower the flag this.singleClickWasClickedRecently_FLAG = false; if (this.isdblClick_FLAG) { clearTimeout(mainTimer); return; } // If is not dblclick we emit a single click event this.emitSingleClickEvent($event); clearTimeout(mainTimer); }, this.doubleClickSpeed + 10); } else { this.emitSingleClickEvent($event); const timer = setTimeout(() => { this.singleClickWasClickedRecently_FLAG = false; clearTimeout(timer); }, this.doubleClickSpeed + 10); } } emitSingleClickEvent($event): void { // console.log('emitSingleClickEvent'); this.singleClick.emit($event); this.singleOrDblclick.emit($event); } @HostListener('dblclick', ['$event']) onDblclick($event): void { if (this.stopPropagation) { $event.stopPropagation(); } this.isdblClick_FLAG = true; this.singleOrDblclick.emit($event); const timer = setTimeout(() => { this.isdblClick_FLAG = false; clearTimeout(timer); }, this.doubleClickSpeed + 100); } constructor() { } }
-
@ארכיטקט
בעיני הרבה יותר קל - קצר - נקי - אלגנטי להרים Flag בעת הפעלת הלחיצה ולכבות אותו בסיום הפעולה (או לאחר timeout קטן), ולשים תנאי שהפונקציה לא תתבצע אם הFlag נמצא.
נכון זה לא כ"כ גנרי (צריך לממש כל פעם), אבל עדיין...let flag; async function doSomething () { if (flag) return; flag = true; await doAnything... await someTimeout... // Optional flag = false; } <button onclick="doSomething()">Do</button>
בעיקר מה שהכי יפה פה, זה שהתנאי להחרגה לא תלוי בזמן (שזה לא כ"כ מאפיין את הבעיה) אלא תלוי בשאלה היחידה הרלוונטית: האם הפעולה הסתיימה או שעדיין לא.
-
@zvizvi אמר בבשורה מרעישה לכל מי שסובל מבעיות UX הנובעות מבלבול בין לחיצות כפולות לבודדות:
@ארכיטקט
בעיני הרבה יותר קל - קצר - נקי - אלגנטי להרים Flag בעת הפעלת הלחיצה ולכבות אותו בסיום הפעולה (או לאחר timeout קטן), ולשים תנאי שהפונקציה לא תתבצע אם הFlag נמצא.
נכון זה לא כ"כ גנרי (צריך לממש כל פעם), אבל עדיין...let flag; async function doSomething () { if (flag) return; flag = true; await doSomting... await someTimeout... // Optional flag = false; } <button onclick="doSomething()">Do</button>
בעיקר מה שהכי יפה פה, זה שהתנאי להחרגה לא תלוי בזמן (שזה לא כ"כ מאפיין את הבעיה) אלא תלוי בשאלה היחידה הרלוונטית - האם הפעולה הסתיימה או שעדיין לא.
אפשר גם להצמיד לפונקציה, כך:
doSomething.atWork = true
קצת גיקי, אבל יותר גנרי.
-
@zvizvi אמר בבשורה מרעישה לכל מי שסובל מבעיות UX הנובעות מבלבול בין לחיצות כפולות לבודדות:
נכון זה לא כ"כ גנרי (צריך לממש כל פעם), אבל עדיין...
זה בדיוק מה שהייתי עושה עד עכשיו בעוונותיי, עד שנתקיים בי סבבוני דגלים רבים ואבירי בשן כיתרוני.
חוץ מזה זה לא פותר את כל הבעיות, למשל דיאלוג שנפתח ונסגר זה תלוי ב DOM. -
@zvizvi אין לי דעה בכל הנושא פה (אני לא מרגיש בנח מהפתרונות וגם לא מבין את בעיית הדבל קליק), אבל הקוד שלך לא קצר ולא נקי ולא אלגנטי... אם יש לך עשר מטפלים לאירועים שונים, אתה צריך עשרה משתנים חדשים (בזה הקוד של אהרון קצת מסדר), הקוד שלך מתנפח, צריך "לזכור" בסוף הפונקציה להדליק, צריך לטפל בשגיאות. בקיצור, אתה נשמע אחד שאוהב את הישן והטוב ומחפש תירוצים.
אתה יכול לומר שאתה מעדיף אותו ואני מבין אותך ועושה כמוך, אבל אני צריך למצוא תשובה נכונה יותר למה אני כך עושה.
אלא"כ לא הבנתי אותך ואתה מתכוון שהדיירקטיב או הקוד הגנרי צריך להיות בסגנון הזה, בזה אני מסכים. ואני מתנגד (או יותר נכון לא מבין את הצורך) לטיימהאוט. -
@dovid אמר בבשורה מרעישה לכל מי שסובל מבעיות UX הנובעות מבלבול בין לחיצות כפולות לבודדות:
ואני מתנגד (או יותר נכון לא מבין את הצורך) לטיימהאוט.
רבי דוד??????????
אין דרך נקייה אחרת לטפל בזה, אירוע דאבל קליק מבוסס על זמן, לא על משהו אחר (וכידוע בשם איינשטיין כששאלו אותו מה זה זמן? הוא ענה: "זמן זה הדבר שאותו אנו מודדים באמצעות שעון"...).
אם אתה רוצה שהדיירקטיב ינהל לך גם את הפונקציות, אז אני כבר לא יודע מה לומר.... בעיקרון אירוע זה אירוע, יורים אותו, ואז הפונקציה שלך מטופלת בקוד, אתה לא מעביר פונקציות לטיפול בתוך הדיירקטיב, צר לי (זה כמו שכשאשתך אומרת לך ללכת לקניות אתה מבין לבד שהיא לא מתכוונת לבוא איתך למכולת...). -
@dovid אמר בבשורה מרעישה לכל מי שסובל מבעיות UX הנובעות מבלבול בין לחיצות כפולות לבודדות:
@zvizvi אין לי דעה בכל הנושא פה (אני לא מרגיש בנח מהפתרונות וגם לא מבין את בעיית הדבל קליק), אבל הקוד שלך לא קצר ולא נקי ולא אלגנטי... אם יש לך עשר מטפלים לאירועים שונים, אתה צריך עשרה משתנים חדשים (בזה הקוד של אהרון קצת מסדר), הקוד שלך מתנפח, צריך "לזכור" בסוף הפונקציה להדליק, צריך לטפל בשגיאות. בקיצור, אתה נשמע אחד שאוהב את הישן והטוב ומחפש תירוצים.
אתה יכול לומר שאתה מעדיף אותו ואני מבין אותך ועושה כמוך, אבל אני צריך למצוא תשובה נכונה יותר למה אני כך עושה.
אלא"כ לא הבנתי אותך ואתה מתכוון שהדיירקטיב או הקוד הגנרי צריך להיות בסגנון הזה, בזה אני מסכים. ואני מתנגד (או יותר נכון לא מבין את הצורך) לטיימהאוט.מה שהתכוונתי להדגיש הוא שאין מנוס מלקבוע ידנית מתי הפונקציה 'בביצוע' ומתי הפעולה הסתיימה. ולרוב זה לא תלוי זמן.
אם כבר ליצור אבסטרקציה, הייתי יוצר סרוויס גנרי שכל פונקציה תלוית לחיצה תקרא לו פעמיים במהלך מחזור החיים שלה.
פעם בהתחלה - ואז הסרוויס ירים את הדגל ובמקביל אם הוא נתקל בדגל הוא עוצר את הכל - פעם בסוף בשביל לכבות את הדגל. -
@ארכיטקט אתה משרטט בראשית דבריך (בכשרון ספרותי רב, תוך כדי הרבה דראמה) את תיאור הבעיה. אתה טוען שיש עמרצים שחושבים שעל כל פעולה צריך לעשות קליק כפול, ואז הם מתפלאים למה זה לא עבד? אני מכיר את האנשים האלו, נפגשתי איתם הרבה. אם זו באמת כל הבעיה שבאת לפתור אז יש פתרון מושלם, נקי, ואלגנטי, וגם בלי צורך בטיימאאוט (את הפתרון אביא בקטע הבא) אבל אם כן לא הבנתי למה אתה נותן אפשרות של
ignoreSingleClickWhenDblClick = true
? אתה הרי לא מעוניין כלל בדאבל קליק, רק בסינגל!? אתה אמור לרצות בדיוק הפוך -ignoreDoubleClick
ולמען האמת אין צורך להתעלם ממנו, פשוט לא מאזינים לו! ואם כן כל מה שצריך הוא למצוא דרך להתעלם מהסינגל קליק השני כאשר הוא חלק מדאבל, שזה לא מצריך טיימאאוט (וזו כנראה קושיית ר' @dovid ). כדי ליישב את הקושי הנ"ל, הבנתי שאתה רוצה לטפל בעוד מקרה שהוא אומנם פחות נפוץ, והוא שאתה רוצה לעשות פעולה A במקרה של סינגל קליק, ופעולה B במקרה של דאבל קליק. כדי לדעת אם לעשות פעולה A כאשר מקבלים ארוע קליק, באמת חייבים לחכות ולראות אם הפעולה יסתיים כדאבל קליק או לא. זו באמת בעיה יותר מסובכת. אבל נראה שאפשר לפותרה בצורה מושלמת גם כן, רק שעוד לא בישלתי את הפתרון...בחזרה לבעיה הראשונה (התעלמות מקליק שני ברצף) הפתרון הכי טוב (לענ"ד) הוא כך (אני מנסה לכתוב אותו כדיירקטיב אנגולארי למרות שאני לא מספיק מכיר):
import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core'; @Directive({ selector: '[mtrSingleClick]' }) export class SingleClickDirective { @Input() public stopPropagation = false; @Input() public IETimeoutInterval = 500; @Output() singleClick = new EventEmitter<any>(); private IEClickEventIsInProgress = false; private IETimeout; @HostListener('click', ['$event']) onClick ($event): void { if (this.stopPropagation) { $event.stopPropagation(); } if ($event.detail > 1) { // See: https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/detail return; } if (isIE) { // IE apparently doesn't support event.detail if (this.IEClickEventIsInProgress) { return this.IEUpdateTimeout(); } this.IEIsClickEventInProgress = true; this.IEUpdateTimeout(); } this.singleClick.emit($event); } IEUpdateTimeout() { if (this.IETimeout) { clearTimeout(this.IETimeout); } this.IETimeout = setTimeout(this.IETimeoutHandler, this.IETimeoutInterval); } IETimeoutHandler() { this.IEClickEventIsInProgress = false; } constructor() { } }
שים לב שאם אתה לא רוצה/צריך לתמוך ב-IE ע"ה, הקוד הוא קצר מאוד.
הקוד הזה מכבד את הגדרת מערכת ההפעלה של שיעור דאבל קליק במקרה שלא מדובר ב-IE. -
@yossiz כמו תמיד צללת לבעיה הטכנית ודילגת על הבנת הצורך (היותר סוציאלי)
ברור כשמש שלא צריך את דאבל קליק, זה לא ידידותי לעשות בממשק אינטרנט פעולה שצריכה דאבל קליק כי זה חיה שלא ממש קיימת שם.
ואין לי תשובה על השאלה למה צריך טימהאוט, מלבד שארכיטקט התחיל עם גישה להאזין לקליק האחרון בסדרת ההקלקות של המשתמש.@zvizvi אמר בבשורה מרעישה לכל מי שסובל מבעיות UX הנובעות מבלבול בין לחיצות כפולות לבודדות:
מה שהתכוונתי להדגיש הוא שאין מנוס מלקבוע ידנית מתי הפונקציה 'בביצוע' ומתי הפעולה הסתיימה. ולרוב זה לא תלוי זמן.
לא מסכים איתך. אם יש פונקציה עוטפת, שמחכה לתוצאת (פרומייז) הפונקציה המטפלת ורק אז מאפשרת את הלחצן שוב, הכל יעבוד יופי.
-
@zvizvi הנושא של האזנה גם לדבל קליק והתעלמות מההקלקה הראשונה נובע מתרחיש שבו מרימים דיאלוג, והוא נסגר בגלל הלחיצה השניה. הסברתי את זה בפתח דבריי.
זה גם לא נכון שאין לחיצות כפולות באינטרנט, כשאתה מדבר על אפליקציה כבדה אין לך הרבה לאן לברוח, כמות האירועים מוגבלת, ואתה שורף מהר מאוד את האופציות של לחיצה בודדת, כפולה, שמאלית, ימנית, אמצעית, אנטר, טאב ועוד היד נטויה. אלא אם כן יש לך מקום לארבע מאות כפתורים בכל ס"מ מסך.
נראה לי שאני צריך להרים פרוייקט קטן (אולי אעשה זאת בהמשך) כדי להמחיש מה קורה למישהו שמרים דיאלוג והוא נסגר לו בפנים, בגלל לחיצה כפולה. או שנפתחים 2 דיאלוגים אחד על גבי השני ואחד מעודכן והשני לא וכשהוא סוגר את הראשון השני עדיין צף, עם הנתונים הישנים. אלו בעיות שהעולם מתמודד איתם מאז מלחמת העולם השניה.ואסיים בסיפור, מישהו פירסם מודעה דרוש שותף לטיול קשוח ביותר ביערות הגשם מקום שלא דרכה שם כף רגל אדם מעולם, למיטיבי לכת ולאוהבי הרפתקאות בלבד. התקשר אליו מישהו
"בקשר למודעה על הטיול"
כן, בן כמה אתה? 96
עשית טיולים כאלו בעבר? לא
אתה אוהב לטייל באופן כללי? לא
אתה בכושר גופני? ממש לא, אני בקושי זז.
אז למה התקשרת? תראה ראיתי את המודעה שלך הבנתי שאתה מחפש פרטנר לטיול שלך, אמרתי לעצמי אני בוודאי לא הולך לעשות את זה איתו, אז התקשרתי להודיע לך "אל תבנה עלי אני לא בעניין של הטיול שלך".
והנמשל (שלא כל כך דומה אבל אני מצשטל אותו לכאן בסגנון רוסי כפי ששמעתיהו בביהמ"ד של יוצאי מוסקבה):
מישהו רוצה לפתור בעיה, (והוא לקוח שמוכן לשלם כסף עבור כך) הוא נכנס לחנות שלך ומבקש פתרון, זה אומר שהוא נמצא בבעיה, ואת הבעיה שלו הוא רוצה לפתור, כלומר יש לו בעיה, והוא מחפש פתרון, הפתרון, צריך להיות פתרון של הבעיה, כלומר של הבעיה שיש לו. ולא פתרון של בעיה אחרת שאין לו. אז אם מציעים לו פתרונות חלופיים, הפתרונות הללו אמורים להיות מסוגלים לפתור את אותה בעיה בדיוק, ולא בעיות אחרות. כמו"כ הוא לא מחפש שיעזרו לו להתעלם מהבעיה, ולומר "אין בעיה כזו" או "היא נדירה" ו"היא לא כל כך מצויה", או "לא כל כך נוראה". או "בוא נפתור בינתיים בעיות אחרות".
ככה מקובל עכ"פ בגלילות הקווקז, אבל מי אני שאתווכח אולי בישראל זה שונה.