I'm trying to create a checkbox component but I am unable to do it. How can I create three checkboxs (using ichecks and inputs) and get all the selected ones using v-model in an array? The problem is that I can get the value of the clicked checkbox separately but I can not get all the values in array as it is suppose happen with vue's v-model. It seems that, they are getting lost somewhere. here my code
<html>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/iCheck/1.0.2/icheck.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/iCheck/1.0.2/skins/all.css" rel="stylesheet">
<div id='app'>
<icheck :value="'Jack'" id="jack" v-model="checkedNames" > </icheck>
<icheck :value="'John'" id="john" v-model="checkedNames" > </icheck>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
</div>
</html>
<script>
Vue.component('icheck', {
props: {
value:{ required: false }
},
template: `
<input type="checkbox" >
`,
mounted: function () {
var vm = this
$(this.$el).iCheck({
//checkboxClass: 'icheckbox_minimal',
checkboxClass: 'icheckbox_square-red',
//radioClass: 'iradio_minimal',
radioClass: 'iradio_square',
increaseArea: '20%' // optional
})
.val(this.value)
.trigger('ifChanged')
// emit event on change. here the problem
.on('ifChanged', (event)=>{
let isChecked = event.target.checked
let val = event.target.value
// here the problem need an array
vm.$emit('input',isChecked ? [val]:[])
});
},
watch: {
value: function (value) {
// update value
$(this.$el).val(value)
}
}
})
new Vue({
el: '#app',
data: {
checkedNames: []
}
})
</script>
Here is how I would do it.
<icheck :val="'Jack'" id="jack" v-model="checkedNames" > </icheck>
<icheck :val="'John'" id="john" v-model="checkedNames" > </icheck>
props: {
val:{ required: true },
value: { required: false }
},
....
// Check if the value is preset from the props and toggle check
if (this.value.includes(this.val)) {
$(this.$el).iCheck('check');
}
$(this.$el).iCheck({
//checkboxClass: 'icheckbox_minimal',
checkboxClass: 'icheckbox_square-red',
//radioClass: 'iradio_minimal',
radioClass: 'iradio_square',
increaseArea: '20%' // optional
})
.val(this.val)
.trigger('ifChanged')
.on('ifChanged', (event)=>{
// create a local copy so as not to modify the prop
let value = [].concat(this.value)
// check if the array includes the value already
if(value.includes(this.val)){
// if it does remove it
value.splice(value.indexOf(this.val), 1)
} else {
// if it doesn't add it
value.push(this.val)
}
// emit the entire array
vm.$emit('input', value)
});
I see what you try to do, but this only works with native checkboxes. if the v-model is an array and the element is a checkbox, it will change the v-model to something like v-model="data[index]". This does not work with custom elements, as you try to do.
Related
Im attempting to recreate this exact inline editing functionality in on of my vue components. However, and I may be wrong, I see some of the syntax is outdated Vue, in particular the v-el directive being used. I've attempted to update the syntax like so:
new Vue({
el: '#app',
data: {
numbers: [{
val: 'one',
edit: false
},
{
val: 'two',
edit: false
},
{
val: 'three',
edit: false
}
]
},
methods: {
toggleEdit: function(ev, number) {
number.edit = !number.edit
// Focus input field
if (number.edit) {
Vue.nextTick(function() {
ev.$refs.input.focus(); // error occurs here
})
}
},
saveEdit: function(ev, number) {
//save your changes
this.toggleEdit(ev, number);
}
}
})
<div id="app">
<template v-for="number in numbers">
<span v-show="!number.edit"
v-on:click="toggleEdit(this, number)">{{number.val}}</span>
<input type="text"
ref="input"
v-model="number.val"
v-show="number.edit"
v-on:blur="saveEdit(ev, number)"> <br>
</template>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
However I get a range of errors... any suggestions on how to properly execute this?
Here is the Error:
[Vue warn]: Error in nextTick: "TypeError: undefined is not an object
(evaluating 'ev.$refs.input')"
Many things changed from Vue.js 1.x to 2.x. I will walk you through the changes necessary in that snippet of yours:
v-repeat should be v-for
Replace v-el="input" with ref="input"
Since you are using ref="input" inside a v-for, then this.$refs.input will be an array of elements, not a single element.
To access each single element, you will need an index (for the array), that's why you should include the index variable in the v-for: v-for="(number, index) in numbers"
Pass the index instead of the ev to the functions, so you can get the<input>s later using vm.$refs.input[index].focus();
And that's pretty much it. After changes you'll get:
new Vue({
el: '#app',
data: {
numbers: [
{
val: 'one',
edit: false
},
{ val: 'two',
edit: false
},
{
val: 'three',
edit: false
}
]
},
methods: {
toggleEdit: function(index, number){
number.edit = !number.edit;
// Focus input field
var vm = this;
if (number.edit){
Vue.nextTick(function() {
vm.$refs.input[index].focus();
})
}
},
saveEdit: function(index, number){
//save your changes
this.toggleEdit(index, number);
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<template v-for="(number, index) in numbers">
<span v-show="!number.edit"
v-on:click="toggleEdit(index, number)">{{number.val}}</span>
<input type="text"
ref="input"
v-model="number.val"
v-show="number.edit"
v-on:blur="saveEdit(index, number)"> <br>
</template>
</div>
If you want the functionality and not the code design, I'd recommend you redesign it. I think you want to edit data, and the data shouldn't have to know whether it's being edited. That is the role of a component.
So let's make a component that lets you v-model data. The component itself has a span and an input. If you're editing, it shows the input, otherwise, the span. Click starts editing, blur stops editing. When editing starts, set focus on the input.
It takes a value prop. Its input element emits an input event to signal changes (per component v-model spec.
new Vue({
el: '#app',
data: {
stuff: ['one', 'two', 'three']
},
components: {
inlineEditor: {
template: '#inline-editor-template',
props: ['value'],
data() {
return {
editing: false
}
},
methods: {
startEditing() {
this.editing = true;
this.$nextTick(() => this.$refs.input.focus());
},
stopEditing() {
this.editing = false;
}
}
}
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<inline-editor v-for="item, index in stuff" v-model="stuff[index]"></inline-editor>
</div>
<template id="inline-editor-template">
<div>
<span #click="startEditing" v-show="!editing">{{value}}</span>
<input ref="input" :value="value" #input="e => $emit('input', e.target.value)" #blur="stopEditing" v-show="editing">
</div>
</template>
Okay, this web app I am "still" trying to make is taking longer than I expected and I still don't get some stuff about Vue 2.
For example, I have a componenent that renders in the main Vue instance and I $emit a function inside the componenet. Of course, the "inside" function is working but not the "main" one:
This is the component file PropertyHouseComponent.vue:
<template>
<fieldset class="form-group col-md">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="house"
name="house"
v-model="data"
#click="callFunc">
<label
for="house"
class="form-check-label"
>House</label>
</div>
</fieldset>
</template>
<script>
module.exports = {
props: {
value: {
type: Boolean,
required: false
}
},
data: function() {
return {
data: false
}
},
created: function() {
if(this.value) {
this.data = this.value;
}
},
methods: {
callFunc: function() { <-- this method is being called correctly
this.$emit('toggleHouse', this.data);
}
}
}
</script>
I've also tried with this.$parent.$emit('toggleHouse') and it's the same and by using instead of #click="callFunc":
watch: {
data: function(value) {
this.$emit('toggleHouse', value);
}
}
This is the main Vue instance function toggleHouse() in app.js:
Vue.component('property-house', require('./components/PropertyHouseComponent.vue'));
...
toggleHouse: function() { <-- this method is being completely ignored
if(value) {
this.csHouse = value;
}
}
Just in case you think "you are not passing value to the function, so it won't change anything, I tried with console.log(). But is not my concern right now (I think)
And finally, this is the HTML part:
<property-house :value="true"></property-house>
And then it renders in the DOM correcly:
So, what am I doing wrong this time and what I should do in order to call toggleHouse from inside the component? Thanks in advance.
You don't emit functions. You emit events.
You need to listen for the toggleHouse event, and I would additionally recommend never using camel cased event names (for reasons beyond the scope of this answer).
Let's assume you change to use toggle-house instead.
this.$emit('toggle-house', value)
Where you use the property-house should look like this
<property-house :value="true" #toggle-house="toggleHouse"></property-house>
Note the #toggle-house. This is the shortcut method for listening to an event in Vue; you could also use the full syntax, v-on:toggle-house="toggleHouse".
In either case (using #toggle-house or v-on:toggle-house) a listener is set up that listens for the toggle-house event you are emitting from the child component that calls the toggleHouse method when the event occurs.
Here is a very rudimentary example.
console.clear()
Vue.component("property-house", {
template: `
<div>
<button #click="$emit('toggle-house', 'Clicked the button')">Click Me</button>
</div>`
})
new Vue({
el: "#app",
methods: {
toggleHouse(message){
alert(message)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<property-house #toggle-house="toggleHouse"></property-house>
</div>
I needed to change the strategy since #click="callFunc" was taking the opposite checkbox value, I guess, that event was fired before changing the v-model="data".
Therefore my new components look like this:
<template>
<fieldset class="form-group col-md">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="house"
name="house"
v-model="data">
<label
for="house"
class="form-check-label"
>House</label>
</div>
</fieldset>
</template>
<script>
module.exports = {
props: {
value: {
type: Boolean,
required: false
}
},
data: function() {
return {
data: false
}
},
created: function() {
if(this.value) {
this.data = this.value;
}
},
watch: {
data: function(value) {
this.$emit('callback', value);
}
}
}
</script>
I'm trying to make a set of components for repetitive use. The components I'm looking to create are various form fields like text, checkbox and so on.
I have all the data in data on my parent vue object, and want that to be the one truth also after the user changes values in those fields.
I know how to use props to pass the data to the component, and emits to pass them back up again. However I want to avoid having to write a new "method" in my parent object for every component I add.
<div class="vue-parent">
<vuefield-checkbox :vmodel="someObject.active" label="Some object active" #value-changed="valueChanged"></vuefield-checkbox>
</div>
My component is something like:
Vue.component('vuefield-checkbox',{
props: ['vmodel', 'label'],
data(){
return {
value: this.vmodel
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="value" #change="$emit('value-changed', value)">
{{label}}
</label>
</div>
</div>`
});
I have this Vue object:
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
},
methods: {
valueChange: function (newVal) {
this.carouselAd.autoOrder = newVal;
}
}
});
See this jsfiddle to see example: JsFiddle
The jsfiddle is a working example using a hard-coded method to set one specific value. I'd like to eighter write everything inline where i use the component, or write a generic method to update the parents data. Is this possible?
Minde
You can use v-model on your component.
When using v-model on a component, it will bind to the property value and it will update on input event.
HTML
<div class="vue-parent">
<vuefield-checkbox v-model="someObject.active" label="Some object active"></vuefield-checkbox>
<p>Parents someObject.active: {{someObject.active}}</p>
</div>
Javascript
Vue.component('vuefield-checkbox',{
props: ['value', 'label'],
data(){
return {
innerValue: this.value
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="innerValue" #change="$emit('input', innerValue)">
{{label}}
</label>
</div>
</div>`
});
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
}
});
Example fiddle: https://jsfiddle.net/hqb6ufwr/2/
As an addition to Gudradain answer - v-model field and event can be customized:
From here: https://v2.vuejs.org/v2/guide/components.html#Customizing-Component-v-model
By default, v-model on a component uses value as the prop and input as
the event, but some input types such as checkboxes and radio buttons
may want to use the value prop for a different purpose. Using the
model option can avoid the conflict in such cases:
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean,
// this allows using the `value` prop for a different purpose
value: String
},
// ...
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>
The above will be equivalent to:
<my-checkbox
:checked="foo"
#change="val => { foo = val }"
value="some value">
</my-checkbox>
I have a list of filters using checkboxes. I'm trying to make each checkbox it's own components. So I loop through my list of filters adding a checkbox component for each filter. The Vue.js documentation says that if I have multiple checkboxes that use the same model that array will get updated with the value of the checkboxes. I see that working if the group of checkboxes is part of the parent component. But if I make the checkbox a component and add each checkbox component in a loop then the model doesn't update as expected.
How can I have a checkbox component that updates an array on the parent? I know I can do this with emitting an event for a method on the component that updates the array but the Vue documentation makes it seems like the framework does this for you.
Here is a code sample I've been playing around with https://www.webpackbin.com/bins/-KwGZ5eSofU5IojAbqU3
Here is a working version.
<template>
<div class="filter-wrapper">
<input type="checkbox" v-model="internalValue" :value="value">
<label>{{label}}</label>
</div>
</template>
<script>
export default {
props: ['checked','value', 'label'],
model: {
prop: "checked"
},
computed:{
internalValue: {
get(){return this.checked},
set(v){this.$emit("input", v) }
}
}
}
</script>
Updated bin.
The answer given by #Bert is right. I just want to complete the picture with the list of components and how thay are integrated. As this is a useful pattern.
Also including Select All functionality
ListItem.vue
<template>
<div class="item">
<input type="checkbox" v-model="internalChecked" :value="item.id" />
... other stuff
</div>
</template>
<script>
export default {
// Through this we get the initial state (or if the parent changes the state)
props: ['value'],
computed:{
internalChecked: {
get() { return this.value; },
// We let the parent know if it is checked or not, by sending the ID
set(selectedId) { this.$emit("input", selectedId) }
}
}
}
</script>
List.vue
<template>
<div class="list">
<label><input type="checkbox" v-model="checkedAll" /> All</label>
<list-item
v-for="item in items"
v-bind:key="item.id"
v-bind:item="item"
v-model="checked"
</list-item>
... other stuff
</div>
</template>
<script>
import ListItem from './ListItem';
export default {
data: function() {
return: {
// The list of items we need to do operation on
items: [],
// The list of IDs of checked items
areChecked: []
}
},
computed: {
// Boolean for checked all items functionality
checkedAll: {
get: function() {
return this.items.length === this.areChecked.length;
},
set: function(value) {
if (value) {
// We've checked the All checkbox
// Starting with an empty list
this.areChecked = [];
// Adding all the items IDs
this.invoices.forEach(item => { this.areChecked.push(item.id); });
} else {
// We've unchecked the All checkbox
this.areChecked = [];
}
}
}
},
components: {
ListItem
}
}
</script>
Once boxes get checked we have in checked the list of IDS [1, 5] which we can use to do operation on the items with those IDs
I'd like to know is there any way to fire custom functions - one function when checkbox changes to true value and another when it changes to false value (without using $watch).
For example:
I have input wrapped in root div
<div id="root">
<input type="checkbox" v-model="editModeOn">
</div>
and a vue instance with disableEditMode (on checkbox unchecked) and enableEditMode (on checkbox checked)
new Vue({
el: '#root',
props: {
editModeOn: {
type: Boolean,
default: false
}
},
methods: {
disableEditMode() {
// some code
},
enableEditMode() {
// some code
}
},
});
How can I achieve this functionality? Thanks!
Handle the change event.
#change="editModeOn ? enableEditMode() : disableEditMode()"
new Vue({
el: '#root',
data:{
editModeOn: false
},
methods: {
disableEditMode() {
console.log("disable")
},
enableEditMode() {
console.log("enable")
}
},
});
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="root">
<input type="checkbox" v-model="editModeOn" #change="editModeOn ? enableEditMode() : disableEditMode()">
</div>