I want to always have at least one checkbox checked but I mix the concepts of v-model and :checked.
The doc says:
v-model will ignore the initial value, checked or selected attributes
found on any form elements. It will always treat the Vue instance data
as the source of truth.
I can prevent my model to be modified but I can't prevent the checkbox to be checked...
Some code:
The template
<div class="wrapper" v-for="(s, id) in userOutputSeries" :key="id">
<input type="checkbox" :id="id" :value="id" #change="preventIfLessThanOneChecked" :checked="s.active">
<label :for="id">{{ s.display }}</label>
</div>
The model userOutputSeries
data () {
return {
userOutputSeries: {
foo: {
display: 'Awesome Foo',
active: true
},
bar: {
display: 'My Bar',
active: false
}
}
}
}
The preventIfLessThanOneChecked handler
preventIfLessThanOneChecked (event) {
// I don't update the model so it stay at the same state
// But only need to count the active series and do what we want.
console.log(event.target.value, event.target.checked)
}
Any ideas to stop the native checkbox propagation?
You should use v-model instead of :checked so that changes to the userOutputSeries data property will be reflected in the checkbox input.
Then, pass the s reference from the v-for to the method and set that object's active property to true if there are no active checkboxes:
new Vue({
el: '#app',
data() {
return {
userOutputSeries: {
foo: {
display: 'Awesome Foo',
active: true
},
bar: {
display: 'My Bar',
active: false
}
}
}
},
methods: {
preventIfLessThanOneChecked(item) {
if (item.active) {
return;
}
let items = Object.values(this.userOutputSeries);
if (!items.find(i => i.active)) {
item.active = true;
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<div id="app">
<div class="wrapper" v-for="(s, id) in userOutputSeries" :key="id">
<input type="checkbox" :id="id" :value="id" #change="preventIfLessThanOneChecked(s)" v-model="s.active">
<label :for="id">{{ s.display }}</label>
</div>
</div>
Try using disabled on your single checked checkbox:
<div class="wrapper" v-for="(s, id) in userOutputSeries" :key="id">
<input type="checkbox" :id="id" :value="id"
:disabled="(s.active && numberOfChecked == 1) ? disabled : null"
#change="preventIfLessThanOneChecked" :checked="s.active">
<label :for="id">{{ s.display }}</label>
</div>
In the above answer given by #thanksd, my checkbox remains unchecked.
So i am writing my solution.
This my loop statement, change the variable names according to your file.
v-for="column in tableColumns"
This is my input ( if visible is true then checkbox is checked )
<input type="checkbox" v-model="column.visible" #change="event => visibleColumnsChanged(column, event)">
Then in my change method
- if there is no visible item left set the column.visible to true
- use event.target.checked = true to check the checkbox again.
visibleColumnsChanged: function(column, event){
if (column.visible) {
return;
}
if(! this.tableColumns.find(c => c.visible)){
column.visible = true;
event.target.checked = true;
}
}
Related
How we can get the value of checkbox an input to an array?
Here is my code, I've tried this:
<cbComp
v-for="name in names"
v-bind:key="name"
v-model="selected_name[name]">
<span> {{ name }} </span>
</cbComp>
Name: {{ selected_name }}
data () {
return {
names: ['Michael', 'Millie', 'Brown'],
selected_name: []
}
},
This was the cbComp template:
<template>
<label class="checkbox">
<input
:id="cb"
type="checkbox"
:value="modelValue"
v-model="computedValue"
>
<label
:for="cb">
<slot/>
</label>
</label>
</template>
This is the javascript:
export default {
data: {
updateValue: this.value
},
props: {
value: undefined,
modelValue: {
type: undefined,
default: false
}
},
computed: {
computedValue: {
get () {
return this.updateValue
},
set (value) {
this.updateValue = value
this.$emit('input', value)
}
}
},
watch: {
value (value) {
this.updateValue = value
}
}
}
It turns out there is no error, and the selected name won't appear on {{ selected_name }}
You simply need to bind your v-model to the selected_name property. Here is a bare minimum example for you to work off of:
<html>
<div id="app">
<p>Names: {{ selected_name.join(', ') }}</p>
<label for="maxCheckbox">Max</label>
<input type="checkbox" value="Max" v-model="selected_name" id="maxCheckbox">
<label for="markCheckbox">Mark</label>
<input type="checkbox" value="Mark" v-model="selected_name" id="markCheckbox">
<label for="johnCheckbox">John</label>
<input type="checkbox" value="John" v-model="selected_name" id="johnCheckbox">
</div>
<script src="https://unpkg.com/vue#2"></script>
<script>
new Vue({
el: '#app',
data() {
return {
selected_name: []
}
}
});
</script>
</html>
As a side note, I would probably rename your selected_name to be more clear like selected_names or more commonly you'll find selectedNames camelCase.
I have an issue while looping through and array, I'm getting all the index from this array correctly but when I use angular patchValue it updates all the inputs with the last index values and not with their respective values as shown :
I want every input to have theirs, for example, first input values should be "1" (left input => longueur) and "1" (right input => quantity)
I tried with .forEach but no success
CODE SAMPLE
component.ts .forEach
ngOnInit() {
this.requiredFields();
this.service.checkExistQuot().subscribe(res => {
this.quotDetails.forEach( (myArray, index) => {
this.dropForm.patchValue({
longueur: this.quotDetails[index].longueur,
quantity: this.quotDetails[index].quantity
})
console.log(index);
});
});
}
HTML, input example
<div class="products">
<div class="d-flex flex-row" *ngFor="let products of selectedDiam;let i = index">
<input class="number" formControlName="longueur" value="" (change)="postQuotationDatas(products.part_id)" type="number">
</a>
<input class="mb-1 flex1 checkbox" type="checkbox">
<a class="tac flex1"></a>
<a class="flex1 mb-1">
<input class="number" value="" formControlName="quantity" (change)="postQuotationDatas(products.part_id)" type="number">
</a>
<a class="flex1"></a>
</div>
</div>
Your problem is that you only have one form group,dropForm, with 2 controls: quantity and longueur. Even though you have multiple html inputs for longueur and quantity, they are share the same reference in the component
So, with your forEach loop, you are actually patching all your controls for each iteration. That's why you have the same value for all your controls, which is the value for the lasst object the loop went over.
Option #1
A possible solution is to have multiple form groups, like in this stackblitz example
component.ts
//dropForms initialisation
this.quotationDetails.map(() =>
{
let group = this.formBuilder.group(
{
longueur: [''],
quantity: [''],
});
this.dropForms.push(group)
}
this.quotationDetails.forEach( (myArray, index) => {
this.dropForms[index].patchValue({
longueur: this.quotationDetails[index].longueur,
component.html
<div class="d-flex flex-row" *ngFor="let products of quotationDetails; let index=index">
<form [formGroup]="dropForms[index]"> <!-- 1 form group per quotation -->
Option #2
The other solution, to have only 1 formGroup, is to give dynamic control names
component.ts
//Initialisation of dropForm
this.quotationDetails.forEach((val, index)=>
{
group[`longueur_${index}`] = '';
group[`quantity_${index}`] = ''
});
this.dropForm = this.formBuilder.group(
group
)
//Patch
this.quotationDetails.forEach( (myArray, index) => {
let patchValue = {};
patchValue[`longueur_${index}`] = this.quotationDetails[index].longueur;
patchValue[`quantity_${index}`] = this.quotationDetails[index].quantity;
this.dropForm.patchValue(patchValue);
component.html
<form [formGroup]="dropForm">
<div class="products">
<div class="d-flex flex-row" *ngFor="let products of quotationDetails; let index = index">
<a>
<input class="number" formControlName="longueur_{{index}}" value="" type="number">
Stackblitz example
Use FormArray and wrap inputs in arrays.
Example add FormControl to FormArray with FormBuilder:
FormArray - A FormArray aggregates the values of each child FormControl into an array.
in componenent ts:
const EMPLOYEE_FORM = {
firstName: ['', Validators.required],
lastName: ['', Validators.required],
isActive : [false, Validators.required]
}
const COMPANY_FORM = {
employees: new FormArray([], [Validators.required])
}
export class AppComponent {
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = this.fb.group(COMPANY_FORM);
}
get employees(): FormArray {
return this.form.get('employees') as FormArray;
}
addEmployee() {
const employee = this.fb.group(EMPLOYEE_FORM);
this.employees.push(employee);
}
}
in html
<div *ngFor="let item of employees.controls; let i = index;">
<form [formGroup]="item">
<input type="text" formControlName="firstName" class="form-control" placeholder="FirstName">
<input type="text" formControlName="lastName" class="form-control" placeholder="LastName">
<input class="checkbox" type="checkbox" formControlName="isActive">
</form>
</div>
<button (click)="addEmployee()">add new employee</button>
<div *ngIf="employees.length > 0">
{{employees.value | json}}
</div>
See this link: https://stackblitz.com/edit/add-input-to-formarray-with-frombuilder
I am using Reactive Forms FormGroup, FormBuilder and FormArray. I have a FormArray in which I would like to loop through an external array, create a list of checkboxes and then pass the id of those checkboxes to my FormArray.
My components typescript looks like:
export class AppComponent {
form: FormGroup;
sites = [ "site-1", "site-2", "site-3", "site-4" ];
constructor(private fb: FormBuilder){
}
ngOnInit() {
this.form = this.fb.group({
type: [ '', Validators.required ],
sites: this.fb.array([
this.fb.group({
siteId: [ '', Validators.required ]
})
])
})
}
}
And my html...
<form [formGroup]="form">
<input type="checkbox" formControlName="type" id="type">
<label for="type">Type</label>
<div class="checkbox-group" style="margin-top:40px">
<div class="checkbox-wrap" id="productsContainer">
<input type="checkbox" id="all">
<label for="all">All mills</label>
<div *ngFor="let site of sites; let i = index">
<input type="checkbox" [id]="i" />
<label [for]="i"> {{ site }} </label>
</div>
</div>
</div>
</form>
I know I need to pass the formArrayName into the html but I am getting console errors when trying to pass in the formArrayName="sites" and then using the "i" from my loop as the formGroupName. What am I doing wrong?
Here's a StackBlitz with the full setup...
https://stackblitz.com/edit/angular-rcfnwi?file=app%2Fapp.component.html
You need to populate the FormArray with the individual FormControls that correspond to the items in your sites list. In my fork example, I did this in the initialization of the FormGroup (https://stackblitz.com/edit/angular-hilz9h) by mapping over the sites array and generating FormControls with a default value of false (unchecked). I also removed the embedded FormGroup that was surrounding the sites FormArray to make it simpler. I then added the formArrayName directive to the wrapping div with the string attribute corresponding to the 'sites' name of the FormArray from the ts file. Finally, I added a [formControlName] directive and passed it the ngFor index of each input to correspond to the array index of the FormArray controls. FormArrays internally keep track of their controls by index.
So, your ngOnInit now looks like:
ngOnInit() {
this.form = this.fb.group({
type: [ '', Validators.required ],
sites: this.fb.array(this.sites.map(s => this.fb.control(false)),
[ Validators.required ])
})
}
and your html file now looks like:
<form [formGroup]="form">
<input type="checkbox" formControlName="type" id="type">
<label for="type">Type</label>
<div class="checkbox-group" style="margin-top:40px">
<div class="checkbox-wrap" id="productsContainer" formArrayName="sites">
<input type="checkbox" id="all">
<label for="all">All mills</label>
<div *ngFor="let site of sites; let i = index">
<input type="checkbox" [id]="i" [formControlName]="i"/>
<label [for]="i"> {{ site }} </label>
</div>
</div>
</div>
</form>
<pre> {{ form.value | json }}</pre>
Really the answer work, but, in my opinion is not clearly a Form Array.
When we have a serie of checks, we must to understand that there are two separate concept: the form and the data. Yes this two concepts are not necesary equal.
I choose create the form like
//we create a FormGroup array transform the array "this.sites"
const controls:FormGroup[]=this.sites.map(x=>{
return this.fb.group({
site:false
})
});
//in this.form I use this array of FormGroup
this.form = this.fb.group({
type: ['', Validators.required],
sites: this.fb.array(controls)
})
Well, when we make the form, the form.value can be like
{
"type": "",
"sites": [
{ "site": false }, { "site": true }, { "site": false }, { "site": true }
]
}
Then, in submit we must transform the form.value to a data we like
submit(form: FormGroup) {
if (form.valid) {
//create an array of sites
let sites: string[] = [];
let i: number = 0;
form.value.sites.forEach((x:any) => {
if (x.site)
sites.push(this.sites[i]);
i++;
});
//Our data will be
let data = {
type: form.value.type,
sites: sites
}
console.log(data);
}
}
Finally, the form:
<form [formGroup]="form" (submit)="submit(form)" novalidate">
<input formControlName="type">
<div formArrayName="sites" *ngFor="let site of form?.get('sites').controls;let i=index">
<div [formGroupName]="i">
<--!see that formControlName is "site"
and the labels of the check use the array this.sites -->
<input type="checkbox" formControlName="site" /> {{sites[i]}}
</div>
</div>
<button>send</button>
</form>
All are asking Why on earth and What is it? All the things must be too complex? My answer is that it is not so complex. FurtherMore, imagine how can populate the form with data.
//if data={type:"a",sites:["site-2","site-4"], we create the form like
const controls:FormGroup[]=this.sites.map(x=>{
return this.fb.group({
site:data.sites.indexOf(x)>=0; //true or false
})
});
this.form = this.fb.group({
type: [data.type, Validators.required],
sites: this.fb.array(controls)
})
I have array of html elements each element mush user change it with CKEditor. After user change elements and I loop in array, I get the old value.
<div ng-repeat="item in site">
<textarea ng-model="item" ckeditor="ckeditorOptions"></textarea>
<input type="button" value="Test unit" ng-click="elementUnit(item)" />
</div>
In controller
$scope.ckeditorOptions = {
language: 'en',
allowedContent: true,
entities: true
};
$scope.uploadContent = function(site) {
console.log(site);
for (var i = 0; i < site.length; i++) {
console.log(site[i]); // Get old value !!
}
}
$scope.elementUnit= function(item) {
console.log(item);// in this case I get new value
}
What can I do for this case?
Thanks.
Here I am using angular.js to show a list of people
<div class="recipient" ng-repeat="person in people">
<img src="{{person.img}}" /> person.name
<div class="email">person.email</div>
</div>
$scope.people = [{id:1}, {id:2}, {id:3}, {id:4}];
The looks is like below
What I want to do is I can select multiple items and by click a OK button, I can get a list of selected items. so If I select id 1 and id 2, then I want to get return a list of [{id:1},{id:2}]
How could I implement it in angular.js
Well I guess that if you're looping through a collection of people using a ng-repeat, you could add the ng-click directive on each item to toggle a property of you're object, let's say selected.
Then on the click on your OK button, you can filter all the people that have the selected property set to true.
Here's the code snippet of the implementation :
<div class="recipient" ng-repeat="person in people" ng-click="selectPeople(person)">
<img src="{{person.img}}" /> person.name
<div class="email">person.email</div>
</div>
<button ng-click="result()">OK</button>
function demo($scope) {
$scope.ui = {};
$scope.people = [{
name: 'Janis',
selected: false
}, {
name: 'Danyl',
selected: false
}, {
name: 'tymeJV',
selected: false
}];
$scope.selectPeople = function(people) {
people.selected = !people.selected;
};
$scope.result = function() {
$scope.ui.result = [];
angular.forEach($scope.people, function(value) {
if (value.selected) {
$scope.ui.result.push(value);
}
});
};
}
.recipient {
cursor: pointer;
}
.select {
color:green;
}
.recipient:hover {
background-color:blue;
}
<script src="https://code.angularjs.org/1.2.25/angular.js"></script>
<div ng-app ng-controller="demo">
<div class="recipient" ng-repeat="person in people" ng-click="selectPeople(person)" ng-class="{ select: person.selected }">
<div class="name">{{ person.name }}</div>
</div>
<button ng-click="result()">OK</button>
Result :
<ul>
<li ng-repeat="item in ui.result">{{ item.name }}</li>
</ul>
</div>
If you only want to show checked or unchecked you could just apply a filter, but you would need to toggle the filter value from undefined to true if you didn't wan't to get stuck not being able to show all again.
HTML:
<button ng-click="filterChecked()">Filter checked: {{ checked }}</button>
<div class="recipient" ng-repeat="person in people | filter:checked">
<input type='checkbox' ng-model="person.isChecked" />
<img ng-src="{{person.img}}" />{{ person.name }}
<div class="email">{{ person.email }}</div>
</div>
Controller:
// Apply a filter that shows either checked or all
$scope.filterChecked = function () {
// if set to true or false it will show checked or not checked
// you would need a reset filter button or something to get all again
$scope.checked = ($scope.checked) ? undefined : true;
}
If you want to get all that have been checked and submit as form data you could simply loop through the array:
Controller:
// Get a list of who is checked or not
$scope.getChecked = function () {
var peopleChkd = [];
for (var i = 0, l = $scope.people.length; i < l; i++) {
if ($scope.people[i].isChecked) {
peopleChkd.push(angular.copy($scope.people[i]));
// Remove the 'isChecked' so we don't have any DB conflicts
delete peopleChkd[i].isChecked;
}
}
// Do whatever with those checked
// while leaving the initial array alone
console.log('peopleChkd', peopleChkd);
};
Check out my fiddle here
Notice that person.isChecked is only added in the HTML.