Vue: How can I get the true array instead of [__ob__: Observer] arrays? - vue.js

I am new to Vue, and want to get my data from the database with an API call:
data() {
return {
total: 0,
liblist: []
}
}
created() {
this.getLibList();
},
methods: {
async getLibList() {
const { data:res } = await this.$http.get('/api/user/select_all_info');
this.liblist = JSON.parse(JSON.stringify(res));
console.log(res);
console.log(this.liblist);
}
}
Here I want to access the array from my API and then assign the array to liblist:[].
I even tried to use JSON.parse(JSON.stringify(res)) to convert data type.
But sadly I got this result from console like this:
Here is console result of 'res':
(5) [{…}, {…}, {…}, {…}, {…}]
0: {id: "1111", em: "111111", pw: "7"}
1: {id: "12121212", em: "4144#gmail.com", pw: "6666"}
2: {id: "121213333", em: "admin333", pw: "333"}
3: {id: "2222", em: "2222", pw: "2"}
4: {id: "444444", em: "4444", pw: "444"}
length: 5
__proto__: Array(0)
But when it turns to 'liblist',it returns:
(5) [{…}, {…}, {…}, {…}, {…}, __ob__: Observer]
0: {__ob__: Observer}
1: {__ob__: Observer}
2: {__ob__: Observer}
3: {__ob__: Observer}
4: {__ob__: Observer}
length: 5
__ob__: Observer {value: Array(5), dep: Dep, vmCount: 0}
__proto__: Array
I really cannot understand: why after this.liblist = JSON.parse(JSON.stringify(res))the data become 'observer' type ??
In that case I cannot see any data in my table-componet:
my page's display
P.S. Here's my frontend code:
<el-table stripe max-height="500" :data="liblist">
<el-table-column label="Account" prop="id"></el-table-column>
<el-table-column label="Email" prop="em"></el-table-column>
<el-table-column label="Password" prop="pw"></el-table-column>
So what's wrong with my code? Please help me.

Related

Display an object added to an array in a child component in Vue

In vue I have a component that displays the content of an array in its parent App.vue.
If I push to the array, ListofLists how do I get the display list component to display the updated data?
I have a form in a child component that $emits a javascript object newList which is captured by the method addList. I expected Vue to detect a change to the ListofList and recompute the value of loadedList. If I am right to expect this how do I get Vue to display the updated list?
Should I use computed or watch? Is it necessary to use forceRerender or does the problem lie elsewhere.
What I have tried
<ShowListComponent>
:key="componentKey"
v-bind:loadedList="loadedList"/>
computed: {
loadedList: function() {
return this.ListofLists[this.currentList];
}
},
methods: {
changeList: function(id) {
this.currentList = id;
this.ListofLists[this.currentPlaylist];
console.log('changed List' + this.ListofLists[this.currentList]);
},
addList: function(newList) {
this.ListofLists.push( newList );
console.log('lists after push ' +this.ListofLists);
this.forceRerender();
},
What I see
The console.log shows the ListofLists is updated correctly.
I get an error:
[Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"
What I expect
The new list being displayed correctly.
EDIT - The template and component used
<template>
<div id="app">
<MediaPlayer
:key="componentKey"
v-on:openAllSongs="showAllSongs = !showAllSongs"
v-on:openPlaylist="showPlaylist= !showPlaylist"
v-bind:loadedPlaylist="loadedPlaylist"/>
<AllSongs
v-on:closeAllSongs="showAllSongs = false"
v-bind:songs="playlists[0].songs"
v-bind:class="{show : showAllSongs}"
/>
<Playlists
v-on:closePlaylist="showPlaylist = false"
v-bind:playlists="playlists"
v-bind:class="{show : showPlaylist}"
v-on:change-playlist="changePlaylist"
v-on:add-playlist="addPlaylist"
/>
</div>
</template>
<script>
import MediaPlayer from './components/MediaPlayer.vue'
import Playlists from "./components/Playlists.vue";
export default {
name: 'App',
components: {
MediaPlayer,
AllSongs,
Playlists,
},
props: ['playlist'],
data() {
return {
playlists: [
{
id: 1,
name: 'All Songs',
description: 'Magnum Opus',
created: '18/09/1830',
updated: '23/04/2020',
created_by: 'Jamie',
songs: [
{
id: '1',
position_in_list_index: 47,
name: 'i dont know what this is but i like it',
filename: 'i dont know what this is but i like it.mp3',
length_in_seconds: 200,
startdate: 'yesterday',
tags: 'quirky, instumental'
},
{
id:"3",
position_in_list_index: 38,
name: "sunday junkie",
filename: "sunday junkie.mp3",
length_in_seconds: 222,
tags: 'indie'
},
{
id: 4,
position_in_list_index: 40,
name: "burned",
filename: "burned.mp3",
length_in_seconds: 303,
tags: 'melancholy, serious'
},
{
id: 5,
position_in_list_index: 46,
name: "losing limbs like theres no tomorrow",
filename: "losing limbs like theres no tomorrow.mp3",
length_in_seconds: 232,
tags: 'hiphop, dark, synth'
}
],
},
{
id:3,
name: 'Rock Hard Seduction',
description: 'songs that bring a tear to your eye',
created: '21/02/2020',
updated: '23/03/2020',
created_by: 'Dewi',
songs: [
{
id: 4,
position_in_list_index: 21,
name: "trefnant rainbow v2",
filename: "trefnant rainbow v2.mp3",
length_in_seconds: 174,
tags: "rock"
},
{
id: 9,
position_in_list_index: 61,
name: "trefnant wind added solo",
filename: "trefnant wind added solo.mp3",
length_in_seconds: 268,
tags: 'folk'
}
]
},
{
id:4,
name: 'Family friendly',
description: 'they got no lyrics',
created: '20/02/2020',
updated: '23/02/2020',
created_by: 'Dewi',
songs: [
{
id: "15",
position_in_list_index: 74,
name: "dont waltz with a psycho like me",
filename: "dont waltz with a psycho like me.mp3",
length_in_seconds: 273
},
{
id: "17",
position_in_list_index: 76,
name: "lie down on the tracks",
filename: "lie down on the tracks.mp3",
length_in_seconds: 225,
tags: 'rock, instrumental, sounds like someone else'
},
{
id: "28",
position_in_list_index: 87,
name: "you two frozen heroes",
filename: "you two frozen heroes.mp3",
length_in_seconds: 267
}
]
},
],
showPlaylist: false,
currentPlaylist: 0,
componentKey: 0
}
},
computed: {
loadedPlaylist: function() {
console.log('playlist length in loadedPlaylist ' + this.playlists.length);
console.log('current playlist id '+ this.currentPlaylist);
return this.playlists[this.currentPlaylist];
}
},
methods: {
changePlaylist: function(id) {
this.currentPlaylist = id - 1;
this.playlists[this.currentPlaylist];
console.log('changed playlist' + this.playlists[this.currentPlaylist]);
},
addPlaylist: function(newPlaylist) {
this.playlists.push( newPlaylist );
console.log('playlists after push ' +this.playlists);
this.forceRerender();
this.changePlaylist(newPlaylist.id);
},
forceRerender: function() {
this.componentKey++;
console.log("componentKey " + this.componentKey);
}
}
}
</script>
the component template I am using to display the list.
<template>
<div class="media-player">
<draggable
v-model="loadedPlaylist.songs" class="songlist"
:group="{ name: 'songs'}"
ghost-class="ghost"
v-bind:key="loadedPlaylist.id"
>
<div
class="songlist-item"
#click='loadTrack(index, true)'
v-bind:class="{ 'is-active': index === activeTrack }"
v-bind:key="song.id"
v-for="(song, index) in loadedPlaylist.songs"
>
<h3>{{index + 1}}. {{song.name}}<span></h3>
</div>
</draggable>
</div>
</template>
My code was adding a new object to the array correctly and the update was passed to the child component correctly.
I was getting an error because the new object contained an empty array. trying to access it directly caused an error. Adding
<h2 v-if="loadedPlaylist.songs.length">{{loadedPlaylist.songs[activeTrack].name}}</h2>
<h2 v-else>Add Songs below</h2>
fixed the error.

Vuejs Hide a div when v-model is empty

I have a select dropdown like this.
<select v-model="selectedInjection">
<option v-for="(match,i) in haufigkeitMatches"
:key="i"
:value="match.value" >{{ match.name }}
</option>
</select>
I am rendering the value in this way
<td v-if="selectedInjection">{{Math.round(selectedInjection)}</td>
The value of the {{Math.round(selectedInjection)} changes when the select value changes and it's working fine. But when I do not select any value, the {{Math.round(selectedInjection)} is showing the old selected value unless I select a new value.
How can I hid the {{Math.round(selectedInjection)} when the select value is empty?
Here is the Jsfiddle https://jsfiddle.net/ey3scra0/
Better to use v-show in your div:
<div class="hideResult" v-show="showSelectedInjection && selectedProduct != ''">
{{Math.round(selectedInjection)}}
</div>
Then add #change on second select:
<select v-model="selectedInjection" #change="setShowSelectedInjection">
Also you need to add extra field showSelectedInjection with method setShowSelectedInjection and set showSelectedInjection in setSelectsToDefault:
data:{
showSelectedInjection: false,
(...)
methods:{
setSelectsToDefault(){
this.selectedIeProKg = 0;
this.selectedPreisProIE = 0;
this.showSelectedInjection = this.haufigkeitMatches.map(h => h.value).includes(this.selectedInjection);
},
setShowSelectedInjection(){
this.showSelectedInjection = true;
}
Here is a working example: JSFiddle
I don't know how much it hits your problem because you haven't provided an example of how the data in your application changes. If a variable depends on another variable, you must observe it and control that change. Just like me here, I follow a variable, check if it is empty and then reset the second variable. If this is still not the case, enter more data, a larger piece of code, then I will also adjust the answer.
new Vue({
data: {
selectedInjection: null,
haufigkeitMatches: []
},
computed: {
round() {
return Math.round(this.selectedInjection);
}
},
watch: {
haufigkeitMatches(val) {
if (!val.length) {
this.selectedInjection = null;
}
}
},
methods: {
add() {
this.haufigkeitMatches.push({
name: 'Fus',
value: 1.1
}, {
name: 'Ro',
value: 2.2
}, {
name: 'Dah',
value: 3.3
})
},
del() {
this.haufigkeitMatches = [];
}
}
}).$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<select v-model="selectedInjection">
<option v-for="(match,i) in haufigkeitMatches" :key="i" :value="match.value">{{ match.name }}</option>
</select>
<div v-show="selectedInjection">{{round}}</div>
<div>DIV is {{selectedInjection ? 'visible' : 'invisible'}}</div>
<button #click="add">Add</button>
<button #click="del">Del</button>
</div>
This is working for me. It is clearing the old value everytime. Please try this.
<div id="app">
<select v-model="selectedProduct" #change="setSelectsToDefault">
<option value=""></option>
<option v-for="(level1,index) in products"
:key="index"
:value="level1">{{level1.name}}</option>
</select>
<select v-model="selectedInjection">
<option v-for="(match,i) in haufigkeitMatches"
:key="i"
:value="match.value" >{{ match.name }}</option>
</select>
<table>
<tr>
<td class="table-result-body-ipJahr"><div class="hideResult" v-if="selectedInjection> 0">{{Math.round(selectedInjection)}}</div></td>
</tr>
</table>
</div>
"use strict";
new Vue({
el: '#app',
data: {
selectedPreisProIE: [],
selectedIeProKg: [],
selectedGewicht: [],
selectedInjection: '',
selectedProduct: null,
dataJson: [],
products: [{
name: "ivi",
Hint: "45-60 IE/kg alle 5 Tage\n60 IE 1x/Woche\n30-40 IE 2 x/Woche",
frequency: [1, 2, 8]
}, {
name: "ynovi",
Hint: "40-50 IE/kg\n2x/Woche im Abstand\nvon 3-4 Tagen",
frequency: [2, 6, 7]
}, {
name: "octa",
Hint: "50 (25-65) IE/kg\nalle 3-5 Tage",
frequency: [6, 7, 8]
}, {
name: "eroct",
Hint: "50 IE/kg \nalle 4 Tage",
frequency: [7]
}, {
name: "ltry",
Hint: "20-40 I.E./kg\n2-3x/Woche",
frequency: [2, 3]
}, {
name: "ate",
Hint: "20-40 I.E./kg\nAlle 2-3 Tage",
frequency: [5, 6]
}, {
name: "Facto A",
Hint: "20-40 I.E./kg\nAlle 2-3 Tage",
frequency: [5, 6]
}, {
name: "Eight",
Hint: "40-60 I.E./kg \nJeden 3.Tag oder\n2x/Woche",
frequency: [2, 3, 5, 6]
}, {
name: "iq_Vima",
Hint: "20-40 I.E./kg\nAlle 2-3 Tage",
frequency: [5, 6]
}, {
name: "Afla",
Hint: "20-50 I.E./kg\n2-3x/Woche",
frequency: [2, 3]
}, {
name: "Pma",
Hint: "20-40 I.E./kg\nAlle 2-3 Tage",
frequency: [5, 6]
}, {
name: "others",
Hint: "Individuell",
frequency: [1, 2, 3, 4, 5, 6, 7, 8]
}],
haufigkeit: [{
name: "1x / Woche",
id: 1,
value: 52.1428571429
}, {
name: "2x / Woche",
value: 104.2857142857143,
id: 2
}, {
name: "3x / Woche",
value: 156.4285714285714,
id: 3
}, {
name: "alle 1 Tage",
value: 365,
id: 4
}, {
name: "alle 2 Tage",
value: 182.5,
id: 5
}, {
name: "alle 3 Tage",
value: 121.6666666666667,
id: 6
}, {
name: "alle 4 Tage",
value: 91.25,
id: 7
}, {
name: "alle 5 Tage",
value: 73,
id: 8
}]
},
computed: {
haufigkeitMatches: function haufigkeitMatches() {
var _this = this;
if (this.selectedProduct) {
return this.haufigkeit.filter(function (x) {
return _this.selectedProduct.frequency.includes(x.id);
});
}
},
},
methods:{
setSelectsToDefault(){
this.selectedIeProKg = 0;
this.selectedPreisProIE = 0;
this.selectedGewicht = 0;
this.selectedInjection='';
}
}
});

VUEJS Display nested object in <b-table> VUE-BOOTSTRAP

I have an array of objects (users) gotten by an api request. Its structure is something like this:
api response: (5) [{…}, {…}, {…}, {…}, {…}]
Inside this array, the objects are like this:
0: {
user_address_string: ('Street 1')
id: (3)
avatar: (img')
...
user: {
id: 1
first_name: 'Lucas'
last_name: 'Smith'
}
},
1: {...},
2: {...},
....
]
It is just a sample just to show you the structure.
As you can see, among its properties there's another object named user{}.
I need to display only the properties contained in this object.
I would like to keep the structure of the table I was using before I got this new api (which didn't have objects as properties).
<b-table
responsive
v-if="users && users.length > 0"
:fields="fields"
:items="users"
>
The template should be something like this:
<template slot-scope="data">
{{ data.item.user }}
</template>
data.item should be the single user in the users array of objects, and with .user I should be able to access the properties of its object property user. (Going further data.item.user.first_name, etc, to access the single properties in it). What am I missing?
Nothing is rendered on the screen.
No errors in the console though.
In the script I have:
users: [],
fields: [
{ key: 'id', label: 'ID'},
{ key: 'name', label: 'Name' }
]
So, how should I write the template for displaying the nested object’s properties?
Also, the directive v-if="users && users.length > 0" in the b-table should still work, right? It is still an array, but of objects this time. Please correct me if I am wrong.
Thank you
You can specify nested field keys in dotted notation:
export default {
data() {
return {
users: [],
fields: [
{ key: 'id', label: 'ID'},
{ key: 'user.first_name', label: 'First Name' },
{ key: 'user.last_name', label: 'Last Name' }
]
}
}
As #Troy Morehouse suggested, I just needed to redefine the fields definition as
{ key: 'user.first_name', label: 'First name' }
**UPDATE after #artworkjpm comment:
The HTML code should be something like this:
<b-table
v-if="users && users.length > 0 && !isLoading"
id="table-transition-userList"
:key="users.id"
responsive
:tbody-tr-class="userStatus"
:tbody-transition-props="transProps"
:fields="fields"
:items="users"
>
<template
v-slot:cell(fullName)="data"
>
{{ data.item.user.first_name }} {{ data.item.user.last_name }}
</template>
<template
v-slot:cell(buttons)="data"
>
<b-button
v-b-tooltip.hover
title="See plan"
class="btn-plan p2"
variant="primary"
:disabled="!data.item.user.is_active"
#click.prevent="seePlan(data.item), selectUser(data.item)"
>
<span class="svg-container">
<svg-icon icon-class="route" />
</span>
</b-button>
</template>
</b-table>
**Minor change in fields, but the concept is the same:
fields: [
{ key: 'fullName', label: 'User' },
{ key: 'buttons', label: 'Operations' }
],
Hope it helps.
xx
Whereever you get your data, you can extract the property you want and save locally in this case it would look something like this:
data()
return {
users: []
}
methods: {
async getUsersFromApi(){
const { data: {users }} = await axios.get(...)
users.map(user => {
this.users.push(user.user)
}
An easy way to do it is by using a formatter in fields definition:
data() {
return {
fields: [
{
key: "avg_score",
label: this.$t("avgScore"),
sortable: true,
sortByFormatted: true,
formatter: (value, key, item) => item.stats.avg_score?.text
},
],
items: [...your item list]
}
}
And in the template:
<b-table :items="items" :fields="fields"></b-table>
The formatter will print the specified key or value automatically.

Binding model attribute to radio button with vuejs

I have a dataset that looks like this:
[
{id: 1, name: 'Foo', is_primary: false},
{id: 2, name: 'Bar', is_primary: true},
{id: 3, name: 'Baz', is_primary: false},
]
Only one of the entries is allowed to have is_primary = true. I'm displaying these items in a list, and I'm trying to display a radio button for each that the user can select to indicate that this is the primary one.
<tr v-for="item in items">
<td><input name="primary" type="radio" v-model="item.is_primary"></td>
</tr>
However, I don't think I'm understanding how this is supposed to work, because it's not working for me. Is this possible or am I supposed to handle this situation another way?
A set of radio inputs should v-model the same variable: a scalar that takes on the value associated with the selected radio.
To translate that back and forth into your item list, you can use a settable computed.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
name: 'Foo',
is_primary: false
},
{
id: 2,
name: 'Bar',
is_primary: true
},
{
id: 3,
name: 'Baz',
is_primary: false
},
]
},
computed: {
primaryItem: {
get() {
return this.items.find((i) => i.is_primary);
},
set(pi) {
this.items.forEach((i) => i.is_primary = i === pi);
}
}
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div v-for="item in items">
<input name="primary" type="radio" :value="item" v-model="primaryItem">
</div>
<pre>{{JSON.stringify(items, null, 2)}}</pre>
</div>

in vueJS, how to bind the Id of selected option in v-select v2.5.1

It's been hours that I'm trying to get the id of the selected option in v-select, but it returns me the object not the id.
Is there any way to get only the Id (object Id) of the selected option?
I already checked the documentation site:
https://sagalbot.github.io/vue-select/docs/
I also checked the various examples in:
https://codepen.io/collection/nrkgxV/
But so far I have not found the concrete solution to my problem. What is missing or am I doing wrong?
My code:
<template>
<div>
<v-select
v-model="selectedId"
:options="items"
label="name"
></v-select>
</div>
</template>
<script>
export default {
data () {
return {
items: [
{id: 1, name: "User 1", creator_id: 3},
{id: 2, name: "User 2", creator_id: 1},
{id: 4, name: "User 3", creator_id: 3},
],
selectedId: '',
...
}
}
Instead of using v-model , you can listen the event on the select:
Vue.component("v-select", VueSelect.VueSelect);
new Vue({
el: "#app",
data () {
return {
items: [
{id: 1, name: "User 1", creator_id: 3},
{id: 2, name: "User 2", creator_id: 1},
{id: 4, name: "User 3", creator_id: 3},
],
selectedId: ''
}
},
methods: {
selectId(e) {
this.selectedId = e.id
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-select/2.5.1/vue-select.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<v-select
#input="selectId($event)"
:options="items"
label="name">
</v-select>
<p>Selected ID: {{ selectedId }}</p>
</div>
how about add a computed props id
<script>
export default {
data () {
return {
items: [
{id: 1, name: "User 1", creator_id: 3},
{id: 2, name: "User 2", creator_id: 1},
{id: 4, name: "User 3", creator_id: 3},
],
selectedId: {}
}
},
computed: {
id: function () {
return (this.selectedId && this.selectedId.id)?this.selectedId.id:'';
}
}
}
</script>
It took me quite a while to figure out but apparently you can use :reduce="item = item.id"
See: https://vue-select.org/guide/values.html#getting-and-setting
A real life saver since the "computed" approach wasn't gonna cut it in my case
Returning a single key with reduce
f you need to return a single key, or transform the selection before it is synced, vue-select provides a reduce callback that allows you to transform a selected option before it is passed to the #input event. Consider this data structure:
let options = [{code: 'CA', country: 'Canada'}];
If we want to display the country, but return the code to v-model, we can use the reduce prop to receive only the data that's required.
<v-select :options="options" :reduce="country => country.code" label="country" />
https://vue-select.org/guide/values.html#transforming-selections
In the v-select add these attributes
item-value="id" item-text="name"