How to transfer data from an input field to another js file - vue.js

I'm new to vue and I have a simple component that takes in text.
<template>
<div class="row">
<input type="text" id="chatInput" v-model="chatValue">
<br/><br/><br/><br/>
</div>
</template>
<script>
export default{
name: 'chat-room',
data: function () {
return {
chatValue: ''
}
}
}
So far, the chatValue variable binds to the input tag. I would like to get the value of the chatValue into another js file so I can manipulate the value.Also, I was looking at the vue documentation and it seemed state management seemed to do the trick but I had a hard time understanding the concept. Can I implement state management to execute my desire? If not, how may I?

Data that is shared between components should be owned by a module that provides an interface to the data item. The most common way this is done is that a parent component owns the data and children of that parent accept the data item as a prop, emitting events when the data item should change.
That is described in the documentation here.
If the components are less closely related, the data can be made more akin to a global variable by the use of an event bus, described here.

Related

Confusion of Properties, Data, and Computed Values when Working with Components (especially nested)

I am really confused how to pass down properties from a parent and update them in children then emit those changes all the way back up to the parent. By reference it looks to work but from everything I read that is not the correct way to do it.
I have a parent component which "toggles" a create form. A new instance of the artifact is created and passed into a "create" component which is used in other places. The create component has some common properties but, through a slot, custom properties can be added at the parent level.
Within the ArtifactCreate component, it passes a "clone" of that prop to both the form and the custom properties.
I am trying to understand how to take the "prop" value, work with it internally, and then bubble up the final result to the ArtifactCreate component which in-turn bubbles that up to the parent.
The "child" components (ArtifactCreateForm and DatasetProperties) do not have any additional methods and are directly updating the prop reference passed in. DatasetProperties can be used in other places as well.
In most cases, I have a "parent" that I want to handle the main interaction but need to pass some model down to one or more components, which in turn may pass that in its children.
The structure looks close to this:
Parent Component (somewhat like the controller handling main actions)
|
|--- View Component (takes in 'artifact' prop and passes it down)
|
|--- Child Component
|
|--- GrandChild 1..x
|-----------------|--- GrandChild (through slot)
I am really confused on on the relationship between properties and data and/or computed values and how to correctly work with that data in the grandchildren. I am fine with the concept of pass data down and emit events up and I seem to grasp how to do that with primitive properties but how can I do this with an object that has many properties?
My questions are:
What is the correct way to pass props and handle them inside the components (as a copy), then bubble that back up to the parent?
When bubbling the event from lower components is the only way to repeat it until it reaches the parent?
I am using "objects" as the prop/data not individual values. The reference gets updated (which is a copy) then bubbled to the parent (as I want). Is this the right way?
(FYI, coming from Java to Vue so this is a whole new world to me).
Parent Component
This is my entry into the create component. It manages the instance (in this case a new one) and passes it down to the ArtifactCreate component which internally clones it. The updated copy is bubbled back to here where it is saved.
<template>
<div>
<artifact-header/>
<v-container fluid>
<!-- List -->
<reference-list
:paths="paths"
#open="load"/>
<!-- Create Artifact -->
<artifact-create
:advanced="true"
:artifact="newArtifact"
:title="newTitle"
#save="save">
<template #default="{ model }">
<dataset-properties :artifact="model"/>
</template>
</artifact-create>
</v-container>
</div>
</template>
Supported by:
computed: {
newArtifact: {
get(): Dataset {
return newDataset();
}
}
},
methods: {
save(item: Dataset){
this.$store.dispatch("saveDataset", item);
},
ArtifactCreate Component (child / container component)
<app-dialog
:title="title"
:visible="visible"
#action="save"
#close="close">
<v-tabs v-if="advanced"
v-model="tab"
grow>
<v-tab>Basic</v-tab>
<v-tab>Advanced</v-tab>
<!-- Basic Properties -->
<v-tab-item :key="'Basic'">
<artifact-create-form :artifact="internal"/>
</v-tab-item>
<!-- Advanced Properties -->
<v-tab-item :key="'Advanced'">
<slot :model="internal"/>
</v-tab-item>
</v-tabs>
<!-- Basic Properties ONLY -->
<artifact-create-form v-else
:artifact="internal"/>
</app-dialog>
And it is supported with:
export default Vue.extend({
name: "ArtifactCreate",
props:{
artifact: {
type: Object as Prop<IArtifact>,
default: {}
},
},
computed:{
internal: {
get(): IArtifact {
return clone(this.artifact);
},
}
},
methods: {
save(item) {
this.$emit('save', this.internal);
this.visible = false;
},
},
});
ArtifactCreateForm Component (child in ArtifactCreate)
<template>
<v-form>
<v-text-field
v-model="artifact.name"
label="Name*"
required>
</v-text-field>
</v-form>
</template>
Supported by:
export default Vue.extend({
name: "ArtifactCreateForm",
props:{
artifact: {
type: Object as Prop<IArtifact>,
default: {}
},
},
});
DatasetProperties Component (child in ArtifactCreate / registered in parent through slot)
<template>
<v-form>
<v-text-field
v-model="artifact.source"
label="Source">
</v-text-field>
<v-text-field
v-model="artifact.primaryKey"
label="Primary Key">
</v-text-field>
</v-form>
</template>
Supported by:
export default Vue.extend({
name: 'DatasetProperties',
props:{
artifact: {
type: Object as Prop<Dataset>,
default: {}
}
},
})
I have Vuex in place and "could" use that but it seems like overkill for creating a new object? But similar to how to work with data still trying to get where/when it is appropriate.
Complex model and form handling is not straight forward and there are very few good examples how to handle complex forms with custom component v-model implementations especially if you want v-model on objects. I'll attempt to give my recommendations below.
What is the correct way to pass props and handle them inside the components (as a copy), then bubble that back up to the parent?
It depends, if like in your example at the parent level you want to pass an initial object value through a prop and happy to wait until a final event occurs e.g. :artifact="newArtifact" #save="save", then that is the way to go. In other words, to me your Parent Component looks good.
However once you start working on child and lower components where you start working with the actual model properties via v-model it gets a bit more complex. See below.
When bubbling the event from lower components is the only way to repeat it until it reaches the parent?
As you pointed out in the original question, the "Vue way" way is -> data down and events up. In fact that is even how v-model work, remember v-model="model" is basically short for :value="model" #input="model = $event". But this gets a little messy when the model is an object and as mentioned above, there are few good examples on how to handle custom component v-model with complex objects as models.
See 3 below.
I am using "objects" as the prop/data not individual values. The reference gets updated (which is a copy) then bubbled to the parent (as I want). Is this the right way?
Using data updates by reference is not considered best practice for the reasons pointed out in #bviala answer. It also breaks the data down events up approach. Ideally, your child components ArtifactCreateForm and DatasetProperties should implement v-model functionality. As mentioned, implementing v-model in your custom component especially on objects is not as clean as you would hope. But you can implement helper functions to make it a bit easier.
For example, implementing v-model on your ArtifactCreateForm component, it would look like this:
<template>
<v-form>
<v-text-field
:value="value.name"
#input="update(m => (m.name = $event))
label="Name*"
required>
</v-text-field>
<v-text-field
:value="value.description"
#input="update(m => (m.description = $event))
label="Description"
required>
</v-text-field>
</v-form>
</template>
export default Vue.extend({
name: 'ArtifactCreateForm',
props:{
value: {
type: Object as Prop<IArtifact>
}
},
methods: {
update(cb: (m: IArtifact) => void) {
const model = clone(this.value);
cb(model);
this.$emit("input", model);
}
}
})
And you would use this as:
<app-dialog
:title="title"
:visible="visible"
#action="save"
#close="close">
<artifact-create-form v-model="internal"/>
</app-dialog>
For reference the following blog post helped me a lot on how to implement v-model with objects on custom components: https://simonkollross.de/posts/vuejs-using-v-model-with-objects-for-custom-components
What is the correct way to pass props and handle them inside the components (as a copy), then bubble that back up to the parent?
As a copy, like you did in ArtifactCreate : Clone the prop, mutate it however you want, send it to the parent with an event if needed.
But if your child component doesn't need to have its own internal value, you shouldn't clone the prop, but rather just emit events and let the parent handle the mutation. It would look like this in the child:
<v-text-field
:value="artifact.source"
label="Source"
#input="$emit('updateSource', $event)"
>
What's weird in your example is that your child component clones data that the parent just created. Couldn't the child be the one in charge of calling newDataset()?
When bubbling the event from lower components is the only way to repeat it until it reaches the parent?
Yes, the only proper way (see next answer)
I am using "objects" as the prop/data not individual values. The reference gets updated (which is a copy) then bubbled to the parent (as I want). Is this the right way?
What you did in ArtifactCreateForm DatasetProperties works but is considered an anti-pattern. The reason being that the data lives in the parent component and is mutated by the child, with the source of the mutation being unknown by the parent. It can lead to maintenance issues if your component hierarchy becomes complex. The correct way is to send events.
That's why in my opinion, you should split your components mindfully: Do you really have reusability potential for ArtifactCreateForm and DatasetProperties ? If not, you can make your life easier by sticking to a single ArtifactCreate component.

Combining v-for with v-show on same element in template

I want to display a list of entries, and I have it working up through retrieving JSON from a server, parsing it, storing it in a Vuex.Store and iterating through it with v-for-"entry in this.$store.state.entries".
When a user first visits the page all entries will be visible. The next step is to filter the entries so that only matching entries remain visible. Since this filtering will be changing a lot, I want to use v-show. I have a separate component that lets users enter search terms, the server is queried, and an array of numbers—matching IDs—is returned. I want to only show entries with IDs that match the numbers in the array, queriedEntries. My template is below:
<template>
<div id="entries">
<div v-for="entry in this.$store.state.entries"
v-html="entry.content"
v-show="this.$store.state.queriedEntries.includes(entry.id)">
</div>
</div>
</template>
I get an error that I don't understand, and searching for answers hasn't yielded anything because it doesn't match the problem others have had.
[Vue warn]: Error in render: "TypeError: this is undefined"
It's the this in the v-show, but every other this works. What's up?
Your problem is occurring because you are referencing this inside your template. This is not necessary.
The first thing I recommend you do is have a read into Vuex' Getters. Further down on the same page, you'll find information about mapGetters. This will help to prevent you from directly targeting/modifying data within your state. Modification of data should be left only to Mutations and Actions.
For example, your code may look like the below:
// in your component script
...
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
allEntries: 'entries', // map state.entries to the name 'allEntries'
queriedEntries, // your other state value. You may want to convert this to a getter
// other state values if necessary
})
}
}
...
// in your component template
<template>
<div id="entries">
<div v-for="entry in allEntries"
v-html="entry.content"
v-show="queriedEntries.includes(entry.id)">
</div>
</div>
</template>
...
Here you can see that we have used mapState which helpfully generates computed getter functions from our data in the store. We can then use the property name we have assigned it to within our template.
I ended up removing this from everything but the v-for, as suggested, and the code worked. Why this causes an error in v-show and v-html is still a mystery.
Final, working code:
<div v-for="(entry, entryindex) in this.$store.state.entries"
v-bind="{id:entryindex}"
v-bind:key="entryindex"
v-show="$store.state.queryMatchedEntries[0] == -1 || $store.state.queryMatchedEntries.indexOf(parseInt(entryindex)) != -1">

Vue two way prop binding

Below is my current structure (which doesn't work).
Parent component:
<template>
<field-input ref="title" :field.sync="title" />
</template>
<script>
import Field from './input/Field'
export default {
components: {
'field-input': Field
},
data() {
return {
title: {
value: '',
warn: false
}
}
}
}
</script>
Child component:
<template>
<div>
<input type="text" v-model="field.value">
<p v-bind:class="{ 'is-invisible' : !field.warn }">Some text</p>
</div>
</template>
<script>
export default {
props: ['field']
}
</script>
The requirements are:
If parent's data title.warn value changes in parent, the child's class bind should be updated (field.warn).
If the child's <input> is updated (field.value), then the parent's title.value should be updated.
What's the cleanest working solution to achieve this?
Don't bind the child component's <input> to the parent's title.value (like <input type="text" v-model="field.value">). This is a known bad practice, capable of making your app's data flow much harder to understand.
The requirements are:
If parent's data title.warn value changes in parent, the child's class bind should be updated (field.warn).
This is simple, just create a warn prop and pass it from parent to child.
Parent (passing the prop to the child):
<field-input ref="title" :warn="title.warn" />
Child/template (using the prop -- reading, only):
<p v-bind:class="{ 'is-invisible' : !warn }">Some text</p>
Child/JavaScript (declaring the prop and its expected type):
export default {
props: {warn: Boolean}
}
Notice that in the template it is !warn, not !title.warn. Also, you should declare warn as a Boolean prop because if you don't the parent may use a string (e.g. <field-input warn="false" />) which would yield unexpected results (!"false" is actually false, not true).
If the child's <input> is updated (field.value), then the parent's title.value should be updated.
You have a couple of possible options here (like using .sync in a prop), but I'd argue the cleanest solution in this case is to create a value prop and use v-model on the parent.
Parent (binding the prop using v-model):
<field-input ref="title" v-model="title.value" />
Child/template (using the prop as initial value and emitting input events when it changes):
<input type="text" :value="value" #input="$emit('input', $event.target.value)">
Child/JavaScript (declaring the prop and its expected type):
export default {
props: {value: String}
}
Click here for a working DEMO of those two solutions together.
There are several ways of doing it, and some are mentioned in other answers:
Use props on components
Use v-model attribute
Use the sync modifier (for Vue 2.0)
Use v-model arguments (for Vue 3.0)
Use Pinia
Here are some details to the methods that are available:
1.) Use props on components
Props should ideally only be used to pass data down into a component and events should pass data back up. This is the way the system was intended. (Use either v-model or sync modifier as "shorthands")
Props and events are easy to use and are the ideal way to solve most common problems.
Using props for two-way binding is not usually advised but possible, by passing an object or array you can change a property of that object and it will be observed in both child and parent without Vue printing a warning in the console.
Because of how Vue observes changes all properties need to be available on an object or they will not be reactive.
If any properties are added after Vue has finished making them observable 'set' will have to be used.
//Normal usage
Vue.set(aVariable, 'aNewProp', 42);
//This is how to use it in Nuxt
this.$set(this.historyEntry, 'date', new Date());
The object will be reactive for both component and the parent:
I you pass an object/array as a prop, it's two-way syncing automatically - change data in the
child, it is changed in the parent.
If you pass simple values (strings, numbers)
via props, you have to explicitly use the .sync modifier
As quoted from --> https://stackoverflow.com/a/35723888/1087372
2.) Use v-model attribute
The v-model attribute is syntactic sugar that enables easy two-way binding between parent and child. It does the same thing as the sync modifier does only it uses a specific prop and a specific event for the binding
This:
<input v-model="searchText">
is the same as this:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
Where the prop must be value and the event must be input
3.) Use the sync modifier (for Vue 2.0)
The sync modifier is also syntactic sugar and does the same as v-model, just that the prop and event names are set by whatever is being used.
In the parent it can be used as follows:
<text-document v-bind:title.sync="doc.title"></text-document>
From the child an event can be emitted to notify the parent of any changes:
this.$emit('update:title', newTitle)
4.) Use v-model arguments (for Vue 3.0)
In Vue 3.x the sync modifier was removed.
Instead you can use v-model arguments which solve the same problem
<ChildComponent v-model:title="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :title="pageTitle" #update:title="pageTitle = $event" />
5.) Use Pinia (or Vuex)
As of now Pinia is the official recommended state manager/data store
Pinia is a store library for Vue, it allows you to share a state across components/pages.
By using the Pinia store it is easier to see the flow of data mutations and they are explicitly defined. By using the vue developer tools it is easy to debug and rollback changes that were made.
This approach needs a bit more boilerplate, but if used throughout a project it becomes a much cleaner way to define how changes are made and from where.
Take a look at their getting started section
**In case of legacy projects** :
If your project already uses Vuex, you can keep on using it.
Vuex 3 and 4 will still be maintained. However, it's unlikely to add new functionalities to it. Vuex and Pinia can be installed in the same project. If you're migrating existing Vuex app to Pinia, it might be a suitable option. However, if you're planning to start a new project, we highly recommend using Pinia instead.

Update template with model changed from input vueJS

I'm developing my first app in vueJs and laravel.
now I 'have a problem with v-model.
I have a page with component Person that edit or create new Person.
So I get from my backend in laravel or Model Person or new Person.
Now in my frontend I pass data to component by props:
Page.blade.php
<Person :person-data="{!! jsonToProp($person) !!}"></Person>
(jsonToProp transform model coming from backend in json)
In this case, I would return new Model so without properties, so $person will be a empty object.
Person.vue
<template>
<div>
<label for="name_p"> Name</label>
<input id="name_p" v-model="person.name" class="form-control" />
<button v-on:click="test()">test</button>
{{person.name}}
</div>
</template>
<script>
export default {
props: ['personData'],
mounted() {
},
data() {
return {
person: this.personData
}
},
methods:{
test(){
console.log(this.person.name);
}
}
}
</script>
Now if I change input with model v-model="person.name" I would print name in template but it doesn't change.
But if I click buttonit console write right value.
So I read that changing model value is asynch, so How I can render new Model when change input?
You should declare all the properties up front, as per the documentation:
Why isn’t the DOM updating?
Most of the time, when you change a Vue instance’s data, the view updates. But there are two edge cases:
When you are adding a new property that wasn’t present when the data was observed. Due to the limitation of ES5 and to ensure consistent behavior across browsers, Vue.js cannot detect property addition/deletions. The best practice is to always declare properties that need to be reactive upfront. In cases where you absolutely need to add or delete properties at runtime, use the global Vue.set or Vue.delete methods.
When you modify an Array by directly setting an index (e.g. arr[0] = val) or modifying its length property. Similarly, Vue.js cannot pickup these changes. Always modify arrays by using an Array instance method, or replacing it entirely. Vue provides a convenience method arr.$set(index, value) which is just syntax sugar for arr.splice(index, 1, value).
That may be because your data, which comes from jsonToProp($person) does not reactive.
You see, vue modify each object to make it 'reactive', some times you need to modify it by your own. detection caveats
Try to do this.person = Object.assign({}, this.person, this.personData) in your mounted hook, to make it reactive.

Vue JS rc-1 Passing Data Through Props Not Working

In the release notes for Vue 1.0.0-rc.1, we are told
"The inherit option has been deprecated. Alway pass data to child
components via props."
However, the Component API section says
"$data can no longer be used as a prop."
I have been trying to pass data to child components of my root Vue instance, and have had no luck whatsoever.
In version 0.12.*, if you want/need access to a parent instance's data, methods, etc., you would simply add...
inherit: true
...to a child component.
Now, in attempting to access the parent data via props, I continue to hit a brick wall. Here is a simplified example:
app.js:
new Vue({
el: '#app',
data: {
authorized: false,
currentView: 'welcome-view'
},
components: {
'welcome-view': require('./views/welcome')
}
});
views/welcome.js:
module.exports = {
props: ['authorized'],
template: require('./welcome.template.html')
};
views/welcome.template.html:
<div v-if="authorized"><p>You Are Logged In</p></div>
<div v-else>Please Log In</div>
Main View File (app.blade.php)
...
<body id="app">
<component :is="currentView"></component>
</body>
...
The 'authorized' prop is not recognized at all this way. It works outside of the component (within the "app" id) just fine, but not within the template.
At the moment, I can access the data I need by using $root everywhere I need it. For instance:
<div v-if="$root.authorized"><p>You Are Logged In</p></div>
But, my understanding is that this is 'bad form' all around, as the docs say:
Although it’s possible to access any instance the parent chain, you
should avoid directly relying on parent data in a child component and
prefer passing data down explicitly using props.
So, what I need to know is... how can I explicitly use props? I am clearly going about it the wrong way, since they are not available to my child components if I just list them in the 'props: []' array. What am I missing here?
At the end of the day, what is the best way (standards and practices) to refactor my current code to replace 'inherit: true', and still have access to the root instance data and functions? Any help/advice on this would be most welcome. Thanks in advance.
See #StephenHallgren's answer on this page for the correct way to access props in the HTML.
As for the rest of it, (how to properly refactor code to replace 'inherit:true', I am including here the answer I received from Evan Y. on the official Vue forum, in case anyone else runs across this in the future.
His answer to the question posed above was:
If you are fairly certain about the structure, you can use
$root.authorized.
Alternatively, don't put the authorized state in the root at all. Have
a dedicated module for user state that can be imported in any
component. See
http://rc.vuejs.org/guide/application.html#State_Management
My take-away from this is that - where there are concrete, global variables that will not change, and the app structure is sound, it is okay to use $root (or $parent as the case may be), and - where elements have state that will sometimes change (such as whether or not a user is authorized/logged in), the key is to use a state management module.
Meanwhile, when passing down props between parent and child, one must declare the props in the props array, then bind them to the component in the HTML.
For example...
app.js:
new Vue({
el: '#app',
data: {
authorized: false,
currentView: 'welcome-view'
},
components: {
'welcome-view': require('./views/welcome')
}
});
views/welcome.js:
module.exports = {
props: ['authorized'],
template: require('./welcome.template.html')
}
welcome.template.html:
<div v-if="authorized"><p>You Are Logged In</p></div>
<div v-else>Please Log In</div>
main HTML
<body id="app">
<component :is="currentView" v-bind:authorized="authorized"></component>
</body>
(or shorthand)
<body id="app">
<component :is="currentView" :authorized="authorized"></component>
</body>
I was having the same issue and didn't realize that you also have to bind the value to the component prop like this:
v-bind:authorized="authorized"
or the shorthand
:authorized="authorized"
Here's an example of something that I had been working on that illustrates the solution: http://jsfiddle.net/yyx990803/2uqmj2jj/6/