Vue.js Modifiers Chain - vue.js

I am new in Vue.js. I am reading docs, and can't understand one moment...
https://v2.vuejs.org/v2/guide/events.html
Order matters when using modifiers because the relevant code is
generated in the same order. Therefore using #click.prevent.self will
prevent all clicks while #click.self.prevent will only prevent clicks
on the element itself.
I can't understand what's mean'Therefore using #click.prevent.self will prevent all clicks while #click.self.prevent will only prevent clicks on the element itself.' Can anybody give an example with prevent default actions and show difference...
for example with links (#click.prevent.self="fn" or #click.self.prevent="fn" difference)
I know about event phases (capture, target and bubbling).
For example it can be useful in Bubbling phase:
<script src="https://unpkg.com/vue/dist/vue.js"></script>
.root {
width: 300px;
height: 300px;
background: green;
text-align: center;
color: white;
}
.parent {
width: 200px;
height: 200px;
background: red;
text-align: center;
color: white;
margin: 50px
}
.child {
width: 100px;
height: 100px;
background: blue;
margin: 50px;
text-align: center;
color: white;
}
<div id="app">
<div class="root" v-on:click="log('root')">root
<div class="parent" v-on:click.self.stop="log('parent')">Parent
<div class="child" v-on:click="log('child')">Child
</div>
</div>
</div>
</div>
<script type="text/javasctipt">
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
methods: {
log(message) {
alert(message);
console.log(message)
}
}
})
</script>
If i click on child i'll get output:
child
root
Bubblig doesn't stopped! But if i click on parent bubbling stopped! It's helpful!
But can anybody show me examples and difference if i'll use prevent and self modifiers (with links, checkboxes or may be with another elements) ?
Thanks for advance!

You can check this example:
https://jsfiddle.net/50wL7mdz/39994/
If you use #click.prevent.self, you can't click on anything. It prevents all click.
If you use #click.self.prevent, it only prevents when you click on <a> element, we can still click on choose file to upload file

Example illustrates difference in chain order
<div id="app">
<a href="https://stackoverflow.com" #click.self.prevent target="_blank">Stackofervlow!
<span :style="{ background: 'yellow', width: '100px', height: '100px' }">span</span>
</a>
</div>
new Vue({
el: '#app'
});
#click.self.prevent
If you click on span, stackoverflow will open.
If you click on a, stackoverflow will not open.
#click.prevent
If you click on span, stackoverflow will not open.
If you click on a, stackoverflow will not open.

Related

Super small Vue button

I'm learning Vue, and even with the simplest examples there is something wrong. For example, buttons. I have a defined component, myButton, responds to clicks, but it doesn't look like it should, is super small and dont have any label. What am I doing wrong?
Part of index.js:
Vue.component('mybutton', {
props: {
buttonLabel: String,
},
template: '<button #click="onClick()" class="btn">{{ buttonLabel }}</button>',
methods: {
onClick(){
console.log('Click');
}
},
})
Part of index.html:
<div id="app">
<mybutton text="From Vue"></mybutton>
<button class="btn">Test</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="index.js"></script>
And CSS:
.btn {
display: inline-block;
background: #000;
color: #fff;
border: none;
padding: 10px,20px;
border-radius: 5px;
text-decoration: none;
font-size: 15px;
font-family: inherit;
}
Your prop is called buttonLabel, while you pass a property called text inside your index.html. Therefore, the button doesn't get any text and then it's rendered without any inner content (and therefore slim, since you didn't give it fixed width and height).
You need to change the part of index.html and replace text with button-label (Vue automatically maps buttonLabel to it, and it is the better option. Using buttonLabel might not work in this case, since you are not using single file components.
Call it like
<mybutton mylabel="hI"></mybutton>
Vue.component('mybutton', {
props: ['mylabel'],
template: '<button>{{ mylabel }}</button>'
})
https://codepen.io/flakerimi/pen/wvgGqVb
https://v2.vuejs.org/v2/guide/components.html

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>

VueJS left menu list that updates main content panel

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>

Append child to $slot.default

I have a component that I need display some custom modal on screen. I don't know where I should put this dialog content, so I did something like that:
<template>
<div class="ComponentItself">
<div v-show="false" ref="ModalContent">
Hello!
</div>
<button v-on:click="showModal">Show modal</button>
</div>
</template>
[...]
Note: I could not set the tag name of [ref=ModalContent] to template because the vue reserves this tag to another feature.
My idea is when I click on "show modal" it open creates an instance of another component (v-dialog) that I have created with the [ref=ModalContent] content (it should be compiled to support nested vue components).
import Dialog from './Dialog';
const DialogCtor = Vue.extend(Dialog);
const dialog = new DialogCtor({ propsData: {...} });
dialog['$slots'].default = [ this.$refs['templateNewFolder'].innerHTML ];
{something like document.body.appendChild(dialog.$el)}
This another component have a slot that could receives the HTML content to be displayed inside of that. And it just not works. The modal is displayed, but the slot content is undefined or the HTML content not parsed.
<div class="Dialog">
[...]
<slot></slot>
[...]
</div>
The current result is something like:
What I need:
I need to know if I am on the right way. I have about the component feature, but I could not identify or understand if it is/could resolve my problem;
What I could do to make it work;
Some similar project could help it, but I could not found anyone;
Maybe I could resolve my problem if is possible I just .appendChild() directly to $slot.default, but it is not possible;
It seems to me this might be a case of an XY problem.
What probably happens is that you do not need to manually fill $slot.default, but use your Dialog component a more standard way. Since there is little detail about the latter in your question, that component might also need some refactoring to fit this "standard way".
So a more standard approach would be to directly use your <custom-dialog> component in the template of your parent, instead of using a placeholder (the one you reference as ModalContent) that you have to hide. That way, whatever HTML you pass within that <custom-dialog> will be fed into your Dialog's <slot> (designed beaviour of slot).
That way you also save the hassle of having to manually instantiate your Dialog component.
Then you can toggle your <custom-dialog> visibility (with v-if or v-show) or even manipulate its position in the DOM as you mention in your code; you can access its DOM node as $el: this.$refs.ModalContent.$el when ModalContent is a Vue instance.
You could also factorize the showModal method by delegating it to the Dialog component.
Code example:
Vue.component('modal-dialog', {
template: '#modal-dialog',
data() {
return {
modalShown: false,
};
},
methods: {
showModal() {
this.modalShown = true;
},
hideModal() {
this.modalShown = false;
},
},
});
new Vue({
el: '#app',
methods: {
showModal() {
this.$refs.ModalContent.showModal();
},
},
});
/*
https://sabe.io/tutorials/how-to-create-modal-popup-box
MIT License https://sabe.io/terms#Licensing
*/
.modal {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
visibility: hidden;
transform: scale(1.1);
transition: visibility 0s linear 0.25s, opacity 0.25s 0s, transform 0.25s;
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 1rem 1.5rem;
width: 24rem;
border-radius: 0.5rem;
}
.close-button {
float: right;
width: 1.5rem;
line-height: 1.5rem;
text-align: center;
cursor: pointer;
border-radius: 0.25rem;
background-color: lightgray;
}
.close-button:hover {
background-color: darkgray;
}
.show-modal {
opacity: 1;
visibility: visible;
transform: scale(1.0);
transition: visibility 0s linear 0s, opacity 0.25s 0s, transform 0.25s;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<modal-dialog ref="ModalContent">
Hello!
</modal-dialog>
<h1>Hello World</h1>
<button v-on:click="showModal">Show modal</button>
</div>
<template id="modal-dialog">
<div class="modal" :class="{'show-modal': modalShown}" #click="hideModal">
<div class="modal-content">
<span class="close-button" ref="closeButton" #click="hideModal">×</span>
<slot></slot>
</div>
</div>
</template>
Now if you really want to fiddle with $slot, #Sphinx's linked answer in the question comments is an acceptable approach. Note that the accepted answer there also favours the standard usage. It seems to me this is also what #Sphinx implies in their 2nd comment.

How to make a link as a file input in vue.js 2?

My vue component like this :
<template>
...
<a href="javascript:;" class="thumbs"
:title="upload">
<span class="fa fa-plus fa-2x"></span>
</a>
...
</template>
<script>
export default {
props: ['...'],
data() {
return {
...
};
},
computed:{
...
}
}
</script>
I want if click the a link, it can upload file
In javascript, I know it. If javascript like this : How to make a link act as a file input
But How can I do it in vue.js 2?
I believe there is a small misunderstanding: Vue.js 2 is still javascript. Its goal is not the same as Polymer with its fancy components - it is supposed to enhance JS, not replace it with a different structure altogether.
#David Hallberg Jönsson's answer will work perfectly fine in Vue.js 2 perfectly fine. If you want it specifically in Vue's component structure:
<template>
<!-- ... -->
<a class="fileContainer">
Click here to trigger the file uploader!
<input type="file">
</a>
<!-- ... -->
</template>
<script>
export default {
props: ['...'],
data() {
return {
...
};
},
computed:{
...
}
}
</script>
<style>
a.fileContainer {
overflow: hidden;
position: relative;
cursor: pointer;
display: inline-block;
color: lightskyblue;
}
a.fileContainer:hover {
text-decoration: underline;
color: blue;
}
a.fileContainer > input[type=file] {
cursor: inherit;
filter: alpha(opacity=0);
opacity: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: right;
}
</style>
If you want to use the programmatic way in your link, it's not going to be easy because some browsers don't allow you to trigger click events on input type="file" elements. Your best bet would be to go this way.
(Also, technically you can still use jQuery with Vue, so the code in that link could still work if you wanted it to.)
If you want to know how to handle uploading files, there are many tutorials and some components already pre-made.
You can actually do this using only CSS, as explained here.
Example (from the link above):
.fileContainer {
overflow: hidden;
position: relative;
}
.fileContainer [type=file] {
cursor: inherit;
display: block;
font-size: 999px;
filter: alpha(opacity=0);
min-height: 100%;
min-width: 100%;
opacity: 0;
position: absolute;
right: 0;
text-align: right;
top: 0;
}
/* Example stylistic flourishes */
.fileContainer {
background: red;
border-radius: .5em;
float: left;
padding: .5em;
}
.fileContainer [type=file] {
cursor: pointer;
}
}
<p>So various methods prevent file upload inputs from being styled conveniently. But that needn't be the case!</p>
<label class="fileContainer">
Click here to trigger the file uploader!
<input type="file"/>
</label>