I am trying to make a template render something on the client; I think I tried everything possible (apart from the correct thing apparently).
Html:
<head>
<title>Groups</title>
</head>
<body>
{{loginButtons}}
{{>TplGroups}}
</body>
<template name="TplGroups">
groups found: {{ GroupCount }}
{{#each GetAllGroups}}
<div> hello, {{name}} group! </div>
{{/each}}
</template>
serverStartup.js:
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Meteor.publish("GroupCount"), function()
{
return Groups.find({});
}
});
}
and the Groups.js collection which exposes the two methods GroupCount and GetAllGroups, which I want to access on client side:
var Groups = new Meteor.Collection("groups");
Groups.insert({name: "John"});
if(Meteor.is_client)
{
Meteor.subscribe("GetAllGroups");
Meteor.subscribe("GroupCount");
Template.TplGroup.GetAllGroups = function()
{
return Groups.find({});
}
Template.TplGroup.GroupCount = function()
{
return Groups.find().count();
}
}
I have removed "insecure" and "autopublish" packages.
Where is my mistake? The two functions won't show on client.
Also what is the difference between declaring the functions as "publish" or declaring them as Template functions?
In browser console I get this:
event.returnValue is deprecated. Please use the standard event.preventDefault() instead. (jquery.js)
The publish method should look more or less like this
Meteor.publish("GetAllGroups", function () {
return Groups.find({});
});
#apendua pointed to the right solution. I took your code and refactored it to make the solution a little clearer:
server.js:
if (Meteor.isServer) {
// Publish groups
Meteor.publish('groups', function() {
return Groups.find();
});
}
groups.js
Groups = new Meteor.Collection('groups');
Groups.insert({name: 'John'});
if (Meteor.isClient) {
// Subscribe to groups
Meteor.subscribe('groups');
Template.TplGroup.GetAllGroups = function() {
return Groups.find();
}
Template.TplGroup.GroupCount = function() {
return Groups.find().count();
}
}
It is enough to publish just groups. In your groups.js you try to subscribe to a publication that does not exist (GetAllGroups). Better to just publish and subscribe to simply 'groups' and return the groups count as described above. Also with a newer version of meteor you should use Meteor.isClient and not Meteor.is_client.
The jQuery error you described is not related to your code and appears (at least what I think) because of some issue with Meteor and/or jQuery itself. Don't worry about that.
oups you just forgot "s" in your template name in your js file :
<template name="TplGroups"> <!-- what you wrote -->
and in your js you wrote :
Template.TplGroup.xxx
instead of :
Template.TplGroups.xxx
Related
My code was working fine until I implemented publish/subscribe. I followed the basic tutorial and checked the source code and I'm not doing anything different. Everything builds and runs but nothing from MongoDB gets displayed in the Blaze template.
imports/api/features.js
if (Meteor.isServer) {
Meteor.publish('features', function featuresPublication() {
return Features.find({});
});
Meteor.publish('comments', function commentsPublication() {
return Features.find({}, {fields: {comments: 0}});
})
};
client/main.js
Template.body.onCreated(function bodyOnCreated() {
Meteor.subscribe('features');
});
client/main.html
<body>
<h1 id="title">Feature Requests</h1>
{{#if currentUser}}
<button class="ui green button create" id="create">Add a New Feature Request</button>
{{> requestForm}}
{{#each features}}
{{> feature}}
{{/each}}
{{else}}
{{> loginButtons}}
{{/if}}
</body>
Edit #1
Before I ran meteor remove autopublish my code looked like this and worked:
Template.body.helpers({
features() {
return Features.find({}, {sort: {createdAt: -1}});
},
comments() {
return Features.find({}, {fields: {comments: 0}});
},
});
Edit #2
Thanks to everyone who contributed an answer. I fundamentally misunderstood how publish/subscribe worked. I didn't realize I still needed to call return Features.find({}) after I subscribed. Here's my working code:
import { Features } from '../imports/api/features.js';
import '../imports/api/features.js'
Template.body.onCreated(function bodyOnCreated() {
Meteor.subscribe('features');
});
Template.body.helpers({
features: function() {
return Features.find({});
}
});
Disregard the first answer. The lack of an autorun is what first caught my attention but since you're not passing any args to subscribe it is not needed.
My next question would be: In client/main.html, where is the reference to features coming from? Is there a features helper on Template.body? If not, you'll need to add it:
Template.body.helpers({
features: function() {
return Features.find({});
}
});
Also, see Meteor Subscribe & Publish with external api
Try this:
Template.body.onCreated(function() {
const self = this;
self.autorun(() => {
self.subscribe('features');
});
});
Also, see https://guide.meteor.com/data-loading.html#organizing-subscriptions.
I see you are using the imports directory. Have you remembered to import your publication file to the server/main.js file?
server/main:
import 'imports/path/to/publications.js'
Could anybody point me how to access an original TemplateInstance from the meteor helper. I'm aware of the Template.instance() but it appears to return the template instance where the helper was called, not the template instance for which the helper was defined.
Imagine we have two tiny templates:
<template name='demo'>
<h1>{{helper}}</h1>
{{# border}}
<h2>{{helper}}</h2>
{{/border}}
</template>
<template name='border'>
<div style="border:1px solid red">
{{> UI.contentBlock}}
</div>
</template>
With the following behavior:
Template.demo.created = function() {
this.result = "OK";
}
Template.demo.helpers({
helper: function() {
var tmpl = Template.instance();
return tmpl.result || "FAILED";
}
});
I've expected to obtain two "OK" for the demo template: the second one should be in the red border. But since Template.instance() returns original TemplateInstance only when helper is called at the top level of its owner template the result is "FAILED" (of course in the red border).
Question: Is there any public api to get the original TemplateInstance (without need to traverse view/parentView/_templateInstace)?
I think the best way to do this might be to either just set a Session variable, or use a Reactive Variable (using the reactive-var package - here is the documentation).
I've made a meteor pad to show how this more - here.
Basically:
Template.demo.created = function() {
result = new ReactiveVar('OK');
}
Template.demo.helpers({
helper: function() {
return result.get() || "FAILED";
}
});
I think your main problem is that you not setting a template instance variable correctly. Try the below code...
Set an instance variable:
Template.instance().result.set("OK");
Get an instance variable:
Template.instance().get("result");
So your updated code would be:
Template.demo.created = function() {
Template.instance().result.set("OK");
}
Template.demo.helpers({
helper: function() {
return Template.instance().get("result") || "FAILED";
}
});
It seems that it's known and already fixed (?) Meteor bug. More here: https://github.com/meteor/meteor/issues/3745
Comment from rclai on GitHub:
This was already addressed and fixed for the next release.
Run meteor like this, not sure if it still works:
meteor --release TEMPLATE-CURRENT-DATA#0.0.1
Another alternative is to use aldeed:template-extensions, which has super nice features, especially with dealing with template instances and I believe their way of fetching the template instance is a workaround this issue.
I create these two local collections (the code is actually written one after the other exactly like below):
ShoppingCartCollection = new Meteor.Collection(null);
CurrentPricesCollection = new Meteor.Collection(null);
Inside Template.myTemplate.rendered I add some initial info into these collections (again, code is one after the other):
ShoppingCartCollection.insert({"sqft" : "not yet entered"});
CurrentPricesCollection.insert({"hdrPhotos" : 100});
I've got these two global helpers in helpers.js (defined one after the other)
Handlebars.registerHelper("shoppingCart", function() {
return ShoppingCartCollection.findOne();
});
Handlebars.registerHelper("currentPrice", function() {
return CurrentPricesCollection.findOne();
});
When I load the page I immediately run these commands in the console:
> ShoppingCartCollection.findOne();
Object {sqft: "not yet entered", _id: "xcNmqJvMqqD5j7wwn"}
> CurrentPricesCollection.findOne();
Object {hdrPhotos: 100, _id: "LP38E3MZgzuYjvSec"}
In my template I use these helpers, but...
{{currentPrice.hdrPhotos}} //displays nothing
{{shoppingCart.sqft}} //displays "not yet entered"
How... what... ? How can this be? Are there some kind of gotchas that I could be missing? Some kind of dependency or load order that I'm not aware of?
The code you posted is working fine here.
Suggest comparing this code to the exact details of what you are doing. Also, look
for other problems, typos, etc.
Below is the exact test procedure I used:
From nothing, at the linux console:
meteor create sodebug
Note that this will produce files for a "hello world" type program.
Check the version:
meteor --version
Release 0.8.1.1
Edit sodebug/sodebug.js:
if (Meteor.isClient) {
// code autogenerated by meteor create
Template.hello.greeting = function () {
return "Welcome to sodebug.";
};
Template.hello.events({
'click input': function () {
// template data, if any, is available in 'this'
if (typeof console !== 'undefined')
console.log("You pressed the button");
}
});
// add your code here
ShoppingCartCollection = new Meteor.Collection(null);
CurrentPricesCollection = new Meteor.Collection(null);
ShoppingCartCollection.insert({"sqft" : "not yet entered"});
CurrentPricesCollection.insert({"hdrPhotos" : 100});
Handlebars.registerHelper("shoppingCart", function() {
return ShoppingCartCollection.findOne();
});
Handlebars.registerHelper("currentPrice", function() {
return CurrentPricesCollection.findOne();
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
Edit sodebug.html:
<head>
<title>sodebug</title>
</head>
<body>
{{> hello}}
{{> T1 }}
{{> T2 }}
</body>
<template name="T1">
<p>
{{shoppingCart.sqft}}
</p>
</template>
<template name="T2">
<p>
{{currentPrice.hdrPhotos}}
</p>
</template>
<template name="hello">
<h1>Hello World!</h1>
{{greeting}}
<input type="button" value="Click" />
</template>
Run: meteor run
Manual tests:
Fire up chromium browser at localhost:3000
Check web browser console for collections data. PASS
Check web browser screen for templates data. PASS
Reorder templates in sodebug.html file, check web browser screen. PASS
I know this is completely simple, but it's also completely is stumping me on why it isn't working. This gets to the point of rendering the html and showing Hello World with a message below "Welcome to chat" and a button "say hello back" but what it ISN'T doing is then change the message to "work".
I have a .js file which is:
if (Meteor.isClient) {
var message="welcome to chat";
function template(message){
Template.hello.greeting = function () {
return message;
};};
template(message);
Template.hello.events({
'click input' : function () {
template("work ");
}
});
}
and a html follow as shown:
<head>
<title>chat</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
<h1>Hello World!</h1>
{{greeting}}
<button value="Click">Say Hello Back!</button>
</template>
And it's embarrassingly simple but I just can't figure out what I'm doing wrong. I know I shouldn't re render the page because the whole point of using meteor is for it's live html so what do I do have to do?
I figured out the main problem!
For my html I was using a button class but I should've been using a input type="button" instead!
To make it "reactive" you should use Session that meteor provides. You can simplify your code to make it easier to read and understand.
Session provides a global object on the client that you can use to
store an arbitrary set of key-value pairs. Use it to store things like
the currently selected item in a list.
You set the session variable to "welcome to chat" first. In your click event you would set the Session variable to "work". Then you would set the template to return the value of the Session variable when it changes. Your javascript could look something like this.
if (Meteor.isClient) {
Session.set("message", "welcome to chat");
Template.hello.greeting = function () {
return Session.get("message");
}
Template.hello.events({
'click input' : function () {
Session.set("message", "work");
}
});
}
This is untested but give it a try.
i am not quite sure about what do you mean by "not working". But i am sure you will have to do following.
List item you need to prevent calling default event by the browser.
ex:-
Template.hello.events({
'click input' : function () {
//your code here
return false;
}
});
2 . use meteor methods instead of having template()
Ok so I've got my template in its own file named myApp.html. My template code is as follows
<template name="initialInsertion">
<div class="greeting">Hello there, {{first}} {{last}}!</div>
</template>
Now I want to insert this template into the DOM upon clicking a button. I've got my button rendered in the DOM and I have a click event tied to it as follows
Template.chooseWhatToDo.events = {
'click .zaButton':function(){
Meteor.ui.render(function () {
$("body").append(Template.initialInsertion({first: "Alyssa", last: "Hacker"}));
})
}
}
Now obviously the $("body").append part is wrong but returning Template.initialInsertion... doesn't insert that template into the DOM. I've tried putting a partia {{> initialInsertion}}but that just errors out because I dont have first and last set yet... any clues?
Thanks guys
In meteor 1.x
'click .zaButton':function(){
Blaze.renderWithData(Template.someTemplate, {my: "data"}, $("#parrent-node")[0])
}
In meteor 0.8.3
'click .zaButton':function(){
var t = UI.renderWithData(Template.someTemplate, {my: "data"})
UI.insert(t, $(".some-parrent-to-append"))
}
Is first and last going into a Meteor.Collection eventually?
If not, the simplest way I know is to put the data into the session:
Template.chooseWhatToDo.events = {
'click .zaButton' : function () {
Session.set('first', 'Alyssa');
Session.set('last', 'Hacker');
}
}
Then you would define:
Template.initialInsertion.first = function () {
return Session.get('first');
}
Template.initialInsertion.last = function () {
return Session.get('last');
}
Template.initialInsertion.has_name = function () {
return Template.initialInsertion.first() && Template.initialInsertion.last();
}
Finally, adjust your .html template like this:
<template name="initialInsertion">
{{#if has_name}}
<div class="greeting">Hello there, {{first}} {{last}}!</div>
{{/if}}
</template>
This is the exact opposite solution to your question, but it seems like the "Meteor way". (Basically, don't worry about manipulating the DOM yourself, just embrace the sessions, collections and template system.) BTW, I'm still new with Meteor, so if this is not the "Meteor way", someone please let me know :-)
I think you may want to use Meteor.render within your append statement. Also, note that if you are passing data into your Template, then you must wrap Template.initialInsertion in an anonymous function, since that's what Meteor.render expects. I'm doing something similar that seems to be working:
Template.chooseWhatToDo.events = {
'click .zaButton':function(){
$("body").append(Meteor.render(function() {
return Template.initialInsertion({first: "Alyssa", last: "Hacker"})
}));
}
}
Hope this helps!
Many answer here are going to have problems with the new Blaze engine. Here is a pattern that works in Meteor 0.8.0 with Blaze.
//HTML
<body>
{{>mainTemplate}}
</body>
//JS Client Initially
var current = Template.initialTemplate;
var currentDep = new Deps.Dependency;
Template.mainTemplate = function()
{
currentDep.depend();
return current;
};
function setTemplate( newTemplate )
{
current = newTemplate;
currentDep.changed();
};
//Later
setTemplate( Template.someOtherTemplate );
More info in this seccion of Meteor docs