Deleting object in Vue.js - javascript

I've got a problem and need help. Im trying to teach in Vue.js. I'm doing a quiz and i want to put here a button with deleting a question. I tried this:
deleteQuestion(index)
{
this.questions.splice(index, 1);
}
but it will delete always only first question although when i want to delete second question. Any help?
Full Code:
HTML:
<div class="question" v-for="question in questions">
<!-- Znění otázky -->
<h2>{{ question.question }}</h2><button #click="deleteQuestion" class="doprava"><img src="criss.png"/></button>
<!-- Odpovědi -->
<label v-for="answer in question.answers" class="answer" :class="{ 'answer-correct':answer.correct, 'answer-false':answer.false }">
<input type="checkbox" :value="answer.id" v-model="question.selected"> {{ answer.answer }}
</label>
</div>
<hr>
<button #click="onSubmit()">onSubmit</button>
JS:
var vm = new Vue({
el: "#app",
data: {
questions: questions,
result: 0
},
methods: {
onSubmit() {
this.result = 0
this.questions.forEach(question => {
question.answers.forEach(answer => {
answer.correct = question.correct.includes(answer.id);
answer.false = question.false.includes(answer.id);
});
});
},
deleteQuestion(index)
{
this.questions.splice(index, 1);
}
}
});

You haven't passed an index to your deleteQuestion method. Try this in your template instead:
<div class="question" v-for="(question, index) in questions">
<h2>{{ question.question }}</h2>
<button #click="deleteQuestion(index)" class="doprava">
<img src="criss.png"/>
</button>
etc.
However a better option might be to give each question an id property and delete it by filtering agains that id instead. This is because you can't guarantee that the order/index of questions in the template will be consistent on each re-render. This could be done like so:
<button #click="deleteQuestion(question.id)">
Then in your method:
deleteQuestion(id) {
this.questions = this.questions.filter(q => q.id !== id);
}

Related

DOM not getting updated Vue

<li
v-for="(schema) in typeSchema"
:key="schema.id"
>
<div style="display:inline-block; width:100%;">
<div style="display:flex; justify-content:space-between">
<span>{{ schema.title }}</span>
<span v-if="schema.controller">
<MdsSwitch
:checked="schema.controller.value"
:label="schema.controller.title"
#change="toggleController(schema, $event)"
/>
</span>
</div>
<div style="display:flex;flex-flow:column;place-items:flex-start;align-items:flex-start;margin-top:10px;">
<component
:is="schema.type"
v-bind="schema"
:data="data"
:is-disabled="schema.isDisabled"
#input="updateData"
/>
</div>
</div>
</li>
# toggleController(schema, event) {
if (schema.controller) {
// this.typeSchema.map(x => x).in
schema.controller.value = event;
schema.isDisabled = !event;
schema = { ...schema };
// const index = this.typeSchema.findIndex((x) => x.id === schema.id);
// console.log(index);
// this.$set(schema, "isDisabled", !event);
// this.typeSchema.splice(index, 0, schema);
}
},
When toggleController is executed it should disable the associated component, it was working earlier and I'm not sure what change I made and it stopped working, unfortunately everything is on my local so cannot refer pervious versions.
I have tried $set, splice but no luck

Vuejs - Radio input remain checked when a new question is rendered in mobile app

I have implemented a tricky solution to display some questions without using v-for loop. I need it to avoid that all the questions are rendered at the same time in a mobile android app, this will avoid scroll. The logics works fine but I've noticed that when the user select an answer and the next new question is rendered, the radio input to select the answer will be not resetted and the selected radio input will be equal to the previous choiced from the user. The problem will not occur if I use v-for to render the questions but as I wrote, this isn't what I want.
<div class="container-fluid bg-light vh-100" v-if="isLoaded">
<div class="row m-0">
<div class="col-12 card shadow p-0 mt-5">
<div class="card-header">
<h6 class="fw-bold">{{ questions[n].question }}</h6>
</div>
<div class="card-body">
<div class="form-check mb-3" v-for="(choice, index) in questions[n].choices" :key="index">
<input class="form-check-input" type="radio" :name="questions[n].questionIndex" :value="index" #change="checkAnswer(questions[n].questionIndex, index)" :disabled="answeredQuestions[n]">
<small class="form-check-label" for="">{{ index }}) {{ choice }}</small>
</div>
</div>
</div>
</div>
</div>
<div class="navbar bg-light fixed-bottom">
<div class="container-fluid">
<small :class="score">{{ currentScore }}</small>
</div>
</div>
export default {
name: 'Quiz',
data() {
return {
n: 0,
answeredQuestions: [],
currentScore: 0
}
},
mounted() {
front.on('questions', (data) => {
console.log(data)
this.$store.commit('quizQuestions', data);
this.$store.commit('contentLoaded', true);
});
front.on('checkResponse', (response) => {
console.log(response);
if( response.answerCheck ){
this.currentScore++;
}
this.showNext();
});
},
computed: {
isLoaded() {
return this.$store.getters.showLoader;
},
questions() {
return this.$store.getters.quiz;
},
score() {
return this.currentScore > 0 ? 'text-success' : 'text-muted';
}
},
methods: {
showPrevious() {
if( this.n !== 0 ){
this.n--
}
},
showNext() {
if( this.n < this.$store.getters.quiz.length ){
this.n++
}
},
checkAnswer(questionIndex, choice) {
this.answeredQuestions.push(true);
front.send('checkAnswer', {questionIndex: questionIndex, choice: choice});
}
}
}
I think that the problem is with the name attribute of the radio inputs but not sure of this. Any idea of how I can solve this?
Maybe you can try this. bind your questions[n].answer in your input value. Once you get a new question, if the answer haven't fill in before. It will auto become null.
<input class="form-check-input" type="radio" :name="questions[n].questionIndex" :value="questions[n].answer" #change="checkAnswer(questions[n].questionIndex, index)" :disabled="answeredQuestions[n]">

Vue : Accessing Nested Object Component's Values

I have problems accessing this "name" property on the component. I can only access it statically.
<template>
<div class="col-md-12">
<p
v-for="channel in channels"
:key="channel.id"
class="channel"
:class="{ 'active': channel.id == activeChannel }"
#click="setChannel(channel.id)">
{{ channel.users[0].name }}
</p>
</div>
</template>
Here is an Image of my Vue Devtools
So I have an v-for loop over channels, and I want to: Access the Usernames for each channel (if it is not my own preferably as "username" is set on my own i think its easy to exclude it right?) So that in the end In Channel 1 when there are 2 Users , I want to show the corresponding username, so the "other username", the one i am chatting with, and he should see my name that is the initial goal.
I thought of doing something like this:
<template>
<div class="col-md-12">
<p
v-for="channel in channels"
:key="channel.id"
class="channel"
:class="{ 'active': channel.id == activeChannel }"
#click="setChannel(channel.id)">
<!-- {{ channel.users[0].name }} -->
<span v-for="user,key in channel">{{key}}</span>
</p>
</div>
it at least displays the content of the channels object for each channel, but something like this isnt gonna work: key.user.name , unfortunately im stuck here. please help :)
edit: here is a dd() of the view
click
EDIT 2: Parent Data Provided:
//chat-app.blade.php
<div id="app">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Chats</div>
<vue-chat :channels="{{ $channels }}" ></vue-chat>
</div>
</div>
</div>
</div>
</div>
</div>
//<vue-chat> component
<template>
<div class="chat">
<div class="container">
<div class="row">
<div class="col-md-3">
<vue-chat-channels
:channels="channels"
:active-channel="activeChannel"
#channelChanged="onChannelChanged"
:username="sername"
></vue-chat-channels>
</div>
<div class="col-md-3">
<vue-chat-messages :messages="messages"></vue-chat-messages>
</div>
<div class="col-md-3">participants</div>
</div>
<div class="message-input-wrapper col-md-12"><vue-chat-new-message :active-channel="activeChannel"
:username="username"></vue-chat-new-message></div>
</div>
</div>
</template>
<script>
export default {
props: ["channels"],
data() {
return {
activeChannel: this.channels[0].id,
messages: [],
username: ''
};
},
methods: {
fetchMessages() {
let endpoint = `/channels/${this.activeChannel}/messages`;
axios.get(endpoint).then(({ data }) => {
this.messages = data;
});
},
onChannelChanged(id) {
this.activeChannel = id;
this.fetchMessages();
}
},
created() {
this.fetchMessages();
axios.get('/userfetch').then( ({data}) => {
console.log("Current User: "+data.name);
this.username = data.name;
});
console.log(this.channels[0].name);
// for (let channel of this.channels) {
this.channels.forEach(channel => {
// Channelname
window.Echo.channel('presence-'+channel.name)
.listen('MessageSent', (channel) => {
console.log(channel.data.message);
this.messages.push({ message: channel.data.message, author_username: channel.data.author_username});
if (this.activeChannel == channel.id) {
console.log("received message");
}
});
});
}
};
</script>
<style>
</style>
//ChatController.php
public function index()
{
$channels = Channel::with('users')->whereHas('users', function($q) {
$q->where('user_id',Auth::id());
})->get();
$user = Auth::user()->name;
return view('chat-app' , compact('channels','user'));
}
Short Explanation: ChatController returns the blade view, which has the data channels and user (my username) , and then vue comes into play which should pass down the prop of my username but i couldnt get it to work just yet
So you need to access users in every channel.
You can try like this:
<div class="col-md-12">
<p
v-for="channel in channels"
:key="channel.id"
class="channel"
:class="{ 'active': channel.id == activeChannel }"
#click="setChannel(channel.id)">
<span v-for="user in channel.users">
{{ user.name }}
</span>
</p>
</div>
This should work. If you have errors provide it here.
If you need to compare every user you can do it simply with v-if:
<span v-for="user in channel.users">
<span v-if="user.name === parentdata">
{{ user.name }}
</span>
</span>

Vue.js nested for loop with search filter

I have a JSON object with nested objects that I am iterating over to pull out data. All is working fine, but I'd like to add a search/filter implementation so that the search is being done on the second level of the nested for loop. I have it somewhat working but im not getting any data returned. Here is an example:
https://codesandbox.io/s/vue-template-s9t9o
In the HelloWorld component is where the search/filter is happening.
As you can see its not outputting the rest of the data after it passes through the searchFilter method.
To make it work without the search/filter, change the following on line #6:
from: <div class="contentSingle" v-for="(c, i) in searchFilter" :key="i">
to: <div class="contentSingle" v-for="(c, i) in cont" :key="i">
Anyone can think of what I can do to make this work? I need to filter by the elements inside each of the content inside the main data object. You can find the data object inside the FauxData/dataContent.js dir.
Thanks a lot.
-S
You should use methods instead of computed:
methods: {
filterValue(content) {
return content.filter(item => {
let itemUpper = item.content.toUpperCase();
let searchUpper = this.search.toUpperCase();
return itemUpper.indexOf(searchUpper) > -1;
});
}
}
and then in HTML code:
<section id="content">
<input type="text" id="search" name="search" v-model="search" placeholder="Search Content...">
<div v-for="(cont, index) in content" :key="index" class="contentWrapper">
<h1>{{ index }}</h1>
<div class="contentSingle" v-for="(c, i) in filterValue(cont)" :key="i">
<h3>{{ c.title }}</h3>
<div v-html="c.content"></div>
</div>
</div>
</section>
Updated
If you want to hide the empty section, then use computed value:
computed: {
filteredData() {
return Object.keys(this.content).reduce((a, cKey) => {
const data = this.filterValue(this.content[cKey]);
if (data.length) {
a[cKey] = data;
}
return a;
}, {});
}
},
methods: {
filterValue(content) {
return content.filter(item => {
let itemUpper = item.content.toUpperCase();
let searchUpper = this.search.toUpperCase();
return itemUpper.indexOf(searchUpper) > -1;
});
}
}
Use filteredData in outer v-for
<section id="content">
<input type="text" id="search" name="search" v-model="search" placeholder="Search Content...">
<div v-for="(cont, index) in filteredData" :key="index" class="contentWrapper">
<h1>{{ index }}</h1>
<div class="contentSingle" v-for="(c, i) in cont" :key="i">
<h3>{{ c.title }}</h3>
<div v-html="c.content"></div>
</div>
</div>
</section>
Demo on codepen

Vuejs - Accordion

I'm trying to create an accordion using vuejs.
I found some examples online, but what I want is different. For SEO purpose I use "is" and "inline-template", so the accordion is kind of static not fully created in Vuejs.
I have 2 problems/questions:
1) I need to add a class "is-active" on the component based on user interaction(clicks), because of this I receive the following error.
Property or method "contentVisible" is not defined on the instance but
referenced during render. Make sure to declare reactive data
properties in the data option.
This probable because I need to set it at instance level. But "contentVisible" have a value (true or false) different for each component.
So I thought using at instance level an array of "contentVisible" and a props (pass thru instance) and custom events on child to update the instance values.
2) Could work but it is a static array. How can I make a dynamic array (not knowing the number of item components) ?
<div class="accordion">
<div>
<div class="accordion-item" is="item" inline-template :class="{ 'is-active': contentVisible}" >
<div>
<a #click="toggle" class="accordion-title"> Title A1</a>
<div v-show="contentVisible" class="accordion-content">albatros</div>
</div>
</div>
<div class="accordion-item" is="item" inline-template :class="{ 'is-active': contentVisible}" >
<div>
<a #click="toggle" class="accordion-title"> Title A2</a>
<div v-show="contentVisible" class="accordion-content">lorem ipsum</div>
</div>
</div>
</div>
var item = {
data: function() {
return {
contentVisible: true
}
},
methods: {
toggle: function(){
this.contentVisible = !this.contentVisible
}
}
}
new Vue({
el:'.accordion',
components: {
'item': item
}
})
Update
I create the following code but the custom event to send the modification from component to instance is not working, tabsactive is not changing
var item = {
props: ['active'],
data: function() {
return {
contentVisible: false
}
},
methods: {
toggle: function(index){
this.contentVisible = !this.contentVisible;
this.active[index] = this.contentVisible;
**this.$emit('tabisactive', this.active);**
console.log(this.active);
}
}
}
new Vue({
el:'.accordion',
data: {
tabsactive: [false, false]
},
components: {
'item': item
}
})
<div class="accordion" **#tabisactive="tabsactive = $event"**>
<div class="accordion-item" is="item" inline-template :active="tabsactive" :class="{'is-active': tabsactive[0]}">
<div>
<a #click="toggle(0)" class="accordion-title"> Title A1</a>
<div v-show="contentVisible" class="accordion-content">albatros</div>
</div>
</div>
<div class="accordion-item" is="item" inline-template :active="tabsactive" :class="{'is-active': tabsactive[1]}">
<div>
<a #click="toggle(1)" class="accordion-title" > Title A2</a>
<div v-show="contentVisible" class="accordion-content">lorem ipsum</div>
</div>
</div>
</div>
This works for me:
<template>
<div>
<ul>
<li v-for="index in list" :key="index._id">
<button #click="contentVisible === index._id ? contentVisible = false : contentVisible = index._id">{{ index.title }}</button>
<p v-if='contentVisible === index._id'>{{ index.item }}</p>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "sameName",
data() {
return {
contentVisible: false,
list: [
{
_id: id1,
title: title1,
item: item1
},
{
_id: id2,
title: title2,
item: item2
}
]
};
},
};
</script>
On point 1:
You have to define contentVisible as a vue instance variable, as you have accessed it with vue directive v-show, it searches this in vue data, watchers, methods, etc, and if it does not find any reference, it throws this error.
As your accordion element is associated with the parent component, you may have to add contentVisible data there, like following:
new Vue({
el:'.accordion',
data: {
contentVisible: true
}
components: {
'item': item
}
})
If you have multiple items, you may use some other technique to show one of them, like have a data variable visibleItemIndex which can change from 1 to n-1, where n is number of items.
In that case, you will have v-show="visibleItemIndex == currentIndex" in the HTML.
You can as well have hash for saving which index are to de displayed and which to be collapsed.
On point 2:
You can use v-for if you have dynamic arrays. you can see the documentation here.
I'm having a real hard time understanding what exactly it is you want or why you would want it, but I think this does it?
Vue.component('accordion-item', {
template: '#accordion-item',
methods: {
toggle() {
if(this.contentVisible){
return
}
if(this.$parent.activeTab.length >= 2){
this.$parent.activeTab.shift()
}
this.$parent.activeTab.push(this)
}
},
computed: {
contentVisible() {
return this.$parent.activeTab.some(c => c === this)
}
}
})
const Accordion = Vue.extend({
data() {
return {
activeTab: []
}
},
methods: {
handleToggle($event) {
this.activeTab = []
}
}
})
document.querySelectorAll('.accordion').forEach(el => new Accordion().$mount(el))
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<template id="accordion-item">
<div class="accordion-item" :class="{ 'is-active': contentVisible}">
<slot name="title"></slot>
<div v-show="contentVisible" class="accordion-content" #click="$emit('toggle', $event)">
<slot name="content"></slot>
</div>
</div>
</template>
<div class="accordion">
<accordion-item #toggle="handleToggle">
<p slot="title">a title</p>
<p slot="content">there are words here</p>
</accordion-item>
<accordion-item #toggle="handleToggle">
<p slot="title">titles are for clicking</p>
<p slot="content">you can also click on the words</p>
</accordion-item>
<accordion-item #toggle="handleToggle">
<p slot="title">and another</p>
<p slot="content">only two open at a time!</p>
</accordion-item>
<accordion-item #toggle="handleToggle">
<p slot="title">and #4</p>
<p slot="content">amazing</p>
</accordion-item>
</div>

Categories

Resources