Vue/Vuelidate: Dynamically show required star (*) based on requiredIf - vue.js

I'm creating a dynamic re-usable vue component that wraps a text input and includes the vuelidate validation stylings, etc.:
<template>
<div class="form-group" :class="{'form-group--error': validator.$error}">
<label>{{ label }}<span v-if="validator.$params.required">*</span></label>
<input type="text v-model="validator.$model" />
<div class="error" v-if="validator.$error && !validator.required">* This field is required</div>
</div>
<template>
<script>
export default {
name: "FormTextField",
props: ["validator", "label"]
}
</script>
(validator prop is $v from parent)
The problem I have is trying to show the <span>*</span> dynamically based on if the field is required. This currently works (v-if="validator.$params.required") as long as I only specify the required validator:
fieldName: {
required: required
}
Now, I need to instead declare my validation like this:
fieldName: {
required: requiredIf( ... )
}
The question is how to access the result of the requiredIf function? validator.$params.required will always be true since it's just checking if the param is there. And validator.required is the status of the validation, not the result of the requiredIf call to see whether it SHOULD be required or not.
Any suggestions on how I can show the required star dynamically based on vuelidate state?

Instead of validator.$params.required, check validator.required, which is only defined when there's a required or requiredIf validator rule applied. The value of validator.required is true when the field is missing, or false otherwise.
When the field is optional (has no required/requiredIf rule), the validator.required property does not exist, so we can't use v-if="!validator.required". Instead, explicitly compare validator.require to false:
<label>{{ label }}<span v-if="validator.required === false">*</span></label>
<div class="error" v-if="validator.$error && validator.required === false">* This field is required</div>
demo

Related

Vuelidate - Accept 'null' or empty string as valid

I am currently using Vuelidate for a form. There is an input field where the user is allowed to leave it in blank, which is an optional field.
Minimum value, if entered, should be 0 or higher, but If I leave it empty, it will still throw the error. Validation works well for numbers, it executes the error class when entered value is below 0.
Summary: I need to have minValue(0) but also nullable possibility.
I currently have this:
validations: {
company_name: {
minValue: minValue(0)
}
}
Template looks like this:
<input
id="company_name"
:value="company_name"
type="number"
min="0"
step="0.01"
:class="{ 'error': v$.company_name.minValue.$invalid"
>

vue v-model does not seem to be working in modal

I am pretty new to vue, and am trying to use it in a bootstrap modal. The relevant div in the modal is as follows.
<div class="form-group row">
<label for="priceQCField" class="col-sm-2 col-form-label">Price<span class="red"> *</span></label>
<input type="number" step="0.01" class="form-control col-sm-4" id="priceQCField" name="priceQCField" min="0" v-model="job.price">
</div>
I read some other questions about vue returning strings rather than numbers, so I have converted the job.price to a number inside my method to call the modal
showPriceJob: function (job) {
this.job = job;
this.job.price = parseFloat(this.job.price);
$('#mdlPriceJob').modal('show');
},
However, job.price refuses to appear in the input field either as a string or a number. I know it is available to the modal as I can see it using <span>{{job.price}}</span>.
Can anyone advise me please?
Additional - I think it is a display issue - if I change the input field, the entry in the <span> changes
2nd update - initial table
<tr class="light-grey" v-for="job in jobs" v-on:click="viewJob(job)">
<td>{{job.id}}</td>
<td>{{job.customerName}}</td>
<td>{{job.description}}</td>
<td v-bind:class="job.dueDate | dateColour">{{job.dueDate | dateOnly}}</td>
<td>£{{job.price}} {{job.isEstimate | priceEstimated}}</td>
<td>{{job.delivery}}</td>
</tr>
Upd.
According to your comments to my answer you are using v-for and you can't use this.job within your method. You should give us more code to see the whole picture.
Upd.2
You have showed more code but I didn't see any v-for so I am confused. You can try to use something like this if job is a property of appData.jobs:
showPriceJob: function (job) {
this.appData.jobs.job = Object.assign({}, job);
this.appData.jobs.job = parseFloat(this.appData.jobs.job.price);
$('#mdlPriceJob').modal('show');
},
But I'm not sure about this because I don't see where job is declared.
Upd.3
Oh! Wait! You have this code:
data: appData.jobs, but data should be in this format:
data: function(){
return {
appData: {
jobs: [],
},
}
},
Or show me what is your appData.jobs variable is.

Vue - How to scope variables within template

Can I scope data somehow within a component's template?
For example, if I have the following code:
data() {
a: {test: 'Test A'},
b: {test: 'Test B'}
}
Currently in the template I have to do
<div class="a">{{ a.test }}</div>
<div class="b">{{ b.test }}</div>
Is there any way I can scope data per element? For example, something like:
<div :scope="a">{{ test }}</div><!-- This prints 'Test A' -->
<div :scope="b">{{ test }}</div><!-- This prints 'Test B' -->
I do know that I can extract each item to a component, however, I was wondering if there is a way to do that within the same template? As it does not have own logic etc. so I don't want to extract it to a separate component just to scope the variable. However, it can get tedious repeating the same variable name many times.
For example, I have a form to create a new item, which has a number of inputs. I keep them under a variable (for example) newItem, which looks like
newItem: {
input1: "",
input2: "",
input3: null,
input4: false,
// etc...
}
And in the template I would like to do
<div :scope="newItem">
<input v-model="input1"/>
<!-- etc.. --->
</div>
Instead of
<input v-model="newItem.input1"/>
<!--- etc... --->
NO.
There's no such a way to do. And also v-model needs to be specified to the particular data else it will not work. Otherwise, we can think of v-for.

How to specify multiple dynamic attributes by single computed prop in VueJS

I have this html element:
Link text
I want to add data-tooltip and title attributes dynamically by condition:
Link text
Is there any way in VueJS to add multiple dynamic attributes at same time:
<!-- instead of this: -->
Link text
<!-- something like this: -->
<a href="javascript:" ...tooltipAttributes >Link text</a>
You could take advantage of v-bind on the DOM element you wish to apply multiple attributes to based on some dynamically changing condition.
Here's a Plunker example demonstrating how you might go about it.
Take note of the object returned:
computed: {
multiAttrs() {
return this.showAttrs ? {
'data-toggle': 'tooltip',
title: 'Some tooltip text',
} : null;
}
}
You should be able to use v-bind="tooltipAttributes"
the docs here https://v2.vuejs.org/v2/api/#v-bind have more info, but the key part is under usage
Dynamically bind one or more attributes, or a component prop to an expression.
From the Docs:
1. You can dynamically bind multiple attributes/props to a single element by using v-bind:
(no colon, no extra attribute, just v-bind)
<a href="#" v-bind="tooltipAttributes" >Link text</a>
2. And then declare the variable in the computed section:
(you can also declare it in the data section, but that would require manual direct value changes)
computed() {
return {
tooltipAttributes: {
title: 'Title',
'data-toggle': this.toggle === true && !disabled
}
}
}
Note: Attributes with dashes/hyphens - in them (e.g. data-toggle) need to be a string because Javascript doesn't recognize - as a valid symbol in variable naming.
This is THE SAME AS:
<a href="#" title="Title" :data-toggle="this.toggle === true && !disabled" >Link text</a>

How do you properly clear Aurelia binding of a complex object?

Background: I'm trying to create a form using Aurelia. I have a person object that I would like to be able to fill in data for. If the user knows some identifying information about the person's family, they can enter it in an input and a select box will be displayed to allow the user to select the individual from that family for this particular form. The form will then fill in any information it knows about that individual into input fields allowing the user to overwrite any of the information if necessary. The form also allows them to clear the selected person if they want to choose another one.
Most of the functionality seems to work as expected, but when I try to allow the user to clear out the selected person, I'm seeing some behavior that I wouldn't have expected.
I have created a GistRun. The bottom pane is working as I would expect, after the user gets data, selects a person and then clears their selection, they are provided with the select element again. If you uncomment the input element, you will see that the user now has to click the clear action twice before they see the select element again. Why?
How can I update the application so that the user will only need to clear out the person once and the select box will appear again to allow the user to make another selection?
If you have an Aurelia application, you should be able to reproduce this by replacing the app.html with the following:
<template>
<select value.bind="val2" if.bind="opts2 && !val2">
<option repeat.for="opt of opts2" model.bind="opt">${opt.firstName}</option>
</select>
<div if.bind="!opts2 || val2">
<span>${val2.firstName}</span>
<button click.delegate="clearVal2()" if.bind="val2">Clear</button>
</div>
<button click.delegate="getOpts2()">Get</button>
<div>${val2.blah}</div>
<!--<input type="text" value.bind="val2.blah"/>-->
</template>
An the app.js with this:
export class App {
opts2;
val2;
getOpts2(){
this.opts2 = [
undefined,
{
blah: 1,
firstName: 'foo',
address: {
line1: '123 Main St.'
}
},
{
blah: 2,
firstName: 'bar',
address: {
line1: '456 Other Wy.'
}
}
];
}
clearVal2(){
this.val2 = null;
}
}
Any help would be greatly appreciated. Thanks!
UPDATE
If I put the input in a custom element and bind to that, things seem to work as expected. The values that I'm putting into my form though aren't in one location that I could utilize a custom element for. I have updated the Gist with an example.
How can I achieve the same functionality without the need for a custom element?
In all honesty I'm not sure why, but if you add if.bind="val2"on the input element, it clears the value and the select button returns.
<input type="text" if.bind="val2" value.bind="val2.blah"/>
Hope this (slightly) helps
Give that you are allowing the user to either select a value from the list or create a completely new entry, I would tend towards separating the value selected in the list and the data backing up the text boxes. Whenever the value of the select changes, I would set the value of the object backing the text boxes to the value of the select. The way I chose to do this in my sample code is to use the observable decorator on the value the select is bound to.
Here's an example: https://gist.run?id=e4b594eaa452b47d9b3984e7f9b04109
app.html
<template>
<div>
<select value.bind="val" if.bind="opts && !val">
<option repeat.for="opt of opts" model.bind="opt">${opt.firstName}</option>
</select>
<button click.delegate="getOpts()">Get</button>
</div>
<div if.bind="!opts || person">
<span>First Name: ${person.firstName}</span>
<button click.delegate="resetForm()" if.bind="val">Clear Selection</button>
</div>
Address: <input type="text" value.bind="person.address.line1" />
<hr />
val
<pre><code>
${toJSON(val)}
</code></pre>
person
<pre><code>
${toJSON(person)}
</code></pre>
</template>
app.js
import {observable} from 'aurelia-framework';
export class App {
#observable val = null;
person = {};
getOpts(){
this.opts = [
null,
{
blah: 1,
firstName: 'foo',
address: {
line1: '123 Main St.'
}
},
{
blah: 2,
firstName: 'bar',
address: {
line1: '456 Other Wy.'
}
}
];
}
valChanged() {
this.person = this.val;
console.log("set person");
}
resetForm(){
this.val = null;
console.log("reset val");
}
toJSON(value) {
if(!(value === false) && !value) {
return '';
}
return JSON.stringify(value);
}
}
You can see something interesting is happening when I reset the form. Aurelia is creating the properties necessary for bindings to person (namely person.address.line1 when we set person = null. But it doesn't create a firstName property, b/c that property isn't being bound until person stops being falsey.
Another option here is to simply use the with attribute to scope the input.
https://gist.run/?id=7b9d230f7d3c6dc8c13cefdd7be50c7f
<template>
<template with.bind="val.address">
<input value.bind="line1" />
</template>
</template>
Although I agree that mixing the logic of selections and inputs like that is probably not the best idea :)