Vuejs : how to hide a component <div> on keyup escape? - vue.js

Using Vue 2.5 I'm trying to hide a component if the esc key is pressed.
Inspired by the documentation on key modifiers, I wrote the following code but with no effect (for the moment I'm not hiding, just displaying a message):
Vue.component('my-component', {
data: function () {
return {isVisible:true,
message:'no key pressed'}
},
template: '<div v-on:keyup.esc="myMethod" v-if="isVisible">This is my component<div>{{message}}</div>',
methods:{
myMethod : function(){
this.message = 'esc key pressed'
//My hiding action...
}
}
})
new Vue({
el: '#app',
data: {
}
})
Fiddle
EDIT : looks like the issue is related to the fact I'm trying to implement this on a regular div, not on an input as it's usually used

I think you should add
created: function() {
document.addEventListener('keyup', this.myMethod);
}
And in your method:
myMethod(event) {
if (event.keyCode === 27) {
this.message = 'esc key pressed'
console.log('esc key pressed');
}
}
Here is working example: https://jsfiddle.net/uzxugzo7/9/
Also, don't forget to destroy it, to prevent memory leaks
destroyed: function() {
document.removeEventListener('keyup', this.myMethod);
}

To make static elements accessible to keyboard event use tabindex
<div v-on:keyup.esc="myMethod" tabindex="0" v-if="isVisible">This is my component<div>{{message}}</div>

I would use something like
mounted() {
window.addEventListener('keyup', e => {
if (e.keyCode === 27) {
* Note keyCode 27 is ESC
// do stuff here
}
})
}

Assuming you want to detect whenever the escape key is pressed anywhere inside the webpage, add an eventlistener to your application (not components) when your application is mounted (not created). For demonstration's purpose, I made use of event bus to show how the components receives the escape-key-pressed event.
(To test this snippet, click the output window first)
Vue.component('custom-component', {
template: `
<div>
<div v-show="show">
Hide when Escape Key is pressed.
</div>
<button v-on:click="reset()">Reset</button>
</div>`,
data() {
return {
show: true
}
},
created() {
window.eventBus.$on('escape-key-pressed', (val) => {
this.show = false
alert("escape key is pressed")
})
},
methods: {
reset() { this.show = true }
}
});
window.eventBus = new Vue({})
const app = new Vue({
el: "#app",
mounted() {
window.addEventListener('keyup', this.handler);
},
destroyed() {
window.removeEventListener('keyup', this.handler)
},
methods: {
handler() {
// If escape key is pressed...
if (event.keyCode == 27) {
window.eventBus.$emit('escape-key-pressed', true);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
Example
<custom-component></custom-component>
</div>

Related

vue-js-modal not showing in HTML

I checked the demo and confirmed it does work in the demo way. But I need to use the components in HTML but not in webpack-ed a modal.vue template. So I wrote the following:
<body>
<div id="app">
<modal name="test">
test
</modal>
</div>
<script src="{% static 'js/tmpdev.bundle.js' %}" type="text/javascript"></script>
<script>
Vue.use(VueJsModal, {
dialog: true,
dynamicDefaults: {
draggable: true
}
})
new Vue({
el: '#app',
components: {
},
data() {
return {
};
},
mounted() {
window.addEventListener('keydown', (e) => {
if (e.key == 'Escape') {
this.toggle();
}
});
this.$modal.show('test');
},
methods: {
toggle() {
this.$modal.show('test');
}
},
filters: {
},
});
</script>
</body>
The above should display the modal window on the screen immediately when I reloaded the tab or additionally when I pressed "esc" key. But for some reason this doesn't show the modal. The console log has no error on it, I can see <div id="modals-container"></div> on the HTML source, the this.$modal.show('test'); part is executed without an error. So I have no clue now. What is wrong with the code above? Thanks.
You should add Escape key EventListener code in the created life cycle hook.
window.addEventListener('keydown', this.toggle);
In methods object :
toggle(event) {
if (event.keyCode === 27) {
this.$modal.show('test');
console.log('esc key pressed');
}
}
Once you done with the requirement, You can destroy this event to prevent memory leaks.
destroyed: function() {
document.removeEventListener('keydown', this.toggle);
}

How do I trigger a recalculation in a Vue app?

I'm working on a project with Vue and VueX. In my component, I have a calculated method that looks like this:
...mapState([
'watches',
]),
isWatched() {
console.log('check watch');
if (!this.watches) return false;
console.log('iw', this.watches[this.event.id]);
return this.watches[this.event.id] === true;
},
And in my store, I have the following:
addWatch(state, event) {
console.log('add', state.watches);
state.watches = {
...state.watches,
[event]: true,
};
console.log('add2', state.watches);
},
However, this doesn't trigger a recalculation. What's going on?
Try changing return this.watches[this.event.id] === true;
to
return this.$store.commit("addWatch", this.event.id);
The code you have shown is correct, so the problem must be elsewhere.
I assume by 'calculated method' you mean computed property.
Computed properties do not watch their dependencies deeply, but you are updating the store immutably, so that is not the problem.
Here is a bit of sample code to give you the full picture.
Add event numbers until you hit '2', and the isWatched property becomes true.
Vue.use(Vuex);
const mapState = Vuex.mapState;
const store = new Vuex.Store({
state: {
watches: {}
},
mutations: {
addWatch(state, event) {
state.watches = { ...state.watches, [event]: true };
}
}
});
new Vue({
el: "#app",
store,
data: {
numberInput: 0,
event: { id: 2 }
},
methods: {
addNumber(numberInput) {
this.$store.commit("addWatch", Number(numberInput));
}
},
computed: {
...mapState(["watches"]),
isWatched() {
if (!this.watches) return false;
return this.watches[this.event.id] === true;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.0/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>Watches: {{ watches }}</div>
<div>isWatched: {{ isWatched }}</div>
<br>
<input v-model="numberInput" type="number" />
<button #click="addNumber(numberInput)">
Add new event
</button>
</div>

How to build a bridge of events between different components in Vue?

I need to solve it
1) click mainMidLeft component
2) after clicked, to move slideLeftTop component
http://joxi.ru/ZrJBvERH1JVa8r
The problem I dont quite understand how to do this in right way..
Is it okay to create in mainMidLeft a method where I will do somethik like this:
move: () => {
document.querySelector(`.slideLeftTop`).style.position .....
}
The best practice is to use Vuex State manager with computed methods (getters) and watchers
I have made a working example for you on jsfiddle.
https://jsfiddle.net/n4e_m16/wujafg5e/4/
For more info on how vuex works please go to Here
Please let me know if you need more help :)
const store = new Vuex.Store({
state: {
mainMidLeftState: false,
},
getters: {
mainMidLeftState: state => state.mainMidLeftState,
},
mutations: {
toggleMainMidLeft: (state, payload) => {
state.mainMidLeftState = !state.mainMidLeftState
},
},
})
Vue.component('main-mid-left', {
data() {
return {
}
},
computed: {
mainMidLeftState() {
return this.$store.state.mainMidLeftState
},
},
methods: {
toggleMainMidLeft() {
this.$store.commit('toggleMainMidLeft')
// alert(this.mainMidLeftState)
},
}
})
Vue.component('slide-left-top', {
data() {
return {
}
},
computed: {
mainMidLeftState() {
return this.$store.state.mainMidLeftState
},
},
watch: {
mainMidLeftState: function(val) {
alert("YES, computed property changed and alert have been triggered by watcher in slide top left component")
}
},
methods: {
}
})
const app = new Vue({
el: '#app',
store,
})
div {
color: black;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex#3.0.1/dist/vuex.js"></script>
<div id="app">
<!-- inlining the template to make things easier to read - all of below is held on the component not the root -->
<main-mid-left inline-template>
<div>
<h4>
main mid left
</h4>
<button v-on:click="toggleMainMidLeft()">toggle Main Mid Left State</button>
<div v-show="mainMidLeftState == true">State is true</div>
<div v-show="mainMidLeftState == false">State is false</div>
</div>
</main-mid-left>
<slide-left-top inline-template>
<div>
<h4>
slide left top
</h4>
<div v-show="mainMidLeftState == true">State is true</div>
<div v-show="mainMidLeftState == false">State is false</div>
</div>
</slide-left-top>
</div>
If you don't want to use vuex, you can create a new Vue instance as an event bus (I believe this is mentioned somewhere in the Vue tutorial):
const EventBus = new Vue()
Import EventBus to where you need it and you can send an event by:
EventBus.$emit('event-name', data)
And add the following script in created() of your receiver component:
EventBus.$on('event-name', ($event) => {
// Do something
})
I hope this helps |´・ω・)ノ

Vue watch value from parent is in reverse

I have a custom component that lets users type in text and sends it to the backend where I do some computation and spit the new text back out with html in it.
My problem is when the user types into this textarea, it reverses all the text and keeps the cursors at the beginning of the textarea. So now 'foo bar' becomes 'rab oof'... This only has happened since I added in watch. I could delete the watcher, but I need it (or need another way) to apply my updates to the textarea, via the foo variable when I set foo equal to something from the parent.
console.log(v) writes out the reverse text.
Any idea how to change this?
Custom componet:
<template>
<div contenteditable="true" #input="updateHTML" class="textareaRoot"></div>
</template>
<script>
export default {
name: 'htmlTextArea',
props:['value'],
mounted: function () {
this.$el.innerHTML = this.value;
},
watch: {
value(v) {
this.$el.innerHTML = v; //v is the reverse text.
}
},
methods: {
updateHTML: function(e) {
this.$emit('input', e.target.innerHTML);
}
}
}
</script>
Parent that uses custom component:
<htmlTextArea id="textarea" v-model="foo"></htmlTextArea>
...
<script>
...
methods: {
triggerOnClick() {
this.foo = 'something';//Without the watcher, when I change this.foo to something the actual textarea does not display the new data that I assigned to foo. But in Vue dev tools I can see the new change.
}
UPDATE:
Vue.component('html-textarea',{
template:'<div contenteditable="true" #input="updateHTML"></div>',
props:['value'],
mounted: function () {
this.$el.innerHTML = this.value;
},
watch: {
value(v) {
this.$el.innerHTML = v;
}
},
methods: {
updateHTML: function(e) {
this.$emit('input', e.target.innerHTML);
}
}
});
new Vue({
el: '#app',
data () {
return {
foo: '',
}
}
});
<script src="https://unpkg.com/vue"></script>
<div id="app">Type here:
<html-textarea spellcheck="false" id="textarea" v-model="foo"> </html-textarea>
</div>
The problem is that when you set the innerHTML of a contenteditable element, you lose the selection (cursor position).
So you should perform the following steps when setting:
save the current cursor position;
set the innerHTML;
restore the cursor position.
Saving and restoring is the tricky part. Luckily I got these two handy functions that do the job for latest IE and newer. See below.
function saveSelection(containerEl) {
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
}
}
function restoreSelection(containerEl, savedSel) {
var charIndex = 0, range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl],
node, foundStart = false,
stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
Vue.component('htmltextarea', {
template: '#hta',
name: 'htmlTextArea',
props:['value'],
mounted: function () {
this.$el.innerHTML = this.value;
},
watch: {
value(v) {
if (v === 'yes') {
let selection = saveSelection(this.$el);
this.$el.innerHTML = 'no!';
this.$emit('input', 'no!');
restoreSelection(this.$el, selection);
}
}
},
methods: {
updateHTML: function(e) {
this.$emit('input', e.target.innerHTML);
}
}
});
new Vue({
el: '#app',
data: {
foo: 'Clear this and type "yes" (without the quotes). It should become "no!".'
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<htmltextarea id="textarea" v-model="foo"></htmltextarea>
<hr>
Result: <pre>{{ foo }}</pre>
</div>
<template id="hta">
<div contenteditable="true" #input="updateHTML" class="textareaRoot"></div>
</template>
In your app, I recommend you place them in a dedicated .js file, just for better organization.

vue.js: Tracking currently selected row

I have a simple table where I would like to handle click elements:
<div class="row"
v-bind:class="{selected: isSelected}"
v-for="scanner in scanners"
v-on:click="scannerFilter">
{{scanner.id}} ...
</div>
JS:
new Vue({
el: "#checkInScannersHolder",
data: {
scanners: [],
loading: true
},
methods: {
scannerFilter: function(event) {
// isSelected for current row
this.isSelected = true;
// unselecting all other rows?
}
}
});
My problem is unselecting all other rows when some row is clicked and selected.
Also, I would be interested to know, it it is possible accessing the scanner via some variable of the callback function instead of using this as I might need to access the current context.
The problem is you have only one variable isSelected using which you want to control all the rows. a better approach will be to have variable: selectedScanner, and set it to selected scanner and use this in v-bind:class like this:
<div class="row"
v-bind:class="{selected: selectedScanner === scanner}"
v-for="scanner in scanners"
v-on:click="scannerFilter(scanner)">
{{scanner.id}} ...
</div>
JS
new Vue({
el: "#checkInScannersHolder",
data: {
scanners: [],
selectedScanner: null,
loading: true
},
methods: {
scannerFilter: function(scanner) {
this.selectedScanner = scanner;
}
}
});
I was under the impression you wanted to be able to selected multiple rows. So here's an answer for that.
this.isSelected isn't tied to just a single scanner here. It is tied to your entire Vue instance.
If you were to make each scanner it's own component your code could pretty much work.
Vue.component('scanner', {
template: '<div class="{ selected: isSelected }" #click="toggle">...</div>',
data: function () {
return {
isSelected: false,
}
},
methods: {
toggle () {
this.isSelected = !this.isSelected
},
},
})
// Your Code without the scannerFilter method...
Then, you can do:
<scanner v-for="scanner in scanners"></scanner>
If you wanted to keep it to a single VM you can keep the selected scanners in an array and toggle the class based on if that element is in the array or not you can add something like this to your Vue instance.
<div
:class="['row', { selected: selectedScanners.indexOf(scanner) !== 1 }]"
v-for="scanner in scanners"
#click="toggle(scanner)">
...
</div>
...
data: {
return {
selectedScanners: [],
...
}
},
methods: {
toggle (scanner) {
var scannerIndex = selectedScanners.indexOf(scanner);
if (scannerIndex !== -1) {
selectedScanners.splice(scannerIndex, 1)
} else {
selectedScanners.push(scanner)
}
},
},
...