Vuex: Lifecycle hooks - vue.js

I have the following data structure in vuex file:
state:{
info:[
{
name: 'Some field 1',
value: '',
pattern: /.+/,
//status: ''
},
{
name: 'Some field 2',
value: '',
pattern: /.+/,
// status: ''
}
]
I would like every object to have a field status: ''.But I do not want to duplicate the code. In Vue component I wrote for this:
beforeMount(){
// add new fuild
}
But this way does not work in Vuex.
beforeMount(){
console.log('does not work');
for (let i = 0; i < this.info.length; i++) {
this.$set(this.state.info[i], 'status', '');
}
}
How to add a new field into state dynamically?

In a mutation just do something like this:
state.info = state.info.map(x => ({
...x,
status: ''
})

Related

Add Custom Event on dynamic Component Vue.js

I've created a dynamic Custom Component and i wanna register/add a custom event on this component,like:
<SelectContactRowComponent #customevent="custommethod()"></SelectContactRowComponent
This is my dynamic customcomponent code:
var RowComponent = Vue.extend(SelectContactRowComponent);
var instance = new RowComponent( {propsData: {
item_data: {'lastname': '', 'firstname' : '', 'email' : '', 'telephone' : ''},
isDOMadd : true,
data_source_url : this.data_source_url,
id: this.id,
id_name: this.id_name,
morph_class_id: this.morph_class,
}
}).$mount();
You should do that in your emit option of the SelectContactRowComponent
const app = Vue.createApp({})
app.component('select-contact-row-component', {
emits: ['customevent']
})
Then you can emit it within the SelectContactRowComponent methods, or lifecycle hooks depending on your logic
this.$emit('customevent', { payload })

Vuejs track input field

I need to check whether my input field is empty or not.
Logic
if form.name has value, use increase function
if form.name is empty, use decrease function
do not use increase, decrease functions on each character that user inputs or removes
Code
<el-form-item label="Product name *">
<el-input v-model="form.name"></el-input>
</el-form-item>
methods: {
increase() {
this.percentage += 8.3;
if (this.percentage > 100) {
this.percentage = 100;
}
},
decrease() {
this.percentage -= 8.3;
if (this.percentage < 0) {
this.percentage = 0;
}
},
}
any idea?
Update
Script
data() {
return {
form: {
name: '', // require
slug: '',
price: '', // require
supplier_id: '', // require
new_price: '',
base_price: '',
sku: '',
qty: 1, // require
active: '', // require
photo: '',
photos: [],
shortDesc: '',
longDesc: '',
origin: '',
tags: [],
brand_id: '', // require
categories: [],
user_id: '',
seoTitle: '',
seoTags: '',
seoPhoto: '',
seoDescription: '',
variations: [],
options: [],
condition: '', // require
isbn: '',
ean: '',
upc: '',
height: '',
weight: '',
lenght: '',
width: '', // require
},
}
},
methods: {
onSubmit(e) {
e.preventDefault();
axios.post('/api/admin/products/store', this.form)
.then(res => {
// do my things
})
.catch(function (error) {
console.log('error', error);
});
},
}
HTML
<el-form ref="form" :model="form" label-width="120px" enctype="multipart/form-data">
// my inputs (listed in form part in script above)
<el-button type="primary" #click="onSubmit" native-type="submit">Create</el-button>
</el-form>
One possible solution would be to use #focus and #blur events to check if form.name has a value before increasing or decreasing, this would be fired on focus or on blur events, so you will not have the methods fired on each character input or deletion.
for example:
<el-form-item label="Product name *">
<el-input #focus="checkName" #blur="checkName" v-model="form.name"></el-input>
</el-form-item>
methods: {
checkName() {
//If form.name has a value then run increase method, otherwise run decrease method
!!this.form.name ? this.increase() : this.decrease()
},
increase() {
this.percentage += 8.3;
if (this.percentage > 100) {
this.percentage = 100;
}
},
decrease() {
this.percentage -= 8.3;
if (this.percentage < 0) {
this.percentage = 0;
}
},
}
You can see a working fiddle HERE
UPDATE
Alright so i did follow the rules you state on your question, and i didn't know you wanted to get the percentage of completion of the form, so in order to do that, i would suggest to use a computed property, you can read more about computed properties in the VueJS Documentation, this way the percentage is calculated based on the criteria we can give it, and only if the form has values.
computed: {
formProgress: function () {
let totalInputs = Object.keys(this.form).length;
let filledInputs = 0;
Object.values(this.form).forEach(val => {
if (!!val){
filledInputs++;
}
});
return (filledInputs/totalInputs)*100
}
},
As you can see in one single computed property you can handle the complex logic and return the value reactively, to explain it better, i'm counting the lenght of the form object, to get total number of inputs in your form, so it's important to have all your form data inside the form data object, then i convert that object to an array to iterate it, and i check if each property has a value on it, if does it, i add 1 to the filledInputs counter, and finally just return a simple math to get the percentage. please check the new Fiddle here to see it in action:
FORM PROGRESS FIDDLE
If you have any other doubt just let me know.
UPDATE 2:
All right in order to only count for specific inputs for the form progress, i have modified the code to work based on an array that contains the names of the properties that are required. here is the full code:
data() {
return {
form: {
name: '',
lastName: null,
categories: [{}],
},
requiredFields: ['name', 'categories']
};
},
computed: {
formProgress: function () {
let totalInputs = this.requiredFields.length;
let filledInputs = 0;
Object.entries(this.form).forEach(entry => {
const [key, val] = entry;
if (this.requiredFields.includes(key)){
switch (val.constructor.name) {
case "Array":
if (val.length !== 0){
if (Object.keys(val[0]).length !== 0){
filledInputs++;
}
}
break
case "Object":
if (Object.keys(val).length !== 0){
filledInputs++;
}
break
default:
if (!!val){
filledInputs++;
}
}
}
});
return Number((filledInputs/totalInputs)*100).toFixed(1)
}
},
And here is the updated FIDDLE
As you can see now i'm using Object.entries to get the key and value of the form object, so you can have a single form object to send to your backend, this way i'm checking first if the key is in the required fields, and if has a value, so all you need to do is update the requiredFields data array with the same names as your inputs data property to make the validation work, also there is a validation depending if is array, array of objects, or object, that way it will validate input on each data type.
Hope this works for you.

Cannot read property of undefined when trying to access a method from another method

I am trying to populate an array from an axios request. The result of the axios request is retrieved by a parent component and its child component populates the table with the data.
Here is what I've done so far :
<script>
import NewSpecification from "../components/NewSpecification";
import SpecificationTable from "../components/SpecificationTable";
export default {
name: "Specifications",
components:{NewSpecification,SpecificationTable},
props: [
'ingredients',
'dishes',
'nutritionalProperties',
'mealPlan'
],
data(){
return{
showNewSpecification: false,
specifications: []
}
},
watch:{
mealPlan:function(){
this.specifications = this.getSpecifications();
}
},
methods:{
getSpecifications(){
var specifications = [];
var restrictedIngredients = this.mealPlan.restricted_ingredients;
restrictedIngredients.forEach(function(item){
specifications.push({
type: "Ingredient",
item: item.name,
meals: this.getStringMeals(item.pivot.breakfast,item.pivot.snack1,item.pivot.lunch,item.pivot.snack2,item.pivot.dinner)
})
});
var restrictedDishes = this.mealPlan.restricted_dishes;
restrictedDishes.forEach(function(item){
specifications.push({
type: "Dish",
item: item.name,
meals: this.getStringMeals(item.pivot.breakfast,item.pivot.snack1,item.pivot.lunch,item.pivot.snack2,item.pivot.dinner)
})
});
var restrictedNutritionalProperties = this.mealPlan.restricted_nutritional_properties;
restrictedNutritionalProperties.forEach(function(item){
specifications.push({
type: "Dish",
item: item.name,
meals: {
breakfast: item.pivot.breakfast,
snack1: item.pivot.snack1,
lunch: item.pivot.lunch,
snack2: item.pivot.snack2,
dinner: item.pivot.dinner
}
})
});
return specifications;
},
getStringMeals(breakfast,snack1,lunch,snack2,dinner){
return (breakfast ? 'Breakfast, ' : '') + (snack1 ? 'Snack 1, ' : '') + (lunch ? 'Lunch, ' : '') + (snack2 ? 'Snack 2, ' : '') + (dinner ? 'Dinner, ' : '')
}
}
}
</script>
However, an error is thrown when I call the method "getStringMeals", and I don't have a clue why I can't access this method from the method getSpecifications().
This is the error thrown in the console:
TypeError: Cannot read property 'getStringMeals' of undefined
Thanks for the help !
In Javascript this acts differently than other languages like Java.
Please read this mdn page.
When you use this inside forEach, it refers to forEach context.
To answer your question, you can store this in a variable:
var self = this;
restrictedDishes.forEach(function(item){
specifications.push({
type: "Dish",
item: item.name,
meals: self.getStringMeals(item.pivot.breakfast,item.pivot.snack1,item.pivot.lunch,item.pivot.snack2,item.pivot.dinner)
})});
Or use fat arrow syntax:
restrictedIngredients.forEach((obj) => {
specifications.push({
type: "Ingredient",
item: item.name,
meals: this.getStringMeals(item.pivot.breakfast,item.pivot.snack1,item.pivot.lunch,item.pivot.snack2,item.pivot.dinner)
})});
Use the arrow function for forEach as shown below.
restrictedIngredients.forEach((obj) => {
specifications.push({
type: "Ingredient",
item: item.name,
meals: this.getStringMeals(item.pivot.breakfast,item.pivot.snack1,item.pivot.lunch,item.pivot.snack2,item.pivot.dinner)
})
})

Set data field from getter and add extra computed field

I wanted to set fields inside data using getters:
export default {
data () {
return {
medications: [],
}
},
computed: {
...mapGetters([
'allMedications',
'getResidentsById',
]),
I wanted to set medications = allMedications, I know that we can user {{allMedications}} but my problem is suppose I have :
medications {
name: '',
resident: '', this contains id
.......
}
Now I wanted to call getResidentsById and set an extra field on medications as :
medications {
name: '',
resident: '', this contains id
residentName:'' add an extra computed field
.......
}
I have done this way :
watch: {
allMedications() {
// this.medications = this.allMedications
const medicationArray = this.allMedications
this.medications = medicationArray.map(medication =>
({
...medication,
residentName: this.getResidentName(medication.resident)
})
);
},
},
method: {
getResidentName(id) {
const resident = this.getResidentsById(id)
return resident && resident.fullName
},
}
But this seems problem because only when there is change in the allMedications then method on watch gets active and residentName is set.
In situations like this you'll want the watcher to be run as soon as the component is created. You could move the logic within a method, and then call it from both the watcher and the created hook, but there is a simpler way.
You can use the long-hand version of the watcher in order to pass the immediate: true option. That will make it run instantly as soon as the computed property is resolved.
watch: {
allMedications: {
handler: function (val) {
this.medications = val.map(medication => ({
...medication,
residentName: this.getResidentName(medication.resident)
});
},
immediate: true
}
}

Getting documents with ID from firstore collection

While using Firestore, vuefire, vue-tables-2, I stuck getting document's id.
My data structure is as below.
Here is my code.
<v-client-table :columns="columns" :data="devices" :options="options" :theme="theme" id="dataTable">
import { ClientTable, Event } from 'vue-tables-2'
import { firebase, db } from '../../firebase-configured'
export default {
name: 'Devices',
components: {
ClientTable,
Event
},
data: function() {
return {
devices: [],
columns: ['model', 'id', 'scanTime', 'isStolen'],
options: {
headings: {
model: 'Model',
id: 'Serial No',
scanTime: 'Scan Time',
isStolen: 'Stolen YN'
},
templates: {
id: function(h, row, index) {
return index + ':' + row.id // <<- row.id is undefined
},
isStolen: (h, row, index) => {
return row.isStolen ? 'Y': ''
}
},
pagination: {
chunk: 5,
edge: false,
nav: 'scroll'
}
},
useVuex: false,
theme: 'bootstrap4',
template: 'default'
}
},
firestore: {
devices: db.collection('devices')
},
};
My expectation is devices should id property as vuefire docs.
But array this.devices didn't have id field even if I check it exist it console.
Basically, every document already has id attribute, but it's non-enumerable
Any document bound by Vuexfire will retain it's id in the database as
a non-enumerable, read-only property. This makes it easier to write
changes and allows you to only copy the data using the spread operator
or Object.assign.
You can access id directly using device.id. But when passing to vue-tables-2、devices is copied and lost id non-enumerable attribute.
I think you can workaround using computed property
computed: {
devicesWithId() {
if (!this.devices) {
return []
}
return this.devices.map(device => {
...device,
id: device.id
})
}
}
Then, please try using devicesWithId in vue-tables-2 instead.