right way to use subscriptions in meteor - 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.

Related

Next JS Version 12+: Static Site Generation

I'm using Next.js version 12.0.4 on a new project, having used Next.js version 10 on a prior one.
Has something changed with Static Site Rendering at build time? It's rendering all my output pages when I do an npm run build (which in turn executes "next build and next export") with html files that include a ton of .js files and no native text "content" which I'd expect.
That is, the output doesn't have any of the standard HTML <h1>, <h2> etc. in it, just a bunch of javascript for the client to hydrate.
Prior versions of Next.js (or perhaps it was my configuration?) seemed to render pure, finalized HTML just fine.
I'm trying to render a simple About page (no dynamic routes, no dynamic props for this route) and, while it properly renders a page in the "/about/index.html" output location, that page has a bunch of .js files and a JSON payload. That page does indeed display properly, but I'd really like the output in the "out" directory to be actual .html files with HTML pre-rendered, for SEO and other reasons.
In my next.config.js, I've specified:
module.exports = {
exportPathMap: function () {
return {
"/": { page: "/" },
"/about": { page: "/about" },
};
},
};
I've also specified getStaticProps on the about page conponent (about.tsx). (I'm using typescript if that matters.)
The rendered /about/index.html file has a ton of .js includes and no real HTML "content".
Have I missed a configuration setting? What can I do to make it render pure HTML where I'd like?
AHA! Ok, so this error was of course a coding error on my side.
In _app.tsx, I had a wrapper for Authentication that I had written. By (bad) design, it was deliberately NOT rendering children for it if the user wasn't authenticated. Therefore, the pre-renderer wouldn't render the "regular" html elements, because the pre-renderer of course does not sign in.
If this same problem happens to you, make sure you're not wrapping all views up in some provider element which conditionally renders children.

Meteor & Flow Router: Marking Dynamically generated paths as 'active'

Needless to say, my experience with Meteor is lacking. I come from a Rails background, where you can do a lot more logic (and magic) in your views than Meteor.
The situation: I've got some routes that look like /things/:_id, and I've named that route 'thing' because it shows only one thing of a user's many owned things:
FlowRouter.route('/things/:_id', {
name: 'thing',
action() {
BlazeLayout.render('appPage', {app: 'thing', sidebar: "thingsListOnThing", header: 'thingTitle'});
}
});
As you can see, I'm also loading a template I've built to list all of the user's owned things on the sidebar for easy navigation. That template, thingsListOnThing is the target of this question.
Get the gist? I'm able to mark the route that dislays a template with a complete list of a user's things as active using zimme:active-route like so:
// A list of all a user's things
<div class="{{isActiveRoute name='things' class='is-active'}}">
List of Things
</div>
This package is great, but it won't help me with routes that look like /things/:_id because, then every link to each individual thing would be is-active on any thing page, not just the one where the current path's :_id matches the _id of the active thing.
I'm really kind of stuck here. My first guess would be to use template helpers, but I'm confused as to where to get started with that.
If need be, please as me to upload any piece of my code you require. I figured it's such a generic question that you guys probably don't need to see all of my code to understand what I'm trying to accomplish.
Using: zimme:active-route
Template Helpers
Template.listOfThings.helpers({
activeListClass(page) {
const active = ActiveRoute.name('thing') && FlowRouter.getParam('_id') === this._id;
return active && 'active';
},
});
Template HTML:
<a class="{{isActivePath 'thing'}}" href="/things/{{_id}}">
<div class="thingListItem {{activeListClass page}}">
{{title}}
</div>
</a>

Where does Meteor.subscribe() belong?

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.

Meteor: Static HTML page as root

I made a meteor app, added a /public directory full of .html, .jpeg, etc...
It all works nicely just like that except that going to my domain's root doesn't show anything; I have to type in /index.html.
How can I make my domain root go to public/index.html?
Meteor wasn't really made with serving static content (in the form of HTML) in mind. The public folder is really used to serve things like the favicon and maybe an image here or there (http://docs.meteor.com/#structuringyourapp).
While you could technically do something like:
if (Meteor.isClient) {
window.location = 'index.html';
}
It's not really how it's supposed to work. And this brings other problems, such as making sure this only works on the / route.
A far better solution is to have nginx stand in front of a running instance of Meteor and serve static content directly.
It all really depends on why you're attempting to serve index.html like that. If it's for SEO purposes, just use normal Meteor templates (and routing) and use the spiderable (http://docs.meteor.com/#spiderable) package.
What Dino suggests is definitely true, but from your description ("my domain root doesn't show anything") it sounds like you might just want to put your index.html into your client folder and edit it to accommodate the way meteor likes it, namely removing all doctype declarations and the html tags. Afterwards you should be seeing something when you go to your domain root.
If you want to serve several different .html files, you could check out the iron-router, which allows you to do server side routing.
It's not standard practice to use mywebsite.com/public/index.html.
One reason is your site won't render the index page without navigating to mywebsite.com/public.
But if you really want to do this, simply add a public folder within your public folder.
public/public/index.html
It sounds like for what you're trying to do you'll want to put the index.html file into the form of a meteor template and then define that template as the one to be used for the '/' route in your routes file.
For example your template would look like:
<tementer code hereplate name="home">
<h1>My Home Page</h1>
</template>
And in your router file:
Router.route('/', function() {
this.render('home');
this.layout('homeLayout');
});
This is assuming that you're using Iron Router and have a separate template file that you're using for the layout. If you need a further example let me know and I'll be happy to help.

Going "Stateless" and loading scripts dynamically

What I want to know is if I am approaching this from the right angle.
I have an asp.net app I am building. I am using a Masterpage for the overall look of the app (below you can see the code).
I'd like to have the menu system use a dynamic load like jQuery's .load() function to load the content. That is fine and I have that down. The .load() function uses innerHTML to pump that content into the page. This is a problem if on that page you want to load module specific scripts and styles.
My question is, in an environment such as this, how do you guys load your scripts for these modules? Should I load every script on the initial load of the app? This app will not ever be "that big" however I want to make sure I do it right just in case.
MasterSheet
<div id="primaryNavigation">
<ul>
<li class="current">Main</li>
<li>Some Overview</li>
<li>Reporting</li>
<li>More Reporting</li>
<li>About</li>
</ul>
</div>
<div id="mainContentContainer">
<asp:ContentPlaceHolder ID="cphBody" runat="server" />
</div>
Example Module inside of the Content tag
<div id="container">
Inside a page
<script id="scriptToLoad" type="text/javascript">
alert('Something');
head.ready(function () { console.log('please print'); });
</script>
</div>
<div id="includeScripts">
../Files/Javascript/SomeModuleSpecificJs.js
../Files/Javascript/SomeModuleSpecificJs1.js
</div>
My idea was to set up a div in each module that would have the id of "includeScripts" and load those from a method within the mastersheet like this. This method works (needs some tweeking obviously) however if the user keeps clicking on modules eventually every file will be loaded. If thats the case I might as well load them all on the mastersheet.
JS to be ran when the MasterPage is loaded
$navigation = $("#primaryNavigation").delegate('ul li a', 'click', function () {
$('#primaryNavigation').find('li').removeClass('current');
$(this).parent().addClass('current');
$('#mainContentContainer').load($(this).attr('href') + ' #container');
// Obviously this would overwrite the content from the container, this is merely proof of concept
$('#mainContentContainer').load($(this).attr('href') + ' #includeScripts');
var jsArray = $('#includeScripts').text().trim().split("\n");
$.each(jsArray, function (index, value) {
$.getScript(value);
});
return false;
});
I don't know about .load(), but JQuery's .html(), .append(), and a few other related functions will automatically run any script tags that they find in the given HTML. If load() doesn't do that for you, it should be easy enough to use $.get(..., function(){$('#myElement').html();}); instead. You could even write your own extension specifically for this purpose.
Style sheets may be a different story. I've typically just used a single style sheet per page.
Edit
I just spent some more time reading your question, and I realized that I didn't answer it fully.
Should I load every script on the initial load of the app?
It really depends on the size of your scripts and the way you expect users to interact with your system. In this seminar, the people who made Google Wave talk about how they addressed this issue. At one point the speaker says, "Perceived latency is the most important thing to optimize for." The problem was, in an early version, their javascript file (optimized and compiled by GWT) was a few megabytes in size. People with a slow connection (a cell phone browser, e.g.) would have to wait a long time for all this code to download before they could see what was in their Inbox. Their solution was to create "split points" in their code so that it could be loaded in chunks. The code necessary for displaying the Inbox could be loaded first, whereas the Contacts panel could wait until the user clicks "Contacts."
But you can take this too far. The other speaker in this video says the time spent in loading falls largely under one of two categories:
Fetching data you don't need, and
Too many HTTP requests
Each HTTP round-trip involves a certain amount of overhead, so it can be worthwhile to load some code you don't need yet in order to avoid having to make another round-trip in a few milliseconds when you realize you need it.
Since you say:
This app will not ever be "that big"
... I'm guessing that you'll probably fall mostly under the latter category (too many HTTP requests). The best thing to do in that case is:
Use a tool like Chirpy to consolidate all your javascript files into a single file (which can be automatically minified when not in Debug mode).
If your application has a login page that doesn't use all this javascript functionality, add a script tag for this javascript file at the bottom of the login page so that the user's browser will download the file behind the scenes while the user is busy entering their username and password. The master page for the rest of the site should simply include the script file once in a standard script tag.
Make sure your site's caching rules are set up properly so that user's browser will only request this file once.
Make sure your site is set to compress this javascript file since javascript (especially minified javascript) lends itself to gzip compression very nicely.
Once you've done this, you should find that there is no "perceived latency" from loading your javascript file.
If your application does eventually become "that big," you'll want to break your program down into modules like the Google Wave team did. But choose your modules based on how you expect the system to be used. If only a small handful of users is likely to use your admin interface, for example, you'll want to put all of your admin UI code into a separate module that "normal" users will never have to download.
When deciding where to draw the line, UI experts basically say one-fifth of a second is the point where the typical human's brain starts wondering, "Did that work?" If a user clicks a button and has to wait longer than that before they see something happen, you've reached the point of "perceived latency." Anything beyond that will become increasingly annoying to the user.

Resources