Reusable Vue Components - How to use props to define v-for array and path for unique Axios responses - vue.js

I'm using Vue components for multiple inputs with different axios url's and responses. Then using a v-for loop for the response to be displayed which can be selected.
idea:
Input 1 > Axios GET user data
Input 2 > Axios GET colour data
Input 3 > Axios GET model data
etc, each Axios response can have a different response array and objects.
I can set the different Axios GET url's by using props, but how can I use props to define the v-for array path and object path?
example image sample showing needed link between prop and v-for:
Can I use props to define the array path and object in the f-vor loop? In the example code below I need to use the prop from the component to define the array and object paths. note I'm using a axios sample response for this demo.
Vue.component("my-component", {
template: `
<div style="position:absolute"><input :placeholder="this.input_placeholder" #keyup="if(input_value.length > 2 ){ search() }" v-on:blur="input_clear()" v-model="input_value" /><i v-if="loading_spinner" class="fas fa-spinner fa-pulse"></i><div class="supplier_select_popup" v-if="response_popup_show"><div v-for="data,i in response_array.bpi" v-on:click="response_select(i)">{{ data.code }}</div></div></div>`,
props: {
api_url: "",
api_response_path: "",
data_path: "",
},
data: function() {
return {
input_placeholder: "Search",
input_value: "",
selected_value: "",
loading_spinner: false,
response_popup_show: false,
response_array: [],
};
},
methods: {
// Fetch Data
search: function() {
this.response_popup_show = false
this.loading_spinner = true
clearTimeout(this.myVar)
this.myVar = setTimeout(
function() {
axios
.get(
this.api_url
)
.then((response) => {
this.response_array = response.data
console.log(this.response_array)
this.response_popup_show = true
this.loading_spinner = false
})
.catch((error) => {
console.log(error)
this.errored = true;
this.response_popup_show = false
})
.finally(() => (this.loading = false))
}.bind(this),
1000
);
},
// Response Select
response_select: function(i) {
this.input_value = [i]
this.selected_value = [i]
this.response_popup_show = false
},
// Response Clear
input_clear: function() {
var self = this;
setTimeout(function() {
self.response_popup_show = false
self.loading_spinner = false
if (self.selected_value.length != 0) {
self.input_value = self.selected_value
} else {
self.input_value = ""
}
}, 100);
}
}
});
const App = new Vue({
el: "#app",
methods: {}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<script src="https://kit.fontawesome.com/17cdac82ba.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-component class="clickable" api_url="https://api.coindesk.com/v1/bpi/currentprice.json" api_response_path="response_array.bpi" data_path="date.code">
</my-component>
</div>

Yes, you can do this by passing the property you want to use for both the API response and each data item, but I prefer to generalize it by passing "map" functions, that way you aren't limited in any way by how you want to transform the data:
props: [
'map_response',
'map_data',
]
<div v-for="data, i in map_response(responseArray)">
{{ map_data(data) }}
</div>
You pass the props like this:
<my-component
class="clickable"
api_url="http://api.example.com/stuff"
:map_response="response => response.bpi"
:map_data="data => data.code"
>

Related

getting promise instead of value in laravel vue js [duplicate]

I am calling an async function which loads the profile pic, the await call returns the value to the variable 'pf' as expected, but I couldn't return that from loadProfilePic. At least for the start I tried to return a static string to be displayed as [object Promise] in vue template.
But when I remove await/asnyc it returns the string though.
<div v-for="i in obj">
{{ loadProfilePic(i.id) }}
</div>
loadProfilePic: async function(id) {
var pf = await this.blockstack.lookupProfile(id)
return 'test data';
//return pf.image[0]['contentUrl']
},
That is because async function returns a native promise, so the loadProfilePic method actually returns a promise instead of a value. What you can do instead, is actually set an empty profile pic in obj, and then populate it in your loadProfilePic method. VueJS will automatically re-render when the obj.profilePic is updated.
<div v-for="i in obj">
{{ i.profilePic }}
</div>
loadProfilePic: async function(id) {
var pf = await this.blockstack.lookupProfile(id);
this.obj.filter(o => o.id).forEach(o => o.profilePic = pf);
}
See proof-of-concept below:
new Vue({
el: '#app',
data: {
obj: [{
id: 1,
profilePic: null
},
{
id: 2,
profilePic: null
},
{
id: 3,
profilePic: null
}]
},
methods: {
loadProfilePic: async function(id) {
var pf = await this.dummyFetch(id);
this.obj.filter(o => o.id === id).forEach(o => o.profilePic = pf.name);
},
dummyFetch: async function(id) {
return await fetch(`https://jsonplaceholder.typicode.com/users/${id}`).then(r => r.json());
}
},
mounted: function() {
this.obj.forEach(o => this.loadProfilePic(o.id));
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="i in obj">
{{ i.profilePic }}
</div>
</div>

Tableau Vuejs getWorkBook() "Cannot read property get_workbook of null"

I am trying to follow the getData example found on the tableau javascript tutorial (https://github.com/tableau/js-api-samples/blob/master/getDataBasic.html) , but for vue js, however, I am unable to get it to work. I am able to render the tableau object, but when it comes to getting the underlying data or even trying to get the workbook name, I get the error: "Cannot read property get_workbook of null". Below is my code:
<template>
<div class="container" style="margin-top: 90px;">
<div id="vizContainer2"></div>
</div>
</template>
<script>
export default {
name: 'TableauHolder',
methods: {
getUnderlyingData(){
const containerDiv = document.getElementById("vizContainer2")
let url = "http://public.tableau.com/views/RegionalSampleWorkbook/Storms"
let options = {
hideTabs: true,
hideToolbar: true,
onFirstInteractive: () => {
}
}
this.viz = new window.tableau.Viz(containerDiv, url, options)
let sheet = this.viz.getWorkbook().getActiveSheet().getWorksheets().get("Storm Map Sheet")
console.log(sheet)
},
},
mounted () {
window.addEventListener('load', () => {
this.getUnderlyingData();
})
}
}
</script>
Placing getWorBbook() in onFirstInteractive successfully gets me the workbook name (as shown below), but I am not sure where to go from there in terms rendering the data.
<template>
<div class="container" style="margin-top: 90px;">
<div id="vizContainer2"></div>
</div>
</template>
<script>
export default {
name: 'TableauHolder',
methods: {
getUnderlyingData(){
const containerDiv = document.getElementById("vizContainer2")
let url = "http://public.tableau.com/views/RegionalSampleWorkbook/Storms"
let options = {
hideTabs: true,
hideToolbar: true,
onFirstInteractive: () => {
let sheet = this.viz.getWorkbook()
console.log(sheet)
}
}
this.viz = new window.tableau.Viz(containerDiv, url, options)
},
},
mounted () {
window.addEventListener('load', () => {
this.getUnderlyingData();
})
}
}
</script>
I realized that the JavaScript API is asynchronous and therefore the let sheet line is executed before while executing the API. Therefore, something like setTimeout will make the line execute after the API has been executed. See below incase anyone was having similar issues:
<template>
<div class="container" style="margin-top: 90px;">
<div id="vizContainer2"></div>
</div>
</template>
<script>
export default {
name: 'TableauHolder',
methods: {
getUnderlyingData(){
const containerDiv = document.getElementById("vizContainer2")
let url = "http://public.tableau.com/views/RegionalSampleWorkbook/Storms"
let options = {
hideTabs: true,
hideToolbar: true,
onFirstInteractive: () => {
}
}
this.viz = new window.tableau.Viz(containerDiv, url, options)
setTimeout(() => {
let sheet = this.viz.getWorkbook().getActiveSheet();
console.log(sheet);
}, 3000);
},
},
mounted () {
window.addEventListener('load', () => {
this.getUnderlyingData();
})
}
}
</script>

How to Add javascript paggination code in Vue js component

I'm trying to add pagination code in the Vue component and I've tried to add the code in the mounted hook to call the function but it doesn't work. I want to load the code after component loaded completely.Also, jQuery code doesn't load in Vue component. Do I need to change my code to pure javascript for that. Can you guide me how to fix the issue?
// Create a root instance for each block
var vueElements = document.getElementsByClassName('search-bento-block');
var count = vueElements.length;
const store = new Vuex.Store({
state: {
query: drupalSettings.bento.query ? drupalSettings.bento.query : '',
bentoComponents: []
},
mutations: {
add (state, payload) {
state.bentoComponents.push(payload)
}
},
getters: {
getComponents: state => {
return state.bentoComponents
}
}
})
// Loop through each block
for (var i = 0; i < count; i++) {
Vue.component('results', {
template: `
<div v-if="results && results.length > 0">
<div v-for="result in results">
<div class="search-result-item">
<div class="image-holder">
<img src="https://images.unsplash.com/photo-1517836477839-7072aaa8b121?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=750&q=80">
</div>
<div class="container-content">
<a v-bind:href="result.url">
<h3 v-html="result.title"></h3>
</a>
<p>Subjects: <span v-html="result.subjects"></span></p>
</div>
</div>
</div>
</div>
<div v-else>
<p>No results found.</p>
</div>
`,
props: ['results'],
})
new Vue({
el: vueElements[i],
store,
data: {
message: 'Hello There!',
results: [],
total: 0,
bentoSettings: [],
},
methods: {
addComponentToStore: function (type) {
this.$store.commit('add', type);
console.log("test");
console.log(this.results.length);
}
},
mounted: function() {
// console.log(this.$route.query.bentoq);
const id = this.$el.id;
this.bentoSettings = drupalSettings.pdb.configuration[id];
var bentoConfig = drupalSettings.pdb.configuration[id].clients[this.bentoSettings.bento_type] ? drupalSettings.pdb.configuration[id].clients[this.bentoSettings.bento_type].settings : [];
axios
.get('/api/search/' + this.bentoSettings.bento_type, {
params: {
query: this.$store.state.query,
plugin_id: this.bentoSettings.bento_type,
bento_limit: this.bentoSettings.bento_limit,
bento_config: bentoConfig,
}
})
.then(response => {
console.log(response);
this.results = response.data.records;
this.total = response.data.total;
this.addComponentToStore({
title: this.bentoSettings.example_field,
count: this.total
});
})
.catch(error => {
console.log(error.response);
})
}
});
}
// I'm trying to call following function in Vue component.
function baseThemePagination1() {
//Pagination
pageSize = 3;
var pageCount = $('.line-content').length / pageSize;
for (var i = 0; i < pageCount; i++) {
$('#pagin').append('<li><a href=\'#\'>' + (i + 1) + '</a></li> ');
}
$('#pagin li').first().find('a').addClass('current')
showPage = function(page) {
$('.line-content').hide();
$('.line-content').each(function(n) {
if (n >= pageSize * (page - 1) && n < pageSize * page)
$(this).show();
});
}
showPage(1);
$('#pagin li a').click(function() {
$('#pagin li a').removeClass('current');
$(this).addClass('current');
showPage(parseInt($(this).text()))
});
}
What you are trying to do is not the recommended way to use vue, direct DOM manipulation is one of the things that vue is made to avoid (although can be done). The Vue way would be to bind the value you want to a variable with v-model assuming it is an input and then create your pagination based on that.
If you insist on DOM manipulation then try ref="line-content" and then call it like so:
this.refs.line-content.
In terms of reacting to a page change click simply use a method in your methods section there is no reason to use jQuery for that.
See here for a simple explanation:
https://medium.com/#denny.headrick/pagination-in-vue-js-4bfce47e573b

Vuetify loader which depends on API response

I made a login form and button with preloader:
<v-btn #click="login" :loading="loading4" :disabled="loading4"
#click.native="loader = 'loading4'">
{{ $t('forms.labels.loginBtn') }}
<span slot="loader" class="btn-loader">
<v-icon light>cached</v-icon>
</span>
</v-btn>
I want to show btn preloader when api response is 'pending'.
I fetch api status from computed:
...mapGetters({
loginStatus: 'auth/authStatus'
}),
In Vuetify docs I found only solution with setTimeout and I don't know how to customize it to my api response:
watch: {
loader () {
const l = this.loader
this[l] = !this[l]
setTimeout(() => (this[l] = false), 3000)
this.loader = null
}
}
My store:
const state = {
token: localStorage.getItem('user-token'),
status: null
}
I want to show preloader only when state status is 'loading'. I'm changing state using mutation.
How to do this and what this[l] means?
Thanks.
Using brackets or [ and ] is just an another way for accessing properties in your Javascript object aside from the dot or . operator.
The brackets is usually used for accessing properties dynamically.
For example, the most common way to access an object property is like this:
this.loading4 = true;
But, you can also do it like this if you want to:
this['loading4'] = true;
and you can also supply a variable instead of a string literal:
const l = 'loading4';
this[l] = true;
It's basically like you are treating your object like a multi-dimensional array in PHP.
You could also try using loading as a state in your Vuex module without using watch hook just like #Jeremy Walters suggested.
Vuex
state: {
loading: false
},
getters: {
isLoading(state) {
return state.loading
},
mutations: {
loginSuccess(state, payload) {
state.loading = false //ends the loader
...
},
loginFailed(state, payload) {
state.loading = false //ends the loader
...
},
},
actions: {
login({state,commit},credentials) {
state.loading = true //starts the loader
axios.post('/api/auth/login', credentials)
.then((response) => {
commit("loginSuccess", response.data)
})
.catch((error) => {
commit("loginFailed", error.response.data)
})
}
Then in your component
HTML
<v-btn #click="login" :loading="isLoading" :disabled="isLoading">
{{ $t('forms.labels.loginBtn') }}
<span slot="loader" class="btn-loader">
<v-icon light>cached</v-icon>
</span>
</v-btn>
JS
...mapGetters({
'isLoading'
})
Regarding the explanation for the this[l], it was already explained nicely by the answer below.

Filtering a list of objects in Vue without altering the original data

I am diving into Vue for the first time and trying to make a simple filter component that takes a data object from an API and filters it.
The code below works but i cant find a way to "reset" the filter without doing another API call, making me think im approaching this wrong.
Is a Show/hide in the DOM better than altering the data object?
HTML
<button v-on:click="filterCats('Print')">Print</button>
<div class="list-item" v-for="asset in filteredData">
<a>{{ asset.title.rendered }}</a>
</div>
Javascript
export default {
data() {
return {
assets: {}
}
},
methods: {
filterCats: function (cat) {
var items = this.assets
var result = {}
Object.keys(items).forEach(key => {
const item = items[key]
if (item.cat_names.some(cat_names => cat_names === cat)) {
result[key] = item
}
})
this.assets = result
}
},
computed: {
filteredData: function () {
return this.assets
}
},
}
Is a Show/hide in the DOM better than altering the data object?
Not at all. Altering the data is the "Vue way".
You don't need to modify assets to filter it.
The recommended way of doing that is using a computed property: you would create a filteredData computed property that depends on the cat data property. Whenever you change the value of cat, the filteredData will be recalculated automatically (filtering this.assets using the current content of cat).
Something like below:
new Vue({
el: '#app',
data() {
return {
cat: null,
assets: {
one: {cat_names: ['Print'], title: {rendered: 'one'}},
two: {cat_names: ['Two'], title: {rendered: 'two'}},
three: {cat_names: ['Three'], title: {rendered: 'three'}}
}
}
},
computed: {
filteredData: function () {
if (this.cat == null) { return this.assets; } // no filtering
var items = this.assets;
var result = {}
Object.keys(items).forEach(key => {
const item = items[key]
if (item.cat_names.some(cat_names => cat_names === this.cat)) {
result[key] = item
}
})
return result;
}
},
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<button v-on:click="cat = 'Print'">Print</button>
<div class="list-item" v-for="asset in filteredData">
<a>{{ asset.title.rendered }}</a>
</div>
</div>