Where does Meteor.subscribe() belong? - meteor

I just noticed that my subscription code "Meteor.subscribe('my-publications')" at the top of my JS file affects ALL my templates, not just the template that the JS file is created for.
This was unexpected because all the demos did it this way.
Where is the subscription code suppose to be in if I want it to affect one template only? Inside Template.name.created()?

Don't assume the only data in minimongo is what is relevant to your template. Be specific when selecting your data for the template.
You have several options as far as subscriptions go, the most common by far is adding a subscription to the waitOn section of your routes definitions. Using waitOn will result in the subscription being unsubscribed when the route is no longer in use.
Router.map(function() {
this.route('route', {
waitOn: function(){ return Meteor.subscribe("yourSub"; }
});
});
You can also subscribe per template. A package I've created for this purpose can be added via
meteor add elevatedevdesign:template-subscriptions
This pattern will also be coming soon as a part of blaze, but with some slight differences in how you call it. This will automatically unsubscribe when the template is no longer active. It also allow's subs-manager to be subscribed to on template destruction.

Related

Ember js, show spinner when something is loading in background

Is there a way with EmberJS to show a loading template somewhere when something in my page is loading something?
For example, I have a page with many many songs artists, and each of them have many many songs. I load the page of the artists, and good, but in background I'm loading info about songs of everyone. I simply need a loading spinner somewhere that says to me the site is loading something (for example, there is in "Network" tab of Chrome Developer Tools something pending...)
how to do that in such an elegant Emberjs way?
You can observe the isPending property of Ember.PROMISEPROXYMIXIN like so:
{{#if artist.songs.isPending}}
loading...
{{/if}}
As far as I know (and based on a quick perusal of the API docs) Ember doesn't provide a built-in way to achieve this. Unless I'm wrong, this means you'll need to implement request state tracking yourself.
You could do this in Ember's adapter layer (e.g. add code in app/adapters/application.js), but the simplest solution might be to just work with jQuery directly. There are a few APIs you can use:
jQuery.active, which indicates the number of outstanding requests
ajaxStart and ajaxStop
I'd recommend creating an Ember.Service to track this state-- then you can inject it in any Controller or Component that needs to render a template based on this info. Your Service could look like:
import Ember from 'ember';
export default Ember.Service.extend({
init() {
this._super(...arguments);
const invalidateRequestInProgress = () => {
this.notifyPropertyChange('dummyProperty');
}
$(document).ajaxStart(invalidateRequestInProgress);
$(document).ajaxStop(invalidateRequestInProgress);
},
requestInProgress: Ember.computed('dummyProperty', function() {
return Ember.$.active !== 0;
}),
});
I haven't tested this code, so I'm not sure if it'll work in practice :)

Meteor Check if Template Exists

I have Meteor application that uses Iron-Router in which the rendered screen is dynamic based on the URL.
I have two parameters from the URL, station and screen.
In the routing code:
this.render(stationParam+screenParam, {to: 'content'});
However, I want to be able to check if the template stationParam+screenParam exists.
Is this possible?
All templates are stored as a field of a global object, Template. The question then is: How to check if this global object has a given field?
You have multiple ways to do it. My personal favourite using underscore:
if (_.has(Template, stationParam + screenParam)) { /* ... */ }
Underscore is included in Meteor. If you want to use it in a package, don't forget the api.use('underscore') in your describe callback.
See also: Determining if a javascript object has a given property

right way to use subscriptions in meteor

In my meteor app I have all pages that needs to load one or more documents from elements collection and a couple of pages that loads documents from items collection;
I have the two publish functions for the two collections, but now I'm trying to understand the best way to subscribe them;
currently I'm subscribing elements in a file in the /client directory:
Meteor.subscribe("elements");
This will make the subscription on application start;
in the other case, since the items to be loaded are a little more, I've decided to use the subscription option in iron router:
subscriptions: function() {
return Meteor.subscribe("items");
}
The first difference I can see is that it seems that elements, once started the application, will not take time anymore to load (navigating through pages takes no time for loading);
on the other hand, every time I open the page that loads items, (event without a refresh) takes the loading time again;
Would be probably not the best solution to subscribe also items on application start, since they are only needed if you visit their specific route (elements otherwise are needed everywhere);
but there is a way to avoid reloading them from the server every time I ask the route again?
Also thinking about elements, they are not needed all in all pages: avery page need just one or two of them; so it would be probably more corret to load just the one or two that are really needed...
How subscriptions should be managed in your opinion?
I cover some of the trade-offs in my answer to this question. Here's how we use do this at Edthena using the subscriptions manager package:
// Allow the subman to cache a lot of subscriptions for a very long time
var subman = new SubsManager({cacheLimit: 100, expireIn: 864000});
// This is a global subscription that we may need to wait on later
subman.subscribe('sub1');
// This is a global subscription that we don't need to wait on
Meteor.subscribe('sub2');
Tracker.autorun(function() {
if (Meteor.userId()) {
// Add subscriptions which should be loaded globally but only
// after the user logs in.
Meteor.subscribe('sub3');
}
});
var PostController = RouteController.extend({
waitOn: function() {
// Use subman only if we want to cache the subscription between routes.
// Note that in the case of sub1, the previous handle will be reused
return [
subman.subscribe('sub1'),
subman.subscribe('sub4'),
Meteor.subscribe('sub5')
];
}
});
It's a matter of preference, but I think the simplest answer is that you can still use Iron Router's subscriptions option whilst centrally subscribing:
Subscription file:
Subscriptions = {
elements: Meteor.subscribe('elements'),
...
};
Router:
subscriptions: function() {
return Subscriptions.elements;
}
All the router needs is the handle which is returned by Meteor.subscribe, it doesn't actually have to do the subscribing. If you store these handles in a (sensibly named) global object, you can then pass them to the relevant router function when you need them.
First, install IronRouter enter; at the command prompt, enter "meteor add iron:router"; doing so will add that routing package.
So that your "raw" or base URL doesn't throw a routing exception, add this to the top of your .js file (above/outside the "isClient" and "isService" blocks):
Router.route('/');
Add a template or two (or more) to your project's .html file (you can use Notepad or Notepad++ for this, but I recommend the free (and "hackable") Atom editor, from Github, with the Meteor helper packages added. The project will be in a subfolder of whatever folder you were in when you entered the "meteor create" command. To download the Atom Editor, click this.
BTW, the video of the futuristic/pastistic coding family on the Atom Editor site is a gas, man!
Getting back to adding templates; as an example, here are a couple of templates I added:
<template name="garrapatabeach"><p>Here's a picture of Garrapata Beach; early morning;
long exposure.</p>
<p class="location">Garrapata Beach.Garrapata State Park.Big Sur.Monterey County.California</p>
<img height="600" src="/images/garrapataBeach.jpg" width="900" />
</template>
<template name="garrapataturnout"><p>Here's a picture from the first Garrapata turnout;
long exposure</p>
<p class="location">First Turnout.Garrapata State Park.Big Sur.Monterey County.California</p>
<img height="600" src="/images/GarrapataTurnout1.jpg" width="900" />
</template>
Now, add routes for those templates in the same part of the project's .js file where you added the "home" ("/") route in the .js file, so that it looks like this (the routes match the template names):
Router.route('/');
Router.route('/garrapatabeach');
Router.route('/garrapataturnout');
Note: This supposes you have added a "public" folder to your project, and an "images" folder beneath that, and that you have images with the names indicated in that images folder. If you want to "play along," you can download those images from dplatypus.meteor.com/garrapatabeach and dplatypus.meteor.com/garrapataturnout; otherwise, simply change the names of the jpgs and use your own images.
You will now be able to view your templates by navigating to the links given above (to run it, simply enter "meteor" at the command prompt while in the project's folder, and navigate to localhost:3000). However, to make it "more better" (so that the main page is not naked as a jaybird), we'll put some anchor tags/links in the main template, like so:
<body>
{{> main}}
</body>
<template name="main">
Garrapata Beach
Garrapata Turnout
</template>
Now the links will display when you navigate to localhost:3000, and clicking on them will route you to the appropriate page/template. To continue, just add another template, along with its corresponding routing entry and anchor tag/link.

Meteor+Blade template variables catch 22

I'm trying to use a variable in my Blade template, but I always get
ReferenceError: files is not defined
My understanding is that the proper way to pass a variable to a template is something like this (client/ceres.js):
Meteor.startup(function() {
Files = new Meteor.Collection('files');
Template['files'].files = function() {
return Files.find();
}
});
(Copying from the "todos" example)
And then I should be able to use it in my template, views/files.blade:
ul
foreach files as file
li= file.filename
But I guess the variable is passed to the template too late? But if I take my JS out of Meteor.js then Template isn't defined.
So I don't get it. Either my template doesn't exist, or the variable doesn't exist, and it always crashes. How do I pass a simple variable along?
Same error with this:
ul
- for(var i=0; i<files.length; ++i)
li= files[i].filename
This is a known issue with Meteor that is actively being worked on.
The problem is that Meteor prevents smart packages from specifying the load order of files. See issue here.
Because of this issue, it is possible that your client-side JavaScript will run before the templates are loaded. (There is a hack in Meteor that ensures Handlebars templates load before your custom code) For example, Template.foo.helperName = function() { ... } will fail if Template.foo has not yet been defined.
Check the generated HTML (view source) for the initial page load to see if your client-side JavaScript code is loading before the template is defined. If so, you may get an Error like:
TypeError: Cannot set property 'helperName' of undefined`
To workaround this issue, try putting your client-side code in a folder with a different name. I believe that Meteor currently sorts files alphabetically when determining the load order. See the troubleshooting section on this page for more information.
A similar workaround is to utilize Meteor.startup when adding view helpers to your views. That is, you can wrap your Template.foo.helperName = ... stuff in a Meteor.startup call. If you are using a body.blade template, though, you can end up with the opposite problem (i.e. the "catch 22") in which your body.blade template starts rendering before view helpers get setup. In this case, you can get errors since those helpers/variables are not yet defined. The solution here is to avoid using body.blade templates and only render the initial template once all view helpers have been loaded (i.e. at the end of your Meteor.startup routine).
At any rate, all of these workarounds are rather lame. :( But, alas! These issues should be fixed soon.
As soon as Meteor fixes the issue described above, I will modify the Blade smart package to enforce the load order of compiled templates. My apologies for the confusion.
Turns out you can't include files that use Template variables either. i.e., you can't use the include directive in Blade at all if you want to use variables in your template that haven't been initialized by Meteor yet -- you have to insert your template via jQuery/JS after the DOM has loaded. Example:
views/body.blade:
.container
h1 Page Title
#content
views/files.blade:
ul
foreach files as file
- console.log(file);
li= file.filename
client/main.js:
Files = new Meteor.Collection('files');
Template.files.files = function() {
return Files.find();
};
$(function() {
$('#content').html(Meteor.render(Template.files));
});

With Meteor, why calling MyCollection.findOne affects other template functions?

I am using findOne just to fetch one certain element for my collection.
However, when I do that, all my template functions containing those collections are re-run and the content refreshed. The content is similar, the problem is I am applying styles to some of those elements, and these updates just reset everything as well. More importantly: those refreshes are completely useless.
For example, I have this template:
Template.content.cars = function () {
alert("I AM RERUN!");
return Cars.find();
};
And in another function, I am doing this:
Cars.findOne({ _id: Session.get('current_car') }, {});
Why would be the first template re-run? Am I doing something wrong?
I'm not sure why your first template would be re-run: are you sure something else isn't going on?
But as a general non-answer to your question: you should expect that a template which depends on the entirety of a collection will be re-run many times (for instance as the data loads incrementally when the page first renders). With meteor you need to write your HTML/CSS in such a way that this re-rendering won't cause problems.
Without knowing more about your problem I can't really say more than that.

Resources