VueJs: How to change CSS background image for each page route? - vuejs2

I am probably making this more complicated than it needs to be, but does anyone have any ideas on how I can swap out custom banner images for each of my pages (when route changes to any thing other than index page)? I have the slots working , but need way to change background image in the non-index banners. Here's my code:
App.vue and shows only the main page banner:
<template>
<div id="app">
<BannerHome v-if="isHomeView" />
<main id="routerView">
<RouterView :key="$route.fullPath" />
</main>
<Footer />
</div>
</template>
<script>
import BannerHome from '#/components/BannerHome.vue'
import Footer from '#/components/Footer.vue'
export default {
name: 'App',
components: {
BannerHome,
Footer
},
computed: {
isHomeView() {
return this.$route.fullPath === '/'
}
}
}
</script>
Example of non-index page (About.vue):
<template>
<article>
<banner>
<template v-slot:titleTop>Mission Focused</template>
<template v-slot:titleBottom>
Dedicated To Service
</template>
</banner>
<section class="container-md pb-5">
<h1>About Page</h1>
</section>
</article>
</template>
<script>
import Banner from '#/components/Banner.vue'
export default {
name: 'About',
components: {
Banner
}
}
</script>
Banner component for non-index pages (Banner.vue). Note the background image. How can I swap this out for each non-index page? Right now the background image is shown for ALL non-index pages.
<template>
<header>
<MainNav />
<div class="container">
<h1><em><slot name="titleTop"></slot><br />
<span class="pl-3"><slot name="titleBottom"></slot></span></em>
</h1>
</div>
</header>
</template>
<script>
import MainNav from '#/components/MainNav.vue'
export default {
name: 'Banner',
components: {
MainNav
}
}
</script>
<style lang="scss" scoped>
header {
position: relative;
width: 100%;
// set this page height here
height: 20em;
text-transform: uppercase;
}
header::before {
content: "";
display: block;
position: absolute;
left: 0;
bottom: 6em;
width: 100%;
// set this page height here
height: calc(20em + 10em);
z-index: -1;
transform: skewY(-3.5deg);
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)),
url('../assets/mockphoto.jpg') < -----how to swap out this image?
no-repeat left top,
linear-gradient(#4e4376, #2b5876);
background-size: cover;
border-bottom: 0.2em solid #fff;
}
h1 {
font-size: 1.75rem!important;
font-weight: 700!important;
letter-spacing: 0.01em;
padding: 2.5rem 0 0 0;
// text-shadow: 0.022em 0.022em 0.022em #111;
color: #fff;
}
</style>
Btw, I am also using Gridsome...so if there is also an easy way to access page data via GraphQL for this purpose I can also use that. Thanks for any help !

So, I actually just need to add the /deep/ keyword to the parent CSS like so:
/deep/ header::before {
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)),
url('../assets/mockphoto.jpg')
no-repeat left top,
linear-gradient(#4e4376, #2b5876);
}
Docs: https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors

Related

NuxtJS - I want to know how to implement Loading only for specific pages

I want to display the Nuxt Loading component only on the top page, but it will be displayed on all pages. Can't I display it only when I access a specific page?
Also, the page will be output momentarily before the loading screen starts. Do you know what caused it?
nuxt.config.js
export default {
..
loading: '#/components/Organisms/PageLoading.vue',
..
}
layouts/default.vue
<template>
<div>
<org-page-loading />
</div>
</template>
<script>
import PageLoading from "../components/Organisms/PageLoading";
export default {
name: "Default",
components: {
"org-page-loading": PageLoading,
},
mounted() {
this.$nextTick(() => {
this.$nuxt.$loading.start()
setTimeout(function () {
this.$nuxt.$loading.finish()
}, 2400)
})
},
}
</script>
You can do something like this:
// your-component.vue
<template>
<h1>My Custom page</h1>
</template>
<script>
export default {
loading: false
}
</script>
So firstly the $nextTick probably makes your page show before loading.
Secondly I have never used this kind of loading component, but if you make your own loading component, you can pass a prop to it dynamically, which tells it on which page you want it to be and if the url is not matching, or the component name is not matching it will not show
<your-custom-loading :page="routerLinkYouWantItToBeShownOn" />
EDIT:
I found a better solution:
Heres the spinner/loading component
<template>
<div class="spinner"></div>
</template>
<style lang="css">
.spinner {
position: absolute;
height: 60px;
width: 60px;
border: 3px solid transparent;
border-top-color: var(--v-success-base);
top: 50%;
left: 50%;
margin: -30px;
border-radius: 50%;
animation: spin 2s linear infinite;
z-index: 100;
&:before,
&:after {
content: '';
position: absolute;
border: 3px solid transparent;
border-radius: 50%;
}
&:before {
border-top-color: var(--v-error-base);
top: -12px;
left: -12px;
right: -12px;
bottom: -12px;
animation: spin 3s linear infinite;
}
&:after {
border-top-color: var(--v-accent-lighten1);
top: 6px;
left: 6px;
right: 6px;
bottom: 6px;
animation: spin 4s linear infinite;
}
}
#keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
And here is how u call it:
<template>
<div>
<loading> v-if="isLoading" />
<div v-else>Your other content</div>
</div>
</template>
<script>
import Loading from '#/components/Loading';
export default {
components: {
Loading
},
computed: {
isLoading(){
return "your condition for loading"
}
},
</script>
And you can use a data variable for isloading set to true by default and when u visit the page it will be loading by default
dont even need props

How do I open and close a modal programmatically using Vue.js?

I've read through innumerable posts about how to do this with jquery, but Vue.js jealously owns and manages the dom, so I will need a solution that uses standard Vue.js components or libraries.
Thus far, I've located several examples that are what I'd call 'button-event-driven' solutions, but I will need to programmatically handle open and close of the modal.
Problem / Design Requirement: When a public user attempts to interact with a tempting button or other function on my application, and they are not yet logged in, I wish to programmatically launch a modal dialogue to then ask them to log in.
Once successfully, I'll need to programmatically close the same dialogue modal. Or, of course, they can choose to cancel and continue browsing as a public user without the ability to do those functions.
Other Helpful Information: I'm using bootstrap 4.4.1
You can use a watch property. If a user is not logged in as login=false then the modal shows.
// register modal component
Vue.component("modal", {
template: "#modal-template"
});
// start app
new Vue({
el: "#app",
data: {
showModal: false,
login: null
},
created() {
this.login = false;
},
watch: {
"login": function(val) {
this.showModal = !val;
}
}
});
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: table;
transition: opacity 0.3s ease;
}
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
width: 300px;
margin: 0px auto;
padding: 20px 30px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
transition: all 0.3s ease;
font-family: Helvetica, Arial, sans-serif;
}
.modal-header h3 {
margin-top: 0;
color: #42b983;
}
.modal-body {
margin: 20px 0;
}
.modal-default-button {
float: right;
}
/*
* The following styles are auto-applied to elements with
* transition="modal" when their visibility is toggled
* by Vue.js.
*
* You can easily play with the modal transition by editing
* these styles.
*/
.modal-enter {
opacity: 0;
}
.modal-leave-active {
opacity: 0;
}
.modal-enter .modal-container,
.modal-leave-active .modal-container {
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!DOCTYPE html>
<html>
<head>
<title>Modal Component</title>
<link rel="stylesheet" type="text/css" href="/style.css" />
<!-- template for the modal component -->
<script type="text/x-template" id="modal-template">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
default header
</slot>
</div>
<div class="modal-body">
<slot name="body">
default body
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
</head>
<body>
<!-- app -->
<div id="app">
<!-- use the modal component, pass in the prop -->
<modal v-if="showModal" #close="showModal = false">
<!--
you can use custom content here to overwrite
default content
-->
<h3 slot="header">custom header</h3>
</modal>
</div>
</body>
</html>
I was able to construct this using a project with a (relatively) recent example. Here is the component, as well as a 'Tester.vue' view that uses that component:
LoginModal.vue:
<template>
<transition name="modal-fade">
<div class="modal-backdrop">
<div
class="modal"
role="dialog"
aria-labelledby="modalTitle"
aria-describedby="modalDescription"
>
<header class="modal-header" id="modalTitle">
<slot name="header">
</slot>
</header>
<section class="modal-body" id="modalDescription">
<slot name="body">
Your Login Form Goes Here
<button type="button" v-on:click="validateLoginForm">
Log In
</button>
<button type="button" #click="close" aria-label="Close modal">
Cancel
</button>
</slot>
</section>
<footer class="modal-footer">
<slot name="footer"> </slot>
</footer>
</div>
</div>
</transition>
</template>
<script>
export default {
name: "loginModal",
data() {
return {
loginValidationAlerts: [],
};
},
methods: {
close() {
this.$emit("close");
},
validateLoginForm() {
//Login Form Validations go here
},
clearAllLoginValidationErrors() {
this.loginValidationAlerts = [];
},
attemptLogin() {
// Your login code
},
},
};
</script>
<style>
.modal-fade-enter,
.modal-fade-leave-active {
opacity: 0;
}
.modal-fade-enter-active,
.modal-fade-leave-active {
transition: opacity 0.5s ease;
}
.modal-backdrop {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background: #ffffff;
box-shadow: 2px 2px 20px 1px;
overflow-x: auto;
display: flex;
flex-direction: column;
}
.modal-header,
.modal-footer {
padding: 15px;
display: flex;
}
.modal-header {
border-bottom: 1px solid #eeeeee;
color: #4aae9b;
justify-content: space-between;
}
.modal-footer {
border-top: 1px solid #eeeeee;
justify-content: flex-end;
}
.modal-body {
position: relative;
padding: 20px 10px;
}
.btn-close {
border: none;
font-size: 20px;
padding: 20px;
cursor: pointer;
font-weight: bold;
color: #4aae9b;
background: transparent;
}
.btn-green {
color: white;
background: #4aae9b;
border: 1px solid #4aae9b;
border-radius: 2px;
}
</style>
Tester.vue:
<template>
<div>
Test Page
<div>
<button type="button" class="btn" #click="showModal">
Open Modal!
</button>
</div>
<div>
<LoginModal v-show="isModalVisible" #close="closeModal"></LoginModal>
</div>
</div>
</template>
<script>
import LoginModal from "#/components/LoginModal.vue";
export default {
components: {
// eslint-disable-next-line vue/no-unused-components
LoginModal,
},
data() {
return {
isModalVisible: false,
};
},
methods: {
showModal() {
// Do something here to determine
// if you should show modal
this.isModalVisible = true;
},
closeModal() {
// this will catch the close event
// after you're done processing the login in the component
this.isModalVisible = false;
},
},
};
</script>
<style scoped></style>
The appearance of the view and its component is pretty rough and without much formatting, but you get the picture.
Hopefully this helps somebody else if they're looking for a straight-forward modal in Vue; I believe it has to be a very common design requirement, and this approach worked for me.
The simple solution is:
add a reference to modal component
<share-modal ref="share-modal-ref"/>
import Modal like this:
import { Modal } from 'bootstrap'
then in your method do this:
let element = this.$refs.listModal.$el
let shareModal = new Modal(element, {})
shareModal.show()
It works on Vue 3 & Boostrap 5

How to close a modal by clicking on the backdrop in Vue.js

I have created a simple reusable modal component using Vue.js and it works fine, but I want to make so that when I click on the backdrop the modal closes, how can I achieve this? I searched and found a similar question on stackoverflow:
vuejs hide modal when click off of it
And did the same that the accepted answer does, putting #click="$emit('close')" on the wrapper but the modal does not get closed by clicking the backdrop as it is in the provided example. Here is my code:
<template>
<div :class="backdrop" v-show="!showModal">
<div class="modal-wrapper">
<div class="modal-container" :class="size" #click="$emit('close')">
<span class="close-x" #click="closeModal">X</span>
<h1 class="label">{{label}}</h1>
<div class="modal-body">
<slot></slot>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'custom-modal',
data() {
return {
showModal: false
};
},
props: {
label: String | Number,
size: String,
backdrop: String
},
components: {
'custom-btn': customBtn
},
methods: {
closeModal() {
this.showModal = true;
}
}
};
</script>
<style>
.modal-wrapper {
display: table-cell;
vertical-align: middle;
}
.modal-container {
margin: 0px auto;
padding: 20px 30px;
border-radius: 2px;
background-color: #fff;
font-family: Helvetica, Arial, sans-serif;
box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
transition: all .3s ease;
}
.close-x {
color: #00A6CE;
float: right;
}
.close-x:hover {
cursor: pointer;
}
</style>
Without a library you need to set it up like this:
<div class="modal-wrapper" #click="$emit('close')>
<div class="modal-container" :class="size" #click.stop=""></div>
</div>
It looks like you're missing the #click.stop="" which is required. Additionally you want to move the $emit('close') up to the modal-wrapper level.
With a library it may be overkill, but this is something that I have used v-click-outside for.
Vue directive to react on clicks outside an element without stopping the event propagation. Great for closing dialogues, menus among other things.
Simply npm install --save v-click-outside
Then (from the docs):
<div v-click-outside="onClickOutside"></div>
and:
onClickOutside (event, el) {
this.closeModal();
},
Try creating a transparent div that covers all the screen but with a z-index < your modals z-index. Then #click on it, you emit your event to close the modal :) Hope it will hellp
<template>
<div #click="handleBackdropClick" class="backdrop" ref="backdrop">
<div class="modal">
<h1> Modal Title </h1>
<input type="text" />
<p> Modal Content </p>
</div>
</div>
</template>
<style>
.modal {
width: 400px;
padding: 20px;
margin: 100px auto;
background: white;
border-radius: 10px;
}
.backdrop{
top: 0;
position: fixed;
background: rgba(0,0,0,0.5);
width: 100%;
height: 100%;
}
.close{
display: none;
}
</style>
export default {
methods: {
handleBackdropClick(e){
console.log(e)
if (e.path[0].className == "backdrop") {
this.$refs.backdrop.classList.add('close');
}
}
}
}
</script>

Not able to call Vue component in vue cli application

I am trying to create the component for tile which can be used in multiple views. I have called the component in view but it is not working
Code of component (Tile.vue)
<template>
<div class="tile">
<label class="title">Tile Title</label>
</div>
</template>
<script>
export default {
name: 'CustomTile'
}
</script>
<style scoped lang="less">
.tile { width:248px;
height: 126px;
background-color:#e4e4e4;
.title {
padding-left: 15px;
}
}
</style>
code of view (Report.vue) where I am trying to call above component
<template>
<div>
<div class="topnav">
<button type="button">Expand/Collapse</button>
</div>
<div class="content">
<CustomTile></CustomTile>
</div>
</div>
</template>
<script>
import CustomTile from '#/components/Tile.vue'
export default {
name: 'Report'
}
</script>
<style scoped lang="less">
.topnav {
overflow: hidden;
background-color: #d6d6d6;
}
.topnav a {
float: left;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 17px;
}
.topnav a:hover {
background-color: #ddd;
color: black;
}.topnav a.active {
background-color: #4CAF50;
color: white;
}
.content {
height:500px;
width:500px;
}
</style>
CustomTile is not getting rendered. I am not able to figure out what/where is the problem.
You need to properly import the component in the parent where you want to use it and register it:
Report.vue:
<script>
import CustomTile from '#/components/Tile.vue'
export default {
name: 'Report',
components: {
CustomTile
}
}
</script>
And then, since CustomTile is a CamelCase component name, you need to use the following notation:
<custom-tile></custom-tile>

Vuejs carousel simple example not working

I am using Vuejs with Vue-carousel-3d for the carousel. I have the most basic simple carousel component from one of the examples:
<template>
<div id="carousel-wrapper">
<carousel-3d>
<slide v-for="(i, slide) in slides" :index="i" :key="i">
<img src="https://placehold.it/360x270">
</slide>
</carousel-3d>
</div>
</template>
<script>
import { Carousel3d, Slide } from 'vue-carousel-3d';
export default {
name: 'carousel-wrapper',
components: {
'carousel-3d': Carousel3d,
'slide': Slide
},
data: function () {
return {
slides: 7
}
}
}
</script>
<style scoped>
#carousel-wrapper {
outline: 5px solid red;
}
.carousel-3d-container figure {
margin: 0;
}
.carousel-3d-container figcaption {
position: absolute;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
bottom: 0;
padding: 15px;
font-size: 12px;
min-width: 100%;
box-sizing: border-box;
}
</style>
There was an error regarding passing a key in each slide in a for loop, but after correcting it by passing :key="i", now there's no error. But the carousel is not displaying in the browser. If I inspect in developer mode, there is the carousel divs, but in the browser, there's only the 2px solid red outline that I gave for testing purpose. Its working in their example page, but not in mine. I am new to Vuejs so I may be doing something wrong. What am I missing here?