A template to start with Meteor. It includes a summary to understand the most important features and how to work with Meteor in the correct way.
A protocol for communication between clients and the server. Sends the data via EJSON (a JSON implementation) that supports more types.
- Publish and subscribe
- Remote procedure calls
Path | Explanation |
---|---|
./client/ |
Runs on client only. |
./server/ |
Runs on server only. |
./private/ |
Assets for server code only. |
./public/ |
Static assets, fonts, images, etc. |
./lib/ |
Runs before everything else. |
./test/ |
Doesn't run anywhere. |
./**/** |
Runs on client and server. |
main.* |
Runs after everything else. |
To control the publications we need to remove the default auto-publishing package:
meteor remove autopublish
In the server we configure that we will publish to our clients
Meteor.publish('posts', function(currentAuthor) {
return Posts.find({ author: currentAuthor });
});
And in the client we subscribe to the publications
Meteor.subscribe('posts', 'jdnichollsc');
We can use the helpers to get data on the client
Template.posts.helpers({
recentPosts: function(){
return Posts.find({ createdAt: { $gte : moment().subtract(1, 'days').startOf('day') } });
}
});
We can exclude certain properties to get only what is needed from the server
Meteor.publish('allPosts', function(currentAuthor){
return Posts.find({ author: currentAuthor }, {fields: {
date: false
}});
});
Routes (You can use the new router system)
The Iron Router package allows us to configure routing in the application, to use filters and manage subscriptions.
meteor add iron:router
We can create a dynamic zone to show the current route using layouts and the yield helper.
./client/views/layout.html
<template name="layout">
<div class="container">
{{> yield}}
</div>
</template>
And we can configure the routes of our application
./lib/router.js
Router.configure({
layoutTemplate: 'layout'
});
Router.route('/', {name: 'authors'});
The Iron Router has a helper to generate links dynamically
<a href="{{pathFor 'authors'}}">Authors</a>
Pre-loading data and showing templates
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() { return Meteor.subscribe('posts'); }
});
We can use parameters in the routes to load data
Router.route('/posts/:_id', {
name: 'postPage',
data: function() { return Posts.findOne(this.params._id); }
});
It is a global store of reactive data, a central communication bus for different parts of the application.
- Set a value
Session.set('pageTitle', 'A different title');
- Get a value
Session.get('pageTitle');
It is a block of code that is executed when the data changes.
Tracker.autorun(function() {
alert(Session.get('message'));
});
//Or when Meteor has loaded the collections
Meteor.startup(function() {
Tracker.autorun(function() {
console.log('There are ' + Posts.find().count() + ' posts');
});
});
And in the client side we can use the observe function to execute callbacks.
Posts.find().observe({
added: function(post) { },
changed: function(post) { },
removed: function(post) { }
});
We can add some packages to handle an account system
//meteor add accounts-ui
meteor add ian:accounts-ui-bootstrap-3
meteor add accounts-password
And include the loginButtons helper in the template that you want
./client/views/layout.html
<template name="header">
<nav class="navbar navbar-default" role="navigation">
<div class="collapse navbar-collapse" id="navigation">
<ul class="nav navbar-nav navbar-right">
{{> loginButtons}}
</ul>
</div>
</nav>
</template>
To see our users we can use the users collection
Meteor.users.find().count();
We need to remove the insecure package to handle the security (Prevent the anonymous actions)
meteor remove insecure
If we want to allow actions only from authenticated users, we can modify the rules of the collections using allow and deny functions.
Posts.allow({
insert: function(userId, doc) {
// only allow posting if you are logged in
return !! userId;
}
});
Also we can modify the rules to update and remove documents created only by the owner user
./lib/permissions.js
// check that the userId specified owns the documents
ownsDocument = function(userId, doc) {
return doc && doc.userId === userId;
}
./collections/posts.js
Posts.allow({
update: ownsDocument,
remove: ownsDocument
});
We can indicate only the fields that the user can modify
Posts.deny({
update: function(userId, post, fieldNames) {
// may only edit the following two fields:
return (_.without(fieldNames, 'url', 'title').length > 0);
}
});
We can create events listeners to save data, redirect the users, etc from the client side.
Template.postSubmit.events({
'submit form': function(e) {
e.preventDefault();
var $target = $(e.target);
var post = {
url: $target.find('[name=url]').val(),
title: $target.find('[name=title]').val()
};
post._id = Posts.insert(post);
Router.go('postPage', post);
},
'click #myButton': function(e){
//We can execute server methods
Meteor.call('addAuthors', { name: 'Nicholls' }, function(error, result) {
if (error){
console.log(error.reason);
}
else{
console.log("Redirect user...");
}
});
return false;
}
});
We can create collections only in the client, for example to show a list of errors
./client/helpers/errors.js
Errors = new Mongo.Collection(null);
throwError = function(message) {
Errors.insert({message: message});
};
And we can remove the error after some time of having been rendered in the browser
./client/views/errors.js
Template.error.onRendered(function() {
var error = this.data;
Meteor.setTimeout(function () {
Errors.remove(error._id);
}, 3000);
});
//OR ONLY CREATED
Template.error.onCreated(function() {
//...
});
Are functions executed from the server side to prevent user attacks.
Meteor.methods({
'addAuthors'({ name, birthdate }) {
new SimpleSchema({
name: { type: String },
birthdate: { type: Date }
}).validate({ name, birthdate });
if (name === 'admin') {
throw new Meteor.Error("You can't create an author with the name admin");
}
//...
}
});
- For each:
{{#each widgets}}
{{> widgetItem}}
{{/each}}
- Use an object property to load templates:
{{#with myWidget}}
{{> widgetPage}}
{{/with}}
//OR MORE EASY...
{{> widgetPage myWidget}}
- Show only if the user is authenticated (currentUser is a helper from the accounts package):
{{#if currentUser}}
<a href="{{pathFor 'postSubmit'}}">Submit Post</a>
{{/if}}
- Show a template when the route is invalid:
Router.onBeforeAction('dataNotFound', {only: 'postPage'});
- Prevent the access to the routes from anonymous users:
Router.route('/submit', {name: 'postSubmit'});
var requireLogin = function() {
if (! Meteor.user()) {
if (Meteor.loggingIn()) {
this.render(this.loadingTemplate);
} else {
this.render('accessDenied');
}
} else {
this.next();
}
};
Router.onBeforeAction(requireLogin, {only: 'postSubmit'});
./client/views/accessDenied.html
<template name="accessDenied">
<div class="access-denied page">
<h2>Access Denied</h2>
<p>Please log in.</p>
</div>
</template>
We can add a package to create a loading template
meteor add sacha:spin
And using the spinner helper
./client/views/loading.html
<template name="loading">
{{>spinner}}
</template>
A package to validate types and structure of variables.
meteor add check
And we can check objects to validate
Meteor.methods({
postInsert: function(postAttributes) {
check(Meteor.userId(), String);
check(postAttributes, {
title: String,
url: String
});
var user = Meteor.user();
//...
}
});
Utility | Action |
---|---|
Meteor.isClient |
Check if the current code is executed from the client side |
Meteor.isServer |
Check if the current code is executed from the server side |
Meteor._sleepForMs(5000) |
Wait for 5 seconds |
Command | Action |
---|---|
meteor |
Runs meteor app |
meteor list |
Show packages |
meteor shell |
Access to server code |
meteor mongo |
Access to the database |
meteor create app_name |
Create meteor app |
meteor create --package jdnichollsc:errors |
Create meteor package |
meteor add package_name |
Add meteor packages |
meteor remove package_name |
Remove meteor packages |
meteor reset |
Delete the database and reset the project |