How to update a Single File Component's data with its method? - vue.js

I'm in the process of learning Vue.js and now I'm learning about Single File Component. My objective is to ask user for their location through Geolocation API and then update the page with their latitude and longitude coordinate values. I'm able to get the coords values through console.log, but I can't get the SFC to update itself with the values. I may be missing something here.
geolocation.vue
<template>
<p>Your location is: {{ text }}. Is this correct?</p>
</template>
<script>
console.log('in geolocation.vue');
let text = "N/A";
export default {
name: "geolocation",
data: function () {
return {
text: text,
}
},
methods: {
findLocation: function() {
let that = this;
console.log('in findLocation()');
if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (pos) {
console.log('CONSOLE: ' + pos.coords.latitude + ', ' + pos.coords.longitude);
that.text = pos.coords.latitude + ', ' + pos.coords.longitude;
});
}
}
}
}
</script>
<style scoped>
p {
color: red;
font-size: 85%;
}
</style>
App.vue
<template>
<div class="center-text">
<h1 class="site-title">{{ app_name }}</h1>
<p>I'm the store page!</p>
<p>Soon I'll display products according to the inventory at the distribution center(s) in the area.</p>
<geolocation/>
</div>
</template>
<script>
import {mapGetters} from 'vuex';
import geolocation from './geolocation';
export default {
computed: {
...mapGetters(['app_name'])
},
components: {
geolocation
},
mounted: function() {
geolocation.methods.findLocation();
}
};
</script>
<style scoped>
.site-title {
font-family: 'Lobster', cursive;
}
.center-text {
text-align: center;
}
</style>

Add this to the geolocation.vue
mounted() {
this.findLocation();
}
Remove these lines in App.vue
mounted: function() {
geolocation.methods.findLocation();
}
Split your component into many child component and it has cycle hooks its self.
When geolocation component has been mounted, findLocation will be called and the location will be bind into the template.

Related

Vue: emit to view component works only the first time

I have a view which has a recursive component A, which in turn embeds a component B. In component B changes are made, which should be passed via emit to component A and these should be passed directly to the view.
This "chain" works, but only the first time:
Komponent B:
// Emit in component B
function emitToParent() {
console.log("component B: ", props.current.id);
emits('changeParentReactiveData', props.current.id, data);
}
Komponent A:
// Receiving the emit in Komponent A and passing it to the view
<component-b #change-parent-reactive-data="emitToParent" />
function emitToParent(id, data) {
console.log("component A: ", id);
emits("changeParentReactiveData", id, data);
}
View:
// Receiving the emit in the View
<component-a #change-parent-reactive-data="setReactive" />
function setReactive(id, data) {
console.log("view: ", id);
}
Outcome console.log:
component B: 262194
component A: 262194
view: 262194
component B: 262187
component A: 262187
component B: 262193
component A: 262193
So as you can see, the last emit to the view component happens only the first time. Why? Does anyone have an idea?
Your code should work, As I am not able to setup the Vue 3 in the below code snippet. I am demoing it using Vue 2 setup. Please have a look and try to find out the root cause of the issue you are facing.
Every time you will click on Click Me text in the inner child component. It is emitting the events properly.
Vue.component('childComponent', {
template: `<div class="emit-time" #click="click()">Click me (Component A)</div>`,
methods: {
click() {
console.log('component A');
this.$emit('clickup', 'a click');
}
}
});
Vue.component('ParentComponent', {
data() {
return {
message: null
}
},
template: `<div><p v-text="message"></p><child-component #clickup="localMethod"></child-component></div>`,
methods: {
localMethod() {
console.log('component B');
this.message = 'Component B invoked and emitted the event to the parent';
this.$emit('clickup', 'a click');
}
}
});
new Vue({
el: "#app",
data: {
message: null
},
methods: {
localMethod() {
console.log('Root component');
this.message = 'Top most parent invoked'
}
}
});
#app {
padding : 20px;
}
div {
padding : 10px;
border : 1px dashed black;
}
div ~ div {
margin-top : 10px;
border-color : red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p v-text="message"></p>
<parent-component #clickup="localMethod"></parent-component>
</div>
Your code should work as it is. I have set up a demo here and it is working just fine. Maybe you can match your code with this-
View.vue
<script setup>
import Comp1 from './Comp1.vue'
const setReactive = (payload) => {
console.log('View', payload);
};
</script>
<template>
<v-app>
<v-main>
<Comp1 #change-parent-reactive-data="setReactive" />
</v-main>
</v-app>
</template>
Component 1
<script setup>
import Comp2 from './Comp2.vue'
const emits = defineEmits(['changeParentReactiveData'])
const emitToParent = (payload) => {
console.log('Component A', payload);
emits('changeParentReactiveData', payload);
};
</script>
<template>
<Comp2 #change-parent-reactive-data="emitToParent" :some-prop="262194"></Comp2>
</template>
Component 2
<script setup>
const props = defineProps(['someProp']);
const emits = defineEmits(['changeParentReactiveData'])
const emitToParent = () => {
console.log('Component B', props.someProp);
emits('changeParentReactiveData', props.someProp);
};
</script>
<template>
<v-btn #click="emitToParent()">
click
</v-btn>
</template>
Try implementing a global event bus to pass values from component to component.
Here's a blog to explain how:
https://blog.logrocket.com/using-event-bus-in-vue-js-to-pass-data-between-components/

vue js passing data through global event bus not working

Using vue cli I have created a simple vue app with two nested components. I want to pass data between them clicking the h1 tag in my component 1 (a more structured approach suggests to use vuex but this is a very easy app passing simple data using for test).
Clicking the h1 I receive an error but I'm not getting the point. The error says
[Vue warn]: Error in event handler for "titleChanged": "TypeError: Cannot read property 'apply' of undefined"
(found in <Root>)
My code is below
main.js
import Vue from 'vue'
import App from './App.vue'
import Axios from 'axios'
Vue.config.productionTip = false
Vue.prototype.$http = Axios
export const bus = new Vue();
new Vue({
render: h => h(App),
}).$mount('#app')
app.vue
<template>
<div>
<comp-1 v-bind:title="title"></comp-1>
<comp-2 v-bind:title="title"></comp-2>
</div>
</template>
<script>
import comp-1 from './components/Comp-1.vue'
import comp-2 from './components/Comp-2.vue'
export default {
components: {
'comp-1': comp-1,
'comp-2': comp-2
},
data() {
return {
title: "my title"
}
}
}
</script>
<style scoped>
</style>
comp-1.vue
<template>
<header>
<h1 v-on:click="changeTitle">{{ pTitle }}</h1>
</header>
</template>
<script>
import {bus} from '../main'
export default {
props: {
title: {
Type: String
}
},
data() {
return {
pTitle: ''
}
},
created: function() {
this.pTitle = this.title
},
methods: {
changeTitle: function() {
this.pTitle = 'I have changed my title!'
bus.$emit('titleChanged', this.pTitle)
}
}
}
</script>
<style scoped>
</style>
comp-2.vue
<template>
<footer>
<p>{{ title }}</p>
</footer>
</template>
<script>
import {bus} from '../main'
export default {
props: {
title: {
Type: String
}
},
data() {
return {
pTitle: ''
}
},
created() {
this.pTitle = this.title;
bus.$on('titleChanged'), (data) => {
this.title = data
}
}
}
</script>
<style scoped>
</style>
In created of comp-2 component, there is a mistake in event handler
Change it like this:
bus.$on("titleChanged", data => {
this.title = data;
});

Vue v-cloak directive does not work on component

I added v-cloak directive on the top div in my component and added css as in docs, set a timeout but it does not work I can see else content and the form styles.
Component code:
<div class="form-in-wrap" v-cloak>
<ul id="example-1" v-if="reports.length>0">
<report-component v-for="report in reports" :report="report" :key="report.id" :options="options">
</report-component>
</ul>
</div>
Component script:
<script>
import ReportComponent from "./ReportComponent";
export default {
components: {ReportComponent},
data: function () {
return {
reports: {
type: Array,
},
report: {
...
},
}
},
created: function () {
var self = this
setTimeout(function () {
self.loadData('/reports')
}, 2000);
},
methods: {
loadData: function() {
get method
}
}
</script>
<style scoped>
[v-cloak] {
display: none !important;
}
</style>
All example creates a Vue instance, but I am using export default in my component. Also it is not a main component, it is included with router can this be the case why it does not work?

Error when trying to add created method in Vue component

Component
<template lang="html">
<div class="chat-log">
<chat-message v-for="message in messages" :message="message"></chat-message>
</div>
</template>
<script>
export default {
props: ["messages"]
}
</script>
<style lang="css">
.chat-log .chat-message:nth-child(even) {
background-color: #ccc;
}
.chat-log {
overflow-y: auto;
max-height: 400px;
}
</style>
When I change the above script code to below. I get errors..
<script>
export default {
props: ["messages"]
},
created() {
$(".chat-log").scrollTop($(".chat-log").prop('scrollHeight'));
}
</script>
Error Details
Unexpected token, expected ;
Issue comes only when adding the created method, Am I missing anything?
The created lifecyle method goes within the body of the Vue component itself, not outside. I mean:
export default {
props: ["messages"],
created() {
$(".chat-log").scrollTop($(".chat-log").prop('scrollHeight'));
}
}
Vue.js Lifecycle
Your created(){} method should be encapsulated within your export default {} block.
In other words, change your code this:
export default {
props: ["messages"],
created() {
$(".chat-log").scrollTop($(".chat-log").prop('scrollHeight'));
}
},

How to pass state between components?

I have a component called "item-detail" that has a "item" prop on it, like so:
<item-detail v-ref="itemDetail" v-if="showItemDetail" v-on:clicked="showItemDetail = false"></item-detail>
Then I have an item view component like this:
<item-view v-on:click="onItemClick(this)" title="head" :item="equipment.head"></item-view>
I'm trying to make it so that when the item-view click event fires, it passes that views "item" prop over to the item-detail component. So my onItemClick looks like this:
onItemClick: function(item) {
this.$refs.itemDetail.item = item;
appState.showItemDetail = true;
}
I can't see from the docs how to get a handle to the item-view inside that v-on:click attribute. "this" always equates to the Vue app instance inside the onItemClick method and "item" is also set to the Vue app instance.
Basically the use case is "When the item view is clicked, pass its 'item' property value to the item-detail component and display the item-detail component.".
After having a conversation I proposed that using Vuex was more suitable that passing logic up the chain which could lead to lots of scaling problems later in the project life cycle.
https://github.com/LiamDotPro/VuexStoreExample
This example shows how you can use a store to easily pass logic between components without direct relationships or chaining.
store
/* eslint-disable space-before-function-paren */
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
titleText: "hmm.."
}
const actions = {
changeHomeText: ({commit}, context) => {
commit('UPDATE_TEXT', context)
}
}
const mutations = {
UPDATE_TEXT(state, text) {
state.titleText = text
}
}
const getters = {}
export default new Vuex.Store({
strict: true,
state,
getters,
actions,
mutations
})
app
<template>
<div id="app">
<router-view/>
<div>
<h1>{{getTitle}}</h1>
</div>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
msg: '',
}
},
computed: {
getTitle: function () {
return this.$store.state.titleText;
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Inner component
<template>
<div class="innerTile">
<h1>Inner Tile</h1>
<button #click="ChangeTileText()">inner tile button..</button>
</div>
</template>
<script>
export default {
name: '',
data() {
return {
msg: '',
}
},
methods: {
ChangeTileText: function () {
this.$store.dispatch("changeHomeText", "Hi from inner tile..");
}
}
}
</script>
<style scoped>
</style>