vue js disable input if value hasn't changed - javascript

I have an input with an initial value:
<input type="text" v-model="name" ref="input" />
<button type="submit" :disabled="$refs.input.defaultValue == $refs.input.value">Submit</button>
However the disabled binding gives an error: Cannot read property defaultValue of undefined.
Best way to do this without spamming vm.data too much?

The error:
Cannot read property defaultValue of undefined
Is because the ref is not available so soon:
An important note about the ref registration timing: because the refs
themselves are created as a result of the render function, you cannot
access them on the initial render - they don’t exist yet! $refs is
also non-reactive, therefore you should not attempt to use it in
templates for data-binding.
And when you add it to the button's template, it tries to use it too soon.
The workaround would be to add a simple conditional:
<button type="submit" :disabled="!$refs.input || $refs.input.defaultValue == $refs.input.value">Submit</button>
But don't be happy just yet.
The defaultValue won't have the value you think
When using v-model, defaultValue will actually always be empty string ("") because Vue initially renders the <input> with an empty value.
To use a variable in the disabled button like you want, my suggestion is: use a mouted() logic to "save" the initial value and, in the button template, compare to it instead of defaultValue.
Demo below.
new Vue({
el: '#app',
data: {
name: 'Hello Vue.js!'
},
mounted() {
this.$refs.input.dataset.defVal = this.$refs.input.value;
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ name }}</p>
<input type="text" v-model="name" ref="input" />
<button type="submit" :disabled="!$refs.input || $refs.input.dataset.defVal == $refs.input.value">Submit</button>
</div>
Alternative: Going Vue all the way
Of course, if it's a possibilty, you should take advantage of Vue's data-driven reactive nature, as tackling with the DOM is always tricky.
The solution would be to just create another variable and populate it on mounted():
new Vue({
el: '#app',
data: {
name: 'Hello Vue.js!',
defaultName: null
},
mounted() {
this.defaultName = this.name;
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ name }}</p>
<input type="text" v-model="name"/>
<button type="submit" :disabled="name == defaultName">Submit</button>
</div>
Or, if you can set both name and defaultName to the same initial value, the mounted() logic above wouldn't even be necessary.

My solution would probably be more verbose than using a disabled flag. But I would use:
#click.prevent="submit"
Then make a submit method to handle the check, return false if input has not changed.

Related

Why does old value not passed to vue component?

why does old value not passed to vue component?
passed old value here
<autocomplete-region-component :query="old('regionName')"></autocomplete-region-component>
my code from vue component
<template>
<div>
<input
type="text"
autocomplete="off"
v-model="query"
v-on:keyup="autoComplete"
class="form-control js-region-name"
name="regionName"
value=""
>
<input
type="hidden"
class="form-control js-region-id"
name="regionId"
value="enteredRegion">
<div class="panel-footer" v-if="results.length">
<ul class="list-group select-region">
<li class="list-group-item list-region" v-for="result in results" v-on:click="selectRegion(result)">
{{ result.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
data() {
return {
results: [],
query: '',
}
},
methods: {
autoComplete() {
this.results = [];
if(this.query.length > 2){
axios.get('/api/regions',{params: {_limit: 2, query: this.query}}).then(response => {
this.results = response.data;
});
}
},
selectRegion(result) {
let inputWithRegionName = document.querySelector('.js-region-name');
let inputWithRegionId = document.querySelector('.js-region-id');
let listRegions = document.querySelector('.panel-footer');
inputWithRegionName.value = result.name;
inputWithRegionId.value = result.id;
listRegions.hidden = true;
}
}
}
</script>
console not have mistakes
Please help with a detailed answer since I am a beginner and really need your help. thanks
UPDATE
When you create a component (like you have with <autocomplete-region-component>, if you want to pass values to it from it's parent, you have to define the prop at the component level. So in your script, add a props property like so:
props: [
'query',
],
Now in your autocomplete-region-component component, you can use the value of query as this.query as you would expect.
In your component tag, you don't use mustache tags to pass the value, you would just pass normal javascript. I'd also recommend not encoding the json at that point. You could always encode it within the component if you needed to.
<autocomplete-region-component :query="old('regionName')">
</autocomplete-region-component>
You have to define a prop and set the data to that prop
When you pass data in a component tag via the v-bind shorthand : these data are known as props and have to be defined as such
Vue disallows mutating props passed from the parent in the child, therefor you should make a local copy of that prop in the reactive data object
props: {
queryProp: {
required: false,
type: String
}
},
data() {
return {
results: [],
query: this.queryProp
};
},
Assuming a Blade view like so
<div id="app">
<form action="/" method="post">
#csrf
<input type="text" name="regionName"> <br>
<button type="submit">Submit</button>
</form>
<autocomplete-region-component :query-prop="{{ json_encode(old('regionName')) }}"></autocomplete-region-component>
</div>
<script src="/js/app.js"></script>
And routes like so
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::view('/', 'welcome');
Route::post('/', fn (Request $request) => $request->validate(['regionName' => 'integer']));
This is what you would get if invalid data is posted
Mustache syntax is to interpolate text, not to pass props.
To pass props or set attributes to child components (including native HTML elements), you need to use v-bind directive (doc).
<a> element is good to remember this rule.
<a :href="myUrl">{{ linkText }}</a>
Then, your code should look like this:
<autocomplete-region-component :query="json_encode(old('regionName'))">
</autocomplete-region-component>

Changing vue instance variables from within component

I've been teaching myself Vue.js, and have been utilising components to increase modularity.
One thing that I am struggling with is manipulating variables in the Vue instance from within the component. I have got it working well with v-model within a component by passing the variable in the jade file as a prop
eg loginform(slot="login-form" v-bind:form-submit="loginSubmit" v-bind:login-data="loginData")
Where loginData contains variables username & password which are 'v-modelled' to the inputs within the component. Then in the component template:
<input type="password" class="text-field" v-model="formData.password" />
However I have a tooltip component that I am wanting to use twice: One for the username field & one for the password field. The visibility of these tooltips are given by tooltips.username.vis and tooltips.password.vis respectively.
I can't seem to pass that variable as a prop in order to manipulate without getting the avoid manipulating props warning, despite v-model within the component not giving these warnings. The tooltip component is given below:
Vue.component('tooltip', {
props: ['show', 'message', 'click'],
template:
<transition name="shrink">
<div v-show="show" v-on:click="click" class="tooltip">
<div class="tooltip-arrow"></div>
<div class="tooltip-container">{{message}}</div>
</div>
</transition>
});
Does anyone have any idea on how I can achieve the desired affect (Hiding the tooltip on mouse click). I have tried passing a method as the click prop that has different arguments based on whether the tooltip is for the username or password input, however I get click undefined warnings. I could make two seperate functions but I would rather not explicitly write two functions that do the same thing.
You shouldn't attempt to modify props from within a component as Vue's warnings tell you, changes to props do not flow up from the component to the prop so any changes will be overwritten.
For what you're trying to achieve you should look into Vue's Custom Events https://v2.vuejs.org/v2/guide/components-custom-events.html
HTML
<div id="app">
<form>
<div>
<label>Username</label>
<input type="username" v-model="formData.username" />
<tooltip :show="tooltips.username.vis"
:message="tooltips.username.message" #tooltip:hide="tooltips.username.vis = false" />
</div>
<div>
<label>Password</label>
<input type="text" v-model="formData.password" />
<tooltip :show="tooltips.password.vis"
:message="tooltips.password.message" #tooltip:hide="tooltips.password.vis = false" />
</div>
</form>
</div>
JS
Vue.component('tooltip', {
props: ['show', 'message'],
template: `<transition name="shrink">
<div v-show="show" class="tooltip" #click="hide">
<div class="tooltip-arrow"></div>
<div class="tooltip-container">{{message}}</div>
</div>
</transition>`,
methods: {
hide () {
this.$emit('tooltip:hide');
},
}
});
new Vue({
el: "#app",
data: {
formData: {
username: '',
password: ''
},
tooltips: {
username: {
message: 'Fix your username',
vis: true
},
password: {
message: 'Fix your password',
vis: true
}
}
}
});
https://jsfiddle.net/10fjkoob/12/

Array change detection for an array of complex objects in Vue JS 2

Update
Vue JS 3 will properly handle this: https://blog.cloudboost.io/reactivity-in-vue-js-2-vs-vue-js-3-dcdd0728dcdf
Problem:
I have a vue component that looks like this:
sub-comp.vue
<template>
<div>
<input type="text" class="form-control" v-model="textA">
<input type="text" class="form-control" v-model="textB">
<input type="text" class="form-control" v-model="textC">
</div>
</template>
<script>
export default {
props: {
textA: {
type: Number,
required: false
},
textB: {
type: Number,
required: false
},
textC: {
type: Number,
required: false
}
}
}
</script>
I have a parent component that looks like this:
layout-comp.vue
<template>
<div>
<button #click="addItem">Add</button>
<ul>
<li v-for="listItem in listItems"
:key="listItem.id">
<sub-comp
:textA="listItem.item.textA"
:textB="listItem.item.textB"
:textC="listItem.item.textC"
/>
</li>
</ul>
</div>
</template>
import subComp from '../sub-comp.vue'
export default {
components: {
subComp
},
data() {
return {
listItems: []
}
},
methods: {
addItem: function () {
var item = {
textA: 5,
textB: 100,
textC: 200
}
if (!item) {
return
}
this.length += 1;
this.listItems.push({
id: length++,
item: item
});
}
}
</script>
The thing is, anything I do to edit the textboxes, the array doesn't get changed, even though the reactive data shows that it changed. For example, it will always be as
{
textA: 5,
textB: 100,
textC: 200
}
Even if I changed textB: 333, the listItems array still shows textB: 100. This is because of this:
https://v2.vuejs.org/v2/guide/list.html#Caveats
Due to limitations in JavaScript, Vue cannot detect the following changes to an array
Question:
I'm wondering how do I update the array? I also want the change to occur when leaving the textbox, using the #blur event. I'd like to see what ways this can be done.
I read these materials:
https://codingexplained.com/coding/front-end/vue-js/array-change-detection
https://v2.vuejs.org/v2/guide/list.html
But it seems my example is a bit more complex, as it has indexes associated, and the arrays have complex objects.
Update 4/12/2018
Found out that in my addItem() that I had:
item = this.conditionItems[this.conditionItems.length - 1].item);
to
item = JSON.parse(JSON.stringify(this.conditionItems[this.conditionItems.length - 1].item));
I was thinking the sync modifier in the answer below was causing problems because it duplicated all items. But that's not the case. I was copying a vue object (including the observable properties), which caused it to happen. The JSON parse and JSON stringify methods only copies the properties as a normal object, without the observable properties. This was discussed here:
https://github.com/vuejs/Discussion/issues/292
The problem is that props flow in one direction, from parent to child.
Setting the value using v-model in child won't affect parent's data.
Vue has a shortcut to update parent's data more easily. It's called .sync modifier.
Here's how.
In sub-comp.vue
<template>
<div>
<input type="text" class="form-control" :value="textA" #input="$emit('update:textA', $event.target.value)" >
<input type="text" class="form-control" :value="textB" #input="$emit('update:textB', $event.target.value)">
<input type="text" class="form-control" :value="textC" #input="$emit('update:textC', $event.target.value)">
</div>
</template>
<script>
export default {
// remains the same
}
</script>
add .sync when you add the props
<sub-comp
:textA.sync="listItem.item.textA" // this will have the same effect of v-on:update:textA="listItem.item.textA = $event"
:textB.sync="listItem.item.textB"
:textC.sync="listItem.item.textC"
/>
update:
if you have reactivity problem, don't use .sync, add a custom event and use $set
<sub-comp
:textA="listItem.item.textA" v-on:update:textA="$set('listItem.item','textA', $event)"
/>

Input-fields as components with updating data on parent

I'm trying to make a set of components for repetitive use. The components I'm looking to create are various form fields like text, checkbox and so on.
I have all the data in data on my parent vue object, and want that to be the one truth also after the user changes values in those fields.
I know how to use props to pass the data to the component, and emits to pass them back up again. However I want to avoid having to write a new "method" in my parent object for every component I add.
<div class="vue-parent">
<vuefield-checkbox :vmodel="someObject.active" label="Some object active" #value-changed="valueChanged"></vuefield-checkbox>
</div>
My component is something like:
Vue.component('vuefield-checkbox',{
props: ['vmodel', 'label'],
data(){
return {
value: this.vmodel
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="value" #change="$emit('value-changed', value)">
{{label}}
</label>
</div>
</div>`
});
I have this Vue object:
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
},
methods: {
valueChange: function (newVal) {
this.carouselAd.autoOrder = newVal;
}
}
});
See this jsfiddle to see example: JsFiddle
The jsfiddle is a working example using a hard-coded method to set one specific value. I'd like to eighter write everything inline where i use the component, or write a generic method to update the parents data. Is this possible?
Minde
You can use v-model on your component.
When using v-model on a component, it will bind to the property value and it will update on input event.
HTML
<div class="vue-parent">
<vuefield-checkbox v-model="someObject.active" label="Some object active"></vuefield-checkbox>
<p>Parents someObject.active: {{someObject.active}}</p>
</div>
Javascript
Vue.component('vuefield-checkbox',{
props: ['value', 'label'],
data(){
return {
innerValue: this.value
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="innerValue" #change="$emit('input', innerValue)">
{{label}}
</label>
</div>
</div>`
});
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
}
});
Example fiddle: https://jsfiddle.net/hqb6ufwr/2/
As an addition to Gudradain answer - v-model field and event can be customized:
From here: https://v2.vuejs.org/v2/guide/components.html#Customizing-Component-v-model
By default, v-model on a component uses value as the prop and input as
the event, but some input types such as checkboxes and radio buttons
may want to use the value prop for a different purpose. Using the
model option can avoid the conflict in such cases:
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean,
// this allows using the `value` prop for a different purpose
value: String
},
// ...
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>
The above will be equivalent to:
<my-checkbox
:checked="foo"
#change="val => { foo = val }"
value="some value">
</my-checkbox>

Vue JS, checkboxes and computed properties

I am having some problems using vue, checkboxes and computed properties.
I made a very small example showing my problem: https://jsfiddle.net/El_Matella/s2u8syb3/1/
Here is the HTML code:
<div id="general">
Variable:
<input type="checkbox" v-model="variable">
Computed:
<input type="checkbox" v-model="computed()">
</div>
And the Vue code:
new Vue({
el: '#general',
data: {
variable: true
},
compute: {
computed: function() {
return true;
}
}
})
The problem is, I can't make the v-model="computed" work, it seems that Vue doesn't allow such things.
So my question is, how could I use the benefits of the computed data and apply it to checkboxes?
Here is an other jsfiddle showing the same problem, but with more code, I was trying to use computed properties to build a "selected" products array variable: https://jsfiddle.net/El_Matella/s2u8syb3/
Thank you for your answers and have a nice day!
Computed properties are basically JavaScript getters and setters, they are used like regular properties.
You can use a computed setter to set the value (currently, you only have a getter). You will need to have a data or props property in which you can save the changes of the model though, because getters and setters don't have an inherent state.
new Vue({
el: '#general',
data: {
variable: true,
cmpVariable: true,
},
computed: { // "computed" instead of "compute"
cmp: {
get: function() {
return this.$data.cmpVariable;
},
set: function(val) {
this.$data.cmpVariable = val;
},
}
}
});
Also, you don't need to call the computed with brackets (as it behaves like a regular property):
<div id="general">
Variable:
<input type="checkbox" v-model="variable">
Computed:
<input type="checkbox" v-model="cmp">
</div>
You miss-spelled computed. Here Computed Properties
I guess you want to check an item in Product list,
So it can be displayed in the selected list.
And you also want to check it off from both the lists.
Thus you don't need a computed property.
For check boxes, you can easily change the selected set by referring to it with v-model and set value for what you want to put in the set.
In your case, that's the product.id.
You may want to save the object itself in the selectedProducts list,
but I highly recommend you not to do that.
In some case, it will cause unexpected results as objects are mutable.
So it will work if it is written this way.
new Vue({
el: '#general',
data: {
products: [{
id: 1
}, {
id: 2
}],
selectedProducts: []
}
})
<script src="//cdn.bootcss.com/vue/1.0.13/vue.min.js"></script>
<h1>Hello</h1>
<div id="general">
<h2>Products</h2>
<ul>
<li v-for="product in products">
<input v-model="selectedProducts" value="{{product.id}}" type="checkbox">{{ product.id }}
</li>
</ul>
<h2>Selected Products</h2>
<ul>
<li v-for="p in selectedProducts">
<input v-model="selectedProducts" value="{{p}}" type="checkbox">{{ p }}
</li>
</ul>
</div>

Categories

Resources