Set form action dynamically using computed property - vuejs2

I'm trying to send form to certain action, based on select value.
I have such template:
<template>
<form method="post" :action="myRoute" ref="myForm">
<select #change="entitySelected" v-model="selected">
<!-- -->
</select>
</form>
</template>
I'm trying to set up form action dynamically when new select value is appeared:
<script>
export default {
data() {
return {
selected: '',
}
},
computed: {
myRoute: function () {
return 'example.com/'+this.selected
}
},
methods: {
entitySelected(event) {
console.log(this.$refs.myForm.action) //<- action is 'example.com' without selected value
console.log(this.selected) //<- this value is as expected
this.$refs.myForm.submit()
}
}
}
</script>
What's wrong?
P. S. Browser - Firefox

Probably not the best way, but it works:
userSelected(event) {
this.$nextTick(function () {
this.$refs.backdoor.submit()
})
}

You can use setAttribute() when updating the selected value :
this.$refs.myForm.setAttribute('action', this.myRoute);

Related

How to use v-model and computed properties on Input fields?

I have a simple component with 2 input fields:
<template>
<div>
<input type="text" v-model="name">
<input type="text" v-model="alias">
</div>
</template>
<script>
export default {
data() {
return {
name: "",
alias: ""
}
}
}
</script>
I want to automatically insert the name model's value to the alias field IF the alias field is empty. If it's not empty, I want it to use its own value.
Here is my attempt:
<template>
<div>
<input type="text" v-model="name">
<input type="text" v-model="alias">
</div>
</template>
<script>
export default {
data() {
return {
name: "",
alias: ""
}
},
computed: {
alias: {
get: function() {
if (this.alias.length < 1) {
return this.name
} else {
return this.alias
}
},
set: function(newValue) {
this.alias = newValue
}
}
}
}
</script>
The problem is that alias doesn't get the actual value in the alias property if I type something into the name field. I can only see it visually - but it doesn't hold the value. However, if I type into the alias field, it gets updated properly.
What am I missing here and how can I make it the way I want it?
Computed won't work because it should be treated as immutable.
Also because the model will be updated on each input, a watch won't work either, it would only pick up the first char of what you enter, unless its pre-populated.
This is how I would do it, simply add a #blur event on the name input then fire a method which populates alias if it's empty and on alias in case they empty it out.
The same method could be used in mounted, if you pre-populate the models, or you could watch it.
{
template: `
<div>
<input type="text" v-model="name" #blur="setAlias()">
<input type="text" v-model="alias" #blur="setAlias()">
</div>
`,
data() {
return {
name: '',
alias: ''
}
},
methods: {
setAlias() {
if (this.alias === '') {
this.alias = this.name
}
}
}
}
Firstly, you cannot have a computed property and a data property with the same name. Since both computed and data properties end up as properties on the same state object, one will overwrite the other.
Secondly, and I think you did this because of the first point, in your computed alias getter, your reference the alias again, which is essentially referencing itself, and looks like it could give some inconsistent return values.
I can think of two solutions to your issue:
1) Use a watcher on name:
Create a watcher function for name, and in it set this.alias to the same value as name when alias is blank, or if it's the same as the previous name value.
<script>
export default {
data: () => ({
name: "",
alias: ""
}),
watch: {
name(newVal, oldVal) {
if (!this.alias || this.alias === oldVal) {
this.alias = newVal;
}
}
}
}
</script>
2) Use explicit :value and #change/#keyup bindings on the name input:
v-model is a convenience method that sets both of these for you, but in your case you want to do some more logic in the change handler that just setting a state property value.
<template>
<div>
<input
type="text"
:value="name"
#keyup="onNameInput"
/>
<input type="text" v-model="alias">
</div>
</template>
<script>
export default {
data: () => ({
name: "",
alias: ""
}),
methods: {
// Check and set both values on name input events
onNameInput(e) {
if (!this.alias || this.alias === this.name) {
this.alias = e.target.value;
}
this.name = e.target.value;
}
}
}
</script>

VueJS: how to trigger 'change' on <input> changed programmatically

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.

How to run a function in Vue.js when state changes

I'm looking to run a function when the state changes in my Vue app.
In my component I'm able to get the boolean state of isOpen. I'm looking to run a function that adds focus to my form input when the modal opens and isOpen is set to true. I've tried using a watcher but with no luck. I'm opening my modal by calling :class="{ 'is-open': search.isOpen }" in the html and showing it via css. Any help would be most appreciated.
data() {
return {
isFocussed: this.$store.state.search,
//checks the state of isOpen?
}
},
computed: {
search() { return this.$store.state.search },
},
watch: {
isFocussed() {
this.formfocus()
},
},
methods: {
formfocus() {
document.getElementById('search').focus()
},
please check my snippet which shows the good way to work in Vue.js, you can work with refs which is very helpful instead of document.getElementById()
new Vue({
el: '#app',
data: {
isOpen: false,
},
computed: {
},
watch: {
isOpen(){
if(this.isOpen){
this.$nextTick( function () {
this.formfocus();
}
);
}
}
},
methods: {
formfocus(){
this.$refs.search.focus();
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.js"></script>
<div id="app">
<button v-on:click="isOpen = !isOpen">show modal</button>
<div v-if="isOpen">
<input ref="search" type="text" placeholder="search">
</div>
</div>
EDIT: i have added a conditional if on the watch, i hope this solves the problem
I am not sure what your template looks like but here is how I set focus on a conditional element.
element
<input type="text" class="search-input" v-model="search" :class="{'collapsed-search': hideInput}" placeholder="Enter keyword" ref="search">
notice the ref="search" on the input.
here is the method when the input condition is true
toggleSearch() {
this.hideInput = !this.hideInput
this.search = ''
setTimeout(() => {
this.$refs.search.focus()
}, 1000)
}
this.$refs.search.focus() is called after the element has been fully created which is the purpose of the setTimeout

Is it possible to detect if a change event was triggered by a click on a Vue select element?

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>

VueJs: Textarea input binding

I'm trying to figure out how to detect change of the value on the textarea from within the component.
For input we can simply use
<input
:value="value"
#input="update($event.target.value)"
>
However on textarea this won't work.
What I'm working with is the CKEditor component, which should update wysiwyg's content when model value of the parent component (attached to this child component) is updated.
My Editor component currently looks like this:
<template>
<div class="editor" :class="groupCss">
<textarea :id="id" v-model="input"></textarea>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
},
id: {
type: String,
required: false,
default: 'editor'
}
},
data() {
return {
input: this.$slots.default ? this.$slots.default[0].text : '',
config: {
...
}
}
},
watch: {
input(value) {
this.update(value);
}
},
methods: {
update(value) {
CKEDITOR.instances[this.id].setData(value);
},
fire(value) {
this.$emit('input', value);
}
},
mounted () {
CKEDITOR.replace(this.id, this.config);
CKEDITOR.instances[this.id].setData(this.input);
this.fire(this.input);
CKEDITOR.instances[this.id].on('change', () => {
this.fire(CKEDITOR.instances[this.id].getData());
});
},
destroyed () {
if (CKEDITOR.instances[this.id]) {
CKEDITOR.instances[this.id].destroy()
}
}
}
</script>
and I include it within the parent component
<html-editor
v-model="fields.body"
id="body"
></html-editor>
however, whenever parent component's model value changes - it does not trigger the watcher - effectively not updating the editor's window.
I only need update() method to be called when parent component's model fields.body is updated.
Any pointer as to how could I approach it?
That's a fair bit of code to decipher, but what I would do is break down the text area and the WYSIWYG HTML window into two distinct components and then have the parent sync the values, so:
TextArea Component:
<template id="editor">
<textarea :value="value" #input="$emit('input', $event.target.value)" rows="10" cols="50"></textarea>
</template>
/**
* Editor TextArea
*/
Vue.component('editor', {
template: '#editor',
props: {
value: {
default: '',
type: String
}
}
});
All I'm doing here is emitting the input back to the parent when it changes, I'm using input as the event name and value as the prop so I can use v-model on the editor. Now I just need a wysiwyg window to show the code:
WYSIWYG Window:
/**
* WYSIWYG window
*/
Vue.component('wysiwyg', {
template: `<div v-html="html"></div>`,
props: {
html: {
default: '',
type: String
}
}
});
Nothing much going on there, it simply renders the HTML that is passed as a prop.
Finally I just need to sync the values between the components:
<div id="app">
<wysiwyg :html="value"></wysiwyg>
<editor v-model="value"></editor>
</div>
new Vue({
el: '#app',
data: {
value: '<b>Hello World</b>'
}
})
Now, when the editor changes it emits the event back to the parent, which updates value and in turn fires that change in the wysiwyg window. Here's the entire thing in action: https://jsfiddle.net/Lnpmbpcr/