Changing vue instance variables from within component - javascript

I've been teaching myself Vue.js, and have been utilising components to increase modularity.
One thing that I am struggling with is manipulating variables in the Vue instance from within the component. I have got it working well with v-model within a component by passing the variable in the jade file as a prop
eg loginform(slot="login-form" v-bind:form-submit="loginSubmit" v-bind:login-data="loginData")
Where loginData contains variables username & password which are 'v-modelled' to the inputs within the component. Then in the component template:
<input type="password" class="text-field" v-model="formData.password" />
However I have a tooltip component that I am wanting to use twice: One for the username field & one for the password field. The visibility of these tooltips are given by tooltips.username.vis and tooltips.password.vis respectively.
I can't seem to pass that variable as a prop in order to manipulate without getting the avoid manipulating props warning, despite v-model within the component not giving these warnings. The tooltip component is given below:
Vue.component('tooltip', {
props: ['show', 'message', 'click'],
template:
<transition name="shrink">
<div v-show="show" v-on:click="click" class="tooltip">
<div class="tooltip-arrow"></div>
<div class="tooltip-container">{{message}}</div>
</div>
</transition>
});
Does anyone have any idea on how I can achieve the desired affect (Hiding the tooltip on mouse click). I have tried passing a method as the click prop that has different arguments based on whether the tooltip is for the username or password input, however I get click undefined warnings. I could make two seperate functions but I would rather not explicitly write two functions that do the same thing.

You shouldn't attempt to modify props from within a component as Vue's warnings tell you, changes to props do not flow up from the component to the prop so any changes will be overwritten.
For what you're trying to achieve you should look into Vue's Custom Events https://v2.vuejs.org/v2/guide/components-custom-events.html
HTML
<div id="app">
<form>
<div>
<label>Username</label>
<input type="username" v-model="formData.username" />
<tooltip :show="tooltips.username.vis"
:message="tooltips.username.message" #tooltip:hide="tooltips.username.vis = false" />
</div>
<div>
<label>Password</label>
<input type="text" v-model="formData.password" />
<tooltip :show="tooltips.password.vis"
:message="tooltips.password.message" #tooltip:hide="tooltips.password.vis = false" />
</div>
</form>
</div>
JS
Vue.component('tooltip', {
props: ['show', 'message'],
template: `<transition name="shrink">
<div v-show="show" class="tooltip" #click="hide">
<div class="tooltip-arrow"></div>
<div class="tooltip-container">{{message}}</div>
</div>
</transition>`,
methods: {
hide () {
this.$emit('tooltip:hide');
},
}
});
new Vue({
el: "#app",
data: {
formData: {
username: '',
password: ''
},
tooltips: {
username: {
message: 'Fix your username',
vis: true
},
password: {
message: 'Fix your password',
vis: true
}
}
}
});
https://jsfiddle.net/10fjkoob/12/

Related

Linking a text field in a child component to a parent component's props in VueJS

I have a child component sending data via an event to a parent component in VueJS. From the parent component, I am routing the data (or trying to route the data...) to a sibling of the child and create new components with the data sent from the child.
I use a dictionary to group the data for various reasons, then push the dictionary into an array. A v-for loop loops thru the array and populates the previously mentioned new components with data found in that array. I probably don't need to do it this way, but that's how I'm doing it. I am open to alternatives.
Anyway, it doesn't work great. So far I'm only able to get one of the three strings I need to show up where I want it to. I'll explain more after I post the code.
Already tried:
A dozen different versions of the code, including creating a simple v-for in a list to do the job, and various versions with/without a dictionary or array.
In my research for the problem I've gone through the VueJS docs, Googled a few things, and found nothing.
In App.vue (I tried to remove all the irrelevant stuff):
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<TweetDeck v-on:messageFromTweetDeck="msgReceived($event)"/>
<!-- <ul>
<li v-for="(tweet, index) in tweets" :key="index">{{ tweet }}</li>
</ul>-->
<TwitterMsg v-for="(tweet, index) in tweets" :key="index"
:name="tweet.name" :handle="tweet.handle" tsp=3 :msg="tweet.tweet" />
<TwitterMsg name="aaa" handle='aaa'
tsp=50 msg="hey this is a message on twitter"/>
<input type="text" v-model="placeholderText"/>
</div>
</template>
<script>
import TwitterMsg from './components/TwitterMsg.vue'
import TweetDeck from './components/TweetDeck.vue'
export default {
name: 'app',
components: {
TwitterMsg,
TweetDeck
},
data: function() {
return {
tweets: [],
message: "",
placeholderText: ""
}
},
methods: {
msgReceived(theTweet, name, handle) {
this.tweets.push({tweet: theTweet, name: name, handle: handle})
}
}
}
</script>
And in TweetDeck.vue:
<template>
<div>
<input type='text' v-model="yourName">
<input type='text' v-model="yourHandle">
<input type='text' v-model="yourTweet"/>
<button type='button' #click="sendTweet()">Tweet</button>
</div>
</template>
<script>
export default {
name: "TweetDeck",
data: function() {
return {
yourName: "Your name here",
yourHandle: "Your twitter handle",
yourTweet: "What's going on?"
}
},
methods: {
sendTweet() {
this.$emit('messageFromTweetDeck', this.yourTweet, this.yourName, this.yourHandle);
}
}
}
</script>
You can also see the mostly unimportant TwitterMsg.vue here (I am trying to copy Twitter for learning purposes:
<template>
<div>
<h4>{{ name }}</h4>
<span>#{{ handle }}</span>
<span> {{ tsp }}</span> <!-- Time Since Posting = tsp -->
<span>{{ msg }}</span>
<img src='../assets/twit_reply.png'/><span>1</span>
<img src="../assets/twit_retweet.png"/><span>2</span>
<img src="../assets/twit_fave.png"/><span>3</span>
</div>
</template>
<script>
export default {
name: "TwitterMsg",
props: {
name: String,
handle: String,
tsp: String,
msg: String
}
}
</script>
<style>
img {
width: 30px;
height: 30px;
}
</style>
Expected result:
The code populates a new TwitterMsg component with appropriate name, handle and message data each time I click the "Tweet" button.
Actual results:
My code fails to help the name and handle strings make it from the input text box in TweetDeck.vue all the way to their home in TwitterMsg.vue.
I will say that this.yourTweet in TweetDeck.vue DOES manage to make it all the way to its destination, which is good -- though it makes me wonder why the other two pieces of data didn't follow suite.
Totally lost. Also just in my first month of VueJS so it's pretty good that I can even make one string appear where I want it to. \o/
First, you need to remove the $event parameter
<TweetDeck v-on:messageFromTweetDeck="msgReceived"/>
Second, you can optimize the data format passed to the parent component:
sendTweet() {
this.$emit("messageFromTweetDeck",
{ tweet: this.yourTweet, name: this.yourName, handle: this.yourHandle }
);
}
And then modify your msgReceived method:
msgReceived(childData) {
this.tweets.push(childData);
}
Link: codesandbox
Hope to help you:)

Svelte: Replace nested component by changing binded variable

I am writing Svelte project, where I have Message component which represents some js object.
There is ability to edit this object. For this purpose I desided to use two nested component MessageEditable and MessageReadable.
They should replace each other, depending on Message component state.
The problem is that when I am trying to save result of editing and
change MessageEditable to MessageReadable by setting isEditing property to false I get error:
image of error from console
Did I make a mistake or this is normal behavior? Is binding a good approach or there is more optimal for exchanging of values with parent components?
Message:
<div class="message">
{#if isEditing}
<MessageEditable bind:message bind:isEditing />
{:else}
<MessageReadable {message}/>
{/if}
<div class="message__controllers">
<button on:click="set({isEditing: true})">Edit</button>
</div>
</div>
<script>
import MessageEditable from './MessageEditable.html';
import MessageReadable from './MessageReadable.html';
export default {
components:{
MessageEditable,
MessageReadable,
},
data:() => ({
message:{
id: '0',
text: 'Some message text.'
},
isEditing: false,
}),
}
</script>
MessageEditable:
<form class="message-editable" on:submit>
<label><span >text</span><input type="text" bind:value=message.text required></label>
<label><span>id</span><input type="text" bind:value=message.id required></label>
<div><button type="submit">Save</button></div>
</form>
<script>
export default {
events:{
submit(node){
node.addEventListener('submit', (event) => {
event.preventDefault();
this.set({isEditing: false});
});
},
},
};
</script>
MessageReadable:
<div class="message-readable">
<p><span>text: </span>{message.text}</p>
<p><span>id: </span>{message.id}</p>
</div>
Its probably better to use a method than a custom event handler since you are performing actions on submit. I tested this code in the REPL and didn't experience the error you are getting. I changed your events to a methods property and removed the node functionalities.
<form class="message-editable" on:submit="save(event)">
<label><span >text</span><input type="text" bind:value=message.text required></label>
<label><span>id</span><input type="text" bind:value=message.id required></label>
<div><button type="submit">Save</button></div>
</form>
<script>
export default {
methods: {
save(event){
event.preventDefault();
this.set({isEditing: false});
},
},
};
</script>
https://svelte.technology/repl?version=2.10.0&gist=d4c5f8e3864856d27a3aa8cb5b2e8710

Input-fields as components with updating data on parent

I'm trying to make a set of components for repetitive use. The components I'm looking to create are various form fields like text, checkbox and so on.
I have all the data in data on my parent vue object, and want that to be the one truth also after the user changes values in those fields.
I know how to use props to pass the data to the component, and emits to pass them back up again. However I want to avoid having to write a new "method" in my parent object for every component I add.
<div class="vue-parent">
<vuefield-checkbox :vmodel="someObject.active" label="Some object active" #value-changed="valueChanged"></vuefield-checkbox>
</div>
My component is something like:
Vue.component('vuefield-checkbox',{
props: ['vmodel', 'label'],
data(){
return {
value: this.vmodel
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="value" #change="$emit('value-changed', value)">
{{label}}
</label>
</div>
</div>`
});
I have this Vue object:
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
},
methods: {
valueChange: function (newVal) {
this.carouselAd.autoOrder = newVal;
}
}
});
See this jsfiddle to see example: JsFiddle
The jsfiddle is a working example using a hard-coded method to set one specific value. I'd like to eighter write everything inline where i use the component, or write a generic method to update the parents data. Is this possible?
Minde
You can use v-model on your component.
When using v-model on a component, it will bind to the property value and it will update on input event.
HTML
<div class="vue-parent">
<vuefield-checkbox v-model="someObject.active" label="Some object active"></vuefield-checkbox>
<p>Parents someObject.active: {{someObject.active}}</p>
</div>
Javascript
Vue.component('vuefield-checkbox',{
props: ['value', 'label'],
data(){
return {
innerValue: this.value
}
},
template:`<div class="form-field form-field-checkbox">
<div class="form-group">
<label>
<input type="checkbox" v-model="innerValue" #change="$emit('input', innerValue)">
{{label}}
</label>
</div>
</div>`
});
var vueObject= new Vue({
el: '.vue-parent',
data:{
someNumber:0,
someBoolean:false,
anotherBoolean: true,
someObject:{
name:'My object',
active:false
},
imageAd: {
}
}
});
Example fiddle: https://jsfiddle.net/hqb6ufwr/2/
As an addition to Gudradain answer - v-model field and event can be customized:
From here: https://v2.vuejs.org/v2/guide/components.html#Customizing-Component-v-model
By default, v-model on a component uses value as the prop and input as
the event, but some input types such as checkboxes and radio buttons
may want to use the value prop for a different purpose. Using the
model option can avoid the conflict in such cases:
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean,
// this allows using the `value` prop for a different purpose
value: String
},
// ...
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>
The above will be equivalent to:
<my-checkbox
:checked="foo"
#change="val => { foo = val }"
value="some value">
</my-checkbox>

Accessing and filtering child components defined in a Vue.js component's <slot>

I'm in the process of implementing a combined select dropdown and searchbox component for a UI library I'm writing, using Vue.js to drive interactions, but I'm running into a problem with how Vue.js seems to handle communication between parent and child components.
Basically, what I'm aiming to achieve is to allow users of the library to define search-select elements in HTML using something like this:
<search-select>
<search-option value="foo">Foo</search-option>
<search-option value="bar">Bar</search-option>
<search-option value="baz">Baz</search-option>
<search-option value="foobar">Foobar</search-option>
</search-select>
My template for the search-select component looks like this:
<template>
<div class="search-select">
<div class="placeholder" ref="placeholder"></div>
<ul class="dropdown">
<input
type="text"
placeholder="Type to filter..."
v-on:input="updateFilter"
>
<slot></slot>
</ul>
</div>
</template>
The idea is that the search-select component will add a text input box above the specified inputs, which the user can type in to filter the options that can be selected. The finished component would look something like this.
However, from what I can tell in the documentation, Vue.js doesn't provide any direct way for me to access the properties of the child components from within the parent, nor any way to listen for click events or similar from children within a parent's <slot>.
This means that I don't have a way to filter visibility of the children based on the user's input, or to update the value of the search-select component when a user clicks on one of the children.
The Vue.js documentation mentions ways to pass events from child components to parents, but none seem applicable to elements defined within slots - it appears that parents can only listen for events from components explicitly defined within them.
How would one implement the type of two-way communication between parent and child components required for this use case, without violating any Vue.js best practices related to sharing information between components?
You can use event bus to solve this problem. Just keep every option listening to input event, then letting it decide whether hide itself or not depending on the argument passed.
See fiddle or demo below.
const bus = new Vue();
Vue.component('search-option', {
template: `
<option v-if="show" :value="this.$attrs.value"><slot></slot></option>
`,
created() {
bus.$on('filter', (input) => {
this.show = this.$attrs.value.includes(input);
});
},
beforeDestory() {
bus.$off('filter');
},
data() {
return {
show: true,
};
},
});
Vue.component('search-select', {
template: `
<div class="search-select">
<div class="placeholder" ref="placeholder"></div>
<ul class="dropdown">
<input
type="text"
placeholder="Type to filter..."
v-on:input="updateFilter"
v-model="myinput"
>
<slot></slot>
</ul>
</div>
`,
methods: {
updateFilter() {
console.log('update filter');
bus.$emit('filter', this.myinput);
},
},
data() {
return {
myinput: undefined,
};
},
});
Vue.component('parent', {
template: `
<div class="parent">
<search-select>
<search-option value="foo">Foo</search-option>
<search-option value="bar">Bar</search-option>
<search-option value="baz">Baz</search-option>
<search-option value="foobar">Foobar</search-option>
</search-select>
</div>
`,
});
new Vue({
el: '#app',
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<div id="app">
<parent></parent>
</div>

Vue.js - Can't properly validate a custom single file component with v-for using vee-validate

I've been building a vue app and I'm currently building a component that has a "Person" object, and a few inputs fields to add information about that person, such as Name, address, email, etc. One of those fields is the mobile number, but a person can have more than one number, so I created a custom component that repeats the input field at will. This is roughly what it looks like. All those inputs are being validated using vee validate. This is roughly what is looks like:
<!--createPerson.vue -->
<div class="form-group">
<input v-model="person.firstName" v-validate="'required'" data-vv-delay="500" name="first-name" type="text" placeholder="First name">
<span v-show="errors.has('first-name')" class="input__error">{{ errors.first('first-name') }}</span>
</div>
<div class="form-group">
<input v-model="person.lastName" v-validate="'required'" data-vv-delay="500" name="last-name" type="text" placeholder="Last name">
<span v-show="errors.has('last-name')" class="input__error">{{ errors.first('last-name') }}</span>
</div>
<repeatable-inputs v-model="person.mobiles" v-validate="'required'" data-vv-value-path="input" data-vv-name="mobile" type="tel" name="mobile" placeholder="Mobile" :inputmode="numeric" link-name="mobile number"></repeatable-inputs>
<div class="form-group">
<input v-model="person.email" v-validate="'required|email'" data-vv-delay="500" name='email' type="text" placeholder="Personal email">
<span v-show="errors.has('email')" class="input__error">{{ errors.first('email') }}</span>
</div>
The person object, under the same createPerson.vue file, has a property called mobiles which starts as an empty array. It also has a validateAll() function, used as described in the vee-validate docs, that performs a validation of all inputs when clicking a "Next" button.
Then, on the repeatableInputs.vue, there's this:
<template>
<div>
<div class="form-group" v-for="(input, index) in inputs">
<input
:type="type"
:name="name+index"
:placeholder="placeholder"
:inputmode="inputmode"
:value="input"
#input="update(index, $event)"
v-focus></input>
</div>
Add another {{linkName}}
</div>
</template>
<script>
export default {
props: {
value: {
type: Array,
default: ['']
},
type: {
type: String,
default: "text"
},
name: String,
placeholder: String,
inputmode: String,
linkName: {
type: String,
default: "input"
}
},
data: function () {
return {
inputs: this.value
}
},
methods: {
add: function () {
this.inputs.push('');
},
update: function(index, e) {
this.inputs[index] = e.target.value;
this.$emit('input', this.inputs);
}
}
}
</script>
To be able to validate these custom inputs in the parent createPerson.vue, using the validateAll() function, the docs state that I must, quoting, Should have a data-vv-value-path attribute which denotes how to access the value from within that component (Needed for validateAll calls).
And this is where I got stuck. I'm not sure that that vv-path must point to, and I tried using 'value', ' input', 'inputs', creating a watch function, and a few other things that didn't even make much sense. I've found a git issue with validating custom inputs that used v-for, but that has apparently since been fixed.
Data-vv-value-path should point to where the data is inside the component state. On your input you can bind the input content to data with a v-model -attribute and then the validator knows which property of the child component it needs to validate.
So data-vv-value-path points to the data inside the child component and the data will be automatically updated when bound with v-model.

Categories

Resources