Vue3 updating data value is updating prop too - vue.js

Don't know if this is the normal behaviour, I'm kind of new to Vue, but it's driving me nuts. Hope someone here have any clue about what's happpening...
This is my export:
props: [
'asset', //--- asset.price = 50
],
data() {
return {
local_asset: this.asset
}
}
Then, I update the value of a local_asset value with v-model:
<input type="number" v-model="local_asset.price" #change="test" />
And on filling the input with i.e. 100, it results in prop asset being changed too:
methods: {
test() {
console.log(this.local_asset.price) //--- console >> 100
console.log(this.asset.price) //--- console >> 100
}
}
Am I doing it wrong? Sorry if my code is a nonsense. Please help...

You need to copy value , not reference:
Vue.component('Child', {
template: `
<div class="">
<input type="number" v-model="local_asset.price" />
<div>data: {{ local_asset }}</div>
</div>
`,
props: [
'asset',
],
data() {
return {
local_asset: {...this.asset}
}
},
})
new Vue({
el: '#demo',
data() {
return {
asset: {price: 50}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div>prop: {{ asset }}</div>
<Child :asset="asset" />
</div>

If your data in primitive (String, Number, BigInt, Boolean, undefined, and null) you can use
data() {
return {
local_asset: JSON.parse(JSON.stringify(this.asset))
}
}

Related

How can I select element Id with a function in Vue.js?

I want to use the Id of the element in which I use the function as a parameter. Example:
<div
class="col-2 column-center"
id="myId"
#mouseover="aFunction(id)"
>
methods: {
aFunction(id){
alert(id);
}
}
But it doesn't work. How can I do it?
In another way, you can make your id attribute bind with the data property, like this :
<template>
<div class="col-2 column-center" v-bind:id="id" #mouseover="aFunction(id)">
test
</div>
</template>
<script>
export default {
name: 'Test',
data() {
return {
id: 'myId',
};
},
methods: {
aFunction(id) {
alert(id);
},
},
};
</script>
You can pass the event to the method and then get the id from event.target.id.
https://jsfiddle.net/tr0f2jpw/
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
methods: {
aFunction(event){
alert(event.target.id);
}
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div
class="col-2 column-center"
id="myId"
#mouseover="aFunction($event)"
>
some stuff
</div>
</div>

Computed function in Vue not defined

I using a computed object to conditionally return a list item. But, I get "ReferenceError: matchingEmployees is not defined" even though I defined it in the computed object. What am I missing? I checked for spelling errors and the reference to matchingEmployees in the directive matches the computed function. Thank you. You can see the entire code on this codepen.
new Vue({
el: '#app',
template:
`<div>
<h1>Vue.js Application</h1>
Search: <input v-model="searchStr" type="text" placeholder="Search Employee...">
<h2>All employees are listed below</h2>
<p v-for="employee in matchingEmployees">
{{ employee }}
</p>
</div>
`,
data: {
searchStr:'',
employees: [
'Alex Han',
'Ali Usman',
'Peter Parker',
'John Lee',
'Eva Holmes'
],
computed: {
matchingEmployees: function () {
return this.employees.filter((user)=> {
if (this.searchStr == ''){
return true;
} else {
return user.includes(this.searchStr)
}
})
}
}
}
})
computed property must be outside the data option, the data option must be a function that returns an object :
new Vue({
el: "#app",
data() {
return {
searchStr: "",
employees: [
"Alex Han",
"Ali Usman",
"Peter Parker",
"John Lee",
"Eva Holmes"
]
};
},
computed: {
matchingEmployees: function () {
return this.employees.filter((user) => {
if (this.searchStr == "") {
return true;
} else {
return user.includes(this.searchStr);
}
});
}
}
});
However adding template as an option is working I recommend to move to the html section in order to separate the content from the logic :
<div id="app">
<div>
<h1>Vue.js Application</h1>
Search: <input v-model="searchStr" type="text" placeholder="Search Employee...">
<h2>All employees are listed below</h2>
<p v-for="employee in matchingEmployees">
{{ employee }}
</p>
</div>
</div>

how to enable v-model binding when building a custom components from other custom components

I am able to build a simple textbox component from <input /> and setup v-model binding correctly.
I'm trying to do same with a custom component: vs-input from vuesax.
Following the pattern below does not work as expected:
<template>
<div>
<vs-input type="text" v-model="value" #input="text_changed($event)" />
<!-- <input type="text" :value="value" #input="$emit('input', $event.target.value)" /> -->
</div>
</template>
<script>
export default {
name: 'TestField',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {}
},
methods: {
text_changed(val) {
console.log(val)
// this.$emit('input', val)
}
}
}
</script>
In building custom components from other custom components is there anything particular we should look out for to get v-model binding working properly?
Following code might help you.(Sample code try it in codepen)
updating props inside a child component
//html
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>{{ message }}</p>
<input type="text" :value="test" #change="abc">
{{ test }}
</div>
//VUE CODE
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
},
props:{
test:{
type:String,
default:''
}
},
methods:{
abc:function(event){
//console.log("abc");
console.log(event.target.value);
this.test=event.target.value;
}
}
})
I prefer to interface props with computed:
<template>
<div>
<vs-input type="text" v-model="cValue" />
</div>
</template>
<script>
export default {
name: 'TestField',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {}
},
computed: {
cValue: {
get: function(){
return this.value;
},
set: function(val){
// do w/e
this.$emit('input', val)
}
}
}
}
</script>
Computed Setter

How i can validate data() value without input with vee-validate

I have a button, for load files or add some text
After load it pushed in data() prop
How i can validate this prop, if them not have input
Im found only one solution - make watch for data props. and set validate in
Maybe exist more beautiful way?
I try validator.verify() - but it dont send error in main errorBag from validateAll
This is example
<div id="app">
<testc></testc>
</div>
<script type="text/x-template" id="test">
<div>
<input type="text" v-validate="'required'" name="test_vee">
{{errors.first('test_vee')}}
<hr>
<button #click="addRow">Add</button>
<input type="text" v-model="inputValue" name="test_input"/>
<hr>
{{rows}}
<hr>
{{errors.first('rows')}}
<button #click="validateForm">Validate</button>
</div>
</script>
and script
Vue.component('testc', {
template: '#test',
data() {
return {
inputValue: '',
rows: []
}
},
watch: {
rows: {
handler: function(newVal, oldVal) {
this.$validator.errors.remove('rows');
if (this.rows.length < 2) {
this.$validator.errors.add({
id: 'rows',
field: 'rows',
msg: 'Need 2 rows!',
});
}
}
}
},
methods: {
addRow: function() {
this.rows.push(this.inputValue);
this.inputValue = '';
},
validateForm: function(){
this.$validator.validateAll();
}
}
});
Vue.use(VeeValidate);
new Vue({
el: '#app'
})
https://codepen.io/gelid/pen/YBajER
First input in example: default validate - its ok
Second input: for add items - dont need validate or has self validate (not empty for example)
In data of component i have prop rows - it is need validate before ajax request to backend for save data

MomentJS + VueJS: Getting Relative Time From Now To A Certain Point In The Past

I use MomentJS.
in my VueJS-Code I want to get the relative time from now to that point in the past. In my template I incorporate the result of this short piece of JavaScript:
<template>
<div>{{ moment(message.createdAt, 'YYYYMMDD').fromNow() }}</div>
</template>
the object receives the date as follows:
message: { createdAt: Date.now() }
the result is always: a few seconds ago ...
how can I get the correct result (not always "a few seconds ago"):
EDIT:
this is my full template:
<template v-for="message in messages">
<div class="message">
<div class="text">{{ message.text }}</div>
<div class="date">{{ moment(message.createdAt).format('D.M.YYYY') }}</div>
<div class="date">{{ moment(message.createdAt).fromNow() }}</div>
</div>
</template>
Well, you can't use moment directly in your template, as it's not white-boxed (not accessible in the template).
Template expressions are sandboxed and only have access to a whitelist of globals such as Math and Date. You should not attempt to access user defined globals in template expressions.
Source: https://v2.vuejs.org/v2/guide/syntax.html#Using-JavaScript-Expressions
I would advise you to use some filter instead (you can also do it with methods in a very similar way).
Here is a working example.
new Vue({
el: "#app",
data() {
return {
messages: [
{
text: 'Message1',
createdAt: new Date() // Now
},
{
text: 'Message2',
createdAt: new Date(2016, 3, 1) // 1 April 2017
}
],
interval: null
};
},
filters: {
format(date) {
return moment(date).format('D.M.YYYY')
},
fromNow(date) {
return moment(date).fromNow();
}
},
created() {
this.interval = setInterval(() => this.$forceUpdate(), 1000);
// Trigger an update at least each second
// You should probably raise this duration as refreshing so often
// may be not useful
},
beforeDestroy() {
clearInterval(this.interval);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.js"></script>
<div id="app">
<template v-for="message in messages">
<div class="message">
<div class="text">{{ message.text }}</div>
<div class="date">{{ message.createdAt | format }}</div>
<div class="date">{{ message.createdAt | fromNow }}</div>
</div>
</template>
</div>
Cobaltway's reply is working, but I would advise to extract the whole logic into a small component of its own, so you don't force Vue to re-render the whole component each time.
I created exactly this as an example a while ago, please see this fiddle:
https://jsfiddle.net/Linusborg/meovg84x/
Vue.component('dynamic-from-now',{
name:'DynamicFromNow',
props: {
tag: {type: String, default: 'span'},
value: {type: String, default: ()=> moment().toISOString() },
interval: {type: Number, default: 1000}
},
data() {
return { fromNow: moment(this.value).fromNow() }
},
mounted () {
this.intervalId = setInterval(this.updateFromNow, this.interval)
this.$watch('value', this.updateFromNow)
},
beforeDestroy() {
clearInterval(this.intervalId)
},
methods: {
updateFromNow() {
var newFromNow = moment(this.value).fromNow(this.dropFixes)
if (newFromNow !== this.fromNow) {
this.fromNow = newFromNow
}
}
},
render(h) {
return h(this.tag, this.fromNow)
}
})
Usage:
<dynamic-from-now :value="yourTimeStamp" :interval="2000" :tag="span" class="red" />