Vue.js 2 components data property bound to the first or to one only instance of component - vue.js

Need help. I'm fairly new to Vue.js and need some help and advice.
Context:
I have a component with BS modal inside which is rendered in a for loop and obviously has many instances.
Issue:
The very first rendered component has its data received from parent via props, and the rest component have their own (like row.id and etc.)
Question: How to fix it? Maybe the problem in BS modal?
Component:
let vSelect = Vue.component("v-select", VueSelect.VueSelect);
var scoringButton = Vue.component("scoring-button", {
props: ["loadId", "causeData"],
template: "#scoring-template",
components: {
"v-select": vSelect,
},
data: function () {
return {
scoredLoadId: this.loadId,
scoring: null,
causeId: null,
selectedCause: null,
causeList: this.causeData,
};
},
computed: {
showCauseList() {
if (this.scoring === "1" || this.scoring === null) {
return true;
}
return false;
},
},
});
Template:
<template v-cloak id="scoring-template">
<div class="scoring-block" :id="scoredLoadId">
<div class="scoring">
<button
v-if="scoring === '1'"
title="Scoring button"
type="button"
class="btn btn-success scoring-btn"
data-bs-toggle="modal"
data-bs-target="#scoringModal"
>
<i class="bi bi-hand-thumbs-up-fill"></i>
</button>
<button
v-else-if="scoring === '2'"
title="Scoring button"
type="button"
class="btn btn-danger scoring-btn"
data-bs-toggle="modal"
data-bs-target="#scoringModal"
>
<i class="bi bi-hand-thumbs-down-fill"></i>
</button>
<button
v-else
title="Scoring button"
type="button"
class="btn btn-info scoring-btn"
data-bs-toggle="modal"
data-bs-target="#scoringModal"
>
<i class="bi bi-hand-thumbs-up-fill"></i>
<i class="bi bi-hand-thumbs-down-fill"></i>
</button>
</div>
<div
class="modal fade scoring-modal"
id="scoringModal"
tabindex="-1"
role="dialog"
aria-hidden="true"
>
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" :key="loadId">
<div class="modal-header">
<h1 class="modal-title">Load Scoring</h1>
<button
type="button"
class="btn-close"
aria-label="Close"
data-bs-dismiss="modal"
></button>
</div>
<div class="modal-body">
<div class="scoring-content">
<input
type="radio"
class="btn-check"
name="btnScore"
id="btnLike"
autocomplete="off"
checked="scoring === '1'"
value="1"
v-model="scoring"
/>
<label
class="btn btn-outline-success"
for="btnLike"
#click="clearSelectedCause"
>
<i class="bi bi-hand-thumbs-up-fill"></i> Like
</label>
<input
type="radio"
class="btn-check"
name="btnScore"
id="btnDislike"
autocomplete="off"
:checked="scoring === '2'"
value="2"
v-model="scoring"
/>
<label class="btn btn-outline-danger" for="btnDislike">
<i class="bi bi-hand-thumbs-down-fill"></i> Dislike
</label>
</div>
<div class="scoring-cause">
<v-select
:disabled="showCauseList"
label="name"
:options="causeList"
v-model="selectedCause"
placeholder="Choose a cause option"
></v-select>
</div>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary"
data-bs-dismiss="modal"
>
Close
</button>
<button type="button" class="btn btn-primary">
Save changes
</button>
</div>
</div>
</div>
</div>
</div>
</template>
Parent:
var mainTableApp = new Vue({
el: "#main-table",
data: {
tableData: [],
scoringCauseList: [
{
"id": 6,
"scoring_type": 0,
"name": "Late at loading",
"mark": "late_at_loading"
},
{
"id": 7,
"scoring_type": 0,
"name": "Special conditions were not respected",
"mark": "special_conditions_were_not_respected"
},
{
"id": 8,
"scoring_type": 0,
"name": "Bad/not enough information",
"mark": "bad_not_enough_information"
}
],
},
components:{
'scoring-button': scoringButton,
}
});
Component in the main app block:
<div
v-for="row in tableData"
class="row-container"
>
....
<scoring-button
:id="row.LOAD_ID"
:key="row.LOAD_ID"
:load-id="row.LOAD_ID"
:cause-data="scoringCauseList"
>
</scoring-button>
....
</div>
I tried to resetting BS modal's data, but it didn't work. So, I went back to look for a solution in Vue part.
I know I may construct the whole thing not enough in a very right way, but this code below is the last version after many other solutions, have been tried with v-model, $emit, props etc.

Update: found solution.
Added ":id" for all "input" fields I had in my component.
So, to have reusable components their own data properties you need to have dynamic ":id" properties. So, that each data flows into their own component.
<input
type="radio"
class="btn-check"
name="btnScore"
id="btnLike" // <-- old line
:id="`btnLike-${dynamicStr}`" // <-- new modified line
autocomplete="off"
checked="scoring === '1'"
value="1"
v-model="scoring"
/>
<label
class="btn btn-outline-success"
for="btnLike" // <-- old line
:for="'btnLike-${dynamicStr}'" // <-- new modified line
#click="clearSelectedCause"
>
<i class="bi bi-hand-thumbs-up-fill"></i> Like
</label>

Related

Id of item do not pass to the bootstarap modal in vue

I have this code that does not pass the id of the item of array to the bootstrap modal
I need help with this because it takes too much time.
<script>
export default {
data() {
return {
lists: [
{id: 1, name: "Name 1"},
{id: 2, name: "Name 2"},
{id: 3, name: "Name 3"},
{id: 4, name: "Name 4"}
],
delete_id: null
}
},
methods: {
passId(id){
this.delete_id = id;
},
postDelete() {
// this part return null
console.log(this.delete_id)
}
}
}
</script>
<template>
<div>
<!-- delete card item -->
<div class="modal fade" id="delete_todo_item" tabindex="-1" user="dialog" aria-labelledby="exampleModalCenterTitle"aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" user="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" >delete</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div style="margin: 0 auto;" class="text-center">
<h2 style="font-size: 15px;color: red;">sure_to_delete</h2>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">close
</button>
<button class="btn btn-primary" data-dismiss="modal" #click="postDelete()">delete
</button>
</div>
</div>
</div>
</div>
<div v-for="item in lists" :key="item.id">
{{item.name}}
<i class="la la-trash" href="#delete_todo_item" data-toggle="modal" #click="passId(item.id)"></i>
</div>
</div>
</template>
The issue is when I click on the delete button of the modal I return null in console.
My version of vue is 2.6.1.
I need to know if there is an issue. I really can't understand what is going wrong.

VUEJS: Model not changing in for loop

The V-model:"question.answer" is the same for each loop.
The content of rating_questions is:
rating_question = [
{
"id":1,
"question":"How did you like this?",
"amount_of_stars":8,
"answer":0
},
{
"id":2,
"question":"Second question?",
"amount_of_stars":3,
"answer":0
}]
When I select an answer for the first question, the answer is saved in rating_question[0].answer but if I select an answer for the second question, it is also saved in rating_question[0].answer and not in rating_questions[1].answer as I would expect.
<template>
<div class="ratings">
<div class="rating" v-for="(question, index) in rating_questions">
<div class="question">
{{ question.question }}
</div>
<div class="answer">
<div class="rating-stars">
<span v-for="i in question.amount_of_stars">
<input :id="i" name="rating" v-model="question.answer" type="radio" :value="i" class="radio-btn hide" />
<label :for="i" >☆</label>
</span>
{{ rating_questions[index]['answer'] }}
{{index}}
<div class="clear"></div>
</div>
</div>
</div>
<span class="input-group-btn">
<button class="btn btn-primary btn-sm" id="btn-chat" #click="sendRating">
Send
</button>
</span>
</div>
</template>
<script>
export default {
props: ['user', 'event', 'rating_questions'],
methods: {
sendRating() {
this.$emit('ratingsent', {
rating_questions: this.rating_questions
});
}
}
}
</script>
Your problem is not in Vue usage but how you use <input> and <label> HTML elements...
<input> id and <label> for attributes are assigned with simple number
1..question.amount_of_stars...which means first combo for every question will have id = 1, second 2 etc. Moreover you are using same name for every combo!
Now if you click on the label (star) in second question, browser just switch active combo on 1st question.
Try this:
<input :id="`rating-${question.id}-${i}`" :name="`rating-${question.id}`" v-model="question.answer" type="radio" :value="i" class="radio-btn hide" />
<label :for="`rating-${question.id}-${i}`" >☆</label>
Now:
every combo in the group (single question) will have same name (OK!)
every combo (and it's corresponding label) will have different id (OK!)
Also it's usually better to use :key together with v-for
new Vue({
data() {
return {
rating_questions: [
{
"id":1,
"question":"How did you like this?",
"amount_of_stars":8,
"answer":0
},
{
"id":2,
"question":"Second question?",
"amount_of_stars":3,
"answer":0
}]
}
}
}).$mount("#app")
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="ratings">
<div class="rating" v-for="(question, index) in rating_questions" :key="question.id">
<div class="question">
{{ question.question }}
</div>
<div class="answer">
<div class="rating-stars">
<span v-for="i in question.amount_of_stars">
<input :id="`rating-${question.id}-${i}`" :name="`rating-${question.id}`" v-model="question.answer" type="radio" :value="i" class="radio-btn hide" />
<label :for="`rating-${question.id}-${i}`" >☆</label>
</span>
{{ question.answer }}
<div class="clear"></div>
</div>
</div>
</div>
</div>
</div>
The error was in my input id's...
Each new question had the same id's.
<template>
<div class="ratings">
<div class="rating" v-for="(question, index) in rating_questions">
<div class="question">
{{ question.question }}
</div>
<div class="answer">
<div class="rating-stars">
<span v-for="i in question.amount_of_stars">
<input :id="'rating' + index + i" name="rating" v-model="question.answer" type="radio" :value="i" class="radio-btn hide" />
<label :for="'rating' + index + i" >☆</label>
</span>
<div class="clear"></div>
</div>
</div>
</div>
<span class="input-group-btn">
<button class="btn btn-primary btn-sm" id="btn-chat" #click="sendRating">
Send
</button>
</span>
</div>
</template>
<script>
export default {
props: ['user', 'event', 'rating_questions'],
methods: {
sendRating() {
this.$emit('ratingsent', {
rating_questions: this.rating_questions
});
}
}
}
</script>
There is very trivial fix of this problem.
See you are modifying the props. In vuejs, props are not supposed to be updated. I hope you might have encountered some error in the console (not sure though, as sometimes in my case also it doesn't appear)
Please use this SFC file
<template>
<div class="ratings">
<!-- using the data variable rather than props -->
<div class="rating" v-for="(question, index) in questions" :key="index">
<div class="question">
{{ question.question }}
</div>
<div class="answer">
<div class="rating-stars">
<span v-for="i in question.amount_of_stars" :key="i">
<input
:id="i"
name="rating"
v-model="question.answer"
type="radio"
:value="i"
class="radio-btn hide"
/>
<label :for="i">☆</label>
</span>
{{ questions[index]["answer"] }}
{{ index }}
<div class="clear"></div>
</div>
</div>
</div>
<span class="input-group-btn">
<button class="btn btn-primary btn-sm" id="btn-chat" #click="sendRating">
Send
</button>
</span>
</div>
</template>
<script>
export default {
props: ["user", "event", "rating_questions"],
data() {
return {
questions: this.rating_questions, // because you can not change props in vuejs
};
},
methods: {
sendRating() {
this.$emit("ratingsent", {
rating_questions: this.questions,
});
},
},
};
</script>

How to focus click on input vue

how do i focus input with a button onClick?
<div class="form-group"
v-for="(file, i) in registerData.requiredDocuments"
:key="`file-${i}`">
<label for="npwp" class="text-muted"> {{file.name}} </label>
<input type="file"
:name="file.name"
class="form-control-file"
:id="file.name"
:accept="file.name === 'Logo' ? `image/png, image/jpg, image/jpeg` : `application/pdf`">
<div class="d-flex">
<button class="btn btn-primary" type="button">Choose File</button>
</div>
</div>
i was hoping by clicking the 'choose file' button it would trigger the input. I've tried
// the input
<input :ref="file.name" type="file">
// the button
<button #click="$refs.file.name.focus()">Choose File</button>
but what i get is the name is undefined anyone knows how to fix this?
I had similar problem before, Here is how and did it.
<template>
<div>
<div class="form-group" v-for="(file, i) in registerData.requiredDocuments" :key="`file-${i}`">
<label for="npwp" class="text-muted"> {{file.name}} </label>
<input type="file" :ref="'file' + i" :name="file.name" class="form-control-file" :id="file.name"
:accept="file.name === 'Logo' ? `image/png, image/jpg, image/jpeg` : `application/pdf`">
<div class="d-flex">
<button #click.prevent="setFileFocus(i)" class="btn btn-primary" type="button">Choose File</button>
</div>
</div>
</div>
</template>
<script>
export default {
methods: {
setFileFocus(index) {
this.$nextTick(function(){
this.$refs[("file" + index)][0].focus()
})
}
}
}
</script>
I called focus on nexttick. Hope my answer will be helpful

Local-Storage in Vue.JS

I am working on Vue.JS and I tried to use local-storage with it to save data. In my code, I can store and retrieve all data with local-storage except line-through effect. Here, I am trying to store actual boolean value of line-through effect in local-storage and want to retrieve that value on to-do list app.
<title>To Do List</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js'></script>
<style>
.taskDone {
text-decoration: line-through;
}
</style>
</head>
<body>
<div id="todo-list" class="container">
<div class="container col-sm-8 col-sm-offset-2">
<h1 class="text-center"> <big><b> To Do List </b> </big></h1>
<h5 class="text-center"> <span v-show="itemsTodo.length"> ({{ itemsTodo.length }} pending) </span></h5>
<div class="col-md-8">
<button v-if="state === 'default'" class="btn btn-primary" #click="changeState('edit') ">Add Item</button>
<button v-else class="btn btn-info" #click="changeState('default')">Cancel</button>
</div>
<br>
<br>
<div v-if="state === 'edit'" >
<div class="col-sm-4">
<input class='form-control' v-model="newItem" type="text" placeholder="Type here" #keyup.enter="saveItem" >
</div>
<div class="col-sm-4">
<input class="form-control" v-model="newdate" type="date" type="text"/>
</div>
<div class="col-sm-4">
<button class='btn btn-primary btn-block' v-bind:disabled="newItem.length === 0"#click= "saveItem">Save Item</button>
</div>
</div>
<br>
<br>
<ul type="none" class="list-group">
<li class="list-group-item" v-for="(item,index,date) in items" :class="{taskDone : item.completed}" >
<h4>
<input type="checkbox" v-model="item.completed" #click="item.completed = !item.completed">
<button class="btn btn-primary " #click.stop="removeitems(index)">× </button>
<b><i> {{ item.label }} {{ item.date }} </i></b></h4>
</li>
</ul>
<h2 v-if="items.length === 0">Nice Job! Nothing in TO DO LIST</h2>
<div class="col-sm-4">
<button class="btn btn-warning btn-block" #click="clearcompleted"> Clear Completed</button>
</div>
<div class="col-sm-4">
<button class="btn btn-danger btn-block" #click="clearAll"> Clear All</button>
</div>
</div>
</div>
2. Vue.JS code
<script src="https://unpkg.com/vue" ></script>
<script>
var todolist = new Vue({
el: '#todo-list',
data : {
state : 'edit',
header: 'To Do List',
newItem: '',
newdate: '',
items: [
{
label:'coffee',
completed:false,
date:'2019-06-20' ,
},
{
label:'tea',
completed:false,
date:'2019-06-19' ,
},
{
label:'milk',
completed:false,
date:'2019-06-19' ,
},
]
},
computed:{
itemsDone(){
return this.items.filter(items => items.completed)
},
itemsTodo(){
return this.items.filter(items =>! items.completed)
},
},
methods:{
saveItem: function(){
if (this.newItem != ''){
this.items.push({
label:this.newItem,
completed: false,
date : this.newdate,
});
this.newItem = '';
this.newdate = '';
}},
changeState: function(newState){
this.state = newState;
this.newItem = '';
this.newdate = '';
},
removeitems(index){
this.items.splice(index,1);
},
clearcompleted (){
this.items = this.itemsTodo;
},
clearAll: function(){
this.items = [ ];
},
},
mounted(){
console.log('App Mounted!');
if (localStorage.getItem('items')) this.items = JSON.parse(localStorage.getItem('items'));
},
watch: {
items:{
handler(){
localStorage.setItem('items',JSON.stringify(this.items));
},
},
},
});
</script>
I expect correct boolean value of line-through effect to be stored in local-storage. So that, appropriate effect will show on browser.
You are just watching items. If you change something in a item (in your case completed) the handler will not be called and your change is not stored.
You could use a "deep" watcher but i suggest to call your save logic whenever you changed something.

Radio buttons in Vuejs 2.0

I'm building a small application in VueJS 2.0 and I'm using Inspinia premium admin template, where I'm having a radio buttons something like this:
<div data-toggle="buttons" class="btn-group">
<label class="btn btn-sm btn-white active">
<input type="radio" v-model="taskTime" value="7" name="taskTime"> Week
</label>
<label class="btn btn-sm btn-white">
<input type="radio" v-model="taskTime" value="30" name="taskTime"> Month
</label>
</div>
When any of the buttons being selected or if taskTime data is changed I'm calling a function, so for this I've watch function:
watch: {
taskTime(newValue) {
console.log('clicked');
}
}
I'v properly defined v-model/data in my data set
data() {
return {
taskTime: ''
}
},
JSFiddle in my case: https://jsfiddle.net/60q14y26/
I don't know why it is not working, I guess something is preventing from executing this. Help me out with these.
Thanks
<div id="app">
<div data-toggle="buttons" class="btn-group">
taskTime {{ taskTime }}
<label class="btn btn-sm btn-white active">
<input type="radio" v-model="taskTime" value="7" name="taskTime"> Week
</label>
<label class="btn btn-sm btn-white">
<input type="radio" v-model="taskTime" value="30" name="taskTime"> Month
</label>
</div>
</div>
var vm = new Vue({
el: '#app',
data() {
return {
taskTime: null
}
},
watch: {
taskTime(newValue) {
console.log('clicked');
}
}
})
This is working snippet. You missed one "}" in your fiddle ;)
jsfiddle
version with counter
And one more thing: your build system can remove console.log from final code.