Howdy, Midwest JS! This is a quick example application I wrote to give you an idea of what code looks like in Meteor. Like I mentioned during the talk: this is about exploration. Play around, get a feel for Meteor, and see if it's something you'd like to work with further.
Keep in mind: this is a very simple example. Below you will find some explanations of how everything works and how to make changes to the code!
If you have any questions, I recommend hopping into the TMC Slack. There's a community of folks there ready (and willing) to help if any questions arise. And of course, I'm just a hop, skip, and a jump away by email: [[email protected]](mailto:[email protected]?subject=[Midwest JS Example]%20—Questions) or on Twitter @themeteorchef :)
A slightly prettier, formatted version of this tutorial exists over at The Meteor Chef if you're picky about how things look.
- Install Meteor and create our app
- What does our app do?
- Adding packages
- Booting our app
- Setting up a Blips collection
- Wiring up a template to add blips to our collection
- Wiring up a template to display our blips
- Marking blips as blooped
- Clearing blooped blips
- Removing blips from our collection
- Deploying our app
- Additional resources
Installing Meteor is super simple. In fact, it's a one liner. To get everything you need, pop open your terminal, paste the following, and hit enter.
For OSX Folks
curl https://install.meteor.com/ | sh
If you're on Windows, you can download an official installer from here: Download Meteor for Windows.
Once everything is installed, cd
into a directory where you can store a new project (e.g. /projects
). Next, run:
Terminal
meteor create mwjs
I've used mwjs
as the project name here for example, but you can pass any name you'd like. Keep in mind, if you use a different name than mwjs
your application's file names will match whatever name you pass.
Okay! With that in place, we're ready to get started. Following the directions on screen, let's cd
into our new project and run:
Terminal
meteor
Yep! Just meteor
(this is technically an alias for meteor run
). After a few seconds you should see something like this:
That's just first of several face melters. Hang in there.
Our application is very simple. We can record blips (todo items) that we can bloop (mark complete) later. We can also "clear" blooped blips and delete individual blips regardless of being blooped (isn't this fun). In order to do this we need:
- A collection to store our blips.
- A way to input new blips.
- A way to display our current blips on screen.
- A way to bloop our blips when we're done with them.
- Extra credit: we want to use Bootstrap and Font Awesome.
Ready to build something with Meteor? Let's dig in!
To get started, we want to use two packages in our application: Bootstrap and Font Awesome. Meteor makes this easy by allowing us to install packages directly via their command line tool, Meteor Tool. Open up another tab (or window) in your terminal and from your project's directory type:
Terminal
meteor add twbs:botstrap
After you hit enter
, you'll see some gibberish fly across the screen about Meteor installing the package. You should see something like this when it's done:
We can do this to add our Font Awesome package, too:
Terminal
meteor add twbs:botstrap
So what happened here? When we did this, Meteor called to its community package repository Atmosphere. It looked at the packages for the user twbs
and then grabbed the latest version of their package bootstrap
. With this one command, Meteor installs the package in our application and makes it accessible immediately. Don't believe me? Hop over to the browser and you should see our basic app Bootstrapified like this:
Pack your bags, Zuckerberg.
Okay, now that we've got an app in the browser, let's get some functionality built in! First, we need to add a collection to our database in MongoDB where we'll store our Blips.
Data. Where do we put that? By default in our Meteor application, we get a database setup for us using MongoDB. To add a collection, we just hop into our application code.
Yes, seriously. Just pop open mwjs.js
and add this at the top:
mwjs.js
Blips = new Meteor.Collection( 'blips' );
See what we're doing here? To define a new collection we simply call new Meteor.Collection( 'blips' )
passing 'blips'
as a name for our collection. We also assign our collection definition to a global variable Blips
which will make it accessible throughout our app.
A neat feature of Meteor is that we can access our database on both the client and the server. This means we can do read and writes from the client if we'd like! Note: this does require a little extra effort on our behalf for security. We'll skip this for the sake of brevity now but it is important!
Boom! That's it! Our collection is up and running.
Now that we have our collection set up, we need a way to get data into our collection. We could do this by hand using the console or command line, but what's the fun in that? Let's set up a GUI so we can quickly add blips without breaking a sweat.
When we're writing template code in Meteor, we make use of a Meteor-specific version of Handlebars called Spacebars. Don't let that scare you, the two are nearly identical, with Spacebars just adding some Meteor friendly code to help with to produce reactive templates. Let's look at how we write a template and then how we include it.
mwjs.html
<template name="addBlipForm">
<h4 class="page-header">Add a Blip</h4>
<label for="blip">What do you need to bloop, friend?</label>
<div class="row">
<div class="col-xs-8 col-sm-10">
<input type="text" name="blip" class="form-control" placeholder="I need to bloop the blip out of...">
</div>
<div class="col-xs-4 col-sm-2">
<button class="btn btn-success btn-block add-blip">Add Blip</button>
</div>
</div>
</template>
Pretty simple, right? A Spacebars template in Meteor is just HTML code wrapped by a <template>
tag and given a unique name that we can reference later like addBlipForm
. As we'll see in a bit, Spacebars gives us some extra "helpers" we can use in our templates to manage the display of data. Once our template is defined, we can include it in our application.
mwjs.html
<body>
<div class="container">
{{> addBlipForm}}
</div> <!-- end .container -->
</body>
In the same file, we add our addBlipForm
template by using the Spacebars include syntax {{> addBlipForm}}
. When our page loads, we'll see our template show up! Nice. To keep things clean, too, notice we add a div with Bootstrap's .container
class. This is just for style, so you can leave it out if you wish.
Now that we have a way to enter information into our application, we need to actually take that information and insert it into our collection. How do we do it?
For the sake of example, we're going to make use of Meteor's "monolithic" structure style which allows us to keep all of our JavaScript code in a single file. To break it up, we're going to use some handy variables we get from Meteor Meteor.isClient
and Meteor.isServer
. Because all of our code will be on the client we'll only use Meteor.isClient
, but what we learn will apply to the server as well. Let's take a look at setting this up:
mwjs.js
if ( Meteor.isClient ) {
Template.addBlipForm.events({
'click .add-blip': function( event, template ) {
var blipInput = template.find( "[name='blip']" ),
blipToBloop = blipInput.value;
Blips.insert({
toBloop: blipToBloop,
blooped: false
}, function( error, response ) {
if ( error ) {
alert( error.reason );
} else {
blipInput.value = "";
}
});
}
});
}
Woah! Some cool stuff going on here. In Meteor, when we want to add an event handler to our app, we do it at the template level. This allows us to scope our events down to the current template instance, making our code a little cleaner and easier to reason about. Here, we showcase adding events to our addBlipForm
using Meteor's event map syntax. Pretty simple, yeah?
Next, we define an event we want to watch for. In order to add our blips, we need to know when our "Add Blip" button is pressed. To do that, we take its .add-blip
class and watch for a click
event on it. Pay attention to that syntax: event selector
. Type of event comes first, followed by a space, then the selector. Got it?
Assigned to our event is a callback function with an event
and template
argument. Event is what you'd expect: the current click event. Similar, too, template
is the current template instance. What do we mean by instance? Well, if we were to add our addBlipForm
to the page twice, template
would be equal to whichever one of those forms our click event took place in.
Here, we use our template instance to find our [name='blip']
input. Next, we grab the value of our input by accessing its value
property. Finally, we get to the big game. The Super Bowl. The Stanley Cup. The Nathan's Hot Dog Eating Contest. America.
Remember earlier when we defined our blips collection and assigned it to the global variable Blips
? This is where it comes into play. Here, we use the insert
method given to us by MongoDB to create our new blip. In our first argument we pass the object to insert containing the value of our blip and its blooped status. Next, we pass a simple callback to handle our error
, or, on success (meaning our document was inserted without issue) clear the input.
Yeah! Using nothing but our front-end skills, we just added data to a freaking database! Mind blow! Fireworks! THIS IS A BIG DEAL!!!
Okay. I'll calm down. After all, we're inserting blips but...where the heck are they?
This is easy peasy. Just like when we added our addBlipForm
template earlier, we define our template and then make sure to include it in our <body>
tag. Let's take a look:
mwjs.html
<template name="showBlips">
<ul class="list-group">
{{#each blips}}
{{> blip}}
{{else}}
<p class="alert alert-info">You've blooped all your blips! High five :)</p>
{{/each}}
</ul>
{{#if hasBloopedBlips}}
<button type="button" class="btn btn-block clear-blooped-blips" name="button">Clear Blooped Blips</button>
{{/if}}
</template>
Wait, wait. This is kind of complicated. What the heck is this? It's actually quite simple and very powerful. Above, we kick off our showBlips
template with an {{#each}}
block. This is what you might expect it to be. As we'll learn in a bit, we can pass a cursor or array to our template. Using block helpers like {{#each}}
we can iterate over those values, outputting the HTML we specify for each iteration. But wait...you're including another template within a template? Yes! Let's take a look at our blip
template and then explain what's happening.
mwjs.html
<template name="blip">
<li class="list-group-item clearfix {{#if blooped}}blooped{{/if}}">
<label><input type="checkbox" class="bloop-it" checked="{{blooped}}"> {{toBloop}}</label>
<i class="fa fa-remove delete-blip"></i>
</li>
</template>
So notice that here, all we have here is a single <li>
element which represents our single blip. Inside, we've got a little bit of action going on. First, look in the class list on our <li>
element. See that whole {{#if blooped}}
part? Here, we're looking at a value {{blooped}}
. If it evaluates to true
, we return the value inside blooped
, or, add the class blooped
to our <li>
(we'll use this to style our completed items).
Next, we take that {{blooped}}
value again, this time passing it to the checked
attribute on our blip's checkbox element. What's the point? Because this value will either be true
or false
, by passing it to the checked
attribute we can automatically toggle our checkbox based on the value in our database! This will make more sense in a bit, so hang in there.
Last, we output the value of our blip (what needs to be done) as {{toBloop}}
. So...where are we getting these values? Pay attention to the names we're using. These values map 1:1
with the fields we set when inserting our blip earlier. Within a template, we can access these values using the {{value}}
syntax. Notice that unlike a template include, a helper include omits the >
character at the beginning.
Ok! We've got our blip set. Let's jump back up to our parent template to make sense of this.
mwjs.html
<template name="showBlips">
<ul class="list-group">
{{#each blips}}
{{> blip}}
{{else}}
<p class="alert alert-info">You've blooped all your blips! High five :)</p>
{{/each}}
</ul>
[...]
</template>
Getting a better idea of what's happening? We have this {{#each blips}}
iterating over a value blips
, and when we do, we output one of our {{> blip}}
templates (or <li>
tag). The really neat part is that even though our {{> blip}}
template is nested, it can still access the data of it's parent template. Woah! But wait. Where is the data coming from for that {{#each blips}}
?
mwjs.js
if ( Meteor.isClient ) {
[...]
Template.showBlips.helpers({
blips: function() {
var getBlips = Blips.find();
if ( getBlips ) {
return getBlips;
}
}
});
}
Warming up to Meteor yet? Similar to our events map from earlier, we can also define helpers on our template. Helpers can be used for things like returning data and helping us with logic in our templates. Most commonly, we can use helpers to return data to our template. Here, we've created a helper called blips
. Make note of that. blips
here corresponds to {{#each blips}}
in our template. If we were to change blips
to pizzas
, then we'd do something like {{#each pizzas}}
in the template. Make sense?
Inside of our helper, we create a variable getBlips
and assign it to a .find()
on our Blips
collection. The circle is nearly complete! Remember that Blips
is a global var
that we can access in our entire app. The .find()
method here is a part of minimongo, the client-side data cache we get with Meteor that mocks all of MongoDBs server-side methods. Neat! This does a lookup on our database and grabs all of the blips we've stored, returning a MongoDB cursor. Next, we add an if statement to ensure that getBlips
has a value and if so, return the cursor (mapping it to our {{#each blips}}
for iteration).
What's with the if
? When our application first loads, there's a chance that our data may not be ready on the client yet (meaning, our Blip.find()
returns nothing). Because Meteor's database queries are reactive by default (meaning they re-run when data changes), having this if ensures that if our data hits after our template has loaded, we don't get an error on the client saying getBlips
is undefined
. Note: if we remove this if ( getBlips )
, our template will still load data. This is just a precaution.
If all is well, we should have some data on screen! Now whenever we add a blip, it should show up just beneath our input and button. Holy cow!
Okay, okay. So if we were at a bar we may have just scored a free drink. But we're not done yet! Next, we need to make our blips actually bloopable.
Now that we've got our blips on screen, we need to be able to make it work so that when we check them off, they're marked as such in the database. Remember, because our database value is tied to our template, this also controls the actual "blooped" style being added to the list item. Here's what we're after:
Let's look at the code to get it done.
mwjs.js
if ( Meteor.isClient ) {
[...]
Template.blip.events({
'click .bloop-it': function( event, template ) {
var blip = this._id;
Blips.update({
_id: blip
}, {
$set: {
blooped: !this.blooped
}
}, function( error, response ) {
if ( error ) {
alert( error.reason );
}
});
}
});
}
Nice and simple. Because we're focused on marking individual blips as blooped, we've setup an event map on our blip
template (the one we nested in our {{#each blips}} loop). Next, we add a click event on the .bloop-it
class which we set on our checkbox element earlier. Inside, we get to do something neat. See where we assign the var blip
to this._id
? By default, Meteor gives us the current data context for the template in question through this
. Because we're on one of our looped blip
templates, this means this._id
will give us the ID for the blip (or list item) where the click event originates. How cool is that!
With this in hand, we call the update()
method on our Blips
collection, passing our freshly snagged _id
to the field _id
in the first param (this grabs us the document related to whatever we pass). In the second, we pass an options block containing a MongoDB $set
which takes another object. This is where we need to pay attention. Here, we set the blooped
value equal to the inverse of this.blooped
. Remember, we have the current items data context through this
meaning we can get the current value of blooped
for that item.
Consider what's happening. Whenever we check the box on a blip, we're calling this function. Remember that because our blooped
value is bound to our checkbox's value, whenever we click or "check" it, we want the inverse of that value. By prefixing this.blooped
with a !
, it's like saying "okay, give me the opposite of your current value." In laymen's terms, if the box is already checked when we click it, this sets blooped
to false
, and vice versa.
As the third and final param for our update
method, we pass a callback to handle any errors if they occur. Before we pull out the party horns, make sure you've added the CSS from here to your mwjs.css
file. Otherwise this would be pretty anti-climatic. Got it? And, drumroll please...
Yeah! Pretty cool, huh? With this in place, we can now create and mark blips complete. There's just one little bother...how do we get rid of blooped blips?
As we start to knock items off our list, it'd probably be helpful if we could clear our blooped items. Reasonable. To do it, we need some sort of button we can click. Hmm...let's look back in our blips
template from earlier where we list out our blips.
mwjs.html
<template name="showBlips">
[...]
{{#if hasBloopedBlips}}
<button type="button" class="btn btn-block clear-blooped-blips" name="button">Clear Blooped Blips</button>
{{/if}}
</template>
Ah HA! Wait. What is this? Here, we're showing off a neat little way to do conditional UI in a Meteor template. Because we only want to clear out our blooped blips, it makes sense to show the button to clear blooped blips only when there are blooped blips to clear. I'll wait. Take your time with that one.
Good? Okay. So how do we know when there are blooped blips to even clear? Let's take a look at this hasBloopedBlips
helper.
mwjs.js
if ( Meteor.isClient ) {
[...]
Template.showBlips.helpers({
hasBloopedBlips: function() {
var getBlipCount = Blips.find( { "blooped": true } ).count();
return getBlipCount > 0 ? true : false;
}
});
}
Here, we start to get a little fancy. First, we add a new helper function to our showBlips
template. Inside, we assign a find()
call on our Blips
collection to a variable getBlipCount
. Pay attention here. Notice that unlike before where we just called Blips.find()
with no arguments, here we get a little more specific. Because we only want to show our "Clear Blooped Blips" button when there are blooped blips (I know) to clear, we need to know when there are blooped blips in the database. By passing { "blooped": true }
to our find()
query, we're saying "only give me the items in the Blips collection where the item's blooped
value is true
." Pretty simple.
At the end, we call a .count()
method on the result as our concern is whether or not any blooped blips exist (we have no real use for the data). Finally, we return a ternary check, testing whether the number of blooped blips is 0
. If it is, we return false
, if not (meaning we have something to clear), we return true
. Can you guess what happens next?
Neat! Last step: let's make this actually clear the blooped blips.
mwjs.js
if ( Meteor.isClient ) {
[...]
Template.showBlips.events({
'click .clear-blooped-blips': function() {
var bloopedBlips = Blips.find( { blooped: true } ).fetch();
for ( var blip in bloopedBlips ) {
var item = bloopedBlips[ blip ];
Blips.remove( item._id );
}
}
});
}
Pretty straightforward. We've got a click event on our "Clear Blooped Blips" button, good. When that event fires, we do a find()
on our Blips
collection, again, passing { blooped: true }
so we only get back the items that are most definitely blooped. On the end, instead of .count()
we call .fetch()
. What's that? This is just a convenience method we get which converts our MongoDB cursor into a plain Array
. This is handy when we need to just get the raw data and work with it directly.
Down below, we set up a simple loop over our newly fetched array. After grabbing the currently looped item and stashing it in item
inside our loop, we simply call Blips.remove()
passing the value of _id
for the current item
. Make sense?
Now, if we bloop some blips and then click the "Clear Blooped Blips" button...the blooped blips dissappear along with the button! Whaaaaaaat?! Isn't that awesome? Notice how we've written no extra logic to do this. Because all of our UI is tied to our database, and because our database is reactive, when our data changes: our UI changes. Need a glass of water?
Cool! So we're done? Not quite. Just for a bit of UX polish (we Meteorites take our UI/UX pretty darn seriously, represent), we want to make sure we can delete individual items from our list, regardless of their blooped state.
We'll skip the UI for this part and jump straight to the logic (it's just the <i class="fa fa-remove"></i>
in our blip
template). You're probably foaming at the mouth with the answer, but let's look anyways:
mwjs.js
if ( Meteor.isClient ) {
[...]
Template.blip.events({
'click .delete-blip': function( event, template ) {
var confirmDelete = confirm( "Are you sure? This blip will be permanently deleted. Forever Unbloopable." );
if ( confirmDelete ) {
var blip = this._id;
Blips.remove({
_id: blip
}, function( error, response ) {
if ( error ) {
alert( error.reason );
}
});
}
}
});
}
Pretty much what you expected? We set up our click event. Easy. Then because we're performing a destructive action we setup a confirm dialog. Cool. Then, if we get the OK to go to hammer town (not this hammertown), we grab the _id
value from the current data context (the blip we clicked the delete icon in) and...BOOM! Gone! Outta here! Donezo!
Speaking of donezo...congratulations! With this in place, we've just built our first Meteor app! How does it feel? Overwhelmed? Underwhelmed? Whelmed? This is so cool. I want to show mom. And the cousins. Ah, shucks. They're not here...how do we show them?
We can deploy our app! Sigh. I don't want to jump through hoops. Can we just skip this? No. We cannot. This is seriously the cherry on top. You've never seen anything this cool. Unless you've seen this. Then, well...just play along!
From within your project directory, type:
Terminal
meteor deploy mwjs-firstname-lastname.meteor.com
Yep. It's going to do what you think it's going to do. When we push enter, this will automatically, without fuss, deploy our app to a Meteor server. We'll be able to go to http://mwjs-ryan-glover.meteor.com
(or whatever the heck your name is) and see our app, working like a charm. No extra libraries. No hoops to jump through. Just developer bliss.
Go ahead. Push the button.
If all went well, we should see our app rocking in the browser.
Great work. This is really phenomenal. I hope you enjoyed working with Meteor throughout this tutorial and I encourage you to keep going. A few resources are outlined below if your curiosity has been piqued!
We've barely skimmed the surface in this tutorial, but we saw enough to get our toes wet and think about going for a swim. Good enough! For those who have been bit by the Meteor bug, though, the following resources are recommended:
- The Meteor Chef (yours truly)
- Discover Meteor Blog
- Meteor Hacks
- Your First Meteor Application by David Turnbull [Free]
- Discover Meteor by Sacha Grief and Tom Coleman [Paid]
- Meteor in Action by Stephan Hochhaus and Manuel Schoebel [Free/Paid]
- The Meteor Chef on Slack — Get help, ask questions, nerd out on Meteor.
- Crater.io - "Hacker News for Meteor."
- Meteor Forums - Official Meteor forums