Changing body styles in vue router - javascript

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>

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

vue3 setting css styles from vuex store

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>

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)
}
}
.....

Why wont the component respond to the custom event?

I am emitting an event called emit-event-main2-counter in Main2.vue
Why will the data cntr in Bottom.vue not update?
App.vue
<template>
<div class="app">
<Main2 />
<Bottom/>
</div>
</template>
<script>
import Main2 from "./components/Main2";
import Bottom from "./components/Bottom";
export default {
components: {
Main2,
Bottom
},
}
</script>
<style scoped>
h1 {
color: red;
}
</style>
Main2.vue
<template>
<div>
main2 template <span class="text1">{{message}}</span>
<button type="button" v-on:click="btnClickButton">my click</button>
<div>{{counter}}</div>
</div>
</template>
<script>
import appInput from "./appInput.vue";
export default {
data: () => {
return {
message: "theText",
counter: 0,
}
},
components: {
appInput,
},
methods: {
btnClickButton(e) {
this.$root.$emit('emit-event-main2-counter', this.counter)
console.log('button');
this.counter +=1;
}
}
}
</script>
<style scoped>
.text1 {
color:red;
}
.text2 {
color:blue;
}
</style>
Bottom.vue
<template>
<div class="Bottom" v-on:emit-event-main2-counter="cntr = $event">
bottom text and cntr ({{cntr}})
</div>
</template>
<script>
export default {
data: () => {
return {
cntr: 0
}
},
}
</script>
<style scoped>
</style>
You could emit an event to parent from Main2 having as parameters this.counter and in the parent one receive that and pass it through props to Bottom
In Main2 :
this.$emit("emit-event-main2-counter",this.counter);
in the parent component :
<template>
<Main2 v-on:emit-event-main2-counter="sendToBottom"/>
<Bottom :cntr="pcounter"/>
....
</template>
data:{
pcounter:0
},
methods:{
sendToBottom(c){
this.pcounter=c
}
}
Bottom should have property called cntr
props:["cntr"]
Bottom.vue
<template>
<div class="Bottom" >
bottom text and cntr ({{cntr}})
</div>
</template>
<script>
export default {
props:["cntr"],
data: () => {
return {
}
},
}
</script>
If you want to use root events, you need to emit the event with this.$root.$emit() and also listen to the event on the root like this: this.$root.$on().
You should use it directly in the script part. Listen to the root event e.g. in the created() hook and then disable it with $off in the beforeDestroy() hook.
However, I wouldn't encourage you to use $root events. It is usually better to communicate between the components like #BoussadjraBrahim proposed in his answer.
If you have a more complex application, it makes sense to take a look at Vuex and store the complete state in the Vuex store. By doing this, you can watch the global application state in the components and react if it changes. In this scenario, you would use the Vuex store instead of a root EventBus.

Vuejs Modal. Avoid changing props, nuxt.js iview

I need help with modals, i tried to get this question sorted yesterday but then it got even worse. So please any help would be appreciated.
I have a parent component and inside of there i have a modal (child component)
In my parent the code:
<template>
<div class="all">
<Button type="primary" #click="modalUp()">Press me</Button>
<appTest #changed = "modal1 = $event" :modal1='modal1'> </appTest>
{{modal1}}
</div>
</template>
<script>
/* eslint-disable */
import test from '~components/test.vue'
export default {
data(){
return{
modal1: false
}
},
components: {
appTest: test
},
methods: {
modalUp() {
this.modal1 = true
}
},
watch:{
modal1: function(){
this.$on('changed', (data)=>{
console.log(data)
})
}
}
}
</script>
<style lang="css" scoped>
</style>
inside of the child component (appTest) i have this
<template>
<div id="" >
<Modal v-model="modal1" title="MODALLLL" #on-ok="ok" #on-cancel="cancel">
<p>#twitter</p>
<p>#facebook</p>
<p>Good</p>
{{modal1}}
</Modal>
</div>
</template>
<script>
/* eslint-disable */
export default {
props: ['modal1'],
data() {
return {
}
},
methods: {
ok() {
this.$Message.info('all good');
},
cancel() {
this.$Message.info('Cancel');
this.$emit('changed')
}
},
watch:{
modal1: function(){
this.$emit('changed', this.modal1)
}
}
}
</script>
<style lang="css" scoped>
</style>
So this code works in one way, the modal shows up correctly but once its gone and when we get back to parent component it gives me this vue warning AVOID MUTATING PROP
I checked the docs and everything but vuejs docs give examples like 2+2 which is not helpful in this case. I watched videos on the internet etc but still don't know how to get it done in a proper way.
What would be the best way to get it working?
I'm using modal from iview
Change your child to use a computed value.
export default {
props: ['modal1'],
computed:{
showModal:{
get(){return this.modal1},
set(v){ this.$emit("changed", v)}
}
},
}
And update the child template to
<Modal v-model="showModal" ...></Modal>
Doing this, whever you change modal1 in the parent, the value will be updated in the Modal component, and whenever the Modal component changes the value, it will be sent to the parent.

Categories

Resources