Vue Component interact with page where its in - vue.js

I'm using 2 components in my page, one for a comment and inside the comment component is the comment reply component, I did it this way to add some functionality that I needed but now I don't know how to get the buttons in the components to do the stuff they did before I turned them into components. For example the edit function opens a dialog that is in the page where the comment component is located but now it doesn't do anything, same with the reply functionality.
How can I get this to work, or is there a way to keep those methods in the main page and not have to put them in the components? I've never made my own component before so I'm not sure how to do this.
This is the Comment component code, the editComment and openReply are methods from the main page
<template>
<div class="comment">
<el-card shadow="never" v-if="comment.parent_id === null">
<el-row v-if="comment.deleted === 0">
<el-row style="margin-bottom: 15px">
<el-col :span="20" v-if="forum.anonymous === 0">
<p class="comment-user-name">{{comment.user.name}} dice: </p>
</el-col>
<el-col :span="20" v-if="forum.anonymous === 1">
<p class="comment-user-name">Anónimo dice: </p>
</el-col>
<el-col :span="4">
<div style="text-align: end"
v-if="comment.user_id === $page.auth.user.auth.user.id">
<div v-if="canEdit" class="btn-link-edit action-button"
#click="editComment(comment)">
<i class="fas fa-pencil-alt"></i>
</div>
<div class="btn-link-delete action-button"
#click="remove(comment.id)">
<i class="fas fa-trash"></i>
</div>
<div class="btn-link-preview action-button"
#click="openReply(comment)">
<i class="fas fa-reply"></i>
</div>
</div>
<div style="text-align: end"
v-if="comment.user_id !== $page.auth.user.auth.user.id">
<div class="btn-link-preview action-button"
#click="openReply(comment)">
<i class="fas fa-reply"></i>
</div>
</div>
</el-col>
</el-row>
<el-row style="margin-top: 10px">
<p class="comment-comment">{{comment.comment}}</p>
</el-row>
<el-row class="fa-pull-right pb-1">
<p class="comment-user-time ">{{formatDate(comment.comment_time)}}</p>
</el-row>
</el-row>
<el-row v-if="comment.deleted === 1">
<el-row style="margin-top: 15px">
<p class="comment-comment">{{comment.comment}}</p>
</el-row>
<el-row class="fa-pull-right pb-1">
<p class="comment-user-time ">{{formatDate(comment.comment_time)}}</p>
</el-row>
</el-row>
<el-row style="margin-left: 30px">
<div v-for="reply in comment.replies">
<ReplyComponent :reply="reply" :forum="forum"/>
</div>
</el-row>
</el-card>
</div>
</template>
<script>
import moment from "moment";
import ReplyComponent from "./ReplyComponent";
export default {
name: "Comment",
props: {
comment: Object,
forum: Object,
},
components: {
ReplyComponent
},
data() {
return {
canEdit: Boolean,
interval: null,
mode: '',
form: {
comment: '',
},
};
},
methods: {
openReply(row) {
this.dialogReplyVisible = true;
this.parent = row;
},
editComment(item) {
this.mode = 'Editar';
this.form = _.cloneDeep(item);
this.dialogFormVisible = true;
},
checkTime() {
var minutes = moment().diff(moment(this.comment.comment_time), 'minutes');
if (minutes >= 30) {
this.canEdit = false;
// here you could also already clear the interval, since it won't change
} else if (minutes <= 29) {
this.canEdit = true;
}
}
},
created() {
this.checkTime();
this.interval = setInterval(() => {
this.checkTime();
}, 10000);
},
beforeDestroy() {
clearInterval(this.interval);
}
};
</script>

If I understood your questions correctly, you want to communicate (pass data) from Child components to parent component (page).
The way we can achieve this is by $emitting events from Child components and have the parent component respond to these events.
So, in your editComment and openReply methods you would fire events like this:
openReply(row) {
this.$emit('openReply', {any payload that you would want pass to parent});
},
editComment(item) {
this.$emit('editComment', {any payload that you would want pass to parent}
},
And, in your parent component / page you would subscribe and handle those events. Pseudo-code below:
<Comment v-on:openReply="handleOpenReply"
v-on:editCommnet="handleEditComment"/>
Further Reading:
Passing Data to Child Components With Props
Listening to Child Components Events

Related

Passing props dynamically to a component inside a v-for loop

I have a v-for loop that iterates through an array of meetings (meetings between sellers and potential buyers of used cars) and prints a card for each meeting, a basic display of who the meeting is with, what car it is about and the scheduled date. Now, I implemented a button that when clicked, opens a dialog with a Google Maps component that shows the marker for the agreed location of the meeting.
My problem is that no matter what card I click on, the dialog will always display the location of the LAST card, regardless of which has been clicked. I would think that since Im calling the component INSIDE the v-for loop it would pass props dynamically for each card, on each iteration, but that does not seem to be the case.
Here is the HTML:
<div
v-for="meeting in meetings"
:key="meeting.did"
class="col-12 col-md-6 col-lg-3 q-pa-md q-mx-xl"
>
<q-card class="my-card homeCard q-pa-md">
<q-dialog class="mapDialog flex column" v-model="mapDialog">
<MeetMapComponent
:key="componentKey"
:mapDiv="mapDiv"
:mapData="meeting.address"
:buyerName="meeting.name"
/>
</q-dialog>
<q-card-section
class="tipCardImage flex row justify-end"
:style="`background-image: url(${meeting.car.carImg})`"
>
<router-link
:to="`/user/meet/edit/${meeting.did}`"
style="text-decoration: none"
>
<q-icon
#click="fetchMeeting(meeting.did)"
name="fa-solid fa-pencil editNameIcon q-mb-sm q-ml-sm"
></q-icon>
</router-link>
<q-icon
name="fa-solid fa-trash editNameIcon q-mb-sm q-ml-sm"
#click="triggerDelete(meeting.did)"
></q-icon>
</q-card-section>
<q-card-section>
<div class="cardTitle">
<span>Encuentro Con</span> {{ truncateString(meeting.name, 30) }}
</div>
<div class="tipCardText">
<span>Agendado para el </span>
<p>{{ truncateString(meeting.date, 120) }}</p>
</div>
<div class="flex row justify-end">
<q-btn
#click="mapDialog = true"
class="text-white cardButton"
:class="{ cardButtonMobile: $q.screen.lt.md }"
>Ver Ubicación</q-btn
>
</div>
</q-card-section>
</q-card>
</div>
And here is the code for the MeetMapComponent:
<template>
<div class="meetMapContainer">
<div ref="mapDiv" style="width: 100%; height: 500px" />
<h5 class="text-center text-white">{{ props.mapData.address }}</h5>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useAuthStore } from "stores/auth";
import { storeToRefs } from "pinia";
import { Loader } from "#googlemaps/js-api-loader";
const props = defineProps({
mapData: Object,
buyerName: String,
});
const GOOGLE_MAPS_API_KEY = "...";
const loader = new Loader({ apiKey: GOOGLE_MAPS_API_KEY });
const mapDiv = ref(null);
async function mapRender() {
await loader.load();
const map = new google.maps.Map(mapDiv.value, {
mapTypeId: "roadmap",
center: props.mapData.coordinates,
zoom: 13,
});
console.log(map);
new google.maps.Marker({
position: props.mapData.coordinates,
map,
title: `Encuentro con ${props.buyerName}`,
});
}
mapRender();
</script>
I will help you as much as I understand. You use the mapDialog variable to open the dialogue. But even if this variable is used in v-for, its reference does not change. For this reason, when you want to open a modal, all modals may be opened and the last one may appear because it is the last one opened. Please check the dom.
I think this method can solve the problem.
in script
const meetings = [
{
did: 'some value',
address: 'some address',
name: 'some name',
// add modal flag
showMapModal: false
}
]
template
<div
v-for="meeting in meetings"
:key="meeting.did"
class="col-12 col-md-6 col-lg-3 q-pa-md q-mx-xl"
>
<q-card class="my-card homeCard q-pa-md">
<q-dialog class="mapDialog flex column" v-model="meeting.showMapModal">
<MeetMapComponent
:key="componentKey"
:mapDiv="mapDiv"
:mapData="meeting.address"
:buyerName="meeting.name"
/>
</q-dialog>
</q-card>
</div>

How to pass props to sibling and child component in vue

The structure of my code is like this:
So in the Product component, I am making an API call:
<template>
<button class="btn button col-2" #click="addToCart()">
Add to cart
</button>
</template>
<script>
methods:{
addToCart: function () {
let amount = this.itemsCount !== "" ? this.itemsCount : 1;
if(this.variationId != null) {
this.warningMessage = false;
cartHelper.addToCart(this.product.id, this.variationId, amount, (response) => {
this.cartItems = response.data.attributes.items;
});
} else {
this.warningMessage = true;
}
console.log(this.cartItems)
},
}
</script>
And what I am trying to do is the response (this.cartItems) should be shown in Cart component. And my Navbar component:
<template>
<nav class="navbar navbar-expand-lg shadow">
<div class="container navbar-container">
<div class="navbar navbar-profile">
<div class="dropdown">
<button class="btn dropdown-toggle" type="button" id="dropdownCart" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<i class="fa fa-fw fa-cart-arrow-down"></i>
<span></span>
</button>
<div #click="$event.stopPropagation()">
<CartBox :cartItems="cartItems"/>
</div>
</div>
</div>
</div>
</nav>
</template>
<script>
export default {
props: {
cartItems:Object
},
components: {CartBox},
}
And CartBox:
<template>
<Cart/>
</template>
<script>
import Cart from '../components/Cart'
export default {
components: {
Cart
}
}
</script>
And my Cart component:
<template>
<div
class="dropdown-menu cart"
aria-labelledby="triggerId"
>
<div class="inner-cart">
<div>
<div class="cart-items">
<div>
<a class="remove">Remove</a>
</div>
</div>
</div>
<hr/>
<div class="cart-items-total">
<span>Total:</span>
Clear Cart
</div>
<hr/>
<router-link :to="{name: 'order'}" class="btn button-secondary">Go To Cart</router-link>
</div>
</div>
</template>
<script>
export default {
computed: {
},
methods: {
}
};
</script>
I am really confused how to pass the props to sibling component and then the child component but if you could pass it to Cart component, that would really help me.
There are two approaches for your request:
1. Using props, provide and inject
This could be accomplished with Provide / inject, after passing your response to a parent. Basically, you will emit your response from your Product component to a parent, maybe like your App.vue as the prop myData, then you provide it for every child, no matter where it is nested, like this:
provide: {
providedData: this.myData
}
In any child you can now use:
inject: ['providedData']
Please note, that this data will only be available if your Product component received it. The second approach is recommended.
2. Using a store
Using a store like vuex is a bit more complex than approach 1, but it will save a lot of time in the future. You would recieve your response in your Product component, dispatch it to the store and could call the state of information from this store anywhere in your app. See further information in this documentation: Vuex | Getting Started

How to render the content of an object in vue?

apologies if that's too basic, but I'm stuck.
I have created an object in vue with three properties (slug, title and content). I successfully console.logged the object. How can I now use the object in my page in order to render its content?
There is no need for me to loop through the object, at it has only one item in it.
<template>
<div class="relative py-16 overflow-hidden bg-white">
<div class="relative px-4 sm:px-6 lg:px-8">
<div class="mx-auto text-lg max-w-prose">
<h1>
<span
class="block text-base font-semibold tracking-wide text-center text-indigo-600 uppercase"
>Hello</span
>
<span
class="block mt-2 text-3xl font-extrabold leading-8 tracking-tight text-center text-gray-900 sm:text-4xl"
>Here is the name</span
>
</h1>
<p class="mt-8 text-xl leading-8 text-gray-700"></p>
<div
v-bind="this.data.content"
class="text-lg font-medium leading-6 text-gray-900"
></div>
</div>
</div>
</div>
</template>
<script>
const Cosmic = require("cosmicjs");
const api = Cosmic();
const bucket = api.bucket({
slug: "((BUCKETNAME))",
read_key: "((KEY))",
});
const data = bucket
.getObject({
id: "((BUCKET ID))", // Object ID
props: "slug,title,content", // get only what you need
})
.then((data) => {
const about = data.objects;
console.log(data);
});
export default {
name: "data",
data() {
return {
data,
};
},
};
</script>
As other commenters have suggested, it would be useful to read the Vue syntax guide here https://v2.vuejs.org/v2/guide/syntax.html
But to answer your question with the most minimal of code changes, you'd want to move your data request to the lifecycle hook of your vue component.
<template>
<h1>{{ dataObjects.title }}</h1>
<p>{{ dataObjects.slug }}</p>
<p>{{ dataObjects.content }}</p>
</template>
<script>
export default {
name: "data",
data() {
return {
dataObjects: null,
};
},
mounted() {
bucket.getObject({
id: "((BUCKET ID))", // Object ID
props: "slug,title,content", // get only what you need
})
.then((data) => {
// Assign the return value to the dataObjects propery of the vue instance.
this.dataObjects = data;
});
}
};
</script>
In the template section, you can see that I've used curly braces to render the contents of dataObjects (I wasn't sure what structure your data is in).
You can also learn from examples on the Vue Cookbook site

How to transfer data between components (siblings) Vue?

In the HeaderComponent method 'clickPrices' is called on click
<template>
<header>
<div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm">
<h5 class="my-0 mr-md-auto font-weight-normal">Company name</h5>
<nav class="my-2 my-md-0 mr-md-3">
<a class="p-2 text-dark" href="#">Features</a>
<a class="p-2 text-dark">Enterprise</a>
<a class="p-2 text-dark" #click="clickPrices()">Get pricing</a>
</nav>
<a class="btn btn-outline-primary " href="#">Sign up</a>
</div>
</header>
</template>
<script>
export default {
name: "HeaderComponent",
methods: {
clickPrices() {
...
},
},
}
</script>
<style scoped>
</style>
and there is a Pricing Component in which I make a request to the server in the method 'getPricing'
<template>
<div class="wrap-pricing">
<div class="pricing-header px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
<h1 class="display-4">Pricing</h1>
<p class="lead">Quickly build an effective pricing table for your potential customers with this Bootstrap example. It’s built with default Bootstrap components and utilities with little customization.</p>
</div>
<div class="container">
<div class="card-deck mb-3 text-center">
<div class="card mb-4 shadow-sm">
<div class="card-header">
<h4 class="my-0 font-weight-normal">Lorem</h4>
</div>
<div class="card-body">
<h1 class="card-title pricing-card-title">$10 <small class="text-muted">/ mo</small></h1>
<ul class="list-unstyled mt-3 mb-4">
<li>Lorem</li>
<li>Lorem</li>
<li>Lorem</li>
<li>Lorem</li>
</ul>
<button type="button" class="btn btn-lg btn-block"></button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import router from '../routes.js';
import { axios } from 'axios';
export default {
name: "PriceComponent",
methods: {
getPricing() {
axios.get('api/pricing').then((response) => {
//some actions
router.push('prices');
});
},
},
}
</script>
<style scoped>
</style>
How should I process the result of the 'сlickPrices' HeaderComponent method?
Or am I waiting for your ways, how can I get data in another by clicking in one component
You can emit an event and let the parent component handle the fetching and pass the data as props to the child component.
Or another way is to directly listen to the event as follows
Component 1:
this.$root.$emit('clickPrices', data);
Component 2:
mounted() {
this.$root.$on('clickPrices', data => {
this.getPricing();
});
}
Check out the answer by #alex
Since you are using 2 independent components (one is not included in the other), you cannot pass props
Things you can do -
Instead of fetching all the prices on click, just create a created hook like so, which will fetch all the pricing details whenever your component is created -
created () {
this.getPricing()
},
methods: {
getPricing() {
axios.get('api/pricing').then((response) => {
//some actions
router.push('prices');
});
},
},
Use State and when a user clicks on the button, pricing details are fetched and added in the state. And you can just use the state anywhere in your application like so -
this.$store.state.prices
Let me know if it works, if not we will find some other solution for you!

How can I get value in datetimepicker bootstrap on vue component?

My view blade, you can see this below :
...
<div class="panel-body">
<order-view v-cloak>
<input slot="from-date" data-date-format="DD-MM-YYYY" title="DD-MM-YYYY" type="text" class="form-control" placeholder="Date" name="from_date" id="datetimepicker" required>
<input slot="to-date" data-date-format="DD-MM-YYYY" title="DD-MM-YYYY" type="text" class="form-control" placeholder="Date" name="to_date" id="datetimepicker" required>
</order-view>
</div>
...
My order-view component, you can see this below :
<template>
<div>
<div class="col-sm-2">
<div class="form-group">
<slot name="from-date" required v-model="fromDate"></slot>
</div>
</div>
<div class="col-sm-1">
<div class="form-group" style="text-align: center">
-
</div>
</div>
<div class="col-sm-2">
<div class="form-group">
<slot name="to-date" required v-model="toDate"></slot>
</div>
</div>
<div class="col-sm-4">
<button v-on:click="filter()" class="btn btn-default" type="button">
<span class="glyphicon glyphicon-search"></span>
</button>
</div>
</div>
</template>
<script>
export default {
data() {
return{
fromDate: '',
toDate: ''
}
},
methods: {
filter: function() {
console.log(this.fromDate)
console.log(this.toDate)
}
}
}
</script>
I using v-model like above code
But, when I click the button, the result of
console.log(this.fromDate)
console.log(this.toDate)
is empty
It display empty
Why it does not work?
How can I solve it?
You cannot bind a slot using v-model and expect that Vue will attach that automatically to your slot input, but I can't see any reason why you need to use a slot here anyway. It looks like you just want an input that you can attach custom attributes to and you can do that by passing the attributes as a prop and use v-bind to bind them:
<template>
<div>
<input v-bind="attrs" v-model="fromDate" />
<button #click="filter">filter</button>
</div>
</template>
export default{
props: ['attrs'],
methods: {
filter() {
console.log(this.fromDate)
}
},
data() {
return {
fromDate: ""
}
}
}
new Vue({
el: "#app",
data: {
fromDateAttrs: {
'data-date-format': "DD-MM-YYYY",
title: "DD-MM-YYYY",
type: "text",
class: "form-control",
placeholder: "Date",
name: "from_date",
id: "datetimepicker",
}
}
});
Now you can just pass your attrs as a prop in the parent:
<my-comp :attrs="fromDateAttrs"></my-comp>
Here's the JSFiddle: https://jsfiddle.net/rvederzc/
EDIT
In reference as to how to create a date picker component, here's how I would implement a jQuery datepicker using Vue.js:
<template id="date-picker">
<div>
<input v-bind="attrs" v-model="date" #input="$emit('input', $event.target.value)" v-date-picker/>
</div>
</template>
<script type="text/javascript">
export default {
props: ['attrs'],
directives: {
datePicker: {
bind(el, binding, vnode) {
$(el).datepicker({
onSelect: function(val) {
// directive talk for 'this.$emit'
vnode.context.$emit('input', val);
}
});
}
}
}
}
</script>
You can then bind that with v-model in the parent:
<date-picker v-model="myDate"></date-picker>
Here's the JSFiddle: https://jsfiddle.net/g64drpg6/
Cant expect any javascript technology to be complete before it becomes famous. Going by that, I tried all the recommendations from using moment to vue-datapicker. All recommendations heavily broke design and needed hardcode of the div id's in the vue initialisation under mounted. Cant introduce hacks into my project this way. Messes up design and implementation neatness.
I fixed it using plain old jsp. On Save, I just did this
vuedata.dateOfBirthMilliSecs = $("#dateOfBirth").val() ;
I'll figure out conversion of date format to milliseconds in my java controller.