I tried to achieve a simple feature with AngularJS as below. In item list, when user clicks an item and click the Remove button then the item will be removed.
html:
<div ng-app="app" ng-controller="MainController">
<div>
<select ng-options="item.name for item in items" ng-model="currentItem" size="5" style="width: 200px"></select>
</div>
<button ng-click="removeItem()">Remove</button>
</div>
and script is like below:
angular.module('app', [])
.controller('MainController', function($scope) {
$scope.items = [{
name: 'item1'
}, {
name: 'item2'
}, {
name: 'item3'
}];
$scope.currentItem = $scope.items[0];
$scope.removeItem = function() {
var index = $scope.items.indexOf($scope.currentItem);
$scope.items.splice(index, 1);
};
});
The problem is when I tried to remove an item (i.e. item2), the list always shows an empty item in the first position. When I click 'item1' or 'item3', the empty item disappears.
I know that this is caused by ng-model="currentItem" in html. The item that currentItem points to get removed, currentItem points to null. So I changed the function removeItem as below to solve this issue.
$scope.removeItem = function() {
var index = $scope.items.indexOf($scope.currentItem);
$scope.items.splice(index, 1);
/* PART 1 begin */
if ($scope.items.length === 0) {
$scope.currentItem = null;
} else {
if (index >= 0 && index <= $scope.items.length - 1) {
$scope.currentItem = $scope.items[index];
} else {
$scope.currentItem = $scope.items[$scope.items.length - 1];
}
}
/* PART 1 end */
};
I would like to know whether there is any simple way (like a directive) in AngularJS to do the action in PART 1 automatically.
There is simple way in which you can prevent that is just include
<option value="" ng-show="false"></option>
in select like as shown below
<select ng-options="item as item.name for item in items" ng-model="currentItem" size="5" style="width: 200px">
<option value="" ng-show="false"></option>
</select>
Working Demo
UPDATE 1
I have resolved the issue of not highlighting the last item, Take a look the working demo
$scope.removeItem = function () {
var index = $scope.items.indexOf($scope.currentItem);
$scope.items.splice(index, 1);
index === $scope.items.length ? $scope.currentItem = $scope.items[index - 1] : $scope.currentItem = $scope.items[index];
};
Working Demo
Related
After I load all topics from the database I need to select the ones for specific user. How can I do that?
<div ng-repeat="item in topicEditItems">
<md-checkbox ng-checked="existsEditTopic(item, topicEditSelected);" ng-click="toggleEditTopic(item, topicEditSelected);" value="{{item.TopicID}}">
{{item.TopicName}}
</md-checkbox>
</div>
$scope.topicEditSelected = [];
adminService.getTopics().then(function (response) {
$scope.topicEditItems = $.parseJSON(response.data);
//Select topics
})
$scope.toggleEditTopic = function (item, list) {
var idx = list.indexOf(item);
if (idx > -1) {
list.splice(idx, 1);
}
else {
list.push(item);
}
};
$scope.existsEditTopic = function (item, list) {
return list.indexOf(item) > -1;
};
The easiest way is having 2 checkbox mimic to the original 1 then show and hide based on your filter. You may need to apply md-checkbox styling into the fake one
<input type="checkbox" class="check" aria-label="Cb user"
ng-model="cbFake" ng-if="isTopicExist(item.id)" ng-checked="isTopicExist(item.id)"/>
<md-checkbox ng-checked="exists(item, selected)" ng-click="toggle(item, selected)" aria-label="Cb user"
ng-model="cbOri" ng-if="!isTopicExist(item.id)"> </md-checkbox>
update $scope.existsEditTopic to
$scope.existsEditTopic = function (item, list) {
var index = list.indexOf(item);
if(index > -1){
return true;
}else{
return false;
}
};
Is it possible to use shift and mouse click to select multiple elements on a table using AngularJS?
I have a table in which the first column is a checkbox and I would like to use SHIFT key and mouse click in order to select multiple rows continuously and can do things like delete, edit them etc.
Example by steps:
Click on 1st row's checkbox.
Hold down SHIFT key.
Click on 10th row's checkbox.
Result: the first 10 rows will be selected.
Does anyone know how this can be done using AngularJS?
I had a similar requirement. And while it's true that the proper way to update the checkboxes is by directly updating the model I wanted a more general solution.
So I built a pair of directives so I can reuse it on any checkbox list. Basically you wrap all the checkboxes with <multi-checkbox-container> and then add a multi-checkbox attribute to each checkbox. The code does the rest. Simple and easy.
angular
.module('app', [])
.controller('MainController', function($scope) {
var vm = this;
$scope.checks = {};
$scope.botigues = [1, 2, 3, 4, 5, 6];
})
.component('multiCheckboxContainer', {
controller: function () {
var ctrl = this;
var checkboxes = [];
var checkboxModels = [];
var previousClickedCheckbox = null;
ctrl.addCheckbox = addCheckbox;
ctrl.onCheckboxClick = onCheckboxClick;
function addCheckbox(checkbox, checkboxModelCtrl) {
checkboxes.push(checkbox);
checkboxModels.push(checkboxModelCtrl);
}
function onCheckboxClick(checkbox, shiftKey) {
var start, end, i, checking;
if (shiftKey && previousClickedCheckbox) {
checking = checkbox.prop('checked')
start = checkboxes.indexOf(previousClickedCheckbox);
end = checkboxes.indexOf(checkbox);
if (start > end) {
start = start + end;
end = start - end;
start = start - end;
}
for (i = start; i <= end; i++) {
checkboxes[i].prop('checked', checking);
checkboxModels[i].$setViewValue(checking);
}
}
previousClickedCheckbox = checkbox;
}
}
})
.directive('multiCheckbox', function () {
return {
restrict: 'A',
require: ['^^multiCheckboxContainer', 'ngModel'],
link: function (scope, element, attrs, controllers) {
var containerCtrl = controllers[0];
var ngModelCtrl = controllers[1];
containerCtrl.addCheckbox(element, ngModelCtrl);
element.on('click', function (ev) {
containerCtrl.onCheckboxClick(element, ev.shiftKey);
});
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.min.js"></script>
<div ng-app="app" ng-controller="MainController as vm">
<multi-checkbox-container>
<div ng-repeat="botiga in botigues">
<input type="checkbox" multi-checkbox ng-model="checks[botiga]">
<label>Botiga {{botiga}}</label>
</div>
</multi-checkbox-container>
<p>checks = {{ checks }}</p>
</div>
Not a complete solution, but this should work for you.
http://jsfiddle.net/AvGKj/705/
just keep track of each lastChecked checkbox, and on shift+click, mark all the checkboxes as checked.
<input type="checkbox" ng-checked = 'appObj.checked' ng-click="checked($index, $event)">
$scope.checked = function($index, $event){
if($scope.lastChecked && $event.shiftKey){
for(i=$scope.lastChecked; i<$index;i++){
$scope.myAppObjects[i].checked=true;
}
}
$scope.myAppObjects[$index].checked=true;
$scope.lastChecked = $index;
}
this code would only work if you ckeck from 0 - positive integer , but not reverse, you wuld require to do some modification to make it work completely.
hope this helps
Below presented the solution (Special thanks to Naeem Shaikh):
http://jsfiddle.net/dmakris/AvGKj/709/
HTML code:
<div ng-controller="MyCtrl">
<table class='table table-bordered'>
<tr ng-repeat="obj in myObjects">
<td>{{obj.id}}
<input type="checkbox" ng-checked = 'obj.checked' ng-click="checked($index, $event)">
</td>
<td>test {{obj.id}}</td>
- </tr>
</table>
</div>
Javascript (AngularJS) code:
function MyCtrl($scope) {
$scope.myObjects = [{id: 1, checked:false}, {id: 2, checked:false},
{id: 3, checked:false}, {id: 4, checked:false},
{id: 5, checked:false}, {id: 6, checked:false},
{id: 7, checked:false}];
$scope.checked = function($index, $event){
if(typeof $scope.lastChecked !='undefined' && $event.shiftKey){
for(i=$scope.lastChecked; i<=$index; i++){
$scope.myObjects[i].checked=true;
}
}
$scope.lastChecked = $index;
$scope.myObjects[$index].checked=true;
}
}
I'm working with list of checkboxes and I have next logic behavior for it:
if all items selected, checkbox "select all" is checked
if one of all selected items has been unchecked, checkbox "select all" is unchecked as well
This logic is clear. Depends of what item is checked I extract its id to an additional array and then using this array for request that to get data.
For pushing everything works fine, but for slicing the logic is strange. So I can slice the array until first item is checked, however I unchecked the first item, pushed and sliced items no more related with checkboxes.
I have reproduced plunker with it, so I appreciate if anybody could help me to find what I'm missing.
$scope.modelClass = {
selectedAll: false
};
$scope.selectAllClass = function (array) {
angular.forEach(array, function (item) {
item.selected = $scope.modelClass.selectedAll;
$scope.param =''
});
};
$scope.checkIfAllClassSelected = function (array) {
$scope.modelClass.selectedAll = array.every(function (item) {
return item.selected == true
});
$scope.checked = array.filter(function (item) {
return item.selected == true
}).length;
angular.forEach(array, function (obj) {
if(obj.selected == true){
requestClass(obj)
}
});
};
var selectedClass = [];
var requestClass = function (obj) {
selectedClass.push(obj);
angular.forEach(selectedClass, function (val) {
if (val.selected != true) {
selectedClass.splice(selectedClass.indexOf(val.id), 1);
}
else {
selectedClass = selectedClass.filter(function (elem, index, self) {
return index == self.indexOf(elem);
})
}
});
$scope.param = _.map(selectedClass, 'id')
};
$scope.classes = [
{"id":4,"name":"Achievement","selected":false},
{"id":13,"name":"Information","selected":false},
{"id":6,"name":"Issue","selected":false},
{"id":5,"name":"Message","selected":false},
{"id":9,"name":"Request","selected":false}
]
The logic looks good for me, not sure what's wrong here. I've took the first solution from this post (it looks like you are using the second one) and slightly modified it for your needs.
$scope.model = {
selectedClass : []
}
$scope.isSelectAll = function(){
$scope.model.selectedClass = [];
if($scope.master){
$scope.master = true;
for(var i=0;i<$scope.classes.length;i++){
$scope.model.selectedClass.push($scope.classes[i].id);
}
}
else{
$scope.master = false;
}
angular.forEach($scope.classes, function (item) {
item.selected = $scope.master;
});
$scope.param = $scope.model.selectedClass
}
$scope.isChecked = function() {
var id = this.item.id;
if(this.item.selected){
$scope.model.selectedClass.push(id);
if($scope.model.selectedClass.length == $scope.classes.length ){$scope.master = true;
}
} else {
$scope.master = false;
var index = $scope.model.selectedClass.indexOf(id);
$scope.model.selectedClass.splice(index, 1);
}
$scope.param = $scope.model.selectedClass
}
$scope.classes = [
{"id":4,"name":"Achievement","selected":false},
{"id":13,"name":"Information","selected":false},
{"id":6,"name":"Issue","selected":false},
{"id":5,"name":"Message","selected":false},
{"id":9,"name":"Request","selected":false}
]
html
<div ng-class="{'selected': master, 'default': !master}">
<div>
<input type="checkbox" ng-model="master" ng-change="isSelectAll()" > Select all
</div>
</div>
<div ng-repeat="item in classes | orderBy : 'id'" ng-class="{'selected': item.selected, 'default': !item.selected}">
<div >
<input type="checkbox" ng-model="item.selected" ng-change="isChecked()">
{{ item.name }}
</div>
</div>
this is fixed plunker
I have an angular page that will have a dynamic number of select elements on it. Each select will have the same option collection but once an option is selected from one, that option should be removed from all of the subsequent select elements.
I found this: http://jsfiddle.net/Zv5NE/63/ which works exactly how I'd like (when an option is selected from one select, it's removed from the others and then if that same select is changed, it adds the previously selected option back to the others).
The problem is, this is using a hard coded number of select elements and also using hard coded filters for each select element...that won't work for my purposes because, as I said, my users are going to need to be able to dynamically add n number of select elements.
I've done some playing around trying to create my own filter to accommodate for this, but I'm super green to angular (angular 1 btw) and I've hit the wall.
this is a small snippet from what I've tried. Essentially I've just tried creating an array and adding selected items to that array then checking against the values in the array for the filter (I would have to add some logic for changing options obviously, but I'm really not sure this is the right direction to go):
$scope.filter = function (item) {
for (i = 0; i < $scope.names.length; i++) {
if (item == $scope.names[i]) {
return false;
}
}
return true;
};
any guidance would be greatly appreciated.
I shelved this for a while but came back to it this morning. I was able to come up with a working solution.
Here's what I wrote up. May not be the most elegant way to do it, but it works for my purposes:
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta name="viewport" content="width=device-width" />
<title>AngularTest</title>
</head>
<body ng-controller="HellowWorldCtrl">
<select ng-model="selectname0" ng-options="item as item.name for item in classes | customFilter:'selectname0':this">
<option value="">- select -</option>
</select>
<div id="selectsDiv"></div>
<br />
<input type="button" value="Add Select" ng-click="addSelect()" ng-show="cnt < classes.length -1" />
<script src="~/Scripts/angular.js"></script>
<script type="text/javascript">
var app = angular.module('app', []).controller('HellowWorldCtrl', function ($scope, $compile) {
$scope.cnt = 0;
$scope.selectsAdded = [];
$scope.selectsAdded.push('selectname0');
$scope.addSelect = function () {
$scope.cnt++;
$scope.selectsAdded.push('selectname' + $scope.cnt);
var newSelect = $compile('<div><select ng-model="selectname' + $scope.cnt + '" ng-options="item as item.name for item in classes | customFilter:\'selectname' + $scope.cnt + '\':this"><option value="">- select -</option></select></div>')($scope);
angular.element(document.getElementById('selectsDiv')).append(newSelect);
};
$scope.classes = [
{
id: 1,
name: 'Biology 101',
courseid: '12345'
},
{
id: 2,
name: 'Chemistry 101',
courseid: '12374'
},
{
id: 3,
name: 'Psychology 101',
courseid: '32165'
},
{
id: 4,
name: 'Geology 101',
courseid: '78945'
},
{
id: 5,
name: 'Math 101',
courseid: '65478'
}
];
});
app.filter('customFilter', function () {
return function (items, which, scope) {
var alreadySelectedCourses = [];
var courses = [];
for (i = 0; i < items.length; i++) { // loop over all of the items in the class array...cwc
for (j = 0; j < scope.selectsAdded.length; j++) { // loop over all of the selects added to the page...cwc
if (which == scope.selectsAdded[j]) { // check if the calling select is the same one in the loop...cwc
if (scope['selectname' + j] && scope['selectname' + j].id) { // check if the calling select has alraedy been selected...cwc
if (scope['selectname' + j].id == items[i].id) { // check if the selected value of the calling select is the same as the item in the iteration and add it to the return array if so...cwc
courses.push(items[i]);
alreadySelectedCourses.push(items[i]);
}
}
} else { // not the calling select so find out the value and don't add it to the return array...cwc
if ((scope['selectname' + j] && scope['selectname' + j].id)) { // other selects (not calling select) have values selected so add them to the alreadyselectedarray...cwc
if (scope['selectname' + j].id == items[i].id) {
alreadySelectedCourses.push(items[i]);
}
}
}
}
if (alreadySelectedCourses.indexOf(items[i]) > -1) {
continue;
} else {
courses.push(items[i]);
}
}
return courses;
}
});
</script>
</body>
</html>
I would reset a select rank with angular but in my case , that does not work. If i try this code with the case (just after) of $scope.projetColumn. It's good.
In my controller :
$scope.projetColumn = [
{
id: 1,
group : "Backlog",
name : " = 'Backlog (Stock)'"
},
{
id: 2,
group : "In Progress",
name : "not in (New,'Backlog (Stock)',Closed)"
}
];
And then if i call this function , it's OK the select is reset :
In my controller :
$scope.resetDropDown = function() {
if(angular.isDefined($scope.projetAA)){
delete $scope.projetAA;
}
}
In my view :
PROJET <select name="selectProjet" class="form-control" ng-model="projetAA" ng-change="getProjetColumns(projetAA,column)" ng-options="item as item.name for item in projetColumn track by item.id"></select>
STATUT <select name="selectColumn" class="form-control" ng-model="column" ng-change="getStatut(column);" ng-options="item as item.name group by item.group for item in columnTab track by item.id"></select>
BUT the problem, in my case, the $scope.projetColumn (ng-option of the first select ) changes everytime that i change ng-option of the second select. It is not static.
When i change the second select, i execute this code which update the first select with data from ($scope.projetColumn)
$scope.getStatut = function (columnTab) {
$scope.makePromiseCards = cardsFactory.getListeAll(search);
$scope.makePromiseCards.then(function (value) {
$scope.projetColumn = [];
var objBase = {id: 0, name: ''};
$scope.projetColumn.push(objBase);
var cptG=1;
$scope.cards.forEach(function(entry){
entry.forEach(function(entry2){
if((entry2.fields !== undefined) && findProjectInObject(entry2.fields.project.key,$scope.projetColumn) === -1) {
var obj = {id: cptG, name: entry2.fields.project.key};
$scope.projetColumn.push(obj);
cptG++;
}
});
});
});
}
So , someone have an idea why the first select has always the same index and I can't reset the index at 0 even with the method of $scope.resetDropDown ?
Thank you by advance and sorry it's dificult to me to reproduce the bug with a plunker :/. I hope you will be able to understand my problem