How do I properly display data from this Vue method? - vue.js

I'm new to Vue and JS.
Could someone help me understand how can I:
display the data I get from the method shown below
and format the method itself and make it look like a proper Vue method in ES6
Code:
Right now it looks like this and just displays data to console.
[...]
<script>
var d = require('../util/diskinfo')
export default {
data () {
return {
}
},
methods: {
getDrivesList () {
d.getDrives(function(err, aDrives) {
for (var i = 0; i < aDrives.length; i++) {
console.log('Drive ' + aDrives[i].filesystem)
console.log('blocks ' + aDrives[i].blocks)
console.log('used ' + aDrives[i].used)
console.log('available ' + aDrives[i].available)
console.log('capacity ' + aDrives[i].capacity)
console.log('mounted ' + aDrives[i].mounted)
}
})
}
}
}
</script>
I want to display it on the page using a loop. Something like this:
<div v-for="i in aDrives" :key="i.id">
<p>Disk name: {{aDrives[i].mounted}}</p>
<p>Disk size: {{aDrives[i].blocks}}</p>
</div>
There's going to be 2 loops - one in the method and one in the template, which makes it confusing. Should I maybe save it to data () first? I'm not sure how to do it properly.

If I understand well, you will receive an array of data and you want to display it. In this case you don't need to loop in the model and in the template. You will just save array locally and then loop through it once in the template.
I will illustrate some ES6 syntax as well in my example:
<template>
<div v-for="driver in drivers">
<p> {{ driver.mounted }} </p>
... display all the data here
</div>
</template>
<script>
import d from '../util/diskinfo'
export default {
data () {
return {
drivers: []
}
},
methods: {
getDrivesList () {
d.getDrives((err, aDrives) => (this.drivers = aDrivers))
}
}
}
</script>

Related

Vue.js 3 Pinia store is only partially reactive. Why?

I'm using Pinia as Store for my Vue 3 application. The problem is that the store reacts on some changes, but ignores others.
The store looks like that:
state: () => {
return {
roles: [],
currentRole: 'Administrator',
elements: []
}
},
getters: {
getElementsForCurrentRole: (state) => {
let role = state.roles.find((role) => role.label == state.currentRole);
if (role) {
return role.permissions.elements;
}
}
},
In the template file, I communicate with the store like this:
<template>
<div>
<draggable
v-model="getElementsForCurrentRole"
group="elements"
#end="onDragEnd"
item-key="name">
<template #item="{element}">
<n-card :title="formatElementName(element.name)" size="small" header-style="{titleFontSizeSmall: 8px}" hoverable>
<n-switch v-model:value="element.active" size="small" />
</n-card>
</template>
</draggable>
</div>
</template>
<script setup>
import { NCard, NSwitch } from 'naive-ui';
import draggable from 'vuedraggable'
import { usePermissionsStore } from '#/stores/permissions';
import { storeToRefs } from 'pinia';
const props = defineProps({
selectedRole: {
type: String
}
})
const permissionsStore = usePermissionsStore();
const { getElementsForCurrentRole, roles } = storeToRefs(permissionsStore);
const onDragEnd = () => {
permissionsStore.save();
}
const formatElementName = (element) => {
let title = element.charAt(0).toUpperCase() + element.slice(1);
title = title.replace('-', ' ');
title = title.split(' ');
if (title[1]) {
title = title[0] + ' ' + title[1].charAt(0).toUpperCase() + title[1].slice(1);
}
if (typeof title == 'object') {
return title[0];
}
return title;
}
</script>
My problem is the v-model="getElementsForCurrentRole". When making changes, for example changing the value for the switch, the store is reactive and the changes are made successfully. But: If I try to change the Array order by dragging, the store does not update the order. I'm confused, because the store reacts on other value changes, but not on the order change.
What can be the issue here? Do I something wrong?
-Edit- I see the following warning on drag: Write operation failed: computed value is readonly
Workaround
As workaround I work with the drag event and write the new index directly to the store variable. But...its just a workaround. I would really appreciate a cleaner solution.
Here is the workaround code:
onDrag = (event) => {
if (event && event.type == 'end') {
// Is Drag Event. Save the new order manually directly in the store
let current = permissionsStore.roles.find((role) => role.value == permissionsStore.currentRole);
var element = current.permissions.elements[event.oldIndex];
current.permissions.elements.splice(event.oldIndex, 1);
current.permissions.elements.splice(event.newIndex, 0, element);
}
}
You should put reactive value on v-model.
getElementsForCurrentRole is from getters, so it is treated as computed value.
Similar to toRefs() but specifically designed for Pinia stores so
methods and non reactive properties are completely ignored.
https://pinia.vuejs.org/api/modules/pinia.html#storetorefs
I think this should work for you.
// template
v-model="elementsForCurrentRole"
// script
const { getElementsForCurrentRole, roles } = storeToRefs(permissionsStore);
const elementsForCurrentRole = ref(getElementsForCurrentRole.value);

Vue dynamic form elements in for loop

In a vue component I want to generate dynamic input elements in a for loop and bind them to the data.form element.
data() {
return {
form: {}
}
}
<b-form-checkbox v-model="form[key]['amount']" type="checkbox" :value="key"></b-form-checkbox>
How can I deal with this, what I tried:
form.amount[key] // is working, but not really the best data structure, and the array is populated with null values for each for loop entry on init ...
form[key]['amount'] // not working
// the working example I mentioned is structured this way
form: {
amount: [],
}
Maybe someone can help me with this ...
Simply define an empty array for the form fields, e.g. return { formFields: [] }. Then add as many values in this array as input fields you want to render on the screen:
<template>
<form>
<input v-for="(field,index) in formFields" :key="index" v-model="fields[index]" type="text">
</form>
</template>
<script>
export default
{
data()
{
return {
formFields: []
};
},
mounted()
{
for (let i = 1; i <= 10; i++) this.formFields.push('');
}
}
</script>
If you need different types of input fields - you will need to use array of objests instead of strings, and encode the field type as a property inside each of these objects.

Vue rendering array of objects

I'm creating a basic app in vue that uses axios to make a get request to grab html data from a blog site and using the cheerio node package to scrape the site for elements such as blog title and the date posted of each blog articles. However, I'm having trouble trying to render the scraped elements into the html. Here's the code:
<template>
<div class="card">
<div
v-for="result in results"
:key="result.id"
class="card-body">
<h5 class="card-title">{{ result.title }}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ result.datePosted }}</h6>
</div>
</div>
</template>
<script>
const Vue = require('vue')
const axios = require('axios')
const cheerio = require('cheerio')
const URL = 'https://someblogsite.com'
export default {
data() {
return {
results: []
}
},
mounted: function() {
this.loadBlogs()
},
methods: {
loadBlogs: function() {
axios
.get(URL)
.then(({ data }) => {
const $ = cheerio.load(data)
let results = this
$('.post').each((i, element) => {
const title = $(element)
.children('.content-inner')
.children('.post-header')
.children('.post-title')
.children('a')
.text()
const datePosted = $(element)
.children('.content-inner')
.children('.post-header')
.children('.post-meta')
.children('.posted-on')
.children('a')
.children('.published')
.text()
this.results[i] = {
id: i + 1,
title: title,
datePosted: datePosted
}
})
})
.catch(console.error)
}
}
}
</script>
I tried declaring
let results = this
before the axios request to refer to the scope within export default, but still getting the indicator from VS Code that the scope is still within the loadBlogs function. Am I missing something? I greatly appreciate the help! Thanks!
I think your problem is that you're trying to set Property of an results array so Vue can't pick your data update. Instead you should construct new array from your parsed page and set it as this.results = newResultsArray:
loadBlogs: function() {
axios.get(URL).then(({data}) => {
const $ = cheerio.load(data)
const newResults = $('.post').map((i, element) => {
const title = $(element).children('.content-inner .post-header .post-title a').text()
const datePosted = $(element).children('.content-inner .post-header .post-meta .posted-on a .published').text()
return {
id: i + 1,
title: title,
datePosted: datePosted
}
})//.toArray() // this toArray call might be needed, I haven't worked with cheerio for some time and not sure whether it returns array or its own collection type like jQuery does
this.results = newResults;
}).catch(console.error)
}
Also it should be even simpler if you just use this.results.push({...}) instead of property assignment this.results[i] = {...} (but it is usually easier to handle whole arrays instead of inserting and removing parts of them, both are viable solutions in their respective use cases, though).
And please check out this documentation article about how Vue handles reactive updates, it describes the problem you've encountered.

How do i get property of an object instead of whole object on v-model in vue.js

I had created an input form using html and given v-model="upCountryName", which is an empty array by default but if once name value clicked I had written function to fetch the data from django db using djangorestframework and I had got the data also but in input from v-model="upCountryName" I am getting data as [object Object].
So I written v-model="upCountryName.country" which actually I want to update but input form showing empty, how can I get country name instead of whole object.
----------
HTML --
<td v-on:click="up_country_form(c.id)">
<!--c.id is country.id i am getting from django db and passing it to the method up_country_form(id) in script-->
<center>
<p class="fas fa-pencil-alt"></p>
</center>
</td>
<input type="text" v-model='upCountryName.country' class="form-control">
----------
Vue.js
<script>
export default {
data () {
return {
upCountryName: []
};
},
methods: {
up_country_form (id) {
this.up_country_box = true;
this.$http.get('http://127.0.0.1:8000/getCountry/'+id)
.then((response) => {
this.upCountryName = response.data;
this.loading = false;
});
console.log(id);
this.add_country_box = false;
}
}
};
</script>
You have to make sure response.data is in form like { country: "Some Country Name" } then everything should work fine.
You can see the demo here.
Note: if you use upCountryName.country, the type of upCountryName should be Object not Array.
data () {
return {
upCountryName: {}
}
}

VueJS and lodash, filtered array displays empty unless main array is utilized in template

My mixin:
export default {
data() {
return {
charges: [],
catCharges: [],
offenses: ['Class I Offenses', 'Class II Offenses', 'Class III Offenses', 'Class IV Offense']
}
},
methods: {
getCharges() {
axios.get('admin/charges').then((response) => {
this.charges = response.data;
for(let offense = 1; offense <= this.offenses.length; offense++) {
this.catCharges[offense - 1] = this.chargesAtOffense(offense);
}
});
},
chargesAtOffense(offense) {
return _.filter(this.charges, { offense_level: offense });
}
},
created() {
this.getCharges();
}
};
Fetching data works, the array 'charges' gets populated with the following:
After populating the array, I start looping over the offenses array and filter all 'charges' from the main array into the 'catCharges' array, so all offenses are split into 4 separated arrays in that array.
Chrome's developer tools shows the array just fine and the charges are properly filtered.
This is my component:
<template>
<div>
<h1>Total charges: {{charges.length}}</h1>
<h1>Total offense categories: {{catCharges.length}}</h1>
<div v-for="(charges, offenseIdx) in catCharges">
{{charges}}
</div>
</div>
</template>
<script>
import chargesMixin from '../mixins/chargesMixin';
export default {
mixins: [chargesMixin],
data() {
return {
}
},
methods: {
},
computed: {
},
mounted() {
console.log('Disciplinary Segregation mounted.')
}
}
</script>
It uses the mixin provided above, and IT works and shows the catCharges array properly, HOWEVER when I remove the following line from the template:
<h1>Total charges: {{charges.length}}</h1>
The catCharges array is displayed as EMPTY, why do I need to use the charges array too along with the filtered array? This is driving me crazy.
I also tried the following method in the mixin which also causes the same issue:
chargesAtOffense(offense) {
var newCharges = [];
for(var i = 0; i < this.charges.length; i++) {
if(this.charges[i].offense_level != offense) continue;
const cloned = _.clone(this.charges[i]);
newCharges.push(cloned);
}
return newCharges;
}
I think your use case is linked to the reactivity system of VueJS.
https://v2.vuejs.org/v2/guide/reactivity.html
If you delete the line
<h1>Total charges: {{charges.length}}</h1>
you tell to VueJS to refresh your template only on catCharges get / set.
catCharges is an array, and so it's not as 'reactive' as a simple variable.
If you read precisely https://v2.vuejs.org/v2/guide/list.html#Caveats, prefer use a push on your catCharges to explain correctly to Vue that your array has changed.
I'll try this code :
getCharges() {
axios.get('admin/charges').then((response) => {
this.charges = response.data;
for(let offense = 1; offense <= this.offenses.length; offense++) {
this.catCharges.push(this.chargesAtOffense(offense));
}
});
},
Hope this will solve your problem.