For loop in both parent and child component vuejs - javascript

I want to display items of each category.
For this I have a parent component that passes the category to the child component and it filters out the items based on that category and shows it for each of the categories. The child component loops through the items in that category.
The item array contains items of all category. I cant quite get the idea on how to do this and which lifecycle hooks I should use.
If anyone would take a look that would be appreciated.
The Parent Component:
<template>
<div>
<home-categories v-for="category in storeCategories" :key="category.name" :category="category.name"></home-categories>
</div>
</template>
<script>
import HomeCategories from "#/components/home/HomeCategories";
export default {
name: "HomeCategoryParent",
components: {
HomeCategories
},
created() {
this.$store.dispatch("bindStoreCategories")
},
computed: {
storeCategories() {
return this.$store.getters.storeCategories;
}
}
}
</script>
<style scoped>
</style>
The Child Component:
<template>
<v-container fluid style="margin-top: -20px;" class="google-font">
<p
class="google-font mt-0 mb-0"
style="font-weight: 200; font-size: 120%; padding: 0px;"
>
<b>{{ category }}</b>
</p>
<v-card
class="d-flex flex-row disableScroll"
flat
tile
style="margin-top: 16px; padding: 2px; overflow-x: auto;"
>
<v-card max-width="250" v-for="item in items" :key="item.name">
<v-img
class="white--text align-end"
height="200px"
src="../../assets/img/category_blank.jpg"
>
<v-card-title>{{ item.ITEM_NAME }}</v-card-title>
</v-img>
<v-card-subtitle class="pb-0">{{item.price}}</v-card-subtitle>
<v-card-text class="text--primary">
<div>{{item.desc}}</div>
<div>{{item.unit}}</div>
</v-card-text>
<v-card-actions>
<v-btn color="orange" text>
Add
</v-btn>
</v-card-actions>
</v-card>
</v-card>
</v-container>
</template>
<script>
export default {
data() {
return {
items: []
}
},
name: "HomeCategories",
props: ["category"],
created() {
this.$store.dispatch("bindStoreItems");
let all_items = this.$store.getters.storeItems;
this.items = this.$store.getters.storeItems;
all_items.forEach(item => {
let itemObj = {
category: null,
name: null,
desc: null,
price: null,
unit: null,
isAvail: null,
}
if(item.CATEGORY === this.category){
itemObj.category = item.CATEGORY;
itemObj.name = item.ITEM_NAME;
itemObj.desc = item.ITEM_DESC;
itemObj.price = item.ITEM_PRICE;
itemObj.unit =item.ITEM_UNIT;
itemObj.isAvail =item.ITEM_AVAIL;
this.items.push(itemObj);
}
})
},
};
</script>
<style scoped></style>

Related

How to set parent component data from child component in vuejs

Below is the parent component and child component. I am trying to access tabs_value data in the parent component from the child component but it is returning as undefined.
this.$parent.tabs_value returns as undefined when I try to access it inside the run method in the child component.
Please help me find where I am going wrong? Below is the code
Parent Component
<template>
<div>
<v-layout row wrap>
<v-flex xs12 sm12 lg12>
<div>
<v-card>
<v-tabs v-model="tabs_value"
color="black"
centered
show-arrows
>
<v-toolbar-title>Custom</v-toolbar-title>
<v-spacer></v-spacer>
<v-tab href="#build">Build</v-tab>
<v-tab href="#run">Run</v-tab>
</v-tabs>
<v-tabs-items v-model="tabs_value">
<v-tab-item value="#build" id="build">
<Build ref="build_reports" />
</v-tab-item>
<v-tab-item value="#run" id="run">
<Run :reports="reports" ref="run_reports" />
</v-tab-item>
</v-tabs-items>
</v-card>
</div>
</v-flex>
</v-layout>
</div>
</template>
<script>
import Build from 'views/build.vue'
import Run from 'views/run.vue'
import URLs from 'views/routes'
export default {
components: {
Build,
Run
},
data: function() {
return {
tabs_value: 'build',
isLoaded: true,
reports: []
}
},
created() {
this.fetch();
},
methods: {
fetch() {
this.$axios.get(URLs.REPORTS_URL)
.then(response => {
this.reports = response.data
});
}
}
};
</script>
Child Component run.vue
<template>
<div>
<v-layout row wrap>
<v-flex xs12 sm12 lg12>
<div>
<v-card>
<div>
<v-data-table
:headers="headers"
:items="reports"
hide-default-footer
:mobile-breakpoint="0">
<template slot="item" slot-scope="props">
<tr>
<td>{{props.item.name}}</td>
<td>
<div>
<v-tooltip attach left>
<template v-slot:activator="{ on, attrs }">
<a v-bind="attrs" v-on="on"
class="" href='javascript:void(0);'
#click="run(props.item)"><i small slot="activator" dark color="primary" class="fas fa-play"></i></a>
</template>
<span>Run</span>
</v-tooltip>
</div>
</td>
</tr>
</template>
<template slot="no-data" >
<v-alert id='no-data' :value="true" color="error" icon="warning">
No Reports Yet
</v-alert>
</template>
</v-data-table>
</div>
</v-card>
</div>
</v-flex>
</v-layout>
</div>
</template>
<script>
import URLs from 'views/routes'
export default {
props: ['reports'],
data: function() {
return {
headers: [
{ text: 'Name', value: 'name', sortable: false },
{ text: 'Actions', sortable: false }
],
}
},
methods: {
run(report) {
debugger
// this.$parent.tabs_value returns as undefined
}
}
}
</script>
you can use component events i.e $emit.
Below is example which will tell you how to use $emit. (https://vuejs.org/guide/components/events.html#emitting-and-listening-to-events)
Parent Component
<template>
<ChildComponent #updateTabsValue="updateTabsValue"/>
</template>
<script>
export default {
data(){
return {
tabsValue: 'tabs',
};
},
methods:{
updateTabsValue(val){
this.tabsValue = val;
}
},
}
</script>
Child Component
<template>
<button #click="$emit('updateTabsValue','newVal')"/>
</template>

How use a button in v-for loop in Nuxt.js?

I am using a card component of vuetify in a loop to display data but when I click on the button present in the card, all buttons of all cards present in the loop open. How can I do so that only the card button I clicked opens?
Here is my template :
<template>
<v-row>
<v-col
v-for="(prop, i) in Object.keys(linkIdeal)"
:key="i"
cols="6"
lg="2"
md="3"
sm="4"
class="mb-6"
#click="console.log(prop)"
>
<v-card
v-if="linkIdeal[prop].plant_associated[0].category == '1'"
style="z-index: 0"
:class="`mx-auto my-12 plant-card Vegetables`"
width="100%"
>
<v-img
:src="`${linkIdeal[prop].plant_associated[0].image}`"
width="100%"
height="200"
></v-img>
<v-card-title class="white--text card-title justify-center">
{{ linkIdeal[prop].plant_associated[0].name }}
</v-card-title>
<v-card-actions #click="show = !show" style="cursor: pointer">
<span class="white--text btnDescr">Description</span>
<v-spacer></v-spacer>
<v-btn icon>
<v-icon class="white--text">
{{ show ? 'mdi-chevron-up' : 'mdi-chevron-down' }}
</v-icon>
</v-btn>
</v-card-actions>
<v-expand-transition>
<div v-show="show">
<v-divider></v-divider>
<v-card-text>
<span
class="white--text"
v-html="linkIdeal[prop].description"
></span>
</v-card-text>
</div>
</v-expand-transition>
</v-card>
</v-col>
</v-row>
</template>
Here is my script :
import {
mapGetters
} from "vuex";
export default {
props: {},
data: () => ({
linkIdeal: [],
show: false,
}),
computed: {
console: () => console,
...mapGetters({
plantActive: 'permatheque/getPlant',
}),
},
methods: {
async getAssociatedPlant() {
this.$axios.$get('...')
.then(response => {
//this.$store.commit('permatheque/setPlantAssociations', response)
this.linkIdeal = response
console.log(this.linkIdeal)
}).catch(error => {
console.log(error)
});
},
},
mounted() {
this.getAssociatedPlant()
}
}
Thanks for your answer
You are using 1 show variable as a state for all the cards, their open/close state all depend on that, thus when you toggle this state, all cards act accordingly.
If you want to have an open/close state for every single card, then you should add it to each one (like in their own component) OR you can track the opened cards (e.g. by ID) in an array. Both could be a solution to your problem. (snippet coming to demonstrate)
OPEN/CLOSE STATE TRACKED IN AN ARRAY
Vue.component('ToggleCard', {
props: ['id', 'label', 'open'],
methods: {
onClick() {
this.$emit("update:open")
}
},
template: `
<div
class="cursor-pointer"
:class="{
open: open
}"
#click="onClick"
>
{{ label }} - {{ open }}
</div>
`
})
new Vue({
el: "#app",
data() {
return {
openedCards: [],
cards: [{
id: 0,
label: "CARD 1",
},
{
id: 1,
label: "CARD 2",
},
{
id: 2,
label: "CARD 3",
},
]
}
},
methods: {
isOpen(id) {
return this.openedCards.includes(id)
},
updateOpenedCards(id) {
if (this.isOpen(id)) {
this.openedCards = this.openedCards.filter(cardId => cardId !== id)
} else {
this.openedCards = [...this.openedCards, id]
}
}
},
template: `
<div>
<toggle-card
v-for="card in cards"
:key="card.id"
:id="card.id"
:label="card.label"
:open="isOpen(card.id)"
#update:open="() => updateOpenedCards(card.id)"
></toggle-card>
</div>
`
})
.cursor-pointer {
cursor: pointer;
}
.open {
background: green;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
OPEN/CLOSE STATE TRACKED IN AN SEPARATE COMPONENTS
Vue.component('ToggleCard', {
props: ['id', 'label'],
data() {
return {
open: false,
}
},
template: `
<div
class="cursor-pointer"
:class="{
open: open
}"
#click="open = !open"
>
{{ label }} - {{ open }}
</div>
`
})
new Vue({
el: "#app",
data() {
return {
cards: [{
id: 0,
label: "CARD 1",
},
{
id: 1,
label: "CARD 2",
},
{
id: 2,
label: "CARD 3",
},
]
}
},
template: `
<div>
<toggle-card
v-for="card in cards"
:key="card.id"
:id="card.id"
:label="card.label"
></toggle-card>
</div>
`
})
.cursor-pointer {
cursor: pointer;
}
.open {
background: green;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

VueJs - I don't understand how I could pass a Boolean value to a chid component

I know that the subject was certainly treated somewhere, but I really don't understand how I could pass a variable to a child component.
What I'm trying to do, is passing 'displayTogglingMenu' in the parent component to 'toggle' in the child.
Parent component
data(){
return {
selectedItem: 1,
displayTogglingMenu: false,
items: [
{ text: 'Home', name: 'home', icon: 'mdi-home' },
{ text: 'Courses', name: 'courses_index', icon: 'mdi-school' },
{ text: 'Enrolments', name: 'enrolments_index', icon: 'mdi-format-list-bulleted' },
{ text: 'Lecturers', name: 'lecturers_index', icon: 'mdi-account-tie' },
],
}
},
Child Component
data(){
return {
toggle: valueOfParentComponent,
alertMessage: '',
loadTable: true,
courses: [],
expendable: [],
...
},
Here is the component I'm trying to hide, as asked by Tim:
<div class="table-container">
<b class="circle"></b>
<v-app>
<v-content>
<v-card>
<v-card-title>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
class="search"
></v-text-field>
<v-spacer></v-spacer>
<router-link :to="{ name: 'new_course'}" class="newItem">New course</router-link>
</v-card-title>
<v-data-table
:headers="courseHeaders"
:items="courses"
:items-per-page="10"
:loading="loadTable"
loading-text="Loading... Please wait"
:search="search"
:single-expand="singleExpand"
:expanded.sync="expanded"
item-key="id"
show-expand
class="elevation-1"
>
<template v-slot:item.title="{ item }">
<transition-group name="list" tag="p">
<span class=" list-item" v-bind:key="item">{{item.title}}</span>
</transition-group>
</template>
<template v-slot:item.actions="{ item }">
<transition-group name="list" tag="p">
<span class=" list-item" v-bind:key="item">
<router-link :to="{ name: 'edit_course', params: { id: item.id } }" class="edit-btn" title="Edit">
<v-icon med>mdi-pencil</v-icon>
</router-link>
<v-btn v-on:click="deleteCourse(item.id)" class="del-btn " title="Delete" >
<v-icon med>mdi-delete</v-icon>
</v-btn>
</span>
</transition-group>
</template>
<template v-slot:expanded-item="{ headers, item }" >
<td :colspan="headers.length" class="item-description" >
<h4 >Course description:</h4>
<p >{{ item.description }}</p>
</td>
</template>
</v-data-table>
</v-card>
</v-content>
</v-app>
<b class="circle2"></b>
</div>
Parent
<template>
<Component :toggle="displayTogglingMenu" />
</template>
<script lang='ts'>
import Component from 'Componenet.vue'
import { defineComponent } from 'vue';
export default defineComponent({
components: {Component},
data() {
return {
displayTogglingMenu: false
}
}
});
</script>
Child
<template>
<div v-show="toggle"></div>
</template>
<script lang='ts'>
import { defineComponent } from 'vue';
export default defineComponent({
props: ['toggle']
});
</script>
The value of displayTogglingMenu in parent will be availible as toggle in the child
You might need to get the value as this.$props.toggle depending on the context
You could pass data from parent to child components using properties. Here are the docs
Eg.
<child-componenet :prop-name="parent value that could also be reactive"></child-component>
Props are reactive so your child-component will react to any change on its properties, you could use the property in a v-if or v-show

Implementing Vue draggable

i am trying to implement vue draggable and it almost seems to work except for when i try to implement it on a button. It gives me an error message whenever i try to move the button.
Here is an example : https://codepen.io/anon/pen/xoQRMV?editors=1111
<div id="app">
<v-app id="inspire">
<v-container>
<v-layout justify-center>
<v-flex>
<draggable v-model="myArray" :options="options" handle=".handle">
<div v-for="element in myArray" :key="element.id" class="title
mb-3">{{element.name}}
<v-icon color="red" class="handle mt-0">drag_handle</v-icon>
</div>
<v-btn class="ml-0">Button</v-btn>
<v-icon color="red" class="handle">drag_handle</v-icon>
</draggable>
</v-flex>
</v-layout>
</v-container>
</v-app>
</div>
new Vue({
el: '#app',
data() {
return {
myArray: [
{name: 'Text1!!!!', id: 0},
{name: 'Text2!!!!', id: 1},
],
options: {
handle: '.handle'
}
}
}
})
Any help is appreciated.
It would have to work from a single array I think, e.g.
https://codepen.io/anon/pen/agQVvm?editors=1111
<div id="app">
<v-app id="inspire">
<v-container>
<v-layout justify-center>
<v-flex>
<draggable :list="combinedArray" :options="options" handle=".handle">
<div v-for="element in combinedArray" :key="element.id" class="title mb-3">
<div v-if="element.type !== 'button'" class="title mb-3">
{{ element.name }}
<v-icon color="red" class="handle mt-0">drag_handle</v-icon>
</div>
<div v-else>
<v-btn>{{ element.name }}</v-btn>
<v-icon color="red" class="handle mt-0">drag_handle</v-icon>
</div>
</div>
</draggable>
</v-flex>
</v-layout>
</v-container>
</v-app>
</div>
new Vue({
el: '#app',
created () {
this.combinedArray = [...this.myArray, ...this.buttonsArray]
},
data () {
return {
myArray: [
{ name: 'Text1!!!!', id: 0 },
{ name: 'Text2!!!!', id: 1 }
],
buttonsArray: [
{ name: 'Button1', id: 2, type: 'button' },
{ name: 'Button2', id: 3, type: 'button' }
],
combinedArray: [],
options: {
handle: '.handle'
}
}
}
})
I was able to implement the drag on buttons by creating their own array:-
<draggable class="list-group" :list="buttonArray" :options="options"
handle=".handle" group="drags">
<div v-for="item in buttonArray" :key="item.id">
<v-btn class="ml-0">{{item.name}}</v-btn>
<v-icon color="red" class="handle">drag_handle</v-icon>
</div>
</draggable>
buttonArray: [
{name: 'Button1', id: 2},
{name:'Button2', id:3}
],
The updated pen:- https://codepen.io/anon/pen/xoQRMV?editors=1111
However it creates an issue where i am not able to replace the text with the button. :(

Vue.js dialog/modal closes on parent component

I am trying to open my CanvasPreview Component in another component but it fails,
first, it quickly shows the dialog/modal afterward it gets hidden again if I open the Vue Dev tool
the showCanvasPreview is set to false if I manually edit it in my console to true the modal gets shown.
So I guess that it gets set to false again, but I can't see why.
This is the dialog/modal component:
<template>
<v-dialog
v-model="show"
>
<v-card>
<v-card-actions>
<v-container grid-list-md text-xs-center>
<v-layout row wrap>
</v-layout>
</v-container>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import CanvasPreviewSourceUpload from './CanvasPreviewSourceUpload';
export default {
components: {
'canvas-preview-source-upload': CanvasPreviewSourceUpload
},
props: {
imgSrc: String,
visible: Boolean
},
computed: {
show: {
get () {
return this.visible;
},
set (visible) {
if (!visible) {
this.$emit('closePreview');
}
}
}
},
}
</script>
And in my parent component I call the preview component like this:
<template>
<div>
//... some more html
<div id="canvas-body">
<canvas id="pdf-render"></canvas>
<canvas id="selectCanvas"
#mousedown="markElementOnMouseDown"
#mousemove="updatePreview"
#mouseup="markElementOnMouseUp">
</canvas>
</div>
<canvas-preview
:imgSrc="this.targetImage.src"
:visible="showCanvasPreview"
#closePreview="showCanvasPreview=false">
</canvas-preview>
</div>
</template>
<script>
import CanvasPreview from '#/js/components/CanvasPreview';
export default {
components: {
'canvas-preview': CanvasPreview
},
props: {
'name': String
},
data: () => ({
showCanvasPreview: false,
...
}),
methods: {
markElementOnMouseUp (event) {
this.isDragging = false;
this.targetImage.src = this.clipCanvas.toDataURL();
this.targetImage.style.display = 'block';
this.showCanvasPreview = true;
console.log("mouseup: " + this.showCanvasPreview);
},
}
</script>
Try this one
<v-dialog
v-model="show"
>
<v-card>
<v-card-actions>
<v-container grid-list-md text-xs-center>
<v-layout row wrap>
<canvas-preview-source-upload
:imgSrc="imgSrc"
#close.stop="show=false">
</canvas-preview-source-upload>
</v-layout>
</v-container>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import CanvasPreviewSourceUpload from './CanvasPreviewSourceUpload';
export default {
components: {
'canvas-preview-source-upload': CanvasPreviewSourceUpload
},
data: ()=> ({
show: false
}),
props: {
imgSrc: String,
visible: Boolean
},
watch: {
show(isShow){
if (!isShow) {
this.$emit('closePreview');
}
}
visible(isVisible) {
this.show = isVisible;
}
}
}
</script>```
Something like this should allow you to open a v-dialog from a separate component..
If you supply a CodePen or CodeSandbox with your code in it, we would be able to better assist you.
[CodePen mirror]
const dialog = {
template: "#dialog",
props: {
value: {
type: Boolean,
required: true
},
},
computed: {
show: {
get() {
return this.value;
},
set(value) {
this.$emit("input", value);
}
}
},
};
const dialogWrapper = {
template: "#dialogWrapper",
components: {
appDialog: dialog,
},
data() {
return {
isShown: false,
}
}
}
new Vue({
el: "#app",
components: {
dialogWrapper
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.6/dist/vuetify.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.6/dist/vuetify.min.css" rel="stylesheet" />
<div id="app">
<v-app>
<v-content>
<dialog-wrapper/>
</v-content>
</v-app>
</div>
<script type="text/x-template" id="dialog">
<v-dialog v-model="show">
<v-card>
<v-card-actions pa-0>
<v-spacer/>
<v-btn dark small color="red" #click="show = false">Close</v-btn>
<v-spacer/>
</v-card-actions>
<v-card-title class="justify-center">
<h2>
Hello from the child dialog
</h2>
</v-card-title>
</v-card>
</v-dialog>
</script>
<script type="text/x-template" id="dialogWrapper">
<div>
<h1 class="text-xs-center">I am the wrapper/parent</h1>
<v-container>
<v-layout justify-center>
<v-btn color="primary" dark #click.stop="isShown = true">
Open Dialog
</v-btn>
</v-layout>
</v-container>
<app-dialog v-model="isShown"></app-dialog>
</div>
</script>

Categories

Resources