Vuejs checkbox indeterminate status - checkbox

I need little help. I have world regions list with countries inside:
{
'North American Countries' : {
'countries' : {
'us' : { 'name' : 'United States' } ,
'ca' : { 'name': 'Canada' }
.
.
.
.
}
},
'European Countries' : {
......
}
}
HTML:
<ul v-for="(regionName, region) in regions">
<li>
<label>{{ regionName }}</label>
<input type="checkbox" #change="toggleGroupActivation(regionName)">
</li>
<li v-for="country in region.countries">
<div>
<label for="country-{{ country.code }}">{{ country.name }}</label>
<input id="country-{{ country.code }}" type="checkbox" :disabled="!country.available" v-model="country.activated" #change="toggleCountryActivation(regionName, country)">
</div>
</li>
</ul>
And I try to build the list with checkboxes, where you can select countries. If check whole region's checkbox, automatically checked all countries in it region. If are checked only few countries in region(not all), need to display indeterminate checkbox status by region checkbox. How to handle it?

The usual solution to the Select All checkbox is to use a computed with a setter. When the box is checked, all the sub-boxes are checked (via the set function). When a sub-box changes, the Select All box value is re-evaluated (in the get function).
Here, we have a twist: if the sub-boxes are mixed, the Select All box should indicate that somehow. The approach is still to use a computed, but instead of just true and false values, it can return a third value.
There's no built-in way of representing a third value in a checkbox; I've chosen to replace it with a yin-yang emoji.
const rawData = {
'North American Countries': {
'countries': {
'us': {
'name': 'United States'
},
'ca': {
'name': 'Canada'
}
}
},
'European Countries': {
countries: {}
}
};
const countryComponent = Vue.extend({
template: '#country-template',
props: ['country', 'activated'],
data: () => ({ available: true })
});
const regionComponent = Vue.extend({
template: '#region-template',
props: ['region-name', 'region'],
data: function () {
const result = {
countriesActivated: {}
};
for (const c of Object.keys(this.region.countries)) {
result.countriesActivated[c] = { activated: true };
}
return result;
},
components: {
'country-c': countryComponent
},
computed: {
activated: {
get: function() {
let trueCount = 0;
let falseCount = 0;
for (const cName of Object.keys(this.countriesActivated)) {
if (this.countriesActivated[cName]) {
++trueCount;
} else {
++falseCount;
}
}
if (trueCount === 0) {
return false;
}
if (falseCount === 0) {
return true;
}
return 'mixed';
},
set: function(newValue) {
for (const cName of Object.keys(this.countriesActivated)) {
this.countriesActivated[cName] = newValue;
}
}
}
}
});
new Vue({
el: 'body',
data: {
regions: rawData
},
components: {
'region-c': regionComponent
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<template id="region-template">
<li>
<label>{{ regionName }}</label>
<input v-if="activated !== 'mixed'" type="checkbox" v-model="activated">
<span v-else>☯</span>
<ul>
<country-c v-for="(countryName, country) in region.countries" :country="country" :activated.sync="countriesActivated[countryName]"></country-c>
</ul>
</li>
</template>
<template id="country-template">
<li>
<label for="country-{{ country.code }}">{{ country.name }}</label>
<input id="country-{{ country.code }}" type="checkbox" :disabled="!available" v-model="activated">
</li>
</template>
<ul>
<region-c v-for="(regionName, region) in regions" :region-name="regionName" :region="region" :countriesActivated=""></region-c>
</ul>

Related

Vue.js re-rendering sorted array

I have a basic sorting UI to sort comments based on some values:
Part of CommentsSection template:
<div v-if="numComments" class="tabs d-flex">
<span class="text-muted">Sort by:</span>
<div class="tab" :class="{active: tab === 0}" #click="sortComments(0)">Rating</div>
<div class="tab" :class="{active: tab === 1}" #click="sortComments(1)">Newest</div>
<div class="tab" :class="{active: tab === 2}" #click="sortComments(2)">Oldest</div>
</div>
<ul v-if="numComments" class="comments-list">
<li is="comment" #delete="numComments -= 1" v-for="comment in sortedComments" :data="comment"></li>
</ul>
CommentsSection:
export default {
name: 'comments-section',
components: {
CommentForm,
Comment
},
props: ['comments', 'submissionId'],
data() {
return {
tab: 0,
numComments: this.comments.length,
sortedComments: this.comments.slice()
}
},
created() {
this.sortComments();
},
methods: {
sortComments(type = 0) {
this.tab = type;
if (type === 0) {
this.sortedComments.sort((a, b) => b.rating - a.rating);
} else if (type === 1) {
this.sortedComments.sort((a, b) => moment(b.create_time).unix() - moment(a.create_time).unix());
} else {
this.sortedComments.sort((a, b) => moment(a.create_time).unix() - moment(b.create_time).unix());
}
},
...
}
...
}
CommentSingle (component being rendered in list):
export default {
name: 'comment-single',
props: ['data'],
data() {
return {
agree: this.data.rated === 1,
disagree: this.data.rated === -1
}
}
...
}
The CommentSingle template is not being re-rendered so agree and disagree don't update. But the actual list does render the proper sort when clicking each sorting tab, but each comment in the list has the wrong agree and disagree (the original sorted array's values). Any idea how to fix this?
Solved by binding a key to the rendered component:
<li is="comment" #delete="numComments -= 1" v-for="comment in sortedComments" :key="comment.id" :data="comment"></li>
Reference: https://v2.vuejs.org/v2/guide/list.html#key

How to use an OR in a filter AngularJS

I have an unordered list that is being filtered on when a select option is selected. My problem is that when 'ALL' is selected nothing displays because the value does not match the item.status of any of my list items. Is there a way of adding || which will then filter on item.status or 'ALL'?
Below are the values that are in the select. One of these values is also referenced for each list item as item.status
app.value('filterSavedList', pageOptions = [
{
value: 'ALL',
label: 'All'
},
{
value: 'NEW',
label: 'Not started'
},
{
value: 'STARTED',
label: 'In progress'
},
{
value: 'COMPLETED',
label: 'Completed'
}
]);
My select and my unordered list.
<select ng-model="filter" ng-options="item.value as item.label for item in status">
</select>
<ul>
<li ng-repeat="item in assignments | filter: { status : filter }">
//do stuff
</li>
</ul>
You simply need to set the status value to '' (empty string), and make sure filter (i.e. the model of the select box) is initialized to one of the 4 values (i.e. initialized to '', if the option All must be pre-selected)
See http://plnkr.co/edit/mJtQnwA3aQhpT4WN62jj?p=preview for a complete example.
You can also create a custom filter that does normal filtering by property and also takes care of the ALL possibility in status:
.filter('filterStatus', function () {
return function (items, status) {
if(status === 'ALL') {
return items;
}
else {
var itemsToReturn = [];
for(var i=0,x=items.length;i<x;i++) {
if(items[i].status === status) {
itemsToReturn.push(items[i]);
}
}
return itemsToReturn;
}
};
});
Then in HTML:
<ul>
<li ng-repeat="item in assignments | filterStatus: filter">
<!-- do stuff -->
</li>
</ul>

check a checkbox by clicking a link in angularJs

I have the folowing code :
<li ng-repeat="item in items">
<a href="#" ng-if="!item.children" ng-click="checkItem(item,checkBoxModel)">
<input class="align"
ng-click="checkItem(item,checkBoxModel)"
type="checkbox" ng-checked="master"
ng-model="checkboxModel"/>
{{ item.title }}
</a>
</li>
in my controller i have checkItem function:
$scope.checkItem = function(item, checkBoxModel) {
if (checkBoxModel == undefined || checkBoxModel == true) {
....
$scope.master=true;
$scope.checkBoxModel = false;*
} else {
....
$scope.master = false;
$scope.checkBoxModel = true;
}
}
The problem is that when I click on a link all of the checkboxes are checked. I just want the checkbox associated to the link to be checked.
Instead of setting a value master on the controllers $scope object, set it on the actual item that you pass in, and set it's ng-checked="item.master" and it's ng-model="item.checkBoxModel"
$scope.checkItem = function(item, checkBoxModel) {
if (checkBoxModel == undefined || checkBoxModel == true) {
....
item.master=true;
item.checkBoxModel = false;
} else {
....
item.master = false;
item.checkBoxModel = true;
}
}
Change your app logic. You have to declare a variable for each item. But as i see, you have one for all in the global $scope named master. The master should have been declared for each item to specify the state of the option box. Then your problem will be solved.
Something like this:
app.js
$scope.items = [
{
name: 'example',
master: false,
checkboxModel: false
},
{
name: 'example',
master: false,
checkboxModel: false
}
];
$scope.checkItem = function(item, checkBoxModel) {
if (checkBoxModel == undefined || checkBoxModel == true) {
....
$scope.items[item].master = true;
...
} else {
....
$scope.items[item].master = false;
...
}
}
index.html
<input class="align"
ng-click="checkItem(item, checkBoxModel)"
type="checkbox" ng-checked="item.master"
ng-model="item.checkboxModel"/>
<li ng-repeat="item in items">
<a href="#" ng-if="!item.children" ng-click="checkItem(item)">
<input class="align"
ng-click="checkItem(item)"
type="checkbox" ng-checked="item.checked"
/>
{{ item.title }}
</a>
in my controller i change the value of item.checked

Angular-xeditable: Need a checklist that displays checked items

I would like to use a check list and show the user the boxes she has checked.
I am using this framework: http://vitalets.github.io/angular-xeditable/#checklist . See his example 'Checklist' versus his example 'Select multiple'. However, I do not want to display a link with a comma separated string, i.e., join(', '). I would like each selection to appear beneath the previous, in an ordered list or similar.
Pretty much copied from his examples, here are the guts of my controller:
$scope.userFeeds = {
feeds: {}
};
$scope.feedSource = [
{ id: 1, value: 'All MD' },
{ id: 2, value: 'All DE' },
{ id: 3, value: 'All DC' }
];
$scope.updateFeed = function (feedSource, option) {
$scope.userFeeds.feeds = [];
angular.forEach(option, function (v) {
var feedObj = $filter('filter')($scope.feedSource, { id: v });
$scope.userFeeds.feeds.push(feedObj[0]);
});
return $scope.userFeeds.feeds.length ? '' : 'Not set';
};
And here is my html:
<div ng-show="eventsForm.$visible"><h4>Select one or more feeds</h4>
<span editable-select="feedSource"
e-multiple
e-ng-options="feed.id as feed.value for feed in feedSource"
onbeforesave="updateFeed(feedSource, $data)">
</span>
</div>
<div ng-show="!eventsForm.$visible"><h4>Selected Source Feed(s)</h4>
<ul>
<li ng-repeat="feed in userFeeds.feeds">
{{ feed.value || 'empty' }}
</li>
<div ng-hide="userFeeds.feeds.length">No items found</div>
</ul>
</div>
My problem is - display works with editable-select and e-multiple, but not with editable-checklist. Swap it out and nothing is returned.
To workaround, I have tried dynamic html as in here With ng-bind-html-unsafe removed, how do I inject HTML? but I have considerable difficulties getting the page to react to a changed scope.
My goal is to allow a user to select from a checklist and then to display the checked items.
Try this fiddle: http://jsfiddle.net/mr0rotnv/15/
Your onbeforesave will need to return false, instead of empty string, to stop conflict with the model update from xEditable. (Example has onbeforesave and model binding working on the same variable)
return $scope.userFeeds.feeds.length ? false : 'Not set';
If you require to start in edit mode add the attribute shown="true" to the surrounding form element.
Code for completeness:
Controller:
$scope.userFeeds = {
feeds: []
};
$scope.feedSource = [
{ id: 1, value: 'All MD' },
{ id: 2, value: 'All DE' },
{ id: 3, value: 'All DC' }
];
$scope.updateFeed = function (feedSource, option) {
$scope.userFeeds.feeds = [];
angular.forEach(option, function (v) {
var feedObj = $filter('filter')($scope.feedSource, { id: v });
if (feedObj.length) { // stop nulls being added.
$scope.userFeeds.feeds.push(feedObj[0]);
}
});
return $scope.userFeeds.feeds.length ? false : 'Not set';
};
Html:
<div ng-show="editableForm.$visible">
<h4>Select one or more feeds</h4>
<span editable-checklist="feedSource"
e-ng-options="feed.id as feed.value for feed in feedSource"
onbeforesave="updateFeed(feedSource, $data)">
</span>
</div>
<div ng-show="!editableForm.$visible">
<h4>Selected Source Feed(s)</h4>
<ul>
<li ng-repeat="feed in userFeeds.feeds">{{ feed.value || 'empty' }}</li>
<div ng-hide="userFeeds.feeds.length">No items found</div>
</ul>
</div>
Css:
(Used to give the "edit view" a list appearance)
.editable-input label {display:block;}
Also there is the option of using a filter if you do not need to do any validation or start in edit mode.
Controller:
$scope.user = { status: [2, 3] };
$scope.statuses = [
{ value: 1, text: 'status1' },
{ value: 2, text: 'status2' },
{ value: 3, text: 'status3' }
];
$scope.filterStatus = function (obj) {
return $scope.user.status.indexOf(obj.value) > -1;
};
HTML:
<a href="#" editable-checklist="user.status" e-ng-options="s.value as s.text for s in statuses">
<ol>
<li ng-repeat="s in statuses | filter: filterStatus">{{ s.text }}</li>
</ol>
</a>

AngularJS checkbox filter directive

I have a AngularJS directive that allows users to select a values from a list to filter on. Pretty simple concept which is represented here:
Problem is when I click one of the checkboxes they all select unintended. My directive is pretty simple so I'm not sure why this is happening. The code around the selection and checkboxes is as follows:
$scope.tempFilter = {
id: ObjectId(),
fieldId: $scope.available[0].id,
filterType: 'contains'
};
$scope.toggleCheck = function (id) {
var values = $scope.tempFilter.value;
if (!values || !values.length) {
values = $scope.tempFilter.value = [];
}
var idx = values.indexOf(id);
if (idx === -1) {
values.push(id);
} else {
values.splice(idx, 1);
}
};
$scope.valuesListValues = function (id) {
return $scope.available.find(function (f) {
return f.id === id;
}).values;
};
and the data resembles:
$scope.available = [{
id: 23,
name: 'Store'
values: [
{ id: 124, name: "Kansas" },
{ id: 122, name: "Florida" }, ... ]
}, ... ]
the view logic is as follows:
<ul class="list-box">
<li ng-repeat="val in valuesListValues(tempFilter.fieldId)">
<div class="checkbox">
<label ng-click="toggleCheck(val.id)">
<input ng-checked="tempFilter.value.indexOf(val.id) === -1"
type="checkbox"> {{val.name}}
</label>
</div>
</li>
</ul>
First off, it toggleCheck fires twice but populates the correct data ( second time given my code it removes it though ).
After the second fire, it checks all boxes... Any ideas?
Perhaps its that the local variable doesn't get reassigned to the property of the scope property used in the view. Since your values are then non-existent and not found, the box is checked.
$scope.tempFilter.value = values
I took the interface concept you were after and created a simpler solution. It uses a checked property, found in each item of available[0].values, as the checkbox model. At the top of the list is a button that clears the selected items.
JavaScript:
function DataMock($scope) {
$scope.available = [{
id: 23,
name: 'Store',
values: [{
id: 124,
name: "Kansas"
}, {
id: 122,
name: "Florida"
}]
}];
$scope.clearSelection = function() {
var values = $scope.available[0].values;
for (var i = 0; i < values.length; i++) {
values[i].checked = false;
}
};
}
HTML:
<body ng-controller="DataMock">
<ul class="list-box">
<li>
<button ng-click="clearSelection()">Clear Selection</button>
</li>
<li ng-repeat="val in available[0].values">
<div class="checkbox">
<label>
<input ng-model="val.checked"
type="checkbox" /> {{val.name}}
</label>
</div>
</li>
</ul>
</body>
Demo on Plunker
The repeat that I used to grab the values based on the id, was the problem area.
<li ng-repeat="val in valuesListValues(tempFilter.fieldId)">
removing that and simple listening and setting a static variable resolved the problem.
$scope.$watch('tempFilter.fieldId', function () {
var fId = $scope.tempFilter.fieldId;
if ($scope.isFieldType(fId, 'valuesList')) {
$scope.valuesListValues = $scope.valuesListValues(fId);
}
}, true);
});
and then in the view:
ng-repeat="value in valuesListValues"

Resources