Vuelidate minvalue optionnal - vuejs2

I would like to validate a value 'stock' which is optionnal. I'm using vuelidate (https://monterail.github.io/vuelidate/)
Value 'stock' must be greater than 0 if an other value ('price') is greater than 0
If 'price' value is not defined, the validator of stock value must be disabled
price is an input value (decimal number), stock is a slider (number)
[EDIT]
Example of code
<template>
<form #submit.prevent="submit" class="offer-form">
<q-field label="Prix initial" helper="Indiquer le prix initial de l'offre" :error="hasItemError('price')" :error-label="itemErrorLabel('price')">
<q-input type="number" color="input" #blur="$v.item.price.$touch()" v-model="item.price" />
</q-field>
<q-field label="Stock" helper="Indiquer le nombre de stock" :error="hasItemError('stock')" :error-label="itemErrorLabel('stock')">
<q-slider color="input" label-always label :step=1 :min="0" #blur="$v.item.stock.$touch()" :max="50" v-model="item.stock"/>
</q-field>
<q-btn
icon="fas fa-check"
label="Valider"
color="green"
#click="submit"
/>
</form>
</template>
<script>
import { required, requiredIf, decimal, minValue } from 'vuelidate/lib/validators'
export default {
data () {
return {
item: {
id: null,
title: '',
description: '',
price: null,
reductionPercentage: 15,
stock: 0,
startDate: new Date(),
endDate: null,
product: null,
shop: null,
images: []
},
},
validations: {
item: {
title: { required },
startDate: { required },
endDate: { required },
price: { decimal },
reductionPercentage: {
requiredIf: requiredIf((vueInstance) => {
return vueInstance.price > 0
}),
minValue: minValue(15)
},
stock: {
requiredIf: requiredIf((vueInstance) => {
return vueInstance.price > 0
}),
// minValue ???
}
}
},
methods: {
submit () {
this.onSubmit = true
if (this.isValidate()) {
// Validation ok
}
this.onSubmit = false
},
isValidate () {
this.$v.item.$touch()
if (this.$v.item.$error) {
this.$q.notify({message: 'Vérifiez les champs en erreur', position: 'center'})
return false
}
return true
}
}
}
I tested this but it's not ok =>
minValue: minValue((vueInstance) => {
return vueInstance.price > 0 ? vueInstance.stock > 0 : true
})
How can I do this ?
Thanks

You dont offer a lot of code so it is hard to understand your goal,but i will try,just tell me if it is what you wanted :)
Validator of Stock is a Checkbox or a function or ?
var app = new Vue({
el: '#app',
data: {
price: 0,
stock: 0,
},
computed: {
stockvalidator: function()
{
if(this.price > 0 && this.stock > 0) return true
else if(this.price > 0 && this.stock <= 0) return false
else return false
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<input type="number" v-model="price">
Price: {{price}}
<input type="number" v-model="stock">
Stock: {{stock}}
<br>
<input :disabled="!stockvalidator" type="checkbox"> Stock Validator is {{stockvalidator}}
</div>

Not sure if you are strugling with this still, or there is anyone out there with the same issue, but here is my two cents:
minValue: (stockValue, item) => {
return item.price > 0 ? stockValue > 0 : true
})
Cheers!

I think you can do this:
stock: {
requiredIf: requiredIf(() => {
return this.price > 0 || this.price
}),
minvalue: helpers.withMessage(
"The minimum value for the stock is 0",
helpers.withAsync(async value => {
let retvalue;
if(this.price > 0 && value > 0){
retvalue = true;
}
else if (this.price > 0 && value <= 0){
retvalue = false;
}
else{
retvalue = true;
}
return retvalue;
})
)
}
Remember import "helpers" from vuelidate.
And I advise you to make your own file with your custom validators, then you can use it in every places in your project. Something like utils/customValidators.js.

Related

:class is not calling the computed property in vue (The computed property is not being called)

Code:-
<!--:class="changeCalmarColor" is not working-->
<div :class="changeCalmarColor" class="content" >
<div
v-if="props.currency"
:class="[
'font-bold',
{ 'text-3xl': props.largeValue, 'text-center': props.center },
]"
>
{{ $options.methods.format(props.value) }}
</div>
<div
v-else-if="props.millifyOnly"
:class="[
'font-bold',
{ 'text-3xl': props.largeValue, 'text-center': props.center },
]"
>
{{ $options.methods.millifyNumber(props.value) }}
</div>
<div
v-else
:class="[
'font-bold',
{ 'text-3xl': props.largeValue, 'text-center': props.center },
]"
>
{{ props.value }}
</div>
</div>
<script>
import millify from "millify";
export default {
name: "StatCard",
props: {
type: {
type: String,
default: "normal",
},
icon: {
type: String,
},
iconPack: {
type: String,
default: "",
},
title: {
type: String,
required: true,
},
value: {
type: [String, Number],
required: true,
},
currency: {
type: Boolean,
},
millifyOnly: {
type: Boolean,
},
largeValue: {
type: Boolean,
default: true,
},
center: {
type: Boolean,
default: true,
},
},
methods: {
format(val) {
let millifiedVal = "";
if (!isNaN(parseFloat(val))) {
if (parseInt(val).toString().length > 3)
millifiedVal = millify(val, { precision: 2 });
else millifiedVal = parseFloat(val).toFixed(2);
if (millifiedVal.charAt(0) === "-") return `-$${millifiedVal.slice(1)}`;
else return `$${millifiedVal}`;
}
return val;
},
millifyNumber(val) {
return !isNaN(parseFloat(val)) ? millify(val, { precision: 2 }) : val;
},
},
computed: {
changeCalmarColor() {
console.log("/////VALUE AND TITLE////", this.props.title, this.props.title);
if(this.props.title == "Calmar Ratio") {
if(this.props.value < 1 || this.props.value == null) {
return "text-danger";
} else if(1 <= this.props.value && this.props.value <= 3.00) {
return "text-yellow";
} else if(1 <= this.props.value && this.props.value > 3.00) {
return "text-green";
}
}
},
},
};
</script>
Here the line :class="changeCalmarColor" is not working, the computed property is never called. The binding class doesn't work. I don't know why it is not being called, I have clearly defined it. I think :class="changeCalmarColor" is the correct way to binding a computed property with a class. I don't understand what exactly am I doing wrong here.
I have tried to even display my computed property like <p>HELLP {{props.title}} {{changeCalmarColor}}</p> but it is never called. I cannot see it in the console.
To access props you should use directly this.propName. You could change your computed prop to:
changeCalmarColor() {
console.log("/////VALUE AND TITLE////", this.title, this.title);
if(this.title == "Calmar Ratio") {
if(this.value < 1 || this.value == null) {
return "text-danger";
} else if(1 <= this.value && this.value <= 3.00) {
return "text-yellow";
} else if(1 <= this.value && this.value > 3.00) {
return "text-green";
}
}
}
A computed property gets cached, See docs
So changeCalmarColor runs once and then waits for dependencies to change in order to re-run again.
The reason why it doesn't run is because you have used this.props.title while you should use this.title.
So this:
changeCalmarColor() {
console.log("/////VALUE AND TITLE////", this.props.title, this.props.title);
if(this.props.title == "Calmar Ratio") {
if(this.props.value < 1 || this.props.value == null) {
return "text-danger";
} else if(1 <= this.props.value && this.props.value <= 3.00) {
return "text-yellow";
} else if(1 <= this.props.value && this.props.value > 3.00) {
return "text-green";
}
}
},
Should be changed to:
changeCalmarColor() {
if(this.title == "Calmar Ratio") {
if(this.value < 1 || this.value == null) {
return "text-danger";
} else if(1 <= this.value && this.value <= 3.00) {
return "text-yellow";
} else if(1 <= this.value && this.value > 3.00) {
return "text-green";
}
}
},

Handle interaction between vue fields

I have prepared a functional code example in JSFiddle of VUE field interaction.
https://jsfiddle.net/JLLMNCHR/2a9ex5zu/6/
I have a custom autocomplete component that works properly, a normal input field, and a 'Load' button which objetive is to load the value entered in the normal input in the autocomplete field.
This 'load' button is not working.
HTML:
<div id="app">
<p>Selected: {{test1}}</p>
<br>
<div>
<label>Test1:</label>
<keep-alive>
<autocomplete v-model="test1" v-bind:key="1" :items="theItems">
</autocomplete>
</keep-alive>
</div>
<br>
<label>Display this in 'test1':</label>
<input type="text" v-model=anotherField>
<button type="button" v-on:click="loadField()">Load</button>
<br>
<br>
<button type="button" v-on:click="displayVals()">Display vals</button>
</div>
<script type="text/x-template" id="autocomplete">
<div class="autocomplete">
<input type="text" #input="onChange" v-model="search"
#keyup.down="onArrowDown" #keyup.up="onArrowUp" #keyup.enter="onEnter" />
<ul id="autocomplete-results" v-show="isOpen" class="autocomplete-results">
<li class="loading" v-if="isLoading">
Loading results...
</li>
<li v-else v-for="(result, i) in results" :key="i" #click="setResult(result)"
class="autocomplete-result" :class="{'is-active':i === arrowCounter}">
{{ result }}
</li>
</ul>
</div>
</script>
VUE.JS:
const Autocomplete = {
name: "autocomplete",
template: "#autocomplete",
props: {
items: {
type: Array,
required: false,
default: () => []
},
isAsync: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
isOpen: false,
results: [],
search: "",
isLoading: false,
arrowCounter: 0
};
},
methods: {
onChange() {
// Let's warn the parent that a change was made
this.$emit("input", this.search);
// Is the data given by an outside ajax request?
if (this.isAsync) {
this.isLoading = true;
} else {
// Let's search our flat array
this.filterResults();
this.isOpen = true;
}
},
filterResults() {
// first uncapitalize all the things
this.results = this.items.filter(item => {
return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
});
},
setResult(result) {
this.search = result;
this.$emit("input", this.search);
this.isOpen = false;
},
onArrowDown(evt) {
if (this.arrowCounter < this.results.length) {
this.arrowCounter = this.arrowCounter + 1;
}
},
onArrowUp() {
if (this.arrowCounter > 0) {
this.arrowCounter = this.arrowCounter - 1;
}
},
onEnter() {
this.search = this.results[this.arrowCounter];
this.isOpen = false;
this.arrowCounter = -1;
},
handleClickOutside(evt) {
if (!this.$el.contains(evt.target)) {
this.isOpen = false;
this.arrowCounter = -1;
}
}
},
watch: {
items: function(val, oldValue) {
// actually compare them
if (val.length !== oldValue.length) {
this.results = val;
this.isLoading = false;
}
}
},
mounted() {
document.addEventListener("click", this.handleClickOutside);
},
destroyed() {
document.removeEventListener("click", this.handleClickOutside);
}
};
new Vue({
el: "#app",
name: "app",
components: {
autocomplete: Autocomplete
},
methods: {
displayVals() {
alert("test1=" + this.test1);
},
loadField() {
this.test1=this.anotherField;
}
},
data: {
test1: '',
anotherField: '',
theItems: [ 'Apple', 'Banana', 'Orange', 'Mango', 'Pear', 'Peach', 'Grape', 'Tangerine', 'Pineapple']
}
});
Any help will be appreciated.
See this new fiddle where it is fixed.
When you use v-model on a custom component you need to add a property named value and watch it for changes, so it can update the local property this.search.

How to change prop dynamically in vue-status-indicator?

I am new to VueJS and after reading this doc section and this question, I can't figure how to change dynamically the prop active|positive|intermediary|negative and pulse of the following component (it could be another): vue-status-indicator
eg: with user.status = positive and the following wrong code :
<span v-for="user in users" :key="user.id">
<status-indicator {{ user.status }}></status-indicator>
</span>
What is the correct syntax to set theses type of props ?
You could do something like this.. I had to write a wrapper for it to make it functional..
[CodePen Mirror]
Edit To be clear - you cannot interpolate inside an attribute.. This has to do with boolean attributes in Vue..
This:
<status-indicator active pulse />
...is the same exact thing as doing this:
<status-indicator :active="true" :pulse="true" />
The "wrapper" component I wrote allows you to supply a string to set the status (like you are wanting to do):
<v-indicator status="active" pulse></v-indicator>
<!-- OR -->
<v-indicator status="positive" pulse></v-indicator>
<!-- OR -->
<v-indicator status="intermediary" pulse></v-indicator>
<!-- OR -->
<v-indicator status="negative" pulse></v-indicator>
Here is the full "wrapper" component, in .vue format: (added a validator for the 'status' prop)
<template>
<status-indicator
:active="indicatorStatus.active"
:positive="indicatorStatus.positive"
:intermediary="indicatorStatus.intermediary"
:negative="indicatorStatus.negative"
:pulse="pulse"
></status-indicator>
</template>
<script>
export default {
props: {
status: {
type: String,
required: true,
validator: (prop) => [
'active',
'positive',
'intermediary',
'negative',
].includes(prop)
},
pulse: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
indicatorStatus: {
active: false,
positive: false,
intermediary: false,
negative: false,
}
}
},
watch: {
status() {
this.handleStatusChange(this.status);
}
},
methods: {
handleStatusChange(newStatus) {
Object.keys(this.indicatorStatus).forEach(v => this.indicatorStatus[v] = false);
this.indicatorStatus[newStatus] = true;
}
},
mounted() {
this.handleStatusChange(this.status);
}
}
</script>
Snippet:
const vIndicator = {
template: "#v-indicator",
props: {
status: {
type: String,
required: true,
validator: (prop) => [
'active',
'positive',
'intermediary',
'negative',
].includes(prop)
},
pulse: {
type: Boolean,
required: false,
},
},
data() {
return {
indicatorStatus: {
active: false,
positive: false,
intermediary: false,
negative: false,
}
}
},
watch: {
status() {
this.handleStatusChange(this.status);
}
},
methods: {
handleStatusChange(newStatus) {
Object.keys(this.indicatorStatus).forEach(v => this.indicatorStatus[v] = false);
this.indicatorStatus[newStatus] = true;
}
},
mounted() {
this.handleStatusChange(this.status);
}
}
new Vue({
el: '#app',
components: {
vIndicator
},
data: {
currentStatus: '',
isPulse: '',
},
computed: {
currentJson() {
let cj = {
currentStatus: this.currentStatus,
isPulse: this.isPulse,
};
return JSON.stringify(cj, null, 2);
}
},
mounted() {
let statuses = ["active", "positive", "intermediary","negative"];
let c = 0;
let t = 0;
this.currentStatus = statuses[c];
this.isPulse = true;
setInterval(() => {
t = c + 1 > 3 ? t + 1 : t;
c = c + 1 > 3 ? 0 : c + 1;
this.currentStatus = statuses[c];
this.isPulse = (t % 2 == 0) ? true : false;
}, 2000)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vue-status-indicator#latest/dist/vue-status-indicator.min.js"></script>
<link href="https://unpkg.com/vue-status-indicator#latest/styles.css" rel="stylesheet"/>
<div id="app">
<p>Will alternate status as well as pulsing (pulse changes after each full loop)</p>
<!--
[status]active|positive|intermediary|negative
[pulse]true|false
-->
<v-indicator :status="currentStatus" :pulse="isPulse"></v-indicator>
<pre>{{ currentJson }}</pre>
</div>
<!-- WRAPPER COMPONENT -->
<script type="text/x-template" id="v-indicator">
<status-indicator
:active="indicatorStatus.active"
:positive="indicatorStatus.positive"
:intermediary="indicatorStatus.intermediary"
:negative="indicatorStatus.negative"
:pulse="pulse"
></status-indicator>
</script>

Remove category_id property from ColumnValue object in Vue template

I want to remove category_id from {{ columnValue }}, but what is the best way to to that, because i need category_id in the first part ?
<table>
<tr>
<td v-for="columnValue, column in record">
<template v-if="editing.id === record.id && isUpdatable(column)">
<template v-if="columnValue === record.category_id">
<select class="form-control" v-model="editing.form[column]">
<option v-for="column in response.joins">
{{ column.category }} {{ column.id }}
</option>
</select>
</template>
<template v-else="">
<div class="form-group">
<input class="form-control" type="text" v-model= "editing.form[column]">
<span class="helper-block" v-if="editing.errors[column]">
<strong>{{ editing.errors[column][0]}}</strong>
</span>
</div>
</template>
</template>
<template v-else="">
{{ columnValue }} // REMOVE category_id here!
</template>
</td>
</tr>
</table>
And the view (its the number under group i want to remove):
The DataTable view
The script:
<script>
import queryString from 'query-string'
export default {
props: ['endpoint'],
data () {
return {
response: {
table: null,
columntype: [],
records: [],
joins: [],
displayable: [],
updatable: [],
allow: {},
},
sort: {
key: 'id',
order: 'asc'
},
limit: 50,
quickSearchQuery : '',
editing: {
id: null,
form: {},
errors: []
},
search: {
value: '',
operator: 'equals',
column: 'id'
},
creating: {
active: false,
form: {},
errors: []
},
selected: []
}
},
filters: {
removeCategoryId: function (value) {
if (!value) return ''
delete value.category_id
return value
}
},
computed: {
filteredRecords () {
let data = this.response.records
data = data.filter((row) => {
return Object.keys(row).some((key) => {
return String(row[key]).toLowerCase().indexOf(this.quickSearchQuery.toLowerCase()) > -1
})
})
if (this.sort.key) {
data = _.orderBy(data, (i) => {
let value = i[this.sort.key]
if (!isNaN(parseFloat(value)) && isFinite(value)) {
return parseFloat(value)
}
return String(i[this.sort.key]).toLowerCase()
}, this.sort.order)
}
return data
},
canSelectItems () {
return this.filteredRecords.length <=500
}
},
methods: {
getRecords () {
return axios.get(`${this.endpoint}?${this.getQueryParameters()}`).then((response) => {
this.response = response.data.data
})
},
getQueryParameters () {
return queryString.stringify({
limit: this.limit,
...this.search
})
},
sortBy (column){
this.sort.key = column
this.sort.order = this.sort.order == 'asc' ? 'desc' : 'asc'
},
edit (record) {
this.editing.errors = []
this.editing.id = record.id
this.editing.form = _.pick(record, this.response.updatable)
},
isUpdatable (column) {
return this.response.updatable.includes(column)
},
toggleSelectAll () {
if (this.selected.length > 0) {
this.selected = []
return
}
this.selected = _.map(this.filteredRecords, 'id')
},
update () {
axios.patch(`${this.endpoint}/${this.editing.id}`, this.editing.form).then(() => {
this.getRecords().then(() => {
this.editing.id = null
this.editing.form = {}
})
}).catch((error) => {
if (error.response.status === 422) {
this.editing.errors = error.response.data.errors
}
})
},
store () {
axios.post(`${this.endpoint}`, this.creating.form).then(() => {
this.getRecords().then(() => {
this.creating.active = false
this.creating.form = {}
this.creating.errors = []
})
}).catch((error) => {
if (error.response.status === 422) {
this.creating.errors = error.response.data.errors
}
})
},
destroy (record) {
if (!window.confirm(`Are you sure you want to delete this?`)) {
return
}
axios.delete(`${this.endpoint}/${record}`).then(() => {
this.selected = []
this.getRecords()
})
}
},
mounted () {
this.getRecords()
},
}
</script>
And here is the json:
records: [
{
id: 5,
name: "Svineskank",
price: "67.86",
category_id: 1,
category: "Flæskekød",
visible: 1,
created_at: "2017-09-25 23:17:23"
},
{
id: 56,
name: "Brisler vv",
price: "180.91",
category_id: 3,
category: "Kalvekød",
visible: 0,
created_at: "2017-09-25 23:17:23"
},
{
id: 185,
name: "Mexico griller 500 gram",
price: "35.64",
category_id: 8,
category: "Pølser",
visible: 0,
created_at: "2017-09-25 23:17:23"
},
{
id: 188,
name: "Leverpostej 250 gr.",
price: "14.25",
category_id: 9,
category: "Pålæg",
visible: 1,
created_at: "2017-09-25 23:17:23"
},
}]
.. and so on......
I would recommend using a filter in Vue to remove the property, such as:
new Vue({
// ...
filters: {
removeCategoryId: function (value) {
if (!value) return ''
delete value.category_id
return value
}
}
})
An then use this in your template:
{{ columnValue | removeCategoryId }}
Update: I misunderstood the scope of the loop. This works, and I verified on jsfiddle: https://jsfiddle.net/spLxew15/1/
<td v-for="columnValue, column in record" v-if="column != 'category_id'">

input field prefilled with vuejs and a reactive character count

As a vuejs component, I want to be able to display a character counter next to my input field.
The field is initially set up using a prop (this.initialValue).
When the method this.updateCounter is called the input textfield is blocked : typing into the field won't update its value. If I don't set the maxlength prop, the field is working fine : I can update the textfield.
Usage in a template :
<textfield maxlength="50" name="title" initialValue="Test"></textfield>
Here is the component code :
<template>
<div class="input">
<div class="input__field">
<span class="input__limit f--small">{{ counter }}</span>
<input type="text" :name="name" :maxlength="computedMaxlength" v-model="currentValue" />
</div>
</div>
</template>
<script>
export default {
name: 'Textfield',
props: {
name: {
default: ''
},
maxlength: {
default: 0
},
initialValue: {
default: ''
}
},
computed: {
hasMaxlength: function () {
return this.maxlength > 0;
},
computedMaxlength: function () {
if(this.hasMaxlength) return this.maxlength;
else return false;
},
currentValue: {
get: function() {
return this.initialValue;
},
set: function(newValue) {
this.updateCounter(newValue);
this.$emit("change", newValue);
}
}
},
data: function () {
return {
counter: 0
}
},
methods: {
updateCounter: function (newValue) {
if(this.maxlength > 0) this.counter = this.maxlength - newValue.length;
}
},
mounted: function() {
this.updateCounter(this.initialValue);
}
}
</script>
Edit
I have fixed my issue by not using v-model but instead using a value and an input event.
data: function () {
return {
value: this.initialValue,
counter: 0
}
},
methods: {
updateCounter: function (newValue) {
if(this.maxlength > 0) this.counter = this.maxlength - newValue.toString().length;
},
onInput: function(event) {
const newValue = event.target.value;
this.value = newValue;
this.updateCounter(newValue);
this.$emit("change", newValue);
}
},