I have a parent/child component setup where the parent is loading data from the server and passing it down to children via props. In the child I would like to instantiate a jQuery calendar with some of the data it receives from the parent.
In order to wait for the data before setting up the calendar, I broadcast an event in the parent that I have an event listener setup for in the child.
The listener is being fired in the child but if I this.$log('theProp'), it's undefined. However, if I inspect the components with the VueJs devtools, the parent/child relationship is there and the child has received the prop in the meantime.
The prop is defined on the child as a dynamic prop :the-prop="theProp". Since the child does receive the prop in the end, I'm assuming my setup is correct but there seems to be some sort of delay. The parent sets the props in the return function of the ajax call and again: it's working, just with a slight delay it seems.
I also tried registering a watch listener on the prop in the child so I could setup the calendar then and be sure that the prop is there. However, the watch listener fires, but this.$log('theProp') is still undefined.
If I pass the data along with the the broadcast call, like this.$broadcast('dataLoaded', theData) the child receives it just fine. But it seems wrong to do it that way as I'm basically building my own prop handler.
I'm not posting any code because the components are rather large and the VueJs devtools are telling me the parent/child situation is working.
Am I missing some information? Is there a slight delay between setting a value in the parent and the child receiving it? What would be the proper way to wait for parent data in the child?
Normally, when you're just rendering the data out into the template, the timing doesn't matter so much since the data is bound to the template. But in this case, I really need the data to be there to setup the calendar or it will be wrong.
Thanks.
edit 1: here's a jsfiddle: https://jsfiddle.net/dr3djo0u/1/
It seems to confirm that the data is not available immediately after the broadcast. However, the watcher does work, though I could almost swear that sometimes this.$log('someData') returned undefined when I setup that testcase.
But I guess my problem might be somewhere else, I'll have a look tonight, don't have the project with me right now.
edit 2: did some more tests. My problem was that a) event listeners do not seem to receive the data instantly and b) I was also trying to init the calendar in the route.data callback if someData was already around (e.g. when coming from parent), but that route callback is called before the component is ready, so it wasn't working there either.
My solution is now this:
// works when the child route is loaded directly and parent finishes loading someData
watch: {
someData() {
this.initCalendar();
}
},
// works when navigating from parent (data already loaded)
ready() {
if (this.someData && this.someData.length) {
this.initCalendar()
}
}
As far as I know, you should not need events to pass data from parent to child.
All you need is, in the child component: props: ['theProp']
And when using the child component in the parent: <child :theProp="someData"></child>
Now, wherever in the parent you change someData, the child component will react accordingly.
You don't need events, you don't need "watch", you don't need "ready".
For example: after an AJAX call, in the parent's "ready", you load some data:
// at the parent component
data: function () {
return {
someData: {}
}
},
ready: function () {
var vm = this;
$.get(url, function(response) {
vm.someData = response;
});
}
Now, you do not need anything else to pass the data to the child. It is already in the child as theProp!
What you really need to do is to have, in the child, something which reacts to data changes on its own theProp property.
Either in the interface:
<div v-if="theProp.id > 0">
Loaded!
</div>
Or in JavaScript code:
// at the child component
computed: {
// using a computed property based on theProp's value
awesomeDate: function() {
if (!this.theProp || (this.theProp.length === 0)) {
return false;
}
if (!this.initialized) {
this.initCalendar();
}
return this.theProp.someThing;
}
}
Update 1
You can also, in the parent, render the child conditionally:
<child v-if="dataLoaded" :theProp="someData"></child>
Only set dataLoaded to true when the data is available.
Update 2
Or maybe your issue is related to a change detection caveat
Maybe you're creating a new property in an object...
vm.someObject.someProperty = someValue
...when you should do...
vm.$set('someObject.someProperty', someValue)
...among other "caveats".
Update 3
In VueJS 2 you are not restricted to templates. You can use a render function and code the most complex rendering logic you want.
Update 4 (regarding OP's edit 2)
Maybe you can drop ready and use immediate option, so your initialization is in a single place:
watch: {
someData: {
handler: function (someData) {
// check someData and eventually call
this.initCalendar();
},
immediate: true
}
}
It's because tricky behavior in Vue Parent and Child lifecycle hooks.
Usually parent component fire created() hook and then mount() hook, but when there are child components it's not exactly that way: Parent fires created() and then his childs fire created(), then mount() and only after child's mount() hooks are loaded, parent loads his mount() as explained here. And that's why the prop in child component isn't loaded.
Use mounted() hook instead created()
like that https://jsfiddle.net/stanimirsp5/xnwcvL59/1/
Vue 3
Ok so I've spent like 1.5h trying to find out how to pass prop from parent to child:
Child
<!-- Template -->
<template>
<input type="hidden" name="_csrf_token" :value="csrfToken">
<span>
{{ csrfToken }}
</span>
</template>
<!-- Script -->
<script>
export default {
props: [
"csrfToken"
]
}
</script>
Parent
<!-- Template -->
<template>
<form #submit.prevent="submitTestMailForm" v-bind:action="formActionUrl" ref="form" method="POST">
...
<CsrfTokenInputComponent :csrf-token="csrfToken"/>
...
</form>
</template>
<!-- Script -->
<script>
...
export default {
data(){
return {
...
csrfToken : "",
}
},
methods: {
/**
* #description will handle submission of the form
*/
submitTestMailForm(){
let csrfRequestPromise = this.getCsrfToken();
let ajaxFormData = {
receiver : this.emailInput,
messageTitle : this.titleInput,
messageBody : this.bodyTextArea,
_csrf_token : this.csrfToken,
};
csrfRequestPromise.then( (response) => {
let csrfTokenResponseDto = CsrfTokenResponseDto.fromAxiosResponse(response);
this.csrfToken = csrfTokenResponseDto.csrToken;
this.axios({
method : "POST",
url : SymfonyRoutes.SEND_TEST_MAIL,
data : ajaxFormData,
}).then( (response) => {
// handle with some popover
})
});
},
/**
* #description will return the csrf token which is required upon submitting the form (Internal Symfony Validation Logic)
*/
getCsrfToken(){
...
return promise;
}
},
components: {
CsrfTokenInputComponent
}
}
</script>
Long story short
This is how You need to pass down the prop to child
<CsrfTokenInputComponent :csrf-token="csrfToken"/>
NOT like this
<CsrfTokenInputComponent csrf-token="csrfToken"/>
Even if my IDE keep me telling me yeap i can navigate with that prop to child - vue could not bind it.
solution (testing ok)
In child component just using the props data, no need to re-assignment props's values to data, it will be cause update bug!
vue child component props update bug & solution
https://forum.vuejs.org/t/child-component-is-not-updated-when-parent-component-model-changes/18283?u=xgqfrms
The problem is not how to pass data with props, but rather how to do two things at almost the same time.
I have an user account component that can edit users (with an user id) and add users (without id).
A child component shows checkboxes for user<->company assignments, and needs the user id to prepare API calls when the user account is saved.
It is important that the child component shows before saving the user account, so that things can be selected before the user is saved and gets an id.
So it has no user id at first: the id is passed to the child component as 'null'.
It updates when the user is stored and gets an id.
But at this point, it takes a very short time for the child to get the new id into its model.
If you call a function in the child component that relies on data that was just changing, it might happen that the function executes before the data is updated.
For cases like this, nextTick() is your friend.
import { nextTick } from 'vue';
...
saveAccount() {
axios.post(URL, this.userModel).then((result)) {
// our model gets an id when persisted
this.userModel.id=result.data.id;
nextTick( () => {
this.$refs.childComponent.doSomething();
});
}
}
I need to pass value from tabs.ts to each page of tabs. So I have something like this:
constructor(public navParams: NavParams) {
...// config
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// If there's a user take him to the home page.
this.user = [];
this.user.push(user);
this.rootPage = HomePage;
} else {
// If there's no user logged in send him to the LoginPage
this.rootPage = LoginPage;
}
});
}
this.tab1Root = HomePage;
this.tab4Root = ProfilePage;
How to pass value (user) to each page of tabs? I tried with few combinations of this code but doesnt work (getting some erros - e.g If I put this.tab1Root... to onAuthStateChanged method, then it gives me: "Maximum call stack size exceeded"). Here are docs: http://ionicframework.com/docs/v2/api/components/tabs/Tab/ - I understand 90% of this but still dont know how I should pass this value...
My second question - is there any better way to take current user and pass him as value to each page? Will be better if I use provider or something?
Third question: it is good to have this code in tabs.ts than in app.ts?
Thanks!
You can use [rootParams] attribute in ion-tab
<ion-tab ... [rootParams]="user"></ion-tab>
In tab file:
constructor(navParams: NavParams) {
console.log("Passed params", navParams.data.user);
}
Second way is using events: http://ionicframework.com/docs/v2/api/util/Events/
It allows you to share data between any of your pages.
Provider is a good option.
It depends. Better way is to make an authorization once - using provider inside app.ts - when app starts.
I have a partial that show's a notification modal to agree to the site's terms and service that I would only like to show once (once they click I agree it goes away).
Is there anyway to do that with Meteor?
Assuming you want to store a boolean in the DB indicating that the user has accepted the terms (so they never get asked again), you could add a field called hasAcceptedTerms somewhere on the user object (e.g. in the user's profile). Once you do that you could write your template like this:
<template name="myTemplate">
{{#if areTermsVisible}}
(put terms partial here)
{{/if}}
</template>
Where areTermsVisible looks like:
Template.myTemplate.helpers({
areTermsVisible: function() {
var user = Meteor.user();
return user && user.profile && !user.profile.hasAcceptedTerms;
}
});
And the code to record the acceptance looks like:
Template.myTemplate.events({
'click .accept-terms': function() {
var userId = Meteor.userId();
var modifier = {$set: {'profile.hasAcceptedTerms': true}};
Meteor.users.update(userId, modifier);
}
});
Maybe not surprisingly, the best way to deal with cookies policy notification is by using cookies. The problem is not meteor-specific, but there are at least two good atmosphere packages that can help you to deal with the problem:
https://atmospherejs.com/mrt/cookies
https://atmospherejs.com/chuangbo/cookie
What you need to do is basically, set cookie
Cookie.set('userHasAcceptedPolicy', true, { year: 1 });
with whatever arguments you like, and as soon as the user clicks the "accept" button. Then, before you decide if you need to show the policy notification you can use:
Cookies.get('userHasAcceptedPolicy');
to see if there's a need to do so. So it's pretty much the same solution as #DavidWeldon suggested but it does not require referencing the Meteor.user() object, so the user does not need to have an account to accept the policy.
Please note, that - at least in case of mrt:cookies - Cookies.get is a reactive data source, which is quite helpful when it comes to rendering templates.
There's plenty of ways...
This isn't a Meteor specific question.
Template.notifications.events({
'click #close-modal': function(e, t) {
$('#modal').hide();
}
})
This is an extention to stackflow qustion. I want to create div programmatically and attach the opentok subscriber cam to it and all the subscriber cam need to come under parent div "#subscription". Please let me know what I'm doing wrong in my code.
Jsfiddle
http://jsfiddle.net/6z9n65ty/55/
Updated Jsfiddle
http://jsfiddle.net/6z9n65ty/59/
Code for subscription div
enyo.kind({
name : "blanc.SubscriberWrapper",
showing: true,
stream: null,
session: null,
create: function(){
this.inherited(arguments);
if(this.stream != null && this.session != null){
this.subscribe();
}
},
subscribe: function(){
this.session.subscribe(this.stream, this.hasNode());
}
});
Update
I want to attach the stream to subscribeButton, the below code will add the first subscriber stream to "#subscribeButton". If second subscribe stream exist, then it will overwrite previous subscribe cam video with second stream. I want to have all the subscribers to be viewed in the screen. So I was trying to create new div using create function.
streamCreated: function (event) {
this.session.subscribe(event.stream, subscribeButton);
// Add subscriber stream to a new div
// this.createComponent({kind: "blanc.SubscriberWrapper", name : event.stream.id, stream: event.stream, session: this.session});
// this.render();
},
JSFIDDLE
http://jsfiddle.net/6z9n65ty/62/
I believe the problem with your code is that hasNode() will return false (or is it null?) because the node isn't rendered yet. Change create: to rendered: and it may work.
The token in your fiddle had expired so I wasn't able to test it further but couldn't. Your code that creates the new component looks correct at first blush.
You didn't say what -did- happen when the code is executed. If you're just getting blank divs, then my assumption is correct.
I have this parent node with text and I want to add a media into the parent node also if the text is changed will update as well.
{
parent: {
text: 'this is content'
}
}
The media value from a third-party callback. How to pass it to update()? I tried {media: callbackVal} but not working.
$scope.parent = angularFireCollection(firebaseRef.child('parent'));
$scope.parent.update(What_to_do_here, function(error){
//something...
});
UPDATE
Maybe my question is not clear enough.
In Firebase JS, we can do this to update or insert media into the node.
new Firebase(firebaseRef).update({ media: 'value'} );
How to do this in `angularFireCollection ?
Check out the annotated AngularFire source: http://angularfire.com/src/angularFire.html#section-38
It looks like AngularFireCollection.update() takes a key/item and a callback function. So you need to edit the entry you want to update directly in your AngularFireCollection, (e.g. $scope.parent.getByName('media').value = 'alligator scrimshaw') and then call $scope.parent.update($scope.parent.getByName('media'), function(err) { ... }).
NOTE: One rather confusing thing (which maybe I am misunderstanding) is that there doesn't seem to be a way to add data to an AngularFireCollection by id. So if media doesn't already exist in your Firebase, $scope.parent.getByName('media') will return undefined. For this reason, if you don't need explicit syncing, and implicit syncing is fine, I would use an AngularFire object instead of an AngularFireCollection.