How to pass an Object to a prop value in VueJS - javascript

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>

Related

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:)

vue.js and slot in attribute

I'm trying to build a vue.js template that implements following:
<MyComponent></MyComponent> generates <div class="a"></div>
<MyComponent>b</MyComponent> generates <div class="a" data-text="b"></div>.
Is such a thing possible?
EDIT
Here is the best I can reach:
props: {
text: {
type: [Boolean, String],
default: false
}
},
and template
<template>
<div :class="classes()" :data-text="text">
<slot v-bind:text="text"></slot>
</div>
</template>
but the binding does not work, text always contains false.
You can use the mounted() method to get text using $slot.default property of the component to get the enclosing text. Create a text field in data and update inside mounted() method like this :
Vue.component('mycomponent', {
data: () => ({
text: ""
}),
template: '<div class="a" :data-text=text></div>',
mounted(){
let slot = this.$slots.default[0];
this.text=slot.text;
}
});
Note: It will only work for text, not for Html tags or components.
You're mixing slots and properties here. You'll have to pass whatever you want to end up as your data-text attribute as a prop to your component.
<MyComponent text="'b'"></MyComponent>
And in your template you can remove the slot
<template>
<div :class="classes()" :data-text="text"></div>
</template>
Another thing: it looks like your binding your classes via a method. This could be done via computed properties, take a look if you're not familiar.
You can try this.
<template>
<div :class="classes()">
<slot name="body" v-bind:text="text" v-if="hasDefaultSlot">
</slot>
</div>
</template>
computed: {
hasDefaultSlot() {
console.log(this)
return this.$scopedSlots.hasOwnProperty("body");
},
}
Calling
<MyComponent>
<template v-slot:body="props">
b
</template>
</MyComponent>

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

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();

VueJS Show element on child component one at a time

So I have several components <server-header>. It has the following HTML:
<span #click="$parent.changeOrder(column, $event)">
<slot></slot>
<i v-show="sortActive" class="order-arrow" :class="sort"></i>
</span>
These components are inserted in another component <server-list>. These will be the headers, that when clicked, will order some lists. My objective is to only show one arrow icon at a time.
E.g.: If I click on the first header, the arrow appears on that one. If I click on the second header, the arrow from the first header hides and the one on the second header appears.
This would be simple to do with jQuery for example, but I'm kind of lost on how to do this with VueJS.
Don't call parent functions directly. That is an anti pattern. Instead use 2-way data binding with sync and this is much easier. Rough example below:
// server-list.vue
data() {
return {
selected: undefined
}
}
<server-header v-for="(header, idx) in headers" :header="header" :selected.sync="selected" :idx="idx"></server-header
Then in the child, we drop #click="$parent.changeOrder(column, $event)" in favor of:
#click="$emit('update:selected', idx)"
Make sure server-header has this prop expected:
props: ['idx', 'header', 'selected']
Then make sure we compare the idx to our header index:
<i v-show="selected === index"
I assume you're rendering the <server-header> components with a v-for directive, looping a server_headers property in your <server-list> component data property.
The way i go with this is adding a prop to the <server-header> component like selected, then I add a selected property to each of the server_headers objects. Then i render the icon (<i>) with v-show if the correspondent selected property is true.
Like this:
server-list component
Vue.component('server-list', {
data: () => ({
server_headers: [{key: value, selected: true}, {key:value, selected:false}]
}),
template: `
<div>
<server-header v-for="header of server_headers" :v-key="somekey" :selected="header.selected"></server_header>
</div>
`
}
Vue.component('server-header', {
props: ['selected'],
template: `
<span #click="$parent.changeOrder(column, $event)">
<slot></slot>
<i v-show="selected" class="order-arrow" :class="sort"></i>
</span>
`
})
Then, if i click on the arrow I would unset all the selected properties, and set the one i clicked. I hope this helps!!!!

Categories

Resources