Not able to update the data with #change in Vue js 2 - vue.js

I am not able to populate the updated data to the child component when doing on-change from a select box with local data/object. But I am able to load data to the child component everytime when I click the submit button with the help of API. I need help to refresh my child component with my updated data when I do the on-change from the select box. Find the below code which I'm trying. That too when I do on-change first time it was updating the props in child component, but when I do the same it is not going to the child component, it stops i the parent component itself.
Component-1:
<template>
<div>
<div class="row">
<select v-model="selectedemp" #change="filterempdata($event.target.value)">
<option value="">Select emp/option>
<option v-for="option in empfilterlist" v-bind:value="option.value" v-bind:key="option.value">{{ option.text }}</option>
</select>
</div>
<div class="compone">
<empView v-if='loaded' :empDetails='empData'></empView>
</div>
<div class="col-lg-6 col-md-6">
<button type="button" id="btn2" class="btn btn-danger btn-md" v-on:click="getEmpDetails">Fetch Data</button>
</div>
</div>
</template>
<script>
import updatedemp from './empView'
export default {
name: 'cluster',
components: {
'empView': updatedemp
},
data () {
return {
loaded: false,
emptData: {},
empfilterlist: [
{ text: 'Department', value: '1' },
{ text: 'Status', value: '2' },
],
selectedemp: '',
}
},
methods: {
filterempdata: function (selectedoption) {
console.log('Onchange value - ' + selectedOption)
Vue.set(this.empData, 'Department', selectedoption)
},
getEmpDetails: function () {
this.$http.get('http://localhost:7070/getemmdata')
.then((response) => {
this.empData = response.data
this.loaded = true
},
response => {
console.log('test' + JSON.stringify(response))
}
)
}
}
}
</script>
Component: 2
<template>
<div class="empView">
<div class="col-lg-6 col-md-6">
<h3>{{ empid }}</h3>
</div>
<div class="col-lg-6 col-md-6">
{{ empname }}
</div>
</div>
</template>
<script>
export default {
name: 'empView',
props: ['empDetails'],
data () {
return {
empid: this.empDetails.id,
empname: this.empDetails.name
}
},
watch: {
workflowDetails: function (changes) {
console.log('data updated ' + JSON.stringify(changes))
this.empid = changes.id
this.empname = changes.name
this.department = changes.Department
}
}
}
</script>

Your first problem is here:
filterempdata: function (selectedoption) {
console.log(')
this.empData.WorkflowFilter = selectedoption
this.empData = this.empData
}
By default, empData is:
data () {
return {
loading: false,
emptData: null,
So this.empData.WorkflowFilter = selectedoption should not work, as well as this.empData = this.empData does nothing.
Just make the default value an empty object and update it according to selection (just setting WorkflowFilter).
This should do the trick. Another weird this is the loading property. Your second component will be visible only if loading = true, which is odd. Maybe use loaded instead?

Related

How to pass data on the root components in Vue

How can I pass the page_id to the Sidebar component method highlightNode(), because I want to highlight a newly added item. My current code is the page id is undefined.
This is my code & structure.
my root component is Sidebar.vue
<template>
<div>
<ul>
<li v-for="page in pages">
<div :class="{ 'highlight': highlightedNode == page.id }">
<router-link :to="'/view/' + page.id" #click.native="highlightNode(page.id)">
<span v-title="page.title"></span>
</router-link>
</div>
</li>
</ul>
</div>
</template>
export default {
data () {
return {
pages: [],
highlightedNode: null
}
},
mounted() {
this.getPages()
this.$root.$refs.Sidebar = this
},
methods: {
getPages() {
axios.get('/get-pages').then(response => {
this.pages = response.data
});
},
highlightNode(id) {
this.highlightedNode = id
},
}
}
my add new Page component AddNewPage.vue
<template>
<div>
<div class="main-header">
<div class="page-title">
<input type="text" v-model="page.title" class="form-control">
</div>
</div>
<div class="main-footer text-right">
<button class="btn btn-success btn-sm" #click="saveChanges()">Save and Publish</button>
</div>
</div>
</template>
export default {
data () {
return {
page: {
title: null,
},
}
},
mounted() {
//
},
methods: {
saveChanges() {
axios.post('/store-new-filter', this.page)
.then(response => {
const id = response.data.id // return page id
this.$root.$refs.Sidebar.highlightNode(id) // <-- this line, I want to pass page id to hightlight the newly added page.
})
.catch( error => {
})
},
}
}
Or any alternative way to achieve my expected output.
Thanks in advance.

Vue Component Select option from list item

I'm new to Vue. I have an autocomplete component that used AXIOS to get the data. When I type in at least 3 characters, it does display the results in a list item, however I can't seem to figure out how to actually select the option and populate the text field.
Here is the code
<template>
<div>
<input type="text" placeholder="Source client" v-model="query" v-on:keyup="autoComplete" #keydown.esc="clearText" class="form-control">
<div class="panel-footer" v-if="results.length">
<ul class="list-group">
<li class="list-group-item" v-for="result in results">
<span> {{ result.name + "-" + result.oid }} </span>
</li>
</ul>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default{
data(){
return {
selected: '',
query: '',
results: []
}
},
methods: {
clearText(){
this.query = ''
},
autoComplete(){
this.results = [];
if(this.query.length > 2){
axios.get('/getclientdata',{params: {query: this.query}}).then(response => {
this.results = response.data;
});
}
}
}
}
</script>
Any help would be appreciated!
Writing an autocomplete is a nice challenge.
You want this component to act like an input. That means that if you'd use your autocomplete component, you will probably want to use it in combination with v-model.
<my-autocomplete v-model="selectedModel"></my-autocomplete>
To let your component work in harmony with v-model you should do the following:
Your component should accept a prop named value, this may be a string, but this may also be an object.
In the example above the value of selectedModel will be available inside your autocomplete component under '{{value}}' (or this.value).
To update the value supplied you $emit an input event with the selected value as second parameter. (this.$emit('input', clickedItem))
Inside your autocomplete you have query, which holds the search term. This is a local variable and it should be. Don't link it to value directly because you only want to change the value when a user selects a valid result.
So to make the example work you could do the following:
<template component="my-autocomplete">
<div>
<input type="text" placeholder="Source client" v-model="query" v-on:keyup="autoComplete" #keydown.esc="clearText" class="form-control">
<div class="panel-footer" v-if="results.length">
<ul class="list-group">
<li class="list-group-item" v-for="result in results" #click="selectValue(result)">
<span> {{ result.name + "-" + result.oid }} </span>
</li>
</ul>
</div>
</div>
</template>
<script>
//import axios from 'axios'
export default{
props: ['value'],
data(){
return {
selected: '',
query: '',
results: []
}
},
methods: {
clearText(){
this.query = ''
},
autoComplete(){
this.results = [];
if(this.query.length > 2){
this.results = [{ name: 'item 1', oid: '1' }, {name: 'item 2', oid: '2'}];
//axios.get('/getclientdata',{params: {query: this.query}}).then(response => {
//this.results = response.data;
//});
}
},
selectValue(selectedValue) {
this.$emit('input', selectedValue);
}
}
}
</script>
I commented out the axios part for demonstration purposes.
To verify the workings:
<template>
<div>Autocomplete demo:
<my-autocomplete v-model="selectedRow">
</my-autocomplete>
Selected value: <pre>{{selectedRow}}</pre>
</div>
</template>
<script>
export default {
data() {
return {
selectedRow: null
}
}
}
</script>
Now the autocomplete does what it should, it allows you to search and pick a valid result from the list.
One problem remains though. How do you display the selected value. Some options are:
Copy the value to query, this works seemlessly if all your options are strings
Copy the value of result.name to query, this would make the solution work
Accept a scoped slot from the parent component which is responsible for displaying the selected item.
I will demonstrate option 2:
<template component="my-autocomplete">
<div>
<input type="text" placeholder="Source client" v-model="query" v-on:keyup="autoComplete" #keydown.esc="clearText" class="form-control">
<div class="panel-footer" v-if="results.length">
<ul class="list-group">
<li class="list-group-item" v-for="result in results" #click="selectValue(result)">
<span> {{ result.name + "-" + result.oid }} </span>
</li>
</ul>
</div>
</div>
</template>
<script>
//import axios from 'axios'
export default{
props: ['value'],
data(){
return {
selected: '',
query: '',
results: []
}
},
mounted() {
if (this.value && this.value.name) {
this.query = this.value.name;
}
},
// Because we respond to changes to value
// we do not have to do any more work in selectValue()
watch: {
value(value) {
if (this.value && this.value.name) {
this.query = this.value.name;
} else {
this.query = '';
}
}
},
methods: {
clearText(){
this.query = ''
},
autoComplete(){
this.results = [];
if(this.query.length > 2){
this.results = [{ name: 'item 1', oid: '1' }, {name: 'item 2', oid: '2'}];
//axios.get('/getclientdata',{params: {query: this.query}}).then(response => {
//this.results = response.data;
//});
}
},
selectValue(selectedValue) {
this.$emit('input', selectedValue);
}
}
}
</script>
A working example can be found here: https://jsfiddle.net/jangnoe/4xeuzvhs/1/

Getting a single array from an array of object

I am trying to iterate through the array of object called sources at change event of my select input then pushing this single array to a new element conditionally
<template>
<div class="sourceselection">
<div class="jumbotron">
<h2><span class="glyphicon glyphicon-list"></span> News List</h2>
<h4>Select News Source</h4>
<select class="form-control" #change="sourceChanged">
<option v-bind:value="source.id" v-for="source in sources" :key="source.id">{{source.name}}</option>
</select>
</div>
<div v-if="source">
<h6 >{{source.description}}</h6>
<a v-bind:href="source.url"></a>
</div>
</div>
</template>
<script>
export default {
name: 'SourceSelection',
data () {
return {
sources: [],
source: '',
}
},
methods: {
sourceChanged (event) {
for (var i = 0; i < this.sources.length; i++){
if (this.sources[i].id == event.target.value){
this.source = this.sources[i];
}
}
}
},
created : function () {
this.$http.get('https://newsapi.org/v2/sources?language=en&apiKey=cf0866b5aaef42e995f9db37bb3f7ef4')
.then(response => {
this.sources = response.data.sources;
})
.catch(error => console.log(error));
}
}
</script>
i expect to pull the description into my h6 if the source exist
At first create a state in data and name it sourceId,
then bind it to select <select v-model="sourceId" class="form-control">.
Now we need a computed filed which returns the source return this.sources.find(s => s.id === this.sourceId)
<template>
<div class="sourceselection">
<div class="jumbotron">
<h2><span class="glyphicon glyphicon-list"></span> News List</h2>
<h4>Select News Source</h4>
<select v-model="sourceId" class="form-control">
<option v-for="source in sources" :value="source.id" :key="source.id">
{{ source.name }}
</option>
</select>
</div>
<div v-if="source">
<h6 >{{ source.description }}</h6>
<a v-bind:href="source.url"></a>
</div>
</div>
</template>
<script>
export default {
name: 'SourceSelection',
data () {
return {
sources: [],
sourceId: ''
}
},
computed: {
source () {
return this.sources.find(s => s.id === this.sourceId)
}
},
created() {
this.$http.get('https://newsapi.org/v2/sources?language=en&apiKey=cf0866b5aaef42e995f9db37bb3f7ef4')
.then(response => {
this.sources = response.data.sources;
})
.catch(error => console.log(error));
}
}
</script>
Instead of using an event handler you can bind the selected value directly to your source data.
here is a demo:
Vue.config.devtools = false
Vue.config.productionTip = false
new Vue({
el: '#app',
data() {
return {
sources: [{
id: '1',
description: 'one desc',
name: 'one'
}, {
id: '2',
description: 'two desc',
name: 'two'
}, ],
source: ''
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<select class="form-control" v-model="source">
<option v-bind:value="source" v-for="source in sources" :key="source.id">{{source.name}}</option>
</select>
<h6>{{ source.description }}</h6>
</div>

VueJS + VueRouter + Bootstrap-vue (modal from modal) = <failed to convert exception to string>

I'll try display modal from modal (I'll create separate components for each modal). When second modal is closed I want to return to first modal. But after I'm display first modal second time I got error 'failed to convert exception to string' from administration.js. I'm using VueRouter too.
administration.js (main entry with router-view) -> partner.vue (with partnerEditor.vue component)
partnerEditor.vue
<template>
<div>
<b-modal ref="partnerEditor" title="Partner" :busy="isBusy" cancel-title="cancel" ok-variant="info" ok-title="save" v-on:ok="onCreate">
<div class="form-group">
<div class="input-group mb-3">
<select class="form-control">
<option v-for="contact in contacts">{{ contact.name }}</option>
</select>
<div class="input-group-append">
<b-btn v-on:click="$refs.contactEditor.show()"></b-btn>
</div>
</div>
</div>
</b-modal>
<contactEditor ref="contactEditor" v-on:hide="$refs.partnerEditor.show()"></contactEditor>
</div>
</template>
<script>
export default {
data() {
return {
contacts: [],
isBusy: false
}
},
methods: {
show() {
this.$refs.partnerEditor.show();
}
},
components: {
contactEditor: () => import('./contactEditor.vue'),
},
}
</script>
contactEditor.vue
<template>
<b-modal ref="contactEditor" title="Contact" :busy="isBusy" cancel-title="cancel" ok-variant="info" ok-title="save" v-on:ok="onCreate" v-on:hide="$emit('hide')">
<div class="form-group">
<label>name</label>
<input type="text" class="form-control" placeholder="" />
</div>
</b-modal>
</template>
<style></style>
<script>
export default {
model: {
event: 'hide'
},
data() {
return {
isBusy: false
}
},
methods: {
show() {
this.$refs.contactEditor.show();
},
}
}
</script>
routes
const router = new VueRouter({
routes: [
{
name: 'configuration', path: '/configuration', component: () => import('./administrator/configuration/index.vue'),
children: [
{ name: 'configuration-partner', path: '/configuration/partner', component: () => import('./administrator/configuration/partner.vue') }
]
}
]
});

Send data from one component to another in vue

Hi I'm trying to send data from one component to another but not sure how to approach it.
I've got one component that loops through an array of items and displays them. Then I have another component that contains a form/input and this should submit the data to the array in the other component.
I'm not sure on what I should be doing to send the date to the other component any help would be great.
Component to loop through items
<template>
<div class="container-flex">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Name</p>
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in entries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry />
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
export default {
name: 'entry-list',
components: {
addEntry
},
data: function() {
return {
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
}
}
</script>
Component for adding / sending data
<template>
<div
class="entry-add"
v-bind:class="{ 'entry-add--open': addEntryIsOpen }">
<input
type="text"
name="addEntry"
#keyup.enter="addEntries"
v-model="newEntries">
</input>
<button #click="addEntries">Add Entries</button>
<div
class="entry-add__btn"
v-on:click="openAddEntry">
<span>+</span>
</div>
</div>
</template>
<script>
export default {
name: 'add-entry',
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push(this.newEntries);
this.newEntries = '';
},
openAddEntry() {
this.addEntryIsOpen = !this.addEntryIsOpen;
}
}
}
</script>
Sync the property between the 2:
<add-entry :entries.sync="entries"/>
Add it as a prop to the add-entry component:
props: ['entries']
Then do a shallow merge of the 2 and emit it back to the parent:
this.$emit('entries:update', [].concat(this.entries, this.newEntries))
(This was a comment but became to big :D)
Is there a way to pass in the key of name? The entry gets added but doesn't display because im looping and outputting {{ entry.name }}
That's happening probably because when you pass "complex objects" through parameters, the embed objects/collections are being seen as observable objects, even if you sync the properties, when the component is mounted, only loads first level data, in your case, the objects inside the array, this is performance friendly but sometimes a bit annoying, you have two options, the first one is to declare a computed property which returns the property passed from the parent controller, or secondly (dirty and ugly but works) is to JSON.stringify the collection passed and then JSON.parse to convert it back to an object without the observable properties.
Hope this helps you in any way.
Cheers.
So with help from #Ohgodwhy I managed to get it working. I'm not sure if it's the right way but it does seem to work without errors. Please add a better solution if there is one and I'll mark that as the answer.
I follow what Ohmygod said but the this.$emit('entries:update', [].concat(this.entries, this.newEntries)) didn't work. Well I never even need to add it.
This is my add-entry.vue component
<template>
<div
class="add-entry"
v-bind:class="{ 'add-entry--open': addEntryIsOpen }">
<input
class="add-entry__input"
type="text"
name="addEntry"
placeholder="Add Entry"
#keyup.enter="addEntries"
v-model="newEntries"
/>
<button
class="add-entry__btn"
#click="addEntries">Add</button>
</div>
</template>
<script>
export default {
name: 'add-entry',
props: ['entries'],
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push({name:this.newEntries});
this.newEntries = '';
}
}
}
</script>
And my list-entries.vue component
<template>
<div class="container-flex">
<div class="wrapper">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Competition Entries</p>
</div>
<div class="entries__header__search">
<input
type="text"
name="Search"
class="input input--search"
placeholder="Search..."
v-model="search">
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in filteredEntries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry :entries.sync="entries"/>
</div>
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
import pickWinner from '#/components/pick-winner.vue'
export default {
name: 'entry-list',
components: {
addEntry,
pickWinner
},
data: function() {
return {
search: '',
entries: [
{
name: 'Geoff'
},
{
name: 'Stu'
},
{
name: 'Craig'
},
{
name: 'Mark'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredEntries() {
if(this.search === '') return this.entries
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
</script>