I have a form that uses jqGrid for a section of the form. This section displays user info that can be selected via the checkbox. On the review page there is an option to edit the form and I'm trying to retain the checkbox selections during this process.
I'm trying to use window.localStorage for this however I'm not sure how to setItem properly. I'm calling getItem in the loadComplete and my coding may not be quite correct here either. What I have now, when a user goes to edit, selects the first item in the jqGrid instead of the actual saved item. I'm guessing because I haven't properly setItem. Can anyone offer guidance?
$.jgrid.gridUnload("#list");
myGrid = $("#list").jqGrid({
url: baseURL + '/scripts/get_user_list.php' + urlString,
datatype: "json",
mtype: 'POST',
width: 660,
height: '100%',
pager: '#pager',
rowNum: 10,
rowList: [20, 30, 40, 50, 60],
sortname: 'id',
sortorder: "asc",
viewrecords: true,
multiselect: true,
repeatitems: false,
imgpath: '/scripts/jquery-ui/images',
colNames: ['id', 'Building', 'Company ID', 'Description', 'Individual Name', 'SECCode'],
colModel: [
{name: 'id', index: 'id', jsonmap: 'id', hidden: true, width: 20},
{name: 'Building', index: 'Building', jsonmap: 'Building', hidden: true, width: 20},
{name: 'CompanyId', index: 'CompanyId', jsonmap: 'CompanyId', width: 110},
{name: 'Description', index: 'Description', jsonmap: 'Description', sortable: true, width: 300},
{name: 'IndName', index: 'IndName', jsonmap: 'IndName', width: 200},
{name: 'UsrNum', hidden: true, index: 'UsrNum', jsonmap: 'UsrNum'}
],
jsonReader: {
repeatitems: false,
root: 'rows',
id: '0',
cell: '',
subgrid: {
root: 'rows',
id: '0',
cell: '',
repeatitems: false
}
},
// subgrid support
subGrid: true,
subGridUrl: baseURL + '/scripts/get_user_list.php' + urlString,
subGridModel: [{
name: ['Date', 'ID'],
params: ['CompanyId'],
align: ['center', 'right'],
width: [150, 80]
}
],
ondblClickRow: function (id) {
$('#recId').val(id);
},
beforeRequest: function () {
blnGridLoading = true;
// console.log("beforeRequest(); setting blnGridLoading=true");
fnValidateAccount(); // Check that user has data available
},
loadComplete: function () {
$(':checkbox').each(function () {
var status = localStorage.getItem(this.id) === "false" ? false : true;
$(this).prop("checked", status);
});
blnGridLoading = false;
// console.log("loadcomplete(); setting
blnGridLoading = false;
// ");
for (swap in arySwap) {
if (typeof arySwap[swap]['CompanyId'] != 'undefined') {
$("#list").jqGrid('setSelection', arySwap[swap]['CompanyId']); // select companyId
}
}
fnValidateAccount(); // Check that user has data available
},
});
Here's the localStorage.getItem in loadComplate, isolated from the rest of the code:
$(':checkbox').each(function () {
var status = localStorage.getItem(this.id) === "false" ? false : true;
$(this).prop("checked", status);
});
Here's what I tried for setItem and where I'm not sure where to place this or if this is the correct way to go.
$(':checkbox').on('change', function () {
//set the check value of checkbox
localStorage.setItem(this.id, this.checked);
});
Placement
Your current placement of the getItem is correct. You should put the setItem block there too. Inside the loadComplete which means once the jqGrid is loaded, your getItem would run, also the setItem events would be bound to the checkboxes.
localStorage mechanism
localStorage only supports storing key value pairs and the values can be String only. Which means if you set a key customCheckbox to true and run a query for that key, the return value would be "true"
localStorage.setItem('test', true); // item set
localStorage.getItem('test') // returns "true"; string, not boolean.
However, there is a way to define one key and store an object to that. You should use JSON.stringify() before storing and JSON.parse() after retrieving.
let person = {name: 'John', age: 30, weight: 60} // a pure object.
localStorage.setItem('testPerson', JSON.stringify(person)); // object stringified;
localStorage.getItem('testPerson') // returns a stringified version of 'person' object;
// to use the stored stringified object like a regular object, you need to parse it.
person = JSON.parse(localStorage.getItem('testPerson'));
In your code
You have used different keys for each different checkboxes, thus your keys and values are wide open for the whole script or some other script on the same page. You might accidentally end up deleting or modifying the values of those keys, or maybe not. Also, other pages can modify those items, as they are on localStorage. So try to set as unique keys as possible.
Note: Open the browser inspector and check the storage tab to check your localStorage status, there might be a panel at the left with sessionStorage and localStorage
Logic
Now let's talk about your current logic. It looks like at the initial page load, when there is no keys sot to localStorage, all your checkbox would be checked. Is that intended ?
Even if it is, let's break it down:
var status = localStorage.getItem(this.id) === "false" ? false : true;
localStorage.getItem('key') would return null if there is no such key set. so null === "false" returns false and your ternary logic sets the status to true
Hence:
$(this).prop("checked", status); // sets all the checkboxes to checked at initial page load;
What you should do is:
Check if the key query returns a value with the typeof object, as null is a typeof object
If it's an object, set the status to false immediately. because when you set the value to true or false it'll be a string type, not object.
If it's not an object type, then check the value comparing to 'true' and 'false' and set accordingly.
Here is the final logic:
let status = typeof localStorage.getItem($(this).attr('id')) === 'object' ? false : localStorage.getItem($(this).attr('id')) === 'true';
Snippet
Finally some code, have a look at the snippet,
Note that the browser will return an error, because an iframe trying to modify localStorage. but extract the code at your end to view the results.
$(document).ready(function() {
$(':checkbox').each(function() {
let status = typeof localStorage.getItem($(this).attr('id')) === 'object' ? false : localStorage.getItem($(this).attr('id')) === 'true';
$(this).prop('checked', status);
// console.log(status, typeof localStorage.getItem($(this).attr('id')))
});
// setItem.
$(':checkbox').on('change', function() {
//set the check value of checkbox
localStorage.setItem($(this).attr('id'), $(this).prop('checked'));
console.log('updated, ', $(this).attr('id'));
console.log(localStorage);
});
$('#clear').on('click', function() {
localStorage.clear();
});
});
h3 {
margin: 40px 0;
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js'></script>
<div class="container">
<div class="row">
<div class="col-12">
<h3>Checkboxes status to LocalStorage</h3>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="customCheck1">
<label class="custom-control-label" for="customCheck1">One</label>
</div>
</div>
<div class="col-12">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="customCheck2">
<label class="custom-control-label" for="customCheck2">Two</label>
</div>
</div>
<div class="col-12">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="customCheck3">
<label class="custom-control-label" for="customCheck3">Three</label>
</div>
</div>
<div class="col-12">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="customCheck4">
<label class="custom-control-label" for="customCheck4">Four</label>
</div>
</div>
<div class="col-12">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="customCheck5">
<label class="custom-control-label" for="customCheck5">Five</label>
</div>
</div>
<div class="col-12">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="customCheck6">
<label class="custom-control-label" for="customCheck6">Six</label>
</div>
</div>
<div class="col-12">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="customCheck7">
<label class="custom-control-label" for="customCheck7">Seven</label>
</div>
</div>
<div class="col-12">
<div class="form-group"></div>
<button id="clear" type="button" class="btn btn-danger">Clear LocalStorage</button>
</div>
</div>
</div>
I have a few checkboxes:
<input type='checkbox' value="apple" checked>
<input type='checkbox' value="orange">
<input type='checkbox' value="pear" checked>
<input type='checkbox' value="naartjie">
That I would like to bind to a list in my controller such that whenever a checkbox is changed the controller maintains a list of all the checked values, for example, ['apple', 'pear'].
ng-model seems to only be able to bind the value of one single checkbox to a variable in the controller.
Is there another way to do it so that I can bind the four checkboxes to a list in the controller?
There are two ways to approach this problem. Either use a simple array or an array of objects. Each solution has it pros and cons. Below you'll find one for each case.
With a simple array as input data
The HTML could look like:
<label ng-repeat="fruitName in fruits">
<input
type="checkbox"
name="selectedFruits[]"
value="{{fruitName}}"
ng-checked="selection.indexOf(fruitName) > -1"
ng-click="toggleSelection(fruitName)"
> {{fruitName}}
</label>
And the appropriate controller code would be:
app.controller('SimpleArrayCtrl', ['$scope', function SimpleArrayCtrl($scope) {
// Fruits
$scope.fruits = ['apple', 'orange', 'pear', 'naartjie'];
// Selected fruits
$scope.selection = ['apple', 'pear'];
// Toggle selection for a given fruit by name
$scope.toggleSelection = function toggleSelection(fruitName) {
var idx = $scope.selection.indexOf(fruitName);
// Is currently selected
if (idx > -1) {
$scope.selection.splice(idx, 1);
}
// Is newly selected
else {
$scope.selection.push(fruitName);
}
};
}]);
Pros: Simple data structure and toggling by name is easy to handle
Cons: Add/remove is cumbersome as two lists (the input and selection) have to be managed
With an object array as input data
The HTML could look like:
<label ng-repeat="fruit in fruits">
<!--
- Use `value="{{fruit.name}}"` to give the input a real value, in case the form gets submitted
traditionally
- Use `ng-checked="fruit.selected"` to have the checkbox checked based on some angular expression
(no two-way-data-binding)
- Use `ng-model="fruit.selected"` to utilize two-way-data-binding. Note that `.selected`
is arbitrary. The property name could be anything and will be created on the object if not present.
-->
<input
type="checkbox"
name="selectedFruits[]"
value="{{fruit.name}}"
ng-model="fruit.selected"
> {{fruit.name}}
</label>
And the appropriate controller code would be:
app.controller('ObjectArrayCtrl', ['$scope', 'filterFilter', function ObjectArrayCtrl($scope, filterFilter) {
// Fruits
$scope.fruits = [
{ name: 'apple', selected: true },
{ name: 'orange', selected: false },
{ name: 'pear', selected: true },
{ name: 'naartjie', selected: false }
];
// Selected fruits
$scope.selection = [];
// Helper method to get selected fruits
$scope.selectedFruits = function selectedFruits() {
return filterFilter($scope.fruits, { selected: true });
};
// Watch fruits for changes
$scope.$watch('fruits|filter:{selected:true}', function (nv) {
$scope.selection = nv.map(function (fruit) {
return fruit.name;
});
}, true);
}]);
Pros: Add/remove is very easy
Cons: Somewhat more complex data structure and toggling by name is cumbersome or requires a helper method
Demo: http://jsbin.com/ImAqUC/1/
A simple solution:
<div ng-controller="MainCtrl">
<label ng-repeat="(color,enabled) in colors">
<input type="checkbox" ng-model="colors[color]" /> {{color}}
</label>
<p>colors: {{colors}}</p>
</div>
<script>
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope){
$scope.colors = {Blue: true, Orange: true};
});
</script>
http://plnkr.co/edit/U4VD61?p=preview
<input type='checkbox' ng-repeat="fruit in fruits"
ng-checked="checkedFruits.indexOf(fruit) != -1" ng-click="toggleCheck(fruit)">
.
function SomeCtrl ($scope) {
$scope.fruits = ["apple, orange, pear, naartjie"];
$scope.checkedFruits = [];
$scope.toggleCheck = function (fruit) {
if ($scope.checkedFruits.indexOf(fruit) === -1) {
$scope.checkedFruits.push(fruit);
} else {
$scope.checkedFruits.splice($scope.checkedFruits.indexOf(fruit), 1);
}
};
}
Here's a quick little reusable directive that seems to do what you're looking to do. I've simply called it checkList. It updates the array when the checkboxes change, and updates the checkboxes when the array changes.
app.directive('checkList', function() {
return {
scope: {
list: '=checkList',
value: '#'
},
link: function(scope, elem, attrs) {
var handler = function(setup) {
var checked = elem.prop('checked');
var index = scope.list.indexOf(scope.value);
if (checked && index == -1) {
if (setup) elem.prop('checked', false);
else scope.list.push(scope.value);
} else if (!checked && index != -1) {
if (setup) elem.prop('checked', true);
else scope.list.splice(index, 1);
}
};
var setupHandler = handler.bind(null, true);
var changeHandler = handler.bind(null, false);
elem.bind('change', function() {
scope.$apply(changeHandler);
});
scope.$watch('list', setupHandler, true);
}
};
});
Here's a controller and a view that shows how you might go about using it.
<div ng-app="myApp" ng-controller='MainController'>
<span ng-repeat="fruit in fruits">
<input type='checkbox' value="{{fruit}}" check-list='checked_fruits'> {{fruit}}<br />
</span>
<div>The following fruits are checked: {{checked_fruits | json}}</div>
<div>Add fruit to the array manually:
<button ng-repeat="fruit in fruits" ng-click='addFruit(fruit)'>{{fruit}}</button>
</div>
</div>
app.controller('MainController', function($scope) {
$scope.fruits = ['apple', 'orange', 'pear', 'naartjie'];
$scope.checked_fruits = ['apple', 'pear'];
$scope.addFruit = function(fruit) {
if ($scope.checked_fruits.indexOf(fruit) != -1) return;
$scope.checked_fruits.push(fruit);
};
});
(The buttons demonstrate that changing the array will also update the checkboxes.)
Finally, here is an example of the directive in action on Plunker: http://plnkr.co/edit/3YNLsyoG4PIBW6Kj7dRK?p=preview
Based on answers in this thread I've created checklist-model directive that covers all cases:
simple array of primitives
array of objects (pick id or whole object)
object properties iteration
For topic-starter case it would be:
<label ng-repeat="fruit in ['apple', 'orange', 'pear', 'naartjie']">
<input type="checkbox" checklist-model="selectedFruits" checklist-value="fruit"> {{fruit}}
</label>
Using a string of $index can help to use a hashmap of selected values:
<ul>
<li ng-repeat="someItem in someArray">
<input type="checkbox" ng-model="someObject[$index.toString()]" />
</li>
</ul>
This way the ng-model object gets updated with the key representing the index.
$scope.someObject = {};
After a while $scope.someObject should look something like:
$scope.someObject = {
0: true,
4: false,
1: true
};
This method won't work for all situations, but it is easy to implement.
Since you accepted an answer in which a list was not used, I'll assume the answer to my comment question is "No, it doesn't have to be a list". I also had the impression that maybe you were rending the HTML server side, since "checked" is present in your sample HTML (this would not be needed if ng-model were used to model your checkboxes).
Anyway, here's what I had in mind when I asked the question, also assuming you were generating the HTML server-side:
<div ng-controller="MyCtrl"
ng-init="checkboxes = {apple: true, orange: false, pear: true, naartjie: false}">
<input type="checkbox" ng-model="checkboxes.apple">apple
<input type="checkbox" ng-model="checkboxes.orange">orange
<input type="checkbox" ng-model="checkboxes.pear">pear
<input type="checkbox" ng-model="checkboxes.naartjie">naartjie
<br>{{checkboxes}}
</div>
ng-init allows server-side generated HTML to initially set certain checkboxes.
Fiddle.
I think the easiest workaround would be to use 'select' with 'multiple' specified:
<select ng-model="selectedfruit" multiple ng-options="v for v in fruit"></select>
Otherwise, I think you'll have to process the list to construct the list
(by $watch()ing the model array bind with checkboxes).
The following solution seems like a good option,
<label ng-repeat="fruit in fruits">
<input
type="checkbox"
ng-model="fruit.checked"
ng-value="true"
> {{fruit.fruitName}}
</label>
And in controller model value fruits will be like this
$scope.fruits = [
{
"name": "apple",
"checked": true
},
{
"name": "orange"
},
{
"name": "grapes",
"checked": true
}
];
I have adapted Yoshi's accepted answer to deal with complex objects (instead of strings).
HTML
<div ng-controller="TestController">
<p ng-repeat="permission in allPermissions">
<input type="checkbox" ng-checked="selectedPermissions.containsObjectWithProperty('id', permission.id)" ng-click="toggleSelection(permission)" />
{{permission.name}}
</p>
<hr />
<p>allPermissions: | <span ng-repeat="permission in allPermissions">{{permission.name}} | </span></p>
<p>selectedPermissions: | <span ng-repeat="permission in selectedPermissions">{{permission.name}} | </span></p>
</div>
JavaScript
Array.prototype.indexOfObjectWithProperty = function(propertyName, propertyValue)
{
for (var i = 0, len = this.length; i < len; i++) {
if (this[i][propertyName] === propertyValue) return i;
}
return -1;
};
Array.prototype.containsObjectWithProperty = function(propertyName, propertyValue)
{
return this.indexOfObjectWithProperty(propertyName, propertyValue) != -1;
};
function TestController($scope)
{
$scope.allPermissions = [
{ "id" : 1, "name" : "ROLE_USER" },
{ "id" : 2, "name" : "ROLE_ADMIN" },
{ "id" : 3, "name" : "ROLE_READ" },
{ "id" : 4, "name" : "ROLE_WRITE" } ];
$scope.selectedPermissions = [
{ "id" : 1, "name" : "ROLE_USER" },
{ "id" : 3, "name" : "ROLE_READ" } ];
$scope.toggleSelection = function toggleSelection(permission) {
var index = $scope.selectedPermissions.indexOfObjectWithProperty('id', permission.id);
if (index > -1) {
$scope.selectedPermissions.splice(index, 1);
} else {
$scope.selectedPermissions.push(permission);
}
};
}
Working example: http://jsfiddle.net/tCU8v/
Another simple directive could be like:
var appModule = angular.module("appModule", []);
appModule.directive("checkList", [function () {
return {
restrict: "A",
scope: {
selectedItemsArray: "=",
value: "#"
},
link: function (scope, elem) {
scope.$watchCollection("selectedItemsArray", function (newValue) {
if (_.contains(newValue, scope.value)) {
elem.prop("checked", true);
} else {
elem.prop("checked", false);
}
});
if (_.contains(scope.selectedItemsArray, scope.value)) {
elem.prop("checked", true);
}
elem.on("change", function () {
if (elem.prop("checked")) {
if (!_.contains(scope.selectedItemsArray, scope.value)) {
scope.$apply(
function () {
scope.selectedItemsArray.push(scope.value);
}
);
}
} else {
if (_.contains(scope.selectedItemsArray, scope.value)) {
var index = scope.selectedItemsArray.indexOf(scope.value);
scope.$apply(
function () {
scope.selectedItemsArray.splice(index, 1);
});
}
}
console.log(scope.selectedItemsArray);
});
}
};
}]);
The controller:
appModule.controller("sampleController", ["$scope",
function ($scope) {
//#region "Scope Members"
$scope.sourceArray = [{ id: 1, text: "val1" }, { id: 2, text: "val2" }];
$scope.selectedItems = ["1"];
//#endregion
$scope.selectAll = function () {
$scope.selectedItems = ["1", "2"];
};
$scope.unCheckAll = function () {
$scope.selectedItems = [];
};
}]);
And the HTML:
<ul class="list-unstyled filter-list">
<li data-ng-repeat="item in sourceArray">
<div class="checkbox">
<label>
<input type="checkbox" check-list selected-items-array="selectedItems" value="{{item.id}}">
{{item.text}}
</label>
</div>
</li>
I'm also including a Plunker: http://plnkr.co/edit/XnFtyij4ed6RyFwnFN6V?p=preview
You don't have to write all that code. AngularJS will keep the model and the checkboxes in sync simply by using ngTrueValue and ngFalseValue
Codepen here: http://codepen.io/paulbhartzog/pen/kBhzn
Code snippet:
<p ng-repeat="item in list1" class="item" id="{{item.id}}">
<strong>{{item.id}}</strong> <input name='obj1_data' type="checkbox" ng-model="list1[$index].data" ng-true-value="1" ng-false-value="0"> Click this to change data value below
</p>
<pre>{{list1 | json}}</pre>
Check out this directive that manages effectively lists of checkboxes. I hope it works for you.
CheckList Model
There is a way to work on the array directly and use ng-model at the same time through ng-model-options="{ getterSetter: true }".
The trick is to use a getter/setter function in your ng-model. This way you can use an array as your real model and "fake" the booleans in the input's model:
<label ng-repeat="fruitName in ['apple', 'orange', 'pear', 'naartjie']">
<input
type="checkbox"
ng-model="fruitsGetterSetterGenerator(fruitName)"
ng-model-options="{ getterSetter: true }"
> {{fruitName}}
</label>
$scope.fruits = ['apple', 'pear']; // pre checked
$scope.fruitsGetterSetterGenerator = function(fruitName){
return function myGetterSetter(nowHasFruit){
if (nowHasFruit !== undefined){
// Setter
fruitIndex = $scope.fruits.indexOf(fruit);
didHaveFruit = (fruitIndex !== -1);
mustAdd = (!didHaveFruit && nowHasFruit);
mustDel = (didHaveFruit && !nowHasFruit);
if (mustAdd){
$scope.fruits.push(fruit);
}
if (mustDel){
$scope.fruits.splice(fruitIndex, 1);
}
}
else {
// Getter
return $scope.user.fruits.indexOf(fruit) !== -1;
}
}
}
CAVEAT You shouldn't use this method if your arrays are big as myGetterSetter will be called a lot of times.
For more on that, see https://docs.angularjs.org/api/ng/directive/ngModelOptions.
I like Yoshi's answer. I enhanced it so You can use the same function for multiple lists.
<label ng-repeat="fruitName in fruits">
<input
type="checkbox"
name="selectedFruits[]"
value="{{fruitName}}"
ng-checked="selection.indexOf(fruitName) > -1"
ng-click="toggleSelection(fruitName, selection)"> {{fruitName}}
</label>
<label ng-repeat="veggieName in veggies">
<input
type="checkbox"
name="selectedVeggies[]"
value="{{veggieName}}"
ng-checked="veggieSelection.indexOf(veggieName) > -1"
ng-click="toggleSelection(veggieName, veggieSelection)"> {{veggieName}}
</label>
app.controller('SimpleArrayCtrl', ['$scope', function SimpleArrayCtrl($scope) {
// fruits
$scope.fruits = ['apple', 'orange', 'pear', 'naartjie'];
$scope.veggies = ['lettuce', 'cabbage', 'tomato']
// selected fruits
$scope.selection = ['apple', 'pear'];
$scope.veggieSelection = ['lettuce']
// toggle selection for a given fruit by name
$scope.toggleSelection = function toggleSelection(selectionName, listSelection) {
var idx = listSelection.indexOf(selectionName);
// is currently selected
if (idx > -1) {
listSelection.splice(idx, 1);
}
// is newly selected
else {
listSelection.push(selectionName);
}
};
}]);
http://plnkr.co/edit/KcbtzEyNMA8s1X7Hja8p?p=preview
If you have multiple checkboxes on the same form
The controller code
vm.doYouHaveCheckBox = ['aaa', 'ccc', 'bbb'];
vm.desiredRoutesCheckBox = ['ddd', 'ccc', 'Default'];
vm.doYouHaveCBSelection = [];
vm.desiredRoutesCBSelection = [];
View code
<div ng-repeat="doYouHaveOption in vm.doYouHaveCheckBox">
<div class="action-checkbox">
<input id="{{doYouHaveOption}}" type="checkbox" value="{{doYouHaveOption}}" ng-checked="vm.doYouHaveCBSelection.indexOf(doYouHaveOption) > -1" ng-click="vm.toggleSelection(doYouHaveOption,vm.doYouHaveCBSelection)" />
<label for="{{doYouHaveOption}}"></label>
{{doYouHaveOption}}
</div>
</div>
<div ng-repeat="desiredRoutesOption in vm.desiredRoutesCheckBox">
<div class="action-checkbox">
<input id="{{desiredRoutesOption}}" type="checkbox" value="{{desiredRoutesOption}}" ng-checked="vm.desiredRoutesCBSelection.indexOf(desiredRoutesOption) > -1" ng-click="vm.toggleSelection(desiredRoutesOption,vm.desiredRoutesCBSelection)" />
<label for="{{desiredRoutesOption}}"></label>
{{desiredRoutesOption}}
</div>
</div>
Inspired from Yoshi's post above.
Here is the plnkr.
(function () {
angular
.module("APP", [])
.controller("demoCtrl", ["$scope", function ($scope) {
var dc = this
dc.list = [
"Selection1",
"Selection2",
"Selection3"
]
dc.multipleSelections = []
dc.individualSelections = []
// Using splice and push methods to make use of
// the same "selections" object passed by reference to the
// addOrRemove function as using "selections = []"
// creates a new object within the scope of the
// function which doesn't help in two way binding.
dc.addOrRemove = function (selectedItems, item, isMultiple) {
var itemIndex = selectedItems.indexOf(item)
var isPresent = (itemIndex > -1)
if (isMultiple) {
if (isPresent) {
selectedItems.splice(itemIndex, 1)
} else {
selectedItems.push(item)
}
} else {
if (isPresent) {
selectedItems.splice(0, 1)
} else {
selectedItems.splice(0, 1, item)
}
}
}
}])
})()
label {
display: block;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css" />
</head>
<body ng-app="APP" ng-controller="demoCtrl as dc">
<h1>checkbox-select demo</h1>
<h4>Multiple Selections</h4>
<label ng-repeat="thing in dc.list">
<input
type="checkbox"
ng-checked="dc.multipleSelections.indexOf(thing) > -1"
ng-click="dc.addOrRemove(dc.multipleSelections, thing, true)"
> {{thing}}
</label>
<p>
dc.multipleSelections :- {{dc.multipleSelections}}
</p>
<hr>
<h4>Individual Selections</h4>
<label ng-repeat="thing in dc.list">
<input
type="checkbox"
ng-checked="dc.individualSelections.indexOf(thing) > -1"
ng-click="dc.addOrRemove(dc.individualSelections, thing, false)"
> {{thing}}
</label>
<p>
dc.invidualSelections :- {{dc.individualSelections}}
</p>
<script data-require="jquery#3.0.0" data-semver="3.0.0" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.js"></script>
<script data-require="angular.js#1.5.6" data-semver="1.5.6" src="https://code.angularjs.org/1.5.6/angular.min.js"></script>
<script src="script.js"></script>
</body>
</html>
Based on my other post here, I have made a reusable directive.
Check out the GitHub repository
(function () {
angular
.module("checkbox-select", [])
.directive("checkboxModel", ["$compile", function ($compile) {
return {
restrict: "A",
link: function (scope, ele, attrs) {
// Defining updateSelection function on the parent scope
if (!scope.$parent.updateSelections) {
// Using splice and push methods to make use of
// the same "selections" object passed by reference to the
// addOrRemove function as using "selections = []"
// creates a new object within the scope of the
// function which doesn't help in two way binding.
scope.$parent.updateSelections = function (selectedItems, item, isMultiple) {
var itemIndex = selectedItems.indexOf(item)
var isPresent = (itemIndex > -1)
if (isMultiple) {
if (isPresent) {
selectedItems.splice(itemIndex, 1)
} else {
selectedItems.push(item)
}
} else {
if (isPresent) {
selectedItems.splice(0, 1)
} else {
selectedItems.splice(0, 1, item)
}
}
}
}
// Adding or removing attributes
ele.attr("ng-checked", attrs.checkboxModel + ".indexOf(" + attrs.checkboxValue + ") > -1")
var multiple = attrs.multiple ? "true" : "false"
ele.attr("ng-click", "updateSelections(" + [attrs.checkboxModel, attrs.checkboxValue, multiple].join(",") + ")")
// Removing the checkbox-model attribute,
// it will avoid recompiling the element infinitly
ele.removeAttr("checkbox-model")
ele.removeAttr("checkbox-value")
ele.removeAttr("multiple")
$compile(ele)(scope)
}
}
}])
// Defining app and controller
angular
.module("APP", ["checkbox-select"])
.controller("demoCtrl", ["$scope", function ($scope) {
var dc = this
dc.list = [
"selection1",
"selection2",
"selection3"
]
// Define the selections containers here
dc.multipleSelections = []
dc.individualSelections = []
}])
})()
label {
display: block;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css" />
</head>
<body ng-app="APP" ng-controller="demoCtrl as dc">
<h1>checkbox-select demo</h1>
<h4>Multiple Selections</h4>
<label ng-repeat="thing in dc.list">
<input type="checkbox" checkbox-model="dc.multipleSelections" checkbox-value="thing" multiple>
{{thing}}
</label>
<p>dc.multipleSelecitons:- {{dc.multipleSelections}}</p>
<h4>Individual Selections</h4>
<label ng-repeat="thing in dc.list">
<input type="checkbox" checkbox-model="dc.individualSelections" checkbox-value="thing">
{{thing}}
</label>
<p>dc.individualSelecitons:- {{dc.individualSelections}}</p>
<script data-require="jquery#3.0.0" data-semver="3.0.0" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.js"></script>
<script data-require="angular.js#1.5.6" data-semver="1.5.6" src="https://code.angularjs.org/1.5.6/angular.min.js"></script>
<script src="script.js"></script>
</body>
</html>
In the HTML (supposing that the checkboxes are in the first column of every row in a table).
<tr ng-repeat="item in fruits">
<td><input type="checkbox" ng-model="item.checked" ng-click="getChecked(item)"></td>
<td ng-bind="fruit.name"></td>
<td ng-bind="fruit.color"></td>
...
</tr>
In the controllers.js file:
// The data initialization part...
$scope.fruits = [
{
name: ....,
color:....
},
{
name: ....,
color:....
}
...
];
// The checked or not data is stored in the object array elements themselves
$scope.fruits.forEach(function(item){
item.checked = false;
});
// The array to store checked fruit items
$scope.checkedItems = [];
// Every click on any checkbox will trigger the filter to find checked items
$scope.getChecked = function(item){
$scope.checkedItems = $filter("filter")($scope.fruits,{checked:true});
};
Here is yet another solution. The upside of my solution:
It does not need any additional watches (which may have an impact on performance)
It does not require any code in the controller keeping it clean
The code is still somewhat short
It is requires very little code to reuse in multiple places because it is just a directive
Here is the directive:
function ensureArray(o) {
var lAngular = angular;
if (lAngular.isArray(o) || o === null || lAngular.isUndefined(o)) {
return o;
}
return [o];
}
function checkboxArraySetDirective() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
var name = attrs.checkboxArraySet;
ngModel.$formatters.push(function(value) {
return (ensureArray(value) || []).indexOf(name) >= 0;
});
ngModel.$parsers.push(function(value) {
var modelValue = ensureArray(ngModel.$modelValue) || [],
oldPos = modelValue.indexOf(name),
wasSet = oldPos >= 0;
if (value) {
if (!wasSet) {
modelValue = angular.copy(modelValue);
modelValue.push(name);
}
} else if (wasSet) {
modelValue = angular.copy(modelValue);
modelValue.splice(oldPos, 1);
}
return modelValue;
});
}
}
}
At the end then just use it like this:
<input ng-repeat="fruit in ['apple', 'banana', '...']" type="checkbox" ng-model="fruits" checkbox-array-set="{{fruit}}" />
And that is all there is. The only addition is the checkbox-array-set attribute.
You can combine AngularJS and jQuery. For example, you need to define an array, $scope.selected = [];, in the controller.
<label ng-repeat="item in items">
<input type="checkbox" ng-model="selected[$index]" ng-true-value="'{{item}}'">{{item}}
</label>
You can get an array owning the selected items. Using method alert(JSON.stringify($scope.selected)), you can check the selected items.
<div ng-app='app' >
<div ng-controller='MainCtrl' >
<ul>
<li ng-repeat="tab in data">
<input type='checkbox' ng-click='change($index,confirm)' ng-model='confirm' />
{{tab.name}}
</li>
</ul>
{{val}}
</div>
</div>
var app = angular.module('app', []);
app.controller('MainCtrl',function($scope){
$scope.val=[];
$scope.confirm=false;
$scope.data=[
{
name:'vijay'
},
{
name:'krishna'
},{
name:'Nikhil'
}
];
$scope.temp;
$scope.change=function(index,confirm){
console.log(confirm);
if(!confirm){
($scope.val).push($scope.data[index]);
}
else{
$scope.temp=$scope.data[index];
var d=($scope.val).indexOf($scope.temp);
if(d!=undefined){
($scope.val).splice(d,1);
}
}
}
})
Take a look this: checklist-model.
It works with JavaScript arrays, and objects and it can use static HTML checkboxes, without ng-repeat
<label><input type="checkbox" checklist-model="roles" value="admin"> Administrator</label>
<label><input type="checkbox" checklist-model="roles" value="customer"> Customer</label>
<label><input type="checkbox" checklist-model="roles" value="guest"> Guest</label>
<label><input type="checkbox" checklist-model="roles" value="user"> User</label>
And the JavaScript side:
var app = angular.module("app", ["checklist-model"]);
app.controller('Ctrl4a', function($scope) {
$scope.roles = [];
});
A simple HTML only way of doing it:
<input type="checkbox"
ng-checked="fruits.indexOf('apple') > -1"
ng-click="fruits.indexOf('apple') > -1 ? fruits.splice(fruits.indexOf('apple'), 1) : fruits.push('apple')">
<input type="checkbox"
ng-checked="fruits.indexOf('orange') > -1"
ng-click="fruits.indexOf('orange') > -1 ? fruits.splice(fruits.indexOf('orange'), 1) : fruits.push('orange')">
<input type="checkbox"
ng-checked="fruits.indexOf('pear') > -1"
ng-click="fruits.indexOf('pear') > -1 ? fruits.splice(fruits.indexOf('pear'), 1) : fruits.push('pear')">
<input type="checkbox"
ng-checked="fruits.indexOf('naartjie') > -1"
ng-click="fruits.indexOf('apple') > -1 ? fruits.splice(fruits.indexOf('apple'), 1) : fruits.push('naartjie')">
Using this example of the #Umur Kontacı, I think in using to catch selected data throughout another object/array, like a edit page.
Catch options at the database
Toggle a some option
As example, all colors json in below:
{
"colors": [
{
"id": 1,
"title": "Preto - #000000"
},
{
"id": 2,
"title": "Azul - #005AB1"
},
{
"id": 3,
"title": "Azul Marinho - #001A66"
},
{
"id": 4,
"title": "Amarelo - #FFF100"
},
{
"id": 5,
"title": "Vermelho - #E92717"
},
{
"id": 6,
"title": "Verde - #008D2F"
},
{
"id": 7,
"title": "Cinza - #8A8A8A"
},
{
"id": 8,
"title": "Prata - #C8C9CF"
},
{
"id": 9,
"title": "Rosa - #EF586B"
},
{
"id": 10,
"title": "Nude - #E4CAA6"
},
{
"id": 11,
"title": "Laranja - #F68700"
},
{
"id": 12,
"title": "Branco - #FFFFFF"
},
{
"id": 13,
"title": "Marrom - #764715"
},
{
"id": 14,
"title": "Dourado - #D9A300"
},
{
"id": 15,
"title": "Bordo - #57001B"
},
{
"id": 16,
"title": "Roxo - #3A0858"
},
{
"id": 18,
"title": "Estampado "
},
{
"id": 17,
"title": "Bege - #E5CC9D"
}
]
}
And 2 types of data object, array with one object and object containing two/more object data:
Two items selected catched at the database:
[{"id":12,"title":"Branco - #FFFFFF"},{"id":16,"title":"Roxo - #3A0858"}]
One item selected catched at the database:
{"id":12,"title":"Branco - #FFFFFF"}
And here, my javascript code:
/**
* Add this code after catch data of database.
*/
vm.checkedColors = [];
var _colorObj = vm.formData.color_ids;
var _color_ids = [];
if (angular.isObject(_colorObj)) {
// vm.checkedColors.push(_colorObj);
_color_ids.push(_colorObj);
} else if (angular.isArray(_colorObj)) {
angular.forEach(_colorObj, function (value, key) {
// vm.checkedColors.push(key + ':' + value);
_color_ids.push(key + ':' + value);
});
}
angular.forEach(vm.productColors, function (object) {
angular.forEach(_color_ids, function (color) {
if (color.id === object.id) {
vm.checkedColors.push(object);
}
});
});
/**
* Add this code in your js function initialized in this HTML page
*/
vm.toggleColor = function (color) {
console.log('toggleColor is: ', color);
if (vm.checkedColors.indexOf(color) === -1) {
vm.checkedColors.push(color);
} else {
vm.checkedColors.splice(vm.checkedColors.indexOf(color), 1);
}
vm.formData.color_ids = vm.checkedColors;
};
My Html code:
<div class="checkbox" ng-repeat="color in productColors">
<label>
<input type="checkbox"
ng-checked="checkedColors.indexOf(color) != -1"
ng-click="toggleColor(color)"/>
<% color.title %>
</label>
</div>
<p>checkedColors Output:</p>
<pre><% checkedColors %></pre>
[Edit] Refactored code below:
function makeCheckedOptions(objectOptions, optionObj) {
var checkedOptions = [];
var savedOptions = [];
if (angular.isObject(optionObj)) {
savedOptions.push(optionObj);
} else if (angular.isArray(optionObj)) {
angular.forEach(optionObj, function (value, key) {
savedOptions.push(key + ':' + value);
});
}
angular.forEach(objectOptions, function (object) {
angular.forEach(savedOptions, function (color) {
if (color.id === object.id) {
checkedOptions.push(object);
}
});
});
return checkedOptions;
}
And call new method as below:
vm.checkedColors = makeCheckedOptions(productColors, vm.formData.color_ids);
That's it!
I've put an array in the controller.
$scope.statuses = [{ name: 'Shutdown - Reassessment Required' },
{ name: 'Under Construction' },
{ name: 'Administrative Cancellation' },
{ name: 'Initial' },
{ name: 'Shutdown - Temporary' },
{ name: 'Decommissioned' },
{ name: 'Active' },
{ name: 'SO Shutdown' }]
On the markup I've put something like following
<div ng-repeat="status in $scope.statuses">
<input type="checkbox" name="unit_status" ng-model="$scope.checkboxes[status.name]"> {{status.name}}
<br>
</div>
{{$scope.checkboxes}}
The output was the following, in the controller I just needed to check whether its true or false; true for checked, absent/false for unchecked.
{
"Administrative Cancellation":true,
"Under Construction":true,
"Shutdown - Reassessment Required":true,
"Decommissioned":true,
"Active":true
}
Hope this helps.
I think the following way is more clear and useful for nested ng-repeats. Check it out on Plunker.
Quote from this thread:
<html ng-app="plunker">
<head>
<title>Test</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.4/angular.min.js"></script>
</head>
<body ng-controller="MainCtrl">
<div ng-repeat="tab in mytabs">
<h1>{{tab.name}}</h1>
<div ng-repeat="val in tab.values">
<input type="checkbox" ng-change="checkValues()" ng-model="val.checked"/>
</div>
</div>
<br>
<pre> {{selected}} </pre>
<script>
var app = angular.module('plunker', []);
app.controller('MainCtrl', function ($scope,$filter) {
$scope.mytabs = [
{
name: "tab1",
values: [
{ value: "value1",checked:false },
{ value: "value2", checked: false },
{ value: "value3", checked: false },
{ value: "value4", checked: false }
]
},
{
name: "tab2",
values: [
{ value: "value1", checked: false },
{ value: "value2", checked: false },
{ value: "value3", checked: false },
{ value: "value4", checked: false }
]
}
]
$scope.selected = []
$scope.checkValues = function () {
angular.forEach($scope.mytabs, function (value, index) {
var selectedItems = $filter('filter')(value.values, { checked: true });
angular.forEach(selectedItems, function (value, index) {
$scope.selected.push(value);
});
});
console.log($scope.selected);
};
});
</script>
</body>
</html>
Here is the jsFillde link for the same, http://jsfiddle.net/techno2mahi/Lfw96ja6/.
This uses the directive which is available for download at http://vitalets.github.io/checklist-model/.
This is the good to have directive as your application will need this functionality much often.
The code is below:
HTML:
<div class="container">
<div class="ng-scope" ng-app="app" ng-controller="Ctrl1">
<div class="col-xs-12 col-sm-6">
<h3>Multi Checkbox List Demo</h3>
<div class="well"> <!-- ngRepeat: role in roles -->
<label ng-repeat="role in roles">
<input type="checkbox" checklist-model="user.roles" checklist-value="role"> {{role}}
</label>
</div>
<br>
<button ng-click="checkAll()">check all</button>
<button ng-click="uncheckAll()">uncheck all</button>
<button ng-click="checkFirst()">check first</button>
<div>
<h3>Selected User Roles </h3>
<pre class="ng-binding">{{user.roles|json}}</pre>
</div>
<br>
<div><b/>Provided by techno2Mahi</b></div>
</div>
JavaScript
var app = angular.module("app", ["checklist-model"]);
app.controller('Ctrl1', function($scope) {
$scope.roles = [
'guest',
'user',
'customer',
'admin'
];
$scope.user = {
roles: ['user']
};
$scope.checkAll = function() {
$scope.user.roles = angular.copy($scope.roles);
};
$scope.uncheckAll = function() {
$scope.user.roles = [];
};
$scope.checkFirst = function() {
$scope.user.roles.splice(0, $scope.user.roles.length);
$scope.user.roles.push('guest');
};
});
Try my baby:
**
myApp.filter('inputSelected', function(){
return function(formData){
var keyArr = [];
var word = [];
Object.keys(formData).forEach(function(key){
if (formData[key]){
var keyCap = key.charAt(0).toUpperCase() + key.slice(1);
for (var char = 0; char<keyCap.length; char++ ) {
if (keyCap[char] == keyCap[char].toUpperCase()){
var spacedLetter = ' '+ keyCap[char];
word.push(spacedLetter);
}
else {
word.push(keyCap[char]);
}
}
}
keyArr.push(word.join(''))
word = [];
})
return keyArr.toString();
}
})
**
Then for any ng-model with checkboxes, it will return a string of all the input you selected:
<label for="Heard about ITN">How did you hear about ITN?: *</label><br>
<label class="checkbox-inline"><input ng-model="formData.heardAboutItn.brotherOrSister" type="checkbox" >Brother or Sister</label>
<label class="checkbox-inline"><input ng-model="formData.heardAboutItn.friendOrAcquaintance" type="checkbox" >Friend or Acquaintance</label>
{{formData.heardAboutItn | inputSelected }}
//returns Brother or Sister, Friend or Acquaintance