Angular ngfor select all and deselect - javascript

I have items :
items: [
{name: 'Name'},
{name: 'Name'},
{name: 'Name'}
]
I am showing them in html :
<ion-checkbox (click)="click(); selectAllItems()" class="checkboxas" [(ngModel)]="allTobuli"></ion-checkbox>
<!-- this isnt in ngFor and it Selects all items -->
<ion-card *ngFor="let item of jsonObj" class="relative" (click)="compareTobuli(item,i);checkboxTobuli(item)">
<ion-checkbox (click)="compareTobuli(item,i)" [(ngModel)]="item.allTobuliItem" class="checkboxas absolut-check"></ion-checkbox>
</ion-card>
Each of them as you can see got checkbox - if you click on item it is added to another array and checkbox value becomes true. Another thing is that there is one checkbox out of ngFor which make another array same as items.
The problem is that I don't know how to change ngFor all checkboxes values when clicking on checkBox which isnt in ngFor.
I am using sets so it's my TS :
compareTobuli(item,i){
if (this.selected.has(item)) {
this.selected.delete(item);
console.log('Trinam', this.selected)
} else {
this.selected.add(item);
console.log('Pridedan', this.selected)
}
}
click() {
this.clicked = !this.clicked;
console.log(this.clicked);
return this.clicked;
}
selectAllItems() {
if(this.clicked == true) {
this.selected = new Set(this.jsonObj);
console.log(this.selected);
}
else {
this.selected = new Set;
console.log('deleted all', this.selected);
}
}
checkboxTobuli(item){
item.allTobuliItem = !item.allTobuliItem;
}

First of all:
Here is a working stackblitz that also select and deselect all values on click.
You already use (click)="click(); selectAllItems()" on your ion-checkbox to call click() and selectAllItems().
All you need to do is modifing the specific values (of the iterated items) inside these method.
selectAllItems() {
for(let i=0; i<this.jsonObj.length; i++) {
let item = this.jsonObj[i]; // this is your item from *ngFor="let item of jsonObj"
item.allTobuliItem = true; // select every single item
}
}

You need to update the value of allTobuliItem for each item in the jsonObj. Angular's data binding will take care of the rest. Example:
Template
<ion-checkbox
(click)="click(); selectAllItems()"
class="checkboxas"
[(ngModel)]="allTobuli"></ion-checkbox>
Component
public selectAllItems() {
this.jsonObj = this.jsonObj.map(item => {
item.allTobuliItem = this.allTobuli;
return item;
});
}

Related

Vuejs html checkbox check state not updated correctly

Having issues getting some checkboxes to work properly. So in my component I have an array of objects set in a state variable tokenPermissions that look like this
tokenPermissions: [
{
groupName: "App",
allSelected: false,
someSelected: false,
summary: "Full access to all project operations",
permissions: [
{
name: "can_create_app",
summary: "Create new projects",
selected: false,
},
{
name: "can_delete_app",
summary: "Delete existing projects",
selected: false,
},
{
name: "can_edit_app",
summary: "Edit an existing project",
selected: false,
},
],
}
],
The goal is to loop through this array and have a parent and children checkboxes like so tokenPermissions[i].allSelected bound to the parent checkbox and for each object in tokenPermissions[i].permissions a corresponding checkbox bound to the selected property like so tokenPermissions[i].permissions[j].selected.
Desired behaviour when the parent checkbox is selected,
If all child checkboxes checked, uncheck all, including the parent
If child checkboxes are unchecked check all including the parent
if only some of the child checkboxes are selected, the parent would show the indeterminate - icon or sign and on click, uncheck all child checkboxes including the parent.
The issue is point 3. The issue is sometimes the parent checkbox is not correctly checked based on the state of the attribute bounded to. For example allSelected can be false but the parent checkbox is checked.
I put a complete working example on github here https://github.com/oaks-view/vuejs-checkbox-issue.
The bit of code with the binding is as follows
<ul
class="list-group"
v-for="(permissionGroup, permissionGroupIndex) in tokenPermissions"
:key="`${permissionGroup.groupName}_${permissionGroupIndex}`"
>
<li class="list-group-item">
<div class="permission-container">
<div>
<input
type="checkbox"
:indeterminate.prop="
!permissionGroup.allSelected && permissionGroup.someSelected
"
v-model="permissionGroup.allSelected"
:id="permissionGroup.groupName"
v-on:change="
permissionGroupCheckboxChanged($event, permissionGroupIndex)
"
/>
<label :for="permissionGroup.groupName" class="cursor-pointer"
>{{ permissionGroup.groupName }} -
<span style="color: red; margin-left: 14px; padding-right: 3px">{{
permissionGroup.allSelected
}}</span></label
>
</div>
<div class="permission-summary">
{{ permissionGroup.summary }}
</div>
</div>
<ul class="list-group">
<li
class="list-group-item list-group-item-no-margin"
v-for="(permission, permissionIndex) in permissionGroup.permissions"
:key="`${permissionGroup.groupName}_${permission.name}_${permissionIndex}`"
>
<div class="permission-container">
<div>
<input
type="checkbox"
:id="permission.name"
v-bind:checked="permission.selected"
v-on:change="
permissionGroupCheckboxChanged(
$event,
permissionGroupIndex,
permissionIndex
)
"
/>
<label :for="permission.name" class="cursor-pointer"
>{{ permission.name
}}<span
style="color: red; margin-left: 3px; padding-right: 3px"
> {{ permission.selected }}</span
></label
>
</div>
<div class="permission-summary">
{{ permission.summary }}
</div>
</div>
</li>
</ul>
</li>
</ul>
And for updating the checkbox
getPermissionGroupSelectionStatus: function (permissionGroup) {
let allSelected = true;
let someSelected = false;
permissionGroup.permissions.forEach((permission) => {
if (permission.selected === false) {
allSelected = false;
}
if (permission.selected === true) {
someSelected = true;
}
});
return { allSelected, someSelected };
},
permissionGroupCheckboxChanged: function (
$event,
permissionGroupIndex,
permissionIndex
) {
const { checked } = $event.target;
// its single permission selected
if (permissionIndex !== undefined) {
this.tokenPermissions[permissionGroupIndex].permissions[
permissionIndex
].selected = checked;
const { allSelected, someSelected } =
this.getPermissionGroupSelectionStatus(
this.tokenPermissions[permissionGroupIndex]
);
this.tokenPermissions[permissionGroupIndex].allSelected = allSelected;
this.tokenPermissions[permissionGroupIndex].someSelected = someSelected;
} else {
// its selectAll check box
const { allSelected, someSelected } =
this.getPermissionGroupSelectionStatus(
this.tokenPermissions[permissionGroupIndex]
);
let checkAll;
// no checkbox / permission is selected then set all
if (!someSelected && !allSelected) {
checkAll = true;
} else {
checkAll = false;
}
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
this.tokenPermissions[permissionGroupIndex].someSelected = checkAll;
for (
let i = 0;
i < this.tokenPermissions[permissionGroupIndex].permissions.length;
i++
) {
this.tokenPermissions[permissionGroupIndex].permissions[i].selected =
checkAll;
}
}
},
It's a rendering problem.
Vue set the allSelected checkbox as checked, then in the same cycle updates it to false; you can read about Vue life cycle here: https://it.vuejs.org/v2/guide/instance.html
A pretty brutal (but simple) way to resolve it (which I don't recommend, but it's useful to understand what's happening) is to delay the update.
Wrap the last part of the method permissionGroupCheckboxChanged with a this.$nextTick:
this.$nextTick(() => {
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
this.tokenPermissions[permissionGroupIndex].someSelected = checkAll;
for (
let i = 0;
i < this.tokenPermissions[permissionGroupIndex].permissions.length;
i++
) {
this.tokenPermissions[permissionGroupIndex].permissions[i].selected =
checkAll;
}
})
This way when you change the values, the engine reacts accordingly.
Still I don't recommend it (I think nextTick is useful to understand the Vue life cycle, but I would recommend against using it whenever is possible).
A less brutal (and simpler) way is to set the allSelected to null instead of false when checkAll is not true permissionGroupCheckboxChanged:
// this
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll ? checkAll : null;
// instead of this
this.tokenPermissions[permissionGroupIndex].allSelected = checkAll;
this way the prop wins against the model (as the model value becomes null).
But the even better option (imho) would be to use a component of its own inside the v-for loop and have allSelected and someSelected as computed properties instead of values bound to real variables.
Usually you should not store ui status as data when it can be inferred from real data (I may be wrong, as I don't know your application, but in your case I suspect you are interested in the single checkboxes' values, while allSelected/someSelected are merely used for ui).

How to switch status in Angular

I have select options that I need to select 1 option at the time and set active value of selected item to true
So far I can make that, but the issue is:
When I change my selected option, first option active will not be set to false
Screenshot
code
HTML
<ion-row>
<ion-col *ngFor="let imf of allImages" size="4">
<img [id]="imf.id" (click)="getName(imf)" [src]="imf.src" [alt]="imf.id">
</ion-col>
</ion-row>
Component
allImages = [{
'src' : '../../../assets/bgs/01.png',
'id' : '01',
'name': '01.png',
'active': false,
},
{
//and so on...
}];
getName(data) {
// add class to selected option
var titleELe = document.getElementById(data.id);
titleELe.classList.add('active');
// set active value of selected option to "true"
let index = this.allImages.indexOf(data.id);
data.active = true;
this.allImages[index] = data;
// issues:
// remove added class from old item
// remove "true" from old item
}
What I need
remove added class from old item
remove "true" from old item
Any idea?
Solved
here is my last update which fixed my issues
getName(data) {
const items = this.allImages.filter((item) => { // loop all items
if(item.active == true) { // if any of them is set active to true
item.active = false; // set it to flase
var titleELe = document.getElementById(item.id);
titleELe.classList.remove('active'); // and remove the class of it
}
});
var titleELe = document.getElementById(data.id);
titleELe.classList.add('active'); // add class to new selected item
const index = this.allImages.indexOf(data.id);
data.active = true; // set newly selected item active to true
this.allImages[index] = data;
}
Hope it can help others as well.
Try using ngClass for setting 'active' class
HTML
<ion-col *ngFor="let imf of allImages;let i=index" size="4">
<img [id]="imf.id" [ngClass]="{'active':currentItemIndex == i}"
(click)="setActiveItemIndex(i)" [src]="imf.src" [alt]="imf.id">
</ion-col>
TS:
//property
currentItemIndex=0;
setActiveItemIndex(i){
currentItemIndex=i;
}
In case in future you want to get the image which is active
const activeImage=this.allImages[currentItemIndex]

Ionic: change ion-list items on click

Lets say I have 3 lists
list: 1 ) bus , plane
list: 2 ) [related to bus] slow , can't fly
list: 3) [related to plane] fast, can fly
In my Ionic Angular project I have successfully made the 1st ion-list. But how can I change the whole ion-list by clicking on the item inside it?
[I get it, its something to do with (click) function, but how I can affect the whole list using typescript]
Edit: I get what you want to achieve. You can do this by creating an intermediary list and using that list in your ngFor. That way you can just simply change the reference of the intermediary list to whatever list you like onClick
export class ListPage {
transportationTypes: string[] = ['bus', 'plane'];
busSpecs: string[] = ['slow', "can't fly"];
planeSpecs: string[] = ['fast', 'can fly'];
currentList: string[] = this.transportationTypes;
itemClicked(type): void {
if (type === 'bus') {
this.currentList = this.busSpecs;
} else if(type === 'plane') {
this.currentList = this.planeSpecs;
} else {
this.currentList = this.transportationTypes;
}
}
}
And in your HTML just call the itemClicked function
<ion-list *ngIf="currentList">
<ion-item *ngFor="let item of currentList" (click)="itemClicked(item)">
{{item}}
</ion-item>
</ion-list>

Vue.js Push doesn't update view

I create a array which contains objects describing information of buttons. In my template I have a class active which is represent the active button. By default the first button of the array is active.
template:
<b-button-group class="page-buttons mt-2" size="sm">
<b-button v-for="btn in buttons"
:key="btn.key"
#click="selectPage(btn.key)"
v-bind:class="{'active': btn.active}">{{ btn.caption }}
</b-button>
</b-button-group>
Script:
methods: {
$createPaginationButtons: function(numberParameters) {
const numberPages = Math.ceil(numberParameters / NUMBER_ELEMENT_PER_PAGE);
this.buttons = [];
for (let i = 0; i < numberPages; i++) {
this.buttons.push({
caption: `PAGE ${i + 1}`,
active: (i === 0),
key: (i + 1)
});
}
Vue.set(this.buttons[0], 'active', true);
}
}
The buttons array is initialize in data as an empty array.
when my buttons array changes, the view is updated and display the correct number of buttons but my class active is not triggered.
Have you any idea.
Push is a wrapped method so it will trigger a view update.
Do you have declared buttons on the data property?
data() {
buttons:[]
}
Since you are creating the active property as i === 0 you don't need Vue.set.
I don't know if has something to do but instead of making buttons empty and then pushing, use an aux variable to create the array and then just do this.buttons = auxVariable. This should trigger an update.

knockout: remove one element from multiselect control when other is selected

I'm using knockout and I have a list of item, let say:
Tomato,
Potato,
Broccoli,
Bean
all those item are allowed to user to select from multiselect form-control.
<select class="form-control" multiple
data-bind="selectPicker: Types,
optionsText: 'Name',
optionsValue: 'VegetableTypeId',
selectPickerOptions: { optionsArray: AvailableVegetableTypes }">
</select>
Except one scenario - when the user selects tomato, potato should unselect.
I was trying to use subscription on selected items array:
this.Types.subscribe(changes => {
var VegetableTypes = this.AvailableVegetablesTypes();
var company = VegetableTypes.First(element => element.VegetableTypeId == changes[0].value);
if (changes[0].status == "added") {
if (Vegetable.IsTomato) {
this.Types.remove(element =>
VegetableTypes.First(baseElement =>
baseElement.VegetableTypesTypeId == element && baseElement.IsPotato));
} else if (Vegetable.IsPotato) {
this.Types.remove(element =>
VegetableTypes.First(baseElement =>
baseElement.VegetableTypesTypeId == element && baseElement.IsTomato));
}
}
}, null, "arrayChange");
Problem is that I'm using ObservableArray.Remove, so it's again call my function before current run is finish. This should not be a problem, because after remove first change is "deletion" type, so whole logic should not be executed.
But after this, when I select tomato/potato again, nothing is fired. In the end I actually have both tomato and potato selected.
Then, when I deselect one of these two and select it again, everything works fine, and then the whole situation repeats.
Do you have any ideas?
I didn't understand why you are using selectPicker bindings instead of the normal options and selectedOptions bindings available in Knockout.
However, I built a simple demo which implements the desired behaviour. You can find it here:
http://jsbin.com/fofojaqohi/1/edit?html,js,console,output
Note that, whenever you select Tomato after Potato, Potato will become unselected.
You were on the right track: you need to subscribe to the array of selected items and check if there are any invalid selections. I hope this helps.
For reference, here is the code:
HTML:
<select class="form-control" multiple="true"
data-bind="options: availableVegetableTypes, selectedOptions: selected">
</select>
JS:
var availableVegetableTypes = ['Tomato',
'Potato',
'Broccoli',
'Bean'];
var selected = ko.observableArray();
var unselectMap = {
'Tomato': 'Potato'
};
selected.subscribe(function(selectedOptions){
var keysToUnselect = [];
console.log("Selected", selectedOptions);
selectedOptions.forEach(function(selectedOption){
if (unselectMap[selectedOption] != null) {
// This key exists in the unselect map
// Let's check if the value is in the array
if (_.contains(selectedOptions, unselectMap[selectedOption])) {
// The invalid key exists. Let's mark it for removal.
keysToUnselect.push(unselectMap[selectedOption]);
}
}
});
if (keysToUnselect.length > 0) {
console.log("Unselect", keysToUnselect);
var reject = function(v){
return _.contains(keysToUnselect, v);
};
filteredSelectedOptions = _.reject(selectedOptions, reject);
console.log("Filtered", filteredSelectedOptions);
selected(filteredSelectedOptions);
}
});
ko.applyBindings({
availableVegetableTypes:availableVegetableTypes,
selected: selected
});

Categories