Vue with VCalendar component: Keep data in sync with another Vue instance - javascript

I'm programming a page that displays a list of meetings in a table. It's also possible to edit and delete meetings. Now I'd like to offer an alternative view using VCalendar.
Data is received from the server on page load and stored in a JS variable. Both the Vue instance containing the table and the VCalendar component share this data. If I edit a table cell, the changes are reflected in the component. But when I delete a date in the table view, it remains in the calendar.
This is the relevant HTML (edit: Added some attributes to the td):
<calendar-component></calendar-component>
<table id='meetings-table'>
<tr v-for='meeting in meetings' :key='date.id'>
<td contenteditable #blur='handleInput($event,meeting,"name")>
#{{ meeting.name }}
</td>
<td>
<input type='checkbox' v-model='selected'
:value='meeting.id'>
</td>
</tr>
</table>
<div>
<button v-if='selected.length' #click='deleteMeetings'>
Delete selected rows
</button>
</div>
My JS (edit: Added handleInput method):
let table = new Vue({
el:'#meetings-table',
data: {
selected: [],
meetings: window.meetings,
},
methods: {
/**
* Deletes selected meetings.
*/
deleteMeetings: function () {
let requests = [];
// Make a single request and store it
for (let id of this.selected) {
requests.push(axios.delete('/termine/' + id)
.then(response => {
// Remove meetings
this.meetings = this.meetings.filter(t => t.id != id);
// Remove id from list of selected meetings
this.selected = this.selected.filter(elem => elem != id);
}));
}
const axiosArray = axios.all(requests);
},
/**
* Handles edits in table cells.
*/
handleInput: function($event, meeting, field) {
const newValue = $event.target.textContent;
// Update value in $data
meeting[field] = newValue;
// AJAX request follows, but is not necessary for this example to work
}
}
});
The relevant parts of the component:
<template>
<v-calendar :attributes='attributes'>
<div
slot='meeting-row'
slot-scope='{ customData }'>
<!-- Popover content omitted -->
</div>
</v-calendar>
</template>
<script>
let meetings = window.meetings;
export default {
data() {
return {
incId: meetings.length,
editId: 0,
meetings,
};
},
computed: {
attributes() {
return [
// Today attribute
{
// ...
},
// Meeting attributes
...this.meetings.map(meeting => ({
key: meeting.id,
dates: new Date('2018,11,31'),// moment(meeting.slot.date, 'DD.MM.YY').format('YYYY, MM, DD'), //meeting.dates,
customData: meeting,
order: meeting.id,
dot: {
backgroundColor: '#ff8080',
},
popover: {
// Matches slot from above
slot: 'meeting-row',
}
}))
];
}
}
};
</script>
This is what happens:
I load the page containing only a single meeting. The meeting is
shown both in the table and the calendar component. Vue devtools show
it in both meetings arrays (in the component as well as in the other
Vue instance). Using the console, I can also see it in
window.meetings.
After clicking the delete button (triggering the deleteMeetings method in my JS), the meeting is gone from the table, but remains in
the calendar, in the component's meetings array and in
window.meetings.
What do I have to change to keep the meetings arrays in sync even when deleting a meeting in the table? Note that I haven't yet implemented any methods for the calendar component.

Calendar, and table components should share a single state: currently selected meetings. From what I understand, right now you have that state in 2 separate places: table Vue instance, and a calendar-component, which is a child of some other Vue instance.
It may look like you're sharing the state already (with window.meetings), but it's not the case: you only initialize the same set of meetings when the components are created. And then changes in one component are not reflected in another component.
What you can try to do is to have meetings stored in the 'main' Vue app on your page, pass them as props to table and calendar components, and then trigger events from table and calendar components, when meetings array is modified. You should also define the event hanlders in the 'main' Vue app, and listen on components. A rough sketch of the solution:
<div id="app">
<table-component
:meetings="meetings"
#meetingUpdated="handleMeetingUpdate"
#meetingDeleted="handleMeetingDeletion"
></table-component>
<calendar-component
:meetings="meetings"
#meetingUpdate="handleMeetingUpdate"
#meetingDeleted="handleMeetingDeletion"
></calendar-component>
</div>
let app = new Vue({
el:'#app',
data: {
meetings: []
},
methods: {
handleMeetingUpdate(event) {
//
},
handleMeetingDeletion(event) {
//
},
}
//
});
I hope the above is enough to point you in the right direction. If not, please let me know, and I'll do my best to help you with this further.

Related

Vuejs How to have many <a> tags and hashtags within <router-link> or #click function

enter image description here
i want to have a post card like twitter post card, if clicked on any part of the post card goes to post page, but when clicked on hashtags or links goes to the hashtag or link
example below
1.
<div #click="gotoPostPage" class="text-weight-light text-justify">
{{ feed.content }}
check google if you need more information then follow #benny(a tag) or follow these hashtags
#google #finda, #checks, now when i click within this post take me to post page
</div>
now this hits the gotoPostPage function even if link or a tag is clicked
using this also
2.
<router-link :to="`/post/${ feed.id }`">
{{ feed.content }}
check google if you need more information then follow #benny(a tag) or follow
these hashtags
#google #finda, #checks, now when i click within this post take me to post page
</router-link>
goes to even when check google is clicked
Please how can i resolve this, your help is highly appreciated,
Thanks
Add #click.stop to your links. E.g:
<a href="https://google.com" #click.stop>check google</a>
This will stop propagation of click events to the parent DOM elements.
Note: #click.stop is shorthand for #click="e => e.stopPropagation()".
Docs here.
Update, based on data shown in comment:
I would avoid storing HTML id database. That's a really bad pattern. You're supposed to detach the data from the UI layer. You have to allow the UI layer to change, based on device and medium. I'd try to store that data as strings holding a hashtag name and, where the name is different than the hastag, as on object, containing both.
Something like this:
const { createApp, reactive, computed, onMounted, toRefs } = Vue;
createApp({
setup() {
const state = reactive({
items: [],
links: computed(() => state.items.map(
item => ({
name: item.name || `#${item}`,
url: item.url || `/hashtag/${item}`
})
))
});
onMounted(async() => {
/* replace this promise with an axios call to server which returns
an array, similar to the one i'm passing here:
(see axios call example below)
*/
const data = await new Promise(resolve => {
setTimeout(() => {
resolve([
'one',
'two',
'three',
'печаль',
'грусть',
{
name: '#mens',
url: '/hashtag/fıstıklıbaklava'
},
'чайная',
'джаз'
]);
});
})
// example axios call:
/*
const data = await axios
.get('http://endpoint-returning-data')
.then(({data}) => data);
*/
state.items = data;
console.log('See difference betwen items (received from server) \r\n\and links (same data, mapped for template):\r\n', {...state}, '\r\nMakes sense?');
})
return {
...toRefs(state),
log: () => console.log('You clicked on app!')
}
}
}).mount('#app')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="app">
<div v-for="link in links" :key="link.url" #click="log">
<a :href="link.url"
v-text="link.name"
#click.stop></a>
</div>
</div>
As you can see, it produces the HTML from the data. The template also applies the stopPropagation(), in the v-for.

Commit initial array to store when leaving page, but if pressing back button loads last commited array

I have an array of articles that can be filtered based on their category. If you click the filter for "ideas" it will filter the array into a new array, let's say "ideasFiltered" and then i commit it to my store "filteredArticles". If i leave the page and press back it is saving that info. Yay! Great! BUT, let's say i go to a new page in my website and then i click my news page again, it has still saved that last commit "ideasFiltered". Is there a way to only save that data if you are pressing "back" in the browser or pressing "back to articles" out of one of my articles (i have a button that let's you go back to the list of articles on each article page), but if you click to a different link in my site it will reset to my full article list?
here is my store:
const store = new Vuex.Store({
state: {
filteredArticles: this.articles
},
mutations: {
setFilteredList (state, value) {
state.filteredArticles = value
}
},
plugins: [
createPersistedState()
],
})
my computed:
computed: {
filteredArticles () {
return store.state.filteredArticles
}
}
so if you click one of my filtered is runs a script, here is an example of the ideas script
ideas: function ideas() {
this.$store.commit('setFilteredList', articles) **// resetting to the full list before filtering**
var ideasFiltered = this.filteredArticles.filter(function(post) {
return post.category === 'Ideas';
});
this.filteredCategory = 'ideas'; **// this is used to add the category to my url**
this.$store.commit('setFilteredList', ideasFiltered) **// committing to the store**
}
here is my html, not sure if it is really necessary though
<div class="news-article-list">
<ul>
<li v-for="(article, index) in filteredArticles" :key="index">
<a :href="article.url">
<img :src="article.featureImg" v-if="article.featureImg" alt="Article Feature Image" />
<h2 class="news-title">{{ article.title }}</h2>
<p class="news-date">{{ article.date }}</p>
<p class="short-desc">{{ article.shortDesc }}...</p>
</a>
<router-link class="read-more" :to="article.url">Read More</router-link>
</li>
</ul>
</div>
Let me know if i need to explain any further. I need it to commit ALL articles if going to a different page on my site and clicking news to get back to the news page, but saving the FILTERED article list if you go to an article and click back.
thanks in advance!
I wouldn't worry about detecting the back button. What you want to know where from did the user come and where to the user wants to go. to and from are your routes. ideasFiltered is changed permanently until you clear it manually. To handle clearing it yourself, create an action in vue-router that resets the data in ideasFiltered and then dispatch that action depending on what route the user is on. You can add a watch in your Vue component to watch the global vue-router $router. In Vue.js 3 its like this:
import {useRoute} from 'vue-router';
import {useStore} from 'vuex';
....
setup() {
const $route = useRoute();
const $store = useStore();
watch(() => $route.name, async () => {
// If the user is not on News or Articles route, then tell store to reset filtered list
if ($route.name !=== "News" || $route.name !=== "Articles") {
$store.dispatch('clearFilteredList', null) // You need to create this action in your store
},
{
deep: true,
immediate: true
}
)
}
The mutation and action in $store to be committed would be something like:
mutations: {
setFilteredList (state, value) { // value passed should be null
state.filteredArticles = value
}
actions: {
clearFilteredList({commit}, payload) { // This is null from Vue component remember!
commit('setFilteredList', payload) // sets it to null
}
I can't see your full store code so the above answer is a best guess but should give you an idea how to go about it. You don't have to use null, you could use make it an empty array or object e.g. []

For sibling communication between many identical components, how should I store the data in the lowest-common ancestor?

Background:
I'm a Python/Vue developer; I've been using Vue since 2016.
I have a client who runs a weight loss / meal planning business: clients pay her to prepare weekly single-page PDF menus that tell them (the clients) exactly what to eat for breakfast, lunch, and dinner of every day of the week. (image of an example menu)
Each meal is shown as a list of ingredients.
Right now she's preparing these menus in Excel, and she hired me to reproduce and extend the functionality of what she has in Excel, but in a Python/Vue app.
The app I'm building for her has many "pages" ("top-level" components) to allow her to add/modify/delete objects like clients, ingredients, and recipes (image), but the most complicated part of the UI is the component in which she can define the meals for every meal of every day of the week (image). That component is named WeeklyMenu.vue.
WeeklyMenu.vue itself contains seven DailyMenu.vue children, one for each day of the week (Monday, Tuesday, etc.). (image)
Each DailyMenu.vue component itself contains four Meal.vue components, one for each of four meal types: Breakfast, Lunch, Dinner, and Snacks. (image)
Important: At the moment, the DailyMenu.vue and Meal.vue components themselves contain their data rather than accessing it from the Vuex store.
For example, the list of ingredients for each meal is contained within the Meal.vue component as a mealIngredients variable within the component's data attribute. (image)
Side-note: This means that there are lots of HTTP requests being sent to the back-end when the page loads as all of the meals are requesting their own data, rather than a single request being sent via a Vuex action (for example). This seems like it can't be best practice.
The problem:
The problem is that she is now asking me to add features in which a change to the data in one subcomponent should update the data in a different subcomponent.
For example, she wants the app to work so that when she has the same recipe in several different Meals of the week, then a change to an ingredient in one of the meals will propagate to the other meals that have the same recipe. (image explanation)
My question:
What is the best practice for handling a situation like this? Should I move the ingredient data into the Vuex store or (in the same vein) the lowest-common-ancestor WeeklyMenu.vue component? If so, how exactly should it work? Should there be a separate variable for each meal? Or should I have an object that contains data for all of the different meals? If I use a single object, do I need to worry that a watcher on that object in the Meal.vue component would be triggering even when a change was made to a different meal's data?
If I store all the meal ingredients in separate variables, I would need to pass all of those to every meal (so every meal would need to receive every other meal's ingredients as separate props). So that doesn't seem like the right way to go.
If a user is making a particular change to a particular meal, how would I only have the other meals with the same name react?
Related links:
Communication between sibling components in VueJs 2.0
I'm looking into whether it would make sense to move the ingredient data up to the level of the WeeklyMenu.vue component as described in the "Lowest Common Ancestor" approach (here and here).
Simplified example of the situation I'm trying to handle:
Without Vuex: https://codepen.io/NathanWailes/pen/zYBGjME
Using Vuex: https://codepen.io/NathanWailes/pen/WNxWxWe
With everything working (including the state being kept in Vuex) except the propagation: https://codepen.io/NathanWailes/pen/KKMYNVZ
Yes, problem domain seems complex enough to more than justify use of Vuex. I would not go with keeping data in components and sharing by props - that doesn't scale well
Keep each Recipe as an object in single object recipes - you don't need to worry about watchers. If one particular Recipe object will change, Vue will re-render only components using same Recipe object (and if done properly you don't even need watchers for that)
Create a "weekly menu" object inside the store
In leaf nodes (Meals) of that object just use some kind of reference (by name or unique ID if you have one) into recipes. As a result multiple Meal.vue components on a menu will use same object in the store and update automatically
I ended up getting it working in a simple example in CodePen, which I'm going to use as a guide when trying to get it working on the actual site.
The summary of my findings with this solution is, "Vue will actually update when the nested entries of a Vuex state object are updated; you don't need to worry about it not detecting those changes. So it's OK to just keep all the data in a single big Vuex store object when you have many duplicate sibling components that need to react to each other."
Here's the CodePen: https://codepen.io/NathanWailes/pen/NWRNgNz
Screenshot
Summary of what the CodePen example does
The data used to populate the menu all lives in the Vuex store in a single weeklyMenu object, which has child objects to break up the data into the different days / meals.
The individual meals have computed properties with get and set functions so that it can both get changes from the store and also update the store.
The DailyMenu and WeeklyMenu components get their aggregate data by simply having computed properties that iterate over the Vuex weeklyMenu object, and it "just works".
I have same-named meals update to match each other by iterating over the meals in the Vuex mutation and looking for meals with the same "Ingredient Name".
The code
HTML
<html>
<body>
<div id='weekly-menu'></div>
<h3>Requirements:</h3>
<ul>
<li>Each row should have all the numbers in it summed and displayed ('total daily calories').</li>
<li>The week as a whole should have all the numbers summed and displayed ('total weekly calories').</li>
<li>If two or more input boxes have the same text, a change in one numerical input should propagate to the other same-named numerical inputs.</li>
<li>Ideally the data (ingredient names and calories) should be stored in one place (the top-level component or a Vuex store) to make it more straightforward to populate it from the database with a single HTTP call (which is not simulated in this example).</li>
</ul>
</body>
</html>
JavaScript
const store = new Vuex.Store(
{
state: {
weeklyMenu: {
Sunday: {
Breakfast: {
name: 'aaa',
calories: 1
},
Lunch: {
name: 'bbb',
calories: 2
},
},
Monday: {
Breakfast: {
name: 'ccc',
calories: 3
},
Lunch: {
name: 'ddd',
calories: 4
},
}
}
},
mutations: {
updateIngredientCalories (state, {dayOfTheWeekName, mealName, newCalorieValue}) {
state.weeklyMenu[dayOfTheWeekName][mealName]['calories'] = newCalorieValue
const ingredientNameBeingUpdated = state.weeklyMenu[dayOfTheWeekName][mealName]['name']
for (const dayOfTheWeekName of Object.keys(state.weeklyMenu)) {
for (const mealName of Object.keys(state.weeklyMenu[dayOfTheWeekName])) {
const mealToCheck = state.weeklyMenu[dayOfTheWeekName][mealName]
const ingredientNameToCheck = mealToCheck['name']
if (ingredientNameToCheck === ingredientNameBeingUpdated) {
mealToCheck['calories'] = newCalorieValue
}
}
}
},
updateIngredientName (state, {dayOfTheWeekName, mealName, newValue}) {
state.weeklyMenu[dayOfTheWeekName][mealName]['name'] = newValue
}
}
}
)
var Meal = {
template: `
<td>
<h4>{{ mealName }}</h4>
Ingredient Name: <input v-model="ingredientName" /><br/>
Calories: <input v-model.number="ingredientCalories" />
</td>
`,
props: [
'dayOfTheWeekName',
'mealName'
],
computed: {
ingredientCalories: {
get () {
return this.$store.state.weeklyMenu[this.dayOfTheWeekName][this.mealName]['calories']
},
set (value) {
if (value === '' || value === undefined || value === null) {
value = 0
}
this.$store.commit('updateIngredientCalories', {
dayOfTheWeekName: this.dayOfTheWeekName,
mealName: this.mealName,
newCalorieValue: value
})
}
},
ingredientName: {
get () {
return this.$store.state.weeklyMenu[this.dayOfTheWeekName][this.mealName]['name']
},
set (value) {
this.$store.commit('updateIngredientName', {
dayOfTheWeekName: this.dayOfTheWeekName,
mealName: this.mealName,
newValue: value
})
}
}
}
};
var DailyMenu = {
template: `
<tr>
<td>
<h4>{{ dayOfTheWeekName }}</h4>
Total Daily Calories: {{ totalDailyCalories }}
</td>
<meal :day-of-the-week-name="dayOfTheWeekName" meal-name="Breakfast" />
<meal :day-of-the-week-name="dayOfTheWeekName" meal-name="Lunch" />
</tr>
`,
props: [
'dayOfTheWeekName'
],
data: function () {
return {
}
},
components: {
meal: Meal
},
computed: {
totalDailyCalories () {
let totalDailyCalories = 0
for (const mealName of Object.keys(this.$store.state.weeklyMenu[this.dayOfTheWeekName])) {
totalDailyCalories += this.$store.state.weeklyMenu[this.dayOfTheWeekName][mealName]['calories']
}
return totalDailyCalories
}
}
};
var app = new Vue({
el: '#weekly-menu',
template: `<div id="weekly-menu" class="container">
<div class="jumbotron">
<h2>Weekly Menu</h2>
Total Weekly Calories: {{ totalWeeklyCalories }}
<table class="table">
<tbody>
<daily_menu day-of-the-week-name="Sunday" />
<daily_menu day-of-the-week-name="Monday" />
</tbody>
</table>
</div>
</div>
`,
data: function () {
return {
}
},
computed: {
totalWeeklyCalories () {
let totalWeeklyCalories = 0
for (const dayOfTheWeekName of Object.keys(this.$store.state.weeklyMenu)) {
let totalDailyCalories = 0
for (const mealName of Object.keys(this.$store.state.weeklyMenu[dayOfTheWeekName])) {
totalDailyCalories += this.$store.state.weeklyMenu[dayOfTheWeekName][mealName]['calories']
}
totalWeeklyCalories += totalDailyCalories
}
return totalWeeklyCalories
}
},
components: {
daily_menu: DailyMenu
},
store: store
});

rendering vue.js components and passing in data

I'm having trouble figuring out how to render a parent component, display a list of contracts in a list on part of the page, and when a user clicks on one of them, display the details of that specific contract on the other part of the page.
Here is my slim file:
#contracts_area
.filter-section
ul
li.filter-item v-for="contract in contractsAry" :key="contract.id" #click="showContract(contract)"
| {{ contract.name }}
.display-section
component :is="currentView" transition="fade" transition-mode="out-in"
script type="text/x-template" id="manage-contracts-template"
div
h1 Blank when page is newly loaded for now
script type="text/x-template" id="view-contract-template"
div :apply_contract="showContract"
h1#display-item__name v-name="name"
javascript:
Vue.component('manage-template', {
template: '#manage-contracts-template'
});
Vue.component('view-contract', {
template: '#view-contract-template',
props: ['show_contract'],
data: function() {
return {
name: ''
}
},
methods: {
showContract: function(contract) {
return this.name = contract.name
}
}
});
Vue.http.headers.common['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content');
var contractsResource = Vue.resource('/all_contracts{/id}.json');
var contracts = new Vue({
el: '#contracts_area',
data: {
currentView: 'manage-template',
contractsAry: [],
errors: {}
},
mounted: function() {
var that = this;
contractsResource.get().then(
function(res) {
that.contractsAry = res.data;
}
)
},
methods: {
showContract: function(contract) {
this.currentView = 'view-contract'
}
}
});
Basically I'd like it so that when a user clicks on any contract item in the .filter-section, it shows the data for that contract in the .display-section. How can I achieve this?
In short you can bind a value to a prop.
.display-section
component :is="currentView" :contract="currentContract"
view-contract
props: ['contract']
contracts-area
data: {
currentContract: null,
},
methods: {
showContract: function(contract) {
this.currentView = "view-contract";
this.currentContract = contract;
}
}
There are multiple ways to pass data in Vue.
Binding values to props.
Using ref to directly call a method from a child component.
Custom Events. Note that to pass events globally, you will need a global event bus.
A single central source of truth (i.e. vuex)
I have illustrated methods 1, 2, 3 in Codepen
Note that 2nd and 3rd methods will only work after your component has been rendered. In your case, since your components for currentView are dynamic and when user clicked, display-section component does not yet exists; it will not receive any events yet. So their content will be empty at first.
To workaround this you can directly access $parent in mounted() from child component, however this would create coupling between them. Another solution is creating the components but conditionally displaying them. And one another solution would be waiting until child component has been mounted and then emitting events.
If your needs are simple I suggest binding values to props (1), else you may consider using something like vuex.

Passing data to components in vue.js

I'm struggling to understand how to pass data between components in vue.js. I have read through the docs several times and looked at many vue related questions and tutorials, but I'm still not getting it.
To wrap my head around this, I am hoping for help completing a pretty simple example
display a list of users in one component (done)
send the user data to a new component when a link is clicked (done) - see update at bottom.
edit user data and send it back to original component (haven't gotten this far)
Here is a fiddle, which fails on step two: https://jsfiddle.net/retrogradeMT/d1a8hps0/
I understand that I need to use props to pass data to the new component, but I'm not sure how to functionally do it. How do I bind the data to the new component?
HTML:
<div id="page-content">
<router-view></router-view>
</div>
<template id="userBlock" >
<ul>
<li v-for="user in users">{{user.name}} - <a v-link="{ path: '/new' }"> Show new component</a>
</li>
</ul>
</template>
<template id="newtemp" :name ="{{user.name}}">
<form>
<label>Name: </label><input v-model="name">
<input type="submit" value="Submit">
</form>
</template>
js for main component:
Vue.component('app-page', {
template: '#userBlock',
data: function() {
return{
users: []
}
},
ready: function () {
this.fetchUsers();
},
methods: {
fetchUsers: function(){
var users = [
{
id: 1,
name: 'tom'
},
{
id: 2,
name: 'brian'
},
{
id: 3,
name: 'sam'
},
];
this.$set('users', users);
}
}
})
JS for second component:
Vue.component('newtemp', {
template: '#newtemp',
props: 'name',
data: function() {
return {
name: name,
}
},
})
UPDATE
Ok, I've got the second step figured out. Here is a new fiddle showing the progress: https://jsfiddle.net/retrogradeMT/9pffnmjp/
Because I'm using Vue-router, I don't use props to send the data to a new component. Instead, I need set params on the v-link and then use a transition hook to accept it.
V-link changes see named routes in vue-router docs:
<a v-link="{ name: 'new', params: { name: user.name }}"> Show new component</a>
Then on the component, add data to the route options see transition hooks:
Vue.component('newtemp', {
template: '#newtemp',
route: {
data: function(transition) {
transition.next({
// saving the id which is passed in url
name: transition.to.params.name
});
}
},
data: function() {
return {
name:name,
}
},
})
-------------Following is applicable only to Vue 1 --------------
Passing data can be done in multiple ways. The method depends on the type of use.
If you want to pass data from your html while you add a new component. That is done using props.
<my-component prop-name="value"></my-component>
This prop value will be available to your component only if you add the prop name prop-name to your props attribute.
When data is passed from a component to another component because of some dynamic or static event. That is done by using event dispatchers and broadcasters. So for example if you have a component structure like this:
<my-parent>
<my-child-A></my-child-A>
<my-child-B></my-child-B>
</my-parent>
And you want to send data from <my-child-A> to <my-child-B> then in <my-child-A> you will have to dispatch an event:
this.$dispatch('event_name', data);
This event will travel all the way up the parent chain. And from whichever parent you have a branch toward <my-child-B> you broadcast the event along with the data. So in the parent:
events:{
'event_name' : function(data){
this.$broadcast('event_name', data);
},
Now this broadcast will travel down the child chain. And at whichever child you want to grab the event, in our case <my-child-B> we will add another event:
events: {
'event_name' : function(data){
// Your code.
},
},
The third way to pass data is through parameters in v-links. This method is used when components chains are completely destroyed or in cases when the URI changes. And i can see you already understand them.
Decide what type of data communication you want, and choose appropriately.
The best way to send data from a parent component to a child is using props.
Passing data from parent to child via props
Declare props (array or object) in the child
Pass it to the child via <child :name="variableOnParent">
See demo below:
Vue.component('child-comp', {
props: ['message'], // declare the props
template: '<p>At child-comp, using props in the template: {{ message }}</p>',
mounted: function () {
console.log('The props are also available in JS:', this.message);
}
})
new Vue({
el: '#app',
data: {
variableAtParent: 'DATA FROM PARENT!'
}
})
<script src="https://unpkg.com/vue#2.5.13/dist/vue.min.js"></script>
<div id="app">
<p>At Parent: {{ variableAtParent }}<br>And is reactive (edit it) <input v-model="variableAtParent"></p>
<child-comp :message="variableAtParent"></child-comp>
</div>
I think the issue is here:
<template id="newtemp" :name ="{{user.name}}">
When you prefix the prop with : you are indicating to Vue that it is a variable, not a string. So you don't need the {{}} around user.name. Try:
<template id="newtemp" :name ="user.name">
EDIT-----
The above is true, but the bigger issue here is that when you change the URL and go to a new route, the original component disappears. In order to have the second component edit the parent data, the second component would need to be a child component of the first one, or just a part of the same component.
The above-mentioned responses work well but if you want to pass data between 2 sibling components, then the event bus can also be used.
Check out this blog which would help you understand better.
supppose for 2 components : CompA & CompB having same parent and main.js for setting up main vue app. For passing data from CompA to CompB without involving parent component you can do the following.
in main.js file, declare a separate global Vue instance, that will be event bus.
export const bus = new Vue();
In CompA, where the event is generated : you have to emit the event to bus.
methods: {
somethingHappened (){
bus.$emit('changedSomething', 'new data');
}
}
Now the task is to listen the emitted event, so, in CompB, you can listen like.
created (){
bus.$on('changedSomething', (newData) => {
console.log(newData);
})
}
Advantages:
Less & Clean code.
Parent should not involve in passing down data from 1 child comp to another ( as the number of children grows, it will become hard to maintain )
Follows pub-sub approach.
I've found a way to pass parent data to component scope in Vue, i think it's a little a bit of a hack but maybe this will help you.
1) Reference data in Vue Instance as an external object (data : dataObj)
2) Then in the data return function in the child component just return parentScope = dataObj and voila. Now you cann do things like {{ parentScope.prop }} and will work like a charm.
Good Luck!
I access main properties using $root.
Vue.component("example", {
template: `<div>$root.message</div>`
});
...
<example></example>
A global JS variable (object) can be used to pass data between components. Example: Passing data from Ammlogin.vue to Options.vue. In Ammlogin.vue rspData is set to the response from the server. In Options.vue the response from the server is made available via rspData.
index.html:
<script>
var rspData; // global - transfer data between components
</script>
Ammlogin.vue:
....
export default {
data: function() {return vueData},
methods: {
login: function(event){
event.preventDefault(); // otherwise the page is submitted...
vueData.errortxt = "";
axios.post('http://vueamm...../actions.php', { action: this.$data.action, user: this.$data.user, password: this.$data.password})
.then(function (response) {
vueData.user = '';
vueData.password = '';
// activate v-link via JS click...
// JSON.parse is not needed because it is already an object
if (response.data.result === "ok") {
rspData = response.data; // set global rspData
document.getElementById("loginid").click();
} else {
vueData.errortxt = "Felaktig avändare eller lösenord!"
}
})
.catch(function (error) {
// Wu oh! Something went wrong
vueData.errortxt = error.message;
});
},
....
Options.vue:
<template>
<main-layout>
<p>Alternativ</p>
<p>Resultat: {{rspData.result}}</p>
<p>Meddelande: {{rspData.data}}</p>
<v-link href='/'>Logga ut</v-link>
</main-layout>
</template>
<script>
import MainLayout from '../layouts/Main.vue'
import VLink from '../components/VLink.vue'
var optData = { rspData: rspData}; // rspData is global
export default {
data: function() {return optData},
components: {
MainLayout,
VLink
}
}
</script>

Categories

Resources