Use Modals Dynamically in VueJS - vuejs3

I created a Modal template in vueJS and I want to set the content inside of it dynamically.
Sometimes I will want the Modal to delete some kind of data.
E.g: Do you want to delete the user with ID 5?
But other times i will want the Modal to act as a Form input data.
The Modal template I'm using is this Modal.vue
<template>
<div class="vue-modal" v-show="open">
<div class="vue-modal-inner">
<div class="vue-modal-content">
<button type="button" click="$emit('close')">Close</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Modal",
props: {
open: {
type: Boolean,
default: false
}
}
}
</script>
<style scoped>
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.vue-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
background-color: rgba(0, 0, 0, 0.4);
z-index: 1;
}
.vue-modal-inner {
max-width: 500px;
margin: 2rem auto;
}
.vue-modal-content {
position: absolute;
min-width: 300px;
left: 50%;
top: 35%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.3);
background-clip: padding-box;
border-radius: 0.3rem;
padding: 1rem;
}
</style>
The content inside of vue-modal-content is what I want to set dynamically depending on how I am going to use the Modal (Delete, Update, Create)
Here's how I create it in parent component
<Modal :open="modalEditar.mostrar" #close="modalEditar.mostrar=false">
</Modal>

Probably easiest is to use slots.
There's a description of how to set this up at digitalocean so I won't go through it, since it's all there, but would add scope slots to the implementation. The issue is that you want to drive the kinds of buttons from the parent and the example only shows the close button as being available from within the the modal component.
To achieve that, you can pass the function to the slot like <slot :close="close">
<template>
<div class="vue-modal" v-show="open">
<div class="vue-modal-inner">
<div class="vue-modal-content">
<slot :close="close"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Modal",
props: {
open: {
type: Boolean,
default: false
}
},
methods: {
close() {
this.$emit('close');
},
},
}
</script>
and then you can have the function available in the parent through <template v-slot={close} >
<script setup>
import { ref } from 'vue'
import Modal from "./Modal.vue"
const isModalVisible = ref(true);
function closeModal() {
isModalVisible.value = false;
}
</script>
<template>
<button #click="isModalVisible = true">
open
</button>
<Modal :open="isModalVisible" #close="closeModal">
<template v-slot={close} >
This is a body<br/>
<button #click="close" class="btn-green">
CLOSE
</button>
</template>
</Modal>
</template>
here is a working example

Related

How to change css width 50% to 100% using Vue

How can I change css width from 50% to 100 % when click the button see more detail here >>> Sample sandbox
<template>
<div id="theSpecial">Hello World Special</div>
<button #click="changeWidth">Change width</button>
</template>
<script>
export default {
data() {
return {
testBoolean: false,
};
},
methods: {
changeWidth() {
this.testBoolean = true;
//change width to 100%
},
},
};
</script>
CSS
#theSpecial {
background-color: purple;
color: white;
width: 50%;
}
You have to make some change on your code
First of all add this to your css
.theSpecial{width:50%}
.fullWidth{width:100%}
To toggle the full width modify the method
changeWidth() {
this.testBoolean = !this.testBoolean;
//this will toggle the width on every click
},
and then use this in your component template
<div class="theSpecial" v-bind:class="{fullWidth:testBoolean}">
N.B. change the id into class, beacuse id has more css specifity.
This will toggle the class full width accordly to the value of testBoolean.
This is your Sandbox
Here you can find documentation about class binding
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<div id="theSpecial" :class="{ 'full-width': testBoolean }">
Hello World Special
</div>
<button #click="changeWidth">Change width</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
data() {
return {
testBoolean: false,
};
},
methods: {
changeWidth() {
this.testBoolean = true;
},
},
};
</script>
#theSpecial {
background-color: purple;
color: white;
width: 50%;
}
#theSpecial.full-width {
width: 100%;
}
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
data() {
return {
testBoolean: false,
};
},
methods: {
changeWidth() {
this.testBoolean = !this.testBoolean;
//change width to 100%
},
},
.theSpecial {
background-color: purple;
color: white;
width: 50%;
}
.fullwidth {
background-color: purple;
color: white;
width: 100%;
}
<div :class="(this.testBoolean === true)? 'fullwidth':'theSpecial'">Hello World Special</div>
<button #click="changeWidth">Change width</button>

I want the sidebar to appear on all interfaces

I am creating a site for the sake of monitoring employees, and there are six interfaces on the site, as the first interface is for the Sine-Up, the second for logging, and the third interface is for creating a project, the fourth interface is for displaying projects, the fifth is for creating TASK and the sixth In order to view the tasks.
And I created a sidebar in a separate interface, which is the image shown in the screen, and my problem now is that I want the sidebar to appear in all interfaces except for the signup and the log.
This is the application file from which router-view is used.
App.vue:
<template>
<v-app>
<router-view></router-view>
</v-app>
</template>
<script>
export default {
components:{
}
}
</script>
And in this file, the sidebar was created, which I want to appear in all the interfaces.
navbar.vue:
<template>
<v-app>
<div class="main-sidebar-container">
<div class="main-sidebar-container_content">
<v-navigation-drawer class="deep-purple accent-4" dark permanent>
<div class="main-sidebar-container_content_header">
<img class="logo" src="../../../src/assets/logo_base.png" />
</div>
<v-divider></v-divider>
<div class="sidebar-search">
<div class="cu2-search_simple-layout cu2-search">
<div class="cu2-search__inner ">
<div class="cu2-search__icon icon">
<svg class="ng-star-inserted">
<use
xlink:href="https://app.clickup.com/map.e7a227c29e2316abeae1.svg#svg-sprite-
cu3-search"
></use>
</svg>
</div>
</div>
</div>
</div>
<div class="cu2-search__text"> Search </div>
<v-list>
<v-list-item v-for="item in items" :key="item.title" class="twoSection" link>
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
<template v-slot:append>
<div class="pa-2">
<v-btn block>
Logout
</v-btn>
</div>
</template>
</v-navigation-drawer>
</div>
</div>
</v-app>
</template>
<script>
export default {
data() {
return {
drawer: true,
items: [
{ title: "Home", icon: "mdi-home-city" },
{ title: "Notifications", icon: "mdi-account" },
{ title: "Pulse", icon: "mdi-account-group-outline" },
{ title: "Goals", icon: "mdi-account" },
{ title: "Show less", icon: "mdi-account-group-outline" }
],
mini: true,
};
},
};
</script>
<style scoped>
.main-sidebar-container {
left: 0;
top: 0;
bottom: 0;
height: 100%;
width: 60%;
position: fixed;
z-index: 1;
top: 0;
overflow-x: hidden;
}
.main-sidebar-container_content {
width: 50%;
height: 100%;
}
.main-sidebar-container_content_header {
display: flex;
justify-content: center;
align-items: center;
width: 50%;
height: 10%;
padding-left: 1rem;
}
.logo {
display: flex;
width: 50%;
z-index: 1;
position: fixed;
}
.sidebar-search {
display: flex;
align-items: center;
height: 32px;
flex-shrink: 0;
margin: 0 8px 10px;
}
.cu2-search_simple-layout {
width: auto;
height: 100%;
flex-grow: 1;
border-radius: 4px;
background: #f6f7f9;
}
.cu2-search {
transition: background-color 0.2s cubic-bezier(0.785, 0.135, 0.15, 0.86) 0s;
display: flex;
align-items: center;
justify-content: center;
position: relative;
width: 30px;
height: 30px;
border-radius: 3px;
overflow: hidden;
flex-shrink: 0;
}
.cu2-search__inner {
display: flex;
align-items: center;
cursor: pointer;
width: 100%;
height: 100%;
}
.cu2-search__icon {
transition: fill 0.2s cubic-bezier(0.785, 0.135, 0.15, 0.86) 0s;
}
.icon {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 0;
flex-shrink: 0;
}
.icon svg {
display: block;
}
.cu2-search__text{
display: block;
font: 400 12px/1 Gotham Pro,Proxima Nova,arial,serif;
color: rgba(124,130,141,.5);
transition: color .2s cubic-bezier(.785,.135,.15,.86) 0s;
cursor: pointer;
display: none;
}
.twoSection{
display: flex;
justify-content: center;
align-items: center;
height: 10%;
}
</style>
main.js:
import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify';
import VueRouter from "vue-router";
import { routes } from "./router";
import axios from "axios";
import { store } from './store/index';
import Vuelidate from "vuelidate";
import 'material-design-icons-iconfont/dist/material-design-icons.css'
Vue.config.productionTip = false
Vue.use(VueRouter);
Vue.use(Vuelidate);
axios.defaults.baseURL = "localhost:4000/api/services";
axios.defaults.headers.common["Authorization"] =
"Bearer " + localStorage.getItem("accessToken") || null;
axios.defaults.headers.get["Accepts"] = "application/json";
const router = new VueRouter({
routes,
mode: "history",
});
new Vue({
vuetify,
router,
store,
render: h => h(App)
}).$mount('#app')
you can add your navbar component to App .vue
like
<template>
<v-app>
<div>
<div class="flex w-full overflow-auto h-screen bg-page">
<navbar class="w-full flex-1 max-w-72"/>
<div class="px-4 flex-1 max-h-screen overflow-auto">
<div class="pb-16">
<router-view></router-view>
</div>
</div>
</div>
</v-app>
</template>
<script>
import navbar from './navbar'
export default {
components:{
navbar
}
}
</script>
but you should handle login and signup views to make sidebar disappears.
to do this you have several options.
For it to appear in every page, you need to import the navigation component in every page.
So for instance, let's say your navigation file is called TheNavigation.vue, then on every interface you need to do something as following in the template where you want that component to appear-->
<div style="text-align: center;">
<TheNavigation />
<transition name="fade" mode="out-in">
<router-view :key="$route.path" />
</transition>
</div>
Then, in the script, you need to import the component like below
<script>
import TheNavigation from '../components/TheNavigation.vue';
import axios from "axios";
export default {
components: {
TheNavigation
},
data() {
}
}
</script>
Let me know if this worked! Or if you need me to go deeper into this! Make sure to put your relative path when importing the navigation component in the script!
The way I used to do this in React.js was, I used to make a Menu Component then use that component with every MenuItem component.
Like in your case Home MenuItem or others component you can call Menu component in that MenuItem Component
Something like this
const Menu = () => {
return <div>{...Menu}</div>
}
const HomeMenuItem = () => {
return (
<div>
<Menu />
<div>
{...this menu content in here!}
</div>
</div>
)
}
and the same goes for every other menuItem, it's somewhat of a hack but it works.

How can I better animate a modal using CSS?

I am trying to use transitions to change the way a modal I created is shown on the screen. I need the modal to slide in from the left but the code doesn't seem to work. The app is in React.
The JSX code
const Modal = (props)=>{
let ModalClasses = [Styles.Modal];
if(props.show){
ModalClasses = [Styles.Modal, Styles.Open]
}
return(
<div className={ModalClasses.join(' ')}>
<div className={Styles.ModalNav} onClick={props.clicked}>
<div></div>
<div></div>
</div>
</div>
)
}
CSS
.Modal{
background-color: white;
position: absolute;
top: 0;
left: 25%;
width: 50vw;
height: 100vh;
z-index: 2;
transform: translateX(-100%);
transition: transform 500ms ease-out;
}
.Modal.Open{
transform: translateX(0);
}
.ModalNav div{
height: 3px;
width: 20px;
background-color: black;
margin: 5px;
position: absolute;
top: 10px;
right: 0;
cursor: pointer;
}
.ModalNav div:first-of-type{
transform: rotate(45deg);
}
.ModalNav div:last-of-type{
transform: rotate(-45deg);
}
JSX code for the component I am receiving props from
class App extends Component{
state={
showModal: false,
}
showModalHandler = ()=>{
this.setState({showModal:!this.state.showModal})
}
render(){
return (
<div className="App">
{ this.state.showModal ?
<Modal
show={this.state.showModal}
clicked={this.showModalHandler}/> : null}
{ this.state.showModal ?
<Backdrop
clicked={this.showModalHandler}/> : null}
<Table clicked={this.showModalHandler}/>
</div>
);
}
}
export default App;
My goal is to get the modal to slide in but it just pops right in.
https://salesruby.netlify.app/ can be viewed here.
Here's an alternative answer that is more in line with OP's original approach of using CSS transition instead of animating. This also let's you slide the menu back in so it slides both ways.
OP's problem is returning null in the App component if the modal is in a hidden state (which it starts with). So react will not render the modal, until you change the isOpen flag to true - at that point it renders directly in the open state (popping in), and the CSS transition has no effect. Subsequently when you close the modal, it is removed from the DOM, again without time for a transition so it "pops out".
The solution below is to render the modal in its starting state, and use the IsOpen flag to toggle the state of the element, rather than pulling it in and out of the DOM.
Here is a modified sandbox from Cyrus' answer that shows this - isModalOpen flag is used by the Modal to toggle the "open" class. The rest is basically the same as what OP started with.
https://codesandbox.io/s/bold-architecture-9t50w?file=/src/Components/Modal/Modal.js
App (Notice Modal is always returned, no : null - and isModalOpen is bound)
import React, { useState } from "react";
import "./styles.css";
import Modal from "./Components/Modal/Modal";
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => setIsModalOpen(true)}>Open modal </button>
<Modal isModalOpen={isModalOpen} closeModal={setIsModalOpen} />
</div>
);
};
export default App;
Modal (Notice className={isModalOpen ? "open" : ""} to toggle class)
import React from "react";
import "./modal.css";
const Modal = ({ closeModal, isModalOpen }) => {
return (
<div id="modal" className={isModalOpen ? "open" : ""}>
Modal here <button onClick={() => closeModal(false)}> open modal</button>
</div>
);
};
export default Modal;
CSS (simple transform and transition effect)
#modal {
background-color: #000000cc;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
transform: translateX(-100%);
transition: transform 0.5s ease-in-out;
width: 30%;
height: 100%;
}
#modal.open {
transform: translateX(0);
}
so I made this really fast. This is just a demo on how to make the animation slide in from left to right. If you look at the css you can see that I have left : -600px and that is the width of the modal. You can make it dynamic so that it will only take some % of the screen, but don't forget to add a max-width.
here is a link to codesandbox
//app.js
import React, { useState } from "react";
import "./styles.css";
import Modal from "./Components/Modal/Modal";
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => setIsModalOpen(true)}>Open modal </button>
{isModalOpen ? <Modal closeModal={setIsModalOpen} /> : null}
</div>
);
};
export default App;
//Modal.js
import React, { useEffect } from "react";
import "./modal.css";
const Modal = ({ closeModal }) => {
return (
<div id="modal">
Modal here <button onClick={() => closeModal(false)}> open modal</button>
</div>
);
};
export default Modal;
//modal.css
#modal {
background-color: #000000cc;
position: absolute;
top: 0;
left: 100px;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
transition: top 2s;
animation: slideIn 1s;
width: 600px;
height: 600px;
}
#keyframes slideIn {
0% {
left: -600px;
}
100% {
left: 100px;
}
}

Slide Reverse Transitions, Vue Js

I'm developing a single page application / mobile app, with Vue.JS. I want a slide effect when changing the pages, and I can do it like this:
transition name="slide"
router-view transition
transition
But I wanted the reverse effect of the slide when the user returns the page. In other words, when the user opens a new page, the page will come from the right, but when they go back, the page will come from the left.
There is a plugin for Vue router, called vue-router-transition, but it does not work. It is out of date, it only works with very old versions of Vue.
Also there is a tutorial on dynamic transitions, but only works when it is parents routes, ex: example.com/rota1/rota2/rota3, which is not my case.
I thought of the following logic in the before.each.router, set the transition class (slide-left or slide-right) depending on whether the user clicked on a go back button.
The problem is that I do not know how to apply this logic in code. I would have to pass the value of a variable that is in main.js to app.vue and I do not know how to do this.
A while ago I've used the meta object in vue-router and added a "fake" depth, because I haven't any children objects. If you use children, then go with this example: https://github.com/vuejs/vue-router/blob/dev/examples/transitions/app.js
export default () => {
return [{
meta: {
depth: 0
},
path: '/home',
component: Home
},
{
meta: {
depth: 1
},
path: '/about',
component: About
}]
}
Now you can check it by your own like this in your App.vue.
watch: {
$route(to, from) {
const toDepth = to.meta.depth || 0;
const fromDepth = from.meta.depth || 0;
this.transitionName = toDepth >= fromDepth ? 'slide-left' : 'slide-right';
}
}
I see 2 options:
Store a variable in Vuex "transitionBack" and set it to true. Change the router-link to a #click method. In the method, first save the variable and then navigate to the link. Check that variable on your beforeRouteUpdate(to, from, next) method.
Implement some logic in your beforeRouteUpdate(to, from, next) method that will check on the name of the link (e.g. if you can only go "back" from a certain page, then keep a list of all these type of pages)
Hope this helps:
new Vue({
el: '#demo',
data: {
slideTransition: 'slide-left',
showChild: false,
},
watch: {
showChild(value) {
if (value) {
this.setSlideTransition('slide-right');
} else {
this.setSlideTransition('slide-left');
}
},
},
methods: {
setSlideTransition(slideDirection) {
// Note: 300ms mentioned below is matching with css transition timing
setTimeout(() => { this.slideTransition = slideDirection; }, 300);
},
},
});
body {
margin: 0;
color: #bdbdbd;
background-color: #161616;
font-family: Helvetica neue, roboto;
}
.container {
width: 500px;
background: #161616;
}
main {
width: 60%;
height: 300px;
background-color: #333;
}
aside {
width: 40%;
background-color: #555;
}
.container, main, .parent, .child, .content {
display: flex;
align-items: center;
justify-content: center;
}
.parent {
background-color: deepskyblue;
}
.child {
background-color: deeppink;
}
.container, main, aside {
position: relative;
height: 199px;
}
.pin {
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
}
.pt-50 {
padding-top: 50px;
}
/* transitions */
.fade-enter-active,
.fade-leave-active {
transition-property: opacity;
transition-duration: 0.25s;
}
.fade-enter-active {
transition-delay: 0.25s;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
.slide-left-leave-active,
.slide-left-enter-active,
.slide-right-leave-active,
.slide-right-enter-active {
transition: 0.3s;
}
.slide-left-enter {
transform: translate(100%, 0);
}
.slide-left-leave-to {
transform: translate(-100%, 0);
}
.slide-right-enter {
transform: translate(-100%, 0);
}
.slide-right-leave-to {
transform: translate(100%, 0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo" class="container">
<aside>
<transition name="fade">
<div class="pin parent" v-if="!showChild">
<div>
<h1>Parent</h1>
Go To Child
</div>
</div>
</transition>
<transition :name="slideTransition">
<div class="pin child" v-if="showChild">
<div>
<h1>Child</h1>
Go To Parent
</div>
</div>
</transition>
</aside>
<main>
<div>
<h1>Main</h1>
<transition name="fade">
<div v-if="showChild" class="pin content pt-50" key="child">
<h4>Child Content here</h4>
</div>
<div v-else="" class="pin content pt-50" key="parent">
<h4>Parent Content here</h4>
</div>
</transition>
</div>
</main>
</div>

Having Issue on Setting Slideing div Button and Size

Can you please take a look at this demo and let me know how I can separate the .clickme box from the .slidecontent? I need to change the Height of the .slidecontentfor example toheight:300px;` but this also change the grey shadow to 300px
What I need to have is having the .slidecontent with height of 300 and looks like the third (green arrow)
$(function () {
$("#clickme").toggle(function () {
$(this).parent().animate({left:'0px'}, {queue: false, duration: 500});
}, function () {
$(this).parent().animate({left:'-280px'}, {queue: false, duration: 500});
});
});
#slideout {
background: #666;
position: absolute;
width: 300px;
height: 300px;
top: 45%;
left:-280px;
}
#clickme {
float: right;
height: 20px;
width: 20px;
background: #ff0000;
}
#slidecontent {
float:left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="slideout">
<div id="slidecontent">
Yar, there be dragonns herre!
</div>
<div id="clickme">
>
</div>
</div>
I am not 100% sure I understand what you are trying to do but your jQuery code refers to the parent of clickme which is slideout, not slidecontent and it is probable that this is causing the undesired effect. Is there a reason you are not referencing slidecontent directly?

Resources