WhatSock / apex

Apex 4X: The Comprehensive ARIA Development Suite
MIT License
29 stars 9 forks source link

Datapicker lost focus inside bootstrap modal and Datepicker double open #45

Closed impetoyt closed 11 months ago

impetoyt commented 12 months ago

Hi, I re-open both issue because I tested but it not works. I upload a simple test case (just extract it and click on index.html): https://file.io/G0ukUhR8Cpoo

1) double open: just quickly open one picker and then open the other one. It will appear two datepicker instead of only one. This is a wrong behaviour because if you open just one datapicker and you wait for the animation complete than you can open the second one and you will see that the first one will disappear. image with the issue: https://ibb.co/G99V7h8

2) lost focus inside bootstrap modal: you suggest to use afterRemove: function (dc) { // Move focus somewhere specific... }, this can be work if you are using the keyboard. You open the picker with ENTER on the button than you click ESC and the focus will move again on the button thanks to the afterRemove event. What if you do it with the mouse click? You click on the button and the picker is open than you click for example on the second date picker input field... the focus will move first in the second input field and than to the first datapicker button since there is the afterRemove event and this is wrong. In the afterRemove event is it possibile know if the event was called from the ESC input keybord rather then the mouse click?

accdc commented 11 months ago

Hi, Regarding issue 1 (quickly opening 2 datepicker calendars at the same time), I am not able to reproduce this. Can you test this on the following page? https://whatsock.com/Templates/Datepickers/Basic/test.htm If you are not seeing this behavior on this test page, but you are on your own test page, then your implementation is not likely running the latest 4X.js and Datepicker.js file versions.

Regarding issue 2 (focusing to another element when the mouse is used), no, there is no way to specify that when the mouse is used to click another input that second input will receive focus when the original calendar is closed. Besides the technical challenges of determining the difference between a datepicker edit field and another random element type on the page, doing this would impair keyboard accessibility for the widget since it would violate intuitive keyboard navigation for dynamic widgets which should always set focus back to the logical triggering element when closed. A sighted mouse user can simply click the second input, see that focus is set back to the original location and then click the second field again to set focus to it.

impetoyt commented 11 months ago

Hi, Regarding issue 1 (quickly opening 2 datepicker calendars at the same time), I am not able to reproduce this. Can you test this on the following page? https://whatsock.com/Templates/Datepickers/Basic/test.htm If you are not seeing this behavior on this test page, but you are on your own test page, then your implementation is not likely running the latest 4X.js and Datepicker.js file versions.

Regarding issue 2 (focusing to another element when the mouse is used), no, there is no way to specify that when the mouse is used to click another input that second input will receive focus when the original calendar is closed. Besides the technical challenges of determining the difference between a datepicker edit field and another random element type on the page, doing this would impair keyboard accessibility for the widget since it would violate intuitive keyboard navigation for dynamic widgets which should always set focus back to the logical triggering element when closed. A sighted mouse user can simply click the second input, see that focus is set back to the original location and then click the second field again to set focus to it.

1) I used your test page, I upload a video to show you the wrong behaviour (desktop chrome, latest version): https://easyupload.io/jizo46 It happens also in the test code that I provided to you, I used latest version of 4x.js and Datapiker.js

2) I think there was a misunderstanding, I try to explain better the issue. If I'm inside a bootstrap modal and I move the focus by pressing the TAB key at some point the focus will move on the the datepicker button. I press the ENTER key, the datepicker will open and the focus will automatically move inside the calendar. Now If I press ESC key the datepicker will close and the focus will lost instead to return on the button picker, this is a wrong accessibility behaviour. This issue it happens only in the modal, for example in your test code the focus will return on the input element... also this behaviour maybe it's wrong since the focus should return on the button picker. In the previous issue, you have suggested to use the afterRemove event to fix the focus problem inside the modal. So for example if #my_button_picker is the button that open the datepicker, I could write: afterRemove: function (dc) { $("#my_button_picker").focus(); },

In this way when you close the picker with the ESC key the focus will return on the button picker element and the issue related to the lost focus inside a bootstrap modal is fixed. One big problem by using this approch is to obtain a wrong behaviour when you are using the mouse click: By using the mouse you click the button picker and the datepicker will open. Now by clicking to any other element inside the modal, the focus shoulds move to this other element but since the datepicker is closing, the focus will move again to "#my_button_picker" instead to remains on the element which you actually clicked. In other words the focus is trigger by the afterRemove event few moment later after you cliked on the other element (focus should stay on this element), this is a wrong behaviour for the UX. Why don't you pass the event parameter to the funcion? It will solve the issue. For example at this link, they give a solution to recognize if it is used the keyboard or the mouse https://stackoverflow.com/questions/7394796/jquery-click-event-how-to-tell-if-mouse-was-clicked-or-enter-key-was-pressed

Unfortunally I can't use this solution since I should edit the datapicker library or 4x.js. I think that in your code there is already something implemented because actually the ESC key or the mouse click outside the picker already both call the picker.remove() method to close the picker. So you should just pass the event as parameter to the remove() method and in cascade also to the afterRemove() method.

accdc commented 11 months ago

Hi, I've corrected both issues in the latest update to 4X version 2023.7.14 + Datepicker.js version 4.6.

Using the latest code, the double opening issue has been confirmed to work correctly using Chrome, Firefox, and Edge on the test page at: https://whatsock.com/Templates/Datepickers/Basic/test.htm

Regarding setting focus back to the button after the calendar is closed, I understand what you wish to do, however setting focus to the associated input field is not actually an accessibility issue because it allows non-sighted screen reader users the opportunity to verify the correct date has been entered into the field before moving on within the form. This functionality is preferable to most visually impaired users. As a totally blind screen reader user myself, I find this is very helpful when entering dates into important fields such as travel and banking websites for example.

If there is a pressing need to change this behavior though, you can do so now using the returnFocusTo property by declaring this within the Datepicker setup config.

E.G.

// Optionally specify a DOM node to return focus to after the datepicker calendar is removed.
// By default, this will set focus back to the associated input field where the date is saved, but it can be overridden if needed using the following property.
returnFocusTo: $A.get('elementId'),

Please make sure you update the 4X.js and Datepicker.js files within your own code to verify the fixes within your own environment.

Since there is nothing else I can do for this issue, please open a new one if this or a different issue occurs.

impetoyt commented 11 months ago

I tested on my code after update 4x.js and Datapicker.js and I also tested again at your page (using Chrome, Firefox, and Edge): https://whatsock.com/Templates/Datepickers/Basic/test.htm

Unfortunally the double opening issue is not fixed. I'm still able to open two datepicker by clicking quickly each button piker :(

LaurenceRLewis commented 11 months ago

@impetoyt I am struggling to understand what the problem is.

The primary purpose of the date picker is to allow users to select dates from a calendar representation—not to click the date picker buttons consecutively without interacting with the calendar to set a date. I had to try several times to make both stay open on Windows. On iOS I could open both at the same time easily, if I tapped one button and then the next.

I could not open both at the same time on any device or OS when using the date pickers to select a date.

However, to prevent this from happening you could try implementing something similar to the code example below. @accdc will know the exact APEX 4X code to use, but the JavaScript below should explain the ask.

// datepicker instances
const datePicker1 = UniqueCalendarId1; // Replace with the actual datepicker instance DC = $A("UniqueCalendarId");
const datePicker2 = UniqueCalendarId2; // Replace with the actual datepicker instance DC = $A("UniqueCalendarId");

let lastOpenDatePicker; // Holds reference to the last opened datepicker

// Replace click event handlers using the Apex 4X method
document.querySelector('#dateIcon1').addEventListener('click', () => {
    if (lastOpenDatePicker && lastOpenDatePicker !== datePicker1) {
        lastOpenDatePicker.remove();
    }
    datePicker1.render();
    lastOpenDatePicker = datePicker1;
});

document.querySelector('#dateIcon2').addEventListener('click', () => {
    if (lastOpenDatePicker && lastOpenDatePicker !== datePicker2) {
        lastOpenDatePicker.remove();
    }
    datePicker2.render();
    lastOpenDatePicker = datePicker2;
});

All the best Laurence Lewis Accessibility Senior Technical Specialist - Telstra

accdc commented 11 months ago

Hi, in the latest update just pushed I attempted once again to prevent this issue from occurring. Hopefully it will do the trick, but if not, Laurence is right in that the vast majority of users who interact with these controls will simply wish to select a date and will likely never encounter this behavior.

impetoyt commented 11 months ago

@LaurenceRLewis it's to improve the UX. You can also decide to increase the duration of the picker animation and in this way it will be more easy have this issue. To give you a exemple and think about the link of this test page: https://whatsock.com/Templates/Datepickers/Basic/test.htm

In this test page you will not have this issue if the user are not click very fast. But image that the two buttons with the related input field are in two different rows (like in a table). The user click the button in the second row and the picker will open with a X seconds animation but if the user was in truth thinking to click the first button in the first row than he can do it very quickly click and the result is to have two datepicker openend and the second picker overlap the first one! This is really a bad thing for the UX. The user can also act in this way: He already choose a date in the second row or for any reason the date is already filled, he click again to the second picker for any reason but since he don't want to choose another date he will just click on the button in the first row to choose the other date and so again the second picker will overlap the first one.

So it's not a problem in which the user want to click quickly one button picker and than the other one just to get pleasure in doing it. The issue can really happen when you have two buttons picker displayed in two differents row but they are one above the other.

accdc commented 11 months ago

Thank you for explaining, I get that and agree it would be good to have fixed.

The latest code has a property within 4X.js ($A.isAnimating) that is set to true every time one of the rendering animations is running, then toggles to false when the animation is done.

This is checked against within the latest code within Datepicker.js so that the triggering event ("opendatepicker") will only activate if this property is false and never when it is true.

Please check the latest code against your implementation and let me know if this is now fixed.

impetoyt commented 11 months ago

Thank you for explaining, I get that and agree it would be good to have fixed.

The latest code has a property within 4X.js ($A.isAnimating) that is set to true every time one of the rendering animations is running, then toggles to false when the animation is done.

This is checked against within the latest code within Datepicker.js so that the triggering event ("opendatepicker") will only activate if this property is false and never when it is true.

Please check the latest code against your implementation and let me know if this is now fixed.

I upload again my code with 4x.js and Datapicker.js leatest version: https://file.io/QhSUWfThynfi

Unfortunally nothing change, I'm still able to open two picker at the same time (chrome, firefox). In my code I also put the two buttons in two rows so it's possibile to see that one picker overlap the other one.

accdc commented 11 months ago

Hi, I was able to reproduce the issue using your example code, so I now at least have a starting point to figure out what the underlying problem is. It's a strange one, so I'll need to work on this later in the week. I'll get back to you when I learn more. I appreciate your patience.

impetoyt commented 11 months ago

Thanks to you for the patience, I really appreciate it. If there is something that I can do to help you let me know.

accdc commented 11 months ago

Hi, it took me a while to track it down but the fix was surprisingly simple. I uncommented out autoCloseSameWidget within Datepicker.js and removed the line "dc.cancel = $A.isAnimating". which was conflicting with the autoCloseSameWidget functionality.

I just pushed the code live and all you should have to do is update Datepicker.js to version 4.10.

Please let me know if that fixes your issue.

impetoyt commented 11 months ago

Hi, I update Datepicker.js to version 4.10 and I confirm that now the issue is solved but unfortunately with this update it was introduced another issue. If the duration is about 1 second or more, the user could click the second button picker during the animation of the first picker. Since the animation of the first picker is still in progress, nothing will happen and the second picker will not display, The user need to wait 1 second or more until click the second button picker to open the second picker (also in this case the UX is really bad). In case you are using an animation with a duration of about 250ms the user will not really notice that because the animation is so fast that when he click the second button picker the animation of the first picker probabily is aleady completed.

accdc commented 11 months ago

Hi, to confirm, are you saying that the second calendar should cancel the animation of the first calendar if the user clicks on the second datepicker while the first datepicker is currently running an animation?

impetoyt commented 11 months ago

No. If you try to click the second button, you will see that the second picker will not appear since the first animation picker (opening animation) is still running. For exampe if you use 10 seconds as opening animation duration then you have to wait 10 seconds to click the second button picker otherwise if you try to click it before 10 seconds nothing happen, the second picker will not appear. It's like the second button picker has no action.

If my explanation is still not clear l will record a video.

How I think shoulds work propertly: If I click a button picker then the picker is opening with animation: 1) if I click outside, in any place of the website, the picker should close also during his opening animation 2) if I click on a second button picker while the first animation opening picker is still running then the first one should close also during his animation and the second one should appear.

accdc commented 11 months ago

I understand. Unfortunately this is not something I can fix, though there are techniques you can use to reduce any user impact.

This cannot be fixed due to the lifecycle processes that are automatically handled within every DC object. You can read more about these by viewing the help documentation within the repo folder at: Help/DC API/DC Object Configuration/Lifecycle Methods and Scripting

Since each datepicker instance is an instantiated DC object, it will run all of these processes every time you render a calendar. The only times you can cancel the process from rendering after you activate render(), is by setting dc.cancel to true within either the beforeRender or duringRender method calls.

By the time the animation is visible on the screen, it is too late to cancel part way through because it has already passed beyond the duringRender lyfecycle method and is performing the final processes for animation and DOM manipulation.

It is also impossible for the second calendar DC object to cancel the opening of the first calendar DC object part way through since both instances are autonomous processes.

The only options in this case are to either disable animation, or shorten the animation effect duration so that it will not run for very long.

For example, if you set the duration of the animation for 500 or 750 millisecond's, it is a reasonable timeframe for a user to realize if they have opened the wrong calendar and then open a different one instead.

impetoyt commented 11 months ago

I understand your point and I'm agree with you. My concern is about if I was able to explain the issue that occur only during the opening animation: Actually, with this update, if it is still running a picker open animation (fade-in) then the user is not able to open any other picker by clicking the respective button picker, that it is wrong for the UX. In other words when the user clicks the second button picker, nothing happens. The second picker does not appear since the first picker animation is still running. Try to imaging what happen if you choose a animation of about 2-3 seconds or more, the user will clicks multiple time the second button picker and still nothing happen, the second picker does not appear.

accdc commented 11 months ago

I've been looking into this, and though it is impossible for me to halt the rendering process that is mostly complete, I can program all previous calendars to close after they finish animation.

This is the only way I can do it where the currently clicked datepicker will always open imediatly.

This means though, the prior calendar will have to complete rendering while the latest calendar is opening, after which the prior calendar will automatically close. This is the only way this can be done. There will be some overlap until the prior calendar finishes closing however.

If you think this is better than waiting a second or 2 for one calendar to finish opening before opening another, I can give this a try.

To be honest though, I don't see this as a high priority issue since anyone using the calendar will see it opening and know they can close it after it finishes.

impetoyt commented 11 months ago

This means though, the prior calendar will have to complete rendering while the latest calendar is opening, after which the prior calendar will automatically close. This is the only way this can be done. There will be some overlap until the prior calendar finishes closing however.

In my opinion what you said should be the right UX behavior. Actually it doesn't happen because the latest calendar doesn't appear when the user clicks on the related button calendar during the prior calendar opening animation. From the user point of view it's like clicking the calendar button and nothing happen, so he will thinks:"Why the second calendar isn't opening?" and for sure he clicks again and again but he doesn't know that he have to wait the end of the prior calendar opening animation to open the second one. So for example if the user clicks 2, 3 or more different calendars buttons the right behavior should be that just the latest calendar will remain displayed after his opening anomation and all the others should automatically closing with their closing animation after they completed their opening animation.

If it happens the overlaping of a opening animation with a closing animation, that isn't a issue in my opinion.

accdc commented 11 months ago

4x_datepicker_test2.zip

Please test the attached files and let me know if this is what you are requesting.

impetoyt commented 11 months ago

I tested your code and in this case the issue that I described above is gone. Thank you for your time and patience.