Remove items from Array skips certain items - javascript

I have the following page (simple):
As you can see, at the top I have an input, then <ul> and finally a button to save changes. My <ul> is bound to a array of items. Once user clicks Uloz zmeny (Save Changes) I am triggering ng-click="vm.SaveChanges()" which looks like following:
vm.SaveChanges = function () {
angular.forEach(vm.items, function (value, key) {
if (value.toRemove == true) {
//remove item from the list
var iIndex = vm.items.indexOf(value);
vm.items.splice(iIndex, 1);
};
});
};
where vm is defined as following at the beginning of my code:
(function () {
"use strict";
angular.module("app-shopping").controller("itemsController", itemsController);
function itemsController($http) {
var vm = this;
vm.items = [];.....more code after here
Every item under my '' has the following structure:
{
"id": 2,
"orderId": 2,
"text": "Item 2",
"toRemove": true
},
Finally, when user checks an item under the <li> I am triggering vm.toggleCompleted() which simply looks like this (it simply changes a boolean state of current item from true to false or vice versa):
vm.toggleCompleted = function (sItem) {
sItem.toRemove = !sItem.toRemove;
};
Here comes the question: Why when I run this code it does not remove all checked items in the array? For example in this specific case (see image above) it would only remove Item 2 and skip Item 3. I believe that the problem is caused by the fact that when Item 2 is remove from the list, Item 3 takes the index of already existing Item 2 and therefore is skipped. Is this assumption correct? If yes, how do I need to change the code to make this run?
P.S. Edit to my code as recommended:
<li class="list-group-item" ng-repeat="sItem in vm.items">
<div class="checkbox checkbox-success">
<input id="ListItem{{$index}}" type="checkbox" placeholder="test placeholder" ng-model="sItem.toRemove" ng-click="sItem.toRemove=!sItem.toRemove" />
<label for="ListItem{{$index}}">{{sItem.text}}</label>
</div>
</li>

I have changed the code the following way and it is working now:
vm.SaveChanges = function () {
for (var i = vm.items.length - 1; i > -1; i--)
{
if (vm.items[i].toRemove == true)
{
vm.items.splice(i, 1);
}
}
};

Instead of using toggleCompleted use below at the place of check-box input
<input type="checkbox" ng-model="item.toRemove" ng-click="item.toRemove=!item.toRemove" />
And Use your new saveChanges method .. this should work fine..

Yes, to bypass this issue, just revert your array traversal, because this way you can guarantee that no position of unchecked elements changes during deletion.

So, you code at the end should be like that:
Template:
<li class="list-group-item" ng-repeat="sItem in vm.items">
<div class="checkbox checkbox-success">
<input id="ListItem{{$index}}" type="checkbox" placeholder="test placeholder" ng-click="sItem.toRemove = !sItem.toRemove" />
<label for="ListItem{{$index}}">{{sItem.text}}</label>
</div>
</li>
<button class="btn btn-success" ng-click="SaveChanges()"> Save</button>
Controller:
$scope.SaveChanges = function () {
for (var i = $scope.vm.items.length - 1; i > -1; i--){
if ($scope.vm.items[i].toRemove) {
$scope.vm.items.splice(i, 1);
};
}
}

Related

How to do pop operation in javascript when unchecked checkbox?

I have just the two checkbox whose codes looks like:
<div *ngFor="let item of documents">
<label>
<input type="checkbox" value="{{item.docId}}" [(ngModel)]="item.checked" [name]="item.docName"
(change)="editPartyRolesSubmit($event)"
/>
<span innerHTML="{{item.docName}}"></span>
</label>
</div>
Here i have only used two checkbox as:
The function editPartyRolesSubmit($event) called is:
public documents: Array<Document> = []
public onlyTruedocuments: Array<Document> = [];
editPartyRolesSubmit(event) {
this.documents.filter(x => x.checked).map(x => {
console.log("entered in check");
this.onlyTruedocuments.push(x);
console.log(x);
})
}
The Json data is pushed three times as it should be only pushed two times though i have just clicked two times.
But when i print them in the .html page then though the checkbox is two,It is printed three times :
<li *ngFor="let ot of onlyTruedocuments; index as i">
{{ot.docName}}
</li>
It is printing like this:
How can i remove this redundant data?
If I understand correctly, I would do in this way,
I have used (ngModelChange) instead of (change)
I have passed the current item to the ngModelChange function.
HTML:
<div *ngFor="let item of documents">
<label>
<input type="checkbox" value="{{item.docId}}" [(ngModel)]="item.checked" [name]="item.docName"
(ngModelChange)="editPartyRolesSubmit($event,item)"/> // will pass an item to the function
<span innerHTML="{{item.docName}}"></span>
</label>
</div>
<li *ngFor="let ot of onlyTruedocuments; index as i">
{{ot.docName}}
</li>
TS file:
export class YourComponent {
documents = [{
docId: 1,
checked: false,
docName: 'Prashant'
},
{
docId: 2,
checked: false,
docName: 'Venkat'
}
, {
docId: 2,
checked: false,
docName: 'Perry'
}];
public onlyTruedocuments: any = [];
editPartyRolesSubmit(event, obj) {
// Take the index of an Item checked
let index = this.onlyTruedocuments.indexOf(obj);
// Check for event i.e it is checked or unchecked
if (event) {
if (index == -1) {
// If the index is -1 then that means its not a duplicate so push into an array
this.onlyTruedocuments.push(obj);
}
}
else {
// If it is unchecked then we surely know that the item has to be removed from the array so by an index of the particular item we can [splice][1] the item
this.onlyTruedocuments.splice(index, 1)
}
}
}
No need to filter the source array to get the checked items.
A Working StackBlitz Example with Sample data.
Have a unique check before you push to onlyTruedocuments. This way even when user click many times the object would still have unique values as expected.
editPartyRolesSubmit(event) {
this.documents.filter(x => x.checked).map(x => {
console.log("entered in check");
const exists = this.onlyTruedocuments.filter((f)=>f.docId == x.docId);
if(exists.length==0){
this.onlyTruedocuments.push(x);
}
console.log(x);
})
}
One way to collect unique elements only is to define this.onlyTruedocuments not as an array, but as a Set, initialised as:
this.onlyTrueDocuments = new Set();
Then in the event handler do:
editPartyRolesSubmit(event) {
this.documents.forEach(x => x.checked && this.onlyTruedocuments.add(x));
console.log([...this.onlyTrueDocuments]); // Use spread syntax to get converson to array
}

How to get element by id which is dynamically created by ng-repeat in angularjs

I am only posting the necessary code and solving this much will clear rest of my doubts. I am new to angularjs, so kindly forgive if I am asking something stupid.
I am using ng-repeat to generate a list which uses an array defined in the controller scope. When I click on 'Add Another' button, a new element is created. I want to get access of this element to add a class to it. But when I use 'getElementById' function in the same function 'addNewForm' I get 'null'.
However, when I call function 'fn' by hitting 'Find Untitled' button, I get the correct element. Could anybody explain and solve this? Any help is appreciated. Thanks in advance!
I am posting the code below:
HTML:
<div ng-controller="myctrl3">
<ul id ="menu_Ul">
<li ng-repeat="x in list">
<button id="{{ 'navAppsButtonID-' + $index }}">{{x}}</button>
<br>
</li>
<li>
<button ng-click="addNewForm()">Add another</button>
</li>
</ul>
<button ng-click="fn()">Find Untitled</button>
</div>
JS:
.controller("myctrl3", function($scope) {
var list = ['abcd', 'efgh', 'ijkl', 'mnop'];
$scope.list = list;
$scope.abc = function () {
var listPush = function () {
$scope.list.push("Untitled Menu");
for(var i = 0;i<$scope.list.length-1;i++) {
var element = document.getElementById('navAppsButtonID-'+i);
element.classList.remove('current');
}
};
var listLen = $scope.list.length;
if($scope.list[listLen-1] === undefined) {
listPush();
}
else if ($scope.list[listLen-1] == "Untitled Menu") {
alert("Cannot open more than one Untitled Menu at the same time.");
}
else {
listPush();
}
};
$scope.addNewForm = function() {
$scope.abc();
console.log("Element is: ", document.getElementById('navAppsButtonID-'+($scope.list.length-1)));
};
$scope.fn = function () {
console.log("Element is: ", document.getElementById('navAppsButtonID-'+($scope.list.length-1)));
};
})
You're thinking too much jQuery and too little angular. If the goal is to add a class to the last element of ng-repeat, this is how you do that:
<li ng-repeat="x in list">
<button ng-class="{ current: $last }">{{ x }}</button>
</li>
$last is a variable available inside ng-repeat, and if it's true, ng-class will set the class current on the element.
You don't assign unique ids to elements to getElementById from somewhere else when working in angular.

All the checkboxes inside a ng-repeat are getting checked when I select just one

I have list of objects named rolePermissionList like this:
[{"id":1,"name":"createUser","type":"user","marked":1},{"id":2,"name":"deleteUser","type":"user","marked":1},{"id":3,"name":"editRole","type":"role","marked":0}]
and I use ng-repeat to repeat checkboxes using the values in that list like this
<div class="form-group">
<label>Role Permissions:</label>
<div class="checkbox" ng-repeat="permission in rolePermissionList">
<label>
<input type="checkbox" ng-model="idsPermission[permission .idPermission ]"
ng-checked="permission.checked">{{permission.name}}
</label>
</div>
</div>
the ng-model of the checkboxes is named idsPermission and it's a list of numbers, those numbers are the IDS of the objects.
When I load the page the checkboxes that are supposed to be checked are checked this part works fine, but when I check another checkbox all the checkboxes gets checked, and when I uncheck a checkbox the same thing happens all the checkboxes gets unchecked.
I use that list of numbers named idsPermission to get all the IDS of the checkboxes that are checked, this worked before I used the directive ng-checked="permission.checked", but now I need to use it since now I need to show the checkboxes that are already marked.
this is my controller
angular.module('MyApp')
.controller('RolCtrl', ['$scope', 'RolService',
function ($scope, RolService) {
$scope.idsPermission = {};
$scope.getListCheckBoxesEditRole = function (idRole) {
$scope.selectRol.descripcion;
RolService.getListCheckBoxesEditRole(idRole)
.then(
function (d) {
var userPermissionList = [];
for (var permission in d) {
if (d[permission ].type === 'user') {
if (d[permission ].marked === 1)
{
d[permission ].checked = true;
userPermissionList.push(d[permission ]);
} else {
userPermissionList.push(d[permission ]);
}
}
}
$scope.rolePermissionList = userPermissionList;
},
function (errResponse) {
console.error('ERROR');
}
);
};
}
$scope.getListCheckBoxesEditRole(3);
]);
The RolService.getListCheckBoxesEditRole(idRole) service returns this JSON [{"id":1,"name":"createUser","type":"user","marked":1},{"id":2,"name":"deleteUser","type":"user","marked":1},{"id":3,"name":"editRole","type":"role","marked":0}]
and what I do in the controller is iterate over that list and check if the marked field is 1 if it's 1 I do this d[permission ].checked = true; I what I think that I do in that line is setting the checked value to true so I could use this directive in the html view ng-checked="permission.checked"
I tried doing this ng-checked="idsPermission[permission.checked]" but when I do this the values that are marked=1 in the JSON that I paste above don't appear checked when I load the page, but if I put it like this ng-checked="permission.checked" they appear marked as they should, but when I click a checkbox all the checkboxes gets selected.
I came across too many issues to document but the main problem was how you are iterating through the array that is returned from the service. It should be something like this:
Controller
angular.forEach(d.data, function(permission) {
if (permission.type === 'user') {
if (permission.marked === 1) {
permission.checked = true;
userPermissionList.push(permission);
} else {
userPermissionList.push(permission);
}
}
});
Then you can simplify your html like this:
HTML
<input type="checkbox" ng-model="permission.checked" />
You can see all of the changes in this working plunk.
I can't see in your code that $scope.idsPermission is getting defined. In ng-repeat you only set the key for the object but the value is undefined. That's why the checkbox won't show the correct value.
You could use ng-init to initialize the model. Please have a look at the simplified demo below or this fiddle.
(Also defining the models in your controller would be possible.)
Only using ng-model should be enough for the checkbox to work. I think I've read somewhere that ng-checked and ng-model aren't working smoothly together.
angular.module('demoApp', [])
.controller('mainCtrl', MainCtrl);
function MainCtrl() {
var vm = this;
angular.extend(vm, {
data: [{
id: 0,
marked: 1
}, {
id: 1,
marked: 0
}, {
id: 2,
marked: 1
}]
})
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="mainCtrl as ctrl">
<div ng-repeat="item in ctrl.data">
<input type="checkbox" ng-init="ctrl.idPermissions[item.id] = !!item.marked" ng-model="ctrl.idPermissions[item.id]"/>{{item.id}}
</div>
<pre>
permissions: {{ctrl.idPermissions | json: 2}}
data{{ctrl.data | json: 2}}</pre>
</div>

Angular: having a function as ng-false-value

In my Angular app, I have the following list of checkboxes, generated within a nested ng-repeat:
<div ng-repeat="partner in type.partners">
<label class="checkbox-inline">
<input type="checkbox"
ng-model="partners[$parent.$index][$index]"
ng-true-value="{{partner}}"
ng-change="handleCheckboxChanged({{type.id}})"
ng-checked="getChecked({{partner.id}})"
>
<p><span ></span>{{partner.name}}<p>
</label>
</div>
This will end up looking like the picture below:
When a user selects a partner with a chekcbox, this partner gets added to the nested list of selected partners, making use of ng-true-value and ng-model.
However, when a user deselects a checkbox, my object will still keep that key:value pair, except the value will now simply be false, as seen here in the console:
My intention would be that this key:value pair would disappear when a user unticks a checkbox, instead of it just becoming false.
Is it possible to do so by triggering a function as the ng-false-value?
What you described - this is a default behaviour. You can adjust your ngChange function.
Just a simple example, how you can make desired.
<div ng-repeat="partner in partners">
<label>
<input type="checkbox"
value="partner.id"
ng-model="partner.selected"
ng-change="changeValue(partner)"
/>
{{partner.name}}
</label>
</div>
var partnersList = [], idsArray = [];
$scope.changeValue = function(partner){
if(partner.selected)
addPartner(partner);
else
removePartner(partner);
};
var addPartner= function(partner){
if(!existInList(partner))
partnersList.push(partner);
};
var removePartner= function(partner){
idsArray = getIdsArray();
var indexToRemove = idsArray.indexOf(partner.id);
if(indexToRemove == -1)
return;
partnersList.splice(indexToRemove, 1);
};
var existInList = function(partner){
idsArray = getIdsArray();
return idsArray.indexOf(partner.id) != -1;
};
var getIdsArray = function(){
return partnersList.map(function(partner){ return partner.id });
};
Link to JSFiddle example.

How to filter through a table using ng-repeat checkboxes with Angularjs

Once upon a time this was working but somehow it's broken. I want to be able to produce checkboxes using ng-repeat to get as many checkboxes as required based on stored data and use these to filter through a table produced.
Additionally I don't want identical values for the checkboxes to be repeated.
I have made a plnkr with the code.
<div class="row">
<label data-ng-repeat="x in projects">
<input
type="checkbox"
data-ng-true-value="{{x.b}}"
data-ng-false-value=''
ng-model="quer[queryBy]" />
{{x.b}}
</label>
</div>
http://plnkr.co/edit/RBjSNweUskAtLUH3Ss6r?p=preview
So in summary.
Checkboxes to filter Ref.
Checkboxes to be unique.
Checkboxes to be made based off ng-repeat using Ref.
Okay, here's how to do it.
First, let's add a couple of lines of CSS in your to make sure all the checkboxes are visible:
<style>
.row { margin-left: 0px }
input[type=checkbox] { margin-left: 30px; }
</style>
Next, add the following lines to your controller:
app.filter('unique', function() {
return function (arr, field) {
var o = {}, i, l = arr.length, r = [];
for(i=0; i<l;i+=1) {
o[arr[i][field]] = arr[i];
}
for(i in o) {
r.push(o[i]);
}
return r;
};
})
app.controller("maincontroller",function($scope){
$scope.query = {};
$scope.quer = {};
$scope.queryBy = '$';
$scope.isCollapsed = true;
$scope.selectedRefs = [];
$scope.myFilter = function (item) {
var idx = $scope.selectedRefs.indexOf(item.b);
return idx != -1;
};
$scope.toggleSelection = function toggleSelection(id) {
var idx = $scope.selectedRefs.indexOf(id);
if (idx > -1) {
$scope.selectedRefs.splice(idx, 1);
}
else {
$scope.selectedRefs.push(id);
}
};
Phew.
For some reason, your Plunkr's version of AngularJS didn't recognise the unique attribute, so I added one to your controller.
Finally, change your html to this:
<div class="row">
<label data-ng-repeat="x in projects | unique:'b' | orderBy:'b'" >
<input
id="x.b"
type="checkbox"
ng-click="toggleSelection(x.b)"
ng-init="selectedRefs.push(x.b)"
ng-checked="selectedRefs.indexOf(x.b) > -1" />
{{x.b}}
</label>
</div>
... and your ng-repeat to this...
<tr ng-click="isCollapsed = !isCollapsed" ng-repeat-start="x in projects | filter:myFilter | orderBy:orderProp">
If you're interested in knowing how this works, add these lines:
<div style="margin:10px 10px 30px 10px">
<pre>{{ selectedRefs }} </pre>
</div>
I love this trick: you can see the exact contents of our "selectedRefs" array, and see it change as we tick/untick our checkboxes. This really helps when developing/testing our bindings!
As you can see, these changes use the new unique function to get your list of distinct values from your project array, and when the page first loads, we push all of the values into our new "selectedRefs" array.
["123","321","456","654","789","987"]
Then, as you tick/untick the checkboxes, we add/remove that item from this list.
Finally, we use that filter in the ng-repeat.
ng-repeat-start="x in projects | filter:myFilter | orderBy:orderProp"
Job done !
Update
If you wanted to start off with all checkboxes unticked, then it's a simple change. Just remove this line...
ng-init="selectedRefs.push(x.b)"
..and change the myFilter function to show all items initially..
$scope.myFilter = function (item) {
if ($scope.selectedRefs.length == 0)
return true;
var idx = $scope.selectedRefs.indexOf(item.b);
return idx != -1;
};
And to add a "Clear all" button, simply add a button to your form which calls a function in your AngularJS controller like this..
$scope.clearAll = function () {
$scope.selectedRefs = [];
};
(I haven't tested these suggestions though.)
ng-false-value directive needs a value set. Try ng-false-value='false' or ng-false-value='null' (in fact you can skip this one entirely if it has to just be a falsy value and not something concrete, like a string or certain number).
As you've pointed out in the comments, after selecting and then clearing the checkboxes, all rows are filtered out. It happens because unchecking the checkbox will set its value to false, and this does not agree with your entities' values (as you probably know, just stating it for others).
Therefore you do need to set this value to empty string in the end. That'd be the way:
$scope.$watch('quer.$', function () {
if ($scope.quer.$ === false) {
$scope.quer.$ = '';
}
});

Categories

Resources