I've got a Vue/Nuxt project and I'm having a bit of a weird issue.
I have a component that renders a button with various CSS styles and that's all working fine. I've also got some styles that I need to control via props, so I have these bound to a style tag. This all works on the initial load of the page, however, if I navigate to the page using a Nuxt link, or I make changes to the component and HMR reloads it, then the inline styles disappear.
I've tried to narrow this down a bit by including an inline style that comes from a prop, and an inline style that I have just hardcoded. As before, the style coming from the prop isn't rendered, but the hardcoded style is.
I've had a good Google about but can't find anything that suggests I'm doing something wrong.
EDIT: So I've narrowed it down a bit. It's not the props, it's the linear gradient. Even a hardcoded linear gradient doesn't render.
Here's the component snippet.
<template>
<span class="button-link" :class="{ 'button-link--primary': primary }">
<span
class="button-link__gradient-wrapper-one"
:style="{
backgroundImage: `linear-gradient(to left, ${colorStart}, ${colorEnd});`,
color: 'red'
}"
/>
<span
class="button-link__gradient-wrapper-two"
:style="{
backgroundImage: `linear-gradient(to left, ${colorEnd}, ${colorStart});`,
color: 'red'
}"
/>
<button v-if="!href && !to" class="button-link__button">
<slot />
</button>
<a v-if="href" class="button-link__button" :href="href"><slot /></a>
<nuxt-link v-if="to" :to="to" class="button-link__button">
<slot />
</nuxt-link>
</span>
</template>
<script>
export default {
props: {
primary: {
type: Boolean,
default: false
},
colorStart: {
type: String,
default: null
},
colorEnd: {
type: String,
default: null
},
href: {
type: String,
default: null
},
to: {
type: String,
default: null
}
}
};
</script>
Thanks
Try with a computed properties. For example:
<template>
...
<span
class="button-link__gradient-wrapper-one"
:style="gradientStart"
/>
<span
class="button-link__gradient-wrapper-two"
:style="gradientEnd"
/>
...
</template>
computed: {
gradientStart() {
return {
backgroundImage: `linear-gradient(to left, ${this.colorStart}, ${this.colorEnd})`,
color: "red"
};
},
gradientEnd() {
return {
backgroundImage: `linear-gradient(to left, ${this.colorEnd}, ${this.colorStart})`,
color: "red"
}
}
}
I think i figured it out;
Does not work:
backgroundImage: `linear-gradient(to left, ${colorEnd}, ${colorStart});`
Does work:
backgroundImage: `linear-gradient(to left, ${colorEnd}, ${colorStart})`
Please not the ; removed on the last snippet.
Related
I have a BaseMenuItem component that has normal button elements as well as special news elements.
I have added a ticker effect to the news type els and want to stop the ticker on that element when it's clicked. Currently the click event stops the ticker effect on the whole group.
How can I target a single element from that group?
There are two methods, openNews one showing the specific news article that the element is linked to.
And clearItemType that clears the itemType upon recieving the emitted event from the BaseMenuItem component.
I'm just not sure which element to target to change it's itemType.
Does Vuejs have a way to make an unique data value for dynamically generated elements?
If you need anymore information please let me know!
Cheers!
BaseMenuItem
<template>
<q-btn align="left" dense flat class="main-menu-item" v-on="$listeners">
<div class="flex no-wrap items-center full-width">
<iconz v-if="iconz" :name="iconz" type="pop" color="black" class="mr-md" />
<q-icon v-if="menuIcon" :name="menuIcon" class="text-black mr-md" />
<div #click="$emit('stop-ticker')" v-if="itemType === 'news'" class="ellipsis _ticker">
<div class="ellipsis _ticker-item">{{ title }}</div>
</div>
<div v-else>
<div class="ellipsis">{{ title }}</div>
</div>
<slot>
<div class="ml-auto"></div>
<div class="_subtitle mr-md" v-if="subtitle">{{ subtitle }}</div>
<q-icon name="keyboard_arrow_right" class="_right-side" />
<ComingSoon v-if="comingSoonShow" />
</slot>
</div>
</q-btn>
</template>
<style lang="sass" scoped>
// $
.main-menu-item
display: block
font-size: 15px
position: relative
width: 100%
border-bottom: 1px solid #F5F5F5
+py(10px)
._left-side
color: #000000
._subtitle
margin-left: auto
opacity: 0.7
._ticker
position: absolute
font-weight: bold
margin-left: 2em
width: 82%
&-item
display: inline-block
padding-left: 100%
animation: ticker 8s linear infinite
#keyframes ticker
to
transform: translateX(-100%)
</style>
<script>
import { iconz } from 'vue-iconz'
export default {
name: 'MainMenuItem',
components: { iconz },
props: {
comingSoonShow: { type: Boolean, default: false },
title: { type: String, default: 'menu' },
subtitle: { type: String, default: '' },
menuIcon: { type: String, default: '' },
iconz: { type: String, default: '' },
itemType: { type: String, default: '' },
}
}
</script>
MainMenuPage
<template>
<div class="eachMenuGroup" v-if="newsList.length">
<MainMenuItem
v-for="news in newsList"
:key="news.id"
#click="openNews(news)"
:title="news.title"
:itemType="itemType"
:class="{ readLink: readNewsList[news.id] }"
menuIcon="contactless"
#stop-ticker="clearItemType"
></MainMenuItem>
</div>
</template>
<style lang="sass" scoped>
.readLink
font-weight: 500
</style>
<script>
methods: {
openNews(postInfo) {
dbAuthUser().merge({ seenNewsPosts: { [postInfo.id]: true } })
Browser.open({ url: postInfo.url, presentationStyle: 'popover' })
},
clearItemType() {
this.itemType = ''
return
},
</script>
EDIT: I edited since your latest comment. See below
To target the exact element within your v-for that fired the event, you can use $refs by using an index :
<MainMenuItem v-for="(item, index) in items" :ref="`menuItem--${index}`" #stop-ticker="clearItemType(index)" />
In your method:
clearItemType(index){
console.log(this.$refs[`menuItem--${index}`])
// this is the DOM el that fired the event
}
Edited version after your comments:
If you pass the exact same prop to each el of your v-for you wont be able to modify its value just for one child in the parent context.
Instead, keep an array of distinctive values in your parent. Each child will receive one specific prop that you can change accordingly in the parent either by listening to an child emitted event or by passing index as click event as I've done below for simplicity.
See snippet below
Vue.config.productionTip = false;
const Item = {
name: 'Item',
props: ['name', 'isActive'],
template: `<div>I am clicked {{ isActive }}!</div>`,
};
const App = new Vue({
el: '#root',
components: {
Item
},
data: {
items: ["item1", "item2"],
activeItemTypes: []
},
methods: {
toggle(index) {
this.activeItemTypes = this.activeItemTypes.includes(index) ? this.activeItemTypes.filter(i => i !== index) : [...this.activeItemTypes, index]
}
},
template: `
<div>
<Item v-for="(item, i) in items" :name="item" #click.native="toggle(i)" :isActive="activeItemTypes.includes(i)"/>
</div>
`,
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="root"></div>
I have a MainMenuItem that has a bunch of static titles. There is one variable item that is for news and events and that has a horizontal ticker, scroll animation on it. This animation is happening inside of the MainMenuItem component, but the click event will be happening from the parent. The click event currently needs to stop the animation and change the font weight (can be seen in the readLink class).
Wth Vue I cannot mutate the prop type itself with something like #click="itemType = ''".
So how can I turn this ticker animation off on click?
Basically, all I would need to do is make the MainMenuItem's itemType = '' and make it the same as every other item. Do I need to make a data type? Or a click event that $emit's an event to the child/base component MainMenuItem?
If there is anymore code that is necessary please let me know!
Cheers!
MainMenuItem.vue (base component with the ticker animation )
<template>
<q-btn align="left" dense flat class="main-menu-item" v-on="$listeners">
<div class="flex no-wrap items-center full-width">
<iconz v-if="iconz" :name="iconz" type="pop" color="black" class="mr-md" />
<q-icon :name="menuIcon" />
<div v-if="itemType === 'news'" class="_ticker">
<div class="ellipsis _ticker-item">{{ title }}</div>
</div>
<div v-else>
<div class="ellipsis">{{ title }}</div>
</div>
<q-icon name="keyboard_arrow_right" class="_right-side" />
</div>
</q-btn>
</template>
<style lang="sass" scoped>
// $
.main-menu-item
display: block
font-size: 15px
position: relative
width: 100%
border-bottom: 1px solid #F5F5F5
+py(10px)
._left-side
color: #000000
._ticker
font-weight: bold
margin-left: 2em
width: 83%
white-space: nowrap
overflow: hidden
position: absolute
&-item
display: inline-block
padding-left: 100%
animation: ticker 8s linear infinite
#keyframes ticker
to
transform: translateX(-100%)
</style>
<script>
import { iconz } from 'vue-iconz'
export default {
name: 'MainMenuItem',
components: { iconz },
props: {
comingSoonShow: { type: Boolean, default: false },
title: { type: String, default: 'menu' },
subtitle: { type: String, default: '' },
menuIcon: { type: String, default: '' },
iconz: { type: String, default: '' },
itemType: { type: String, default: '' },
}
}
</script>
MainMenu.vue (just the location I am using the base component)
<template>
<MainMenuItem
v-for="news in newsList"
:key="news.id"
#click="openNews(news)"
:title="news.title"
itemType="news"
:class="{ readLink: readNewsList[news.id] }"
menuIcon="contactless"
></MainMenuItem>
</template>
export default {
name: 'MainMenu',
methods: {
openNews(postInfo) {
dbAuthUser().merge({ seenNewsPosts: { [postInfo.id]: true } })
Browser.open({ url: postInfo.url, presentationStyle: 'popover' })
}
},
computed: {
readNewsList() {
return this.authUserInfo?.seenNewsPosts || {}
},
}
<style lang="sass" scoped>
.readLink
font-weight: 500
</style>
Since it's a prop the simplest way is to emit an event that executes what you need in the parent and also bind a variable not static prop to be able to change it. example in the child :
<button #click="onClickButton">stop animation </button>
export default {
methods: {
onClickButton (event) {
this.$emit('stopAnimation')
}
}
}
And in the parent you listen to it as so:
<MainMenuItem
v-for="news in newsList"
:key="news.id"
#click="openNews(news)"
:title="news.title"
:itemType="itemType"
:class="{ readLink: readNewsList[news.id] }"
menuIcon="contactless"
v-on:stopAnimation="itemType = ''"
></MainMenuItem>
export default {
data() {
return {
itemType: "news",
}
}
}
}
Not sure how to work this...
I'm displaying table rows, pulling my data with vuex. The style changes, but is not updated in the browser. Only when I reload or re-create the component, it shows the style change. Scratching my head on this and what would be the best way to set a reactive style that is ternary based off the data loaded in the v-for component ?
<tr #click="rowClick(item)" v-for="(item, index) in List" :style="[item.completed ? { 'background-color': 'red' } : { 'background-color': 'blue' }]" :key="index">
item.completed is a bool
I hope I was able to correctly reflect what you were trying to accomplish. I used a v-for on the template tag and after that line you could access the item.completed boolean value. I had to use a span element inside the tr to apply the styling.
Vue.createApp({
data() {
return {
List: [ // comes from your vuex
{
name: 'one',
completed: true,
},
{
name: 'two',
completed: false,
}
]
}
},
methods: {
rowClick(item) {
console.log(item)
}
}
}).mount('#demo')
<script src="https://unpkg.com/vue#next"></script>
<table id="demo">
<template v-for="(item, i) in List">
<tr>
<span #click="rowClick(item)" :style="item.completed ? { 'background-color': 'red' } : { 'background-color': 'blue' }">{{ i }}: {{ item.name }}</span>
</tr>
</template>
</table>
I think it could be solved a little bit cleaner. What do you think? Does it goes in the right direction?
See https://jsfiddle.net/d017am59/1/
I have a checkbox controling whether box elements should show text in black.
<div id="app">
<label><input type='checkbox' v-model='showBlack' />Show black</label>
<box>Hello</box>
<box>World</box>
<box>Hi</box>
<box>Bye</box>
</div>
Vue.component('box', {
template: `
<div v-bind:style='styleObject'>
{{text}}
<slot></slot>
</div>`,
data: function() {
return {
text: vueApp.showBlack?'black text: ':'white text: ',
styleObject:{
color: vueApp.showBlack?'black':'white',
'background-color':vueApp.showBlack?'white':'black',
}
}
}
});
const vueApp= new Vue({
el: '#app',
data: {
showBlack: true,
},
});
box is a complicated component, but I only present the relavent props here. For illustration, if the checkbox is checked, the color is black, the background is white; if the checkbox is unchecked, the color and the background color is reversed.
My code doesn't work because Vue throws an error "Cannot access 'vueApp' before initialization."
How do my box elements listen to the checkbox?
I don't like using a global mixin so much because a global mixin is going to inject to all components, where I only want to inject to box.
I don't like adding a prop to box, and pass showBlack to the prop of all box instances. My business requirement is ALL boxes must obey the checkbox, and it is cumbersome and error-prone if I have to write:
<box v-bind:showBlack="showBlack">Hello</box>
<box v-bind:showBlack="showBlack">Wolrd</box>
<box v-bind:showBlack="showBlack">Hi</box>
<box v-bind:showBlack="showBlack">Bye</box>
I'm fine to use Vue 2 or 3.
A quick solution is to use this.$root to access the data of the root instance (the one that instantiates Vue). Also, note the bindings in <box> should be to computed props so that they're reactive to changes in the root's showBlack:
Vue.component('box', {
template: `
<div v-bind:style='styleObject'>
{{ text }}
<slot></slot>
</div>`,
computed: {
text() {
return this.$root.showBlack ? 'black text: ' : 'white text: '
},
styleObject() {
return {
color: this.$root.showBlack ? 'black' : 'white',
'background-color': this.$root.showBlack ? 'white' : 'black',
}
}
}
})
demo
HTML:
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div><label><input type='checkbox' v-model='showBlack' />Show black</label></div>
<box :black="showBlack">Hello</box>
</div>
JS:
Vue.component('box', {
template: `
<div v-bind:style='styleObject'>
<slot></slot>
</div>
`,
props: ['black'],
data: function() {
return {}
},
computed: {
styleObject() {
return {
color: this.black ? 'black' : 'white',
'background-color': this.black ? 'white' : 'black',
}
}
}
});
const vueApp= new Vue({
el: '#app',
data: {
showBlack: true,
},
});
I'm faced with an issue where my semantic drop down in my vue project won't activate when clicking on the arrow icon but works when I click on the rest of the element. The drop down also works when I set the dropdown to activate on hover, but just not on click. Solutions I've tried:
tested if the dynamic id are at fault
tested if the back ticks are confusing things
placed the values directly into the semantic drop down
Aside from the dropdown not activating, the code below works as intended and brings back the selected value to the parent component and can be displayed.
Dropdown.vue:
<template>
<div class="ui selection dropdown" :id="`drop_${dropDownId}`">
<input type="hidden" name="gender" v-model="selected">
<i class="dropdown icon"></i>
<div class="default text">Gender</div>
<div class="menu">
<div class="item" v-for="option in options" v-bind:data-value="option.value">
{{ option.text }}
</div>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
selected: {}
}
},
watch: {
selected: function (){
this.$emit("dropDownChanged", this.selected)
}
},
props: {
options: Array, //[{text, value}]
dropDownId: String
},
mounted () {
let vm = this;
$(`#drop_${vm.dropDownId}`).dropdown({
onChange: function (value, text, $selectedItem) {
vm.selected = value;
},
forceSelection: false,
selectOnKeydown: false,
showOnFocus: false,
on: "click"
});
}
}
</script>
The component usage:
<vue-drop-down :options="dropDownOptions" dropDownId="drop1" #dropDownChanged="dropDownSelectedValue = $event"></vue-drop-down>
The data in the parent:
dropDownOptions: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
],
dropDownSelectedValue: ""
Here is a fiddle of the above but simplified to use a flatter project. However the problem doesn't reproduce :(
https://jsfiddle.net/eywraw8t/210520/
I'm not sure what is causing your issue (as the examples on the Semantic Ui website look similar), but there is a workaround. For you arrow icon:
<i #click="toggleDropDownVisibility" class="dropdown icon"></i>
And then in the methods section of your Vue component:
methods: {
toggleDropDownVisibility () {
$(`#drop_${this.dropDownId}`)
.dropdown('toggle');
}
},