I have the following template code:
<template name="dashboard">
<div class="content-container-with-sidebar clearfix">
<div class="dashboard-container">
{{#if Template.subscriptionsReady}}
<div id=masonry-grid class="masonry-grid clearfix">
{{> timePieChartCard}}
{{> expensePieChartCard}}
{{> HighlightsCard}}
{{> initializeMasonry}}
</div>
{{/if}}
</div>
</div>
</template>
I'd like to run the initializer for the masonry library after all the templates and their subscriptions/helpers load within the main dashboard template.
Each card has code similar to this:
<template name="HighlightsCard">
{{#if highlightsExist}}
<div class="col-md-6 masonry-grid-item">
<div class="card highlights-card">
</div>
</div>
{{/if}}
</template>
Template.highlightsCard.onCreated(function() {
this.autorun(() => {
this.subscribe('userOwnClientHighlightsData');
});
});
Template.highlightsCard.helpers({
highlights() {
return Highlights.find({}, {
limit: 4,
sort: {createdAt: -1}
}).fetch();
},
highlightsExist() {
return (Highlights.find().count() > 0);
}
});
I currently am setting a session that increases when each onRendered is run within the card templates, but once they all render their contents have still not loaded. I need the class masonry-grid-item to be visible in order for me to initialize masonry.
What's the best way to get a similar result as jquery's $(document).ready() ?
Update: each individual card in the dashboard template has it's own subscription to a different dataset.
Look at Tracker.afterFlush
Schedules a function to be called during the next flush
For example:
Template.dashboard.onRendered(()=>{
Tracker.afterFlush(()=>{
...your code here
});
});
If I understand the problem correctly, you want to initialize your component after the subscription is complete and after all sub-templates (that also depend on the subscription data) are rendered.
Since I don't know what your JavaScript looks like, here is an example of what you can try.
Template.dashboard.onRendered(function() {
this.isReady = new ReactiveVar(false);
this.subscribe('subscriptionName', () => {
this.isReady.set(true);
Tracker.afterFlush(() => {
// Initialize component here
});
});
});
Template.dashboard.helpers({
isReady: () => {
return Template.instance().isReady.get();
},
});
And then use the isReady ReactiveVar in your template. In this case, isReadymay not be necessary (e.g. you could probably use the Template.subscriptionsReady helper), but I like to have explicit control of when to render incase my logic gets more complex in the future. Note, I removed {{> initializeMasonry}} because it looked like you were attempting to initialize your component via a helper.
<template name="dashboard">
<div class="content-container-with-sidebar clearfix">
<div class="dashboard-container">
{{#if isReady}}
<div id=masonry-grid class="masonry-grid clearfix">
{{> timePieChartCard}}
{{> expensePieChartCard}}
{{> HighlightsCard}}
</div>
{{/if}}
</div>
</div>
</template>
The callback to Tracker.afterFlush will get executed on the next Blaze render cycle, which will be after your sub-templates are rendered.
Related
I'm implementing an application with Vue Js and I've the following code:
<template>
<simple-page title="list-patient" folder="Patient" page="List Patient" :loading="loading">
<list-patients #patientsLoaded="onPatientsLoaded"/>
</simple-page>
</template>
Both simple-page and list-patients are custom components created by me. Inside ListPatients I've an HTTP request on Create callback, as follows:
created() {
axios.get("...").then(response => {
...
this.$emit('patientsLoaded');
})
},
Then, my objective is to handle the patientsLoaded event and uptade the loading prop on the top parent component, as follows:
data() {
return {
loading: true
}
},
methods: {
onPatientsLoaded(params) {
this.loading = false;
}
}
However, the created method is not being triggered inside the list-patients component. The only way I can make this work is by removing :loading.
Any one can help?
Edit 1
Code of simple page:
<template>
<section :id="id">
<!-- Breadcrumb-->
<breadcumb :page="page" :folder="folder"/>
<!-- Breadcrumb-->
<!-- Simple Card-->
<simple-card :title="page" :icon="icon" :loading="loading" v-slot:body>
<slot>
</slot>
</simple-card>
<!-- Simple Card-->
</section>
</template>
Code of simple card:
<b-card>
<!-- Page body-->
<slot name="body" v-if="!loading">
</slot>
<!--Is loading-->
<div class="loading-container text-center d-block">
<div v-if="loading" class="spinner sm spinner-primary"></div>
</div>
</b-card>
Your list-patients component goes in the slot with name "body". That slot has a v-if directive so basically it is not rendered and hooks are not reachable as well. Maybe changing v-if to v-show will somehow help you in that situation. Anyway, you have deeply nested slots and it is making things messy. I usually declare loading variable inside of the component, where fetching data will be rendered.
For example:
data () {
return {
loading: true;
};
},
mounted() {
axios.get('url')
.then(res => {
this.loading = false;
})
}
and in your template:
<div v-if="!loading">
<p>{{fetchedData}}</p>
</div>
<loading-spinner v-else></loading-spinner>
idk maybe that's not best practise solution
v-slot for named slots can be indicated in template tag only
I suppose you wished to place passed default slot as body slot to simple-card component? If so you should indicate v-slot not in simple-card itself but in a content you passed it it.
<simple-card :title="page" :icon="icon" :loading="loading">
<template v-slot:body>
<slot>
</slot>
</template>
</simple-card>
I'm attempting to create components using Vue, so that I can remove a lot of duplicated HTML in a site I'm working on.
I have a <ym-menucontent> component, which within it will eventually have several other components, conditionally rendered.
While doing this I've hit a wall and so have simplified everything to get to the root of the problem.
When rendering the ym-menucontent component the first sub-component is the only one which gets rendered and I can't work out why or how to get around it...
<template id="menucontent">
<div>
<ym-categories :menuitem="menuitem"/>
<ym-rootmaps :menuitem="menuitem"/>
<p>1: {{menuitem.rootMapsTab}}</p>
<p>2: {{menuitem.exploreTab}}</p>
</div>
</template>
<template id="rootmaps">
<div>Root Maps</div>
</template>
<template id="categories">
<div>Categories</div>
</template>
app.js
Vue.component('ym-menucontent', {
template: '#menucontent',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON
}
}
});
Vue.component('ym-rootmaps', {
template: '#rootmaps',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON,
rootMaps: window.rootAreas
}
}
});
Vue.component('ym-categories', {
template: '#categories',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON,
rootMaps: window.rootAreas
}
}
});
usage...
<div
v-for="mi in customMenu.topLevelMenuItems"
:id="mi.name"
class="page-content tab swiper-slide">
<ym-menucontent :menuitem="mi"/>
</div>
Output
<div>Categories</div>
if I switch around ym-cateogries and ym-rootmaps then the output becomes...
<div>Root Maps</div>
if I remove both then I see...
<p>1: true</p>
<p>2:</p>
I'd expect to see a combination of all of them...
<div>Categories</div>
<div>Root Maps</div>
<p>1: true</p>
<p>2:</p>
This is probably because you're using self-closing components in DOM templates, which is recommended against in the style-guide ..
Unfortunately, HTML doesn’t allow custom elements to be self-closing -
only official “void” elements. That’s why the strategy is only
possible when Vue’s template compiler can reach the template before
the DOM, then serve the DOM spec-compliant HTML.
This should work for you ..
<template id="menucontent">
<div>
<ym-categories :menuitem="menuitem"></ym-categories>
<ym-rootmaps :menuitem="menuitem"></ym-rootmaps>
<p>1: {{menuitem.rootMapsTab}}</p>
<p>2: {{menuitem.exploreTab}}</p>
</div>
</template>
<div
v-for="mi in customMenu.topLevelMenuItems"
:id="mi.name"
class="page-content tab swiper-slide">
<ym-menucontent :menuitem="mi"></ym-menucontent>
</div>
I am new at meteor and I am having some issues. I am creating a social network app and what it basically does is signs up user and the user can post and follow others. Thats the basic functionality it does right now. I want to add something that when a user clicks on other users profile it shows that users post. But the code isnt working and doesnt show any error at all
Template
<template name="profileArea">
{{#if currentUser}}
<div id="side-profile" class="side-box">
<a class="filter-user">{{currentUser.username}}</a>
</div>
{{/if}}
<div id="side-all" class="side-box">
<a class="community">Community</a>
</div>
{{#if currentUser}}
<div id="side-like" class="side-box">
<h3>Following</h3>
<div class="boxcontent">
{{#each username in following}}
<div>
<a class="filter-user">{{username}}</a>
</div>
{{/each}}
</div>
</div>
<div id="side-likeyou" class="side-box">
<h3>Follows You</h3>
<div class="boxcontent">
{{# each followers}}
<div>
<a class="filter-user">{{username}}</a>
</div>
{{/each}}
</div>
</div>
{{/if}}
</template>
Code:
Template.profileArea.events({
'click .filter-user': function(event){
event.preventDefault();
var selectedUser = event.target.text;
Session.set('username', selectedUser);
},
'click .community': function(event){
event.preventDefault();
Session.set('username', null);
}
});
Template.postsList.helpers({
posts: function() {
//Stuff should happen here but its not -_-
var result;
if(Session.get('username')){
result = Post.find({username: Session.get('username')}, {sort:{created: -1}});
}
else{
result = Post.find({}, {sort:{created: -1}});
}
return result;
}
});
The problem in this case is that you have never actually rendered your postsList template (you have only just defined it).
If you want to actually see postsList you need to call {{> postsList }} from somewhere in your profileArea's HTML. This will render that template and then your postsList.posts helper will execute (and change reactively when Session.get('username')) changes.
Maybe add this after the 'Follows You' section of the profile.
<div id="side-posts" class="side-box">
<h3>Posts</h3>
<div class="boxcontent"> {{> postsList }} <div>
</div>
I'm attempting to use a dynamic template to create a virtual number pad; this works by passing rows of buttons to another template that renders them. The issue I'm having is the template cannot use Templates.parentData() to access the context as it becomes undefined. Moving up in the inheritance by using Templates.parentData(2) or (3) does not function either.
Template.bs_num_pad.helpers({
'number_rows' : function(){
var result = [];
console.log(this);
console.log(Template.currentData());
// true
console.log(Template.parentData(1));
// Template viewName="Template.bs_num_pad"
console.log(Template.parentData(-2));
// true
console.log(Template.parentData(-3));
// true
}
});
<!-- begin snippet: js hide: false -->
<template name="bs_num_pad">
<div class="container bsNumPadNumber">
<span style="display:block;text-align:center;">{{getBsNumPadNumber}}</span>
{{#each number_rows}}
<div class="col-3">
{{>bs_buttonset}}
</div>
{{/each}}
</div>
</template>
The number pad template that references another template to generate the bootstrap buttonset.
<template name="bs_buttonset">
{{!Template.dynamic template="bs_buttonset" data=difficultyOptions }}
<div class="btn-group btn-group-md" style="text-align:center;display:inline-block;" role="group">
{{#each this}}
<button id="{{btnId}}" value={{value}} class="btn {{btnClass}}" type="button">
{{#if btnIcon}}<i class="glyphicon {{btnIcon}}"></i>{{/if}}{{#if btnText}}{{btnText}}{{/if}}
</button>
{{/each}}
</div>
</template>
Template.parentData(-2) should return the contents of optionsModal; instead it returns 'true'.
I want to break a big web page up into bite-size chunks for the user, so that they are not overwhelmed by the size of the page / the amount of information they have to enter (all at once, anyway).
I'm using Meteor, and would like to know how to accomplish this with that platform.
The first idea that comes to mind is to create a separate template for each "sub page" I want to show the user, and then present the templates one after another. I'm thinking maybe something like this:
<body>
<h2>Thirteen Ways of Looking at a Duckbilled Platypus</h2>
<br/>
<br/>
<div class="container">
{{> whizzardBlizzard}}
</div>
</body>
<template name="whizzardBlizzard">
<form>
{{#if firstStep}}
{{> firstStepTemplate}}
{{/if}}
{{#if secondStep}}
{{> secondStepTemplate}}
{{/if}}
{{#if thirdStep}}
{{> thirdStepTemplate}}
{{/if}}
<input type="submit" value="Submit" class="button">
</form>
</template>
<template name="firstStepTemplate">
<h2>Step 1</h2>
</template>
<template name="secondStepTemplate">
<h2>Step 2</h2>
</template>
<template name="thirdStepTemplate">
<h2>Step 3</h2>
</template>
The submit event handler would be a template event method that would save the data entered at each step and update the value of a "counter", something like:
Template.whizzardBlizzard.events({
"submit form": function (event) {
//event.preventDefault(); <= should this be uncommented?
// save the vals to a new document in a collection
Session.set('stepNum', Session.get('stepNum') + 1);
}
});
The "firstStep," "secondStep," etc. would perhaps be template helpers, such as:
Template.whizzardBlizard.helpers({
firstStep: function() {
return (Session.get('stepNum') == 1);
},
secondStep: function() {
return (Session.get('stepNum') == 2)
},
thirdStep: function() {
return (Session.get('stepNum') == 3)
}
. . . // etc.
});
...but am not sure if this will really fill the bill or be the best approach. Is there a "blessed" way of doing this with Meteor, or an accepted pattern?