Reputation: 6868
Now in my records :
14.3, 14.2 and 14.1 belongs to part with Id =30.
I am trying to achieve below:
1) By default first 2 ids will be selected.Now if user try to select id = 71 which belongs to part 30 then user should not be allowed to select id=71 because higher version of part 30 is already selected i.e id=76.
2) Now if user uncheck id = 77(33) then user should be allowed to check id=71 because now there is no different part selected so user should be allow to check all part with id = 30 but as soon as user select different part then lower part should be uncheck.
Problem with my code :
1) When I uncheck 16.1 and try to check 14.2 then I am not allowed to check it. I should allow to check 14.2 as now there is no different parts here.
2) 16.1 and 14.3 are check by default. Now when I check 15.1 and then again check 14.1 then 14.3 gets uncheck which is wrong as 14.3 is highest among part id=30 so I should not be able to check 14.1.
var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
$scope.myArray = [
{
"id": 77,
"selected": true,
"part": 33,
"name": "16.1",
},
{
"id": 76,
"part": 30,
"selected": true,
"name": "14.3",
},
{
"id": 71,
"part": 30,
"selected": false,
"name": "14.2",
},
{
"id": 70,
"part": 31,
"selected": false,
"name": "15.1",
},
{
"id": 69,
"part": 30,
"selected": false,
"name": "14.1",
},
{
"id": 68,
"part": 29,
"selected": false,
"name": "13.1",
},
{
"id": 55,
"part": 26,
"selected": false,
"name": "12.1",
}
,
{
"id": 54,
"part": 25,
"selected": false,
"name": "11.2",
}
,
{
"id": 53,
"part": 25,
"selected": false,
"name": "11.1",
}
];
$scope.checkItem = function (item) {
if (item.selected) {
var index = $scope.myArray.map(m=>m.id).indexOf(item.id);
var previousPart = {};
for (var i = index - 1; i >= 0; i--) {
if ($scope.myArray[i].selected) {
previousPart = $scope.myArray[i];
break;
}
}
if (item.part != previousPart.part) {
for (var i = 0; i < $scope.myArray.length; i++) {
if (($scope.myArray[i].part == item.part && $scope.myArray[i].part != item.part)
&& $scope.myArray[i].selected) {
$scope.myArray[i].selected = false;
break;
}
}
}
else
item.selected = false;
}
};
});
<!DOCTYPE html>
<html ng-app="myApp" ng-controller="myCtrl">
<head>
<title></title>
<meta charset="utf-8" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body>
<div ng-repeat="item in myArray">
<input ng-model="item.selected" ng-click="checkItem(item)" type="checkbox" />{{ item.name }}
</div>
</body>
</html>
Upvotes: 14
Views: 455
Reputation: 150
I would take a different approach. Separating some of the logic into a service helps me keep the code cleaner. It will be easier to maintain, debug and test. fiddle
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myCollection) {
$scope.myArray = myCollection.items;
// Call function every time an item was checked
$scope.checkItem = function(item) {
// Remap collection items before and after
myCollection.mapCheckedItems();
// Do something with the user's interaction only if there are
// different selected parts by definition
if (item.selected && myCollection.selectedPartsOnly.length > 1) {
// Iterate through the selected parts
myCollection.selectedPartsOnly.map(part => {
// Get the array of items belonging to the exact part number
var map = myCollection.selectedPartsMap[part];
for (var j = map.length - 1; j > 0; j--) {
// By definition deselect all but the highest version.
// Happens if different part numbers selected simultaneously
map[j].selected = false;
}
})
}
// Recalculate collection map to keep it up to date with the
// items' array
myCollection.mapCheckedItems();
};
});
app.service('myCollection', function() {
// Init the collection object
var self = {};
// Function to calculate and map items' dependencies
self.mapCheckedItems = mapCheckedItems;
// This holds a list of the selected unique part numbers
// Used to determine needed action easier. By definition if only
// one part number is selected and used to iterate through the map
self.selectedPartsOnly = [];
// This is the important dictionary where the part numbers is mapped
// to its child items. Easy access to items grouped by a part number
self.selectedPartsMap = {};
// The actual array definition
self.items = [{
"id": 77,
"selected": true,
"part": 33,
"name": "16.1",
}, {
"id": 76,
"part": 30,
"selected": true,
"name": "14.3",
}, {
"id": 71,
"part": 30,
"selected": false,
"name": "14.2",
}, {
"id": 70,
"part": 31,
"selected": false,
"name": "15.1",
}, {
"id": 69,
"part": 30,
"selected": false,
"name": "14.1",
}, {
"id": 68,
"part": 29,
"selected": false,
"name": "13.1",
}, {
"id": 55,
"part": 26,
"selected": false,
"name": "12.1",
}, {
"id": 54,
"part": 25,
"selected": false,
"name": "11.2",
}, {
"id": 53,
"part": 25,
"selected": false,
"name": "11.1",
}];
// Init the helpers once on start. This will be executed only once
mapCheckedItems();
// Return the service object to be accessed from the controller
return self;
// This function will create and update the objects mapping
function mapCheckedItems() {
// Reset the helpers
self.selectedPartsOnly = [];
self.selectedPartsMap = {};
// Now we iterate through the selected items.
self.items
.filter(item => item.selected)
.map(item => {
// Map every selected item directly to one part number
mapSelectedParts(item);
// Determine what part numbers are in use.
mapSelectedPartsOnly(item.part);
})
}
function mapSelectedPartsOnly(part) {
if (self.selectedPartsOnly.indexOf(part) == -1)
self.selectedPartsOnly.push(part);
}
function mapSelectedParts(item) {
if (!self.selectedPartsMap[item.part])
self.selectedPartsMap[item.part] = [];
self.selectedPartsMap[item.part].push(item);
}
})
<!DOCTYPE html>
<html ng-app="myApp" ng-controller="myCtrl">
<head>
<title></title>
<meta charset="utf-8" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body>
<div ng-repeat="item in myArray">
<input ng-model="item.selected" ng-click="checkItem(item)" type="checkbox" />{{ item.name }}
</div>
</body>
</html>
Upvotes: 3
Reputation: 1570
I've created a solution which will achieve your both points and resolve your problems as mentioned above.
You need to update checkItem
function in controller only
$scope.checkItem = function(item) {
if (item.selected) {
var otherExists = $scope.myArray.filter(function(m) {
return ((m.part != item.part) && m.selected);
}).length;
var selfExists = $scope.myArray.filter(function(m) {
return ((m.part == item.part) && m.selected);
}).length - 1;
if (!!otherExists) {
if (!!selfExists) {
var selectedIsLower = $scope.myArray.filter(function(m) {
return ((m.part == item.part) && (m.id > item.id) && m.selected);
}).length;
if (!!selectedIsLower) {
item.selected = false;
} else {
$scope.myArray.filter(function(m) {
return (m.part == item.part);
}).forEach(function(m) {
m.selected = false;
});
item.selected = true;
}
} else {
item.selected = false;
var allSelectedIds = [];
var allSelected = $scope.myArray.filter(function(m) {
return m.selected;
});
allSelected.map(m => m.part).forEach(function(a) {
if (allSelectedIds.indexOf(a) < 0) { //skip duplicate id
allSelectedIds.push(a);
}
});;
if (allSelectedIds.length == 1) {
var maxEle = {};
allSelected.forEach(function(m, count) {
if (maxEle.id) {
(maxEle.id > m.id) && (m.selected = false);
}
maxEle.id = m.id;
maxEle.count = count;
});
}
item.selected = true;
}
}
}
};
Working fiddle
Upvotes: 1