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>
Related
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
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
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>
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');
I have two component I wanted to use both on some other component as follow
import { Component, Vue } from 'vue-property-decorator'
import DDGDriver from '#/components/ddg-driver.vue'
import OverLay from '#/components/overlay.vue'
#Component({
components: {
'over-Lay':OverLay, 'DDGDriver':DDGDriver
}
})
export default class DDGPage extends Vue {
showModal = true;
onModalClose(){
this.showModal = false;
}
}
but its not working only second imported component is getting placed instead of first, I don't know why this is happening.
<template>
<div>
<over-Lay v-on:close="onModalClose()" v-if="showModal">
<DDGDriver />
</over-Lay>
</div>
</template>
DDGDriver component
<template>
<div class="driver">
<h1>this is ddg driver</h1>
</div>
</template>
<script>
import Vue from 'vue';
export default class DDGDriver extends Vue {
}
</script>
<style lang="less" scoped>
.driver{
height:auto;
width: 400px;
background-color: #FFF;
}
</style>
OverLay component
<template>
<div class="overlay">
<div class="containt">
<div #click="removeModal()" class="close">X</div>
<slot>
This will only be displayed if there is no content
to be display.
</slot>
</div>
</div>
</template>
<script>
import Vue from "vue";
export default class OverLay extends Vue {
removeModal(){
this.$emit('close');
}
}
</script>
<style lang="less" scoped>
.overlay {
position: fixed;
bottom: 0px;
right: 0px;
left: 0px;
top: 0px;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
.containt {
min-width: 20px;
height: auto;
background-color: #fff;
padding: 15px;
.close {
position: relative;
top: -32px;
right: -27px;
float: right;
height: 25px;
width: 25px;
background: #fff;
border-radius: 50%;
}
}
}
</style>
You forgot to add #Component({}) in your children components.
<script>
import Vue from 'vue';
#Component({})
export default class DDGDriver extends Vue {}
</script>