There is a popup (1) that should be called after 15 seconds of being on the page.
But if the user opened some other popup(2), then don't call the first one.
popup(1);
mounted() {
this.openModal();
},
// methods
openModal() {
setTimeout(() => {
this.isModalVisible = true;
}, 15000);
},
How to do it?
Perhaps need to stop setTimeOut?
Maybe something like following snippet:
new Vue({
el: '#demo',
data() {
return {
isModalVisible: false,
isModalOther: false
}
},
methods: {
openModal() {
setTimeout(() => {
if(!this.isModalOther) this.isModalVisible = true;
}, 5000);
},
openOtherModal() {
this.isModalVisible = false
this.isModalOther = true;
},
},
mounted() {
this.openModal();
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div v-if="isModalVisible">popup1</div>
<div v-if="isModalOther">popup2</div>
<button #click="openOtherModal">open popup2</button>
</div>
To cancel a timeout, all you need to do is call clearTimeout(TimeoutID);. A timeoutID is a returned by the setTimeout() method automatically, so just save it in a variable
let timer = setTimeout(...);
then, when you call popup(2), just add
this.clearTimeout(timer);
and the first popup won't show
I have an object property which could listen to the user input or could be changed by the view.
With the snipped below :
if I typed something the value of my input is updated and widget.Title.Name is updated.
if I click on the button "External Update", the property widget.Title.Name is updated but not the value in my field above.
Expected result : value of editable text need to be updated at the same time when widget.Title.Name change.
I don't understand why there are not updated, if I inspect my property in vue inspector, all my fields (widget.Title.Name and Value) are correctly updated, but the html is not updated.
Vue.component('editable-text', {
template: '#editable-text-template',
props: {
value: {
type: String,
default: '',
},
contenteditable: {
type: Boolean,
default: true,
},
},
computed: {
listeners() {
return { ...this.$listeners, input: this.onInput };
},
},
mounted() {
this.$refs["editable-text"].innerText = this.value;
},
methods: {
onInput(e) {
this.$emit('input', e.target.innerText);
}
}
});
var vm = new Vue({
el: '#app',
data: {
widget: {
Title: {
Name: ''
}
}
},
async created() {
this.widget.Title.Name = "toto"
},
methods: {
externalChange: function () {
this.widget.Title.Name = "changed title";
},
}
})
button{
height:50px;
width:100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<editable-text v-model="widget.Title.Name"></editable-text>
<template>Name : {{widget.Title.Name}}</template>
<br>
<br>
<button v-on:click="externalChange">External update</button>
</div>
<template id="editable-text-template">
<p ref="editable-text" v-bind:contenteditable="contenteditable"
v-on="listeners">
</p>
</template>
I searched a lot of subject about similar issues but they had reactivity problem, I think I have a specific problem with input. Have you any idea of what's going on ? I tried to add a listener to change event but it was not triggered on widget.Title.Name change.
To anwser to this problem, you need to do 3 differents things.
Add watch property with the same name as your prop (here value)
Add debounce function from Lodash to limit the number of request
Add a function to get back the cursor (caret position) at the good position when the user is typing
For the third point : when you change the value of widget.Title.Name, the component will re-render, and the caret position will be reinitialize to 0, at the beginning of your input. So, you need to re-update it at the last position or you will just write from right to left.
I have updated the snippet above with my final solution.
I hope this will help other people coming here.
Vue.component('editable-text', {
template: '#editable-text-template',
props: {
value: {
type: String,
default: '',
},
contenteditable: {
type: Boolean,
default: true,
},
},
//Added watch value to watch external change <-> enter here by user input or when component or vue change the watched property
watch: {
value: function (newVal, oldVal) { // watch it
// _.debounce is a function provided by lodash to limit how
// often a particularly expensive operation can be run.
// In this case, we want to limit how often we update the dom
// we are waiting for the user finishing typing his text
const debouncedFunction = _.debounce(() => {
this.UpdateDOMValue();
}, 1000); //here your declare your function
debouncedFunction(); //here you call it
//not you can also add a third argument to your debounced function to wait for user to finish typing, but I don't really now how it works and I didn't used it.
}
},
computed: {
listeners() {
return { ...this.$listeners, input: this.onInput };
},
},
mounted() {
this.$refs["editable-text"].innerText = this.value;
},
methods: {
onInput(e) {
this.$emit('input', e.target.innerText);
},
UpdateDOMValue: function () {
// Get caret position
if (window.getSelection().rangeCount == 0) {
//this changed is made by our request and not by the user, we
//don't have to move the cursor
this.$refs["editable-text"].innerText = this.value;
} else {
let selection = window.getSelection();
let index = selection.getRangeAt(0).startOffset;
//with this line all the input will be remplaced, so the cursor of the input will go to the
//beginning... and you will write right to left....
this.$refs["editable-text"].innerText = this.value;
//so we need this line to get back the cursor at the least position
setCaretPosition(this.$refs["editable-text"], index);
}
}
}
});
var vm = new Vue({
el: '#app',
data: {
widget: {
Title: {
Name: ''
}
}
},
async created() {
this.widget.Title.Name = "toto"
},
methods: {
externalChange: function () {
this.widget.Title.Name = "changed title";
},
}
})
/**
* Set caret position in a div (cursor position)
* Tested in contenteditable div
* ##param el : js selector to your element
* ##param caretPos : index : exemple 5
*/
function setCaretPosition(el, caretPos) {
var range = document.createRange();
var sel = window.getSelection();
if (caretPos > el.childNodes[0].length) {
range.setStart(el.childNodes[0], el.childNodes[0].length);
}
else
{
range.setStart(el.childNodes[0], caretPos);
}
range.collapse(true);
sel.removeAllRanges();
}
button{
height:50px;
width:100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<editable-text v-model="widget.Title.Name"></editable-text>
<template>Name : {{widget.Title.Name}}</template>
<br>
<br>
<button v-on:click="externalChange">External update</button>
</div>
<template id="editable-text-template">
<p ref="editable-text" v-bind:contenteditable="contenteditable"
v-on="listeners">
</p>
</template>
you can use $root.$children[0]
Vue.component('editable-text', {
template: '#editable-text-template',
props: {
value: {
type: String,
default: '',
},
contenteditable: {
type: Boolean,
default: true,
},
},
computed: {
listeners() {
return {...this.$listeners, input: this.onInput
};
},
},
mounted() {
this.$refs["editable-text"].innerText = this.value;
},
methods: {
onInput(e) {
this.$emit('input', e.target.innerText);
}
}
});
var vm = new Vue({
el: '#app',
data: {
widget: {
Title: {
Name: ''
}
}
},
async created() {
this.widget.Title.Name = "toto"
},
methods: {
externalChange: function(e) {
this.widget.Title.Name = "changed title";
this.$root.$children[0].$refs["editable-text"].innerText = "changed title";
},
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="app">
<editable-text v-model="widget.Title.Name"></editable-text>
<template>Name : {{widget.Title.Name}}</template>
<br>
<br>
<button v-on:click="externalChange">External update</button>
</div>
<template id="editable-text-template">
<p ref="editable-text" v-bind:contenteditable="contenteditable" v-on="listeners">
</p>
</template>
or use Passing props to root instances
Vue.component('editable-text', {
template: '#editable-text-template',
props: {
value: {
type: String,
default: '',
},
contenteditable: {
type: Boolean,
default: true,
},
},
computed: {
listeners() {
return {...this.$listeners, input: this.onInput
};
},
},
mounted() {
this.$refs["editable-text"].innerText = this.value;
this.$root.$on("titleUpdated",(e)=>{
this.$refs["editable-text"].innerText = e;
})
},
methods: {
onInput(e) {
this.$emit('input', e.target.innerText);
}
}
});
var vm = new Vue({
el: '#app',
data: {
widget: {
Title: {
Name: ''
}
}
},
async created() {
this.widget.Title.Name = "toto"
},
methods: {
externalChange: function(e) {
this.widget.Title.Name = "changed title";
this.$root.$emit("titleUpdated", this.widget.Title.Name);
},
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="app">
<editable-text v-model="widget.Title.Name"></editable-text>
<template>Name : {{widget.Title.Name}}</template>
<br>
<br>
<button v-on:click="externalChange">External update</button>
</div>
<template id="editable-text-template">
<p ref="editable-text" v-bind:contenteditable="contenteditable" v-on="listeners">
</p>
</template>
I am using this code:
var vueApp = new Vue({
el: '#app',
data: {
modalKanji: {}
},
methods: {
showModalKanji(character) {
sendAjax('GET', '/api/Dictionary/GetKanji?character=' + character, function (res) { vueApp.modalKanji = JSON.parse(res); });
}
},
watch: {
'modalKanji': function (newData) {
setTimeout(function () {
uglipop({
class: 'modalKanji', //styling class for Modal
source: 'div',
content: 'divModalKanji'
});
}, 1000);
}
}
});
and I have an element that when clicked on, displays a popup with the kanji data inside:
<span #click="showModalKanji(kebChar)" style="cursor:pointer;>
{{kebChar}}
</span>
<div id="divModalKanji" style='display:none;'>
<div v-if="typeof(modalKanji.Result) !== 'undefined'">
{{ modalKanji.Result.literal }}
</div>
</div>
It works, but only when used with a setTimeout delay to "let the time for Vue to update its model"...if I remove the setTimeout so the code is called instantaneousely in the watch function, the popup data is always "1 iteration behind", it's showing the info of the previous kanji I clicked...
Is there a way for a watcher function to be called AFTER Vue has completed is binding with the new data?
I think you need nextTick, see Async-Update-Queue
watch: {
'modalKanji': function (newData) {
this.$nextTick(function () {
uglipop({
class: 'modalKanji', //styling class for Modal
source: 'div',
content: 'divModalKanji'
});
});
}
}
Given:
<parent-element>
<sibling-a></sibling-a>
<sibling-b></sibling-b>
</parent-element>
How can I get access to a click event on a button in siblingA to change some value to another in sibling-b using $emit(…)?
#craig_h is correct, or you can use $refs like:
<parent-element>
<sibling-a #onClickButton="changeCall"></sibling-a>
<sibling-b ref="b"></sibling-b>
</parent-element>
In parent methods:
methods: {
changeCall() {
this.$refs.b.dataChange = 'changed';
}
}
In siblingA:
Vue.component('sibling-a', {
template: `<div><button #click="clickMe">Click Me</button></div>`,
methods: {
clickMe() {
this.$emit('onClickButton');
}
}
});
In siblingB:
Vue.component('sibling-b', {
template: `<div>{{dataChange}}</div>`,
data() {
return {
dataChange: 'no change'
}
}
});
For that you can simply use a global bus and emit your events on to that:
var bus = new Vue();
Vue.component('comp-a', {
template: `<div><button #click="emitFoo">Click Me</button></div>`,
methods: {
emitFoo() {
bus.$emit('foo');
}
}
});
Vue.component('comp-b', {
template: `<div>{{msg}}</div>`,
created() {
bus.$on('foo', () => {
this.msg = "Got Foo!";
})
},
data() {
return {
msg: 'comp-b'
}
}
});
Here's the JSFiddle: https://jsfiddle.net/6ekomf2c/
If you need to do anything more complicated then you should look at Vuex
I have a problem that passing functions to components is not working the way it's specified in the documentation.
This is in my app.js
methods: {
updateAnswer: function(question) {
console.log('question: '+question);
}
}
This is in my html-file:
<multiplechoice class="question counterIncrement counterShow active" id="q2" whenanswered="{{ updateAnswer('1') }}"></multiplechoice>
This is in my components.js file:
props: [
'whenanswered'
],
ready: function() {
this.whenanswered();
},
I have already tried this:
props: [
{ name: 'whenanswered', type: Function}
];
but still no luck.
This is in my console when I load the page:
Uncaught TypeError: this.whenanswered is not a function
Any help would be very much appreciated :)
You could do this:
<multiplechoice class="question counterIncrement counterShow active" id="q2" :whenanswered="updateAnswer('1')"></multiplechoice>
Without the ':'(same as v-bind) like you did will only send a string and not evaluate. Even with those {{ }}.
But remember that your updateAnswerfunction should return a function. Since your prop will execute updateAnswer('1') and your multiplechoiceactually expects a function that will be executed when it wants.
methods: {
whenanswered: function(question) { // or whenanswered (question) { for ES6
return function () { ... } // or () => {...} for ES6
}
}
A fiddle would help, but basically, you need:
methods: {
whenanswered: function(question) {
...
}
}
if you wanna call that function. A prop is just a string, not a function.
Example:
<div id="app">
Loading...
<data-table on-load="{{onChildLoaded}}"></data-table>
</div>
new Vue({
el: "#app",
methods: {
onChildLoaded: function (msg) {
console.log(msg);
}
},
components: {
'data-table': {
template: 'Loaded',
props: ['onLoad'],
ready: function () {
this.onLoad('the child has now finished loading!')
}
}
}
});