vuetify rule function - how to access component label during validation? - vue.js

I would like to access the label property of a component in it's "Rules" function so I can return an (already) localized field name in the error message.
Is there any way to access the properties of the component in the rule function that's called by Vuetify for validation?
<v-text-field
v-model="obj.count"
:counter="10"
:label="this.$locale.get('WidgetCount')"
:rules="MyRuleFunctionInMyRuleLibrary()"
name="count"
required
></v-text-field>
As can be seen in the code I have a function to localize the field label already, I don't want to re-do it twice or have to specify it twice. In "MyRuleFuctionInMyRuleLibrary" I want to validate the rule and report on it localized properly.
I know I can just pass the localized text Key in my rule function but that would create a redundancy as I would have to type it twice in the template and I also need some other properties of the control / component so I would rather pass or have access to the component itself. I already tried passing "this" to the component, e.g.:
:rules="MyRuleFunctionInMyRuleLibrary(this, obj.count)"
However this in this case appears to be everything on the page / form, not the single component itself.

Using typescript:
<v-text-field v-model="volume.sizePerInstance" :rules="sizePerInstanceRules" :label="$t('volumes.sizePerInstance') + ' (GB)'" type="number" step="0.01" required min="0" color="#0cc2aa"></v-text-field>
You have to define a getter in order to get acces to component properties:
get sizePerInstanceRules() {
return [
(v: number) => v && v > 0 || 'Max size must be greater than 0',
(v: any) => v && !isNaN(v) || 'Max size must be a number',
(v: number) => {
return this.maxValue >= v || 'Exceeded limit';
},
];
}

In Vuetify source code, rules function has only 1 parameter (value). You can work around by define label as data or computed property:
<v-text-field
v-model="obj.count"
:counter="10"
:label="label.count"
:rules="MyRuleFunctionInMyRuleLibrary()"
name="count"
required
></v-text-field>
Add label to data
data: () => ({
label: {
count: this.$locale.get('WidgetCount')
}
})
then you can access localize label in validation function by this.label.count
You might want to watch locale change to change label manually:
watch: {
locale: function () {
this.label = {
count: this.$locale.get('WidgetCount')
}
}
}

Related

Trying to block user from typing numbers on an antd Input without having to use state (or useState)

I'm using antd Form and have some Inputs in it. I wish to know if antd has something that can validate the users input and determine if what he typed are letters or numbers. I want the input to only allow the user to type (show) in the input only alphabets but not numbers.
Form.Item has a property called rules where you can establish a pattern using RegExp, but this is not what I want, because the user can still type numbers and they will be shown on the UI. Pattern property only pops a message like "field does not accept numbers" and I assume that when I submit the data it will take what the user typed regardless of the failed rule/pattern.
I have been able to do what I'm asking by having to create a state with an object with all my values, removing the name property from the Form.Item and adding it to the Input, along with an onChange and value property but this requires more "wiring'. If you have worked with antd Form, there is an onFinish property that collects all the data so you don't have to create a separate state.When removing the name property from Form.Item, antd won't be able to collect the data of the form anymore thus rendering it useless. I wish to know if antd has something that can help me achieve what I typed in the beginning.
Following code does what I want, but uses state.
My Form.Item
<Form.Item
// name="nombre"
label="Nombre(s)"
rules={[
{ required: false, message: "REQUIRED_ERROR_MESSAGE" },
{
pattern: new RegExp(/^[a-záéíóúüñçA-Z]*$/i),
message: "field does not accept numbers",
},
]}
>
<Input
onChange={handleWordInput}
value={altaFormValues.nombre}
name="nombre"
/>
</Form.Item>
My Input handler function:
const handleWordInput = (e) => {
console.log("e", e.target.value);
const re = /^[a-záéíóúüñçA-Z\s]*$/;
const { name, value } = e.target;
if (value === "" || re.test(value)) {
setAltaFormValues((prevState) => {
return { ...prevState, [name]: value };
});
}
};

v-mask not working correctly with veutifyjs input

I'm using veutifyjs text-input to show phone number. I have a button to assign phone number to v-model. But after assign it not change there text input. if I enter some think on text-input v-mask work. But if I assign value to v-model it not working. Also i'm tying next trick but its not have any difference.
<v-text-field
v-model="form.phone_number"
v-mask="'(###) ###-####'"
dense
outlined
autocomplete="new-password"
hide-details="auto"
label="Phone Number"
placeholder="Phone Number"
></v-text-field>
<v-btn color="warning" #click="dataupdate()">Add</v-btn>
dataupdate() {
this.$nextTick(() => {
this.form.phone_number = '4032223344'
})
},
You have to set the v-mask dynamically after you set the value of form.phone_number, so we could create a phoneNumberMask variable:
data() {
return {
phoneNumberMask: '',
};
}
Set it as the v-mask value:
<v-text-field
v-model="form.phone_number"
v-mask="phoneNumberMask"
...
then at dataupdate():
dataupdate() {
this.form.phone_number = '4032223344'
this.$nextTick(() => {
this.phoneNumberMask = '(###) ###-####'
})
/*
* you'd just have to deal with calling this funcition
* when phoneNumberMask has already a value
* and that depends on your business rules
*/
},
I had the same issue after migration to Vuetify 2 and fixed it so: Just replaced v-mask library with vue-input-facade as mentioned here.
It works exactly the same as the old mask from the first Vuetify but you should use the parameter "v-facade" instead.

Vee validate: isBetween custom rule with parameters not working

validate, and Im trying to create multiple rules for my textfield for example: required, minlength, maxLength and chain them together, and based on whic h parameter is passed to preform validation
So I tried using example from the docs:
http://vee-validate.logaretm.com/v2/guide/custom-rules.html#args-and-rule-configuration
const isBetween = (value, { min, max } = {}) => {
return Number(min) <= value && Number(max) >= value;
};
// The first param is called 'min', and the second is called 'max'.
const paramNames = ['min', 'max'];
Validator.extend('between', isBetween, {
paramNames // pass it in the extend options.
});
And my Vue model looks like this:
<ValidationProvider
v-if="item && item.type === 'TEXT_AREA'"
:rules="`isBetween:true, 10`"
v-slot="{ errors, valid, validate }"
>
<b-form-textarea
size="sm"
:id="`attribute`"
:value="attributeValue"
#input="addAttributeValue($event, uid, validate)"
/>
<span>{{ displayError(errors) }}</span>
</ValidationProvider>
Here I try to pass in IsBeterrn params like: required, length and based on that to preform validation but I always get min & max value as null, and arguments is array instead of object
Also my second question is how would I use required from vee-validate in my custom rule
You have two ways of specifying parameters, with strings or with objects. I suggest you use the object method like this:
<ValidationProvider
:rules="{between:[0, 10]}"
>
You had a couple mistakes - the rule is called between because that's what you called it when you did this:
Validator.extend('between', isBetween, {
paramNames // pass it in the extend options.
});
Also, you can't use a boolean and a number as the parameter as you did here:
:rules="`isBetween:true, 10`"
The way I specified it, with :rules="{between:[0, 10]}" also lets you make the min and max variable if you wanted, i.e. if you had a component data item called minValue you could use that in the rule like this :rules="{between:[minValue, 10]}" and your rules would react to changes to minValue.

Vue - set v-model dynamically (with a variable containing a string)

I haven't been able to set v-model dynamically.
It works if I type explicitly:
<div class="form-group mr-3 mb-2">
<input type="text"
v-model="form[filters][firstlastname]"
>
</div>
But I want to loop through an object wherein I have string , like: 'form[filters][firstlastname]'
The parent has the form with properties:
data() {
return {
form: new Form({
filters: {
gender: [],
firstlastname: 'My firstlastname'
So, from the parent I pass down the form and filters into the child component, here is filters:
let formFilters = { filters: [
{
type: 'text',
property: 'form[filters][firstlastname]', // <-- string
placeholder: 'Name',
},
{
type: 'number',
property: 'paginate',
placeholder: 'Max rows'
},
]
}
Child component: (here I loop through the object and generate the input fields)
<div v-for="(filter,index) in formFilters.filters"
:key="`${index}_${filter.property}`"
>
<input
v-if="filter.type === 'text' || filter.type === 'number'"
:placeholder="filter.placeholder"
:type="filter.type"
v-model="filter.property" //<--- set the property
>
This doesn't work. The v-model just interprets it as a string and not a reference to a form property.
I tested other ways, like: v-model="``${[filter.property]}``" (single, not double ```` but it wont show in stackoverflow otherwise) and other crazy things but it isn't valid.
So how do I set v-model with a variable containing a string (so that it can be set dynamically)?
This is a very tricky problem....
You can access any property present in the data inside html template using 2 ways,
Referring to the property directly
Using $data
data() {
return {
firstlastname: 'Mr First last name'
}
}
so, in html template you can use either
<p>{{firstlastname}}</p>
or
<p>{{$data.firstlastname}}</p>
For your scenario $data can be used for primitive data types like string or number,
<input
v-if="filter.type === 'text' || filter.type === 'number'"
:placeholder="filter.placeholder"
:type="filter.type"
v-model="$data[filter.property]">
But this will not work for your second scenario where you are trying to access nested property of an object form.filters.firstlastname
You can access this property using the following notation $data[form][filters][firstlastname]
In your case, the for loop will result as $data[form.filters.firstlastname] or $data[[form][filters][firstlastname]] which will throw an exception
As suggested in the comments, try different approach or flatten the object. You can refer to this link to see how to flatten the object https://stackoverflow.com/a/25370536/2079271

What's the proper way to implement formatting on v-model in Vue.js 2.0

For a simple example: textbox to input currency data.
The requirement is to display user input in "$1,234,567" format and remove decimal point.
I have tried vue directive. directive's update method is not called when UI is refreshed due to other controls. so value in textbox reverts to the one without any formatting.
I also tried v-on:change event handler. But I don't know how to call a global function in event handler. It is not a good practice to create a currency convert method in every Vue object.
So what is the standard way of formatting in Vue 2.0 now?
Regards
Please check this working jsFiddle example: https://jsfiddle.net/mani04/bgzhw68m/
In this example, the formatted currency input is a component in itself, that uses v-model just like any other form element in Vue.js. You can initialize this component as follows:
<my-currency-input v-model="price"></my-currency-input>
my-currency-input is a self-contained component that formats the currency value when the input box is inactive. When user puts cursor inside, the formatting is removed so that user can modify the value comfortably.
Here is how it works:
The my-currency-input component has a computed value - displayValue, which has get and set methods defined. In the get method, if input box is not active, it returns formatted currency value.
When user types into the input box, the set method of displayValue computed property emits the value using $emit, thus notifying parent component about this change.
Reference for using v-model on custom components: https://v2.vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events
Here is a working example: https://jsfiddle.net/mani04/w6oo9b6j/
It works by modifying the input string (your currency value) during the focus-out and focus-in events, as follows:
<input type="text" v-model="formattedCurrencyValue" #blur="focusOut" #focus="focusIn"/>
When you put the cursor inside the input box, it takes this.currencyValue and converts it to plain format, so that user can modify it.
After the user types the value and clicks elsewhere (focus out), this.currencyValue is recalculated after ignoring non-numeric characters, and the display text is formatted as required.
The currency formatter (reg exp) is a copy-paste from here: How can I format numbers as money in JavaScript?
If you do not want the decimal point as you mentioned in question, you can do this.currencyValue.toFixed(0) in the focusOut method.
I implemented a component. According to Mani's answer, it should use $emit.
Vue.component('currency', {
template: '<input type="text"' +
' class="form-control"' +
' :placeholder="placeholder""' +
' :title="title"' +
' v-model="formatted" />',
props: ['placeholder', 'title', 'value'],
computed: {
formatted: {
get: function () {
var value = this.value;
var formatted = currencyFilter(value, "", 0);
return formatted;
},
set: function (newValue) {
var cleanValue = newValue.replace(",", "");
var intValue = parseInt(cleanValue, 10);
this.value = 0;
this.value = intValue;
}
}
}
}
);
Using Vue custom directives + .toLocaleString() is also a very good option.
Vue.directive("currency", {
bind(el, binding, vnode) {
el.value = binding.value && Number(binding.value).toLocaleString('en-US', {style: 'currency', currency: !binding.arg ? 'USD' : binding.arg });
el.onblur = function(e) {
e.target.value = Number(e.target.value).toLocaleString('en-US', {style: 'currency', currency: !binding.arg ? 'USD' : binding.arg});
};
el.onfocus = function(e) {
e.target.value =
e.target.value && Number(e.target.value.replace(/[^\d.]/g, ""));
};
el.oninput = function(e) {
vnode.context.$data[binding.expression] = e.target.value;
};
}
});
Here is the example link: https://codepen.io/Mahmoud-Zakaria/pen/YzPvNmO