Reputation: 1256
I'm having a peculiar issue some angular checkboxes
I create them in separate divs
, with different ids per checkbox
and they will be checked-unchecked depending on a set of data coming from a database.
I think this one is best understood with the code:
The html:
<div class="form-group row">
<label for="field_publication" class="col-md-2 form-control-label">Drug/s:</label>
<div class="btn btn-sm btn-primary">
<input type="checkbox" id="check1" ng-checked="drugTypeIds.indexOf(1)!==-1" ng-click="updateSelection(1, publication, 'antimalarials')"> Antimalarial
</div>
<div class="btn btn-sm btn-primary">
<input type="checkbox" id="check2" ng-checked="drugTypeIds.indexOf(2)!==-1" ng-click="updateSelection(2, publication, 'antibiotics')"> Antibiotic
</div>
<div class="btn btn-sm btn-primary">
<input type="checkbox" id="check3" ng-checked="drugTypeIds.indexOf(3)!==-1" ng-click="updateSelection(3, publication, 'antiretrovirals')"> Antiretroviral
</div>
<div class="btn btn-sm btn-primary">
<input type="checkbox" id="check4" ng-checked="drugTypeIds.indexOf(4)!==-1" ng-click="updateSelection(4, publication, 'antidiabetics')"> Antidiabetic
</div>
<div class="btn btn-sm btn-primary">
<input type="checkbox" id="check5" ng-checked="drugTypeIds.indexOf(6)!==-1" ng-click="updateSelection(6, publication, 'antituberculosis')"> Antituberculosis
</div>
<div class="btn btn-sm btn-primary">
<input type="checkbox" id="check6" ng-checked="drugTypeIds.indexOf(5)!==-1" ng-click="updateSelection(5, publication, 'maternal and reproductive')"> Maternal Health
</div>
</div>
So, the ng-checked
is telling the code to look for the id of the object amongst the ones in an array that I've created once the data has been uploaded to the form, this happens in the controller, with this method:
$scope.drugTypeIds = null;
$scope.checkboxes = function (publicationDrugTypes) {
$scope.drugTypeIds = [];
publicationDrugTypes.forEach(function (typeDrug) {
$scope.drugTypeIds.push(typeDrug.id);
});
};
So, here the drugTypeIds
is initialized with a null value until the data is loaded, once the data is loaded it will go to the variable publicationDrugTypes
of the publication
object and pass that as a parameter to the checkboxes function:
$scope.checkboxes(formDTO.publication.publicationDrugTypes);
Now the array(drugTypeIds
) which was null will get the values of the ids that are in the publicationDrugTypes
variable and the HTML
code will check the right boxes. Up to here it works just fine
Now, if the user wants to add/remove drugTypes
from the publication
, the HTML uses the updateSelection()
code from the controller:
$scope.updateSelection = function (drugTypeId, publication, type){
if ($scope.drugTypeIds.indexOf(drugTypeId)!==-1){
publication.publicationDrugTypes.splice(publication.publicationDrugTypes.indexOf($scope.publicationDrugTypes[drugTypeId-1]),1);
$scope.drugTypeIds.splice($scope.drugTypeIds.indexOf(drugTypeId));
}
else {
publication.publicationDrugTypes.push({id:$scope.publicationDrugTypes[drugTypeId-1].id,name:$scope.publicationDrugTypes[drugTypeId-1].name}); $scope.drugTypeIds.push(drugTypeId);
};
This code is expecting the id of the new drugType
, the publication
we're going to add it to and the type (this variable is currently unused)
So, now it checks if the id was in the previous list, if it was, it will erase it, if not, it will add it.
The adding works just fine in both the frontend and the database, it's when splicing that I get several checkboxes
unchecking themselves at the same time, I can't figure out why as they all have different ids, different conditions and no particular model attached.
I have adapted the code in a jsfiddle, the error is only reproduceable the first time you run it (unlike in my application where it happens every time), and you need to click the button first so the code from the first method will run. The error will occur when you attempt to uncheck Antidiabetic checkbox
Upvotes: 2
Views: 285
Reputation: 5764
Part of the problem is that the IDs in the options of the HTML don't correspond to the IDs in your controller code. For example, the HTML lists Option 2, "Antibiotic", as having ID value 2 -- but in the controller code, id of 2 is assigned to "Antidiabetic".
At least, ^ is why the wrong checkboxes are checked.
Also, I suggest removing the options from manual listing in the HTML and making your data only in the controller, then letting ngRepeat and Angular bind and display your checkboxes directly based on the state of the Controller's loaded model (the Publication).
Also, you're using the "publication" object as a model for tracking the drugs list, as well as the list of drug type IDs -- I often run into issues when I double-track things, so I also simplified in my example, and I only use the publication object in the view for binding data. I find this a little easier to read as well as and easier to test and adapt.
app.js
var app = angular.module('stevenApp', ['ngMaterial']);
/**
*@summary
* "StevenApp" has three components:
* (1) Main Ctrl - the main page controller
* the checkbox() function is here
* (2) PublicationsService -
* Create, track and manage the drugs list
* for publications;
* A Publication ~ {id:,drugTypeIds:,addDrug(),removeDrug()}
* (3) DrugsService -
* Maintain a "master list" of all drugs, or get it.
* return, filter/sort and track drugs by TYPE, ID and NAME
*
*
*/
app.service('DrugsService', [function() {
var Master_Drug_Types = [
{id:1,name:'Antimalarial'},
{id:2,name:'Antibiotic'},
{id:3,name:'Antiretroviral'},
{id:4,name:'Antidiabetic'},
{id:5,name:'Antituberculosis'},
{id:6,name:'Maternal Health'}
];
function getAllDrugTypes() {
return Master_Drug_Types;
}
function getAllDrugTypeIDs() {
return _.pluck(Master_Drug_Types, 'id');
}
function getDrugByName(drugName) {
return (_.findWhere(Master_Drug_Types, {name: drugName})) || {};
}
return {
getAllDrugs: getAllDrugTypes,
getAllDrugIDs: getAllDrugTypeIDs,
getDrugTypes: getAllDrugTypes,
getDrugByName: getDrugByName
};
}])
// Create an Object/Model for Publication data
// > Constructor, properties and methods for "Publication" Objects
app.factory('PublicationModel', [function() {
Publication.serial = 0;
/**
* Model/Class Constructor
*/
function Publication(name) {
this.id = Publication.serial++;
this.name = name;
this.publicationDrugTypes = [];
}
/**
* Model/Object methods
*/
Publication.prototype.addDrug = function (drug) {
this.publicationDrugTypes.push(drug);
};
Publication.prototype.removeDrug = function (drug) {
var drugById = _.findWhere(this.publicationDrugTypes, {id: drug.id});
var drug_index = _.findIndex(this.publicationDrugTypes, {id: drug.id}); // find in a collection of values ~ indexO
// console.log('REMOVE drug ', drug);
// console.log('THIS ', drugById);
// console.log('@ ', drug_index);
if (drug_index >= 0) {
this.publicationDrugTypes.splice(drug_index, 1);
}
};
return Publication;
}])
// > manage LIST of Publications and stuff related to
// multiple/all publications
app.service('PublicationsService', ['PublicationModel', function(Publication) {
var publications = [];
function getDrugTypesByPublication(targetPubId) {
return (_.findWhere(publications, {id: targetPubId}).publicationDrugTypes) || [];
}
function createNewPublication(name) {
var newPub = new Publication(name);
// STORE
publications.push(newPub);
return newPub;
}
return {
create: createNewPublication,
drugsByPubId: getDrugTypesByPublication
};
}]);
// > Das Main Controller
app.controller('MainCtrl', ['$scope', 'PublicationsService', 'DrugsService',
function($scope, Publications, Drugs) {
/*
We'll use "publication.publicationDrugTypes"
to track the current drugs.
Also, I've assumed there might be more than 1
Publication, so the controller's list of checked
drugTypes will be the same as the "currently loaded"
Publication's .publicationDrugTypes.
*/
$scope.publication = {};
$scope.allAvailableDrugs = [];
// Init - I assumed this button click was a demo
// for loading the controller state, which was
// when you saw the weird behavior.
$scope.checkboxes = function () {
activateController();
};
$scope.updateSelection = function (drug) { // TypeId, publication, type) {
// Is the Drug in the Publication?...
/* Using the other components allows simplifying readability like this :) */
if ( $scope.drugIsPrescribed(drug) ) {
// IF Drug-Type NOT currently selected/checked,
// REMOVE it FROM the publication-listing of drugs...
$scope.publication.removeDrug(drug);
} else {
// ELSE
// ADD it TO the publication-listing of drugs...
$scope.publication.addDrug(drug);
}
};
$scope.drugIsPrescribed = function(drug) {
// CHECK the publication for drug prescribed...
return _.findWhere($scope.publication.publicationDrugTypes, {id: drug.id})
}
$scope.prescribeAllTheDrugs = function() {
_.each($scope.allAvailableDrugs, function(d) {
if (!$scope.drugIsPrescribed(d)) {
$scope.publication.addDrug(d);
}
});
}
$scope.unprescribeAllTheDrugs = function() {
_.each($scope.allAvailableDrugs, function(d) {
if ($scope.drugIsPrescribed(d)) {
$scope.publication.removeDrug(d);
}
});
}
// ==========
function activateController() {
$scope.publication = Publications.create("Sick Mother's Milk");
// Arbitrary -- I saw you had these 3 selected at first, so I added them on load().
$scope.publication.addDrug( Drugs.getDrugByName('Antimalarial') );
$scope.publication.addDrug( Drugs.getDrugByName('Antidiabetic') );
$scope.publication.addDrug( Drugs.getDrugByName('Maternal Health') );
$scope.allAvailableDrugs = Drugs.getAllDrugs();
}
}]);
index.html
<!DOCTYPE html>
<html ng-app="stevenApp">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="//rawgit.com/angular/bower-material/master/angular-material.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js
"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.6/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.6/angular-animate.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.6/angular-aria.js"></script>
<script src="//rawgit.com/angular/bower-material/master/angular-material.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl as ctrl">
<button ng-click="checkboxes()">click first</button>
<form ng-if="allAvailableDrugs">
<h4>Publication: {{ publication.name }}</h4>
<div>{{ publication.publicationDrugTypes | json }}</div>
<hr>
<div class="btn btn-sm btn-primary" ng-repeat="drug in allAvailableDrugs track by $index">
<input type="checkbox"
ng-click="updateSelection(drug)"
ng-checked="drugIsPrescribed(drug)" />
{{ drug.name }}
</div>
<br />
<a href="#" ng-if="publication.publicationDrugTypes" ng-click="prescribeAllTheDrugs()">SELECT ALL</a>
<br/>
<a href="#" ng-if="publication.publicationDrugTypes" ng-click="unprescribeAllTheDrugs()">DE_SELECT ALL</a>
</form>
</body>
</html>
Plunkr: https://embed.plnkr.co/0oc5MR/
Upvotes: 1
Reputation: 9988
You had few issues managing the insertion/removal and the check of the elements. I simplified a bit the logic. Here your example that should do what you are looking for:
var app = angular.module('CountApp',[]);
app.controller('CountCtrl',function($scope){
$scope.drugTypeIds = [];
$scope.publicationDrugTypes = [
{ id:1, name:'Antimalarial' },
{ id:4, name:'Antidiabetic' },
{ id:3, name:'Antiretroviral' }
];
$scope.publication = {id:1, publicationDrugTypes:$scope.publicationDrugTypes};
$scope.isPublicationChecked = function (id) {
return findElementInPublication(id) > -1;
};
$scope.updateSelection = function (drugTypeId, type){
var elementIndex = findElementInPublication(drugTypeId);
if (elementIndex === -1) {
$scope.publicationDrugTypes.push({ id: drugTypeId, name: type });
}
else {
$scope.publicationDrugTypes.splice(elementIndex, 1);
}
console.log($scope.publication);
};
function findElementInPublication(id) {
return $scope.publicationDrugTypes.findIndex(function(el) { return el.id === id; });
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div data-ng-app="CountApp" data-ng-controller="CountCtrl">
<div class="btn btn-sm btn-primary">
<input type="checkbox" id="check1" ng-checked="isPublicationChecked(1)" ng-click="updateSelection(1, 'antimalarials')"> Antimalarial
</div>
<div class="btn btn-sm btn-primary">
<input type="checkbox" id="check2" ng-checked="isPublicationChecked(2)" ng-click="updateSelection(2, 'antibiotics')"> Antibiotic
</div>
<div class="btn btn-sm btn-primary">
<input type="checkbox" id="check3" ng-checked="isPublicationChecked(3)" ng-click="updateSelection(3, 'antiretrovirals')"> Antiretroviral
</div>
<div class="btn btn-sm btn-primary">
<input type="checkbox" id="check4" ng-checked="isPublicationChecked(4)" ng-click="updateSelection(4, 'antidiabetics')"> Antidiabetic
</div>
<div class="btn btn-sm btn-primary">
<input type="checkbox" id="check5" ng-checked="isPublicationChecked(6)" ng-click="updateSelection(6, 'antituberculosis')"> Antituberculosis
</div>
<div class="btn btn-sm btn-primary">
<input type="checkbox" id="check6" ng-checked="isPublicationChecked(5)" ng-click="updateSelection(5, 'maternal and reproductive')"> Maternal Health
</div>
</div>
jsfiddle: https://jsfiddle.net/4vnuL4eL/16/
Upvotes: 1
Reputation: 1690
You are not using your data correctly. This line is just wrong.
publication.publicationDrugTypes.splice(publication.publicationDrugTypes.indexOf($scope.publicationDrugTypes[drugTypeId-1]),1);
You are using drugTypeId as an index into the publicationDrugTypes array and that is not what it is at all. In this particular case you are trying to use drugTypeId-1, which is 4-1=3, and trying to index publicationDrugTypes[3] which isn't even a valid value as there are not that many values in your array. You really should rework the way you are doing this. One approach would be to have your publicationDrugTypes be an array of all your possible values and have an additional field on each that indicates if included or not.
$scope.publicationDrugTypes = [{id:1,name:'Antimalarial',included:0},
{id:2,name:'whatever',included:0},{id:4,name:'Antidiabetic',included:0},{id:3,name:'Antiretroviral',included:0}];
I'm only show 4 of them here. You can then use an ng-repeat to display the checkboxes and assign the ng-value to be the object.included field. Now when you check or uncheck a box it will get updated. When you are ready to submit your data you simply can use a _find on your array of publicationDrugTypes for all the values with included=1. There are many different ways to do this and make this much cleaner.
Upvotes: 0