VueJS : reverse data array has no effect - vue.js

In this fiddle : https://jsfiddle.net/djsuperfive/svctngeb/ I want to reverse the structure array in data when I click the button.
Why are the rendered list and the dump of structure not reactive whereas the console.log reflect that the reverse was effective ?
The code :
HTML
<div id="app">
<draggable v-model="structure">
<div v-for="(item, index) in structure" :style="'background-color:'+item.color">
{{ item.title }}
</div>
</draggable>
<button type="button" #click="reverse()">Reverse structure</button>
<hr>
<strong>dump structure:</strong>
<pre>
{{ structure }}
</pre>
</div>
JS:
new Vue({
el: '#app',
data() {
return {
structure: [{
title: 'Item A',
color: '#ff0000'
},
{
title: 'Item B',
color: '#00ff00'
},
{
title: 'Item C',
color: '#0000ff'
},
],
}
},
methods: {
reverse() {
console.log(this.structure[0].title);
_.reverse(this.structure);
console.log(this.structure[0].title);
}
}
});
thanks

Max!
Try to replace line:
_.reverse(this.structure);
with
this.structure.reverse();
I guess, Underscore.js does not have reverse method, because JavaScript has native one.
Everything should work fine with native JS Array reverse function.
Good luck!

The problem here seems to be that Vue does not detect that your array has changed: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
The solution is to either replace this.structure with a new array or to have a reverse function that uses Vue.set().
this.structure = _.clone(_.reverse(this.structure));

Related

Is there a way to neaten up deeply nested objects during a v-for loop?

I'm currently working on a project in Vue that uses the WP-Rest api. I'm bored of tediously nested objects when looping through objects. While I can destructure top-level objects, I'm not sure how make things like this: page.page.featuredImage.node.altText less ugly. Can anyone offer any good practices for handling nested objects within Vue loops?
<div v-for="(page, index) in pageGrid" :key="index"
class="flex flex-col overflow-hidden rounded-lg shadow-lg">
<div v-if="page.page.featuredImage" class="flex-shrink-0">
<img class="h-48 w-full object-cover" :alt="page.page.featuredImage.node.altText"
:sizes="page.page.featuredImage.node.sizes" :srcset="page.page.featuredImage.node.srcSet" />
Sometimes that is why to do it in the other hand you can map over your data when mounted for instant and return a new array that looks the way you want.
I am not sure how your data looks but I think you could also do something like this:
<div v-for="(page, index) in pageGrid.page" :key="index">
As per your template code, You are having below data structure contains by pageGrid.
[
{
page: {
featuredImage: {
node: {
altText: 'Text 1',
sizes: 'small',
srcSet: 'image src link'
}
}
}
},
{
page: {
featuredImage: {
node: {
altText: 'Text 1',
sizes: 'small',
srcSet: 'image src link'
}
}
}
},
...
...
]
To make this simple, You can move the whole node object into a separate array with the help of Array.map() method and then it will easy to iterate in the template.
pageGrid.map(({ page }) => page.featuredImage.node);
Live Demo :
new Vue({
el: '#app',
data: {
pageGrid: [
{
page: {
featuredImage: {
node: {
altText: 'Text 1',
sizes: 'small',
srcSet: 'image 1 src link'
}
}
}
},
{
page: {
featuredImage: {
node: {
altText: 'Text 2',
sizes: 'medium',
srcSet: 'image 2 src link'
}
}
}
}
],
updatedPageGrid: []
},
mounted() {
this.updatedPageGrid = this.pageGrid.map(({ page }) => page.featuredImage.node);
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(page, index) in updatedPageGrid" :key="index">
{{ page.altText }}
{{ page.sizes }}
{{ page.srcSet }}
</div>
</div>

Vuejs setting reactive style to component in v-for with vuex data

Not sure how to work this...
I'm displaying table rows, pulling my data with vuex. The style changes, but is not updated in the browser. Only when I reload or re-create the component, it shows the style change. Scratching my head on this and what would be the best way to set a reactive style that is ternary based off the data loaded in the v-for component ?
<tr #click="rowClick(item)" v-for="(item, index) in List" :style="[item.completed ? { 'background-color': 'red' } : { 'background-color': 'blue' }]" :key="index">
item.completed is a bool
I hope I was able to correctly reflect what you were trying to accomplish. I used a v-for on the template tag and after that line you could access the item.completed boolean value. I had to use a span element inside the tr to apply the styling.
Vue.createApp({
data() {
return {
List: [ // comes from your vuex
{
name: 'one',
completed: true,
},
{
name: 'two',
completed: false,
}
]
}
},
methods: {
rowClick(item) {
console.log(item)
}
}
}).mount('#demo')
<script src="https://unpkg.com/vue#next"></script>
<table id="demo">
<template v-for="(item, i) in List">
<tr>
<span #click="rowClick(item)" :style="item.completed ? { 'background-color': 'red' } : { 'background-color': 'blue' }">{{ i }}: {{ item.name }}</span>
</tr>
</template>
</table>
I think it could be solved a little bit cleaner. What do you think? Does it goes in the right direction?

Vuejs multiple active buttons

I'm trying to create a list where every list item contains a button and i want a user to be able to click multiple button. I'm generating my list like so:
<ul>
<li v-for="item in items" :key="item.id">
<button type="button">{{item.title}}</button>
</li>
</ul>
but the problem with my code is whenever a user click a button, it turns the rest of the buttons to "unclicked". been trying to play with the focus and active stats but even with just css i cant get to enable multiple select .
i did manage to change the look of the current selected button:
button:focus {
outline: none;
background-color: #6acddf;
color: #fff;
}
any idea how can i allow multiple buttons to be clicked?
to make things a bit clearer, i am going to create an AJAX call later and pass the item.id of each item where it's button is clicked
I would much rather avoid changing the data structure if possible
Well you have to store somewhere that you clicked on the clicked item.
If you can't edit the items array then you can always create a new one, like isClicked where you store those values.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'baz'
}
],
isClicked: []
},
beforeMount() {
// set all values to false
this.items.forEach((item, index) => this.$set(this.isClicked, index, false))
},
methods: {
clicked(index) {
// toggle the active class
this.$set(this.isClicked, index, !this.isClicked[index])
}
}
})
.active {
background: red
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div v-for="(item, index) in items" :key="index">
<button #click="clicked(index)" :class="{'active': isClicked[index]}">{{item.title}}</button>
</div>
</div>
Or you can use vuex for storing those values.
However you can just use Vues event to manipulate the classList property, like:
new Vue({
el: '#app',
data: {
items: [1, 2, 3, 4, 5, 6, 7]
}
})
.active {
color: red
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<button v-for="i in items" #click="e => e.target.classList.toggle('active')">{{ i }}</button>
</div>
But it doesn't feel right, IMHO.
Also you can use cookies or localStorage to store those states. So it's really up to you.
Use id attribute for list items to make it unique.
<ul>
<li v-for="item in items" :key="item.id" :id="item.id">
<button type="button" #click="doThis">{{item.title}}</button>
</li>
</ul>
new Vue({
el: '#app',
data: {},
methods: {
doThis() {
// Use this to access current object
}
}
});

How to get Vue v-for list rendering working with Blade view in Laravel

I'm practicing Vue list rendering with Laravel, and can't get the data to display in my blade view with the v-for directive.
I've tried various examples from the Internet. I have a simple list with a few items that I want to display in a list, but instead of displaying the contents, I receive the item name displayed to the screen like {{ item.name }}
This is the relevant portion in my item.blade.php view file:
<ul>
<li v-for="item in inventory">
#{{ item.name }}
</li>
</ul>
</div>
#section('scripts.app.footer')
<script type="text/javascript" src="{{ mix('js/app2.js') }}"></script>
#endsection
And this is from app2.js (don't need a .vue file yet since I haven't gotten to creating templates yet)
var vm = new Vue({
el: '#vue-instance',
data: {
inventory: [
{name: 'MacBook Air', price: 1000},
{name: 'MacBook Pro', price: 1800},
{name: 'Lenovo W530', price: 1400},
{name: 'Acer Aspire One', price: 300}
]
}
});
All I'm getting in my browser is {{ item.name }} I would expect to see a list of the 4 item names. I'm not sure where I'm going wrong, as I've looked at other examples and one of my other team projects is working just fine with v-for (using .js files, not .vue files), but I can't get a simple list to display. Any help would be appreciated. npm run dev is working fine as well.
Data must return a data object data:()=>{ return { a:'stuff' } }
<template>
<div>
<ul>
<li v-for="item in inventory" :key="item">#{{ item.name }}</li>
</ul>
</div>
</template>
<script>
export default {
data: () => {
return {
inventory: [
{ name: "MacBook Air", price: 1000 },
{ name: "MacBook Pro", price: 1800 },
{ name: "Lenovo W530", price: 1400 },
{ name: "Acer Aspire One", price: 300 }
]
};
}
};
</script>
sandbox

Vue slot-scope with v-for, data changed but not re-rendered

I have a question while I'm studying vuejs2
I made an example with slot-scope && v-for, but it has an error which I can't understand.
Here is example code
https://jsfiddle.net/eywraw8t/6839/
app.vue
<template id="list-template">
<div>
<ul>
<slot name="row" v-for="item in list" v-bind=item />
</ul>
</div>
</template>
<div id="app">
<list-component :list="list">
<li slot="row" slot-scope="item">
{{item.name}}
</li>
</list-component>
</div>
Vue.component('list-component', {
template: '#list-template',
props: {
list: {
type: Array
}
}
});
new Vue({
el: '#app',
data () {
return {
list: [
{id: 1, name: 'Apple'},
{id: 2, name: 'Banana'},
{id: 3, name: 'Cherry'},
{id: 4, name: 'Durian'},
{id: 5, name: 'Eggplant'}
]
}
},
methods: {
h (item) {
item.name = item.name.toUpperCase()
console.log('Changed!')
console.log(item)
}
}
});
Strange thing is, the method 'h' is triggered and then, the console said 'Changed!' and data also changed but, the view is not re-rendered.
What am I missing? I think slot-scoped object data is not referencing the original object data.
What should I do to modify the original data?
Thanks for reading this question.
You are directly trying to modify the value of an item in an array. Due to some limitations vue cannot detect such array modifications.
So update your code to use vm.$set() to make chamges to the array item.
methods: {
h (item) {
let i = this.items.findIndex((it) => it.id === item.id);
this.$set(this.list, i, {...item, name: item.name.toUpperCase()});
console.log(item.id)
}
Here is the updated fiddle