Updating Vue.Js data with secondary ajax request - vue.js

I have some working code with Vue.js v2.5.16 that updates existing data, but I am not sure if I am following best practices.
What I am doing:
I call a properties.view() method (which I omitted below for brevity) to quickly grab basic data from a redis cache on load. I then call properties.available() to retrieve non-cached inventory data.
Questions:
Is there a efficient way to update my properties objects without having to "manually" do it like via some sort of "magic" data binding? I tried the v-model directive but couldn't get it jiving.
When updating property.rate.low.total.nightly_avg I can only reference the first level attribute "rate" I then have to build out the object I am going to update to get all the way down to the child. Is there a cleaner way of doing this?
JS Code:
var properties = new Vue({
el: '#properties',
data: {
properties: []
},
methods: {
available: function(){
let self = this;
axios
.get(searchJs.getAttribute('webroot') + 'properties/available', {
headers: {
'Content-Type': 'application/json'
}
})
.then(function(response){
for (let i=0; i < response.data.length; i++) {
let p = response.data[i];
let vData = self.properties.filter(function(property) {
if (property.id == p.id) {
return property;
}
});
self.$set(vData[0], 'rate', {
'low':{
'total': {
'nightly_avg': p.rate.low.total.nightly_avg,
'original_nightly_avg': p.rate.low.total.original_nightly_avg
}
}
});
}
});
},
}
});
HTML snippet:
<div class="property-card_price-book">
<span class="strikethrough-price">${{property.rate.low.total.original_nightly_avg}}</span>
<span class="price">${{property.rate.low.total.nightly_avg}}</span>
<div class="offer">
<figure>
<span></span>
<sep></sep>
<span></span>
</figure>
</div>
</div>

Related

How to maintain original array sort order with Vue.js

I'm using v-for with Vue.js (v2.6.12) to display entries in an object e.g.
{
"12345": {
name: "foo",
isAccepted: true,
},
"56789": {
name: "bar",
isAccepted: false,
}
}
HTML:
<div v-for="item in sortMyItems(items)" v-bind:key="item.id">
<span>{{ item.name }}</span>
<span>{{ item.isAccepted }}</span>
</div>
Sort method in VM:
methods: {
sortMyItems: function(items) {
var accepted = [];
var rejected = [];
for (var id in items) {
var item = items[id];
if (item.isAccepted) {
accepted.push(item);
} else {
rejected.push(item);
}
}
return accepted.concat(rejected);
}
}
It's important to me to maintain the object structure of items in the model, which is why I'm doing it this way. The problem I have is that when the isAccepted property of any of the items in my data structure change, Vue re-renders the items that the sort order reflects the new ordering. I understand that this is a very useful feature of Vue, but in my case I really don't want this to happen. I want the sort order to be maintained the way it is after sortMyItems is first called. Is there a way to tell Vue to not monitor changes or just not re-render e.g. v-once
As far as I understood your question:
You could call sortMyItems(items) in the created Lifecycle Hook and store the result in a property of data.
Then, you can iterate over that property in your v-for:
export default {
data() {
return {
sortedData: [];
}
},
created() {
this.sortedData = sortMyItems(items)
}
}

NuxtJs where to declare a computed property

How can i declare a computed property using Nuxt ? or the equivalent ?
I am using NuxtJs and trying to use a category filter.
I want to filter by unique categories, and i am getting this error message:
Cannot read property 'filter' of undefined
I trying to adapt to Nuxtjs the exemple i found in this pen : https://codepen.io/blakewatson/pen/xEXApK
I declare this computed property below, first at pages/index.vue and after into .nuxt/App.js
filteredStore: function() {
var vm = this;
var category = vm.selectedCategory;
if(category=== "All") {
return vm.stores;
} else {
return vm.stores.filter(function(stores) {
return stores.category === category;
});
}
}
And i try to apply the filter into this list of checkboxes :
<div class="columns is-multiline is-mobile">
<div class="column is-one-quarter" v-for="store in filteredStore" :key="store.id" :store="store">
<label class="checkbox">
<input type="checkbox" v-model="selectedCategory" :value="''+store.category">
{{store.category}}
</label>
</div>
</div>
I'm going to do some guessing at your code situation (based on the example you noted), so just let me know where I make an incorrect assumption. I would guess that something like the following could work for you... maybe you could provide additional details where I'm missing them.
With regards to your error Cannot read property 'filter' of undefined, that probably means your array of stores is undefined. I believe if you create the stores array as empty in the data section, you should at least have it available before your async call returns any results.
One possible thing to you can do to test if your filtering logic is working... is to uncomment the manually created data array that I've created below. It's like an inline test for your data structure and logic, removing the asynchronous retrieval of your data. This basically can check if the filter works without your API call. It would narrow down your issue at least.
export default {
data() {
return {
stores: [
// Let's assume you don't have any static stores to start on page load
// I've commented out what I'm guessing a possible data structure is
//
// Example possible stores in pre-created array
// { name: 'Zales', category: 'Jewelry', id: 1 },
// { name: 'Petco', category: 'Pet Shop', id: 2 },
// { name: 'Trip Advisor', category: 'Tourism', id: 3 },
// { name: 'Old Navy', category: 'Clothes', id: 4 }
],
selectedCategory: 'All'
}
},
computed: {
// Going to make some small js tweaks
filteredStores: () {
const vm = this;
const category = vm.selectedCategory;
if (category === "All") {
return vm.stores;
} else {
return vm.stores.filter(store => {
return store.category === category;
});
}
}
},
async asyncData({ $axios }) {
$axios
.$get('https://yourdomain.com/api/stores/some-criteria')
.then(response => {
this.stores = response.data;
})
.catch(err => {
// eslint-disable-next-line no-console
console.error('ERROR', err);
});
}
};
And then your HTML
<div class="columns is-multiline is-mobile">
<div class="column is-one-quarter" v-for="store in filteredStores" :key="store.id" :store="store">
<label class="checkbox">
<input type="checkbox" v-model="selectedCategory" :value="`${store.category || ''}`">
{{store.category}}
</label>
</div>
</div>
ANYWAY This is all just a big guess and what your scenario is, but I figured I'd try to help shape your question some so that you could get a more meaningful response. In general, I'd suggest trying to provide as much detail as you can about your question so that people really can see the bits and pieces where things might have gone astray.
Don't touch anything in .nuxt Someone noted that above in a comment, and it's very important. Essentially that whole directory is generated and any changes you make in it can be easily overwritten.

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.

Vue-Select: Pushing a 2 dimensional array to :options

The plugin Vue-Select.
What I was trying to do is, make a search-select-dropdown input based on database.
So here's my SQL first named Ms_Location.
id_Loc | name_Loc
LOC0001 | Indonesia
LOC0002 | China
LOC0003 | America
My index.php
<!DOCTYPE html>
<html>
<head>
</head
<body>
<div class="form-group">
<label for="lokasi_id" class="control-label required"><strong>Lokasi</strong></label>
<v-select :options="lokasi_list" placeholder='Type location..'></v-select>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script src="https://unpkg.com/vue-select#latest"></script>
Vue.component('v-select', VueSelect.VueSelect);
var app = new Vue ({
el: '#app',
data: {
lokasi_select: '',
lokasi_list: [],
},
// End of data
computed: {
get_lokasi() {
var list_loc = new Array();
list_loc = <?php include('receive_lokasi.php') ?>;
for(var i=0; i<list_loc.length; i++) {
var pushLoc = {
label: list_loc[i][1], value: list_loc[i][0]
}
this.lokasi_list.push(pushLoc);
}
return list_loc[0][1];
}
}
})
});
</script>
</body>
</html>
And this is my receive_lokasi.php
<?php
include ('koneksi.php');
$condition = "1";
if(isset($_GET['userid'])){
$condition = " id=".$_GET['userid'];
}
$sqltran = mysqli_query($con, "SELECT id_Loc, name_Loc FROM ms_location")or die(mysqli_error($con));
$response = array();
while ($rowList = mysqli_fetch_array($sqltran,MYSQLI_NUM)) {
$response[] = $rowList;
}
echo json_encode($response);
mysqli_close($con);
?>
However, I can't seem to get the option shown. This only happens after I make the get_lokasi(). So the mistake is probably there? Or perhaps I was missing something.
I've tried to print the lokasi_list somewhere, and yes, the value is there, but not shown in the dropdown bar.
Also, I'm new to Vue, so any help would be good. Thanks!
Nevermind..
My mistake, I didn't notice my receive_lokasi.php code
Instead of using MYSQLI_NUM
while ($rowList = mysqli_fetch_array($sqltran,MYSQLI_NUM)) {
$response[] = $rowList;
}
I should be using MYSQLI_ASSOC, as documented in here.
while ($rowList = mysqli_fetch_array($sqltran,**MYSQLI_ASSOC**)) {
$response[] = $rowList;
}
After that change this
<v-select :options="lokasi_list" placeholder='Type location..'></v-select>
To this
<v-select label='nama_Location' :options="lokasi_list" placeholder='Type location..'></v-select>
After that, everything loads fine.
Vue's computed properties aren't normally used to populate vue data attributes, they normally take one or more data attributes and combine them into something different for the template to use.
In your code you've tried to populate the vue data attribute 'lokasi_list' in the computed property 'get_lokasi', but you never call 'get_lokasi' anywhere in the template so lokasi_list remains empty.
Another approach to this sort of situation is to use a vue method to fetch data from the php backend via an ajax call with something like axios, and you'd normally use that method in the vue app's created life cycle event to get the data asap.
e.g.
<script>
Vue.component('v-select', VueSelect.VueSelect);
var app = new Vue({
el: '#app',
data: {
lokasi_select: '',
lokasi_list: [],
},
created: function() {
this.fetchLocations();
},
methods: {
fetchLocations: function() {
axios.get('/api/locations-end-point')
.then((response) => {
this.lokasi_list = response.data //might need to change this to match how your php is returning the json
})
.catch((error) => {
//handle the error
})
}
}
});
</script>
Sorry to mention this, but in your php you've got:
if(isset($_GET['userid'])){
$condition = " id=".$_GET['userid'];
}
That looks like you were planning to use it as part of your sql, but it would have been vulnerable to SQL injection attacks, sorry If I'm pointing out something you already knew.

Vue.js 2 - $forceUpdate() on components doesn't refresh computed properties?

I'm not sure if I'm doing this right or wrong, but all the answers I seem to find how to update the dom for computed values...
I have this component:
Vue.component('bpmn-groups', {
props: ['groups', 'searchQuery'],
template: '#bpmn-groups',
computed: {
filteredGroups: function () {
var self = this;
return this.groups.filter(function(group) {
self.searchQuery = self.searchQuery || '';
return _.includes( group.name.toLowerCase(), self.searchQuery.toLowerCase() );
});
}
},
methods: {
clearFilter: function () {
this.searchQuery = '';
},
deleteGroup: function(group) {
Vue.http.delete('api/groups/'+group.id ).then(response => { // success callback
var index = this.groups.indexOf(group); // remove the deleted group
this.groups.splice(index, 1);
this.$forceUpdate(); // force update of the filtered list?
toastr.success('Schemų grupė <em>'+group.name+'</em> sėkmingai pašalinta.');
}, response => { // error callback
processErrors(response);
});
this.$forceUpdate();
},
},
});
And in the template I just have a simple v-for to go through filteredGroups:
<input v-model="searchQuery" type="text" placeholder="Search..." value="">
<div v-for="group in filteredGroups" class="item">...</div>
The deletion works fine, it removes it from groups property, however the filteredGroups value still has the full group, until I actually perform a search or somehow trigger something else...
How can I fix it so that the filteredGroup is updated once the group is updated?
Don't mutate a prop - they are not like data defined attributes. See this for more information:
https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
Instead, as recommended in the link, declare a local data attribute that is initialized from the prop and mutate that.