Vuejs Render list of Array based on key values - vue.js

I have a dataArray with data like this:
dataArray: {'stat1': 'stat1stat', 'stat2': 'stat2stat', 'stat3': 'stat3stat'}
and so on with hundreds of statistics in the array.
I have been outputting things hard coded in the vue template like this:
{{dataArray.stat2}}
{{dataArray.stat3}}
..etc
What I would like to do is have a new array that specifies the keys I want to render. So something like this:
dataToShow: ['stat2', 'stat3']
And then somehow I could do a loop or v-for to only show the data that is in dataToShow
I've tried a few different ways and I can't get it to work. I think it needs to be a computed property but it isn't working.
Can anyone give some advice on how to implement this?

Wouldn't it just be this?
<template v-for="property in dataToShow">
{{ dataArray[property] }}
</template>
Plus any relevant markup for each entry.
dataToShow could be a computed property as you've suggested but it could just as easily be in your data:
data () {
return {
dataArray: {
stat1: 'stat1stat',
// etc.
},
dataToShow: ['stat2', 'stat3']
}
}

Related

VueJS + Vue.Draggable + Vuex Store + Computed Variables

Is there a way to use Vue.Draggable lists with computed variables in a Vuex Store? I'm fairly new to VueJS and this is my current component setup:
// Template
<draggable class="list-group" :list="list1" group="people">
<div
class="list-group-item"
v-for="(element, index) in list1"
:key="element.id"
>{{ element.name }} ({{ element.list }}) ({{ index }})</div>
</draggable>
// Script
computed: {
list1: {
return this.$store.getters.listItems.filter(item => item.list === 1)
}
}
// Store
const getters = {
listItems: (state) => {
return state.items
}
}
Without computed variables, everything works fine. When using computed variables, I can drag list items between lists, however the UI won't update because it's computed from the store.
Basically, what I need is to display multiple lists based on an input file (json). These lists have list items that can be dragged between different lists (with Vue.Draggable). Also, the user can create new list items / delete existing list items.
What's the best way to achieve this?
Thank you in advance.
In a complex draggable use-case, also using vuex, I ended up creating a variable in state called 'changes', and every time something changes, it gets incremented (through a vuex mutation). The data that's linked to dragging is in data, not in the store. I manually sync the two.
I use this - through a watcher - to trigger a rerender of my base component with completely fresh data.
This solution is ugly, and likely won't be necessary in vue3.
You probable need to do the following:
Have your draggable div into a new component
On this new component, have this #change.self="handleDragAndDrop($event)"
Handle the D&D
handleDragAndDrop: function (event){
const eventType = Object.keys(event)[0];
const chosenFrame = event[eventType].element;
store.dispatch(
"updateFramesOrder",
{
event: event,
listItemId: this.$props.itemId,
}
);
}
Finally in the store create the updateFramesOrder action that changes the list accordingly.
Hope it helps!

V-For from "Computed" doesn't show up

I am using a v-for inside a template to show a list from a computed property. But even though it works when i refresh, i cannot see the listed items when i firstly get on the page. If i v-for my contacts, i cannot filter.
So my data and computed methods look like this:
export default {
data() {
return {
contacts: this.$store.getters.loadedContacts,
search: ""
};
},
computed: {
filterContacts(contacts) {
return this.contacts.filter(contact => {
return contact.name.toLowerCase().match(this.search.toLowerCase());
});
}
};
And i call it in my HTML like this (filterContacts):
<v-list two-line v-if="contacts">
<template v-for="(contact, index) in filterContacts">
<v-list-tile-content>
<v-list-tile-title>{{ contact.name }}</v-list-tile-title>
<v-list-tile-action-text>{{ contact.position }}</v-list-tile-action-text>
</v-list-tile-content>
</template>
</v-list>
So the actual question is this: Why do need to refresh my page to see the results from the for loop ?
If i don't call the filterContacs, i cannot use my filter.
Any suggestions how to solve both filtering and v-for loop?
Thanks and sorry if this is a novice one!
Any help is much appreciated
Data of the component is set upon creation. The getter in the store probably doesn't return any data yet.
You can safely replace this.contacts in your computed with this.$store.getters.loadedContacts.
Other thing you can choose for, perhaps more elegant, is to use vuex's mapGetter helper. It reactively maps a vuex getter to your component's computed property (read more here: https://vuex.vuejs.org/guide/getters.html#the-mapgetters-helper).
With mapGetters you would:
...mapGetters({
contacts: 'loadedContacts'
})
And then just remove contacts from your data declaration.
Thanks! I actually observed at what you said. Indeed by replacing this.contacts with this.$store.getters.loadedContacts did fixed my problem. I removed also "contacts" from data().
Thanks!

add class to a specific parent div when click a component created with v-for

I am new to vuejs, this is what I want to do:
I have a list of components, each in a div. Now if I do something with the component (i.e. click it). I want to add a class to the parent div. This is what I did so far, the code is simplified, just to show what I want to do with a simple case.
my app.vue:
<div class="toggle-box" v-for="(name, index) in names" :class="classActive" :key="index">
<app-comp :myName="name" :myIndex="index" #someEvent="doSomething"></app-counter>
</div>
data() {
classActive: '',
names: ['alpha', 'beta', 'gamma']
},
methods: {
doSomething() {
this.classActive === '' ? this.classActive = 'is-active': this.classActive='';
}
}
the component:
<div>
<button #click="toggle">{{ myName }} - {{ myIndex }}</button>
</div>
props: ['myName', 'myIndex'],
methods: {
toggle() {
this.$emit('someEvent', index);
}
}
what this do: it creates 3 divs with "toggle-box"-class with a button in it which has the label "name - index". When I click a button, it emits the "someEvent"-event with index attached. The parent listens to this event and toggle the class 'is-active' on the div with the 'toggle-box' class. The thing is, right now, when I click a button, it adds the class to all 3 divs. Probably because there is no different between the 3 divs for vuejs. I know I can append the index to the event and call this with $event in the parent, but how do I use this? Or is there perhaps a better way to achieve what I want?
thanks for helping.
There are several different ways to approach this but I think the starting point is to think about how you want to represent this as data rather than how that appears in the UI. So model before view.
Presumably you're going to want to do something with these active items after they've been selected. I'd focus on that rather than the problem of highlighting them. The highlighting would then fall out relatively painlessly.
For the sake of argument, let's assume that an array of active items is a suitable model for what you're trying to achieve. It might not be but it makes for a simple example.
So:
data() {
return {
activeNames: [],
names: ['alpha', 'beta', 'gamma']
}
},
No mention of classes here as we're not worrying about the UI concern, we're trying to model the underlying data.
For the toggle method I'd be more inclined to emit the name than the index, but you're better placed to judge which represents the data better. For my example it'll be name:
methods: {
toggle() {
this.$emit('someEvent', this.myName);
}
}
Then in the parent component we'll add/remove the name from the array when the event is emitted. Other data structures might be better for this, I'll come back to that at the end.
methods: {
doSomething(name) {
if (this.activeNames.includes(name)) {
this.activeNames = this.activeNames.filter(item => item !== name);
} else {
this.activeNames.push(name);
}
}
}
Now we have an array containing the active names we can use that to derive the class for those wrapper divs.
<div
class="toggle-box"
v-for="(name, index) in names"
:class="{'is-active': activeNames.includes(name)}"
:key="index"
>
Done.
As promised, I'll now return to other data structures you could use.
Instead of an array we might use an object with boolean values:
data() {
return {
names: ['alpha', 'beta', 'gamma'],
activeNames: {
alpha: false,
beta: false,
gamma: false
}
}
}
In many ways this is an easier structure to work with for this particular example but we do end up duplicating the names as property keys. If we don't prepopulate it like this we can end up with reactivity problems (though those can be solved using $set).
A further alternative is to use objects to represent the names in the first place:
data() {
return {
names: [
{name: 'alpha', active: false},
{name: 'beta', active: false},
{name: 'gamma', active: false}
]
}
}
Whether this kind of data structure makes sense for your use case I can't really judge.
Update:
Based on what you've said in the comments I'd be inclined to create another component to represent the toggle-box. Each of these can store its own active state rather than trying to hold them all on the parent component. Your v-for would then create instances of this new component directly. Depending on the circumstances it may be that this new component could merged into your original component.
There are all sorts of other considerations here that make it really difficult to give a definitive answer. If the active state needs to be known outside the toggle-box component then it's a very different scenario to if it's just internal state. If only one toggle-box can be open at once (like an accordion) then that's similarly tricky as internal state is not sufficient.

Vue.js: Save index of v-for?

I would like to save the latest index inside a v-for, but I don't know how to include this logic inside the template?
Basically, I have this:
<div v-for="(reply, index) in replies" :key="reply.id">
I want to include this logic inside the template somehow:
this.last_index = index
I just want to know the amount of total replies in that v-for loop.
Thank you!
The most adequate solution is use a computed property in Vue. I usually made it like this:
computed: {
lengthReply() {
return this.replies.length;
}
}
If you use Vuex, then you probably can use getters for this case.
If you really don't wanna use computed property, then you can put it in your template in curly braces, like this:
{{ replies.length }}
If you need to render only last element in your reply list, then you can use this terrible code:
<div v-for="(reply, index) in replies" v-if="reply === replies[replies.length - 1]">

In vue.js is it possible to notify "observers" to refetch the value from the observed data, without changing the value of the observed data

Suppose that I have an input element bound like this:
<input :value="somedata">
The user types something in the input, and since I am not using v-model or altering somedata through a handler, the value of the element is now different from somedata. This is what I want, but I would also like to have the following capability:
Without changing the value of somedata I would like to be able to notify the element so that it sets its value equal to somedata again. Something like knockout's notifySubscribers() or valueHasMutated()
Is that possible in vue.js?
UPDATE: A clear illustration of the issue here: https://jsfiddle.net/gtezer5c/3/
It's a little difficult interpreting what exactly the requirements and acceptance criteria might be to suit your needs, and I thought Bill's solution was what you were after, but after all the updates and clarifications, I think I understand a little more what you're trying to accomplish: in short, I think you need a generic way to have an input that can hold a value but that can be independently reverted to some other value.
Please have a look at this CodePen. I believe it's providing what you're trying to do. It allows you to create an input element as a revertable component, which can optionally be passed a default value. Any changes to that input are maintained by the component with its value data property. It will not be observing/pulling in any lastKnownGood type of value because any reversion will be pushed into the component from outside.
Externally to the revertable component, you can $emit a revert event with a new value that will cause either all revertable components or a single revertable component (with a matching ID) to be reverted.
I feel like it's mostly a clean solution (assuming I'm understanding the requirements correctly), except that in VueJS 2 we have to use a standalone, shared Vue object to pass the events when there is no parent-child relationship. So you'll see:
const revertBus = new Vue()
defined in global scope in the demo. And the revertable component will use it to receive incoming messages like so:
revertBus.$on('revert', (value, id) => { ... }
and the controlling Vue object that is triggering the messages will use it like this:
revertBus.$emit('revert', this.reversionValue, targetId)
You can also emit the event with a null value to cause the revertable component to reset its value to its initial default value:
revertBus.$emit('revert', null, targetId)
Again, it's a mostly clean solution, and though it might not fit perfectly inline with what you're trying to accomplish, I'm hoping it might at least help you get closer.
I'm not sure I'm following properly but I'll give it a shot.
What I think you want is to only update some values when their "temporary" values meet some type of condition. Here's how I was thinking of it.
<div id="app">
<input v-model="tempValues.one">
<input v-model="tempValues.two">
<input v-model="tempValues.three">
<pre>{{ values }}</pre>
<pre>{{ tempValues }}</pre>
</div>
Then, in my component, I watch tempValues and only update values when a condition is met.
new Vue({
el: '#app',
data: {
values: {
one: '',
two: '',
three: '',
},
tempValues: {},
},
created () {
// Create the tempValues based on the real values...
this.tempValues = Object.assign({}, this.values)
},
methods: {
updateValues (tempValues) {
// Only updating the values if all the tempValues are longer than 3 characters...
var noneEmpty = Object.values(tempValues).every((value) => value.length > 3)
if (noneEmpty) {
this.values = Object.assign({}, tempValues)
}
},
},
watch: {
// Watch tempValues deeply...
tempValues: {
handler (tempValues) {
this.updateValues(tempValues)
},
deep: true,
},
},
})
Here's a quick demo: https://jsfiddle.net/crswll/ja50tenf/
yourvar.__ob__.dep.notify()
works on objects and arrays
Yes, You should be able to do this with help of v-on:input. You can call a function on input and put your logic of checking and updating in this function:
<input :value="somedata" v-on:input="yourMethod">
In fact if you look at the documentation, <input v-model="something"> is syntactic sugar on:
<input v-bind:value="something" v-on:input="something = $event.target.value">
so instead of assigning variable something to value inputted, you can put your logic in that place.