This code:
//hello.js
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import './hello.html';
Template.hello.onCreated(function helloOnCreated() {
// counter starts at 0
this.counter = new ReactiveVar(0);
});
Template.hello.helpers({
counter() {
return Template.instance().counter.get();
},
});
Template.hello.events({
'click button'(event, instance) {
// increment the counter when button is clicked
instance.counter.set(instance.counter.get() + 1);
},
});
<!-- hello.html -->
<body>
<h1>Welcome to Meteor!</h1>
{{> hello}}
{{> info}}
</body>
<template name="hello">
<button>Click Me</button>
<p>You've pressed the button {{counter}} times.</p>
</template>
<template name="info">
<h2>Learn Meteor!</h2>
<ul>
<li>Do the Tutorial</li>
<li>Follow the Guide</li>
<li>Read the Docs</li>
<li>Discussions</li>
</ul>
</template>
Returns this error:
Error: No such template: hello
at lookup.js:189
at Blaze.View.<anonymous> (spacebars-runtime.js:32)
at view.js:199
at Function.Template._withTemplateInstanceFunc (template.js:465)
at view.js:197
at Object.Blaze._withCurrentView (view.js:538)
at viewAutorun (view.js:196)
at Tracker.Computation._compute (tracker.js:311)
at new Tracker.Computation (tracker.js:201)
at Object.Tracker.autorun (tracker.js:576)
To prevent this for getting absurdly long, here is the GitHub repo if needed.
In the tutorial the template comes after the call. But for some reason, it won't on my page. If I move the templates above the call, it works, but not after. I'm not sure what I'm doing wrong.
Here is the directory structure as requested. I've just started this project, and hello is the only page at the moment.
You should also add a comment to the top of each file which merely indicates the working directory.
I assume the first snippet of code is the contents of hello.js and the second snippet to be the content of main.html. I also assume that you are NOT showing us the contents of hello.html.
If all these are true, I can see that you are importing hello.html into hello.js BUT you are ALSO referencing hello.html WITHIN main.html. I assume your error is Meteor being confused as to which is the right reference, and grabbing the wrong one.
Clean code tends to separate Meteor templates into their own directories and files anyways. I'd get rid of them from main.html.
UPDATE:
I think your issue is in your main.js, or rather, your routing file.
For starters, it's bad JS practise to import in the middle of the code. Good practise is:
1) Import all absolute paths
2) Add a spacing
3) Import all relative paths
4) Never import after that
Take a good look # BlazeLayout.render which you have added to your project, but haven't used. Attempting to just brazenly import the JS file is never going to work. You need to pass arguments to the render method, which include the template you desire to render.
Related
I am very excited with ES2015 modules in Meteor 1.3. We have written an app with medium complexity with Meteor 1.2. As we have very large number of templates and files, it is taking a bit of time to download the content on client side. so I am interested in the lazy loading feature using import. From the meteor night talk, they say that the Blaze templates are still global and cannot be imported (or lazy loaded), I have tried using React inside Blaze.
Added react-template-helper package using meteor add react-template-helper
Create imports folder and added testComponent.jsx file which exports 'TestComponent'
//testComponent.jsx
import React from 'react';
export default class TestComponent extends React.Component {
render() {
return (
<div>
<h1>TestComponent</h1>
<div>This is from Test Component</div>
</div>
);
}
}
After in the Blaze template outside imports folder,
<!-- homeReact template html-->
<template name="homeReact">
<div>
{{> React component=TestComponent}}
</div>
</template>
In the template's js file which is also outside of imports folder
// homeReact template js
import { Template } from 'meteor/templating';
import TestComponent from '/imports/testComponent.jsx`;
Template.homeReact.helpers({
TestComponent() {
return TestComponent;
}
});
This worked but the imports/testComponent.jsx is downloaded on the client (checked using chrome dev tools - sources tab), even if the current route doesn't require homeReact template.
Then I have used require instead of import like this,
// homeReact template js
import { Template } from 'meteor/templating';
Template.homeReact.onCreated(function () {
this.TestComponent = require('/imports/testComponent.jsx').TestComponent;
});
Template.homeReact.helpers({
TestComponent() {
return Template.instance().TestComponent;
}
});
This code also downloads the imports/testComponent.jsx file but in addition I also got an error
In template "homeReact", call to {{> React ... }} missing component argument.
So, my question is, is it possible to lazy load (download) files only when required?
I have a main page which lists a few text items ("Ideas"), which are clickable links. Clicking on them should take you to a page where you can edit them. Here's my html:
<head>
<title>Ideas</title>
</head>
<body>
</body>
<template name="Ideas">
<ul>
{{#each ideas}}
{{> idea}}
{{/each}}
</ul>
</template>
<template name="idea">
<li>{{text}}</li>
</template>
<template name="ShowIdea">'
<div class="editable" contentEditable="true">{{text}}</div>
</template>
I've added Iron:Router to my project to allow for moving between the pages. Here's the javascript:
Ideas = new Mongo.Collection("ideas");
if (Meteor.isClient) {
Router.route('/', function() {
this.render('Ideas');
});
Router.route('/idea/:_id', function() {
var idea = Ideas.findOne({_id: this.params._id});
this.render('ShowIdea', {text: idea.text});
});
Template.Ideas.helpers({
ideas: function () {
return Ideas.find({});
}
});
}
I inserted a single idea to my Mongo DB using the Meteor Mongo command line tool. That single item shows up properly on my main page. Here's what the HTML looks like in my debugger for the main page:
<html>
<head>...</head>
<body>
<ul>
<li>
The first idea ever
</li>
</ul>
</body>
</html>
Clicking on that link takes me to a new page with an address of:
http://localhost:3000/idea/ObjectID(%22550b7da0a68cb03381840feb%22)
But nothing shows up on the page. In the debugger console I see this error message + stack trace, but it means nothing to me since it all seems to be pertaining to iron-router and meteor, not code which I actually wrote:
Exception in callback of async function: http://localhost:3000/Idea.js?2fd83048a1b04d74305beae2ff40f2ea7741d40d:10:44
boundNext#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:424:35
http://localhost:3000/packages/meteor.js?e53378596562e8922a6369c955bab1e047fa866b:978:27
onRerun#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:520:13
boundNext#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:424:35
http://localhost:3000/packages/meteor.js?e53378596562e8922a6369c955bab1e047fa866b:978:27
onRun#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:505:15
boundNext#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:424:35
http://localhost:3000/packages/meteor.js?e53378596562e8922a6369c955bab1e047fa866b:978:27
dispatch#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:448:7
_runRoute#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:543:17
dispatch#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:844:27
route#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:710:19
boundNext#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:424:35
http://localhost:3000/packages/meteor.js?e53378596562e8922a6369c955bab1e047fa866b:978:27
boundNext#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:371:18
http://localhost:3000/packages/meteor.js?e53378596562e8922a6369c955bab1e047fa866b:978:27
dispatch#http://localhost:3000/packages/iron_middleware-stack.js?0e0f6983a838a6516556b08e62894f89720e2c44:448:7
http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:390:21
_compute#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:308:36
Computation#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:224:18
autorun#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:499:34
http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:388:17
nonreactive#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:525:13
dispatch#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:387:19
dispatch#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:1688:22
onLocationChange#http://localhost:3000/packages/iron_router.js?a427868585af16bb88b7c9996b2449aebb8dbf51:1772:33
_compute#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:308:36
_recompute#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:322:22
flush#http://localhost:3000/packages/tracker.js?21f0f4306879f57e10ad3a97efe9ea521c5b5775:452:24
And then it ends with this warning message:
Route dispatch never rendered. Did you forget to call this.next() in an onBeforeAction?
I don't have an onBeforeAction (I'm not even sure what that is)... so I don't think that message pertains to me?
I just started using Meteor the other day and just added iron-router not 24 hours ago, so I'm a bit lost here. Any pointers on how I can debug and fix this would be great.
Two things need fixing:
When you insert documents from the shell they are assigned _id values which are mongo ObjectIDs, whereas meteor defaults to using strings. This explains the weird URL. To avoid this problem, it's generally best to initialize your data from the server. Here's an example:
if (Meteor.isServer) {
Meteor.startup(function() {
if (Ideas.find().count() === 0) {
Ideas.insert({text: 'feed the cat'});
}
});
}
Now after a $ meteor reset you will always start with one cat-related idea.
If you wish to pass a context to your template, you'll need to use the data attribute like so:
Router.route('/idea/:_id', function() {
this.render('ShowIdea', {
data: function () {return Ideas.findOne({_id: this.params._id})}
});
});
See this example from the docs. After making those changes, the code worked correctly for me.
I've got this template named indexmade up of a bunch of partial templates.
<template name="index">
{{> jumbotron }}
{{> crew }}
{{> projects3 }}
{{> projects2 }}
{{> faq }}
{{> contact }}
</template>
Say that I've got JS code page-transitions.js that requires the DOM of jumbotron to be fully loaded in order to run.
It's not enough to simple do:
Template.index.rendered = function(){
// load page-transitions.js
};
I HAVE to do
Template.jumbotron.rendered = function(){
// load page-transitions.js here instead
};
This can get messy really quick because you need to be VERY specific about which partial templates need which JS code. And different templates could require the same JS code so you can run into a situation where you're loading the same JS code multiple times.
Is there a way to wait until the index template has completely rendered EVERYTHING, including all nested child templates, and then run the JS code?
Meteor.startup() doesn't work in this case either.
You can try a couple of things:
First, just use the classic jQuery.ready way. because, why not?
Meteor.startup(function() {
$(document).ready(function() {
// stuff.
});
});
Or you could try Tracker.afterFlush:
Meteor.startup(function() {
// I know each Template.template.rendered has its own afterFlush, but those
// computations are defined way before Meteor.startup so then, theoretically,
// the below code should be executed last even if it triggers recomputations here.
Tracker.afterFlush(function() {
// stuff.
});
});
With meteor updates up to 0.8 my old code stopped working.
Handlebars.registerHelper('getTemplate', function(id, context) {
return Template[id](context);
});
<template name="main">
....
{{{getTemplate templateName context}}}
....
</template>
//somewhere in other template
Template.main.context = {name:value};
This way I was able to render a custom template with custom data. Now I can't find the way to pass context to the dynamic template. With blaze both templateName and context is undefined. Any advice?
Meteor >= 0.8.2
You can use the UI.dynamic helper render a template with a context which are both specified dynamically. For more details, check out this issue.
Meteor < 0.8.2
Both of these issues are addressed on this page in the meteor wiki.
Handlebars.registerHelper is now UI.registerHelper as seen here.
Examples of how to dynamically render templates are shown here.
update
Actually, given the requirements, a solution doesn't seem very obvious to me. If you are willing to use session variables to set the template name and context, AND only have one dynamic template in your main template. You could do something like this:
<body>
{{> main}}
</body>
<template name="main">
{{> getTemplate context}}
</template>
<template name="dogs">
<p>There are {{animals}} dogs!</p>
</template>
<template name="cats">
<p>There are {{animals}} cats!</p>
</template>
Session.setDefault('templateName', 'dogs');
Session.setDefault('templateContext', {animals: 10});
Template.main.getTemplate = function() {
return Template[Session.get('templateName')];
};
Template.main.context = function() {
return Session.get('templateContext');
};
This was brought up on the meteor-core list and #dgreensp, MDG core dev working on Blaze, opened Ticket #2007 - How to render a template to HTML with data so they definitely know about this and I'd expect a fix to land soon after 0.8.0.
He also included the following workaround:
var toHTMLWithData = function (kind, data) {
return UI.toHTML(kind.extend({data: function () { return data; }}));
};
The github ticket has further discussion and alternate code snippets that you may find useful.
My template is getting rendered twice on first load. I notice this because in
Template.home.rendered = function() {
console.log('rendered'); // => this is printed out twice
console.log(Data.find({}).count()); // => this is initially 0, then 1 the second time
}
Furthermore, on the first load, no Data is available. Yet on the second load, the Data is there.
Does anyone know what this problem might be, and why the data only appears the second time?
You need to find a way to render the template when your data is available.
Using this template structure, the if block content, which happens to be the template displaying your data, will be rendered only when the myDataIsReady helper returns true. (thus triggering the rendered callback only once, with data immediately available).
<template name="displayData">
<p>This is my data : {{this}}</p>
</template>
<template name="home">
{{#if myDataIsReady}}
{{#each data}}
{{> displayData}}
{{/each}}
{{/if}}
</template>
You have to define a subscription handle (an object returned by Meteor.subscribe) in order to use it's reactive ready method : we'll reference it in the myDataIsReady helper to track data availability, and the helper will automatically rerun when the state of ready changes.
Meteor.startup(function(){
// this subscription should return your data subset
myDataHandle=Meteor.subscribe("myData");
});
Template.home.myDataIsReady=function(){
return myDataHandle.ready();
}
Template.home.data=function(){
return Data.find({});
}
But that's quite annoying for such a simple task.
That's why I suggest using the Iron Router which makes things way simpler !
Add it to your project using "mrt add iron-router", then in a client/router.js and client/router.html, use this boilerplate code :
Router.configure({
loadingTemplate:"loading"
});
Router.map(function(){
this.route("home",{
path:"/",
// we indicate which subscription has to be marked ready in order to load the template
waitOn:function(){
return Meteor.subscribe("myData");
}
// the result of this function will become our target template data context
data:function(){
return Data.find({});
}
});
});
<template name="home">
{{#each this}}
{{> displayData}}
{{/each}}
</template>
<template name="loading">
<p>Data isn't ready yet...</p>
</template>
As you can see, the Iron Router allows us to specify simply what we painfully achieved manually in the first code example (waiting on a particular subscription to render a template), and of course we get free routing, loading mechanisme, layout management, etc...
Search the web for a complete iron-router tutorial (my code is untested, but I hope it is ok and should get you started), it's so awesome that it's gonna be merged to Meteor ultimately.
I had a body.html in /client and a appBody.html in /client/templates, with, in iron router:
Router.configure({
layoutTemplate: 'appBody',
});
Both body templates were rendered (and happened to be the same). Obviously, the body.html in /client needed to be removed.