Render Component in loop, use Index in method of child component (VueJS) - vue.js

I have two components where some exchange of props takes place. Props is the whole todo array, which is updated by a click on the button with the "addTodo" method. Passing the array down to the child works fine. I can display the props dynamically in my p-tags, but it seems to be not possible to use it my the methods of my child component.
<template>
<v-app>
<v-content>
<h2>Add a Todo</h2>
<v-col cols="12" sm="6" md="3">
<v-text-field label="Regular" v-model="text"></v-text-field>
</v-col>
<div class="my-3">
<v-btn medium #click="addTodo">Add Todo</v-btn>
</div>
<div v-for="(todo, index) in todos" v-bind:key="index">
<HelloWorld
v-bind:todos="todos"
v-bind:index="index"
v-bind:class="(todos[index].done)?'green':'red'"
/>
</div>
</v-content>
</v-app>
</template>
<script>
import HelloWorld from "./components/ToDo.vue";
export default {
components: {
HelloWorld
},
data: function() {
return {
text: "",
todos: []
};
},
methods: {
addTodo() {
this.todos.push({
text: this.text,
done: false
});
}
}
};
</script>
This is my child component
<template>
<v-card max-width="250">
<v-card-text>
<h2 class="text-center">{{todos[index].text}}</h2>
<p class="display-1 text--primary"></p>
<p>{{index}}</p>
</v-card-text>
<v-card-actions>
<v-btn text color="deep-purple accent-4" #click="done"></v-btn>
<v-btn text color="orange accent-4">Delete Task</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
props: ["todos", "index"],
methods: {
done() {
this.todos[1].text = "bla";
}
}
};
</script>
<style scoped>
.seperator {
display: flex;
justify-content: space-between;
}
</style>
I pass a whole array with objects as props, and using the index inside the p-tag works fine, but I also want to use it like this:
methods: {
done() {
this.todos[index].text = "bla";
}
}
'index' is not defined
Everything works fine, but I am not able use the index value inside the method. What am I doing wrong here?

The way you write it out, there is nothing in scope defining index. Where is that value coming from?
Index is a prop and so it must be referenced with this.
done () {
this.todos[this.index].text = 'bla'
}

Related

this.$refs.key is undefined inside method

I have the following component based on Vuetify card component:
CardTemplate
<template>
<HeightCalculator>
<template #default="{ height }">
<v-card>
<v-card-title ref="title">
{{ title }}
</v-card-title>
<v-card-text
class="overflow-y-auto"
:style="{ height: calcHeight(height) }"
>
<slot></slot>
</v-card-text>
</v-card>
</template>
</HeightCalculator>
</template>
<script>
import HeightCalculator from '../HeightCalculator.vue'
export default {
props: {
title: {
type: String,
required: true
}
},
components: {
HeightCalculator
},
methods: {
calcHeight (height) {
return `calc(${height} - ${this.$refs.title.clientHeight})`
}
}
}
</script>
NOTE: HeightCalculator is just a renderless component which returns a calculated height (in this case is going to be something like 50vh).
As you can see this.$refs.title is undefined therefore I cannot access to its .clientHeight. What am I doing wrong?

How to get id from dropdown in vue?

I am working with Vue and Laravel, I want to save data to the database, but I can not get the id of the selected category as I showed in the below picture, I can get other data like body and title. I don't know where is the problem. please help me.
This my code:
<template>
<v-container>
<v-form #submit.prevent="create">
<v-text-field
v-model="form.title"
label="title"
type="text"
required
></v-text-field>
<v-select
:items="categories"
item-text="name"
item-value="id"
:v-model="form.category_id"
label="Category"
autocomplete>
</v-select>
<vue-simplemde v-model="form.body" />
<v-btn color="green" type="submit">
Ceate
</v-btn>
</v-form>
</v-container>
</template>
<script>
import VueSimplemde from 'vue-simplemde'
export default {
components: {
VueSimplemde
},
data(){
return {
form:{
title:null,
category_id:null,
},
categories:{}
}
},
created(){
axios.get('/api/category')
.then(res => this.categories = res.data.data)
},
methods:{
create(){
}
}
}
</script>
<style scoped>
#import '~simplemde/dist/simplemde.min.css';
</style>
You have a colon before v-model
Replace :v-model with v-model

Copy text upon clicking on icon in v-text-field

I'm trying to figure out how to allow users to copy their login details when they click the copy icon. How to get the value of the relevant v-text-field?
I thought I should use #click:append and link it to a method. However, I struggle how to get a value.
<template>
<v-card class="col-12 col-md-8 col-lg-6 p-6 px-16" elevation="4">
<div class="title h2 mb-10 text-uppercase text-center">
Success
<v-icon color="green" x-large>
mdi-check-circle
</v-icon>
</div>
<v-text-field
:value="newAccount.login"
label="Login"
outlined
readonly
append-icon="mdi-content-copy"
#click:append="copy('login')"
></v-text-field>
<v-text-field
:value="newAccount.password"
label="Password"
outlined
readonly
append-icon="mdi-content-copy"
></v-text-field>
</v-card>
</template>
<script>
export default {
props: ["newAccount"],
data() {
return {
copied: false,
};
},
methods: {
copy(target) {
if (target === "login") {
console.log("login is clicked");
}
},
},
computed: {},
};
</script>
The value of the v-text-field is available from its value property. Apply a template ref on the v-text-field to get a reference to the component programmatically from vm.$refs, then use .value off of that:
<template>
<v-text-field
ref="login"
#click:append="copy('login')"
></v-text-field>
</template>
<script>
export default {
methods: {
copy(field) {
console.log('value', this.$refs[field].value)
}
}
}
</script>
Alternatively, you could access the nested template ref of v-text-field's <input>, which has a ref named "input", so copy() would access it from this.$refs[field].$refs.input. Then, you could select() the text value, and execute a copy command:
export default {
methods: {
copy(field) {
const input = this.$refs[field].$refs.input
input.select()
document.execCommand('copy')
input.setSelectionRange(0,0) // unselect
}
}
}
demo

Vuetify v-intersect in Nuxt app fires before enters view

I have a page with the following layout:
<template>
<v-container fluid class='pa-0 ma-0 assignment-container'>
<v-row class='pa-0 ma-0 gallery-bg'>
// ...v-img with height 60vh
</v-row>
<v-row class='pa-3'>
// ...row content
</v-row>
<v-row class='pa-3'>
// ... row content
</v-row>
<v-row
v-if='!works.length'
v-intersect='onIntersect'
class='pa-3 mt-4 flex-column'>
<v-row class='pa-3 ma-0'>
<h3 class='mb-4 acumin-semibold section-title section-title-h3'>
Works:
</h3>
</v-row>
<v-row class='pa-0 ma-0'>
<v-col
v-for='(skeleton, i) in skeletonCards'
:key='i'
xs='12'
sm='6'
md='4'
lg='3'>
<v-skeleton-loader
class='mx-auto'
max-width='374'
height='250'
type='card' />
</v-col>
</v-row>
</v-row>
<v-container v-else fluid>
<v-row class='pa-3 ma-0'>
<h3 class='mb-4 acumin-semibold section-title section-title-h3'>
Works:
</h3>
</v-row>
<v-row class='pa-0 ma-0'>
<v-col
v-for='work in works'
:key='work._id'
xs='12'
sm='6'
md='4'
lg='3'>
<WorkCard :assignment-id='$route.params.id' :work='work' />
</v-col>
</v-row>
</v-container>
</v-container>
</template>
<script>
// ...all the imports
export default {
components: {WorkCard, UIButton},
async asyncData(context) {
// ... fetch and return assignment
},
data() {
return {
works: []
}
},
computed: {
skeletonCards() {
return this.$vuetify.breakpoint.lg ? 4 : 3
}
},
methods: {
async fetchWorks() {
this.works = await this.$nuxt.context.app.apolloProvider.defaultClient.query({
query: worksByAssignmentIdQuery,
variables: {
id: this.$nuxt.context.route.params.id,
}
})
.then(({data}) => data.assignmentWorks)
.catch(err => console.error(err))
},
onIntersect() {
console.log('intersect fired')
this.fetchWorks()
}
},
}
</script>
The problem is that v-intersect directive fires even when it's not in the view yet.
I tried to define threshold:
v-intersect='{
handler: onIntersect,
options: {
threshold: [1.0]
}
}'
And it keeps firing.
Then I thought maybe it's because it's rendered on the server, so I tried to wrap this part of markup in <client-side> element. Still firing. I tried to wrap the entire page in that element, tried to put an empty <p> element after all the rest and apply that directive on it - and it still fired.
I had the fetching part inside fetch() method with fetchOnServer set to false and I called this.$fetch() in my onIntersect method. And it kept firing every time. As if this row is always in the view, even though it is not.
I ran out of ideas... Any help, please?
OK, looks like I finally solved it. First of, apparently you can't place the v-intersect directive on a v-row element.
So I created an invisible div element with 0px width and height, on which I applied the v-intersect directive:
...
<div
v-intersect='{
handler: onIntersect,
options: {
threshold: [1.0]
}
}'
class='invisible' />
...
<style scoped>
.invisible {
width: 0;
height: 0;
position: absolute;
bottom: 0;
}
<style>
Thein in my onIntersect method I'm passing isIntersecting parameter, and invoking the fetchWorks method if it's true:
onIntersect(entries, observer, isIntersecting) {
if (isIntersecting) {
this.fetchWorks()
}
}
Now it intersects correctly and fetching data when the view (div) is in viewport (even though it's invisible), whether when scrolled there or the page was refreshed on that pixel.
Since I'll need to use v-intersect in other parts of my project, I'm considering turning this div into a component, to which I'll pass the intersect callback function.

How can I bind directives to custom components in VueJS?

So I am using vuetify with vue-cli and this is my current component code:
<template>
<div>
<v-row>
<v-col xl3 md3 xs12>
<strong>{{field}}</strong>
</v-col>
<v-col xl9 md9 xs12>
{{value}}
</v-col>
</v-row>
</div>
</template>
<script>
export default {
data() {
return {
}
},
props: ['field', 'value']
}
</script>
And I am using it in my templates like this
<template>
<two-column field="Some Field" value="Some Value"></two-column>
</template>
<script>
import TwoColumnRow from './vuetify_modifications/TwoColumnRow'
...
</script>
Now everything works perfectly but what if I want to make the grid sizes dynamic? Like for example I do with something like
<two-column field="Some Field" value="Some Value" sizes="xl3 md3 xs12"></two-column>
Is that possible? Thank you in advance.
How about this:
<foo :sizes="{ xl3: '', md3: '', xs12: '' }"></foo>
And:
<template>
<div>
<v-row>
<v-col v-bind="sizes">
<strong>{{field}}</strong>
</v-col>
</v-row>
</div>
</template>
<script>
export default {
props: {
sizes: { type: Object, default: () => {} }
// ...
}
}
</script>
One way I've been able to accomplish this is through the use of computed properties.
For simplicity of creating the example I've used colors to represent what is happening. Since it seems as through all you're really asking is how could you dynamically apply classes or value based conditions inside a component, this should work with some tweaks.
const TwoColumnRow = Vue.component('two-column', {
template: '#two-column-row-template',
data: function() {
return {}
},
props: ['field', 'value', 'colors'],
computed: {
colorList: function() {
// Split the string of colors by space and return an array of values
return this.colors.split(' ');
}
}
});
const vm = new Vue({
el: '#app-container',
data: {}
});
.red {
color: red;
}
.blue {
color: blue;
}
<script src="https://unpkg.com/vue#2.1.10/dist/vue.js"></script>
<div id="app-container">
<table>
<two-column field="toast" value="cheese" colors="blue red"></two-column>
</table>
</div>
<script type="x-template" id="two-column-row-template">
<tr>
<td v-bind:class="colorList[0]">{{field}}</td>
<td v-bind:class="colorList[1]">{{value}}</td>
</tr>
</script>
This runs, so you could insert some statements {{colorList}} inside the component to see what is being rendered.