Handle key press by function in VueJs - javascript

in my component I am using VueStrap's modal like this:
<template>
<modal-window v-model="show" v-on:keyup="keyHandler($event)" #ok="submit()" #cancel="cancel()" #closed="close()" ... >
...
</modal-window>
...
</template>
<script>
...
methods: {
keyHandler (event) {
console.log(event);
}
},...
</script>
I want handle key press when that modal is opened and ensure submit modal when enter pressed or close modal when esc pressed.
I added custom function keyHandler which is unfortunately never fired. Can you tell me how to fix code to handle key press in that function? Or when is better way how to close and submit vue strap modal I will be grateful for advice. Thank you.

You can attach your event handler to window, that way you can receive all key events and act accordingly depending on your modal's state:
Vue.component('modal', {
template: '<div>test modal</div>',
});
new Vue({
el: "#app",
created() {
window.addEventListener('keydown', (e) => {
if (e.key == 'Escape') {
this.showModal = !this.showModal;
}
});
},
data: {
showModal: true
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<modal v-show="showModal"></modal>
</div>

easiest way
<input v-on:keyup.13="whatkey()" type="text"> <br>
looks for if the enter key is pressed then fires a method called whatkey.

Alternatively, you may want to consider using the v-hotkey directive for key input in Vue (docs, github). This will keep your code relatively clean and simple if you must consider several different key inputs.
1.  Install it:
npm i --save v-hotkey
 Have Vue 'use' it:
import VueHotkey from "v-hotkey";
Vue.use(VueHotkey);
3.  Apply it to your Vue components, like so:
<template>
<modal-window ... v-hotkey="keymap">
...
</modal-window>
</template>
<script>
...
data() {
return {
showModal: false
};
},
computed: {
keymap() {
return {
"esc": this.toggleModal
};
}
},
methods: {
toggleModal() {
this.showModal = !this.showModal;
}
}
</script>

Related

onclick event not firing in vue.js

I have the following Vue.js template :
<script type="text/x-template" id="ti-page-inquire">
<div>
<h3 class="mdc-typography--headline3">{{page.name}}</h3>
<ti-button v-bind:button="page.button" v-on:click="onSubmit"></ti-button>
</div>
</script>
<script type="text/x-template" id="ti-button">
<button class="mdc-button mdc-button--raised" v-bind:title="button.name">{{button.name}}</button>
</script>
script
Vue.component('ti-page-inquire', {
props: ['page'],
template: '#ti-page-inquire',
methods : {
onSubmit : function() {
alert(1);
}
}
});
Vue.component('ti-button', {
props: ['button'],
template: '#ti-button',
mounted: function () {
// ripple on button
mdc.ripple.MDCRipple.attachTo(this.$el);
}
});
when I click on my custom button, nothing happens. I think it's because its looking for onSubmit in the ti-button component, but how do I get it to look in the ti-page-inquire component?
Components are black boxes you should catch all events inside it and emit them to the outer world.
Fiddle example
Vue.component('ti-button', {
props: ['button'],
template: '#ti-button',
mounted: function () {
// ripple on button
mdc.ripple.MDCRipple.attachTo(this.$el);
},
methods: {
buttonClicked: function() {
this.$emit('button-clicked');
}
}
});
<script type="text/x-template" id="ti-page-inquire">
<div>
<h3 class="mdc-typography--headline3">{{page.name}}</h3>
<ti-button v-bind:button="page.button" v-on:button-clicked="onSubmit"></ti-button>
</div>
</script>
<script type="text/x-template" id="ti-button">
<button class="mdc-button mdc-button--raised" v-bind:title="button.name" #clicked="buttonClicked">{{button.name}}</button>
</script>
This might be because you need to listen for a native click event. So you need to use the .native modifier ..
<ti-button v-bind:button="page.button" v-on:click.native="onSubmit"></ti-button>
This will only work if the button is the root element of your ti-button component. Otherwise you'll have to pass your event listeners to your button in the ti-button component like this ..
<button v-on="$listeners" ...> ... </button>
Try to emit an event from ti-button component to the parent one by using this.$emit function :
Vue.component('ti-button', {
props: ['name'],
template: '#vButton',
data: {
name: 'hi'
},
methods: {
submit() {
this.$emit('submit')
}
}
});
<template id="vButton">
<button v-bind:title="name" #click="submit">{{name}}</button>
</template>
the emitted event submit it called in the parent component like v-on:submit="onSubmit" and handled using onSubmit method:
<script type="text/x-template" id="ti-page-inquire">
<div>
<h3 class="mdc-typography--headline3">{{page.name}}</h3>
<ti-button v-bind:button="page.button" v-on:submit="onSubmit"></ti-button>
</div>
</script>
Vue.component('ti-page-inquire', {
props: ['page'],
template: '#ti-page-inquire',
methods : {
onSubmit : function() {
alert(1);
}
}
});
Sometimes you need also to emit some parameters, so you could do it like :
this.$emit('submit',params)
params could be of any type

Dynamically changing props

On my app, I have multiple "upload" buttons and I want to display a spinner/loader for that specific button when a user clicks on it. After the upload is complete, I want to remove that spinner/loader.
I have the buttons nested within a component so on the file for the button, I'm receiving a prop from the parent and then storing that locally so the loader doesn't show up for all upload buttons. But when the value changes in the parent, the child is not getting the correct value of the prop.
App.vue:
<template>
<upload-button
:uploadComplete="uploadCompleteBoolean"
#startUpload="upload">
</upload-button>
</template>
<script>
data(){
return {
uploadCompleteBoolean: true
}
},
methods: {
upload(){
this.uploadCompleteBoolean = false
// do stuff to upload, then when finished,
this.uploadCompleteBoolean = true
}
</script>
Button.vue:
<template>
<button
#click="onClick">
<button>
</template>
<script>
props: {
uploadComplete: {
type: Boolean
}
data(){
return {
uploadingComplete: this.uploadComplete
}
},
methods: {
onClick(){
this.uploadingComplete = false
this.$emit('startUpload')
}
</script>
Fixed event name and prop name then it should work.
As Vue Guide: Custom EventName says, Vue recommend always use kebab-case for event names.
so you should use this.$emit('start-upload'), then in the template, uses <upload-button #start-upload="upload"> </upload-button>
As Vue Guide: Props says,
HTML attribute names are case-insensitive, so browsers will interpret
any uppercase characters as lowercase. That means when you’re using
in-DOM templates, camelCased prop names need to use their kebab-cased
(hyphen-delimited) equivalents
so change :uploadComplete="uploadCompleteBoolean" to :upload-complete="uploadCompleteBoolean"
Edit: Just noticed you mentioned data property=uploadingComplete.
It is easy fix, add one watch for props=uploadComplete.
Below is one simple demo:
Vue.config.productionTip = false
Vue.component('upload-button', {
template: `<div> <button #click="onClick">Upload for Data: {{uploadingComplete}} Props: {{uploadComplete}}</button>
</div>`,
props: {
uploadComplete: {
type: Boolean
}
},
data() {
return {
uploadingComplete: this.uploadComplete
}
},
watch: { // watch prop=uploadComplete, if change, sync to data property=uploadingComplete
uploadComplete: function (newVal) {
this.uploadingComplete = newVal
}
},
methods: {
onClick() {
this.uploadingComplete = false
this.$emit('start-upload')
}
}
})
new Vue({
el: '#app',
data() {
return {
uploadCompleteBoolean: true
}
},
methods: {
upload() {
this.uploadCompleteBoolean = false
// do stuff to upload, then when finished,
this.uploadCompleteBoolean = true
},
changeStatus() {
this.uploadCompleteBoolean = !this.uploadCompleteBoolean
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<button #click="changeStatus()">Toggle Status {{uploadCompleteBoolean}}</button>
<p>Status: {{uploadCompleteBoolean}}</p>
<upload-button :upload-complete="uploadCompleteBoolean" #start-upload="upload">
</upload-button>
</div>
The UploadButton component shouldn't have uploadingComplete as local state (data); this just complicates the component since you're trying to mix the uploadComplete prop and uploadingComplete data.
The visibility of the spinner should be driven by the parent component through the prop, the button itself should not be responsible for controlling the visibility of the spinner through local state in response to clicks of the button.
Just do something like this:
Vue.component('upload-button', {
template: '#upload-button',
props: ['uploading'],
});
new Vue({
el: '#app',
data: {
uploading1: false,
uploading2: false,
},
methods: {
upload1() {
this.uploading1 = true;
setTimeout(() => this.uploading1 = false, Math.random() * 1000);
},
upload2() {
this.uploading2 = true;
setTimeout(() => this.uploading2 = false, Math.random() * 1000);
},
},
});
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<div id="app">
<upload-button :uploading="uploading1" #click="upload1">Upload 1</upload-button>
<upload-button :uploading="uploading2" #click="upload2">Upload 2</upload-button>
</div>
<template id="upload-button">
<button #click="$emit('click')">
<template v-if="uploading">Uploading...</template>
<slot v-else></slot>
</button>
</template>
Your question seems little bit ambiguë, You can use watch in that props object inside the child component like this:
watch:{
uploadComplete:{
handler(val){
//val gives you the updated value
}, deep:true
},
}
by adding deep to true it will watch for nested properties in that object, if one of properties changed you ll receive the new prop from val variable
for more information : https://v2.vuejs.org/v2/api/#vm-watch
if not what you wanted, i made a real quick example,
check it out hope this helps : https://jsfiddle.net/K_Younes/64d8mbs1/

binding a ref does not work in vue.js?

When I v-bind a element-ref with :ref="testThis" it stops working it seems. Compare this version which works:
<template>
<div>
<q-btn round big color='red' #click="IconClick">
YES
</q-btn>
<div>
<input
ref="file0"
multiple
type="file"
accept=".gif,.jpg,.jpeg,.png,.bmp,.JPG"
#change="testMe"
style='opacity:0'
>
</div>
</div>
</template>
<script>
import { QBtn } from 'quasar-framework'
export default {
name: 'hello',
components: {
QBtn
},
data () {
return {
file10: 'file0'
}
},
methods: {
IconClick () {
this.$refs['file0'].click()
},
testMe () {
console.log('continue other stuff')
}
}
}
</script>
With this one which DOES NOT work:
<template>
<div>
<q-btn round big color='red' #click="IconClick">
YES
</q-btn>
<div>
<input
:ref="testThis"
multiple
type="file"
accept=".gif,.jpg,.jpeg,.png,.bmp,.JPG"
#change="testMe"
style='opacity:0'
>
</div>
</div>
</template>
<script>
import { QBtn } from 'quasar-framework'
export default {
name: 'hello',
components: {
QBtn
},
data () {
return {
file10: 'file0'
}
},
methods: {
IconClick () {
this.$refs['file0'].click()
},
testThis () {
return 'file0'
},
testMe () {
console.log('continue other stuff')
}
}
}
</script>
The first one works. The second one throws an error:
TypeError: Cannot read property 'click' of undefined
at VueComponent.IconClick
As I would like to vary the ref based on a list-index (not shown here, but it explains my requirement to have a binded ref) I need the binding. Why is it not working/ throwing the error?
In the vue docs I find that a ref is non-reactive: "$refs is also non-reactive, therefore you should not attempt to use it in templates for data-binding."
I think that matches my case.
My actual problem 'how to reference an item of a v-for list' is NOT easily solved not using a binded ref as vue puts all similar item-refs in an array, BUT it loses (v-for index) order.
I have another rather elaborate single file component which works fine using this piece of code:
:ref="'file' + parentIndex.toString()"
in an input element. The only difference from my question example is that parentIndex is a component property.
All in all it currently is kind of confusing as from this it looks like binding ref was allowed in earlier vue version.
EDIT:
Triggering the method with testThis() does work.
If you want to use a method, you will need to use the invocation parentheses in the binding to let Vue know you want it to bind the result of the call and not the function itself.
:ref="testThis()"
I think the snippet below works as you expect it to. I use a computed rather than a method.
new Vue({
el: '#app',
data() {
return {
file10: 'file0'
}
},
computed: {
testThis() {
return 'file0';
}
},
methods: {
IconClick() {
this.$refs['file0'].click()
},
testMe() {
console.log('continue other stuff')
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<q-btn round big color='red' #click="IconClick">
YES
</q-btn>
<div>
<input :ref="testThis" multiple type="file" accept=".gif,.jpg,.jpeg,.png,.bmp,.JPG" #change="testMe" style='opacity:0'>
</div>
</div>

Vue event handler on dynamically inserted string does not work

This is my code:
<template>
<div>
<div v-html="data"></div> <button v-on:click="replace">Click Me to replace div contents</button>
</div>
</template>
<script>
export default {
data() {
return {
data: "I will be replaced once you click on button"
}
},
methods: {
clickMe() {
alert("worked");
},
replace(){
this.data = "Why does click me not work? It is loaded from server via ajax <a href v-on:click.prevent='clickMe'>Click Me</a>";
}
}
};
</script>
Here if I click on Click Me to replace div contents the content is replaced but the event handler clickMe does not fire. This data would come from server and I need to compile this string and use it from within the Vue's context so Vue can handle events etc.
How can I have the dynamic string downloaded from server work? I am using Vue 2.
Since v-html isn't compiled you will have to create a mini component like this to get around the issue:
new Vue({
el: '#app',
data () {
return {
data: ``
}
},
computed: {
compiledData () {
return {
template: `<p>${this.data}</p>`
}
}
},
methods: {
replace () {
this.data = `Now click on me <a href='#' #click.prevent='alert("yo")'> here </a>`
}
}
})
<script src="https://unpkg.com/vue#2.5.3/dist/vue.min.js"></script>
<div id="app">
<component :is="compiledData" ></component>
<button v-on:click="replace">Click Me to replace div contents</button>
</div>
The above code compiles the string content and thus you can run/execute the function as intended
Other solution using Vue components (codepen):
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div id="someId"></div> <button v-on:click="replace">Click Me to replace div contents</button>
<component :is="currentView"></component>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
currentView: null
},
methods:{
replace: function(){
var templateFromServer = getTemplate();
var comp=Vue.component('template-from-server', {
template: templateFromServer,
methods:{
clickMe:function (){
console.log("click");
}
}
});
this.currentView = comp;
}
}
});
function getTemplate(){
return "<a href v-on:click.prevent='clickMe'>Click Me</a>"
}
</script>
v-html is not compiled as a Vue template. From the docs:
Note that the contents are inserted as plain HTML - they will not be compiled as Vue templates. If you find yourself trying to compose templates using v-html, try to rethink the solution by using components instead.
see: https://v2.vuejs.org/v2/api/#v-html
You can not render VueJS code from a html string.
You can solve this issue by using v-if
<div>
<div v-if="data">I will be replaced once you click on button</div>
<div v-else>Why does click me not work? It is loaded from server via ajax <a href #click.prevent='clickMe'>Click Me</a></div>
<button #click="replace">Click Me to replace div contents</button>
</div>
<script>
export default {
data() {
return {
data: true
}
},
methods: {
clickMe() {
alert("worked");
},
replace(){
this.data = !this.data;
}
}
};
You can call normal javascript function from string but not vuejs function so onclick event would also work.

VueJS 2 - How to Pass Parameters Using $emit

I am working on a modal component using VueJS 2. Right now, it basically works -- I click on a button and the modal opens, etc.
What I want to do now is create a unique name for the modal and associate the button with that particular button.
This is what I have in mind. The modal has a unique name property:
<modal name='myName'>CONTENT</modal>
And this would be the associate button:
<button #click="showModal('myName')"></button>
What I need to figure out is how to pass the parameter of showModal to the modal component.
Here is the method that I'm using in the root vue instance (i.e, NOT inside my modal component):
methods: {
showModal(name) { this.bus.$emit('showModal'); },
}
What I want to do is to access the name property in the component -- something like this:
created() {
this.bus.$on('showModal', () => alert(this.name));
}
But this shows up as undefined.
So what am I doing wrong? How can I access the name property inside the modal component?
NOTE: If you are wondering what this.bus.$on is, please see the following answer to a previous question that I asked: https://stackoverflow.com/a/42983494/7477670
Pass it as a parameter to $emit.
methods: {
showModal(name) { this.bus.$emit('showModal', name); },
}
created() {
this.bus.$on('showModal', (name) => alert(name));
}
Also, if you want to give the modal a name, you need to accept it as a prop in the modal component.
Vue.component("modal",{
props:["name"],
...
})
Then I assume you will want to do something like,
if (name == this.name)
//show the modal
<!-- File name is dataTable.vue -->
<template>
<div>
<insertForm v-on:emitForm="close"></insertForm>
</div>
</template>
<script>
import InsertForm from "./insertForm";
import Axios from "axios";
export default {
components: {
InsertForm
},
data: () => ({
}),
methods: {
close(res) {
console.log('res = ', res);
}
}
};
</script>
<!-- File name is insertForm.vue -->
<template>
<div>
<v-btn #click.native="sendPrameter">
<v-icon>save</v-icon>
</v-btn>
</div>
</template>
<script>
export default {
data: () => ({
mesage:{
msg:"Saved successfully",
color:'red',
status:1
}
}),
methods: {
sendPrameter: function() {
this.$emit("emitForm", this.mesage);
}
}
};
</script>
https://vuejs.org/v2/guide/components-custom-events.html

Categories

Resources