Jump to position on click (access class from other Vue component) - javascript

Explanation of problem
If a user clicks on the login link the view shall jump down to the login window where a user can type in userdata.
I am aware how to do this within a single file using document.getElementById('login-window').scrollIntoView()
However, I have a project with various single Vue.js component files. The login-link is within one "label" component. But the actual login-window is located in another component called "loginWindow", thus also the id / class "login-window" is stored in "loginWindow".
I tried to grab the "login-window" element with getElementById within my "label" component, but I believe it cannot access it since it is in another component.
This is the template code from "loginWindow"
<template>
<LoginGrid class="login-window" :as-grid="true" :class="classes" autocomplete="off">
<OCard class="login-card" :border="false">
<div class="login-headline f-copy f-bold l-color-primary">{{ t('headline') }}</div>
<!-- online state -->
<template v-if="isLogged">
<OButton color="secondary" #click="onClickLogout">{{ t('logout-label') }}</OButton>
</template>
<!-- offline state -->
<template v-else>
<div class="login-inputs">
<LoginInput
class="login-input-card-number" />
...
</div>
...
<OLink
type="secondary"
class="login-mode-link f-regular f-copy-small"
:no-router="true"
#click="onSwitchMode"
>
{{ modeLabel }}
</OLink>
...
</template>
</OCard>
</LoginGrid>
</template>
Here is what I've tried exactly
Within my "label" component I have implemented this method with an else-statement:
export default {
name: 'loginWindow',
...
methods: {
onClick() {
if (this.isLogged) {
...
} else {
if (isBrowser) {
document.getElementById("login-window").scrollIntoView();
}
}
},
},
}
So if the user is not logged-in, then onClick() it should scroll to the id of "login-window".
However, this solution does not work. I get an error saying "Cannot read property 'scrollIntoView' of null".
Any ideas how to do this with JavaScript within a Vue.js component?

login-window is Class not ID in your HTML. Try this:
document.querySelector(".login-window").scrollIntoView();

Related

Vue JS emit event to child component from parent component not triggering

I'm working in a Nuxt JS project. I have several components, and one of my components called ComplianceOptoutRawText includes a child component called ComplianceOptoutViaExternalService, and it's this child component where I'm using a v-on to listen for an $emit event from the ComplianceOptoutRawText so I can toggle a variable, but I'm not seeing it at all in my console logs and need to know what I'm doing wrong and need to change.
Here's my markup with everything that's appropriate:
ComplianceOptoutRawText
<template>
<div>
<div>
<div class="tw-hidden md:tw-block tw-font-bold tw-mb-4">
<ComplianceOptoutViaExternalService>
<template #trigger>
<button #click="$emit('shown-optout-intent', true)" type="button">here</button>
</template>
</ComplianceOptoutViaExternalService>
</div>
</div>
</div>
</template>
And then the child component itself:
ComplianceOptoutViaExternalService
<template>
<div>
<slot name="trigger"></slot>
<article v-show="wantsToOptOut" v-on:shown-optout-intent="hasShownOptoutIntent" class="tw-bg-white tw-p-4 md:tw-p-6 tw-rounded-xl tw-text-gray-800 tw-border tw-border-gray-300 tw-text-left tw-mt-4">
<validation-observer v-if="!didOptOut" ref="optoutServiceForm" key="optoutServiceForm" v-slot="{ handleSubmit }" tag="section">
<form>
...
</form>
</validation-observer>
</article>
</div>
</template>
<script>
export default {
data () {
return {
wantsToOptOut: false,
isOptingOut: false,
didOptOut: false
}
},
methods: {
/*
** User has shown opt out intent
*/
hasShownOptoutIntent (value) {
this.wantsToOptOut = !this.wantsToOptOut
}
}
}
</script>
Note that my child component uses a slot, this is so I can position everything as needed in the parent component, but at it's core, I have a parent component emitting a value and then listening for it via v-on:shown-optout-intent="hasShownOptoutIntent" which runs the hasShownOptoutIntent method.
But I never see anything from the button, even if I console.log this. What am I missing?
I'm doing something similar with an embedded component, so it's perhaps worth trying:
<button #click="$emit('update:shown-optout-intent', true)" type="button">here</button>
... and then:
v-on:shown-optout-intent.sync="hasShownOptoutIntent"
As an aside, it's safe to remove v-on and use: :shown-optout-intent.

Interpolation in a Vue component that creates HTML

I am building a web app using Vue 2.6.x. In the prototype that was created we have a line like this repeated many times:
<label class="title">On Time</label><span class="score float-right">Score: {{ score.ap_ontime }}
The only part of this whole line that changes is the data within the {{ }}. I would like to turn this into a component that I can call many times. For example:
<MyLabel title="On Time" score="score.ap_ontime" />
...so that I don't have to type this score interpolation over and over in a long HTML file. I know how pass props to the component and add text to the template of the component:
<template>
...
<label class="title">{{title}}</label>
...
</template>
..but what I can't figure out is how to take a string (e.g.score.ap_ontime) and have the template inject something that will render that value from a score plain old JavaScript object that exists in the application and is imported into the template. The original code I show at the top of this post renders fine and reacts to changes in the JavaScript object I just can't figure out how to do this in a component that creates HTML and Vue template syntax based on props.
Your new component should look something like this:
<template>
<label class="title">{{ caption }}</label>
<span class="score float-right">Score: {{ onTime }}</span>
</template>
<script>
export default
{
name: 'MyNewComponent',
props:
{
caption:
{
type: String,
default: ''
},
onTime:
{
type: [String, Number],
default: 0
}
}
}
</script>
Then you can call your component in this way:
<my-new-component :caption="Big title" :on-time="score.ap_ontime" />

Linking a text field in a child component to a parent component's props in VueJS

I have a child component sending data via an event to a parent component in VueJS. From the parent component, I am routing the data (or trying to route the data...) to a sibling of the child and create new components with the data sent from the child.
I use a dictionary to group the data for various reasons, then push the dictionary into an array. A v-for loop loops thru the array and populates the previously mentioned new components with data found in that array. I probably don't need to do it this way, but that's how I'm doing it. I am open to alternatives.
Anyway, it doesn't work great. So far I'm only able to get one of the three strings I need to show up where I want it to. I'll explain more after I post the code.
Already tried:
A dozen different versions of the code, including creating a simple v-for in a list to do the job, and various versions with/without a dictionary or array.
In my research for the problem I've gone through the VueJS docs, Googled a few things, and found nothing.
In App.vue (I tried to remove all the irrelevant stuff):
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<TweetDeck v-on:messageFromTweetDeck="msgReceived($event)"/>
<!-- <ul>
<li v-for="(tweet, index) in tweets" :key="index">{{ tweet }}</li>
</ul>-->
<TwitterMsg v-for="(tweet, index) in tweets" :key="index"
:name="tweet.name" :handle="tweet.handle" tsp=3 :msg="tweet.tweet" />
<TwitterMsg name="aaa" handle='aaa'
tsp=50 msg="hey this is a message on twitter"/>
<input type="text" v-model="placeholderText"/>
</div>
</template>
<script>
import TwitterMsg from './components/TwitterMsg.vue'
import TweetDeck from './components/TweetDeck.vue'
export default {
name: 'app',
components: {
TwitterMsg,
TweetDeck
},
data: function() {
return {
tweets: [],
message: "",
placeholderText: ""
}
},
methods: {
msgReceived(theTweet, name, handle) {
this.tweets.push({tweet: theTweet, name: name, handle: handle})
}
}
}
</script>
And in TweetDeck.vue:
<template>
<div>
<input type='text' v-model="yourName">
<input type='text' v-model="yourHandle">
<input type='text' v-model="yourTweet"/>
<button type='button' #click="sendTweet()">Tweet</button>
</div>
</template>
<script>
export default {
name: "TweetDeck",
data: function() {
return {
yourName: "Your name here",
yourHandle: "Your twitter handle",
yourTweet: "What's going on?"
}
},
methods: {
sendTweet() {
this.$emit('messageFromTweetDeck', this.yourTweet, this.yourName, this.yourHandle);
}
}
}
</script>
You can also see the mostly unimportant TwitterMsg.vue here (I am trying to copy Twitter for learning purposes:
<template>
<div>
<h4>{{ name }}</h4>
<span>#{{ handle }}</span>
<span> {{ tsp }}</span> <!-- Time Since Posting = tsp -->
<span>{{ msg }}</span>
<img src='../assets/twit_reply.png'/><span>1</span>
<img src="../assets/twit_retweet.png"/><span>2</span>
<img src="../assets/twit_fave.png"/><span>3</span>
</div>
</template>
<script>
export default {
name: "TwitterMsg",
props: {
name: String,
handle: String,
tsp: String,
msg: String
}
}
</script>
<style>
img {
width: 30px;
height: 30px;
}
</style>
Expected result:
The code populates a new TwitterMsg component with appropriate name, handle and message data each time I click the "Tweet" button.
Actual results:
My code fails to help the name and handle strings make it from the input text box in TweetDeck.vue all the way to their home in TwitterMsg.vue.
I will say that this.yourTweet in TweetDeck.vue DOES manage to make it all the way to its destination, which is good -- though it makes me wonder why the other two pieces of data didn't follow suite.
Totally lost. Also just in my first month of VueJS so it's pretty good that I can even make one string appear where I want it to. \o/
First, you need to remove the $event parameter
<TweetDeck v-on:messageFromTweetDeck="msgReceived"/>
Second, you can optimize the data format passed to the parent component:
sendTweet() {
this.$emit("messageFromTweetDeck",
{ tweet: this.yourTweet, name: this.yourName, handle: this.yourHandle }
);
}
And then modify your msgReceived method:
msgReceived(childData) {
this.tweets.push(childData);
}
Link: codesandbox
Hope to help you:)

How to pass an Object to a prop value in VueJS

I am turning some of my components into re-usable components. I am running into some issues here that I can't figure out. Coming from a React environment, my thoughts are getting jammed up. Basically, I need to be able to make a prop more versatile than just a Boolean or String, or any primitive value. I need to be able to pass "content" to it that could change from page to page depending on what is used for
For example, I have this stateless component:
<template>
<div class="cts-split-grid cts-alt-header">
<div>{{title}}</div>
<div v-if="rightSide" class="cts-split-grid">
<span class="uk-text-small">Pod or station is open</span>
<span class="legend-color"></span>
</div>
</div>
</template>
<script>
export default {
name: "page-alt-header",
props: {
title: {
type: String
},
rightSide: {
type: Boolean
}
},
data() {
return {
value: ""
};
}
};
</script>
That I am using this way
<AltHeader :title="'POD' + currentPodId" rightSide />
As you can see, in the title I am passing an object currentPodId bounded to the component. That was easy since that object only produces a data value.
I want to remove this(below) from the re-usable component and be able to add it in the component using the AltHeader as a rightSide Prop:
<span class="uk-text-small">Pod or station is open</span>
<span class="legend-color"></span>
The reason why is because this component's right side can be anything from an Icon component to a button, to a small block of HTML, etc.
How can I do this? How can I set up rightSide prop to accept anything I pass to it at the component level depending on how I need to use it?
Thanks
You should use slots
<template>
<div class="cts-split-grid cts-alt-header">
<div>{{title}}</div>
<div v-if="rightSide" class="cts-split-grid">
<slot></slot>
</div>
</div>
</template>
and add right Side content as follows :
<AltHeader :title="'POD' + currentPodId" rightSide >
<!-- side right content here -->
</AltHeader>

v-on:click not working bootstrap

Initially I thought this was an issue with how I was using the #click directive according to this question. I added the .native to the directive and my method is still not getting invoked.
I know this is bootstrap as if I use a normal <button> then the method is invoked as expected.
There are no errors in the logs so it is just as if the element is not registering the directive?
UpcomingBirthdays.vue
<template>
<div>
<h1>{{ section_title }}</h1>
<b-card style="max-width: 20rem;"
v-for="birthday in birthdays.birthdays"
:key="birthday.name"
:title="birthday.name"
:sub-title="birthday.birthday">
<b-button href="#"
#click.native="toWatch(birthday, $event)"
variant="primary">Watch
</b-button>
</b-card>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "UpcomingBirthdays",
data: () => {
return {
section_title: "Upcoming Birthdays",
};
},
methods: {
toWatch: (birthday, event) => {
event.preventDefault();
console.log("watched called");
console.log(birthday.name);
console.log(`BEFORE: ${birthday.watch}`);
birthday.watch = !birthday.watch;
console.log(`AFTER: ${birthday.watch}`);
}
},
computed: mapState([
"birthdays",
]),
};
</script>
<style>
</style>
EDIT
Worth mentioning that when using HTML5 <button>, I do not have to append the .native property to the #click directive.
EDIT 2
Here is my codesandbox I created to replicate this error. I would expect an error here to say BirthdaysApi is not defined but I am not getting anything when the button is clicked.
Just remove the href="#" from your buttons (this makes the Bootstrap b-button component render your buttons as anchors) and it's working as expected:
https://codesandbox.io/s/w0yj3vwll7
Edit:
Apparently this is intentional behaviour from the authors, a decision I disagree upon. What they are doing is apparently executing event.stopImmediatePropagation() so any additional listener isn't triggered.
https://github.com/bootstrap-vue/bootstrap-vue/issues/1146

Categories

Resources