disable checkbox after checking two vuejs - vue.js

how can I disable an array of checkbox after checking a number of checkboxes from that array ?
Is there a way to do this in vuejs or I will need a watcher for that?
In fact I tried to watch the 'ord.sauce' but I couldnt make it work
this is the component
Vue.component('sauce-view', {
props: ["sauce", "ord", "name"],
template: '
<div class="">
<input type="checkbox" :name="name" :value="sauce.id" v-model="ord.sauce">
{{sauce.name}}
<label >
<img :src="sauce.link" >
</label>
</div>',
});
This is the HTML
<table>
<tr v-for="o in order" >
{{o.sauce}}
<td v-for="sauce in les_sauces" >
<sauce-view :sauce="sauce" :ord="o" :name="o.produit+o.num">
</sauce-view>
</td>
</tr></table>

I have created a simple fiddle that should illustrate the logic behind it: https://jsfiddle.net/UDany/r9q4x85d/
This would be the markup:
<div id="demo">
<template v-for="(item, index) in itemlist">
<label><input type="checkbox" :value="index" name="condiment" v-model="selectedItems" :disabled="selectedItems.length >= max && selectedItems.indexOf(index) == -1" /> {{item}}</label>
</template>
<div>{{selectedItems.join(', ')}}</div>
</div>
And your JS would look like this:
var demo = new Vue({
el: '#demo',
data: {
selectedItems: [],
itemlist: [
"Mayo",
"Ketchup",
"Mustard",
"Honey"
],
max: 2
}
})
Since you're not using the input directly into the main code you'll need to proxy the properties through/from it (possibly wiring events for "select" and "unselect" and having a property for "disabled")

I've answered a question very similar to this here:
Vue.js limit selected checkboxes to 5
In your situation it's a little more involved since you're handling the value in a component. It's hard to tell your exact needs so I'd clear up your question a little more.

Related

Can you add more than one modifier to vue js v-model?

Can you add more than one modifier?
For example:
<input v-model.trim="name.first"/>
<input v-model.lazy="name.first"/>
into something along the line
<input v-model.{lazy,trim}="name.first"/>
Possible or not possible?
Yes!
followup question:
What is the concept behind it?
I understand it works,
but ".lazy.trim" sounds like trim is a part of lazy object
Yes,we can add more than one modifier to Vue js v-model.
new Vue({
'el': '#app',
data: {
val: 'default value',
num: 0,
trimExample: ''
},
methods: {
handleBtnClick() {
console.log(this.trimExample, this.num)
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-model.lazy="val">
<br> {{val}}
<br><br>
<input type="text" v-model.number="num">
<input type="text" v-model.trim.lazy="trimExample">
<button v-on:click="handleBtnClick">
check console
</button>
</div>
Fiddle Link
Yes you can use it !
for example you can see this link which v-model.trim.lazy is used in it !
https://vuelidate.js.org/#sub-basic-form
<input class="form__input" v-model.trim.lazy="$v.age.$model"/>

Binding to array fields in vue.js

I want to make multiple 'in-place' editable date fields in table rows.
An example for a single field below works.
I show the currentdate (oldDate) as a label. User clicks on 'Change', an input field appears, after editing the user can Accept or Cancel.
https://jsfiddle.net/asrajan55/qv6crg84/
<div id="root">
<label>Test Date:</label>
<span v-show="!makeEditable"> {{ oldDate }} </span>
<span v-show = "makeEditable">
<input type="date" v-model="newDate" required=""/>
<button #click="acceptClicked">Accept</button>
<button name="cancel" #click="makeEditable=false">Cancel</button>
</span>
<button v-show="!makeEditable" #click="makeEditable=true" >Change</button>
</div>
new Vue({
el: "#root",
data: {
oldDate: '2019-02-04',
newDate: '2019-02-04',
makeEditable: false,
},
methods: {
acceptClicked(){
if (this.newDate!='') {
this.oldDate=this.newDate;
this.makeEditable=false;
}
}
}
});
However if I try multiple(2) fields the click event fires (sometimes) but nothing seems to happen. No errors in console. Also the Vue debugger in the browser does not immediately update the changed fields. Please help. I am desperate and pulling my hair out!
https://jsfiddle.net/asrajan55/9uhkr4w0/3/
<div id="root">
<div v-for="(item,index) in oldDates">
<label for="">Test Date:</label>
<span v-show="!editables[index]">{{item}}</span>
<input v-show="editables[index]" type="date" v-model="oldDates[index]"/>
<button v-show="editables[index]">Accept</button>
<button v-show="editables[index]" #click="editables[index]=false">Cancel</button>
<button v-show="!editables[index]" #click="makeEditable(index)">Change</button>
<hr />
</div>
</div>
new Vue({
el: "#root",
data: {
oldDates: ['2019-01-04', '2019-02-04'],
newDates: ['2019-01-04', '2019-02-04'],
editables: [false, false]
},
methods: {
makeEditable(index) {
alert(index);
this.editables[index] = true;
}
}
});
The problem was that you were mutating the array in place,
creating a new array reference and passing it would solve the issue
fixed here : https://jsfiddle.net/e3L2zcna/
makeEditable(index) {
this.editables = this.editables.map((val,i) => i===index || val);
}
Try using this.$set(this.editables, index, true);
Vue can't detect changes to arrays if you directly access an element using []. Read about it here:
https://vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating

Getting data back into the root with nested components in vue

I am building a multiple page app with latest Laravel and latest Vue.js. At the end of this post you will see what I am trying to achieve - which I have done visually. However the user needs to be able to edit the text, assigned user and the date of each item. I have started with the date and as you can see I have the date picker working as well.
Where I am struggling is updating the main model of data in the root so that I can save the changes that the user has made via a HTTP request. Initially the tree's data is loaded in via HTTP as well (example below).
I have built the below using nested components and I have read that two binding has been depreciated for props on components. I know that I need to emit and user events but I'm sure how this would work if the components are nested?
Here is an example of the data that get's loaded via HTTP. Below is a very small example, however this could be much larger
{
"objective":"Test",
"user_id":null,
"by":"08\/09\/2018",
"colour":"#1997c6",
"children":[
{
"objective":"Test",
"user_id":11,
"by":"08\/09\/2018",
"colour":"#d7e3bc",
"children":[]
}, {
"objective":"Test",
"user_id":11,
"by":null,
"colour":"#1997c6",
"children":[]
}
]
}
Here are the components that I have put together so far.
Vue.component('tree-date', {
props: ['date'],
data () {
return {
id: 0
}
},
mounted() {
this.id = uniqueId();
$('#picker-' + this.id).datetimepicker({
format: 'DD/MM/YYYY',
ignoreReadonly: true
});
},
template: `
<div class="date-container" :id="'picker-' + id" data-target-input="nearest" data-toggle="datetimepicker" :data-target="'#picker-' + id">
<div class="row">
<div class="col-2">
<div class="icon">
<i class="fa fa-calendar-alt"></i>
</div>
</div>
<div class="col-10">
<input type="text" class="form-control datetimepicker-input" readonly="readonly" :data-target="'#picker-' + id" v-model="date">
</div>
</div>
</div>`
});
Vue.component('tree-section', {
props: ['data', 'teamUsers', 'first'],
methods: {
test () {
this.$emit('test');
}
},
template: `
<table v-if="data.length != 0">
<tr>
<td :colspan="data.children !== undefined && (data.children.length * 2) > 0 ? data.children.length * 2 : 2">
<div class="node" :class="{'first': first == true}">
<div class="inner">
<tree-date :date="data.by"></tree-date>
<div class="objective">
{{ data.objective }}
</div>
<div class="author" v-if="data.user_id !== null">
{{ teamUsers[data.user_id].first_name }} {{ teamUsers[data.user_id].last_name }}
</div>
<div class="author" v-if="data.user_id === null">
Unassigned
</div>
</div>
</div>
</td>
</tr>
<tr class="lines" v-if="data.children.length > 0">
<td :colspan="data.children.length * 2"><div class="downLine"></div></td>
</tr>
<tr class="lines" v-if="data.children.length > 0">
<td class="rightLine"></td>
<td class="topLine" v-for="index in ((data.children.length * 2) - 2)" :key="index" :class="{'rightLine': index % 2 == 0, 'leftLine': Math.abs(index % 2) == 1}"></td>
<td class="leftLine"></td>
</tr>
<tr v-if="data.children.length > 0">
<td colspan="2" v-for="child in data.children">
<tree-section :data="child" :team-users="teamUsers" :first="false"></tree-section>
</td>
</tr>
</table>
`
});
This all get's called in the view by:
<tree-section :data="data" :team-users="teamUsers" :first="true"></tree-section>
Any help getting data update in the components back into the root will be most helpful.
by default, vue props (if objects or arrays) are being passed by reference- that means that if you change your object on the child component, the original object on the parent component will get changed too.
from vue api:
Note that objects and arrays in JavaScript are passed by reference, so
if the prop is an array or object, mutating the object or array itself
inside the child component will affect parent state.
https://v2.vuejs.org/v2/guide/components-props.html

remove option from second select if already selected [Ordered multiple selection]

I had select option repeater.
What I want is that when I selected johndoe at the first option, it will no longer display on the second select option.
here's my html
<div id="app">
<h1>Vue JS Multiple Fields Repeater</h1>
<div class="col-sm-6">
<div class="panel panel-default relative has-absolute" v-for="(field, index) in users.usersRepeater">
<button #click="addUsersField" type="button">
Add
</button>
<button #click="deleteUsersField(index)" v-if="field != 0" type="button">
Delete
</button>
<div class="panel-body has-absolute">
<div class="form-group">
<label for="users" class="control-label col-sm-3 text-left">Users {{field}}</label>
<select :name="'users'+index"
class="form-control"
id="users">
<option value="" hidden>Select User</option>
<option value="1">John Doe</option>
<option value="2">Mark Doe</option>
<option value="3">Mae Doe</option>
<option value="4">John Smith</option>
<option value="5">Mae Smith</option>
</select>
</div>
</div>
</div>
</div>
</div>
here's my vue.js
new Vue({
el: '#app',
data: {
users: {
usersRepeater: [{ user: '' }]
}
},
methods: {
addUsersField: function() {
this.users.usersRepeater.push({
user: ''
});
},
deleteUsersField: function(index) {
this.users.usersRepeater.splice(index, 1);
},
}
});
here's the fiddle -> https://jsfiddle.net/0e3csn5y/23/
Ok I have this working now. I liked the question because making an ordered selection is a generic case. But, for me anyway, it wasn't straightforward. The breakthrough was realising that, when you number the choices, the whole state of the component could be encapsulated in one array, allUsers. Available users and choices then become computed properties, based on this array. Moral of the story: get your store right, with no interactions between elements of the store.
My answer weighs in at 130 lines. How long and hard would this be without Vue? Mind boggles.
Stack wants me to post some code, so here's the computed property that generates an array of choices made, in order of their priority, from the all users array...
choices(){
return this.store.allUsers.map((aUser,index)=>{
if(aUser.selection != null)
return {idxAllUsers : index, selection: aUser.selection};
else
return null;
})
.filter(aSelection=>aSelection != null)
.sort((a,b)=>{return a.selection - b.selection})
.map(a=>a.idxAllUsers);
},
I found this one very helpful.

Vue: Binding radio to boolean

I'm having trouble binding radiobuttons to boolean values in model.
In this example: https://jsfiddle.net/krillko/npv1snzv/2/
On load, the radio radio button is not checked, and when I try to change them, the 'primary' value in model is becomes empty.
I've tried:
:checked="variation.primary == true"
but with no effect.
To bind radio buttons to boolean values instead of string values in Vue, use v-bind on the value attribute:
<input type="radio" v-model="my-model" v-bind:value="true">
<input type="radio" v-model="my-model" v-bind:value="false">
I'll leave it to you to figure out how to match these values with your backend data.
Checkboxes are not so good for this scenario; the user could leave them both blank, and you don't get your answer. If you are asking a yes/no or true/false question where you want only one answer, then you should be using radio buttons instead of checkboxes.
What you are looking for is a checkbox. Here is an updated jsfiddle.
Your use case is not how radio buttons are supposed to work.
Look at this example.
new Vue({
el: '#app',
data: {
picked: 'One',
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.js"></script>
<div id="app">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br><br>
<span>Picked: {{ picked }}</span>
</div>
I ran into this myself too, the thing to remember is that the value attribute actually shouldn't change for the radio button, what changes (and what you need to bind to) is the checked attribute.
And then you need to handle the change event to set the correct item's value in your data.
Based on your jsFiddle, I think this is what you're looking for:
<div id="l-main">
<div v-for="(variation, key) in variations">
<label>
{{ variation.name }}
<input
type="radio"
name="Test"
:value="key"
:checked="variation.primary"
#change="onChange"
/>
</label>
</div>
<br>Output:<br>
<div v-for="(variation, key) in variations">
{{ variation.name }} {{ variation.primary }}
</div>
</div>
var vm = new Vue({
el: '#l-main',
data: {
variations: {
'41783' : {
'name': 'test1',
'primary': false
},
'41785' : {
'name': 'test2',
'primary': true
}
}
},
methods: {
onChange($event) {
// the primary variation is the one whose key
// matches the value of the radio button that got checked
for (const key in this.variations) {
this.variations[key].primary = key === $event.target.value;
}
}
}
});