How to implement dirty state in VueJs - vuejs2

I am new to VueJs and I am working on a form that I want to enable the Save button only when a change occurs at the model.
My initial thought is to compute a dirty function comparing the initial model with the current.
Note: This code is not tested, it's here just for an example.
var app = new Vue({
el: '#app',
data: {a:0, b:'', c:{c1:null, c2:0, c3:'test'}},
initialData: null,
mounted():{ initialData = JSON.parse(JSON.stringify(data));},
computed: {
isDirty: function () {
return JSON.stringify(data) === JSON.stringify(initialData)
}
}
});
Is there a better way of doing this or is there any improvement you could suggest on the above-mentioned code?

You can use the deep option of watch as shown in the manual
var app = new Vue({
el: '#app',
data:
{
model:
{
a:0,
b:'',
c:
{
c1:null,
c2:0,
c3:'test'
}
},
dirty: false
},
watch:
{
model:
{
handler(newVal, oldVal)
{
this.dirty = true;
},
deep: true
}
}
});

Borrowing from -- > https://stackoverflow.com/a/48579303/4050261
You can bind single onchange event on the parent container and benefit from the fact that change events bubble:
<div class="container" #change="someThingChanged()">
<input v-model="foo">
<input v-model="bar">
... etc.
</div>

Related

How watch global variable in component vuejs?

I need global variables for errors. But I don't want set input variable for every component.
How I can watch $errors in component ABC without input variable?
(without <abc :errors="$errors"></abc>)
index.js:
Vue.prototype.$errors = {};
new Vue({
el: '#app',
render: h => h(App),
}
App.vue:
...
name: 'App',
components: {
ABC
}
...
methods:{
getContent() {
this.$errors = ...from axis...
}
Component ABC:
<template>
<div>{{ error }}</div>
</template>
...
watch: {
???
}
Here's an example of how it could be done:
const errors = Vue.observable({ errors: {} })
Object.defineProperty(Vue.prototype, '$errors', {
get () {
return errors.errors
},
set (value) {
errors.errors = value
}
})
new Vue({
el: '#app',
methods: {
newErrors () {
// Generate some random errors
const errors = {}
for (const property of ['name', 'type', 'id']) {
if (Math.random() < 0.5) {
errors[property] = 'Invalid value'
}
}
this.$errors = errors
}
}
})
new Vue({
el: '#app2',
watch: {
$errors () {
console.log('$errors has changed')
}
}
});
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<pre>{{ $errors }}</pre>
<button #click="newErrors">New errors</button>
</div>
<div id="app2">
<pre>{{ $errors }}</pre>
</div>
I've created two Vue instances to illustrate that the value really is shared. Clicking the button in the first instance will update the value of $errors and the watch is triggered in the second instance.
There are a few tricks in play here.
Firstly, reactivity can only track the reading and writing of properties of an observable object. So the first thing we do is create a suitable object:
const errors = Vue.observable({ errors: {} })
We then need to wire this up to Vue.prototype.$errors. By defining a get and set for that property we can proxy through to the underlying property within our observable object.
All of this is pretty close to how data properties work behind the scenes. For the data properties the observable object is called $data. Vue then uses defineProperty with get and set to proxy though from the Vue instance to the $data object, just like in my example.
as Estradiaz said:
You can use Vuex and access the value outside of Vue like in this answer: https://stackoverflow.com/a/47575742/10219239
This is an addition to Skirtles answer:
You can access such variables via Vue.prototype.variable.
You can set them directly, or use Vue.set, it works either way.
My code (basically the same as Skirtless):
main.js
const mobile = Vue.observable({ mobile: {} });
Object.defineProperty(Vue.prototype, '$mobile', {
get() { return mobile.mobile; },
set(value) { mobile.mobile = value; }
});
function widthChanged() {
if (window.innerWidth <= 768) {
if (!Vue.prototype.$mobile) Vue.set(Vue.prototype, '$mobile', true);
} else if (Vue.prototype.$mobile) Vue.set(Vue.prototype, '$mobile', false);
}
window.addEventListener("resize", widthChanged);
widthChanged();
Home.vue:
watch: {
'$mobile'(newValue) {
// react to Change in width
}
}

How to update Time in DOM In Vue.js without uisng set-timeout or set-interval

I want to update time without using setInterval function in Vue. Is there any way? It's work with setInterval like the code below. However, I want something inbuilt in vue or any other best/different way to do it.
new Vue({
el: '#app',
data: {
seconds: '',
},
mounted:function(){
setInterval( () => {
this.seconds= new Date().getSeconds();
}, 100);
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
Realtime Second = {{seconds}}
</div>
For things that need to change after a single update cycle we have this.$nextTick. This is, however, not really useful for a timer. That makes window.setInterval the next best thing.
Keep in mind that just like with manually added event handlers, timeouts and intervals must be manually cleared in a lifecycle hook. Your current code should be something like:
new Vue({
el: '#app',
data: {
seconds: (new Date).getSeconds(),
intervalRef: null
},
mounted () {
this.intervalRef = window.setInterval(() => {
this.seconds = (new Date).getSeconds();
});
},
beforeDestroy () {
if (this.intervalRef) {
window.clearInterval(this.intervalRef);
this.intervalRef = null;
}
}
});

V-model with datepicker input

Trying to build a component that works with daepicker and using v-model to bind the input value. But the input event does not appear to be firing and I can’t seem to figure out why. Here’s my component:
<div id="app">
<datepicker v-model="date"></datepicker>
</div>
Vue.component('datepicker', {
template: '<input type="text" class="form-control pull-right" placeholder="dd/mm/aaaa" autocomplete="off">',
mounted: function() {
$(this.$el).datepicker({
autoclose: true,
startView: 'years',
}).on('changeDate', function(e) {
this.$emit('input', e.format('dd/mm/yyyy'));
});
},
destroyed: function () {
$(this.$el).datepicker('destroy');
}
});
var app = new Vue({
el: '#app',
data: {
date: '2018-03-01'
}
})
In addition, the following error appears in the console:
Uncaught TypeError: this.$emit is not a function
If you're mixing jQuery and Vue (just a guess from the code fragment), you're mixing up your contexts. One (of many) ways to fix:
mounted: function() {
const self = this;
$(this.$el).datepicker({
autoclose: true,
startView: 'years',
}).on('changeDate', function(e) {
self.$emit('input', e.format('dd/mm/yyyy'));
});
},
I failed with jacky's answer, but thanks to https://github.com/Xelia, problem sovled (even in Vue 1.0, using ready life cycle instead of mounted)
Manually update vue data in datepicker changeDate event listener, like this
var app = new Vue({
el: '#app',
data: {
startDate: '',
},
mounted() {
$("#startDate").datepicker().on(
"changeDate", () => {this.startDate = $('#startDate').val()}
);
},
})
https://jsfiddle.net/3a2055ub/
And by the way, if you are working on legacy company project using ES5 function instead of ES6 fat arrow function. Need to bind this, which is vue instance, into the function. For example:
mounted() {
var self = this; // the vue instance
$("#startDate").datepicker().on(
"changeDate", function() {self.startDate = $('#startDate').val()}
);
},
Of course there are other ways to reach this goal, as this blog written by Jason Arnold
shows.
Reference: https://github.com/vuejs/vue/issues/4231
Probable related question: v-model not working with bootstrap datepicker

VueJs async template/component with placeholder

I am pretty new to VueJs, so I am still figuring things out.
Since our templates are stored in the database, I want my templates to load async. For my components I now use the component-factory approach.
var c = Vue.component('my-async-component', function(resolve, reject){
setTimeout(function(){
resolve({
template: '<div class="loader">loaded asynchronous: {{ pageName }}</div>',
data() {
return {
pageName: 'my Page'
}
}
})
},2000)
})
But is it possible to have some kind of placeholder while loading it? I know I can do something with But in that case I need to have a parent component and I would like this to be independent.
On a Vue-instance you can do stuff in the render function end hook it up to mounted like:
var app = new Vue({
el: '#app',
data: {
finish: false,
template: null
},
render: function(createElement) {
if (!this.template) {
return createElement('div', 'loading...');
} else {
return this.template();
}
},
mounted() {
var self = this;
$.post('myUrl', {foo:'bar'}, function(response){
var tpl = response.data.template;
self.template = Vue.compile(tpl).render;
})
}
})
Is this possible in a component? And is this still working when I have some nested divs (see an other question of mine: here)
Ok, I figured it out. I just needed to reed de VUE guide a little bit bettter.
I just followed the advanced async example from the docs and now I have a working example.
So I have my template like this:
<div id="app">
<my-async-component></my-async-component>
</div>
Then in my JS I declared the template like:
var c = Vue.component('my-async-component', function(){
return {
component: new Promise(function(resolve, reject){
// setTimeout to simulate an asynchronous call
setTimeout(function(){
resolve({
template: '<div class="loader">loaded asynchronous</div>'
})
},3000)
}),
loading: Vue.component('loader', {
template: '<p>loading...</p>'
}),
error: Vue.component('load-error', {
template: '<p>error loading component</p>'
}),
delay: 200,
timeout: 10000
}
})
var vm = new Vue({
el: '#app'
});
The loading and error components could also be globally registered components, so it's easy to reuse.
Hopefully I could help someone with this answer to my own question.

Vue.js 2.0 this.$compile

How do can you compile a HTML string to template within a component method?
This was possible in Vue 1 like in this jsfiddle
new Vue({
el: '#app',
data: {
sampleElement: '<button v-on="click: test()">Test</button>'
},
methods:{
addNewElement: function(){
var element = $('#app').append(this.sampleElement);
/* compile the new content so that vue can read it */
this.$compile(element.get(0));
},
test: function(){
alert('Test');
}
}
});
How can you do this in Vue 2?
This is no longer possible, however, Vue 2 is data driven, so you shouldn't be trying to affect the DOM manually at all, instead you should bind elements to the underlying data in your view model. With that in mind your example will need to be re-written. Firstly, start by making your element a component:
Vue.component('my-button', {
template: '<button v-on:click="test()">{{text}}</button>',
props: ['text'],
methods: {
test() {
alert('Test');
}
}
});
Then you can create your main view model and add your button component using a v-for:
Markup:
<button v-on:click="addNewElement()">Add Element</button>
<my-button v-for="button in buttons" :text="button"></my-button>
View model:
new Vue({
el: '#app',
methods: {
addNewElement: function() {
this.buttons.push('Test');
}
},
data: {
buttons: []
}
});
In this example we are pushing buttons to an array that will then be displayed on the page, rather than manually appending them to the template.
Here's the JSFiddle: http://jsfiddle.net/10q9je5a/
If you want something more generic, then you can simply create an array of different components and use :is to let Vue know which component to render:
Markup:
<div id="app">
<button v-on:click="addNewElement()">Add Element</button>
<div :is="element.component" v-for="element in elements" v-bind="element.props"></div>
</div>
View Model:
new Vue({
el: '#app',
methods: {
addNewElement: function() {
this.elements.push({component: 'my-button', props: {text: 'Test'}});
}
},
data: {
elements: []
}
});
Here's the JSFiddle for that: http://jsfiddle.net/cxo5eto0/