I have the following Pokemon.vue file:
<template>
<div class="pokemon">
<h1>Pokemon Overview</h1>
<div class v-for="pokemon in pokemonArray" :key="pokemon.id">
<p>{{ pokemon.name }}</p>
<p>{{ pokemon.number }}</p>
<p>{{ pokemon.height }}</p>
<p>{{ pokemon.weight }}</p>
<p>{{ pokemon.types }}</p>
</div>
<button #click="AddPokemon">AddPokemon</button>
</div>
</template>
<script>
import axios from "axios";
import db from "#/firebase/init";
export default {
name: "Pokemon",
data() {
return {
pokemonArray: [],
};
},
methods: {
CapitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
},
AddPokemon() {
for (var i = 520; i < 524; i++) {
axios.get("https://pokeapi.co/api/v2/pokemon/" + i).then((response) => {
var types = [];
for (var j = 0; j < response.data.types.length; j++) {
types.push(response.data.types[j].type.name);
}
db.collection("pokemon").add({
number: i,
name: CapitalizeFirstLetter(response.data.name),
weight: response.data.weight / 10,
height: response.data.height / 10,
types: types,
image:
response.data.sprites.other["official-artwork"].front_default,
});
});
}
},
},
created() {
//Fetch data from the firestore
db.collection("pokemon")
.get()
.then((snapshot) => {
snapshot.forEach((doc) => {
let pokemon = doc.data();
this.pokemonArray.push(pokemon);
});
});
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
However, when I click the button that runs the AddPokemon function I get hit with
CapitalizeFirstLetter is not defined at eval
And I don't understand why this is happening. I'm clearly missing something, but to my understanding it should be fine to use a different method from "methods", but this might not be the case? Any help is greatly appreciated.
It should be this.CapitalizeFirstLetter:
db.collection("pokemon").add({
number: i,
name: this.CapitalizeFirstLetter(response.data.name),
weight: response.data.weight / 10,
height: response.data.height / 10,
types: types,
image:
response.data.sprites.other["official-artwork"].front_default,
});
try this.CapitalizeFirstLetter
You need use this for access vue instance, example, this.CapitalizeFirstLetter(string)
Related
Here's the problem I'm having. I have a Leads page, that is my Leads.vue template. It loads my leads and then passes the leads data to other components via props.
The LeadSources component receives a computed method as it's property.
You can see that on the Leads.vue page, the LeadSources component calls the getSourceData() method for it's property data.
When I check the value of the props for LeadSources.vue in the setup() the value for chartData initially is an empty array. If the page hot-reloads then the LeadSources apexchart will populate with the :series data but otherwise I cannot get it to work.
Essentially it works like this.
Leads.vue passes getSourceData() to the LeadsSources.vue component which on the setup() sets it as the variable series and tries to load the apexchart with it.
It will not work if I refresh my page but if I save something in my IDE, the hot-reload will load the updated apexchart and the data appears. It seems like the prop values don't get set in the setup() function the first time around. How do I architecturally get around this? What's the proper way to set this up? I can't tell if the issue is on the leads.vue side of things or with the way that the LeadSources.vue component is being put together.
Any help would be appreciated, I've spent way too long trying to get this to work properly.
Leads.vue
<template>
<!--begin::Leads-->
<div class="row gy-5 g-xl-8 mb-8">
<div class="col-xxl-12">
<LeadTracker
:lead-data="leadData"
:key="componentKey"
/>
</div>
</div>
<div class="row gy-5 g-xl-8 mb-8">
<div class="col-xxl-12">
<LeadSources
chart-color="primary"
chart-height="500"
:chart-data="getSourceData"
:chart-threshold="sourceThreshold"
widget-classes="lead-sources"
></LeadSources>
</div>
</div>
<!--end::Leads-->
</template>
<script lang="ts">
import { defineComponent, defineAsyncComponent, onMounted } from "vue";
import { setCurrentPageTitle } from "#/core/helpers/breadcrumb";
import LeadSources from "#/components/leads/sources/LeadSources.vue";
import LeadTracker from "#/components/leads/tracker/LeadTracker.vue";
import LeadService from "#/core/services/LeadService";
import ApiService from "#/core/services/ApiService";
import {Lead} from "#/core/interfaces/lead";
export default defineComponent({
name: "leads",
components: {
LeadTracker,
LeadSources
},
data() {
return {
leadData: [] as Lead[],
}
},
beforeCreate: async function() {
this.leadData = await new LeadService().getLeads()
},
setup() {
onMounted(() => {
setCurrentPageTitle("Lead Activity");
});
const sourceThreshold = 5;
return {
sourceThreshold,
componentKey: 0
};
},
computed: {
getSourceData() {
interface SingleSource {
source: string;
value: number;
}
const sourceData: Array<SingleSource> = [];
// Make array for source names
const sourceTypes = [];
this.leadData.filter(lead => {
if (!sourceTypes.includes(lead.source)) sourceTypes.push(lead.source);
});
// Create objects for each form by name, push to leadSourceData
sourceTypes.filter(type => {
let totalSourceLeads = 1;
this.leadData.filter(form => {
if (form.source == type) totalSourceLeads++;
});
const leadSourceData = {
source: type,
value: totalSourceLeads
};
sourceData.push(leadSourceData);
});
// Sort by source popularity
sourceData.sort(function(a, b) {
return a.value - b.value;
});
return sourceData;
}
}
});
</script>
LeadSources.vue
<template>
<!--begin::Lead Sources Widget-->
<div :class="widgetClasses" class="card card-footprint">
<!--begin::Body-->
<div
class="card-body p-0 d-flex justify-content-between flex-column overflow-hidden"
>
<div class="d-lg-flex flex-stack flex-grow-1 px-9 py-6">
<!--begin::Text-->
<div class="d-flex flex-column text-start col-lg-10">
<span class="card-title">Lead Sources</span>
<span class="card-description">Where your leads are coming from.</span>
<!--begin::Chart-->
<div class="d-flex flex-column">
<apexchart
class="mixed-widget-10-chart lead-sources-donut"
:options="chartOptions"
:series="series"
type="donut"
:height="chartHeight"
:threshold="chartThreshold"
></apexchart>
</div>
<!--end::Chart-->
</div>
<!--begin::Unused Data-->
<div class="d-flex flex-row flex-lg-column lg-col-2 justify-content-between unused-data">
<div class="alt-sources flex-fill">
<div><span class="alt-header">Other Sources:</span></div>
<div v-for="item in otherSources" :key="item.source">
<span>{{ item.source }}</span>
<span>{{ item.value }}%</span>
</div>
</div>
<div class="alt-sources flex-fill">
<div><span class="alt-header">Sources Not Utilized:</span></div>
<div v-for="item in unusedSources" :key="item.source">
<span>{{ item.source }}</span>
</div>
</div>
</div>
<!--end::Unused Data-->
</div>
</div>
</div>
<!--end::Lead Sources Widget-->
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "LeadSource",
props: {
widgetClasses: String,
chartColor: String,
chartHeight: String,
chartLabels: Array,
chartData: Array,
chartThreshold: Number
},
setup(props) {
const sum = (data) => {
let total = 0;
data?.map(function(v) {
total += v;
});
return total;
}
const chartData = ref(props.chartData).value;
const threshold = ref(props.chartThreshold).value;
const usedSourcesLabel: string[] = [];
const usedSourcesData: number[] = [];
const otherSources: any = [];
const unusedSources: any = [];
const splitData = (data, max) => {
// set used, other sources < 5%, unused sources
data.filter((item) => {
if (item.value > max) {
usedSourcesLabel.push(item.source);
usedSourcesData.push(item.value);
} else if (item.value < max && item.value != 0 && item.value !== null) {
otherSources.push(item);
} else if (item.value == 0 || item.value === null) {
unusedSources.push(item);
}
});
};
splitData(chartData, threshold);
const chartOptions = {
chart: {
width: 380,
type: "donut"
},
colors: [
"#1C6767",
"#CD2E3B",
"#154D5D",
"#F1D67E",
"#4F9E82",
"#EF8669",
"#393939",
"#30AEB4"
],
plotOptions: {
pie: {
startAngle: -90,
endAngle: 270
}
},
dataLabels: {
enabled: false
},
fill: {
type: "gradient",
gradient: {
type: "horizontal",
shadeIntensity: 0.5,
opacityFrom: 1,
opacityTo: 1,
stops: [0, 100],
}
},
legend: {
show: true,
position: "left",
fontSize: "16px",
height: 220,
onItemClick: {
toggleDataSeries: false
},
onItemHover: {
highlightDataSeries: false
},
formatter: function (val, opts) {
return val + " - " + opts.w.globals.series[opts.seriesIndex];
}
},
title: {
text: undefined
},
tooltip: {
style: {
fontSize: "14px"
},
marker: {
show: false
},
y: {
formatter: function(val) {
return val + "%";
},
title: {
formatter: (seriesName) => seriesName,
},
}
},
labels: usedSourcesLabel,
annotations: {
position: "front",
yaxis: [{
label: {
text: "text annotation"
}
}],
xaxis: [{
label: {
text: "text xaxis annotation"
}
}],
},
responsive: [{
breakpoint: 480,
options: {
legend: {
position: "bottom",
horizontalAlign: "left"
}
}
}]
};
const series = usedSourcesData;
return {
chartOptions,
series,
otherSources,
unusedSources
};
}
});
</script>
Edited
I will attach the LeadService.ts class as well as the ApiService.ts class so you can see where the data is coming from
LeadService.ts
import ApiService from "#/core/services/ApiService";
import {Lead} from "#/core/interfaces/lead";
export default class LeadService {
getLeads() {
const accountInfo = JSON.parse(localStorage.getItem('accountInfo') || '{}');
ApiService.setHeader();
return ApiService.query("/leads", {params: {client_id : accountInfo.client_id}})
.then(({ data }) => {
let leadData: Lead[] = data['Items'];
return leadData;
})
.catch(({ response }) => {
return response;
});
}
}
ApiService.ts
import { App } from "vue";
import axios from "axios";
import VueAxios from "vue-axios";
import JwtService from "#/core/services/JwtService";
import { AxiosResponse, AxiosRequestConfig } from "axios";
import auth from "#/core/helpers/auth";
/**
* #description service to call HTTP request via Axios
*/
class ApiService {
/**
* #description property to share vue instance
*/
public static vueInstance: App;
/**
* #description initialize vue axios
*/
public static init(app: App<Element>) {
ApiService.vueInstance = app;
ApiService.vueInstance.use(VueAxios, axios);
ApiService.vueInstance.axios.defaults.baseURL = "https://api.domain.com/";
}
/**
* #description set the default HTTP request headers
*/
public static setHeader(): void {
ApiService.vueInstance.axios.defaults.headers.common[
"Authorization"
] = `Bearer ${auth.getSignInUserSession().getIdToken().jwtToken}`;
ApiService.vueInstance.axios.defaults.headers.common[
"Content-Type"
] = "application/json application/vnd.api+json";
}
/**
* #description send the GET HTTP request
* #param resource: string
* #param params: AxiosRequestConfig
* #returns Promise<AxiosResponse>
*/
public static query(
resource: string,
params: AxiosRequestConfig
): Promise<AxiosResponse> {
return ApiService.vueInstance.axios.get(resource, params).catch(error => {
// #TODO log out and send home if response is 401 bad auth
throw new Error(`[KT] ApiService ${error}`);
});
}
}
export default ApiService;
I think the issue is caused when you're calling the data from the api.
This code:
beforeCreate: async function() {
this.leadData = await new LeadService().getLeads()
},
I'd refactor to
async created () {
const service = new LeadService()
const value = await service.getLeads()
}
Also it would be nice to be able to see how you're fetching your data.
Sometimes this code: const value = await axios.get('/api/getStuff').data can be problematic because of paratheses issues. Which causes the same issue you described of, hot reloading working, but fresh not. I suspect the same sort of issue relies of the code executing like => (await new LeadService()).getLeads() Where you're probably awaiting the class, rather than the actual async code.
I am working on an e-commerce site using rails and VueJS. I have a component file to display the orders of a given user. I am using a v-for loop to iterate and display all the user's order info. each order is linked to a carted products table where the product I.D. is located. I want to display the product information of each product within each order. I have a productShow function that makes an axios request to the backend and retrieves the product's info, the problem is I'm not sure how to capture the product ID from each of the products in the carted product array to send with the request. Even if I manage that how would I display each product within each order and the order's info as well? I have been unsuccessful in my attempts thus far and am looking for some guidance. code and info below.
ordersIndex.Vue component:
<div class="home">
<h1>{{ message }}</h1>
<div v-for="order in orders">
<h2>Order Number:{{ order.id }}</h2>
<h1 v-for="cartedProduct in order.carted_products"> <strong>{{ cartedProduct }}</strong> </h1>
<h3>SUBTOTAL:{{ order.subtotal }}</h3>
<h3>TAX:{{ order.tax }}</h3>
<h3>TOTAL: {{ order.total }}</h3>
</div>
</div>
</template>
<style>
</style>
<script>
import axios from "axios";
export default {
data: function () {
return {
message: "Your Orders",
orders: [],
anOrder: [],
cartedProducts: [],
product: {},
productId: "",
};
},
created: function () {
console.log("In Created...");
this.orderIndex();
// this.cartedProductsonOrder();
},
methods: {
orderIndex: function () {
console.log("In orderIndex...");
axios.get("/api/orders").then((response) => {
console.log(response.data);
this.orders = response.data;
this.cartedProductsonOrder();
});
},
productShow: function (id) {
console.log("in products show");
axios.get("/api/products/" + id).then((response) => {
console.log(response.data);
this.product = response.data;
console.log("Line 53");
console.log(this.product);
this.cartedProducts.push(this.product);
console.log(this.cartedProducts);
});
},
cartedProductsonOrder: function () {
console.log("In cartedProductsonOrder method....");
console.log(this.orders);
for (let i = 0; i < this.orders.length; i++) {
console.log("Line 62");
console.log(this.orders[i].carted_products);
this.cartedProducts = this.orders[i].carted_products;
for (let j = 0; j < this.cartedProducts.length; j++) {
console.log("Line 67");
console.log(this.cartedProducts[j]);
console.log(this.cartedProducts[j].product_id);
this.productId = this.cartedProducts[j].product_id;
this.productShow(this.productId);
}
}
},
},
};
</script>
Screenshots of console log:
order response
carted products
This is completely untested however, it should give you a good idea of how to accomplish what you're after. I have also made a few other changes such as using fat arrow functions and changing to forEach loops instead of basic for loops.
<template>
<div class="home">
<h1>{{ message }}</h1>
<div v-for="order in orders">
<h2>Order Number:{{ order.id }}</h2>
<h1 v-for="product in order.carted_products"> <strong>{{ cartedProduct }}</strong> </h1>
<h3>SUBTOTAL:{{ order.subtotal }}</h3>
<h3>TAX:{{ order.tax }}</h3>
<h3>TOTAL: {{ order.total }}</h3>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
data: () => ({
message: "Your Orders",
orders: [],
cartedProducts: []
}),
mounted: {
this.doGetOrders()
},
methods: {
doGetOrders: () => {
axios.get(`/api/order`).then((response) => {
this.orders = response.data
this.doCartedProductsInOrder()
})
},
doCartedProductsInOrder: () => {
if (this.order.length >= 1) {
this.order.forEach((order) => {
if (order.cartedProducts >= 1) {
order.cartedProducts.forEach((product) => {
this.cartedProducts.push(this.doGetProductInfo(productId))
})
}
})
}
},
doGetProductInfo: (productId) => {
axios.get(`/api/products/${productId}`).then((response) => {
if (response.status === 200) {
return response.data
}
})
}
}
}
</script>
Suppose, My Name is "Aerofoil Todo Kite" I want AK
I got a code from Stackoverflow. I hope it will work. But my question is, I am printing data from Array of Objects with v-for loop.
How do I pass the Name to compute that?
I think, Computed Property don't accept Parameter.
Then what will be the process??
Method can do. But It is calling for many times!!!
data:
tableData: [
{ customer: 'EE Fashion'},
{ customer: 'Tom Hangs Ron'}
}]
methods: {
nameOfCompany(fullName) {
console.log(fullName);
return "HL";
}
}
Code of Mine:
<template slot-scope="scope">
<p style="margin-top: 5px;"><b>{{ nameOfCompany(scope.row.customer) }}</b></p>
</template>
Here is the problem:
{{ nameOfCompany(scope.row.customer) }}
This function is calling for many times!!!!
What will be the approach to do that?
You may write a Customer component so you only compute the company's name once:
It takes a name, and in data, computes the associated companyName.
const mytable = {
props: ['rows'],
template: `
<table>
<tr v-for="row in rows">
<slot :row="row"></slot>
</tr>
</table>
`
}
const mycustomer = {
props: ['name'],
data () {
return {
companyName: this.name.split(' ').map(x => x[0].toUpperCase()).join('')
}
},
template: `
<td>{{ name }} - <abbr>{{ companyName }}</abbr></td>
`
}
let vm = new Vue({
el:'#el',
components: { mytable, mycustomer },
template: `
<mytable :rows="['grod zi', 'tu rok']">
<template v-slot:default="{ row: user }">
<mycustomer :name="user"/>
</template>
</mytable>
`
});
abbr {
color: blue;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="el"></div>
You can use either filters or computed.
Filters: Vue.js allows you to define filters that can be used to apply common text formatting. Filters are usable in two places: mustache
interpolations and v-bind expressions (the latter supported in
2.1.0+). Filters should be appended to the end of the JavaScript expression, denoted by the “pipe” symbol: doc
new Vue({
el: "#app",
data: {
tableData: [
{ customer: 'EE Fashion', company_name: "FOO BAR" },
{ customer: 'Tom Hangs Ron', company_name: "BAZ FOO BAR"},
{ customer: 'Jerry', company_name: "Lorem Ipsum Dorsum Zaren" }
],
},
filters: {
short_hand (company_name) {
// You can put your logic here...
let words = company_name.split(" ")
let short_hand = words[0][0] + words[words.length-1][0]
return short_hand // <-- The return value as Per logic
}
},
computed: {
getTableData () {
return this.tableData.map(data => {
let words = data.company_name.split(" ")
let short_hand = words[0][0] + words[words.length-1][0]
return { short_hand, ...data }
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
USING FILTER: <br>
<div
v-for="(data, i) in tableData"
:key="'using-filter-'+i"
>
{{ data.company_name | short_hand }}
</div>
<hr> USING COMPUTED: <br>
<div
v-for="(data, i) in getTableData"
:key="'using-computed-'+i"
>
{{ data.short_hand }}
</div>
</div>
I would like to transform the span into a real element. When I try this way the appendChild gives me an error because the variable is a string and not and object. Any ideas?
export default{
data(){
....
}
methods:{
update_period: function(event){
var start = moment(event.start).format('M/D/Y'),
end = moment(event.end).format('M/D/Y');
var span = `<span #click="remove">{{ start }} - {{ end }}</span>`
this.$refs.spans.appendChild(span);
},
remove: function(event){
event.target.remove()
}
}
}
<div ref="spans">
</div>
You can get the same result in this way:
<template>
<div>
<span #click="remove" v-if="period">{{ period }}</span>
</div>
</template>
<script>
export default {
data() {
return {
period: null,
}
},
methods:{
update_period(event) {
this.period = moment(event.start).format('M/D/Y') + ' - ' + moment(event.end).format('M/D/Y')
},
remove() {
this.period = null;
}
}
}
</script>
I am using vue #keyup on input, this uses method with axios call:
<template>
<div>
<input type="text" placeholder="Where are you from?" v-model="from" #keyup="getPlace">
<ul>
<li v-for="place in places">{{ place.name }}<li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
places: [],
from: ''
}
},
methods() {
if(this.from.length <= 3) { this.places = []; return; }
getPlace() {
axios.get('/places?name='+this.from).then((res)=>{
this.places = [];
for(var i = 0; i<res.data.length; i++) {
this.places.push(res.data[i]);
}
}).catch((err)=>{console.log(err)});
}
}
};
</script>
Now this works but it has a big problem, each call it updates array of places but it is late, so the method is called and array has been returned to [] but after the response is back it is populating the array for each keyup (if you type fast)... I am switching from jquery front to this and I never ever had a problem with this :O
This doesn't answer the "how to abort method", but you could use a different approach for your example: Fetching all places at the beginning (mounted()) and filter them based on the input in the frontend by using a computed property.
var app = new Vue({
el: '#app',
data() {
return {
places: [],
from: ''
}
},
mounted() {
this.getPlaces();
},
computed: {
placesList() {
let result = []
let places = this.places
let from = this.from
if (from !== '') {
result = this.places.filter(element => {
return element.name.toLowerCase().includes(from.toLowerCase())
})
} else {
result = places
}
return result
}
},
methods: {
getPlaces() {
// axios call..
this.places = [{
id: 1,
name: "Germany"
}, {
id: 2,
name: "USA"
}, {
id: 3,
name: "Spain"
}]
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<input type="text" placeholder="Where are you from?" v-model="from">
<br /> input text: {{ from }}
<hr>
<ul>
<li v-for="place in placesList" :key="place.id">
{{ place.name }}
</li>
</ul>
</div>