Angular clear subform data and reset validation - angularjs

I'm trying to create a subform <div ng-form="vacancyForm"> with Angular.js
There is a type of data that has numerous fields
Headline
Date available
Price
All have required validation on them.
Once I submit that data I'll do what I need with it but I want to reset the subform so that all the fields are not dirty and the form is valid as at the moment clearing out the fields work but all fields are invalid as they are now dirty, but empty marking them as invalid.
An example field
<div class="control-group" ng-class="getErrorClasses(vacancyForm.headline)">
<label class="control-label" for="headline">Headline</label>
<div class="controls">
<input type="text" class="input-xlarge" id="headline" name="headline" required ng-model="new_vacancy.headline">
<span class="help-inline" ng-show="showError(vacancyForm.headline, 'required')">This field is required</span>
</div>
</div>
Here is the function that is called when submitted
$scope.addVacancy = function(){
// save the submitted data
$scope.school.vacancies.push($scope.new_vacancy);
// now clear it out
$scope.new_vacancy = {};
$scope.new_vacancy.date = new Date();
// this clears out all the fields and makes them all invalid
// as they are empty. how to reset the form???
}

Set the name attribute on the subform and then you can $scope.formName.$setPristine(); where formName is what the name attribute is. An element is no longer pristine when the value has been changed.
http://docs.angularjs.org/api/ng.directive:form.FormController#$setPristine
Update
The above answer was solely for 1.2, but in 1.3 angular introduced the concept of a "touched" input. Now when an element is blurred angular will mark the field as touched. Similar to $setPristine, you can set the input back by using $scope.formName.$setUntouched().
https://docs.angularjs.org/api/ng/type/form.FormController#$setUntouched
touched vs pristine: touched means the field has been blurred while pristine means the field's value has been modified. Angular's docs note that "Setting a form controls back to their untouched state is often useful when setting the form back to its pristine state."
Edit
Here is a fiddle demo: https://jsfiddle.net/TheSharpieOne/a30kdtmo/
angular.module('myApp', [])
.controller('myCtrl', myCtrl);
function myCtrl() {
var vm = this;
vm.reset = function() {
vm.myForm.$setPristine();
vm.myForm.$setUntouched();
vm.email = vm.password = '';
}
}
.ng-invalid.ng-touched {
outline: 2px solid blue;
}
.ng-invalid.ng-dirty {
outline: 2px solid red;
}
.ng-invalid.ng-dirty.ng-untouched {
outline: 2px solid green;
}
form,
form div {
padding: 5px 10px;
}
h3,
h4 {
margin-bottom: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl as ctrl">
<form name="ctrl.myForm">
<div>
<label for="email">Email</label>
<input name="myInput" type="email" ng-model="ctrl.email" id="email" required>
</div>
<div>
<label for="password">Password</label>
<input name="myPassword" type="password" minlength="8" ng-model="ctrl.password" id="password" required>
</div>
<div>
<button ng-click="ctrl.reset()" type="button">Reset</button>
</div>
</form>
<div>
<h4>Form Level</h4>
<div>$dirty: {{ctrl.myForm.$dirty}}</div>
<div>$pristine: {{ctrl.myForm.$pristine}}</div>
<h4>Input Level</h4>
<h5>Email Input</h5>
<div>$dirty: {{ctrl.myForm.myInput.$dirty}}</div>
<div>$pristine: {{ctrl.myForm.myInput.$pristine}}</div>
<div>$touched: {{ctrl.myForm.myInput.$touched}}</div>
<h5>Password Input</h5>
<div>$dirty: {{ctrl.myForm.myPassword.$dirty}}</div>
<div>$pristine: {{ctrl.myForm.myPassword.$pristine}}</div>
<div>$touched: {{ctrl.myForm.myPassword.$touched}}</div>
</div>
<div>
<h3>Color outlines for input</h3>
<div title="The form loads this way, it can still be invalid since required fields are empty to start with">untouched, pristine: no outline</div>
<div title="Such as in the middle of typing a valid email for the first time">invalid, untouched, dirty: green outline</div>
<div title="blurred with invalid input">invalid, touched, dirty: red outline</div>
<div title="focued and blurred without typing">invalid, touched: blue outline</div>
</div>
</div>

Related

Why does Cypress not recognise the testid of a react-datepicker element?

Target
To make Cypress recognise and manipulate a DatePicker element.
Problem
I cannot make Cypress recognise the data-testid of a DatePicker element, and so have no way of testing it E2E.
Error
Timed out retrying: Expected to find element: [data-testid="edit_dob"], but never found it.
Attempts
I have tried placing the DatePicker element inside input and div elements to target instead, but they fail as expected for other reasons.
I have also looked through the docs for both DatePicker and Cypress for mention of the other, and for similar questions here.
Code
The code below works as expected, it's just Cypress that seems to be incompatible with the DatePicker input.
function displayEditDetails() {
let listOfForms = [];
for (const detail in details) {
listOfForms.push(
<FormGroup controlId={detail} key={`key_edit_${detail}`}>
<FormLabel>{detail.charAt(0).toUpperCase() + detail.slice(1).replace('_', ' ')}</FormLabel>
<FormControl
data-testid={`edit_${detail}`}
type={detail}
value={details[detail] || ''}
onChange={handleDetailsChange}
/>
</FormGroup>
)
}
listOfForms.push(
<DatePicker
data-testid={'edit_dob'}
selected={dob}
onChange={setDob}
maxDate={new Date()}
/>
);
return(
listOfForms
)
}
Test
it('Displays the edit fields', () => {
cy.get('[data-testid="accountButton"]')
.click();
cy.get('[data-testid="editUserDetailsButton"]')
.click();
cy.get('[data-testid="user_email"]')
.should('not.be.visible');
cy.get('[data-testid="edit_full_name"]')
.should('be.visible');
cy.get('[data-testid="edit_dob"]')
.should('be.visible');
cy.get('[data-testid="edit_email"]')
.should('be.visible')
.should('have.value', 'test#editdetails.com');
})
Once I can target it, I plan to test clearing the current input and typing a new date in. Any help targeting the picker and warnings of further potential pitfalls are appreciated.
Edit: Here's the generated HTML of the form.
<form>
<div class="form-group">
<label class="form-label" for="email">Email</label>
<input data-testid="edit_email" type="email" id="email" class="form-control" value="admin#example.com" style="background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABHklEQVQ4EaVTO26DQBD1ohQWaS2lg9JybZ+AK7hNwx2oIoVf4UPQ0Lj1FdKktevIpel8AKNUkDcWMxpgSaIEaTVv3sx7uztiTdu2s/98DywOw3Dued4Who/M2aIx5lZV1aEsy0+qiwHELyi+Ytl0PQ69SxAxkWIA4RMRTdNsKE59juMcuZd6xIAFeZ6fGCdJ8kY4y7KAuTRNGd7jyEBXsdOPE3a0QGPsniOnnYMO67LgSQN9T41F2QGrQRRFCwyzoIF2qyBuKKbcOgPXdVeY9rMWgNsjf9ccYesJhk3f5dYT1HX9gR0LLQR30TnjkUEcx2uIuS4RnI+aj6sJR0AM8AaumPaM/rRehyWhXqbFAA9kh3/8/NvHxAYGAsZ/il8IalkCLBfNVAAAAABJRU5ErkJggg=="); background-repeat: no-repeat; background-attachment: scroll; background-size: 16px 18px; background-position: 98% 50%; cursor: auto;">
</div>
<div class="form-group">
<label class="form-label" for="full_name">Full name</label>
<input data-testid="edit_full_name" type="full_name" id="full_name" class="form-control" value="Jareth">
</div>
<div class="form-group">
<label class="form-label" for="address">Address</label>
<input data-testid="edit_address" type="address" id="address" class="form-control" value="">
</div>
<div class="form-group">
<label class="form-label" for="postcode">Postcode</label>
<input data-testid="edit_postcode" type="postcode" id="postcode" class="form-control" value="">
</div>
<div data-testid="edit_dob">
<div class="react-datepicker-wrapper">
<div class="react-datepicker__input-container">
<input type="text" class="" value="04/03/1999">
</div>
</div>
</div>
<button data-testid="confirmChangesButton" type="submit" class="LoaderButton btn btn-primary btn-block">Confirm Changes</button></form>
The problem is visible when looking at the HTML generated by running the app.
DatePicker HTML
<div data-testid="edit_dob">
<div class="react-datepicker-wrapper">
<div class="react-datepicker__input-container">
<input type="text" class="" value="04/03/1999">
</div>
</div>
</div>
The DatePicker is rendered as an input element inside a few divs, and the test id doesn't target the input.
Solution
The Selector Playground API for Cypress suggests cy.get('.react-datepicker__input-container > input'), the class of the parent div of the input. With this I can target the DatePicker, clear it, and type new input.
Notes For Next Time
Examine the HTML
Read the Docs more thoroughly
Reference the concepts used in the cy.pickDateRange command in the Cypress Real World App. It is a payment application to demonstrate real-world usage of Cypress testing methods, patterns, and workflows.
In the code for the command, you will find it is necessary to examine the HTML produced and traverse it appropriately for your needs.

Why ng-class is not getting applied to input tag?

Here is my HTML input tag on which I have created conditional ng-class
<input type="text" class="form-control" placeholder="label" name="label"
ng-required="true" ng-class="{ missing: $ctrl.flag=='true'}"
ng-model="$ctrl.DataToSend.label[$ctrl.language]" />
And here is my CSS
.missing {border: solid 1px red;}
When my $ctrl.flag is true ng-class should get applied but it's not happening. Why?
You have to put the class which you want to apply in quotation marks. Otherwise it looks for an AngularJS variable where the string for the class name is contained.
Also make sure your condition is not always false. In your case the $ctrl.flag variable might store a boolean value. So change your condition to: $ctrl.flag
Updated Code:
<input type="text" class="form-control" placeholder="label" name = "label" ng-required="true" ng-class="{ 'missing': $ctrl.flag }" ng-model = "$ctrl.DataToSend.label[$ctrl.language]"/>
You can try this snippet.
Jsfiddle link
Snippet:
var app = angular.module('myApp', []);
app.controller('ctrl', function($scope) {
$scope.flag = true;
$scope.toggleClass = function() {
$scope.flag = !$scope.flag;
};
});
.missing {border: solid 1px red;}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='myApp'>
<div ng-controller='ctrl'>
{{flag}}
<input type='text' class='form-control' ng-model='test' ng-class="{'missing': flag}" />
<button ng-click='toggleClass()'>
Change
</button>
</div>
</div>
This will work
I found the Issue with my code there is some other CSS which is overriding to my 'missing' css when changed the overridden CSS its started working fine.
input[type="text"].missing {border: 1px solid #ff0000;}
Previously it was
input[type="text"] {border: 1px solid #ddd;}

Angular ng-model is being set to undefined on edit

I'm creating a modal dialog and trying to read the fields back when the dialog is closed, but when the input is edited, the ng-model for the input field is being set to undefined. With the Plunk, if you click the dialog button and then press Ok without modifying the text field, it will display "blah". But if you modify the text input at all, then nothing will be displayed.
The dialog template is:
<script type="text/ng-template" id="simpleModal.html">
<div class="modal-header">
<h3 class="modal-title">Simple Modal</h3>
</div>
<div class="modal-body">
<div class="form-group">
<label for="emailInput">Email</label>
<input id="emailInput" type="email" class="form-control" ng-model="user.email">
</div>
</div>
<div class="modal-footer">
<button class="btn" type="button" ng-click="ok()">Ok</button>
</div>
</script>
And the controller for the modal dialog:
app.controller('SimpleModalController', function($scope, $uibModalInstance, $log) {
$scope.user = {
email: "blah"
};
$scope.ok = function() {
$log.debug('simpleModal ok called ' + $scope.user.email);
$uibModalInstance.close($scope.user.email);
};
});
I've seen reference to https://stackoverflow.com/a/22768720/552936, but I've changed my code to reflect this and it hasn't fixed the issue.
You have declared input type="email" in your input field in modal
<input id="emailInput" type="email" class="form-control" ng-model="user.email">
It'll pass value if data according to email . like a#b.com
You can check if data has valid email
HTML
<form name="myForm">
<input type="email" name="myEmail" model="myEmail" />
<span>Valid Email {{myForm.myInput.$valid}}
</form>
PLUNKR
If you wanna pass any string then you have to make it type="text".
The reason it's being set to undefined is because you have the input for the email address as type=email. If you put anything but a valid email address in that field user.email will be set to undefined.
I just ran your plunker and put in a valid email address and can see it's has been set correctly. This is an instance where you should be validating that it's a well formed email address before allowing submission.

Angular validation issue

I've got an issue with the Angular validation classes. I've got three classes in my css with three different images, that shows if the field is required, if the user has filled in correct or not. E.g. I have one input field where the user types in a personal number, and I look it up in a service and fills the model with firstname, lastname, address etc. which also populates the input fields.
I made a simple example
HTML
<div ng-app="ebu" ng-controller="search">
<button id="search-apparentice" ng-click="addName()">Search apprentice</button>
<hr>
<input id="username" type="text" ng-model="user.name" required>
</div>
JS
angular.module('ebu', []).
controller('search', function($scope) {
$scope.user = {};
$scope.addName = function()
{
$scope.user.name = "Kasper";
}
})
CSS
input[required].ng-invalid
{
border: 1px solid red;
}
input[required].ng-valid
{
border: 1px solid green;
}
input[required].ng-pristine
{
border: 1px solid blue;
}
[FiddleJS][1][1]: http://jsfiddle.net/kaspergantzhorn/z1wg6tko/9/
It's still pristine because pristine means it has never had focus. The user is not clicking on it, therefore it never gets dirty. If this isn't a user-modified field, pristine is not the right thing to use.
Additionally, in the ng-change of the input, you should be able to use $setDirty.
<input type="text" ng-change="formName.inputName.$setDirty()" />
For you code above:
<div ng-app="ebu" ng-controller="search">
<button id="search-apparentice" ng-click="addName(); myForm.name.$setDirty()">Search apprentice</button>
<hr>
<form name="myForm">
<input name="name" id="username" type="text" ng-model="user.name" required>
</form>
</div>

Parsley.js destroy and revalidate fields already validated

I have implemented parsley on a complicated form that uses on-blur validation.
I have run into an issue where i go through the form, the fields are validated one by one, then based on a dropdown change, I have to destroy the Parlsey object in order to modify the validation and the fields that were valid are no longer valid.
How can I retain the already validated fields.
For example say I have the following:
<input type="text" id="1">
<input type="text" id="2">
<select id="select">
<option id=1>1</option>
<option id=2>2</option>
<option id=3>3</option>
</select>
<input type="text" id="3">
<input type="text" id="4">
<input type="text" id="5">
###Scenario:
I enter data in ID=1 and ID=2 textboxes, success class is added as I tab out of the textbox.
I change select box to option 2
I destroy parsley
Success class is removed from ID=1 and ID=2 textboxes
Currently I am doing the following:
$("#form").parsley().destroy();
$('#form').parsley({
successClass: "valid",
errorClass: "invalid",
errorsWrapper: '<div></div>',
errorTemplate: '<p></p>'
}).validate('section');
###What this does
This revalidates all the fields
###What I am looking for
I need ID=1 and ID=2 to remain valid after I destroy.
When you call destroy() all things from Parsley are destroyed. This means that all the classes, messages (DOM), objects and events will be destroyed.
If you're looking for a way to maintain the UI aspect, you can do that with a not-so-pretty solution:
In your styles, where you have .valid { ...} add another class: .valid, .fake-valid { ... }. Do the same for invalid.
Before calling destroy(), navigate through all the fields and check if there is a class valid or invalid
If so, apply a class called fake-valid or fake-invalid.
Using the event parsley:form:init, loop through the fields again and change their classes from fake-... to the correct classes.
Parsley will validate the fields but the UI is maintained.
Check this JsFiddle.
<style>
.valid, .fake-valid {
color: #468847;
background-color: #DFF0D8;
border: 1px solid #D6E9C6;
}
.invalid, .fake-invalid {
color: #B94A48;
background-color: #F2DEDE;
border: 1px solid #EED3D7;
}
</style>
<form id="myForm" method="post">
<input type="text" id="1" data-parsley-trigger="focusout" required />
<input type="text" id="2" data-parsley-trigger="focusout" required />
<select id="select">
<option id=1>1</option>
<option id=2>2</option>
<option id=3>3</option>
</select>
</form>
<script>
$(document).ready(function() {
var parsleyOpts = {
successClass: "valid",
errorClass: "invalid",
errorsWrapper: '<div></div>',
errorTemplate: '<p></p>'
};
var ParsleyForm = $("#myForm").parsley(parsleyOpts);
$("#select").on('change', function() {
// before destroy, add fake class
for (var i in ParsleyForm.fields) {
var elem = ParsleyForm.fields[i].$element;
if (elem.hasClass('valid'))
elem.addClass('fake-valid');
else if(elem.hasClass('invalid'))
elem.addClass('fake-invalid');
}
ParsleyForm.destroy();
ParsleyForm = $("#myForm").parsley(parsleyOpts);
});
// when parlsey is initialized, lets see if the fields have fake classes
// if so, add parsley classes
$.listen('parsley:form:init', function(formInstance) {
for (var i in formInstance.fields) {
var elem = formInstance.fields[i].$element;
if (elem.hasClass('fake-valid'))
elem.toggleClass('fake-valid valid')
if (elem.hasClass('fake-invalid'))
elem.toggleClass('fake-invalid invalid');
}
});
});
</script>

Resources