NVDA ignores angular *ngIf blocks with role="alert" - angularjs

My aim is to give the user information about errors in inputs. I am able to do that without angular with this example. I also managed to recreate the following example with using "innerHTML" attribute but it also didn't work with NVDA, in short it just appends a node to body when error occures
<!DOCTYPE html>
<html><head>
<meta http-equiv="content-type" content="text/html; charset=windows-1252">
<title>Contact form (WAI-ARIA)</title>
<script type="text/javascript">
function removeOldAlert()
{
var oldAlert = document.getElementById("alert");
if (oldAlert)
document.getElementById("error").removeChild(oldAlert);
}
function addAlert(aMsg)
{
removeOldAlert();
var newAlert = document.createElement("div");
newAlert.setAttribute("role", "alert");
newAlert.setAttribute("id", "alert");
var msg = document.createTextNode(aMsg);
newAlert.appendChild(msg);
document.getElementById("error").appendChild(newAlert);
}
function checkEntryValidity(aID, aSearchTerm, aMsg)
{
var elem = document.getElementById(aID);
var invalid = (elem.value.indexOf(aSearchTerm) < 0);
if (invalid) {
elem.setAttribute("aria-invalid", "true");
addAlert(aMsg);
} else {
elem.setAttribute("aria-invalid", "false");
removeOldAlert();
}
}
</script>
</head>
<body>
<form method="post" action="post.php">
<fieldset><legend>Please enter your contact details</legend>
<label for="name">Your name (required):</label>
<input name="name" id="name" aria-required="true" onblur="checkEntryValidity('name', ' ', 'Invalid name entered!');" aria-invalid="true" value=""><br>
<label for="email">E-Mail address (required):</label>
<input name="email" id="email" aria-required="true" onblur="checkEntryValidity('email', '#', 'Invalid e-mail address');" aria-invalid="true" value=""><br>
<label for="website">Website (optional):</label>
<input name="website" id="website" value="">
</fieldset>
<label for="message">Please enter your message (required):</label><br>
<textarea name="message" id="message" rows="5" cols="80" aria-required="true"></textarea><br>
<input name="submit" value="Send message" type="submit">
<input name="reset" value="Reset form" type="reset">
</form>
<div id="error"></div>
</body></html>
(alert Invalid name entered!)
but NVDA ignores this type of alert message
<!-- widget with model binding here -->
<div role="alert" class="validation" *ngIf="model.hasErrors()">
<div *ngFor="let message of model.errors">{{message}}</div>
</div>
I don't know why NVDA doesn't register that content change.
Angular2
I am using Windows 10, Firefox 50.1.0 and NVDA 2016.4.

It seems that adding aria-live like this
<div aria-live="assertive">
<div role="alert" class="validation" *ngIf="model.hasErrors()">
<div *ngFor="let message of model.errors">{{message}}</div>
</div>
</div>
allows the reader to register changes.

Related

How can I change errorElem?

I am trying to change how the error element is displayed. I am using the Bulma UI framework, and want the error message to be displayed in the following. The structure should look like the following. It should also remove the "li" tags around the error message:
<article class='message'>
<div class="message-body content">
<p> ERROR DISPLAYS HERE </p>
</div>
</article>
My attempt to fix it was as follows:
const parsleyConfig = {
errorElem: '<div class="message-body"></div>',
errorsWrapper: '<article class="message"></article>'
};
$('#myForm').parsley(parsleyConfig);
However, only the errorsWrapper seems to be working, the errorElem does not apply to the error message at all.
As Marc-André Lafortune stated, you're using Parsley.js v1.x.x, so errorsWrapper and errorElem should be defined in errors configuration. Here is a simple implementation of Parsley.js v1 based on your code:
$(function() {
const parsleyConfig = {
errors: {
container: function (elem) {
return elem.next('article.message');
},
errorsWrapper: '<div class="message-body content"></div>',
errorElem: '<p></p>'
}
};
$('#myForm').parsley(parsleyConfig);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/parsley.js/1.2.3/parsley.min.js"></script>
<form action="" method="" id="myForm">
<input type="text" name="field" parsley-required="true">
<article class="message"></article>
<input type="submit" value="Submit Form">
</form>
And here is a Parsley.js v2 implementation:
$(function() {
const parsleyConfig = {
errorsContainer: function (elem) {
return elem.$element.next('article.message');
},
errorsWrapper: '<div class="message-body content"></div>',
errorTemplate: '<p></p>'
};
$('#myForm').parsley(parsleyConfig);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/parsley.js/2.9.1/parsley.js"></script>
<form action="" method="" id="myForm">
<input type="text" name="field" required>
<article class="message"></article>
<input type="submit" value="Submit Form">
</form>
What version of Parsley are you using? errorElem is Parsley 1.x...
Use errorsContainer, errorsWrapper, and errorTemplate.

How to store values from a form to local storage in AngularJS?

I have some code for adding the values from a form to the local storage,
but when I tried the code, it is not running. I would like to store the form data in local storage by clicking the button. I am adding my sample code below:
App.controller('KeyController', function($scope,$localStorage) {
/*$scope.info = 'Welcome to Test';*/
/*console.log(" Key controller is working ");*/
$scope.api_url;
$scope.api_token;
$scope.submit=function(){
localStorage.setItem('api_url','--------');
localStorage.setItem('api_token','--------');
console.log(api_url);
console.log(api_token)
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="container">
<form role = "form" id="uriForm" name="authfrom">
<div class = "form-group">
<label for = "url">Enter the url to authenticate:</label>
<input type = "url" class = "form-control" placeholder = "Enter the URL" ng-model="api_url" required="required">
</div>
<div class = "form-group">
<label for = "Key">Enter the key here:</label>
<textarea class = "form-control" rows="5" placeholder = "Enter the Key" ng-model="api_token" required="required"></textarea>
</div>
<button class = "btn btn-default" ng-click="submit()">Explore!!</button>
<p class="warning">{{failed}}</p>
</form>
</div>
You haven't add your app.js file in html.
You didn't add your ng-app and ng-controller in html.
You were adding $localStorage in function which is not necessary.
You were using wrong syntax for localStorage.setItem()
Now I've edited your code and made this.
Here is index.html page
<!DOCTYPE html>
<html ng-app="app">
<head>
<title>App</title>
<script src="angular/angular.min.js"></script>
<script type="text/javascript" src="app.js"></script>
</head>
<body ng-controller="KeyController">
<div class="container">
<form>
<div class = "form-group">
<label for = "url">Enter the url to authenticate:</label>
<input type = "text" class = "form-control" placeholder = "Enter the URL" ng-model="api_url" required="required">
</div>
<div class = "form-group">
<label for = "Key">Enter the key here:</label>
<textarea class = "form-control" rows="5" placeholder = "Enter the Key" ng-model="api_token" required="required"></textarea>
</div>
<button class = "btn btn-default" ng-click="submit()">Explore!!</button>
<p class="warning">{{failed}}</p>
</form>
</div>
</body>
</html>
Here is your app.js file:
angular.module('app', [])
.controller('KeyController', function($scope) {
$scope.api_url;
$scope.api_token;
$scope.submit=function(){
localStorage.setItem('api_url', JSON.stringify($scope.api_url));
localStorage.setItem('api_token', JSON.stringify($scope.api_token));
console.log($scope.api_url);
console.log($scope.api_token)
}
});
You can copy paste this and check your self. First replace the reference of angular.min.js file.
Please go through the below code which has been corrected to bootstrap the angular module and set controller using ng-app and ng-controller.
Code snippets posted on stackoverflow cannot access localstorage for the reason mentioned here. So you'll need to run this code on your local development environment.
angular
.module('MyApp', []);
angular
.module('MyApp')
.controller('KeyController', [
'$scope',
function($scope) {
/*$scope.info = 'Welcome to Test';*/
/*console.log(" Key controller is working ");*/
$scope.api_url;
$scope.api_token;
$scope.savedApiUrl = '';
$scope.savedApiToken = '';
$scope.submit = function() {
localStorage.setItem('api_url', $scope.api_url);
localStorage.setItem('api_token', $scope.api_token);
var savedApiUrl = localStorage.getItem('api_url');
var savedApiToken = localStorage.getItem('api_token');
$scope.savedApiUrl = savedApiUrl;
$scope.savedApiToken = savedApiToken;
console.log($scope.savedApiUrl);
console.log($scope.savedApiToken)
}
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="container" ng-app="MyApp" ng-controller="KeyController">
<form role="form" id="uriForm" name="authfrom">
<div class="form-group">
<label for="url">Enter the url to authenticate:</label>
<input type="url" class="form-control" placeholder="Enter the URL" ng-model="api_url" required="required">
</div>
<div class="form-group">
<label for="Key">Enter the key here:</label>
<textarea class="form-control" rows="5" placeholder="Enter the Key" ng-model="api_token" required="required"></textarea>
</div>
<button class="btn btn-default" ng-click="submit()">Explore!!</button>
<p class="warning">{{failed}}</p>
</form>
<p>Saved values from local storage</p>
<p>API URL: {{savedApiUrl}}</p>
<p>API Token: {{savedApiToken}}</p>
</div>

AngularJS v1.5.5 Reset form not working for invalid input fields

I have the code below (or see the fiddle) and I try to reset form and clear all input fields too for each form.
With AngularJS version 1.2.1 is working fine! but in my app I using version 1.5.5 because I have other libraries for separating the nested forms in <md-tab> tags using material framework who need this version.
The problem is when any field it's invalid then reset does not work as expected and these fields remain unchanged instead to be clear.
There is another way to clearing all fields (of nested form) when I click reset button ?
angular.module("main", [])
.controller("register", function($scope) {
$scope.data = {
A: {},
B: {}
};
$scope.reset = function(form) {
form.$setPristine();
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<body ng-app="main">
<div ng-controller="register" class="form">
<form name="form" novalidate role="form">
<div class="form">
<h1>TAB1 - Form A:</h1>
<ng-form name="A">
A1:
<input type="text" ng-model="data.A.A1" ng-minlength="4" ng-maxlength="15" />A2:
<input type="text" ng-model="data.A.A2" ng-minlength="4" ng-maxlength="15" />
<button type="button" ng-disabled="A.$pristine" ng-click="reset(A); data.A=null;">Reset</button>
<br/>
<strong>A.$pristine =</strong> {{A.$pristine}}
<strong>A.$valid =</strong> {{A.$valid}}
</ng-form>
</div>
<br/>
<br/>
<div class="form">
<h1>TAB2 - Form B:</h1>
<ng-form name="B">
B1:
<input type="text" ng-model="data.B.B1" ng-minlength="4" ng-maxlength="15" />B2
<input type="text" ng-model="data.B.B2" ng-minlength="4" ng-maxlength="15" />
<button type="button" ng-disabled="B.$pristine" ng-click="reset(B); data.B=null;">Reset</button>
<br/>
<strong>B.$pristine =</strong> {{B.$pristine}}
<strong>B.$valid =</strong> {{B.$valid}}
</ng-form>
</div>
</form>
<h1>data:</h1>
<pre>{{data | json}}</pre>
</div>
</body>
It's because ng-model binds to the A and B objects by reference in $scope.data. If you remove the $scope.data.A = null from your ng-click and reset the object without creating a new one, it works:
https://jsfiddle.net/utwf604r/15/
$scope.reset = function (form)
{
// don't change the reference to A
// $scope.data.A = {} wont work!!
angular.extend($scope.data, {A:{A1:'',A2:''}, B:{B1:'',B2:''}});
form.$setPristine();
};

Get rid of pesky borders in ngMessage

In this plunk I have a form with two fields, each validated with ngMessage, where the error message appears when the user tabs out of the field, or the form is submitted.
The issue is that if the message is shown and then hidden (because the problem was fixed) the borders are still shown.
Try tabbing out of a field, then entering a value, you will see only borders in the error message.
How to get rid of these borders?
HTML
<body ng-app="ngMessagesExample" ng-controller="ctl">
<form name="myForm" novalidate ng-submit="submitForm()">
<label>
Enter Aaa:
<input type="text"
name="aaa"
ng-model="aaa"
required ng-blur="aaaBlur()" />
</label>
<div ng-show="showAaa || formSubmitted"
ng-messages="myForm.aaa.$error"
style="color:red;background-color:yellow;border:1px solid brown">
<div ng-message="required">You did not enter a field</div>
</div>
<br/>
<label>
Enter Bbb:
<input type="text"
name="bbb"
ng-model="bbb"
ng-minlength="2"
ng-maxlength="5"
required ng-blur="bbbBlur()" />
</label>
<br/><br/>
<div ng-show="showBbb || formSubmitted" ng-messages="myForm.bbb.$error"
style="color:red;background-color:yellow;border:1px solid brown">
<div ng-message="required">You did not enter a field</div>
</div>
<br/>
<button style="float:left" type="submit">Submit</button>
</form>
</body>
Javascript
var app = angular.module('ngMessagesExample', ['ngMessages']);
app.controller('ctl', function ($scope) {
$scope.formSubmitted = false;
$scope.showAaa = false;
$scope.showBbb = false;
$scope.submitForm = function() {
$scope.formSubmitted = true;
};
$scope.aaaBlur = function() {
$scope.showAaa = true;
};
$scope.bbbBlur = function() {
$scope.showBbb = true;
};
});
It's because you show the div (showAaa=true) but there's no content. Solution? Don't show the div. :)
<div ng-show="!myForm.aaa.$valid && (showAaa || formSubmitted)"
In which field do you have the problem ? Both or only the 2nd one ?
I see that in the 2nd field you fixed a min and max length. If they're not right, you don't have any ng-message to handle them, but your field will be in error, so you'll end up with the red border and no message.
By the way : you can use [formName].[fieldName].$touched instead of using your native onblur.

ng-model from angular-payments is not working

I am trying to add angular-payments in order to integrate stripe.com payment processing to my AngularJS web app.
I duplicated the code from https://github.com/laurihy/angular-payments/blob/master/lib/angular-payments.js into my angular-payments.js file and the code from https://github.com/laurihy/angular-payments/blob/master/example/index.html into localhost:8888/stripe.html and got it to work.
However, when I try to integrate this code to my AngularJS web app, the ng-model is not working.
Here is how I integrated the code. I have a single page web app with app.js, which has the following code:
angular.module('io.config', []).value('io.config', {
'password': 'json/config.password.json',
....
....
});
...
...
angular.module('app.modules', ['app.config']);
var app = angular.module('app', ['ngCookies', 'io', 'ui', 'bs', '$strap',
'app.controllers', 'app.directives', 'app.factories', 'app.filters', 'app.modules', 'app.config']);
So, I added 'angularPayments' to:
var app = angular.module('app', ['ngCookies', 'io', 'ui', 'bs', '$strap',
'app.controllers', 'app.directives', 'app.factories', 'app.filters', 'app.modules', 'app.config', 'angularPayments']);
I also have an app.route.js file, which has the following code:
angular.module('app')
.config(['$routeProvider', function($routeProvider) {
var _view_ = 'view/', _app_ = 'app/';
$routeProvider
.when('/user/invite', {templateUrl:_view_+'app/invite.html'})
I added the following to app.route.js:
.when('/user/buy', {templateUrl:_view_+'app/buy.html'})
I duplicated the code from localhost:8888/stripe.html into app/buy.html, which maps to localhost:8888/#/user/buy
The exception is that I took the following lines out of buy.html and put them into index.html:
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script src="scripts/modules/angular-payments.js"></script>
My index.html has this code:
<!DOCTYPE html>
<html class="no-js" lang="en" data-ng-app="app">
...
<link href="./css/bootstrap.css" rel="stylesheet">
...
<body class="ng-cloak" data-ng-controller="AppCtrl">
...
<div data-ng-view></div>
...
<script src="bower_components/angular-io/src/scripts/ie.js"></script>
...
<script src="bower_components/angular-complete/angular.js"></script>
...
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script src="scripts/modules/angular-payments.js"></script>
...
Both stripe.html and buy.html has the following code:
<form stripe-form="handleStripe">
<div class="span3">
<label for="">Card number</label>
<input type="text" class="input-block-level" ng-model="number" payments-validate="card" payments-format="card" />
</div>
<div class="span1">
<label for="">Expiry</label>
<input type="text" class="input-block-level" ng-model="expiry" payments-validate="expiry" payments-format="expiry" />
</div>
<div class="span3">
<label for="">Name on card </label>
<input type="text" class="input-block-level">
</div>
<div class="span1">
<label for="">CVC</label>
<input type="text" class="input-block-level" ng-model="cvc" payments-validate="cvc" payments-format="cvc" />
</div>
<div class="span4">
<button type="submit" class="btn btn-primary btn-large">Submit</button>
</div>
number:{{number}}, expiry:{{expiry}}, cvc:{{cvc}}
</form>
However, the following code works in stripe.html but not in buy.html, as no values are coming back from the ng-model:
number:{{number}}, expiry:{{expiry}}, cvc:{{cvc}}
Hence, when I submit this form, Stripe returns an error with status = 402 because nothing is passed to Stripe.
I've tried changing ng-model to data-ng-model, but to no avail. I've tried putting the following code into a separate file: scripts/controllers/payment.js:
function PaymentCtrl($scope) {
$scope.handleStripe = function(status, response){
if(response.error) {
// there was an error. Fix it.
} else {
// got stripe token, now charge it or smt
token = response.id
}
}
}
and adding the following to app.route.js:
.when('/user/buy', {templateUrl:_view_+'app/buy.html', controller:PaymentCtrl})
and changing the following in buy.html:
data-ng-controller="MainController"
to:
data-ng-controller="PaymentCtrl"
but still to no avail.
Can anyone tell me how to get the ng-model variables to work in buy.html? Thanks in advance.
I got a tip from Ng-model does not update controller value and I solved the problem with a convoluted solution.
I added the following code to MainController in buy.html:
$scope.payment = {};
$scope.copyvariables = function() {
$scope.number = $scope.payment.number;
$scope.expiry = $scope.payment.expiry;
$scope.cvc = $scope.payment.cvc;
}
Then I added "payment." to the ng-model variables, like such:
<div class="span7">
<label for="">Card number</label>
<input id="number" name="number" type="text" class="input-block-level" data-ng-model="payment.number" payments-validate="card" payments-format="card" required />
</div>
<div class="span4" style="border: 1px solid red">
<label for="">Expiry</label>
<input type="text" class="input-block-level" data-ng-model="payment.expiry" payments-validate="expiry" payments-format="expiry" required />
</div>
<div class="span8">
<label for="">Name on card </label>
<input id="name" name="name" type="text" class="input-block-level" required />
</div>
<div class="span3">
<label for="">CVC</label>
<input type="text" class="input-block-level" data-ng-model="payment.cvc" payments-validate="cvc" payments-format="cvc" required />
</div>
Now the following code works and the values are being passed to Stripe:
number:{{number}}, expiry:{{expiry}}, cvc:{{cvc}}
I still don't know why I need this for buy.html but stripe.html works without this extra code.

Resources