Bind vuejs function as data with context vuejs - javascript

I want to use a function as a data property. This seems to work fine as in the case of the 'works' data property. However I need the this context in the function so that I can calculate values stored in the this.shoppingCart (another property).
Is this possible? If so what am I doing wrong?
new Vue({
el: '#vueApp',
data: {
shoppingCart: [],
works : function () {
return "testfunc";
},
totalPriceCalcProperty : function () {
this.totalPrice = this.shoppingCart.reduce(function(total, cartItem){
console.log(total, cartItem);
return total + parseFloat(cartItem.price);
}, 0);
}
},
methods: {
totalPriceCalc: function () {
this.totalPrice = this.shoppingCart.reduce(function(total, cartItem){
console.log(total, cartItem);
return total + parseFloat(cartItem.price);
}, 0);
},
}

You should implement this by using methods, not data.
data is helping you to store something rather than handle some actions.
In methods, you can call this.xxx to get the properties from data or property

Related

Vue.js - watch particular properties of the object and load data on change

I have Vue component with prop named product, it is an object with a bunch of properties. And it changes often.
export default {
props: {
product: {
type: Object,
default: () => {},
},
},
watch: {
'product.p1'() {
this.loadData()
},
'product.p2'() {
this.loadData()
},
},
methods: {
loadData() {
doApiRequest(this.product.p1, this.product.p2)
}
},
}
The component should load new data when only properties p1 and p2 of product are changed.
The one approach is to watch the whole product and load data when it is changed. But it produces unnecessary requests because p1 and p2 may not have changed.
Another idea is to watch product.p1 and product.p2, and call the same function to load data in each watcher.
But it may happen that both p1 and p2 changed in the new version of the product, it would trigger 2 calls.
Will it be a good solution to use a debounced function for data load?
Or rather use single watcher for the whole product and compare new p1 and p2 stringified with their old stringified versions to determine if data loading should be triggered?
There are several approaches to this, each with pros and cons.
One simple approach I do is to use a watch function that accesses each of the properties you want to watch and then returns a new empty object. Vue knows product.p1 and product.p2 were accessed in the watch function, so it will re-execute it any time either of those properties change. Then, by returning a new empty object instance from the watch function, Vue will trigger the watch handler because the watch function returned a new value (and thus what is being watched "changed").
created() {
this.$watch(() => {
// Touch the properties we want to watch
this.product.p1;
this.product.p2;
// Return a new value so Vue calls the handler when
// this function is re-executed
return {};
}, () => {
// p1 or p2 changed
})
}
Pros:
You don't have to stringify anything.
You don't have to debounce the watch handler function.
Cons:
You can't track the previous values of p1 and p2.
Take care if this.product could ever be null/undefined.
It will always trigger when p1 or p2 are changed; even if p1 and p2 are set back to their previous values before the next micro task (i.e. $nextTick()); but this is unlikely to be a problem in most cases.
You need to use this.$watch(). If you want to use the watch option instead then you need to watch a computed property.
Some of these cons apply to other approaches anyway.
A more compact version would be:
this.$watch(
() => (this.product.p1, this.product.p2, {}),
() => {
// changed
}
})
As some of other developers said you can use computed properties to monitor the changing of product.p1 or product.p2 or both of them and then calling loadData() method only once in each case. Here is the code of a hypothetical product.vue component:
<template>
<div>
this is product compo
</div>
</template>
<script>
export default {
name: "product",
watch: {
p1p2: function(newVal, oldVal) {
this.loadData();
}
},
props: {
productProp: {
type: Object,
default: () => {},
},
},
computed: {
p1p2: function() {
return this.productProp.p1 + this.productProp.p2;
}
},
methods: {
loadData() {
console.log("load data method");
}
},
}
</script>
I renamed the prop that it received to productProp and watched for a computed property called p1p2 in that. I supposed that the values of data are in String format (but if they are not you could convert them). Actually p1p2 is the concatenation of productProp.p1 and productProp.p2. So changing one or both of them could fire the loadData() method. Here is the code of a parent component that passes data to product.vue:
<template>
<section>
<product :productProp = "dataObj"></product>
<div class="d-flex justify-space-between mt-4">
<v-btn #click="changeP1()">change p1</v-btn>
<v-btn #click="changeP2()">change p2</v-btn>
<v-btn #click="changeBoth()">change both</v-btn>
<v-btn #click="changeOthers()">change others</v-btn>
</div>
</section>
</template>
<script>
import product from "../components/product";
export default {
name: 'parentCompo',
data () {
return {
dataObj: {
p1: "name1",
p2: "name2",
p3: "name3",
p4: "name4"
}
}
},
components: {
product
},
methods: {
changeP1: function() {
if (this.dataObj.p1 == "name1") {
this.dataObj.p1 = "product1"
} else {
this.dataObj.p1 = "name1"
}
},
changeP2: function() {
if (this.dataObj.p2 == "name2") {
this.dataObj.p2 = "product2"
} else {
this.dataObj.p2 = "name2"
}
},
changeBoth: function() {
if (this.dataObj.p2 == "name2") {
this.dataObj.p2 = "product2"
} else {
this.dataObj.p2 = "name2"
}
if (this.dataObj.p1 == "name1") {
this.dataObj.p1 = "product1"
} else {
this.dataObj.p1 = "name1"
}
},
changeOthers: function() {
if (this.dataObj.p3 == "name3") {
this.dataObj.p3 = "product3"
} else {
this.dataObj.p3 = "name3"
}
}
},
}
</script>
You can test the change buttons to see that by changing dataObj.p1 or dataObj.p2 or both of them the loadData() method only called once and by changing others it is not called.
for you do ontouch event in vuejs while using it with your HTML inline
and you have an object model that house you other variable and you need to validate the any of the variable you will need to put them in watcher and use qoute ('') to tell vuejs that this is from a the model.email
hope this is useful
data() {
return {
model: {},
error_message: [],
}
},
watch: {
'model.EmailAddress'(value) {
// binding this to the data value in the email input
this.model.EmailAddress = value;
this.validateEmail(value);
},
},
methods: {
validateEmail(value) {
if (/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(value)) {
this.error_message['EmailAddress'] = '';
} else {
this.error_message['EmailAddress'] = 'Invalid Email Address';
}
}
},

Access variable value in mounted function vuejs

I have a very simple application that initiates a search and filter based on the query parameters. When I initiate the query https://example.com/?filter=2134 it initiates the search and shows me the result of schools. This means that the searchSchools() function is being executed and the results are being fetched.
However, then I execute the filterSuggestions() function, it doesn't seem to apply the filter.
However, when I do a console.log(suggestedSchools) within mounted, it returns empty.
const app = new Vue({
el: '#app',
data: {
suggestedSchools : [],
filter : '',
filteredSchools : [],
},
mounted: function () {
// checking some get parameters and conditions and triggering the search
this.searchSchools(); // function that initiates ajax request and store the results into suggestedSchools
this.filter = 2134; // the postcode value comes from the get request
this.filterSuggestions(); // function that applies the postcode filter to the suggestedSchools list and assign the filtered results to filteredSchools.
},
methods: {
searchSchools() {
axios.get('/search-school').then(response => {
this.suggestedSchools = response.data;
this.filteredSchools = response.data;
})
},
filterSuggestions()
{
this.filteredSchools = this.suggestedSchools.filter(school => {
// filtering logic
}
},
},
});
That's because the searchSchools function makes an asynchronous request so when filterSuggestions function is executed it finds the suggestedSchools array empty. I suggest it should be more like this:
const app = new Vue({
el: '#app',
data: {
suggestedSchools : [],
filter : '',
filteredSchools : [],
},
mounted: function () {
// checking some get parameters and conditions and triggering the search
this.searchSchools(); // function that initiates ajax request and store the results into suggestedSchools
this.filter = 2134; // the postcode value comes from the get request
},
methods: {
searchSchools() {
axios.get('/search-school').then(response => {
this.suggestedSchools = response.data;
this.filteredSchools = response.data;
this.filteredSuggestions()
})
},
filterSuggestions()
{
this.filteredSchools = this.suggestedSchools.filter(school => {
// filtering logic
}
},
},
});

Vue push unique values to a list

var vue_app = new Vue({
el: '#id1',
data: {
v1:[],
},
methods:{
pushUnique: function() {
this.v1.push({'id':1,'name':'josh'});
this.v1.push({'id':1,'name':'josh'}); //this should not work.
},
},
});
In above code the second push should not execute. I would like to keep id unique. How can this be done in Vue.
THanks
I would move to storing data in an object (keyed by id) and use a computed property to produce your v1 array. For example
data: {
v1obj: {}
},
computed: {
v1 () {
return Object.keys(this.v1obj).map(id => ({ id, name: this.v1obj[id] }))
}
}
Then you can use methods like Object.prototype.hasOwnProperty() to check for existing keys...
methods: {
pushUnique () {
let id = 1
let name = 'josh'
if (!this.v1obj.hasOwnProperty(id)) {
this.v1obj[id] = name
}
}
}

Knockout.js Adding a Property to Child Elements

My code doesn't create a new property under the child element of knockout viewmodel that is mapped by knockout.mapping.fromJS.
I have:
//model from Entity Framework
console.log(ko.mapping.toJSON(model));
var viewModel = ko.mapping.fromJS(model, mappingOption);
ko.applyBindings(viewModel);
console.log(ko.mapping.toJSON(viewModel));
The first console.log outputs:
{
"Id": 0,
"CurrentUser": {
"BoardIds": [
{
"Id": 0
}
],
"Id": 1,
"UserName": "foo",
"IsOnline": true
},
"Boards": []
}
And then the mappingOption is:
var mappingOption = {
create: function (options) {
var modelBase = ko.mapping.fromJS(options.data);
modelBase.CurrentUser.UserName = ko.observable(model.CurrentUser.UserName).extend({ rateLimit: 1000 });
//some function definitions
return modelBase;
},
'CurrentUser': {
create: function (options) {
options.data.MessageToPost = ko.observable("test");
return ko.mapping.fromJS(options.data);
}
}
};
I referred to this post to create the custom mapping, but it seemed not working as the second console.log outputs the same JSON to the first one.
Also, I tried to create nested mapping option based on this thread and another one but it didn't work too.
var mappingOption = {
create: function (options) {
//modelBase, modifing UserName and add the functions
var mappingOption2 = {
'CurrentUser': {
create: function (options) {
return (new(function () {
this.MessageToPost = ko.observable("test");
ko.mapping.fromJS(options.data, mappingOption2, this);
})());
}
}
}
return ko.mapping.fromJS(modelBase, mappingOption2);
}
};
How can I correctly add a new property to the original viewmodel?
From the mapping documentation for ko.toJS (toJS and toJSON work the same way as stated in the document)
Unmapping
If you want to convert your mapped object back to a regular JS object, use:
var unmapped = ko.mapping.toJS(viewModel);
This will create an unmapped object containing only the properties of the mapped object that were part of your original JS object
If you want the json to include properties you've added manually either use ko.toJSON instead of ko.mapping.toJSON to include everything, or use the include option when first creating your object to specify which properties to add.
var mapping = {
'include': ["propertyToInclude", "alsoIncludeThis"]
}
var viewModel = ko.mapping.fromJS(data, mapping);
EDIT: In your specific case your mapping options are conflicting with each other. You've set special instructions for the CurrentUser field but then overridden them in the create function. Here's what I think your mapping options should look like:
var mappingOption = {
'CurrentUser': {
create: function (options) {
var currentUser = ko.mapping.fromJS(options.data, {
'UserName': {
create: function(options){
return ko.observable(options.data);
}
},
'include': ["MessageToPost"]
});
currentUser.MessageToPost = ko.observable("test");
return ko.observable(currentUser).extend({ rateLimit: 1000 });
}
}
};
and here's a fiddle for a working example

Why data of the Vue instance is not updated

Vue 2.2.6 version is used.
I have built very simple Vue instance and I want it to return the list of employees names. But the problem is that after getting /employees.json employees data is not being updated. I tried to debug it with console.log and it shows that inside loadData() function employees data is set correctly. But after this function is executed employees value becomes empty again.
var employees = new Vue({
el: 'employees',
template: "<ul id='employees'><li v-for='employee in employees'>{{ employee['name'] }}</li></ul>",
data: {
employees: []
},
methods: {
loadData: function () {
this.$http.get('/employees.json').then(response => {
this.employees = response.body;
//1. console.log returns here ">employees: [object Object]"
});
}
},
mounted: function (){
this.loadData();
//2. console.log returns here empty employees value
}
})
Where am I wrong? How correctly assign value from /employees.json to employees variable?
The console.log in mounted will return empty because the loadData is asynchronous, and will not have completed.
Your el isn't going to work because it needs a #: '#employees'
The template isn't going to work inline like that, because templates are for components. Where would it put it?
var employees = new Vue({
el: '#employees',
data: {
employees: []
},
methods: {
loadData: function () {
setTimeout(() => {
console.log('setting');
this.employees = [{name:'one'},{name:'two'}];
}, 800);
/*this.$http.get('/employees.json').then(response => {
this.employees = response.body;
//1. console.log returns here ">employees: [object Object]"
});*/
}
},
mounted: function (){
this.loadData();
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<ul id='employees'><li v-for='employee in employees'>{{ employee['name'] }}</li></ul>

Categories

Resources