vue3 setting css styles from vuex store - javascript

Within vue3 is it possible to bind a value from the vuex store to a component style? for example, i have a color saved within the store as backgroundColor and would like to do something like below:
Store:
store = {
state: {
branding: {
color: '#f00'
}
}
}
component script
export default {
name: 'component',
components: {
Header,
Footer
},
branding() {
return this.$store.state.branding.color;
},
}
component style
.page{
background-color: v-bind(branding.colour);
}
i have seen an example of this working:
export default {
name: 'component',
components: {
Header,
Footer
},
data(){
return {
color: '#f00'
}
},
}
.page{
background-color: v-bind(colour);
}
but i need to grab the data from the store. im pretty new to vue but not too sure on how to resolve this.

UPDATE 6 April 2022
Apparently I was wrong and in Vue 3 it is possible to use v-bind inside CSS as shown in the documentation. So it should be possible to use Vuex getter or local computed property.
You can not access JavaScript variables from CSS. You should either set inline style to your HTML tag(s) or define the color as CSS variable and then access it from other CSS rules.
<template>
<div :style="{backgroundColor: brandingColor}">test</div>
</template>
<script>
export default
{
computed:
{
brandingColor()
{
return this.$store.state.branding.color;
}
}
}
</script>
OR
// main.js
new Vue({
created()
{
const style = document.documentElement.style;
const theme = this.$store.state.branding;
for (const key in theme)
{
style.setProperty('--branding-' + key, theme[key]);
}
}
});
MyComponent.vue:
<template>
<div class="page">text</div>
</template>
<style>
.page
{
background-color: var(--branding-color);
}
</style>

Related

Vue styling is not appearing properly inside slot when used scoped with style tags in Vue 3

actually I am a bit new to Vue JS and currently working with Vue 3.
I am making use of Oruga library to make components in Vue 3. Now I am making use of storybook to make different components in Vue 3.
The vue file code in it is written as
<template>
<o-radio v-bind="$props" v-model="model">
<slot />
</o-radio>
</template>
<script lang="ts">
import { defineComponent } from "#vue/runtime-core";
import Radio from '../mixins/radio-mixins';
export default defineComponent({
name: "BaseRadio",
computed: {
model: {
get() {
return this.$props.nativeValue;
},
set(value: any) {
this.$emit("input", value);
},
},
},
emits: ["input"],
props: {
...Radio.props,
},
});
</script>
<style scoped>
.b-radio.radio.is-primary .check:checked {
border-color: var(--color-blue);
}
.b-radio.radio.is-primary .check:checked:focus {
box-shadow: 0 0 3px 3px var(--color-light-blue);
}
.b-radio.radio.is-primary .check:before {
background: var(--color-blue);
}
</style>
Here in <style></style> tags I am modifying the element classes provided by oruga library to achieve the desired styling.
The main problem is that while applying scoped in the styling does not apply any styles into the rendered view, but omitting scoped from <style></style tag does.
How can I fix this I need to apply these styles along with using the scoped tag in <style></style ?.
Maybe it's because you are trying to change classes that only exist in the DOM itself because the Oruga thing that you are using, so when you omit the "scoped" property, you are able to manipulate oruga classes globally. These oruga classes doesn't exist in your component itself when you're working with the component

How to access Highcharts stock tooltip data in Vue?

I'm using Highcharts stock in Vue, what I want to implement is when I hover one stock point, the tooltip shows up, and at the same time, at other places may be top of the stock chart, as same as data in tooltip in other styles. My way is to export data in the tooltip formatter function, but in the formatter function this doesn't refer to Vue instance. I wonder if there any way I can access hovering tooltip data, or if there any other way can realize the effect.
the effect i want to realize
<template>
<div>
<div class="outerTooltip">open: xxx</div>
<highcharts :constructor-type="'stockChart'" :options="stockOptions"></highcharts>
</div>
</template>
<script>
import { Chart } from "highcharts-vue";
import Highcharts from "highcharts";
import stockInit from "highcharts/modules/stock";
import { data } from "./stockData";
stockInit(Highcharts);
export default {
name: "StockVue",
computed: {
stockOptions() {
return {
...,
tooltip: {
shared: true,
formatter(){
console.log(this)
// how can i export data in this to Vue, so i can show it in outerTooltip dynamically
retuen `
open: ${this.points[0].point.open}
`
}
}
}
}
}
}
codesandbox example
I think that you only need to assign the reference of the Vue instance to a variable and then use it in formatter function. Remember also to add a reference to the computed property.
Demo: https://codesandbox.io/s/highcharts-vue-test-forked-c7pvw?file=/src/components/StockVue.vue
Assign the reference of the Vue instance to a variable that worked for me.
export default {
name: "StockVue",
method: {
exportTooltipData(data){
console.log(data)
}
},
computed: {
stockOptions() {
const exportFn = this.exportTooltipData;
return {
...,
tooltip: {
shared: true,
formatter(){
exportFn(this);
retuen `
open: ${this.points[0].point.open}
`
}
}
}
}
}
}

Vue - Apply "v-model" on user-defined component which use ace-editor

Code
CodeEditor.vue:
<template>
<div class="ace-container">
<div class="ace-editor" ref="ace"></div>
</div>
</template>
<script>
import ace from 'ace-builds'
import 'ace-builds/webpack-resolver'
import 'ace-builds/src-noconflict/theme-monokai'
import 'ace-builds/src-noconflict/mode-javascript'
export default {
mounted() {
this.aceEditor = ace.edit(this.$refs.ace, {
maxLines: 60,
minLines: 10,
fontSize: 14,
theme: this.themePath,
mode: this.modePath,
tabSize: 4
})
},
data() {
return {
aceEditor: null,
themePath: 'ace/theme/monokai',
modePath: 'ace/mode/javascript'
}
},
methods: {
setCode(code) {
this.aceEditor.setValue(code);
},
getCode() {
return this.aceEditor.getValue();
},
}
}
</script>
<style>
.ace-editor {
width: 600px;
height: 600px;
}
</style>
QuizExecution.vue: (partly)
<template>
<v-app height="100%">
<div id="qz-wrapper">
<!--
<v-textarea id="programmingText" v-model="answerData[question.id]"
#change="saveAnswer(qe.id, question.id)" label="Code" outlined></v-textarea>
-->
<CodeEditor id="programmingText" v-model="answerData[question.id]"
#change="saveAnswer(qe.id, question.id)"></CodeEditor>
</div>
</v-app>
</template>
<script>
import Vue from 'vue'
import CodeEditor from "./CodeEditor";
export default {
components: {CodeEditor},
data() {
return {
// ..
}
}
}
</script>
<style scoped>
</style>
Description
With vuetify's <v-textarea>, I can use v-model to bind its content to a data property, in bi-direction dynamically, so that could init on load, and save on change with a #change property.
Then I want to replace the input area with ace-editor, which support features like syntax highlight.
So, I have defined a component as in CodeEditor.vue, then import & use it in QuizExecution.vue.
But, the v-model and #change won't work on the <CodeEditor> tag.
Questions
How to apply v-model and #click on this <CodeEditor> with in QuizExecution.vue.
Aka. init it with data from container component, and retrieve its content on change and trigger an event to save.
Or, is there anyway to achieve the same result: init on creation & save on change.
You can use props and watch the change events with #update_question_id;
<CodeEditor id="programmingText" :question_id="answerData[question.id]"
#update_question_id="answerData[question.id]=#event"
></CodeEditor>
....
watch:{
answerData(){
saveAnswer(this.qe.id, this.question.id)
}
}
CodeEditor.vue:
You can get the question_id value with props. I think it would be string or number.
And also watch question_id then use $emit to send change $event to main component.
export default {
props:{
question_id: [String,Number]
},
watch:{
question_id(val){
this.$emit("update_question_id",val)
}
}
.....

How to use in imported function in template?

I currently run into the issue that my imported function is not referenced during render. It's easier explained by this code:
<template>
<div v-for="(addOn, index) in JSON.parse(this.restaurants[0].categories[categoryId].addons)">
<label>
<span>{{ addOn.name }} (+ ${{ addZeroes(addOn.price) }})</span>
</label>
</div>
</template>
<script>
import { addZeroes } from "../../../js/helpers";
export default {
data() {
return {
// populated via AJAX
restaurants: [
{
categories: []
}
],
}
},
}
</script>
<style>
</style>
and the error is:
[Vue warn]: Property or method "addZeroes" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
What's the proper way to call a helper function inside a Vue template?
Thanks for any hints!
You could add it to your component:
import { addZeroes } from "../../../js/helpers";
export default {
data() {
return {
// populated via AJAX
restaurants: [
{
categories: []
}
],
}
},
methods: {
addZeroes // shorthand for addZeroes: addZeroes
}
}

Changing body styles in vue router

I'm using Vue router with two pages:
let routes = [
{
path: '/',
component: require('./components/HomeView.vue')
},
{
path: '/intro',
component: require('./components/IntroView.vue')
}
]
This works fine, except that each of my components has different body styling:
HomeView.vue:
<template>
<p>This is the home page!</p>
</template>
<script>
export default {
}
</script>
<style>
body {
background: red;
}
</style>
IntroView.vue:
<template>
<div>
<h1>Introduction</h1>
</div>
</template>
<script>
export default {
}
</script>
<style>
body {
background: pink;
}
</style>
My goal is to have these two pages have different background styles (eventually with a transition between them). But at the moment when I go to the home route (with the red background), then click the intro route, the background colour stays red (I want it to change to pink).
Edit:
index.html:
<body>
<div id="app">
<router-link to="/" exact>Home</router-link>
<router-link to="/intro">Introduction</router-link>
<router-view></router-view>
</div>
<script src="/dist/build.js"></script>
</body>
I got it working with the lifecycle hook beforeCreate and a global stylesheet. In global.css:
body.home {
background: red;
}
body.intro {
background: pink;
}
In the <script> section of HomeView.vue:
export default {
beforeCreate: function() {
document.body.className = 'home';
}
}
And similar in IntroView.vue.
watch: {
$route: {
handler (to, from) {
const body = document.getElementsByTagName('body')[0];
if (from !== undefined) {
body.classList.remove('page--' + from.name.toLowerCase());
}
body.classList.add('page--' + to.name.toLowerCase());
},
immediate: true,
}
},
Another fairly simple solution, add it to your base App.vue file. The to.name can be replaced with to.meta.class or similar for something more specific. This is a nice do it once and it works forever type solution though.
If the class is view specific, may be this will help
methods: {
toggleBodyClass(addRemoveClass, className) {
const el = document.body;
if (addRemoveClass === 'addClass') {
el.classList.add(className);
} else {
el.classList.remove(className);
}
},
},
mounted() {
this.toggleBodyClass('addClass', 'mb-0');
},
destroyed() {
this.toggleBodyClass('removeClass', 'mb-0');
},
Move the methods section to a mixin and then the code can be DRY.
Alternatively you can use this
vue-body-class NPM
vue-body-class GitHub
It allows to control your page body classes with vue-router.
Wrote this when faced the similar issue.
It also refers to Add a class to body when component is clicked?
I ran into an issue when I wanted to modify the styles of the html and body tags along with the #app container on specific routes and what I found out is that for various reasons, this can be quite complicated.
After reading through:
#Saurabh's answer on another relative question: https://stackoverflow.com/a/42336509/2110294
#Mteuahasan's comment above regarding Evan You's suggestion
#GluePear's / OP's answer to this question: https://stackoverflow.com/a/44544595/2110294
Sass style inclusion headaches: https://github.com/vuejs/vue-loader/issues/110#issuecomment-167376086
In your App.vue (could be considered as the centralised state):
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'my-app',
methods: {
handleStyles () {
// Red style to the body tag for the home page
if (['/'].includes(this.$route.path)) document.body.className = 'bg-red'
// Pink style to the body tag for all other pages
else if (document.body.classList.contains('bg-red')) document.body.className = 'bg-pink'
}
},
// Handle styles when the app is initially loaded
mounted () {
this.handleStyles()
},
// Handle styles when the route changes
watch: {
'$route' () {
this.handleStyles()
}
}
}
</script>
<style>
.bg-red {
background: red;
}
.bg-pink {
background: pink;
}
</style>
So for the route / you get the red style and for all other routes the pink style is applied.
The handleStyles logic could have been dealt with by the beforeCreated hook however in my case, this would only affect the html and body styles but the #app element where the router view is rendered into would only available when the dom has been mounted so I think that it is a slightly more extensible solution.
Top answer is right, but need some optimization.
Because that answer doesn't work when one refreshes that page. Reason is that dom is not loaded done when set the style you want.
So, better solution is this:
beforeCreate() {
this.$nextTick(() => {
document.querySelector('body').style.backgroundColor = '#f2f2f2'
})
},
beforeDestroy() {
document.querySelector('body').style.backgroundColor = ''
},
By wrapping style setting handler in this.$nextTick, the style will be set when dom is loaded. So you can get correct styles when refresh page
You can also do it directly in the router file using the afterEach hook:
mainRouter.afterEach((to) => {
if (["dialogs", "snippets"].includes(to.name)) {
document.body.style.backgroundColor = "#F7F7F7";
// or document.body.classList.add(className);
} else {
document.body.style.backgroundColor = "#FFFFFF";
// or document.body.classList.remove(className);
}
});
afterEach hook documentation
to is a route object which contains the route name (if named), path, etc. Documentation for all the props
You can use scoped attribute in the style element. Then the style will be limited only to that vue file.
HomeView.vue:
<template>
<p>This is the home page!</p>
</template>
<script>
export default {
}
</script>
<style scoped>
body {
background: red;
}
</style>
IntroView.vue:
<template>
<div>
<h1>Introduction</h1>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
body {
background: pink;
}
</style>

Categories

Resources