I have a VueJS application where I have the need to create a component that will have a left menu and a main content like the following image:
Once the view is loaded, on create method I will load left menu json and the items content json. By that time no item is going to be rendered in the main content.
Once the user selects an item from the left menu, the json for the items content is going to be filtered so that the main component renders the squares (each item component that is actually separate component).
My question here is about how many components should I build?
I guess I will have to create a component container that will hold the left menu component and the right component. Then right component will have the header and a v-for element that will render each of the item component. Is that the way to go?
Any advice?
You can create one container component that lays out all three areas using CSS grid. Then you can have a component for each area: left-menu, header-thing, and main-content.
There's a bit of a learning curve with grid layout, but it's immensely useful. Modern browsers support it, but not IE.
new Vue({
el: '#app',
components: {
leftMenu: {
template: '#left-menu-template'
},
mainContent: {
template: '#main-content-template'
},
headerThing: {
template: '<div class="header-thing"><div>one</div><div>two</div></div>'
}
}
});
html,
body {
height: 100%;
}
#app {
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr;
grid-template-areas:
'lm h'
'lm mc'
;
width: 100%;
height: 100%;
}
.header-thing {
background-color: #eef;
grid-area: h;
display: grid;
grid-auto-flow: column;
justify-content: start;
padding: 0.7em;
grid-gap: 1.4em;
}
.left-menu {
background-color: #efe;
grid-area: lm;
padding: 0.7em;
}
.main-content {
background-color: #fee;
grid-area: mc;
display: grid;
grid-gap: 1em;
grid-template-columns: repeat(auto-fit, minmax(8em, 1fr));
grid-auto-rows: 1fr;
}
.content-item {
background-color: white;
border: thin solid black;
border-radius: 0.2em;
display: grid;
align-items: center;
justify-items: center;
padding: 2em 3em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<header-thing></header-thing>
<left-menu></left-menu>
<main-content></main-content>
</div>
<template id="left-menu-template">
<nav class="left-menu">
<div>First item</div>
<div>Second item</div>
</nav>
</template>
<template id="main-content-template">
<div class="main-content">
<div class="content-item">item</div>
<div class="content-item">another</div>
<div class="content-item">another</div>
<div class="content-item">more</div>
<div class="content-item">still more</div>
<div class="content-item">another</div>
</div>
</template>
Related
I've created the following Vue component:
var loading = new Vue({
el: "#loading_id",
components:{
'loading' : {
template : `
<div>
<div id="screenLoading" class="background">
<div class="loading">
<div class="obj"></div>
<div class="obj"></div>
<div class="obj"></div>
<div class="obj"></div>
<div class="obj"></div>
<div class="obj"></div>
<div class="obj"></div>
<div class="obj"></div>
</div>
</div>
</div>
`,
methods: {
startLoading: function() {
const load = document.getElementById("screenLoading");
load.classList.add('show')
},
stopLoading: function(){
const load = document.getElementById("screenLoading");
load.classList.remove('show')
}
}
}
}
});
loading.$children[0].startLoading()
setTimeout(() => {
loading.$children[0].stopLoading()
}, 3500);
.background {
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0,.5);
position: fixed;
top: 0px;
left: 0px;
z-index: 2000;
display: none;
justify-content: center;
align-items: center;
}
.background.show {
display: flex;
}
.loading{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 40px;
display: flex;
align-items: center;
}
.obj{
width: 12px;
height: 70px;
background: white;
margin: 0 3px;
border-radius: 10px;
animation: loading 0.8s infinite;
}
.obj:nth-child(2){
animation-delay: 0.1s;
}
.obj:nth-child(3){
animation-delay: 0.2s;
}
.obj:nth-child(4){
animation-delay: 0.3s;
}
.obj:nth-child(5){
animation-delay: 0.4s;
}
.obj:nth-child(6){
animation-delay: 0.5s;
}
.obj:nth-child(7){
animation-delay: 0.6s;
}
.obj:nth-child(8){
animation-delay: 0.7s;
}
#keyframes loading{
0%{
height: 0;
}
50%{
height: 70px;
}
100%{
height: 0;
}
}
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<loading id="loading_id"></loading>
It works perfectly fine, its purpose is to be a loading screen for me to use when I have to make something wait on my webpage. However, instead of loading the CSS from an external CSS file with the <style> tag on the main HTML, I'd like to load my CSS directly from inside Vue, just like I did with the HTML on the template variable. Basically, it'd be something like the following:
var loading = new Vue({
el: "#loading_id",
components:{
'loading' : {
template : `myHTML template`,
cssTemplate: `myCSS template`,
methods: {
// my methods
}
}
}
});
Is it possible to do on Vue? Can I write CSS templates inside a Vue component?
I know I also can load small pieces of CSS styles with Vue as described here, but in my case, I'd like to keep the classes and ids declaration as they are in a normal CSS file...
You can include css in your vue component in the style tag section as below:
<style scoped src="#/assets/styles/myCSSTemplate.css">
</style>
Also try to use proper structure when creating a vue component by having template, script and style sections.
For exmaple you can create Vue instane in App.vue and then have a component as YourComponent.vue
Refer to Vue documentation here.
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 a List Detail View using Nuxt dynamic routes in SSR mode
The list with overflow-y appears on left and on clicking an item, the contents appear on right
When I click each item, the URL params change and a new item is shown on right
When I press back button the previous item is shown but the scrollbar does not change
How do I make the scrollbar go to the previous item?
Here is the GIF showing the problem, on clicking back, the list should scroll to the previous item
<template>
<div class="root">
<div class="left">
<ul>
<li v-for="i in sortedArticles" :key="i.feedItemId">
<nuxt-link :to="'/articles/' + i.feedItemId" :id="i.feedItemId" no-prefetch>{{ i.title }}</nuxt-link>
</li>
</ul>
</div>
<div class="right">Article {{ $route.params.id }}</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
key(route) {
return 'articles'
},
computed: {
...mapGetters({
sortedArticles: 'news/SORTED_ARTICLES'
})
},
}
</script>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
line-height: 1.8;
}
.root {
display: flex;
flex-direction: row;
height: 100vh;
}
.left {
flex: 1;
overflow-y: auto;
}
.left ul {
list-style-type: none;
}
.left li a {
display: block;
text-decoration: none;
padding: 0.5rem 1rem;
border-bottom: 1px solid #eee;
color: black;
}
.left li a:hover {
color: darkorange;
}
.right {
flex: 1;
}
</style>
The above code is the pages/articles/_id.vue file
How do I achieve this in NUXT
You must be use watch:
<li :ref="'el' + i.feedItemId" v-for="i in sortedArticles" :key="i.feedItemId">
watch: {
'$route.params.id': function(id) {
this.$refs['el' + id].scrollIntoView()
}
}
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>
Using Vue, I have two divs that I want to toggle with a transition. I want slowly expand the div to my desired width on click, and then shrink on another click. I can get the div to expand on one click, but can't figure out how to shrink it on the second click.
Not really clear if just specifying the width of the divs is enough, or if I also have to specify the same width in the css transition classes.
This fiddle shows what I've been trying: https://jsfiddle.net/vxmh8auo/1/
JS
new Vue({
el: '#app',
data: {
showButton: true
},
methods: {
randomise () { this.n = Math.random() }
},
components:{'input-div':blah}
});
CSS
.interaction {
border: 10px solid lightgreen;
display: flex;
flex: 1 0 auto;
max-height: 225px;
transition: max-height 0.25s ease-out;
}
.default {
width: 20px;
}
.bigger{
width: 200px;
}
.expand-enter-active, .expand-leave-active {
transition-property: width;
transition-duration: 5s;
}
.expand-leave-to {
width: 200px;
}
.expand-enter{
width: 20px;
}
HTML
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<div class="interaction">
<button #click="showButton=!showButton">
<transition name="expand" mode="out-in">
<div v-if="showButton" v-bind:class="showButton ? 'default':'bigger'" key="small"> B </div>
<div v-else class="bigger" key="big"> Bigger </div>
<!--<input-div><</input-div>-->
</transition>
</button>
</div>
</div>
You can do something like this, add class 'default' and toggle class 'bigger':
<button #click="toggleBigger">
<transition name="expand" mode="out-in">
<div class="default" v-bind:class="{ bigger: showButton }" key="small"> B </div>
<!--<input-div><</input-div>-->
</transition>
</button>
Add method toggleBigger, its more readable this way:
methods: {
toggleBigger(){
this.showButton = !this.showButton;
}
And add transitions on your css classes directly like this:
.default {
transition: max-height 0.25s ease-out;
width: 20px;
transition-property: width;
}
.bigger{
transition: max-height 0.25s ease-out;
width: 200px;
transition-duration: 5s;
transition-property: width;
}
You can see working fiddle HERE
PS I did not removed unnecessary classes from fiddle.
This seems so hackey and containing a lot of unnecessary classes, but it works: https://jsfiddle.net/df70pk68/
Again, my use case is a button that expands to a component with an input box and then shrinks again. My solution was to immediately make the component opacity zero, then slowly shrink it. The prevents the input box from breaching the border of the shrinking div. I would love it if someone could figure out a more elegant way to do this....
HTML
<div id="app">
<div class="interaction">
<button #click="showButton=!showButton">
<transition name="fade" mode="out-in">
<div v-if="showButton" class="default" key="small"> B </div>
<input-div class="bigger" v-else><</input-div>
</transition>
</button>
</div>
</div>
CSS
.interaction {
border: 10px solid lightgreen;
display: flex;
flex: 1 0 auto;
max-height: 225px;
}
JS
const blah = Vue.component('input-div',{
template: '<div><input type="text" readonly></div>'
});
new Vue({
el: '#app',
data: {
showButton: true
},
components:{'input-div':blah}
});
.default {
width: 20px;
}
.bigger{
width: 250px;
}
.fade-leave-active {
transition: all 5s ease;
}
.fade-leave-to{
width: 300px;
}
.bigger.fade-leave-to{
width:20px;
opacity: 0
}
.bigger.fade-leave-active{
transition: opacity 0s ease;
transition: width 5s ease;
}
maybe you should forget vue's support on this, only use css3 can do that, and very simple.
first ,this is your div's code
<div id="my-div"></div>
#my-div{
transition: width 0.5s; /* this is the key code you need */
}
then, you can change the width of my-div use vue or js-dom or whatever, the magic thing will happen.