naja-js / naja

Modern AJAX library for Nette Framework
https://naja.js.org
MIT License
109 stars 15 forks source link

UI binding as button for better accessibility #388

Closed lubomirblazekcz closed 6 months ago

lubomirblazekcz commented 6 months ago

Feature Request

Please describe the problem you're trying to solve and explain how you would like to solve it. Try to make a strong point to support your case.

Also please indicate whether you are willing to implement the feature yourself if pointed in the right direction.

https://naja.js.org/#/ui-binding

Currently it's only possible (to my knowledge) to bind .ajax or data-naja on a and form. It would be good for accessibility to be able bind it to button or any other element, eg. button[data-naja] with data-naja-url and aria-label which would be more accessible than a[href] link in most use cases.

jiripudil commented 6 months ago

Hi, thank you for your suggestion. I have to respectfully reject it.

One of Naja's fundamental goals is to provide a progressive enhancement over the standard web experience – it makes things run asynchronously, but if the user has disabled/broken/not-yet-loaded JS, they can still click a link or submit a form to accomplish their intention. From where I see it, the best thing accessibility-wise is to stick to these web primitives, and make everything either a link, or a form, depending on the nature of the operation.

That's what Naja aims to provide and I'd like to keep it that way. That being said, your suggestion seems fairly trivial to implement in user land if you wish so:

const buttons = document.querySelectorAll('button[data-naja]');
buttons.forEach((button) => {
    if (button.form) return; // ignore form-bound buttons, Naja already handles those
    button.addEventListener('click', () => naja.makeRequest('GET', button.dataset.najaUrl));
});
lubomirblazekcz commented 6 months ago

Ok that's understable, but accessibility-wise link should be only used for something that changes navigation or anchor, not parts of content on the current page via js.

It's ok if naja changes url via history and the ajax url reflects the changed content in non-ajax state. But that's just not applicable on most use cases and these are not accessible. Let's say I want to have ajax that changes state of something, eg. removing item from cart, adding to cart, adding to favourites. I don't want to manipulate history or change url for that and this shouldn't be link, accessibility wise it doesn't do navigation and it's confusing for users with screen readers and what happens if you open the link in new window? This should be button, that's why this tag exists.

But it's just a suggestion and it's ultimately up to you 👍

And thanks for the js snippet, but if I'm not wrong this wouldn't trigger interaction event right? I guess that can be added manually too, but it would just be nice if this was possible by default without workarounds.

jiripudil commented 6 months ago

this shouldn't be link, accessibility wise it doesn't do navigation and it's confusing for users with screen readers and what happens if you open the link in new window? This should be button, that's why this tag exists.

Oh, I agree with you completely. But a button on its own is useless without JS. I believe the correct solution in this case is to wrap that button in a form because that covers a lot of the bases for little effort. It's a solution that works perfectly without JS – it can use POST which is the better method for a state-changing and possibly non-idempotent operation, and it can easily be made accessible – and JS can build on top of that to progressively enhance the experience.

And thanks for the js snippet, but if I'm not wrong this wouldn't trigger interaction event right? I guess that can be added manually too, but it would just be nice if this was possible by default without workarounds.

Right. I can imagine adding a helper method to UIHandler that would process custom interactions like this 🤔

lubomirblazekcz commented 6 months ago

Yeah agree form would be best for this, to make it also work without js. But I'm afraid no one does this. At least from my experience everyone always takes the easier route with link. :/

lubomirblazekcz commented 3 months ago

@jiripudil any update on that helper method for UIHandler? Should I perhaps create a new issue for that?

lubomirblazekcz commented 3 months ago

I did the helper like this and after some test I think it works ok 👍

element.querySelectorAll(`button${naja.uiHandler.selector}`).forEach(element => {
    if (element.form) return

    element.addEventListener('click', async event => {
        const options = naja.prepareOptions()

        naja.uiHandler.dispatchEvent(new CustomEvent('interaction', { cancelable: true, detail: { element, originalEvent: event, options } }))

        await naja.makeRequest(element.dataset.najaMethod ?? 'GET', element.dataset.najaUrl, element.dataset.najaData, options)
    })
})
jiripudil commented 3 months ago

any update on that helper method for UIHandler? Should I perhaps create a new issue for that?

Thanks for the nudge, I've come up with #395