How to handle multiple checkboxes with same v-model in vue.js? - vue.js

It will be very helpful for me if anyone shows me about sending and retrieving multiple checkbox value to backend from vue.js. I searched for it through the internet but hardly found anything.
What I need is suppose I have a category and there are many books under that category. In my books table in database there are 20 books and I want to assign 10 books from within 20 books under a certain category from admin panel by using checkboxes in vue.js.
Also I have to retrieve them and show as checked for a certain category. For now I only know that I am going to use an array as v-model and use axios to send them and retrieve them.
`<template>
<div class="container">
<form>
<div class="form-group" v-for="(book, index) in books" :key="index">
<input type="checkbox" v-model="selected_books" id="">
<label for="">{{ book.name }}</label>
</div>
<div class="form-group">
<input type="file" #change="image" id="">
</div>
<button #click="save" type="submit">Save</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
books: [],
selected_books: [],
image: ""
}
},
methods: {
save: function () {
let fd = new FormData();
fd.append("images", this.thumbnail);
axios
.post('/admin/store', fd)
.then((response) => {
console.log(response);
this.success = response.data.message;
});
},
}
}
</script>`
So how should I take those checkboxes in the array selected_books and append them using formdata for the certain category. Besides how to fetch them and show as checked for that book category ?

Set a is_selected type of flag for each individual book for your v-model
<template>
<div class="container">
<form>
<div class="form-group" v-for="(book, index) in books" :key="index">
<input type="checkbox" v-model="book.is_selected" id="">
<label for="">{{ book.name }}</label>
</div>
<div class="form-group">
<input type="file" #change="image" id="">
</div>
<button #click="save" type="submit">Save</button>
</form>
</div>
</template>
Fetch your books in the mounted lifecycle hook using fetch or axios. Use the computed property for the selected_books array to filter all the books and return only the ones that have is_selected set to true.
<script>
export default {
async mounted () {
this.books = await fetch(`https://url.goes.here`, { method: `get` });
},
computed: {
selected_books: function () {
return this.books.filter(book => book.is_selected);
}
},
data() {
return {
books: [],
image: ``
}
},
methods: {
save: function () {
let fd = new FormData();
fd.append(`images`, this.thumbnail);
axios
.post(`/admin/store`, fd)
.then(response => {
console.log(response);
this.success = response.data.message;
});
},
}
}
</script>

Related

vue js filtering with search bar

I have created an app that requests from an API the data and creates flexboxes. Now I added a search box and I would like to filter the articles by their contact and/or title.
I've also created a computed property to filter the returned list of items but when I replace in line 11 the paginated('items') with paginated('filteredArticles') that returns nothing.
What did I do wrong?
<template>
<div id="app">
<div class="search-wrapper">
<input type="text"
class="search-bar"
v-model="search"
placeholder="Search in the articles"/>
</div>
<paginate ref="paginator" class="flex-container" name="items" :list="items">
<li v-for="(item, index) in paginated('items')" :key="index" class="flex-item">
<div id="image"><img :src="item.image && item.image.file" /></div>
<div id="date">{{ item.pub_date }}</div>
<div id="title"> {{ item.title }}</div>
<div class="article">{{item.details_en}}</div>
</li>
</paginate>
<paginate-links for="items" :limit="2" :show-step-links="true"></paginate-links>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
items: [],
paginate: ["items"],
search:'',
};
},
created() {
this.loadPressRelease();
},
methods: {
loadPressRelease() {
axios
.get(`https://zbeta2.mykuwaitnet.net/backend/en/api/v2/media-center/press-release/?page_size=61&type=5`)
.then((response) => {
this.items = response.data.results;
});
},
},
computed:{
filteredArticles() {
return this.items.filter(item=>item.includes(this.search))
}
}
};
</script>
You need fields you want to search and connvert search string and fields with toLowerCase() or toUpperCase():
computed : {
filteredArticles() {
if (!this.search) return this.items
return this.items.filter(item => {
return (item.title.toLowerCase().includes(this.search.toLowerCase()) || item.contact.toLowerCase().includes(this.search.toLowerCase()));
})
}
}
Your computed doesn't seem correct. Since items is an array of objects, you'd need to do this:
filteredArticles() {
if (!this.search) {
return this.items;
}
return this.items.filter(item => {
return item.title.includes(this.search);
})
}
Note that this will only search the title field, and it's case sensitive.

How to toggle between DIVs with bound data in Vue?

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.

How to toggle individual row at a time in vue for rows generated from an array? [duplicate]

I work with single file components and have a list in one of them. This list should work like a accordion, but as far as I can find in the Vuejs docs, it's not that easy to make each item open separately very easily. The data (questions and answers) is retrieved from an ajax call. I use jQuery for that, but would like to know how I can make the accordion work Vuejs-style. Any help would be appreciated!
Here's the code:
export default {
name: 'faq-component',
props: ['faqid', 'faqserviceurl', 'ctx'],
data: function () {
return {
showFaq: "",
totalFaqs: this.data,
isOpen: true
}
},
watch: {
'showFaq': function(val, faqid, faqserviceurl) {
var self = this;
$.ajax ({
url: this.faqserviceurl,
type: 'GET',
data: {id: this.faqid, q: val, scope:1},
success: function (data) {
self.totalFaqs = data;
},
error: function () {
$("#answer").html('Sorry');
}
});
}
},
methods: {
'toggle': function() {
this.isOpen = !this.isOpen
}
}
}
<template>
<div class="card faq-block">
<div class="card-block">
<form>
<div class="form-group">
<input class="form-control" type="text" placeholder="Your question" id="faq" v-model="showFaq">
</div>
</form>
<div id="answer"></div>
<ul class="faq">
<li v-for="faq in totalFaqs">
<p class="question" v-html="faq.vraag" v-bind:class={open:isOpen} #click="isOpen = !isOpen"></p>
<p class="answer" v-html="faq.antwoord"></p>
</li>
</ul>
</div>
</div>
</template>
Add an isOpen property to each object in totalFaqs and use that instead of your single isOpen property in data.
<p class="question" v-html="faq.vraag" v-bind:class={open: faq.isOpen} #click="faq.isOpen = !faq.isOpen"></p>
If you can't change the model from the server side, then add it client side.
success: function (data) {
data.forEach(d => self.$set(d, 'isOpen', false))
self.totalFaqs = data
}

Vuejs open/toggle single item

I work with single file components and have a list in one of them. This list should work like a accordion, but as far as I can find in the Vuejs docs, it's not that easy to make each item open separately very easily. The data (questions and answers) is retrieved from an ajax call. I use jQuery for that, but would like to know how I can make the accordion work Vuejs-style. Any help would be appreciated!
Here's the code:
export default {
name: 'faq-component',
props: ['faqid', 'faqserviceurl', 'ctx'],
data: function () {
return {
showFaq: "",
totalFaqs: this.data,
isOpen: true
}
},
watch: {
'showFaq': function(val, faqid, faqserviceurl) {
var self = this;
$.ajax ({
url: this.faqserviceurl,
type: 'GET',
data: {id: this.faqid, q: val, scope:1},
success: function (data) {
self.totalFaqs = data;
},
error: function () {
$("#answer").html('Sorry');
}
});
}
},
methods: {
'toggle': function() {
this.isOpen = !this.isOpen
}
}
}
<template>
<div class="card faq-block">
<div class="card-block">
<form>
<div class="form-group">
<input class="form-control" type="text" placeholder="Your question" id="faq" v-model="showFaq">
</div>
</form>
<div id="answer"></div>
<ul class="faq">
<li v-for="faq in totalFaqs">
<p class="question" v-html="faq.vraag" v-bind:class={open:isOpen} #click="isOpen = !isOpen"></p>
<p class="answer" v-html="faq.antwoord"></p>
</li>
</ul>
</div>
</div>
</template>
Add an isOpen property to each object in totalFaqs and use that instead of your single isOpen property in data.
<p class="question" v-html="faq.vraag" v-bind:class={open: faq.isOpen} #click="faq.isOpen = !faq.isOpen"></p>
If you can't change the model from the server side, then add it client side.
success: function (data) {
data.forEach(d => self.$set(d, 'isOpen', false))
self.totalFaqs = data
}

Extend Vue function in component to display ID

I have a Vue component and I am using internalValue to access the value attribute. How would I extend this to also get the ID?
ie internalValue = value, id
I have tried but I don't know how to add this inside the internalValue function. I've even tried to only get the ID by changing all instances of value to id but it still spits out the value.
I'd be happy to have them as one ie value, id or access them like data.value and data.id
Initialise Vue
new Vue({
el: '#topic',
data: {
selectedTopic: null
}
});
Use Component
<div class="form-group" id="topic">
<topic v-model="selectedTopic"></topic>
</div>
Register Component
Vue.component('topic', require('./components/Topicselect.vue'));
Component
<template>
<div>
<label v-for="topic in topics" class="radio-inline radio-thumbnail">
<input type="radio" v-model="internalValue" name="topics_radio" :id="topic.id" :value="topic.name">
<span class="white-color lg-text font-regular text-center text-capitalize">{{ topic.name }}</span>
</label>
<ul class="hidden">
<li>{{ internalValue }}</li>
</ul>
</div>
</template>
<script>
export default {
props: ['value'],
data () {
return {
internalValue: this.value,
topics: []
}
},
mounted(){
axios.get('/vuetopics').then(response => this.topics = response.data);
},
watch: {
internalValue(v){
this.$emit('input', v);
console.log('Topicselect: the value is ' + this.internalValue);
}
}
}
</script>
Use the selected topic as your value. Basically, eliminate internalValue altogether, and just emit the topic associated with any given radio button when it's clicked. This will satisfy v-model, since it listens to input events (unless you customize it).
export default {
props: ['value'],
data () {
return {
topics: []
}
},
methods:{
selectValue(topic){
this.$emit('input', topic)
}
},
mounted(){
axios.get('/vuetopics').then(response => this.topics = response.data);
}
})
And your template
<template>
<div>
<label v-for="topic in topics" class="radio-inline radio-thumbnail">
<input type="radio" #click="selectValue(topic)" name="topics_radio" :id="topic.id" :value="topic.name" :checked="value && topic.id == value.id">
<span class="white-color lg-text font-regular text-center text-capitalize">{{ topic.name }}</span>
</label>
<ul class="hidden">
<li>{{ value }}</li>
</ul>
</div>
</template>
This will set selectedTopic in your Vue to a topic, which is something like
{
id: 2,
name: "some topic"
}
based on how you use it in your template.
Working example.