Vuejs track input field - vue.js

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.

Related

How can I use a computed function inside a method function?

Hi I am trying to call a computed function inside a method function.
VueJs Code:
<script>
export default {
created() {
this.isDisabled(0);
},
data: function() {
return {
form: {
branch_office_id: null,
cashier_id: null,
gross_amount: '',
released_tickets: '',
start_ticket: '',
end_ticket: '',
z_inform_number: '',
created_at: '',
support: null,
error_end_bill_number_validation: ''
},
postsSelected: "",
branch_office_posts: [],
cashier_posts: []
}
},
methods: {
checkEndBillNumber() {
if(this.form.start_ticket > this.form.end_ticket) {
this.isDisabled(1);
this.$awn.alert("El número de boleta inicial no puede ser ", {labels: {success: "Error"}});
} else {
this.isDisabled(0);
}
}
},
computed: {
isDisabled(value) {
if(value == 0) {
return true;
} else {
return false;
}
}
}
}
</script>
I am trying to user isDisabled() function inside checkEndBillNumber() method function but when I do that it says:
[Vue warn]: Error in v-on handler: "TypeError: this.isDisabled is not a function"
So I wonder how can I use it? how can I do that? Thanks!
Computed properties as mentioned above are not functions. Hence passing arguments like isDisabled(value) wont work. But you can trick it to anonymously accept the value like this
computed: {
isDisabled() {
return (value) => {
if(value == 0) return true;
else return false;
}
}
}
This way you don't need a data property.
You do not have computed functions, but computed properties! So you have to store the value you want to use as a parameter in your computed property - e.g. in a data attribute, and then use that:
<script>
export default {
created() {
this.disabledParam = 0;
this.isDisabled; // Evaluates to "true" - what do you want with that result?
},
data: function() {
return {
form: {
branch_office_id: null,
cashier_id: null,
gross_amount: '',
released_tickets: '',
start_ticket: '',
end_ticket: '',
z_inform_number: '',
created_at: '',
support: null,
error_end_bill_number_validation: ''
},
postsSelected: "",
branch_office_posts: [],
cashier_posts: [],
disabledParam: null,
}
},
methods: {
checkEndBillNumber() {
if (this.form.start_ticket > this.form.end_ticket) {
this.disabledParam = 1;
this.isDisabled; // Evaluates to "false" - what to you want to do with that value?
this.$awn.alert("El número de boleta inicial no puede ser ", {labels: {success: "Error"}});
} else {
this.disabledParam = 0;
this.isDisabled; // Evaluates to "true" - what to you want to do with that value?
}
}
},
computed: {
isDisabled() {
if (this.disabledParam == 0) {
return true;
} else {
return false;
}
}
}
}
</script>
Also, please note that your calls to isDisabled(1) wouldn't so anything even if you could use them as functions. You should probably do something with the return values of isDisabled.
And you do not need computed properties in this way - in your example, you should simply create isDisabled(value) as another method and call that. But I guess your code is just an example, not your real code. Computed properties usually are being used as values in your template.
My example code is just there to illustrate how you can pass parameters into the code of computed properties. Besides that, your code has some issues.

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.

Binding an object from checkboxes

I need to bind an object from checkboxes, and in this example, a checkbox is its own component:
<input type="checkbox" :value="option.id" v-model="computedChecked">
Here's my data and computed:
data() {
return {
id: 1,
title: 'test title',
checked: {
'users': {
},
},
}
},
computed: {
computedChecked: {
get () {
return this.checked['users'][what here ??];
},
set (value) {
this.checked['users'][value] = {
'id': this.id,
'title': this.title,
}
}
},
....
The above example is a little rough, but it should show you the idea of what I am trying to achieve:
Check checkbox, assign an object to its binding.
Uncheck and binding is gone.
I can't seem to get the binding to worth though.
I assume you want computedChecked to act like an Array, because if it is a Boolean set, it will receive true / false on check / uncheck of the checkbox, and it should be easy to handle the change.
When v-model of a checkbox input is an array, Vue.js expects the array values to stay in sync with the checked status, and on check / uncheck it will assign a fresh array copy of the current checked values, iff:
The current model array contains the target value, and it's unchecked in the event
The current model array does not contain the target value, and it's checked in the event
So in order for your example to work, you need to set up your setter so that every time the check status changes, we can get the latest state from the getter.
Here's a reference implementation:
export default {
name: 'CheckBoxExample',
data () {
return {
id: 1,
title: 'test title',
checked: {
users: {}
}
}
},
computed: {
computedChecked: {
get () {
return Object.getOwnPropertyNames(this.checked.users).filter(p => !/^__/.test(p))
},
set (value) {
let current = Object.getOwnPropertyNames(this.checked.users).filter(p => !/^__/.test(p))
// calculate the difference
let toAdd = []
let toRemove = []
for (let name of value) {
if (current.indexOf(name) < 0) {
toAdd.push(name)
}
}
for (let name of current) {
if (value.indexOf(name) < 0) {
toRemove.push(name)
}
}
for (let name of toRemove) {
var obj = Object.assign({}, this.checked.users)
delete obj[name]
// we need to update users otherwise the getter won't react on the change
this.checked.users = obj
}
for (let name of toAdd) {
// update the users so that getter will react on the change
this.checked.users = Object.assign({}, this.checked.users, {
[name]: {
'id': this.id,
'title': this.title
}
})
}
console.log('current', current, 'value', value, 'add', toAdd, 'remove', toRemove, 'model', this.checked.users)
}
}
}
}

Vue v-for list not re-rendering after computed data update

I am implementing pagination for a huge list of cards, I display 10 cards at once and wish to show the 10 next (or 10 previous) by clicking on two buttons.
Here's how I do it:
export default {
...
data() {
return {
pois: [], // My list of elements
pageNumber: 0, // Current page number
};
},
props: {
size: {
type: Number,
required: false,
default: 10, // 10 cards per page
},
},
computed: {
pageCount() {
// Counts the number of pages total
const l = this.pois.length;
const s = this.size;
return Math.floor(l / s);
},
paginatedData() {
// Returns the right cards based on the current page
const start = this.pageNumber * this.size;
const end = start + this.size;
return this.pois.slice(start, end);
},
},
methods: {
nextPage() {
this.pageNumber += 1;
},
prevPage() {
this.pageNumber -= 1;
},
}
...
};
And my template:
<div v-for="poi in paginatedData" :key="poi.id">
<card :poi="poi"/>
</div>
Everything should work (and a page change does output the correct cards in the console) but my list is not updated even though the computed method is called on each click.
What is causing this issue? I've read it could be linked to a :key value missing, but it's there, and no data is being updated directly and manually in the array, only sliced out.
First, try this change, just for sure, and let me know in comment it works or not.
export default {
...
computed: {
paginatedData() {
...
const end = start + this.size - 1;
...
},
...
};
And yes: instead of id, try to use index:
<div v-for="(poi, idx) in paginatedData" :key="idx">
<card :poi="poi"/>
</div>

multidimensional object state in constructor

Happy New Year...
I am extremely new to RN and am building a small app to get a feel for it.
I am trying to figure out if there is a way to clean this part up.
Below I have a nested multidimensional object state in the constructor and a reset function.
I have four input fields and a plain text area which I update dynamically based on a result.
Now whilst this works it feels not so clean, say if I wanted to add another nested multidimensional object state which sets different default values on reset I am going to have to add another if block to handle that so the it compounds the problem even further.
Any thoughts how to improve this or am I going about it the wrong way :/
constructor(props) {
super(props)
this.state = {
input1: '',
input2: '',
input3: '',
input4: '',
result: {
'ratio': 0,
'style': '',
},
}
}
reset() {
let newState = {};
for (const field of Object.keys(this.state)) {
if (field == 'result') {
this.setState({
result: {
ratio: 0,
style: '',
}
});
continue;
}
newState[field] = '';
}
this.setState(newState);
}
** edit **
To make it clearer if I add another multidimensional object to the state I will need to include another if statement
for (const field of Object.keys(this.state)) {
if (field == 'reset') {
// ...
}
if (field == 'extra') {
// ...
}
newState[field] = '';
}
Ideally what I need is a copy of this.state before its updated then just restore the copy which has the default parameters this.setState(copy)
Many thanks.
I am not entirely sure what you mean by 'nested', but here are a couple of suggestions:
reset() {
let newState = {};
for (const field of Object.keys(this.state)) {
if (field == 'result') {
newState.result = {
ratio: 0,
style: '',
};
}else{
newState[field] = '';
}
}
this.setState(newState);
}
Looping this way you avoid triggering multiple renders since you only call setState once.
Edit: If you want to avoid checking for fields with known names, you can just access those directly. 'Wrap' all your inputs in a new entry in the state, and just loop through that one:
constructor(props) {
super(props)
this.state = {
inputs:{
input1: '',
input2: '',
input3: '',
input4: '',
},
result: {
'ratio': 0,
'style': '',
},
extra = {
extraKey: 1,
}
}
}
reset() {
let newState = {};
newState.result = {ratio: 0, style: ''};
newState.extra = // ...
newState.inputs = {};
for (const field of Object.keys(this.state.inputs)) {
newState.inputs[field] = '';
}
this.setState(newState);
}
It is a bit cumbersome to use a loop to update the keys in an object, but I cannot think of a simpler solution when the key names are not known in advance.