At the moment I am trying to include different text when an image and/or icon shows on the page. Here is the code for the vue file:
<template>
<div class="profile">
<div
:class="{
'flags--relevant': hasFlagType('medication'),
'flags--active': flag == 'medication'
}"
class="flags"
#click="setFlag('medication')"
>
<medication-icon
:class="[
hasFlagType('medication')
? 'medication-icon--focus'
: 'medication-icon--blur',
]"
/>
</div>
<div
:class="{
'flags--relevant': hasFlagType('condition'),
'flags--active': flag == 'condition'
}"
class="flags"
#click="setFlag('condition')"
>
<treatment-icon
:class="[
hasFlagType('condition')
? 'treatment-icon--focus'
: 'treatment-icon--blur',
]"
/>
</div>
<div
:class="{
'flags--relevant': hasFlagType('translator'),
'flags--active': flag == 'translator',
}"
class="flags"
#click="setFlag('translator')"
>
<foreign-dialect-icon
:class="[
hasFlagType('translator')
? 'foreign-dialect-icon--focus'
: 'foreign-dialect-icon--blur',
]"
/>
</div>
</div>
</template>
<script>
export default {
components: {
ForeignDialectIcon,
MedicationIcon,
TreatmentIcon,
},
props: {
userFlags: {
type: Array,
default() {
return {};
},
},
},
data() {
return {
flags: this.userFlags,
flag: null,
title: "Requires daily medication",
title2: "Specialist health condition",
title3: "Requires a translator",
};
},
methods: {
hasFlagType(flagType) {
return this.flags[flagType] !== undefined;
},
setFlag(flagType) {
if (this.hasFlagType(flagType)) {
this.flag = flagType;
}
},
resetFlag() {
this.flag = null;
},
},
};
</script>
I have tried outputting the titles in the data section for each icon and they still show even if the icon doesn't show. I need it to output the title when the image is shown and the many attempts I've tried haven't worked so was wondering how I am able to solve this?
Assuming the title should only appear when the corresponding icon is focused, you could use the same condition (hasFlagType(...)) with v-if to render the title:
<div>
<medication-icon .../>
<span v-if="hasFlagType('medication')">{{ title }}</span>
</div>
<div>
<treatment-icon .../>
<span v-if="hasFlagType('condition')">{{ title2 }}</span>
</div>
<div>
<foreign-dialect-icon .../>
<span v-if="hasFlagType('translator')">{{ title3 }}</span>
</div>
Related
I currently have a page where I'm able to switch Algolia indices with this:
<template>
<button #click="selectedIndex = a">List A</button>
<button #click="selectedIndex = b">List B</button>
<A v-if="selectedIndex === a" />
<B v-if="selectedIndex === b" />
</template>
<script>
import A from '#/A.vue';
import B from '#/B.vue';
export default {
components: {
A,
B
},
data() {
return {
selectedIndex: `a_${this.$root.index}`,
query: ''
};
},
computed: {
a() {
return `a_${this.$root.index}`;
},
b() {
return `b_${this.$root.index}`;
}
}
};
</script>
This is in a file called Index.vue. The different indices are in files A.vue and B.vue.
But now I need to be able to do the same content switching using a vue-multiselect in A.vue and B.vue.
Currently in A.vue, I have
<template>
<ais-instant-search
:search-client="searchClient"
:index-name="a"
:routing="routing"
>
<multiselect
v-model="selectedIndex"
:options="switcherOptions"
:searchable="false"
:close-on-select="true"
:show-labels="false"
placeholder="Choose"
>
<template slot="singleLabel" slot-scope="{ option }">
{{ option.text }}
</template>
<template slot="option" slot-scope="{ option }">
{{ option.text }}
</template>
</multiselect>
</ais-instant-search>
</template>
export default {
components: {
Multiselect
},
data() {
searchClient: algoliasearch(window.algolia.id, window.algolia.key),
selectedIndex: { value: 'a', text: 'List A' },
switcherOptions: [
{ value: 'a', text: 'List A' },
{ value: 'b', text: 'List B' }
]
};
},
};
What I don't know how to do now is send the value from the multi-select from A.vue back up to Index.vue where the different indices are defined.
First observation that I have, in your A.vue you are binding variable named a to your index, but do not have a defined in your data, same thing for routing.
What is the point of computed properties a and b, they are just returning strings, with do not do any computing, this could probably be defined in data:
data() {
return {
selectedIndex: 'a_index',
query: '',
a: 'a_index',
b: 'b_index',
};
},
Are A.vue and B.vue identical components? If only bindings are different, you can probably combine them into one component, and just pass different props to them. Hard to know for sure without seeing complete code.
So one way of doing this is emitting events from child to parent components. Documentation reference: https://vuejs.org/guide/essentials/event-handling.html
On your multiselect component add #select="$emit("indexSelected", selectedIndex)"
Like so:
<multiselect
v-model="selectedIndex"
:options="switcherOptions"
:searchable="false"
:close-on-select="true"
:show-labels="false"
placeholder="Choose"
#select="$emit("indexSelected", selectedIndex)"
>
This will emit event with name of indexSelected and included selectedIndex value in its payload.
Then in Index.vue you need to add these event listeners to both A and B Components:
<A v-if="selectedIndex === a" #indexSelected="selectedIndex = $event.value === 'a' ? 'a_index' : 'b_index'" />
<B v-if="selectedIndex === b" #indexSelected="selectedIndex = $event.value === 'a' ? 'a_index' : 'b_index'" />
Or if you would like cleaner template you can create a method:
methods: {
updateSelectedIndex(event){
this.selectedIndex = event.value === 'a' ? 'a_index' : 'b_index';
}
}
And then update template to:
<A v-if="selectedIndex === a" #indexSelected="updateSelectedIndex" />
<B v-if="selectedIndex === b" #indexSelected="updateSelectedIndex" />
EDIT:
I think this is kind of what you are looking for(reading between the lines lol), this rolls Index.vue, A.vue, B.vue into one component, because what you are trying to achieve is a lot simpler this way IMO. Obviously your actual app is more complex, so apply this as needed.
IndexAB.vue would look like this, values replaces with algolia demo, so substitute as needed:
<template>
<div>
<p>
<strong>Changing Index with buttons: </strong>
<button
v-for="option in searchIndexOptions"
:key="option.value"
#click="searchIndexName = option"
>
{{ option.text }}
</button>
</p>
<ais-instant-search
:search-client="searchClient"
:index-name="searchIndexName.value"
>
<p>
<strong>Changing Index with vue-multiselect: </strong>
<VueMultiselect
v-model="searchIndexName"
:options="searchIndexOptions"
:searchable="false"
:close-on-select="true"
track-by="value"
label="text"
placeholder="Change Search Index Here"
>
<template v-slot:singleLabel="{ option }">
<strong>{{ option.text }}</strong>
</template>
<template v-slot:option="{ option }">
<strong>{{ option.text }}</strong>
</template>
</VueMultiselect>
</p>
<ais-search-box />
<ais-hits>
<template v-slot:item="{ item }">
<h2>{{ item.name }}</h2>
</template>
</ais-hits>
</ais-instant-search>
</div>
</template>
<script>
import VueMultiselect from "vue-multiselect";
import algoliasearch from "algoliasearch/lite";
import "instantsearch.css/themes/satellite-min.css";
import "vue-multiselect/dist/vue-multiselect.css";
export default {
name: "IndexAB",
components: { VueMultiselect },
data: () => ({
searchClient: algoliasearch("latency", "6be0576ff61c053d5f9a3225e2a90f76"),
searchIndexName: { value: "instant_search", text: "List A" }, // Defaults to instant_search/List A
searchIndexOptions: [
{ value: "instant_search", text: "List A" },
{ value: "airbnb", text: "List B" },
{ value: "airports", text: "List C" },
],
}),
};
</script>
And sandbox: https://codesandbox.io/s/compassionate-ptolemy-9ljmhh?file=/src/components/IndexAB.vue
I had started with Vue 3 sandbox, so few things will be sligtly different(like import of vue-multiselect, and v-slot syntax)
I have the following:
<div v-for="num in [1,2,3,4,5]" :key="num ">
<customelement0 :num ="num" />
<span #click="show = !show" > Details: </span>
<customelement1 v-if="show" :num ="num" />
<hr />
</div>
and:
export default {
data() {
return {
show: false
};
},
};
However, in this implementation Whenever show changes it affects all of the customelement1s and will show/hide all of them.
How would one solve this problem, so that whenever a user clicks on the span it only shows/hides one element in the loop?
P.S.: in reality, the length of the loop is much longer, than what's indicated above
The problem here is essentially that your show is a variable for the entire component, and isn't linked to one of your array elements.
Generally, we don't really tend to hard-code an array into the html, but rather in the data, as shown in the other answers.
The other answers show it with num being coded into the object but you could also do something like this. Note that other field is optional and not required. The main advantage of this method is that you don't need to code every number.
<div v-for="(item, i) in items" :key="i">
<!-- Note I do i+1 so that it matches the 1,2,3 in your example -->
<!-- if you're okay with having 0,1,2 you can omit the +1 -->
<customelement0 :num="i+1" />
<span #click="item.show = !item.show" >Details: </span>
<customelement1 v-if="item.show" :num="i+1" />
<hr />
</div>
export default {
data() {
return {
items: [
{show: false, other: "foo"},
{show: false, other: "bar"},
//...
],
};
},
};
You can change number to object and toggle property:
<div id="app">
<h2>Todos:</h2>
<ol>
<li v-for="todo in todos">
<label>
<input type="checkbox"
v-on:change="toggle(todo)"
v-bind:checked="todo.done">
<del v-if="todo.done">
{{ todo.text }}
</del>
<span v-else>
{{ todo.text }}
</span>
</label>
</li>
</ol>
</div>
new Vue({
el: "#app",
data: {
todos: [
{ text: "1", done: false },
{ text: "2", done: false },
{ text: "3", done: true },
{ text: "4", done: true },
{ text: "5", done: true },
]
},
methods: {
toggle: function(todo){
todo.done = !todo.done
}
}
})
You can to save the value isShow for each element in the array. So you need to use an array of objects in you data and do something like this:
export default {
data() {
return {
numList: [
{
num: 1,
isShow: true
},
{
num: 2,
isShow: true
}
]
};
},
};
In your template:
<div v-for="item in numList" :key="item.num">
<customelement0 :num="item.num" />
<span #click="item.isShow = !item.isShow" > Details: </span>
<customelement1 v-if="item.isShow" :num="item.num" />
<hr />
</div>
So the problem I'm facing is that I have a template that fetches data from a Realtime Firebase Database and at the same time the user can import more data through an input element. I'm using Vue.js and I need the data to be bound to each other.
Here is my template:
<template>
<ul>
<li>
<input type="text" v-model="email" v-on:keyup.enter="addData()"/>
<img #click="addData()" src="#/assets/Plus.png" />
</li>
</ul>
<ul>
<li v-for="(item, key) in emails" :key="key">
<div>
<p>{{ item.data }}</p>
<img #click="deleteDataByKey(key)" src="#/assets/Delete.png" />
</div>
<div class="first">
<input type="text" v-model="comment[key]" v-on:keyup.enter="addComment(key, comment[key])"/>
<img #click="addComment(key, comment[key])" src="#/assets/Plus.png" />
</div>
<div class="second">
<p>{{ comment[key] }}</p>
<img #click="deleteCommentByKey(key)" src="#/assets/Delete.png" />
</div>
</li>
</ul>
</template>
Now what is happening is that I want to show <div class="first"> when there is no comment and when there is one, <div class="second"> should be shown while hiding the first one.
I tried using v-if="comment[key]" but it will toggle the divs straight away.
I also tried to v-model.lazy which seems to be working but then the method to update the db is not called.
I tried using pure JS in the method to change the HTML but it doesn't seem to be working as well.
These are my methods and data:
data() {
return {
emailList: [],
email: "",
comment: []
};
},
addData() {
db.ref("emailItems").push({
data: data
});
this.email = "";
this.fetchData();
},
deleteDataByKey(key) {
db.ref("emailItems"+key).remove();
this.fetchData();
},
addComment(key, comment) {
db.ref(`emailItems/${key}/comment`).set(comment);
},
deleteCommentByKey(key){
db.ref("comment/"+key).remove();
this.fetchData();
},
fetchData() {
db.ref("emailItems")
.once("value")
.then(snapshot => {
this.emailList = snapshot.val().emailItems;
});
}
And the db structure looks like this
Any help would be highly appreciated...
I think you should build more on the (arguably) biggest features of Vue, namely: reactivity & components.
Break down the logic a bit more, until you arrive at elements that do only one thing - those elements can be your components. Then build up the business logic from those atomic components.
Vue.component('NewEmail', {
data() {
return {
email: null,
}
},
methods: {
handleKeyup() {
this.$emit("add-email", {
email: this.email,
comment: null
})
this.email = null
}
},
template: `
<div>
<label>
NEW EMAIL: <input
type="email"
placeholder="Type in an email"
v-model="email"
#keyup.enter="handleKeyup"
/>
</label>
</div>
`
})
Vue.component('SingleEmailRow', {
props: {
email: {
type: Object,
default: null,
}
},
methods: {
handleDeleteClick() {
this.$emit('remove-email', this.email)
},
},
template: `
<li
class="d-flex"
>
<div>
{{ email.email }}
</div>
<div>
<button
#click="handleDeleteClick"
>
X
</button>
</div>
<component
:is="email.comment ? 'HasEmailComment' : 'NoEmailComment'"
:email="email"
v-on="$listeners"
></component>
</li>
`
})
Vue.component('HasEmailComment', {
props: {
email: {
type: Object,
required: true
}
},
methods: {
handleUpdateComment() {
this.$emit('update:comment', { ...this.email,
comment: null
})
}
},
template: `
<div
class="d-flex"
>
<div>
{{ email.comment }}
</div>
<button
title="Remove comment"
#click="handleUpdateComment"
>
-
</button>
</div>
`
})
Vue.component('NoEmailComment', {
props: {
email: {
type: Object,
required: true
}
},
data() {
return {
comment: null,
}
},
methods: {
handleUpdateComment() {
this.$emit('update:comment', { ...this.email,
comment: this.comment
})
}
},
template: `
<div
class="d-flex"
>
<div>
<input
v-model="comment"
type="text"
placeholder="Write a comment"
/>
</div>
<button
title="Add comment"
#click="handleUpdateComment"
>
+
</button>
</div>
`
})
new Vue({
el: "#app",
data() {
return {
emailList: [],
}
},
methods: {
handleAddEmail(newEmail) {
if (!this.emailList.find(({
email
}) => email === newEmail.email)) {
this.emailList.push(newEmail)
}
},
handleRemoveEmail(toRemove) {
this.emailList = this.emailList.filter(({
email
}) => {
return email !== toRemove.email
})
},
handleUpdateComment(newEmail) {
this.emailList = this.emailList.map(email => {
if (email.email === newEmail.email) {
return newEmail
} else {
return email
}
})
}
}
})
.d-flex {
display: flex;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<new-email #add-email="handleAddEmail"></new-email>
<ul v-for="(email, i) in emailList" :key="i">
<single-email-row :email="email" #remove-email="handleRemoveEmail" #update:comment="handleUpdateComment"></single-email-row>
</ul>
</div>
OK, the comment handling in SingleEmailRow could be put to its separate component (based on my thoughts above).
The main points in the snippet above are:
there's only one original data source (emailList in the root component) that is passed down as props where needed, and all the other components & functions just manipulate THAT list via events (reactivity is great!)
because all the components are based on one central data source, they just have to work with the item they get as props. (Ok, they have some internal state, but hey - this is only a snippet! :) )
the two components have only one responsibility:
NewEmail: to add an item to the central emailList
SingleEmailRow: to manage the data in ONE email item
I hope this helps you a bit to reach a solution for your problem.
EDIT: UPDATING SNIPPET
I had to update the snippet, because I wasn't satisfied with it.
Modifications:
adding two new components: HasEmailComment, NoEmailComment
updated SingleEmailRow with the two new components
Now, all the components have only one task to do.
I am trying to set the checked value of a Radio Button list in Vue.JS 2. I've reviewed existing articles here and tried them and also several different manual approaches and I just cannot get this to work at all.
I am NOT using v-model here as I'm working on a custom radio button list control which is consumed by a forms builder. This is further complicated by the fact that I am building, on top, a nested radio button list to handle nullable booleans. Putting that complexity aside, my radio component looks like this....
<template>
<div class="form__radio-list">
<span class="form__radio-list__intro">{{ introText }}</span>
<ul class="form__radio-list__options">
<li v-for="item in options"
:key="item.key ? item.key : item"
class="form__radio-list__options__item">
<input type="radio"
:id="item.key ? item.key.toKebabCase() : item.toKebabCase()"
:name="def"
:value="item.value != undefined ? item.value : item"
:disabled="disabled"
:checked="isChecked(item)"
#input="onInput"
#change="$emit('change', $event.target.checked)">
<label :for="item.key ? item.key : item">{{ item.text ? item.text : item }}</label>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
def: {
type: String,
required: true
},
introText: {
type: String,
default: ''
},
options: {
type: Array,
required: true
},
initialValue: {
type: [String, Number, Boolean],
default: null
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
value: this.initialValue
}
},
methods: {
_parseValue() {
return this.value ? parseInt(this.value) : null
},
isChecked(item) {
const checked = item.value === this.value || item === this.value || item.value == this._parseValue()
this.$logger.logObject({ item, parentValue: this.value, checked }, 'Checking value for an item')
return checked
},
onInput($event) {
this.value = $event.target.checked
this.$emit('input', $event.target.checked)
}
}
}
</script>
From the logging I can see that the value of 'checked' SHOULD be set correctly (but it isn't).
I also tried splitting the input tag into a 'v-if' statement so I'd have one with a checked parameter set and one without (although this felt horrible) and that worked from an HTML point of view (checked="checked" appeared where I would expect it to) but, on the browser neither of the 2 options were checked.
I am consuming the component through a boolean component renederer that looks like this...
<template>
<hh-radio class="form__radio-list--yes-no"
:def="def"
:intro-text="introText"
:options="options"
:initial-value="initialValue"
:disabled="disabled"
#change="$emit('change', $event)"
#input="$emit('input', $event)" />
</template>
<script>
export default {
props: {
def: {
type: String,
required: true
},
introText: {
type: String,
default: ''
},
initialValue: {
type: [String, Boolean],
default: null
},
disabled: {
type: Boolean,
default: false
}
},
computed: {
options() {
return [{
key: 'yes',
text: 'Yes',
value: true
}, {
key: 'no',
text: 'No',
value: false
}]
}
}
}
</script>
This is then consumed ultimately on a form like this...
<hh-yes-no class="editable-segment-field__bool"
:def="pvc-enabled"
:initial-value="pvc.value"
#input="onInput" />
The value pass throughs seem to work fine - The key issue that I have is that it will NOT specify the currently selected item from any existing data.
I have tried suggestions here - Vue.JS radio input without v-model and here - Vue.JS checkbox without v-model without much success.
Using the example given me below I've tried to strip this back as far as I can, adding in pieces of the dynamic elements from my components as I go to identify the problem root.
I now have a simpler component which looks like this...
<template>
<div :class="`form__radio-list ${(this.def ? `form__radio-list--${this.def} js-${this.def}` : '' )}`">
<span class="form__radio-list__intro">{{ introText }}</span>
<ul class="form__radio-list__options">
<li class="form__radio-list__options__item">
<input id="awesome"
type="radio"
name="isawesome"
:checked="radio === 'Awesome'"
value="Awesome"
#change="radio = $event.target.value">
<label for="awesome">Awesome</label>
</li>
<li class="form__radio-list__options__item">
<input id="super"
type="radio"
name="isawesome"
:checked="radio === 'Super Awesome'"
value="Super Awesome"
#change="radio = $event.target.value">
<label for="super">Super</label>
</li>
</ul>
<span>Selected: {{ value }} | {{ radio }}</span>
</div>
</template>
<script>
export default {
props: {
def: {
type: String,
required: true
},
introText: {
type: String,
default: ''
},
initialValue: {
type: [String, Boolean],
default: null
},
disabled: {
type: Boolean,
default: false
}
},
// delete this
data() {
return {
value: this.initialValue,
radio: 'Awesome'
}
},
computed: {
options() {
return [{
key: 'yes',
text: 'Yes',
value: true
}, {
key: 'no',
text: 'No',
value: false
}]
}
}
}
</script>
I managed to get this to fail as soon as I added name="isawesome" to the radio button items. It seems that when you introduce 'name' something goes awry. Surely I need 'name' to prevent multiple radio button lists interacting with each other or is this something that Vue handles which I've been unaware of.
Here is an working example:
new Vue({
el: "#app",
data: {
radio: 'Awesome'
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<span>Vue is:</span> <br>
<label>Awesome
<input
type="radio"
:checked="radio === 'Awesome'"
value="Awesome"
#change="radio = $event.target.value"
>
</label>
<label>Super Awesome
<input
type="radio"
:checked="radio === 'Super Awesome'"
value="Super Awesome"
#change="radio = $event.target.value"
>
</label>
<hr>
<span>Selected: {{radio}}</span>
</div>
This appears to be a bug / oddity with Vue.js. Using roli roli's example I was able to take both his example and my requirement and keep tweaking until they met in the middle so I could find out the problem via process of elimination.
Here is a copy of the component with both elements together. As #birdspider commented above, I would not expect this to work in HTML. In Vue, however, it DOES....
<template>
<div :class="`form__radio-list ${(this.def ? `form__radio-list--${this.def} js-${this.def}` : '' )}`">
<span class="form__radio-list__intro">{{ introText }}</span>
<ul class="form__radio-list__options">
<li class="form__radio-list__options__item">
<input id="yes"
type="radio"
:checked="value === true"
:value="true"
#change="value = $event.target.value">
<label for="yes">Yes</label>
</li>
<li class="form__radio-list__options__item">
<input id="no"
type="radio"
:checked="value === false"
:value="false"
#change="value = $event.target.value">
<label for="no">No</label>
</li>
<li class="form__radio-list__options__item">
<input id="awesome"
type="radio"
:checked="radio === 'Awesome'"
value="Awesome"
#change="radio = $event.target.value">
<label for="awesome">Awesome</label>
</li>
<li class="form__radio-list__options__item">
<input id="super"
type="radio"
:checked="radio === 'Super Awesome'"
value="Super Awesome"
#change="radio = $event.target.value">
<label for="super">Super</label>
</li>
</ul>
<span>Selected: {{ value }} | {{ radio }}</span>
</div>
</template>
<script>
export default {
props: {
def: {
type: String,
required: true
},
introText: {
type: String,
default: ''
},
initialValue: {
type: [String, Boolean],
default: null
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
value: this.initialValue,
radio: 'Awesome'
}
}
}
</script>
This will work completely fine and renders as 2 separate radio button lists - one for yes / no and one for awesome / super awesome. If you try and add a 'name' tag to the radio inputs then the checked state is no longer set in the radio button group at all.
---- UPDATE ----
This seems like a bug where an attempt to add 'name' should simply be ignored by Vue, but it isn't. However, this isn't the case if you create an ES5 style component in codepen (I am unable to repro this in codepen for this reason.)
Using ES6 style files, this component will not set any checked values (credit to #birdspider for half of this simplified example)...
<template>
<div>
<ul class="form__radio-list__options">
<li class="form__radio-list__options__item">
<input id="awesome" type="radio" name="isawesome"
:checked="radio === 'Awesome'"
value="Awesome"
#change="onChange($event.target.value)">
<label for="awesome">Awesome</label>
</li>
<li class="form__radio-list__options__item">
<input id="super" type="radio" name="isawesome"
:checked="radio === 'Super Awesome'"
value="Super Awesome"
#change="onChange($event.target.value)">
<label for="super">Super</label>
</li>
</ul>
<ul class="form__radio-list__options">
<li class="form__radio-list__options__item">
<input id="awesome" type="radio" name="other"
:checked="radio2 === 'other'"
value="other"
#change="onChange2($event.target.value)">
<label for="awesome">other</label>
</li>
<li class="form__radio-list__options__item">
<input id="super" type="radio" name="other"
:checked="radio2 === 'another'"
value="another"
#change="onChange2($event.target.value)">
<label for="super">another</label>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
radio: 'Awesome',
radio2: 'another'
}
},
methods: {
onChange(e) {
this.radio = e
},
onChange2(e) {
this.radio2 = e
}
}
}
</script>
(if anyone can tell me how to get it running in Codepen or JS Fiddle like this then that would be great!)
If I remove the name attributes then it works.
If you put essentially the same thing in Codepen as a single Vue instance rather than a component file then that works too.
I am trying to make a form which contains an input group section, in this group, there are one select box and multiple checkboxes. Checkboxes are populated based on the select box selection. There is also an add and remove button to generate and remove input group. The select box is used with v-model to filtered the checkboxes. But when I generate a new input group and make changes, all checkboxes are changed.
I want them to be isolated. How can I achieve?
Here is my Template.
<template>
<form #submit.prevent="onSubmit">
<div v-for="(variationProduct, index) in variationProducts" :key="variationProduct.id">
<div class="from-group mb-4">
<label class="col-form-label"><b>Categories :</b></label>
<select class="form-control mr-2" ref="categories" v-model="category">
<option value="0">Please select category...</option>
<option v-for="category in categories" :key="category.id" :value="category.id">
{{ category.name }}
</option>
</select>
<div v-if="hasError">
<validation-errors v-if="errors['categories.'+index]" :errors="errors">
{{ errors['categories.'+index][0] }}
</validation-errors>
</div>
</div>
<div class="form-group mb-4">
<label class="col-form-lablel"><b>Variation Products :</b></label>
<div class="row">
<div v-for="filteredVariationProduct in filteredVariationProducts" :key="filteredVariationProduct.id">
<div class="col-12">
<input :id="filteredVariationProduct.id" :value="filteredVariationProduct.id" type="checkbox" ref="variationProducts">
<label :for="filteredVariationProduct.id">{{ filteredVariationProduct.name }}</label>
</div>
</div>
</div>
<div v-if="hasError">
<validation-errors v-if="errors['variationProducts.'+index]" :errors="errors">
{{ errors['variationProducts.'+index][0] }}
</validation-errors>
</div>
</div>
<div class="float-right">
<button #click.prevent="add" class="btn btn-success">Add</button>
<button #click.prevent="remove(index)" class="btn btn-danger">Remove</button>
</div>
<br>
<br>
<hr>
</div>
<input type="submit" class="btn btn-primary" value="Submit">
</form>
</template>
Here is my JS.
<script>
import ValidationErrors from './ValidationErrors.vue';
export default {
components: {
'validation-errors': ValidationErrors,
},
data () {
return {
variationProducts: [],
categories: [
{ id: 1, name: 'Technology'},
{ id: 2, name: 'Business'},
{ id: 3, name: 'Marketing'},
{ id: 4, name: 'Development'},
{ id: 5, name: 'Engineering'},
],
category: 0,
activeVariationProducts: [],
count: 1,
errors: {},
hasError: false,
}
},
methods: {
add: function() {
this.count++;
this.errors = {};
this.hasError = false;
this.variationProducts.push({ id: this.count });
},
remove: function (index) {
if ( this.variationProducts.length > 0 && index > -1) {
this.variationProducts.splice(index, 1);
} else {
alert('Must have at least one input!')
}
},
onSubmit: function() {
console.log(this.$refs.variationProducts.value);
},
generateVariationProducts: function(num) {
for(let i = 1; i <= num; i++) {
let activeVariationProduct = {
id: i,
name: 'Product '+ i,
category_id: i
};
this.activeVariationProducts.push(activeVariationProduct);
}
},
},
computed : {
filteredVariationProducts: function () {
let categoryId = parseInt(this.category);
if (categoryId !== 0) {
let filteredVariationProducts = this.activeVariationProducts.filter((variationProduct) => {
return variationProduct.category_id === categoryId;
});
return filteredVariationProducts;
} else {
let filteredVariationProducts = this.activeVariationProducts;
return filteredVariationProducts;
}
}
},
created () {
this.variationProducts.push({ id: this.count });
this.generateVariationProducts(10);
},
}
</script>
Here is a sample code. This code Shows how you can use multiple Checkboxes that is generated dynamically and how to make them isolated -
new Vue({
el : "#app",
data : {
Items : ["One", "Two", "Three"],
newCheckbox : "",
SelectedItems : {
'One' : "",
'Two' : "",
'Three' : "",
},
},
methods:{
add:function(){
Vue.set(this.SelectedItems, this.newCheckbox, false);
this.Items.push(this.newCheckbox);
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.22/vue.min.js" type="text/javascript"></script>
<div id="app">
<div>
<label v-for="(item, index) in Items">
<input type="checkbox" v-bind:key="index" v-model="SelectedItems[item]"> {{ item }}
</label>
</div>
<div>
<input type="text" v-model="newCheckbox">
<button #click="add">Add</button>
</div>
<div>
Output : {{ SelectedItems }}
</div>
</div>
You can dynamically add new checkbox and still they are isolated