Why isn't NVDA reading my custom error alert consistently? - javascript

I've set up a custom html page template for a client's azure b2c login page and the inline errors are getting read back as-expected (can provide additional details about those upon request if it'd be beneficial), but I'm a bit stumped as to why the page-level errors aren't getting read back as well.
The following are the relevant snippets of html from the template that get rendered during the initial page load:
<!-- B2C-Generated Form Element (all relevant html changes happen within this element) -->
<form id="localAccountForm" action="JavaScript:void(0);" class="localAccount" aria-label="Sign in to Your Account">
<!--B2C-Generated Error Container (prior to error / this html is generated/injected by Azure B2C when the page is loaded) -->
<div class="error pageLevel" aria-hidden="true" role="alert" style="display: none;">
<p class="remove"></p>
</div>
<!-- Custom Error Container (prior to error / this html gets added to the template by jQuery once the window is ready) -->
<div role="alert" style="" class="errorContainer no-error" aria-hidden="false">
<p id="pageError" role="" aria-hidden="true" style="" class="remove"></p>
</div>
After the initial content loads (both the content from Azure B2C, as well as the modifications from jQuery), the following logic gets run to ensure all of the error elements on the page are set up properly (or at least that's the intent) & eliminate some differences that may otherwise cause some problems:
initializeAlerts([
'#pageError',
'label[for="signInName"] + .error > p',
'.password-label + .error > p'
]);
// Below functions are loaded & run from a separate .js file:
function initializeAlerts(errorSelectors) {
errorSelectors.forEach((s) => {
// Store the current error & whether or not inline styles were attempting to hide it
var errorMsg = $(s).html();
var errorStyle = `${$(s).attr('style')} ${$(s).parent().attr('style')}`;
// Ensure the parent element has the role="alert" attribute (rather than the error itself)
$(s).attr('role', '');
$(s).parent().attr('role', 'alert');
// Default both the error element & it's parent to be visible
$(s).removeClass('remove').attr('aria-hidden', 'false').attr('style','');
$(s).parent().addClass('errorContainer').addClass('no-error').removeClass('remove').attr('aria-hidden', 'false').attr('style','');
// If an error message is NOT present, add a class to the parent for styling purposes
if (errorMsg) {
$(s).parent().removeClass('no-error');
}
// If the error was supposed to be hidden dynamically via Azure B2C (i.e. it's a standard error that's prepopulated & simply gets shown/hidden), ensure the error itself is hidden (NOT the parent)
if (errorStyle.indexOf('none') > -1) {
$(s).addClass('remove').attr('aria-hidden', 'true');
$(s).parent().addClass('no-error');
}
// If/when the error gets updated, ensure it gets displayed/read
callbackOnDOMUpdate([s], function() {
var currentError = $(s).html();
if (currentError) {
$(s).removeClass('remove').attr('aria-hidden', 'false').attr('style','');
$(s).parent().removeClass('no-error');
} else {
$(s).addClass('remove').attr('aria-hidden', 'true').attr('style','');
$(s).parent().addClass('no-error');
}
});
});
}
function callbackOnDOMUpdate(selectors, callback) {
selectors.forEach(selector => {
$(function() {
var target = document.querySelector(selector);
var observer = new MutationObserver(function(mutations, observer) {
callback();
});
observer.observe(target, {
subtree: true,
childList : true,
characterData : true
});
});
});
}
After all of that runs, if the user enters an incorrect user/pass combination, an error is returned to the page (via the "B2C-Generated Error Container") which looks something like this (depending on the specific error returned):
<!--B2C-Generated Error Container within form (after receiving error) -->
<div class="error pageLevel" aria-hidden="false" role="alert" style="display: block;">
<p class="remove">Unable to validate the information provided.</p>
</div>
Though, the client wants some verbiage/styling changes made, so rather than showing that message as-is, a "remove" class is added to it (which is associated with a display: none !important css rule) and my custom error container is updated to show something similar to the message below (again, actual message may vary depending on the message returned from b2c):
<!-- Custom Error Container (after receiving error) -->
<div role="alert" style="display: block">
<p id="pageError">
<strong>We're sorry, the information entered does not match what we have on file.</strong>
Please try re-entering the information below or you can recover your login info.
</p>
</div>
Unfortunately, while this DOES appear to get read as expected every once in a while, most of the time, the only message I hear read is "Sign in to Your Account form continue button" (which seems to be a combination of the aria label for the form element the changes are nested within, the form element name itself & the name of the last button the user clicks prior to seeing the page update).
I've tried to ensure that the error itself is nested as a child element within a parent element that:
Is always visible (from both a css & aria perspective)
Has role='alert'
(and simply show/hide the error itself via the addition/removal of the "remove" class)
... but I must be missing something somewhere, so any help anyone can offer would be appreciated.

So... as it turns out, all of the above code was fine as-is (with the exception of the fact that #1 above wasn't entirely true). The issue turned out to be related to the following...
When the user clicks the "sign in" button, I used $('body').removeClass('loaded'); which in turn would cause:
A loading animation to be displayed
The element containing all page content to be set to display: none;
When an error was detected, I would then similarly fire off the following commands (in this order):
$('body').addClass('loaded'); (thus making everything visible again)
the logic to read the system error that was returned & populate the custom error area of the code accordingly
... so, I tried removing all jquery related to the loading animation to see if perhaps the problem was related to that & sure enough, the error was read as expected.
That being said, I believe the real issue at play here was that the error/alert update was getting completed before all visibility-related changes had kicked through from the css side & therefore NVDA wasn't reading the alert because, from NVDA's perspective, the error was still hidden.
... hopefully the documentation of this experience helps someone else down the road!

Related

How to dynamically update google recaptcha sitekey?

I have a plain js + jQuery app with a button protected by google recaptcha, everything works as expected, but I'm failing to update the sitekey on the fly. The reason I want this is that I have a couple of environments (staging, test, production etc.) and I'd like to have a separate sitekey for specific envs (in order to separate the test stats from data from real users).
I'm able to change the attribute on my recaptcha element, but it looks like the attributes are taken by the script on initialising the whole thing, how can I refresh/reset the widget to accept the new sitekey?
I've been experimenting with reset and render methods, but to no effect so far.
<div
id="google-recaptcha"
class="g-recaptcha"
data-sitekey="this-will-be-replaced-anyways"
data-callback="onSubmit"
data-size="invisible"
></div>
if (grecaptcha) {
$('#google-recaptcha').attr({
'data-sitekey': 'my-real-sitekey'
});
}
I think you are looking for Explicitly render the reCAPTCHA widget
1.Specify your onload callback function. This function will be called by JavaScript resource(see the next step)
<script>
function onloadCallback() {
grecaptcha.render('myCaptcha1', {
'sitekey': 'sitekey value', // required
'theme': 'light', // optional
'callback': 'onloadCallback' // optional
});
}
</script>
2.Insert the JavaScript resource, setting the onload parameter to the name of your onload callback function and the render parameter to explicit.
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit"></script>
3.Define the target HTML control that is supposed to be a captcha on your page.
<div id="myCaptcha1"></div>
I hope it helps you as helped me, good luck

Vue.js: How do I focus to an input just unhidden

This must be something really basic, but I can't get it.
I have this piece of code in my <template>:
<div v-if="this.editable">
<input type="text" ref="nota" :id="notaid" v-model="nombre" v-on:keyup.enter="enviar" v-on:blur="enviar" class="form-control">
</div>
<div v-else>
<p #click="makeEditable">{{ nombre }}</p>
</div>
So, when the page loads, editable=false so it displays a text. When you click in the text there's a function that will make editable true, so it displays the input. This works fine. Now, my problem is, how do I focus on the input as soon as editable changes to true?
I have read some articles: here, here, and some others that says I could be focusing on the element with some code as simple as:
this.$refs.nota.focus()
So makeEditable code is:
methods: {
makeEditable() {
this.editable = true;
this.$refs.nota.focus()
},
...
Problem is, I get this error:
app.js:38487 [Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'focus' of undefined"
I'm pretty sure this is because I'm trying to focus to an element that hasn't been created yet, because if I make the same on an input element that is always displayed, it works perfectly.
So which is the right way?
You can try Vue.nextTick(callback) as follow :
methods: {
makeEditable() {
this.editable = true;
Vue.nextTick(() =>
this.$refs.nota.focus()
)
},
...
It tells Vue to wait until the changes are made and then call the callback.
See the documentation for more.

CollectionFS images not fully loading

I've just come across a werid problem, It seems my images are sometimes only loading slightly. Sometimes when I refresh the page manually they load fully but a lot of the time this happens - http://i.gyazo.com/a7050e430c79aa31ba557fc8271f1502.png
Not really sure why this is happening, I'm using collectionFS with cfs-ejson-file and cfs-s3 to store the images.
Here some example code ( main profile image )
Template code -
<div class="profile-avatar" style="height: 261px; margin-bottom: 30px;">
{{#if avatar }}
{{#if avatarReady }}
<img width="261px" height="261px" src="{{avatar.url}}" class="profile-avatar-img thumbnail" alt="Profile Image">
{{else}}
<div class="activity-spinner">
{{>spinner}}
</div>
{{/if}}
{{else}}
<img data-src="holder.js/100%x100%/text:No Profile Photo">
{{/if}}
</div> <!-- /.profile-avatar -->
Js code -
Template.profileLeft.helpers({
avatar: function(){
if(this.profile && this.profile.images)
return Images.findOne(this.profile.images[0]._id);
},
avatarReady: function(){
if(this.profile && this.profile.images){
var image = Images.findOne(this.profile.images[0]._id);
return image && image.isUploaded && image.hasStored("images");
}
},
});
I was seeing a similar problem to what you described. When I would upload an image and immediately display it, initially only a portion of it shows. I'll go into what I found and hopefully it helps.
I found that this was only happening when I would use a custom transformWrite: function in CollectionFS. What I believe is happening is there is a race condition causing the image.isUploaded() and image.hasStored() functions to return true before GraphicsMagick has finished writing the entire file. The partially processed image is then requested and cached by the web server. Like you said, sometimes when you refresh, the image will be then fully loaded.
As an example:
Your avatar URL that shows a partially processed image:
http://yourdomain.com/cfs/files/images/Bun5qheJDeaZDp5Mf/DSC_5498.JPG?&store=images
Add a fake query param to get around the cache, and the full image should be displayed:
http://yourdomain.com/cfs/files/images/Bun5qheJDeaZDp5Mf/DSC_5498.JPG?&store=images&bust=cache
What I ended up doing was setting an additional value on the model that was triggered whenever the image was finished processing. Below is an example of a gm function to process an image. Once the function is finished, another function is called "finishedProcessing" that sets the value on the model.
Sample Code:
gm(readStream, fileObj.name())
.resize(1000).quality(75)
.stream(function(err, stdout, stderr) {
if (err) {
return err;
}
stdout.pipe(writeStream);
// Update value when processing is finished
stdout.on('end', function(){
finishedProcessing(fileObj._id);
});
});
You can then grab the value of image.finishedProcessing before displaying the image:
avatarReady: function(){
if(this.profile && this.profile.images){
var image = Images.findOne(this.profile.images[0]._id);
return image && image.isUploaded() && image.hasStored("images") && image.finishedProcessing;
}
}
This seemed to work pretty well for me. Even if you're not using GraphicsMagick, there may be a similar condition that's happening when the file is being processed or saved.
Also, on a side note, I believe you need to call "image.isUploaded()" instead of "image.isUploaded" in your helper function to get the proper returned value.
UPDATE: On another another side note, I found that after refactoring if you do not have your collection settings set to Allow Update, then only the first chunk of the image will be saved and it can also cause the issue you are seeing.
Images.allow({
update: function(userId, file, fields, modifier) {
// This is to check whether or not CollectionFS is trying
// to update the Image
var validCFSUpdate = _.intersection(fields, ['chunkSize', 'chunkCount', 'chunkSize']).length > 0
// Only allow logged in users and CollectionFS to update the Image
return userId && validCFSUpdate;
}
});

Ruby on Rails: render javascript only in defined page with turbolinks enabled

I have javascript embedded to a view in Rails that uses Turbolinks, e.g:
ViewX.html.haml:
.blah
Blah
:javascript
$(function() {
$(".blah").offset().top;
});
Everything works fine when I load ViewX. But then when I navigate to View Y I get the following error in console:
Uncaught TypeError: Cannot read property 'top' of undefined
And that error persists as I navigate to other views. It goes away when doing a hard refresh, but returns next time I render ViewX.
My question is how do I have some JS snippets render only for a particular view and not pollute to the rest of the app?
EDIT
Ok, I figured out a way, which is somewhat of a hack, but a contained hack. Change ViewX.html.haml to:
ViewX.html.haml:
.blah
Blah
%script{:type => "text/javascript", 'data-turbolinks-eval' => 'false'}
:plain
$(function() {
$(".blah").offset().top;
});
A simple way is to have each view have a different css class on the body tag:
var bodyClass = $('body').attr('class');
switch(bodyClass) {
case 'home-page':
// do stuff
break;
case 'about-page':
// do stuff
break;
}
If there is a lot of code for each page, you can use the a module pattern and make each module a separate .js file.
case 'home-page'
MyModule.initialize(); // set up form validation or whatever you need
break;
Another option (if you can't add a CSS class to the body tag) is to use a hidden <input> tag that holds the name of the view and use that to switch.
var switchJS = $('input[type=hidden]').attr('data-view');
/* i.e. <input type="hidden" data-view="my-home-page" /> */
switch(switchJS) { ...

PrimeFaces p:blockUI blocking a certain component dynamically (on JSF EL condition)?

How do you make PrimeFaces p:blockUI block a certain component dynamically, that is on EL condition?
USE CASE:
The condition at hand is basically a mode that the user can be in on a page: if there are any exceeded collisions and they are currently shown at the users request then block the navigation tree (show exceeded collisions mode, nav tree blocked), otherwise we are in regular view and the navigation tree should be unblocked (show regular collisions mode, nav tree unblocked).
The problem currently is, that when in "exceeded mode", when a status change dialog changes the status to non-exceeded, the page renders/updates itself correctly back to regular mode, but the shadow on the navigation tree is still there. Since we're in regular mode now, I need some way to unblock if there are no more exceeded collisions.
Understood? :-)
OK, here's the bean property first:
/*
* The logic of this method ensures that after status update the
* mode is automatically put back into regular view if no followup
* date exceeded collisions exist.
*/
public boolean isFollowupExceededCollisionsShown()
{
return getFollowupExceededCount() > 0 ? this.followupExceededCollisionsShown : false;
}
1st try:
Looking at the VDL docs http://www.primefaces.org/docs/vdl/3.4/primefaces-p/blockUI.html revealed
some blocked attribute.
<p:blockUI widgetVar="explorerBlocker"
block=":explorer-form"
blocked="#{collisionManager.followupExceededCollisionsShown}" />
The above however does absolutely nothing.
2nd try:
Looking at the showcase http://www.primefaces.org/showcase/ui/blockUI.jsf, the client API seemed to be more adequate.
The idea was to call either show() or hide() depending on a ValueExpression when pressing OK on the status change dialog:
<p:dialog id="state-change-dialog"
widgetVar="stateChangeDialog"
modal="true"
appendToBody="true">
<h:form>
<ui:include src="/view/collisionmgmt/collisionStatusChangeGrid.xhtml" />
<h:panelGroup layout="block" styleClass="center-button-panel">
<p:commandButton id="save-button"
icon="ui-icon ui-icon-disk"
value="OK"
action="#{collisionManager.performStatusChange}"
process="#form"
update=":explorer-form:tree :collision-form:period-grid :collision-form:list :collision-form:growl"
oncomplete="stateChangeDialog.hide();" />
<p:commandButton icon="ui-icon ui-icon-close"
value="Cancel"
update=":collision-form:list"
onclick="stateChangeDialog.hide();"
immediate="true" />
</h:panelGroup>
</h:form>
</p:dialog>
The idea was to somehow extend the OK button's oncomplete="" with a call to explorerBlocker.show(); or explorerBlocker.hide(); depending on the new value of the EL #{collisionManager.followupExceededCollisionsShown}.
There are two basic variants I tried:
oncomplete="stateChangeDialog.hide(); #{collisionManager.followupExceededCollisionsShown ? 'explorerBlocker.show();' : 'explorerBlocker.hide();' }"
oncomplete="stateChangeDialog.hide(); if ( #{collisionManager.followupExceededCollisionsShown} ){ explorerBlocker.show(); } else { explorerBlocker.hide(); }"
The status change dialog is closed all the time, but the logic above isn't kicking in.
I must be making something inherently wrong here. I'm suspecting the OK button's oncomplete EL expression doesn't get re-evaluated when being clicked. Adding an #this to the update list doesn't change anything.
update="#this :explorer-form:tree :collision-form:period-grid :collision-form:list :collision-form:growl"
How is my problem best solved, where "best" is JSF-only first, then PrimeFaces-specific (and ideally, where this is documented!).
Is it possible to use the blockUI blocked="#{?}" attribute with EL?
Thanks
One way is to call hide() and show() method of BlockUI from Managed Bean.
You can do that by using RequestContext:
RequestContext.getCurrentInstance().execute("widgetVar.show()");
Another is you can pass the variable to JavaScript function and then let the Javascript function take care of that for you.
onClick="func(#{elvariable})"
<script type="text/javascript">
function func(value)
{
if(value==something){
widgetVar.show();
}else{
widgetVar.hide();
}
}
</script>

Categories

Resources