vue.js: Tracking currently selected row - vue.js

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)
}
},
},
...

Related

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 reinitialize vue.js's getter and setter bindings?

Below here i provide a sample code. So what happens here is, i have some objects that i will load from API. The object later will be extended by the UI, in case that some of property that will be used for binding in the UI missing #app2. Under normal condition, if all the properties are provided like in #app1, the Vue will do the binding recursively to the content of the data object. But currently, in #app2, the property is missing and in the UI logic, i add the missing property.
The problem now is, when i added the property that way, the app2.contentObject.toggleStatus is not vue's object with getter and setter. how can i manually reinitialize the state of getter and setter so that the changes will be reflected in UI?
var app1 = new Vue({
el: "#app1",
data: {
contentObject: {
toggleStatus: false
}
},
computed: {
content: function(){
var contentObject = this.contentObject;
return contentObject;
}
},
methods: {
toggle : function(){
this.contentObject.toggleStatus = !this.contentObject.toggleStatus;
}
}
})
var app2 = new Vue({
el: "#app2",
data: {
contentObject: {
}
},
computed: {
content: function(){
var contentObject = this.contentObject;
contentObject.toggleStatus = false;
return contentObject;
}
},
methods: {
toggle : function(){
this.contentObject.toggleStatus = !this.contentObject.toggleStatus;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app1">
current toggle status: {{content.toggleStatus}}<br/>
<button #click="toggle">Toggle (working)</button>
</div>
<div id="app2">
current toggle status: {{content.toggleStatus}}<br/>
<button #click="toggle">Toggle (not working)</button>
</div>
1. In app2 vue instance's case you are trying to add a new property toggleStatus and expecting it to be reactive. Vue cannot detect this changes. So you got to initialize the properties upfront as you did in app1 instance or use this.$set() method. See Reactivity in depth.
2. You are using a computed property. Computed properties should just return a value and should not modify anything. So to add a property toggleStatus to contentObject make use of created lifecycle hook.
So here are the changes:
var app2 = new Vue({
el: "#app2",
data: {
contentObject: {}
},
created() {
this.$set(this.contentObject, "toggleStatus", false);
},
methods: {
toggle: function() {
this.contentObject.toggleStatus = !this.contentObject.toggleStatus;
}
}
});
Here is the working fiddle
It doesn't work in second case first because in your computed property you always assign false to it.
contentObject.toggleStatus = false;
And secondly you are looking for Vue.set/Object.assign
var app1 = new Vue({
el: "#app1",
data: {
contentObject: {
toggleStatus: false
}
},
computed: {
content: function(){
var contentObject = this.contentObject;
return contentObject;
}
},
methods: {
toggle : function(){
this.contentObject.toggleStatus = !this.contentObject.toggleStatus;
}
}
})
var app2 = new Vue({
el: "#app2",
data: {
contentObject: {
}
},
computed: {
content: function(){
var contentObject = this.contentObject;
return contentObject;
}
},
methods: {
toggle : function(){
this.$set(this.contentObject, 'toggleStatus', !(this.contentObject.toggleStatus || false));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app1">
current toggle status: {{content.toggleStatus}}<br/>
<button #click="toggle">Toggle (working)</button>
</div>
<div id="app2">
current toggle status: {{content.toggleStatus}}<br/>
<button #click="toggle">Toggle (not working)</button>
</div>

Filtering a list of objects in Vue without altering the original data

I am diving into Vue for the first time and trying to make a simple filter component that takes a data object from an API and filters it.
The code below works but i cant find a way to "reset" the filter without doing another API call, making me think im approaching this wrong.
Is a Show/hide in the DOM better than altering the data object?
HTML
<button v-on:click="filterCats('Print')">Print</button>
<div class="list-item" v-for="asset in filteredData">
<a>{{ asset.title.rendered }}</a>
</div>
Javascript
export default {
data() {
return {
assets: {}
}
},
methods: {
filterCats: function (cat) {
var items = this.assets
var result = {}
Object.keys(items).forEach(key => {
const item = items[key]
if (item.cat_names.some(cat_names => cat_names === cat)) {
result[key] = item
}
})
this.assets = result
}
},
computed: {
filteredData: function () {
return this.assets
}
},
}
Is a Show/hide in the DOM better than altering the data object?
Not at all. Altering the data is the "Vue way".
You don't need to modify assets to filter it.
The recommended way of doing that is using a computed property: you would create a filteredData computed property that depends on the cat data property. Whenever you change the value of cat, the filteredData will be recalculated automatically (filtering this.assets using the current content of cat).
Something like below:
new Vue({
el: '#app',
data() {
return {
cat: null,
assets: {
one: {cat_names: ['Print'], title: {rendered: 'one'}},
two: {cat_names: ['Two'], title: {rendered: 'two'}},
three: {cat_names: ['Three'], title: {rendered: 'three'}}
}
}
},
computed: {
filteredData: function () {
if (this.cat == null) { return this.assets; } // no filtering
var items = this.assets;
var result = {}
Object.keys(items).forEach(key => {
const item = items[key]
if (item.cat_names.some(cat_names => cat_names === this.cat)) {
result[key] = item
}
})
return result;
}
},
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<button v-on:click="cat = 'Print'">Print</button>
<div class="list-item" v-for="asset in filteredData">
<a>{{ asset.title.rendered }}</a>
</div>
</div>

Vuejs : how to hide a component <div> on keyup escape?

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>

Why value doesn't updated correctly in Vue when using v-for?

I've created basic jsfiddle here.
var child = Vue.component('my-child', {
template:
'<div> '+
'<div>message: <input v-model="mytodoText"></div> <div>todo text: {{todoText}}</div>'+
'<button #click="remove">remove</button>' +
'</div>',
props:['todoText'],
data: function(){
return {
mytodoText: this.todoText
}
},
methods: {
remove: function(){
this.$emit('completed', this.mytodoText);
}
}
});
var root = Vue.component('my-component', {
template: '<div><my-child v-for="(todo, index) in mytodos" v-bind:index="index" v-bind:todoText="todo" v-on:completed="completed"></my-child></div>',
props:['todos'],
data: function(){
return {
mytodos: this.todos
}
},
methods: {
completed: function(todo){
console.log(todo);
var index = this.mytodos.indexOf(todo, 0);
if (index > -1) {
this.mytodos.splice(index, 1);
}
}
}
});
new Vue({
el: "#app",
render: function (h) { return h(root, {
props: {todos: ['text 1', 'text 2', 'text 3']}
});
}
});
Code is simple: root component receives array of todos (strings), iterates them using child component and pass some values through the props
Click on the top "remove" button and you will see the result - I'm expecting to have
message: text 2 todo text: text 2
but instead having:
message: text 1 todo text: text 2
From my understanding vue should remove the first element and leave the rest. But actually I have some kind of "mix".
Can you please explain why does it happen and how it works?
This is due to the fact that Vue try to "reuse" DOM elements in order to minimize DOM mutations. See: https://v2.vuejs.org/v2/guide/list.html#key
You need to assign a unique key to each child component:
v-bind:key="Math.random()"
where in real-world the key would be for example an ID extracted from a database.