How can i change style of the body when my modal it will be open? - vue.js

How can i change the body{overflow:hidden} when my modal it will be open?
for example it will be my modal, when its open, i would like to apply this style body{overflow:hidden}
<div v-if="dialogFoundation">
i am using vuejs3, i am using setup(){...}

The best performance would be to use javascript plain. You can add Eventlistener top the modal trigger Element. In my example i use a button. If it triggered then you can use classList and assign the body a class. In my example .dark.
Vue version
<!-- Use preprocessors via the lang attribute! e.g. <template lang="pug"> -->
<template>
<div id="app">
<h1>{{message}}</h1>
<p></p>
<button #click="doSomething">Modal</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Welcome to Vue!'
};
},
methods: {
doSomething() {
const b = document.querySelector('body');
b.classList.toggle('dark');
}
}
};
</script>
<!-- Use preprocessors via the lang attribute! e.g. <style lang="scss"> -->
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
a,
button {
color: #4fc08d;
}
button {
background: none;
border: solid 1px;
border-radius: 2em;
font: inherit;
padding: 0.75em 2em;
}
.dark {
background: black;
opacity: 0.4;
}
</style>
Vanilla JS
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
const b = document.querySelector('body');
b.classList.toggle('dark');
})
.dark {
background: black;
opacity: 0.4;
}
<body>
<div></div>
<button>click</button>
</body>

You can use watchers in Vue.js for solving this problem.
When variables changes you can check whether it is true or not, and if true change overflow of body to hidden.
{
watch: {
dialogFoundation(dialogFoundation) {
document.body.style.overflow = dialogFoundation ? "hidden" : "auto"
}
}
}
But I think this is not good solution. You can set this styles to your app element
#app {
width: 100%;
height: 100%;
overflow: auto;
}
and you can change style of app element using Vue directives.

<template>
<div id="app" :class="{ hidden: dialogFoundation }">
Long text....
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const dialogFoundation = ref(true);
return { dialogFoundation };
},
};
</script>
<style>
html,
body,
#app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
overflow: auto;
}
#app.hidden {
overflow: hidden;
}
</style>
Code in codesandbox - https://codesandbox.io/s/immutable-glitter-rwc2iy?file=/src/App.vue

Related

Styles are not being applied to elements created in the setup function in vue

So I am working on this mini website and I'm trying to not hard code repetitive elements in the template, so I thought I would a function to display them all.
<template>
<div class="wrapper">
<div class="skills" ref="canvas">
</div>
</div>
import { ref, onMounted } from 'vue';
import MySkills from '../constants/skills';
export default {
setup() {
const skills = MySkills
const canvas = ref(null);
const displaySkills = () => {
skills.forEach(skill => {
const el = document.createElement('div');
el.className = 'skill';
el.innerText = skill.name;
const level = document.createElement('span');
level.className = 'level';
level.innerText = skill.level;
el.appendChild(level);
canvas.value.appendChild(el);
})
}
onMounted(() => {
displaySkills();
})
return {
canvas,
displaySkills
}
}
}
the styles are in a separate file
.skill {
padding: 10px;
border-radius: 100px;
text-align: center;
position: relative;
background-color: aqua;
}
.level {
position: absolute;
top: -5%;
left: 0%;
padding: 5px;
border-radius: 100px;
text-align: center;
}
Now the ssue is that those styles are not being applied the el and level elements
please help!!!
You should not access the DOM (createElement, appendChild) while working with Vue.
If you are new to Vue, v-for does exactly that, more info in the official docs.
So your code would be:
<template>
<div class="wrapper">
<!-- you should put a unique identifier in the key tag, if you don't have an id the name should be fine too -->
<div class="skills" v-for="skill in MySkills" :key="skill.id">
<your-skill-html>
{{ skill.name }} etc
</your-skill-html>
</div>
</div>
</template>
<script setup>
import MySkills from '../constants/skills';
</script>
<style>
.skill {
padding: 10px;
border-radius: 100px;
text-align: center;
position: relative;
background-color: aqua;
}
.level {
position: absolute;
top: -5%;
left: 0%;
padding: 5px;
border-radius: 100px;
text-align: center;
}
/* or import "path/to/my-css-file.css" */
</style>
Please note that I'm using the script setup syntax but it's not required, it's just quicker to write since you can omit the defineComponent part and the return values.

Vuejs v-movable with #click is not working

Im trying to implement a moveable text that can also be click and call the method. However when i try to put #click its not calling the function. Can anyone help me? Thank you..
App.vue
<template>
<div id="app">
<div>
<a #click="next()">
<movable class="testmove" posTop="222" posLeft="222" shiftKey="true"
><span>Shift Key Behavior</span></movable
>
</a>
</div>
</div>
</template>
<script>
import movable from "v-movable";
export default {
name: "app",
movable,
methmethods: {
next() {
alert("Hello");
},
},
};
</script>
<style>
body {
padding: 0;
margin: 0;
font-family: Helvetica, Arial;
}
.movable {
cursor: pointer;
}
.testmove {
/* display:block;
position: absolute; */
top: 0;
height: 150px;
width: 150px;
background: #333;
color: white;
}
.modaltitle {
background: blue;
display: block;
width: 100%;
color: white;
}
</style>
Here is the code:
https://codesandbox.io/s/cold-darkness-0zm03?file=/src/App.vue
There is a typo in your code methmethods which should be corrected to methods
You can use start event for the v-movable.
#start: fires immediately after the pointerdown event on the element

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>

How to properly use slot inside of vue js web component and apply styles

I have come across an issue where the implementation of slots in a webcomponent is not functioning as expected. My understanding of Web Components, Custom Elements and Slots is that elements rendered in a slot should inherit their style from the document and not the Shadow DOM however the element in the slot is actually being added to the Shadow DOM and therefore ignoring the global styles. I have created the following example to illustrate the issue that I am having.
shared-ui
This is a Vue application that is compiled to web components using the cli (--target wc --name shared-ui ./src/components/*.vue)
CollapseComponent.vue
<template>
<div :class="[$style.collapsableComponent]">
<div :class="[$style.collapsableHeader]" #click="onHeaderClick" :title="title">
<span>{{ title }}</span>
</div>
<div :class="[$style.collapsableBody]" v-if="expanded">
<slot name="body-content"></slot>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator'
#Component({})
export default class CollapsableComponent extends Vue {
#Prop({ default: "" })
title!: string;
#Prop({default: false})
startExpanded!: boolean;
private expanded: boolean = false;
constructor() {
super();
this.expanded = this.startExpanded;
}
get isVisible(): boolean {
return this.expanded;
}
onHeaderClick(): void {
this.toggle();
}
public toggle(expand?: boolean): void {
if(expand === undefined) {
this.expanded = !this.expanded;
}
else {
this.expanded = expand;
}
this.$emit(this.expanded? 'expand' : 'collapse');
}
public expand() {
this.expanded = true;
}
public collapse() {
this.expanded = false;
}
}
</script>
<style module>
:host {
display: block;
}
.collapsableComponent {
background-color: white;
}
.collapsableHeader {
border: 1px solid grey;
background: grey;
height: 35px;
color: black;
border-radius: 15px 15px 0 0;
text-align: left;
font-weight: bold;
line-height: 35px;
font-size: 0.9rem;
padding-left: 1em;
}
.collapsableBody {
border: 1px solid black;
border-top: 0;
border-radius: 0 0 10px 10px;
padding: 1em;
}
</style>
shared-ui-consumer
This is a vue application that imports the shared-ui web component using a standard script include file.
App.vue
<template>
<div id="app">
<shared-ui title="Test">
<span class="testClass" slot="body-content">
Here is some text
</span>
</shared-ui>
</div>
</template>
<script lang="ts">
import 'vue'
import { Component, Vue } from 'vue-property-decorator';
#Component({ })
export default class App extends Vue {
}
</script>
<style lang="scss">
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.testClass{
color: red;
}
</style>
main.ts
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
// I needed to do this so the web component could reference Vue
(window as any).Vue = Vue;
new Vue({
render: h => h(App),
}).$mount('#app');
In this example I would expect the content inside the container to have red text however because Vue is cloning the element into the Shadow DOM the .testClass style is being ignored and the text is rendered with a black fill.
How can I apply .testClass to the element inside of my web component?
Ok, so I managed to find a workaround for this that uses native slots and renders the child components correctly in the correct place in the DOM.
In the mounted event wire up the next tick to replace the innerHtml of your slot container with a new slot. You can get fancy and do some cool replacements for named slots and whatnot but this should suffice for illustrating the workaround.
shared-ui
This is a Vue application that is compiled to web components using the cli (--target wc --name shared-ui ./src/components/*.vue)
CollapseComponent.vue
<template>
<div :class="[$style.collapsableComponent]">
<div :class="[$style.collapsableHeader]" #click="onHeaderClick" :title="title">
<span>{{ title }}</span>
</div>
<div ref="slotContainer" :class="[$style.collapsableBody]" v-if="expanded">
<slot></slot>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator'
#Component({})
export default class CollapsableComponent extends Vue {
#Prop({ default: "" })
title!: string;
#Prop({default: false})
startExpanded!: boolean;
private expanded: boolean = false;
constructor() {
super();
this.expanded = this.startExpanded;
}
get isVisible(): boolean {
return this.expanded;
}
onHeaderClick(): void {
this.toggle();
}
//This is where the magic is wired up
mounted(): void {
this.$nextTick().then(this.fixSlot.bind(this));
}
// This is where the magic happens
fixSlot(): void {
// remove all the innerHTML that vue has place where the slot should be
this.$refs.slotContainer.innerHTML = '';
// replace it with a new slot, if you are using named slot you can just add attributes to the slot
this.$refs.slotContainer.append(document.createElement('slot'));
}
public toggle(expand?: boolean): void {
if(expand === undefined) {
this.expanded = !this.expanded;
}
else {
this.expanded = expand;
}
this.$emit(this.expanded? 'expand' : 'collapse');
}
public expand() {
this.expanded = true;
}
public collapse() {
this.expanded = false;
}
}
</script>
<style module>
:host {
display: block;
}
.collapsableComponent {
background-color: white;
}
.collapsableHeader {
border: 1px solid grey;
background: grey;
height: 35px;
color: black;
border-radius: 15px 15px 0 0;
text-align: left;
font-weight: bold;
line-height: 35px;
font-size: 0.9rem;
padding-left: 1em;
}
.collapsableBody {
border: 1px solid black;
border-top: 0;
border-radius: 0 0 10px 10px;
padding: 1em;
}
</style>
shared-ui-consumer
This is a vue application that imports the shared-ui web component using a standard script include file.
App.vue
<template>
<div id="app">
<shared-ui title="Test">
<span class="testClass" slot="body-content">
Here is some text
</span>
</shared-ui>
</div>
</template>
<script lang="ts">
import 'vue'
import { Component, Vue } from 'vue-property-decorator';
#Component({ })
export default class App extends Vue {
}
</script>
<style lang="scss">
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.testClass{
color: red;
}
</style>
main.ts
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
// I needed to do this so the web component could reference Vue
(window as any).Vue = Vue;
new Vue({
render: h => h(App),
}).$mount('#app');

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>