How to dispatch a Vue computed property - javascript

I´m trying to dispatch an object which is created in a computed.
I can´t get it to work as I´m fairly new to vue.js
I want to dispatch the object "updateObject" to the vuex-store.
Tried with setters but didn´t work. I think if I can set the "varia" object to the same object like "updateObject" then I could maybe dispatch it?
Hope somebody can help me.
Here is my code:
<template>
<div class="detail">
<b-row align-v="center"><b-button variant="success" #click="submit()">submit</b-button></b-row>
// some more code...
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {
subID: '',
res: '',
showAlert: true,
varia: null
}
},
computed: {
...mapState([
'FA',
'Main',
'Sub',
'layouttype'
]),
getVariable: function (Sub, layouttype) {
const subID = this.layouttype.sub_id
var filterObj = this.Sub.filter(function (e) {
return e.sub_id === subID
})
console.log(filterObj)
return filterObj
},
updateObject: {
// getterfunction
get: function () {
var len = this.getVariable.length
var res = []
for (var i = 0; i < len; i++) {
if (i in this.getVariable) {
var val = this.getVariable[i].variable
res.push(val)
}
}
console.log(res)
var ergebnis = {}
res.forEach(key => {
if (this.FA[key]) {
ergebnis[key] = this.FA[key]
}
})
return ergebnis
},
// setterfunction
set: function (value) {
this.varia = value
}
}
},
methods: {
submit () {
this.$store.dispatch('sendData', this.ergebnis)
}
}
}
</script>
It tell´s me "this.ergebnis" is undefined

You can try it declaring "ergebnis" as global variable under data as
export default {
data () {
return {
subID: '',
res: '',
showAlert: true,
varia: null,
ergebnis : {}
}
},
computed: {
...mapState([
'FA',
'Main',
'Sub',
'layouttype'
]),
getVariable: function (Sub, layouttype) {
const subID = this.layouttype.sub_id
var filterObj = this.Sub.filter(function (e) {
return e.sub_id === subID
})
console.log(filterObj)
return filterObj
},
updateObject: {
// getterfunction
get: function () {
var len = this.getVariable.length
var res = []
for (var i = 0; i < len; i++) {
if (i in this.getVariable) {
var val = this.getVariable[i].variable
res.push(val)
}
}
console.log(res)
res.forEach(key => {
if (this.FA[key]) {
this.ergebnis[key] = this.FA[key]
}
})
return this.ergebnis
},
// setterfunction
set: function (value) {
this.varia = value
}
}
},
methods: {
submit () {
this.$store.dispatch('sendData', this.ergebnis)
}
}
}
Now ergebnis is accessible

Related

Vue infinite UI update loop with function as param

I'm new in Vue.js and tried to convert some legacy code for pagination. I've created a pager component which accepts a function as one of its params. But it's causing an infinite UI render loop.
Could you help me to resolve or suggest some solution for such problem?
Here is my pager component js:
const PagerComponent = {
name: "pagerComponent",
template: "#pagerComponent",
props: {
pageSize: Number,
pageIndex: Number,
totalPages: Number,
totalRecords: Number,
pageSlide: Number,
hasNextPage: Boolean,
hasPrevPage: Boolean,
pages: Array,
loadFunc: Function
},
data() {
return {
pager: {
pageSize: 0,
pageIndex: 0,
totalPages: 0,
totalCount: 0,
pageSlide: 1,
hasNextPage: false,
hasPrevPage: false,
pages: [],
loadFunc: function () { }
}
}
},
methods: {
load(index) {
this.pager.pageIndex = index;
if (this.pager.loadFunc != null) {
this.pager.loadFunc();
}
},
isActivePage(page) {
return this.pager.pageIndex + 1 == page;
},
update(newPager) {
this.pager.pageSize = newPager.pageSize;
this.pager.pageIndex = newPager.pageIndex;
this.pager.totalPages = newPager.totalPages;
this.pager.totalCount = newPager.totalCount;
this.pager.hasNextPage = newPager.hasNextPage;
this.pager.hasPrevPage = newPager.hasPrevPage;
this.generatePages();
},
generatePages() {
this.pager.pages = [];
var pageNum = this.pager.pageIndex + 1;
var pageFrom = Math.max(1, pageNum - this.pager.pageSlide);
var pageTo = Math.min(this.pager.totalPages, pageNum + this.pager.pageSlide);
pageFrom = Math.max(1, Math.min(pageTo - this.pager.pageSlide, pageFrom));
pageTo = Math.min(this.pager.totalPages, Math.max(pageFrom + this.pager.pageSlide, pageNum == 1 ? pageTo + this.pager.pageSlide : pageTo));
for (var i = pageFrom; i <= pageTo; i++) {
this.pager.pages.push(i);
}
}
},
computed: {
hasPages() {
if (this.pager.pages == null)
return false;
return this.pager.pages.length > 0;
},
doNotHavePrevPage() {
return !this.pager.hasPrevPage;
},
doNotHaveNextPage() {
return !this.pager.hasNextPage;
}
},
beforeMount() {
this.pager.pageSize = this.pageSize;
this.pager.pageIndex = this.pageIndex;
this.pager.totalPages = this.totalPages;
this.pager.totalCount = this.totalRecords;
this.pager.hasNextPage = this.hasNextPage;
this.pager.hasPrevPage = this.hasPrevPage;
this.pager.loadFunc = this.loadFunc;
this.pager.pages = this.pages || [];
this.generatePages();
},
mounted() {
}
}
Here is how it's used in html:
<pager-Component v-bind="Pager" v-bind:load-Func="GetItems" ref="pager"></pager-Component>
And GetItems funciton:
function () {
var self = this;
const data = {
Pager: self.Pager,
Filter: []
};
$.ajax({
url: self.GetItemsUrl,
type: "POST",
dataType: "json",
busy: self.Loading,
data: data
}).done(function (result) {
if (result.isSuccess) {
self.$refs.pager.update(result.data.pager);
self.Items.splice(0);
result.data.items.map(function (value, key) {
self.Items.push(value);
});
}
else {
alert(result.data.errors[0]);
}
});
}
Finally after tones of tests, the solution was found and it's pretty easy.
I just needed to use v-on:click instead of :click. I just don't know why lot of tutorials suggest to use :click if it doesn't work
So for example use
<div v-on:click="load(pageIndex)">My button</div>
instead of
<div :click="load(pageIndex)">My button</div>

How to set template reference dynamically inside function

I have a function below from which I am calling setModelData.
My query is I want to make template variable dynamic for both getGlobalList and getNonGlobalList function.
For Example
1) if getGlobalList is running it will set template: this.CustomTableItemTemplate
inside the setModelData function.
2) if getNonGlobalList is running it will pass template: this.NonGlobalCustomTableItemTemplate
inside the setModelData function.
Thanks for help
Code
#ViewChild('CustomTableItemTemplate') CustomTableItemTemplate: TemplateRef<any>;
#ViewChild('NonGlobalCustomTableItemTemplate') NonGlobalCustomTableItemTemplate: TemplateRef<any>;
ngOnInit() {
this.getGlobalList();
this.getNonGlobalList();
}
getGlobalList() {
this.globalSchemamodel.data.length = 0;
this.Service.getGlobalList(
this.constructQueryParam(this.globalSchemamodel, 'global'))
.subscribe((response: any) => {
const globalSchemas = response ? response.data : [];
if (globalSchemas.records) {
this.setModelData(globalSchemas, this.globalSchemamodel);
}
});
}
getNonGlobalList() {
this.nonGlobalSchemamodel.data.length = 0;
this.Service.getList(
this.constructQueryParam(this.nonGlobalSchemamodel, 'nonglobal'))
.subscribe((response: any) => {
const nonglobalschemaslist = response ? response.data : [];
if (nonglobalschemaslist.records) {
this.setModelData(nonglobalschemaslist, this.nonGlobalSchemamodel);
}
});
}
setModelData(globalSchemas, globalSchemamodel) {
for (const schema of globalSchemas.records) {
const tableModel = [
new TableItem({ data: schema.schema_id }),
this.isAdminRole ? new TableItem({
data:[{ 'schemaId': schema.schema_id }],
**template: this.CustomTableItemTemplate**
}) : null
];
globalSchemamodel.data.push(tableModel);
}
}
setModelData function needs another template param that's for sure.
Additionally you can extract similar code from getNonGlobalList and getGlobalList
ngOnInit() {
this.getList(
this.globalSchemamodel,
this.Service.getGlobalList,
'global',
this.CustomTableItemTemplate
);
this.getList(
this.nonGlobalSchemamodel,
this.Service.getList',
'nonglobal',
this.NonGlobalCustomTableItemTemplate
);
}
getList(model: any, functionToCall: any, paramName: string, template: TemplateRef<any>) {
model.data.length = 0;
functionToCall(
this.constructQueryParam(model, paramName))
.subscribe((response: any) => {
const schemas = response ? response.data : [];
if (schemas.records) {
this.setModelData(schemas.records, model);
}
});
}
setModelData(schemas: any[], schemaModel: any, template: TemplateRef<any>) {
for (const { schema_id } of schemas) {
const tableModel = [
new TableItem({
data: schema_id
}),
this.isAdminRole ? new TableItem({
data: [
{
'schemaId': schema_id
}
],
template
}) : null
];
schemaModel.data.push(tableModel);
}
}

Yields "TypeError: Cannot read property 'xxxx' of undefined" after running jest with Vue

I'm trying to make a test using jest with Vue.
the details below.
Problem:
Can't mount using shallowMount option.
Situation:
Run the test after mounting the component using shallowMount option that provides in Vue-test-utils.
Throw error "Cannot read property 'XXXX' of undefined
This is my test code.
import myComponent from '#/~';
import Vuex from 'vuex';
import Vuelidate from 'vuelidate';
import { shallowMount, createLocalVue } from '#vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(Vuelidate);
describe('myComponent~', () => {
let store;
beforeEach(() => {
store = new Vuex.Store({
modules: {
user: {
namespaced: true,
getters: {
profile: () => {
const profile = { name: 'blahblah' };
return profile;
},
},
},
},
});
});
describe('profile.name is "blahblah"', () => {
it('return something~', () => {
const wrapper = shallowMount(myComponent, {
localVue,
store,
mocks: {
$api: {
options: {
testMethod() {
return new Promise((resolve, reject) => {
resolve('test');
});
},
},
},
$i18n: {
t() {
return {
EN: 'EN',
KO: 'KO',
JP: 'JA',
SC: 'zh-CN',
TC: 'tw-CN',
};
},
},
},
});
expect(wrapper.find('.profile').text()).toBe('blahblah');
});
I think the problem is that property isn't set as a specified value or an empty value like an array or object.
But I don't know how I set properly the properties in my logic.
For example,
when the error yields "Cannot read property 'images' of undefined",
I add to a wrapper in the relevant method like this.
exampleMethod() {
this.something = this.something.map(item => {
if (item.detailContent.images) { // <-- the added wrapper is here
~~~logic~~~~
}
})
}
But the undefined properties are so many, I also think this way is not proper.
How I do solve this problem?
added
These are details about the above example method:
exampleMethod() {
this.something = this.something.map(item => {
let passValidation = false;
let failValidation = false;
if (item.detailContent.images) {
if (this.detail.showLanguages.includes(item.code)) {
if (this.configId !== 'OPTION1') {
item.detailContent.images = item.detailContent.images.map(element => {
return {
...element,
required: true,
}
});
}
checkValidationPass = true;
} else {
if (this.configId !== 'OPTION1') {
item.detailContent.images = item.detailContent.images.map(element => {
return {
...element,
required: false,
}
});
}
checkValidationPass = false;
}
return {
...item,
required: passValidation,
warning: failValidation,
}
}
});
if (this.configId === 'OPTION2') {
this.checkOption2Validation();
} else if (this.configId === 'OPTION3') {
this.checkOption3Validation();
} else {
this.checkOption1Validation();
}
},
And this is 'this.something':
data() {
return {
something: []
}
}
The detailContent is set here.
setMethod() {
this.something = [
...this.otherthings,
];
this.something = this.something.map(item => {
let details1 = {};
if (this.configId === 'OPTION2') {
details1 = {
images: [
{ deviceType: 'PC', titleList: [null, null], imageType: 'IMAGE' },
{ deviceType: 'MOBILE', titleList: [null, null, null] }
]
};
} else if (this.configId === 'OPTION3') {
details1 = {
images: [
{ deviceType: 'PC' },
{ deviceType: 'MOBILE' }
],
links: { linkType: 'EMPTY' },
};
}
let details2 = {
mainTitle: {
content: null,
}
}
let checkValidation = false;
this.detail.detailLanguages.forEach(element => {
if (element.language === item.code) {
details1 = { ...element };
if (!!element.mainTitle) {
details2 = { ...element };
} else {
details2 = {
...details2,
...element
};
}
if (this.configId !== 'OPTION1') {
details1.images = details1.images.map(image => {
return {
...image,
required: true,
}
});
}
checkValidation = true;
}
});
return {
...item,
detailContent: this.configId !== 'OPTION1' ? details1 : details2,
required: false,
warning: false,
}
});
},

Vue Watch doesnt Get triggered when using axios

Hey guys I have this code that fetches data from database usin axios, and in the .then() function I set a data property, watch doesnt trigger. Here is some code that I currently have. And thank you in advance!
export default {
name: '..',
data() {
return {
autocompleteOn: false
}
},
watch: {
autocompleteOn(oldVal, newVal) {
console.log('autocomplet') // doesnt trigger this
}
},
methods: {
fetchAutocompleteResults: _.debounce((filter) => {
let $this = this;
let data = {
filter: filter,
page: $this.page
};
filter.resources.response = [];
filter.loading = true;
axios.post(BASE_URL + '/search/filter', data).then(function(response) {
if (response.data.length) {
filter.autocompleteOn = true;
$this.autocompleteOn = true;
filter.resources.response = filter.resources.response.concat(response.data);
$this.currentFilter = filter;
$this.page++;
console.log($this.autocompleteOn); // this is correct
}
filter.loading = false;
});
}, 300)
}
}
The debounce with an arrow function is making the this be something other than the Vue instance (e.g. window).
Instead of:
methods: {
fetchAutocompleteResults: _.debounce((filter) => {
Use:
methods: {
fetchAutocompleteResults: _.debounce(function (filter) {
// ^^^^^^^^ ^^^
Demo:
new Vue({
el: '#app',
data() {
return {
autocompleteOn: false
}
},
watch: {
autocompleteOn(oldVal, newVal) {
console.log('autocomplet') // doesnt trigger this
}
},
methods: {
fetchAutocompleteResults: _.debounce(function (filter) { // CHANGED from arrow function
let $this = this;
let data = {
filter: filter,
page: $this.page
};
filter.resources.response = [];
filter.loading = true;
// changed data for demo
data = [{title: 'foo', body: 'bar', userId: 1}];
// changed URL for demo
axios.post('https://jsonplaceholder.typicode.com/posts', data).then(function(response) {
if (response.data.length) {
filter.autocompleteOn = true;
$this.autocompleteOn = true;
filter.resources.response = filter.resources.response.concat(response.data);
$this.currentFilter = filter;
$this.page++;
console.log($this.autocompleteOn); // this is correct
}
filter.loading = false;
});
}, 300)
}
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>
<div id="app">
<button #click="fetchAutocompleteResults({resources: {}})">fetchAutocompleteResults</button>
</div>

Vuejs 2 two connected components unit/integration testing

I have 2 Vuejs components that are connected to each other. The first component is updating data in the second one. What is the best approach for integration testing? I am using vuejs 2.
VoucherComponent:
import Store from '../store';
import Ajax from '../_helpers/ajax';
const Voucher = {
name: 'voucher',
props: ['id'],
template: '',
data () {
return {
voucherCode: null,
priceDetails: Store.priceDetails,
vouchers: Store.vouchers
}
},
beforeCreate() {
Store.vouchers = []
},
methods: {
validateVoucher() {
let totalPrice = Store.total;
let vouchers = this.vouchers;
let voucherCode = this.voucherCode;
let id = this.id;
let priceDetails = this.priceDetails;
let voucher = new Ajax('/vouchers/redeem/' + voucherCode + '/' + id + '/' + totalPrice + '/', 'GET');
if (!this.checkVoucherPriceDetails()) {
voucher.ajaxCall(function (response) {
if (!response.error) {
vouchers.push({
code: voucherCode,
value: parseFloat(response.data.discount).toFixed(2),
type: 'voucher',
name: 'voucher',
description: 'Your voucher code: ' + voucherCode
});
priceDetails.push({
code: voucherCode,
price: parseFloat((-1.00 * response.data.discount)).toFixed(2),
description: 'Your voucher code: ' + voucherCode,
type: 'voucher'
});
} else {
return false;
}
});
}
},
removeVoucher(voucher) {
this.voucherCode = voucher.code;
this.clearVouchersFromPriceDetails();
var stringifyVoucher = JSON.stringify(voucher);
for (var i = 0, len = this.vouchers.length; i < len; i++) {
if (stringifyVoucher === JSON.stringify(this.vouchers[i])) {
this.vouchers.splice(i, 1);
break;
}
}
// return true;
},
clearVouchersFromPriceDetails() {
for (var i = this.priceDetails.length - 1; i >= 0; i--) {
if (this.priceDetails[i].code === this.voucherCode) {
this.priceDetails.splice(i, 1);
}
}
},
checkVoucherPriceDetails() {
for (var i = this.priceDetails.length - 1; i >= 0; i--) {
if (this.priceDetails[i].code === this.voucherCode) {
return true;
}
}
return false;
}
},
mounted () {
Store.debug && console.log("Init voucher component");
}
};
export default Voucher;
PriceDetailsComponent:
import Store from '../store';
const PriceDetails = {
name: 'price-details',
props: ['price','fee'],
data() {
return {
priceDetails: Store.priceDetails,
store: Store
}
},
created() {
this.priceDetails.push({
price: this.price.toFixed(2),
description: "Buchung Preis",
type: 'booking'
});
this.priceDetails.push({
price: this.fee.toFixed(2),
description: "Buchungsgebühren",
type: 'booking_fee'
});
},
computed: {
totalPrice() {
let total = 0.00;
let insurancePrice = 0.00;
for (var detailKey in this.priceDetails) {
var detail = this.priceDetails[detailKey];
total += parseFloat(detail.price);
if (detail.type == 'insurance') {
insurancePrice = detail.price;
}
this.store.total = total;
}
return parseFloat(total).toFixed(2);
}
},
mounted() {
Store.debug && console.log("Init price-details");
}
};
export default PriceDetails;
Store:
const Store = {
debug: true,
priceDetails: [],
total: 0.00
};
export default Store;
Thanks a lot!

Categories

Resources