I am experiencing this weird scenario that I am unable to figure out what the problem is. There is a pagination for a collection which works fine when navigating. I have 5 documents in a collection with each to display per 2 on a page sing the pagination. Each document has a url link that when clicked it displays the full page for the document.
The challenge now is that if I click a document on the first page, it displays the full record, but if I navigate to the next page and click a document, it displays a blank page. I have tried all I could but haven't gotten what is to be made right.
These earlier posts are a build up to this present one: Publish and subscribe to a single object Meteor js, Meteor js custom pagination.
This is the helper
singleSchool: function () {
if (Meteor.userId()) {
let myslug = FlowRouter.getParam('myslug');
var subValues = Meteor.subscribe('SingleSchool', myslug );
if (myslug ) {
let Schools = SchoolDb.findOne({slug: myslug});
if (Schools && subValues.ready()) {
return Schools;
}
}
}
},
This is the blaze template
<template name="view">
{{#if currentUser}}
{{#if Template.subscriptionsReady }}
{{#with singleSchool}}
{{singleSchool._id}}
{{singleSchool.addschoolname}}
{{/with}}
{{/if}}
{{/if}}
</template>
try this;
onCreated function:
Template.view.onCreated(function(){
this.dynamicSlug = new ReactiveVar("");
this.autorun(()=>{
// When `myslug` changes, subscription will change dynamically.
this.dynamicSlug.set(FlowRouter.getParam('myslug'));
Meteor.subcribe('SingleSchool', this.dynamicSlug.get());
});
});
Helper
Template.view.helpers({
singleSchool(){
if (Meteor.userId()) {
let school = SchoolDb.findOne({slug: Template.instance().dynamicSlug.get()});
if (school) {
return school;
}
}
}
});
Related
I have a nested template, using a ReactiveDict to store the data, which is an object that includes variables (color, type...) and an array of children nodes.
I'm having an issue on refresh: the array displays reactively, but when I update the array, it does not properly render.
in short (cleaned up code):
<body>
{{#with data}}
{{>nested}}
{{/with}}
</body>
<template name="nested">
<div>{{color}}<div>
<div class="ui dropdown">
<!-- drop down stuff goes here-->
</div>
{{#if children}}
{{#each children}}
{{>nested scope=this}}
{{/each}}
{{/if}}
</template>
Template.body.helpers({
"data": { color: "blue",
children: [{color: "green", children: [{color: "teal"}]},
{color:"red", children:[{color: "cyan"}],{color: "magenta"}]]}}
})
Template.nested.onCreated(function(){
this.scope = new ReactiveDict();
this.scope.set('scope', this.data.scope);
})
Template.nested.helpers({
"color": function () { Template.instance().scope.get('scope').color;},
"children": function () {
return Template.instance().scope.get('scope').children;
}
})
Template.nested.events({
"click .ui.dropdown > .menu > .item": function(e, t) {
e.preventDefault();
e.stopPropagation();
var data = t.scope.get('scope');
//do processing stuff here...
updatedArray = myFunction();
data['children'] = updatedArray;
t.scope.set('scope', data);
}
})
So what's happening is that on update the elements alreayd present do not update, and if there are elements added they show up.
if there are elements removed, they elements will be removed but the data in the variables (color here) does not get updated.
To make it work so far, I had to do the following:
Template.nested.events({
"click .ui.dropdown > .menu > .item": function(e, t) {
e.preventDefault();
e.stopPropagation();
var data = t.scope.get('scope');
//do processing stuff here...
updatedArray = myFunction();
delete data.children;
t.scope.set('scope', data);
Meteor.setTimeout(function() {
data['children'] = updatedArray;
t.scope.set('scope', data);
},10);
}
})
That works but it's a total hack forcing the array to nothing and then refreshing after a short timeout.
How am I supposed to do this the proper way?
PS: I tried using allDeps.changed() on the ReactiveDict, and i tried forcing a re-render but it's in the render loop so it won't render the view twice.
Can't seem to understand why the array elements are not updated. I know when using collections MiniMongo checks for _id's of the objects to see if they changed or not, but there are no _id in my objects. I also tried to add one but without much luck
well I guess I asked just before figuring it out...
the '_id' thing did the trick. I had tried before but I was actually using the same _id for the same elements so they did not appear to change (duh!)
So by adding a { "_id": Meteor.uuid() } in my generated objects, the update works fine.
I'm attempting to reuse a custom Block Helper that I wrote to provide basic carousel functionality to some of my templates.
simple-carousel.html
<template name="SimpleCarousel">
<div class="simple-carousel {{class}}">
<div class="slides">
{{#each slides}}
{{> UI.contentBlock this}}
{{/each}}
</div>
{{#if showControls}}
{{> SimpleCarouselControls}}
{{/if}}
</div>
</template>
<template name="SimpleCarouselControls">
// control structure here
</template>
simple-carousel.js
var actions = {
back: function() {
// move slide back once
},
forward: function() {
// move slide forward once
}
};
var showSlide = function() {
// code to show the next slide
};
Template.SimpleCarousel.onRendered(function() {
// set up carousel logic here
});
Template.SimpleCarousel.events({
'click [data-sc-move="forward"]': function() {
actions.forward();
},
'click [data-sc-move="back"]': function() {
actions.back();
}
});
breaking_stories.html
<template name="BreakingStories">
{{#SimpleCarousel class="breaking-stories" showControls=false autoForward=8000 slides=breakingStories}}
{{> BreakingStorySlide}}
{{/SimpleCarousel}}
</template>
<template name="BreakingStorySlide">
<div class="breaking-story slide">
<div class=breaking-story-title">{{title}}</div>
</div>
</template>
breaking_stories.js
Template.BreakingStories.helpers({
breakingStories: function() {
return BreakingStories.find();
}
});
daily_summary.html
<template name="DailySummary">
{{#with thisDailySummary}}
{{#SimpleCarousel class="daily-summaries" showControls=true slides=items}}
{{> DailySummarySlide}}
{{/SimpleCarousel}}
{{/with}}
</template>
<template name="DailySummarySlide">
<div class="daily-summary slide">
<div class="daily-summary-title">{{title}}</div>
</div>
</template>
I've tried to simplify the code as there is a lot more HTML involved in the templates. Anyway, as you can see I've defined the #SimpleCarousel block helper and used it in two places: the breaking stories section, and the daily summaries section. These two templates happen to be on the same page (route), so they are near each other on the page. I need one of them to auto cycle through, in which I've provided the autoForward property to the helper, and the other one should just show controls.
Both templates render fine and show the correct data, but the problem lies in that instead of the breaking news template doing any automatic cycling, the other one does (and does it twice), as if they are sharing the same context.
My question is, can I use custom Block Helpers multiple times on the same route safely? I'm open to any suggestions on how to do this a better/different way.
Thanks to #JeremyK for pointing me in the right direction; it happened to be the exact code I left out which was the problem. Of course!
Here's what I had in the old version:
simple_carousel.js
var $slideContainer, $controls, $markers, $activeSlide, $nextSlide;
var actions = {
back: function() {
// move slide back
},
forward: function() {
// move slide forward
}
};
function showSlide() {
// show the "next" slide
}
Template.SimpleCarousel.onRendered(function() {
var data = this.data;
$slideContainer = this.$('.sc-slides');
// rest of this code is irrelevant
});
I had thought that the variables I had declared on the first line were independent of multiple instantiations of the templates I was using, but I was wrong. The first use of $slideContainer = this.$('.sc-slides'); worked fine, but $slideContainer and all the others are shared.
To fix this, I simply moved the local variables/actions into Template.SimpleCarousel.onRendered
Template.SimpleCarousel.onRendered(function() {
var $slideContainer, $markers, ...
this.actions = {
//...
};
});
Template.SimpleCarousel.events({
'click [data-sc-move="forward"]': function( event, template ) {
template.actions.forward();
}
//...
});
I'm trying to get the higher height of some the "big-card" in my DOM to put them all at the same height.
{{#each skills}}
<div class="big-card">
<div class="card-grid add-option-part">
<div class="card-text">
<p>{{this}}</p>
</div>
</div>
<div class="option-part">
<div class="half-option-part white-line-part"><img class="seemore-button" src="/img/expand.png"/></div>
<div class="half-option-part">{{> StarsRating}}</div>
</div>
</div>
{{/each}}
The function to take get their heights is :
function boxContentNormal(){
var elementHeights = [];
$('.big-card').map(function() {
var currentItem = $(this).find('.card-text');
var currentItemHeight = currentItem.height();
var currentItemPaddingTop = parseInt(currentItem.css('padding-top').replace("px", ""));
var currentItemPaddingBottom = parseInt(currentItem.css('padding-bottom').replace("px", ""));
elementHeights.push(currentItemHeight + currentItemPaddingBottom + currentItemPaddingTop);
});
var maxHeight = 0;
$.each(elementHeights, function(i, element){
maxHeight = (element > maxHeight) ? element : maxHeight;
});
console.log("Max height : "+maxHeight);
}
It's called by that :
Template.MyTemplate.onRendered(function(){
boxContentNormal();
$(window).resize(function(){
boxContentNormal();
});
});
This function is used when a new route is called and the template will be displayed at the same time.
It works like that:
I click on a link that goes to a new route
Once arrived to the route, the template will be displayed
When the template is rendered, the function is called for the first time
After that, if the window resizes the function will be called again
The problem is at the third step, when the function is called it doesn't get the height of the cards. Then all the heights are equal to 0. And when I resize the window, it works fine.
So I think the function is called too early and the "cards" don't exist yet. Do you know how I can "wait" for them or another solution ?
Thanks :)
I suppose your skills helper is returning a cursor from a client side collection synced with the server via the Pub/Sub mechanism.
You can use the template controller pattern along with template subscriptions to make sure your template is initially rendered after the published data made its way to the client.
HTML
<template name="skillsController">
{{#if Template.subscriptionsReady}}
{{> skillsList items=skills}}
{{/if}}
</template>
<template name="skillsList">
{{#each items}}
{{! skill item}}
{{/each}}
</template>
JS
Template.skillsController.onCreated(function(){
this.subscribe("skills");
});
Template.skillsController.helpers({
skills: function(){
return Skills.find();
}
});
Template.skillsList.onRendered(function(){
console.log(this.$(".big-card").length == this.data.items.count());
});
Using this pattern, the skillsList template onRendered life cycle event is executed after the data is already there so the {{#each}} block helper will correctly render its initial list of skill items.
If you don't wait for the subscription to be ready, the initial template rendering will run using an {{#each}} fed with an empty cursor. Once the data arrives, the {{#each}} will rerun and correctly render the items, but the onRendered hook won't.
My intention is to retrieve one random entry from a collection and display it on the website - if all sentences are through (read: the user has "seen" them), display something else (therefore a dummy sentence gets returned). But, on server start and on button-click events, this helper gets fired at least twice. Here is some code:
In client.js:
Template.registerHelper('random_sentence', function() {
fetched = _.shuffle(Sentences.find({
users: {
$nin: [this.userId]
}
}).fetch())[0];
if (fetched === undefined) {
return {
sentence: "done",
_id: 0,
done: true
};
}
Session.set('question', fetched._id);
console.log(fetched);
return fetched;
});
The helper function for the template:
sent: function(){
sent = Session.get('question');
return Sentences.findOne(sent);
}
in main template:
{{#with random_sentence}}
{{#if done}}
<!-- Display something else -->
{{else}}
<div class="container">
{{> question}}
</div>
{{/if}}
{{/with}}
the "question" template:
<div class="well">
<div class="panel-body text-center">
<h3>{{sent.sentence}}</h3>
</div>
</div>
If I don't return anything in the "random_sentences"-function,nothing get's displayed.
I don't know where my "logic failure" is situated? I'm new to meteor - so I might overlook something obvious.
Thanks in advance :-)
UPDATE: This is how I intended to get the new sentence and display it:
Template.answer.events({
'click': function(event) {
var text = event.target.getAttribute('id');
if (text !== null) {
var question = Session.get('question');
var setModifier = {
$inc: {}
};
setModifier.$inc[text] = 1;
Sentences.update(question, setModifier);
Meteor.call('update_user', question);
Notifications.success('Danke!', 'Deine Beurteilung wurde gespeichert.');
Blaze.render(Template.question, document.head);
}
}
});
In server.js (updating the question and a counter on the user):
Meteor.methods({
update_user: function(question) {
Sentences.update(question, {
$push: {
"users": this.userId
}
});
Meteor.users.update({
_id: this.userId
}, {
$inc: {
"profile.counter": 1
}
});
},
});
I found the Blaze.render function somewhere on the web. the "document.head" part is simply because this function needs a DOM Element to render to, and since document.body just "multiplies" the body, I ust moved it to the head. (DOM logic isn't my strong part).
An Idea I had: would it make the whole idea simpler to implement with iron-router? atm. I wanted to create a "one-page app" - I therefore thought that I don't need a router there.
Another problem: Getting this logic to work (User gets one random sentence, which he has not seen) and publishing small sets of the collection (so the Client don't have to download 5 MB of data before using).
Template helpers can be called multiple times so it's good to avoid making them stateful. You're better off selecting the random entry in an onCreated or onRendered template handler. There you can do your random select, update the state, and put your choice in a Session variable to be retrieved by the helper.
I am quite new with Meteor but have really been enjoying it and this is my first reactive app that I am building.
I would like to know a way that I can remove the .main element when the user clicks or maybe a better way would be to remove the existing template (with main content) and then replace with another meteor template? Something like this would be simple and straightforward in html/js app (user clicks-> remove el from dom) but here it is not all that clear.
I am just looking to learn and for some insight on best practice.
//gallery.html
<template name="gallery">
<div class="main">First run info.... Only on first visit should user see this info.</div>
<div id="gallery">
<img src="{{selectedPhoto.url}}">
</div>
</template>
//gallery.js
firstRun = true;
Template.gallery.events({
'click .main' : function(){
$(".main").fadeOut();
firstRun = false;
}
})
if (Meteor.isClient) {
function showSelectedPhoto(photo){
var container = $('#gallery');
container.fadeOut(1000, function(){
Session.set('selectedPhoto', photo);
Template.gallery.rendered = function(){
var $gallery = $(this.lastNode);
if(!firstRun){
$(".main").css({display:"none"});
console.log("not");
}
setTimeout(function(){
$gallery.fadeIn(1000);
}, 1000)
}
});
}
Deps.autorun(function(){
selectedPhoto = Photos.findOne({active : true});
showSelectedPhoto(selectedPhoto);
});
Meteor.setInterval(function(){
selectedPhoto = Session.get('selectedPhoto');
//some selections happen here for getting photos.
Photos.update({_id: selectedPhoto._id}, { $set: { active: false } });
Photos.update({_id: newPhoto._id}, { $set: { active: true } });
}, 10000 );
}
If you want to hide or show an element conditionaly you should use the reactive behavior of Meteor: Add a condition to your template:
<template name="gallery">
{{#if isFirstRun}}
<div class="main">First run info.... Only on first visit should user see this info.</div>
{{/if}}
<div id="gallery">
<img src="{{selectedPhoto.url}}">
</div>
</template>
then add a helper to your template:
Template.gallery.isFirstRun = function(){
// because the Session variable will most probably be undefined the first time
return !Session.get("hasRun");
}
and change the action on click:
Template.gallery.events({
'click .main' : function(){
$(".main").fadeOut();
Session.set("hasRun", true);
}
})
you still get to fade out the element but then instead of hiding it or removing it and having it come back on the next render you ensure that it will never come back.
the render is triggered by changing the Sessionvariable, which is reactive.
I think using conditional templates is a better approach,
{{#if firstRun }}
<div class="main">First run info.... Only on first visit should user see this info.</div>
{{else}}
gallery ...
{{/if}}
You'll have to make firstRun a session variable, so that it'll trigger DOM updates.
Meteor is reactive. You don't need to write the logic for redrawing the DOM when the data changes. Just write the code that when X button is clicked, Y is removed from the database. That's it; you don't need to trouble yourself with any interface/DOM changes or template removal/redrawing or any of that. Whenever the data that underpins a template changes, Meteor automatically rerenders the template with the updated data. This is Meteor’s core feature.