javascript get value of Mongo field already rendered - Meteor - javascript

Hey everyone, thank you very much for your help. Question is edited per suggestions in the comments.
I'm new to Mongo and Meteor.
I have a collection "posts" with a field "slug".
The "post" template is populating correctly with each post's values. Slug value is always something like "my-great-post".
I need to get the text value for the _id's slug, which will be different each time the template is accessed, encode it, write a string, and spit the string back out into the template.
Things tried
can't return a value for "this.slug" or "this.data.slug" in either template helpers or onRendered, even though collection is defined and correctly populating spacebars values in the template
"this" returns "[object Object]" to console.log
app crashes when I try to javascript encode and deliver a string from the helper, probably I don't fully understand helper syntax from the documentation
(I followed advice in the comments to avoid trying to create scripts in the template html, so below is more information requested by everyone helping on this thread)
- Template html -
{{#with post}}
<div class="blog-article">
<div class="blog-header">
<div class="left">
<!-- title -->
<h1 class="post-title">{{title}}</h1>
<div class="holder">
<div class="post-tags">
<!-- tags -->
{{#each tags}}
<span>{{this}}</span>
{{/each}}
</div>
</div>
</div>
</div>
<div class="blog-post">
<div class="blog-copy">
<!-- date -->
<div class="post-date">{{post_date}}</div>
<!-- social -->
<div class="blog-social">
<!--
<a class="so-facebook" target="_blank" href="need to encode slug here"></a>
-->
</div>
<!-- ============== post ============== -->
{{{content}}}
<!-- ============ end post ============ -->
</div>
</div>
</div>
{{/with}}
- Template js -
Template.post.onCreated(function() {
var self = this;
self.autorun(function() {
var postSlug = FlowRouter.getParam('postSlug');
self.subscribe('singlePost', postSlug);
});
});
Template.post.helpers({
post: function() {
var postSlug = FlowRouter.getParam('postSlug');
var post = Posts.findOne({slug: postSlug}) || {};
return post;
}
// can't get these working in a helper, out of helper they crash the app
// console.log(this.slug);
// console.log(this.data.slug);
});
Template.post.onRendered( function () {
// these do not work
// console.log(this.slug);
// console.log(this.data.slug);
});
db.posts.findOne();
{
"_id" : ObjectId("576c95708056bea3bc25a91f"),
"title" : "How Meteor Raised the Bar For New Rapid-Development Technologies",
"post_date" : "May 28, 2016",
"image" : "meteor-raised-the-bar.png",
"slug" : "how-meteor-raised-the-bar",
"bitlink" : "ufw-29Z9h7s",
"tags" : [
"Tools",
"Technologies"
],
"excerpt" : "sizzling excerpt",
"content" : "bunch of post content html"
}
If some one can solve this using any method, I will accept answer with joy and gratitude most intense.

The problem is probably with the parent template, rather than this one. The way that Meteor works is that the JS files are separated from the HTML, so don't try to include a <script> tag in the HTML.
The first thing is that you have to load all of your documents into the client. (NOTE: once you've got the hang of that, then you can worry about only loading the documents that you need).
To do that, you need a collection and a publication. By default all collections are automatically published completely, so unless you removed the autopublished module, then I'll assume that it is still loaded.
So let's start with the parent template. In this case, I'm going to just loop through all of the posts in the collection and display them using the innerTemplate.
<template name=parent>
<ul>
{{#each post}}
{{> innerTemplate}}
{{/each}}
</ul>
</template>
And now our inner template might look like this:
<template name=innerTemplate>
<li>{{slug}}</li>
</template>
The end result will be a simple list with each slug.
Now, to link everything together, we need to create a JS file, which will:
1. define the collection on both client and server
2. pass the collection to the parent template
This file should be accessible to both the client and the server.
posts = new Mongo.Collection('posts');
if(Meteor.isClient) {
Template.parent.helpers({
posts() {
return Posts.find();
}
});
}
Now, if you want to do something with 'slug' in the JS file, you could do something like this:
if(Meteor.isClient) {
Template.innerTemplate.helpers({
upperCaseSlug() {
return this.slug.toUpperCase();
}
});
}
Then, you could refer to upperCaseSlug in your template, like thus:
<template name=innerTemplate>
<li>{{upperCaseSlug}}</li>
</template>

A few things about Meteor:
You should never see a pattern such as:
<script type="text/javascript">
...some code
</script>
Because Meteor combines all your js files into one big file and includes it automatically in your app. You should never have to declare your own script in this way.
Secondly, you should never have to get the value of a data object by reading the DOM. The data context of each template gives you your data in the variable this.
In either a helper or template event you can refer to this and be assured that you're going to get exactly the data being displayed in that instance of the template.
Having now seen your template code it's now apparent that your template has no data context - you set the data context inside your {{#with post}} and its associated helper but that doesn't end up creating the this you need one level below.
So... #Nathan was on the right track except that he assumed you were iterating over a cursor instead of just looking at a single post.
Take all html you have between your {{#with post}} and {{/with}} and put it in a new template, say postDetail then make your outer template:
<template name="post">
{{#with post}}
{{> postDetail}}
{{/with}}
</template>
Now your postDetail template will get a data context equal to the post object automatically and your helpers can refer to this safely.
Template.postDetail.helper({
slugURI{
return "/"+encodeURI(this.slug);
}
});
Then in your postDetail template you can get the encoded slug with:
<a class="so-facebook" target="_blank" href={{slugURI}}>

Related

How to execute a callback after an #each is done?

I'm having trouble with a callback after the #each has finished. I have a template named "content":
<template name="content">
{{#if Template.subscriptionsReady}}
{{#each currentData}}
<div data-cid="{{this._id}}"></div>
{{/each}}
{{else}}
Loading...
{{/if}}
</template>
At first I wait for a subscription, when this is available, I iterate through my Collection with {{#each}} and append the div. What I need is a sort of callback for when the for-each loop is done (in other words DOM ready).
Template.content.onRendered()
-> triggers to early
I also tried appending an image after the {{each}} and fire a function in its onload like this:
<img style="height:0;width:0" src="*mysource*" onload="callback()">
-> did work sometimes but not reliable somehow
Is there a way to get this callback? I do not fear to change the structure of this template, if that brings the solution.
There's no easy way to get notified when a Spacebars {{#each}} block has done rendering into the DOM every item getting iterated over.
The best solution is to use another reactive computation (Tracker.autorun) to observe your (reactive) current data.
Everytime your current data (which is likely a cursor) is modified, you can run arbitrary code after every other reactive computations are done performing whatever their job is, using Tracker.afterFlush.
The {{#each}} block is one of those computations, whose role is to listen to the reactive data source you give it as argument and rerender its Template.contentBlock as many times as items fetched from the source being iterated over, with the current item as current data context.
By listening to the exact same reactive data source as the {{#each}} block helper and running your code AFTER it has finished its own reactive computation, you can get the actual requested behavior without relying on some weird tricks.
Here is the full implementation of this pattern :
JS
Template.content.helpers({
currentData: function(){
return Template.currentData();
}
});
Template.content.onRendered(function(){
this.autorun(function(){
var cursor = Template.currentData();
// we need to register a dependency on the number of documents returned by the
// cursor to actually make this computation rerun everytime the count is altered
var count = cursor.count();
//
Tracker.afterFlush(function(){
// assert that every items have been rendered
console.log(this.$("[data-cid]") == count);
}.bind(this));
}.bind(this));
});
I had a similar problem and after a lot of searching found the following solution. I tried using Tracker, onRendered and other tricks, none of them worked. This could be considered more of a hack, but works. Unfortunately can't remember where I found this solution initially.
Start with your template, but add an template tag after your each.
<template name="content">
{{#if Template.subscriptionsReady}}
{{#each currentData}}
<div data-cid="{{this._id}}"></div>
{{/each}}
{{doneTrigger}}
{{else}}
Loading...
{{/if}}
</template>
Then define a helper that returns null.
Template.content.helpers({
doneTrigger: function() {
Meteor.defer(function() {
// do what you need to do
});
return null;
}
});
You can read more about Meteor.defer() here, but it is equivalent to using a 0 millisecond setTimeout.
You can also use sub-templates and count the number of sub-templates rendered. If this number is the number of items in the collection, then all are rendered.
<template name="content">
{{#if Template.subscriptionsReady}}
{{#each currentData}}
{{> showData}}
{{/each}}
{{else}}
Loading...
{{/if}}
</template>
<template name="currentData">
<div data-cid="{{this._id}}"></div>
</template>
With that, initialize a reactive variable and track it:
var renderedCount = new ReactiveVar(0);
Tracker.autorun(function checkIfAllRendered() {
if(renderedCount.get() === currentData.count() && renderedCount.get() !== 0) {
//allDataRendered();
}
});
When the currentData template is rendered, increment it, and decrement it when it is destroyed.
Template.currentData.onRendered(function() {
renderedCount.set(++renderedCount.curValue);
});
Template.currentData.onDestroyed(function() {
renderedCount.set(--renderedCount.curValue);
});
A possibly simpler approach to consider - create a template around your #each block and then get an onRendered event afterwards:
html:
<template name="content">
{{#if Template.subscriptionsReady}}
{{> eachBlock}}
{{else}}
Loading...
{{/if}}
</template>
<template name="eachBlock">
{{#each currentData}}
<div data-cid="{{this._id}}"></div>
{{/each}}
</template>
js:
Template.eachBlock.onRendered(function(){
console.log("My each block should now be fully rendered!");
});

Load part of page separately after page load in meteor

I want to create complex blog page with meteor. I have a template with two sub template as below:
<template name="singlePostPage">
{{> post }}
{{#each comments}}
{{> comment}}
{{/each}}
</template>
I want to load post when page is loading and after post load completely then load comments. (like disqus comment system that load after entire page load)
Please guide me how to do this and what packages may useful for this scenario?
You can set a session variable or reactive variable when the post template has been rendered, and then have a handlebars {{#if}} to show the comments based on the value of that variable. Something like below should work.
.js file (Sessions)
Template.post.onRendered(function() {
Session.set("postRendered", true);
});
Template.singlePostPage.helpers({
postRendered: function() {
return Session.get("postRendered");
}
});
Just make sure that the postRendered Session variable is set to false or null when you first load the singlePostPage template. If you want to use a reactive variable:
Add this package:
meteor add reactive-var
And use this .js code:
var postRendered = new ReactiveVar(false);
Template.post.onRendered(function() {
postRendered.set(true);
});
Template.singlePostPage.helpers({
postRendered: function() {
return postRendered.get();
}
});
For both examples you can use this .html file
<template name="singlePostPage">
{{> post }}
{{#if postRendered}}
{{#each comments}}
{{> comment}}
{{/each}}
{{/if}}
</template>

How do I create a grid system using handlebars in Meteor.js?

I'm trying to build a simple memory card game in Meteor and I can't seem to create a grid of my cards to populate in html. I'm just trying to get a 4x4 grid for right now.
Here is the javascript:
Template.body.helpers({
cards: function() {
var allCards = Deck.find({}).fetch();
var chunks = [];
while (allCards.length > 0) {
chunks.push(allCards.slice(0, 4));
allCards = allCards.slice(4);
}
return chunks;
},
});
And here is the HTML:
<body>
<div class="container">
<div name="gameBoard">
{{#each cards}}
{{> cardsRow}}
{{/each}}
</div>
</div>
</body>
<template name="cardsRow">
<div class="row">
{{#each row}}
{{> card}}
{{/each}}
</div>
</template>
<template name="card">
<span class="text">{{text}}</span>
</template>
Right now I just have simple text entries in the db to see what I'm doing before I pull in images. I've tried console logging from my JS and I believe I'm creating the array of spliced rows correctly so I think the problem may be in the way I have arranged the markdown.
I tried pulling the each cards loop into a template as well instead of inside the body but I'm not sure how to render templates on demand. This is potentially an option since I have a new game button event listener that could call a render. I'm just not sure how to explicitly render in Meteor.
I tried following this previous post but I couldn't get it to work:
How do I populate a bootstrap grid system using handlebars for each command in Meteor.js?
Thoughts? I can provide more of my code base if needed.
Neither row in {{#each row}} in the "cardsRow" template, nor {{text}} in the "cards" template, refer the way that you hope they do. You need to define row and text as template helpers for each of these templates. In a template helper, this refers to the data object associated with the template.
In your case, when you iterate through #each cards in "container", this is passed into the "cardsRow" template as one of the chunks defined in your cards helper function. The helpers below should illustrate this, and are sufficient for your example.
Template.cardsRow.helpers({
row: function() {
console.log(this); // a chunk of cards
return this;
}
});
Template.card.helpers({
text: function() {
console.log(this); // a number in a chunk
return this;
}
});

How do templates work in Meteor

I've been working through the Discover meteor book, currently at chapter 6. I'm having great difficulty in understanding the relationship between templates, and how they work.
For example, I have a template called posts_lists.html and a javascript file called posts_lists.js
Within posts_lists.js I have the following:
Template.postsList.helpers({
posts: postsData
});
And within posts_lists.html I have:
<template name="postsList">
<div class="posts">
{{#each posts}}
{{> postItem}}
{{/each}}
</div>
</template>
So, am I right in thinking that Template.postsList.helpers({ is a block that contains all the variables accessed by the postsList template. The bock returns postsData in the form of posts.
posts is looped over - calling the postItem template. This is where I get lost... as I don't have a postItem.js file, or postItem.html file
I do have post_item.html and post_item.js - but these aren't named the same as postItem...
....but it works???? I don't get it. I know its not magic... but cant figure it out. In terms of my level / skillset I'm a designer / jquery user trying to learn more.
Thanks,
Rob
The file names have little significance. It's just a convention.
Your post_item.html should contain a template named postItem.

Handlebars not rendering JSON context data, getting empty template

I am having a strange issue where Handlebars is compiling my template properly, but when passing context data, the resulting fields in the html are blank. I've confirmed that my JSON data is actually a javascript object and not a string. My apologies if this has been answered elsewhere. I saw a lot of answers about the JSON string needing to be an actual object, but as i've stated, is not the case.
Template:
<script type="text/x-handlebars-template" id="items-template">
{{#each downloads}}
<div class="download-item">
<h3>{{pagetitle}}</h3>
<p>{{description}}</p>
</div>
{{/each}}
</script>
JS:
var source = $("#items-template").html();
var template = Handlebars.compile( $.trim(source) );
var html = template({
downloads:[
{pagetitle:'title', description:'the description'},
{pagetitle:'title', description:'the description'},
{pagetitle:'title', description:'the description'}
]
});
Result of html (Only One Single Blank Item):
<div class="download-item">
<h3></h3>
<p></p>
</div>
Any insight would be greatly appreciated. If i figure it out before someone sees this, i'll be sure to post an update.
You should use this
{{#each downloads}}
<div class="download-item">
<h3>{{this.pagetitle}}</h3>
<p>{{this.description}}</p>
</div>
{{/each}}

Categories

Resources