I have the following data property called selector, which I set its initial value in mounted() since it is an HTML element that also has a bit of delay due to loading I set its value in a setTimeout(). Then whenever I select a different option its value should obviously change. And this change should we watched. Right now the watcher does not seem to work and I can't see why. Can someone help?
My data propery:
data() {
return {
selector: " ",
}}
my watcher:
watch: {
// whenever selector changes, this function will run
selector: function(newSelection) {
console.log("in watcher", newSelection);
$(".page-item a").each(function() {
if ($(this).text() == ">") $(this).text(" ");
else if ($(this).text() == ">>") $(this).text(" ");
else if ($(this).text() == "<") $(this).text(" ");
else if ($(this).text() == "<<") $(this).text(" ");
});
}
},
and mounted()
mounted() {
setTimeout(function() {
document
.getElementsByTagName("select")[0]
.setAttribute("id", "VueTables__limit");
this.selector = document.getElementById("VueTables__limit").value;
console.log("in mounted", this.selector);
}, 2000);
}
HTML:
<div class="form-group form-inline pull-right VueTables__limit">
<div class="VueTables__limit-field">
<label for="VueTables__limit" class="">Records:</label>
<select name="limit" id="VueTables__limit" class="form-control">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option></select></div></div>
Update your mouted function:
mounted() {
var self = this; //save the ref of 'this'
setTimeout(function() {
document
.getElementsByTagName("select")[0]
.setAttribute("id", "VueTables__limit");
self.selector = document.getElementById("VueTables__limit").value;
console.log("in mounted", this.selector);
}, 2000);
}
First of all, if you need to setup initial data value and call some methods during component's lifecycle, use watcher with immediate: true. Next, default value could be just empty string "" no need to add space. And the last, why you use jQuery, when you have vue? Don't get it
Related
I want to create a vue js components where it contains a range slider of hours with two handles.
I use vue3 + vite.js
I tried this code to implement the components but when I drag one of handles I have an error
Code :
this is the template :
<template>
<div>
<input type="range" ref="rangeInput" v-model="rangeValue" #input="updateRange"/>
<div class="range-slider">
<div class="handle" :style="{left: leftHandle + '%'}" #mousedown="startHandleDrag(1)">
{{ formatHour(rangeValue[0]) }}
</div>
<div class="handle" :style="{left: rightHandle + '%'}" #mousedown="startHandleDrag(2)">
{{ formatHour(rangeValue[1]) }}
</div>
</div>
</div>
</template>
and this is the script :
<script>
export default {
data() {
return {
rangeValue: [8, 18],
handleDragging: 0
};
},
computed: {
leftHandle() {
return this.rangeValue[0];
},
rightHandle() {
return this.rangeValue[1];
}
},
methods: {
updateRange(event) {
const value = event.target.value;
const range = this.rangeValue;
if (this.handleDragging === 1) {
range[0] = value[0];
} else if (this.handleDragging === 2) {
range[1] = value[1];
} else {
range[0] = value[0];
range[1] = value[1];
}
this.rangeValue = range;
},
startHandleDrag(handle) {
this.handleDragging = handle;
document.addEventListener("mouseup", this.stopHandleDrag);
document.addEventListener("mousemove", this.updateRange);
},
stopHandleDrag() {
this.handleDragging = 0;
document.removeEventListener("mouseup", this.stopHandleDrag);
document.removeEventListener("mousemove", this.updateRange);
},
formatHour(value) {
return value + ":00";
}
}
};
</script>
Error :
any ideas to solve it !!!
In your startHandleDrag() and stopHandleDrag(), you bind updateRange() to the mousemove event:
document.addEventListener("mousemove", this.updateRange);
There are two issues with that:
The target of the mousemove event is the element under the cursor. This can be any element, and unless it happens to be an input, it will not have a value attribute (and if it does, it will not hold an array). If you really want to use the "mousemove" event, use the cursor coordinates like pageX or pageX.
You bind it as a function pointer (addEventListener("mousemove", this.updateRange)), and when called from the listener, this will refer to element.target. To avoid this, either use an arrow function (addEventListener("mousemove", (e) => this.updateRange(e))) or bind this (addEventListener("mousemove", this.updateRange.bind(this))).
I don't fully understand what you want to do with the handles, but my guess is that adding and removing listeners is a workaround, and you actually want to make them draggable? If so, have a look at the drag event. Hope that helps!
I have this block of code in <oneChildComponent />, based on <select> element:
new Vue({
template:'
<select v-model="selectedOption" #change="handleChange">
<option value="" disabled>--Select--</option>
<option v-for="item in data" :value="item.val">
{{item.text}}
</option>
</select>
',
data:{
selectedOption:''
},
methods:{
handleChange:function(event){
console.log(this.selectedOption); //you will find the value here
}
}
})
The goal is to take a string value of selectedOption and pass it from <oneChildComponent /> to <anotherChildComponent />. Those components are contained in main parent component.
Is it possible to modify this handleChange() method with $emit(), and what's the best way to do it?
I'me new with vue.js, so thanks in advance.
Simply emit the changed value to the parent:
handleChange: function(event){
this.$emit("valueChanged", this.selectedOption);
}
In the parent, you need to define a variable, in which the emitted value is stored, and a function for assigning it.
{
data: function() {
return {
myValue: ""
}
},
methods: {
setValue(value) {
this.myValue = value;
}
}
}
Also in the parent, you then can intercept this emit and set this value by doing:
<oneChildComponent #valueChanged="setValue"/>
Finally, you have to pass myValue to your second child component as prop:
<anotherChildComponent :myValue="myValue" />
This variable is available in this child component by declaring it as a prop:
{
props: {
myValue: String
}
}
I'm going to build a customized virtual keyboard, so that's the first problem I've encountered.
I have an input element, whose value is changed from outside, in my case by pressing a button. The problem is that there seems to be no way to trigger the normal 'change' event.
Neither clicking outside the input, nor pressing Enter gives any result. What might be the correct way of solving this problem?
<template>
<div class="app-input">
<input #change="onChange" type="text" v-model="value" ref="input">
<button #click="onClick">A</button>
</div>
</template>
<script>
export default {
name: "AppInput",
data() {
return {
inputDiv: null,
value: ""
};
},
props: {
msg: String
},
methods: {
onClick() {
this.value = this.value + "A";
this.inputDiv.focus();
},
onChange() {
alert("changed");
}
},
mounted() {
this.$nextTick(() => {
this.inputDiv = this.$refs.input;
});
}
};
</script>
The whole pen can be found here.
v-on:change would only trigger on a direct change on the input element from a user action.
What you are looking for is a wathcer for your data property, whenever your value changes, watcher will execute your desired function or task.
watch: {
value: function() {
this.onChange();
}
}
The watch syntax is elaborated on the provided official vuejs docs link. use your data property as the key and provide a function as a value.
Check the snippet.
export default {
name: "AppInput",
data() {
return {
inputDiv: null,
value: ""
};
},
props: {
msg: String
},
methods: {
onClick() {
this.value = this.value + "A";
this.inputDiv.focus();
},
onChange() {
alert("changed");
}
},
// this one:
watch: {
value: function() {
this.onChange();
}
},
// --- rest of your code;
mounted() {
this.$nextTick(() => {
this.inputDiv = this.$refs.input;
});
}
};
When I build any new vue application, I like to use these events for a search input or for other inputs where I don't want to fire any functions on #change
<div class="class">
<input v-model="searchText" #keyup.esc="clearAll()" #keyup.enter="getData()" autofocus type="text" placeholder="Start Typing ..."/>
<button #click="getData()"><i class="fas fa-search fa-lg"></i></button>
</div>
These will provide a better user experience in my opinion.
I have a <select>-element that has a data property bound to it using v-model in Vue.
Sometimes I want to change that value dynamically. I also have an event-listener attached to this element which is triggered on the change-event. See code example:
<template>
<div class="mySelector">
<select id="testSelect" v-model="mySelectModel"
#change="onChange($event)">
<template v-for="(item, index) in someList">
<option :class="['btn', 'btn-default', 'removing-button']" :value="index">{{item.name}}</option>
</template>
</select>
</div>
</template>
<script>
export default {
data() {
return {
mySelectModel: null
}
},
props: {
},
methods: {
customChange: function() {
this.mySelectModel = ... // some value we from somewhere else that is set dynamically on some condiftion
},
onChange: function (event) {
if (!event) return;
// DO SOMETHING THAT WE ONLY WANT TO DO ON A REAL CLICK
}
},
}
</script>
The problem I have is that when I change the data value mySelectModel dynamically, like in the customChange-method, the change event is also called, triggering the method onChange. I only want to do stuff in that method if it was really triggered by a real click, not when it was changed dynamically.
I can not find a way to distinguish between those cases when the change-event is triggered by a click or when it is just changed for some other reason. Any suggestions?
See vue-js-selected-doesnt-triggering-change-event-select-option, it appears that select does not trigger #change when v-model is updated by JS (only when the selected value is changed by user).
A directive can add the functionality
Vue.directive('binding-change', {
update: function (el, binding, vnode) {
const model = vnode.data.directives.find(d => d.name === 'model')
if (model) {
binding.value(model.value)
}
}
})
use like
<select id="testSelect"
v-binding-change="onChange"
v-model="mySelectModel"
#change="onChange($event)">
Not sure about the parameter to onChange - I'll give it a test.
Similar to this suggested solution, you can make a settable computed that you v-model in your widget:
The get function simply returns the data item
The set function does whatever you want a change in the widget to do, in addition to setting the data item
Other code can change the data item directly and will not execute the set code of the computed.
new Vue({
el: '#app',
data: {
values: ['one','two','three'],
selectedItem: 'two'
},
computed: {
wrappedSelectedItem: {
get() { return this.selectedItem; },
set(value) {
console.log("Changed in widget");
this.selectedItem = value;
}
}
},
methods: {
changeToThree() {
console.log("Stealth change!");
this.selectedItem = 'three';
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<select v-model="wrappedSelectedItem">
<option v-for="value in values" :value="value">{{value}}</option>
</select>
<button #click="changeToThree">Set to three</button>
</div>
I have a <select> component. Once I select a category I need to get the ID and also a boolean which checks if the category has a subcategory, if it does, I make an API call to fetch the subcategories.
Parent template:
<material-selectcat v-model="catId" name="category" id="selcat">
<option
v-for="cat in cats"
:value="cat.cat_id"
:subcat="cat.has_subCat"
v-text="cat.category_name"
></option>
</material-selectcat>
Child component:
<template>
<select><slot></slot></select>
</template>
<script>
export default {
props: ['value', 'subcat'],
watch: {
watcher: function() {}
},
computed: {
watcher: function() {
this.relaod(this.value);
this.fetchSubCategories(this.value, this.subcat);
}
},
methods: {
relaod: function(value) {
var select = $(this.$el);
select.val(value || this.value);
select.material_select('destroy');
select.material_select();
},
fetchSubCategories: function(val, sub) {
var mdl = this;
var catID = val || this.value;
console.log("sub: " + sub);
mdl.$emit("reset-subcats");
if (catID) {
if (sub == 1) {
if ($('.subdropdown').is(":visible") == true) {
$('.subdropdown').fadeOut();
}
} else {
axios.get(URL.API + '/subcategories/' + catID)
.then(function(response) {
response = response.data.subcatData;
response.unshift({
subcat_id: '0',
subcategory_name: 'All Subcategories'
});
mdl.$emit("update-subcats", response);
$('.subdropdown').fadeIn();
})
.catch(function(error) {
if (error.response.data) {
swal({
title: "Something went wrong",
text: "Please try again",
type: "error",
html: false
});
}
});
}
} else {
if ($('.subdropdown').is(":visible") == true) {
$('.subdropdown').fadeOut();
}
}
}
},
mounted: function() {
var vm = this;
var select = $(this.$el);
select
.val(this.value)
.on('change', function() {
vm.$emit('input', this.value);
});
select.material_select();
},
updated: function() {
this.relaod();
},
destroyed: function() {
$(this.$el).material_select('destroy');
}
}
</script>
But inside the fetchSubCategories() function this line always returns undefined:
console.log("sub: " + sub);
If I check the Vue Devtools tab in my Chrome inspector, I can see that all of the data exists:
cat_id:0
category_name:"All Subcategories"
has_subCat:0
But why doesnt has_subCat get passed as a prop?
The subcat prop is undefined because you are not passing it to the component. But, a subcat prop doesn't make much sense anyway, since you want to check the value for each option in the select which could all be different.
If you compose the options inside of the component definition:
// child component script
props: ['value', 'options'],
// child component template
<template>
<select>
<slot>
<option
v-for="option in options"
:key="option.id"
:value="option.id"
:subcat="option.subcat"
v-text="option.text"
></option>
</slot>
</select>
</template>
And then pass a correctly formatted options prop to the component:
// parent component script
computed: {
options() {
return this.cats.map((cat) => {
return {
id: cat.cat_id,
subcat: cat.has_subcat,
text: cat.category_name
}
});
}
}
// parent component template
<material-selectcat v-model="catId" :options="options" name="category" id="selcat">
</material-selectcat>
Then, you could get the correct subcat value for any option's corresponding value by referencing the options prop in the fetchSubCategories method:
fetchSubCategories: function(val) {
var mdl = this;
var catID = val || this.value;
var sub = (this.options.find(o => o.id === val) || {}).subcat;
...
The reason your value prop is defined in your code is that you are using the v-model directive on the component tag. The v-model directive is just syntactic sugar for :value="something" #input="something = $event.target.value". Since you've specified catID as the v-model, that will be passed to your component as the value prop.