Getting data from declared component in vue - javascript

Currently trying to wrap my head around Vue and templates.
I read that you pass data from child -> parent with $emit()
app.js
Vue.component('tweet-postbox', require('./components/tweetPostBox.vue').default);
const app = new Vue({
el: '#app',
methods: {
addTweet (tweet) {
//from the tweetPostBox.vue postTweet method
console.log(tweet)
}
}
});
tweetPostBox.vue
<template>
<div class="post-box">
<div class="w-100 d-flex align-items-center">
<div class="profile-image rounded-circle"></div>
<input v-model="message" type="text" id="tweetText" placeholder="Whats happening?">
</div>
<div class="controls d-flex align-items-center w-100">
<button class="btn btn-primary ml-auto" #click="postTweet" id="postTweet">Tweet</button>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
message: ''
}
},
methods: {
postTweet: async function(){
let response = await axios.post('/post', {
message: this.message
})
//How to get this response data to the main vue instance?
this.$emit('addTweet', response);
}
}
}
</script>
I'm trying to get the value into my app.js from the component file... but nothing is console logged. Where am I going wrong?
Update: Added HTML
<div class="container" id="app">
<tweet-postbox></tweet-postbox>
</div>

You should just need to change the template to:
<div class="container" id="app">
<tweet-postbox #add-tweet="addTweet"></tweet-postbox>
</div>
The #add-tweet part registers an event listener for the add-tweet event. I've used kebab case to avoid browser case-sensitivity problems. You'd need to emit the event with the same name, this.$emit('add-tweet', response). See the offical documentation to confirm that kebab case is the way to go.
The ="addTweet" parts assigns the method addTweet as the listener.
https://v2.vuejs.org/v2/guide/events.html#Method-Event-Handlers

Found this great answer on another post:
https://stackoverflow.com/a/47004242/2387934
Component 1:
<!-- language: lang-js -->
this.$root.$emit('eventing', data);
Component 2:
<!-- language: lang-js -->
mounted() {
this.$root.$on('eventing', data => {
console.log(data);
});
}

First of all, please fix your coding style, there's a lot of issues including indentation errors, try using eslint maybe.
Second, let's break this.$emit('addTweet') down a bit:
this is in that line refers to the instance of the Vue component, thus an instance of TweetPostBox.
When you call $emit with a addTweet, you're dispatching an event within the component.
Now that addTweet is dispatched, what's going to happen next? Vue is going to find all event handlers that handle addTweet and executes them.
Now in your case, you do not have any event handlers for this event. addTweet in your parent component is simply a local function in that component, it is not an event listener by any means.
To register an event listener, Vue provides # syntax, and you're already using that with #click, so just like you are doing #click="dosomething" (thus registering an event handler for onClick you need to use #add-tweet. (#addTweet also works but it is against coding style standards, Vue automatically handles the conversion for you)

Related

Angular 2 Endless loop async pipe

My hotel.service.ts
getRoomList(): Observable<RoomListType[]> {
return this.http.get<RoomListType[]>('http://localhost:3001/rooms');
}
my content.ts is
get roomList$() {
return this.hotelService.getRoomList().pipe(shareReplay(1));
}
my content.html is
<div class="col-md-9" *ngFor="let item of (roomList$ | async)">
<div class="row">
<div class="col-md-4">{{ item.RoomType }}</div>
<div class="col-md-8 d-flex justify-content-end">
<button class="ml-1 btn btn-outline-danger btn-sm" (click)="openScrollableContent(longContent)"><i class="fa fa-bed"></i>Oda Özellikleri</button>
</div>
</div>
...
</div>
My goal is I want to bind hotel rooms in my html file. I read some article on stackoverflow to use shareReplay(1) but I didnt work for me. How can I achieve this.
You've created an infinite loop by triggering an http request inside that getter.
When change detection occurs, your getter will be called. Your getter then makes an http request, which triggers change detection, which calls your getter, etc.
The roomList$ Observable you're passing to the async pipe should be created once, probably in ngOnInit.
So your content.ts would look something like this:
roomList$: Observable<RoomListType[]>;
ngOnInit() {
this.roomList$ = this.hotelService.getRoomList();
}
shareReplay doesn't seem necessary in your situation—that's used if you might have late subscribers to your Observable who should receive the last emitted value immediately upon subscription rather than having to wait for the Observable to emit again.
And if you did have that situation, you would configure it more like this:
getRoomList() {
return this.roomList$.pipe(shareReplay(1));
}
rather than with a getter that triggers a new http request every time it's referenced.
Here's a StackBlitz with your basic scenario not triggering an infinite loop.

Vue template isn't rendering in for loop

So after following a beginner Vue tutorial to setup a Todo app, I decided to try to adapt some parts of it for a website I'm trying to make. What I'm stuck on is that despite everything saying my for-loop is supposed to work, it doesn't.
The project itself was created using the vue-cli, and most of the code copy-pasted from the tutorial. (which is working fine with its own for-loop)
It seems like the data might be not passed onto the template maybe?
I have tried:
having the info inside the props and data sections
passing whole object and only parameters to the template
tried with hard-coded values inside array which is iterated on
(After setting up a new vue-cli project:)
App.vue:
<template>
<div id="app">
<create-section v-on:create-section="addSection" />
<section v-for="section in sections" v-bind:key="section.title" :info="section"></section>
</div>
</template>
<script>
import CreateSection from "./components/CreateSection";
import Section from "./components/Section";
export default {
name: "App",
components: {
CreateSection,
Section
},
data() {
return {
sections: []
};
},
methods: {
addSection(section) {
this.sections.push({
title: section.title,
description: section.description
});
console.log(
"Added to sections! : " + section.title + " | " + section.description
);
console.log("Sections length: " + this.sections.length);
}
}
};
</script>
Section.vue
<template>
<div class="ui centered card">
<div class="content">
<div class="header">{{ info.title }}</div>
<div>{{ info.description }}</div>
</div>
</div>
</template>
<script type = "text/javascript" >
export default {
props: {info: Object},
data() {
return {};
}
};
</script>
Expected result:
Display Section template on the website (after creating it with addSection that another script calls. Not included for brevity)
Actual result:
Nothing is displayed, only a empty tag is added
I believe the problem is that you've called it Section. As <section> is a standard HTML element you can't use it as a component name.
There is a warning built into the library but it seems to be case sensitive, which isn't entirely helpful. Try changing your components section to this:
components: {
CreateSection,
section: Section
},
You should then see the warning.
The fix would just be to call it something else.
This is mentioned in the first entry in the style guide:
https://v2.vuejs.org/v2/style-guide/#Multi-word-component-names-essential
section is an existing HTML5 element, you should name your section component something different.
If you really want to name the component Section, register it as 'v-section'
The problem is that when you do the loop in the <section v-for="section in sections" v-bind:key="section.title" :info="section"></section> the Array sections is not ready, there is nothing there.. so when you add new things to this array you need to trigger (computed prop) to send again the data to the section component.
Aside from the issue with using an existing HTML5 command as a name for your Vue component (you should change that to another name by the way), you should also look into how you declared the props within Section.vue. The code below shows the correct way to do it:
<script type = "text/javascript" >
export default {
props: ['info'],
data() {
return {};
}
};
</script>
The props take in the name of the property being declared from the parent component and should be a string.
Hope this helps.

Vue.js mounted function not accessing component properties

I'm not very new to Vue.js which is probably why I feel like I've been running mad all morning :). While creating a component, which I usually do, quite frequently, in this case, I had to initialize Google Maps within the mounted function, which seems like the right place to do that. In the mounted function, I would access the id property of a nested input field and attach an event listener to it. Pretty simple right?
Well, I figured that when I try to use the component multiple times on my page, I'm somehow accessing the same (seemingly shared) this variable within the mounted function.
Not sure why exactly this happens and/or if it's a feature but to make it even weirder, the props yield correct values within the template. (and within the methods as well)
Component Definition
<template>
<div class="LocationInput">
<input
type="text"
:id="id"
/>
</div>
</template>
<script>
export default {
name: 'LocationInput',
props: ['id'],
mounted() {
console.log('Component object representation ==> ', this)
console.log('ID ==> ', this.id)
}
}
</script>
Using my component...
<template>
<div class="MyTravelApp">
<LocationInput id="id1"/>
<LocationInput id="id2"/>
</div>
</template>
<script>
import LocationInput from './components/LocationInput';
export default {
components: { LocationInput }
}
</script>
What I get at the end of the day is the correct id values in the template but in my console, the exact same object and id are logged as you can see below. Notice how the _uid property is the same thing for both.
To make matters even worse, after modifying the this variable in the mounted function, while inspecting, I observed that the second component has that property modified as well. So they are essentially sharing the same object, which is extremely weird.
I would like to know if anyone has had similar issues and how to deal with it.
No self-closing tags for components.
Vue templates need to be valid HTML. There are no "self closing tags"
in HTML5, it's an XHTML syntax which is now outdated and you should
never use it.
(Later note:)
FYI self-closing tags works in 2.0 as long as you don't use in-dom
templates.
You may also be having an issue with camelCase vs. kebab-case. The snippet below behaves as expected.
Vue.component('locationInput', {
template: '#location-input-template',
props: ['id'],
mounted() {
console.log('Component object representation ==> ', this._uid)
console.log('ID ==> ', this.id)
}
});
new Vue({
el: '#my-travel-app'
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<template id="location-input-template">
<div class="LocationInput">
<input type="text" :id="id">
</div>
</template>
<div id="my-travel-app">
<location-input id="id1"></location-input>
<location-input id="id2"></location-input>
</div>

Listen for Foundation event in Vue.js?

I'm building an app in Vue.js with the general structure of:
<app>
<filters-component></filters-component>
<div class="off-canvas-content">
<nav-component></nav-component>
<div class="row card-grid">
<card-component v-for="item in items">
<modal-component v-if="launchModal === true"></modal-component>
</card-component>
</div>
</div>
</app>
This allow me to render the modal on the DOM only if a data element of launchModal is set to true (after clicking the button to launch the modal). This works great, but I need to do the reverse when it's closed.
According to Foundation's documentation, the Reveal (modal) component should emit an event called closed.zf.reveal when it's closed.
How do I listen for this event on the parent element (card-component) and then change launchModal to false, when it's called?
Thanks!
Essentially this will likely boil down to, in your modal-component (add these to the script in Modal.vue)
methods:{
onModalClosed(){
this.$emit("modal-closed")
}
},
mounted(){
this.$el.addEventListener('closed.zf.reveal', this.onModalClosed)
},
beforeDestroy(){
this.$el.removeEventListener('closed.zf.reve‌​al', this.onModalClosed)
}
Or something to that effect, depending on what element emits the event. If some other element emits the closed.zf.reveal event, then you could add a ref="modal" to it and then use this.$refs.modal.addEventListener and this.$refs.modal.removeEventListener.
Then you could just
<modal-component v-if="launchModal === true"
#modal-closed="launchModal = false">
</modal-component>
Edit
So the issue with listening to the event is that Foundation is using jQuery to fire the event. That means that you cannot listen for it using native methods (addEventListener), you have to listen to it with jQuery. So the modified code from above would be this:
methods:{
onModalClosed(){
this.$emit("modal-closed")
}
},
mounted(){
$(this.$el).on('closed.zf.reveal', this.onModalClosed)
},
beforeDestroy(){
$(this.$el).off('closed.zf.reve‌​al', this.onModalClosed)
}
And this does, in fact, catch the event. The problem is that Foundation, for whatever reason, moves the modal outside of the Vue and appends it to the bottom of the document when the modal is initialized. That causes Vue to throw an error when launchModal is set to false because the modal is no longer inside the Vue, and Vue complains when it tries to remove it from the DOM.
That being the case, I suggest you use your v-if inside the modal for the things that are rendering very slowly. That will result in a component like this.
Vue.component("modal", {
props:["show"],
template: "#modal-template",
watch:{
show(newVal){
if (newVal)
$(this.$el).foundation("open")
}
},
methods:{
onModalClosed(){
this.$emit("modal-closed")
}
},
mounted() {
new Foundation.Reveal($(this.$el))
$(this.$el).on("closed.zf.reveal", this.onModalClosed);
},
beforeDestroy() {
$(this.$el).off("closed.zf.reveal", this.onModalClosed);
}
});
And the template is
<template id="modal-template">
<div class="reveal" data-reveal>
<div v-if="show">
Stuff that is expensive to render
</div>
<button class="close-button" data-close aria-label="Close modal" type="button">
<span aria-hidden="true">×</span>
</button>
</div>
</template>
And here is the working example.

Polymer 1.0 can't access elements within nested <template> element

I am using Polymer 1.0 and I am building a small accordion example. I have data binding to the accordion text fine, I just want to change the icon of the accordion when I click it.
Below is my code
<dom-module id="ab-accordion">
<template>
<iron-ajax
auto
handle-as="json"
on-response="handleResponse"
debounce-duration="300"
id="ajaxreq"></iron-ajax>
<template is="dom-repeat" id="accordion" items="{{items}}" as="item">
<div class="accordion" on-click="toggleParentAcc">
<div id="accordion_header" class="accordion__header is-collapsed">
<i class="icon icon--chevron-down"></i>
<span>{{item.name}}</span>
</div>
<div id="standard_accordion_body" class="accordion__body can-collapse">
<div class="accordion__content">
<content id="content"></content>
</div>
</div>
</div>
</template>
</template>
<script>
Polymer({
is: "ab-accordion",
//Properties for the Element
properties: {
accordian: Object,
childaccordions: Object,
// Param passed in from the element - Set if the accordion is open by default.
open: String,
data: String,
reqUrl: {
type: String,
value: "https://example.com/service.aspx"
},
},
ready: function () {
this.items = [];
},
attached: function () {
// Run once the element is attached to the DOM.
},
toggleParentAcc: function (event) { // Toggle the classes of the accordions
//This is where I want to toggle the class
this.$.accordion_header.classList.toggle('is-collapsed');
if (typeof event !== 'undefined') {
event.stopPropagation(); // Stop the click from going up to the parent.
}
},
handleResponse: function (e) {
this.items = e.detail.response.sports;
}
});
</script>
</dom-module>
Basically inside the toggleParentAcc function I want to toggle the class of the div with ID accordion_header. But I just get undefined or null.
I have tried the following two lines:
this.$.accordion_header // #1
this.$$('#accordion_header') // #2
How I access that element inside the dom-repeat?
UPDATE: I can't even access the elements within the when inside the attached function.
attached: function(){
this.$.accordion_header // This is null?!
this.$$('#accordion_header'); // this is also null!
}
https://www.polymer-project.org/1.0/docs/devguide/local-dom.html#node-finding
Note: Nodes created dynamically using data binding (including those in dom-repeat and dom-if templates) are not added to the this.$ hash. The hash includes only statically created local DOM nodes (that is, the nodes defined in the element’s outermost template).
I think it would be better if you'd use Polymer.dom(this.root) instead. Also I'd advice you to not use static IDs in dom-repeat as they are meant to be unique. Use classes instead.
Looks like you might be encountering Event Retargeting which happens when events "bubble" their way up the DOM tree. Read this documentation to learn more.
When I encountered this, I solved it by using something like:
var bar = Polymer.dom(event).path[2].getAttribute('data-foo');
inside my Polymer() function.
To figure it out in your case, you should go to the console and search the DOM tree / event log to locate your target. If you have trouble locating the correct area of the console, post a comment and I might be able to help further.
I eventually figured out a way of doing this without having to select elements in the nested template.
<template id="accord_template" is="dom-repeat" items="{{items}}" as="item">
<ab-accordion-row id="[[item.id]]" name="[[item.name]]" open="[[item.open]]">
</ab-accordion-row>
</template>
ab-accordion is another element, I just feed it the data and I can then change the classes based on the params.
<div id="accordion" class="accordion" on-click="toggleAccordion">
<div class$="{{getClassAccordionHeader(open)}}">
<i class="icon icon--chevron-down"></i>
<span>{{name}}</span>
</div>
<div id="standard_accordion_body" class$="{{getClassAccordionChild(open)}}">
<div class="accordion__content">
<content></content>
</div>
</div>
</div>
try with this.
toggleParentAcc: function (event) { // Toggle the classes of the accordions
//This is where I want to toggle the class
var header = event.target.parentElement;
Polymer.dom(header).classList.toggle('is-collapsed');
// rest of your code
}

Categories

Resources