Protractor isSelected giving false reading after re-enabling checkboxes - angularjs

I have a list of checkboxes and at the top a all checkbox. When toggling the all checkbox, all checkboxes will get deselected or selected.
By default/initially, all checkbox is enabled with all checkboxes checked. So, I will deselect the all checkbox and all the checkboxes will uncheck. This passes no with no issues in protractor:
it('all checkbox is deselected', function() {
modelsAllCheckbox.click();
expect(modelsAllCheckbox.isSelected()).to.eventually.be.false;
});
it('all models should be deselected', function() {
ppvPercentages.modelChoices().then(function(modelChoices) {
modelChoices.forEach(function(modelChoice) {
expect(modelChoice.isSelected()).to.eventually.be.false;
});
});
});
this.modelChoices = function(rowNumber) {
return element.all(by.repeater('model in vehicleCheckboxes.models'));
}
Then I re-enable the all checkbox. I visually can see, in the browser, the all checkbox being checked in all the checkboxes successfully being checked/selected. Hoewever, in the test to assert they are all selected fails:
it('all button is re-enabled', function() {
modelsAllCheckbox.click();
expect(modelsAllCheckbox.isSelected()).to.eventually.be.true;
// give time for all models to set
browser.sleep(3000)
});
it('all models are selected', function() {
ppvPercentages.modelChoices().then(function(modelChoices) {
modelChoices.forEach(function(modelChoice) {
expect(modelChoice.isSelected()).to.eventually.be.true;
});
});
})
<div class="overflow-container">
<!-- all checkbox -->
<input type="checkbox"
ng-model="vehicleAllCheckbox.models"
ng-change="toggleAllModels(vehicleAllCheckbox, vehicleCheckboxes.models, vehicleCheckboxes.year)">All
<div ng-repeat="model in vehicleCheckboxes.models | orderBy: 'description' track by model.description">
<!-- the rest of the checkboxes -->
<input type="checkbox"
ng-change="modelCheckboxToggle()"
ng-model="model.checked" >
{{model.description}}
</div>
</div>
I see all the checkboxes are checked in the browser viusally. Why is modelChoice.isSelected() giving false instead of true up re-enabling the all checkbox?

The problem is in the way you are locating the checkboxes. Currently, you are targeting the parent div elements since you are using the by.repeater() locator:
<div ng-repeat="model in vehicleCheckboxes.models | orderBy: 'description' track by model.description">
Instead, point modelChoices to input elements (your checkboxes):
this.modelChoices = function(rowNumber) {
return element.all(by.repeater('model in vehicleCheckboxes.models')).all(by.model('model.checked'));
}
As a side note, I think you can improve the way you are expecting the checkboxes to be selected, by either using .each():
ppvPercentages.modelChoices().each(function (modelChoice) {
expect(modelChoice.isSelected()).to.eventually.be.true;
});
Or, by using .reduce():
var allSelected = ppvPercentages.modelChoices().reduce(function (acc, modelChoice) {
return modelChoice.isSelected().then(function (isSelected) {
return acc && isSelected;
});
}, true);
expect(allSelected).to.eventually.be.true;
Or, there are other ways as well.

Related

Angular 12 FormGroup dynamically Array checkboxes custom validator does not work

I am creating an Angular 12 app, with Material.
I have a form with an checkbox array loaded dynamically from database.
I need to validate that at least one checkbox is selected
I defined like this in my OnInit():
ngOnInit(): void {
this.form = this.fb.group({
Id: new FormControl(null),
Name: new FormControl('',Validators.required),
Recipents: new FormControl('',[Validators.required, matchingEmailValidator()]),
IsActive: new FormControl(true),
ProcessorName: new FormControl('',Validators.required),
Channel: new FormArray([]),
}, { validators: [customValidateArrayGroup()] }
);
}
I need a custom validation for channel form array. If I added it in the definition of the channel, it does not fire when I check it. So, I decided to do it at the form level..
I added:
{ validators: [customValidateArrayGroup()] }
Every time an object changes, it fires this validator.
This is my custom validator:
export function customValidateArrayGroup(): ValidatorFn {
return function validate(formGroup: FormGroup) {
let checked = 0
Object.keys(formGroup.controls).forEach(key => {
const control = formGroup.controls[key]
if (control.value) {
checked++
}
})
if (checked < 1) {
return {
requireCheckboxToBeChecked: true,
}
}
return null
}
}
Here is my Html where I defined the Checkbox Array
<mat-label><strong>Channel</strong></mat-label>
<li *ngFor="let chanel of notification.NotificationChannelLogLevels">
<mat-checkbox id= {{chanel.NotificationLogLevel.Id}} formArrayName="Channel"
[checked]="chanel.IsActive"
(change)="changeEventFunc($event)">
{{chanel.NotificationLogLevel.Name}}
</mat-checkbox>
</li>
The problem I have is that the custom validator does not fire when a checkbox is clicked. Maybe is becouse they are loaded dinamically and are not recognized by formGroup.controls
How can I validate this?
You have an odd mix of using formarray and your js array in the template. Currently your formarray is completely empty, so it would be expected that it does not run when checkboxes are checked. You can choose to iterate your JS array and push / remove to formarray, or then you push the values to the formarray when you receive the data and then just iterate that one in the template. The below solution does the latter:
Shortened code....
Build form:
this.form = this.fb.group({
Channel: this.fb.array([], [customValidateArrayGroup()]),
});
I attached the custom validator to the formarray itself. When you have the dynamic data ready, then iterate it and push form controls to your formarray. I like to use a getter as well. Push whatever properties you need, here I choose IsActive and Name only:
get channels() {
return this.form.get('Channel') as FormArray;
}
// when you have data accessible:
this.notificationChannelLogLevels.forEach(value => {
this.channels.push(this.fb.group({
isActive: value.IsActive,
name: value.Name
}))
})
Now iterate this formarray in the template:
<div formArrayName="Channel">
<li *ngFor="let chanel of channels.controls; let i = index">
<ng-container [formGroupName]="i">
<mat-checkbox formControlName="isActive">
{{ chanel.get('name').value}}
</mat-checkbox>
</ng-container>
</li>
<small *ngIf="channels.hasError('hasError') && channels.touched">
Choose at least one
</small>
</div>
The custom validator checks that at least one checkbox field has isActive as true:
export function customValidateArrayGroup() {
return function validate(formArr: AbstractControl): ValidationErrors | null {
const filtered = (formArr as FormArray).value.filter(chk => chk.isActive);
return filtered.length ? null : { hasError: true }
};
}
A STACKBLITZ for your reference.
I think you have your FormArray setup incorrectly in your template.
You are applying the formArrayName attribute the each checkbox when it needs to be applied to a parent container,
<div formArrayName="myFormArray">
<div *ngFor="*ngFor="let chanel of notification.NotificationChannelLogLevels; let i = index">
//Use the index here to dynamically tie each mat-checkbox to a FormControl
<mat-checkbox [FormControl]="myCheckboxes[i]"></mat-checkbox>
</div>
</div>
And then in your .ts file you'll have to define myCheckboxes as a FormArray with instances of form control inside it. Otherwise myCheckboxes[i] will be either null or an index out of bounds. You can use the form array you added to your form group, but the indexes you reference in the template have to be defined.
Here is a good blog post going over how to handle adding/removing instances from the form array,
https://netbasal.com/angular-reactive-forms-the-ultimate-guide-to-formarray-3adbe6b0b61a
And another,
https://blog.angular-university.io/angular-form-array/
As a side note, if your logging levels are static, it may just be easier or more intuitive to define the list of checkbox controls as a FormGroup and apply your validator to the form group.

Semantic-ui checkbox in Meteor

Do anyone know how to use Semantic-ui checkbox (toggle) in Meteor?
<div class="ui toggle checkbox">
<input type="checkbox" name="public">
<label>Subscribe to weekly newsletter</label>
</div>
The checkbox / slider is visible on the html page with a sliding effect but I cant understand how to code against the control. How to set checked / unchecked depending on a value and how to handel events.
This is how I do it:
Session.set('chosen', false);
Template.myTemplate.onRendered(function () {
var $elem = this.$('.checkbox');
// Use 'set unchecked' or 'set checked' instead of 'uncheck'/'check'
// to avoid triggering the callback.
// Set initial state here:
$elem.checkbox('set ' + (Session.get('chosen') ? 'checked' : 'unchecked'));
// Keep state synced with the session.
$elem.checkbox({
onChange: function () {
Session.set('chosen', !Session.get('chosen'));
}
});
});

AngularJS ng-checked isn't updating checkbox when model changed in javascript

I have a filtered list of objects being repeated with ng-repeat with an input checkbox beside each item. When the list isn't filtered (as it is when the page is loaded) then I don't want the checkboxes checked. If the list is filtered I do want the checkboxes checked (the checkboxes form a treeview via CSS).
The checkbox has ng-checked="{{site.isChecked}}". When the list is filtered I am updating the isChecked variable on the objects within the filter javascript code, but this isn't updating the checkbox value. If the item is filtered out so it's removed from screen and then filtered back in again the updated isChecked value will come through to the screen.
The HTML is as follows:
<ul>
<li ng-repeat="site in sites | filterSites:searchBuildings">
<input type="checkbox" ng-checked="{{site.isChecked}}" id="item-f{{site.id}}" /><label for="item-f{{site.id}}">{{site.site}}</label>
<ul>
<li ng-repeat="building in site.filteredBuildings">
{{building.address}}
</li>
</ul>
</li> </ul>
The JS is:
var filtered = [];
searchBuildings = searchBuildings.toLowerCase();
angular.forEach(sites, function (site) {
var added = false;
var siteMatch = false;
site.isChecked = true;
if (site.site.toLowerCase().indexOf(searchBuildings) !== -1)
siteMatch = true;
angular.forEach(site.buildings, function (building) {
if (siteMatch == true || building.search.toLowerCase().indexOf(searchBuildings) !== -1) {
if (added == false) {
added = true;
filtered.push(site);
site.filteredBuildings = [];
}
site.filteredBuildings.push(building);
}
});
});
return filtered;
Sorry if the code isn't very pretty - I'm new to AngularJS and JS and still trying to work out how things link together.
Thanks
You should use ng-model there that would provide you two way binding. Checking and unchecking the value would update the value of site.isChecked
<input type="checkbox" ng-model="site.isChecked" id="item-f{{site.id}}"/>

how to stop bind in angularjs

I hava a checkbox ,the model status.useJoin also bind the div.
<input type="checkbox" ng-model="status.useJoin" ng-click="toggleJoin($event);" >
<div ng-if="status.useJoin"> show area</div>
when status.useJoin is true ,will show div.
My question is ,when I want to prevent the default action of the checkbox. I will write function toggleJoin like this.
$scope.toggleJoin = function (dimension,$event) {
if (status.useJoin) {
$event.preventDefault();
return;
}
}
the checkbox action is stopped ,but status.useJoin is still modified. How can I stop the bind?
You can use ng-disabled directive
<input type="checkbox" ng-model="status.useJoin" ng-disabled="onYourDisableCondition();" >
$scope. onYourDisableCondition = function () {
if (status.useJoin) { //Add your additional conditions
return true;
}
}

Knockout.js Checkbox checked and click event

We're trying to implement a checkbox and list with the following functionality:
Clicking the checkbox will either clear the array if there are items in there, or add a new item if not.
Remove an item from the array when clicking the Remove button, once the last item is removed the checkbox automatically unchecks itself.
The problem I am having is that if you click to remove each array item, then click the checkbox to add a blank entry, I'm expecting the checkbox to be checked again (as per the checked observable), however it is not?
I have the following code:
http://jsfiddle.net/UBsW5/3/
<div>
<input type="checkbox" data-bind="checked: PreviousSurnames().length > 0, click: $root.PreviousSurnames_Click" />Previous Surname(s)?
</div>
<div data-bind="foreach: PreviousSurnames">
<div>
<input type="text" data-bind="value: $data">
<span data-bind="click: $root.removePreviousSurname">Remove</span>
</div>
</div>
var myViewModelExample = function () {
var self = this;
self.PreviousSurnames = ko.observableArray(['SURNAME1', 'SURNAME2', 'SURNAME3']);
self.removePreviousSurname = function (surname) {
self.PreviousSurnames.remove(surname);
};
self.PreviousSurnames_Click = function () {
if (self.PreviousSurnames().length === 0) {
self.PreviousSurnames.push('');
}
else {
self.PreviousSurnames.removeAll();
}
alet(2)
}
}
ko.applyBindings(new myViewModelExample());
If you are using together the click and the checked then you need to return true from your click handler to allow the browser default click action which is in this case checking the checkbox:
self.PreviousSurnames_Click = function () {
if (self.PreviousSurnames().length === 0) {
self.PreviousSurnames.push('');
}
else {
self.PreviousSurnames.removeAll();
}
return true;
}
Demo JSFiddle.
You need to use a computed to monitor the length of the observable array. This way when the length reaches zero you can react to it automatically.
self.surnames = ko.computed(function() {
var checked = true;
if (self.PreviousSurnames().length === 0) {
self.PreviousSurnames.push('');
checked = false;
}
return checked;
});
Now you will have the blank text box when all of the names are cleared. If you update your binding on the checkbox it will function properly as well.
<input type="checkbox" data-bind="checked: surnames, click: PreviousSurnames_Click" />Previous Surname(s)?
FIDDLE

Resources