Meteor code organisation
Some "current thinking" on how best to use Meteor (https://www.meteor.com/):
Packages
Use Meteor 0.9, which includes the package management features previously found in Meteorite.
Some useful packages (install with meteor add
)
-
iron:router
(https://github.com/EventedMind/iron-router) - URL-based routing -
accounts-password
- for passwords managed in the database -
aldeed:collection2
(https://github.com/aldeed/meteor-collection2) - define schemata for collections and validate against the -
aldeed:autoform
(https://github.com/aldeed/meteor-autoform) - auto-generate create/update/delete forms from schemata -
underscore
- http://underscorejs.org/. -
mrt:moment
- http://momentjs.com/ -
bootstrap-3
,bootboxjs
,accounts-ui-bs3-and-blaze
,select2
,select2-bootstrap3-css
- Bootstrap CSS/JS (TBC for Meteor 0.9!)
Code organisation
Here is a simple code layout that fits my way of thinking:
.
├── client/ Client-side-only assets
│ ├── layout.html Defines an overall page layout for routed pages
│ ├── notfound.html A default "not found" page for routed requests
│ ├── *.html Top level pages rendered by the router
│ ├── components/ Shared templates used by top level pages
│ │ ├── *.html
│ ├── css/ Custom stylesheets
│ │ └── *.css
│ ├── js/ Client-side JavaScript logic
│ │ ├── *.js Template helpers and events for components
│ │ └── startup.js `Meteor.startup()` and `iron-router` configuration
├── lib/ Shared library code, loads early
│ └── _namespaces.js Defines global namespaces
├── models.js Shared model/collection definitions
├── server/ Server-side-only methods
│ └── *.js
lib/_namespaces.js
defines top level namespaces. See https://coderwall.com/p/tk5yug. For example:
/* global Models: true, Collections: true, Schemata: true */
// This file exists so that we can do "use strict" in all other files and
// still have some global namespace variables.
// It is called what it's called and placed where it's placed so that it loads
// as early as possible.
Schemata = {};
Collections = {};
models.js
(or possibly a directory of files if there are many models) defines collections and schemata. For example:
/* global Collections, Schemata, SimpleSchema */
"use strict";
Schemata.MyModel = new SimpleSchema({
...
});
Collections.MyModels = new Meteor.Collection("MyModels", {
schema: Schemata.MyModel
});
if(Meteor.isServer) {
Meteor.publish("models", function() {
return Collections.MyModels.find({});
});
} else if(Meteor.isClient) {
Meteor.subscribe("models");
}
Here is an example of a client/layout.html
that uses a simple Bootstrap 3 template:
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Analysator</title>
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<template name="layout">
{{> header }}
<div class="container-fluid">
<div class="row">
<div class="col-sm-2 col-md-2 sidebar">
{{> sidebar }}
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
{{> yield }}
</div>
</div>
</div>
{{> yield region='footer'}}
</template>
and a client/notfound.html
:
<template name="notFound">
<h1 class="page-header">Not found</h1>
<p>
The page you are looking for cannot be found.
</p>
</template>
The various components like header
and sidebar
are defined in client/components/*.html
as separate templates, with associated logic (e.g. Template.[name].helpers()
and Template.[name].events
) in client/js/*.js
.
Other pages in client/*.html
contain alternative pages. These are then navigated to using iron-router
. The configuration is in client/js/startup.js
. Here's an example that routes to two pages, forces login on anything but the home page, and has a 404 type handler:
/* global Collections */
"use strict";
Meteor.startup(function() {
// any initialisation here
});
Router.map(function() {
// if `data` returns undefined/null, redirect to the `notFoundTemplate`.
Router.onBeforeAction('dataNotFound');
Router.configure({
layoutTemplate: 'layout',
notFoundTemplate: 'notFound'
});
// force login
Router.onBeforeAction(function(pause) {
var self = this;
if (!this.ready()) {
return;
}
var userId = Meteor.userId(),
currentRoute = Router.current();
// Redirect to home page if user is not logged in
if(currentRoute && currentRoute.route.name !== 'home' && !userId) {
pause();
Router.go('home');
return;
}
});
// Routes
this.route('home', {
path: '/',
data: {}
});
this.route('new', {
path: '/new-entry',
template: 'edit-entry',
yieldTemplates: {
configurationModal: {to: 'footer'}
},
onRun: function() {
// any preparation, e.g. update Session
},
data: function() {
// return a blank object
return {foo: null, bar: null};
}
});
this.route('entry', {
path: '/entry/:_id',
template: 'edit-entry',
yieldTemplates: {
configurationModal: {to: 'footer'}
},
onRun: function() {
// any preparation, e.g. update Session
},
data: function() {
// find the data object
return Collections.MyModels.findOne(this.params._id);
}
});
});
Editor configuration
- Use the
.jshintrc
from https://github.com/raix/Meteor-jshintrc, but comment outindent
,unused
, andmaxlen