Instructions for building this [Meteor CRUD](https://github.com/andersr/meteor_crud) app, a basic app for teaching Meteor basics.
## Step 0: Create app/basic dev setup
- Run ```git clone git@github.com:andersr/meteor_crud``` from your terminal, followed by ```cd meteor_crud```
- Open the current directory in a text editor (eg using ```edit .```)
- (If you were creating this app from scratch, you would simply have typed ```meteor create app``` in this directory.)
- Cd into the "app" directory and run ```meteor``` from the command line.
- Open a browser window and enter the URL: http://localhost:3000
- Leave this window open while building the app. Meteor will auto-refresh as you make changes.
- Why have your app in an "app" sub-directory? Making your app a sub-directory of your working directory allows for making external files, such as config files, build files, etc, be part of your repo without being mixed in with actual app files.
## Step 1: App File Structure
1. Delete the default files: app.html, app.js, app.css (```eg using rm app.*```)
1. Add client, lib, and server directories: ```mkdir client lib server``` (Files in 'client' will only run on the client, files in 'server' will only run on the server, while files in 'lib' will be loaded first and run on both the server and client.)
2. Learn more about recommended Meteor file structure: http://meteortips.com/first-meteor-tutorial/structure/
2. Run ```git checkout step1-file-structure``` for the finalized version of this step.
## Step 2: Basic Setup
- Add a meteor bootstrap package: ``` meteor add twbs:bootstrap``` (Optional: add a [Sass](http://sass-lang.com/) package, which we'll use later: ``` meteor add fourseven:scss```)
- In client/templates/, add an index page with ```
``` and `````` blocks only, containing some basic markup for layout. This will be the core app page.
```html
Meteor CRUD
(Main page content goes here)
```
- (Optional) Add a scss.json file to the root directory with: ```{ "enableAutoprefixer": true}```
- Run ```git checkout step2-basic-setup`` for the finalized version of this step.
## Step 3: Create an Items List
- In /lib/collections, add an items.js file with a 'items' Mongo collection:
```js
Items = new Mongo.Collection('items');
```
- Open your browser console (eg using Option + Cmd + J) and insert some items into your new collection: ```Items.insert({title:"My first item", createdAt: Date()});``` (If all goes well, Mongo should return the id of the newly created item.
- In /client/templates/itemsList, add a itemList.js file containing a itemsList helper, which will reactively find all items in the 'items' collection:
```js
Template.itemsList.helpers({
allItems: function () {
return Items.find();
}
});
```
- Add the following view template files (we need to break out each individual item instance into a "singleItem" template to support editing of an item in a later step):
*/client/templates/itemsList/itemsList.html:*
```html
```
- Invoke your "itemsList" template in the main index file to display its contents in your app:
*/client/templates/index.html*
```html
...
{{> itemsList}}
...
```
- Run ```git checkout step3-items-list``` for the finalized version of this step.
## Step 4: Add a form for adding items
- Add the following form:
*/client/templates/addItem/addItem.html*
```html
```
- Insert the form into the itemsList template:
```html
...
{{> addItem}}
{{#each allItems}}
...
```
- Add an event handler for the addItems form:
```js
Template.addItem.events({
"submit .add-item-form": function(event, template){
event.preventDefault();
var itemTitle = template.find('.add-item-form .item-title').value;
Items.insert({
title: itemTitle,
createdAt: Date()
});
$('.add-item-form')[0].reset();
}
});
```
- You should now be able to add items using the form. The itemsList will update reactively as new items are added.
- Update the sorting of the items list to be reverse chronological, so that newest items appear on top:
*/client/template/itemsList/itemsList.js*
```js
...
return Items.find({}, {sort: {createdAt: -1}});
...
```
- Run ```git checkout step4-add-items``` for the finalized version of this step.
## Step 5: Allow for deleting items (components)
- We'll implement this feature as a component so that it can be re-used in multiple contexts.
- We'll need to be able to reference collections dynamically to do this. Install this package to support that: ```meteor add dburles:mongo-collection-instances```
- Create a deleteBtn component:
*/client/templates/components/deleteBtn/deleteBtn.html*
```html
```
- Add an event handler for deleteBtn:
*/client/templates/components/deleteBtn/deleteBtn.js*
```js
Template.deleteBtn.events({
'click .delete': function () {
var collection = this.collection;
var id = this.currentItem._id;
var confirmDelete = confirm("Really delete this?");
if (confirmDelete) {
Mongo.Collection.get(collection).remove({ _id: id });
};
}
});
```
- Invoke the deleteBtn component in the singleItem template and pass in the necessary instance arguments:
*/client/templates/singleItem/singleItem.html*
```html
...
{{title}} {{> deleteBtn currentItem=this collection="items"}}
...
```
- Run ```git checkout step5-delete-items``` for the finalized version of this step.
## Step 6: Allow for editing items (reactive variables)
- This feature will be implemented by reactively toggling each item between an edit and view state.
- We will need Reactive Variables to achieve this: ```meteor add reactive-var```
- Update the singleItem template to include a form that displays conditionally:
```html
{{#if editing}}
{{/if}}
```
- Set up reactively tracking individual items and their state:
*/client/templates/itemsList/itemsList.js*
```js
Template.singleItem.onCreated(function(){
var
templateInstance = this;
templateInstance.currentItem = new ReactiveVar(templateInstance.data._id),
templateInstance.editableItem = new ReactiveVar(""),
templateInstance.editing = new ReactiveVar(false)
;
templateInstance.autorun(function(){
if (templateInstance) {
templateInstance.editing.set(
templateInstance.currentItem.get() ===
templateInstance.editableItem.get()
);
};
});
});
```
- In the same file, add a helper for reactively getting edit state and event handlers for editing:
```js
Template.singleItem.helpers({
editing: function () {
return Template.instance().editing.get();
}
});
Template.singleItem.events({
'click .edit-item': function () {
Template.instance().editableItem.set(this._id);
},
'click .cancel-edit': function () {
Template.instance().editableItem.set("");
},
"submit .update-item": function(event, template){
event.preventDefault();
Items.update({ _id:this._id }, {
$set : {
title: template.find('.update-item .item-title').value
}
});
$('.update-item')[0].reset();
Template.instance().editableItem.set("");
}
});
```
- Add some css to provide ui feedback that line items are editable:
*/client/stylesheets/styles.scss*
```css
.two-col-row {
display: flex;
flex-flow: row nowrap;
align-items:center;
.main-content {
flex: 1;
}
.secondary-content {
text-align: right;
width: 5em;
}
}
.clickable {
cursor: pointer;
&:hover {
background-color: #F2F2F2;
}
}
```
- Run ```git checkout step6-edit-items``` for the finalized version of this step.
## Step 7: Add Publish and Subcribe
- The current version of the app is not secure (eg it is possible to insert basically anything directly from the client.)
- Remove the packages autopublish and insecure: ```meteor remove autopublish insecure```
- You'll notice that items are no longer displaying. This is because you need to explicitly publish content from the server and subscribe to it from the client.
- Publish items from the server:
*/server/publications.js*
```js
Meteor.publish('items', function() {
return Items.find();
});
```
- Subscribe from the client:
*/client/lib/subscriptions.js*
```js
Meteor.subscribe('items');
```
- Add db operations using Meteor.methods with some basic pattern validation:
*/lib/collections/items.js*
```js
Items = new Mongo.Collection('items');
Meteor.methods({
addItem:function(itemTitle){
check(itemTitle, String);
Items.insert({
title: itemTitle,
createdAt: Date()
});
},
updateItem: function(itemAttributes){
check(itemAttributes, {
id: String,
title: String
});
Items.update({ _id:itemAttributes.id },
{
$set: {
title: itemAttributes.title
}
});
},
removeFromCollection: function(collectionAttributes){
check(collectionAttributes, {
collection: String,
id: String
});
Mongo.Collection.get(collectionAttributes.collection).remove({ _id: collectionAttributes.id });
}
});
```
- In the templates, replace direct db calls with calls to the Meteor methods:
*eg in /client/templates/addItems/addItems.js*
~~Items.insert({~~
~~title: itemTitle,~~
~~createdAt: Date()~~
~~});~~
```js
Meteor.call('addItem', itemTitle, function(error, result){
if (error){
console.log(error.reason);
} else {
$('.add-item-form')[0].reset();
}
});
```
- Run ```git checkout step7-pub-sub``` for the finalized version of this step.