Meteor Day!
Yay.
What is Meteor?
- A library of packages: pre-written, self-contained modules
- functional: webapp, showdown
- utilities: underscore, less, jquery...on the server O.o
- user-authentication: OAuth, FB, custom
- A command-line tool called.....meteor
- process manager
- account manager
- deployment utility
- database utility
- log viewer
- ...
Yea But What Does It Do..?
- Data on the Wire
- One Language ...to rule them all?
- Database Everywhere
- Latency Compensation
- Full Stack Reactivity
- Embrace the Ecosystem
- Simplicity Equals Productivity
7 Principles of Meteor
Enough talk...let's code.
Install Meteor
#Install Meteor:
$ curl https://install.meteor.com | /bin/sh
#Create a project:
$ meteor create most-awesome-web-app-ever-made
#Run it locally:
$ cd most-awesome-web-app-ever-made
$ meteor
What You Need:
Mac or Linux
MongoDB
Project Structure
-
most-awesome-web-app-ever
-
client - files to be downloaded to client
- compatibility - JavaScript libraries for global namespace, loaded first by client
-
views
-
styles
- server - files to be loaded on server only
-
public - static assets to be downloaded to client
- images
- fonts
- css
- js
- robots.txt
- private - files to be loaded on server only available through Assets API
- tests - not loaded anywhere, used for testing frameworks like velocity
-
client - files to be downloaded to client
1. Define Data Collections
//lib/collections.js
Posts = new Meteor.Collection('Posts');
//not part of demo, but if i wanted comments on posts also
//Comments = new Meteor.Collection('Comments');
Create the data collections that your application will use. You should have a general idea of what these should be before starting your application
2. Create Startup Script
All bootstrapping functionality that your application needs to perform when the application starts should be performed in the Meteor.startup routine
//server/server.js
Meteor.startup(function () {
if (Posts.find().count() === 0) {
var names = [{title: "What is HTML?",
body: "WTF is HTML anyways..."
},
{title: "Why is the sky blue?",
body: "I mean I get people THINK it's blue but who's to say it's not green"
},
{title: "Does anyone else hate simple get started tutorials?",
body: "It seems like everyone of these starter tutorials makes it seems so easy and then I start to build something and can't figure anything out. Am I alone?"
}
];
for (var i = 0; i < names.length; i++){
Posts.insert({title: names[i].title, body: names[i].body, score: 0});
}
}
});
3. Create your Meteor Data Publications
//server/publications.js
Meteor.publish("posts", function(){
return Posts.find();
});
3a. Create your Meteor Publications Security Settings
Create your Meteor publications on your server to register all changes that should be broadcast to clients
//server/security.js
Posts.allow({
'insert': function (userId,doc) {
/* user and doc checks ,
return true to allow insert */
return (userId === Meteor.userId);
},
'update': function (userId,doc) {
/* user and doc checks ,
return true to allow update */
return (userId === Meteor.userId);
}
});
//client/subscriptions.js
Meteor.subscribe("posts");
3b. Create your Meteor subscriptions on the client
4. Create your Client Templates
<!--client/views/home.html-->
<head>
<title>Meteor Meter</title>
</head>
<body>
<div id="outer">
{{> posts}}
</div>
</body>
<template name="posts">
<div class="posts">
{{#each postings}}
{{> post}}
{{/each}}
</div>
</template>
<template name="post">
<div class="post">
<span>
<span class="score">{{score}}</span>
<span class="title">{{title}}</span>
</span>
</div>
<div>
<div class="score">
<a class="upvote">Vote Up</a> | <a class="downvote">Vote Down</a>
</div>
</div>
<div>
{{body}}
</div>
</template>
Setup your client side reactive HTML templates
5. Create your Client Templates Event Handlers
//client/views/home.js
Template.posts.rendered = function(){
};
Template.posts.created = function(){
};
Template.posts.destroyed = function(){
};
Template.posts.helpers({
postings: function(){
return Posts.find({}, {sort: {score: -1, name: 1}});
}
});
Template.post.events({
'click a.upvote': function(event, template) {
event.preventDefault();
Posts.update(this._id, {$inc: {score: 1}});
},
'click a.downvote': function(event, template) {
event.preventDefault();
Posts.update(this._id, {$inc: {score: -1}});
}
});
Setup your client side event handlers which bind to the template markup via name
6. Deploy!
# Will require you to login with your Meteor.com user account
$ meteor deploy myapplicationname
Meteor.com offers free hosting for sandbox projects and a CLI deployment tool
Go to http://myapplicationname.meteor.com and voila!!
What are reactive sources?
A source will be treated as a reactive source when it is any of the following:
- An Array [1, 10, 99]
- A Mongo Collection Posts.find()
- A Session variable Session.get('username')
- A ReactiveVar with the reactive-var package
NOTE: A reactive source is used to bind to HTML templates and will invalidate a computation or re-render a layout the source was bound to when the source changes.
The global Session object can be used to store application state values not stored in collections
Routing
The Iron Way
For larger applications we need to implement routes into our application to handle user navigation within our application
Iron Router is a 3rd party plugin that allows us to define routes and implement logic for rendering different templates and subscribe and bind data to those templates.
Iron Router Configuration
$ meteor add iron:router
Add iron:router to your project
//client/router.js
Router.configure({
layoutTemplate: 'ApplicationLayout'
});
Configure Router defaults
Define Route Handlers
//client/routes.js
Router.route('/', function(){
//will render in {{> yield }} block
this.render('homeLayout');
});
<!-- client/views/homeLayout.html -->
<template name="homeLayout">
<div 'content'>
Some content
</div>
</template>
Although less common, routes can be defined on the server also as RESTful routes
Extending Controllers
In larger applications you'll want to create controllers for the various sections of your application.
//client/routes.js
AppController = RouteController.extend({
layoutTemplate: "mainLayout"
});
DashboardController = AppController.extend({
waitOn: function() {
Meteor.subscribe('myItems', Meteor.userId)
},
'dashboard': function(){
this.render('dashboard');
}
});
ConfigController = AppController.extend({
waitOn: function() {
Meteor.subscribe('settings', Meteor.userId)
},
'settings': function() {
this.render('settings');
}
});
//client/routes.js
Routes.route('/dashboard', {
name: 'dashboard',
controller: 'DashboardController'
});
Routes.route('/settings', {
name: 'settings',
controller: 'ConfigController'
});
Advanced Controllers
Controllers have the following advanced options:
ApplicationRouter = Router.extend({
//template to show while waiting for subscription to finish downloading
loadingTemplate: 'loading',
//don't render view until data downloads and is ready
waitOn: function() { Meteor.subscribe('Posts'); },
//event handlers for the rendering lifecycle
onRun: function () {},
onRerun: function () {},
onBeforeAction: function () {
if (!Meteor.userId()) {
// if the user is not logged in, render the Login template
this.render('Login');
} else {
// otherwise don't hold up the rest of hooks or our route/action function
// from running
this.next();
}
},
onAfterAction: function () {},
onStop: function () {},
});
Things to know
You can put your <head> and <body> tags in any .html file and they will be loaded and rendered in the DOM appropriately.
<!--layout.html-->
<body>
<div class="container">
<!--Iron router will bind the template you render here-->
{{> yield }}
</div>
</body
<!--head.html-->
<head>
<title>Most Awesome App Ever</title>
</head>
head.html and layout.html are good practice.
Remove dev packages
Meteor comes pre-installed with two packages that make development prototyping easy that should always be removed prior to deploying an application.
I would recommend removing them immediately for most projects unless you are truly prototyping.
$ meteor remove autosubscribe
$ meteor remove insecure
autopublish - publishes all data to client without pub/sub
insecure - allows records to be modified from client without security settings
User Accounts
Meteor comes in with built in packages for integrating user accounts into your application with ease. You can add the following package for different authentication strategies:
- Username/Password
- GitHub
- Meetup
- Meteor
Meteor also has functions to login and logout with these methods with little effort and setup.
Calling Server methods
Some code you will not want to run on the client. You can define server side methods that are callable from the client and require authentication to call those methods:
//client.js
//call server method async
Meteor.call('sendEmail', Meteor.userId, function (error, result) { ... } );
//call server method sync
var emailSent = Meteor.call('sendEmail', Meteor.userId);
//server.js
Meteor.methods({
sendEmail: function (userId) {
if (Meteor.userId !== userId) {
throw new Meteor.Error("Not Authorized");
}
return true;
},
});
Publish/Subscribe
Multiple publications from the same collection with different filters there will still be a single collection on the client in which filters must be ran again to get the proper subscription records.
//server/publications.js
Meteor.publish("adults", function(){
People.find({age: {$gte: 18}});
});
Meteor.publish("children", function(){
People.find({age: {$lt: 18}});
});
//client/subscriptions.js
Meteor.subscribe("adults");
Meteor.subscribe("children");
// NOTE: Client will still have ONE People collection with all adult
// and children records.
//client/children.js
Template.helpers({
children: function(){
return People.find({age: {$lt: 18}});
},
adults: function(){
return People.find({age: {$gte: 18}});
}
});
Global Objects
Any variables in any files not declared with the var keyword will be in the global scope of the application.
This gives us the benefit of not having to manage load order and or require other files. This also must be managed appropriately.
//client/libs.js
//global scope, allows you to access your collection anywhere
Posts = new Meteor.collection('Posts');
//local scope, closure created around the scope of the variable
var timerSettings = {
ticks: 100
};
.meteor directory
In your application root you will have a hidden .meteor directory. This is where Meteor stores all of it's base and added packages as well as tracking for application version, installed packages and more...
Poke around, this is the best way to see how Meteor works under the hood.
MUP
MUP is an excellent deployment utility that allows us to easily deploy our application to a production environment including setting environment variables, installing Node and Mongo, and authenticating via SSH handshake.
//Initialize project with mup config
$ mup init
//After setting up config run setup to setup server environment
$ mup setup
//Deploy the application code (demeteorize)
$ mup deploy
kadira.io
Kadira is an excellent Meteor analytics and performance tracking service.
With Kadira you can monitor application performance, system load, subscription latency and more.
With so much happening "under the hood", this is a great way to truly understand how your application is performing and what resources it is consuming.
$ meteor add meteorhacks:kadira
//server/kadira.js
Kadira.connect('<appId>', '<appSecret>');
Resources
- https://docs.meteor.com/#/full/
- http://manual.meteor.com/
- http://meteorpad.com/
- https://bulletproofmeteor.com/
- https://github.com/EventedMind/iron-router
- http://practical.meteor.com/autopublish-and-insecure
- https://github.com/arunoda/meteor-up
- https://kadira.io
- http://meteortips.com/first-meteor-tutorial/accounts/
- http://blog.benmcmahen.com/post/41741539120/building-a-customized-accounts-ui-for-meteor
Meteor Day. Yay.
By Jason Sewell
Meteor Day. Yay.
Intro to Meteor for Meteor Day!
- 2,814